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