From 09db6151e47ee0f4f9a1f1b7488b19fbfee7fdae Mon Sep 17 00:00:00 2001 From: clear-xmr Date: Fri, 1 Apr 2022 10:58:46 +0200 Subject: [PATCH 01/30] build: android: always clone zlib --- scripts/android/build_openssl.sh | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/scripts/android/build_openssl.sh b/scripts/android/build_openssl.sh index 572925056..c5dad3d41 100755 --- a/scripts/android/build_openssl.sh +++ b/scripts/android/build_openssl.sh @@ -11,9 +11,8 @@ ZLIB_DIR=$WORKDIR/zlib ZLIB_TAG=v1.2.11 ZLIB_COMMIT_HASH="cacf7f1d4e3d44d871b605da3b647f07d718623f" -if [ ! -d "$ZLIB_DIR" ] ; then - git clone -b $ZLIB_TAG --depth 1 https://github.com/madler/zlib $ZLIB_DIR -fi +rm -rf $ZLIB_DIR +git clone -b $ZLIB_TAG --depth 1 https://github.com/madler/zlib $ZLIB_DIR cd $ZLIB_DIR git reset --hard $ZLIB_COMMIT_HASH CC=clang CXX=clang++ ./configure --static From a63042591b2c9fa276fcdfe67512011e5bfad591 Mon Sep 17 00:00:00 2001 From: clear-xmr Date: Fri, 1 Apr 2022 11:13:22 +0200 Subject: [PATCH 02/30] build: android: update build deps in doc --- howto-build-android.md | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/howto-build-android.md b/howto-build-android.md index b4f5a453f..28478cdd1 100644 --- a/howto-build-android.md +++ b/howto-build-android.md @@ -19,6 +19,8 @@ These steps will help you configure and execute a build of CakeWallet from its s CakeWallet cannot be built without the following packages installed on your build system. +- curl + - unzip - automake @@ -41,9 +43,11 @@ CakeWallet cannot be built without the following packages installed on your buil - openjdk-8-jre-headless +- clang + You may easily install them on your build system with the following command: -`$ sudo apt-get install -y unzip automake build-essential file pkg-config git python libtool libtinfo5 cmake openjdk-8-jre-headless` +`$ sudo apt-get install -y curl unzip automake build-essential file pkg-config git python libtool libtinfo5 cmake openjdk-8-jre-headless clang` ### 2. Installing Android Studio and Android toolchain From cb2cc903e0126b14a4ede5236da8055bb3366b94 Mon Sep 17 00:00:00 2001 From: clear-xmr Date: Fri, 1 Apr 2022 11:27:33 +0200 Subject: [PATCH 03/30] build: android: don't exit terminal if no app name specified --- scripts/android/app_env.sh | 1 + 1 file changed, 1 insertion(+) diff --git a/scripts/android/app_env.sh b/scripts/android/app_env.sh index e2733b590..7034a7ba9 100755 --- a/scripts/android/app_env.sh +++ b/scripts/android/app_env.sh @@ -33,6 +33,7 @@ HAVEN_PACKAGE="com.cakewallet.haven" if ! [[ " ${TYPES[*]} " =~ " ${APP_ANDROID_TYPE} " ]]; then echo "Wrong app type." + return 1 2>/dev/null exit 1 fi From 076a3b40759230001e1bb63ee39ee8213be07ddd Mon Sep 17 00:00:00 2001 From: M Date: Thu, 7 Apr 2022 11:51:06 +0100 Subject: [PATCH 04/30] Delete haven.dart --- lib/haven/haven.dart | 144 ------------------------------------------- tool/configure.dart | 1 + 2 files changed, 1 insertion(+), 144 deletions(-) delete mode 100644 lib/haven/haven.dart diff --git a/lib/haven/haven.dart b/lib/haven/haven.dart deleted file mode 100644 index 2c9ecae8e..000000000 --- a/lib/haven/haven.dart +++ /dev/null @@ -1,144 +0,0 @@ -import 'package:mobx/mobx.dart'; -import 'package:flutter/foundation.dart'; -import 'package:cw_core/wallet_credentials.dart'; -import 'package:cw_core/wallet_info.dart'; -import 'package:cw_core/transaction_priority.dart'; -import 'package:cw_core/transaction_history.dart'; -import 'package:cw_core/transaction_info.dart'; -import 'package:cw_core/balance.dart'; -import 'package:cw_core/output_info.dart'; -import 'package:cake_wallet/view_model/send/output.dart'; -import 'package:cw_core/wallet_service.dart'; -import 'package:hive/hive.dart'; -import 'package:cw_core/get_height_by_date.dart'; -import 'package:cw_core/monero_amount_format.dart'; -import 'package:cw_core/monero_transaction_priority.dart'; -import 'package:cw_haven/haven_wallet_service.dart'; -import 'package:cw_haven/haven_wallet.dart'; -import 'package:cw_haven/haven_transaction_info.dart'; -import 'package:cw_haven/haven_transaction_history.dart'; -import 'package:cw_core/account.dart' as monero_account; -import 'package:cw_haven/api/wallet.dart' as monero_wallet_api; -import 'package:cw_haven/mnemonics/english.dart'; -import 'package:cw_haven/mnemonics/chinese_simplified.dart'; -import 'package:cw_haven/mnemonics/dutch.dart'; -import 'package:cw_haven/mnemonics/german.dart'; -import 'package:cw_haven/mnemonics/japanese.dart'; -import 'package:cw_haven/mnemonics/russian.dart'; -import 'package:cw_haven/mnemonics/spanish.dart'; -import 'package:cw_haven/mnemonics/portuguese.dart'; -import 'package:cw_haven/mnemonics/french.dart'; -import 'package:cw_haven/mnemonics/italian.dart'; -import 'package:cw_haven/haven_transaction_creation_credentials.dart'; - -part 'cw_haven.dart'; - -Haven haven = CWHaven(); - -class Account { - Account({this.id, this.label}); - final int id; - final String label; -} - -class Subaddress { - Subaddress({this.id, this.accountId, this.label, this.address}); - final int id; - final int accountId; - final String label; - final String address; -} - -class HavenBalance extends Balance { - HavenBalance({@required this.fullBalance, @required this.unlockedBalance}) - : formattedFullBalance = haven.formatterMoneroAmountToString(amount: fullBalance), - formattedUnlockedBalance = - haven.formatterMoneroAmountToString(amount: unlockedBalance), - super(unlockedBalance, fullBalance); - - HavenBalance.fromString( - {@required this.formattedFullBalance, - @required this.formattedUnlockedBalance}) - : fullBalance = haven.formatterMoneroParseAmount(amount: formattedFullBalance), - unlockedBalance = haven.formatterMoneroParseAmount(amount: formattedUnlockedBalance), - super(haven.formatterMoneroParseAmount(amount: formattedUnlockedBalance), - haven.formatterMoneroParseAmount(amount: formattedFullBalance)); - - final int fullBalance; - final int unlockedBalance; - final String formattedFullBalance; - final String formattedUnlockedBalance; - - @override - String get formattedAvailableBalance => formattedUnlockedBalance; - - @override - String get formattedAdditionalBalance => formattedFullBalance; -} - -abstract class HavenWalletDetails { - @observable - Account account; - - @observable - HavenBalance balance; -} - -abstract class Haven { - HavenAccountList getAccountList(Object wallet); - - MoneroSubaddressList getSubaddressList(Object wallet); - - TransactionHistoryBase getTransactionHistory(Object wallet); - - HavenWalletDetails getMoneroWalletDetails(Object wallet); - - String getTransactionAddress(Object wallet, int accountIndex, int addressIndex); - - int getHeigthByDate({DateTime date}); - TransactionPriority getDefaultTransactionPriority(); - TransactionPriority deserializeMoneroTransactionPriority({int raw}); - List getTransactionPriorities(); - List getMoneroWordList(String language); - - WalletCredentials createHavenRestoreWalletFromKeysCredentials({ - String name, - String spendKey, - String viewKey, - String address, - String password, - String language, - int height}); - WalletCredentials createHavenRestoreWalletFromSeedCredentials({String name, String password, int height, String mnemonic}); - WalletCredentials createHavenNewWalletCredentials({String name, String password, String language}); - Map getKeys(Object wallet); - Object createHavenTransactionCreationCredentials({List outputs, TransactionPriority priority, String assetType}); - String formatterMoneroAmountToString({int amount}); - double formatterMoneroAmountToDouble({int amount}); - int formatterMoneroParseAmount({String amount}); - Account getCurrentAccount(Object wallet); - void setCurrentAccount(Object wallet, int id, String label); - void onStartup(); - int getTransactionInfoAccountId(TransactionInfo tx); - WalletService createHavenWalletService(Box walletInfoSource); -} - -abstract class MoneroSubaddressList { - ObservableList get subaddresses; - void update(Object wallet, {int accountIndex}); - void refresh(Object wallet, {int accountIndex}); - List getAll(Object wallet); - Future addSubaddress(Object wallet, {int accountIndex, String label}); - Future setLabelSubaddress(Object wallet, - {int accountIndex, int addressIndex, String label}); -} - -abstract class HavenAccountList { - ObservableList get accounts; - void update(Object wallet); - void refresh(Object wallet); - List getAll(Object wallet); - Future addAccount(Object wallet, {String label}); - Future setLabelAccount(Object wallet, {int accountIndex, String label}); -} - \ No newline at end of file diff --git a/tool/configure.dart b/tool/configure.dart index 843d665b3..dfce79dc9 100644 --- a/tool/configure.dart +++ b/tool/configure.dart @@ -15,6 +15,7 @@ Future main(List args) async { final hasHaven = args.contains('${prefix}haven'); await generateBitcoin(hasBitcoin); await generateMonero(hasMonero); + await generateHaven(hasHaven); await generatePubspec(hasMonero: hasMonero, hasBitcoin: hasBitcoin, hasHaven: hasHaven); await generateWalletTypes(hasMonero: hasMonero, hasBitcoin: hasBitcoin, hasHaven: hasHaven); } From a4feab8e3ddd542a68439b03c92c8308321fa8e4 Mon Sep 17 00:00:00 2001 From: M Date: Thu, 7 Apr 2022 11:51:43 +0100 Subject: [PATCH 05/30] Add haven.dart into .gitignore --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index d929b12dc..7e3f38beb 100644 --- a/.gitignore +++ b/.gitignore @@ -119,6 +119,7 @@ cw_haven/android/.cxx/ lib/bitcoin/bitcoin.dart lib/monero/monero.dart +lib/haven/haven.dart ios/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_180.png ios/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_120.png From 3e43ef9a0ec261d48a7c932bc17b61cf63155163 Mon Sep 17 00:00:00 2001 From: M Date: Thu, 7 Apr 2022 12:19:10 +0100 Subject: [PATCH 06/30] Fixes for haven dependencies --- lib/entities/update_haven_rate.dart | 12 +++++------ lib/haven/cw_haven.dart | 11 ++++++++++ .../dashboard/transaction_list_item.dart | 6 +----- tool/configure.dart | 20 +++++++++++++++---- 4 files changed, 34 insertions(+), 15 deletions(-) diff --git a/lib/entities/update_haven_rate.dart b/lib/entities/update_haven_rate.dart index 1bb1a88e1..c1fc591b2 100644 --- a/lib/entities/update_haven_rate.dart +++ b/lib/entities/update_haven_rate.dart @@ -1,15 +1,15 @@ import 'package:cake_wallet/store/dashboard/fiat_conversion_store.dart'; import 'package:cw_core/crypto_currency.dart'; import 'package:cw_core/monero_amount_format.dart'; -import 'package:cw_haven/api/balance_list.dart'; +import 'package:cake_wallet/haven/haven.dart'; Future updateHavenRate(FiatConversionStore fiatConversionStore) async { - final rate = getRate(); - final base = rate.firstWhere((row) => row.getAssetType() == 'XUSD', orElse: () => null); + final rate = haven.getAssetRate(); + final base = rate.firstWhere((row) => row.asset == 'XUSD', orElse: () => null); rate.forEach((row) { - final cur = CryptoCurrency.fromString(row.getAssetType()); - final baseRate = moneroAmountToDouble(amount: base.getRate()); - final rowRate = moneroAmountToDouble(amount: row.getRate()); + final cur = CryptoCurrency.fromString(row.asset); + final baseRate = moneroAmountToDouble(amount: base.rate); + final rowRate = moneroAmountToDouble(amount: row.rate); if (cur == CryptoCurrency.xusd) { fiatConversionStore.prices[cur] = 1.0; diff --git a/lib/haven/cw_haven.dart b/lib/haven/cw_haven.dart index 6261e1fc9..d253ec929 100644 --- a/lib/haven/cw_haven.dart +++ b/lib/haven/cw_haven.dart @@ -295,4 +295,15 @@ class CWHaven extends Haven { final havenWallet = wallet as HavenWallet; return havenWallet.getTransactionAddress(accountIndex, addressIndex); } + + CryptoCurrency assetOfTransaction(TransactionInfo tx) { + final tx = transaction as HavenTransactionInfo; + final asset = CryptoCurrency.fromString(tx.assetType); + return asset; + } + + List getAssetRate() + => getRate() + .map((HavenRate rate) => AssetRate(rate.getAssetType(), rate.getRate())) + .toList(); } diff --git a/lib/view_model/dashboard/transaction_list_item.dart b/lib/view_model/dashboard/transaction_list_item.dart index a7467ffb0..99c306419 100644 --- a/lib/view_model/dashboard/transaction_list_item.dart +++ b/lib/view_model/dashboard/transaction_list_item.dart @@ -1,9 +1,7 @@ import 'package:cake_wallet/entities/balance_display_mode.dart'; import 'package:cake_wallet/entities/fiat_currency.dart'; -import 'package:cw_core/crypto_currency.dart'; import 'package:cw_core/transaction_info.dart'; import 'package:cake_wallet/store/settings_store.dart'; -import 'package:cake_wallet/utils/mobx.dart'; import 'package:cake_wallet/view_model/dashboard/action_list_item.dart'; import 'package:cake_wallet/monero/monero.dart'; import 'package:cake_wallet/haven/haven.dart'; @@ -12,7 +10,6 @@ import 'package:cake_wallet/entities/calculate_fiat_amount_raw.dart'; import 'package:cake_wallet/view_model/dashboard/balance_view_model.dart'; import 'package:cw_core/keyable.dart'; import 'package:cw_core/wallet_type.dart'; -import 'package:cw_haven/haven_transaction_info.dart'; class TransactionListItem extends ActionListItem with Keyable { TransactionListItem( @@ -53,8 +50,7 @@ class TransactionListItem extends ActionListItem with Keyable { price: price); break; case WalletType.haven: - final tx = transaction as HavenTransactionInfo; - final asset = CryptoCurrency.fromString(tx.assetType); + final asset = haven.assetOfTransaction(transaction); final price = balanceViewModel.fiatConvertationStore.prices[asset]; amount = calculateFiatAmountRaw( cryptoAmount: haven.formatterMoneroAmountToDouble(amount: transaction.amount), diff --git a/tool/configure.dart b/tool/configure.dart index dfce79dc9..f74d9c9d3 100644 --- a/tool/configure.dart +++ b/tool/configure.dart @@ -275,7 +275,8 @@ abstract class MoneroAccountList { } Future generateHaven(bool hasImplementation) async { - final outputFile = File(moneroOutputPath); + + final outputFile = File(havenOutputPath); const havenCommonHeaders = """ import 'package:mobx/mobx.dart'; import 'package:flutter/foundation.dart'; @@ -288,7 +289,8 @@ import 'package:cw_core/balance.dart'; import 'package:cw_core/output_info.dart'; import 'package:cake_wallet/view_model/send/output.dart'; import 'package:cw_core/wallet_service.dart'; -import 'package:hive/hive.dart';"""; +import 'package:hive/hive.dart'; +import 'package:cw_core/crypto_currency.dart';"""; const havenCWHeaders = """ import 'package:cw_core/get_height_by_date.dart'; import 'package:cw_core/monero_amount_format.dart'; @@ -310,6 +312,7 @@ import 'package:cw_haven/mnemonics/portuguese.dart'; import 'package:cw_haven/mnemonics/french.dart'; import 'package:cw_haven/mnemonics/italian.dart'; import 'package:cw_haven/haven_transaction_creation_credentials.dart'; +import 'package:cw_haven/api/balance_list.dart'; """; const havenCwPart = "part 'cw_haven.dart';"; const havenContent = """ @@ -354,6 +357,13 @@ class HavenBalance extends Balance { String get formattedAdditionalBalance => formattedFullBalance; } +class AssetRate { + final String asset; + final int rate; + + AssetRate(this.asset, this.rate); +} + abstract class HavenWalletDetails { @observable Account account; @@ -399,6 +409,8 @@ abstract class Haven { void onStartup(); int getTransactionInfoAccountId(TransactionInfo tx); WalletService createHavenWalletService(Box walletInfoSource); + CryptoCurrency assetOfTransaction(TransactionInfo tx); + List getAssetRate(); } abstract class MoneroSubaddressList { @@ -421,8 +433,8 @@ abstract class HavenAccountList { } """; - const havenEmptyDefinition = 'Monero monero;\n'; - const havenCWDefinition = 'Monero monero = CWMonero();\n'; + const havenEmptyDefinition = 'Haven haven;\n'; + const havenCWDefinition = 'Haven haven = CWHaven();\n'; final output = '$havenCommonHeaders\n' + (hasImplementation ? '$havenCWHeaders\n' : '\n') From 905e982bd2edf58adf60c053ecd43334fec58805 Mon Sep 17 00:00:00 2001 From: M Date: Thu, 7 Apr 2022 12:32:04 +0100 Subject: [PATCH 07/30] Fix for getAssetRate --- lib/haven/cw_haven.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/haven/cw_haven.dart b/lib/haven/cw_haven.dart index d253ec929..8e9325bb9 100644 --- a/lib/haven/cw_haven.dart +++ b/lib/haven/cw_haven.dart @@ -304,6 +304,6 @@ class CWHaven extends Haven { List getAssetRate() => getRate() - .map((HavenRate rate) => AssetRate(rate.getAssetType(), rate.getRate())) + .map((rate) => AssetRate(rate.getAssetType(), rate.getRate())) .toList(); } From 04db28e2765364fc991302089f05f46954446cae Mon Sep 17 00:00:00 2001 From: M Date: Thu, 7 Apr 2022 12:35:41 +0100 Subject: [PATCH 08/30] Change version for monero.com app --- scripts/android/app_env.sh | 4 ++-- scripts/ios/app_env.sh | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/scripts/android/app_env.sh b/scripts/android/app_env.sh index 387efcc46..168b400b6 100755 --- a/scripts/android/app_env.sh +++ b/scripts/android/app_env.sh @@ -14,8 +14,8 @@ TYPES=($MONERO_COM $CAKEWALLET $HAVEN) APP_ANDROID_TYPE=$1 MONERO_COM_NAME="Monero.com" -MONERO_COM_VERSION="1.0.4" -MONERO_COM_BUILD_NUMBER=11 +MONERO_COM_VERSION="1.0.5" +MONERO_COM_BUILD_NUMBER=12 MONERO_COM_BUNDLE_ID="com.monero.app" MONERO_COM_PACKAGE="com.monero.app" diff --git a/scripts/ios/app_env.sh b/scripts/ios/app_env.sh index fda8aa8df..6cda6a082 100755 --- a/scripts/ios/app_env.sh +++ b/scripts/ios/app_env.sh @@ -13,8 +13,8 @@ TYPES=($MONERO_COM $CAKEWALLET $HAVEN) APP_IOS_TYPE=$1 MONERO_COM_NAME="Monero.com" -MONERO_COM_VERSION="1.0.4" -MONERO_COM_BUILD_NUMBER=14 +MONERO_COM_VERSION="1.0.5" +MONERO_COM_BUILD_NUMBER=15 MONERO_COM_BUNDLE_ID="com.cakewallet.monero" CAKEWALLET_NAME="Cake Wallet" From 135ede6a78f8d293ce97ad0ac7591ce7349375b1 Mon Sep 17 00:00:00 2001 From: clear <100709132+clear-xmr@users.noreply.github.com> Date: Tue, 12 Apr 2022 17:38:47 +0200 Subject: [PATCH 09/30] Cw-58: Show subaddress label on the transaction details page (#333) * CW-58: Show subaddress label on the transaction details page * add translations * Fix interface --- cw_monero/ios/Classes/monero_api.cpp | 5 +++++ cw_monero/lib/api/signatures.dart | 4 ++++ cw_monero/lib/api/types.dart | 6 +++++- cw_monero/lib/api/wallet.dart | 8 ++++++++ cw_monero/lib/monero_wallet.dart | 4 ++++ lib/monero/cw_monero.dart | 5 +++++ lib/view_model/transaction_details_view_model.dart | 9 +++++++++ res/values/strings_de.arb | 1 + res/values/strings_en.arb | 1 + res/values/strings_es.arb | 3 ++- res/values/strings_fr.arb | 1 + res/values/strings_hi.arb | 3 ++- res/values/strings_hr.arb | 3 ++- res/values/strings_it.arb | 3 ++- res/values/strings_ja.arb | 3 ++- res/values/strings_ko.arb | 3 ++- res/values/strings_nl.arb | 3 ++- res/values/strings_pl.arb | 3 ++- res/values/strings_pt.arb | 3 ++- res/values/strings_ru.arb | 3 ++- res/values/strings_uk.arb | 3 ++- res/values/strings_zh.arb | 3 ++- tool/configure.dart | 2 ++ 23 files changed, 69 insertions(+), 13 deletions(-) diff --git a/cw_monero/ios/Classes/monero_api.cpp b/cw_monero/ios/Classes/monero_api.cpp index 6dbc09765..f81f63d16 100644 --- a/cw_monero/ios/Classes/monero_api.cpp +++ b/cw_monero/ios/Classes/monero_api.cpp @@ -766,6 +766,11 @@ extern "C" return strdup(m_wallet->getTxKey(std::string(txId)).c_str()); } + char *get_subaddress_label(uint32_t accountIndex, uint32_t addressIndex) + { + return strdup(get_current_wallet()->getSubaddressLabel(accountIndex, addressIndex).c_str()); + } + #ifdef __cplusplus } #endif diff --git a/cw_monero/lib/api/signatures.dart b/cw_monero/lib/api/signatures.dart index e97003dc0..9781aff2e 100644 --- a/cw_monero/lib/api/signatures.dart +++ b/cw_monero/lib/api/signatures.dart @@ -120,3 +120,7 @@ typedef close_current_wallet = Void Function(); typedef on_startup = Void Function(); typedef rescan_blockchain = Void Function(); + +typedef get_subaddress_label = Pointer Function( + Int32 accountIndex, + Int32 addressIndex); \ No newline at end of file diff --git a/cw_monero/lib/api/types.dart b/cw_monero/lib/api/types.dart index d65f2d0d7..4caa1283f 100644 --- a/cw_monero/lib/api/types.dart +++ b/cw_monero/lib/api/types.dart @@ -117,4 +117,8 @@ typedef CloseCurrentWallet = void Function(); typedef OnStartup = void Function(); -typedef RescanBlockchainAsync = void Function(); \ No newline at end of file +typedef RescanBlockchainAsync = void Function(); + +typedef GetSubaddressLabel = Pointer Function( + int accountIndex, + int addressIndex); \ No newline at end of file diff --git a/cw_monero/lib/api/wallet.dart b/cw_monero/lib/api/wallet.dart index 97245990e..72507e912 100644 --- a/cw_monero/lib/api/wallet.dart +++ b/cw_monero/lib/api/wallet.dart @@ -112,6 +112,10 @@ final rescanBlockchainAsyncNative = moneroApi .lookup>('rescan_blockchain') .asFunction(); +final getSubaddressLabelNative = moneroApi + .lookup>('get_subaddress_label') + .asFunction(); + int getSyncingHeight() => getSyncingHeightNative(); bool isNeededToRefresh() => isNeededToRefreshNative() != 0; @@ -327,3 +331,7 @@ Future isConnected() => compute(_isConnected, 0); Future getNodeHeight() => compute(_getNodeHeight, 0); void rescanBlockchainAsync() => rescanBlockchainAsyncNative(); + +String getSubaddressLabel(int accountIndex, int addressIndex) { + return convertUTF8ToString(pointer: getSubaddressLabelNative(accountIndex, addressIndex)); +} \ No newline at end of file diff --git a/cw_monero/lib/monero_wallet.dart b/cw_monero/lib/monero_wallet.dart index 1d675cd8c..6ac31c9f0 100644 --- a/cw_monero/lib/monero_wallet.dart +++ b/cw_monero/lib/monero_wallet.dart @@ -314,6 +314,10 @@ abstract class MoneroWalletBase extends WalletBase _getAllTransactions(dynamic _) => monero_transaction_history .getAllTransations() diff --git a/lib/monero/cw_monero.dart b/lib/monero/cw_monero.dart index 878fdda99..98ba26446 100644 --- a/lib/monero/cw_monero.dart +++ b/lib/monero/cw_monero.dart @@ -294,4 +294,9 @@ class CWMonero extends Monero { final moneroWallet = wallet as MoneroWallet; return moneroWallet.getTransactionAddress(accountIndex, addressIndex); } + + String getSubaddressLabel(Object wallet, int accountIndex, int addressIndex) { + final moneroWallet = wallet as MoneroWallet; + return moneroWallet.getSubaddressLabel(accountIndex, addressIndex); + } } diff --git a/lib/view_model/transaction_details_view_model.dart b/lib/view_model/transaction_details_view_model.dart index 5c9b18caa..41b4e0c60 100644 --- a/lib/view_model/transaction_details_view_model.dart +++ b/lib/view_model/transaction_details_view_model.dart @@ -60,6 +60,7 @@ abstract class TransactionDetailsViewModelBase with Store { addressIndex != null) { try { final address = monero.getTransactionAddress(wallet, accountIndex, addressIndex); + final label = monero.getSubaddressLabel(wallet, accountIndex, addressIndex); if (address?.isNotEmpty ?? false) { isRecipientAddressShown = true; @@ -68,6 +69,14 @@ abstract class TransactionDetailsViewModelBase with Store { title: S.current.transaction_details_recipient_address, value: address)); } + + if (label?.isNotEmpty ?? false) { + _items.add( + StandartListItem( + title: S.current.address_label, + value: label) + ); + } } catch (e) { print(e.toString()); } diff --git a/res/values/strings_de.arb b/res/values/strings_de.arb index b29cbf053..306cc9e8b 100644 --- a/res/values/strings_de.arb +++ b/res/values/strings_de.arb @@ -268,6 +268,7 @@ "new_subaddress_label_name" : "Bezeichnung", "new_subaddress_create" : "Erstellen", + "address_label" : "Address label", "subaddress_title" : "Unteradressenliste", diff --git a/res/values/strings_en.arb b/res/values/strings_en.arb index 2de02e8b8..42f3e05f2 100644 --- a/res/values/strings_en.arb +++ b/res/values/strings_en.arb @@ -268,6 +268,7 @@ "new_subaddress_label_name" : "Label name", "new_subaddress_create" : "Create", + "address_label" : "Address label", "subaddress_title" : "Subaddress list", diff --git a/res/values/strings_es.arb b/res/values/strings_es.arb index a3cbb755a..8e00ddcdb 100644 --- a/res/values/strings_es.arb +++ b/res/values/strings_es.arb @@ -268,6 +268,7 @@ "new_subaddress_label_name" : "Nombre de etiqueta", "new_subaddress_create" : "Crear", + "address_label" : "Address label", "subaddress_title" : "Lista de subdirecciones", @@ -528,4 +529,4 @@ "search": "Búsqueda", "new_template" : "Nueva plantilla", "electrum_address_disclaimer": "Generamos nuevas direcciones cada vez que usa una, pero las direcciones anteriores siguen funcionando" -} \ No newline at end of file +} diff --git a/res/values/strings_fr.arb b/res/values/strings_fr.arb index 7e373a3af..c173e526f 100644 --- a/res/values/strings_fr.arb +++ b/res/values/strings_fr.arb @@ -266,6 +266,7 @@ "new_subaddress_label_name" : "Nom", "new_subaddress_create" : "Créer", + "address_label" : "Address label", "subaddress_title" : "Liste des sous-adresses", diff --git a/res/values/strings_hi.arb b/res/values/strings_hi.arb index 6baae968c..bad0ae879 100644 --- a/res/values/strings_hi.arb +++ b/res/values/strings_hi.arb @@ -268,6 +268,7 @@ "new_subaddress_label_name" : "लेबल का नाम", "new_subaddress_create" : "सर्जन करना", + "address_label" : "Address label", "subaddress_title" : "उपखंड सूची", @@ -528,4 +529,4 @@ "search": "खोज", "new_template" : "नया टेम्पलेट", "electrum_address_disclaimer": "हर बार जब आप एक का उपयोग करते हैं तो हम नए पते उत्पन्न करते हैं, लेकिन पिछले पते काम करना जारी रखते हैं" -} \ No newline at end of file +} diff --git a/res/values/strings_hr.arb b/res/values/strings_hr.arb index 5650fd30e..03e7708bb 100644 --- a/res/values/strings_hr.arb +++ b/res/values/strings_hr.arb @@ -268,6 +268,7 @@ "new_subaddress_label_name" : "Oznaka", "new_subaddress_create" : "Izradi", + "address_label" : "Address label", "subaddress_title" : "Lista podadresa", @@ -528,4 +529,4 @@ "search": "Traži", "new_template" : "novi predložak", "electrum_address_disclaimer": "Minden egyes alkalommal új címeket generálunk, de a korábbi címek továbbra is működnek" -} \ No newline at end of file +} diff --git a/res/values/strings_it.arb b/res/values/strings_it.arb index 715698d31..e92f4da5f 100644 --- a/res/values/strings_it.arb +++ b/res/values/strings_it.arb @@ -268,6 +268,7 @@ "new_subaddress_label_name" : "Nome etichetta", "new_subaddress_create" : "Crea", + "address_label" : "Address label", "subaddress_title" : "Lista sottoindirizzi", @@ -528,4 +529,4 @@ "search": "Ricerca", "new_template" : "Nuovo modello", "electrum_address_disclaimer": "Generiamo nuovi indirizzi ogni volta che ne utilizzi uno, ma gli indirizzi precedenti continuano a funzionare" -} \ No newline at end of file +} diff --git a/res/values/strings_ja.arb b/res/values/strings_ja.arb index 410fe3a89..734c769df 100644 --- a/res/values/strings_ja.arb +++ b/res/values/strings_ja.arb @@ -268,6 +268,7 @@ "new_subaddress_label_name" : "ラベル名", "new_subaddress_create" : "作成する", + "address_label" : "Address label", "subaddress_title" : "サブアドレス一覧", @@ -528,4 +529,4 @@ "search": "検索", "new_template" : "新しいテンプレート", "electrum_address_disclaimer": "使用するたびに新しいアドレスが生成されますが、以前のアドレスは引き続き機能します" -} \ No newline at end of file +} diff --git a/res/values/strings_ko.arb b/res/values/strings_ko.arb index bec483e44..cd6712288 100644 --- a/res/values/strings_ko.arb +++ b/res/values/strings_ko.arb @@ -268,6 +268,7 @@ "new_subaddress_label_name" : "라벨 이름", "new_subaddress_create" : "몹시 떠들어 대다", + "address_label" : "Address label", "subaddress_title" : "하위 주소 목록", @@ -528,4 +529,4 @@ "search": "찾다", "new_template" : "새 템플릿", "electrum_address_disclaimer": "사용할 때마다 새 주소가 생성되지만 이전 주소는 계속 작동합니다." -} \ No newline at end of file +} diff --git a/res/values/strings_nl.arb b/res/values/strings_nl.arb index 8987df4ec..365858b8b 100644 --- a/res/values/strings_nl.arb +++ b/res/values/strings_nl.arb @@ -268,6 +268,7 @@ "new_subaddress_label_name" : "Label naam", "new_subaddress_create" : "Creëren", + "address_label" : "Address label", "subaddress_title" : "Subadreslijst", @@ -528,4 +529,4 @@ "search": "Zoekopdracht", "new_template" : "Nieuwe sjabloon", "electrum_address_disclaimer": "We generate new addresses each time you use one, but previous addresses continue to work" -} \ No newline at end of file +} diff --git a/res/values/strings_pl.arb b/res/values/strings_pl.arb index 7249c88b0..97101bfd7 100644 --- a/res/values/strings_pl.arb +++ b/res/values/strings_pl.arb @@ -271,6 +271,7 @@ "new_subaddress_label_name" : "Nazwa etykiety", "new_subaddress_create" : "Stwórz", + "address_label" : "Address label", "subaddress_title" : "Lista podadresów", @@ -531,4 +532,4 @@ "search": "Szukaj", "new_template" : "Nowy szablon", "electrum_address_disclaimer": "Za każdym razem, gdy korzystasz z jednego z nich, generujemy nowe adresy, ale poprzednie adresy nadal działają" -} \ No newline at end of file +} diff --git a/res/values/strings_pt.arb b/res/values/strings_pt.arb index 15db684fd..db7369e56 100644 --- a/res/values/strings_pt.arb +++ b/res/values/strings_pt.arb @@ -268,6 +268,7 @@ "new_subaddress_label_name" : "Nome", "new_subaddress_create" : "Criar", + "address_label" : "Address label", "subaddress_title" : "Sub-endereços", @@ -528,4 +529,4 @@ "search": "Procurar", "new_template" : "Novo modelo", "electrum_address_disclaimer": "Geramos novos endereços cada vez que você usa um, mas os endereços anteriores continuam funcionando" -} \ No newline at end of file +} diff --git a/res/values/strings_ru.arb b/res/values/strings_ru.arb index 2719e82ee..2d685b801 100644 --- a/res/values/strings_ru.arb +++ b/res/values/strings_ru.arb @@ -268,6 +268,7 @@ "new_subaddress_label_name" : "Имя", "new_subaddress_create" : "Создать", + "address_label" : "Address label", "subaddress_title" : "Список субадресов", @@ -528,4 +529,4 @@ "search": "Поиск", "new_template" : "Новый шаблон", "electrum_address_disclaimer": "Мы генерируем новые адреса каждый раз, когда вы их используете, но предыдущие адреса продолжают работать." -} \ No newline at end of file +} diff --git a/res/values/strings_uk.arb b/res/values/strings_uk.arb index 383f812f0..9bf2d85c7 100644 --- a/res/values/strings_uk.arb +++ b/res/values/strings_uk.arb @@ -267,6 +267,7 @@ "new_subaddress_label_name" : "Ім'я", "new_subaddress_create" : "Створити", + "address_label" : "Address label", "subaddress_title" : "Список Субадрес", @@ -527,4 +528,4 @@ "search": "Пошук", "new_template" : "Новий шаблон", "electrum_address_disclaimer": "Ми створюємо нові адреси щоразу, коли ви використовуєте їх, але попередні адреси продовжують працювати" -} \ No newline at end of file +} diff --git a/res/values/strings_zh.arb b/res/values/strings_zh.arb index 787d7bd58..c55b7147d 100644 --- a/res/values/strings_zh.arb +++ b/res/values/strings_zh.arb @@ -268,6 +268,7 @@ "new_subaddress_label_name" : "标签名称", "new_subaddress_create" : "创建", + "address_label" : "Address label", "subaddress_title" : "子地址列表", @@ -526,4 +527,4 @@ "search": "搜索", "new_template" : "新模板", "electrum_address_disclaimer": "每次您使用一个地址时,我们都会生成新地址,但之前的地址仍然有效" -} \ No newline at end of file +} diff --git a/tool/configure.dart b/tool/configure.dart index f74d9c9d3..dcbac59c5 100644 --- a/tool/configure.dart +++ b/tool/configure.dart @@ -209,6 +209,8 @@ abstract class Monero { String getTransactionAddress(Object wallet, int accountIndex, int addressIndex); + String getSubaddressLabel(Object wallet, int accountIndex, int addressIndex); + int getHeigthByDate({DateTime date}); TransactionPriority getDefaultTransactionPriority(); TransactionPriority deserializeMoneroTransactionPriority({int raw}); From eab47fcb96d51f9e920b54133322800b569ea2f8 Mon Sep 17 00:00:00 2001 From: Mathias Herberts Date: Tue, 12 Apr 2022 17:39:51 +0200 Subject: [PATCH 10/30] Minor fixes post 4.4.0 release (#324) * Minor fixes post 4.4.0 release * Further minor changes --- res/values/strings_fr.arb | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/res/values/strings_fr.arb b/res/values/strings_fr.arb index c173e526f..f210290d2 100644 --- a/res/values/strings_fr.arb +++ b/res/values/strings_fr.arb @@ -52,7 +52,7 @@ "received" : "Reçus", "sent" : "Envoyés", "pending" : " (en attente)", - "rescan" : "Rescan", + "rescan" : "Analyser la blockchain", "reconnect" : "Reconnecter", "wallets" : "Wallets", "show_seed" : "Visualiser le seed", @@ -62,7 +62,7 @@ "reconnect_alert_text" : "Êtes vous certain de vouloir vous reconnecter ?", - "exchange" : "Échange", + "exchange" : "Échanger", "clear" : "Effacer", "refund_address" : "Adresse de Remboursement", "change_exchange_provider" : "Changer de Plateforme d'Échange", @@ -103,7 +103,7 @@ "expired" : "Expirée", "time" : "${minutes}m ${seconds}s", "send_xmr" : "Envoyer XMR", - "exchange_new_template" : "Nouveau modèle", + "exchange_new_template" : "Nouveau modèle d'échange", "faq" : "FAQ", @@ -140,8 +140,8 @@ "new_node_testing" : "Test du nouveau nœud", - "use" : "Changer ver ", - "digit_pin" : "-digit PIN", + "use" : "Changer vers code PIN à ", + "digit_pin" : " chiffres", "share_address" : "Partager l'adresse", @@ -211,7 +211,7 @@ "send_estimated_fee" : "Estimation des frais :", "send_priority" : "Actuellement les frais sont positionnés à la priorité ${transactionPriority}.\nLa priorité de la transaction peut être modifiée dans les réglages", "send_creating_transaction" : "Création de la transaction", - "send_templates" : "Modèles", + "send_templates" : "Modèles d'envois", "send_new" : "Nouveau", "send_amount" : "Montant :", "send_fee" : "Frais :", @@ -225,7 +225,7 @@ "settings_nodes" : "Nœuds", "settings_current_node" : "Nœud actuel", "settings_wallets" : "Wallets", - "settings_display_balance_as" : "Afficher le solde en", + "settings_display_balance_as" : "Affichage du solde", "settings_currency" : "Devise", "settings_fee_priority" : "Priorité des frais", "settings_save_recipient_address" : "Sauvegarder l'adresse du bénéficiaire", @@ -475,7 +475,7 @@ "exchange_incorrect_current_wallet_for_xmr" : "Si vous souhaitez échanger des XMR du solde Monero de votre Cake Wallet, merci de sélectionner votre wallet Monero au préalable.", "confirmed" : "Confirmé", "unconfirmed" : "Non confirmé", - "displayable" : "Affichable", + "displayable" : "Visible", "submit_request" : "soumettre une requête", From ecdf1b3b3e1b864b15d882cc4e343742bf301f34 Mon Sep 17 00:00:00 2001 From: Paul Verbeke Date: Tue, 12 Apr 2022 17:42:57 +0200 Subject: [PATCH 11/30] i18n: fix french translations and add new ones (#330) Co-authored-by: mkyq <53115730+mkyq@users.noreply.github.com> --- res/values/strings_fr.arb | 84 +++++++++++++++++++-------------------- 1 file changed, 42 insertions(+), 42 deletions(-) diff --git a/res/values/strings_fr.arb b/res/values/strings_fr.arb index f210290d2..224ee7112 100644 --- a/res/values/strings_fr.arb +++ b/res/values/strings_fr.arb @@ -1,13 +1,13 @@ { "welcome" : "Bienvenue sur", "cake_wallet" : "Cake Wallet", - "first_wallet_text" : "Super wallet pour Monero, Bitcoin et Litecoin", - "please_make_selection" : "Merci de faire un choix ci-dessous pour créer ou restaurer votre wallet.", - "create_new" : "Créer un Nouveau Wallet", - "restore_wallet" : "Restaurer un Wallet", + "first_wallet_text" : "Super portefeuille pour Monero, Bitcoin et Litecoin", + "please_make_selection" : "Merci de faire un choix ci-dessous pour créer ou restaurer votre portefeuille.", + "create_new" : "Créer un Nouveau Portefeuille", + "restore_wallet" : "Restaurer un Portefeuille", "monero_com": "Monero.com par Cake Wallet", - "monero_com_wallet_text": "Super wallet pour Monero", + "monero_com_wallet_text": "Super portefeuille pour Monero", "accounts" : "Comptes", @@ -22,7 +22,7 @@ "cancel" : "Annuler", "ok" : "OK", "contact_name" : "Nom de Contact", - "reset" : "Remise à zéro", + "reset" : "Réinitialiser", "save" : "Sauvegarder", "address_remove_contact" : "Supprimer contact", "address_remove_content" : "Êtes vous certain de vouloir supprimer le contact sélectionné ?", @@ -54,7 +54,7 @@ "pending" : " (en attente)", "rescan" : "Analyser la blockchain", "reconnect" : "Reconnecter", - "wallets" : "Wallets", + "wallets" : "Portefeuilles", "show_seed" : "Visualiser le seed", "show_keys" : "Visualiser seed/clefs", "address_book_menu" : "Carnet d'Adresses", @@ -76,8 +76,8 @@ "min_value" : "Min: ${value} ${currency}", "max_value" : "Max: ${value} ${currency}", "change_currency" : "Changer de Devise", - "overwrite_amount" : "Overwrite amount", - "qr_payment_amount" : "This QR code contains a payment amount. Do you want to overwrite the current value?", + "overwrite_amount" : "Écraser montant", + "qr_payment_amount" : "Ce QR code contient un montant de paiement. Voulez-vous écraser la valeur actuelle?", "copy_id" : "Copier l'ID", "exchange_result_write_down_trade_id" : "Merci de copier ou d'écrire l'ID d'échange pour continuer.", @@ -92,7 +92,7 @@ "offer_expires_in" : "L'Offre expire dans : ", "trade_is_powered_by" : "Cet échange est proposé par ${provider}", "copy_address" : "Copier l'Adresse", - "exchange_result_confirm" : "En pressant confirmer, vous enverrez ${fetchingLabel} ${from} depuis votre wallet nommé ${walletName} vers l'adresse ci-dessous. Vous pouvez aussi envoyer depuis votre wallet externe vers l'adresse/QR code ci-dessous.\n\nMerci d'appuyer sur confirmer pour continuer ou retournez en arrière pour modifier les montants.", + "exchange_result_confirm" : "En pressant confirmer, vous enverrez ${fetchingLabel} ${from} depuis votre portefeuille nommé ${walletName} vers l'adresse ci-dessous. Vous pouvez aussi envoyer depuis votre portefeuille externe vers l'adresse/QR code ci-dessous.\n\nMerci d'appuyer sur confirmer pour continuer ou retournez en arrière pour modifier les montants.", "exchange_result_description" : "Vous devez envoyer un minimum de ${fetchingLabel} ${from} à l'adresse indiquée page suivante. Si vous envoyez un montant inférieur à ${fetchingLabel} ${from} il pourrait ne pas être converti et ne pas être remboursé.", "exchange_result_write_down_ID" : "*Merci de copier ou écrire votre ID présenté ci-dessus.", "confirm" : "Confirmer", @@ -109,13 +109,13 @@ "enter_your_pin" : "Entrez votre code PIN", - "loading_your_wallet" : "Chargement de votre wallet", + "loading_your_wallet" : "Chargement de votre portefeuille", - "new_wallet" : "Nouveau Wallet", - "wallet_name" : "Nom du Wallet", + "new_wallet" : "Nouveau Portefeuille", + "wallet_name" : "Nom du Portefeuille", "continue_text" : "Continuer", - "choose_wallet_currency" : "Merci de choisir la devise du wallet :", + "choose_wallet_currency" : "Merci de choisir la devise du portefeuille :", "node_new" : "Nouveau Nœud", @@ -155,9 +155,9 @@ "accounts_subaddresses" : "Comptes et sous-adresses", - "restore_restore_wallet" : "Restaurer le Wallet", + "restore_restore_wallet" : "Restaurer le Portefeuille", "restore_title_from_seed_keys" : "Restaurer depuis un seed ou des clefs", - "restore_description_from_seed_keys" : "Restaurez votre wallet depuis un seed ou des clefs que vous avez stockés en lieu sûr", + "restore_description_from_seed_keys" : "Restaurez votre portefeuille depuis un seed ou des clefs que vous avez stockés en lieu sûr", "restore_next" : "Suivant", "restore_title_from_backup" : "Restaurer depuis une sauvegarde", "restore_description_from_backup" : "Vous pouvez restaurer l'intégralité de l'application Cake Wallet depuis un fichier de sauvegarde", @@ -205,7 +205,7 @@ "send_your_wallet" : "Votre wallet", "send_address" : "adresse ${cryptoCurrency}", "send_payment_id" : "ID de paiement (optionnel)", - "all" : "TOUS", + "all" : "TOUT", "send_error_minimum_value" : "La valeur minimale du montant est 0.01", "send_error_currency" : "La monnaie ne peut contenir que des nombres", "send_estimated_fee" : "Estimation des frais :", @@ -224,8 +224,8 @@ "settings_title" : "Réglages", "settings_nodes" : "Nœuds", "settings_current_node" : "Nœud actuel", - "settings_wallets" : "Wallets", - "settings_display_balance_as" : "Affichage du solde", + "settings_wallets" : "Portefeuilles", + "settings_display_balance_as" : "Afficher le solde en", "settings_currency" : "Devise", "settings_fee_priority" : "Priorité des frais", "settings_save_recipient_address" : "Sauvegarder l'adresse du bénéficiaire", @@ -294,14 +294,14 @@ "transaction_details_recipient_address" : "Adresse du bénéficiaire", - "wallet_list_title" : "Monero Wallet", - "wallet_list_create_new_wallet" : "Créer un Nouveau Wallet", - "wallet_list_restore_wallet" : "Restaurer un Wallet", - "wallet_list_load_wallet" : "Charger wallet", - "wallet_list_loading_wallet" : "Chargement du wallet ${wallet_name}", - "wallet_list_failed_to_load" : "Échec de chargement du wallet ${wallet_name}. ${error}", - "wallet_list_removing_wallet" : "Suppression du wallet ${wallet_name}", - "wallet_list_failed_to_remove" : "Échec de la suppression du wallet ${wallet_name}. ${error}", + "wallet_list_title" : "Portefeuille Monero", + "wallet_list_create_new_wallet" : "Créer un Nouveau Portefeuille", + "wallet_list_restore_wallet" : "Restaurer un Portefeuille", + "wallet_list_load_wallet" : "Charger Portefeuille", + "wallet_list_loading_wallet" : "Chargement du portefeuille ${wallet_name}", + "wallet_list_failed_to_load" : "Échec de chargement du portefeuille ${wallet_name}. ${error}", + "wallet_list_removing_wallet" : "Suppression du portefeuille ${wallet_name}", + "wallet_list_failed_to_remove" : "Échec de la suppression du portefeuille ${wallet_name}. ${error}", "widgets_address" : "Adresse", @@ -316,7 +316,7 @@ "error_text_account_name" : "Le nom de compte ne peut contenir que des lettres et des chiffres\net sa longueur doit être comprise entre 1 et 15 caractères", "error_text_contact_name" : "Un nom de contact ne peut pas contenir les symboles ` , ' \"\net doit faire entre 1 et 32 caractères", - "error_text_address" : "L'adresse de wallet doit correspondre au type de\ncryptomonnaie", + "error_text_address" : "L'adresse du portefeuille doit correspondre au type de\ncryptomonnaie", "error_text_node_address" : "Merci d'entrer une adresse IPv4", "error_text_node_port" : "Le port d'un nœud doit être un nombre compris entre 0 et 65535", "error_text_payment_id" : "Un ID de paiement ne peut être constitué que de 16 à 64 caractères hexadécimaux", @@ -324,8 +324,8 @@ "error_text_fiat" : "La valeur du montant ne peut dépasser le solde disponible.\nLa partie décimale doit comporter au plus 2 chiffres", "error_text_subaddress_name" : "Le nom de sous-adresse ne peut contenir les symboles ` , ' \"\net sa longueur doit être comprise entre 1 et 20 caractères", "error_text_amount" : "Le montant ne peut comporter que des nombres", - "error_text_wallet_name" : "Le nom du wallet ne peut contenir que des lettres et des chiffres\net sa longueur doit être comprise entre 1 et 15 caractères", - "error_text_keys" : "Les clefs du wallet ne peuvent être constituées que de 64 caractères hexadécimaux", + "error_text_wallet_name" : "Le nom du portefeuille ne peut contenir que des lettres et des chiffres\net sa longueur doit être comprise entre 1 et 15 caractères", + "error_text_keys" : "Les clefs du portefeuille ne peuvent être constituées que de 64 caractères hexadécimaux", "error_text_crypto_currency" : "La partie décimale\ndoit comporter au plus 12 chiffres", "error_text_minimal_limit" : "Échange pour ${provider} non créé. Le montant est inférieur au minimum : ${min} ${currency}", "error_text_maximum_limit" : "Échange pour ${provider} non créé. Le montant est supérieur au maximum : ${max} ${currency}", @@ -337,7 +337,7 @@ "auth_store_banned_for" : "Banni pour ", "auth_store_banned_minutes" : " minutes", "auth_store_incorrect_password" : "Mauvais code PIN", - "wallet_store_monero_wallet" : "Wallet Monero", + "wallet_store_monero_wallet" : "Portefeuille Monero", "wallet_restoration_store_incorrect_seed_length" : "Longueur de seed incorrecte", @@ -410,21 +410,21 @@ "picker_description" : "Pour choisir ChangeNOW ou MorphToken, merci de modifier d'abord la paire de votre échange", - "change_wallet_alert_title" : "Changer le wallet actuel", - "change_wallet_alert_content" : "Souhaitez-vous changer le wallet actuel vers ${wallet_name}?", + "change_wallet_alert_title" : "Changer le portefeuille actuel", + "change_wallet_alert_content" : "Souhaitez-vous changer le portefeuille actuel vers ${wallet_name}?", - "creating_new_wallet" : "Création d'un nouveau wallet", + "creating_new_wallet" : "Création d'un nouveau portefeuille", "creating_new_wallet_error" : "Erreur : ${description}", "seed_alert_title" : "Attention", - "seed_alert_content" : "Le seed est la seule façon de restaurer votre wallet. L'avez-vous correctement écrit ?", + "seed_alert_content" : "Le seed est la seule façon de restaurer votre portefeuille. L'avez-vous correctement écrit ?", "seed_alert_back" : "Retour", "seed_alert_yes" : "Oui, je suis sûr", - "exchange_sync_alert_content" : "Merci d'attendre que votre wallet soit synchronisé", + "exchange_sync_alert_content" : "Merci d'attendre que votre portefeuille soit synchronisé", "pre_seed_title" : "IMPORTANT", - "pre_seed_description" : "Sur la page suivante vous allez voir une série de ${words} mots. Ils constituent votre seed unique et privé et sont le SEUL moyen de restaurer votre wallet en cas de perte ou de dysfonctionnement. Il est de VOTRE responsabilité d'écrire cette série de mots et de les stocker dans un lieu sûr en dehors de l'application Cake Wallet.", + "pre_seed_description" : "Sur la page suivante vous allez voir une série de ${words} mots. Ils constituent votre seed unique et privé et sont le SEUL moyen de restaurer votre portefeuille en cas de perte ou de dysfonctionnement. Il est de VOTRE responsabilité d'écrire cette série de mots et de les stocker dans un lieu sûr en dehors de l'application Cake Wallet.", "pre_seed_button_text" : "J'ai compris. Montrez moi mon seed", "xmr_to_error" : "Erreur XMR.TO", @@ -472,17 +472,17 @@ "xlm_extra_info" : "Merci de ne pas oublier de spécifier l'ID de mémo lors de l'envoi de la transaction XLM de l'échange", "xrp_extra_info" : "Merci de ne pas oublier de spécifier le tag de destination lors de l'envoi de la transaction XRP de l'échange", - "exchange_incorrect_current_wallet_for_xmr" : "Si vous souhaitez échanger des XMR du solde Monero de votre Cake Wallet, merci de sélectionner votre wallet Monero au préalable.", + "exchange_incorrect_current_wallet_for_xmr" : "Si vous souhaitez échanger des XMR du solde Monero de votre Cake Wallet, merci de sélectionner votre portefeuille Monero au préalable.", "confirmed" : "Confirmé", "unconfirmed" : "Non confirmé", "displayable" : "Visible", "submit_request" : "soumettre une requête", - "buy_alert_content" : "Pour le moment nous ne supportons que l'achat de Bitcoin et Litecoin. Pour acheter du Bitcoin ou du Litecoin, merci de créer ou de sélectionner votre wallet Bitcoin ou Litecoin.", - "sell_alert_content": "Pour le moment nous ne supportons que la vente de Bitcoin. Pour vendre du Bitcoin, merci de créer ou de sélectionner votre wallet Bitcoint.", + "buy_alert_content" : "Pour le moment nous ne supportons que l'achat de Bitcoin et Litecoin. Pour acheter du Bitcoin ou du Litecoin, merci de créer ou de sélectionner votre portefeuille Bitcoin ou Litecoin.", + "sell_alert_content": "Pour le moment nous ne supportons que la vente de Bitcoin. Pour vendre du Bitcoin, merci de créer ou de sélectionner votre portefeuille Bitcoint.", - "outdated_electrum_wallet_description" : "Les nouveaux wallet Bitcoin créés dans Cake ont dorénavant un seed de 24 mots. Il est impératif que vous créiez un nouveau wallet Bitcoin, que vous y transfériez tous vos fonds puis que vous cessiez d'utiliser le wallet avec un seed de 12 mots. Merci de faire cela immédiatement pour assurer la sécurité de vos avoirs.", + "outdated_electrum_wallet_description" : "Les nouveaux portefeuilles Bitcoin créés dans Cake ont dorénavant un seed de 24 mots. Il est impératif que vous créiez un nouveau portefeuille Bitcoin, que vous y transfériez tous vos fonds puis que vous cessiez d'utiliser le portefeuille avec un seed de 12 mots. Merci de faire cela immédiatement pour assurer la sécurité de vos avoirs.", "understand" : "J'ai compris", "apk_update" : "Mise à jour d'APK", @@ -491,7 +491,7 @@ "buy_with" : "Acheter avec", "moonpay_alert_text" : "Le montant doit être au moins égal à ${minAmount} ${fiatCurrency}", - "outdated_electrum_wallet_receive_warning": "Si ce wallet a un seed de 12 mots et a été créé dans Cake, NE PAS y déposer de Bitcoin. Tous les BTC transférés vers ce wallet seront perdus. Créez un nouveau wallet avec seed de 24 mots (appuyez sur le menu en haut à droite, sélectionnez Wallets puis Créer un Nouveau Wallet et enfin Bitcoin) et transférez y IMMÉDIATEMENT vos BTC. Les nouveaux wallets BTC Cake (avec seed de 24 mots) sont sécurisés", + "outdated_electrum_wallet_receive_warning": "Si ce portefeuille a un seed de 12 mots et a été créé dans Cake, NE PAS y déposer de Bitcoin. Tous les BTC transférés vers ce portefeuille seront perdus. Créez un nouveau portefeuille avec seed de 24 mots (appuyez sur le menu en haut à droite, sélectionnez Portefeuilles puis Créer un Nouveau Portefeuille et enfin Bitcoin) et transférez y IMMÉDIATEMENT vos BTC. Les nouveaux portefeuilles BTC Cake (avec seed de 24 mots) sont sécurisés", "do_not_show_me": "Ne plus me montrer ceci à l'avenir", "unspent_coins_title" : "Pièces (coins) non dépensées", From 6378d052ac18ddfa858690277b425c872c8a708c Mon Sep 17 00:00:00 2001 From: Godwin Asuquo <41484542+godilite@users.noreply.github.com> Date: Wed, 13 Apr 2022 14:28:21 +0100 Subject: [PATCH 12/30] Cw 72 implement sideshift exchange (#332) * add sideshift exchange provider * add secret key * Fix issues * Fix issues * refactor code * add permission checks to side shift * fix formatting issues --- assets/images/sideshift.png | Bin 0 -> 4073 bytes .../exchange_provider_description.dart | 5 + .../sideshift_exchange_provider.dart | 265 ++++++++++++++++++ lib/exchange/sideshift/sideshift_request.dart | 17 ++ lib/exchange/trade_state.dart | 3 +- .../screens/dashboard/widgets/trade_row.dart | 3 + .../widgets/present_provider_picker.dart | 3 + .../exchange/exchange_view_model.dart | 16 +- lib/view_model/trade_details_view_model.dart | 10 + tool/utils/secret_key.dart | 2 + 10 files changed, 322 insertions(+), 2 deletions(-) create mode 100644 assets/images/sideshift.png create mode 100644 lib/exchange/sideshift/sideshift_exchange_provider.dart create mode 100644 lib/exchange/sideshift/sideshift_request.dart diff --git a/assets/images/sideshift.png b/assets/images/sideshift.png new file mode 100644 index 0000000000000000000000000000000000000000..9d2c109708a2701ac71a1d6dbc4e986d7cec50ed GIT binary patch literal 4073 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 G@qYmQ$Stq{ literal 0 HcmV?d00001 diff --git a/lib/exchange/exchange_provider_description.dart b/lib/exchange/exchange_provider_description.dart index cae5a5683..ded72a2f9 100644 --- a/lib/exchange/exchange_provider_description.dart +++ b/lib/exchange/exchange_provider_description.dart @@ -11,6 +11,9 @@ class ExchangeProviderDescription extends EnumerableItem static const morphToken = ExchangeProviderDescription(title: 'MorphToken', raw: 2); + static const sideShift = + ExchangeProviderDescription(title: 'SideShift', raw: 3); + static ExchangeProviderDescription deserialize({int raw}) { switch (raw) { case 0: @@ -19,6 +22,8 @@ class ExchangeProviderDescription extends EnumerableItem return changeNow; case 2: return morphToken; + case 3: + return sideShift; default: return null; } diff --git a/lib/exchange/sideshift/sideshift_exchange_provider.dart b/lib/exchange/sideshift/sideshift_exchange_provider.dart new file mode 100644 index 000000000..d4db3dcae --- /dev/null +++ b/lib/exchange/sideshift/sideshift_exchange_provider.dart @@ -0,0 +1,265 @@ +import 'dart:convert'; + +import 'package:cake_wallet/exchange/exchange_pair.dart'; +import 'package:cake_wallet/exchange/exchange_provider.dart'; +import 'package:cake_wallet/exchange/exchange_provider_description.dart'; +import 'package:cake_wallet/exchange/sideshift/sideshift_request.dart'; +import 'package:cake_wallet/exchange/trade_not_created_exeption.dart'; +import 'package:cake_wallet/exchange/trade_not_found_exeption.dart'; +import 'package:cake_wallet/exchange/trade_state.dart'; +import 'package:cake_wallet/.secrets.g.dart' as secrets; +import 'package:cw_core/crypto_currency.dart'; +import 'package:cake_wallet/exchange/trade_request.dart'; +import 'package:cake_wallet/exchange/trade.dart'; +import 'package:cake_wallet/exchange/limits.dart'; +import 'package:flutter/foundation.dart'; +import 'package:http/http.dart'; + +class SideShiftExchangeProvider extends ExchangeProvider { + SideShiftExchangeProvider() + : super( + pairList: CryptoCurrency.all + .map((i) => CryptoCurrency.all + .map((k) => ExchangePair(from: i, to: k, reverse: true)) + .where((c) => c != null)) + .expand((i) => i) + .toList()); + + static const apiKey = secrets.sideShiftApiKey; + static const affiliateId = secrets.sideShiftAffiliateId; + static const apiBaseUrl = 'https://sideshift.ai/api'; + static const rangePath = '/v1/pairs'; + static const orderPath = '/v1/orders'; + static const quotePath = '/v1/quotes'; + static const permissionPath = '/v1/permissions'; + static const apiHeaderKey = 'x-sideshift-secret'; + + @override + ExchangeProviderDescription get description => + ExchangeProviderDescription.sideShift; + + @override + Future calculateAmount( + {CryptoCurrency from, + CryptoCurrency to, + double amount, + bool isFixedRateMode, + bool isReceiveAmount}) async { + try { + if (amount == 0) { + return 0.0; + } + final fromCurrency = normalizeCryptoCurrency(from); + final toCurrency = normalizeCryptoCurrency(to); + final url = + apiBaseUrl + rangePath + '/' + fromCurrency + '/' + toCurrency; + final response = await get(url); + final responseJSON = json.decode(response.body) as Map; + final rate = double.parse(responseJSON['rate'] as String); + final max = double.parse(responseJSON['max'] as String); + + if (amount > max) return 0.00; + + final estimatedAmount = rate * amount; + + return estimatedAmount; + } catch (_) { + return 0.00; + } + } + + @override + Future checkIsAvailable() async { + const url = apiBaseUrl + permissionPath; + final response = await get(url); + + if (response.statusCode == 500) { + final responseJSON = json.decode(response.body) as Map; + final error = responseJSON['error']['message'] as String; + + throw Exception('$error'); + } + + if (response.statusCode != 200) { + return false; + } + + final responseJSON = json.decode(response.body) as Map; + final canCreateOrder = responseJSON['createOrder'] as bool; + final canCreateQuote = responseJSON['createQuote'] as bool; + return canCreateOrder && canCreateQuote; + } + + @override + Future createTrade( + {TradeRequest request, bool isFixedRateMode}) async { + final _request = request as SideShiftRequest; + final quoteId = await _createQuote(_request); + final url = apiBaseUrl + orderPath; + final headers = {apiHeaderKey: apiKey, 'Content-Type': 'application/json'}; + final body = { + 'type': 'fixed', + 'quoteId': quoteId, + 'affiliateId': affiliateId, + 'settleAddress': _request.settleAddress, + 'refundAddress': _request.refundAddress + }; + final response = await post(url, headers: headers, body: json.encode(body)); + + if (response.statusCode != 201) { + if (response.statusCode == 400) { + final responseJSON = json.decode(response.body) as Map; + final error = responseJSON['error']['message'] as String; + + throw TradeNotCreatedException(description, description: error); + } + + throw TradeNotCreatedException(description); + } + + final responseJSON = json.decode(response.body) as Map; + final id = responseJSON['id'] as String; + final inputAddress = responseJSON['depositAddress']['address'] as String; + final settleAddress = responseJSON['settleAddress']['address'] as String; + + return Trade( + id: id, + provider: description, + from: _request.depositMethod, + to: _request.settleMethod, + inputAddress: inputAddress, + refundAddress: settleAddress, + state: TradeState.created, + amount: _request.depositAmount, + createdAt: DateTime.now(), + ); + } + + Future _createQuote(SideShiftRequest request) async { + final url = apiBaseUrl + quotePath; + final headers = {apiHeaderKey: apiKey, 'Content-Type': 'application/json'}; + final depositMethod = normalizeCryptoCurrency(request.depositMethod); + final settleMethod = normalizeCryptoCurrency(request.settleMethod); + final body = { + 'depositMethod': depositMethod, + 'settleMethod': settleMethod, + 'affiliateId': affiliateId, + 'depositAmount': request.depositAmount, + }; + final response = await post(url, headers: headers, body: json.encode(body)); + + if (response.statusCode != 201) { + if (response.statusCode == 400) { + final responseJSON = json.decode(response.body) as Map; + final error = responseJSON['error']['message'] as String; + + throw TradeNotCreatedException(description, description: error); + } + + throw TradeNotCreatedException(description); + } + + final responseJSON = json.decode(response.body) as Map; + final quoteId = responseJSON['id'] as String; + + return quoteId; + } + + @override + Future fetchLimits( + {CryptoCurrency from, CryptoCurrency to, bool isFixedRateMode}) async { + final fromCurrency = normalizeCryptoCurrency(from); + final toCurrency = normalizeCryptoCurrency(to); + final url = apiBaseUrl + rangePath + '/' + fromCurrency + '/' + toCurrency; + final response = await get(url); + + if (response.statusCode == 500) { + final responseJSON = json.decode(response.body) as Map; + final error = responseJSON['error']['message'] as String; + + throw Exception('$error'); + } + + if (response.statusCode != 200) { + return null; + } + + final responseJSON = json.decode(response.body) as Map; + final min = double.parse(responseJSON['min'] as String); + final max = double.parse(responseJSON['max'] as String); + + return Limits(min: min, max: max); + } + + @override + Future findTradeById({@required String id}) async { + final url = apiBaseUrl + orderPath + '/' + id; + final response = await get(url); + + if (response.statusCode == 404) { + throw TradeNotFoundException(id, provider: description); + } + + if (response.statusCode == 400) { + final responseJSON = json.decode(response.body) as Map; + final error = responseJSON['error']['message'] as String; + + throw TradeNotFoundException(id, + provider: description, description: error); + } + + if (response.statusCode != 200) { + return null; + } + + final responseJSON = json.decode(response.body) as Map; + final fromCurrency = responseJSON['depositMethodId'] as String; + final from = CryptoCurrency.fromString(fromCurrency); + final toCurrency = responseJSON['settleMethodId'] as String; + final to = CryptoCurrency.fromString(toCurrency); + final inputAddress = responseJSON['depositAddress']['address'] as String; + final expectedSendAmount = responseJSON['depositAmount'].toString(); + final deposits = responseJSON['deposits'] as List; + TradeState state; + + if (deposits != null && deposits.isNotEmpty) { + final status = deposits[0]['status'] as String; + state = TradeState.deserialize(raw: status); + } + + final expiredAtRaw = responseJSON['expiresAtISO'] as String; + final expiredAt = + expiredAtRaw != null ? DateTime.parse(expiredAtRaw).toLocal() : null; + + return Trade( + id: id, + from: from, + to: to, + provider: description, + inputAddress: inputAddress, + amount: expectedSendAmount, + state: state, + expiredAt: expiredAt, + ); + } + + @override + bool get isAvailable => true; + + @override + String get title => 'SideShift'; + + static String normalizeCryptoCurrency(CryptoCurrency currency) { + const bnbTitle = 'bsc'; + const usdterc20 = 'usdtErc20'; + + switch (currency) { + case CryptoCurrency.bnb: + return bnbTitle; + case CryptoCurrency.usdterc20: + return usdterc20; + default: + return currency.title.toLowerCase(); + } + } +} diff --git a/lib/exchange/sideshift/sideshift_request.dart b/lib/exchange/sideshift/sideshift_request.dart new file mode 100644 index 000000000..04deea8a5 --- /dev/null +++ b/lib/exchange/sideshift/sideshift_request.dart @@ -0,0 +1,17 @@ +import 'package:cake_wallet/exchange/trade_request.dart'; +import 'package:cw_core/crypto_currency.dart'; + +class SideShiftRequest extends TradeRequest { + final CryptoCurrency depositMethod; + final CryptoCurrency settleMethod; + final String depositAmount; + final String settleAddress; + final String refundAddress; + + SideShiftRequest( + {this.depositMethod, + this.settleMethod, + this.depositAmount, + this.settleAddress, + this.refundAddress,}); +} diff --git a/lib/exchange/trade_state.dart b/lib/exchange/trade_state.dart index 4ddce2e02..46472bdd9 100644 --- a/lib/exchange/trade_state.dart +++ b/lib/exchange/trade_state.dart @@ -33,7 +33,8 @@ class TradeState extends EnumerableItem with Serializable { TradeState(raw: 'waitingAuthorization', title: 'Waiting authorization'); static const failed = TradeState(raw: 'failed', title: 'Failed'); static const completed = TradeState(raw: 'completed', title: 'Completed'); - + static const settling = TradeState(raw: 'settling', title: 'Settlement in progress'); + static const settled = TradeState(raw: 'settled', title: 'Settlement completed'); static TradeState deserialize({String raw}) { switch (raw) { case 'pending': diff --git a/lib/src/screens/dashboard/widgets/trade_row.dart b/lib/src/screens/dashboard/widgets/trade_row.dart index 88745aa2c..faaac9edb 100644 --- a/lib/src/screens/dashboard/widgets/trade_row.dart +++ b/lib/src/screens/dashboard/widgets/trade_row.dart @@ -87,6 +87,9 @@ class TradeRow extends StatelessWidget { case ExchangeProviderDescription.morphToken: image = Image.asset('assets/images/morph.png', height: 36, width: 36); break; + case ExchangeProviderDescription.sideShift: + image = Image.asset('assets/images/sideshift.png', width: 36, height: 36); + break; default: image = null; } diff --git a/lib/src/screens/exchange/widgets/present_provider_picker.dart b/lib/src/screens/exchange/widgets/present_provider_picker.dart index 4656ba74e..16b35d7bb 100644 --- a/lib/src/screens/exchange/widgets/present_provider_picker.dart +++ b/lib/src/screens/exchange/widgets/present_provider_picker.dart @@ -71,6 +71,9 @@ class PresentProviderPicker extends StatelessWidget { case ExchangeProviderDescription.morphToken: images.add(Image.asset('assets/images/morph_icon.png')); break; + case ExchangeProviderDescription.sideShift: + images.add(Image.asset('assets/images/sideshift.png', width: 20)); + break; } } diff --git a/lib/view_model/exchange/exchange_view_model.dart b/lib/view_model/exchange/exchange_view_model.dart index e485a5129..3eed44011 100644 --- a/lib/view_model/exchange/exchange_view_model.dart +++ b/lib/view_model/exchange/exchange_view_model.dart @@ -1,3 +1,5 @@ +import 'package:cake_wallet/exchange/sideshift/sideshift_exchange_provider.dart'; +import 'package:cake_wallet/exchange/sideshift/sideshift_request.dart'; import 'package:cw_core/wallet_base.dart'; import 'package:cw_core/crypto_currency.dart'; import 'package:cw_core/sync_status.dart'; @@ -33,7 +35,7 @@ abstract class ExchangeViewModelBase with Store { this.tradesStore, this._settingsStore) { const excludeDepositCurrencies = [CryptoCurrency.xhv]; const excludeReceiveCurrencies = [CryptoCurrency.xlm, CryptoCurrency.xrp, CryptoCurrency.bnb, CryptoCurrency.xhv]; - providerList = [ChangeNowExchangeProvider()]; + providerList = [ChangeNowExchangeProvider(), SideShiftExchangeProvider()]; _initialPairBasedOnWallet(); isDepositAddressEnabled = !(depositCurrency == wallet.currency); isReceiveAddressEnabled = !(receiveCurrency == wallet.currency); @@ -253,6 +255,18 @@ abstract class ExchangeViewModelBase with Store { String amount; CryptoCurrency currency; + if (provider is SideShiftExchangeProvider) { + request = SideShiftRequest( + depositMethod: depositCurrency, + settleMethod: receiveCurrency, + depositAmount: depositAmount?.replaceAll(',', '.'), + settleAddress: receiveAddress, + refundAddress: depositAddress, + ); + amount = depositAmount; + currency = depositCurrency; + } + if (provider is XMRTOExchangeProvider) { request = XMRTOTradeRequest( from: depositCurrency, diff --git a/lib/view_model/trade_details_view_model.dart b/lib/view_model/trade_details_view_model.dart index a367c17f4..043028c90 100644 --- a/lib/view_model/trade_details_view_model.dart +++ b/lib/view_model/trade_details_view_model.dart @@ -3,6 +3,7 @@ import 'package:cake_wallet/exchange/changenow/changenow_exchange_provider.dart' import 'package:cake_wallet/exchange/exchange_provider.dart'; import 'package:cake_wallet/exchange/exchange_provider_description.dart'; import 'package:cake_wallet/exchange/morphtoken/morphtoken_exchange_provider.dart'; +import 'package:cake_wallet/exchange/sideshift/sideshift_exchange_provider.dart'; import 'package:cake_wallet/exchange/trade.dart'; import 'package:cake_wallet/exchange/xmrto/xmrto_exchange_provider.dart'; import 'package:cake_wallet/utils/date_formatter.dart'; @@ -31,6 +32,9 @@ abstract class TradeDetailsViewModelBase with Store { case ExchangeProviderDescription.morphToken: _provider = MorphTokenExchangeProvider(trades: trades); break; + case ExchangeProviderDescription.sideShift: + _provider = SideShiftExchangeProvider(); + break; } items = ObservableList(); @@ -102,6 +106,12 @@ abstract class TradeDetailsViewModelBase with Store { })); } + if (trade.provider == ExchangeProviderDescription.sideShift) { + final buildURL = 'https://sideshift.ai/orders/${trade.id.toString()}'; + items.add(TrackTradeListItem( + title: 'Track', value: buildURL, onTap: () => launch(buildURL))); + } + if (trade.createdAt != null) { items.add(StandartListItem( title: S.current.trade_details_created_at, diff --git a/tool/utils/secret_key.dart b/tool/utils/secret_key.dart index e9bcfe253..aa9928c8c 100644 --- a/tool/utils/secret_key.dart +++ b/tool/utils/secret_key.dart @@ -23,6 +23,8 @@ class SecretKey { SecretKey('wyreAccountId', () => ''), SecretKey('moonPayApiKey', () => ''), SecretKey('moonPaySecretKey', () => ''), + SecretKey('sideShiftAffiliateId', () => ''), + SecretKey('sideShiftApiKey', () => ''), ]; final String name; From 6eb06d75e3c5fa1cd8194a05b8728c10474ae4b7 Mon Sep 17 00:00:00 2001 From: M Date: Fri, 22 Apr 2022 12:30:47 +0100 Subject: [PATCH 13/30] Fixes for ios build --- scripts/ios/setup.sh | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/scripts/ios/setup.sh b/scripts/ios/setup.sh index a2a48f058..bddfa21ed 100755 --- a/scripts/ios/setup.sh +++ b/scripts/ios/setup.sh @@ -3,7 +3,14 @@ . ./config.sh cd $EXTERNAL_IOS_LIB_DIR -libtool -static -o libboost.a ./boost/*.a + +LIBRANDOMX_PATH=${EXTERNAL_IOS_LIB_DIR}/monero/librandomx.a + +if [ -f "$LIBRANDOMX_PATH" ]; then + cp $LIBRANDOMX_PATH ./haven +fi + +libtool -static -o libboost.a ./libboost_*.a libtool -static -o libhaven.a ./haven/*.a libtool -static -o libmonero.a ./monero/*.a @@ -13,6 +20,7 @@ CW_MONERO_EXTERNAL_LIB=../../../../../cw_monero/ios/External/ios/lib CW_MONERO_EXTERNAL_INCLUDE=../../../../../cw_monero/ios/External/ios/include mkdir -p $CW_HAVEN_EXTERNAL_INCLUDE +mkdir -p $CW_MONERO_EXTERNAL_INCLUDE mkdir -p $CW_HAVEN_EXTERNAL_LIB mkdir -p $CW_MONERO_EXTERNAL_LIB From 1874d7b2dee9b54751218982f91e0ad1282de19f Mon Sep 17 00:00:00 2001 From: M Date: Fri, 22 Apr 2022 14:02:24 +0100 Subject: [PATCH 14/30] Add build haven to build all script for ios --- scripts/ios/build_all.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/ios/build_all.sh b/scripts/ios/build_all.sh index 85c1f63b9..565679e2d 100755 --- a/scripts/ios/build_all.sh +++ b/scripts/ios/build_all.sh @@ -9,6 +9,6 @@ DIR=$(dirname "$0") case $APP_IOS_TYPE in "monero.com") $DIR/build_monero_all.sh ;; - "cakewallet") $DIR/build_monero_all.sh ;; + "cakewallet") $DIR/build_monero_all.sh && $DIR/build_haven.sh ;; "haven") $DIR/build_haven_all.sh ;; esac From 2e17c59a005f1d7935c3892e1766f5ffbc8f7f3b Mon Sep 17 00:00:00 2001 From: Serhii Date: Fri, 22 Apr 2022 18:00:28 +0300 Subject: [PATCH 15/30] hidden mode for balance on send screen (#344) --- lib/di.dart | 1 + lib/view_model/send/send_view_model.dart | 5 ++++- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/lib/di.dart b/lib/di.dart index 3cc4dacae..dc6ec367d 100644 --- a/lib/di.dart +++ b/lib/di.dart @@ -339,6 +339,7 @@ Future setup( getIt.get().settingsStore, getIt.get(), getIt.get(), + getIt.get(), _transactionDescriptionBox)); getIt.registerFactory( diff --git a/lib/view_model/send/send_view_model.dart b/lib/view_model/send/send_view_model.dart index 5c4a15c27..d82dd979f 100644 --- a/lib/view_model/send/send_view_model.dart +++ b/lib/view_model/send/send_view_model.dart @@ -1,5 +1,6 @@ import 'package:cake_wallet/entities/balance_display_mode.dart'; import 'package:cake_wallet/entities/transaction_description.dart'; +import 'package:cake_wallet/view_model/dashboard/balance_view_model.dart'; import 'package:cw_core/transaction_priority.dart'; import 'package:cake_wallet/view_model/send/output.dart'; import 'package:cake_wallet/view_model/send/send_template_view_model.dart'; @@ -36,6 +37,7 @@ abstract class SendViewModelBase with Store { this._settingsStore, this.sendTemplateViewModel, this._fiatConversationStore, + this.balanceViewModel, this.transactionDescriptionBox) : state = InitialExecutionState() { final priority = _settingsStore.priority[_wallet.type]; @@ -128,7 +130,7 @@ abstract class SendViewModelBase with Store { PendingTransaction pendingTransaction; @computed - String get balance => _wallet.balance[selectedCryptoCurrency].formattedAvailableBalance ?? '0.0'; + String get balance => balanceViewModel.availableBalance ?? '0.0'; @computed bool get isReadyForSend => _wallet.syncStatus is SyncedSyncStatus; @@ -160,6 +162,7 @@ abstract class SendViewModelBase with Store { final WalletBase _wallet; final SettingsStore _settingsStore; final SendTemplateViewModel sendTemplateViewModel; + final BalanceViewModel balanceViewModel; final FiatConversionStore _fiatConversationStore; final Box transactionDescriptionBox; From 873402ad3cfd33d8ee004b14d1b858a26070867c Mon Sep 17 00:00:00 2001 From: Serhii Date: Fri, 22 Apr 2022 18:02:31 +0300 Subject: [PATCH 16/30] Increased tapping area on the dashbord buttons (#345) --- .../dashboard/widgets/action_button.dart | 51 ++++++++++--------- 1 file changed, 26 insertions(+), 25 deletions(-) diff --git a/lib/src/screens/dashboard/widgets/action_button.dart b/lib/src/screens/dashboard/widgets/action_button.dart index 66d16c5eb..fecbbd17e 100644 --- a/lib/src/screens/dashboard/widgets/action_button.dart +++ b/lib/src/screens/dashboard/widgets/action_button.dart @@ -23,36 +23,37 @@ class ActionButton extends StatelessWidget { .display3 .backgroundColor; - return Container( - padding: EdgeInsets.only(top: 14, bottom: 16, left: 10, right: 10), - alignment: alignment, - child: Column( - mainAxisSize: MainAxisSize.max, - crossAxisAlignment: CrossAxisAlignment.center, - children: [ - GestureDetector( - onTap: () { - if (route?.isNotEmpty ?? false) { - Navigator.of(context, rootNavigator: true).pushNamed(route); - } else { - onClick?.call(); - } - }, - child: Container( + return GestureDetector( + onTap: () { + if (route?.isNotEmpty ?? false) { + Navigator.of(context, rootNavigator: true).pushNamed(route); + } else { + onClick?.call(); + } + }, + child: Container( + color: Colors.transparent, + padding: EdgeInsets.only(top: 14, bottom: 16, left: 10, right: 10), + alignment: alignment, + child: Column( + mainAxisSize: MainAxisSize.max, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Container( alignment: Alignment.center, decoration: BoxDecoration( shape: BoxShape.circle), child: image, ), - ), - SizedBox(height: 4), - Text( - title, - style: TextStyle( - fontSize: 10, - color: _textColor), - ) - ], + SizedBox(height: 4), + Text( + title, + style: TextStyle( + fontSize: 10, + color: _textColor), + ) + ], + ), ), ); } From 6cd07278cc5dd71bc8b5a2ffb3ef00d9847e356d Mon Sep 17 00:00:00 2001 From: Mathias Herberts Date: Fri, 22 Apr 2022 17:15:05 +0200 Subject: [PATCH 17/30] Fixed localization of "View in Block Explorer" (#342) Fixed localization of "View in Block Explorer --- lib/view_model/transaction_details_view_model.dart | 10 +++++----- res/values/strings_de.arb | 2 ++ res/values/strings_en.arb | 4 +++- res/values/strings_es.arb | 2 ++ res/values/strings_fr.arb | 2 ++ res/values/strings_hi.arb | 2 ++ res/values/strings_hr.arb | 2 ++ res/values/strings_it.arb | 2 ++ res/values/strings_ja.arb | 2 ++ res/values/strings_ko.arb | 2 ++ res/values/strings_nl.arb | 2 ++ res/values/strings_pl.arb | 2 ++ res/values/strings_pt.arb | 2 ++ res/values/strings_ru.arb | 2 ++ res/values/strings_uk.arb | 2 ++ res/values/strings_zh.arb | 2 ++ 16 files changed, 36 insertions(+), 6 deletions(-) diff --git a/lib/view_model/transaction_details_view_model.dart b/lib/view_model/transaction_details_view_model.dart index 41b4e0c60..6de28f238 100644 --- a/lib/view_model/transaction_details_view_model.dart +++ b/lib/view_model/transaction_details_view_model.dart @@ -142,7 +142,7 @@ abstract class TransactionDetailsViewModelBase with Store { final type = wallet.type; items.add(BlockExplorerListItem( - title: "View in Block Explorer", + title: S.current.view_in_block_explorer, value: _explorerDescription(type), onTap: () => launch(_explorerUrl(type, tx.id)))); @@ -191,13 +191,13 @@ abstract class TransactionDetailsViewModelBase with Store { String _explorerDescription(WalletType type) { switch (type) { case WalletType.monero: - return 'View Transaction on XMRChain.net'; + return S.current.view_transaction_on + 'XMRChain.net'; case WalletType.bitcoin: - return 'View Transaction on Blockchain.com'; + return S.current.view_transaction_on + 'Blockchain.com'; case WalletType.litecoin: - return 'View Transaction on Blockchair.com'; + return S.current.view_transaction_on + 'Blockchair.com'; case WalletType.haven: - return 'View Transaction on explorer.havenprotocol.org'; + return S.current.view_transaction_on + 'explorer.havenprotocol.org'; default: return ''; } diff --git a/res/values/strings_de.arb b/res/values/strings_de.arb index 306cc9e8b..a501cd72e 100644 --- a/res/values/strings_de.arb +++ b/res/values/strings_de.arb @@ -443,6 +443,8 @@ "enter_your_note" : "Geben Sie Ihre Bemerkung ein…", "note_optional" : "Bemerkung (optional)", "note_tap_to_change" : "Bemerkung (zum Ändern tippen)", + "view_in_block_explorer" : "View in Block Explorer", + "view_transaction_on" : "View Transaction on ", "transaction_key" : "Transaktionsschlüssel", "confirmations" : "Bestätigungen", "recipient_address" : "Empfängeradresse", diff --git a/res/values/strings_en.arb b/res/values/strings_en.arb index 42f3e05f2..dfe3b84f3 100644 --- a/res/values/strings_en.arb +++ b/res/values/strings_en.arb @@ -443,6 +443,8 @@ "enter_your_note" : "Enter your note…", "note_optional" : "Note (optional)", "note_tap_to_change" : "Note (tap to change)", + "view_in_block_explorer" : "View in Block Explorer", + "view_transaction_on" : "View Transaction on ", "transaction_key" : "Transaction Key", "confirmations" : "Confirmations", "recipient_address" : "Recipient address", @@ -529,4 +531,4 @@ "search": "Search", "new_template" : "New Template", "electrum_address_disclaimer": "We generate new addresses each time you use one, but previous addresses continue to work" -} \ No newline at end of file +} diff --git a/res/values/strings_es.arb b/res/values/strings_es.arb index 8e00ddcdb..a033025f3 100644 --- a/res/values/strings_es.arb +++ b/res/values/strings_es.arb @@ -443,6 +443,8 @@ "enter_your_note" : "Ingresa tu nota…", "note_optional" : "Nota (opcional)", "note_tap_to_change" : "Nota (toque para cambiar)", + "view_in_block_explorer" : "View in Block Explorer", + "view_transaction_on" : "View Transaction on ", "transaction_key" : "Clave de transacción", "confirmations" : "Confirmaciones", "recipient_address" : "Dirección del receptor", diff --git a/res/values/strings_fr.arb b/res/values/strings_fr.arb index 224ee7112..f4311a070 100644 --- a/res/values/strings_fr.arb +++ b/res/values/strings_fr.arb @@ -441,6 +441,8 @@ "enter_your_note" : "Entrez votre note…", "note_optional" : "Note (optionnelle)", "note_tap_to_change" : "Note (appuyez pour changer)", + "view_in_block_explorer" : "Voir dans l'Explorateur de Blocs", + "view_transaction_on" : "Voir la Transaction sur ", "transaction_key" : "Clef de Transaction", "confirmations" : "Confirmations", "recipient_address" : "Adresse bénéficiaire", diff --git a/res/values/strings_hi.arb b/res/values/strings_hi.arb index bad0ae879..4efe457c3 100644 --- a/res/values/strings_hi.arb +++ b/res/values/strings_hi.arb @@ -443,6 +443,8 @@ "enter_your_note" : "अपना नोट दर्ज करें ...", "note_optional" : "नोट (वैकल्पिक)", "note_tap_to_change" : "नोट (टैप टू चेंज)", + "view_in_block_explorer" : "View in Block Explorer", + "view_transaction_on" : "View Transaction on ", "transaction_key" : "लेन-देन की", "confirmations" : "पुष्टिकरण", "recipient_address" : "प्राप्तकर्ता का पता", diff --git a/res/values/strings_hr.arb b/res/values/strings_hr.arb index 03e7708bb..95004d689 100644 --- a/res/values/strings_hr.arb +++ b/res/values/strings_hr.arb @@ -443,6 +443,8 @@ "enter_your_note" : "Unesite svoju poruku…", "note_optional" : "Poruka (nije obvezno)", "note_tap_to_change" : "Poruka (dodirnite za promjenu)", + "view_in_block_explorer" : "View in Block Explorer", + "view_transaction_on" : "View Transaction on ", "transaction_key" : "Transakcijski ključ", "confirmations" : "Potvrde", "recipient_address" : "Primateljeva adresa", diff --git a/res/values/strings_it.arb b/res/values/strings_it.arb index e92f4da5f..a65078b3e 100644 --- a/res/values/strings_it.arb +++ b/res/values/strings_it.arb @@ -443,6 +443,8 @@ "enter_your_note" : "Inserisci la tua nota…", "note_optional" : "Nota (opzionale)", "note_tap_to_change" : "Nota (clicca per cambiare)", + "view_in_block_explorer" : "View in Block Explorer", + "view_transaction_on" : "View Transaction on ", "transaction_key" : "Chiave Transazione", "confirmations" : "Conferme", "recipient_address" : "Indirizzo di destinazione", diff --git a/res/values/strings_ja.arb b/res/values/strings_ja.arb index 734c769df..5cb3039c1 100644 --- a/res/values/strings_ja.arb +++ b/res/values/strings_ja.arb @@ -443,6 +443,8 @@ "enter_your_note" : "メモを入力してください…", "note_optional" : "注(オプション)", "note_tap_to_change" : "注(タップして変更)", + "view_in_block_explorer" : "View in Block Explorer", + "view_transaction_on" : "View Transaction on ", "transaction_key" : "トランザクションキー", "confirmations" : "確認", "recipient_address" : "受信者のアドレス", diff --git a/res/values/strings_ko.arb b/res/values/strings_ko.arb index cd6712288..db099be63 100644 --- a/res/values/strings_ko.arb +++ b/res/values/strings_ko.arb @@ -443,6 +443,8 @@ "enter_your_note" : "메모를 입력하세요…", "note_optional" : "참고 (선택 사항)", "note_tap_to_change" : "메모 (변경하려면 탭하세요)", + "view_in_block_explorer" : "View in Block Explorer", + "view_transaction_on" : "View Transaction on ", "transaction_key" : "거래 키", "confirmations" : "확인", "recipient_address" : "받는 사람 주소", diff --git a/res/values/strings_nl.arb b/res/values/strings_nl.arb index 365858b8b..207979462 100644 --- a/res/values/strings_nl.arb +++ b/res/values/strings_nl.arb @@ -443,6 +443,8 @@ "enter_your_note" : "Voer uw notitie in ...", "note_optional" : "Opmerking (optioneel)", "note_tap_to_change" : "Opmerking (tik om te wijzigen)", + "view_in_block_explorer" : "View in Block Explorer", + "view_transaction_on" : "View Transaction on ", "transaction_key" : "Transactiesleutel", "confirmations" : "Bevestigingen", "recipient_address" : "Adres ontvanger", diff --git a/res/values/strings_pl.arb b/res/values/strings_pl.arb index 97101bfd7..3da2cc94d 100644 --- a/res/values/strings_pl.arb +++ b/res/values/strings_pl.arb @@ -446,6 +446,8 @@ "enter_your_note" : "Wpisz notatkę…", "note_optional" : "Notatka (opcjonalnie)", "note_tap_to_change" : "Notatka (dotknij, aby zmienić)", + "view_in_block_explorer" : "View in Block Explorer", + "view_transaction_on" : "View Transaction on ", "transaction_key" : "Klucz transakcji", "confirmations" : "Potwierdzenia", "recipient_address" : "Adres odbiorcy", diff --git a/res/values/strings_pt.arb b/res/values/strings_pt.arb index db7369e56..d32e47bd0 100644 --- a/res/values/strings_pt.arb +++ b/res/values/strings_pt.arb @@ -443,6 +443,8 @@ "enter_your_note" : "Insira sua nota ...", "note_optional" : "Nota (opcional)", "note_tap_to_change" : "Nota (toque para alterar)", + "view_in_block_explorer" : "View in Block Explorer", + "view_transaction_on" : "View Transaction on ", "transaction_key" : "Chave de transação", "confirmations" : "Confirmações", "recipient_address" : "Endereço do destinatário", diff --git a/res/values/strings_ru.arb b/res/values/strings_ru.arb index 2d685b801..78c67943a 100644 --- a/res/values/strings_ru.arb +++ b/res/values/strings_ru.arb @@ -443,6 +443,8 @@ "enter_your_note" : "Введите примечание…", "note_optional" : "Примечание (необязательно)", "note_tap_to_change" : "Примечание (нажмите для изменения)", + "view_in_block_explorer" : "View in Block Explorer", + "view_transaction_on" : "View Transaction on ", "transaction_key" : "Ключ транзакции", "confirmations" : "Подтверждения", "recipient_address" : "Адрес получателя", diff --git a/res/values/strings_uk.arb b/res/values/strings_uk.arb index 9bf2d85c7..c2a96ab49 100644 --- a/res/values/strings_uk.arb +++ b/res/values/strings_uk.arb @@ -442,6 +442,8 @@ "enter_your_note" : "Введіть примітку…", "note_optional" : "Примітка (необов’язково)", "note_tap_to_change" : "Примітка (натисніть для зміни)", + "view_in_block_explorer" : "View in Block Explorer", + "view_transaction_on" : "View Transaction on ", "transaction_key" : "Ключ транзакції", "confirmations" : "Підтвердження", "recipient_address" : "Адреса одержувача", diff --git a/res/values/strings_zh.arb b/res/values/strings_zh.arb index c55b7147d..43132041a 100644 --- a/res/values/strings_zh.arb +++ b/res/values/strings_zh.arb @@ -442,6 +442,8 @@ "enter_your_note" : "输入您的笔记...", "note_optional" : "注释(可选)", "note_tap_to_change" : "注释(轻按即可更改)", + "view_in_block_explorer" : "View in Block Explorer", + "view_transaction_on" : "View Transaction on ", "transaction_key" : "交易密码", "confirmations" : "确认", "recipient_address" : "收件人地址", From 2b81647fe1dea7865951533cb1533f8afda31270 Mon Sep 17 00:00:00 2001 From: Justin Ehrenhofer Date: Fri, 22 Apr 2022 11:17:43 -0500 Subject: [PATCH 18/30] Change Monero block explorer to Monero.com Merge after Monero.com goes live --- lib/view_model/transaction_details_view_model.dart | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/view_model/transaction_details_view_model.dart b/lib/view_model/transaction_details_view_model.dart index 6de28f238..64c764812 100644 --- a/lib/view_model/transaction_details_view_model.dart +++ b/lib/view_model/transaction_details_view_model.dart @@ -176,7 +176,7 @@ abstract class TransactionDetailsViewModelBase with Store { String _explorerUrl(WalletType type, String txId) { switch (type) { case WalletType.monero: - return 'https://xmrchain.net/search?value=${txId}'; + return 'https://monero.com/tx/${txId}'; case WalletType.bitcoin: return 'https://www.blockchain.com/btc/tx/${txId}'; case WalletType.litecoin: @@ -191,7 +191,7 @@ abstract class TransactionDetailsViewModelBase with Store { String _explorerDescription(WalletType type) { switch (type) { case WalletType.monero: - return S.current.view_transaction_on + 'XMRChain.net'; + return S.current.view_transaction_on + 'Monero.com'; case WalletType.bitcoin: return S.current.view_transaction_on + 'Blockchain.com'; case WalletType.litecoin: From d3b83091a34fe964843b1babc6dd2391e65c6785 Mon Sep 17 00:00:00 2001 From: Justin Ehrenhofer Date: Wed, 27 Apr 2022 09:55:57 -0500 Subject: [PATCH 19/30] Update privacy policy Minor changes. Clarify definition of Node and broaden supported network language. Made is clearer we do not retain node information --- PRIVACY.md | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/PRIVACY.md b/PRIVACY.md index dc3014967..fda8863aa 100644 --- a/PRIVACY.md +++ b/PRIVACY.md @@ -1,6 +1,6 @@ Privacy Policy -Last modified: January 11, 2022 +Last modified: April 27, 2022 Introduction ============ @@ -22,32 +22,32 @@ Definitions - "App" means any software program provided by the Company, downloaded by You on any electronic device, including but not limited to Cake Wallet and Monero.com. - "Device" means any device that can access the App, such as a cell phone or tablet device. - - "Node" means a full Monero or Bitcoin or Litecoin Node (or any other node on a supported cryptocurrency network), which transmits data to your App for processing and synchronization, and to which your Device transmits transactions which you would like to submit to the Monero or Bitcoin or Litecoin networks (or any other supported cryptocurrency network). + - "Node" means a server on a supported cryptocurrency network which transmits data to your App for processing and synchronization, and to which your Device transmits transactions which you would like to submit to the supported cryptocurrency networks. This includes full nodes, Electrum servers, and lightning network nodes. - "Cake Labs Nodes" refers to the set of cryptocurrency nodes operated and maintained by Cake Labs LLC. - "Service" refers to the App. - - "Third-party Service" refers to any service integrated into the App. This includes ChangeNOW, Wyre, MoonPay, and BlockBuy. + - "Third-party Service" refers to any service integrated into the App. This includes but is not limited to ChangeNOW, Wyre, MoonPay, and BlockBuy. - "Usage Data" refers to data collected automatically about your usage of an App. - "You" means the individual, group, corporation, or any other entity accessing or using the Service. Information We Collect About You and How We Collect It ------------------------------------------------------ - We collect several types of information from and about users of our App, including information: + We may collect several types of information from and about users of our App, including information: - By which you may be personally identified, such as name, e-mail address, or and a/any other identifier by which you may be contacted online or offline ("personal information" or "Personal Data”), ONLY when you provide it to us; - - Device IP address, the block height to which your wallet is synchronized, and any transactions which you use our Node to submit to the Monero or Bitcoin or Litecoin networks. + - Device IP address, the block height to which your wallet is synchronized, and any transactions which you use our Node to submit to supported cryptocurrency networks. We collect this information: - Directly from you when you provide it to us. - Automatically as you use the App, if you use one of the Cake Labs Nodes. Information collected automatically may include IP address and block height. Usage Data (including the date and time at which you use an application, the duration of using it, and other ‘metadata’) is NOT collected by Cake Labs through the usage of the App. Cake Labs has no reason to care about any aspect of your continued usage of our App. We believe that this data is your own property and that we have no right to collect it. - Personal information is received by Cake Labs ONLY in the event that you choose to provide it to us. This is provided either by synchronizing your wallet using the Nodes maintained by Cake Labs, or by voluntarily contacting Cake Labs regarding support, questions or suggestions. You also have the right to choose not to provide data to Cake Labs, by choosing a different Node. The option to do so is provided by default with a list of Nodes, but Cake Wallet and Monero.com also provide the ability to add another Node not listed, or use your own Node. + Personal information is received by Cake Labs ONLY in the event that you choose to provide it to us. This is provided either by synchronizing your wallet using the Nodes maintained by Cake Labs, or by voluntarily contacting Cake Labs regarding support, questions or suggestions. You also have the right to choose not to provide data to Cake Labs, by choosing a different Node. The option to do so is provided by default with a list of Nodes, but our Apps also provide the ability to add another Node not listed, or use your own Node. Data relating to your funds, and their security and privacy, remains on your device at ALL times. Your private keys, seeds, backup files, and wallet passcode are your own responsibility. This data is not received, collected, or stored by Cake Labs at any time, for any reason. - Personal Data collected through the Cake Labs Nodes is limited to your device's IP address, the block height to which your wallet is synchronized, and any transactions which you use our Node to submit to the Monero or Bitcoin or Litecoin networks (or any other supported cryptocurrency network). Personal Data received by Cake Labs in this manner is not stored for any length of time, and thus Cake Labs is both unwilling to and incapable of sharing this data, or using it for any purpose beyond ensuring your appropriate connection to our Nodes. + Personal Data sent through the Cake Labs Nodes is limited to your device's IP address, the block height to which your wallet is synchronized, and any transactions which you use our Node to submit to the supported cryptocurrency networks. Personal Data received by Cake Labs in this manner is not stored for any length of time, and thus Cake Labs is both unwilling to and incapable of sharing this data, or using it for any purpose beyond ensuring your appropriate connection to our Nodes. - If you decide to use a Node offered by any third party, some of which we offer by default in our Apps, said third party will receive this Personal Data instead of Cake Labs. We take no responsibility for the actions of any third-party Node offered within the Application. If you decide to synchronize your Wallet using your own Node, neither Cake Labs nor any third party will have access to this Personal Data. + If you decide to use a Node offered by any third party, some of which we include in our Apps, said third party will receive this Personal Data instead of Cake Labs. We take no responsibility for the actions of any third-party Node offered within the Application. If you decide to synchronize your Wallet using your own Node, neither Cake Labs nor any third party will have access to this Personal Data. In any of these situations, Cake Labs takes no responsibility for interception of this data by any outside individual, group, corporation, or institution. You should understand this and take any and all appropriate actions to secure your own data. @@ -76,7 +76,7 @@ Disclosure of Your Information We may disclose personal information that we collect or you provide as described in this Privacy Policy: - To our subsidiaries and affiliates. - To contractors, service providers, and other third parties we use to support our business and who are bound by contractual obligations to keep personal information confidential and use it only for the purposes for which we disclose it to them. - - To a buyer or other successor in the event of a merger, divestiture, restructuring, reorganization, dissolution, or other sale or transfer of some or all of Cake Technology's assets, whether as a going concern or as part of bankruptcy, liquidation, or similar proceeding, in which personal information held by Cake Technology about our App users is among the assets transferred. However, we will provide notice before this Personal Data is transferred and becomes subject to a different Privacy Policy. + - To a buyer or other successor in the event of a merger, divestiture, restructuring, reorganization, dissolution, or other sale or transfer of some or all of Cake Labs's assets, whether as a going concern or as part of bankruptcy, liquidation, or similar proceeding, in which personal information held by Cake Labs about our App users is among the assets transferred. However, we will provide notice before this Personal Data is transferred and becomes subject to a different Privacy Policy. - To fulfill the purpose for which you provide it. - For any other purpose disclosed by us when you provide the information. - With your consent. From 5f373f36133c7f827cb1e3603aae8a4cb2d518ef Mon Sep 17 00:00:00 2001 From: Justin Ehrenhofer Date: Fri, 29 Apr 2022 14:15:42 -0500 Subject: [PATCH 20/30] Clarify data is not retained --- PRIVACY.md | 42 +++++++++++++++++++++++++++--------------- 1 file changed, 27 insertions(+), 15 deletions(-) diff --git a/PRIVACY.md b/PRIVACY.md index fda8863aa..bd372c553 100644 --- a/PRIVACY.md +++ b/PRIVACY.md @@ -1,6 +1,6 @@ Privacy Policy -Last modified: April 27, 2022 +Last modified: April 29, 2022 Introduction ============ @@ -29,27 +29,37 @@ Definitions - "Usage Data" refers to data collected automatically about your usage of an App. - "You" means the individual, group, corporation, or any other entity accessing or using the Service. +Information We Never Receive Nor Collect +---------------------------------------- + + Usage Data (including the date and time at which you use an application, the duration of using it, and other ‘metadata’) is NOT collected by Cake Labs through the usage of the App. Cake Labs has no reason to care about any aspect of your continued usage of our App. We believe that this data is your own property and that we have no right to collect it. + + Data relating to your funds, and their security and privacy, remains on your device at ALL times. Your private keys (including your Monero private view keys), seeds, backup files, and wallet passcode are your own responsibility. This data is not received, collected, or stored by Cake Labs at any time, for any reason. + + +Information We May Receive But Do Not Retain +-------------------------------------------- + + We receive but do NOT store information from and about users of our App, including: + - The device IP address, the block height to which your wallet is synchronized, and any transactions or channels which you use our Node to submit to supported cryptocurrency networks. + We collect this infoirmation: + - Automatically as you use the App, if you use one of the Cake Labs Nodes. + + This data is provided by connecting to the Nodes maintained by Cake Labs. You have the right to choose not to provide data to Cake Labs by choosing a different Node. We provide a list of Nodes in the app that include our own and third party Nodes, or you can use your own Node (which we recommend). + + Personal Data sent through the Cake Labs Nodes is limited to your device's IP address, the block height to which your wallet is synchronized, and any transactions or channels which you use our Node to submit to the supported cryptocurrency networks. Personal Data received by Cake Labs in this manner is not stored for any length of time, and thus Cake Labs is both unwilling to and incapable of sharing this data, or using it for any purpose beyond ensuring your appropriate connection to our Nodes. + + If you decide to use a Node offered by any third party, some of which we include in our Apps, said third party will receive this Personal Data instead of Cake Labs. We take no responsibility for the actions of any third-party Node offered within the Application. We recommend connecting to your own Node to limit third party sharing of your Personal Information. + Information We Collect About You and How We Collect It ------------------------------------------------------ - We may collect several types of information from and about users of our App, including information: + We collect several types of information from and about users of our App, including information: - By which you may be personally identified, such as name, e-mail address, or and a/any other identifier by which you may be contacted online or offline ("personal information" or "Personal Data”), ONLY when you provide it to us; - - Device IP address, the block height to which your wallet is synchronized, and any transactions which you use our Node to submit to supported cryptocurrency networks. We collect this information: - Directly from you when you provide it to us. - - Automatically as you use the App, if you use one of the Cake Labs Nodes. Information collected automatically may include IP address and block height. - Usage Data (including the date and time at which you use an application, the duration of using it, and other ‘metadata’) is NOT collected by Cake Labs through the usage of the App. Cake Labs has no reason to care about any aspect of your continued usage of our App. We believe that this data is your own property and that we have no right to collect it. - - Personal information is received by Cake Labs ONLY in the event that you choose to provide it to us. This is provided either by synchronizing your wallet using the Nodes maintained by Cake Labs, or by voluntarily contacting Cake Labs regarding support, questions or suggestions. You also have the right to choose not to provide data to Cake Labs, by choosing a different Node. The option to do so is provided by default with a list of Nodes, but our Apps also provide the ability to add another Node not listed, or use your own Node. - - Data relating to your funds, and their security and privacy, remains on your device at ALL times. Your private keys, seeds, backup files, and wallet passcode are your own responsibility. This data is not received, collected, or stored by Cake Labs at any time, for any reason. - - Personal Data sent through the Cake Labs Nodes is limited to your device's IP address, the block height to which your wallet is synchronized, and any transactions which you use our Node to submit to the supported cryptocurrency networks. Personal Data received by Cake Labs in this manner is not stored for any length of time, and thus Cake Labs is both unwilling to and incapable of sharing this data, or using it for any purpose beyond ensuring your appropriate connection to our Nodes. - - If you decide to use a Node offered by any third party, some of which we include in our Apps, said third party will receive this Personal Data instead of Cake Labs. We take no responsibility for the actions of any third-party Node offered within the Application. If you decide to synchronize your Wallet using your own Node, neither Cake Labs nor any third party will have access to this Personal Data. - - In any of these situations, Cake Labs takes no responsibility for interception of this data by any outside individual, group, corporation, or institution. You should understand this and take any and all appropriate actions to secure your own data. + Personal information is received by Cake Labs ONLY in the event that you choose to provide it to us by voluntarily contacting Cake Labs regarding support, questions or suggestions. How We Use Your Information --------------------------- @@ -96,6 +106,8 @@ Data Security You are responsible for your personal data, including all data related to the safety of your funds. You should take all appropriate action to protect this data. Cake Labs cannot recover any data related to your funds in the event that you lose or give away this data. Cake Labs strongly recommends that you take action to secure your funds, by writing down your seeds or keys, storing encrypted backups, and never sharing this information. Cake Labs undertakes any and all reasonable steps possible to secure any data that you voluntarily transmit to us. However, we cannot guarantee that any outside system used to transmit this data is entirely secure and, as such, we recommend that you exercise caution when voluntarily transmitting data to us. + + In any situation, Cake Labs takes no responsibility for interception of personal data by any outside individual, group, corporation, or institution. You should understand this and take any and all appropriate actions to secure your own data. Links to Other Websites ----------------------- From c423e8760b93511414ee60ea779e073f7dfb6192 Mon Sep 17 00:00:00 2001 From: Justin Ehrenhofer Date: Fri, 29 Apr 2022 16:08:53 -0500 Subject: [PATCH 21/30] Clarify price API --- PRIVACY.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/PRIVACY.md b/PRIVACY.md index bd372c553..30e6a7552 100644 --- a/PRIVACY.md +++ b/PRIVACY.md @@ -42,17 +42,17 @@ Information We May Receive But Do Not Retain We receive but do NOT store information from and about users of our App, including: - The device IP address, the block height to which your wallet is synchronized, and any transactions or channels which you use our Node to submit to supported cryptocurrency networks. - We collect this infoirmation: - - Automatically as you use the App, if you use one of the Cake Labs Nodes. + We receive this infoirmation: + - Automatically as you use the App. - This data is provided by connecting to the Nodes maintained by Cake Labs. You have the right to choose not to provide data to Cake Labs by choosing a different Node. We provide a list of Nodes in the app that include our own and third party Nodes, or you can use your own Node (which we recommend). + This data is provided by connecting to the Nodes and price API maintained by Cake Labs. You have the right to choose not to provide synchronization data to Cake Labs by choosing a different Node. We provide a list of Nodes in the app that include our own and third party Nodes, or you can use your own Node (which we recommend). Personal Data sent through the Cake Labs Nodes is limited to your device's IP address, the block height to which your wallet is synchronized, and any transactions or channels which you use our Node to submit to the supported cryptocurrency networks. Personal Data received by Cake Labs in this manner is not stored for any length of time, and thus Cake Labs is both unwilling to and incapable of sharing this data, or using it for any purpose beyond ensuring your appropriate connection to our Nodes. If you decide to use a Node offered by any third party, some of which we include in our Apps, said third party will receive this Personal Data instead of Cake Labs. We take no responsibility for the actions of any third-party Node offered within the Application. We recommend connecting to your own Node to limit third party sharing of your Personal Information. -Information We Collect About You and How We Collect It ------------------------------------------------------- +Information We May Collect About You and How We Collect It +---------------------------------------------------------- We collect several types of information from and about users of our App, including information: - By which you may be personally identified, such as name, e-mail address, or and a/any other identifier by which you may be contacted online or offline ("personal information" or "Personal Data”), ONLY when you provide it to us; From 863a0fe5bf0a9161f550ce10cc6b3820c7971239 Mon Sep 17 00:00:00 2001 From: Paul Verbeke Date: Mon, 2 May 2022 15:21:27 +0200 Subject: [PATCH 22/30] i18n: fix french translations subaddress label wallet seed --- res/values/strings_fr.arb | 68 +++++++++++++++++++-------------------- 1 file changed, 34 insertions(+), 34 deletions(-) diff --git a/res/values/strings_fr.arb b/res/values/strings_fr.arb index f4311a070..9a3ef3cb3 100644 --- a/res/values/strings_fr.arb +++ b/res/values/strings_fr.arb @@ -55,8 +55,8 @@ "rescan" : "Analyser la blockchain", "reconnect" : "Reconnecter", "wallets" : "Portefeuilles", - "show_seed" : "Visualiser le seed", - "show_keys" : "Visualiser seed/clefs", + "show_seed" : "Visualiser la graine", + "show_keys" : "Visualiser graine/clefs", "address_book_menu" : "Carnet d'Adresses", "reconnection" : "Reconnexion", "reconnect_alert_text" : "Êtes vous certain de vouloir vous reconnecter ?", @@ -156,38 +156,38 @@ "restore_restore_wallet" : "Restaurer le Portefeuille", - "restore_title_from_seed_keys" : "Restaurer depuis un seed ou des clefs", - "restore_description_from_seed_keys" : "Restaurez votre portefeuille depuis un seed ou des clefs que vous avez stockés en lieu sûr", + "restore_title_from_seed_keys" : "Restaurer depuis une graine ou des clefs", + "restore_description_from_seed_keys" : "Restaurez votre portefeuille depuis une graine ou des clefs que vous avez stockés en lieu sûr", "restore_next" : "Suivant", "restore_title_from_backup" : "Restaurer depuis une sauvegarde", "restore_description_from_backup" : "Vous pouvez restaurer l'intégralité de l'application Cake Wallet depuis un fichier de sauvegarde", - "restore_seed_keys_restore" : "Restaurer depuis Seed/Clefs", - "restore_title_from_seed" : "Restaurer depuis seed", - "restore_description_from_seed" : "Restaurez votre wallet depuis une combinaison de 25 ou 13 mots", + "restore_seed_keys_restore" : "Restaurer depuis Graine/Clefs", + "restore_title_from_seed" : "Restaurer depuis graine", + "restore_description_from_seed" : "Restaurez votre portefeuille depuis une combinaison de 25 ou 13 mots", "restore_title_from_keys" : "Restaurer depuis des clefs", - "restore_description_from_keys" : "Restaurer votre wallet d'après les séquences de touches générées d'après vos clefs privées", - "restore_wallet_name" : "Nom du wallet", + "restore_description_from_keys" : "Restaurer votre portefeuille d'après les séquences de touches générées d'après vos clefs privées", + "restore_wallet_name" : "Nom du portefeuille", "restore_address" : "Adresse", "restore_view_key_private" : "Clef d'audit (view key) (privée)", "restore_spend_key_private" : "Clef de dépense (spend key) (privée)", "restore_recover" : "Restaurer", - "restore_wallet_restore_description" : "Description de la restauration de wallet", - "restore_new_seed" : "Nouveau seed", - "restore_active_seed" : "Seed actif", - "restore_bitcoin_description_from_seed" : "Restaurer votre wallet à l'aide d'une combinaison de 24 mots", - "restore_bitcoin_description_from_keys" : "Restaurer votre wallet d'après la chaîne WIF générée d'après vos clefs privées", + "restore_wallet_restore_description" : "Description de la restauration de portefeuille", + "restore_new_seed" : "Nouvelle graine", + "restore_active_seed" : "Graine actif", + "restore_bitcoin_description_from_seed" : "Restaurer votre portefeuille à l'aide d'une combinaison de 24 mots", + "restore_bitcoin_description_from_keys" : "Restaurer votre portefeuille d'après la chaîne WIF générée d'après vos clefs privées", "restore_bitcoin_title_from_keys" : "Restaurer depuis la chaîne WIF", - "restore_from_date_or_blockheight" : "Merci d'entrer une date antérieure de quelques jours à la date de création de votre wallet. Ou si vous connaissez la hauteur de bloc, merci de la spécifier plutôt qu'une date", + "restore_from_date_or_blockheight" : "Merci d'entrer une date antérieure de quelques jours à la date de création de votre portefeuille. Ou si vous connaissez la hauteur de bloc, merci de la spécifier plutôt qu'une date", - "seed_reminder" : "Merci d'écrire votre seed au cas où vous perdriez ou effaceriez votre téléphone", - "seed_title" : "Seed", - "seed_share" : "Partager le seed", + "seed_reminder" : "Merci d'écrire votre graine au cas où vous perdriez ou effaceriez votre téléphone", + "seed_title" : "Graine", + "seed_share" : "Partager la graine", "copy" : "Copier", - "seed_language_choose" : "Merci de choisir la langue du seed :", - "seed_choose" : "Choisissez la langue du seed", + "seed_language_choose" : "Merci de choisir la langue de la graine :", + "seed_choose" : "Choisissez la langue de la graine", "seed_language_next" : "Suivant", "seed_language_english" : "Anglais", "seed_language_chinese" : "Chinois", @@ -202,7 +202,7 @@ "send_title" : "Envoyer", - "send_your_wallet" : "Votre wallet", + "send_your_wallet" : "Votre portefeuille", "send_address" : "adresse ${cryptoCurrency}", "send_payment_id" : "ID de paiement (optionnel)", "all" : "TOUT", @@ -251,8 +251,8 @@ "setup_successful" : "Votre code PIN a été configuré avec succès !", - "wallet_keys" : "Seed/Clefs du wallet", - "wallet_seed" : "Seed du wallet", + "wallet_keys" : "Graine/Clefs du portefeuille", + "wallet_seed" : "Graine du portefeuille", "private_key" : "Clef privée", "public_key" : "Clef publique", "view_key_private" : "Clef d'audit (view key) (privée)", @@ -266,7 +266,7 @@ "new_subaddress_label_name" : "Nom", "new_subaddress_create" : "Créer", - "address_label" : "Address label", + "address_label" : "Étiquette de l'adresse", "subaddress_title" : "Liste des sous-adresses", @@ -308,7 +308,7 @@ "widgets_restore_from_blockheight" : "Restaurer depuis une hauteur de bloc", "widgets_restore_from_date" : "Restaurer depuis une date", "widgets_or" : "ou", - "widgets_seed" : "Seed", + "widgets_seed" : "Graine", "router_no_route" : "Aucune route définie pour ${name}", @@ -338,7 +338,7 @@ "auth_store_banned_minutes" : " minutes", "auth_store_incorrect_password" : "Mauvais code PIN", "wallet_store_monero_wallet" : "Portefeuille Monero", - "wallet_restoration_store_incorrect_seed_length" : "Longueur de seed incorrecte", + "wallet_restoration_store_incorrect_seed_length" : "Longueur de graine incorrecte", "full_balance" : "Solde Complet", @@ -387,7 +387,7 @@ "change_language_to" : "Changer la langue vers ${language}?", "paste" : "Coller", - "restore_from_seed_placeholder" : "Merci d'entrer ou de coller votre seed ici", + "restore_from_seed_placeholder" : "Merci d'entrer ou de coller votre graine ici", "add_new_word" : "Ajouter un nouveau mot", "incorrect_seed" : "Le texte entré est invalide.", @@ -406,7 +406,7 @@ "template" : "Modèle", "confirm_delete_template" : "Cette action va supprimer ce modèle. Souhaitez-vous continuer ?", - "confirm_delete_wallet" : "Cette action va supprimer ce wallet. Souhaitez-vous contnuer ?", + "confirm_delete_wallet" : "Cette action va supprimer ce portefeuille. Souhaitez-vous contnuer ?", "picker_description" : "Pour choisir ChangeNOW ou MorphToken, merci de modifier d'abord la paire de votre échange", @@ -417,15 +417,15 @@ "creating_new_wallet_error" : "Erreur : ${description}", "seed_alert_title" : "Attention", - "seed_alert_content" : "Le seed est la seule façon de restaurer votre portefeuille. L'avez-vous correctement écrit ?", + "seed_alert_content" : "La graine est la seule façon de restaurer votre portefeuille. L'avez-vous correctement écrit ?", "seed_alert_back" : "Retour", "seed_alert_yes" : "Oui, je suis sûr", "exchange_sync_alert_content" : "Merci d'attendre que votre portefeuille soit synchronisé", "pre_seed_title" : "IMPORTANT", - "pre_seed_description" : "Sur la page suivante vous allez voir une série de ${words} mots. Ils constituent votre seed unique et privé et sont le SEUL moyen de restaurer votre portefeuille en cas de perte ou de dysfonctionnement. Il est de VOTRE responsabilité d'écrire cette série de mots et de les stocker dans un lieu sûr en dehors de l'application Cake Wallet.", - "pre_seed_button_text" : "J'ai compris. Montrez moi mon seed", + "pre_seed_description" : "Sur la page suivante vous allez voir une série de ${words} mots. Ils constituent votre graine unique et privé et sont le SEUL moyen de restaurer votre portefeuille en cas de perte ou de dysfonctionnement. Il est de VOTRE responsabilité d'écrire cette série de mots et de les stocker dans un lieu sûr en dehors de l'application Cake Wallet.", + "pre_seed_button_text" : "J'ai compris. Montrez moi ma graine", "xmr_to_error" : "Erreur XMR.TO", "xmr_to_error_description" : "Montant invalide. La partie décimale doit contenir au plus 8 chiffres", @@ -484,7 +484,7 @@ "buy_alert_content" : "Pour le moment nous ne supportons que l'achat de Bitcoin et Litecoin. Pour acheter du Bitcoin ou du Litecoin, merci de créer ou de sélectionner votre portefeuille Bitcoin ou Litecoin.", "sell_alert_content": "Pour le moment nous ne supportons que la vente de Bitcoin. Pour vendre du Bitcoin, merci de créer ou de sélectionner votre portefeuille Bitcoint.", - "outdated_electrum_wallet_description" : "Les nouveaux portefeuilles Bitcoin créés dans Cake ont dorénavant un seed de 24 mots. Il est impératif que vous créiez un nouveau portefeuille Bitcoin, que vous y transfériez tous vos fonds puis que vous cessiez d'utiliser le portefeuille avec un seed de 12 mots. Merci de faire cela immédiatement pour assurer la sécurité de vos avoirs.", + "outdated_electrum_wallet_description" : "Les nouveaux portefeuilles Bitcoin créés dans Cake ont dorénavant une graine de 24 mots. Il est impératif que vous créiez un nouveau portefeuille Bitcoin, que vous y transfériez tous vos fonds puis que vous cessiez d'utiliser le portefeuille avec une graine de 12 mots. Merci de faire cela immédiatement pour assurer la sécurité de vos avoirs.", "understand" : "J'ai compris", "apk_update" : "Mise à jour d'APK", @@ -493,7 +493,7 @@ "buy_with" : "Acheter avec", "moonpay_alert_text" : "Le montant doit être au moins égal à ${minAmount} ${fiatCurrency}", - "outdated_electrum_wallet_receive_warning": "Si ce portefeuille a un seed de 12 mots et a été créé dans Cake, NE PAS y déposer de Bitcoin. Tous les BTC transférés vers ce portefeuille seront perdus. Créez un nouveau portefeuille avec seed de 24 mots (appuyez sur le menu en haut à droite, sélectionnez Portefeuilles puis Créer un Nouveau Portefeuille et enfin Bitcoin) et transférez y IMMÉDIATEMENT vos BTC. Les nouveaux portefeuilles BTC Cake (avec seed de 24 mots) sont sécurisés", + "outdated_electrum_wallet_receive_warning": "Si ce portefeuille a une graine de 12 mots et a été créé dans Cake, NE PAS y déposer de Bitcoin. Tous les BTC transférés vers ce portefeuille seront perdus. Créez un nouveau portefeuille avec graine de 24 mots (appuyez sur le menu en haut à droite, sélectionnez Portefeuilles puis Créer un Nouveau Portefeuille et enfin Bitcoin) et transférez y IMMÉDIATEMENT vos BTC. Les nouveaux portefeuilles BTC Cake (avec graine de 24 mots) sont sécurisés", "do_not_show_me": "Ne plus me montrer ceci à l'avenir", "unspent_coins_title" : "Pièces (coins) non dépensées", @@ -519,7 +519,7 @@ "yat_error" : "Erreur Yat", "yat_error_content" : "Aucune adresse associée à ce Yat. Essayez un autre Yat", "choose_address" : "\n\nMerci de choisir l'adresse :", - "yat_popup_title" : "L'adresse de votre wallet peut être emojifiée.", + "yat_popup_title" : "L'adresse de votre portefeuille peut être emojifiée.", "yat_popup_content" : "Vous pouvez à présent envoyer et recevoir des cryptos dans Cake Wallet à l'aide de votre Yat - un nom d'utilisateur court à base d'emoji. Gérér les Yats à tout moment depuis l'écran de paramétrage", "second_intro_title" : "Une adresse emoji pour les gouverner tous", "second_intro_content" : "Votre Yat est une seule et unique adresse emoji qui remplace toutes vos longues adresses hexadécimales pour toutes vos cryptomonnaies.", From 75da854121f213c4e292f95bd5e5151d2146b758 Mon Sep 17 00:00:00 2001 From: Serhii Date: Tue, 3 May 2022 12:58:25 +0300 Subject: [PATCH 23/30] Add collapse button to the address book (#357) * Added collapsible list to the Address Book * fix text style --- .../screens/contact/contact_list_page.dart | 12 ++- .../widgets/collapsible_standart_list.dart | 92 +++++++++++++++++++ lib/src/widgets/standard_list.dart | 49 ++++++++-- 3 files changed, 138 insertions(+), 15 deletions(-) create mode 100644 lib/src/widgets/collapsible_standart_list.dart diff --git a/lib/src/screens/contact/contact_list_page.dart b/lib/src/screens/contact/contact_list_page.dart index dca2afdf8..7a46e99c3 100644 --- a/lib/src/screens/contact/contact_list_page.dart +++ b/lib/src/screens/contact/contact_list_page.dart @@ -12,7 +12,7 @@ import 'package:cw_core/crypto_currency.dart'; import 'package:cake_wallet/src/screens/base_page.dart'; import 'package:cake_wallet/src/widgets/alert_with_two_actions.dart'; import 'package:cake_wallet/view_model/contact_list/contact_list_view_model.dart'; -import 'package:cake_wallet/src/widgets/standard_list.dart'; +import 'package:cake_wallet/src/widgets/collapsible_standart_list.dart'; class ContactListPage extends BasePage { ContactListPage(this.contactListViewModel, {this.isEditable = true}); @@ -62,9 +62,12 @@ class ContactListPage extends BasePage { padding: EdgeInsets.only(top: 20.0, bottom: 20.0), child: Observer( builder: (_) { - return SectionStandardList( + return CollapsibleSectionList( context: context, sectionCount: 2, + themeColor: Theme.of(context).primaryTextTheme.title.color, + dividerThemeColor: + Theme.of(context).primaryTextTheme.caption.decorationColor, sectionTitleBuilder: (_, int sectionIndex) { var title = 'Contacts'; @@ -73,7 +76,7 @@ class ContactListPage extends BasePage { } return Container( - padding: EdgeInsets.only(left: 24, bottom: 20), + padding: EdgeInsets.only(bottom: 10), child: Text(title, style: TextStyle(fontSize: 36))); }, itemCounter: (int sectionIndex) => sectionIndex == 0 @@ -144,11 +147,10 @@ class ContactListPage extends BasePage { child: Container( color: Colors.transparent, padding: - const EdgeInsets.only(left: 24, top: 16, bottom: 16, right: 24), + const EdgeInsets.only(top: 16, bottom: 16, right: 24), child: Row( mainAxisSize: MainAxisSize.min, mainAxisAlignment: MainAxisAlignment.start, - crossAxisAlignment: CrossAxisAlignment.center, children: [ image ?? Offstage(), Expanded( diff --git a/lib/src/widgets/collapsible_standart_list.dart b/lib/src/widgets/collapsible_standart_list.dart new file mode 100644 index 000000000..1f7c5d91f --- /dev/null +++ b/lib/src/widgets/collapsible_standart_list.dart @@ -0,0 +1,92 @@ +import 'package:cake_wallet/src/widgets/standard_list.dart'; +import 'package:flutter/material.dart'; + +class CollapsibleSectionList extends SectionStandardList { + CollapsibleSectionList( + {bool hasTopSeparator, + BuildContext context, + int sectionCount, + int Function(int sectionIndex) itemCounter, + Widget Function(BuildContext context, int sectionIndex, int itemIndex) + itemBuilder, + Widget Function(BuildContext context, int sectionIndex) + sectionTitleBuilder, + Color themeColor, + Color dividerThemeColor}) + : super( + hasTopSeparator: hasTopSeparator, + sectionCount: sectionCount, + itemCounter: itemCounter, + itemBuilder: itemBuilder, + sectionTitleBuilder: sectionTitleBuilder, + themeColor: themeColor, + dividerThemeColor: dividerThemeColor); + + @override + List transform( + bool hasTopSeparator, + BuildContext context, + int sectionCount, + int Function(int sectionIndex) itemCounter, + Widget Function(BuildContext context, int sectionIndex, int itemIndex) + itemBuilder, + Widget Function(BuildContext context, int sectionIndex) + sectionTitleBuilder, + themeColor, + dividerThemeColor) { + final items = []; + + for (var sectionIndex = 0; sectionIndex < sectionCount; sectionIndex++) { + final itemCount = itemCounter(sectionIndex); + + items.add(Theme( + data: ThemeData( + textTheme: TextTheme(subtitle1: TextStyle(color: themeColor,fontFamily: 'Lato')), + backgroundColor: dividerThemeColor, + unselectedWidgetColor: themeColor, + accentColor: themeColor) + .copyWith(dividerColor: Colors.transparent), + child: Padding( + padding: const EdgeInsets.only(left: 24.0), + child: ListTileTheme( + contentPadding: EdgeInsets.only(right: 16,top:sectionIndex>0?26:0), + child: ExpansionTile( + title: sectionTitleBuilder == null + ? Container() + : Container(child: buildTitle(items, sectionIndex, context)), + initiallyExpanded: true, + children: buildSection(itemCount, items, sectionIndex, context), + ), + ), + ), + )); + + } + + items.add(StandardListSeparator(padding: EdgeInsets.only(left: 24))); + return items; + } + + @override + Widget buildTitle( + List items, int sectionIndex, BuildContext context) { + final title = sectionTitleBuilder(context, sectionIndex); + return title; + } + + @override + List buildSection(int itemCount, List items, int sectionIndex, + BuildContext context) { + final List section = []; + + for (var itemIndex = 0; itemIndex < itemCount; itemIndex++) { + final item = itemBuilder(context, sectionIndex, itemIndex); + + section.add(StandardListSeparator()); + + section.add(item); + + } + return section; + } +} diff --git a/lib/src/widgets/standard_list.dart b/lib/src/widgets/standard_list.dart index c9c555e81..c12f1b347 100644 --- a/lib/src/widgets/standard_list.dart +++ b/lib/src/widgets/standard_list.dart @@ -120,9 +120,20 @@ class SectionStandardList extends StatelessWidget { @required this.sectionCount, this.sectionTitleBuilder, this.hasTopSeparator = false, + this.themeColor, + this.dividerThemeColor, BuildContext context}) - : totalRows = transform(hasTopSeparator, context, sectionCount, - itemCounter, itemBuilder, sectionTitleBuilder); + : totalRows = [] { + totalRows.addAll(transform( + hasTopSeparator, + context, + sectionCount, + itemCounter, + itemBuilder, + sectionTitleBuilder, + themeColor, + dividerThemeColor)); + } final int sectionCount; final bool hasTopSeparator; @@ -132,8 +143,10 @@ class SectionStandardList extends StatelessWidget { final Widget Function(BuildContext context, int sectionIndex) sectionTitleBuilder; final List totalRows; + final Color themeColor; + final Color dividerThemeColor; - static List transform( + List transform( bool hasTopSeparator, BuildContext context, int sectionCount, @@ -141,7 +154,9 @@ class SectionStandardList extends StatelessWidget { Widget Function(BuildContext context, int sectionIndex, int itemIndex) itemBuilder, Widget Function(BuildContext context, int sectionIndex) - sectionTitleBuilder) { + sectionTitleBuilder, + Color themeColor, + Color dividerThemeColor) { final items = []; for (var sectionIndex = 0; sectionIndex < sectionCount; sectionIndex++) { @@ -150,16 +165,12 @@ class SectionStandardList extends StatelessWidget { } if (sectionTitleBuilder != null) { - items.add(sectionTitleBuilder(context, sectionIndex)); + items.add(buildTitle(items, sectionIndex, context)); } final itemCount = itemCounter(sectionIndex); - for (var itemIndex = 0; itemIndex < itemCount; itemIndex++) { - final item = itemBuilder(context, sectionIndex, itemIndex); - - items.add(item); - } + items.addAll(buildSection(itemCount, items, sectionIndex, context)); items.add(sectionIndex + 1 != sectionCount ? SectionHeaderListRow() @@ -169,6 +180,24 @@ class SectionStandardList extends StatelessWidget { return items; } + Widget buildTitle( + List items, int sectionIndex, BuildContext context) { + final title = sectionTitleBuilder(context, sectionIndex); + return title; + } + + List buildSection(int itemCount, List items, int sectionIndex, + BuildContext context) { + final List section = []; + + for (var itemIndex = 0; itemIndex < itemCount; itemIndex++) { + final item = itemBuilder(context, sectionIndex, itemIndex); + + section.add(item); + } + return section; + } + @override Widget build(BuildContext context) { return ListView.separated( From c08f2c1667ac4ad2556edd250a67c5feedfedcae Mon Sep 17 00:00:00 2001 From: Serhii Date: Tue, 3 May 2022 13:44:13 +0300 Subject: [PATCH 24/30] Cw 55 allow saving templateswith fiat amount (#343) * added xhv logo * Added prefix icon widget * Added sending fiat template * template for sending selected fiat currency * fix issues --- assets/images/xhv_logo.png | Bin 0 -> 20464 bytes lib/di.dart | 3 +- lib/entities/template.dart | 14 +++- .../exchange/widgets/currency_utils.dart | 2 + lib/src/screens/send/send_page.dart | 24 +++++-- lib/src/screens/send/send_template_page.dart | 65 ++++++++++-------- .../widgets/prefix_currency_icon_widget.dart | 32 +++++++++ lib/store/templates/send_template_store.dart | 6 +- .../send/send_template_view_model.dart | 28 +++++++- 9 files changed, 135 insertions(+), 39 deletions(-) create mode 100644 assets/images/xhv_logo.png create mode 100644 lib/src/screens/send/widgets/prefix_currency_icon_widget.dart diff --git a/assets/images/xhv_logo.png b/assets/images/xhv_logo.png new file mode 100644 index 0000000000000000000000000000000000000000..118c920fdc4494b0f3c7b53dc493d39634fb9d7e GIT binary patch literal 20464 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 D6CZ;d literal 0 HcmV?d00001 diff --git a/lib/di.dart b/lib/di.dart index dc6ec367d..c066b6cf1 100644 --- a/lib/di.dart +++ b/lib/di.dart @@ -343,7 +343,8 @@ Future setup( _transactionDescriptionBox)); getIt.registerFactory( - () => SendPage(sendViewModel: getIt.get())); + () => SendPage(sendViewModel: getIt.get(), + settingsViewModel: getIt.get())); getIt.registerFactory(() => SendTemplatePage( sendTemplateViewModel: getIt.get())); diff --git a/lib/entities/template.dart b/lib/entities/template.dart index b64910699..c37197e91 100644 --- a/lib/entities/template.dart +++ b/lib/entities/template.dart @@ -4,7 +4,7 @@ part 'template.g.dart'; @HiveType(typeId: Template.typeId) class Template extends HiveObject { - Template({this.name, this.address, this.cryptoCurrency, this.amount}); + Template({this.name,this.isCurrencySelected, this.address, this.cryptoCurrency, this.amount, this.fiatCurrency, this.amountFiat}); static const typeId = 6; static const boxName = 'Template'; @@ -20,4 +20,14 @@ class Template extends HiveObject { @HiveField(3) String amount; -} \ No newline at end of file + + @HiveField(4) + String fiatCurrency; + + @HiveField(5) + bool isCurrencySelected; + + @HiveField(6) + String amountFiat; +} + diff --git a/lib/src/screens/exchange/widgets/currency_utils.dart b/lib/src/screens/exchange/widgets/currency_utils.dart index aaf9ea57c..83c6d0af9 100644 --- a/lib/src/screens/exchange/widgets/currency_utils.dart +++ b/lib/src/screens/exchange/widgets/currency_utils.dart @@ -48,6 +48,8 @@ class CurrencyUtils { return 'assets/images/xlm_icon.png'; case CryptoCurrency.xrp: return 'assets/images/xrp_icon.png'; + case CryptoCurrency.xhv: + return 'assets/images/xhv_logo.png'; default: return null; } diff --git a/lib/src/screens/send/send_page.dart b/lib/src/screens/send/send_page.dart index 070e452f1..791548f3d 100644 --- a/lib/src/screens/send/send_page.dart +++ b/lib/src/screens/send/send_page.dart @@ -1,10 +1,12 @@ import 'dart:ui'; +import 'package:cake_wallet/entities/fiat_currency.dart'; import 'package:cake_wallet/src/screens/dashboard/widgets/sync_indicator_icon.dart'; import 'package:cake_wallet/src/screens/send/widgets/send_card.dart'; import 'package:cake_wallet/src/widgets/alert_with_two_actions.dart'; import 'package:cake_wallet/src/widgets/picker.dart'; import 'package:cake_wallet/src/widgets/template_tile.dart'; import 'package:cake_wallet/view_model/send/output.dart'; +import 'package:cake_wallet/view_model/settings/settings_view_model.dart'; import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; import 'package:flutter_mobx/flutter_mobx.dart'; @@ -26,11 +28,13 @@ import 'package:smooth_page_indicator/smooth_page_indicator.dart'; import 'package:cw_core/crypto_currency.dart'; class SendPage extends BasePage { - SendPage({@required this.sendViewModel}) : _formKey = GlobalKey(); + SendPage({@required this.sendViewModel,@required this.settingsViewModel }) : _formKey = GlobalKey(),fiatFromSettings = settingsViewModel.fiatCurrency; final SendViewModel sendViewModel; + final SettingsViewModel settingsViewModel; final GlobalKey _formKey; final controller = PageController(initialPage: 0); + final FiatCurrency fiatFromSettings ; bool _effectsInstalled = false; @@ -49,6 +53,12 @@ class SendPage extends BasePage { @override AppBarStyle get appBarStyle => AppBarStyle.transparent; + @override + void onClose(BuildContext context) { + settingsViewModel.setFiatCurrency(fiatFromSettings); + Navigator.of(context).pop(); + } + @override Widget middle(BuildContext context) => Row( mainAxisAlignment: MainAxisAlignment.center, @@ -212,12 +222,18 @@ class SendPage extends BasePage { return TemplateTile( key: UniqueKey(), to: template.name, - amount: template.amount, - from: template.cryptoCurrency, + amount: template.isCurrencySelected ? template.amount : template.amountFiat, + from: template.isCurrencySelected ? template.cryptoCurrency : template.fiatCurrency, onTap: () async { + final fiatFromTemplate = FiatCurrency.all.singleWhere((element) => element.title == template.fiatCurrency); final output = _defineCurrentOutput(); output.address = template.address; - output.setCryptoAmount(template.amount); + if(template.isCurrencySelected){ + output.setCryptoAmount(template.amount); + }else{ + settingsViewModel.setFiatCurrency(fiatFromTemplate); + output.setFiatAmount(template.amountFiat); + } output.resetParsedAddress(); await output.fetchParsedAddress(context); }, diff --git a/lib/src/screens/send/send_template_page.dart b/lib/src/screens/send/send_template_page.dart index a3e0b8928..9170bccfe 100644 --- a/lib/src/screens/send/send_template_page.dart +++ b/lib/src/screens/send/send_template_page.dart @@ -1,4 +1,5 @@ import 'package:cake_wallet/utils/payment_request.dart'; +import 'package:flutter_mobx/flutter_mobx.dart'; import 'package:mobx/mobx.dart'; import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; @@ -12,6 +13,7 @@ import 'package:cake_wallet/src/widgets/base_text_form_field.dart'; import 'package:cake_wallet/src/widgets/keyboard_done_button.dart'; import 'package:cake_wallet/src/widgets/primary_button.dart'; import 'package:cake_wallet/src/widgets/scollable_with_bottom_section.dart'; +import 'package:cake_wallet/src/screens/send/widgets/prefix_currency_icon_widget.dart'; class SendTemplatePage extends BasePage { SendTemplatePage({@required this.sendTemplateViewModel}) { @@ -142,7 +144,13 @@ class SendTemplatePage extends BasePage { ), Padding( padding: const EdgeInsets.only(top: 20), - child: BaseTextFormField( + child: Focus( + onFocusChange: (hasFocus) { + if (hasFocus) { + sendTemplateViewModel.selectCurrency(); + } + }, + child: BaseTextFormField( focusNode: _cryptoAmountFocus, controller: _cryptoAmountController, keyboardType: TextInputType.numberWithOptions( @@ -151,17 +159,14 @@ class SendTemplatePage extends BasePage { FilteringTextInputFormatter.deny( RegExp('[\\-|\\ ]')) ], - prefixIcon: Padding( - padding: EdgeInsets.only(top: 9), - child: Text( - sendTemplateViewModel.currency.title + - ':', - style: TextStyle( - fontSize: 16, - fontWeight: FontWeight.w600, - color: Colors.white, - )), - ), + prefixIcon: Observer( + builder: (_) => PrefixCurrencyIcon( + title: sendTemplateViewModel + .currency.title, + isSelected: + sendTemplateViewModel + .isCurrencySelected, + )), hintText: '0.0000', borderColor: Theme.of(context) .primaryTextTheme @@ -179,10 +184,16 @@ class SendTemplatePage extends BasePage { fontWeight: FontWeight.w500, fontSize: 14), validator: - sendTemplateViewModel.amountValidator)), + sendTemplateViewModel.amountValidator))), Padding( padding: const EdgeInsets.only(top: 20), - child: BaseTextFormField( + child: Focus( + onFocusChange: (hasFocus) { + if (hasFocus) { + sendTemplateViewModel.selectFiat(); + } + }, + child: BaseTextFormField( focusNode: _fiatAmountFocus, controller: _fiatAmountController, keyboardType: TextInputType.numberWithOptions( @@ -191,16 +202,13 @@ class SendTemplatePage extends BasePage { FilteringTextInputFormatter.deny( RegExp('[\\-|\\ ]')) ], - prefixIcon: Padding( - padding: EdgeInsets.only(top: 9), - child: Text( - sendTemplateViewModel.fiat.title + ':', - style: TextStyle( - fontSize: 16, - fontWeight: FontWeight.w600, - color: Colors.white, - )), - ), + prefixIcon: Observer( + builder: (_) => PrefixCurrencyIcon( + title: sendTemplateViewModel + .fiat.title, + isSelected: sendTemplateViewModel + .isFiatSelected, + )), hintText: '0.00', borderColor: Theme.of(context) .primaryTextTheme @@ -217,7 +225,7 @@ class SendTemplatePage extends BasePage { .decorationColor, fontWeight: FontWeight.w500, fontSize: 14), - )), + ))), ], ), ) @@ -231,10 +239,13 @@ class SendTemplatePage extends BasePage { onPressed: () { if (_formKey.currentState.validate()) { sendTemplateViewModel.addTemplate( + isCurrencySelected: sendTemplateViewModel.isCurrencySelected, name: _nameController.text, address: _addressController.text, - cryptoCurrency: sendTemplateViewModel.currency.title, - amount: _cryptoAmountController.text); + cryptoCurrency:sendTemplateViewModel.currency.title, + fiatCurrency: sendTemplateViewModel.fiat.title, + amount: _cryptoAmountController.text, + amountFiat: _fiatAmountController.text); Navigator.of(context).pop(); } }, diff --git a/lib/src/screens/send/widgets/prefix_currency_icon_widget.dart b/lib/src/screens/send/widgets/prefix_currency_icon_widget.dart new file mode 100644 index 000000000..030c1e652 --- /dev/null +++ b/lib/src/screens/send/widgets/prefix_currency_icon_widget.dart @@ -0,0 +1,32 @@ +import 'package:flutter/material.dart'; + +class PrefixCurrencyIcon extends StatelessWidget { + PrefixCurrencyIcon({ + @required this.isSelected, + @required this.title, + }); + + final bool isSelected; + final String title; + + @override + Widget build(BuildContext context) { + return Padding( + padding: EdgeInsets.fromLTRB(0, 6.0, 8.0, 0), + child: Column(children: [ + Container( + padding: EdgeInsets.symmetric(vertical: 4, horizontal: 8), + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(26), + color: isSelected ? Colors.green : Colors.transparent, + ), + child: Text(title + ':', + style: TextStyle( + fontSize: 16, + fontWeight: FontWeight.w600, + color: Colors.white, + )), + ) + ])); + } +} diff --git a/lib/store/templates/send_template_store.dart b/lib/store/templates/send_template_store.dart index b65362a1c..46d0dc451 100644 --- a/lib/store/templates/send_template_store.dart +++ b/lib/store/templates/send_template_store.dart @@ -23,9 +23,9 @@ abstract class SendTemplateBase with Store { templates.replaceRange(0, templates.length, templateSource.values.toList()); @action - Future addTemplate({String name, String address, String cryptoCurrency, String amount}) async { - final template = Template(name: name, address: address, - cryptoCurrency: cryptoCurrency, amount: amount); + Future addTemplate({String name,bool isCurrencySelected, String address, String cryptoCurrency, String fiatCurrency, String amount,String amountFiat}) async { + final template = Template(name: name,isCurrencySelected: isCurrencySelected, address: address, + cryptoCurrency: cryptoCurrency, fiatCurrency: fiatCurrency, amount: amount, amountFiat: amountFiat); await templateSource.add(template); } diff --git a/lib/view_model/send/send_template_view_model.dart b/lib/view_model/send/send_template_view_model.dart index e52022f8d..7ca2f39b4 100644 --- a/lib/view_model/send/send_template_view_model.dart +++ b/lib/view_model/send/send_template_view_model.dart @@ -36,6 +36,24 @@ abstract class SendTemplateViewModelBase with Store { FiatCurrency get fiat => _settingsStore.fiatCurrency; + @observable + bool isCurrencySelected = true; + + @observable + bool isFiatSelected = false; + + @action + void selectCurrency () { + isCurrencySelected = true; + isFiatSelected = false; + } + + @action + void selectFiat () { + isFiatSelected = true; + isCurrencySelected = false; + } + @computed ObservableList