diff --git a/.fvm/fvm_config.json b/.fvm/fvm_config.json index fe1bf01a5..69fe1472f 100644 --- a/.fvm/fvm_config.json +++ b/.fvm/fvm_config.json @@ -1,4 +1,4 @@ { - "flutterSdkVersion": "3.3.9", + "flutterSdkVersion": "3.7.7", "flavors": {} } \ No newline at end of file diff --git a/.gitignore b/.gitignore index 24a4a85ed..fdae26f28 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 @@ -133,3 +133,4 @@ app.*.map.json #private key related *.env +*.env.dev diff --git a/README.md b/README.md index 5ad0d41a1..4e76c8803 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,74 @@ $ 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 + +#### 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. ``` -### iOS +#### 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 +256,40 @@ $ ./install_altme.sh -android $ ./install_altme.sh -ios ``` - ## 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). + +#### Note + +Please check the [Setting Up PolygonID Flutter SDK] section for key-dependencies of polygonid-flutter-sdk. ## Building Altme @@ -276,7 +310,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 +350,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 +362,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 +376,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 +397,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 +417,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 +430,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 +480,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 +609,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 +635,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 +647,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 diff --git a/analysis_options.yaml b/analysis_options.yaml index be7297dee..6ec9b67c0 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 @@ -14,4 +14,6 @@ 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 + depend_on_referenced_packages: true 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() 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": [ { diff --git a/assets/faq.json b/assets/faq.json index abf105647..f6758e20b 100644 --- a/assets/faq.json +++ b/assets/faq.json @@ -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.", diff --git a/assets/icon/cash-in-hand.png b/assets/icon/cash-in-hand.png index 85825302d..0dfaad0ea 100644 Binary files a/assets/icon/cash-in-hand.png and b/assets/icon/cash-in-hand.png differ diff --git a/assets/image/bloometa-dummy.png b/assets/image/bloometa-dummy.png new file mode 100644 index 000000000..776567e65 Binary files /dev/null and b/assets/image/bloometa-dummy.png differ diff --git a/assets/image/bloometa-pass.png b/assets/image/bloometa-pass.png index fe58e3eeb..1e59e2ec7 100644 Binary files a/assets/image/bloometa-pass.png and b/assets/image/bloometa-pass.png differ diff --git a/assets/image/eu_diploma_card.png b/assets/image/eu_diploma_card.png new file mode 100644 index 000000000..839339928 Binary files /dev/null and b/assets/image/eu_diploma_card.png differ diff --git a/assets/image/eu_verifiable_id.png b/assets/image/eu_verifiable_id.png new file mode 100644 index 000000000..bf468caef Binary files /dev/null and b/assets/image/eu_verifiable_id.png differ 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/lib/app/shared/constants/altme_strings.dart b/lib/app/shared/constants/altme_strings.dart index 114c07001..0b7830c1c 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/constant_list.dart b/lib/app/shared/constants/constant_list.dart index 3ea4a9b3f..2817c8971 100644 --- a/lib/app/shared/constants/constant_list.dart +++ b/lib/app/shared/constants/constant_list.dart @@ -10,9 +10,11 @@ class DiscoverList { // // CredentialSubjectType.dogamiPass, // CredentialSubjectType.bunnyPass, ]; + static final List communityCategories = [ // CredentialSubjectType.talaoCommunityCard ]; + static final List identityCategories = [ CredentialSubjectType.emailPass, //CredentialSubjectType.gender, @@ -25,6 +27,7 @@ class DiscoverList { CredentialSubjectType.phonePass, CredentialSubjectType.twitterCard, ]; + static final List myProfessionalCategories = [ // CredentialSubjectType.linkedInCard, ]; diff --git a/lib/app/shared/constants/image_strings.dart b/lib/app/shared/constants/image_strings.dart index 9960e7f8a..987f3cb93 100644 --- a/lib/app/shared/constants/image_strings.dart +++ b/lib/app/shared/constants/image_strings.dart @@ -67,7 +67,10 @@ 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 euVerifiableId = '$imagePath/eu_verifiable_id.png'; static const String myAccountCard = '$imagePath/my_account_card.png'; static const String pooAccountCard = '$imagePath/poo_account_card.png'; @@ -90,6 +93,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/parameters.dart b/lib/app/shared/constants/parameters.dart index ef2cd05e7..0dba30dc4 100644 --- a/lib/app/shared/constants/parameters.dart +++ b/lib/app/shared/constants/parameters.dart @@ -20,14 +20,14 @@ class Parameters { isGamingEnabled: false, isIdentityEnabled: true, isBlockchainAccountsEnabled: false, + isEducationEnabled: true, isSocialMediaEnabled: true, isCommunityEnabled: true, isOtherEnabled: true, isPassEnabled: true, ); - static const ebsiUniversalLink = - 'https://app.talao.co/app/download/credential'; + static const ebsiUniversalLink = 'https://app.talao.co/app/download/ebsi'; static const web3RpcMainnetUrl = 'https://mainnet.infura.io/v3/'; } diff --git a/lib/app/shared/constants/secure_storage_keys.dart b/lib/app/shared/constants/secure_storage_keys.dart index ee8400609..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'; @@ -12,6 +14,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'; @@ -67,4 +70,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/constants/urls.dart b/lib/app/shared/constants/urls.dart index 51434b017..dcafb945b 100644 --- a/lib/app/shared/constants/urls.dart +++ b/lib/app/shared/constants/urls.dart @@ -29,6 +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 chainbornMembershipCardUrl = 'https://issuer.talao.co/chainborn/membershipcard/'; @@ -49,7 +51,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'; @@ -58,6 +61,9 @@ 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 +84,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/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/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/app/shared/enum/message/response_string/response_string.dart b/lib/app/shared/enum/message/response_string/response_string.dart index cc1458efa..bf9dacaa0 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,10 @@ enum ResponseString { RESPONSE_STRING_transactionIsLikelyToFail, RESPONSE_STRING_linkedInBannerSuccessfullyExported, RESPONSE_STRING_credentialSuccessfullyExported, + 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 78199f64d..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,6 +4,15 @@ 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; @@ -421,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/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/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/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/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/enum/type/credential_subject_type/credential_subject_type.dart b/lib/app/shared/enum/type/credential_subject_type/credential_subject_type.dart index 912ae113d..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 @@ -56,4 +56,6 @@ enum CredentialSubjectType { aragoOver18, 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 c1a3bd36f..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 @@ -76,6 +76,8 @@ extension CredentialSubjectTypeExtension on CredentialSubjectType { case CredentialSubjectType.binancePooAddress: case CredentialSubjectType.tezosPooAddress: case CredentialSubjectType.pcdsAgentCertificate: + case CredentialSubjectType.euDiplomaCard: + case CredentialSubjectType.euVerifiableId: return Colors.white; } } @@ -143,6 +145,8 @@ extension CredentialSubjectTypeExtension on CredentialSubjectType { case CredentialSubjectType.binancePooAddress: case CredentialSubjectType.tezosPooAddress: case CredentialSubjectType.pcdsAgentCertificate: + case CredentialSubjectType.euDiplomaCard: + case CredentialSubjectType.euVerifiableId: return Icons.perm_identity; } } @@ -262,6 +266,10 @@ 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.euVerifiableId: + return 'https://api-conformance.ebsi.eu/trusted-schemas-registry/v2/schemas/z22ZAMdQtNLwi51T2vdZXGGZaYyjrsuP1yzWyXZirCAHv'; case CredentialSubjectType.defaultCredential: return ''; } @@ -373,6 +381,10 @@ extension CredentialSubjectTypeExtension on CredentialSubjectType { return BinancePooAddressModel.fromJson(json); case CredentialSubjectType.tezosPooAddress: return TezosPooAddressModel.fromJson(json); + case CredentialSubjectType.euDiplomaCard: + return EUDiplomaCardModel.fromJson(json); + case CredentialSubjectType.euVerifiableId: + return EUVerifiableIdModel.fromJson(json); } } @@ -401,4 +413,153 @@ 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 || + 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; + } + + 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/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/app/shared/helper_functions/helper_functions.dart b/lib/app/shared/helper_functions/helper_functions.dart index 518d25912..b2cb27fbf 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, @@ -235,12 +264,17 @@ 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(' '); } Future getStoragePermission() async { + if (isAndroid()) { + return true; + } if (await Permission.storage.request().isGranted) { return true; } else if (await Permission.storage.request().isPermanentlyDenied) { @@ -293,3 +327,7 @@ Future getRandomP256PrivateKey( return p256PrivateKey; } } + +bool isVerifiableDiplomaType(CredentialModel credentialModel) { + return credentialModel.credentialPreview.type.contains('VerifiableDiploma'); +} 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/shared/message_handler/global_message.dart b/lib/app/shared/message_handler/global_message.dart index 55493811a..ea42bc12f 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; @@ -355,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 36cacc812..043ba396e 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); @@ -593,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/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/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 15de7bfb9..000000000 --- a/lib/app/shared/widget/add_account/add_account_popup.dart +++ /dev/null @@ -1,121 +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.surface, - 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/altme_logo.dart b/lib/app/shared/widget/altme_logo.dart index bd51f6d5c..9b481f47b 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.contain, ); } diff --git a/lib/app/shared/widget/button/my_elevated_button.dart b/lib/app/shared/widget/button/my_elevated_button.dart index 56d46ce0e..d561973c9 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_gradient_button.dart b/lib/app/shared/widget/button/my_gradient_button.dart index 4e724ec83..3559199ff 100644 --- a/lib/app/shared/widget/button/my_gradient_button.dart +++ b/lib/app/shared/widget/button/my_gradient_button.dart @@ -49,7 +49,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/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/app/shared/widget/custom_listtile_card.dart b/lib/app/shared/widget/custom_listtile_card.dart index 0ee61a6b6..111930abb 100644 --- a/lib/app/shared/widget/custom_listtile_card.dart +++ b/lib/app/shared/widget/custom_listtile_card.dart @@ -1,4 +1,4 @@ -import 'package:altme/app/shared/constants/sizes.dart'; +import 'package:altme/app/app.dart'; import 'package:altme/theme/theme.dart'; import 'package:flutter/material.dart'; @@ -31,13 +31,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/app/shared/widget/default_dialog.dart b/lib/app/shared/widget/default_dialog.dart index 3ecd9c340..f60e08e43 100644 --- a/lib/app/shared/widget/default_dialog.dart +++ b/lib/app/shared/widget/default_dialog.dart @@ -19,7 +19,8 @@ class DefaultDialog extends StatelessWidget { @override Widget build(BuildContext context) { return AlertDialog( - backgroundColor: Theme.of(context).colorScheme.onBackground, + 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 bf26e710b..7e1141faf 100644 --- a/lib/app/shared/widget/dialog/becareful_dialog.dart +++ b/lib/app/shared/widget/dialog/becareful_dialog.dart @@ -43,7 +43,8 @@ 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, + 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 7c81f9865..8c90022b4 100644 --- a/lib/app/shared/widget/dialog/confirm_dialog.dart +++ b/lib/app/shared/widget/dialog/confirm_dialog.dart @@ -28,12 +28,14 @@ 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 text = textColor ?? Theme.of(context).colorScheme.dialogText; + final background = bgColor ?? Theme.of(context).colorScheme.popupBackground; + final textColor = + this.textColor ?? Theme.of(context).colorScheme.dialogText; 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)), @@ -49,8 +51,10 @@ class ConfirmDialog extends StatelessWidget { ), Text( title, - style: - Theme.of(context).textTheme.dialogTitle.copyWith(color: text), + style: Theme.of(context) + .textTheme + .defaultDialogTitle + .copyWith(color: textColor), textAlign: TextAlign.center, ), const SizedBox(height: Sizes.spaceXSmall), @@ -59,8 +63,8 @@ class ConfirmDialog extends StatelessWidget { subtitle!, style: Theme.of(context) .textTheme - .dialogSubtitle - .copyWith(color: text), + .defaultDialogSubtitle + .copyWith(color: textColor), textAlign: TextAlign.center, ), const SizedBox(height: 24), diff --git a/lib/app/shared/widget/dialog/info_dialog.dart b/lib/app/shared/widget/dialog/info_dialog.dart index abc55b407..bb5ccef0c 100644 --- a/lib/app/shared/widget/dialog/info_dialog.dart +++ b/lib/app/shared/widget/dialog/info_dialog.dart @@ -25,10 +25,11 @@ 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, + surfaceTintColor: Colors.transparent, contentPadding: const EdgeInsets.symmetric(horizontal: 20, vertical: 15), shape: const RoundedRectangleBorder( borderRadius: BorderRadius.all(Radius.circular(25)), @@ -44,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) @@ -53,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 40e5766a9..380edb186 100644 --- a/lib/app/shared/widget/dialog/text_field_dialog.dart +++ b/lib/app/shared/widget/dialog/text_field_dialog.dart @@ -53,11 +53,13 @@ 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( backgroundColor: background, + surfaceTintColor: Colors.transparent, contentPadding: const EdgeInsets.symmetric(horizontal: 20, vertical: 15), shape: RoundedRectangleBorder( side: BorderSide( @@ -71,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) @@ -80,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/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/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/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/shared/widget/phrase_word.dart b/lib/app/shared/widget/phrase_word.dart index 543c24933..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,14 +32,21 @@ 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), ), - child: MyText( - '$order. $word', - textAlign: TextAlign.center, - style: Theme.of(context).textTheme.passPhraseText, + child: SizedBox( + height: 25, + child: Center( + child: MyText( + showOrder ? '$order. $word' : word, + textAlign: TextAlign.center, + style: Theme.of(context).textTheme.passPhraseText, + ), + ), ), - ); + ), + ); + } } diff --git a/lib/app/view/app.dart b/lib/app/view/app.dart index c06c67a06..6e4077dc4 100644 --- a/lib/app/view/app.dart +++ b/lib/app/view/app.dart @@ -29,10 +29,8 @@ 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: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}); @@ -131,6 +129,7 @@ class App extends StatelessWidget { walletCubit: context.read(), beacon: Beacon(), walletConnectCubit: context.read(), + secureStorageProvider: secure_storage.getSecureStorage, ), ), BlocProvider( @@ -173,14 +172,14 @@ class App extends StatelessWidget { manageNetworkCubit: context.read(), ), ), - BlocProvider( - create: (context) => LiveChatCubit( - dioClient: DioClient('', Dio()), - didCubit: context.read(), + BlocProvider( + lazy: false, + create: (context) => AltmeChatSupportCubit( secureStorageProvider: getSecureStorage, - client: Client( - 'AltMeUser', - ), + matrixChat: MatrixChatImpl(), + invites: [AltMeStrings.matrixSupportId], + storageKey: SecureStorageKeys.supportRoomId, + roomNamePrefix: 'Altme', ), ), ], diff --git a/lib/bootstrap.dart b/lib/bootstrap.dart index 481fcc9f1..4303b3c72 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 @@ -126,6 +126,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/connection_bridge/repository/connected_dapp_repository.dart b/lib/connection_bridge/repository/connected_dapp_repository.dart index f02ffb19b..f4c14dd04 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'); @@ -88,16 +93,15 @@ class ConnectedDappRepository { (SavedDappData savedData) { switch (savedDappData.blockchainType) { case BlockchainType.ethereum: + case BlockchainType.fantom: + case BlockchainType.polygon: + 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; - case BlockchainType.fantom: - case BlockchainType.polygon: - case BlockchainType.binance: - throw Exception(); } }, @@ -112,15 +116,14 @@ class ConnectedDappRepository { late String id; switch (savedDappData.blockchainType) { case BlockchainType.ethereum: + case BlockchainType.fantom: + case BlockchainType.polygon: + case BlockchainType.binance: id = savedDappData.wcSessionStore!.session.topic; break; case BlockchainType.tezos: id = savedDappData.peer!.publicKey; break; - case BlockchainType.fantom: - case BlockchainType.polygon: - case BlockchainType.binance: - throw Exception(); } 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 efa71b5c4..cc2c5dcd8 100644 --- a/lib/connection_bridge/wallet_connect/cubit/wallet_connect_cubit.dart +++ b/lib/connection_bridge/wallet_connect/cubit/wallet_connect_cubit.dart @@ -26,12 +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, + + 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/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/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/connection/confirm_connection/cubit/confirm_connection_cubit.dart b/lib/dashboard/connection/confirm_connection/cubit/confirm_connection_cubit.dart index 896c0f581..4884a3106 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'; @@ -105,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!, @@ -128,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/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 0c1753d32..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,13 +92,9 @@ class RightsCubit extends Cubit { ); } break; - case BlockchainType.fantom: - case BlockchainType.polygon: - case BlockchainType.binance: - throw Exception(); } - } 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'); diff --git a/lib/dashboard/dashboard/view/dashboard_page.dart b/lib/dashboard/dashboard/view/dashboard_page.dart index 1538d92b2..f3ce271ee 100644 --- a/lib/dashboard/dashboard/view/dashboard_page.dart +++ b/lib/dashboard/dashboard/view/dashboard_page.dart @@ -44,8 +44,10 @@ class _DashboardViewState extends State { context.read().deepLink(); context.read().startBeacon(); - if (context.read().state.isNewVersion) { + final splashCubit = context.read(); + if (splashCubit.state.isNewVersion) { WhatIsNewDialog.show(context); + splashCubit.disableWhatsNewPopUp(); } }); }); @@ -90,6 +92,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; @@ -165,6 +182,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) { @@ -174,15 +194,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(), @@ -253,7 +265,7 @@ class _DashboardViewState extends State { WertPage() else SearchPage(), - LiveChatPage(hideAppBar: true), + AltmeSupportChatPage(), ], ), ), @@ -289,12 +301,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..f833bff30 100644 --- a/lib/dashboard/dashboard/widgets/bottom_bar_item.dart +++ b/lib/dashboard/dashboard/widgets/bottom_bar_item.dart @@ -1,4 +1,5 @@ import 'package:altme/theme/theme.dart'; +import 'package:badges/badges.dart' as badges; import 'package:flutter/material.dart'; class BottomBarItem extends StatelessWidget { @@ -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/dashboard/widgets/new_content.dart b/lib/dashboard/dashboard/widgets/new_content.dart new file mode 100644 index 000000000..90a775364 --- /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: background, + size: 18, + ), + ), + const SizedBox(width: 5), + Expanded( + child: Text( + features[index], + textAlign: TextAlign.left, + style: Theme.of(context) + .textTheme + .defaultDialogBody + .copyWith( + color: background, + ), + ), + ), + ], + ), + ); + }, + ) + ], + ); + } +} diff --git a/lib/dashboard/dashboard/widgets/new_feature.dart b/lib/dashboard/dashboard/widgets/new_feature.dart deleted file mode 100644 index f7d62da50..000000000 --- a/lib/dashboard/dashboard/widgets/new_feature.dart +++ /dev/null @@ -1,38 +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: 15), - child: Row( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Padding( - padding: const EdgeInsets.only(top: 3), - child: Icon( - Icons.check_circle, - color: Theme.of(context).colorScheme.primary, - size: 20, - ), - ), - const SizedBox(width: 5), - Expanded( - child: Text( - feature, - style: Theme.of(context).textTheme.defaultDialogBody, - textAlign: TextAlign.justify, - ), - ), - ], - ), - ); - } -} diff --git a/lib/dashboard/dashboard/widgets/what_is_new_dialog.dart b/lib/dashboard/dashboard/widgets/what_is_new_dialog.dart index b243884ce..640a15a10 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'; @@ -14,6 +16,7 @@ class WhatIsNewDialog extends StatelessWidget { static void show(BuildContext context) { showDialog( context: context, + useSafeArea: true, builder: (_) => const WhatIsNewDialog(), ); } @@ -25,205 +28,187 @@ 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, - 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 Talao', - ), - const NewFeature( - 'Compliance with EBSI and support of new official ID documents (diplomas...)', - ), - Text( - '1.8.13', - style: Theme.of(context).textTheme.defaultDialogSubtitle, - textAlign: TextAlign.center, - ), - const SizedBox(height: Sizes.spaceXSmall), - 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( - 'Credential manifest input descriptors update', - ), - 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 Talaoโ€™s privacy, terms and conditions', - ), - 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( - 'New Drawer', - ), - const NewFeature( - 'New Device Info credential', - ), - const NewFeature( - 'Bug fix', - ), - 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( - '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('Update design of credentials'), - Text( - '1.4.4', - style: Theme.of(context).textTheme.defaultDialogSubtitle, - textAlign: TextAlign.center, - ), - const SizedBox(height: Sizes.spaceXSmall), - const NewFeature( - 'Improvements of user experience', - ), - Text( - '1.3.7', - style: Theme.of(context).textTheme.defaultDialogSubtitle, - textAlign: TextAlign.center, - ), - const SizedBox(height: Sizes.spaceXSmall), - const NewFeature( - 'Get multiple identity credentials after identity verification (OpenID for VC Issuance)', - ), - 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( - 'Multiple credentials presentation', - ), - const NewFeature( - 'Wording', - ), - const NewFeature( - 'Bug correction', - ), - 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(); - }, - ) - ], + 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, + vertical: Sizes.spaceSmall, + ), + shape: const RoundedRectangleBorder( + borderRadius: BorderRadius.all( + Radius.circular(Sizes.normalRadius), + ), + ), + content: SizedBox( + width: double.maxFinite, + child: Stack( + children: [ + 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( + children: [ + const AltMeLogo( + color: background, + size: Sizes.logoLarge * 1.05, + ), + Text( + l10n.whatsNew, + style: Theme.of(context) + .textTheme + .defaultDialogTitle + .copyWith( + color: background, + ), + textAlign: TextAlign.center, + ), + 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 Talao', + 'Specific design for EBSI diploma card', + ], + ), + const NewContent( + version: '1.9.4', + features: [ + 'Integration of Matrix.org to give users access to a decentralized chat in Talao', + 'Compliance with EBSI and support of new official ID documents (diplomas...)', + ], + ), + const NewContent( + version: '1.8.13', + features: [ + 'New features : Help center', + 'New wallet certificate credential', + ], + ), + const NewContent( + version: '1.7.6', + features: [ + 'Bug correction', + ], + ), + const NewContent( + version: '1.7.5', + features: [ + 'Credential manifest input descriptors update', + ], + ), + const NewContent( + version: '1.7.1', + features: [ + 'Improve compatibility with more wallets', + 'Update Talaoโ€™s privacy, terms and conditions', + 'New category for Professional credentials', + ], + ), + const NewContent( + version: '1.6.5', + features: [ + 'Bug correction', + ], + ), + const NewContent( + version: '1.6.3', + features: [ + 'New Drawer', + 'New Device Info credential', + 'Bug fix', + ], + ), + 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', + 'Privacy and terms update', + 'Enforced security', + 'User experience improvements' + ], + ), + const NewContent( + version: '1.4.8', + features: ['Update design of credentials'], + ), + const NewContent( + version: '1.4.4', + features: [ + 'Improvements of user experience', + ], + ), + const NewContent( + version: '1.3.7', + features: [ + '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', + ], + ), + const NewContent( + version: '1.1.0', + features: [ + '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(); + }, + ), + ) + ], + ), + const Align( + alignment: Alignment.topRight, + child: WhiteCloseButton(), + ), + ], + ), ), ), ); diff --git a/lib/dashboard/dashboard/widgets/widgets.dart b/lib/dashboard/dashboard/widgets/widgets.dart index faa27cf3f..f4e1e3918 100644 --- a/lib/dashboard/dashboard/widgets/widgets.dart +++ b/lib/dashboard/dashboard/widgets/widgets.dart @@ -1,5 +1,5 @@ export 'bottom_bar_decoration.dart'; export 'bottom_bar_item.dart'; export 'home_title_leading.dart'; -export 'new_feature.dart'; +export 'new_content.dart'; export 'what_is_new_dialog.dart'; diff --git a/lib/dashboard/discover/view/discover_details_page.dart b/lib/dashboard/discover/view/discover_details_page.dart index 9c8784e9a..bad2aafbd 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( @@ -104,7 +105,7 @@ class DiscoverDetailsView extends StatelessWidget { ), ), ), - _buildDetailsFields(context, l10n), + DetailFields(homeCredential: homeCredential), ], ), ), @@ -125,8 +126,19 @@ class DiscoverDetailsView extends StatelessWidget { ), ); } +} + +class DetailFields extends StatelessWidget { + const DetailFields({ + super.key, + required this.homeCredential, + }); + + final HomeCredential homeCredential; - Widget _buildDetailsFields(BuildContext context, AppLocalizations l10n) { + @override + Widget build(BuildContext context) { + final l10n = context.l10n; if (homeCredential.credentialSubjectType.isDisabled) { return DiscoverDynamicDetial( title: l10n.credentialManifestDescription, @@ -134,13 +146,22 @@ class DiscoverDetailsView 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/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/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/drawer/advance_settings/cubit/advance_settings_cubit.dart b/lib/dashboard/drawer/advance_settings/cubit/advance_settings_cubit.dart index 70b481c96..444d1fca2 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) ?? 'false') == '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..6afbe94ce 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.educationCredentials, + isSelected: state.isEducationEnabled, + onPressed: advancedSettingsCubit.toggleEducationRadio, + ), AdvanceSettingsRadioItem( title: l10n.community, isSelected: state.isCommunityEnabled, 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/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/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..ec922605c --- /dev/null +++ b/lib/dashboard/drawer/chat_room/cubit/chat_room_cubit.dart @@ -0,0 +1,289 @@ +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(), + ) { + secureStorageProvider.get(SecureStorageKeys.did).then((did) { + if (did != null && did.isNotEmpty) { + 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 52% 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 f4cbed177..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,35 @@ -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, + message: message ?? this.message, ); } @@ -34,5 +38,6 @@ class LiveChatState extends Equatable { status, messages, user, + message, ]; } 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..7407c1fa2 --- /dev/null +++ b/lib/dashboard/drawer/chat_room/matrix_chat/matrix_chat_impl.dart @@ -0,0 +1,540 @@ +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, + }) { + secureStorageProvider.get(SecureStorageKeys.did).then((did) { + if (did != null && did.isNotEmpty) { + 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? ?? 0, + 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? ?? 0, + 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? ?? 0, + ), + name: event.body, + 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( + 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, + }) { + if (uri.trim().isEmpty) return ''; + 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/chat_room/view/chat_room_view.dart b/lib/dashboard/drawer/chat_room/view/chat_room_view.dart new file mode 100644 index 000000000..b7d3cbd33 --- /dev/null +++ b/lib/dashboard/drawer/chat_room/view/chat_room_view.dart @@ -0,0 +1,195 @@ +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'; +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; +import 'package:visibility_detector/visibility_detector.dart'; + +class ChatRoomView extends StatefulWidget { + const ChatRoomView({ + super.key, + this.appBarTitle, + this.chatWelcomeMessage, + }); + + final String? appBarTitle; + final String? chatWelcomeMessage; + + @override + _ChatRoomViewState createState() => _ChatRoomViewState(); +} + +class _ChatRoomViewState extends State { + late final B liveChatCubit; + + bool pageIsVisible = false; + + @override + void initState() { + liveChatCubit = context.read(); + super.initState(); + } + + @override + void dispose() { + super.dispose(); + } + + @override + Widget build(BuildContext context) { + final l10n = context.l10n; + return BasePage( + title: widget.appBarTitle, + scrollView: false, + titleLeading: + widget.appBarTitle == null ? null : const BackLeadingButton(), + titleAlignment: Alignment.topCenter, + padding: const EdgeInsets.all(Sizes.spaceSmall), + body: BlocBuilder( + builder: (context, ChatRoomState state) { + if (state.status == AppStatus.loading) { + return const Center( + 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: ErrorView( + message: message, + onTap: liveChatCubit.init, + ), + ); + } else { + if (pageIsVisible) { + liveChatCubit.setMessagesAsRead(); + } + 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( + widget.chatWelcomeMessage ?? + 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, + ), + Flexible( + child: MyText( + l10n.e2eEncyptedChat, + maxLines: 1, + minFontSize: 8, + style: Theme.of(context).textTheme.subtitle4, + ), + ), + ], + ) + ], + ), + ), + ], + ), + ); + } + }, + ), + ); + } + + void _handleAttachmentPressed() { + showModalBottomSheet( + context: context, + builder: (BuildContext context) => SafeArea( + child: SizedBox( + height: 144, + child: Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + TextButton( + onPressed: () { + Navigator.pop(context); + liveChatCubit.handleImageSelection(); + }, + child: const Align( + alignment: AlignmentDirectional.centerStart, + child: Text('Photo'), + ), + ), + TextButton( + onPressed: () { + Navigator.pop(context); + liveChatCubit.handleFileSelection(); + }, + child: const Align( + alignment: AlignmentDirectional.centerStart, + child: Text('File'), + ), + ), + TextButton( + onPressed: () => Navigator.pop(context), + child: const Align( + alignment: AlignmentDirectional.centerStart, + child: Text('Cancel'), + ), + ), + ], + ), + ), + ), + ); + } + + Future _handleMessageTap(BuildContext _, Message message) async { + await liveChatCubit.handleMessageTap(message); + } +} 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/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 aefb8c8f6..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( @@ -71,9 +81,9 @@ 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), + const Spacer(), Icon( Icons.chevron_right, size: 26, @@ -83,12 +93,6 @@ class HelpCenterView extends StatelessWidget { ), ), ), - DrawerItem( - title: l10n.altmeSupport, - onTap: () { - Navigator.of(context).push(LiveChatPage.route()); - }, - ), ], ), ), diff --git a/lib/dashboard/drawer/drawer/view/reset_wallet_menu.dart b/lib/dashboard/drawer/drawer/view/reset_wallet_menu.dart index 9f740e9c4..1ed8a40d5 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'; @@ -110,11 +109,14 @@ class ResetWalletView extends StatelessWidget { await getSecureStorage.get(SecureStorageKeys.pinCode); if (pinCode?.isEmpty ?? true) { await context.read().resetWallet(); + await context.read().dispose(); } else { await Navigator.of(context).push( PinCodePage.route( - isValidCallback: () => - context.read().resetWallet(), + isValidCallback: () { + context.read().resetWallet(); + context.read().dispose(); + }, restrictToBack: false, ), ); 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/live_chat/cubit/live_chat_cubit.dart b/lib/dashboard/drawer/live_chat/cubit/live_chat_cubit.dart deleted file mode 100644 index be26b145c..000000000 --- a/lib/dashboard/drawer/live_chat/cubit/live_chat_cubit.dart +++ /dev/null @@ -1,454 +0,0 @@ -import 'dart:async'; -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: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: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.didCubit, - required this.secureStorageProvider, - required this.client, - required this.dioClient, - }) : super( - const LiveChatState(), - ) { - init(); - } - - final SecureStorageProvider secureStorageProvider; - final Client client; - final DioClient dioClient; - final logger = getLogger('LiveChatCubit'); - String _roomId = ''; - StreamSubscription? _onEventSubscription; - final DIDCubit didCubit; - - Future onSendPressed(PartialText partialText) async { - try { - final eventId = await client.getRoomById(_roomId)?.sendTextEvent( - partialText.text, - txid: const Uuid().v4(), - ); - 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 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 client.getRoomById(_roomId)?.sendFileEvent( - MatrixFile( - bytes: bytes, - name: result.name, - ), - txid: messageId, - ); - } - } - - Future init() async { - try { - 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 secureStorageProvider.set( - SecureStorageKeys.isUserRegisteredMatrix, - true.toString(), - ); - userId = await _login( - username: username, - password: await _getPasswordForDID(), - ); - } else { - userId = await _login( - username: username, - password: await _getPasswordForDID(), - ); - } - _roomId = await _createRoomAndInviteSupport( - username, - ); - logger.i('roomId : $_roomId'); - _subscribeToEventsOfRoom(); - emit( - state.copyWith( - status: AppStatus.init, - user: User(id: userId), - ), - ); - } catch (e, s) { - logger.e('error: $e, stack: $s'); - emit(state.copyWith(status: AppStatus.error)); - } - } - - 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?; - if (state.messages.any( - (element) => element.id == txId, - )) { - final index = state.messages.indexWhere( - (element) => element.id == txId, - ); - final updatedMessage = state.messages[index].copyWith( - status: Status.delivered, - ); - final newMessages = List.of(state.messages); - 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, - ), - ); - } - emit( - state.copyWith( - messages: [message, ...state.messages], - ), - ); - } - } - }); - } - - 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, - ); - await secureStorageProvider.set( - SecureStorageKeys.supportRoomId, - roomId, - ); - return roomId; - } catch (e, s) { - logger.e('e: $e, s: $s'); - final roomId = await client.joinRoom(name); - return roomId; - } - } else { - return mRoomId; - } - } - - Future _initClient() async { - client.homeserver = Uri.parse(Urls.matrixHomeServer); - await client.init(); - } - - 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 didCubit.didKitProvider.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 username, - }) async { - final did = didCubit.state.did!; - 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 { - client.homeserver = Uri.parse(Urls.matrixHomeServer); - final loginResonse = await client.login( - LoginType.mLoginPassword, - password: password, - identifier: AuthenticationUserIdentifier(user: username), - ); - return loginResonse.userId!; - } - - Future dispose() async { - await client.logout(); - await client.dispose(); - await _onEventSubscription?.cancel(); - } - - 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.seen; - case EventStatus.sending: - return Status.sending; - case EventStatus.sent: - return Status.sent; - case EventStatus.synced: - return Status.delivered; - } - } -} 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/live_chat/view/live_chat_page.dart b/lib/dashboard/drawer/live_chat/view/live_chat_page.dart deleted file mode 100644 index 369a4ca8c..000000000 --- a/lib/dashboard/drawer/live_chat/view/live_chat_page.dart +++ /dev/null @@ -1,154 +0,0 @@ -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_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; - -class LiveChatPage extends StatelessWidget { - const LiveChatPage({ - super.key, - this.hideAppBar = false, - }); - - 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; - - @override - State createState() => _ContactUsViewState(); -} - -class _ContactUsViewState extends State { - late final LiveChatCubit liveChatCubit; - @override - void initState() { - liveChatCubit = context.read(); - super.initState(); - } - - @override - void dispose() { - super.dispose(); - } - - @override - Widget build(BuildContext context) { - final l10n = context.l10n; - return BasePage( - title: widget.hideAppBar ? null : l10n.altmeSupport, - scrollView: false, - titleLeading: widget.hideAppBar ? null : const BackLeadingButton(), - titleAlignment: Alignment.topCenter, - padding: const EdgeInsets.all(Sizes.spaceSmall), - body: BlocBuilder( - builder: (context, state) { - if (state.status == AppStatus.loading) { - return const Center( - child: CircularProgressIndicator(), - ); - } else if (state.status == AppStatus.error) { - return Center( - child: Text(l10n.somethingsWentWrongTryAgainLater), - ); - } else { - 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.spaceNormal), - margin: const EdgeInsets.all(Sizes.spaceNormal), - child: Text( - l10n.supportChatWelcomeMessage, - textAlign: TextAlign.center, - style: Theme.of(context).textTheme.bodyMedium, - ), - ), - ], - ); - } - }, - ), - ); - } - - void _handleAttachmentPressed() { - showModalBottomSheet( - context: context, - builder: (BuildContext context) => SafeArea( - child: SizedBox( - height: 144, - child: Column( - crossAxisAlignment: CrossAxisAlignment.stretch, - children: [ - TextButton( - onPressed: () { - Navigator.pop(context); - liveChatCubit.handleImageSelection(); - }, - child: const Align( - alignment: AlignmentDirectional.centerStart, - child: Text('Photo'), - ), - ), - TextButton( - onPressed: () { - Navigator.pop(context); - liveChatCubit.handleFileSelection(); - }, - child: const Align( - alignment: AlignmentDirectional.centerStart, - child: Text('File'), - ), - ), - TextButton( - onPressed: () => Navigator.pop(context), - child: const Align( - alignment: AlignmentDirectional.centerStart, - child: Text('Cancel'), - ), - ), - ], - ), - ), - ), - ); - } - - Future _handleMessageTap(BuildContext _, Message message) async { - await liveChatCubit.handleMessageTap(message); - } -} 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..958806acb --- /dev/null +++ b/lib/dashboard/drawer/loyalty_card_support_chat/view/loyalty_card_support_chat_page.dart @@ -0,0 +1,55 @@ +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, + 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, + }) { + return MaterialPageRoute( + builder: (_) => LoyaltyCardSupportChatPage( + appBarTitle: appBarTitle, + companySupportId: companySupportId, + loyaltyCardType: loyaltyCardType, + chatWelcomeMessage: chatWelcomeMessage, + ), + 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, + chatWelcomeMessage: chatWelcomeMessage, + ), + ); + } +} 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 848c144b8..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,6 +1,8 @@ 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'; class AccountPrivateKeyPage extends StatefulWidget { const AccountPrivateKeyPage({ @@ -62,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, @@ -80,6 +97,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 +125,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_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/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/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/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/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: 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..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 @@ -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 @@ -23,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(); @@ -55,10 +57,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/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/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/lib/dashboard/home/home/widgets/finish_kyc_dialog.dart b/lib/dashboard/home/home/widgets/finish_kyc_dialog.dart index df71e0165..8f440d34f 100644 --- a/lib/dashboard/home/home/widgets/finish_kyc_dialog.dart +++ b/lib/dashboard/home/home/widgets/finish_kyc_dialog.dart @@ -12,7 +12,8 @@ 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, + surfaceTintColor: Colors.transparent, contentPadding: const EdgeInsets.symmetric(horizontal: 20, vertical: 15), shape: const RoundedRectangleBorder( borderRadius: BorderRadius.all(Radius.circular(25)), @@ -26,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/dashboard/home/home/widgets/kyc_dialog.dart b/lib/dashboard/home/home/widgets/kyc_dialog.dart index 6372b1809..f7f7448c6 100644 --- a/lib/dashboard/home/home/widgets/kyc_dialog.dart +++ b/lib/dashboard/home/home/widgets/kyc_dialog.dart @@ -15,7 +15,8 @@ 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, + surfaceTintColor: Colors.transparent, contentPadding: const EdgeInsets.symmetric(horizontal: 20, vertical: 15), shape: const RoundedRectangleBorder( borderRadius: BorderRadius.all(Radius.circular(25)), @@ -49,6 +50,7 @@ class KycDialog extends StatelessWidget { ), Row( mainAxisAlignment: MainAxisAlignment.center, + crossAxisAlignment: CrossAxisAlignment.center, mainAxisSize: MainAxisSize.min, children: [ Row( @@ -66,6 +68,7 @@ class KycDialog extends StatelessWidget { ), const SizedBox(width: Sizes.spaceNormal), Row( + crossAxisAlignment: CrossAxisAlignment.center, children: [ Image.asset( IconStrings.checkCircleBlue, @@ -100,16 +103,19 @@ class KycDialog extends StatelessWidget { ), Row( mainAxisSize: MainAxisSize.max, - mainAxisAlignment: MainAxisAlignment.center, + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.center, children: [ Image.asset( IconStrings.lockCircle, width: Sizes.icon, + color: Theme.of(context).colorScheme.kycKeyIconColor, ), - 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/home/widgets/token_reward_dialog.dart b/lib/dashboard/home/home/widgets/token_reward_dialog.dart index 4b5514ec7..08ce76f89 100644 --- a/lib/dashboard/home/home/widgets/token_reward_dialog.dart +++ b/lib/dashboard/home/home/widgets/token_reward_dialog.dart @@ -29,7 +29,8 @@ 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, + 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 63184ba18..ddaa315ac 100644 --- a/lib/dashboard/home/home/widgets/wallet_dialog.dart +++ b/lib/dashboard/home/home/widgets/wallet_dialog.dart @@ -14,7 +14,8 @@ 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, + surfaceTintColor: Colors.transparent, contentPadding: const EdgeInsets.symmetric(horizontal: 20, vertical: 15), shape: const RoundedRectangleBorder( borderRadius: BorderRadius.all(Radius.circular(25)), @@ -27,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( @@ -43,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/credentials/detail/cubit/credential_details_cubit.dart b/lib/dashboard/home/tab_bar/credentials/detail/cubit/credential_details_cubit.dart index b4cd3c6a9..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,30 +2,37 @@ import 'dart:convert'; import 'package:altme/app/app.dart'; import 'package:altme/dashboard/dashboard.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'; 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'; part 'credential_details_state.dart'; class CredentialDetailsCubit extends Cubit { - CredentialDetailsCubit({required this.didKitProvider}) - : super(const CredentialDetailsState()); + 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) { emit(state.copyWith(credentialDetailTabStatus: credentialDetailTabStatus)); } 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!); @@ -36,23 +43,58 @@ class CredentialDetailsCubit extends Cubit { status: AppStatus.idle, ), ); + return; } } - 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, - ), - ); + + if (isEbsiIssuer(item)) { + final issuerDid = item.data['issuer']! as String; + //const issuerDid = 'did:ebsi:zeFCExU2XAAshYkPCpjuahA'; + + final VerificationType isVerified = await verifyEncodedData( + issuerDid, + client, + secureStorageProvider, + item.jwt!, + ); + + late CredentialStatus credentialStatus; + + 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 { - 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/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/view/credentials_details_page.dart b/lib/dashboard/home/tab_bar/credentials/detail/view/credentials_details_page.dart index 5e0b53602..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 @@ -1,5 +1,6 @@ -import 'dart:convert'; +// ignore_for_file: lines_longer_than_80_chars +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'; @@ -7,9 +8,11 @@ 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'; +import 'package:secure_storage/secure_storage.dart'; import 'package:share_plus/share_plus.dart'; class CredentialsDetailsPage extends StatelessWidget { @@ -40,6 +43,8 @@ class CredentialsDetailsPage extends StatelessWidget { return BlocProvider( create: (context) => CredentialDetailsCubit( didKitProvider: DIDKitProvider(), + secureStorageProvider: getSecureStorage, + client: DioClient('', Dio()), ), child: CredentialsDetailsView( credentialModel: credentialModel, @@ -131,6 +136,9 @@ class _CredentialsDetailsViewState extends State { .credentialSubjectModel.credentialSubjectType == CredentialSubjectType.linkedInCard; + final bool isEbsiCard = widget.credentialModel.credentialPreview + .credentialSubjectModel.credentialSubjectType.isEbsiCard; + final bool disAllowDelete = widget.credentialModel.credentialPreview .credentialSubjectModel.credentialSubjectType == CredentialSubjectType.walletCredential || @@ -142,82 +150,7 @@ 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)); - // }, - // icon: Icon( - // Icons.qr_code, - // color: Theme.of(context).colorScheme.onBackground, - // ), - // ), 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 { - 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( - widget.credentialModel.id, - widget.credentialModel, - ), - ); - }, - text: l10n.credentialDetailShare, - ) - else - Container(), - ], - ), - ), - ), scrollView: false, body: Column( children: [ @@ -265,6 +198,37 @@ class _CredentialsDetailsViewState extends State { ), ), ), + 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( @@ -278,12 +242,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( @@ -312,6 +274,96 @@ class _CredentialsDetailsViewState extends State { ), ], ), + navigation: widget.readOnly + ? null + : SafeArea( + child: Container( + padding: const EdgeInsets.symmetric( + horizontal: 12, + 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 (isEbsiCard) { + /// 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/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/helper_functions/discover_credential.dart b/lib/dashboard/home/tab_bar/credentials/helper_functions/discover_credential.dart index 98bbb1127..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 @@ -12,29 +12,45 @@ 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)) { 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!, 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/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..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 @@ -20,6 +20,7 @@ class CredentialListCubit extends Cubit { final identityCredentials = []; final passCredentials = []; final blockchainAccountsCredentials = []; + final educationCredentials = []; final othersCredentials = []; final myProfessionalCredentials = []; final gamingCategories = state.gamingCategories; @@ -48,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; @@ -84,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: @@ -102,6 +103,8 @@ class CredentialListCubit extends Cubit { case CredentialSubjectType.fantomPooAddress: case CredentialSubjectType.polygonPooAddress: case CredentialSubjectType.binancePooAddress: + case CredentialSubjectType.euDiplomaCard: + case CredentialSubjectType.euVerifiableId: break; } @@ -136,6 +139,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 @@ -145,7 +154,12 @@ class CredentialListCubit extends Cubit { case CredentialCategory.othersCards: /// adding real credentials - othersCredentials.add(HomeCredential.isNotDummy(credential)); + if (isVerifiableDiplomaType(credential)) { + educationCredentials.add(HomeCredential.isNotDummy(credential)); + } else { + othersCredentials.add(HomeCredential.isNotDummy(credential)); + } + break; } } @@ -166,6 +180,7 @@ class CredentialListCubit extends Cubit { communityCredentials: communityCredentials, identityCredentials: identityCredentials, blockchainAccountsCredentials: blockchainAccountsCredentials, + educationCredentials: educationCredentials, passCredentials: passCredentials, othersCredentials: othersCredentials, myProfessionalCredentials: myProfessionalCredentials, @@ -207,6 +222,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: @@ -226,17 +259,6 @@ class CredentialListCubit extends Cubit { final credentialSubjectType = credential .credentialPreview.credentialSubjectModel.credentialSubjectType; switch (credentialSubjectType) { - case CredentialSubjectType.tezotopiaMembership: - case CredentialSubjectType.chainbornMembership: - case CredentialSubjectType.voucher: - case CredentialSubjectType.tezVoucher: - _removeDummyIfCredentialExist( - credentials, - gamingCategories, - credentialSubjectType, - ); - break; - case CredentialSubjectType.ageRange: case CredentialSubjectType.nationality: case CredentialSubjectType.identityPass: @@ -254,6 +276,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: @@ -262,7 +289,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: @@ -292,6 +318,8 @@ class CredentialListCubit extends Cubit { case CredentialSubjectType.fantomPooAddress: case CredentialSubjectType.polygonPooAddress: case CredentialSubjectType.binancePooAddress: + case CredentialSubjectType.euDiplomaCard: + case CredentialSubjectType.euVerifiableId: break; } @@ -312,6 +340,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 @@ -323,9 +359,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 (isVerifiableDiplomaType(credential)) { + 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; } } @@ -435,38 +478,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 (isVerifiableDiplomaType(credential)) { + 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; } } @@ -491,7 +567,8 @@ class CredentialListCubit extends Cubit { if (credentialSubjectType == CredentialSubjectType.tezotopiaMembership || credentialSubjectType == - CredentialSubjectType.chainbornMembership) { + CredentialSubjectType.chainbornMembership || + credentialSubjectType == CredentialSubjectType.bloometaPass) { gamingCategories.add(credentialSubjectType); } @@ -593,6 +670,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: @@ -608,6 +686,7 @@ class CredentialListCubit extends Cubit { case CredentialSubjectType.fantomPooAddress: case CredentialSubjectType.polygonPooAddress: case CredentialSubjectType.binancePooAddress: + case CredentialSubjectType.euVerifiableId: break; } @@ -627,6 +706,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( @@ -636,11 +723,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 (isVerifiableDiplomaType(credential)) { + 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; } } @@ -652,6 +747,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..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,14 +57,13 @@ class CredentialListData extends StatelessWidget { ), const SizedBox(height: Sizes.spaceNormal), ], - if (advanceSettingsState.isBlockchainAccountsEnabled && - state.blockchainAccountsCredentials.isNotEmpty) ...[ - /// BlockchainAccounts Credentials + if (advanceSettingsState.isEducationEnabled && + state.educationCredentials.isNotEmpty) ...[ + /// Education Credentials HomeCredentialWidget( - title: l10n.blockChainAccounts, - credentials: state.blockchainAccountsCredentials, - categorySubtitle: - l10n.blockchainAccountsCredentialHomeSubtitle, + title: l10n.educationCredentials, + credentials: state.educationCredentials, + categorySubtitle: l10n.educationCredentialHomeSubtitle, ), const SizedBox(height: Sizes.spaceNormal), ], @@ -100,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), + ], ], ), ), 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, 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..384221d05 --- /dev/null +++ b/lib/dashboard/home/tab_bar/credentials/models/eu_diploma_card/eu_diploma_card_model.dart @@ -0,0 +1,146 @@ +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 = '', + 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 EUDiplomaCardModel.fromJson(Map json) => + _$EUDiplomaCardModelFromJson(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() => _$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); + + dynamic ectsCreditPoints; + dynamic eqfLevel; + String id; + List? iscedfCode; + List? nqfLevel; + + Map toJson() => _$LearningSpecificationToJson(this); +} 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..610e17faf --- /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.euVerifiableId, + 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/home_credential/home_credential.dart b/lib/dashboard/home/tab_bar/credentials/models/home_credential/home_credential.dart index 0cf5d089e..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: @@ -247,6 +254,26 @@ class HomeCredential extends Equatable { break; 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: + 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: @@ -258,7 +285,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: @@ -267,16 +293,14 @@ 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: case CredentialSubjectType.polygonPooAddress: case CredentialSubjectType.binancePooAddress: + case CredentialSubjectType.euDiplomaCard: + case CredentialSubjectType.euVerifiableId: break; } @@ -291,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), ); } @@ -303,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); @@ -320,10 +348,11 @@ class HomeCredential extends Equatable { image, isDummy, credentialSubjectType, - websiteGameLink, + websiteLink, whyGetThisCard, expirationDateDetails, howToGetIt, dummyDescription, + longDescription, ]; } diff --git a/lib/dashboard/home/tab_bar/credentials/models/model.dart b/lib/dashboard/home/tab_bar/credentials/models/model.dart index 61993dbb9..e19857db3 100644 --- a/lib/dashboard/home/tab_bar/credentials/models/model.dart +++ b/lib/dashboard/home/tab_bar/credentials/models/model.dart @@ -27,6 +27,8 @@ 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 '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/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..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 @@ -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( @@ -121,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( @@ -168,8 +163,7 @@ class CredentialManifestOfferPickView extends StatelessWidget { presentationDefinition .inputDescriptors[ inputDescriptorIndex]; - //no sure if I'm correct to take first field - //to check optional + final isOptional = inputDescriptor .constraints ?.fields 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..e6aaec643 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,12 @@ class CredentialDisplay extends StatelessWidget { case CredentialSubjectType.linkedInCard: return LinkedinCardWidget(credentialModel: credentialModel); + 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_manifest_widgets/credential_manifest_card.dart b/lib/dashboard/home/tab_bar/credentials/widgets/credential_manifest_widgets/credential_manifest_card.dart index 3f292fb08..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,8 +15,9 @@ class CredentialManifestCard extends StatelessWidget { @override Widget build(BuildContext context) { - final textColor = - getColorFromCredential(outputDescriptor.styles?.text, Colors.black); + final textColor = isVerifiableDiplomaType(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/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/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/credential_widget.dart b/lib/dashboard/home/tab_bar/credentials/widgets/credential_widget/credential_widget.dart index af7f0873d..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 @@ -20,6 +20,8 @@ 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 '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/default_credential_detail_widget.dart b/lib/dashboard/home/tab_bar/credentials/widgets/credential_widget/default_credential_detail_widget.dart index 457895ba6..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, @@ -63,11 +72,18 @@ class DefaultCredentialDetailWidget extends StatelessWidget { aspectRatio: Sizes.credentialAspectRatio, child: DecoratedBox( decoration: BaseBoxDecoration( - borderRadius: BorderRadius.circular(20), - color: getColorFromCredential( - outputDescriptors.first.styles?.background, - Colors.white, - ), + borderRadius: BorderRadius.circular(Sizes.credentialBorderRadius), + 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 ffa3ffa84..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,39 +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) - : 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 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/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..c4d03597a --- /dev/null +++ b/lib/dashboard/home/tab_bar/credentials/widgets/credential_widget/eu_diploma_card_widget.dart @@ -0,0 +1,33 @@ +import 'package:altme/app/app.dart'; +import 'package:altme/dashboard/dashboard.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 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!), + ); + } +} 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!), + ); + } +} 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/home/tab_bar/credentials/widgets/descriptioni_dialog.dart b/lib/dashboard/home/tab_bar/credentials/widgets/descriptioni_dialog.dart index fc329cbfa..d80ec6b30 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,10 +15,11 @@ 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, + 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/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/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/dashboard/home/tab_bar/nft/cubit/nft_cubit.dart b/lib/dashboard/home/tab_bar/nft/cubit/nft_cubit.dart index cbe5e8c0c..959320b44 100644 --- a/lib/dashboard/home/tab_bar/nft/cubit/nft_cubit.dart +++ b/lib/dashboard/home/tab_bar/nft/cubit/nft_cubit.dart @@ -147,21 +147,41 @@ class NftCubit extends Cubit { return []; } - return List.from( + 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, 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?, ); }), ).toList(); + + for (final element in nftList) { + if (element.thumbnailUri == null) { + 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; } catch (e, s) { getLogger(toString()).e('e: $e, s: $s'); return []; 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..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 @@ -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_bloc/flutter_bloc.dart'; import 'package:flutter_html/flutter_html.dart'; class NftDetailsPage extends StatelessWidget { @@ -81,45 +82,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), - // 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); - // }, - // ), - // ], - // ) + ...buildEthereumMoreDetails(l10n), ], ), ), @@ -152,10 +135,41 @@ class _NftDetailsViewState extends State { List buildTezosMoreDetails(AppLocalizations l10n) { final nftModel = widget.nftModel as TezosNftModel; return [ - if (nftModel.identifier != null) + const SizedBox(height: Sizes.spaceNormal), + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + '${l10n.contractAddress} : ', + style: Theme.of(context).textTheme.titleMedium, + ), + Row( + children: [ + Flexible( + child: Text( + 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) ...[ const SizedBox(height: Sizes.spaceNormal), - if (nftModel.identifier != null) - Row( + Column( children: [ Text( '${l10n.identifier} : ', @@ -167,61 +181,167 @@ class _NftDetailsViewState extends State { ) ], ), - if (nftModel.creators != null) + ], + if (nftModel.creators != null && nftModel.creators!.isNotEmpty) ...[ const SizedBox( height: Sizes.spaceXSmall, ), - if (nftModel.creators != null) + 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(', ') ?? '?', 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 && nftModel.publishers!.isNotEmpty) ...[ + const SizedBox( + height: Sizes.spaceXSmall, + ), + Text( + '${l10n.publishers} : ', + style: Theme.of(context).textTheme.titleMedium, + ), + Row( + children: [ + 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, + ); + }, + ), ], ), - if (nftModel.publishers != null) + ], + if (nftModel.date != null) ...[ const SizedBox( height: Sizes.spaceXSmall, ), - if (nftModel.publishers != null) Row( children: [ Text( - '${l10n.publishers} : ', + '${l10n.createDate} : ', style: Theme.of(context).textTheme.titleMedium, ), Text( - nftModel.publishers?.join(', ') ?? '?', + UiDate.normalFormat(nftModel.date) ?? '?', style: Theme.of(context).textTheme.bodySmall3, - ) + ), ], ), - if (nftModel.date != null) + ] + ]; + } + + List buildEthereumMoreDetails(AppLocalizations l10n) { + final nftModel = widget.nftModel as EthereumNftModel; + return [ + const SizedBox(height: Sizes.spaceNormal), + Text( + '${l10n.contractAddress} : ', + style: Theme.of(context).textTheme.titleMedium, + ), + Row( + children: [ + Flexible( + child: Text( + 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') ...[ + const SizedBox( + height: Sizes.spaceXSmall, + ), + Text( + '${l10n.creator} : ', + style: Theme.of(context).textTheme.titleMedium, + ), + Row( + children: [ + Flexible( + child: Text( + nftModel.minterAddress ?? '?', + 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!, + ); + }, + ), + ], + ), + ], + if (nftModel.lastMetadataSync != null) ...[ const SizedBox( height: Sizes.spaceXSmall, ), - if (nftModel.date != null) Row( children: [ Text( - '${l10n.createDate} : ', + '${l10n.lastMetadataSync} : ', style: Theme.of(context).textTheme.titleMedium, ), Text( - UiDate.normalFormat(nftModel.date) ?? '?', + UiDate.normalFormat(nftModel.lastMetadataSync) ?? '?', style: Theme.of(context).textTheme.bodySmall3, ), ], ), + ] ]; } } 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..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 @@ -55,73 +55,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.coins, + 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, ), ], 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) { 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..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, + ), + ), ); } } @@ -448,7 +460,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/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..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 @@ -40,7 +40,8 @@ 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, + surfaceTintColor: Colors.transparent, contentPadding: const EdgeInsets.symmetric( horizontal: Sizes.spaceNormal, vertical: Sizes.spaceSmall, @@ -57,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/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..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 @@ -99,7 +99,7 @@ class _TokenAmountCalculatorPageState extends State { mainAxisSize: MainAxisSize.max, children: [ const SizedBox( - height: Sizes.spaceLarge, + height: Sizes.spaceSmall, ), BlocBuilder( builder: (context, state) { @@ -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/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..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', @@ -141,11 +142,13 @@ class SendReceiveHomeCubit extends Cubit { params = { 'anyof.sender.target': walletAddress, 'amount.gt': 0, + 'limit': 1000 }; } else { params = { 'anyof.sender.target': contractAddress, 'entrypoint': 'transfer', + 'limit': 1000, 'parameter.in': jsonEncode([ {'to': walletAddress}, {'from': walletAddress} 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, ); 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); }, 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/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 f6c2417b6..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'); @@ -80,19 +84,10 @@ 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); + final uri = Uri.parse(scannedResponse); + await verify(uri: uri); } } on FormatException { log.i('Format Exception'); @@ -108,67 +103,27 @@ class QRCodeScanCubit extends Cubit { if (e is MessageHandler) { emit(state.error(messageHandler: e)); } else { - emit( - state.error( - messageHandler: ResponseMessage( - ResponseString - .RESPONSE_STRING_SOMETHING_WENT_WRONG_TRY_AGAIN_LATER, - ), - ), - ); - } - } - } + 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; + } - 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, - ), - ), + state.error(messageHandler: ResponseMessage(message)), ); } } } 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( @@ -186,67 +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 { - ///Check if SIOPV2 request - if (uri?.queryParameters['scope'] == 'openid') { + /// verifier side (siopv2) without request_uri + if (state.uri?.queryParameters['scope'] == 'openid') { // 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 launchSiopV2RequestFlow(); // final openIdCredential = getCredentialName(sIOPV2Param.claims!); // final openIdIssuer = getIssuersName(sIOPV2Param.claims!); @@ -304,10 +214,15 @@ 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); if (e is MessageHandler) { emit(state.error(messageHandler: e)); } else { @@ -323,18 +238,84 @@ class QRCodeScanCubit extends Cubit { } } - Future accept({ - required Uri uri, - required Issuer issuer, - required bool isScan, - }) async { - emit(state.loading(isScan: isScan)); + Future launchSiopV2RequestFlow() async { + // Check if we can respond to presentation request: + // having credentials? + // having correct crv in ebsi key + + if (!await isSiopV2WithRequestURIValid(state.uri!)) { + emit( + state.copyWith( + qrScanStatus: QrScanStatus.success, + route: IssuerWebsitesPage.route(''), + ), + ); + } else { + 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 Issuer issuer}) async { + emit(state.loading()); final log = getLogger('QRCodeScanCubit - accept'); late final dynamic data; try { - final dynamic response = await client.get(uri.toString()); + /// ebsi credential + /// issuer side (oidc4VCI) + if (state.uri.toString().startsWith('openid://initiate_issuance?')) { + await initiateEbsiCredentialIssuance( + state.uri.toString(), + client, + walletCubit, + getSecureStorage, + ); + + emit(state.copyWith(qrScanStatus: QrScanStatus.goBack)); + return; + } + + 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(state.uri!.toString()); data = response is String ? jsonDecode(response) : response; log.i('data - $data'); @@ -385,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, ), @@ -429,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, ); @@ -440,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, ), @@ -456,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, ), @@ -470,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, ), @@ -502,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) { @@ -548,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( @@ -573,20 +614,20 @@ 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); } 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/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/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/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/lib/ebsi/add_ebsi_credential.dart b/lib/ebsi/add_ebsi_credential.dart index 5910662fa..4cc821ab4 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!); @@ -40,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())], ); diff --git a/lib/ebsi/initiate_ebsi_credential_issuance.dart b/lib/ebsi/initiate_ebsi_credential_issuance.dart index 3248e08cd..4245ffe83 100644 --- a/lib/ebsi/initiate_ebsi_credential_issuance.dart +++ b/lib/ebsi/initiate_ebsi_credential_issuance.dart @@ -1,16 +1,9 @@ -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'; +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'; 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, @@ -21,7 +14,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, @@ -30,7 +24,7 @@ Future initiateEbsiCredentialIssuance( final dynamic encodedCredentialFromEbsi = await ebsi.getCredential( uriFromScannedResponse, null, - p256PrivateKey, + privateKey, ); await addEbsiCredential( diff --git a/lib/ebsi/verify_encoded_data.dart b/lib/ebsi/verify_encoded_data.dart new file mode 100644 index 000000000..932037083 --- /dev/null +++ b/lib/ebsi/verify_encoded_data.dart @@ -0,0 +1,27 @@ +import 'package:altme/app/app.dart'; +import 'package:dio/dio.dart'; +import 'package:ebsi/ebsi.dart'; +import 'package:secure_storage/secure_storage.dart'; + +Future verifyEncodedData( + String issuerDid, + DioClient client, + SecureStorageProvider secureStorageProvider, + 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 holderKid = await ebsi.getKid(null, privateKey); + + final VerificationType verificationType = await ebsi.verifyEncodedData( + issuerDid: issuerDid, + jwt: jwt, + holderKid: holderKid, + ); + return verificationType; +} diff --git a/lib/import_wallet/cubit/import_wallet_cubit.dart b/lib/import_wallet/cubit/import_wallet_cubit.dart index c32029318..021dd6aac 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' , @@ -66,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; @@ -94,6 +103,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..96d486d51 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, @@ -111,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 7117094b1..38820c065 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, @@ -109,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(), @@ -150,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, @@ -167,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, @@ -211,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, @@ -230,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/issuer_websites_page/widget/kyc_button.dart b/lib/issuer_websites_page/widget/kyc_button.dart index 9cb075513..8300163b5 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,9 @@ class KYCButton extends StatelessWidget { await showDialog( context: context, builder: (context) => AlertDialog( - backgroundColor: Theme.of(context).colorScheme.background, + backgroundColor: + Theme.of(context).colorScheme.popupBackground, + surfaceTintColor: Colors.transparent, 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 ef2c893ca..4cec3515a 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", @@ -336,6 +336,7 @@ "wallet": "Wallet", "manageAccounts": "Manage blockchain accounts", "blockchainAccounts": "Blockchain accounts", + "educationCredentials": "Education credentials", "security": "Security", "networkAndRegistries": "Network & Registries", "chooseNetwork": "Choose Network", @@ -378,6 +379,8 @@ "selectAccount": "Select account", "onbordingSeedPhrase": "Seed Phrase", "onboardingPleaseStoreMessage": "Please, write down your Recovery Phrase", + "onboardingVerifyPhraseMessage": "Confirm Recovery Words", + "onboardingVerifyPhraseMessageDetails": "To ensure your Recovery Phrase is written correctly, select the words in correct order.", "onboardingAltmeMessage": "Talao 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", @@ -387,6 +390,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", @@ -405,6 +409,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", @@ -423,7 +428,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 +557,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.", @@ -570,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.", @@ -578,9 +583,11 @@ "emailPassHowToGetIt": "Itโ€™s super easy. Talao 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 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 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.", @@ -588,7 +595,7 @@ "linkedinCardHowToGetIt": "You can claim this card by following Talao 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 Talao 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 Talao 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 Talaoโ€™s KYC check.", @@ -659,11 +666,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.", @@ -766,7 +774,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", @@ -781,9 +789,9 @@ "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": "Talao 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:", @@ -795,5 +803,23 @@ "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 Talao." + "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", + "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.", + "verifyNow": "Verify Now", + "verifyLater": "Verify Later", + "welDone": "Well done!", + "mnemonicsVerifiedMessage": "Your revovery phrase is saved correctly.", + "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 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.", + "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 6273ef09a..9b99be847 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", @@ -570,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 a4ed13467..72973c1ab 100644 --- a/lib/l10n/untranslated.json +++ b/lib/l10n/untranslated.json @@ -244,7 +244,7 @@ "version", "cards", "nfts", - "tokens", + "coins", "getCards", "close", "profile", @@ -286,6 +286,7 @@ "wallet", "manageAccounts", "blockchainAccounts", + "educationCredentials", "security", "networkAndRegistries", "chooseNetwork", @@ -328,6 +329,8 @@ "selectAccount", "onbordingSeedPhrase", "onboardingPleaseStoreMessage", + "onboardingVerifyPhraseMessage", + "onboardingVerifyPhraseMessageDetails", "onboardingAltmeMessage", "onboardingWroteDownMessage", "copyToClipboard", @@ -337,6 +340,7 @@ "import", "accountName", "importWalletText", + "importWalletTextRecoveryPhraseOnly", "recoveryPhraseDescriptions", "importEasilyFrom", "templeWallet", @@ -348,6 +352,7 @@ "other", "otherWalletApp", "importWalletHintText", + "importWalletHintTextRecoveryPhraseOnly", "kycDialogTitle", "idVerificationProcess", "idCheck", @@ -366,7 +371,6 @@ "verfiedButton", "verifiedNotificationTitle", "verifiedNotificationDescription", - "blockChainAccounts", "showDecentralizedID", "manageDecentralizedID", "addressBook", @@ -489,6 +493,7 @@ "identityCredentialDiscoverSubtitle", "myProfessionalCredentialDiscoverSubtitle", "blockchainAccountsCredentialHomeSubtitle", + "educationCredentialHomeSubtitle", "passCredentialHomeSubtitle", "communityCredentialHomeSubtitle", "communityCredentialDiscoverSubtitle", @@ -506,7 +511,7 @@ "transactionErrorTxRollupInvalidZeroTransfer", "transactionErrorTxRollupUnknownAddress", "transactionErrorInactiveChain", - "websiteGame", + "website", "whyGetThisCard", "howToGetIt", "emailPassWhyGetThisCard", @@ -514,9 +519,11 @@ "emailPassHowToGetIt", "tezotopiaMembershipWhyGetThisCard", "tezotopiaMembershipExpirationDate", + "tezotopiaMembershipLongDescription", "chainbornMembershipHowToGetIt", "chainbornMembershipWhyGetThisCard", "chainbornMembershipExpirationDate", + "chainbornMembershipLongDescription", "twitterHowToGetIt", "twitterWhyGetThisCard", "twitterExpirationDate", @@ -595,11 +602,12 @@ "failedToInitCamera", "chooseMethodPageOver18Title", "chooseMethodPageOver13Title", + "chooseMethodPageAgeRangeTitle", "chooseMethodPageSubtitle", - "idDocumentCheck", - "idDocumentCheckDescription", - "realTimePhoto", - "realTimePhotoDescription", + "kycTitle", + "kycSubtitle", + "passbaseTitle", + "passbaseSubtitle", "verifyYourAge", "verifyYourAgeSubtitle", "verifyYourAgeDescription", @@ -731,7 +739,25 @@ "backToHome", "help", "searchCredentials", - "supportChatWelcomeMessage" + "supportChatWelcomeMessage", + "cardChatWelcomeMessage", + "creator", + "contractAddress", + "lastMetadataSync", + "e2eEncyptedChat", + "pincodeAttemptMessage", + "verifyNow", + "verifyLater", + "welDone", + "mnemonicsVerifiedMessage", + "chatWith", + "sendAnEmail", + "service_not_registered_message", + "bloometaPassHowToGetIt", + "bloometaPassExpirationDate", + "bloometaPassWhyGetThisCard", + "bloometaPassLongDescription", + "chat" ], "es": [ @@ -979,7 +1005,7 @@ "version", "cards", "nfts", - "tokens", + "coins", "getCards", "close", "profile", @@ -1021,6 +1047,7 @@ "wallet", "manageAccounts", "blockchainAccounts", + "educationCredentials", "security", "networkAndRegistries", "chooseNetwork", @@ -1063,6 +1090,8 @@ "selectAccount", "onbordingSeedPhrase", "onboardingPleaseStoreMessage", + "onboardingVerifyPhraseMessage", + "onboardingVerifyPhraseMessageDetails", "onboardingAltmeMessage", "onboardingWroteDownMessage", "copyToClipboard", @@ -1072,6 +1101,7 @@ "import", "accountName", "importWalletText", + "importWalletTextRecoveryPhraseOnly", "recoveryPhraseDescriptions", "importEasilyFrom", "templeWallet", @@ -1083,6 +1113,7 @@ "other", "otherWalletApp", "importWalletHintText", + "importWalletHintTextRecoveryPhraseOnly", "kycDialogTitle", "idVerificationProcess", "idCheck", @@ -1101,7 +1132,6 @@ "verfiedButton", "verifiedNotificationTitle", "verifiedNotificationDescription", - "blockChainAccounts", "showDecentralizedID", "manageDecentralizedID", "addressBook", @@ -1224,6 +1254,7 @@ "identityCredentialDiscoverSubtitle", "myProfessionalCredentialDiscoverSubtitle", "blockchainAccountsCredentialHomeSubtitle", + "educationCredentialHomeSubtitle", "passCredentialHomeSubtitle", "communityCredentialHomeSubtitle", "communityCredentialDiscoverSubtitle", @@ -1241,7 +1272,7 @@ "transactionErrorTxRollupInvalidZeroTransfer", "transactionErrorTxRollupUnknownAddress", "transactionErrorInactiveChain", - "websiteGame", + "website", "whyGetThisCard", "howToGetIt", "emailPassWhyGetThisCard", @@ -1249,9 +1280,11 @@ "emailPassHowToGetIt", "tezotopiaMembershipWhyGetThisCard", "tezotopiaMembershipExpirationDate", + "tezotopiaMembershipLongDescription", "chainbornMembershipHowToGetIt", "chainbornMembershipWhyGetThisCard", "chainbornMembershipExpirationDate", + "chainbornMembershipLongDescription", "twitterHowToGetIt", "twitterWhyGetThisCard", "twitterExpirationDate", @@ -1330,11 +1363,12 @@ "failedToInitCamera", "chooseMethodPageOver18Title", "chooseMethodPageOver13Title", + "chooseMethodPageAgeRangeTitle", "chooseMethodPageSubtitle", - "idDocumentCheck", - "idDocumentCheckDescription", - "realTimePhoto", - "realTimePhotoDescription", + "kycTitle", + "kycSubtitle", + "passbaseTitle", + "passbaseSubtitle", "verifyYourAge", "verifyYourAgeSubtitle", "verifyYourAgeDescription", @@ -1466,10 +1500,43 @@ "backToHome", "help", "searchCredentials", - "supportChatWelcomeMessage" + "supportChatWelcomeMessage", + "cardChatWelcomeMessage", + "creator", + "contractAddress", + "lastMetadataSync", + "e2eEncyptedChat", + "pincodeAttemptMessage", + "verifyNow", + "verifyLater", + "welDone", + "mnemonicsVerifiedMessage", + "chatWith", + "sendAnEmail", + "service_not_registered_message", + "bloometaPassHowToGetIt", + "bloometaPassExpirationDate", + "bloometaPassWhyGetThisCard", + "bloometaPassLongDescription", + "chat" ], "fr": [ + "coins", + "educationCredentials", + "onboardingVerifyPhraseMessage", + "onboardingVerifyPhraseMessageDetails", + "importWalletTextRecoveryPhraseOnly", + "importWalletHintTextRecoveryPhraseOnly", + "educationCredentialHomeSubtitle", + "website", + "tezotopiaMembershipLongDescription", + "chainbornMembershipLongDescription", + "chooseMethodPageAgeRangeTitle", + "kycTitle", + "kycSubtitle", + "passbaseTitle", + "passbaseSubtitle", "altmeSupport", "manageKeyDecentralizedId", "manageEbsiDecentralizedId", @@ -1479,7 +1546,25 @@ "backToHome", "help", "searchCredentials", - "supportChatWelcomeMessage" + "supportChatWelcomeMessage", + "cardChatWelcomeMessage", + "creator", + "contractAddress", + "lastMetadataSync", + "e2eEncyptedChat", + "pincodeAttemptMessage", + "verifyNow", + "verifyLater", + "welDone", + "mnemonicsVerifiedMessage", + "chatWith", + "sendAnEmail", + "service_not_registered_message", + "bloometaPassHowToGetIt", + "bloometaPassExpirationDate", + "bloometaPassWhyGetThisCard", + "bloometaPassLongDescription", + "chat" ], "it": [ @@ -1727,7 +1812,7 @@ "version", "cards", "nfts", - "tokens", + "coins", "getCards", "close", "profile", @@ -1769,6 +1854,7 @@ "wallet", "manageAccounts", "blockchainAccounts", + "educationCredentials", "security", "networkAndRegistries", "chooseNetwork", @@ -1811,6 +1897,8 @@ "selectAccount", "onbordingSeedPhrase", "onboardingPleaseStoreMessage", + "onboardingVerifyPhraseMessage", + "onboardingVerifyPhraseMessageDetails", "onboardingAltmeMessage", "onboardingWroteDownMessage", "copyToClipboard", @@ -1820,6 +1908,7 @@ "import", "accountName", "importWalletText", + "importWalletTextRecoveryPhraseOnly", "recoveryPhraseDescriptions", "importEasilyFrom", "templeWallet", @@ -1831,6 +1920,7 @@ "other", "otherWalletApp", "importWalletHintText", + "importWalletHintTextRecoveryPhraseOnly", "kycDialogTitle", "idVerificationProcess", "idCheck", @@ -1849,7 +1939,6 @@ "verfiedButton", "verifiedNotificationTitle", "verifiedNotificationDescription", - "blockChainAccounts", "showDecentralizedID", "manageDecentralizedID", "addressBook", @@ -1972,6 +2061,7 @@ "identityCredentialDiscoverSubtitle", "myProfessionalCredentialDiscoverSubtitle", "blockchainAccountsCredentialHomeSubtitle", + "educationCredentialHomeSubtitle", "passCredentialHomeSubtitle", "communityCredentialHomeSubtitle", "communityCredentialDiscoverSubtitle", @@ -1989,7 +2079,7 @@ "transactionErrorTxRollupInvalidZeroTransfer", "transactionErrorTxRollupUnknownAddress", "transactionErrorInactiveChain", - "websiteGame", + "website", "whyGetThisCard", "howToGetIt", "emailPassWhyGetThisCard", @@ -1997,9 +2087,11 @@ "emailPassHowToGetIt", "tezotopiaMembershipWhyGetThisCard", "tezotopiaMembershipExpirationDate", + "tezotopiaMembershipLongDescription", "chainbornMembershipHowToGetIt", "chainbornMembershipWhyGetThisCard", "chainbornMembershipExpirationDate", + "chainbornMembershipLongDescription", "twitterHowToGetIt", "twitterWhyGetThisCard", "twitterExpirationDate", @@ -2078,11 +2170,12 @@ "failedToInitCamera", "chooseMethodPageOver18Title", "chooseMethodPageOver13Title", + "chooseMethodPageAgeRangeTitle", "chooseMethodPageSubtitle", - "idDocumentCheck", - "idDocumentCheckDescription", - "realTimePhoto", - "realTimePhotoDescription", + "kycTitle", + "kycSubtitle", + "passbaseTitle", + "passbaseSubtitle", "verifyYourAge", "verifyYourAgeSubtitle", "verifyYourAgeDescription", @@ -2214,6 +2307,24 @@ "backToHome", "help", "searchCredentials", - "supportChatWelcomeMessage" + "supportChatWelcomeMessage", + "cardChatWelcomeMessage", + "creator", + "contractAddress", + "lastMetadataSync", + "e2eEncyptedChat", + "pincodeAttemptMessage", + "verifyNow", + "verifyLater", + "welDone", + "mnemonicsVerifiedMessage", + "chatWith", + "sendAnEmail", + "service_not_registered_message", + "bloometaPassHowToGetIt", + "bloometaPassExpirationDate", + "bloometaPassWhyGetThisCard", + "bloometaPassLongDescription", + "chat" ] } 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 609d709b6..bb807c582 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, @@ -44,6 +59,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; @@ -51,19 +68,42 @@ 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: [ - const MStepper( + MStepper( step: 2, - totalStep: 3, + totalStep: byPassScreen ? 2 : 3, ), const Spacer(), Text( @@ -124,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, ), @@ -141,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 936cdb21c..aa35d53a7 100644 --- a/lib/onboarding/gen_phrase/cubit/onboarding_gen_phrase_cubit.dart +++ b/lib/onboarding/gen_phrase/cubit/onboarding_gen_phrase_cubit.dart @@ -1,13 +1,14 @@ 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'; @@ -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,52 +32,27 @@ class OnBoardingGenPhraseCubit extends Cubit { 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()); - 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 generateAccount( + mnemonic: mnemonic, + secureStorageProvider: secureStorageProvider, + keyGenerator: keyGenerator, + didKitProvider: didKitProvider, + didCubit: didCubit, + homeCubit: homeCubit, + walletCubit: walletCubit, + splashCubit: splashCubit, ); - 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, - ); - - /// crypto wallet - await walletCubit.createCryptoWallet( - mnemonicOrKey: mnemonicFormatted, - isImported: false, - isFromOnboarding: true, + await secureStorageProvider.set( + SecureStorageKeys.hasVerifiedMnemonics, + 'no', ); - - 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 a0a51ed45..2bf72c80a 100644 --- a/lib/onboarding/gen_phrase/view/onboarding_gen_phrase.dart +++ b/lib/onboarding/gen_phrase/view/onboarding_gen_phrase.dart @@ -3,8 +3,9 @@ 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/cubit/wallet_cubit.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'; @@ -30,6 +31,7 @@ class OnBoardingGenPhrasePage extends StatelessWidget { keyGenerator: KeyGenerator(), homeCubit: context.read(), walletCubit: context.read(), + splashCubit: context.read(), ), child: const OnBoardingGenPhraseView(), ); @@ -51,7 +53,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 @@ -72,8 +75,9 @@ class _OnBoardingGenPhraseViewState extends State { stateMessage: state.message!, ); } + if (state.status == AppStatus.success) { - context.read().init(); + context.read().init(); Navigator.pushAndRemoveUntil( context, WalletReadyPage.route(), @@ -143,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( @@ -198,16 +155,32 @@ class _OnBoardingGenPhraseViewState extends State { 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, + 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!, + isFromOnboarding: true, + ), + ); + }, + ), + ], ), ), ), diff --git a/lib/onboarding/helper_function/helper_function.dart b/lib/onboarding/helper_function/helper_function.dart new file mode 100644 index 000000000..b3993a1f3 --- /dev/null +++ b/lib/onboarding/helper_function/helper_function.dart @@ -0,0 +1,61 @@ +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, + ); + + /// did + 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, + ); + + ///polygon + + /// 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..673cacdab 100644 --- a/lib/onboarding/onboarding.dart +++ b/lib/onboarding/onboarding.dart @@ -1,8 +1,10 @@ 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'; 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/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, - ), ], ), ), 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..4575f309d --- /dev/null +++ b/lib/onboarding/verify_phrase/cubit/onboarding_verify_phrase_cubit.dart @@ -0,0 +1,142 @@ +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'; +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, + required this.flavorCubit, + }) : super(OnBoardingVerifyPhraseState()); + + final SecureStorageProvider secureStorageProvider; + final KeyGenerator keyGenerator; + final DIDKitProvider didKitProvider; + final DIDCubit didCubit; + final HomeCubit homeCubit; + final WalletCubit walletCubit; + final FlavorCubit flavorCubit; + final SplashCubit splashCubit; + + 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)); + } + if (flavorCubit.state != FlavorMode.development) { + oldState.shuffle(); + } + + emit(state.copyWith(mnemonicStates: oldState, status: AppStatus.idle)); + } + + List tempMnemonics = []; + + Future verify({ + required List mnemonic, + required int index, + }) async { + 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 { + 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)); + } + } + } + + Future generateSSIAndCryptoAccount({ + required List mnemonic, + required bool isFromOnboarding, + }) async { + emit(state.loading()); + try { + 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) { + 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..9b8a77e07 --- /dev/null +++ b/lib/onboarding/verify_phrase/cubit/onboarding_verify_phrase_state.dart @@ -0,0 +1,99 @@ +part of 'onboarding_verify_phrase_cubit.dart'; + +@JsonSerializable() +class OnBoardingVerifyPhraseState extends Equatable { + OnBoardingVerifyPhraseState({ + this.status = AppStatus.init, + this.message, + this.isVerified = false, + List? mnemonicStates, + }) : mnemonicStates = mnemonicStates ?? []; + + factory OnBoardingVerifyPhraseState.fromJson(Map json) => + _$OnBoardingVerifyPhraseStateFromJson(json); + + final AppStatus status; + final StateMessage? message; + final bool isVerified; + final List mnemonicStates; + + OnBoardingVerifyPhraseState loading() { + return OnBoardingVerifyPhraseState( + status: AppStatus.loading, + isVerified: isVerified, + mnemonicStates: mnemonicStates, + ); + } + + OnBoardingVerifyPhraseState error({ + required MessageHandler messageHandler, + }) { + return OnBoardingVerifyPhraseState( + status: AppStatus.error, + message: StateMessage.error(messageHandler: messageHandler), + isVerified: isVerified, + mnemonicStates: mnemonicStates, + ); + } + + OnBoardingVerifyPhraseState success({ + MessageHandler? messageHandler, + String? filePath, + }) { + return OnBoardingVerifyPhraseState( + status: AppStatus.success, + message: messageHandler == null + ? null + : StateMessage.success(messageHandler: messageHandler), + isVerified: isVerified, + mnemonicStates: mnemonicStates, + ); + } + + OnBoardingVerifyPhraseState copyWith({ + required AppStatus status, + StateMessage? message, + bool? isVerified, + List? mnemonicStates, + }) { + return OnBoardingVerifyPhraseState( + 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, 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/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..cef108096 --- /dev/null +++ b/lib/onboarding/verify_phrase/view/onboarding_verify_phrase.dart @@ -0,0 +1,259 @@ +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'; +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, + required this.isFromOnboarding, + super.key, + }); + + final List mnemonic; + final bool isFromOnboarding; + + static Route route({ + required List mnemonic, + required bool isFromOnboarding, + }) => + MaterialPageRoute( + builder: (context) => OnBoardingVerifyPhrasePage( + mnemonic: mnemonic, + isFromOnboarding: isFromOnboarding, + ), + 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(), + flavorCubit: context.read(), + ), + child: OnBoardingVerifyPhraseView( + mnemonic: mnemonic, + isFromOnboarding: isFromOnboarding, + ), + ); + } +} + +class OnBoardingVerifyPhraseView extends StatefulWidget { + const OnBoardingVerifyPhraseView({ + required this.mnemonic, + required this.isFromOnboarding, + super.key, + }); + + final List mnemonic; + final bool isFromOnboarding; + + @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; + + 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) { + if (widget.isFromOnboarding) { + context.read().init(); + Navigator.pushAndRemoveUntil( + context, + WalletReadyPage.route(), + (Route route) => route.isFirst, + ); + } else { + Navigator.pushReplacement( + context, + KeyVerifiedPage.route(), + ); + } + } + }, + builder: (context, state) { + return BasePage( + scrollView: true, + useSafeArea: true, + padding: const EdgeInsets.symmetric(horizontal: Sizes.spaceXSmall), + titleLeading: const BackLeadingButton(), + secureScreen: true, + body: state.mnemonicStates.length < 12 + ? Container() + : Column( + children: [ + if (widget.isFromOnboarding) ...[ + const MStepper( + step: 3, + totalStep: 3, + ), + 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.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( + horizontal: Sizes.spaceSmall, + vertical: Sizes.spaceSmall, + ), + child: MyGradientButton( + text: l10n.onBoardingGenPhraseButton, + verticalSpacing: 18, + onPressed: state.isVerified + ? () async { + await context + .read() + .generateSSIAndCryptoAccount( + mnemonic: widget.mnemonic, + isFromOnboarding: widget.isFromOnboarding, + ); + } + : 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/widgets.dart b/lib/onboarding/widgets/widgets.dart index 45898e847..2d42d490f 100644 --- a/lib/onboarding/widgets/widgets.dart +++ b/lib/onboarding/widgets/widgets.dart @@ -1,3 +1,3 @@ -export 'm_stepper.dart'; +export 'm_stepper.dart'; export 'onboarding_widget.dart'; export 'page_tracker.dart'; 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..52dc7077d 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, @@ -68,6 +70,8 @@ class _ConfirmPinCodeViewState extends State { final StreamController _verificationNotifier = StreamController.broadcast(); + bool get byPassScreen => !Parameters.hasCryptoCallToAction; + @override void initState() { super.initState(); @@ -91,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 ed5723899..529946920 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, @@ -55,6 +57,8 @@ class EnterNewPinCodeView extends StatefulWidget { } class _EnterNewPinCodeViewState extends State { + bool get byPassScreen => !Parameters.hasCryptoCallToAction; + @override void initState() { super.initState(); @@ -77,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/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 afca8a4f5..c1ca51523 100644 --- a/lib/pin_code/widgets/circle_ui_config.dart +++ b/lib/pin_code/widgets/circle_ui_config.dart @@ -24,11 +24,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) { @@ -40,7 +42,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/scan/cubit/scan_cubit.dart b/lib/scan/cubit/scan_cubit.dart index 2ae676923..60bc5d00c 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/wallet/wallet.dart'; import 'package:bloc/bloc.dart'; import 'package:did_kit/did_kit.dart'; @@ -55,17 +56,19 @@ class ScanCubit extends Cubit { final log = getLogger('ScanCubit - credentialOffer'); try { - if (uri.queryParameters['scope'] == 'openid') { - // final mnemonic = - // await secureStorageProvider.get(SecureStorageKeys.ssiMnemonic); + 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 credentialList = credentialsToBePresented! .map((e) => jsonEncode(e.toJson())) .toList(); - final String p256PrivateKey = - await getRandomP256PrivateKey(secureStorageProvider); - - await ebsi.sendPresentation(uri, credentialList, null, p256PrivateKey); + await ebsi.sendPresentation(uri, credentialList, null, privateKey); await presentationActivity( credentialModels: credentialsToBePresented, diff --git a/lib/splash/bloclisteners/blocklisteners.dart b/lib/splash/bloclisteners/blocklisteners.dart index 43ef74d7d..37a6c94e4 100644 --- a/lib/splash/bloclisteners/blocklisteners.dart +++ b/lib/splash/bloclisteners/blocklisteners.dart @@ -164,9 +164,31 @@ 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 != ''; + + /// 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; + // } + + /// verifier side (siopv2) with request_uri + if (state.uri.toString().startsWith('openid://?client_id')) { + isIssuerVerificationSettingTrue = + state.uri!.queryParameters['request_uri'] != null; + } + log.i('checking issuer - $isIssuerVerificationSettingTrue'); + if (isIssuerVerificationSettingTrue) { try { approvedIssuer = await CheckIssuer( @@ -175,6 +197,7 @@ final qrCodeBlocListener = BlocListener( state.uri!, ).isIssuerInApprovedList(); } catch (e) { + log.e(e); if (e is MessageHandler) { await context.read().emitError(e); } else { @@ -191,16 +214,39 @@ 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}'''; + + /// 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(); + // } + + /// 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, - subtitle: (approvedIssuer.did.isEmpty) - ? state.uri!.host - : '''${approvedIssuer.organizationInfo.legalName}\n${approvedIssuer.organizationInfo.currentAddress}''', + title: title, + subtitle: subtitle, yes: l10n.communicationHostAllow, no: l10n.communicationHostDeny, //lock: state.uri!.scheme == 'http', @@ -211,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/lib/splash/cubit/splash_cubit.dart b/lib/splash/cubit/splash_cubit.dart index d8c608329..43dbfd4e9 100644 --- a/lib/splash/cubit/splash_cubit.dart +++ b/lib/splash/cubit/splash_cubit.dart @@ -99,6 +99,16 @@ class SplashCubit extends Cubit { versionNumber: packageInfo.version, buildNumber: packageInfo.buildNumber, isNewVersion: isNewVersion, + status: SplashStatus.idle, + ), + ); + } + + void disableWhatsNewPopUp() { + emit( + state.copyWith( + isNewVersion: false, + status: SplashStatus.idle, ), ); } diff --git a/lib/splash/view/splash_page.dart b/lib/splash/view/splash_page.dart index 5ac654336..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; @@ -67,64 +64,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 secure_storage.getSecureStorage.get( - // SecureStorageKeys.ssiMnemonic, - // ); - 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, - ); - 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 != '') { - 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; diff --git a/lib/theme/app_theme/app_theme.dart b/lib/theme/app_theme/app_theme.dart index 90f762e86..639e16c7a 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, @@ -136,6 +136,7 @@ abstract class AppTheme { } extension CustomColorScheme on ColorScheme { + Color get redColor => const Color(0xFFFF0045); Color get transactionApplied => applied; Color get transactionFailed => failed; Color get transactionSkipped => skipped; @@ -143,6 +144,10 @@ extension CustomColorScheme on ColorScheme { Color get greyText => onTertiary; + Color get kycKeyIconColor => const Color(0xFF86809D); + + Color get popupBackground => onPrimary; + Color get cardHighlighted => cardHighlight; Color get defaultDialogDark => defaultDialog; @@ -236,6 +241,8 @@ extension CustomColorScheme on ColorScheme { Color get qrScanInnerShadow => const Color(0xff0A0215); + Color get qrScanOuterShadow => const Color(0xff430F91); + Color get dialogText => const Color(0xFF180B2B); Color get tabBarNotSelected => const Color(0xFF280164); @@ -279,18 +286,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, @@ -326,38 +333,44 @@ extension CustomTextTheme on TextTheme { fontWeight: FontWeight.w600, ); - TextStyle get onBoardingTitleStyle => GoogleFonts.roboto( + TextStyle get badgeStyle => GoogleFonts.nunito( + color: const Color(0xFFEEEEEE), + fontSize: 8, + fontWeight: FontWeight.w500, + ); + + 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, @@ -369,7 +382,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, @@ -381,19 +394,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, @@ -405,31 +418,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, @@ -469,7 +482,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, @@ -487,7 +500,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, @@ -499,123 +512,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, @@ -627,67 +641,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), @@ -708,25 +722,31 @@ extension CustomTextTheme on TextTheme { TextStyle get defaultDialogTitle => GoogleFonts.poppins( fontSize: 25, fontWeight: FontWeight.bold, - color: const Color(0xff180B2B), + color: const Color(0xffF5F5F5), ); TextStyle get defaultDialogBody => GoogleFonts.poppins( - fontSize: 15, - fontWeight: FontWeight.w600, - color: const Color(0xFF5F556F), + fontSize: 14, + fontWeight: FontWeight.w400, + color: const Color(0xFF86809D), ); TextStyle get defaultDialogSubtitle => GoogleFonts.poppins( fontSize: 20, fontWeight: FontWeight.bold, - color: const Color(0xff180B2B), + color: const Color(0xff86809D), + ); + + TextStyle get newVersionTitle => GoogleFonts.poppins( + fontSize: 18, + fontWeight: FontWeight.w800, + color: background, ); - TextStyle get kycDialogTitle => GoogleFonts.nunito( + TextStyle get kycDialogTitle => GoogleFonts.poppins( fontSize: 25, fontWeight: FontWeight.bold, - color: const Color(0xff180B2B), + color: const Color(0xffF5F5F5), ); TextStyle get kycDialogBodySmall => GoogleFonts.poppins( @@ -738,52 +758,52 @@ extension CustomTextTheme on TextTheme { TextStyle get kycDialogBody => GoogleFonts.poppins( fontSize: 14, fontWeight: FontWeight.w700, - color: const Color(0xFF180B2B), + color: const Color(0xff86809D), ); TextStyle get kycDialogFooter => GoogleFonts.poppins( fontSize: 10, fontWeight: FontWeight.w500, - color: const Color(0xFF180B2B), + 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, @@ -795,19 +815,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, @@ -837,12 +857,18 @@ extension CustomTextTheme on TextTheme { color: const Color(0xff71CBFF), ); - TextStyle get identitiyBaseLightText => GoogleFonts.roboto( + TextStyle get pheaseVerifySubmessage => GoogleFonts.nunito( + fontSize: 18, + fontWeight: FontWeight.w400, + color: const Color(0xff86809D), + ); + + 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, @@ -861,13 +887,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, @@ -885,115 +911,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, + fontWeight: FontWeight.normal, ); - 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/lib/wallet/cubit/wallet_cubit.dart b/lib/wallet/cubit/wallet_cubit.dart index 35ff80a2f..076e721de 100644 --- a/lib/wallet/cubit/wallet_cubit.dart +++ b/lib/wallet/cubit/wallet_cubit.dart @@ -533,6 +533,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) { @@ -651,64 +658,24 @@ 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, - ); + 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); + } + } } - ///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); - - //save dapps - await connectedDappRepository.deleteAll(); + // await credentialsRepository.deleteAll(); + // await profileCubit.resetProfile(); + // await connectedDappRepository.deleteAll(); /// clear app states homeCubit.emitHasNoWallet(); 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/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 a5e2d39ea..2472847df 100644 --- a/packages/ebsi/lib/src/ebsi.dart +++ b/packages/ebsi/lib/src/ebsi.dart @@ -4,18 +4,18 @@ 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'; +import 'package:ebsi/src/verification_type.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 +import 'package:uuid/uuid.dart'; /// {@template ebsi} /// EBSI wallet compliance @@ -65,10 +65,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, @@ -202,12 +202,15 @@ 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, + openidConfigurationResponse, ); final credentialEndpoint = @@ -263,15 +266,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 holderKid, + Response> didDocumentResponse, + ) { + final jsonPath = JsonPath(r'$..verificationMethod'); + final data = jsonPath.read(didDocumentResponse.data).first.value + ..where( + (dynamic e) => e['id'].toString() == holderKid, + ).toList(); + + final value = data.first['publicKeyJwk']; + + return jsonDecode(jsonEncode(value)) as Map; + } + Future> getPrivateKey( String? mnemonic, String? privateKey, @@ -284,33 +323,116 @@ class Ebsi { } else { private = jsonDecode(privateKey!) as Map; } - return private; } Future> buildCredentialData( Map response, - String? mnemonic, - String? privateKey, - String issuer, + IssuerTokenParameters issuerTokenParameters, Uri credentialRequestUri, + Response> openidConfigurationResponse, ) async { final nonce = response['c_nonce'] as String; - final private = await getPrivateKey(mnemonic, privateKey); + final vcJwt = await getIssuerJwt(issuerTokenParameters, nonce); + + //final issuerDid = readIssuerDid(openidConfigurationResponse); + + // final isVerified = await verifyEncodedData( + // issuerDid: issuerDid, + // jwt: vcJwt, + // holderKid: issuerTokenParameters.kid, + // ); + + // if (isVerified == VerificationType.notVerified) { + // 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; } + Future verifyEncodedData({ + required String issuerDid, + required String holderKid, + required String jwt, + }) async { + try { + final didDocument = await getDidDocument(issuerDid); + + final publicKeyJwk = readPublicKeyJwk(holderKid, didDocument); + + 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; + + final publicKeyBytes = base64Url.decode(xString); + + final publicKey = cryptography.SimplePublicKey( + publicKeyBytes, + type: cryptography.KeyPairType.ed25519, + ); + + 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; + } else { + return VerificationType.notVerified; + } + } catch (e) { + return VerificationType.unKnown; + } + } + + 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, ) { @@ -375,31 +497,41 @@ 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']!); + + final private = await getPrivateKey(mnemonic, privateKey); - // structures - final verifierIdToken = await getIdToken(tokenParameters); + final tokenParameters = VerifierTokenParameters( + private, + uri, + credentialsToBePresented, + ); - /// build vp token + // structures + final verifierIdToken = await getIdToken(tokenParameters); - final vpToken = await getVpToken(tokenParameters); + /// build vp token - final responseHeaders = { - 'Content-Type': 'application/x-www-form-urlencoded', - }; + final vpToken = await getVpToken(tokenParameters); - final responseData = { - 'id_token': verifierIdToken, - 'vp_token': vpToken - }; - try { - final presentationResponse = await client.post( + final responseHeaders = { + 'Content-Type': 'application/x-www-form-urlencoded', + }; + + final responseData = { + 'id_token': verifierIdToken, + 'vp_token': vpToken + }; + + await client.post( uri.queryParameters['redirect_uri']!, options: Options(headers: responseHeaders), data: responseData, @@ -427,15 +559,18 @@ class Ebsi { 'id': 'http://example.org/presentations/talao/01', 'type': ['VerifiablePresentation'], 'holder': tokenParameters.didKey, - 'verifiableCredential': [jsonEncode(tokenParameters.jwtsOfCredentials)] + 'verifiableCredential': tokenParameters.jsonIdOrJwtList }, 'nonce': tokenParameters.nonce }; + + print(vpTokenPayload); + final verifierVpJwt = generateToken(vpTokenPayload, tokenParameters); + return verifierVpJwt; } - @visibleForTesting String generateToken( Map vpTokenPayload, TokenParameters tokenParameters, @@ -443,6 +578,13 @@ 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); + final vpBuilder = JsonWebSignatureBuilder() // set the content ..jsonContent = vpVerifierClaims.toJson() @@ -452,10 +594,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(); @@ -468,6 +608,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, @@ -478,11 +621,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'$', } @@ -503,4 +646,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 e032b36ac..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); @@ -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/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, +} diff --git a/packages/ebsi/lib/src/verifier_token_parameters.dart b/packages/ebsi/lib/src/verifier_token_parameters.dart index 19f33ef25..085921b96 100644 --- a/packages/ebsi/lib/src/verifier_token_parameters.dart +++ b/packages/ebsi/lib/src/verifier_token_parameters.dart @@ -1,77 +1,38 @@ import 'dart:convert'; import 'package:ebsi/src/token_parameters.dart'; -import 'package:jose/jose.dart'; /// Extends [TokenParameters] to handle additional parameters /// 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(); - - /// [audience] is is from first jwt claims - String get audience { - final jwt = JsonWebToken.unverified(jwtsOfCredentials[0]); - final claims = jwt.claims; - return claims['iss'] as String; + /// [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']); + } else { + list.add(credentialJson['data']); + } + } + return list; } - // @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'''; - - // return kid; - // } - - // @visibleForTesting - // @override - // String get alg { - // return 'ES256K'; - // } - - // @visibleForTesting - // @override - // List get thumbprint { - // return [1, 2, 3]; - // } + /// [audience] is is client id of the request + String get audience => uri.queryParameters['client_id'] ?? ''; } diff --git a/packages/ebsi/pubspec.yaml b/packages/ebsi/pubspec.yaml index b55baedcc..0784d5fd7 100644 --- a/packages/ebsi/pubspec.yaml +++ b/packages/ebsi/pubspec.yaml @@ -11,26 +11,28 @@ 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.6.4 + 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 + 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.4 + pinenacl: ^0.3.3 # tezart from git depends on pinenacl ^0.3.3 secp256k1: ^0.3.0 tezart: git: url: https://github.com/autonomy-system/tezart.git ref: bd4b8db6e3a352590a6e556d31df8aef60db3465 + uuid: ^3.0.7 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/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/ebsi_test.dart b/packages/ebsi/test/src/ebsi_test.dart index 1dc170268..8155e293a 100644 --- a/packages/ebsi/test/src/ebsi_test.dart +++ b/packages/ebsi/test/src/ebsi_test.dart @@ -16,20 +16,11 @@ 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() { - // 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 +32,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); @@ -85,7 +81,7 @@ void main() { ]; const expectedJwk = { - 'crv': 'P-256K', + 'crv': 'secp256k1', 'd': 'ccWWNSjGiv1iWlNh4kfhWvwG3yyQMe8o31Du0uKRzrs', 'kty': 'EC', 'x': 'J4vQtLUyrVUiFIXRrtEq4xurmBZp2eq9wJmXkIA_stI', @@ -96,6 +92,25 @@ void main() { expect(jsonDecode(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': 'secp256k1', + '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', () { final jwk = ebsi.jwkFromSeed(seedBytes: Uint8List.fromList(seedBytes)); expect(jwk, expectedJwk); @@ -117,33 +132,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); @@ -153,7 +204,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', @@ -201,19 +252,22 @@ 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}'; + '{"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, @@ -233,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, @@ -245,22 +299,394 @@ 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"}'; - final tokenData = ebsi.buildTokenData( - credentialRequest, + 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', + ), + ), ); - 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('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 + 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); + }); + }); + + group('getIssuer', () { + final ebsi = Ebsi(client); + 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', // ignore: lines_longer_than_80_chars + () { + 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"}'; + + final ebsi = Ebsi(client); + + final issuer = ebsi.readTokenEndPoint( + Response( + requestOptions: RequestOptions(path: ''), + data: jsonDecode(openidConfigurationResponse) as Map, + ), + ); + 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 ebsi = Ebsi(client); + + final issuer = ebsi.readIssuerDid( + Response( + requestOptions: RequestOptions(path: ''), + data: jsonDecode(openidConfigurationResponse) as Map, + ), + ); + expect(issuer, 'did:ebsi:zhSw5rPXkcHjvquwnVcTzzB'); }); - test('get readTokenEndPoint with openidConfigurationResponse', () async {}); + 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"}'; // ignore: lines_longer_than_80_chars + + final ebsi = Ebsi(client); + + final publicKey = ebsi.readPublicKeyJwk( + issuerDid, + Response( + requestOptions: RequestOptions(path: ''), + data: jsonDecode(didDocumentResponse) as Map, + ), + ); + expect(jsonEncode(publicKey), expectedPublicKey); + }); + }); + + 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'; + + 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"}]}'; + + 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, + (request) => request.reply(200, jsonDecode(didDocumentResponse)), + ) + ..onGet( + didDocumentUrl2, + (request) => request.reply(200, jsonDecode(didDocumentResponse2)), + ) + ..onGet( + didDocumentUrl3, + (request) => request.reply(200, jsonDecode(didDocumentResponse3)), + ); + + 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.verifyEncodedData( + issuerDid: issuerDid2, + jwt: vcJwt, + holderKid: holderKid, + ); + + expect(isVerified, VerificationType.verified); + }); + + test('returns VerificationType.notVerified', () async { + const vcJwt = + 'eyJ0eXAiOiJKV1QiLCJhbGciOiJFUzI1NksiLCJqd2siOnsiY3J2IjoiUC0yNTZLIiwia3R5IjoiRUMiLCJ4IjoiSjR2UXRMVXlyVlVpRklYUnJ0RXE0eHVybUJacDJlcTl3Sm1Ya0lBX3N0SSIsInkiOiJFVVU2dlhvRzNCR1gyenp3alhyR0RjcjRFeUREMFZmazNfNWZnNWtTZ0tFIn0sImtpZCI6ImRpZDplYnNpOnpvOUZSMVlmQUtGUDNRNmR2cWh4Y1h4bmZlRGlKRFA5N2ttbnFoeUFVU0FDaiNDZ2NnMXk5eGo5dVdGdzU2UE1jMjlYQmQ5RVJlaXh6dm5mdEJ6OEp3UUZpQiJ9.eyJpc3MiOiJkaWQ6ZWJzaTp6bzlGUjFZZkFLRlAzUTZkdnFoeGNYeG5mZURpSkRQOTdrbW5xaHlBVVNBQ2oiLCJub25jZSI6IjdhMDdkZTBmLWE4NzktMTFlZC04MjJiLTBhMTYyODk1ODU2MCIsImlhdCI6MTY3NzA1MDc0MDEyMzIzNSwiYXVkIjoiaHR0cHM6Ly90YWxhby5jby9zYW5kYm94L2Vic2kvaXNzdWVyL3ZndmdoeWxvemwifQ.htjRCpFWbRwanAyQcAq9XZ4vxCXyFbzaaN3yPbPxWIcKFFzDDcA4QCHTUl-L4vzWq0R3LSgQFXQ9bo5D9uCm4w'; // ignore: lines_longer_than_80_chars + + final isVerified = await ebsi.verifyEncodedData( + issuerDid: issuerDid1, + 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.verifyEncodedData( + issuerDid: issuerDid2, + jwt: vcJwt, + holderKid: holderKid, + ); + expect(isVerified, VerificationType.unKnown); + }); + }); + + 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}'; // ignore: lines_longer_than_80_chars + + 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); + }); + + 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"}'; // ignore: lines_longer_than_80_chars + 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('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', () { @@ -281,8 +707,30 @@ 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')); }); + + // 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); + + // }); } 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/issuer_token_parameters_test.dart b/packages/ebsi/test/src/issuer_token_parameters/issuer_token_parameters_test.dart new file mode 100644 index 000000000..5de3e5960 --- /dev/null +++ b/packages/ebsi/test/src/issuer_token_parameters/issuer_token_parameters_test.dart @@ -0,0 +1,47 @@ +import 'package:flutter_test/flutter_test.dart'; + +import 'issuer_token_parameters_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, + ); + + test( + 'if alg is not null then return as it is', + issuerTokenParameterTest.algorithmIsNotNullTest, + ); + }); + + 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/issuer_token_parameters_test.dart b/packages/ebsi/test/src/issuer_token_parameters_test.dart deleted file mode 100644 index 30d21ebb4..000000000 --- a/packages/ebsi/test/src/issuer_token_parameters_test.dart +++ /dev/null @@ -1,8 +0,0 @@ -// 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. - -void main() {} 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/token_parameters_test.dart b/packages/ebsi/test/src/token_parameters/token_parameters_test.dart new file mode 100644 index 000000000..59003c46a --- /dev/null +++ b/packages/ebsi/test/src/token_parameters/token_parameters_test.dart @@ -0,0 +1,129 @@ +// ignore: unnecessary_lambdas + +// 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:dio/dio.dart'; +import 'package:ebsi/ebsi.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:mocktail/mocktail.dart'; + +import 'token_parameters_class.dart'; + +class MockDio extends Mock implements Dio {} + +void main() { + group('TokenParameters', () { + final tokenParametersTest = TokenParameterTest(); + + test( + 'public key is P-256K private key without d parameter', + tokenParametersTest.publicKeyTest, + ); + + test('did EBSI', tokenParametersTest.didTest); + + test('kID EBSI', tokenParametersTest.keyIdTest); + + 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, + ); + + test( + 'if alg is not null then return as it is', + tokenParametersTest.algorithmIsNotNullTest, + ); + }); + + group('thumbprint test', () { + test('thumbprint of the public Key', tokenParametersTest.thumprintOfKey); + + test( + 'thumbrprint of the Key from exemple in rfc 7638', + tokenParametersTest.thumprintOfKeyForrfc7638, + ); + }); + + group('more didKey test', () { + test('did EBSI from Thierry s key', () { + final thierryPrivate1 = { + 'crv': 'secp256k1', + 'kty': 'EC', + 'x': 'XMO-urq7MgxkcpQDAJQY84lIO7sTo1Ab-_cqvUyreno', + 'y': 'sRnukSTqSCXUShUyDitq6MvTLr5F1ETs6xnR455WW_g' + }; + + const expectedDid = + 'did:ebsi:zkEcb5YVNX5ZRq3nZVH3FVWePLE6vxbqDattYsGan6iLi'; + final tokenParameters = TokenParameters(thierryPrivate1); + expect(tokenParameters.didKey, expectedDid); + }); + + test('did EBSI 1 from Thierry vectors', () { + final thierryPrivate = { + 'crv': 'secp256k1', + 'd': '5DCgRx7Snk-ltE3exxHy94L6LPf8gBSb5_-U8NgRH10', + 'kty': 'EC', + 'x': 'XMO-urq7MgxkcpQDAJQY84lIO7sTo1Ab-_cqvUyreno', + 'y': 'sRnukSTqSCXUShUyDitq6MvTLr5F1ETs6xnR455WW_g' + }; + const expectedDid = + 'did:ebsi:zkEcb5YVNX5ZRq3nZVH3FVWePLE6vxbqDattYsGan6iLi'; + final tokenParameters = TokenParameters(thierryPrivate); + expect(tokenParameters.didKey, expectedDid); + }); + test('did EBSI 2 from Thierry vectors', () { + final thierryPrivate = { + 'crv': 'secp256k1', + 'd': '2CYcyeeIGExqssjp0W3jzHdoEzSWHGBr0ukO66r0h2g', + 'kty': 'EC', + 'x': 'sg-ra2GWe8qoBIsBL2ZF7HjV71PP02nWuJLnTL2bn7E', + 'y': 'h8F81NtkFSHgyY_KCEjXhDfQEU_Jv0AEHMvR9EW65xs' + }; + const expectedDid = + 'did:ebsi:zcpweJw1cLnMM4yaWHRGjSKpuyGNq7SSTuQrBLp5tCpEz'; + final tokenParameters = TokenParameters(thierryPrivate); + expect(tokenParameters.didKey, expectedDid); + }); + test('did EBSI 3 from Thierry vectors', () { + final thierryPrivate = { + 'crv': 'secp256k1', + 'd': 'zlZrsHYH8aeaJWu4ptTjNnDhgBGyFc0UguJ8N4zbsdA', + 'kty': 'EC', + 'x': 'hdEzHBKnhigtNoU7nIaxQJWIVTqVcuEZdpOKUzkAfkA', + 'y': 'Gq1zCf9H0_Wyo5nNyjR8IA-XgTkX1PYaBYb2WKYF3PQ' + }; + const expectedDid = + 'did:ebsi:zgfaTZiwnaK7k4Zf9ce9ydcYQKh76QJcpf2MFDeKYSj1c'; + final tokenParameters = TokenParameters(thierryPrivate); + expect(tokenParameters.didKey, expectedDid); + }); + + test('did EBSI from Alice s key', () { + final aliceKey = { + 'crv': 'P-256', + 'd': 'd_PpSCGQWWgUc1t4iLLH8bKYlYfc9Zy_M7TsfOAcbg8', + 'kty': 'EC', + 'x': 'ngy44T1vxAT6Di4nr-UaM9K3Tlnz9pkoksDokKFkmNc', + 'y': 'QCRfOKlSM31GTkb4JHx3nXB4G_jSPMsbdjzlkT_UpPc', + }; + + const expectedDid = + 'did:ebsi:znxntxQrN369GsNyjFjYb8fuvU7g3sJGyYGwMTcUGdzuy'; + final tokenParameters = TokenParameters(aliceKey); + expect(tokenParameters.didKey, expectedDid); + }); + }); + }); +} diff --git a/packages/ebsi/test/src/token_parameters_test.dart b/packages/ebsi/test/src/token_parameters_test.dart deleted file mode 100644 index 6f7ed9ac4..000000000 --- a/packages/ebsi/test/src/token_parameters_test.dart +++ /dev/null @@ -1,231 +0,0 @@ -// 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:dio/dio.dart'; -import 'package:ebsi/ebsi.dart'; -import 'package:flutter_test/flutter_test.dart'; -import 'package:mocktail/mocktail.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); - }); - - test('did EBSI', () { - expect(tokenParameters.didKey, didKey); - }); - - test('kid EBSI', () { - expect(tokenParameters.kid, kid); - }); - - group('algorithm test', () { - test("algorithm is ES256K when key's curve is not P-256", () { - expect(tokenParameters.alg, ES256KAlg); - }); - - 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('thumbprint test', () { - test('thumbprint of the public Key', () { - expect(tokenParameters.thumbprint, thumbprint); - }); - - 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); - }); - }); - - group('more didKey test', () { - test('did EBSI from Thierry s key', () { - final thierryPrivate1 = { - 'crv': 'secp256k1', - 'kty': 'EC', - 'x': 'XMO-urq7MgxkcpQDAJQY84lIO7sTo1Ab-_cqvUyreno', - 'y': 'sRnukSTqSCXUShUyDitq6MvTLr5F1ETs6xnR455WW_g' - }; - - const expectedDid = - 'did:ebsi:zkEcb5YVNX5ZRq3nZVH3FVWePLE6vxbqDattYsGan6iLi'; - final tokenParameters = TokenParameters(thierryPrivate1); - expect(tokenParameters.didKey, expectedDid); - }); - - test('did EBSI 1 from Thierry vectors', () { - final thierryPrivate = { - 'crv': 'secp256k1', - 'd': '5DCgRx7Snk-ltE3exxHy94L6LPf8gBSb5_-U8NgRH10', - 'kty': 'EC', - 'x': 'XMO-urq7MgxkcpQDAJQY84lIO7sTo1Ab-_cqvUyreno', - 'y': 'sRnukSTqSCXUShUyDitq6MvTLr5F1ETs6xnR455WW_g' - }; - const expectedDid = - 'did:ebsi:zkEcb5YVNX5ZRq3nZVH3FVWePLE6vxbqDattYsGan6iLi'; - final tokenParameters = TokenParameters(thierryPrivate); - expect(tokenParameters.didKey, expectedDid); - }); - test('did EBSI 2 from Thierry vectors', () { - final thierryPrivate = { - 'crv': 'secp256k1', - 'd': '2CYcyeeIGExqssjp0W3jzHdoEzSWHGBr0ukO66r0h2g', - 'kty': 'EC', - 'x': 'sg-ra2GWe8qoBIsBL2ZF7HjV71PP02nWuJLnTL2bn7E', - 'y': 'h8F81NtkFSHgyY_KCEjXhDfQEU_Jv0AEHMvR9EW65xs' - }; - const expectedDid = - 'did:ebsi:zcpweJw1cLnMM4yaWHRGjSKpuyGNq7SSTuQrBLp5tCpEz'; - final tokenParameters = TokenParameters(thierryPrivate); - expect(tokenParameters.didKey, expectedDid); - }); - test('did EBSI 3 from Thierry vectors', () { - final thierryPrivate = { - 'crv': 'secp256k1', - 'd': 'zlZrsHYH8aeaJWu4ptTjNnDhgBGyFc0UguJ8N4zbsdA', - 'kty': 'EC', - 'x': 'hdEzHBKnhigtNoU7nIaxQJWIVTqVcuEZdpOKUzkAfkA', - 'y': 'Gq1zCf9H0_Wyo5nNyjR8IA-XgTkX1PYaBYb2WKYF3PQ' - }; - const expectedDid = - 'did:ebsi:zgfaTZiwnaK7k4Zf9ce9ydcYQKh76QJcpf2MFDeKYSj1c'; - final tokenParameters = TokenParameters(thierryPrivate); - expect(tokenParameters.didKey, expectedDid); - }); - - test('did EBSI from Alice s key', () { - final aliceKey = { - 'crv': 'P-256', - 'd': 'd_PpSCGQWWgUc1t4iLLH8bKYlYfc9Zy_M7TsfOAcbg8', - 'kty': 'EC', - 'x': 'ngy44T1vxAT6Di4nr-UaM9K3Tlnz9pkoksDokKFkmNc', - 'y': 'QCRfOKlSM31GTkb4JHx3nXB4G_jSPMsbdjzlkT_UpPc', - }; - - const expectedDid = - 'did:ebsi:znxntxQrN369GsNyjFjYb8fuvU7g3sJGyYGwMTcUGdzuy'; - final tokenParameters = TokenParameters(aliceKey); - expect(tokenParameters.didKey, expectedDid); - }); - }); - }); -} 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 59% 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 9d9a3f3e3..e0e6f3a7e 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,6 +8,8 @@ import 'package:ebsi/ebsi.dart'; import 'package:flutter_test/flutter_test.dart'; +import 'verifier_token_parameters_class.dart'; + void main() { group('Verifier TokenParameters', () { const url = @@ -24,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 = @@ -32,11 +35,97 @@ 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 - '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.', // ignore: lines_longer_than_80_chars + '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', // ignore: lines_longer_than_80_chars + 'nonce': '3ea68dae-d07a-4daa-932b-fbb58f5c20c4', + 'type': 'EcdsaSecp256k1Signature2019' + }, + 'type': [ + 'VerifiableCredential', + 'VerifiableAttestation', + 'VerifiableDiploma' + ], + 'validFrom': '2023-02-08T15:10:56Z' + } ]; test('nonce', () { @@ -44,37 +133,53 @@ void main() { }); test('jwtsOfCredentials', () { - expect(verifierTokenParameters.jwtsOfCredentials, jwtsOfCredentials); + expect(verifierTokenParameters.jsonIdOrJwtList, jwtsOfCredentials); }); test('audience', () { 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'; - // 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, + ); + + test( + 'if alg is not null then return as it is', + verifierTokenParametersTest.algorithmIsNotNullTest, + ); + }); + + 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/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 cd63da429..dd7be72cc 100644 --- a/packages/jwt_decode/pubspec.yaml +++ b/packages/jwt_decode/pubspec.yaml @@ -13,4 +13,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 f02542384..8a7789ccb 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -37,10 +37,10 @@ packages: dependency: transitive description: name: archive - sha256: d6347d54a2d8028e0437e3c099f66fdb8ae02c4720c1e7534c9f24c10351f85d + sha256: ed7cc591a948744994714375caf9a2ce89e1d82e8243997c8a2994d57181c212 url: "https://pub.dev" source: hosted - version: "3.3.6" + version: "3.3.5" args: dependency: transitive description: @@ -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: @@ -126,18 +134,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: @@ -358,18 +366,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: @@ -428,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: @@ -489,15 +497,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: @@ -506,14 +505,30 @@ 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: 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 +654,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: @@ -712,10 +719,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: @@ -821,10 +828,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: @@ -833,6 +840,14 @@ 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_parsed_text: dependency: transitive description: @@ -853,26 +868,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: @@ -893,10 +908,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: @@ -909,10 +924,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 @@ -951,10 +966,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: @@ -1039,10 +1054,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: @@ -1245,10 +1260,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: @@ -1285,10 +1300,10 @@ packages: dependency: "direct main" description: name: mobile_scanner - sha256: d0824e90ec6b202287df5daa6a727d73edd191a20cef6692a50686ff26d66704 + sha256: "531725451c7506f4c57b4720da1fe33a1394cdf9d4a5075a3a2dd51e21113928" url: "https://pub.dev" source: hosted - version: "3.0.0-beta.4" + version: "2.1.0" mockingjay: dependency: "direct dev" description: @@ -1397,10 +1412,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: @@ -1413,10 +1428,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: @@ -1569,6 +1584,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: @@ -1756,10 +1819,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: @@ -2082,10 +2145,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: @@ -2150,6 +2213,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: @@ -2162,12 +2249,12 @@ 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 + dependency: "direct main" description: name: visibility_detector sha256: "15c54a459ec2c17b4705450483f3d5a2858e733aee893dcee9d75fd04814940d" @@ -2321,4 +2408,4 @@ packages: version: "3.1.1" sdks: dart: ">=2.19.0 <3.0.0" - flutter: ">=3.3.7" + flutter: ">=3.7.0-0" diff --git a/pubspec.yaml b/pubspec.yaml index 1389b5cd1..52bcfc799 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,24 +1,25 @@ name: altme description: AltMe Flutter App -version: 1.9.2+143 +version: 1.12.0+153 publish_to: none 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 - 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 @@ -29,42 +30,40 @@ dependencies: 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 + 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_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_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 http: ^0.13.5 - image: ^3.1.3 - image_picker: ^0.8.6+1 - intl: ^0.17.0 + image: ^3.0.2 + 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 json_path: ^0.4.2 @@ -72,67 +71,71 @@ 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.18.0 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: ^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.2 - passbase_flutter: ^2.13.3 - path: ^1.8.2 - path_provider: ^2.0.8 + package_info_plus: ^3.0.3 + 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 - pointycastle: ^3.5.2 + platform_device_id: ^1.0.1 + 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: 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.0.17 - uuid: ^3.0.5 + 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 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 + # 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.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 @@ -154,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 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* ]]; 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, 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