From 92cefe41861ff1751e16ddeaa0e338a6315fdd5a Mon Sep 17 00:00:00 2001 From: Andrei Ashikhmin Date: Tue, 26 Sep 2023 22:41:31 +0700 Subject: [PATCH 01/28] fix: import QR private key crash (#1206) --- .../wallet/ui/ImportSharedImageActivity.kt | 76 +++++++++---------- 1 file changed, 36 insertions(+), 40 deletions(-) diff --git a/wallet/src/de/schildbach/wallet/ui/ImportSharedImageActivity.kt b/wallet/src/de/schildbach/wallet/ui/ImportSharedImageActivity.kt index 0acfd8706b..f8e02fd19e 100644 --- a/wallet/src/de/schildbach/wallet/ui/ImportSharedImageActivity.kt +++ b/wallet/src/de/schildbach/wallet/ui/ImportSharedImageActivity.kt @@ -25,12 +25,11 @@ import com.bumptech.glide.Glide import com.bumptech.glide.request.target.CustomTarget import com.bumptech.glide.request.transition.Transition import com.google.zxing.* -import com.google.zxing.common.HybridBinarizer import de.schildbach.wallet.WalletApplication import de.schildbach.wallet.data.PaymentIntent -import de.schildbach.wallet.ui.util.InputParser.StringInputParser import de.schildbach.wallet.ui.payments.SweepWalletActivity import de.schildbach.wallet.ui.send.SendCoinsActivity +import de.schildbach.wallet.ui.util.InputParser.StringInputParser import de.schildbach.wallet_test.R import org.bitcoinj.core.PrefixedChecksummedBytes import org.bitcoinj.core.Transaction @@ -83,46 +82,40 @@ class ImportSharedImageActivity : AppCompatActivity() { val imageUri = intent.getParcelableExtra(Intent.EXTRA_STREAM) if (imageUri != null) { Glide.with(this) - .asBitmap() - .load(imageUri).into(object : CustomTarget() { - - override fun onResourceReady(resource: Bitmap, transition: Transition?) { - val qrCode = Qr.scanQRImage(resource) - if (qrCode != null) { - handleQRCode(qrCode) - } else { - log.info("no QR code found in image {}", imageUri) - showErrorDialog( - R.string.import_image_not_valid_qr_code, - R.string.import_image_please_use_valid_qr_code, - R.drawable.ic_not_valid_qr_code) - } + .asBitmap() + .load(imageUri).into(object : CustomTarget() { + override fun onResourceReady(resource: Bitmap, transition: Transition?) { + val qrCode = Qr.scanQRImage(resource) + if (qrCode != null) { + handleQRCode(qrCode) + } else { + log.info("no QR code found in image {}", imageUri) + AdaptiveDialog.create( + R.drawable.ic_not_valid_qr_code, + getString(R.string.import_image_not_valid_qr_code), + getString(R.string.import_image_please_use_valid_qr_code), + getString(R.string.button_ok) + ).show(this@ImportSharedImageActivity) } + } - override fun onLoadFailed(errorDrawable: Drawable?) { - log.info("load image failed {}", imageUri) - showErrorDialog( - R.string.import_image_not_valid_qr_code, - R.string.import_image_please_use_valid_qr_code, - R.drawable.ic_not_valid_qr_code) - } + override fun onLoadFailed(errorDrawable: Drawable?) { + log.info("load image failed {}", imageUri) + AdaptiveDialog.create( + R.drawable.ic_not_valid_qr_code, + getString(R.string.import_image_not_valid_qr_code), + getString(R.string.import_image_please_use_valid_qr_code), + getString(R.string.button_ok) + ).show(this@ImportSharedImageActivity) + } - override fun onLoadCleared(placeholder: Drawable?) { - // nothing to do - } - }) + override fun onLoadCleared(placeholder: Drawable?) { + // nothing to do + } + }) } } - private fun showErrorDialog(title: Int, msg: Int, image: Int) { - AdaptiveDialog.create( - image, - getString(title), - getString(msg), - getString(R.string.button_ok) - ).show(this) - } - private fun handleQRCode(input: String) { object : StringInputParser(input, true) { override fun handlePaymentIntent(paymentIntent: PaymentIntent) { @@ -142,9 +135,12 @@ class ImportSharedImageActivity : AppCompatActivity() { } override fun error(x: Exception?, messageResId: Int, vararg messageArgs: Any) { - showErrorDialog( - R.string.import_image_invalid_private, 0, - R.drawable.ic_not_valid_qr_code) + AdaptiveDialog.create( + R.drawable.ic_not_valid_qr_code, + getString(R.string.import_image_invalid_private), + getString(messageResId, *messageArgs), + getString(R.string.button_ok) + ).show(this@ImportSharedImageActivity) } }.parse() } @@ -152,4 +148,4 @@ class ImportSharedImageActivity : AppCompatActivity() { companion object { private val log = LoggerFactory.getLogger(ImportSharedImageActivity::class.java) } -} \ No newline at end of file +} From 73c0b2a969a52e1807fbfffe5d82e5218a94372c Mon Sep 17 00:00:00 2001 From: Andrei Ashikhmin Date: Tue, 26 Sep 2023 22:42:08 +0700 Subject: [PATCH 02/28] fix: adjust font size if overflow on shortcut buttons (#1207) --- wallet/res/layout/home_content.xml | 5 ++--- wallet/res/layout/shortcut_button.xml | 5 ++++- .../schildbach/wallet/ui/widget/ShortcutsPane.kt | 14 ++++---------- 3 files changed, 10 insertions(+), 14 deletions(-) diff --git a/wallet/res/layout/home_content.xml b/wallet/res/layout/home_content.xml index e6ae58c709..00a3a59e47 100644 --- a/wallet/res/layout/home_content.xml +++ b/wallet/res/layout/home_content.xml @@ -91,11 +91,10 @@ diff --git a/wallet/res/layout/shortcut_button.xml b/wallet/res/layout/shortcut_button.xml index 07677f2a75..cd779b2d49 100644 --- a/wallet/res/layout/shortcut_button.xml +++ b/wallet/res/layout/shortcut_button.xml @@ -32,7 +32,10 @@ android:lineSpacingMultiplier="0.9" android:maxLines="2" android:minLines="1" - tools:ignore="SmallSp" + android:autoSizeTextType="uniform" + android:autoSizeMinTextSize="10sp" + android:autoSizeMaxTextSize="12sp" + android:autoSizeStepGranularity="1sp" tools:text="Send" /> \ No newline at end of file diff --git a/wallet/src/de/schildbach/wallet/ui/widget/ShortcutsPane.kt b/wallet/src/de/schildbach/wallet/ui/widget/ShortcutsPane.kt index ac1292b3c1..64976b8551 100644 --- a/wallet/src/de/schildbach/wallet/ui/widget/ShortcutsPane.kt +++ b/wallet/src/de/schildbach/wallet/ui/widget/ShortcutsPane.kt @@ -81,15 +81,6 @@ class ShortcutsPane(context: Context, attrs: AttributeSet) : FlexboxLayout(conte this ) } - val configButton: ShortcutButton by lazy { - ShortcutButton( - context, - R.drawable.ic_shortcut_add, - R.string.shortcut_add_shortcut, - this - ) - } - val explore: ShortcutButton by lazy { ShortcutButton( context, @@ -170,7 +161,10 @@ class ShortcutsPane(context: Context, attrs: AttributeSet) : FlexboxLayout(conte private fun addShortcut(shortcut: ShortcutButton) { if (!children.contains(shortcut)) { val index = min(childCount, shortcuts.indexOf(shortcut)) - val layoutParams = ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT) + val layoutParams = ViewGroup.LayoutParams( + ViewGroup.LayoutParams.MATCH_PARENT, + ViewGroup.LayoutParams.MATCH_PARENT + ) addView(shortcut, index, layoutParams) } } From 421c2da61785854b0dbc0c7174b41c4f93b2e4f9 Mon Sep 17 00:00:00 2001 From: Andrei Ashikhmin Date: Tue, 3 Oct 2023 22:20:57 +0700 Subject: [PATCH 03/28] feat(coinbase): transition to v3 Buy API and other refactoring (#1212) * chore: refactor to host Coinbase in MainActivity * feat: switch Buy Dash endpoints * fix: tests * fix: increase java version for GitHub Action Workflows * fix: increase java version for ktlint CI --- .github/workflows/dashwallet.yml | 2 +- .github/workflows/ktlint.yml | 2 +- .github/workflows/manual_distribution.yml | 2 +- build.gradle | 2 +- .../dash/wallet/common/ui/LockScreenAware.kt | 22 ++ .../common/ui/dialogs/AdaptiveDialog.kt | 13 +- .../org/dash/wallet/common/util/Constants.kt | 1 - .../dash/wallet/common/util/MonetaryExt.kt | 2 +- .../src/main}/res/drawable/ic_relogin.xml | 44 +-- common/src/main/res/values/strings.xml | 1 + gradle/wrapper/gradle-wrapper.properties | 4 +- integrations/coinbase/build.gradle | 6 +- integrations/coinbase/proguard-rules.pro | 6 +- .../coinbase}/ExampleUnitTest.kt | 0 .../coinbase_integration/model/BuyOrder.kt | 126 -------- .../model/CoinBaseUserAccountInfo.kt | 133 --------- .../ui/CoinbaseBuyDashFragment.kt | 164 ----------- .../ui/CoinbaseBuyDashOrderReviewFragment.kt | 272 ------------------ .../ui/CoinbaseServicesFragment.kt | 240 ---------------- .../model/BaseIdForFaitDataUIState.kt | 9 - .../viewmodels/CoinbaseActivityViewModel.kt | 179 ------------ .../CoinbaseBuyDashOrderReviewViewModel.kt | 141 --------- .../viewmodels/CoinbaseBuyDashViewModel.kt | 195 ------------- .../coinbase}/CoinbaseConstants.kt | 13 +- .../coinbase}/Mapper.kt | 34 +-- .../coinbase}/di/CoinBaseModule.kt | 24 +- .../coinbase/model/AccountsResponse.kt | 60 ++++ .../coinbase}/model/AddressesResponse.kt | 4 +- .../coinbase}/model/BaseIdForUSDModel.kt | 2 +- .../model/CoinBaseAccountAddressResponse.kt | 32 ++- .../coinbase}/model/CoinBaseExchangeRate.kt | 2 +- .../coinbase}/model/CoinBaseExchangeRates.kt | 2 +- .../coinbase/model/CoinBaseUserAccountInfo.kt | 72 +++++ .../coinbase}/model/CoinbaseErrorResponse.kt | 14 +- .../coinbase/model/CoinbaseInfraModels.kt | 34 +++ .../coinbase}/model/CoinbasePaymentMethod.kt | 2 +- .../coinbase/model/DepositRequest.kt | 30 ++ .../coinbase/model/DepositResponse.kt | 43 +++ .../coinbase}/model/PaymentMethods.kt | 20 +- .../coinbase/model/PlaceOrderParams.kt | 34 +++ .../coinbase/model/PlaceOrderResponse.kt | 60 ++++ .../coinbase/model/ProductsResponse.kt | 44 +++ .../model/SendTransactionToWallet.kt | 25 +- .../coinbase}/model/SwapTradeResponse.kt | 2 +- .../coinbase}/model/TokenResponse.kt | 2 +- .../coinbase}/model/TradesRequest.kt | 2 +- .../model/UserAuthorizationInfoResponse.kt | 2 +- .../coinbase}/network/RemoteDataSource.kt | 15 +- .../repository/CoinBaseRepository.kt | 208 ++++++++------ .../remote/CustomCacheInterceptor.kt | 8 +- .../repository/remote/HeadersInterceptor.kt | 4 +- .../repository/remote/TokenAuthenticator.kt | 9 +- .../coinbase}/service/CoinBaseAuthApi.kt | 4 +- .../service/CoinBaseClientConstants.kt | 4 +- .../coinbase}/service/CoinBaseServicesApi.kt | 41 ++- .../service/CoinBaseTokenRefreshApi.kt | 4 +- .../coinbase/ui}/CoinBaseWebClientActivity.kt | 17 +- .../coinbase/ui/CoinbaseBuyDashFragment.kt | 170 +++++++++++ .../ui/CoinbaseConversionPreviewFragment.kt | 22 +- .../ui/CoinbaseConvertCryptoFragment.kt | 79 +++-- .../ui/CoinbaseOrderReviewFragment.kt | 189 ++++++++++++ .../coinbase/ui/CoinbaseServicesFragment.kt | 214 ++++++++++++++ .../ui/EnterAmountToTransferFragment.kt | 16 +- .../coinbase}/ui/EnterTwoFaCodeFragment.kt | 32 ++- .../coinbase}/ui/TransferDashFragment.kt | 55 ++-- .../ui/convert_currency/ConvertView.kt | 8 +- .../convert_currency/ConvertViewFragment.kt | 189 ++++++------ .../ui/convert_currency/CryptoConvertItem.kt | 6 +- .../ui/convert_currency/TransferView.kt | 10 +- .../model/BaseIdForFiatData.kt | 25 ++ .../model/PaymentMethodsUiState.kt | 4 +- .../convert_currency/model/ServiceWallet.kt | 4 +- .../ui/convert_currency/model/SwapRequest.kt | 2 +- .../model/SwapValueErrorType.kt | 2 +- .../ui/dialogs/CoinBaseResultDialog.kt | 32 ++- .../ui/dialogs/CoinbaseFeeInfoDialog.kt | 13 +- .../crypto_wallets/CryptoWalletsDialog.kt | 24 +- .../CryptoWalletsDialogViewModel.kt | 4 +- .../coinbase}/utils/CoinbaseConfig.kt | 3 +- .../viewmodels/CoinbaseBuyDashViewModel.kt | 197 +++++++++++++ .../CoinbaseConversionPreviewViewModel.kt | 15 +- .../CoinbaseConvertCryptoViewModel.kt | 87 +++--- .../viewmodels/CoinbaseServicesViewModel.kt | 71 ++--- .../coinbase/viewmodels/CoinbaseViewModel.kt | 137 +++++++++ .../viewmodels/ConvertViewViewModel.kt | 46 ++- .../coinbase/viewmodels/Delegate.kt | 27 ++ .../EnterAmountToTransferViewModel.kt | 21 +- .../viewmodels/EnterTwoFaCodeViewModel.kt | 32 +-- .../viewmodels/TransferDashViewModel.kt | 187 ++++++------ .../content_review_buy_order_coinbase.xml | 1 + .../src/main/res/layout/convert_view.xml | 6 +- .../res/layout/fragment_coinbase_buy_dash.xml | 8 - .../fragment_coinbase_convert_crypto.xml | 2 +- ...xml => fragment_coinbase_order_review.xml} | 0 .../main/res/layout/item_cyrpto_convert.xml | 2 +- .../res/layout/transfer_dash_fragment.xml | 2 +- .../src/main/res/navigation/nav_coinbase.xml | 50 ++-- .../src/main/res/values-xhdpi/dimens.xml | 2 +- .../src/main/res/values-xxhdpi/dimens.xml | 2 +- .../coinbase/src/main/res/values/strings.xml | 21 +- .../coinbase}/CoinBaseRepositoryTest.kt | 45 +-- .../coinbase}/TestUtils.kt | 20 +- integrations/uphold/build.gradle | 2 +- integrations/uphold/proguard-rules.pro | 6 +- .../uphold/api/RemoteDataSource.kt | 2 +- .../uphold/api/TopperClient.kt | 4 +- .../uphold/api/UpholdClient.java | 10 +- .../uphold/api/UpholdClientExt.kt | 10 +- .../uphold/api/UpholdService.kt | 12 +- .../currencyModel/UpholdCurrencyResponse.kt | 2 +- .../uphold/data/ForbiddenError.kt | 4 +- .../uphold/data/RequirementsCheckResult.kt | 2 +- .../uphold/data/SupportedTopperAssets.kt | 2 +- .../uphold/data/UpholdAccessToken.java | 2 +- .../uphold/data/UpholdAddress.java | 2 +- .../uphold/data/UpholdApiException.java | 4 +- .../uphold/data/UpholdBankAccount.java | 2 +- .../uphold/data/UpholdCapability.kt | 2 +- .../uphold/data/UpholdCard.java | 2 +- .../uphold/data/UpholdCardAddress.kt | 2 +- .../uphold/data/UpholdConstants.kt | 2 +- .../uphold/data/UpholdCryptoCardAddress.java | 2 +- .../uphold/data/UpholdException.java | 2 +- .../uphold/data/UpholdTransaction.java | 2 +- .../uphold/ui/UpholdOtpDialog.java | 6 +- .../uphold/ui/UpholdPortalFragment.kt | 8 +- .../uphold/ui/UpholdTransferActivity.kt | 12 +- .../uphold/ui/UpholdViewModel.kt | 22 +- .../uphold/ui/UpholdWithdrawalHelper.java | 18 +- .../uphold/UpholdClientTest.kt | 8 +- .../uphold/UpholdErrorTest.kt | 4 +- wallet/AndroidManifest.xml | 9 +- wallet/res/layout/activity_coinbase.xml | 32 --- wallet/res/navigation/nav_home.xml | 23 +- wallet/res/values/strings.xml | 3 - .../schildbach/wallet/WalletApplication.java | 9 +- .../src/de/schildbach/wallet/di/AppModule.kt | 2 +- .../schildbach/wallet/di/IntegrationModule.kt | 2 +- .../wallet/ui/LockScreenActivity.kt | 11 +- .../wallet/ui/WalletUriHandlerActivity.java | 2 +- .../BuyAndSellIntegrationsFragment.kt | 14 +- .../wallet/ui/buy_sell/BuyAndSellViewModel.kt | 32 +-- .../buy_sell/IntegrationOverviewFragment.kt | 9 +- .../buy_sell/IntegrationOverviewViewModel.kt | 4 +- .../wallet/ui/coinbase/CoinbaseActivity.kt | 143 --------- 145 files changed, 2309 insertions(+), 2667 deletions(-) create mode 100644 common/src/main/java/org/dash/wallet/common/ui/LockScreenAware.kt rename {wallet => common/src/main}/res/drawable/ic_relogin.xml (82%) rename integrations/coinbase/src/androidTest/java/org/dash/wallet/{integration/coinbase_integration => integrations/coinbase}/ExampleUnitTest.kt (100%) delete mode 100644 integrations/coinbase/src/main/java/org/dash/wallet/integration/coinbase_integration/model/BuyOrder.kt delete mode 100644 integrations/coinbase/src/main/java/org/dash/wallet/integration/coinbase_integration/model/CoinBaseUserAccountInfo.kt delete mode 100644 integrations/coinbase/src/main/java/org/dash/wallet/integration/coinbase_integration/ui/CoinbaseBuyDashFragment.kt delete mode 100644 integrations/coinbase/src/main/java/org/dash/wallet/integration/coinbase_integration/ui/CoinbaseBuyDashOrderReviewFragment.kt delete mode 100644 integrations/coinbase/src/main/java/org/dash/wallet/integration/coinbase_integration/ui/CoinbaseServicesFragment.kt delete mode 100644 integrations/coinbase/src/main/java/org/dash/wallet/integration/coinbase_integration/ui/convert_currency/model/BaseIdForFaitDataUIState.kt delete mode 100644 integrations/coinbase/src/main/java/org/dash/wallet/integration/coinbase_integration/viewmodels/CoinbaseActivityViewModel.kt delete mode 100644 integrations/coinbase/src/main/java/org/dash/wallet/integration/coinbase_integration/viewmodels/CoinbaseBuyDashOrderReviewViewModel.kt delete mode 100644 integrations/coinbase/src/main/java/org/dash/wallet/integration/coinbase_integration/viewmodels/CoinbaseBuyDashViewModel.kt rename integrations/coinbase/src/main/java/org/dash/wallet/{integration/coinbase_integration => integrations/coinbase}/CoinbaseConstants.kt (75%) rename integrations/coinbase/src/main/java/org/dash/wallet/{integration/coinbase_integration => integrations/coinbase}/Mapper.kt (61%) rename integrations/coinbase/src/main/java/org/dash/wallet/{integration/coinbase_integration => integrations/coinbase}/di/CoinBaseModule.kt (66%) create mode 100644 integrations/coinbase/src/main/java/org/dash/wallet/integrations/coinbase/model/AccountsResponse.kt rename integrations/coinbase/src/main/java/org/dash/wallet/{integration/coinbase_integration => integrations/coinbase}/model/AddressesResponse.kt (91%) rename integrations/coinbase/src/main/java/org/dash/wallet/{integration/coinbase_integration => integrations/coinbase}/model/BaseIdForUSDModel.kt (94%) rename integrations/coinbase/src/main/java/org/dash/wallet/{integration/coinbase_integration => integrations/coinbase}/model/CoinBaseAccountAddressResponse.kt (65%) rename integrations/coinbase/src/main/java/org/dash/wallet/{integration/coinbase_integration => integrations/coinbase}/model/CoinBaseExchangeRate.kt (94%) rename integrations/coinbase/src/main/java/org/dash/wallet/{integration/coinbase_integration => integrations/coinbase}/model/CoinBaseExchangeRates.kt (94%) create mode 100644 integrations/coinbase/src/main/java/org/dash/wallet/integrations/coinbase/model/CoinBaseUserAccountInfo.kt rename integrations/coinbase/src/main/java/org/dash/wallet/{integration/coinbase_integration => integrations/coinbase}/model/CoinbaseErrorResponse.kt (82%) create mode 100644 integrations/coinbase/src/main/java/org/dash/wallet/integrations/coinbase/model/CoinbaseInfraModels.kt rename integrations/coinbase/src/main/java/org/dash/wallet/{integration/coinbase_integration => integrations/coinbase}/model/CoinbasePaymentMethod.kt (93%) create mode 100644 integrations/coinbase/src/main/java/org/dash/wallet/integrations/coinbase/model/DepositRequest.kt create mode 100644 integrations/coinbase/src/main/java/org/dash/wallet/integrations/coinbase/model/DepositResponse.kt rename integrations/coinbase/src/main/java/org/dash/wallet/{integration/coinbase_integration => integrations/coinbase}/model/PaymentMethods.kt (70%) create mode 100644 integrations/coinbase/src/main/java/org/dash/wallet/integrations/coinbase/model/PlaceOrderParams.kt create mode 100644 integrations/coinbase/src/main/java/org/dash/wallet/integrations/coinbase/model/PlaceOrderResponse.kt create mode 100644 integrations/coinbase/src/main/java/org/dash/wallet/integrations/coinbase/model/ProductsResponse.kt rename integrations/coinbase/src/main/java/org/dash/wallet/{integration/coinbase_integration => integrations/coinbase}/model/SendTransactionToWallet.kt (78%) rename integrations/coinbase/src/main/java/org/dash/wallet/{integration/coinbase_integration => integrations/coinbase}/model/SwapTradeResponse.kt (97%) rename integrations/coinbase/src/main/java/org/dash/wallet/{integration/coinbase_integration => integrations/coinbase}/model/TokenResponse.kt (94%) rename integrations/coinbase/src/main/java/org/dash/wallet/{integration/coinbase_integration => integrations/coinbase}/model/TradesRequest.kt (93%) rename integrations/coinbase/src/main/java/org/dash/wallet/{integration/coinbase_integration => integrations/coinbase}/model/UserAuthorizationInfoResponse.kt (91%) rename integrations/coinbase/src/main/java/org/dash/wallet/{integration/coinbase_integration => integrations/coinbase}/network/RemoteDataSource.kt (82%) rename integrations/coinbase/src/main/java/org/dash/wallet/{integration/coinbase_integration => integrations/coinbase}/repository/CoinBaseRepository.kt (63%) rename integrations/coinbase/src/main/java/org/dash/wallet/{integration/coinbase_integration => integrations/coinbase}/repository/remote/CustomCacheInterceptor.kt (92%) rename integrations/coinbase/src/main/java/org/dash/wallet/{integration/coinbase_integration => integrations/coinbase}/repository/remote/HeadersInterceptor.kt (90%) rename integrations/coinbase/src/main/java/org/dash/wallet/{integration/coinbase_integration => integrations/coinbase}/repository/remote/TokenAuthenticator.kt (87%) rename integrations/coinbase/src/main/java/org/dash/wallet/{integration/coinbase_integration => integrations/coinbase}/service/CoinBaseAuthApi.kt (90%) rename integrations/coinbase/src/main/java/org/dash/wallet/{integration/coinbase_integration => integrations/coinbase}/service/CoinBaseClientConstants.kt (92%) rename integrations/coinbase/src/main/java/org/dash/wallet/{integration/coinbase_integration => integrations/coinbase}/service/CoinBaseServicesApi.kt (75%) rename integrations/coinbase/src/main/java/org/dash/wallet/{integration/coinbase_integration => integrations/coinbase}/service/CoinBaseTokenRefreshApi.kt (89%) rename {wallet/src/de/schildbach/wallet/ui/coinbase => integrations/coinbase/src/main/java/org/dash/wallet/integrations/coinbase/ui}/CoinBaseWebClientActivity.kt (90%) create mode 100644 integrations/coinbase/src/main/java/org/dash/wallet/integrations/coinbase/ui/CoinbaseBuyDashFragment.kt rename integrations/coinbase/src/main/java/org/dash/wallet/{integration/coinbase_integration => integrations/coinbase}/ui/CoinbaseConversionPreviewFragment.kt (95%) rename integrations/coinbase/src/main/java/org/dash/wallet/{integration/coinbase_integration => integrations/coinbase}/ui/CoinbaseConvertCryptoFragment.kt (86%) create mode 100644 integrations/coinbase/src/main/java/org/dash/wallet/integrations/coinbase/ui/CoinbaseOrderReviewFragment.kt create mode 100644 integrations/coinbase/src/main/java/org/dash/wallet/integrations/coinbase/ui/CoinbaseServicesFragment.kt rename integrations/coinbase/src/main/java/org/dash/wallet/{integration/coinbase_integration => integrations/coinbase}/ui/EnterAmountToTransferFragment.kt (93%) rename integrations/coinbase/src/main/java/org/dash/wallet/{integration/coinbase_integration => integrations/coinbase}/ui/EnterTwoFaCodeFragment.kt (90%) rename integrations/coinbase/src/main/java/org/dash/wallet/{integration/coinbase_integration => integrations/coinbase}/ui/TransferDashFragment.kt (91%) rename integrations/coinbase/src/main/java/org/dash/wallet/{integration/coinbase_integration => integrations/coinbase}/ui/convert_currency/ConvertView.kt (96%) rename integrations/coinbase/src/main/java/org/dash/wallet/{integration/coinbase_integration => integrations/coinbase}/ui/convert_currency/ConvertViewFragment.kt (73%) rename integrations/coinbase/src/main/java/org/dash/wallet/{integration/coinbase_integration => integrations/coinbase}/ui/convert_currency/CryptoConvertItem.kt (95%) rename integrations/coinbase/src/main/java/org/dash/wallet/{integration/coinbase_integration => integrations/coinbase}/ui/convert_currency/TransferView.kt (94%) create mode 100644 integrations/coinbase/src/main/java/org/dash/wallet/integrations/coinbase/ui/convert_currency/model/BaseIdForFiatData.kt rename integrations/coinbase/src/main/java/org/dash/wallet/{integration/coinbase_integration => integrations/coinbase}/ui/convert_currency/model/PaymentMethodsUiState.kt (92%) rename integrations/coinbase/src/main/java/org/dash/wallet/{integration/coinbase_integration => integrations/coinbase}/ui/convert_currency/model/ServiceWallet.kt (87%) rename integrations/coinbase/src/main/java/org/dash/wallet/{integration/coinbase_integration => integrations/coinbase}/ui/convert_currency/model/SwapRequest.kt (91%) rename integrations/coinbase/src/main/java/org/dash/wallet/{integration/coinbase_integration => integrations/coinbase}/ui/convert_currency/model/SwapValueErrorType.kt (71%) rename integrations/coinbase/src/main/java/org/dash/wallet/{integration/coinbase_integration => integrations/coinbase}/ui/dialogs/CoinBaseResultDialog.kt (92%) rename integrations/coinbase/src/main/java/org/dash/wallet/{integration/coinbase_integration => integrations/coinbase}/ui/dialogs/CoinbaseFeeInfoDialog.kt (91%) rename integrations/coinbase/src/main/java/org/dash/wallet/{integration/coinbase_integration => integrations/coinbase}/ui/dialogs/crypto_wallets/CryptoWalletsDialog.kt (91%) rename integrations/coinbase/src/main/java/org/dash/wallet/{integration/coinbase_integration => integrations/coinbase}/ui/dialogs/crypto_wallets/CryptoWalletsDialogViewModel.kt (92%) rename integrations/coinbase/src/main/java/org/dash/wallet/{integration/coinbase_integration => integrations/coinbase}/utils/CoinbaseConfig.kt (94%) create mode 100644 integrations/coinbase/src/main/java/org/dash/wallet/integrations/coinbase/viewmodels/CoinbaseBuyDashViewModel.kt rename integrations/coinbase/src/main/java/org/dash/wallet/{integration/coinbase_integration => integrations/coinbase}/viewmodels/CoinbaseConversionPreviewViewModel.kt (94%) rename integrations/coinbase/src/main/java/org/dash/wallet/{integration/coinbase_integration => integrations/coinbase}/viewmodels/CoinbaseConvertCryptoViewModel.kt (67%) rename integrations/coinbase/src/main/java/org/dash/wallet/{integration/coinbase_integration => integrations/coinbase}/viewmodels/CoinbaseServicesViewModel.kt (66%) create mode 100644 integrations/coinbase/src/main/java/org/dash/wallet/integrations/coinbase/viewmodels/CoinbaseViewModel.kt rename integrations/coinbase/src/main/java/org/dash/wallet/{integration/coinbase_integration => integrations/coinbase}/viewmodels/ConvertViewViewModel.kt (87%) create mode 100644 integrations/coinbase/src/main/java/org/dash/wallet/integrations/coinbase/viewmodels/Delegate.kt rename integrations/coinbase/src/main/java/org/dash/wallet/{integration/coinbase_integration => integrations/coinbase}/viewmodels/EnterAmountToTransferViewModel.kt (94%) rename integrations/coinbase/src/main/java/org/dash/wallet/{integration/coinbase_integration => integrations/coinbase}/viewmodels/EnterTwoFaCodeViewModel.kt (85%) rename integrations/coinbase/src/main/java/org/dash/wallet/{integration/coinbase_integration => integrations/coinbase}/viewmodels/TransferDashViewModel.kt (67%) rename integrations/coinbase/src/main/res/layout/{fragment_coinbase_buy_dash_order_review.xml => fragment_coinbase_order_review.xml} (100%) rename integrations/coinbase/src/test/java/org/dash/wallet/{integration/coinbase_integration => integrations/coinbase}/CoinBaseRepositoryTest.kt (60%) rename integrations/coinbase/src/test/java/org/dash/wallet/{integration/coinbase_integration => integrations/coinbase}/TestUtils.kt (72%) rename integrations/uphold/src/main/java/org/dash/wallet/{integration => integrations}/uphold/api/RemoteDataSource.kt (96%) rename integrations/uphold/src/main/java/org/dash/wallet/{integration => integrations}/uphold/api/TopperClient.kt (97%) rename integrations/uphold/src/main/java/org/dash/wallet/{integration => integrations}/uphold/api/UpholdClient.java (95%) rename integrations/uphold/src/main/java/org/dash/wallet/{integration => integrations}/uphold/api/UpholdClientExt.kt (96%) rename integrations/uphold/src/main/java/org/dash/wallet/{integration => integrations}/uphold/api/UpholdService.kt (86%) rename integrations/uphold/src/main/java/org/dash/wallet/{integration => integrations}/uphold/currencyModel/UpholdCurrencyResponse.kt (87%) rename integrations/uphold/src/main/java/org/dash/wallet/{integration => integrations}/uphold/data/ForbiddenError.kt (91%) rename integrations/uphold/src/main/java/org/dash/wallet/{integration => integrations}/uphold/data/RequirementsCheckResult.kt (93%) rename integrations/uphold/src/main/java/org/dash/wallet/{integration => integrations}/uphold/data/SupportedTopperAssets.kt (96%) rename integrations/uphold/src/main/java/org/dash/wallet/{integration => integrations}/uphold/data/UpholdAccessToken.java (95%) rename integrations/uphold/src/main/java/org/dash/wallet/{integration => integrations}/uphold/data/UpholdAddress.java (95%) rename integrations/uphold/src/main/java/org/dash/wallet/{integration => integrations}/uphold/data/UpholdApiException.java (99%) rename integrations/uphold/src/main/java/org/dash/wallet/{integration => integrations}/uphold/data/UpholdBankAccount.java (97%) rename integrations/uphold/src/main/java/org/dash/wallet/{integration => integrations}/uphold/data/UpholdCapability.kt (95%) rename integrations/uphold/src/main/java/org/dash/wallet/{integration => integrations}/uphold/data/UpholdCard.java (97%) rename integrations/uphold/src/main/java/org/dash/wallet/{integration => integrations}/uphold/data/UpholdCardAddress.kt (94%) rename integrations/uphold/src/main/java/org/dash/wallet/{integration => integrations}/uphold/data/UpholdConstants.kt (98%) rename integrations/uphold/src/main/java/org/dash/wallet/{integration => integrations}/uphold/data/UpholdCryptoCardAddress.java (95%) rename integrations/uphold/src/main/java/org/dash/wallet/{integration => integrations}/uphold/data/UpholdException.java (85%) rename integrations/uphold/src/main/java/org/dash/wallet/{integration => integrations}/uphold/data/UpholdTransaction.java (97%) rename integrations/uphold/src/main/java/org/dash/wallet/{integration => integrations}/uphold/ui/UpholdOtpDialog.java (94%) rename integrations/uphold/src/main/java/org/dash/wallet/{integration => integrations}/uphold/ui/UpholdPortalFragment.kt (97%) rename integrations/uphold/src/main/java/org/dash/wallet/{integration => integrations}/uphold/ui/UpholdTransferActivity.kt (95%) rename integrations/uphold/src/main/java/org/dash/wallet/{integration => integrations}/uphold/ui/UpholdViewModel.kt (91%) rename integrations/uphold/src/main/java/org/dash/wallet/{integration => integrations}/uphold/ui/UpholdWithdrawalHelper.java (93%) rename integrations/uphold/src/test/java/org/dash/wallet/{integration => integrations}/uphold/UpholdClientTest.kt (94%) rename integrations/uphold/src/test/java/org/dash/wallet/{integration => integrations}/uphold/UpholdErrorTest.kt (99%) delete mode 100644 wallet/res/layout/activity_coinbase.xml delete mode 100644 wallet/src/de/schildbach/wallet/ui/coinbase/CoinbaseActivity.kt diff --git a/.github/workflows/dashwallet.yml b/.github/workflows/dashwallet.yml index 83e7e59bdc..fcf637cb11 100644 --- a/.github/workflows/dashwallet.yml +++ b/.github/workflows/dashwallet.yml @@ -26,7 +26,7 @@ jobs: - name: set up JDK uses: actions/setup-java@v3 with: - java-version: '11' + java-version: '17' distribution: 'adopt' cache: gradle diff --git a/.github/workflows/ktlint.yml b/.github/workflows/ktlint.yml index 8537dcfa8b..0bf8157979 100644 --- a/.github/workflows/ktlint.yml +++ b/.github/workflows/ktlint.yml @@ -14,7 +14,7 @@ jobs: - name: set up JDK uses: actions/setup-java@v3 with: - java-version: '11' + java-version: '17' distribution: 'adopt' cache: gradle diff --git a/.github/workflows/manual_distribution.yml b/.github/workflows/manual_distribution.yml index 8e0e15b31e..db03bd0bcf 100644 --- a/.github/workflows/manual_distribution.yml +++ b/.github/workflows/manual_distribution.yml @@ -61,7 +61,7 @@ jobs: - name: set up JDK uses: actions/setup-java@v3 with: - java-version: '16' + java-version: '17' distribution: 'adopt' cache: gradle diff --git a/build.gradle b/build.gradle index 2c76cc6aa3..bb42ae4331 100644 --- a/build.gradle +++ b/build.gradle @@ -14,7 +14,7 @@ buildscript { // Architecture lifecycleVersion = '2.5.1' - navigationVersion = '2.5.3' + navigationVersion = '2.6.0' datastoreVersion = "1.0.0" hiltVersion = '2.45' hiltWorkVersion = '1.0.0' diff --git a/common/src/main/java/org/dash/wallet/common/ui/LockScreenAware.kt b/common/src/main/java/org/dash/wallet/common/ui/LockScreenAware.kt new file mode 100644 index 0000000000..2b33261bc9 --- /dev/null +++ b/common/src/main/java/org/dash/wallet/common/ui/LockScreenAware.kt @@ -0,0 +1,22 @@ +/* + * Copyright 2023 Dash Core Group. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package org.dash.wallet.common.ui + +interface LockScreenAware { + fun onLockScreenActivated() +} diff --git a/common/src/main/java/org/dash/wallet/common/ui/dialogs/AdaptiveDialog.kt b/common/src/main/java/org/dash/wallet/common/ui/dialogs/AdaptiveDialog.kt index a3538492ec..4241193a70 100644 --- a/common/src/main/java/org/dash/wallet/common/ui/dialogs/AdaptiveDialog.kt +++ b/common/src/main/java/org/dash/wallet/common/ui/dialogs/AdaptiveDialog.kt @@ -74,14 +74,15 @@ open class AdaptiveDialog(@LayoutRes private val layout: Int): DialogFragment() action: suspend () -> T ): T { val dialog = progress(message) - dialog.show(activity) { } - val result = action.invoke() - if (dialog.activity != null && dialog.isAdded) { - dialog.dismissAllowingStateLoss() + try { + dialog.show(activity) { } + return action.invoke() + } finally { + if (dialog.activity != null && dialog.isAdded) { + dialog.dismissAllowingStateLoss() + } } - - return result } @JvmStatic diff --git a/common/src/main/java/org/dash/wallet/common/util/Constants.kt b/common/src/main/java/org/dash/wallet/common/util/Constants.kt index 28f76a702d..471feac04e 100644 --- a/common/src/main/java/org/dash/wallet/common/util/Constants.kt +++ b/common/src/main/java/org/dash/wallet/common/util/Constants.kt @@ -37,7 +37,6 @@ object Constants { const val PREFIX_ALMOST_EQUAL_TO = CHAR_ALMOST_EQUAL_TO.toString() + CHAR_THIN_SPACE const val USER_BUY_SELL_DASH = 101 - const val RESULT_CODE_GO_HOME = 100 var MAX_MONEY: Coin = MainNetParams.get().maxMoney val ECONOMIC_FEE: Coin = Coin.valueOf(1000) diff --git a/common/src/main/java/org/dash/wallet/common/util/MonetaryExt.kt b/common/src/main/java/org/dash/wallet/common/util/MonetaryExt.kt index b527f41943..34a6fbf097 100644 --- a/common/src/main/java/org/dash/wallet/common/util/MonetaryExt.kt +++ b/common/src/main/java/org/dash/wallet/common/util/MonetaryExt.kt @@ -85,5 +85,5 @@ fun Fiat.toFormattedStringNoCode(): String { return format.format(this).toString() } -fun Fiat.discountBy(percentage: Double) = +fun Fiat.discountBy(percentage: Double): Fiat = Fiat.valueOf(currencyCode, (value * (100.0 - percentage) / 100).toLong()) diff --git a/wallet/res/drawable/ic_relogin.xml b/common/src/main/res/drawable/ic_relogin.xml similarity index 82% rename from wallet/res/drawable/ic_relogin.xml rename to common/src/main/res/drawable/ic_relogin.xml index a6538c97b4..021096220b 100644 --- a/wallet/res/drawable/ic_relogin.xml +++ b/common/src/main/res/drawable/ic_relogin.xml @@ -5,19 +5,8 @@ android:viewportWidth="68" android:viewportHeight="68"> - - - - - - - + android:pathData="M33.999,54.14C37.23,54.14 40.265,53.534 43.103,52.321C45.958,51.123 48.467,49.465 50.631,47.345C52.795,45.211 54.497,42.754 55.736,39.974C56.975,37.18 57.594,34.2 57.594,31.037C57.594,27.874 56.975,24.902 55.736,22.123C54.497,19.328 52.795,16.871 50.631,14.752C48.467,12.617 45.958,10.951 43.103,9.753C40.249,8.54 37.206,7.934 33.976,7.934C30.745,7.934 27.703,8.54 24.848,9.753C22.01,10.951 19.508,12.617 17.344,14.752C15.18,16.871 13.478,19.328 12.239,22.123C11.016,24.902 10.404,27.874 10.404,31.037C10.404,34.2 11.016,37.18 12.239,39.974C13.478,42.754 15.18,45.211 17.344,47.345C19.524,49.465 22.033,51.123 24.872,52.321C27.726,53.534 30.769,54.14 33.999,54.14Z" + android:fillColor="#FFFDFDFD"/> - - - - - - - + android:strokeColor="#F0F1F2" + android:fillColor="#FFFDFDFD" /> @@ -54,17 +32,7 @@ - - - - - - + android:pathData="M33.393,23.565C33.711,23.565 33.98,23.679 34.2,23.906C34.419,24.126 34.529,24.391 34.529,24.702V32.054L33.427,28.736L36.984,33.486C37.188,33.751 37.275,34.043 37.245,34.361C37.222,34.679 37.078,34.933 36.813,35.122C36.541,35.319 36.249,35.391 35.938,35.338C35.628,35.285 35.359,35.111 35.132,34.815L32.563,31.44C32.465,31.304 32.389,31.164 32.336,31.02C32.283,30.876 32.257,30.728 32.257,30.577V24.702C32.257,24.391 32.366,24.126 32.586,23.906C32.806,23.679 33.075,23.565 33.393,23.565ZM33.813,42.031C32.669,42.031 31.533,41.857 30.404,41.508C29.283,41.16 28.237,40.671 27.268,40.043C26.306,39.421 25.491,38.698 24.825,37.872C24.484,37.478 24.317,37.077 24.325,36.668C24.34,36.251 24.507,35.906 24.825,35.633C25.173,35.346 25.548,35.24 25.95,35.315C26.359,35.383 26.722,35.581 27.041,35.906C27.601,36.558 28.23,37.118 28.927,37.588C29.632,38.05 30.393,38.406 31.211,38.656C32.029,38.914 32.897,39.043 33.813,39.043C35.056,39.05 36.222,38.819 37.313,38.349C38.404,37.887 39.362,37.243 40.188,36.418C41.014,35.592 41.658,34.633 42.12,33.543C42.59,32.452 42.825,31.281 42.825,30.031C42.825,28.781 42.59,27.615 42.12,26.531C41.658,25.44 41.014,24.482 40.188,23.656C39.362,22.83 38.404,22.187 37.313,21.724C36.222,21.262 35.056,21.031 33.813,21.031C32.571,21.031 31.408,21.262 30.325,21.724C29.241,22.179 28.287,22.812 27.461,23.622C26.635,24.433 25.987,25.376 25.518,26.452C25.048,27.528 24.806,28.683 24.791,29.918H21.802C21.817,28.289 22.139,26.758 22.768,25.327C23.404,23.887 24.272,22.622 25.37,21.531C26.476,20.433 27.749,19.577 29.188,18.963C30.628,18.342 32.169,18.031 33.813,18.031C35.457,18.031 37.003,18.346 38.45,18.974C39.897,19.596 41.173,20.459 42.279,21.565C43.385,22.671 44.249,23.948 44.87,25.395C45.499,26.842 45.813,28.387 45.813,30.031C45.813,31.683 45.499,33.232 44.87,34.679C44.249,36.126 43.385,37.399 42.279,38.497C41.173,39.603 39.897,40.467 38.45,41.088C37.003,41.717 35.457,42.031 33.813,42.031ZM20.484,27.508H26.779C27.112,27.508 27.359,27.592 27.518,27.758C27.684,27.925 27.764,28.13 27.757,28.372C27.757,28.615 27.669,28.857 27.495,29.099L24.37,33.281C24.173,33.546 23.927,33.679 23.632,33.679C23.344,33.679 23.101,33.546 22.904,33.281L19.768,29.099C19.594,28.857 19.503,28.615 19.495,28.372C19.487,28.122 19.567,27.918 19.734,27.758C19.9,27.592 20.15,27.508 20.484,27.508Z" + android:fillColor="#FFFFFFFF"> diff --git a/common/src/main/res/values/strings.xml b/common/src/main/res/values/strings.xml index 4c09199f8f..bb45fc1613 100644 --- a/common/src/main/res/values/strings.xml +++ b/common/src/main/res/values/strings.xml @@ -100,4 +100,5 @@ Disconnected available Not available + Log In \ No newline at end of file diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 5de3fe56c0..dcf4285446 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ -#Thu Oct 08 11:03:21 CEST 2020 +#Mon Oct 02 18:25:36 ICT 2023 distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-7.4-bin.zip zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-7.3.3-all.zip diff --git a/integrations/coinbase/build.gradle b/integrations/coinbase/build.gradle index 72cedb3b41..600c9b9aac 100644 --- a/integrations/coinbase/build.gradle +++ b/integrations/coinbase/build.gradle @@ -41,7 +41,7 @@ android { includeAndroidResources = true } } - namespace 'org.dash.wallet.integration.coinbase_integration' + namespace 'org.dash.wallet.integrations.coinbase' } kapt { @@ -57,7 +57,7 @@ dependencies { // Core implementation 'androidx.core:core-ktx:1.6.0' implementation 'androidx.appcompat:appcompat:1.3.1' - implementation "org.jetbrains.kotlinx:kotlinx-coroutines-play-services:1.5.0" + implementation "org.jetbrains.kotlinx:kotlinx-coroutines-play-services:$coroutinesVersion" implementation "org.dashj:dashj-core:$dashjVersion" // Architecture @@ -73,7 +73,7 @@ dependencies { // UI implementation "com.google.android.material:material:$materialVersion" - implementation 'androidx.recyclerview:recyclerview:1.2.1' + implementation "androidx.recyclerview:recyclerview:$recyclerViewVersion" implementation "androidx.constraintlayout:constraintlayout:$constrainLayoutVersion" implementation "io.coil-kt:coil:$coilVersion" implementation 'me.grantland:autofittextview:0.2.1' diff --git a/integrations/coinbase/proguard-rules.pro b/integrations/coinbase/proguard-rules.pro index fb93abc950..16dd904c61 100644 --- a/integrations/coinbase/proguard-rules.pro +++ b/integrations/coinbase/proguard-rules.pro @@ -21,12 +21,12 @@ #-renamesourcefileattribute SourceFile -keepattributes Exceptions, InnerClasses --keep class org.dash.wallet.integration.coinbase_integration.** { +-keep class org.dash.wallet.integrations.coinbase.** { public protected private *; } --keep interface org.dash.wallet.integration.coinbase_integration.** {*;} +-keep interface org.dash.wallet.integrations.coinbase.** {*;} --keep class org.dash.wallet.integration.uphold.** { +-keep class org.dash.wallet.integrations.uphold.** { public protected private *; } diff --git a/integrations/coinbase/src/androidTest/java/org/dash/wallet/integration/coinbase_integration/ExampleUnitTest.kt b/integrations/coinbase/src/androidTest/java/org/dash/wallet/integrations/coinbase/ExampleUnitTest.kt similarity index 100% rename from integrations/coinbase/src/androidTest/java/org/dash/wallet/integration/coinbase_integration/ExampleUnitTest.kt rename to integrations/coinbase/src/androidTest/java/org/dash/wallet/integrations/coinbase/ExampleUnitTest.kt diff --git a/integrations/coinbase/src/main/java/org/dash/wallet/integration/coinbase_integration/model/BuyOrder.kt b/integrations/coinbase/src/main/java/org/dash/wallet/integration/coinbase_integration/model/BuyOrder.kt deleted file mode 100644 index cafe955e15..0000000000 --- a/integrations/coinbase/src/main/java/org/dash/wallet/integration/coinbase_integration/model/BuyOrder.kt +++ /dev/null @@ -1,126 +0,0 @@ -package org.dash.wallet.integration.coinbase_integration.model - -import android.os.Parcelable -import com.google.gson.annotations.SerializedName -import kotlinx.parcelize.Parcelize -import org.dash.wallet.common.util.Constants -import org.dash.wallet.integration.coinbase_integration.CoinbaseConstants - -@Parcelize -data class BuyOrderResponse( - val `data`: BuyOrderData? -): Parcelable { - companion object { - val EMPTY_PLACE_BUY = PlaceBuyOrderUIModel("", "", "", "") - val EMPTY_COMMIT_BUY = CommitBuyOrderUIModel("", "", "", "") - } -} - -@Parcelize -data class BuyOrderData( - val amount: Amount? = null, - val committed: Boolean? = null, - @SerializedName("created_at") - val created_at: String? = null, - val fee: Fee? = null, - val hold_days: Int? = null, - val hold_until: String? = null, - val id: String? = null, - val idem: String? = null, - val instant: Boolean? = null, - @SerializedName("is_first_buy") - val isFirstBuy: Boolean? = null, - @SerializedName("payment_method") - val paymentMethod: PaymentMethod? = null, - @SerializedName("payout_at") - val payoutAt: String? = null, - @SerializedName("requires_completion_step") - val requiresCompletionStep: Boolean, - val resource: String? = null, - @SerializedName("resource_path") - val resourcePath: String? = null, - val status: String? = null, - val subtotal: Subtotal? = null, - val total: Total? = null, - val transaction: Transaction? = null, - @SerializedName("unit_price") - val unitPrice: UnitPrice? = null, - @SerializedName("updated_at") - val updatedAt: String? = null, - @SerializedName("user_reference") - val userReference: String? = null -): Parcelable - -@Parcelize -data class UnitPrice( - val amount: String? = null, - val currency: String? = null, - val scale: Int? = null -): Parcelable - -@Parcelize -data class PaymentMethod( - val id: String? = null, - val resource: String? = null, - @SerializedName("resource_path") - val resourcePath: String? = null -): Parcelable - -@Parcelize -data class Transaction( - val id: String? = null, - val resource: String? = null, - @SerializedName("resource_path") - val resourcePath: String? = null -): Parcelable - -@Parcelize -data class Amount( - val amount: String? = null, - val currency: String? = null -): Parcelable - -@Parcelize -data class Total( - val amount: String? = null, - val currency: String? = null -): Parcelable - -@Parcelize -data class Subtotal( - val amount: String? = null, - val currency: String? = null -): Parcelable - -@Parcelize -data class Fee( - val amount: String? = null, - val currency: String? = null -): Parcelable - -data class PlaceBuyOrderParams( - val amount: String? = null, - val currency: String? = null, - val payment_method: String? = null, - val commit: Boolean = false -) - -@Parcelize -data class PlaceBuyOrderUIModel( - val buyOrderId:String = "", - val paymentMethodId:String = "", - val purchaseAmount:String = "", - val purchaseCurrency: String = "", - val coinBaseFeeAmount:String = "", - val coinbaseFeeCurrency: String = "", - val totalAmount:String = "", - val totalCurrency: String = "", - val dashAmount:String = "", -): Parcelable - -data class CommitBuyOrderUIModel( - val dashAmount: String? = "", - val dashCurrency: String = Constants.DASH_CURRENCY, - val dashAddress: String? = "", - val transactionType: String = CoinbaseConstants.TRANSACTION_TYPE_SEND -) \ No newline at end of file diff --git a/integrations/coinbase/src/main/java/org/dash/wallet/integration/coinbase_integration/model/CoinBaseUserAccountInfo.kt b/integrations/coinbase/src/main/java/org/dash/wallet/integration/coinbase_integration/model/CoinBaseUserAccountInfo.kt deleted file mode 100644 index d8f31e38dc..0000000000 --- a/integrations/coinbase/src/main/java/org/dash/wallet/integration/coinbase_integration/model/CoinBaseUserAccountInfo.kt +++ /dev/null @@ -1,133 +0,0 @@ -/* - * Copyright 2021 Dash Core Group. - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ -package org.dash.wallet.integration.coinbase_integration.model - -import android.os.Parcelable -import kotlinx.parcelize.Parcelize -import org.bitcoinj.core.Coin -import org.bitcoinj.utils.Fiat -import org.dash.wallet.common.data.entity.ExchangeRate -import org.dash.wallet.common.util.GenericUtils -import org.dash.wallet.common.util.toFormattedString -import java.math.RoundingMode - -@Parcelize -data class CoinBaseUserAccountInfo( - val `data`: List? = null, - val pagination: Pagination? = null -) : Parcelable - -@Parcelize -data class CoinBaseUserAccountData( - val allow_deposits: Boolean? = null, - val allow_withdrawals: Boolean? = null, - val balance: CoinBaseBalance? = null, - val created_at: String? = null, - val currency: CoinBaseCurrency? = null, - val id: String? = null, - val name: String? = null, - val primary: Boolean? = null, - val resource: String? = null, - val resource_path: String? = null, - val type: String? = null, - val updated_at: String? = null -) : Parcelable { - companion object { - val EMPTY = CoinBaseUserAccountData( - allow_deposits = false, - allow_withdrawals = false, - balance = null, - created_at = "", - currency = null, - id = null, - name = null, - primary = null, - resource = null, - resource_path = null, - type = null, - updated_at = null - ) - } -} - -@Parcelize -data class Pagination( - val ending_before: String? = null, - val limit: Int? = null, - val next_starting_after: String? = null, - val next_uri: String? = null, - val order: String? = null, - val previous_ending_before: String? = null, - val previous_uri: String? = null, - val starting_after: String? = null -) : Parcelable - -@Parcelize -data class CoinBaseCurrency( - val address_regex: String? = null, - val asset_id: String? = null, - val code: String? = null, - val color: String? = null, - val destination_tag_name: String? = null, - val destination_tag_regex: String? = null, - val exponent: Int? = null, - val name: String? = null, - val slug: String? = null, - val sort_index: Int? = null, - val type: String? = null -) : Parcelable - -@Parcelize -data class CoinBaseBalance( - val amount: String? = null, - val currency: String? = null -) : Parcelable - -@Parcelize -data class CoinBaseUserAccountDataUIModel( - override val coinBaseUserAccountData: CoinBaseUserAccountData, - val currencyToCryptoCurrencyExchangeRate: String, - override val currencyToDashExchangeRate: String, - val cryptoCurrencyToDashExchangeRate: String, - override val currencyToUSDExchangeRate: String -) : CoinbaseToDashExchangeRateUIModel(coinBaseUserAccountData, currencyToDashExchangeRate, currencyToUSDExchangeRate), Parcelable - -fun CoinBaseUserAccountDataUIModel.getCoinBaseExchangeRateConversion( - currentExchangeRate: ExchangeRate -): Pair { - val cleanedValue = - this.coinBaseUserAccountData.balance?.amount?.toBigDecimal()!! / - this.currencyToCryptoCurrencyExchangeRate.toBigDecimal() - val bd = cleanedValue.setScale(8, RoundingMode.HALF_UP) - - val currencyRate = org.bitcoinj.utils.ExchangeRate(Coin.COIN, currentExchangeRate.fiat) - val fiatAmount = Fiat.parseFiat(currencyRate.fiat.currencyCode, bd.toString()) - val dashAmount = currencyRate.fiatToCoin(fiatAmount) - - return Pair(fiatAmount.toFormattedString(), dashAmount) -} - -@Parcelize -open class CoinbaseToDashExchangeRateUIModel( - open val coinBaseUserAccountData: CoinBaseUserAccountData, - open val currencyToDashExchangeRate: String, - open val currencyToUSDExchangeRate: String -): Parcelable { - companion object { - val EMPTY = CoinbaseToDashExchangeRateUIModel(CoinBaseUserAccountData.EMPTY, "", "") - } -} diff --git a/integrations/coinbase/src/main/java/org/dash/wallet/integration/coinbase_integration/ui/CoinbaseBuyDashFragment.kt b/integrations/coinbase/src/main/java/org/dash/wallet/integration/coinbase_integration/ui/CoinbaseBuyDashFragment.kt deleted file mode 100644 index 580e3c3298..0000000000 --- a/integrations/coinbase/src/main/java/org/dash/wallet/integration/coinbase_integration/ui/CoinbaseBuyDashFragment.kt +++ /dev/null @@ -1,164 +0,0 @@ -/* - * Copyright 2021 Dash Core Group. - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -package org.dash.wallet.integration.coinbase_integration.ui - -import android.os.Bundle -import android.view.View -import androidx.activity.addCallback -import androidx.core.view.isVisible -import androidx.fragment.app.Fragment -import androidx.fragment.app.activityViewModels -import androidx.fragment.app.commit -import androidx.fragment.app.viewModels -import androidx.lifecycle.lifecycleScope -import androidx.navigation.fragment.findNavController -import androidx.navigation.fragment.navArgs -import dagger.hilt.android.AndroidEntryPoint -import kotlinx.coroutines.launch -import org.bitcoinj.core.Coin -import org.bitcoinj.utils.MonetaryFormat -import org.dash.wallet.common.services.analytics.AnalyticsConstants -import org.dash.wallet.common.ui.dialogs.AdaptiveDialog -import org.dash.wallet.common.ui.enter_amount.EnterAmountFragment -import org.dash.wallet.common.ui.enter_amount.EnterAmountViewModel -import org.dash.wallet.common.ui.viewBinding -import org.dash.wallet.common.util.GenericUtils -import org.dash.wallet.common.util.safeNavigate -import org.dash.wallet.common.util.toFormattedString -import org.dash.wallet.integration.coinbase_integration.R -import org.dash.wallet.integration.coinbase_integration.databinding.FragmentCoinbaseBuyDashBinding -import org.dash.wallet.integration.coinbase_integration.databinding.KeyboardHeaderViewBinding -import org.dash.wallet.integration.coinbase_integration.viewmodels.CoinbaseBuyDashViewModel - -@AndroidEntryPoint -class CoinbaseBuyDashFragment : Fragment(R.layout.fragment_coinbase_buy_dash) { - private val binding by viewBinding(FragmentCoinbaseBuyDashBinding::bind) - private val viewModel by viewModels() - private val amountViewModel by activityViewModels() - private val args by navArgs() - private var loadingDialog: AdaptiveDialog? = null - private lateinit var fragment: EnterAmountFragment - - override fun onViewCreated(view: View, savedInstanceState: Bundle?) { - super.onViewCreated(view, savedInstanceState) - - if (savedInstanceState == null) { - fragment = EnterAmountFragment.newInstance( - isMaxButtonVisible = false, - showCurrencySelector = false - ) - val headerBinding = KeyboardHeaderViewBinding.inflate(layoutInflater, null, false) - fragment.setViewDetails(getString(R.string.button_continue), headerBinding.root) - - parentFragmentManager.commit { - setReorderingAllowed(true) - add(R.id.enter_amount_fragment_placeholder, fragment) - } - } - - setupPaymentMethodPayment() - binding.toolbar.setNavigationOnClickListener { - findNavController().popBackStack() - } - - requireActivity().onBackPressedDispatcher.addCallback(viewLifecycleOwner){ - findNavController().popBackStack() - } - - amountViewModel.selectedExchangeRate.observe(viewLifecycleOwner) { rate -> - rate?.let { - binding.toolbarSubtitle.text = getString( - R.string.exchange_rate_template, - Coin.COIN.toPlainString(), - rate.fiat.toFormattedString() - ) - } - } - - amountViewModel.onContinueEvent.observe(viewLifecycleOwner) { pair -> - lifecycleScope.launch { - binding.authLimitBanner.root.isVisible = viewModel.isInputGreaterThanLimit(pair.first) - if (!binding.authLimitBanner.root.isVisible) { - val dashToFiat = amountViewModel.dashToFiatDirection.value ?: true - - val dashAmount = MonetaryFormat().withLocale(GenericUtils.getDeviceLocale()) - .noCode().minDecimals(6).optionalDecimals().format( pair.first) - - viewModel.onContinueClicked(dashToFiat, dashAmount, binding.paymentMethodPicker.selectedMethodIndex) - } - } - } - - viewModel.placeBuyOrder.observe(viewLifecycleOwner) { - safeNavigate(CoinbaseBuyDashFragmentDirections.buyDashToOrderReview( - binding.paymentMethodPicker.paymentMethods[binding.paymentMethodPicker.selectedMethodIndex], it)) - } - - viewModel.showLoading.observe(viewLifecycleOwner){ - if (it) { - showProgress(R.string.loading) - } else - dismissProgress() - } - - viewModel.placeBuyOrderFailedCallback.observe(viewLifecycleOwner) { - AdaptiveDialog.create( - R.drawable.ic_error, - getString(R.string.error), - it, - getString(R.string.close) - ).show(requireActivity()) - } - - binding.paymentMethodPicker.setOnClickListener { - viewModel.logEvent(AnalyticsConstants.Coinbase.BUY_CHANGE_PAYMENT_METHOD) - } - - binding.authLimitBanner.root.setOnClickListener { - viewModel.logEvent(AnalyticsConstants.Coinbase.BUY_AUTH_LIMIT) - AdaptiveDialog.custom(R.layout.dialog_withdrawal_limit_info).show(requireActivity()) - } - - viewModel.isDeviceConnectedToInternet.observe(viewLifecycleOwner) { hasInternet -> - fragment.handleNetworkState(hasInternet) - } - } - - private fun setupPaymentMethodPayment() { - viewModel.activePaymentMethods.observe(viewLifecycleOwner) { - if (it.isNotEmpty()) { - binding.paymentMethodPicker.paymentMethods = it - } - } - viewModel.setActivePaymentMethods(args.paymentMethods) - } - - private fun showProgress(messageResId: Int) { - if (loadingDialog != null && loadingDialog?.isAdded == true) { - loadingDialog?.dismissAllowingStateLoss() - } - loadingDialog = AdaptiveDialog.progress(getString(messageResId)) - loadingDialog?.show(parentFragmentManager, "progress") - } - - private fun dismissProgress() { - if (loadingDialog != null && loadingDialog?.isAdded == true) { - loadingDialog?.dismissAllowingStateLoss() - } - } -} \ No newline at end of file diff --git a/integrations/coinbase/src/main/java/org/dash/wallet/integration/coinbase_integration/ui/CoinbaseBuyDashOrderReviewFragment.kt b/integrations/coinbase/src/main/java/org/dash/wallet/integration/coinbase_integration/ui/CoinbaseBuyDashOrderReviewFragment.kt deleted file mode 100644 index a1a2ec8b16..0000000000 --- a/integrations/coinbase/src/main/java/org/dash/wallet/integration/coinbase_integration/ui/CoinbaseBuyDashOrderReviewFragment.kt +++ /dev/null @@ -1,272 +0,0 @@ -/* - * Copyright 2021 Dash Core Group. - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ -package org.dash.wallet.integration.coinbase_integration.ui - -import android.os.Bundle -import android.os.CountDownTimer -import android.view.View -import androidx.activity.addCallback -import androidx.annotation.ColorRes -import androidx.annotation.StyleRes -import androidx.core.view.isVisible -import androidx.fragment.app.Fragment -import androidx.fragment.app.activityViewModels -import androidx.fragment.app.viewModels -import androidx.navigation.fragment.findNavController -import dagger.hilt.android.AndroidEntryPoint -import kotlinx.coroutines.ExperimentalCoroutinesApi -import org.dash.wallet.common.services.analytics.AnalyticsConstants -import org.dash.wallet.common.ui.dialogs.AdaptiveDialog -import org.dash.wallet.common.ui.enter_amount.EnterAmountViewModel -import org.dash.wallet.common.ui.payment_method_picker.CardUtils -import org.dash.wallet.common.ui.payment_method_picker.PaymentMethodType -import org.dash.wallet.common.ui.setRoundedBackground -import org.dash.wallet.common.ui.viewBinding -import org.dash.wallet.common.util.GenericUtils -import org.dash.wallet.common.util.safeNavigate -import org.dash.wallet.integration.coinbase_integration.R -import org.dash.wallet.integration.coinbase_integration.databinding.FragmentCoinbaseBuyDashOrderReviewBinding -import org.dash.wallet.integration.coinbase_integration.model.* -import org.dash.wallet.integration.coinbase_integration.ui.dialogs.CoinBaseResultDialog -import org.dash.wallet.integration.coinbase_integration.viewmodels.CoinbaseBuyDashOrderReviewViewModel - -@ExperimentalCoroutinesApi -@AndroidEntryPoint -class CoinbaseBuyDashOrderReviewFragment : Fragment(R.layout.fragment_coinbase_buy_dash_order_review) { - private val binding by viewBinding(FragmentCoinbaseBuyDashOrderReviewBinding::bind) - private val viewModel by viewModels() - private val amountViewModel by activityViewModels() - private lateinit var selectedPaymentMethodId: String - private var loadingDialog: AdaptiveDialog? = null - private var isRetrying = false - private var newBuyOrderId: String? = null - - private val countDownTimer by lazy { - object : CountDownTimer(10000, 1000) { - - override fun onTick(millisUntilFinished: Long) { - binding.confirmBtn.text = getString(R.string.confirm_sec, (millisUntilFinished / 1000).toString()) - binding.retryIcon.visibility = View.GONE - setConfirmBtnStyle(org.dash.wallet.common.R.style.PrimaryButtonTheme_Large_Blue, org.dash.wallet.common.R.color.dash_white) - } - - override fun onFinish() { - binding.confirmBtn.text = getString(R.string.retry) - binding.retryIcon.visibility = View.VISIBLE - isRetrying = true - setConfirmBtnStyle(org.dash.wallet.common.R.style.PrimaryButtonTheme_Large_LightBlue, org.dash.wallet.common.R.color.dash_blue) - } - } - } - override fun onViewCreated(view: View, savedInstanceState: Bundle?) { - super.onViewCreated(view, savedInstanceState) - - requireActivity().onBackPressedDispatcher.addCallback(viewLifecycleOwner) { - viewModel.logEvent(AnalyticsConstants.Coinbase.BUY_QUOTE_ANDROID_BACK) - findNavController().popBackStack() - } - - binding.toolbar.setNavigationOnClickListener { - viewModel.logEvent(AnalyticsConstants.Coinbase.BUY_QUOTE_TOP_BACK) - findNavController().popBackStack() - } - - binding.cancelBtn.setOnClickListener { - viewModel.logEvent(AnalyticsConstants.Coinbase.BUY_QUOTE_CANCEL) - val dialog = AdaptiveDialog.simple( - getString(R.string.cancel_transaction), - getString(R.string.no_keep_it), - getString(R.string.yes_cancel) - ) - dialog.isCancelable = false - dialog.show(requireActivity()) { result -> - if (result == true) { - viewModel.logEvent(AnalyticsConstants.Coinbase.BUY_QUOTE_CANCEL_YES) - findNavController().popBackStack() - } else { - viewModel.logEvent(AnalyticsConstants.Coinbase.BUY_QUOTE_CANCEL_NO) - } - } - } - - arguments?.let { - CoinbaseBuyDashOrderReviewFragmentArgs.fromBundle(it).paymentMethod.apply { - binding.contentOrderReview.paymentMethodName.text = this.name - val cardIcon = if (this.paymentMethodType == PaymentMethodType.Card) { - CardUtils.getCardIcon(this.account) - } else { - null - } - binding.contentOrderReview.paymentMethodName.isVisible = cardIcon == null - binding.contentOrderReview.paymentMethodIcon.setImageResource(cardIcon ?: 0) - binding.contentOrderReview.account.text = this.account - selectedPaymentMethodId = this.paymentMethodId - } - - CoinbaseBuyDashOrderReviewFragmentArgs.fromBundle(it).placeBuyOrderUIModel.apply { - updateOrderReviewUI() - } - } - - binding.confirmBtnContainer.setOnClickListener { - countDownTimer.cancel() - if (isRetrying) { - getNewBuyOrder() - isRetrying = false - } else { - newBuyOrderId?.let { buyOrderId -> viewModel.commitBuyOrder(buyOrderId) } - } - } - - - viewModel.commitBuyOrderSuccessState.observe(viewLifecycleOwner) { params -> - safeNavigate( - CoinbaseBuyDashOrderReviewFragmentDirections.coinbaseBuyDashOrderReviewToTwoFaCode( - CoinbaseTransactionParams(params, TransactionType.BuyDash) - ) - ) - } - viewModel.showLoading.observe(viewLifecycleOwner) { showLoading -> - if (showLoading) { - showProgress(R.string.loading) - } else - dismissProgress() - } - - - viewModel.commitBuyOrderFailureState.observe(viewLifecycleOwner) { - showBuyOrderDialog(it) - } - - binding.contentOrderReview.coinbaseFeeInfoContainer.setOnClickListener { - viewModel.logEvent(AnalyticsConstants.Coinbase.BUY_QUOTE_FEE_INFO) - safeNavigate(CoinbaseBuyDashOrderReviewFragmentDirections.orderReviewToFeeInfo()) - } - - viewModel.placeBuyOrderFailedCallback.observe(viewLifecycleOwner) { - AdaptiveDialog.create( - R.drawable.ic_error, - getString(R.string.something_wrong_title), - getString(R.string.retry_later_message), - getString(R.string.close) - ).show(requireActivity()) - } - - viewModel.placeBuyOrder.observe(viewLifecycleOwner) { - it.updateOrderReviewUI() - countDownTimer.start() - } - - viewModel.isDeviceConnectedToInternet.observe(viewLifecycleOwner) { hasInternet -> - setNetworkState(hasInternet) - } - observeNavigationCallBack() - } - - private fun PlaceBuyOrderUIModel.updateOrderReviewUI() { - newBuyOrderId = this.buyOrderId - binding.contentReviewBuyOrderDashAmount.dashAmount.text = this.dashAmount - binding.contentReviewBuyOrderDashAmount.message.text = - getString(R.string.you_will_receive_dash_on_your_dash_wallet, this.dashAmount) - binding.contentOrderReview.purchaseAmount.text = - getString( - R.string.fiat_balance_with_currency, - this.purchaseAmount, - GenericUtils.currencySymbol(this.purchaseCurrency) - ) - binding.contentOrderReview.coinbaseFeeAmount.text = - getString( - R.string.fiat_balance_with_currency, - this.coinBaseFeeAmount, - GenericUtils.currencySymbol(this.coinbaseFeeCurrency) - ) - binding.contentOrderReview.totalAmount.text = - getString( - R.string.fiat_balance_with_currency, - this.totalAmount, - GenericUtils.currencySymbol(this.totalCurrency) - ) - } - - private fun showProgress(messageResId: Int) { - if (loadingDialog != null && loadingDialog?.isAdded == true) { - loadingDialog?.dismissAllowingStateLoss() - } - loadingDialog = AdaptiveDialog.progress(getString(messageResId)) - loadingDialog?.show(parentFragmentManager, "progress") - } - - private fun dismissProgress() { - if (loadingDialog != null && loadingDialog?.isAdded == true) { - loadingDialog?.dismissAllowingStateLoss() - } - } - - private fun showBuyOrderDialog(responseMessage: String) { - val transactionStateDialog = CoinBaseResultDialog.newInstance(CoinBaseResultDialog.Type.PURCHASE_ERROR, responseMessage).apply { - this.onCoinBaseResultDialogButtonsClickListener = object : CoinBaseResultDialog.CoinBaseResultDialogButtonsClickListener { - override fun onPositiveButtonClick(type: CoinBaseResultDialog.Type) { - when (type) { - CoinBaseResultDialog.Type.PURCHASE_ERROR -> { - viewModel.logEvent(AnalyticsConstants.Coinbase.BUY_ERROR_CLOSE) - dismiss() - findNavController().popBackStack() - } - else -> {} - } - } - } - } - transactionStateDialog.showNow(parentFragmentManager, "CoinBaseBuyDashDialog") - } - - override fun onResume() { - super.onResume() - countDownTimer.start() - } - - override fun onPause() { - countDownTimer.cancel() - super.onPause() - } - - private fun setNetworkState(hasInternet: Boolean) { - binding.networkStatusStub.isVisible = !hasInternet - binding.previewOfflineGroup.isVisible = hasInternet - } - - private fun setConfirmBtnStyle(@StyleRes buttonStyle: Int, @ColorRes colorRes: Int) { - binding.confirmBtnContainer.setRoundedBackground(buttonStyle) - binding.confirmBtn.setTextColor(resources.getColor(colorRes)) - } - - private fun observeNavigationCallBack() { - findNavController().currentBackStackEntry?.savedStateHandle?.getLiveData("resume_review") - ?.observe(viewLifecycleOwner) { isOrderReviewResumed -> - if (isOrderReviewResumed) { - getNewBuyOrder() - } - } - } - - private fun getNewBuyOrder() { - viewModel.onRefreshOrderClicked( - amountViewModel.onContinueEvent.value?.second, - selectedPaymentMethodId - ) - } -} diff --git a/integrations/coinbase/src/main/java/org/dash/wallet/integration/coinbase_integration/ui/CoinbaseServicesFragment.kt b/integrations/coinbase/src/main/java/org/dash/wallet/integration/coinbase_integration/ui/CoinbaseServicesFragment.kt deleted file mode 100644 index 5ca84e23df..0000000000 --- a/integrations/coinbase/src/main/java/org/dash/wallet/integration/coinbase_integration/ui/CoinbaseServicesFragment.kt +++ /dev/null @@ -1,240 +0,0 @@ -/* - * Copyright 2021 Dash Core Group. - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ -package org.dash.wallet.integration.coinbase_integration.ui - -import android.animation.ObjectAnimator -import android.content.Intent -import android.net.Uri -import android.os.Bundle -import android.view.View -import androidx.activity.addCallback -import androidx.core.view.isVisible -import androidx.fragment.app.Fragment -import androidx.fragment.app.activityViewModels -import androidx.fragment.app.viewModels -import dagger.hilt.android.AndroidEntryPoint -import org.bitcoinj.core.Coin -import org.dash.wallet.common.databinding.FragmentIntegrationPortalBinding -import org.dash.wallet.common.services.analytics.AnalyticsConstants -import org.dash.wallet.common.services.analytics.AnalyticsService -import org.dash.wallet.common.ui.blinkAnimator -import org.dash.wallet.common.ui.dialogs.AdaptiveDialog -import org.dash.wallet.common.ui.viewBinding -import org.dash.wallet.common.util.safeNavigate -import org.dash.wallet.common.util.toFormattedString -import org.dash.wallet.integration.coinbase_integration.R -import org.dash.wallet.integration.coinbase_integration.ui.convert_currency.model.PaymentMethodsUiState -import org.dash.wallet.integration.coinbase_integration.viewmodels.CoinbaseActivityViewModel -import org.dash.wallet.integration.coinbase_integration.viewmodels.CoinbaseServicesViewModel -import javax.inject.Inject - -@AndroidEntryPoint -class CoinbaseServicesFragment : Fragment(R.layout.fragment_integration_portal) { - private val binding by viewBinding(FragmentIntegrationPortalBinding::bind) - private val viewModel by viewModels() - private var loadingDialog: AdaptiveDialog? = null - private val sharedViewModel: CoinbaseActivityViewModel by activityViewModels() - private var balanceAnimator: ObjectAnimator? = null - @Inject lateinit var analyticsService: AnalyticsService - - override fun onViewCreated(view: View, savedInstanceState: Bundle?) { - super.onViewCreated(view, savedInstanceState) - - binding.toolbar.setNavigationOnClickListener { - requireActivity().finish() - } - binding.toolbarTitle.text = getString(R.string.coinbase) - binding.toolbarIcon.setImageResource(R.drawable.ic_coinbase) - binding.balanceHeader.text = getString(R.string.balance_on_coinbase) - binding.transferSubtitle.text = getString(R.string.between_dash_wallet_and_coinbase) - binding.convertSubtitle.text = getString(R.string.between_dash_wallet_and_coinbase) - binding.disconnectTitle.text = getString(R.string.disconnect_coinbase_account) - - requireActivity().onBackPressedDispatcher.addCallback(viewLifecycleOwner) { - requireActivity().finish() - } - - binding.disconnectBtn.setOnClickListener { - viewModel.disconnectCoinbaseAccount() - } - - binding.buyBtn.setOnClickListener { - sharedViewModel.paymentMethodsUiState.observe(viewLifecycleOwner) { uiState -> - // New value received - when (uiState) { - is PaymentMethodsUiState.Success -> { - val paymentMethodsArray = uiState.paymentMethodsList.filter { it.isValid }.toTypedArray() - - if (paymentMethodsArray.isEmpty()) { - if (uiState.paymentMethodsList.isEmpty()) { - showNoPaymentMethodsError() - } else { - showBuyingNotAllowedError() - } - } else { - viewModel.logEvent(AnalyticsConstants.Coinbase.BUY_DASH) - safeNavigate(CoinbaseServicesFragmentDirections.servicesToBuyDash(paymentMethodsArray)) - } - } - is PaymentMethodsUiState.Error -> { - if (uiState.isError) { - showNoPaymentMethodsError() - } - } - is PaymentMethodsUiState.LoadingState ->{ - if (uiState.isLoading) { - showProgress(R.string.loading) - } else { - dismissProgress() - } - } - } - } - } - - binding.convertBtn.setOnClickListener { - viewModel.logEvent(AnalyticsConstants.Coinbase.CONVERT_DASH) - safeNavigate(CoinbaseServicesFragmentDirections.servicesToConvertCrypto(true)) - } - - binding.transferBtn.setOnClickListener { - viewModel.logEvent(AnalyticsConstants.Coinbase.TRANSFER_DASH) - safeNavigate(CoinbaseServicesFragmentDirections.servicesToTransferDash()) - } - - binding.balanceDash.setFormat(viewModel.balanceFormat) - binding.balanceDash.setApplyMarkup(false) - binding.balanceDash.setAmount(Coin.ZERO) - this.balanceAnimator = binding.balanceHeader.blinkAnimator - - binding.root.setOnRefreshListener { - viewModel.refreshBalance() - } - - viewModel.balanceUIState.observe(viewLifecycleOwner) { state -> - binding.balanceDash.setAmount(state.balance) - binding.balanceLocal.text = state.balanceFiat?.toFormattedString() ?: "" - - if (state.isUpdating) { - this.balanceAnimator?.start() - } else { - binding.root.isRefreshing = false - this.balanceAnimator?.end() - } - } - - viewModel.showLoading.observe(viewLifecycleOwner) { - if (it) { - showProgress(R.string.loading) - } else { - dismissProgress() - } - } - - viewModel.userAccountError.observe(viewLifecycleOwner) { - AdaptiveDialog.create( - R.drawable.ic_error, - getString(R.string.coinbase_dash_wallet_error_title), - getString(R.string.coinbase_dash_wallet_error_message), - getString(R.string.close), - getString(R.string.create_dash_account), - ).show(requireActivity()) { createAccount -> - if (createAccount == true) { - viewModel.logEvent(AnalyticsConstants.Coinbase.BUY_CREATE_ACCOUNT) - openCoinbaseWebsite() - } - } - } - - viewModel.coinbaseLogOutCallback.observe(viewLifecycleOwner) { - requireActivity().finish() - } - - viewModel.isDeviceConnectedToInternet.observe(viewLifecycleOwner){ isConnected -> - setNetworkState(isConnected) - } - - viewModel.refreshBalance() - } - - private fun showProgress(messageResId: Int) { - if (loadingDialog != null && loadingDialog?.isAdded == true) { - loadingDialog?.dismissAllowingStateLoss() - } - loadingDialog = AdaptiveDialog.progress(getString(messageResId)) - loadingDialog?.show(parentFragmentManager, "progress") - } - - private fun dismissProgress() { - if (loadingDialog != null && loadingDialog?.isAdded == true) { - loadingDialog?.dismissAllowingStateLoss() - } - } - - override fun onStop() { - dismissProgress() - super.onStop() - } - - private fun setNetworkState(hasInternet: Boolean) { - binding.lastKnownBalance.isVisible = !hasInternet - binding.networkStatusStub.isVisible = !hasInternet - binding.actionsView.isVisible = hasInternet - binding.disconnectBtn.isVisible = hasInternet - binding.disconnectedIndicator.isVisible = !hasInternet - } - - private fun showNoPaymentMethodsError() { - AdaptiveDialog.create( - R.drawable.ic_error, - getString(R.string.coinbase_no_payment_methods_error_title), - getString(R.string.coinbase_no_payment_methods_error_message), - getString(R.string.close), - getString(R.string.add_payment_method), - ).show(requireActivity()) { addMethod -> - if (addMethod == true) { - viewModel.logEvent(AnalyticsConstants.Coinbase.BUY_ADD_PAYMENT_METHOD) - openCoinbaseWebsite() - } - } - } - - private fun showBuyingNotAllowedError() { - AdaptiveDialog.create( - R.drawable.ic_error, - getString(R.string.coinbase_unusable_payment_method_error_title), - getString(R.string.coinbase_unusable_payment_method_error_message), - getString(R.string.close), - getString(R.string.coinbase_open_account), - ).show(requireActivity()) { addMethod -> - if (addMethod == true) { - openCoinbaseWebsite() - } - } - } - - private fun openCoinbaseWebsite() { - val defaultBrowser = Intent.makeMainSelectorActivity(Intent.ACTION_MAIN, Intent.CATEGORY_APP_BROWSER) - defaultBrowser.data = Uri.parse(getString(R.string.coinbase_website)) - startActivity(defaultBrowser) - } - - override fun onDestroy() { - super.onDestroy() - this.balanceAnimator = null - } -} diff --git a/integrations/coinbase/src/main/java/org/dash/wallet/integration/coinbase_integration/ui/convert_currency/model/BaseIdForFaitDataUIState.kt b/integrations/coinbase/src/main/java/org/dash/wallet/integration/coinbase_integration/ui/convert_currency/model/BaseIdForFaitDataUIState.kt deleted file mode 100644 index e140e2be67..0000000000 --- a/integrations/coinbase/src/main/java/org/dash/wallet/integration/coinbase_integration/ui/convert_currency/model/BaseIdForFaitDataUIState.kt +++ /dev/null @@ -1,9 +0,0 @@ -package org.dash.wallet.integration.coinbase_integration.ui.convert_currency.model - -import org.dash.wallet.integration.coinbase_integration.model.BaseIdForUSDData - -sealed class BaseIdForFaitDataUIState { - data class Success(val baseIdForFaitDataList: List): BaseIdForFaitDataUIState() - data class LoadingState(val isLoading:Boolean): BaseIdForFaitDataUIState() - data class Error(val isError:Boolean): BaseIdForFaitDataUIState() -} \ No newline at end of file diff --git a/integrations/coinbase/src/main/java/org/dash/wallet/integration/coinbase_integration/viewmodels/CoinbaseActivityViewModel.kt b/integrations/coinbase/src/main/java/org/dash/wallet/integration/coinbase_integration/viewmodels/CoinbaseActivityViewModel.kt deleted file mode 100644 index 214a3645e9..0000000000 --- a/integrations/coinbase/src/main/java/org/dash/wallet/integration/coinbase_integration/viewmodels/CoinbaseActivityViewModel.kt +++ /dev/null @@ -1,179 +0,0 @@ -/* - * Copyright 2021 Dash Core Group. - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ -package org.dash.wallet.integration.coinbase_integration.viewmodels - -import androidx.lifecycle.LiveData -import androidx.lifecycle.MutableLiveData -import androidx.lifecycle.ViewModel -import androidx.lifecycle.viewModelScope -import dagger.hilt.android.lifecycle.HiltViewModel -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.launch -import kotlinx.coroutines.runBlocking -import org.dash.wallet.common.data.ResponseResource -import org.dash.wallet.common.data.SingleLiveEvent -import org.dash.wallet.common.data.WalletUIConfig -import org.dash.wallet.common.ui.payment_method_picker.PaymentMethod -import org.dash.wallet.common.ui.payment_method_picker.PaymentMethodType -import org.dash.wallet.integration.coinbase_integration.repository.CoinBaseRepositoryInt -import org.dash.wallet.integration.coinbase_integration.ui.convert_currency.model.BaseIdForFaitDataUIState -import org.dash.wallet.integration.coinbase_integration.ui.convert_currency.model.PaymentMethodsUiState -import org.dash.wallet.integration.coinbase_integration.utils.CoinbaseConfig -import org.slf4j.LoggerFactory -import javax.inject.Inject - -@HiltViewModel -class CoinbaseActivityViewModel @Inject constructor( - private val config: CoinbaseConfig, - private val walletUIConfig: WalletUIConfig, - private val coinBaseRepository: CoinBaseRepositoryInt -) : ViewModel() { - - companion object { - private val log = LoggerFactory.getLogger(CoinbaseActivityViewModel::class.java) - } - private val _paymentMethodsUiState = MutableLiveData( - PaymentMethodsUiState.LoadingState(true) - ) - val paymentMethodsUiState: LiveData - get() = _paymentMethodsUiState - - private val _baseIdForFaitModelCoinBase = MutableLiveData( - BaseIdForFaitDataUIState.LoadingState(true) - ) - val baseIdForFaitModelCoinBase: LiveData - get() = _baseIdForFaitModelCoinBase - - val coinbaseLogOutCallback = SingleLiveEvent() - init { - viewModelScope.launch { - config.observe(CoinbaseConfig.LOGOUT_COINBASE) - .collect { - if (it == true) { - config.clearAll() - } - coinbaseLogOutCallback.value = it - } - } - } - - suspend fun loginToCoinbase(code: String): Boolean { - when (val response = coinBaseRepository.completeCoinbaseAuthentication(code)) { - is ResponseResource.Success -> { - if (response.value) { - return true - } - } - - is ResponseResource.Failure -> { - log.error("Coinbase login error ${response.errorCode}: ${response.errorBody ?: "empty"}") - } - } - - return false - } - fun getBaseIdForFaitModel() = viewModelScope.launch(Dispatchers.Main) { - _baseIdForFaitModelCoinBase.value = BaseIdForFaitDataUIState.LoadingState(true) - - when ( - val response = coinBaseRepository.getBaseIdForUSDModel( - walletUIConfig.getExchangeCurrencyCode() - ) - ) { - is ResponseResource.Success -> { - _baseIdForFaitModelCoinBase.value = BaseIdForFaitDataUIState.LoadingState(false) - response.value?.data?.let { - _baseIdForFaitModelCoinBase.value = BaseIdForFaitDataUIState.Success(it) - } - } - - is ResponseResource.Failure -> { - runBlocking { config.set(CoinbaseConfig.UPDATE_BASE_IDS, true) } - _baseIdForFaitModelCoinBase.value = BaseIdForFaitDataUIState.LoadingState(false) - _baseIdForFaitModelCoinBase.value = BaseIdForFaitDataUIState.Error(true) - } - else -> { } - } - } - - fun getPaymentMethods() = viewModelScope.launch(Dispatchers.Main) { - _paymentMethodsUiState.value = PaymentMethodsUiState.LoadingState(true) - - when (val response = coinBaseRepository.getActivePaymentMethods()) { - is ResponseResource.Success -> { - _paymentMethodsUiState.value = PaymentMethodsUiState.LoadingState(false) - - if (response.value.isEmpty()) { - _paymentMethodsUiState.value = PaymentMethodsUiState.Error(true) - } else { - val result = response.value - .map { - val type = paymentMethodTypeFromCoinbaseType(it.type ?: "") - val nameAccountPair = splitNameAndAccount(it.name, type) - PaymentMethod( - it.id ?: "", - nameAccountPair.first, - nameAccountPair.second, - "", // set "Checking" to get "****1234 • Checking" in subtitle - paymentMethodType = type, - isValid = it.isBuyingAllowed ?: false - ) - } - _paymentMethodsUiState.value = PaymentMethodsUiState.Success(result) - } - } - is ResponseResource.Failure -> { - _paymentMethodsUiState.value = PaymentMethodsUiState.Error(true) - } - } - } - - private fun splitNameAndAccount(nameAccount: String?, type: PaymentMethodType): Pair { - nameAccount?.let { - val match = when (type) { - PaymentMethodType.BankAccount, PaymentMethodType.Card, PaymentMethodType.PayPal -> { - "(\\d+)?\\s?[a-z]?\\*+".toRegex().find(nameAccount) - } - PaymentMethodType.Fiat -> { - "\\(.*\\)".toRegex().find(nameAccount) - } - else -> null - } - - return match?.range?.first?.let { index -> - val name = nameAccount.substring(0, index).trim(' ', '-', ',', ':') - val account = nameAccount.substring(index, nameAccount.length).trim() - return Pair(name, account) - } ?: Pair(nameAccount, "") - } - - return Pair("", "") - } - - private fun paymentMethodTypeFromCoinbaseType(type: String): PaymentMethodType { - return when (type) { - "fiat_account" -> PaymentMethodType.Fiat - "secure3d_card", "worldpay_card", "credit_card", "debit_card" -> PaymentMethodType.Card - "ach_bank_account", "sepa_bank_account", - "ideal_bank_account", "eft_bank_account", "interac" -> PaymentMethodType.BankAccount - "bank_wire" -> PaymentMethodType.WireTransfer - "paypal_account" -> PaymentMethodType.PayPal - "apple_pay" -> PaymentMethodType.ApplePay - else -> PaymentMethodType.Unknown - } - } -} diff --git a/integrations/coinbase/src/main/java/org/dash/wallet/integration/coinbase_integration/viewmodels/CoinbaseBuyDashOrderReviewViewModel.kt b/integrations/coinbase/src/main/java/org/dash/wallet/integration/coinbase_integration/viewmodels/CoinbaseBuyDashOrderReviewViewModel.kt deleted file mode 100644 index 45625357de..0000000000 --- a/integrations/coinbase/src/main/java/org/dash/wallet/integration/coinbase_integration/viewmodels/CoinbaseBuyDashOrderReviewViewModel.kt +++ /dev/null @@ -1,141 +0,0 @@ -/* - * Copyright 2021 Dash Core Group. - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ -package org.dash.wallet.integration.coinbase_integration.viewmodels - -import androidx.lifecycle.* -import dagger.hilt.android.lifecycle.HiltViewModel -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.ExperimentalCoroutinesApi -import kotlinx.coroutines.launch -import org.bitcoinj.utils.Fiat -import org.dash.wallet.common.WalletDataProvider -import org.dash.wallet.common.data.ServiceName -import org.dash.wallet.common.data.SingleLiveEvent -import org.dash.wallet.common.services.TransactionMetadataProvider -import org.dash.wallet.common.services.analytics.AnalyticsConstants -import org.dash.wallet.common.services.analytics.AnalyticsService -import org.dash.wallet.integration.coinbase_integration.model.* -import org.dash.wallet.common.data.ResponseResource -import org.dash.wallet.common.services.NetworkStateInt -import org.dash.wallet.integration.coinbase_integration.repository.CoinBaseRepositoryInt -import java.util.* -import javax.inject.Inject - -@ExperimentalCoroutinesApi -@HiltViewModel -class CoinbaseBuyDashOrderReviewViewModel @Inject constructor( - private val coinBaseRepository: CoinBaseRepositoryInt, - private val walletDataProvider: WalletDataProvider, - private val analyticsService: AnalyticsService, - networkState: NetworkStateInt, - private val transactionMetadataProvider: TransactionMetadataProvider -) : ViewModel() { - private val _showLoading: MutableLiveData = MutableLiveData() - val showLoading: LiveData - get() = _showLoading - - val commitBuyOrderFailureState = SingleLiveEvent() - - var sendFundToWalletParams: SendTransactionToWalletParams ? = null - val placeBuyOrderFailedCallback = SingleLiveEvent() - private val _placeBuyOrder: MutableLiveData = MutableLiveData() - val placeBuyOrder: LiveData - get() = _placeBuyOrder - val isDeviceConnectedToInternet: LiveData = networkState.isConnected.asLiveData() - - val commitBuyOrderSuccessState = SingleLiveEvent() - - fun commitBuyOrder(params: String) = viewModelScope.launch(Dispatchers.Main) { - analyticsService.logEvent(AnalyticsConstants.Coinbase.BUY_QUOTE_CONFIRM, mapOf()) - - _showLoading.value = true - when (val result = coinBaseRepository.commitBuyOrder(params)) { - is ResponseResource.Success -> { - _showLoading.value = false - if (result.value == BuyOrderResponse.EMPTY_COMMIT_BUY) { - commitBuyOrderFailureState.call() - } else { - sendFundToWalletParams = SendTransactionToWalletParams( - amount = result.value.dashAmount, - currency = result.value.dashCurrency, - idem = UUID.randomUUID().toString(), - to = walletDataProvider.freshReceiveAddress().toBase58(), - type = result.value.transactionType - ).apply { - commitBuyOrderSuccessState.value = this - transactionMetadataProvider.markAddressAsTransferInAsync(to!!, ServiceName.Coinbase) - } - - } - } - is ResponseResource.Failure -> { - _showLoading.value = false - val error = result.errorBody - if (error.isNullOrEmpty()) { - commitBuyOrderFailureState.call() - } else { - val message = CoinbaseErrorResponse.getErrorMessage(error)?.message - if (message.isNullOrEmpty()) { - commitBuyOrderFailureState.call() - } else { - commitBuyOrderFailureState.value = message - } - } - } - } - } - - fun logEvent(eventName: String) { - analyticsService.logEvent(eventName, mapOf()) - } - - private fun placeBuyOrder(params: PlaceBuyOrderParams) = viewModelScope.launch(Dispatchers.Main) { - _showLoading.value = true - when (val result = coinBaseRepository.placeBuyOrder(params)) { - is ResponseResource.Success -> { - if (result.value == BuyOrderResponse.EMPTY_PLACE_BUY) { - _showLoading.value = false - placeBuyOrderFailedCallback.call() - } else { - _showLoading.value = false - - _placeBuyOrder.value = result.value - } - } - is ResponseResource.Failure -> { - _showLoading.value = false - - val error = result.errorBody - if (error.isNullOrEmpty()) { - placeBuyOrderFailedCallback.call() - } else { - val message = CoinbaseErrorResponse.getErrorMessage(error)?.message - if (message.isNullOrEmpty()) { - placeBuyOrderFailedCallback.call() - } else { - placeBuyOrderFailedCallback.value = message - } - } - } - } - } - - fun onRefreshOrderClicked(fiat: Fiat?, paymentMethodId: String) { - analyticsService.logEvent(AnalyticsConstants.Coinbase.BUY_QUOTE_RETRY, mapOf()) - placeBuyOrder(PlaceBuyOrderParams(fiat?.toPlainString(), fiat?.currencyCode, paymentMethodId)) - } -} diff --git a/integrations/coinbase/src/main/java/org/dash/wallet/integration/coinbase_integration/viewmodels/CoinbaseBuyDashViewModel.kt b/integrations/coinbase/src/main/java/org/dash/wallet/integration/coinbase_integration/viewmodels/CoinbaseBuyDashViewModel.kt deleted file mode 100644 index 47bbb82bb5..0000000000 --- a/integrations/coinbase/src/main/java/org/dash/wallet/integration/coinbase_integration/viewmodels/CoinbaseBuyDashViewModel.kt +++ /dev/null @@ -1,195 +0,0 @@ -/* - * Copyright 2021 Dash Core Group. - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ -package org.dash.wallet.integration.coinbase_integration.viewmodels - -import androidx.lifecycle.* -import dagger.hilt.android.lifecycle.HiltViewModel -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.flow.first -import kotlinx.coroutines.launch -import org.bitcoinj.core.Coin -import org.bitcoinj.utils.Fiat -import org.dash.wallet.common.data.ResponseResource -import org.dash.wallet.common.data.SingleLiveEvent -import org.dash.wallet.common.data.entity.ExchangeRate -import org.dash.wallet.common.services.ExchangeRatesProvider -import org.dash.wallet.common.services.NetworkStateInt -import org.dash.wallet.common.services.analytics.AnalyticsConstants -import org.dash.wallet.common.services.analytics.AnalyticsService -import org.dash.wallet.common.ui.payment_method_picker.PaymentMethod -import org.dash.wallet.common.util.Constants -import org.dash.wallet.common.util.GenericUtils -import org.dash.wallet.integration.coinbase_integration.CoinbaseConstants -import org.dash.wallet.integration.coinbase_integration.model.* -import org.dash.wallet.integration.coinbase_integration.repository.CoinBaseRepositoryInt -import org.dash.wallet.integration.coinbase_integration.utils.CoinbaseConfig -import java.lang.NumberFormatException -import javax.inject.Inject - -@HiltViewModel -class CoinbaseBuyDashViewModel @Inject constructor( - private val coinBaseRepository: CoinBaseRepositoryInt, - private val config: CoinbaseConfig, - var exchangeRates: ExchangeRatesProvider, - networkState: NetworkStateInt, - private val analyticsService: AnalyticsService -) : ViewModel() { - private val _showLoading: MutableLiveData = MutableLiveData() - val showLoading: LiveData - get() = _showLoading - - private val _activePaymentMethods: MutableLiveData> = MutableLiveData() - val activePaymentMethods: LiveData> - get() = _activePaymentMethods - - val isDeviceConnectedToInternet: LiveData = networkState.isConnected.asLiveData() - - val placeBuyOrder = SingleLiveEvent() - val placeBuyOrderFailedCallback = SingleLiveEvent() - - var exchangeRate: ExchangeRate? = null - private set - - init { - getWithdrawalLimit() - } - - fun onContinueClicked( - dashToFiat: Boolean, - dashAmount: CharSequence, - paymentMethodIndex: Int - ) { - _activePaymentMethods.value?.let { - if (paymentMethodIndex < it.size) { - val paymentMethod = it[paymentMethodIndex] - - analyticsService.logEvent(AnalyticsConstants.Coinbase.BUY_CONTINUE, mapOf()) - analyticsService.logEvent( - AnalyticsConstants.Coinbase.BUY_PAYMENT_METHOD, - mapOf( - AnalyticsConstants.Parameter.VALUE to paymentMethod.paymentMethodType.name - ) - ) - analyticsService.logEvent( - if (dashToFiat) { - AnalyticsConstants.Coinbase.BUY_ENTER_DASH - } else { - AnalyticsConstants.Coinbase.BUY_ENTER_FIAT - }, - mapOf() - ) - - viewModelScope.launch { - placeBuyOrder( - PlaceBuyOrderParams( - dashAmount.toString(), - Constants.DASH_CURRENCY, - paymentMethod.paymentMethodId - ) - ) - } - } - } - } - - private suspend fun placeBuyOrder(params: PlaceBuyOrderParams) { - _showLoading.value = true - when (val result = coinBaseRepository.placeBuyOrder(params)) { - is ResponseResource.Success -> { - if (result.value == BuyOrderResponse.EMPTY_PLACE_BUY) { - _showLoading.value = false - placeBuyOrderFailedCallback.call() - } else { - _showLoading.value = false - placeBuyOrder.value = result.value - } - } - is ResponseResource.Failure -> { - _showLoading.value = false - - val error = result.errorBody - if (error.isNullOrEmpty()) { - placeBuyOrderFailedCallback.call() - } else { - val message = CoinbaseErrorResponse.getErrorMessage(error)?.message - if (message.isNullOrEmpty()) { - placeBuyOrderFailedCallback.call() - } else { - placeBuyOrderFailedCallback.value = message - } - } - } - } - } - - fun setActivePaymentMethods(coinbasePaymentMethods: Array) { - _activePaymentMethods.value = coinbasePaymentMethods.toList() - } - - private fun getWithdrawalLimit() = viewModelScope.launch(Dispatchers.Main) { - when (val response = coinBaseRepository.getWithdrawalLimit()) { - is ResponseResource.Success -> { - val withdrawalLimit = response.value - exchangeRate = getCurrencyExchangeRate(withdrawalLimit.currency) - } - is ResponseResource.Failure -> { - // todo use case when limit is not fetched - } - } - } - - suspend fun isInputGreaterThanLimit(amountInDash: Coin): Boolean { - return amountInDash.toPlainString().toDoubleOrZero.compareTo(getWithdrawalLimitInDash()) > 0 - } - - fun logEvent(eventName: String) { - analyticsService.logEvent(eventName, mapOf()) - } - - private suspend fun getWithdrawalLimitInDash(): Double { - val withdrawalLimit = config.get(CoinbaseConfig.USER_WITHDRAWAL_LIMIT) - return if (withdrawalLimit.isNullOrEmpty()) { - 0.0 - } else { - val formattedAmount = GenericUtils.formatFiatWithoutComma(withdrawalLimit) - val currency = config.get(CoinbaseConfig.SEND_LIMIT_CURRENCY) ?: CoinbaseConstants.DEFAULT_CURRENCY_USD - val fiatAmount = try { - Fiat.parseFiat(currency, formattedAmount) - } catch (x: Exception) { - Fiat.valueOf(currency, 0) - } - if (exchangeRate?.fiat != null) { - val newRate = org.bitcoinj.utils.ExchangeRate(Coin.COIN, exchangeRate?.fiat) - val amountInDash = newRate.fiatToCoin(fiatAmount) - amountInDash.toPlainString().toDoubleOrZero - } else { - 0.0 - } - } - } - - private suspend fun getCurrencyExchangeRate(currency: String): ExchangeRate? { - return exchangeRates.observeExchangeRate(currency).first() - } -} - -val String.toDoubleOrZero: Double - get() = try { - this.toDouble() - } catch (e: NumberFormatException) { - 0.0 - } diff --git a/integrations/coinbase/src/main/java/org/dash/wallet/integration/coinbase_integration/CoinbaseConstants.kt b/integrations/coinbase/src/main/java/org/dash/wallet/integrations/coinbase/CoinbaseConstants.kt similarity index 75% rename from integrations/coinbase/src/main/java/org/dash/wallet/integration/coinbase_integration/CoinbaseConstants.kt rename to integrations/coinbase/src/main/java/org/dash/wallet/integrations/coinbase/CoinbaseConstants.kt index e49f5eebf9..bd8300462e 100644 --- a/integrations/coinbase/src/main/java/org/dash/wallet/integration/coinbase_integration/CoinbaseConstants.kt +++ b/integrations/coinbase/src/main/java/org/dash/wallet/integrations/coinbase/CoinbaseConstants.kt @@ -14,7 +14,7 @@ * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ -package org.dash.wallet.integration.coinbase_integration +package org.dash.wallet.integrations.coinbase import android.content.Context import java.io.File @@ -25,13 +25,9 @@ object CoinbaseConstants { const val CB_2FA_TOKEN_KEY = "CB-2FA-TOKEN" const val CB_VERSION_VALUE = "2021-09-07" const val TRANSACTION_TYPE_SEND = "send" - const val TRANSACTION_STATUS_PENDING = "pending" - const val TRANSACTION_STATUS_COMPLETED = "completed" - const val TRANSACTION_STATUS_FAILED = "failed" - const val TRANSACTION_STATUS_EXPIRED = "expired" - const val TRANSACTION_STATUS_CANCELED = "canceled" - const val TRANSACTION_STATUS_WAITING_FOR_SIGNATURE = "waiting_for_signature" - const val TRANSACTION_STATUS_WAITING_FOR_CLEARING = "waiting_for_clearing" + const val TRANSACTION_TYPE_BUY = "BUY" + const val DASH_USD_PAIR = "DASH-USD" + const val FIAT_ACCOUNT_TYPE = "ACCOUNT_TYPE_FIAT" const val ERROR_ID_INVALID_REQUEST = "invalid_request" const val ERROR_ID_2FA_REQUIRED = "two_factor_required" const val ERROR_MSG_INVALID_REQUEST = "That code was invalid" @@ -39,6 +35,7 @@ object CoinbaseConstants { const val DEFAULT_CURRENCY_USD = "USD" const val MIN_USD_COINBASE_AMOUNT = "2" const val BASE_IDS_REQUEST_URL = "v2/assets/prices?filter=holdable&resolution=latest" + const val BUY_FEE = 0.006 fun getCacheDir(context: Context): File { return File(context.cacheDir, "coinbase") diff --git a/integrations/coinbase/src/main/java/org/dash/wallet/integration/coinbase_integration/Mapper.kt b/integrations/coinbase/src/main/java/org/dash/wallet/integrations/coinbase/Mapper.kt similarity index 61% rename from integrations/coinbase/src/main/java/org/dash/wallet/integration/coinbase_integration/Mapper.kt rename to integrations/coinbase/src/main/java/org/dash/wallet/integrations/coinbase/Mapper.kt index e2c23344db..f2f63967d0 100644 --- a/integrations/coinbase/src/main/java/org/dash/wallet/integration/coinbase_integration/Mapper.kt +++ b/integrations/coinbase/src/main/java/org/dash/wallet/integrations/coinbase/Mapper.kt @@ -15,34 +15,14 @@ * along with this program. If not, see . */ -package org.dash.wallet.integration.coinbase_integration +package org.dash.wallet.integrations.coinbase -import org.dash.wallet.integration.coinbase_integration.model.* +import org.dash.wallet.integrations.coinbase.model.* interface Mapper { fun map(input: Input): Output } -class PlaceBuyOrderMapper : Mapper { - override fun map(input: BuyOrderData?): PlaceBuyOrderUIModel { - return if (input == null) - BuyOrderResponse.EMPTY_PLACE_BUY - else { - PlaceBuyOrderUIModel( - input.id ?: "", - input.paymentMethod?.id ?: "", - input.subtotal?.amount ?: "", - input.subtotal?.currency ?: "", - input.fee?.amount ?: "", - input.fee?.currency ?: "", - input.total?.amount ?: "", - input.total?.currency ?: "", - input.amount?.amount ?: "" - ) - } - } -} - class SwapTradeMapper : Mapper { override fun map(input: SwapTradeResponseData?): SwapTradeUIModel { return if (input == null) @@ -63,16 +43,6 @@ class SwapTradeMapper : Mapper { } } -class CommitBuyOrderMapper : Mapper { - override fun map(input: BuyOrderData?): CommitBuyOrderUIModel { - return if (input == null) - BuyOrderResponse.EMPTY_COMMIT_BUY - else { - CommitBuyOrderUIModel(dashAmount = input.amount?.amount ?: "") - } - } -} - class CoinbaseAddressMapper : Mapper { override fun map(input: CoinBaseAccountAddressResponse?): String { return if (input == null) diff --git a/integrations/coinbase/src/main/java/org/dash/wallet/integration/coinbase_integration/di/CoinBaseModule.kt b/integrations/coinbase/src/main/java/org/dash/wallet/integrations/coinbase/di/CoinBaseModule.kt similarity index 66% rename from integrations/coinbase/src/main/java/org/dash/wallet/integration/coinbase_integration/di/CoinBaseModule.kt rename to integrations/coinbase/src/main/java/org/dash/wallet/integrations/coinbase/di/CoinBaseModule.kt index ad5b2f1579..9733216531 100644 --- a/integrations/coinbase/src/main/java/org/dash/wallet/integration/coinbase_integration/di/CoinBaseModule.kt +++ b/integrations/coinbase/src/main/java/org/dash/wallet/integrations/coinbase/di/CoinBaseModule.kt @@ -14,7 +14,7 @@ * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ -package org.dash.wallet.integration.coinbase_integration.di +package org.dash.wallet.integrations.coinbase.di import android.content.Context import dagger.Binds @@ -24,16 +24,14 @@ import dagger.hilt.InstallIn import dagger.hilt.android.qualifiers.ApplicationContext import dagger.hilt.components.SingletonComponent import org.dash.wallet.common.Configuration -import org.dash.wallet.integration.coinbase_integration.CoinbaseAddressMapper -import org.dash.wallet.integration.coinbase_integration.CommitBuyOrderMapper -import org.dash.wallet.integration.coinbase_integration.PlaceBuyOrderMapper -import org.dash.wallet.integration.coinbase_integration.SwapTradeMapper -import org.dash.wallet.integration.coinbase_integration.network.RemoteDataSource -import org.dash.wallet.integration.coinbase_integration.repository.CoinBaseRepository -import org.dash.wallet.integration.coinbase_integration.repository.CoinBaseRepositoryInt -import org.dash.wallet.integration.coinbase_integration.service.CoinBaseAuthApi -import org.dash.wallet.integration.coinbase_integration.service.CoinBaseServicesApi -import org.dash.wallet.integration.coinbase_integration.utils.CoinbaseConfig +import org.dash.wallet.integrations.coinbase.CoinbaseAddressMapper +import org.dash.wallet.integrations.coinbase.SwapTradeMapper +import org.dash.wallet.integrations.coinbase.network.RemoteDataSource +import org.dash.wallet.integrations.coinbase.repository.CoinBaseRepository +import org.dash.wallet.integrations.coinbase.repository.CoinBaseRepositoryInt +import org.dash.wallet.integrations.coinbase.service.CoinBaseAuthApi +import org.dash.wallet.integrations.coinbase.service.CoinBaseServicesApi +import org.dash.wallet.integrations.coinbase.utils.CoinbaseConfig import javax.inject.Singleton @Module @@ -65,10 +63,6 @@ object CoinBaseModule { return remoteDataSource.buildApi(CoinBaseServicesApi::class.java) } - @Provides - fun providePlaceBuyOrderMapper(): PlaceBuyOrderMapper = PlaceBuyOrderMapper() - @Provides - fun provideCommitBuyOrderMapper(): CommitBuyOrderMapper = CommitBuyOrderMapper() @Provides fun provideSwapTradeMapper(): SwapTradeMapper = SwapTradeMapper() diff --git a/integrations/coinbase/src/main/java/org/dash/wallet/integrations/coinbase/model/AccountsResponse.kt b/integrations/coinbase/src/main/java/org/dash/wallet/integrations/coinbase/model/AccountsResponse.kt new file mode 100644 index 0000000000..551d220934 --- /dev/null +++ b/integrations/coinbase/src/main/java/org/dash/wallet/integrations/coinbase/model/AccountsResponse.kt @@ -0,0 +1,60 @@ +/* + * Copyright 2023 Dash Core Group. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package org.dash.wallet.integrations.coinbase.model + +import android.os.Parcelable +import com.google.gson.annotations.SerializedName +import kotlinx.parcelize.Parcelize +import java.util.UUID + +@Parcelize +data class AccountsResponse( + val accounts: List +): Parcelable + +@Parcelize +data class CoinbaseAccount( + val uuid: UUID, + val name: String, + val currency: String, + @SerializedName("available_balance") + val availableBalance: Balance, + val default: Boolean, + val active: Boolean, + val type: String, + val ready: Boolean +): Parcelable { + companion object { + val EMPTY = CoinbaseAccount( + UUID.randomUUID(), + "", + "", + Balance("", ""), + default = false, + active = false, + type = "", + ready = false + ) + } +} + +@Parcelize +data class Balance( + val value: String, + val currency: String +): Parcelable diff --git a/integrations/coinbase/src/main/java/org/dash/wallet/integration/coinbase_integration/model/AddressesResponse.kt b/integrations/coinbase/src/main/java/org/dash/wallet/integrations/coinbase/model/AddressesResponse.kt similarity index 91% rename from integrations/coinbase/src/main/java/org/dash/wallet/integration/coinbase_integration/model/AddressesResponse.kt rename to integrations/coinbase/src/main/java/org/dash/wallet/integrations/coinbase/model/AddressesResponse.kt index b038b06852..2db856920a 100644 --- a/integrations/coinbase/src/main/java/org/dash/wallet/integration/coinbase_integration/model/AddressesResponse.kt +++ b/integrations/coinbase/src/main/java/org/dash/wallet/integrations/coinbase/model/AddressesResponse.kt @@ -1,8 +1,8 @@ -package org.dash.wallet.integration.coinbase_integration.model +package org.dash.wallet.integrations.coinbase.model import android.os.Parcelable import com.google.gson.annotations.SerializedName -import kotlinx.android.parcel.Parcelize +import kotlinx.parcelize.Parcelize @Parcelize data class AddressesResponse( diff --git a/integrations/coinbase/src/main/java/org/dash/wallet/integration/coinbase_integration/model/BaseIdForUSDModel.kt b/integrations/coinbase/src/main/java/org/dash/wallet/integrations/coinbase/model/BaseIdForUSDModel.kt similarity index 94% rename from integrations/coinbase/src/main/java/org/dash/wallet/integration/coinbase_integration/model/BaseIdForUSDModel.kt rename to integrations/coinbase/src/main/java/org/dash/wallet/integrations/coinbase/model/BaseIdForUSDModel.kt index bbfda9c6ab..3af936c7b3 100644 --- a/integrations/coinbase/src/main/java/org/dash/wallet/integration/coinbase_integration/model/BaseIdForUSDModel.kt +++ b/integrations/coinbase/src/main/java/org/dash/wallet/integrations/coinbase/model/BaseIdForUSDModel.kt @@ -14,7 +14,7 @@ * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ -package org.dash.wallet.integration.coinbase_integration.model +package org.dash.wallet.integrations.coinbase.model import android.os.Parcelable import com.google.gson.annotations.SerializedName diff --git a/integrations/coinbase/src/main/java/org/dash/wallet/integration/coinbase_integration/model/CoinBaseAccountAddressResponse.kt b/integrations/coinbase/src/main/java/org/dash/wallet/integrations/coinbase/model/CoinBaseAccountAddressResponse.kt similarity index 65% rename from integrations/coinbase/src/main/java/org/dash/wallet/integration/coinbase_integration/model/CoinBaseAccountAddressResponse.kt rename to integrations/coinbase/src/main/java/org/dash/wallet/integrations/coinbase/model/CoinBaseAccountAddressResponse.kt index 19b383acb5..2bc0dbb4b7 100644 --- a/integrations/coinbase/src/main/java/org/dash/wallet/integration/coinbase_integration/model/CoinBaseAccountAddressResponse.kt +++ b/integrations/coinbase/src/main/java/org/dash/wallet/integrations/coinbase/model/CoinBaseAccountAddressResponse.kt @@ -1,8 +1,24 @@ -package org.dash.wallet.integration.coinbase_integration.model +/* + * Copyright 2021 Dash Core Group. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package org.dash.wallet.integrations.coinbase.model import android.os.Parcelable import com.google.gson.annotations.SerializedName -import kotlinx.android.parcel.Parcelize +import kotlinx.parcelize.Parcelize @Parcelize data class CoinBaseAccountAddressResponse( @@ -94,3 +110,15 @@ data class DataItem( @field:SerializedName("id") val id: String? = null ) : Parcelable + +@Parcelize +data class Pagination( + val ending_before: String? = null, + val limit: Int? = null, + val next_starting_after: String? = null, + val next_uri: String? = null, + val order: String? = null, + val previous_ending_before: String? = null, + val previous_uri: String? = null, + val starting_after: String? = null +) : Parcelable diff --git a/integrations/coinbase/src/main/java/org/dash/wallet/integration/coinbase_integration/model/CoinBaseExchangeRate.kt b/integrations/coinbase/src/main/java/org/dash/wallet/integrations/coinbase/model/CoinBaseExchangeRate.kt similarity index 94% rename from integrations/coinbase/src/main/java/org/dash/wallet/integration/coinbase_integration/model/CoinBaseExchangeRate.kt rename to integrations/coinbase/src/main/java/org/dash/wallet/integrations/coinbase/model/CoinBaseExchangeRate.kt index 8bd6e6bb55..cb6138e3ab 100644 --- a/integrations/coinbase/src/main/java/org/dash/wallet/integration/coinbase_integration/model/CoinBaseExchangeRate.kt +++ b/integrations/coinbase/src/main/java/org/dash/wallet/integrations/coinbase/model/CoinBaseExchangeRate.kt @@ -14,7 +14,7 @@ * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ -package org.dash.wallet.integration.coinbase_integration.model +package org.dash.wallet.integrations.coinbase.model import androidx.collection.ArrayMap import com.google.gson.annotations.SerializedName diff --git a/integrations/coinbase/src/main/java/org/dash/wallet/integration/coinbase_integration/model/CoinBaseExchangeRates.kt b/integrations/coinbase/src/main/java/org/dash/wallet/integrations/coinbase/model/CoinBaseExchangeRates.kt similarity index 94% rename from integrations/coinbase/src/main/java/org/dash/wallet/integration/coinbase_integration/model/CoinBaseExchangeRates.kt rename to integrations/coinbase/src/main/java/org/dash/wallet/integrations/coinbase/model/CoinBaseExchangeRates.kt index a29236065d..66aa476486 100644 --- a/integrations/coinbase/src/main/java/org/dash/wallet/integration/coinbase_integration/model/CoinBaseExchangeRates.kt +++ b/integrations/coinbase/src/main/java/org/dash/wallet/integrations/coinbase/model/CoinBaseExchangeRates.kt @@ -14,7 +14,7 @@ * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ -package org.dash.wallet.integration.coinbase_integration.model +package org.dash.wallet.integrations.coinbase.model import android.os.Parcelable import com.google.gson.annotations.SerializedName diff --git a/integrations/coinbase/src/main/java/org/dash/wallet/integrations/coinbase/model/CoinBaseUserAccountInfo.kt b/integrations/coinbase/src/main/java/org/dash/wallet/integrations/coinbase/model/CoinBaseUserAccountInfo.kt new file mode 100644 index 0000000000..adf950b076 --- /dev/null +++ b/integrations/coinbase/src/main/java/org/dash/wallet/integrations/coinbase/model/CoinBaseUserAccountInfo.kt @@ -0,0 +1,72 @@ +/* + * Copyright 2021 Dash Core Group. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package org.dash.wallet.integrations.coinbase.model + +import android.os.Parcelable +import kotlinx.parcelize.Parcelize +import org.bitcoinj.core.Coin +import org.bitcoinj.utils.Fiat +import org.dash.wallet.common.data.entity.ExchangeRate +import org.dash.wallet.common.util.toFormattedString +import java.math.BigDecimal +import java.math.RoundingMode + +@Parcelize +data class CoinBaseUserAccountDataUIModel( + override val coinbaseAccount: CoinbaseAccount, + val currencyToCryptoCurrencyExchangeRate: BigDecimal, + override val currencyToDashExchangeRate: BigDecimal, + override val currencyToUSDExchangeRate: BigDecimal +) : CoinbaseToDashExchangeRateUIModel( + coinbaseAccount, + currencyToDashExchangeRate, + currencyToUSDExchangeRate +) { + fun getCryptoToDashExchangeRate(): BigDecimal { + return currencyToDashExchangeRate / currencyToCryptoCurrencyExchangeRate + } +} + +fun CoinBaseUserAccountDataUIModel.getCoinBaseExchangeRateConversion( + currentExchangeRate: ExchangeRate +): Pair { + val cleanedValue = + this.coinbaseAccount.availableBalance.value.toBigDecimal() / + this.currencyToCryptoCurrencyExchangeRate + val bd = cleanedValue.setScale(8, RoundingMode.HALF_UP) + + val currencyRate = org.bitcoinj.utils.ExchangeRate(Coin.COIN, currentExchangeRate.fiat) + val fiatAmount = Fiat.parseFiat(currencyRate.fiat.currencyCode, bd.toString()) + val dashAmount = currencyRate.fiatToCoin(fiatAmount) + + return Pair(fiatAmount.toFormattedString(), dashAmount) +} + +@Parcelize +open class CoinbaseToDashExchangeRateUIModel( + open val coinbaseAccount: CoinbaseAccount, + open val currencyToDashExchangeRate: BigDecimal, + open val currencyToUSDExchangeRate: BigDecimal +): Parcelable { + companion object { + val EMPTY = CoinbaseToDashExchangeRateUIModel( + CoinbaseAccount.EMPTY, + BigDecimal.ZERO, + BigDecimal.ZERO + ) + } +} diff --git a/integrations/coinbase/src/main/java/org/dash/wallet/integration/coinbase_integration/model/CoinbaseErrorResponse.kt b/integrations/coinbase/src/main/java/org/dash/wallet/integrations/coinbase/model/CoinbaseErrorResponse.kt similarity index 82% rename from integrations/coinbase/src/main/java/org/dash/wallet/integration/coinbase_integration/model/CoinbaseErrorResponse.kt rename to integrations/coinbase/src/main/java/org/dash/wallet/integrations/coinbase/model/CoinbaseErrorResponse.kt index 0ac2d17e16..0dcbda96ca 100644 --- a/integrations/coinbase/src/main/java/org/dash/wallet/integration/coinbase_integration/model/CoinbaseErrorResponse.kt +++ b/integrations/coinbase/src/main/java/org/dash/wallet/integrations/coinbase/model/CoinbaseErrorResponse.kt @@ -15,12 +15,24 @@ * along with this program. If not, see . */ -package org.dash.wallet.integration.coinbase_integration.model +package org.dash.wallet.integrations.coinbase.model import android.os.Parcelable import com.google.gson.Gson import kotlinx.parcelize.Parcelize +enum class CoinbaseErrorType { + NONE, + BUY_FAILED, + USER_ACCOUNT_ERROR, + INSUFFICIENT_BALANCE, + NO_BANK_ACCOUNT, + NO_EXCHANGE_RATE, + DEPOSIT_FAILED +} + +class CoinbaseException(val errorType: CoinbaseErrorType, message: String?) : Exception(message) + @Parcelize data class CoinbaseErrorResponse( val errors: List? = null diff --git a/integrations/coinbase/src/main/java/org/dash/wallet/integrations/coinbase/model/CoinbaseInfraModels.kt b/integrations/coinbase/src/main/java/org/dash/wallet/integrations/coinbase/model/CoinbaseInfraModels.kt new file mode 100644 index 0000000000..767168c178 --- /dev/null +++ b/integrations/coinbase/src/main/java/org/dash/wallet/integrations/coinbase/model/CoinbaseInfraModels.kt @@ -0,0 +1,34 @@ +/* + * Copyright 2023 Dash Core Group. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package org.dash.wallet.integrations.coinbase.model + +import android.os.Parcelable +import com.google.gson.annotations.SerializedName +import kotlinx.parcelize.Parcelize + +@Parcelize +data class OrderConfiguration( + @SerializedName("market_market_ioc") + val marketMarketIoc: MarketMarketIoc +): Parcelable + +@Parcelize +data class MarketMarketIoc( + @SerializedName("quote_size") + val quoteSize: String +): Parcelable diff --git a/integrations/coinbase/src/main/java/org/dash/wallet/integration/coinbase_integration/model/CoinbasePaymentMethod.kt b/integrations/coinbase/src/main/java/org/dash/wallet/integrations/coinbase/model/CoinbasePaymentMethod.kt similarity index 93% rename from integrations/coinbase/src/main/java/org/dash/wallet/integration/coinbase_integration/model/CoinbasePaymentMethod.kt rename to integrations/coinbase/src/main/java/org/dash/wallet/integrations/coinbase/model/CoinbasePaymentMethod.kt index f106f2ed9f..7781440466 100644 --- a/integrations/coinbase/src/main/java/org/dash/wallet/integration/coinbase_integration/model/CoinbasePaymentMethod.kt +++ b/integrations/coinbase/src/main/java/org/dash/wallet/integrations/coinbase/model/CoinbasePaymentMethod.kt @@ -15,7 +15,7 @@ * along with this program. If not, see . */ -package org.dash.wallet.integration.coinbase_integration.model +package org.dash.wallet.integrations.coinbase.model import android.os.Parcelable import kotlinx.parcelize.Parcelize diff --git a/integrations/coinbase/src/main/java/org/dash/wallet/integrations/coinbase/model/DepositRequest.kt b/integrations/coinbase/src/main/java/org/dash/wallet/integrations/coinbase/model/DepositRequest.kt new file mode 100644 index 0000000000..1f2401013d --- /dev/null +++ b/integrations/coinbase/src/main/java/org/dash/wallet/integrations/coinbase/model/DepositRequest.kt @@ -0,0 +1,30 @@ +/* + * Copyright 2023 Dash Core Group. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package org.dash.wallet.integrations.coinbase.model + +import android.os.Parcelable +import com.google.gson.annotations.SerializedName +import kotlinx.parcelize.Parcelize + +@Parcelize +data class DepositRequest( + val amount: String, + val currency: String, + @SerializedName("payment_method") + val paymentMethod: String +): Parcelable diff --git a/integrations/coinbase/src/main/java/org/dash/wallet/integrations/coinbase/model/DepositResponse.kt b/integrations/coinbase/src/main/java/org/dash/wallet/integrations/coinbase/model/DepositResponse.kt new file mode 100644 index 0000000000..31140f64bf --- /dev/null +++ b/integrations/coinbase/src/main/java/org/dash/wallet/integrations/coinbase/model/DepositResponse.kt @@ -0,0 +1,43 @@ +/* + * Copyright 2023 Dash Core Group. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package org.dash.wallet.integrations.coinbase.model + +import android.os.Parcelable +import com.google.gson.annotations.SerializedName +import kotlinx.parcelize.Parcelize + +@Parcelize +data class DepositResponse( + val data: DepositResponseData +): Parcelable + +@Parcelize +data class DepositResponseData( + val id: String, + val status: String, + @SerializedName("created_at") + val createdAt: String, + @SerializedName("updated_at") + val updatedAt: String, + val resource: String, + @SerializedName("resource_path") + val resourcePath: String, + val committed: Boolean, + @SerializedName("payout_at") + val payoutAt: String +): Parcelable diff --git a/integrations/coinbase/src/main/java/org/dash/wallet/integration/coinbase_integration/model/PaymentMethods.kt b/integrations/coinbase/src/main/java/org/dash/wallet/integrations/coinbase/model/PaymentMethods.kt similarity index 70% rename from integrations/coinbase/src/main/java/org/dash/wallet/integration/coinbase_integration/model/PaymentMethods.kt rename to integrations/coinbase/src/main/java/org/dash/wallet/integrations/coinbase/model/PaymentMethods.kt index d58600d3ab..7827faaebc 100644 --- a/integrations/coinbase/src/main/java/org/dash/wallet/integration/coinbase_integration/model/PaymentMethods.kt +++ b/integrations/coinbase/src/main/java/org/dash/wallet/integrations/coinbase/model/PaymentMethods.kt @@ -1,4 +1,20 @@ -package org.dash.wallet.integration.coinbase_integration.model +/* + * Copyright 2021 Dash Core Group. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package org.dash.wallet.integrations.coinbase.model import com.google.gson.annotations.SerializedName @@ -57,4 +73,4 @@ data class Limits( data class MinimumPurchaseAmount( val amount: String? = null, val currency: String? = null -) \ No newline at end of file +) diff --git a/integrations/coinbase/src/main/java/org/dash/wallet/integrations/coinbase/model/PlaceOrderParams.kt b/integrations/coinbase/src/main/java/org/dash/wallet/integrations/coinbase/model/PlaceOrderParams.kt new file mode 100644 index 0000000000..0b8bdde1ca --- /dev/null +++ b/integrations/coinbase/src/main/java/org/dash/wallet/integrations/coinbase/model/PlaceOrderParams.kt @@ -0,0 +1,34 @@ +/* + * Copyright 2023 Dash Core Group. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package org.dash.wallet.integrations.coinbase.model + +import android.os.Parcelable +import com.google.gson.annotations.SerializedName +import kotlinx.parcelize.Parcelize +import java.util.UUID + +@Parcelize +data class PlaceOrderParams( + @SerializedName("client_order_id") + val clientOrderId: UUID, + @SerializedName("product_id") + val productId: String, + val side: String, + @SerializedName("order_configuration") + val orderConfiguration: OrderConfiguration +): Parcelable diff --git a/integrations/coinbase/src/main/java/org/dash/wallet/integrations/coinbase/model/PlaceOrderResponse.kt b/integrations/coinbase/src/main/java/org/dash/wallet/integrations/coinbase/model/PlaceOrderResponse.kt new file mode 100644 index 0000000000..0f1dd3ac59 --- /dev/null +++ b/integrations/coinbase/src/main/java/org/dash/wallet/integrations/coinbase/model/PlaceOrderResponse.kt @@ -0,0 +1,60 @@ +/* + * Copyright 2023 Dash Core Group. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package org.dash.wallet.integrations.coinbase.model + +import android.os.Parcelable +import com.google.gson.annotations.SerializedName +import kotlinx.parcelize.Parcelize + +@Parcelize +data class PlaceOrderResponse( + val success: Boolean, + @SerializedName("failure_reason") + val failureReason: String, + @SerializedName("order_id") + val orderId: String, + @SerializedName("error_response") + val errorResponse: ErrorResponse?, + @SerializedName("success_response") + val successResponse: SuccessResponse?, + @SerializedName("order_configuration") + val orderConfiguration: OrderConfiguration? +): Parcelable + +@Parcelize +data class SuccessResponse( + @SerializedName("order_id") + val orderId: String, + @SerializedName("product_id") + val productId: String, + val side: String, + @SerializedName("client_order_id") + val clientOrderId: String +): Parcelable + +@Parcelize +data class ErrorResponse( + @SerializedName("error") + val error: String, + @SerializedName("message") + val message: String, + @SerializedName("error_details") + val errorDetails: String, + @SerializedName("preview_failure_reason") + val previewFailureReason: String +): Parcelable diff --git a/integrations/coinbase/src/main/java/org/dash/wallet/integrations/coinbase/model/ProductsResponse.kt b/integrations/coinbase/src/main/java/org/dash/wallet/integrations/coinbase/model/ProductsResponse.kt new file mode 100644 index 0000000000..8aa743e3bb --- /dev/null +++ b/integrations/coinbase/src/main/java/org/dash/wallet/integrations/coinbase/model/ProductsResponse.kt @@ -0,0 +1,44 @@ +/* + * Copyright 2023 Dash Core Group. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package org.dash.wallet.integrations.coinbase.model + +import android.os.Parcelable +import com.google.gson.annotations.SerializedName +import kotlinx.parcelize.Parcelize + +@Parcelize +data class ProductsResponse( + val products: List +): Parcelable + +@Parcelize +data class Product( + @SerializedName("product_id") + val productId: String, + val price: String, + val status: String, + @SerializedName("trading_disabled") + val tradingDisabled: Boolean, + @SerializedName("quote_currency_id") + val quoteCurrencyId: String, + @SerializedName("base_currency_id") + val baseCurrencyId: String, + val alias: String, + @SerializedName("alias_to") + val aliasTo: List +): Parcelable diff --git a/integrations/coinbase/src/main/java/org/dash/wallet/integration/coinbase_integration/model/SendTransactionToWallet.kt b/integrations/coinbase/src/main/java/org/dash/wallet/integrations/coinbase/model/SendTransactionToWallet.kt similarity index 78% rename from integrations/coinbase/src/main/java/org/dash/wallet/integration/coinbase_integration/model/SendTransactionToWallet.kt rename to integrations/coinbase/src/main/java/org/dash/wallet/integrations/coinbase/model/SendTransactionToWallet.kt index a34d4492ca..433168fbde 100644 --- a/integrations/coinbase/src/main/java/org/dash/wallet/integration/coinbase_integration/model/SendTransactionToWallet.kt +++ b/integrations/coinbase/src/main/java/org/dash/wallet/integrations/coinbase/model/SendTransactionToWallet.kt @@ -1,4 +1,20 @@ -package org.dash.wallet.integration.coinbase_integration.model +/* + * Copyright 2021 Dash Core Group. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package org.dash.wallet.integrations.coinbase.model import android.os.Parcelable import com.google.gson.annotations.SerializedName @@ -11,7 +27,6 @@ data class SendTransactionToWalletResponse( @Parcelize data class SendTransactionToWalletData( - val amount: Amount? = null, val application: Application? = null, @SerializedName("created_at") val createdAt: String? = null, @@ -105,7 +120,7 @@ data class SendTransactionToWalletParams( val idem: String?, val to: String?, val type: String?, - val description: String?="Dash Wallet App" + val description: String? = "Dash Wallet App" ): Parcelable sealed class TransactionType: Parcelable { @@ -119,5 +134,5 @@ sealed class TransactionType: Parcelable { data class CoinbaseTransactionParams( val params: SendTransactionToWalletParams, val type: TransactionType, - val coinbaseWalletName:String?=null -): Parcelable \ No newline at end of file + val coinbaseWalletName:String? = null +): Parcelable diff --git a/integrations/coinbase/src/main/java/org/dash/wallet/integration/coinbase_integration/model/SwapTradeResponse.kt b/integrations/coinbase/src/main/java/org/dash/wallet/integrations/coinbase/model/SwapTradeResponse.kt similarity index 97% rename from integrations/coinbase/src/main/java/org/dash/wallet/integration/coinbase_integration/model/SwapTradeResponse.kt rename to integrations/coinbase/src/main/java/org/dash/wallet/integrations/coinbase/model/SwapTradeResponse.kt index 6601fde407..7589afdcac 100644 --- a/integrations/coinbase/src/main/java/org/dash/wallet/integration/coinbase_integration/model/SwapTradeResponse.kt +++ b/integrations/coinbase/src/main/java/org/dash/wallet/integrations/coinbase/model/SwapTradeResponse.kt @@ -14,7 +14,7 @@ * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ -package org.dash.wallet.integration.coinbase_integration.model +package org.dash.wallet.integrations.coinbase.model import android.os.Parcelable import com.google.gson.annotations.SerializedName diff --git a/integrations/coinbase/src/main/java/org/dash/wallet/integration/coinbase_integration/model/TokenResponse.kt b/integrations/coinbase/src/main/java/org/dash/wallet/integrations/coinbase/model/TokenResponse.kt similarity index 94% rename from integrations/coinbase/src/main/java/org/dash/wallet/integration/coinbase_integration/model/TokenResponse.kt rename to integrations/coinbase/src/main/java/org/dash/wallet/integrations/coinbase/model/TokenResponse.kt index 3aabff92f0..4c53652f39 100644 --- a/integrations/coinbase/src/main/java/org/dash/wallet/integration/coinbase_integration/model/TokenResponse.kt +++ b/integrations/coinbase/src/main/java/org/dash/wallet/integrations/coinbase/model/TokenResponse.kt @@ -14,7 +14,7 @@ * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ -package org.dash.wallet.integration.coinbase_integration.model +package org.dash.wallet.integrations.coinbase.model import com.google.gson.annotations.SerializedName diff --git a/integrations/coinbase/src/main/java/org/dash/wallet/integration/coinbase_integration/model/TradesRequest.kt b/integrations/coinbase/src/main/java/org/dash/wallet/integrations/coinbase/model/TradesRequest.kt similarity index 93% rename from integrations/coinbase/src/main/java/org/dash/wallet/integration/coinbase_integration/model/TradesRequest.kt rename to integrations/coinbase/src/main/java/org/dash/wallet/integrations/coinbase/model/TradesRequest.kt index b642f4dae0..c66e413bc4 100644 --- a/integrations/coinbase/src/main/java/org/dash/wallet/integration/coinbase_integration/model/TradesRequest.kt +++ b/integrations/coinbase/src/main/java/org/dash/wallet/integrations/coinbase/model/TradesRequest.kt @@ -14,7 +14,7 @@ * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ -package org.dash.wallet.integration.coinbase_integration.model +package org.dash.wallet.integrations.coinbase.model import android.os.Parcelable import kotlinx.parcelize.Parcelize diff --git a/integrations/coinbase/src/main/java/org/dash/wallet/integration/coinbase_integration/model/UserAuthorizationInfoResponse.kt b/integrations/coinbase/src/main/java/org/dash/wallet/integrations/coinbase/model/UserAuthorizationInfoResponse.kt similarity index 91% rename from integrations/coinbase/src/main/java/org/dash/wallet/integration/coinbase_integration/model/UserAuthorizationInfoResponse.kt rename to integrations/coinbase/src/main/java/org/dash/wallet/integrations/coinbase/model/UserAuthorizationInfoResponse.kt index 643ab50659..e669bad707 100644 --- a/integrations/coinbase/src/main/java/org/dash/wallet/integration/coinbase_integration/model/UserAuthorizationInfoResponse.kt +++ b/integrations/coinbase/src/main/java/org/dash/wallet/integrations/coinbase/model/UserAuthorizationInfoResponse.kt @@ -1,4 +1,4 @@ -package org.dash.wallet.integration.coinbase_integration.model +package org.dash.wallet.integrations.coinbase.model import android.os.Parcelable import com.google.gson.annotations.SerializedName diff --git a/integrations/coinbase/src/main/java/org/dash/wallet/integration/coinbase_integration/network/RemoteDataSource.kt b/integrations/coinbase/src/main/java/org/dash/wallet/integrations/coinbase/network/RemoteDataSource.kt similarity index 82% rename from integrations/coinbase/src/main/java/org/dash/wallet/integration/coinbase_integration/network/RemoteDataSource.kt rename to integrations/coinbase/src/main/java/org/dash/wallet/integrations/coinbase/network/RemoteDataSource.kt index 628492064c..da6b7f15da 100644 --- a/integrations/coinbase/src/main/java/org/dash/wallet/integration/coinbase_integration/network/RemoteDataSource.kt +++ b/integrations/coinbase/src/main/java/org/dash/wallet/integrations/coinbase/network/RemoteDataSource.kt @@ -14,7 +14,7 @@ * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ -package org.dash.wallet.integration.coinbase_integration.network +package org.dash.wallet.integrations.coinbase.network import android.content.Context import dagger.hilt.android.qualifiers.ApplicationContext @@ -23,13 +23,12 @@ import okhttp3.Cache import okhttp3.OkHttpClient import okhttp3.logging.HttpLoggingInterceptor import org.dash.wallet.common.BuildConfig -import org.dash.wallet.common.Configuration -import org.dash.wallet.integration.coinbase_integration.CoinbaseConstants -import org.dash.wallet.integration.coinbase_integration.repository.remote.CustomCacheInterceptor -import org.dash.wallet.integration.coinbase_integration.repository.remote.HeadersInterceptor -import org.dash.wallet.integration.coinbase_integration.repository.remote.TokenAuthenticator -import org.dash.wallet.integration.coinbase_integration.service.CoinBaseTokenRefreshApi -import org.dash.wallet.integration.coinbase_integration.utils.CoinbaseConfig +import org.dash.wallet.integrations.coinbase.CoinbaseConstants +import org.dash.wallet.integrations.coinbase.repository.remote.CustomCacheInterceptor +import org.dash.wallet.integrations.coinbase.repository.remote.HeadersInterceptor +import org.dash.wallet.integrations.coinbase.repository.remote.TokenAuthenticator +import org.dash.wallet.integrations.coinbase.service.CoinBaseTokenRefreshApi +import org.dash.wallet.integrations.coinbase.utils.CoinbaseConfig import retrofit2.Retrofit import retrofit2.converter.gson.GsonConverterFactory import javax.inject.Inject diff --git a/integrations/coinbase/src/main/java/org/dash/wallet/integration/coinbase_integration/repository/CoinBaseRepository.kt b/integrations/coinbase/src/main/java/org/dash/wallet/integrations/coinbase/repository/CoinBaseRepository.kt similarity index 63% rename from integrations/coinbase/src/main/java/org/dash/wallet/integration/coinbase_integration/repository/CoinBaseRepository.kt rename to integrations/coinbase/src/main/java/org/dash/wallet/integrations/coinbase/repository/CoinBaseRepository.kt index 7aa7f11e78..05d80a074f 100644 --- a/integrations/coinbase/src/main/java/org/dash/wallet/integration/coinbase_integration/repository/CoinBaseRepository.kt +++ b/integrations/coinbase/src/main/java/org/dash/wallet/integrations/coinbase/repository/CoinBaseRepository.kt @@ -14,7 +14,7 @@ * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ -package org.dash.wallet.integration.coinbase_integration.repository +package org.dash.wallet.integrations.coinbase.repository import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers @@ -26,30 +26,57 @@ import org.bitcoinj.utils.Fiat import org.dash.wallet.common.data.ResponseResource import org.dash.wallet.common.data.WalletUIConfig import org.dash.wallet.common.data.safeApiCall +import org.dash.wallet.common.services.ExchangeRatesProvider import org.dash.wallet.common.util.Constants import org.dash.wallet.common.util.GenericUtils -import org.dash.wallet.integration.coinbase_integration.* -import org.dash.wallet.integration.coinbase_integration.model.* -import org.dash.wallet.integration.coinbase_integration.service.CoinBaseAuthApi -import org.dash.wallet.integration.coinbase_integration.service.CoinBaseClientConstants -import org.dash.wallet.integration.coinbase_integration.service.CoinBaseServicesApi -import org.dash.wallet.integration.coinbase_integration.utils.CoinbaseConfig -import org.dash.wallet.integration.coinbase_integration.viewmodels.toDoubleOrZero +import org.dash.wallet.integrations.coinbase.* +import org.dash.wallet.integrations.coinbase.model.* +import org.dash.wallet.integrations.coinbase.service.CoinBaseAuthApi +import org.dash.wallet.integrations.coinbase.service.CoinBaseClientConstants +import org.dash.wallet.integrations.coinbase.service.CoinBaseServicesApi +import org.dash.wallet.integrations.coinbase.utils.CoinbaseConfig +import org.dash.wallet.integrations.coinbase.viewmodels.toDoubleOrZero import java.math.BigDecimal import javax.inject.Inject +interface CoinBaseRepositoryInt { + val hasValidCredentials: Boolean + val isAuthenticated: Boolean + + suspend fun getUserAccount(): CoinbaseAccount + suspend fun getUserAccounts(exchangeCurrencyCode: String): List + suspend fun getFiatAccount(): CoinbaseAccount + suspend fun getBaseIdForUSDModel(baseCurrency: String): ResponseResource + suspend fun getExchangeRates(currencyCode: String): Map + suspend fun disconnectCoinbaseAccount() + suspend fun createAddress(): ResponseResource + suspend fun getUserAccountAddress(): ResponseResource + suspend fun getActivePaymentMethods(): List + suspend fun depositToFiatAccount(paymentMethodId: String, amountUSD: String) + suspend fun placeBuyOrder(placeBuyOrderParams: PlaceOrderParams): PlaceOrderResponse + suspend fun sendFundsToWallet( + sendTransactionToWalletParams: SendTransactionToWalletParams, + api2FATokenVersion: String? + ): ResponseResource + suspend fun swapTrade(tradesRequest: TradesRequest): ResponseResource + suspend fun commitSwapTrade(buyOrderId: String): ResponseResource + suspend fun completeCoinbaseAuthentication(authorizationCode: String): ResponseResource + suspend fun refreshWithdrawalLimit() + suspend fun getExchangeRateFromCoinbase(): ResponseResource + suspend fun getWithdrawalLimitInDash(): Double +} + class CoinBaseRepository @Inject constructor( private val servicesApi: CoinBaseServicesApi, private val authApi: CoinBaseAuthApi, private val config: CoinbaseConfig, private val walletUIConfig: WalletUIConfig, - private val placeBuyOrderMapper: PlaceBuyOrderMapper, private val swapTradeMapper: SwapTradeMapper, - private val commitBuyOrderMapper: CommitBuyOrderMapper, - private val coinbaseAddressMapper: CoinbaseAddressMapper + private val coinbaseAddressMapper: CoinbaseAddressMapper, + private val exchangeRates: ExchangeRatesProvider ) : CoinBaseRepositoryInt { private val configScope = CoroutineScope(Dispatchers.IO) - private var userAccountInfo: List = listOf() + private var userAccountInfo: List = listOf() override val hasValidCredentials: Boolean get() = CoinBaseClientConstants.CLIENT_ID.isNotEmpty() && @@ -64,50 +91,54 @@ class CoinBaseRepository @Inject constructor( .launchIn(configScope) } - override suspend fun getUserAccount(): ResponseResource = safeApiCall { - val apiResponse = servicesApi.getUserAccounts() - userAccountInfo = apiResponse?.data ?: listOf() + override suspend fun getUserAccount(): CoinbaseAccount { + val accountsResponse = servicesApi.getAccounts() + userAccountInfo = accountsResponse.accounts val userAccountData = userAccountInfo.firstOrNull { - it.balance?.currency?.equals(Constants.DASH_CURRENCY) ?: false - } - userAccountData?.also { - config.set(CoinbaseConfig.USER_ACCOUNT_ID, it.id ?: "") - config.set(CoinbaseConfig.LAST_BALANCE, Coin.parseCoin(it.balance?.amount ?: "0.0").value) + it.currency == Constants.DASH_CURRENCY + } ?: throw IllegalStateException("No DASH account found") + + return userAccountData.also { + config.set(CoinbaseConfig.USER_ACCOUNT_ID, it.uuid.toString()) + config.set(CoinbaseConfig.LAST_BALANCE, Coin.parseCoin(it.availableBalance.value).value) } } - override suspend fun getUserAccounts(exchangeCurrencyCode: String) = safeApiCall { + override suspend fun getUserAccounts(exchangeCurrencyCode: String): List { if (userAccountInfo.isEmpty()) { getUserAccount() } - val exchangeRates = servicesApi.getExchangeRates(exchangeCurrencyCode)?.data + val exchangeRates = servicesApi.getExchangeRates(exchangeCurrencyCode)?.data // TODO: cache? val currencyToDashExchangeRate = exchangeRates?.rates?.get(Constants.DASH_CURRENCY).orEmpty() val currencyToUSDExchangeRate = exchangeRates?.rates?.get(Constants.USD_CURRENCY).orEmpty() - return@safeApiCall userAccountInfo.map { - val currencyToCryptoCurrencyExchangeRate = exchangeRates?.rates?.get(it.currency?.code).orEmpty() - val cryptoCurrencyToDashExchangeRate = ( - BigDecimal(currencyToDashExchangeRate) / BigDecimal( - currencyToCryptoCurrencyExchangeRate - ) - ).toString() - + return userAccountInfo.map { + val currencyToCryptoCurrencyExchangeRate = exchangeRates?.rates?.get(it.currency).orEmpty() CoinBaseUserAccountDataUIModel( it, - currencyToCryptoCurrencyExchangeRate, - currencyToDashExchangeRate, - cryptoCurrencyToDashExchangeRate, - currencyToUSDExchangeRate + BigDecimal(currencyToCryptoCurrencyExchangeRate), + // TODO: below values don't depend on a specific account. Refactor out + BigDecimal(currencyToDashExchangeRate), + BigDecimal(currencyToUSDExchangeRate) ) } } + override suspend fun getFiatAccount(): CoinbaseAccount { + return userAccountInfo.first { + it.type == CoinbaseConstants.FIAT_ACCOUNT_TYPE && + it.currency == CoinbaseConstants.DEFAULT_CURRENCY_USD + } + } + override suspend fun getBaseIdForUSDModel(baseCurrency: String) = safeApiCall { servicesApi.getBaseIdForUSDModel(baseCurrency = baseCurrency) } - override suspend fun getExchangeRates() = safeApiCall { servicesApi.getExchangeRates() } + override suspend fun getExchangeRates(currencyCode: String): Map { + return servicesApi.getExchangeRates(currencyCode)?.data?.rates ?: mapOf() + } override suspend fun disconnectCoinbaseAccount() { val accessToken = config.get(CoinbaseConfig.LAST_ACCESS_TOKEN) @@ -125,18 +156,47 @@ class CoinBaseRepository @Inject constructor( swapTradeMapper.map(apiResult?.data) } - override suspend fun getActivePaymentMethods() = safeApiCall { + override suspend fun getActivePaymentMethods(): List { val apiResult = servicesApi.getActivePaymentMethods() - apiResult?.data ?: emptyList() + return apiResult?.data ?: emptyList() } - override suspend fun placeBuyOrder(placeBuyOrderParams: PlaceBuyOrderParams) = safeApiCall { - val userAccountId = config.get(CoinbaseConfig.USER_ACCOUNT_ID) ?: "" - val apiResult = servicesApi.placeBuyOrder( - accountId = userAccountId, - placeBuyOrderParams = placeBuyOrderParams + override suspend fun depositToFiatAccount(paymentMethodId: String, amountUSD: String) { + val result = servicesApi.depositTo( + accountId = getFiatAccount().uuid.toString(), + request = DepositRequest( + amount = amountUSD, + currency = CoinbaseConstants.DEFAULT_CURRENCY_USD, + paymentMethod = paymentMethodId + ) ) - placeBuyOrderMapper.map(apiResult?.data) + + if (!result.isSuccessful) { + throw CoinbaseException( + CoinbaseErrorType.DEPOSIT_FAILED, + result.errorBody()?.string()?.let { + CoinbaseErrorResponse.getErrorMessage(it) + }?.message ?: "" + ) + } else if (result.body()?.data?.status != "created") { + throw CoinbaseException( + CoinbaseErrorType.DEPOSIT_FAILED, + result.body()?.data?.status + ) + } + } + + override suspend fun placeBuyOrder(placeBuyOrderParams: PlaceOrderParams): PlaceOrderResponse { + val result = servicesApi.placeBuyOrder(placeBuyOrderParams) + + if (!result.success) { + throw CoinbaseException( + CoinbaseErrorType.BUY_FAILED, + result.errorResponse?.message ?: result.failureReason + ) + } + + return result } override suspend fun getUserAccountAddress(): ResponseResource = safeApiCall { @@ -145,15 +205,6 @@ class CoinBaseRepository @Inject constructor( coinbaseAddressMapper.map(apiResult) } - override suspend fun commitBuyOrder(buyOrderId: String) = safeApiCall { - val userAccountId = config.get(CoinbaseConfig.USER_ACCOUNT_ID) ?: "" - val commitBuyResult = servicesApi.commitBuyOrder( - accountId = userAccountId, - buyOrderId = buyOrderId - ) - commitBuyOrderMapper.map(commitBuyResult?.data) - } - override suspend fun sendFundsToWallet( sendTransactionToWalletParams: SendTransactionToWalletParams, api2FATokenVersion: String? @@ -171,23 +222,18 @@ class CoinBaseRepository @Inject constructor( it?.let { tokenResponse -> config.set(CoinbaseConfig.LAST_ACCESS_TOKEN, tokenResponse.accessToken) config.set(CoinbaseConfig.LAST_REFRESH_TOKEN, tokenResponse.refreshToken) - config.set(CoinbaseConfig.LOGOUT_COINBASE, false) getUserAccount() } } !config.get(CoinbaseConfig.LAST_ACCESS_TOKEN).isNullOrEmpty() } - override suspend fun getWithdrawalLimit() = safeApiCall { + override suspend fun refreshWithdrawalLimit() { val apiResponse = servicesApi.getAuthorizationInformation() apiResponse?.data?.oauthMeta?.let { metadata -> config.set(CoinbaseConfig.USER_WITHDRAWAL_LIMIT, metadata.sendLimitAmount) config.set(CoinbaseConfig.SEND_LIMIT_CURRENCY, metadata.sendLimitCurrency) } - WithdrawalLimitUIModel( - apiResponse?.data?.oauthMeta?.sendLimitAmount, - apiResponse?.data?.oauthMeta?.sendLimitCurrency ?: "" - ) } override suspend fun getExchangeRateFromCoinbase() = safeApiCall { @@ -196,7 +242,7 @@ class CoinBaseRepository @Inject constructor( } val userAccountData = userAccountInfo.firstOrNull { - it.balance?.currency?.equals(Constants.DASH_CURRENCY) ?: false + it.currency == Constants.DASH_CURRENCY } val currencyCode = walletUIConfig.getExchangeCurrencyCode() @@ -207,8 +253,8 @@ class CoinBaseRepository @Inject constructor( val currencyToUSDExchangeRate = exchangeRates?.rates?.get(Constants.USD_CURRENCY).orEmpty() CoinbaseToDashExchangeRateUIModel( it, - currencyToDashExchangeRate, - currencyToUSDExchangeRate + BigDecimal(currencyToDashExchangeRate), + BigDecimal(currencyToUSDExchangeRate) ) } ?: CoinbaseToDashExchangeRateUIModel.EMPTY } @@ -218,9 +264,15 @@ class CoinBaseRepository @Inject constructor( servicesApi.createAddress(accountId = userAccountId)?.addresses?.address ?: "" } - override suspend fun getWithdrawalLimitInDash(exchangeRate: ExchangeRate): Double { + override suspend fun getWithdrawalLimitInDash(): Double { val withdrawalLimit = config.get(CoinbaseConfig.USER_WITHDRAWAL_LIMIT) - return if (withdrawalLimit.isNullOrEmpty()) { + val withdrawalLimitCurrency = config.get(CoinbaseConfig.SEND_LIMIT_CURRENCY) + ?: CoinbaseConstants.DEFAULT_CURRENCY_USD + val exchangeRate = exchangeRates.getExchangeRate(withdrawalLimitCurrency)?.let { + ExchangeRate(Coin.COIN, it.fiat) + } + + return if (withdrawalLimit.isNullOrEmpty() || exchangeRate == null) { 0.0 } else { val formattedAmount = GenericUtils.formatFiatWithoutComma(withdrawalLimit) @@ -236,33 +288,3 @@ class CoinBaseRepository @Inject constructor( } } -interface CoinBaseRepositoryInt { - val hasValidCredentials: Boolean - val isAuthenticated: Boolean - - suspend fun getUserAccount(): ResponseResource - suspend fun getUserAccounts(exchangeCurrencyCode: String): ResponseResource> - suspend fun getBaseIdForUSDModel(baseCurrency: String): ResponseResource - suspend fun getExchangeRates(): ResponseResource - suspend fun disconnectCoinbaseAccount() - suspend fun createAddress(): ResponseResource - suspend fun getUserAccountAddress(): ResponseResource - suspend fun getActivePaymentMethods(): ResponseResource> - suspend fun placeBuyOrder(placeBuyOrderParams: PlaceBuyOrderParams): ResponseResource - suspend fun commitBuyOrder(buyOrderId: String): ResponseResource - suspend fun sendFundsToWallet( - sendTransactionToWalletParams: SendTransactionToWalletParams, - api2FATokenVersion: String? - ): ResponseResource - suspend fun swapTrade(tradesRequest: TradesRequest): ResponseResource - suspend fun commitSwapTrade(buyOrderId: String): ResponseResource - suspend fun completeCoinbaseAuthentication(authorizationCode: String): ResponseResource - suspend fun getWithdrawalLimit(): ResponseResource - suspend fun getExchangeRateFromCoinbase(): ResponseResource - suspend fun getWithdrawalLimitInDash(exchangeRate: ExchangeRate): Double -} - -data class WithdrawalLimitUIModel( - val amount: String?, - val currency: String -) diff --git a/integrations/coinbase/src/main/java/org/dash/wallet/integration/coinbase_integration/repository/remote/CustomCacheInterceptor.kt b/integrations/coinbase/src/main/java/org/dash/wallet/integrations/coinbase/repository/remote/CustomCacheInterceptor.kt similarity index 92% rename from integrations/coinbase/src/main/java/org/dash/wallet/integration/coinbase_integration/repository/remote/CustomCacheInterceptor.kt rename to integrations/coinbase/src/main/java/org/dash/wallet/integrations/coinbase/repository/remote/CustomCacheInterceptor.kt index 717dc4e0e5..8dcc0a6f6b 100644 --- a/integrations/coinbase/src/main/java/org/dash/wallet/integration/coinbase_integration/repository/remote/CustomCacheInterceptor.kt +++ b/integrations/coinbase/src/main/java/org/dash/wallet/integrations/coinbase/repository/remote/CustomCacheInterceptor.kt @@ -14,7 +14,7 @@ * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ -package org.dash.wallet.integration.coinbase_integration.repository.remote +package org.dash.wallet.integrations.coinbase.repository.remote import android.content.Context import com.google.gson.Gson @@ -22,9 +22,9 @@ import kotlinx.coroutines.runBlocking import okhttp3.* import okhttp3.MediaType.Companion.toMediaType import okhttp3.ResponseBody.Companion.toResponseBody -import org.dash.wallet.integration.coinbase_integration.CoinbaseConstants -import org.dash.wallet.integration.coinbase_integration.model.BaseIdForUSDModel -import org.dash.wallet.integration.coinbase_integration.utils.CoinbaseConfig +import org.dash.wallet.integrations.coinbase.CoinbaseConstants +import org.dash.wallet.integrations.coinbase.model.BaseIdForUSDModel +import org.dash.wallet.integrations.coinbase.utils.CoinbaseConfig import org.slf4j.LoggerFactory import java.io.File import java.io.FileReader diff --git a/integrations/coinbase/src/main/java/org/dash/wallet/integration/coinbase_integration/repository/remote/HeadersInterceptor.kt b/integrations/coinbase/src/main/java/org/dash/wallet/integrations/coinbase/repository/remote/HeadersInterceptor.kt similarity index 90% rename from integrations/coinbase/src/main/java/org/dash/wallet/integration/coinbase_integration/repository/remote/HeadersInterceptor.kt rename to integrations/coinbase/src/main/java/org/dash/wallet/integrations/coinbase/repository/remote/HeadersInterceptor.kt index 1ca7cc9e4b..297fb1043c 100644 --- a/integrations/coinbase/src/main/java/org/dash/wallet/integration/coinbase_integration/repository/remote/HeadersInterceptor.kt +++ b/integrations/coinbase/src/main/java/org/dash/wallet/integrations/coinbase/repository/remote/HeadersInterceptor.kt @@ -14,12 +14,12 @@ * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ -package org.dash.wallet.integration.coinbase_integration.repository.remote +package org.dash.wallet.integrations.coinbase.repository.remote import kotlinx.coroutines.runBlocking import okhttp3.Interceptor import okhttp3.Response -import org.dash.wallet.integration.coinbase_integration.utils.CoinbaseConfig +import org.dash.wallet.integrations.coinbase.utils.CoinbaseConfig import javax.inject.Inject class HeadersInterceptor @Inject constructor( diff --git a/integrations/coinbase/src/main/java/org/dash/wallet/integration/coinbase_integration/repository/remote/TokenAuthenticator.kt b/integrations/coinbase/src/main/java/org/dash/wallet/integrations/coinbase/repository/remote/TokenAuthenticator.kt similarity index 87% rename from integrations/coinbase/src/main/java/org/dash/wallet/integration/coinbase_integration/repository/remote/TokenAuthenticator.kt rename to integrations/coinbase/src/main/java/org/dash/wallet/integrations/coinbase/repository/remote/TokenAuthenticator.kt index 4fac880793..eafdaca7c7 100644 --- a/integrations/coinbase/src/main/java/org/dash/wallet/integration/coinbase_integration/repository/remote/TokenAuthenticator.kt +++ b/integrations/coinbase/src/main/java/org/dash/wallet/integrations/coinbase/repository/remote/TokenAuthenticator.kt @@ -14,7 +14,7 @@ * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ -package org.dash.wallet.integration.coinbase_integration.repository.remote +package org.dash.wallet.integrations.coinbase.repository.remote import kotlinx.coroutines.runBlocking import kotlinx.coroutines.sync.Mutex @@ -25,9 +25,9 @@ import okhttp3.Response import okhttp3.Route import org.dash.wallet.common.data.ResponseResource import org.dash.wallet.common.data.safeApiCall -import org.dash.wallet.integration.coinbase_integration.model.TokenResponse -import org.dash.wallet.integration.coinbase_integration.service.CoinBaseTokenRefreshApi -import org.dash.wallet.integration.coinbase_integration.utils.CoinbaseConfig +import org.dash.wallet.integrations.coinbase.model.TokenResponse +import org.dash.wallet.integrations.coinbase.service.CoinBaseTokenRefreshApi +import org.dash.wallet.integrations.coinbase.utils.CoinbaseConfig import javax.inject.Inject class TokenAuthenticator @Inject constructor( @@ -55,7 +55,6 @@ class TokenAuthenticator @Inject constructor( else -> { config.set(CoinbaseConfig.LAST_ACCESS_TOKEN, "") config.set(CoinbaseConfig.LAST_REFRESH_TOKEN, "") - config.set(CoinbaseConfig.LOGOUT_COINBASE, true) null } } diff --git a/integrations/coinbase/src/main/java/org/dash/wallet/integration/coinbase_integration/service/CoinBaseAuthApi.kt b/integrations/coinbase/src/main/java/org/dash/wallet/integrations/coinbase/service/CoinBaseAuthApi.kt similarity index 90% rename from integrations/coinbase/src/main/java/org/dash/wallet/integration/coinbase_integration/service/CoinBaseAuthApi.kt rename to integrations/coinbase/src/main/java/org/dash/wallet/integrations/coinbase/service/CoinBaseAuthApi.kt index 99c9f20a49..7adeeb17ff 100644 --- a/integrations/coinbase/src/main/java/org/dash/wallet/integration/coinbase_integration/service/CoinBaseAuthApi.kt +++ b/integrations/coinbase/src/main/java/org/dash/wallet/integrations/coinbase/service/CoinBaseAuthApi.kt @@ -14,9 +14,9 @@ * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ -package org.dash.wallet.integration.coinbase_integration.service +package org.dash.wallet.integrations.coinbase.service -import org.dash.wallet.integration.coinbase_integration.model.TokenResponse +import org.dash.wallet.integrations.coinbase.model.TokenResponse import retrofit2.http.Field import retrofit2.http.FormUrlEncoded import retrofit2.http.POST diff --git a/integrations/coinbase/src/main/java/org/dash/wallet/integration/coinbase_integration/service/CoinBaseClientConstants.kt b/integrations/coinbase/src/main/java/org/dash/wallet/integrations/coinbase/service/CoinBaseClientConstants.kt similarity index 92% rename from integrations/coinbase/src/main/java/org/dash/wallet/integration/coinbase_integration/service/CoinBaseClientConstants.kt rename to integrations/coinbase/src/main/java/org/dash/wallet/integrations/coinbase/service/CoinBaseClientConstants.kt index 94aea9c946..42fba0e119 100644 --- a/integrations/coinbase/src/main/java/org/dash/wallet/integration/coinbase_integration/service/CoinBaseClientConstants.kt +++ b/integrations/coinbase/src/main/java/org/dash/wallet/integrations/coinbase/service/CoinBaseClientConstants.kt @@ -17,11 +17,11 @@ * */ -package org.dash.wallet.integration.coinbase_integration.service +package org.dash.wallet.integrations.coinbase.service object CoinBaseClientConstants { @kotlin.jvm.JvmField var CLIENT_ID = "" @kotlin.jvm.JvmField var CLIENT_SECRET = "" -} \ No newline at end of file +} diff --git a/integrations/coinbase/src/main/java/org/dash/wallet/integration/coinbase_integration/service/CoinBaseServicesApi.kt b/integrations/coinbase/src/main/java/org/dash/wallet/integrations/coinbase/service/CoinBaseServicesApi.kt similarity index 75% rename from integrations/coinbase/src/main/java/org/dash/wallet/integration/coinbase_integration/service/CoinBaseServicesApi.kt rename to integrations/coinbase/src/main/java/org/dash/wallet/integrations/coinbase/service/CoinBaseServicesApi.kt index 056d208338..a0477aa916 100644 --- a/integrations/coinbase/src/main/java/org/dash/wallet/integration/coinbase_integration/service/CoinBaseServicesApi.kt +++ b/integrations/coinbase/src/main/java/org/dash/wallet/integrations/coinbase/service/CoinBaseServicesApi.kt @@ -14,24 +14,32 @@ * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ -package org.dash.wallet.integration.coinbase_integration.service +package org.dash.wallet.integrations.coinbase.service import org.dash.wallet.common.util.Constants -import org.dash.wallet.integration.coinbase_integration.* -import org.dash.wallet.integration.coinbase_integration.model.* +import org.dash.wallet.integrations.coinbase.* +import org.dash.wallet.integrations.coinbase.model.* +import retrofit2.Response import retrofit2.http.* interface CoinBaseServicesApi { + @GET("api/v3/brokerage/accounts") + suspend fun getAccounts( + @Query("limit") limit: Int = 250 + ): AccountsResponse - @GET("v2/accounts") - suspend fun getUserAccounts( - @Header(CoinbaseConstants.CB_VERSION_KEY) apiVersion: String = CoinbaseConstants.CB_VERSION_VALUE, - @Query("limit") limit: Int = 300 - ): CoinBaseUserAccountInfo? + @GET("api/v3/brokerage/products") + suspend fun getProducts(): ProductsResponse + + @POST("v2/accounts/{account_id}/deposits") + suspend fun depositTo( + @Path("account_id") accountId: String, + @Body request: DepositRequest + ): Response @GET("v2/exchange-rates") suspend fun getExchangeRates( - @Query("currency")currency: String = Constants.DASH_CURRENCY + @Query("currency") currency: String = Constants.DASH_CURRENCY ): CoinBaseExchangeRates? @GET("v2/payment-methods") @@ -39,19 +47,10 @@ interface CoinBaseServicesApi { @Header(CoinbaseConstants.CB_VERSION_KEY) apiVersion: String = CoinbaseConstants.CB_VERSION_VALUE ): PaymentMethodsResponse? - @POST("v2/accounts/{account_id}/buys") + @POST("api/v3/brokerage/orders") suspend fun placeBuyOrder( - @Header(CoinbaseConstants.CB_VERSION_KEY) apiVersion: String = CoinbaseConstants.CB_VERSION_VALUE, - @Path("account_id") accountId: String, - @Body placeBuyOrderParams: PlaceBuyOrderParams - ): BuyOrderResponse? - - @POST("v2/accounts/{account_id}/buys/{buy_id}/commit") - suspend fun commitBuyOrder( - @Header(CoinbaseConstants.CB_VERSION_KEY) apiVersion: String = CoinbaseConstants.CB_VERSION_VALUE, - @Path("account_id") accountId: String, - @Path("buy_id") buyOrderId: String - ): BuyOrderResponse? + @Body placeOrderParams: PlaceOrderParams + ): PlaceOrderResponse @POST("v2/accounts/{account_id}/transactions") suspend fun sendCoinsToWallet( diff --git a/integrations/coinbase/src/main/java/org/dash/wallet/integration/coinbase_integration/service/CoinBaseTokenRefreshApi.kt b/integrations/coinbase/src/main/java/org/dash/wallet/integrations/coinbase/service/CoinBaseTokenRefreshApi.kt similarity index 89% rename from integrations/coinbase/src/main/java/org/dash/wallet/integration/coinbase_integration/service/CoinBaseTokenRefreshApi.kt rename to integrations/coinbase/src/main/java/org/dash/wallet/integrations/coinbase/service/CoinBaseTokenRefreshApi.kt index 45187b6f40..a0b60677ad 100644 --- a/integrations/coinbase/src/main/java/org/dash/wallet/integration/coinbase_integration/service/CoinBaseTokenRefreshApi.kt +++ b/integrations/coinbase/src/main/java/org/dash/wallet/integrations/coinbase/service/CoinBaseTokenRefreshApi.kt @@ -14,9 +14,9 @@ * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ -package org.dash.wallet.integration.coinbase_integration.service +package org.dash.wallet.integrations.coinbase.service -import org.dash.wallet.integration.coinbase_integration.model.TokenResponse +import org.dash.wallet.integrations.coinbase.model.TokenResponse import retrofit2.http.Field import retrofit2.http.FormUrlEncoded import retrofit2.http.POST diff --git a/wallet/src/de/schildbach/wallet/ui/coinbase/CoinBaseWebClientActivity.kt b/integrations/coinbase/src/main/java/org/dash/wallet/integrations/coinbase/ui/CoinBaseWebClientActivity.kt similarity index 90% rename from wallet/src/de/schildbach/wallet/ui/coinbase/CoinBaseWebClientActivity.kt rename to integrations/coinbase/src/main/java/org/dash/wallet/integrations/coinbase/ui/CoinBaseWebClientActivity.kt index 546573662b..244814bd70 100644 --- a/wallet/src/de/schildbach/wallet/ui/coinbase/CoinBaseWebClientActivity.kt +++ b/integrations/coinbase/src/main/java/org/dash/wallet/integrations/coinbase/ui/CoinBaseWebClientActivity.kt @@ -1,5 +1,5 @@ /* - * Copyright 2021 Dash Core Group. + * Copyright 2023 Dash Core Group. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -14,7 +14,7 @@ * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ -package de.schildbach.wallet.ui.coinbase +package org.dash.wallet.integrations.coinbase.ui import android.annotation.SuppressLint import android.content.Intent @@ -22,11 +22,10 @@ import android.net.Uri import android.os.Bundle import android.view.MenuItem import android.webkit.* -import androidx.appcompat.widget.Toolbar -import de.schildbach.wallet_test.BuildConfig -import de.schildbach.wallet_test.R import org.dash.wallet.common.InteractionAwareActivity import org.dash.wallet.common.databinding.FragmentWebviewBinding +import org.dash.wallet.integrations.coinbase.R +import org.dash.wallet.integrations.coinbase.service.CoinBaseClientConstants class CoinBaseWebClientActivity : InteractionAwareActivity() { @@ -68,13 +67,15 @@ class CoinBaseWebClientActivity : InteractionAwareActivity() { WebStorage.getInstance().deleteAllData() val loginUrl = - "https://www.coinbase.com/oauth/authorize?client_id=${BuildConfig.COINBASE_CLIENT_ID}" + + "https://www.coinbase.com/oauth/authorize?client_id=${CoinBaseClientConstants.CLIENT_ID}" + "&redirect_uri=authhub://oauth-callback&response_type" + "=code&scope=wallet:accounts:read,wallet:user:read,wallet:payment-methods:read," + "wallet:buys:read,wallet:buys:create,wallet:transactions:transfer," + - "wallet:sells:create,wallet:sells:read," + + "wallet:sells:create,wallet:sells:read,wallet:deposits:create," + "wallet:transactions:request,wallet:transactions:read,wallet:trades:create," + - "wallet:supported-assets:read,wallet:transactions:send," + "wallet:addresses:read,wallet:addresses:create" + "&meta[send_limit_amount]=1" + + "wallet:supported-assets:read,wallet:transactions:send," + + "wallet:addresses:read,wallet:addresses:create" + + "&meta[send_limit_amount]=1" + "&meta[send_limit_currency]=USD" + "&meta[send_limit_period]=month" + "&account=all" diff --git a/integrations/coinbase/src/main/java/org/dash/wallet/integrations/coinbase/ui/CoinbaseBuyDashFragment.kt b/integrations/coinbase/src/main/java/org/dash/wallet/integrations/coinbase/ui/CoinbaseBuyDashFragment.kt new file mode 100644 index 0000000000..b35ca778af --- /dev/null +++ b/integrations/coinbase/src/main/java/org/dash/wallet/integrations/coinbase/ui/CoinbaseBuyDashFragment.kt @@ -0,0 +1,170 @@ +/* + * Copyright 2021 Dash Core Group. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package org.dash.wallet.integrations.coinbase.ui + +import android.content.Intent +import android.net.Uri +import android.os.Bundle +import android.view.View +import androidx.core.view.isVisible +import androidx.fragment.app.Fragment +import androidx.fragment.app.activityViewModels +import androidx.fragment.app.commit +import androidx.lifecycle.lifecycleScope +import androidx.navigation.fragment.findNavController +import dagger.hilt.android.AndroidEntryPoint +import kotlinx.coroutines.launch +import org.bitcoinj.core.Coin +import org.dash.wallet.common.services.analytics.AnalyticsConstants +import org.dash.wallet.common.ui.dialogs.AdaptiveDialog +import org.dash.wallet.common.ui.enter_amount.EnterAmountFragment +import org.dash.wallet.common.ui.enter_amount.EnterAmountViewModel +import org.dash.wallet.common.ui.viewBinding +import org.dash.wallet.common.util.observe +import org.dash.wallet.common.util.safeNavigate +import org.dash.wallet.common.util.toFormattedString +import org.dash.wallet.integrations.coinbase.R +import org.dash.wallet.integrations.coinbase.databinding.FragmentCoinbaseBuyDashBinding +import org.dash.wallet.integrations.coinbase.databinding.KeyboardHeaderViewBinding +import org.dash.wallet.integrations.coinbase.model.CoinbaseErrorType +import org.dash.wallet.integrations.coinbase.viewmodels.CoinbaseBuyDashViewModel +import org.dash.wallet.integrations.coinbase.viewmodels.CoinbaseViewModel +import org.dash.wallet.integrations.coinbase.viewmodels.coinbaseViewModels + +@AndroidEntryPoint +class CoinbaseBuyDashFragment : Fragment(R.layout.fragment_coinbase_buy_dash) { + private val binding by viewBinding(FragmentCoinbaseBuyDashBinding::bind) + private val sharedViewModel by coinbaseViewModels() + private val viewModel by coinbaseViewModels() + private val amountViewModel by activityViewModels() + private lateinit var fragment: EnterAmountFragment + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + + if (savedInstanceState == null) { + fragment = EnterAmountFragment.newInstance( + isMaxButtonVisible = false, + showCurrencySelector = false + ) + val headerBinding = KeyboardHeaderViewBinding.inflate(layoutInflater, null, false) + fragment.setViewDetails(getString(R.string.button_continue), headerBinding.root) + + parentFragmentManager.commit { + setReorderingAllowed(true) + add(R.id.enter_amount_fragment_placeholder, fragment) + } + } + + binding.toolbar.setNavigationOnClickListener { + findNavController().popBackStack() + } + + amountViewModel.selectedExchangeRate.observe(viewLifecycleOwner) { rate -> + rate?.let { + binding.toolbarSubtitle.text = getString( + R.string.exchange_rate_template, + Coin.COIN.toPlainString(), + rate.fiat.toFormattedString() + ) + } + } + + amountViewModel.onContinueEvent.observe(viewLifecycleOwner) { pair -> + lifecycleScope.launch { + val validated = AdaptiveDialog.withProgress(getString(R.string.loading), requireActivity()) { + validate(pair.first, retryWithDeposit = false) + } + + if (validated) { + safeNavigate(CoinbaseBuyDashFragmentDirections.buyDashToOrderReview()) + } + } + } + + binding.authLimitBanner.root.setOnClickListener { + sharedViewModel.logEvent(AnalyticsConstants.Coinbase.BUY_AUTH_LIMIT) + AdaptiveDialog.custom(R.layout.dialog_withdrawal_limit_info).show(requireActivity()) + } + + sharedViewModel.uiState.observe(viewLifecycleOwner) { + if (it.isSessionExpired) { + findNavController().popBackStack() + } else { + fragment.handleNetworkState(it.isNetworkAvailable) + } + } + } + + private suspend fun validate(dashAmount: Coin, retryWithDeposit: Boolean): Boolean { + val isMoreThanLimit = sharedViewModel.isInputGreaterThanLimit(dashAmount) + binding.authLimitBanner.root.isVisible = isMoreThanLimit + + if (isMoreThanLimit) { + return false + } + + return when (viewModel.validateBuyDash(dashAmount, retryWithDeposit)) { + CoinbaseErrorType.NONE -> true + CoinbaseErrorType.INSUFFICIENT_BALANCE -> { + if (shouldRetryWithDeposit()) { + validate(dashAmount, retryWithDeposit = true) + } else { + false + } + } + + CoinbaseErrorType.NO_BANK_ACCOUNT -> { + showNoPaymentMethodsError() + false + } + else -> false + } + } + + private suspend fun shouldRetryWithDeposit(): Boolean { + return AdaptiveDialog.create( + R.drawable.ic_warning, + getString(R.string.you_dont_have_enough_balance), + getString(R.string.coinbase_use_bank_account), + getString(R.string.cancel), + getString(R.string.confirm) + ).showAsync(requireActivity()) ?: false + } + + private fun showNoPaymentMethodsError() { + AdaptiveDialog.create( + R.drawable.ic_error, + getString(R.string.coinbase_no_payment_methods_error_title), + getString(R.string.coinbase_no_payment_methods_error_message), + getString(R.string.close), + getString(R.string.add_payment_method), + ).show(requireActivity()) { addMethod -> + if (addMethod == true) { + viewModel.logEvent(AnalyticsConstants.Coinbase.BUY_ADD_PAYMENT_METHOD) + openCoinbaseWebsite() + } + } + } + + private fun openCoinbaseWebsite() { + val defaultBrowser = Intent.makeMainSelectorActivity(Intent.ACTION_MAIN, Intent.CATEGORY_APP_BROWSER) + defaultBrowser.data = Uri.parse(getString(R.string.coinbase_website)) + startActivity(defaultBrowser) + } +} \ No newline at end of file diff --git a/integrations/coinbase/src/main/java/org/dash/wallet/integration/coinbase_integration/ui/CoinbaseConversionPreviewFragment.kt b/integrations/coinbase/src/main/java/org/dash/wallet/integrations/coinbase/ui/CoinbaseConversionPreviewFragment.kt similarity index 95% rename from integrations/coinbase/src/main/java/org/dash/wallet/integration/coinbase_integration/ui/CoinbaseConversionPreviewFragment.kt rename to integrations/coinbase/src/main/java/org/dash/wallet/integrations/coinbase/ui/CoinbaseConversionPreviewFragment.kt index f59cef4189..a3d93cb1b1 100644 --- a/integrations/coinbase/src/main/java/org/dash/wallet/integration/coinbase_integration/ui/CoinbaseConversionPreviewFragment.kt +++ b/integrations/coinbase/src/main/java/org/dash/wallet/integrations/coinbase/ui/CoinbaseConversionPreviewFragment.kt @@ -14,7 +14,7 @@ * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ -package org.dash.wallet.integration.coinbase_integration.ui +package org.dash.wallet.integrations.coinbase.ui import android.os.Bundle import android.os.CountDownTimer @@ -30,7 +30,6 @@ import coil.load import coil.size.Scale import coil.transform.CircleCropTransformation import dagger.hilt.android.AndroidEntryPoint -import kotlinx.coroutines.ExperimentalCoroutinesApi import org.dash.wallet.common.util.Constants import org.dash.wallet.common.services.analytics.AnalyticsConstants import org.dash.wallet.common.ui.dialogs.AdaptiveDialog @@ -38,13 +37,12 @@ import org.dash.wallet.common.ui.setRoundedBackground import org.dash.wallet.common.ui.viewBinding import org.dash.wallet.common.util.GenericUtils import org.dash.wallet.common.util.safeNavigate -import org.dash.wallet.integration.coinbase_integration.R -import org.dash.wallet.integration.coinbase_integration.databinding.FragmentCoinbaseConversionPreviewBinding -import org.dash.wallet.integration.coinbase_integration.model.* -import org.dash.wallet.integration.coinbase_integration.ui.dialogs.CoinBaseResultDialog -import org.dash.wallet.integration.coinbase_integration.viewmodels.CoinbaseConversionPreviewViewModel +import org.dash.wallet.integrations.coinbase.R +import org.dash.wallet.integrations.coinbase.databinding.FragmentCoinbaseConversionPreviewBinding +import org.dash.wallet.integrations.coinbase.model.* +import org.dash.wallet.integrations.coinbase.ui.dialogs.CoinBaseResultDialog +import org.dash.wallet.integrations.coinbase.viewmodels.CoinbaseConversionPreviewViewModel -@ExperimentalCoroutinesApi @AndroidEntryPoint class CoinbaseConversionPreviewFragment : Fragment(R.layout.fragment_coinbase_conversion_preview) { private val binding by viewBinding(FragmentCoinbaseConversionPreviewBinding::bind) @@ -98,7 +96,6 @@ class CoinbaseConversionPreviewFragment : Fragment(R.layout.fragment_coinbase_co } arguments?.let { - CoinbaseConversionPreviewFragmentArgs.fromBundle(it).swapModel.apply { updateConversionPreviewUI() swapTradeUIModel = this @@ -157,7 +154,7 @@ class CoinbaseConversionPreviewFragment : Fragment(R.layout.fragment_coinbase_co safeNavigate( CoinbaseConversionPreviewFragmentDirections.conversionPreviewToTwoFaCode( - CoinbaseTransactionParams(params, TransactionType.BuySwap,walletName) + CoinbaseTransactionParams(params, TransactionType.BuySwap, walletName) ) ) } @@ -301,8 +298,9 @@ class CoinbaseConversionPreviewFragment : Fragment(R.layout.fragment_coinbase_co CoinBaseResultDialog.Type.CONVERSION_SUCCESS -> { viewModel.logEvent(AnalyticsConstants.Coinbase.CONVERT_SUCCESS_CLOSE) dismiss() - requireActivity().setResult(Constants.RESULT_CODE_GO_HOME) - requireActivity().finish() + val navController = findNavController() + val home = navController.graph.startDestinationId + navController.popBackStack(home, false) } else -> {} } diff --git a/integrations/coinbase/src/main/java/org/dash/wallet/integration/coinbase_integration/ui/CoinbaseConvertCryptoFragment.kt b/integrations/coinbase/src/main/java/org/dash/wallet/integrations/coinbase/ui/CoinbaseConvertCryptoFragment.kt similarity index 86% rename from integrations/coinbase/src/main/java/org/dash/wallet/integration/coinbase_integration/ui/CoinbaseConvertCryptoFragment.kt rename to integrations/coinbase/src/main/java/org/dash/wallet/integrations/coinbase/ui/CoinbaseConvertCryptoFragment.kt index f9924fa8e0..9946fc0044 100644 --- a/integrations/coinbase/src/main/java/org/dash/wallet/integration/coinbase_integration/ui/CoinbaseConvertCryptoFragment.kt +++ b/integrations/coinbase/src/main/java/org/dash/wallet/integrations/coinbase/ui/CoinbaseConvertCryptoFragment.kt @@ -14,7 +14,7 @@ * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ -package org.dash.wallet.integration.coinbase_integration.ui +package org.dash.wallet.integrations.coinbase.ui import android.annotation.SuppressLint import android.content.Intent @@ -26,13 +26,11 @@ import androidx.constraintlayout.widget.ConstraintLayout import androidx.core.view.isGone import androidx.core.view.isVisible import androidx.fragment.app.Fragment -import androidx.fragment.app.activityViewModels import androidx.fragment.app.commit import androidx.fragment.app.viewModels import androidx.lifecycle.lifecycleScope import androidx.navigation.fragment.findNavController import dagger.hilt.android.AndroidEntryPoint -import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.launch import org.bitcoinj.core.Coin import org.bitcoinj.utils.ExchangeRate @@ -43,31 +41,33 @@ import org.dash.wallet.common.ui.dialogs.AdaptiveDialog import org.dash.wallet.common.ui.dialogs.MinimumBalanceDialog import org.dash.wallet.common.ui.viewBinding import org.dash.wallet.common.util.GenericUtils +import org.dash.wallet.common.util.observe import org.dash.wallet.common.util.safeNavigate import org.dash.wallet.common.util.toFormattedString -import org.dash.wallet.integration.coinbase_integration.R -import org.dash.wallet.integration.coinbase_integration.databinding.FragmentCoinbaseConvertCryptoBinding -import org.dash.wallet.integration.coinbase_integration.model.CoinBaseUserAccountDataUIModel -import org.dash.wallet.integration.coinbase_integration.model.getCoinBaseExchangeRateConversion -import org.dash.wallet.integration.coinbase_integration.ui.convert_currency.ConvertViewFragment -import org.dash.wallet.integration.coinbase_integration.ui.convert_currency.model.* -import org.dash.wallet.integration.coinbase_integration.ui.dialogs.crypto_wallets.CryptoWalletsDialog -import org.dash.wallet.integration.coinbase_integration.viewmodels.CoinbaseActivityViewModel -import org.dash.wallet.integration.coinbase_integration.viewmodels.CoinbaseConvertCryptoViewModel -import org.dash.wallet.integration.coinbase_integration.viewmodels.ConvertViewViewModel - -@ExperimentalCoroutinesApi +import org.dash.wallet.integrations.coinbase.R +import org.dash.wallet.integrations.coinbase.databinding.FragmentCoinbaseConvertCryptoBinding +import org.dash.wallet.integrations.coinbase.model.CoinBaseUserAccountDataUIModel +import org.dash.wallet.integrations.coinbase.model.getCoinBaseExchangeRateConversion +import org.dash.wallet.integrations.coinbase.ui.convert_currency.ConvertViewFragment +import org.dash.wallet.integrations.coinbase.ui.convert_currency.model.* +import org.dash.wallet.integrations.coinbase.ui.dialogs.crypto_wallets.CryptoWalletsDialog +import org.dash.wallet.integrations.coinbase.viewmodels.CoinbaseConvertCryptoViewModel +import org.dash.wallet.integrations.coinbase.viewmodels.CoinbaseViewModel +import org.dash.wallet.integrations.coinbase.viewmodels.ConvertViewViewModel +import org.dash.wallet.integrations.coinbase.viewmodels.coinbaseViewModels + @AndroidEntryPoint class CoinbaseConvertCryptoFragment : Fragment(R.layout.fragment_coinbase_convert_crypto) { private val binding by viewBinding(FragmentCoinbaseConvertCryptoBinding::bind) private val viewModel by viewModels() + private val convertViewModel by coinbaseViewModels() + private val sharedViewModel by coinbaseViewModels() + private var loadingDialog: AdaptiveDialog? = null - private val convertViewModel by activityViewModels() private var cryptoWalletsDialog: CryptoWalletsDialog? = null private var selectedCoinBaseAccount: CoinBaseUserAccountDataUIModel? = null private val dashFormat = MonetaryFormat().withLocale(GenericUtils.getDeviceLocale()) .noCode().minDecimals(8).optionalDecimals() - private val sharedViewModel: CoinbaseActivityViewModel by activityViewModels() private lateinit var fragment: ConvertViewFragment @@ -170,7 +170,7 @@ class CoinbaseConvertCryptoFragment : Fragment(R.layout.fragment_coinbase_conver lifecycleScope.launch { var list = listOf() val selectedCurrencyCode = convertViewModel.selectedCryptoCurrencyAccount.value - ?.coinBaseUserAccountData?.currency?.code ?: convertViewModel.selectedLocalCurrencyCode + ?.coinbaseAccount?.currency ?: convertViewModel.selectedLocalCurrencyCode cryptoWalletsDialog = CryptoWalletsDialog(selectedCurrencyCode) { index, dialog -> list.getOrNull(index)?.let { @@ -243,26 +243,25 @@ class CoinbaseConvertCryptoFragment : Fragment(R.layout.fragment_coinbase_conver setGuidelinePercent(true) } - sharedViewModel.baseIdForFaitModelCoinBase.observe(viewLifecycleOwner) { uiState -> - // New value received - when (uiState) { - is BaseIdForFaitDataUIState.Success -> { - uiState.baseIdForFaitDataList.let { list -> - viewModel.setBaseIdForFaitModelCoinBase(list) + sharedViewModel.uiState.observe(viewLifecycleOwner) { uiState -> + if (uiState.isSessionExpired) { + findNavController().popBackStack() + } else { + // New value received + when (uiState.baseIdForFiatModel) { + is BaseIdForFiatData.Success -> { + dismissProgress() + uiState.baseIdForFiatModel.baseIdForFaitDataList.let { list -> + viewModel.setBaseIdForFaitModelCoinBase(list) + } } - } - - is BaseIdForFaitDataUIState.LoadingState ->{ - if (uiState.isLoading) { + is BaseIdForFiatData.LoadingState -> { showProgress(R.string.loading) - } else { - dismissProgress() } - } - is BaseIdForFaitDataUIState.Error ->{ - if (uiState.isError) { + is BaseIdForFiatData.Error -> { + dismissProgress() // Retry in case of error - sharedViewModel.getBaseIdForFaitModel() + sharedViewModel.getBaseIdForFiatModel() } } } @@ -378,19 +377,19 @@ class CoinbaseConvertCryptoFragment : Fragment(R.layout.fragment_coinbase_conver private fun setConvertViewInput() { convertViewModel.selectedCryptoCurrencyAccount.value?.let { - val accountData = it.coinBaseUserAccountData - val currency = accountData.balance?.currency?.lowercase() - val iconUrl = if (!accountData.balance?.currency.isNullOrEmpty() && currency != null) { + val accountData = it.coinbaseAccount + val currency = accountData.currency.lowercase() + val iconUrl = if (accountData.currency.isNotEmpty()) { GenericUtils.getCoinIcon(currency) } else { null } binding.convertView.input = ServiceWallet( - it.coinBaseUserAccountData.currency?.name ?: "", + it.coinbaseAccount.name, getString(R.string.coinbase), - it.coinBaseUserAccountData.balance?.amount ?: "", - it.coinBaseUserAccountData.balance?.currency ?: "", + it.coinbaseAccount.availableBalance.value, + it.coinbaseAccount.currency, convertViewModel.selectedLocalExchangeRate.value?.let { rate -> it.getCoinBaseExchangeRateConversion(rate).first } ?: "", diff --git a/integrations/coinbase/src/main/java/org/dash/wallet/integrations/coinbase/ui/CoinbaseOrderReviewFragment.kt b/integrations/coinbase/src/main/java/org/dash/wallet/integrations/coinbase/ui/CoinbaseOrderReviewFragment.kt new file mode 100644 index 0000000000..17a511be0c --- /dev/null +++ b/integrations/coinbase/src/main/java/org/dash/wallet/integrations/coinbase/ui/CoinbaseOrderReviewFragment.kt @@ -0,0 +1,189 @@ +/* + * Copyright 2021 Dash Core Group. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package org.dash.wallet.integrations.coinbase.ui + +import android.os.Bundle +import android.view.View +import androidx.activity.addCallback +import androidx.core.view.isVisible +import androidx.fragment.app.Fragment +import androidx.lifecycle.lifecycleScope +import androidx.navigation.fragment.findNavController +import dagger.hilt.android.AndroidEntryPoint +import kotlinx.coroutines.launch +import org.bitcoinj.utils.MonetaryFormat +import org.dash.wallet.common.services.analytics.AnalyticsConstants +import org.dash.wallet.common.ui.dialogs.AdaptiveDialog +import org.dash.wallet.common.ui.payment_method_picker.PaymentMethodType +import org.dash.wallet.common.ui.viewBinding +import org.dash.wallet.common.util.GenericUtils +import org.dash.wallet.common.util.observe +import org.dash.wallet.common.util.safeNavigate +import org.dash.wallet.common.util.toFormattedString +import org.dash.wallet.integrations.coinbase.R +import org.dash.wallet.integrations.coinbase.databinding.FragmentCoinbaseOrderReviewBinding +import org.dash.wallet.integrations.coinbase.model.* +import org.dash.wallet.integrations.coinbase.ui.dialogs.CoinBaseResultDialog +import org.dash.wallet.integrations.coinbase.viewmodels.CoinbaseBuyDashViewModel +import org.dash.wallet.integrations.coinbase.viewmodels.CoinbaseBuyUIState +import org.dash.wallet.integrations.coinbase.viewmodels.CoinbaseViewModel +import org.dash.wallet.integrations.coinbase.viewmodels.coinbaseViewModels + +@AndroidEntryPoint +class CoinbaseOrderReviewFragment : Fragment(R.layout.fragment_coinbase_order_review) { + private val binding by viewBinding(FragmentCoinbaseOrderReviewBinding::bind) + private val viewModel by coinbaseViewModels() + private val sharedViewModel by coinbaseViewModels() + private val dashFormat = MonetaryFormat().withLocale( + GenericUtils.getDeviceLocale() + ).noCode().minDecimals(6).optionalDecimals() + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + + requireActivity().onBackPressedDispatcher.addCallback(viewLifecycleOwner) { + viewModel.logEvent(AnalyticsConstants.Coinbase.BUY_QUOTE_ANDROID_BACK) + findNavController().popBackStack() + } + + binding.toolbar.setNavigationOnClickListener { + viewModel.logEvent(AnalyticsConstants.Coinbase.BUY_QUOTE_TOP_BACK) + findNavController().popBackStack() + } + + binding.cancelBtn.setOnClickListener { + viewModel.logEvent(AnalyticsConstants.Coinbase.BUY_QUOTE_CANCEL) + val dialog = AdaptiveDialog.simple( + getString(R.string.cancel_transaction), + getString(R.string.no_keep_it), + getString(R.string.yes_cancel) + ) + dialog.isCancelable = false + dialog.show(requireActivity()) { result -> + if (result == true) { + viewModel.logEvent(AnalyticsConstants.Coinbase.BUY_QUOTE_CANCEL_YES) + findNavController().popBackStack() + } else { + viewModel.logEvent(AnalyticsConstants.Coinbase.BUY_QUOTE_CANCEL_NO) + } + } + } + + binding.confirmBtnContainer.setOnClickListener { + lifecycleScope.launch { + if (tryBuyDash()) { + val params = viewModel.getTransferDashParams() + val twoFaParams = CoinbaseTransactionParams(params, TransactionType.BuyDash) + safeNavigate(CoinbaseOrderReviewFragmentDirections.coinbaseBuyDashOrderReviewToTwoFaCode(twoFaParams)) + } + } + } + + binding.contentOrderReview.coinbaseFeeInfoContainer.setOnClickListener { + viewModel.logEvent(AnalyticsConstants.Coinbase.BUY_QUOTE_FEE_INFO) + safeNavigate(CoinbaseOrderReviewFragmentDirections.orderReviewToFeeInfo()) + } + + sharedViewModel.uiState.observe(viewLifecycleOwner) { state -> + if (state.isSessionExpired) { + findNavController().popBackStack(R.id.coinbaseServicesFragment, false) + } else { + setNetworkState(state.isNetworkAvailable) + } + } + + viewModel.uiState.observe(viewLifecycleOwner) { state -> + updateOrderReviewUI(state) + } + } + + private fun updateOrderReviewUI(state: CoinbaseBuyUIState) { + binding.contentReviewBuyOrderDashAmount.dashAmount.text = dashFormat.format(state.dashAmount) + binding.contentReviewBuyOrderDashAmount.message.text = + getString(R.string.you_will_receive_dash_on_your_dash_wallet, dashFormat.format(state.dashAmount)) + + if (state.order != null && state.fee != null) { + binding.contentOrderReview.purchaseAmount.text = state.order.toFormattedString() + binding.contentOrderReview.coinbaseFeeAmount.text = state.fee.toFormattedString() + binding.contentOrderReview.totalAmount.text = state.order.add(state.fee).toFormattedString() + } + + if (state.paymentMethod != null) { + val (name, account) = splitNameAndAccount(state.paymentMethod.name, state.paymentMethod.paymentMethodType) + binding.contentOrderReview.paymentMethodName.text = name + binding.contentOrderReview.account.text = account + } + } + + private fun showBuyOrderDialog(responseMessage: String) { + val transactionStateDialog = CoinBaseResultDialog.newInstance(CoinBaseResultDialog.Type.PURCHASE_ERROR, responseMessage).apply { + this.onCoinBaseResultDialogButtonsClickListener = object : CoinBaseResultDialog.CoinBaseResultDialogButtonsClickListener { + override fun onPositiveButtonClick(type: CoinBaseResultDialog.Type) { + when (type) { + CoinBaseResultDialog.Type.PURCHASE_ERROR -> { + viewModel.logEvent(AnalyticsConstants.Coinbase.BUY_ERROR_CLOSE) + dismiss() + findNavController().popBackStack() + } + else -> {} + } + } + } + } + transactionStateDialog.showNow(parentFragmentManager, "CoinBaseBuyDashDialog") + } + + private fun setNetworkState(hasInternet: Boolean) { + binding.networkStatusStub.isVisible = !hasInternet + binding.previewOfflineGroup.isVisible = hasInternet + } + + private suspend fun tryBuyDash(): Boolean { + try { + AdaptiveDialog.withProgress(getString(R.string.loading), requireActivity()) { + viewModel.buyDash(true) + } + } catch (ex: Exception) { + showBuyOrderDialog(ex.message ?: getString(R.string.retry_later_message)) + return false + } + + return true + } + + private fun splitNameAndAccount(nameAccount: String?, type: PaymentMethodType): Pair { + nameAccount?.let { + val match = when (type) { + PaymentMethodType.BankAccount, PaymentMethodType.Card, PaymentMethodType.PayPal -> { + "(\\d+)?\\s?[a-z]?\\*+".toRegex().find(nameAccount) + } + PaymentMethodType.Fiat -> { + "\\(.*\\)".toRegex().find(nameAccount) + } + else -> null + } + + return match?.range?.first?.let { index -> + val name = nameAccount.substring(0, index).trim(' ', '-', ',', ':') + val account = nameAccount.substring(index, nameAccount.length).trim() + return Pair(name, account) + } ?: Pair(nameAccount, "") + } + + return Pair("", "") + } +} diff --git a/integrations/coinbase/src/main/java/org/dash/wallet/integrations/coinbase/ui/CoinbaseServicesFragment.kt b/integrations/coinbase/src/main/java/org/dash/wallet/integrations/coinbase/ui/CoinbaseServicesFragment.kt new file mode 100644 index 0000000000..47ef6c962f --- /dev/null +++ b/integrations/coinbase/src/main/java/org/dash/wallet/integrations/coinbase/ui/CoinbaseServicesFragment.kt @@ -0,0 +1,214 @@ +/* + * Copyright 2021 Dash Core Group. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package org.dash.wallet.integrations.coinbase.ui + +import android.animation.ObjectAnimator +import android.app.Activity +import android.content.Intent +import android.net.Uri +import android.os.Bundle +import android.view.View +import androidx.activity.result.contract.ActivityResultContracts +import androidx.core.view.isVisible +import androidx.fragment.app.Fragment +import androidx.fragment.app.viewModels +import androidx.lifecycle.lifecycleScope +import androidx.navigation.fragment.findNavController +import dagger.hilt.android.AndroidEntryPoint +import org.bitcoinj.core.Coin +import org.dash.wallet.common.databinding.FragmentIntegrationPortalBinding +import org.dash.wallet.common.services.analytics.AnalyticsConstants +import org.dash.wallet.common.ui.blinkAnimator +import org.dash.wallet.common.ui.dialogs.AdaptiveDialog +import org.dash.wallet.common.ui.viewBinding +import org.dash.wallet.common.util.observe +import org.dash.wallet.common.util.safeNavigate +import org.dash.wallet.common.util.toFormattedString +import org.dash.wallet.integrations.coinbase.R +import org.dash.wallet.integrations.coinbase.model.CoinbaseErrorType +import org.dash.wallet.integrations.coinbase.viewmodels.CoinbaseViewModel +import org.dash.wallet.integrations.coinbase.viewmodels.CoinbaseServicesViewModel +import org.dash.wallet.integrations.coinbase.viewmodels.coinbaseViewModels + +@AndroidEntryPoint +class CoinbaseServicesFragment : Fragment(R.layout.fragment_integration_portal) { + private val binding by viewBinding(FragmentIntegrationPortalBinding::bind) + private val viewModel by viewModels() + private val sharedViewModel by coinbaseViewModels() + private var balanceAnimator: ObjectAnimator? = null + + private val coinbaseAuthLauncher = registerForActivityResult( + ActivityResultContracts.StartActivityForResult() + ) { result -> + val data = result.data + + if (result.resultCode == Activity.RESULT_OK) { + data?.extras?.getString(CoinBaseWebClientActivity.RESULT_TEXT)?.let { code -> + lifecycleScope.launchWhenResumed { + handleCoinbaseAuthResult(code) + } + } + } + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + + binding.toolbar.setNavigationOnClickListener { + findNavController().popBackStack() + } + binding.toolbarTitle.text = getString(R.string.coinbase) + binding.toolbarIcon.setImageResource(R.drawable.ic_coinbase) + binding.balanceHeader.text = getString(R.string.balance_on_coinbase) + binding.transferSubtitle.text = getString(R.string.between_dash_wallet_and_coinbase) + binding.convertSubtitle.text = getString(R.string.between_dash_wallet_and_coinbase) + binding.disconnectTitle.text = getString(R.string.disconnect_coinbase_account) + + binding.disconnectBtn.setOnClickListener { + viewModel.disconnectCoinbaseAccount() + } + + binding.buyBtn.setOnClickListener { + viewModel.logEvent(AnalyticsConstants.Coinbase.BUY_DASH) + safeNavigate(CoinbaseServicesFragmentDirections.servicesToBuyDash()) + } + + binding.convertBtn.setOnClickListener { + viewModel.logEvent(AnalyticsConstants.Coinbase.CONVERT_DASH) + safeNavigate(CoinbaseServicesFragmentDirections.servicesToConvertCrypto(true)) + } + + binding.transferBtn.setOnClickListener { + viewModel.logEvent(AnalyticsConstants.Coinbase.TRANSFER_DASH) + safeNavigate(CoinbaseServicesFragmentDirections.servicesToTransferDash()) + } + + binding.balanceDash.setFormat(viewModel.balanceFormat) + binding.balanceDash.setApplyMarkup(false) + binding.balanceDash.setAmount(Coin.ZERO) + this.balanceAnimator = binding.balanceHeader.blinkAnimator + + binding.root.setOnRefreshListener { + viewModel.refreshBalance() + } + + sharedViewModel.uiState.observe(viewLifecycleOwner) { state -> + setNetworkState(state.isNetworkAvailable) + + if (state.isSessionExpired) { + sharedViewModel.clearWasLoggedOut() + + AdaptiveDialog.create( + R.drawable.ic_relogin, + getString(R.string.your_coinbase_session_has_expired), + getString(R.string.please_log_in_to_your_coinbase_account), + getString(R.string.cancel), + getString(R.string.log_in) + ).also { + it.isCancelable = false + }.show(requireActivity()) { login -> + if (login == true) { + coinbaseAuthLauncher.launch( + Intent(requireContext(), CoinBaseWebClientActivity::class.java) + ) + } else { + findNavController().popBackStack() + } + } + } + } + + viewModel.uiState.observe(viewLifecycleOwner) { state -> + if (!state.isLoggedIn) { + findNavController().popBackStack() + return@observe + } + + if (state.error == CoinbaseErrorType.USER_ACCOUNT_ERROR) { + AdaptiveDialog.create( + R.drawable.ic_error, + getString(R.string.coinbase_dash_wallet_error_title), + getString(R.string.coinbase_dash_wallet_error_message), + getString(R.string.close), + getString(R.string.create_dash_account), + ).show(requireActivity()) { createAccount -> + if (createAccount == true) { + viewModel.logEvent(AnalyticsConstants.Coinbase.BUY_CREATE_ACCOUNT) + openCoinbaseWebsite() + } + } + viewModel.clearError() + } else { + binding.balanceDash.setAmount(state.balance) + binding.balanceLocal.text = state.balanceFiat?.toFormattedString() ?: "" + + if (state.isBalanceUpdating) { + this.balanceAnimator?.start() + } else { + binding.root.isRefreshing = false + this.balanceAnimator?.end() + } + } + } + + viewModel.refreshBalance() + sharedViewModel.getBaseIdForFiatModel() + } + + private fun setNetworkState(hasInternet: Boolean) { + binding.lastKnownBalance.isVisible = !hasInternet + binding.networkStatusStub.isVisible = !hasInternet + binding.actionsView.isVisible = hasInternet + binding.disconnectBtn.isVisible = hasInternet + binding.disconnectedIndicator.isVisible = !hasInternet + } + + private fun openCoinbaseWebsite() { + val defaultBrowser = Intent.makeMainSelectorActivity(Intent.ACTION_MAIN, Intent.CATEGORY_APP_BROWSER) + defaultBrowser.data = Uri.parse(getString(R.string.coinbase_website)) + startActivity(defaultBrowser) + } + + private suspend fun handleCoinbaseAuthResult(code: String) { + val success = AdaptiveDialog.withProgress(getString(R.string.loading), requireActivity()) { + sharedViewModel.loginToCoinbase(code) + } + + if (success) { + return + } + + val retry = AdaptiveDialog.create( + R.drawable.ic_error, + getString(R.string.login_error_title, getString(R.string.coinbase)), + getString(R.string.login_error_message, getString(R.string.coinbase)), + getString(android.R.string.cancel), + getString(R.string.retry) + ).showAsync(requireActivity()) + + if (retry == true) { + handleCoinbaseAuthResult(code) + } else { + findNavController().popBackStack() + } + } + + override fun onDestroy() { + super.onDestroy() + this.balanceAnimator = null + } +} diff --git a/integrations/coinbase/src/main/java/org/dash/wallet/integration/coinbase_integration/ui/EnterAmountToTransferFragment.kt b/integrations/coinbase/src/main/java/org/dash/wallet/integrations/coinbase/ui/EnterAmountToTransferFragment.kt similarity index 93% rename from integrations/coinbase/src/main/java/org/dash/wallet/integration/coinbase_integration/ui/EnterAmountToTransferFragment.kt rename to integrations/coinbase/src/main/java/org/dash/wallet/integrations/coinbase/ui/EnterAmountToTransferFragment.kt index bb1fbc7003..4aba268220 100644 --- a/integrations/coinbase/src/main/java/org/dash/wallet/integration/coinbase_integration/ui/EnterAmountToTransferFragment.kt +++ b/integrations/coinbase/src/main/java/org/dash/wallet/integrations/coinbase/ui/EnterAmountToTransferFragment.kt @@ -15,7 +15,7 @@ * along with this program. If not, see . */ -package org.dash.wallet.integration.coinbase_integration.ui +package org.dash.wallet.integrations.coinbase.ui import android.os.Bundle import android.text.Spannable @@ -25,10 +25,8 @@ import android.text.style.RelativeSizeSpan import android.view.View import androidx.core.view.isVisible import androidx.fragment.app.Fragment -import androidx.fragment.app.activityViewModels import androidx.lifecycle.lifecycleScope import dagger.hilt.android.AndroidEntryPoint -import kotlinx.coroutines.ExperimentalCoroutinesApi import org.bitcoinj.core.Coin import org.bitcoinj.utils.ExchangeRate import org.bitcoinj.utils.Fiat @@ -37,12 +35,12 @@ import org.dash.wallet.common.ui.viewBinding import org.dash.wallet.common.util.Constants import org.dash.wallet.common.util.GenericUtils import org.dash.wallet.common.util.isCurrencyFirst -import org.dash.wallet.integration.coinbase_integration.CoinbaseConstants -import org.dash.wallet.integration.coinbase_integration.R -import org.dash.wallet.integration.coinbase_integration.databinding.EnterAmountToTransferFragmentBinding -import org.dash.wallet.integration.coinbase_integration.viewmodels.EnterAmountToTransferViewModel +import org.dash.wallet.integrations.coinbase.CoinbaseConstants +import org.dash.wallet.integrations.coinbase.R +import org.dash.wallet.integrations.coinbase.databinding.EnterAmountToTransferFragmentBinding +import org.dash.wallet.integrations.coinbase.viewmodels.EnterAmountToTransferViewModel +import org.dash.wallet.integrations.coinbase.viewmodels.coinbaseViewModels -@ExperimentalCoroutinesApi @AndroidEntryPoint class EnterAmountToTransferFragment : Fragment(R.layout.enter_amount_to_transfer_fragment) { @@ -51,7 +49,7 @@ class EnterAmountToTransferFragment : Fragment(R.layout.enter_amount_to_transfer fun newInstance() = EnterAmountToTransferFragment() } - private val viewModel by activityViewModels() + private val viewModel by coinbaseViewModels() private val binding by viewBinding(EnterAmountToTransferFragmentBinding::bind) private var exchangeRate: ExchangeRate? = null diff --git a/integrations/coinbase/src/main/java/org/dash/wallet/integration/coinbase_integration/ui/EnterTwoFaCodeFragment.kt b/integrations/coinbase/src/main/java/org/dash/wallet/integrations/coinbase/ui/EnterTwoFaCodeFragment.kt similarity index 90% rename from integrations/coinbase/src/main/java/org/dash/wallet/integration/coinbase_integration/ui/EnterTwoFaCodeFragment.kt rename to integrations/coinbase/src/main/java/org/dash/wallet/integrations/coinbase/ui/EnterTwoFaCodeFragment.kt index 8a5dcb05d7..38dcaab08c 100644 --- a/integrations/coinbase/src/main/java/org/dash/wallet/integration/coinbase_integration/ui/EnterTwoFaCodeFragment.kt +++ b/integrations/coinbase/src/main/java/org/dash/wallet/integrations/coinbase/ui/EnterTwoFaCodeFragment.kt @@ -15,7 +15,7 @@ * along with this program. If not, see . */ -package org.dash.wallet.integration.coinbase_integration.ui +package org.dash.wallet.integrations.coinbase.ui import android.content.ActivityNotFoundException import android.content.Intent @@ -31,29 +31,26 @@ import androidx.fragment.app.Fragment import androidx.fragment.app.viewModels import androidx.navigation.fragment.findNavController import dagger.hilt.android.AndroidEntryPoint +import org.dash.wallet.common.ui.LockScreenAware import org.dash.wallet.common.util.Constants import org.dash.wallet.common.ui.dialogs.AdaptiveDialog import org.dash.wallet.common.ui.enter_amount.NumericKeyboardView import org.dash.wallet.common.ui.setRoundedBackground import org.dash.wallet.common.ui.viewBinding -import org.dash.wallet.integration.coinbase_integration.R -import org.dash.wallet.integration.coinbase_integration.databinding.EnterTwoFaCodeFragmentBinding -import org.dash.wallet.integration.coinbase_integration.model.TransactionType -import org.dash.wallet.integration.coinbase_integration.ui.dialogs.CoinBaseResultDialog -import org.dash.wallet.integration.coinbase_integration.viewmodels.EnterTwoFaCodeViewModel -import org.dash.wallet.integration.coinbase_integration.viewmodels.TransactionState +import org.dash.wallet.integrations.coinbase.R +import org.dash.wallet.integrations.coinbase.databinding.EnterTwoFaCodeFragmentBinding +import org.dash.wallet.integrations.coinbase.model.TransactionType +import org.dash.wallet.integrations.coinbase.ui.dialogs.CoinBaseResultDialog +import org.dash.wallet.integrations.coinbase.viewmodels.EnterTwoFaCodeViewModel +import org.dash.wallet.integrations.coinbase.viewmodels.TransactionState @AndroidEntryPoint -class EnterTwoFaCodeFragment : Fragment(R.layout.enter_two_fa_code_fragment) { +class EnterTwoFaCodeFragment : Fragment(R.layout.enter_two_fa_code_fragment), LockScreenAware { private val binding by viewBinding(EnterTwoFaCodeFragmentBinding::bind) private val viewModel by viewModels() private lateinit var loadingDialog: AdaptiveDialog - companion object { - fun newInstance() = EnterTwoFaCodeFragment() - } - override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) handleBackPress() @@ -65,7 +62,7 @@ class EnterTwoFaCodeFragment : Fragment(R.layout.enter_two_fa_code_fragment) { } binding.keyboardView.onKeyboardActionListener = keyboardActionListener - viewModel.loadingState.observe(viewLifecycleOwner){ + viewModel.loadingState.observe(viewLifecycleOwner) { setLoadingState(it) } @@ -204,8 +201,9 @@ class EnterTwoFaCodeFragment : Fragment(R.layout.enter_two_fa_code_fragment) { CoinBaseResultDialog.Type.CONVERSION_SUCCESS, CoinBaseResultDialog.Type.DEPOSIT_SUCCESS, CoinBaseResultDialog.Type.TRANSFER_DASH_SUCCESS -> { viewModel.logClose(type) dismiss() - requireActivity().setResult(Constants.RESULT_CODE_GO_HOME) - requireActivity().finish() + val navController = findNavController() + val home = navController.graph.startDestinationId + navController.popBackStack(home, false) } else -> {} } @@ -218,4 +216,8 @@ class EnterTwoFaCodeFragment : Fragment(R.layout.enter_two_fa_code_fragment) { } transactionStateDialog.showNow(parentFragmentManager, "CoinBaseBuyDashDialog") } + + override fun onLockScreenActivated() { + findNavController().popBackStack(R.id.coinbaseServicesFragment, false) + } } diff --git a/integrations/coinbase/src/main/java/org/dash/wallet/integration/coinbase_integration/ui/TransferDashFragment.kt b/integrations/coinbase/src/main/java/org/dash/wallet/integrations/coinbase/ui/TransferDashFragment.kt similarity index 91% rename from integrations/coinbase/src/main/java/org/dash/wallet/integration/coinbase_integration/ui/TransferDashFragment.kt rename to integrations/coinbase/src/main/java/org/dash/wallet/integrations/coinbase/ui/TransferDashFragment.kt index 079dbb6ce4..c2d59587c4 100644 --- a/integrations/coinbase/src/main/java/org/dash/wallet/integration/coinbase_integration/ui/TransferDashFragment.kt +++ b/integrations/coinbase/src/main/java/org/dash/wallet/integrations/coinbase/ui/TransferDashFragment.kt @@ -14,7 +14,7 @@ * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ -package org.dash.wallet.integration.coinbase_integration.ui +package org.dash.wallet.integrations.coinbase.ui import android.annotation.SuppressLint import android.content.Intent @@ -26,12 +26,10 @@ import androidx.constraintlayout.widget.ConstraintLayout import androidx.core.view.isVisible import androidx.core.view.updateLayoutParams import androidx.fragment.app.Fragment -import androidx.fragment.app.activityViewModels import androidx.fragment.app.commit import androidx.lifecycle.lifecycleScope import androidx.navigation.fragment.findNavController import dagger.hilt.android.AndroidEntryPoint -import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.launch import org.bitcoinj.core.Coin import org.bitcoinj.utils.ExchangeRate @@ -44,18 +42,19 @@ import org.dash.wallet.common.ui.* import org.dash.wallet.common.ui.dialogs.AdaptiveDialog import org.dash.wallet.common.ui.dialogs.MinimumBalanceDialog import org.dash.wallet.common.util.* -import org.dash.wallet.integration.coinbase_integration.CoinbaseConstants -import org.dash.wallet.integration.coinbase_integration.R -import org.dash.wallet.integration.coinbase_integration.databinding.TransferDashFragmentBinding -import org.dash.wallet.integration.coinbase_integration.ui.convert_currency.model.BaseServiceWallet -import org.dash.wallet.integration.coinbase_integration.ui.convert_currency.model.SwapValueErrorType -import org.dash.wallet.integration.coinbase_integration.ui.dialogs.CoinBaseResultDialog -import org.dash.wallet.integration.coinbase_integration.viewmodels.EnterAmountToTransferViewModel -import org.dash.wallet.integration.coinbase_integration.viewmodels.SendDashResponseState -import org.dash.wallet.integration.coinbase_integration.viewmodels.TransferDashViewModel +import org.dash.wallet.integrations.coinbase.CoinbaseConstants +import org.dash.wallet.integrations.coinbase.R +import org.dash.wallet.integrations.coinbase.databinding.TransferDashFragmentBinding +import org.dash.wallet.integrations.coinbase.ui.convert_currency.model.BaseServiceWallet +import org.dash.wallet.integrations.coinbase.ui.convert_currency.model.SwapValueErrorType +import org.dash.wallet.integrations.coinbase.ui.dialogs.CoinBaseResultDialog +import org.dash.wallet.integrations.coinbase.viewmodels.CoinbaseViewModel +import org.dash.wallet.integrations.coinbase.viewmodels.EnterAmountToTransferViewModel +import org.dash.wallet.integrations.coinbase.viewmodels.SendDashResponseState +import org.dash.wallet.integrations.coinbase.viewmodels.TransferDashViewModel +import org.dash.wallet.integrations.coinbase.viewmodels.coinbaseViewModels import javax.inject.Inject -@ExperimentalCoroutinesApi @AndroidEntryPoint class TransferDashFragment : Fragment(R.layout.transfer_dash_fragment) { @@ -63,8 +62,9 @@ class TransferDashFragment : Fragment(R.layout.transfer_dash_fragment) { fun newInstance() = TransferDashFragment() } - private val enterAmountToTransferViewModel by activityViewModels() - private val transferDashViewModel by activityViewModels() + private val enterAmountToTransferViewModel by coinbaseViewModels() + private val transferDashViewModel by coinbaseViewModels() + private val sharedViewModel by coinbaseViewModels() private val binding by viewBinding(TransferDashFragmentBinding::bind) private var loadingDialog: AdaptiveDialog? = null @Inject lateinit var securityFunctions: AuthenticationManager @@ -184,11 +184,10 @@ class TransferDashFragment : Fragment(R.layout.transfer_dash_fragment) { transferDashViewModel.userAccountOnCoinbaseState.observe(viewLifecycleOwner){ enterAmountToTransferViewModel.coinbaseExchangeRate = it - val fiatVal = it.coinBaseUserAccountData.balance?.amount?.let { amount -> - enterAmountToTransferViewModel.getCoinbaseBalanceInFiatFormat(amount) - } ?: CoinbaseConstants.VALUE_ZERO + val amount = it.coinbaseAccount.availableBalance.value + val fiatVal = enterAmountToTransferViewModel.getCoinbaseBalanceInFiatFormat(amount) binding.transferView.balanceOnCoinbase = BaseServiceWallet( - it.coinBaseUserAccountData.balance?.amount ?: CoinbaseConstants.VALUE_ZERO, + it.coinbaseAccount.availableBalance.value, fiatVal ) // After initial load when coinbase exchange rate loaded @@ -319,6 +318,12 @@ class TransferDashFragment : Fragment(R.layout.transfer_dash_fragment) { } } } + + sharedViewModel.uiState.observe(viewLifecycleOwner) { + if (it.isSessionExpired) { + findNavController().popBackStack() + } + } } private fun setIsSyncing(isSyncing: Boolean) { @@ -376,8 +381,9 @@ class TransferDashFragment : Fragment(R.layout.transfer_dash_fragment) { CoinBaseResultDialog.Type.TRANSFER_DASH_SUCCESS -> { transferDashViewModel.logClose(type) dismiss() - requireActivity().setResult(Constants.RESULT_CODE_GO_HOME) - requireActivity().finish() + val navController = findNavController() + val home = navController.graph.startDestinationId + navController.popBackStack(home, false) } else -> { transferDashViewModel.logRetry() @@ -405,9 +411,10 @@ class TransferDashFragment : Fragment(R.layout.transfer_dash_fragment) { @SuppressLint("SetTextI18n") private fun setMaxAmountError() { - val fiatVal = transferDashViewModel.userAccountOnCoinbaseState.value?.coinBaseUserAccountData?.balance?.amount?.let { amount -> - enterAmountToTransferViewModel.getCoinbaseBalanceInFiatFormat(amount) - } ?: CoinbaseConstants.VALUE_ZERO + val fiatVal = transferDashViewModel.userAccountOnCoinbaseState.value + ?.coinbaseAccount?.availableBalance?.value?.let { amount -> + enterAmountToTransferViewModel.getCoinbaseBalanceInFiatFormat(amount) + } ?: CoinbaseConstants.VALUE_ZERO binding.dashWalletLimitBanner.text = "${getString(R.string.entered_amount_is_too_high)} $fiatVal" } diff --git a/integrations/coinbase/src/main/java/org/dash/wallet/integration/coinbase_integration/ui/convert_currency/ConvertView.kt b/integrations/coinbase/src/main/java/org/dash/wallet/integrations/coinbase/ui/convert_currency/ConvertView.kt similarity index 96% rename from integrations/coinbase/src/main/java/org/dash/wallet/integration/coinbase_integration/ui/convert_currency/ConvertView.kt rename to integrations/coinbase/src/main/java/org/dash/wallet/integrations/coinbase/ui/convert_currency/ConvertView.kt index 8a6bc8eda4..b4622a39e2 100644 --- a/integrations/coinbase/src/main/java/org/dash/wallet/integration/coinbase_integration/ui/convert_currency/ConvertView.kt +++ b/integrations/coinbase/src/main/java/org/dash/wallet/integrations/coinbase/ui/convert_currency/ConvertView.kt @@ -15,7 +15,7 @@ * along with this program. If not, see . */ -package org.dash.wallet.integration.coinbase_integration.ui.convert_currency +package org.dash.wallet.integrations.coinbase.ui.convert_currency import android.annotation.SuppressLint import android.content.Context @@ -31,9 +31,9 @@ import org.bitcoinj.utils.MonetaryFormat import org.dash.wallet.common.util.Constants import org.dash.wallet.common.util.GenericUtils import org.dash.wallet.common.util.toFormattedString -import org.dash.wallet.integration.coinbase_integration.R -import org.dash.wallet.integration.coinbase_integration.databinding.ConvertViewBinding -import org.dash.wallet.integration.coinbase_integration.ui.convert_currency.model.ServiceWallet +import org.dash.wallet.integrations.coinbase.R +import org.dash.wallet.integrations.coinbase.databinding.ConvertViewBinding +import org.dash.wallet.integrations.coinbase.ui.convert_currency.model.ServiceWallet import java.math.RoundingMode class ConvertView(context: Context, attrs: AttributeSet) : ConstraintLayout(context, attrs) { diff --git a/integrations/coinbase/src/main/java/org/dash/wallet/integration/coinbase_integration/ui/convert_currency/ConvertViewFragment.kt b/integrations/coinbase/src/main/java/org/dash/wallet/integrations/coinbase/ui/convert_currency/ConvertViewFragment.kt similarity index 73% rename from integrations/coinbase/src/main/java/org/dash/wallet/integration/coinbase_integration/ui/convert_currency/ConvertViewFragment.kt rename to integrations/coinbase/src/main/java/org/dash/wallet/integrations/coinbase/ui/convert_currency/ConvertViewFragment.kt index 9ada46fb0c..5302a4e413 100644 --- a/integrations/coinbase/src/main/java/org/dash/wallet/integration/coinbase_integration/ui/convert_currency/ConvertViewFragment.kt +++ b/integrations/coinbase/src/main/java/org/dash/wallet/integrations/coinbase/ui/convert_currency/ConvertViewFragment.kt @@ -15,7 +15,7 @@ * along with this program. If not, see . */ -package org.dash.wallet.integration.coinbase_integration.ui.convert_currency +package org.dash.wallet.integrations.coinbase.ui.convert_currency import android.os.Bundle import android.text.Spannable @@ -25,30 +25,27 @@ import android.text.style.RelativeSizeSpan import android.view.View import androidx.core.view.isVisible import androidx.fragment.app.Fragment -import androidx.fragment.app.activityViewModels import androidx.lifecycle.lifecycleScope import dagger.hilt.android.AndroidEntryPoint -import kotlinx.coroutines.ExperimentalCoroutinesApi import org.bitcoinj.core.Coin import org.bitcoinj.utils.ExchangeRate import org.bitcoinj.utils.Fiat -import org.dash.wallet.common.util.Constants import org.dash.wallet.common.ui.enter_amount.NumericKeyboardView import org.dash.wallet.common.ui.viewBinding +import org.dash.wallet.common.util.Constants import org.dash.wallet.common.util.GenericUtils import org.dash.wallet.common.util.isCurrencyFirst -import org.dash.wallet.integration.coinbase_integration.CoinbaseConstants -import org.dash.wallet.integration.coinbase_integration.R -import org.dash.wallet.integration.coinbase_integration.databinding.FragmentConvertCurrencyBinding -import org.dash.wallet.integration.coinbase_integration.model.CoinBaseUserAccountDataUIModel -import org.dash.wallet.integration.coinbase_integration.viewmodels.ConvertViewViewModel +import org.dash.wallet.integrations.coinbase.R +import org.dash.wallet.integrations.coinbase.databinding.FragmentConvertCurrencyBinding +import org.dash.wallet.integrations.coinbase.model.CoinBaseUserAccountDataUIModel +import org.dash.wallet.integrations.coinbase.viewmodels.ConvertViewViewModel +import org.dash.wallet.integrations.coinbase.viewmodels.coinbaseViewModels import java.math.BigDecimal import java.math.RoundingMode import java.text.DecimalFormat import java.text.DecimalFormatSymbols @AndroidEntryPoint -@ExperimentalCoroutinesApi class ConvertViewFragment : Fragment(R.layout.fragment_convert_currency) { companion object { private const val ARG_DASH_TO_FIAT = "dash_to_fiat" @@ -61,14 +58,14 @@ class ConvertViewFragment : Fragment(R.layout.fragment_convert_currency) { } private val binding by viewBinding(FragmentConvertCurrencyBinding::bind) - private val viewModel by activityViewModels() + private val viewModel by coinbaseViewModels() private val format = Constants.SEND_PAYMENT_LOCAL_FORMAT.noCode() private val decimalSeparator = DecimalFormatSymbols.getInstance(GenericUtils.getDeviceLocale()).decimalSeparator private var maxAmountSelected: Boolean = false - var currencyConversionOptionList: List = emptyList() + private var currencyConversionOptionList: List = emptyList() private var hasInternet: Boolean = true - + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) @@ -89,7 +86,6 @@ class ConvertViewFragment : Fragment(R.layout.fragment_convert_currency) { } } - binding.bottomCard.isVisible = false binding.currencyOptions.pickedOptionIndex = 0 @@ -97,19 +93,18 @@ class ConvertViewFragment : Fragment(R.layout.fragment_convert_currency) { binding.maxButton.setOnClickListener { viewModel.selectedCryptoCurrencyAccount.value?.let { userAccountData -> getMaxAmount()?.let { maxAmount -> - if (viewModel.selectedPickerCurrencyCode == userAccountData.coinBaseUserAccountData.balance?.currency - ) { + val currency = userAccountData.coinbaseAccount.currency + + if (viewModel.selectedPickerCurrencyCode == currency) { applyNewValue(maxAmount, viewModel.selectedPickerCurrencyCode) } else { val cleanedValue = if (viewModel.selectedPickerCurrencyCode == viewModel.selectedLocalCurrencyCode) { - maxAmount.toBigDecimal() / - userAccountData.currencyToCryptoCurrencyExchangeRate.toBigDecimal() + userAccountData.currencyToCryptoCurrencyExchangeRate } else { - maxAmount.toBigDecimal() * - userAccountData.cryptoCurrencyToDashExchangeRate.toBigDecimal() + userAccountData.getCryptoToDashExchangeRate() }.setScale(8, RoundingMode.HALF_UP).toString() applyNewValue(cleanedValue, viewModel.selectedPickerCurrencyCode) @@ -127,26 +122,26 @@ class ConvertViewFragment : Fragment(R.layout.fragment_convert_currency) { } private fun getMaxAmount(): String? { - - if (viewModel.dashToCrypto.value == true) {//from wallet -> coinbase + if (viewModel.dashToCrypto.value == true) { // from wallet -> coinbase viewModel.selectedCryptoCurrencyAccount.value?.let { account -> val cleanedValue = viewModel.maxForDashWalletAmount.toBigDecimal() / - account.cryptoCurrencyToDashExchangeRate.toBigDecimal() + account.getCryptoToDashExchangeRate() return cleanedValue.setScale(8, RoundingMode.HALF_UP).toString() } - } else { // coinbase -> wallet + } else { // coinbase -> wallet return viewModel.maxCoinBaseAccountAmount } return null } private fun resetViewSelection(it: CoinBaseUserAccountDataUIModel?) { - it?.coinBaseUserAccountData?.balance?.currency?.let { currencyCode -> - currencyConversionOptionList = if (viewModel.dashToCrypto.value == true) + it?.coinbaseAccount?.currency?.let { currencyCode -> + currencyConversionOptionList = if (viewModel.dashToCrypto.value == true) { listOf(Constants.DASH_CURRENCY, viewModel.selectedLocalCurrencyCode, currencyCode) - else + } else { listOf(currencyCode, viewModel.selectedLocalCurrencyCode, Constants.DASH_CURRENCY) + } binding.currencyOptions.apply { pickedOptionIndex = 0 provideOptions(currencyConversionOptionList) @@ -157,83 +152,74 @@ class ConvertViewFragment : Fragment(R.layout.fragment_convert_currency) { binding.currencyOptions.isVisible = true binding.maxButtonWrapper.isVisible = true binding.inputWrapper.isVisible = true - if (hasInternet) + if (hasInternet) { binding.bottomCard.isVisible = true + } } } private fun setAmountValue(pickedCurrencyOption: String, valueToBind: String) { viewModel.selectedCryptoCurrencyAccount.value?.let { userAccountData -> - - - val cleanedValue = if (viewModel.selectedPickerCurrencyCode !== pickedCurrencyOption && (viewModel.enteredConvertAmount.toBigDecimalOrNull() ?: BigDecimal.ZERO) > BigDecimal.ZERO) { - val convertedValue = when { - (userAccountData.coinBaseUserAccountData.balance?.currency == viewModel.selectedPickerCurrencyCode) -> { - if (pickedCurrencyOption == viewModel.selectedLocalCurrencyCode) { - - ( - valueToBind.toBigDecimal() / - userAccountData.currencyToCryptoCurrencyExchangeRate.toBigDecimal() - ) - .setScale(8, RoundingMode.HALF_UP).toString() + val enteredConvertAmount = (viewModel.enteredConvertAmount.toBigDecimalOrNull() ?: BigDecimal.ZERO) + val cleanedValue = if (viewModel.selectedPickerCurrencyCode !== pickedCurrencyOption && + enteredConvertAmount > BigDecimal.ZERO + ) { + val currency = userAccountData.coinbaseAccount.currency + val convertedValue = when { + (currency == viewModel.selectedPickerCurrencyCode) -> { + if (pickedCurrencyOption == viewModel.selectedLocalCurrencyCode) { + ( + valueToBind.toBigDecimal() / + userAccountData.currencyToCryptoCurrencyExchangeRate + ) + .setScale(8, RoundingMode.HALF_UP).toString() + } else { + val bd = viewModel.toDashValue(valueToBind, userAccountData, true) + val coin = try { + Coin.parseCoin(bd.toString()) + } catch (x: Exception) { + Coin.ZERO + } + if (coin.isZero) { + 0.toBigDecimal() } else { - - val bd = viewModel.toDashValue(valueToBind, userAccountData, true) - val coin = try { - Coin.parseCoin(bd.toString()) - } catch (x: Exception) { - Coin.ZERO - } - if (coin.isZero) { - 0.toBigDecimal() - } else { - bd - } + bd } } - (viewModel.selectedLocalCurrencyCode == viewModel.selectedPickerCurrencyCode) -> { - if (pickedCurrencyOption == userAccountData.coinBaseUserAccountData.balance?.currency) { - ( - valueToBind.toBigDecimal() * - userAccountData.currencyToCryptoCurrencyExchangeRate.toBigDecimal() - ) - .setScale(8, RoundingMode.HALF_UP).toString() + } + (viewModel.selectedLocalCurrencyCode == viewModel.selectedPickerCurrencyCode) -> { + if (pickedCurrencyOption == userAccountData.coinbaseAccount.currency) { + (valueToBind.toBigDecimal() * userAccountData.currencyToCryptoCurrencyExchangeRate) + .setScale(8, RoundingMode.HALF_UP).toString() + } else { + val bd = viewModel.toDashValue(valueToBind, userAccountData) + val coin = try { + Coin.parseCoin(bd.toString()) + } catch (x: Exception) { + Coin.ZERO + } + if (coin.isZero) { + 0.toBigDecimal() } else { - val bd = viewModel.toDashValue(valueToBind, userAccountData) - val coin = try { - Coin.parseCoin(bd.toString()) - } catch (x: Exception) { - Coin.ZERO - } - if (coin.isZero) { - 0.toBigDecimal() - } else { - bd - } + bd } } + } - else -> { - if (pickedCurrencyOption == userAccountData.coinBaseUserAccountData.balance?.currency) { - ( - valueToBind.toBigDecimal() / - userAccountData.cryptoCurrencyToDashExchangeRate.toBigDecimal() - ) - .setScale(8, RoundingMode.HALF_UP).toString() - } else { - - ( - valueToBind.toBigDecimal() / - userAccountData.currencyToDashExchangeRate.toBigDecimal() - ) - .setScale(8, RoundingMode.HALF_UP).toString() - } + else -> { + if (pickedCurrencyOption == userAccountData.coinbaseAccount.currency) { + (valueToBind.toBigDecimal() / userAccountData.getCryptoToDashExchangeRate()) + .setScale(8, RoundingMode.HALF_UP).toString() + } else { + (valueToBind.toBigDecimal() / userAccountData.currencyToDashExchangeRate) + .setScale(8, RoundingMode.HALF_UP).toString() } } - convertedValue.toString() - } else { - valueToBind } + convertedValue.toString() + } else { + valueToBind + } applyNewValue(cleanedValue, pickedCurrencyOption) } @@ -264,8 +250,9 @@ class ConvertViewFragment : Fragment(R.layout.fragment_convert_currency) { binding.inputAmount.text.split(" ") .first { it != binding.currencyOptions.pickedOption } } - if (inputValue != "0") + if (inputValue != "0") { value.append(inputValue) + } } override fun onNumber(number: Int) { @@ -387,14 +374,16 @@ class ConvertViewFragment : Fragment(R.layout.fragment_convert_currency) { val hasBalance = balance.isNotEmpty() && (balance.toBigDecimalOrNull() ?: BigDecimal.ZERO) > BigDecimal.ZERO - if (hasBalance) { viewModel.selectedCryptoCurrencyAccount.value?.let { viewModel.selectedLocalExchangeRate.value?.let { ExchangeRate(Coin.COIN, it.fiat) - }?.let { rate -> + }?.let { _ -> val dashAmount = when { - (it.coinBaseUserAccountData.balance?.currency == currencyCode && it.coinBaseUserAccountData.balance.currency != Constants.DASH_CURRENCY) -> { + ( + it.coinbaseAccount.currency == currencyCode && + it.coinbaseAccount.currency != Constants.DASH_CURRENCY + ) -> { val bd = viewModel.toDashValue(balance, it, true) try { @@ -403,7 +392,10 @@ class ConvertViewFragment : Fragment(R.layout.fragment_convert_currency) { Coin.ZERO } } - (viewModel.selectedLocalCurrencyCode == currencyCode && it.coinBaseUserAccountData.balance?.currency != Constants.DASH_CURRENCY) -> { + ( + viewModel.selectedLocalCurrencyCode == currencyCode && + it.coinbaseAccount.currency != Constants.DASH_CURRENCY + ) -> { // USD val bd = viewModel.toDashValue(balance, it) @@ -443,14 +435,17 @@ class ConvertViewFragment : Fragment(R.layout.fragment_convert_currency) { val textSize = 21.0f / binding.inputAmount.paint.textSize return spannable.apply { setSpan( - RelativeSizeSpan(textSize), from, - to, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE + RelativeSizeSpan(textSize), + from, + to, + Spannable.SPAN_EXCLUSIVE_EXCLUSIVE ) setSpan( - context?.resources?.getColor(R.color.content_primary) + context?.resources?.getColor(R.color.content_primary, null) ?.let { ForegroundColorSpan(it) }, from, - to, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE + to, + Spannable.SPAN_EXCLUSIVE_EXCLUSIVE ) } } diff --git a/integrations/coinbase/src/main/java/org/dash/wallet/integration/coinbase_integration/ui/convert_currency/CryptoConvertItem.kt b/integrations/coinbase/src/main/java/org/dash/wallet/integrations/coinbase/ui/convert_currency/CryptoConvertItem.kt similarity index 95% rename from integrations/coinbase/src/main/java/org/dash/wallet/integration/coinbase_integration/ui/convert_currency/CryptoConvertItem.kt rename to integrations/coinbase/src/main/java/org/dash/wallet/integrations/coinbase/ui/convert_currency/CryptoConvertItem.kt index d59f87dace..77fb5c7556 100644 --- a/integrations/coinbase/src/main/java/org/dash/wallet/integration/coinbase_integration/ui/convert_currency/CryptoConvertItem.kt +++ b/integrations/coinbase/src/main/java/org/dash/wallet/integrations/coinbase/ui/convert_currency/CryptoConvertItem.kt @@ -14,7 +14,7 @@ * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ -package org.dash.wallet.integration.coinbase_integration.ui.convert_currency +package org.dash.wallet.integrations.coinbase.ui.convert_currency import android.content.Context import android.graphics.drawable.Drawable @@ -29,8 +29,8 @@ import androidx.core.view.updateLayoutParams import coil.load import coil.size.Scale import coil.transform.CircleCropTransformation -import org.dash.wallet.integration.coinbase_integration.R -import org.dash.wallet.integration.coinbase_integration.databinding.ItemCyrptoConvertBinding +import org.dash.wallet.integrations.coinbase.R +import org.dash.wallet.integrations.coinbase.databinding.ItemCyrptoConvertBinding class CryptoConvertItem @JvmOverloads constructor( context: Context, diff --git a/integrations/coinbase/src/main/java/org/dash/wallet/integration/coinbase_integration/ui/convert_currency/TransferView.kt b/integrations/coinbase/src/main/java/org/dash/wallet/integrations/coinbase/ui/convert_currency/TransferView.kt similarity index 94% rename from integrations/coinbase/src/main/java/org/dash/wallet/integration/coinbase_integration/ui/convert_currency/TransferView.kt rename to integrations/coinbase/src/main/java/org/dash/wallet/integrations/coinbase/ui/convert_currency/TransferView.kt index 12247551f6..8ce4706f3f 100644 --- a/integrations/coinbase/src/main/java/org/dash/wallet/integration/coinbase_integration/ui/convert_currency/TransferView.kt +++ b/integrations/coinbase/src/main/java/org/dash/wallet/integrations/coinbase/ui/convert_currency/TransferView.kt @@ -15,7 +15,7 @@ * along with this program. If not, see . */ -package org.dash.wallet.integration.coinbase_integration.ui.convert_currency +package org.dash.wallet.integrations.coinbase.ui.convert_currency import android.annotation.SuppressLint import android.content.Context @@ -33,10 +33,10 @@ import org.bitcoinj.utils.MonetaryFormat import org.dash.wallet.common.util.Constants import org.dash.wallet.common.util.GenericUtils import org.dash.wallet.common.util.toFormattedString -import org.dash.wallet.integration.coinbase_integration.CoinbaseConstants -import org.dash.wallet.integration.coinbase_integration.R -import org.dash.wallet.integration.coinbase_integration.databinding.ConvertViewBinding -import org.dash.wallet.integration.coinbase_integration.ui.convert_currency.model.BaseServiceWallet +import org.dash.wallet.integrations.coinbase.CoinbaseConstants +import org.dash.wallet.integrations.coinbase.R +import org.dash.wallet.integrations.coinbase.databinding.ConvertViewBinding +import org.dash.wallet.integrations.coinbase.ui.convert_currency.model.BaseServiceWallet class TransferView(context: Context, attrs: AttributeSet) : ConstraintLayout(context, attrs) { diff --git a/integrations/coinbase/src/main/java/org/dash/wallet/integrations/coinbase/ui/convert_currency/model/BaseIdForFiatData.kt b/integrations/coinbase/src/main/java/org/dash/wallet/integrations/coinbase/ui/convert_currency/model/BaseIdForFiatData.kt new file mode 100644 index 0000000000..1824457dfd --- /dev/null +++ b/integrations/coinbase/src/main/java/org/dash/wallet/integrations/coinbase/ui/convert_currency/model/BaseIdForFiatData.kt @@ -0,0 +1,25 @@ +/* + * Copyright 2021 Dash Core Group. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package org.dash.wallet.integrations.coinbase.ui.convert_currency.model + +import org.dash.wallet.integrations.coinbase.model.BaseIdForUSDData + +sealed class BaseIdForFiatData { + data class Success(val baseIdForFaitDataList: List): BaseIdForFiatData() + object LoadingState : BaseIdForFiatData() + object Error: BaseIdForFiatData() +} diff --git a/integrations/coinbase/src/main/java/org/dash/wallet/integration/coinbase_integration/ui/convert_currency/model/PaymentMethodsUiState.kt b/integrations/coinbase/src/main/java/org/dash/wallet/integrations/coinbase/ui/convert_currency/model/PaymentMethodsUiState.kt similarity index 92% rename from integrations/coinbase/src/main/java/org/dash/wallet/integration/coinbase_integration/ui/convert_currency/model/PaymentMethodsUiState.kt rename to integrations/coinbase/src/main/java/org/dash/wallet/integrations/coinbase/ui/convert_currency/model/PaymentMethodsUiState.kt index f8f8d179b2..2358032ffd 100644 --- a/integrations/coinbase/src/main/java/org/dash/wallet/integration/coinbase_integration/ui/convert_currency/model/PaymentMethodsUiState.kt +++ b/integrations/coinbase/src/main/java/org/dash/wallet/integrations/coinbase/ui/convert_currency/model/PaymentMethodsUiState.kt @@ -14,7 +14,7 @@ * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ -package org.dash.wallet.integration.coinbase_integration.ui.convert_currency.model +package org.dash.wallet.integrations.coinbase.ui.convert_currency.model import org.dash.wallet.common.ui.payment_method_picker.PaymentMethod @@ -22,4 +22,4 @@ sealed class PaymentMethodsUiState { data class Success(val paymentMethodsList: List): PaymentMethodsUiState() data class Error(val isError:Boolean): PaymentMethodsUiState() data class LoadingState(val isLoading:Boolean): PaymentMethodsUiState() -} \ No newline at end of file +} diff --git a/integrations/coinbase/src/main/java/org/dash/wallet/integration/coinbase_integration/ui/convert_currency/model/ServiceWallet.kt b/integrations/coinbase/src/main/java/org/dash/wallet/integrations/coinbase/ui/convert_currency/model/ServiceWallet.kt similarity index 87% rename from integrations/coinbase/src/main/java/org/dash/wallet/integration/coinbase_integration/ui/convert_currency/model/ServiceWallet.kt rename to integrations/coinbase/src/main/java/org/dash/wallet/integrations/coinbase/ui/convert_currency/model/ServiceWallet.kt index d95fc8bce8..0ab76ca3ff 100644 --- a/integrations/coinbase/src/main/java/org/dash/wallet/integration/coinbase_integration/ui/convert_currency/model/ServiceWallet.kt +++ b/integrations/coinbase/src/main/java/org/dash/wallet/integrations/coinbase/ui/convert_currency/model/ServiceWallet.kt @@ -14,9 +14,9 @@ * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ -package org.dash.wallet.integration.coinbase_integration.ui.convert_currency.model +package org.dash.wallet.integrations.coinbase.ui.convert_currency.model -import org.dash.wallet.integration.coinbase_integration.CoinbaseConstants +import org.dash.wallet.integrations.coinbase.CoinbaseConstants data class ServiceWallet( val cryptoWalletName: String, diff --git a/integrations/coinbase/src/main/java/org/dash/wallet/integration/coinbase_integration/ui/convert_currency/model/SwapRequest.kt b/integrations/coinbase/src/main/java/org/dash/wallet/integrations/coinbase/ui/convert_currency/model/SwapRequest.kt similarity index 91% rename from integrations/coinbase/src/main/java/org/dash/wallet/integration/coinbase_integration/ui/convert_currency/model/SwapRequest.kt rename to integrations/coinbase/src/main/java/org/dash/wallet/integrations/coinbase/ui/convert_currency/model/SwapRequest.kt index 1513ca86ff..62d3631453 100644 --- a/integrations/coinbase/src/main/java/org/dash/wallet/integration/coinbase_integration/ui/convert_currency/model/SwapRequest.kt +++ b/integrations/coinbase/src/main/java/org/dash/wallet/integrations/coinbase/ui/convert_currency/model/SwapRequest.kt @@ -15,7 +15,7 @@ * along with this program. If not, see . */ -package org.dash.wallet.integration.coinbase_integration.ui.convert_currency.model +package org.dash.wallet.integrations.coinbase.ui.convert_currency.model import org.bitcoinj.core.Coin import org.bitcoinj.utils.Fiat diff --git a/integrations/coinbase/src/main/java/org/dash/wallet/integration/coinbase_integration/ui/convert_currency/model/SwapValueErrorType.kt b/integrations/coinbase/src/main/java/org/dash/wallet/integrations/coinbase/ui/convert_currency/model/SwapValueErrorType.kt similarity index 71% rename from integrations/coinbase/src/main/java/org/dash/wallet/integration/coinbase_integration/ui/convert_currency/model/SwapValueErrorType.kt rename to integrations/coinbase/src/main/java/org/dash/wallet/integrations/coinbase/ui/convert_currency/model/SwapValueErrorType.kt index 0d76aeab53..4b6dc48cd5 100644 --- a/integrations/coinbase/src/main/java/org/dash/wallet/integration/coinbase_integration/ui/convert_currency/model/SwapValueErrorType.kt +++ b/integrations/coinbase/src/main/java/org/dash/wallet/integrations/coinbase/ui/convert_currency/model/SwapValueErrorType.kt @@ -1,4 +1,4 @@ -package org.dash.wallet.integration.coinbase_integration.ui.convert_currency.model +package org.dash.wallet.integrations.coinbase.ui.convert_currency.model enum class SwapValueErrorType(var amount: String? = null) { LessThanMin, diff --git a/integrations/coinbase/src/main/java/org/dash/wallet/integration/coinbase_integration/ui/dialogs/CoinBaseResultDialog.kt b/integrations/coinbase/src/main/java/org/dash/wallet/integrations/coinbase/ui/dialogs/CoinBaseResultDialog.kt similarity index 92% rename from integrations/coinbase/src/main/java/org/dash/wallet/integration/coinbase_integration/ui/dialogs/CoinBaseResultDialog.kt rename to integrations/coinbase/src/main/java/org/dash/wallet/integrations/coinbase/ui/dialogs/CoinBaseResultDialog.kt index fec1b1c05f..301339e4e9 100644 --- a/integrations/coinbase/src/main/java/org/dash/wallet/integration/coinbase_integration/ui/dialogs/CoinBaseResultDialog.kt +++ b/integrations/coinbase/src/main/java/org/dash/wallet/integrations/coinbase/ui/dialogs/CoinBaseResultDialog.kt @@ -14,7 +14,7 @@ * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ -package org.dash.wallet.integration.coinbase_integration.ui.dialogs +package org.dash.wallet.integrations.coinbase.ui.dialogs import android.content.ActivityNotFoundException import android.content.Intent @@ -32,8 +32,8 @@ import androidx.core.view.isVisible import androidx.fragment.app.DialogFragment import androidx.navigation.fragment.findNavController import org.dash.wallet.common.ui.viewBinding -import org.dash.wallet.integration.coinbase_integration.R -import org.dash.wallet.integration.coinbase_integration.databinding.DialogCoinbaseResultBinding +import org.dash.wallet.integrations.coinbase.R +import org.dash.wallet.integrations.coinbase.databinding.DialogCoinbaseResultBinding class CoinBaseResultDialog : DialogFragment() { private val binding by viewBinding(DialogCoinbaseResultBinding::bind) @@ -80,14 +80,16 @@ class CoinBaseResultDialog : DialogFragment() { binding.coinbaseBuyDialogPositiveButton.setOnClickListener { onCoinBaseResultDialogButtonsClickListener?.onPositiveButtonClick( - Type.values().first { it.ordinal == type }) + Type.values().first { it.ordinal == type } + ) } } binding.coinbaseBuyDialogNegativeButton.setOnClickListener { type?.let { onCoinBaseResultDialogButtonsClickListener?.onNegativeButtonClick( - Type.values().first { it.ordinal == type }) + Type.values().first { it.ordinal == type } + ) } dismiss() findNavController().popBackStack() @@ -105,7 +107,7 @@ class CoinBaseResultDialog : DialogFragment() { binding.coinbaseBuyDialogTitle.setTextAppearance(R.style.Headline5_Red) val errorMessage = arguments?.getString(ARG_MESSAGE) if (errorMessage.isNullOrEmpty()) { - binding.coinbaseBuyDialogMessage.setText(R.string.purchase_failed_msg) + binding.coinbaseBuyDialogMessage.setText(R.string.something_wrong_title) } else { binding.coinbaseBuyDialogMessage.text = errorMessage } @@ -148,7 +150,7 @@ class CoinBaseResultDialog : DialogFragment() { private fun setConversionError() { binding.coinbaseBuyDialogIcon.setImageResource(R.drawable.ic_error) binding.coinbaseBuyDialogTitle.setText(R.string.conversion_failed) - binding.coinbaseBuyDialogMessage.setText(R.string.purchase_failed_msg) + binding.coinbaseBuyDialogMessage.setText(R.string.something_wrong_title) binding.coinbaseBuyDialogTitle.setTextAppearance(R.style.Headline5_Red) binding.buyDialogContactCoinbaseSupport.isVisible = true binding.coinbaseBuyDialogNegativeButton.isVisible = true @@ -159,7 +161,7 @@ class CoinBaseResultDialog : DialogFragment() { private fun setSwapError() { binding.coinbaseBuyDialogIcon.setImageResource(R.drawable.ic_error) binding.coinbaseBuyDialogTitle.setText(R.string.conversion_failed) - binding.coinbaseBuyDialogMessage.setText(R.string.purchase_failed_msg) + binding.coinbaseBuyDialogMessage.setText(R.string.something_wrong_title) binding.coinbaseBuyDialogTitle.setTextAppearance(R.style.Headline5_Red) binding.buyDialogContactCoinbaseSupport.isVisible = true binding.coinbaseBuyDialogNegativeButton.isVisible = true @@ -172,11 +174,14 @@ class CoinBaseResultDialog : DialogFragment() { binding.coinbaseBuyDialogTitle.setText(R.string.conversion_successful) binding.coinbaseBuyDialogTitle.setTextAppearance(R.style.Headline5_Green) binding.coinbaseBuyDialogMessage.setText( - if (dashToCoinbase) + if (dashToCoinbase) { getString( R.string.it_could_take_up_to_5_minutes_to_coinbase, coinbaseWallet ?: "" - ) else getString(R.string.it_could_take_up_to_5_minutes, coinbaseWallet ?: "") + ) + } else { + getString(R.string.it_could_take_up_to_5_minutes, coinbaseWallet ?: "") + } ) binding.buyDialogContactCoinbaseSupport.isGone = true binding.coinbaseBuyDialogNegativeButton.isGone = true @@ -188,8 +193,11 @@ class CoinBaseResultDialog : DialogFragment() { binding.coinbaseBuyDialogTitle.setText(R.string.transfer_dash_successful) binding.coinbaseBuyDialogTitle.setTextAppearance(R.style.Headline5_Green) binding.coinbaseBuyDialogMessage.setText( - if (dashToCoinbase) - R.string.it_could_take_up_to_10_minutes_to_coinbase else R.string.it_could_take_up_to_10_minutes + if (dashToCoinbase) { + R.string.it_could_take_up_to_10_minutes_to_coinbase + } else { + R.string.it_could_take_up_to_10_minutes + } ) binding.buyDialogContactCoinbaseSupport.isGone = true binding.coinbaseBuyDialogNegativeButton.isGone = true diff --git a/integrations/coinbase/src/main/java/org/dash/wallet/integration/coinbase_integration/ui/dialogs/CoinbaseFeeInfoDialog.kt b/integrations/coinbase/src/main/java/org/dash/wallet/integrations/coinbase/ui/dialogs/CoinbaseFeeInfoDialog.kt similarity index 91% rename from integrations/coinbase/src/main/java/org/dash/wallet/integration/coinbase_integration/ui/dialogs/CoinbaseFeeInfoDialog.kt rename to integrations/coinbase/src/main/java/org/dash/wallet/integrations/coinbase/ui/dialogs/CoinbaseFeeInfoDialog.kt index d56894ce6c..a2056be90e 100644 --- a/integrations/coinbase/src/main/java/org/dash/wallet/integration/coinbase_integration/ui/dialogs/CoinbaseFeeInfoDialog.kt +++ b/integrations/coinbase/src/main/java/org/dash/wallet/integrations/coinbase/ui/dialogs/CoinbaseFeeInfoDialog.kt @@ -15,7 +15,7 @@ * along with this program. If not, see . */ -package org.dash.wallet.integration.coinbase_integration.ui.dialogs +package org.dash.wallet.integrations.coinbase.ui.dialogs import android.content.Intent import android.graphics.Color @@ -34,8 +34,8 @@ import androidx.navigation.fragment.findNavController import org.dash.wallet.common.UserInteractionAwareCallback import org.dash.wallet.common.customtabs.CustomTabActivityHelper import org.dash.wallet.common.ui.viewBinding -import org.dash.wallet.integration.coinbase_integration.R -import org.dash.wallet.integration.coinbase_integration.databinding.DialogCoinbaseFeeInfoBinding +import org.dash.wallet.integrations.coinbase.R +import org.dash.wallet.integrations.coinbase.databinding.DialogCoinbaseFeeInfoBinding class CoinbaseFeeInfoDialog: DialogFragment() { private val binding by viewBinding(DialogCoinbaseFeeInfoBinding::bind) @@ -73,7 +73,10 @@ class CoinbaseFeeInfoDialog: DialogFragment() { .build() val uri = Uri.parse(feeInfoHelpLink) - CustomTabActivityHelper.openCustomTab(requireActivity(), customTabsIntent, uri + CustomTabActivityHelper.openCustomTab( + requireActivity(), + customTabsIntent, + uri ) { _, _ -> val intent = Intent(Intent.ACTION_VIEW) intent.data = uri @@ -90,4 +93,4 @@ class CoinbaseFeeInfoDialog: DialogFragment() { } } } -} \ No newline at end of file +} diff --git a/integrations/coinbase/src/main/java/org/dash/wallet/integration/coinbase_integration/ui/dialogs/crypto_wallets/CryptoWalletsDialog.kt b/integrations/coinbase/src/main/java/org/dash/wallet/integrations/coinbase/ui/dialogs/crypto_wallets/CryptoWalletsDialog.kt similarity index 91% rename from integrations/coinbase/src/main/java/org/dash/wallet/integration/coinbase_integration/ui/dialogs/crypto_wallets/CryptoWalletsDialog.kt rename to integrations/coinbase/src/main/java/org/dash/wallet/integrations/coinbase/ui/dialogs/crypto_wallets/CryptoWalletsDialog.kt index e806e8cd52..7d97af1526 100644 --- a/integrations/coinbase/src/main/java/org/dash/wallet/integration/coinbase_integration/ui/dialogs/crypto_wallets/CryptoWalletsDialog.kt +++ b/integrations/coinbase/src/main/java/org/dash/wallet/integrations/coinbase/ui/dialogs/crypto_wallets/CryptoWalletsDialog.kt @@ -14,7 +14,7 @@ * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ -package org.dash.wallet.integration.coinbase_integration.ui.dialogs.crypto_wallets +package org.dash.wallet.integrations.coinbase.ui.dialogs.crypto_wallets import android.content.Context import android.os.Bundle @@ -43,8 +43,8 @@ import org.dash.wallet.common.ui.radio_group.IconifiedViewItem import org.dash.wallet.common.ui.radio_group.RadioGroupAdapter import org.dash.wallet.common.ui.viewBinding import org.dash.wallet.common.util.GenericUtils -import org.dash.wallet.integration.coinbase_integration.model.CoinBaseUserAccountDataUIModel -import org.dash.wallet.integration.coinbase_integration.model.getCoinBaseExchangeRateConversion +import org.dash.wallet.integrations.coinbase.model.CoinBaseUserAccountDataUIModel +import org.dash.wallet.integrations.coinbase.model.getCoinBaseExchangeRateConversion @AndroidEntryPoint class CryptoWalletsDialog( @@ -120,26 +120,28 @@ class CryptoWalletsDialog( private fun refreshItems(rate: ExchangeRate?, dataList: List) { itemList = dataList.map { - val accountData = it.coinBaseUserAccountData - val icon = getFlagFromCurrencyCode(accountData.currency?.code ?: "") - val iconUrl = if (icon == null && !accountData.currency?.code.isNullOrEmpty()) { + val accountData = it.coinbaseAccount + val icon = getFlagFromCurrencyCode(accountData.currency) + val iconUrl = if (icon == null && accountData.currency.isNotEmpty()) { "https://raw.githubusercontent.com/jsupa/crypto-icons/main/icons/" + - "${accountData.currency?.code?.lowercase()}.png" + "${accountData.currency.lowercase()}.png" } else { null } val cryptoCurrencyBalance = - if (accountData.balance?.amount.isNullOrEmpty() || accountData.balance?.amount?.toDouble() == 0.0) { + if (accountData.availableBalance.value.isEmpty() || + accountData.availableBalance.value.toDouble() == 0.0 + ) { MonetaryFormat().withLocale(GenericUtils.getDeviceLocale()) .noCode().minDecimals(2).optionalDecimals().format(Coin.ZERO).toString() } else { - accountData.balance?.amount + accountData.availableBalance.value } IconifiedViewItem( - accountData.currency?.code ?: "", - accountData.currency?.name ?: "", + accountData.currency, + accountData.name, icon, iconUrl, IconSelectMode.None, diff --git a/integrations/coinbase/src/main/java/org/dash/wallet/integration/coinbase_integration/ui/dialogs/crypto_wallets/CryptoWalletsDialogViewModel.kt b/integrations/coinbase/src/main/java/org/dash/wallet/integrations/coinbase/ui/dialogs/crypto_wallets/CryptoWalletsDialogViewModel.kt similarity index 92% rename from integrations/coinbase/src/main/java/org/dash/wallet/integration/coinbase_integration/ui/dialogs/crypto_wallets/CryptoWalletsDialogViewModel.kt rename to integrations/coinbase/src/main/java/org/dash/wallet/integrations/coinbase/ui/dialogs/crypto_wallets/CryptoWalletsDialogViewModel.kt index 9bf3eeef42..bdd274d26a 100644 --- a/integrations/coinbase/src/main/java/org/dash/wallet/integration/coinbase_integration/ui/dialogs/crypto_wallets/CryptoWalletsDialogViewModel.kt +++ b/integrations/coinbase/src/main/java/org/dash/wallet/integrations/coinbase/ui/dialogs/crypto_wallets/CryptoWalletsDialogViewModel.kt @@ -14,7 +14,7 @@ * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ -package org.dash.wallet.integration.coinbase_integration.ui.dialogs.crypto_wallets +package org.dash.wallet.integrations.coinbase.ui.dialogs.crypto_wallets import androidx.lifecycle.* import dagger.hilt.android.lifecycle.HiltViewModel @@ -27,7 +27,7 @@ import org.dash.wallet.common.Configuration import org.dash.wallet.common.data.WalletUIConfig import org.dash.wallet.common.data.entity.ExchangeRate import org.dash.wallet.common.services.ExchangeRatesProvider -import org.dash.wallet.integration.coinbase_integration.model.CoinBaseUserAccountDataUIModel +import org.dash.wallet.integrations.coinbase.model.CoinBaseUserAccountDataUIModel import javax.inject.Inject @OptIn(ExperimentalCoroutinesApi::class) diff --git a/integrations/coinbase/src/main/java/org/dash/wallet/integration/coinbase_integration/utils/CoinbaseConfig.kt b/integrations/coinbase/src/main/java/org/dash/wallet/integrations/coinbase/utils/CoinbaseConfig.kt similarity index 94% rename from integrations/coinbase/src/main/java/org/dash/wallet/integration/coinbase_integration/utils/CoinbaseConfig.kt rename to integrations/coinbase/src/main/java/org/dash/wallet/integrations/coinbase/utils/CoinbaseConfig.kt index 4225a0410f..c55293cb17 100644 --- a/integrations/coinbase/src/main/java/org/dash/wallet/integration/coinbase_integration/utils/CoinbaseConfig.kt +++ b/integrations/coinbase/src/main/java/org/dash/wallet/integrations/coinbase/utils/CoinbaseConfig.kt @@ -15,7 +15,7 @@ * along with this program. If not, see . */ -package org.dash.wallet.integration.coinbase_integration.utils +package org.dash.wallet.integrations.coinbase.utils import android.content.Context import androidx.datastore.preferences.SharedPreferencesMigration @@ -52,7 +52,6 @@ class CoinbaseConfig @Inject constructor( const val PREFERENCES_NAME = "coinbase" val LAST_BALANCE = longPreferencesKey("last_balance") val UPDATE_BASE_IDS = booleanPreferencesKey("should_update_base_ids") - val LOGOUT_COINBASE = booleanPreferencesKey("logout_coinbase") val LAST_ACCESS_TOKEN = stringPreferencesKey("last_coinbase_access_token") val LAST_REFRESH_TOKEN = stringPreferencesKey("last_coinbase_refresh_token") val USER_ACCOUNT_ID = stringPreferencesKey("coinbase_account_id") diff --git a/integrations/coinbase/src/main/java/org/dash/wallet/integrations/coinbase/viewmodels/CoinbaseBuyDashViewModel.kt b/integrations/coinbase/src/main/java/org/dash/wallet/integrations/coinbase/viewmodels/CoinbaseBuyDashViewModel.kt new file mode 100644 index 0000000000..4a3459fa36 --- /dev/null +++ b/integrations/coinbase/src/main/java/org/dash/wallet/integrations/coinbase/viewmodels/CoinbaseBuyDashViewModel.kt @@ -0,0 +1,197 @@ +/* + * Copyright 2021 Dash Core Group. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package org.dash.wallet.integrations.coinbase.viewmodels + +import androidx.lifecycle.ViewModel +import dagger.hilt.android.lifecycle.HiltViewModel +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.asStateFlow +import kotlinx.coroutines.flow.update +import org.bitcoinj.core.Coin +import org.bitcoinj.utils.ExchangeRate +import org.bitcoinj.utils.Fiat +import org.dash.wallet.common.WalletDataProvider +import org.dash.wallet.common.services.ExchangeRatesProvider +import org.dash.wallet.common.services.analytics.AnalyticsConstants +import org.dash.wallet.common.services.analytics.AnalyticsService +import org.dash.wallet.common.ui.payment_method_picker.PaymentMethod +import org.dash.wallet.common.ui.payment_method_picker.PaymentMethodType +import org.dash.wallet.common.util.Constants +import org.dash.wallet.common.util.toBigDecimal +import org.dash.wallet.common.util.toCoin +import org.dash.wallet.common.util.toFiat +import org.dash.wallet.integrations.coinbase.CoinbaseConstants +import org.dash.wallet.integrations.coinbase.model.CoinbaseErrorType +import org.dash.wallet.integrations.coinbase.model.MarketMarketIoc +import org.dash.wallet.integrations.coinbase.model.OrderConfiguration +import org.dash.wallet.integrations.coinbase.model.PlaceOrderParams +import org.dash.wallet.integrations.coinbase.model.SendTransactionToWalletParams +import org.dash.wallet.integrations.coinbase.repository.CoinBaseRepositoryInt +import java.math.RoundingMode +import java.util.UUID +import javax.inject.Inject + +data class CoinbaseBuyUIState( + val dashAmount: Coin = Coin.ZERO, + val order: Fiat? = null, + val fee: Fiat? = null, + val paymentMethod: PaymentMethod? = null +) + +@HiltViewModel +class CoinbaseBuyDashViewModel @Inject constructor( + private val coinBaseRepository: CoinBaseRepositoryInt, + var exchangeRates: ExchangeRatesProvider, + private val analyticsService: AnalyticsService, + private val walletDataProvider: WalletDataProvider +) : ViewModel() { + + private val _uiState = MutableStateFlow(CoinbaseBuyUIState()) + val uiState: StateFlow = _uiState.asStateFlow() + + suspend fun validateBuyDash(amount: Coin, retryWithDeposit: Boolean): CoinbaseErrorType { + previewBuyOrder(amount) + + val fiatAmount = uiState.value.order ?: return CoinbaseErrorType.NO_EXCHANGE_RATE + val fiatAccount = coinBaseRepository.getFiatAccount() + val balance = Fiat.parseFiatInexact( + CoinbaseConstants.DEFAULT_CURRENCY_USD, + fiatAccount.availableBalance.value + ) + + val paymentMethod: PaymentMethod + + if (balance >= fiatAmount) { + paymentMethod = PaymentMethod( + fiatAccount.uuid.toString(), + fiatAccount.name, + account = fiatAccount.currency, + accountType = fiatAccount.type, + paymentMethodType = PaymentMethodType.Fiat, + isValid = true + ) + } else if (retryWithDeposit) { + val bankAccount = coinBaseRepository.getActivePaymentMethods().firstOrNull { + paymentMethodTypeFromCoinbaseType(it.type ?: "") == PaymentMethodType.BankAccount + } ?: return CoinbaseErrorType.NO_BANK_ACCOUNT + + paymentMethod = PaymentMethod( + bankAccount.id ?: "", + bankAccount.name ?: "", + account = "", + accountType = bankAccount.type ?: "", + paymentMethodType = PaymentMethodType.BankAccount, + isValid = true + ) + } else { + return CoinbaseErrorType.INSUFFICIENT_BALANCE + } + + _uiState.update { it.copy(paymentMethod = paymentMethod) } + return CoinbaseErrorType.NONE + } + + suspend fun buyDash(dashToFiat: Boolean) { + val amount = uiState.value.order ?: return + + analyticsService.logEvent(AnalyticsConstants.Coinbase.BUY_CONTINUE, mapOf()) + analyticsService.logEvent( + if (dashToFiat) { + AnalyticsConstants.Coinbase.BUY_ENTER_DASH + } else { + AnalyticsConstants.Coinbase.BUY_ENTER_FIAT + }, + mapOf() + ) + + val format = Constants.SEND_PAYMENT_LOCAL_FORMAT.noCode().roundingMode(RoundingMode.UP) + val amountStr = format.format(amount).toString() + + if (uiState.value.paymentMethod?.paymentMethodType == PaymentMethodType.BankAccount) { + coinBaseRepository.depositToFiatAccount( + uiState.value.paymentMethod!!.paymentMethodId, + amountStr + ) + } + + val params = PlaceOrderParams( + UUID.randomUUID(), + productId = CoinbaseConstants.DASH_USD_PAIR, + side = CoinbaseConstants.TRANSACTION_TYPE_BUY, + OrderConfiguration( + MarketMarketIoc(amountStr) + ) + ) + + coinBaseRepository.placeBuyOrder(params) + } + + fun getTransferDashParams(): SendTransactionToWalletParams { + return SendTransactionToWalletParams( + amount = uiState.value.dashAmount.toPlainString(), + currency = Constants.DASH_CURRENCY, + idem = UUID.randomUUID().toString(), + to = walletDataProvider.freshReceiveAddress().toBase58(), + type = CoinbaseConstants.TRANSACTION_TYPE_SEND + ) + } + + fun logEvent(eventName: String) { + analyticsService.logEvent(eventName, mapOf()) + } + + private suspend fun previewBuyOrder(dashAmount: Coin) { + _uiState.update { it.copy(dashAmount = dashAmount) } + + val coinbaseFee = dashAmount.toBigDecimal().multiply(CoinbaseConstants.BUY_FEE.toBigDecimal()).toCoin() + val rates = coinBaseRepository.getExchangeRates(CoinbaseConstants.DEFAULT_CURRENCY_USD) + var order: Fiat? = null + var feeInFiat: Fiat? = null + + rates[Constants.DASH_CURRENCY]?.let { rate -> + val dashRate = 1.toBigDecimal().divide(rate.toBigDecimal(), 8, RoundingMode.HALF_UP) + val exchangeRate = dashRate?.let { + ExchangeRate(Coin.COIN, it.toFiat(CoinbaseConstants.DEFAULT_CURRENCY_USD)) + } + order = exchangeRate?.coinToFiat(dashAmount) + feeInFiat = exchangeRate?.coinToFiat(coinbaseFee) + } + + _uiState.update { it.copy(dashAmount = dashAmount, order = order, fee = feeInFiat) } + } + + private fun paymentMethodTypeFromCoinbaseType(type: String?): PaymentMethodType { + return when (type) { + "fiat_account" -> PaymentMethodType.Fiat + "secure3d_card", "worldpay_card", "credit_card", "debit_card" -> PaymentMethodType.Card + "ach_bank_account", "sepa_bank_account", + "ideal_bank_account", "eft_bank_account", "interac" -> PaymentMethodType.BankAccount + "bank_wire" -> PaymentMethodType.WireTransfer + "paypal_account" -> PaymentMethodType.PayPal + "apple_pay" -> PaymentMethodType.ApplePay + else -> PaymentMethodType.Unknown + } + } +} + +val String.toDoubleOrZero: Double + get() = try { + this.toDouble() + } catch (e: NumberFormatException) { + 0.0 + } diff --git a/integrations/coinbase/src/main/java/org/dash/wallet/integration/coinbase_integration/viewmodels/CoinbaseConversionPreviewViewModel.kt b/integrations/coinbase/src/main/java/org/dash/wallet/integrations/coinbase/viewmodels/CoinbaseConversionPreviewViewModel.kt similarity index 94% rename from integrations/coinbase/src/main/java/org/dash/wallet/integration/coinbase_integration/viewmodels/CoinbaseConversionPreviewViewModel.kt rename to integrations/coinbase/src/main/java/org/dash/wallet/integrations/coinbase/viewmodels/CoinbaseConversionPreviewViewModel.kt index d8de9abccd..aef2fd1954 100644 --- a/integrations/coinbase/src/main/java/org/dash/wallet/integration/coinbase_integration/viewmodels/CoinbaseConversionPreviewViewModel.kt +++ b/integrations/coinbase/src/main/java/org/dash/wallet/integrations/coinbase/viewmodels/CoinbaseConversionPreviewViewModel.kt @@ -14,12 +14,10 @@ * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ -package org.dash.wallet.integration.coinbase_integration.viewmodels +package org.dash.wallet.integrations.coinbase.viewmodels import androidx.lifecycle.* import dagger.hilt.android.lifecycle.HiltViewModel -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.launch import org.bitcoinj.core.Address import org.bitcoinj.core.Coin @@ -32,15 +30,14 @@ import org.dash.wallet.common.services.TransactionMetadataProvider import org.dash.wallet.common.services.analytics.AnalyticsConstants import org.dash.wallet.common.services.analytics.AnalyticsService import org.dash.wallet.common.util.Constants -import org.dash.wallet.integration.coinbase_integration.CoinbaseConstants -import org.dash.wallet.integration.coinbase_integration.model.* +import org.dash.wallet.integrations.coinbase.CoinbaseConstants +import org.dash.wallet.integrations.coinbase.model.* import org.dash.wallet.common.data.ResponseResource import org.dash.wallet.common.services.NetworkStateInt -import org.dash.wallet.integration.coinbase_integration.repository.CoinBaseRepositoryInt +import org.dash.wallet.integrations.coinbase.repository.CoinBaseRepositoryInt import java.util.* import javax.inject.Inject -@ExperimentalCoroutinesApi @HiltViewModel class CoinbaseConversionPreviewViewModel @Inject constructor( private val coinBaseRepository: CoinBaseRepositoryInt, @@ -74,7 +71,7 @@ class CoinbaseConversionPreviewViewModel @Inject constructor( var isFirstTime = true - fun commitSwapTrade(tradeId: String, inputCurrency: String, inputAmount: String) = viewModelScope.launch(Dispatchers.Main) { + fun commitSwapTrade(tradeId: String, inputCurrency: String, inputAmount: String) = viewModelScope.launch { analyticsService.logEvent(AnalyticsConstants.Coinbase.CONVERT_QUOTE_CONFIRM, mapOf()) _showLoading.value = true @@ -122,7 +119,7 @@ class CoinbaseConversionPreviewViewModel @Inject constructor( } } - private fun swapTrade(swapTradeUIModel: SwapTradeUIModel) = viewModelScope.launch(Dispatchers.Main) { + private fun swapTrade(swapTradeUIModel: SwapTradeUIModel) = viewModelScope.launch { _showLoading.value = true swapTradeUIModel.assetsBaseID?.let { val tradesRequest = TradesRequest( diff --git a/integrations/coinbase/src/main/java/org/dash/wallet/integration/coinbase_integration/viewmodels/CoinbaseConvertCryptoViewModel.kt b/integrations/coinbase/src/main/java/org/dash/wallet/integrations/coinbase/viewmodels/CoinbaseConvertCryptoViewModel.kt similarity index 67% rename from integrations/coinbase/src/main/java/org/dash/wallet/integration/coinbase_integration/viewmodels/CoinbaseConvertCryptoViewModel.kt rename to integrations/coinbase/src/main/java/org/dash/wallet/integrations/coinbase/viewmodels/CoinbaseConvertCryptoViewModel.kt index eb599da9dc..92db1b5b08 100644 --- a/integrations/coinbase/src/main/java/org/dash/wallet/integration/coinbase_integration/viewmodels/CoinbaseConvertCryptoViewModel.kt +++ b/integrations/coinbase/src/main/java/org/dash/wallet/integrations/coinbase/viewmodels/CoinbaseConvertCryptoViewModel.kt @@ -14,12 +14,14 @@ * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ -package org.dash.wallet.integration.coinbase_integration.viewmodels +package org.dash.wallet.integrations.coinbase.viewmodels -import androidx.lifecycle.* +import androidx.lifecycle.LiveData +import androidx.lifecycle.MutableLiveData +import androidx.lifecycle.ViewModel +import androidx.lifecycle.asLiveData +import androidx.lifecycle.viewModelScope import dagger.hilt.android.lifecycle.HiltViewModel -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.flow.first import kotlinx.coroutines.launch import org.bitcoinj.core.Coin import org.bitcoinj.utils.Fiat @@ -27,16 +29,20 @@ import org.dash.wallet.common.WalletDataProvider import org.dash.wallet.common.data.ResponseResource import org.dash.wallet.common.data.SingleLiveEvent import org.dash.wallet.common.data.WalletUIConfig -import org.dash.wallet.common.data.entity.ExchangeRate import org.dash.wallet.common.services.ExchangeRatesProvider import org.dash.wallet.common.services.NetworkStateInt import org.dash.wallet.common.services.analytics.AnalyticsConstants import org.dash.wallet.common.services.analytics.AnalyticsService import org.dash.wallet.common.util.Constants import org.dash.wallet.common.util.toFormattedStringNoCode -import org.dash.wallet.integration.coinbase_integration.model.* -import org.dash.wallet.integration.coinbase_integration.repository.CoinBaseRepositoryInt -import org.dash.wallet.integration.coinbase_integration.utils.CoinbaseConfig +import org.dash.wallet.integrations.coinbase.model.BaseIdForUSDData +import org.dash.wallet.integrations.coinbase.model.CoinBaseUserAccountDataUIModel +import org.dash.wallet.integrations.coinbase.model.CoinbaseErrorResponse +import org.dash.wallet.integrations.coinbase.model.SwapTradeResponse +import org.dash.wallet.integrations.coinbase.model.SwapTradeUIModel +import org.dash.wallet.integrations.coinbase.model.TradesRequest +import org.dash.wallet.integrations.coinbase.repository.CoinBaseRepositoryInt +import org.dash.wallet.integrations.coinbase.utils.CoinbaseConfig import javax.inject.Inject @HiltViewModel @@ -65,11 +71,7 @@ class CoinbaseConvertCryptoViewModel @Inject constructor( val isDeviceConnectedToInternet: LiveData = networkState.isConnected.asLiveData() - var exchangeRate: ExchangeRate? = null - private set - init { - getWithdrawalLimit() setDashWalletBalance() } @@ -89,12 +91,12 @@ class CoinbaseConvertCryptoViewModel @Inject constructor( _baseIdForFaitModelCoinBase.value?.firstOrNull { it.base == Constants.DASH_CURRENCY }?.base_id ?: "" } else { _baseIdForFaitModelCoinBase.value?.firstOrNull { - it.base == selectedCoinBaseAccount.coinBaseUserAccountData.currency?.code + it.base == selectedCoinBaseAccount.coinbaseAccount.currency }?.base_id ?: "" } val targetAsset = if (dashToCrypt) { _baseIdForFaitModelCoinBase.value?.firstOrNull { - it.base == selectedCoinBaseAccount.coinBaseUserAccountData.currency?.code + it.base == selectedCoinBaseAccount.coinbaseAccount.currency }?.base_id ?: "" } else { _baseIdForFaitModelCoinBase.value?.firstOrNull { it.base == Constants.DASH_CURRENCY }?.base_id ?: "" @@ -120,10 +122,10 @@ class CoinbaseConvertCryptoViewModel @Inject constructor( this.inputCurrencyName = if (dashToCrypt) { "Dash" } else { - selectedCoinBaseAccount.coinBaseUserAccountData.currency?.name ?: "" + selectedCoinBaseAccount.coinbaseAccount.currency } this.outputCurrencyName = if (dashToCrypt) { - selectedCoinBaseAccount.coinBaseUserAccountData.currency?.name ?: "" + selectedCoinBaseAccount.coinbaseAccount.currency } else { "Dash" } @@ -149,23 +151,20 @@ class CoinbaseConvertCryptoViewModel @Inject constructor( } } - suspend fun getUserWalletAccounts(dashToCrypt: Boolean): List { + suspend fun getUserWalletAccounts(dashToCrypto: Boolean): List { analyticsService.logEvent(AnalyticsConstants.Coinbase.CONVERT_SELECT_COIN, mapOf()) - return when ( - val response = coinBaseRepository.getUserAccounts( - walletUIConfig.getExchangeCurrencyCode() - ) - ) { - is ResponseResource.Success -> response.value - else -> listOf() + return try { + coinBaseRepository.getUserAccounts(walletUIConfig.getExchangeCurrencyCode()) + } catch (ex: Exception) { + listOf() }.filter { - if (dashToCrypt) { + if (dashToCrypto) { isValidCoinBaseAccount(it) } else { - isValidCoinBaseAccount(it) && it.coinBaseUserAccountData.balance?.amount?.toDouble() != 0.0 + isValidCoinBaseAccount(it) && it.coinbaseAccount.availableBalance.value.toDouble() != 0.0 } - }.sortedBy { item -> item.coinBaseUserAccountData.currency?.code } + }.sortedBy { item -> item.coinbaseAccount.currency } } fun logEvent(eventName: String) { @@ -176,40 +175,18 @@ class CoinbaseConvertCryptoViewModel @Inject constructor( return Coin.valueOf(config.get(CoinbaseConfig.LAST_BALANCE) ?: 0) } - private fun isValidCoinBaseAccount( - it: CoinBaseUserAccountDataUIModel - ) = ( - it.coinBaseUserAccountData.balance?.amount?.toDouble() != null && - !it.coinBaseUserAccountData.balance.amount.toDouble().isNaN() && - it.coinBaseUserAccountData.type != "fiat" && - it.coinBaseUserAccountData.balance.currency != Constants.DASH_CURRENCY + private fun isValidCoinBaseAccount(it: CoinBaseUserAccountDataUIModel) = ( + !it.coinbaseAccount.availableBalance.value.toDouble().isNaN() && + it.coinbaseAccount.type != "fiat" && + it.coinbaseAccount.currency != Constants.DASH_CURRENCY ) private fun setDashWalletBalance() { _dashWalletBalance.value = walletDataProvider.getWalletBalance() } - private fun getWithdrawalLimit() = viewModelScope.launch(Dispatchers.Main) { - when (val response = coinBaseRepository.getWithdrawalLimit()) { - is ResponseResource.Success -> { - val withdrawalLimit = response.value - exchangeRate = getCurrencyExchangeRate(withdrawalLimit.currency) - } - is ResponseResource.Failure -> { - // todo use case when limit is not fetched - } - } - } - - private suspend fun getCurrencyExchangeRate(currency: String): ExchangeRate? { - return exchangeRates.observeExchangeRate(currency).first() - } - suspend fun isInputGreaterThanLimit(amountInDash: Coin): Boolean { - exchangeRate?.let { - val rate = org.bitcoinj.utils.ExchangeRate(Coin.COIN, it.fiat) - val withdrawalLimitInDash = coinBaseRepository.getWithdrawalLimitInDash(rate) - return amountInDash.toPlainString().toDoubleOrZero.compareTo(withdrawalLimitInDash) > 0 - } ?: return true + val withdrawalLimitInDash = coinBaseRepository.getWithdrawalLimitInDash() + return amountInDash.toPlainString().toDoubleOrZero.compareTo(withdrawalLimitInDash) > 0 } } diff --git a/integrations/coinbase/src/main/java/org/dash/wallet/integration/coinbase_integration/viewmodels/CoinbaseServicesViewModel.kt b/integrations/coinbase/src/main/java/org/dash/wallet/integrations/coinbase/viewmodels/CoinbaseServicesViewModel.kt similarity index 66% rename from integrations/coinbase/src/main/java/org/dash/wallet/integration/coinbase_integration/viewmodels/CoinbaseServicesViewModel.kt rename to integrations/coinbase/src/main/java/org/dash/wallet/integrations/coinbase/viewmodels/CoinbaseServicesViewModel.kt index dbcf80343d..18f23f7dd2 100644 --- a/integrations/coinbase/src/main/java/org/dash/wallet/integration/coinbase_integration/viewmodels/CoinbaseServicesViewModel.kt +++ b/integrations/coinbase/src/main/java/org/dash/wallet/integrations/coinbase/viewmodels/CoinbaseServicesViewModel.kt @@ -14,12 +14,14 @@ * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ -package org.dash.wallet.integration.coinbase_integration.viewmodels +package org.dash.wallet.integrations.coinbase.viewmodels import androidx.lifecycle.* import dagger.hilt.android.lifecycle.HiltViewModel -import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.flow.filterNotNull import kotlinx.coroutines.flow.flatMapLatest import kotlinx.coroutines.flow.launchIn @@ -27,21 +29,27 @@ import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.launch import org.bitcoinj.core.Coin +import org.bitcoinj.utils.Fiat import org.bitcoinj.utils.MonetaryFormat import org.dash.wallet.common.Configuration -import org.dash.wallet.common.data.SingleLiveEvent import org.dash.wallet.common.data.WalletUIConfig -import org.dash.wallet.common.data.unwrap import org.dash.wallet.common.services.ExchangeRatesProvider -import org.dash.wallet.common.services.NetworkStateInt import org.dash.wallet.common.services.analytics.AnalyticsConstants import org.dash.wallet.common.services.analytics.AnalyticsService -import org.dash.wallet.common.ui.BalanceUIState -import org.dash.wallet.integration.coinbase_integration.repository.CoinBaseRepositoryInt -import org.dash.wallet.integration.coinbase_integration.utils.CoinbaseConfig +import org.dash.wallet.integrations.coinbase.model.CoinbaseErrorType +import org.dash.wallet.integrations.coinbase.repository.CoinBaseRepositoryInt +import org.dash.wallet.integrations.coinbase.utils.CoinbaseConfig import org.slf4j.LoggerFactory import javax.inject.Inject +data class CoinbaseServicesUIState( + val balance: Coin = Coin.ZERO, + val balanceFiat: Fiat? = null, + val isBalanceUpdating: Boolean = false, + val isLoggedIn: Boolean = true, + val error: CoinbaseErrorType = CoinbaseErrorType.NONE +) + @OptIn(ExperimentalCoroutinesApi::class) @HiltViewModel class CoinbaseServicesViewModel @Inject constructor( @@ -50,35 +58,23 @@ class CoinbaseServicesViewModel @Inject constructor( private val preferences: Configuration, private val config: CoinbaseConfig, private val walletUIConfig: WalletUIConfig, - networkState: NetworkStateInt, private val analyticsService: AnalyticsService ) : ViewModel() { companion object { private val log = LoggerFactory.getLogger(CoinbaseServicesViewModel::class.java) } - private val _balanceUIState: MutableLiveData = MutableLiveData(BalanceUIState()) - val balanceUIState: LiveData - get() = _balanceUIState - - private val _showLoading: MutableLiveData = MutableLiveData() - val showLoading: LiveData - get() = _showLoading - - private val _userAccountError: MutableLiveData = MutableLiveData() - val userAccountError: LiveData - get() = _userAccountError - - val coinbaseLogOutCallback = SingleLiveEvent() - - val isDeviceConnectedToInternet: LiveData = networkState.isConnected.asLiveData() + private val _uiState = MutableStateFlow( + CoinbaseServicesUIState(isLoggedIn = coinBaseRepository.isAuthenticated) + ) + val uiState: StateFlow = _uiState.asStateFlow() val balanceFormat: MonetaryFormat get() = preferences.format.noCode() init { config.observe(CoinbaseConfig.LAST_BALANCE) - .map { _balanceUIState.value?.copy(balance = Coin.valueOf(it ?: 0)) } + .map { uiState.value.copy(balance = Coin.valueOf(it ?: 0)) } .filterNotNull() .flatMapLatest { state -> walletUIConfig.observe(WalletUIConfig.SELECTED_CURRENCY) @@ -91,34 +87,39 @@ class CoinbaseServicesViewModel @Inject constructor( } state.copy(balanceFiat = fiatBalance) } - }.onEach { state -> _balanceUIState.value = state } + }.onEach { state -> _uiState.value = state } .launchIn(viewModelScope) + + viewModelScope.launch { coinBaseRepository.refreshWithdrawalLimit() } } fun refreshBalance() { viewModelScope.launch { try { - _balanceUIState.value = _balanceUIState.value?.copy(isUpdating = true) - val response = coinBaseRepository.getUserAccount().unwrap() + _uiState.value = _uiState.value.copy(isBalanceUpdating = true) + val response = coinBaseRepository.getUserAccount() config.set( CoinbaseConfig.LAST_BALANCE, - Coin.parseCoin(response?.balance?.amount ?: "0.0").value + Coin.parseCoin(response.availableBalance.value).value ) + } catch (ex: IllegalStateException) { + _uiState.value = _uiState.value.copy(error = CoinbaseErrorType.USER_ACCOUNT_ERROR) } catch (ex: Exception) { log.error("Error refreshing Coinbase balance", ex) } finally { - _balanceUIState.value = _balanceUIState.value?.copy(isUpdating = false) + _uiState.value = _uiState.value.copy(isBalanceUpdating = false) } } } - fun disconnectCoinbaseAccount() = viewModelScope.launch(Dispatchers.Main) { + fun disconnectCoinbaseAccount() = viewModelScope.launch { analyticsService.logEvent(AnalyticsConstants.Coinbase.DISCONNECT, mapOf()) - - _showLoading.value = true coinBaseRepository.disconnectCoinbaseAccount() - _showLoading.value = false - coinbaseLogOutCallback.call() + _uiState.value = _uiState.value.copy(isLoggedIn = false) + } + + fun clearError() { + _uiState.value = _uiState.value.copy(error = CoinbaseErrorType.NONE) } fun logEvent(eventName: String) { diff --git a/integrations/coinbase/src/main/java/org/dash/wallet/integrations/coinbase/viewmodels/CoinbaseViewModel.kt b/integrations/coinbase/src/main/java/org/dash/wallet/integrations/coinbase/viewmodels/CoinbaseViewModel.kt new file mode 100644 index 0000000000..f23597831b --- /dev/null +++ b/integrations/coinbase/src/main/java/org/dash/wallet/integrations/coinbase/viewmodels/CoinbaseViewModel.kt @@ -0,0 +1,137 @@ +/* + * Copyright 2021 Dash Core Group. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package org.dash.wallet.integrations.coinbase.viewmodels + +import androidx.lifecycle.ViewModel +import androidx.lifecycle.viewModelScope +import dagger.hilt.android.lifecycle.HiltViewModel +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.asStateFlow +import kotlinx.coroutines.flow.distinctUntilChanged +import kotlinx.coroutines.flow.filterNotNull +import kotlinx.coroutines.flow.launchIn +import kotlinx.coroutines.flow.onEach +import kotlinx.coroutines.flow.update +import kotlinx.coroutines.launch +import kotlinx.coroutines.runBlocking +import org.bitcoinj.core.Coin +import org.dash.wallet.common.data.ResponseResource +import org.dash.wallet.common.data.WalletUIConfig +import org.dash.wallet.common.services.NetworkStateInt +import org.dash.wallet.common.services.analytics.AnalyticsService +import org.dash.wallet.integrations.coinbase.repository.CoinBaseRepositoryInt +import org.dash.wallet.integrations.coinbase.ui.convert_currency.model.BaseIdForFiatData +import org.dash.wallet.integrations.coinbase.utils.CoinbaseConfig +import org.slf4j.LoggerFactory +import javax.inject.Inject + +data class CoinbaseUIState( + val baseIdForFiatModel: BaseIdForFiatData = BaseIdForFiatData.LoadingState, + val isSessionExpired: Boolean = false, + val isNetworkAvailable: Boolean = false +) + +@HiltViewModel +class CoinbaseViewModel @Inject constructor( + private val config: CoinbaseConfig, + private val walletUIConfig: WalletUIConfig, + private val coinBaseRepository: CoinBaseRepositoryInt, + private val analytics: AnalyticsService, + networkState: NetworkStateInt +) : ViewModel() { + + companion object { + private val log = LoggerFactory.getLogger(CoinbaseViewModel::class.java) + } + + private val _uiState = MutableStateFlow(CoinbaseUIState()) + val uiState: StateFlow = _uiState.asStateFlow() + + init { + config.observe(CoinbaseConfig.LAST_REFRESH_TOKEN) + .distinctUntilChanged() + .filterNotNull() + .onEach { token -> + _uiState.update { it.copy(isSessionExpired = token.isEmpty()) } + } + .launchIn(viewModelScope) + + networkState.isConnected + .onEach { isConnected -> + _uiState.update { it.copy(isNetworkAvailable = isConnected) } + } + .launchIn(viewModelScope) + } + + suspend fun loginToCoinbase(code: String): Boolean { + when (val response = coinBaseRepository.completeCoinbaseAuthentication(code)) { + is ResponseResource.Success -> { + if (response.value) { + return true + } + } + + is ResponseResource.Failure -> { + log.error("Coinbase login error ${response.errorCode}: ${response.errorBody ?: "empty"}") + } + } + + return false + } + fun getBaseIdForFiatModel() = viewModelScope.launch { + _uiState.update { it.copy(baseIdForFiatModel = BaseIdForFiatData.LoadingState) } + + when ( + val response = coinBaseRepository.getBaseIdForUSDModel( + walletUIConfig.getExchangeCurrencyCode() + ) + ) { + is ResponseResource.Success -> { + _uiState.update { + it.copy( + baseIdForFiatModel = if (response.value?.data != null) { + BaseIdForFiatData.Success(response.value?.data!!) + } else { + BaseIdForFiatData.LoadingState + } + ) + } + } + + is ResponseResource.Failure -> { + runBlocking { config.set(CoinbaseConfig.UPDATE_BASE_IDS, true) } + _uiState.update { it.copy(baseIdForFiatModel = BaseIdForFiatData.Error) } + } + else -> { } + } + } + + fun clearWasLoggedOut() { + _uiState.update { it.copy(isSessionExpired = false) } + } + + suspend fun isInputGreaterThanLimit(amountInDash: Coin): Boolean { + return amountInDash.toPlainString().toDoubleOrZero.compareTo( + coinBaseRepository.getWithdrawalLimitInDash() + ) > 0 + } + + fun logEvent(eventName: String) { + analytics.logEvent(eventName, mapOf()) + } +} diff --git a/integrations/coinbase/src/main/java/org/dash/wallet/integration/coinbase_integration/viewmodels/ConvertViewViewModel.kt b/integrations/coinbase/src/main/java/org/dash/wallet/integrations/coinbase/viewmodels/ConvertViewViewModel.kt similarity index 87% rename from integrations/coinbase/src/main/java/org/dash/wallet/integration/coinbase_integration/viewmodels/ConvertViewViewModel.kt rename to integrations/coinbase/src/main/java/org/dash/wallet/integrations/coinbase/viewmodels/ConvertViewViewModel.kt index 7be88672fd..260404a2c9 100644 --- a/integrations/coinbase/src/main/java/org/dash/wallet/integration/coinbase_integration/viewmodels/ConvertViewViewModel.kt +++ b/integrations/coinbase/src/main/java/org/dash/wallet/integrations/coinbase/viewmodels/ConvertViewViewModel.kt @@ -15,7 +15,7 @@ * along with this program. If not, see . */ -package org.dash.wallet.integration.coinbase_integration.viewmodels +package org.dash.wallet.integrations.coinbase.viewmodels import androidx.lifecycle.* import dagger.hilt.android.lifecycle.HiltViewModel @@ -39,10 +39,10 @@ import org.dash.wallet.common.services.analytics.AnalyticsService import org.dash.wallet.common.util.Constants import org.dash.wallet.common.util.GenericUtils import org.dash.wallet.common.util.toBigDecimal -import org.dash.wallet.integration.coinbase_integration.CoinbaseConstants -import org.dash.wallet.integration.coinbase_integration.model.CoinBaseUserAccountDataUIModel -import org.dash.wallet.integration.coinbase_integration.ui.convert_currency.model.SwapRequest -import org.dash.wallet.integration.coinbase_integration.ui.convert_currency.model.SwapValueErrorType +import org.dash.wallet.integrations.coinbase.CoinbaseConstants +import org.dash.wallet.integrations.coinbase.model.CoinBaseUserAccountDataUIModel +import org.dash.wallet.integrations.coinbase.ui.convert_currency.model.SwapRequest +import org.dash.wallet.integrations.coinbase.ui.convert_currency.model.SwapValueErrorType import java.math.BigDecimal import java.math.RoundingMode import javax.inject.Inject @@ -122,26 +122,19 @@ class ConvertViewViewModel @Inject constructor( } fun setSelectedCryptoCurrency(account: CoinBaseUserAccountDataUIModel) { - maxCoinBaseAccountAmount = account.coinBaseUserAccountData.balance?.amount ?: "0" + maxCoinBaseAccountAmount = account.coinbaseAccount.availableBalance.value this._selectedLocalExchangeRate.value = selectedLocalExchangeRate.value?.currencyCode?.let { - val cleanedValue = - 1.toBigDecimal() / - account.currencyToDashExchangeRate.toBigDecimal() + val cleanedValue = 1.toBigDecimal() / account.currencyToDashExchangeRate val bd = cleanedValue.setScale(8, RoundingMode.HALF_UP) - ExchangeRate( - it, - bd.toString() - ) + ExchangeRate(it, bd.toString()) } this._selectedCryptoCurrencyAccount.value = account // To check if the user has different fiat than usd the min is 2 usd val minFaitValue = CoinbaseConstants.MIN_USD_COINBASE_AMOUNT.toBigDecimal() / - account.currencyToUSDExchangeRate.toBigDecimal() - - val cleanedValue: BigDecimal = - minFaitValue * account.currencyToDashExchangeRate.toBigDecimal() + account.currencyToUSDExchangeRate + val cleanedValue: BigDecimal = minFaitValue * account.currencyToDashExchangeRate minAllowedSwapAmount = minFaitValue.toString() val bd = cleanedValue.setScale(8, RoundingMode.HALF_UP) @@ -155,7 +148,7 @@ class ConvertViewViewModel @Inject constructor( minAllowedSwapDashCoin = coin val value = - (maxCoinBaseAccountAmount.toBigDecimal() * account.cryptoCurrencyToDashExchangeRate.toBigDecimal()) + (maxCoinBaseAccountAmount.toBigDecimal() * account.getCryptoToDashExchangeRate()) .setScale(8, RoundingMode.HALF_UP) val maxCoinValue = try { @@ -171,11 +164,10 @@ class ConvertViewViewModel @Inject constructor( _enteredConvertDashAmount.value = value if (!value.isZero) { _selectedCryptoCurrencyAccount.value?.let { - val cryptoCurrency = (value.toBigDecimal() / it.cryptoCurrencyToDashExchangeRate.toBigDecimal()) + val cryptoCurrency = (value.toBigDecimal() / it.getCryptoToDashExchangeRate()) .setScale(8, RoundingMode.HALF_UP).toString() - _enteredConvertCryptoAmount.value = - Pair(cryptoCurrency, it.coinBaseUserAccountData.currency?.code.toString()) + _enteredConvertCryptoAmount.value = Pair(cryptoCurrency, it.coinbaseAccount.currency) } } @@ -254,7 +246,7 @@ class ConvertViewViewModel @Inject constructor( when (currencyInputType) { CurrencyInputType.Crypto -> { val cleanedValue = enteredConvertAmount.toBigDecimal() / - account.currencyToCryptoCurrencyExchangeRate.toBigDecimal() + account.currencyToCryptoCurrencyExchangeRate val bd = cleanedValue.setScale(8, RoundingMode.HALF_UP) Fiat.parseFiat(rate.fiat.currencyCode, bd.toString()) @@ -266,7 +258,7 @@ class ConvertViewViewModel @Inject constructor( else -> { val cleanedValue = enteredConvertAmount.toBigDecimal() / - account.currencyToDashExchangeRate.toBigDecimal() + account.currencyToDashExchangeRate val bd = cleanedValue.setScale(8, RoundingMode.HALF_UP) Fiat.parseFiat(rate.fiat.currencyCode, bd.toString()) @@ -292,11 +284,9 @@ class ConvertViewViewModel @Inject constructor( fromCrypto: Boolean = false ): BigDecimal { val convertedValue = if (fromCrypto) { - valueToBind.toBigDecimal() * - userAccountData.cryptoCurrencyToDashExchangeRate.toBigDecimal() + valueToBind.toBigDecimal() * userAccountData.getCryptoToDashExchangeRate() } else { - valueToBind.toBigDecimal() * - userAccountData.currencyToDashExchangeRate.toBigDecimal() + valueToBind.toBigDecimal() * userAccountData.getCryptoToDashExchangeRate() }.setScale(8, RoundingMode.HALF_UP) return convertedValue } @@ -324,7 +314,7 @@ class ConvertViewViewModel @Inject constructor( private suspend fun getCurrencyInputType(currencyCode: String): CurrencyInputType { val code = currencyCode.lowercase() val account = selectedCryptoCurrencyAccount.value - val currency = account?.coinBaseUserAccountData?.balance?.currency?.lowercase() + val currency = account?.coinbaseAccount?.currency?.lowercase() return when { currency == Constants.DASH_CURRENCY.lowercase() -> CurrencyInputType.Dash diff --git a/integrations/coinbase/src/main/java/org/dash/wallet/integrations/coinbase/viewmodels/Delegate.kt b/integrations/coinbase/src/main/java/org/dash/wallet/integrations/coinbase/viewmodels/Delegate.kt new file mode 100644 index 0000000000..459dba7bba --- /dev/null +++ b/integrations/coinbase/src/main/java/org/dash/wallet/integrations/coinbase/viewmodels/Delegate.kt @@ -0,0 +1,27 @@ +/* + * Copyright 2023 Dash Core Group. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package org.dash.wallet.integrations.coinbase.viewmodels + +import androidx.fragment.app.Fragment +import androidx.lifecycle.ViewModel +import androidx.navigation.navGraphViewModels +import org.dash.wallet.integrations.coinbase.R + +inline fun Fragment.coinbaseViewModels(): Lazy { + return navGraphViewModels(R.id.nav_coinbase) { defaultViewModelProviderFactory } +} diff --git a/integrations/coinbase/src/main/java/org/dash/wallet/integration/coinbase_integration/viewmodels/EnterAmountToTransferViewModel.kt b/integrations/coinbase/src/main/java/org/dash/wallet/integrations/coinbase/viewmodels/EnterAmountToTransferViewModel.kt similarity index 94% rename from integrations/coinbase/src/main/java/org/dash/wallet/integration/coinbase_integration/viewmodels/EnterAmountToTransferViewModel.kt rename to integrations/coinbase/src/main/java/org/dash/wallet/integrations/coinbase/viewmodels/EnterAmountToTransferViewModel.kt index 34a6305b7e..d83b256792 100644 --- a/integrations/coinbase/src/main/java/org/dash/wallet/integration/coinbase_integration/viewmodels/EnterAmountToTransferViewModel.kt +++ b/integrations/coinbase/src/main/java/org/dash/wallet/integrations/coinbase/viewmodels/EnterAmountToTransferViewModel.kt @@ -15,7 +15,7 @@ * along with this program. If not, see . */ -package org.dash.wallet.integration.coinbase_integration.viewmodels +package org.dash.wallet.integrations.coinbase.viewmodels import androidx.lifecycle.* import dagger.hilt.android.lifecycle.HiltViewModel @@ -37,8 +37,8 @@ import org.dash.wallet.common.util.Constants import org.dash.wallet.common.util.GenericUtils import org.dash.wallet.common.util.toBigDecimal import org.dash.wallet.common.util.toFiat -import org.dash.wallet.integration.coinbase_integration.CoinbaseConstants -import org.dash.wallet.integration.coinbase_integration.model.CoinbaseToDashExchangeRateUIModel +import org.dash.wallet.integrations.coinbase.CoinbaseConstants +import org.dash.wallet.integrations.coinbase.model.CoinbaseToDashExchangeRateUIModel import java.math.BigDecimal import java.math.RoundingMode import java.text.DecimalFormat @@ -197,26 +197,21 @@ class EnterAmountToTransferViewModel @Inject constructor( } private val maxAmountCoinbaseAccount: String - get() { - return coinbaseExchangeRate?.let { - it.coinBaseUserAccountData.balance?.amount - } ?: CoinbaseConstants.VALUE_ZERO - } + get() = coinbaseExchangeRate?.coinbaseAccount?.availableBalance?.value ?: CoinbaseConstants.VALUE_ZERO private fun applyCoinbaseExchangeRate(amount: String): String { return coinbaseExchangeRate?.let { uiModel -> val cleanedValue = amount .replace(',', '.') // TODO: the amount sometimes comes here with a comma as decimal separator. // TODO: it's better to identify the root of this and replace in there to prevent this problem from appearing anywhere else. - .toBigDecimal() / uiModel.currencyToDashExchangeRate.toBigDecimal() + .toBigDecimal() / uiModel.currencyToDashExchangeRate cleanedValue.setScale(8, RoundingMode.HALF_UP).toPlainString() } ?: CoinbaseConstants.VALUE_ZERO } fun applyExchangeRateToFiat(fiatValue: Fiat): Coin { return coinbaseExchangeRate?.let { - val cleanedValue = - fiatValue.toBigDecimal() * it.currencyToDashExchangeRate.toBigDecimal() + val cleanedValue = fiatValue.toBigDecimal() * it.currencyToDashExchangeRate val plainValue = cleanedValue.setScale(8, RoundingMode.HALF_UP).toPlainString() try { Coin.parseCoin(plainValue) @@ -287,14 +282,14 @@ class EnterAmountToTransferViewModel @Inject constructor( fun getExchangeRate(): org.bitcoinj.utils.ExchangeRate? { return coinbaseExchangeRate?.let { - val rate = BigDecimal.ONE.divide(it.currencyToDashExchangeRate.toBigDecimal(), 10, RoundingMode.HALF_UP) + val rate = BigDecimal.ONE.divide(it.currencyToDashExchangeRate, 10, RoundingMode.HALF_UP) org.bitcoinj.utils.ExchangeRate(rate.toFiat(localCurrencyCode)) } } private fun scaleValue(valueToScale: String): String { return coinbaseExchangeRate?.let { - val cleanedValue = valueToScale.toBigDecimal() * it.currencyToDashExchangeRate.toBigDecimal() + val cleanedValue = valueToScale.toBigDecimal() * it.currencyToDashExchangeRate cleanedValue.setScale(8, RoundingMode.HALF_UP).toPlainString() } ?: "" } diff --git a/integrations/coinbase/src/main/java/org/dash/wallet/integration/coinbase_integration/viewmodels/EnterTwoFaCodeViewModel.kt b/integrations/coinbase/src/main/java/org/dash/wallet/integrations/coinbase/viewmodels/EnterTwoFaCodeViewModel.kt similarity index 85% rename from integrations/coinbase/src/main/java/org/dash/wallet/integration/coinbase_integration/viewmodels/EnterTwoFaCodeViewModel.kt rename to integrations/coinbase/src/main/java/org/dash/wallet/integrations/coinbase/viewmodels/EnterTwoFaCodeViewModel.kt index 18c9f8f6ca..5e756fd046 100644 --- a/integrations/coinbase/src/main/java/org/dash/wallet/integration/coinbase_integration/viewmodels/EnterTwoFaCodeViewModel.kt +++ b/integrations/coinbase/src/main/java/org/dash/wallet/integrations/coinbase/viewmodels/EnterTwoFaCodeViewModel.kt @@ -15,9 +15,8 @@ * along with this program. If not, see . */ -package org.dash.wallet.integration.coinbase_integration.viewmodels +package org.dash.wallet.integrations.coinbase.viewmodels -import androidx.core.os.bundleOf import androidx.lifecycle.LiveData import androidx.lifecycle.MutableLiveData import androidx.lifecycle.ViewModel @@ -25,15 +24,15 @@ import androidx.lifecycle.viewModelScope import dagger.hilt.android.lifecycle.HiltViewModel import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch +import org.dash.wallet.common.data.ResponseResource import org.dash.wallet.common.data.SingleLiveEvent import org.dash.wallet.common.services.analytics.AnalyticsConstants import org.dash.wallet.common.services.analytics.AnalyticsService -import org.dash.wallet.integration.coinbase_integration.CoinbaseConstants -import org.dash.wallet.integration.coinbase_integration.model.CoinbaseErrorResponse -import org.dash.wallet.integration.coinbase_integration.model.SendTransactionToWalletParams -import org.dash.wallet.common.data.ResponseResource -import org.dash.wallet.integration.coinbase_integration.repository.CoinBaseRepositoryInt -import org.dash.wallet.integration.coinbase_integration.ui.dialogs.CoinBaseResultDialog +import org.dash.wallet.integrations.coinbase.CoinbaseConstants +import org.dash.wallet.integrations.coinbase.model.CoinbaseErrorResponse +import org.dash.wallet.integrations.coinbase.model.SendTransactionToWalletParams +import org.dash.wallet.integrations.coinbase.repository.CoinBaseRepositoryInt +import org.dash.wallet.integrations.coinbase.ui.dialogs.CoinBaseResultDialog import java.io.IOException import java.util.UUID import javax.inject.Inject @@ -61,10 +60,11 @@ class EnterTwoFaCodeViewModel @Inject constructor( } fun sendInitialTransactionToSMSTwoFactorAuth( - params: SendTransactionToWalletParams? ) =viewModelScope.launch(Dispatchers.Main) { - val sendTransactionToWalletParams= params?.copy(idem = UUID.randomUUID().toString()) - sendTransactionToWalletParams?.let { - coinBaseRepository.sendFundsToWallet(it,null) + params: SendTransactionToWalletParams? + ) = viewModelScope.launch { + val sendTransactionToWalletParams = params?.copy(idem = UUID.randomUUID().toString()) + sendTransactionToWalletParams?.let { + coinBaseRepository.sendFundsToWallet(it, null) } } @@ -99,8 +99,9 @@ class EnterTwoFaCodeViewModel @Inject constructor( if (result.errorCode == 400 || result.errorCode == 402 || result.errorCode == 429) { error?.let { errorMsg -> val errorContent = CoinbaseErrorResponse.getErrorMessage(errorMsg) - if (errorContent?.id.equals(CoinbaseConstants.ERROR_ID_INVALID_REQUEST, true) - && errorContent?.message?.contains(CoinbaseConstants.ERROR_MSG_INVALID_REQUEST) == true){ + if (errorContent?.id.equals(CoinbaseConstants.ERROR_ID_INVALID_REQUEST, true) && + errorContent?.message?.contains(CoinbaseConstants.ERROR_MSG_INVALID_REQUEST) == true + ) { twoFaErrorState.call() } else { _transactionState.value = TransactionState(false, errorContent?.message) @@ -157,8 +158,7 @@ class EnterTwoFaCodeViewModel @Inject constructor( } } - data class TransactionState( val isTransactionSuccessful: Boolean, val responseMessage: String? = null -) \ No newline at end of file +) diff --git a/integrations/coinbase/src/main/java/org/dash/wallet/integration/coinbase_integration/viewmodels/TransferDashViewModel.kt b/integrations/coinbase/src/main/java/org/dash/wallet/integrations/coinbase/viewmodels/TransferDashViewModel.kt similarity index 67% rename from integrations/coinbase/src/main/java/org/dash/wallet/integration/coinbase_integration/viewmodels/TransferDashViewModel.kt rename to integrations/coinbase/src/main/java/org/dash/wallet/integrations/coinbase/viewmodels/TransferDashViewModel.kt index 91ec5f00b1..40d9997ee8 100644 --- a/integrations/coinbase/src/main/java/org/dash/wallet/integration/coinbase_integration/viewmodels/TransferDashViewModel.kt +++ b/integrations/coinbase/src/main/java/org/dash/wallet/integrations/coinbase/viewmodels/TransferDashViewModel.kt @@ -1,10 +1,9 @@ -package org.dash.wallet.integration.coinbase_integration.viewmodels +package org.dash.wallet.integrations.coinbase.viewmodels import androidx.annotation.StringRes import androidx.lifecycle.* import dagger.hilt.android.lifecycle.HiltViewModel import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.flow.* import kotlinx.coroutines.launch import org.bitcoinj.core.Address @@ -16,30 +15,29 @@ import org.bitcoinj.wallet.Wallet.DustySendRequested import org.dash.wallet.common.Configuration import org.dash.wallet.common.R import org.dash.wallet.common.WalletDataProvider -import org.dash.wallet.common.data.entity.ExchangeRate +import org.dash.wallet.common.data.ResponseResource import org.dash.wallet.common.data.ServiceName import org.dash.wallet.common.data.SingleLiveEvent +import org.dash.wallet.common.data.WalletUIConfig +import org.dash.wallet.common.data.entity.ExchangeRate +import org.dash.wallet.common.services.* import org.dash.wallet.common.services.analytics.AnalyticsConstants import org.dash.wallet.common.services.analytics.AnalyticsService import org.dash.wallet.common.util.Constants import org.dash.wallet.common.util.GenericUtils -import org.dash.wallet.integration.coinbase_integration.CoinbaseConstants -import org.dash.wallet.integration.coinbase_integration.model.CoinbaseToDashExchangeRateUIModel -import org.dash.wallet.integration.coinbase_integration.model.CoinbaseTransactionParams -import org.dash.wallet.integration.coinbase_integration.model.SendTransactionToWalletParams -import org.dash.wallet.integration.coinbase_integration.model.TransactionType -import org.dash.wallet.common.data.ResponseResource -import org.dash.wallet.common.data.WalletUIConfig -import org.dash.wallet.common.services.* -import org.dash.wallet.integration.coinbase_integration.repository.CoinBaseRepositoryInt -import org.dash.wallet.integration.coinbase_integration.ui.convert_currency.model.SwapValueErrorType -import org.dash.wallet.integration.coinbase_integration.ui.dialogs.CoinBaseResultDialog +import org.dash.wallet.integrations.coinbase.CoinbaseConstants +import org.dash.wallet.integrations.coinbase.model.CoinbaseToDashExchangeRateUIModel +import org.dash.wallet.integrations.coinbase.model.CoinbaseTransactionParams +import org.dash.wallet.integrations.coinbase.model.SendTransactionToWalletParams +import org.dash.wallet.integrations.coinbase.model.TransactionType +import org.dash.wallet.integrations.coinbase.repository.CoinBaseRepositoryInt +import org.dash.wallet.integrations.coinbase.ui.convert_currency.model.SwapValueErrorType +import org.dash.wallet.integrations.coinbase.ui.dialogs.CoinBaseResultDialog import java.math.BigDecimal import java.math.RoundingMode import java.util.* import javax.inject.Inject -@ExperimentalCoroutinesApi @HiltViewModel class TransferDashViewModel @Inject constructor( private val coinBaseRepository: CoinBaseRepositoryInt, @@ -61,7 +59,6 @@ class TransferDashViewModel @Inject constructor( val dashBalanceInWalletState: LiveData get() = _dashBalanceInWalletState - private var withdrawalLimitCurrency = MutableStateFlow(null) private var exchangeRate: ExchangeRate? = null @@ -97,7 +94,6 @@ class TransferDashViewModel @Inject constructor( private var maxForDashCoinBaseAccount: Coin = Coin.ZERO init { - getWithdrawalLimitOnCoinbase() getUserAccountAddress() walletDataProvider.observeBalance() .onEach(_dashBalanceInWalletState::postValue) @@ -117,21 +113,8 @@ class TransferDashViewModel @Inject constructor( .launchIn(viewModelScope) } - private fun getWithdrawalLimitOnCoinbase() = viewModelScope.launch(Dispatchers.Main){ - when (val response = coinBaseRepository.getWithdrawalLimit()){ - is ResponseResource.Success -> { - withdrawalLimitCurrency.value = response.value.currency - getUserData() - } - is ResponseResource.Failure -> { - // todo: still lacking the use-case when withdrawal limit could not be fetched - _loadingState.value = false - } - } - } - - private fun getUserAccountAddress() = viewModelScope.launch(Dispatchers.Main){ - when (val response = coinBaseRepository.getUserAccountAddress()){ + private fun getUserAccountAddress() = viewModelScope.launch(Dispatchers.Main) { + when (val response = coinBaseRepository.getUserAccountAddress()) { is ResponseResource.Success -> { observeCoinbaseUserAccountAddress.value = response.value ?: "" } @@ -140,11 +123,9 @@ class TransferDashViewModel @Inject constructor( } } - private fun calculateCoinbaseMinAllowedValue(account:CoinbaseToDashExchangeRateUIModel){ - val minFaitValue = CoinbaseConstants.MIN_USD_COINBASE_AMOUNT.toBigDecimal() / account.currencyToUSDExchangeRate.toBigDecimal() - - val cleanedValue: BigDecimal = - minFaitValue * account.currencyToDashExchangeRate.toBigDecimal() + private fun calculateCoinbaseMinAllowedValue(account:CoinbaseToDashExchangeRateUIModel) { + val minFaitValue = CoinbaseConstants.MIN_USD_COINBASE_AMOUNT.toBigDecimal() / account.currencyToUSDExchangeRate + val cleanedValue: BigDecimal = minFaitValue * account.currencyToDashExchangeRate val bd = cleanedValue.setScale(8, RoundingMode.HALF_UP) @@ -164,37 +145,33 @@ class TransferDashViewModel @Inject constructor( } } - private fun calculateCoinbaseMaxAllowedValue(account:CoinbaseToDashExchangeRateUIModel){ + private fun calculateCoinbaseMaxAllowedValue(account:CoinbaseToDashExchangeRateUIModel) { val maxCoinValue = try { - Coin.parseCoin(account.coinBaseUserAccountData.balance?.amount) + Coin.parseCoin(account.coinbaseAccount.availableBalance.value) } catch (x: Exception) { Coin.ZERO } maxForDashCoinBaseAccount = maxCoinValue } - - suspend fun isInputGreaterThanLimit(amountInDash: Coin): Boolean { - exchangeRate?.let { - val rate = org.bitcoinj.utils.ExchangeRate(Coin.COIN, it.fiat) - val withdrawalLimitInDash = coinBaseRepository.getWithdrawalLimitInDash(rate) - return amountInDash.toPlainString().toDoubleOrZero.compareTo(withdrawalLimitInDash) > 0 - } ?: return true + private suspend fun isInputGreaterThanLimit(amountInDash: Coin): Boolean { + val withdrawalLimitInDash = coinBaseRepository.getWithdrawalLimitInDash() + return amountInDash.toPlainString().toDoubleOrZero.compareTo(withdrawalLimitInDash) > 0 } suspend fun checkEnteredAmountValue(amountInDash: Coin): SwapValueErrorType { return when { - (amountInDash == minAllowedSwapDashCoin || amountInDash.isGreaterThan(minAllowedSwapDashCoin)) && - maxForDashCoinBaseAccount.isLessThan(minAllowedSwapDashCoin) -> SwapValueErrorType.NotEnoughBalance - amountInDash.isLessThan(minAllowedSwapDashCoin) -> SwapValueErrorType.LessThanMin - amountInDash.isGreaterThan(maxForDashCoinBaseAccount) -> SwapValueErrorType.MoreThanMax.apply { - amount = userAccountOnCoinbaseState.value?.coinBaseUserAccountData?.balance?.amount - } - isInputGreaterThanLimit(amountInDash)-> { - SwapValueErrorType.UnAuthorizedValue - } - else -> SwapValueErrorType.NOError + (amountInDash == minAllowedSwapDashCoin || amountInDash.isGreaterThan(minAllowedSwapDashCoin)) && + maxForDashCoinBaseAccount.isLessThan(minAllowedSwapDashCoin) -> SwapValueErrorType.NotEnoughBalance + amountInDash.isLessThan(minAllowedSwapDashCoin) -> SwapValueErrorType.LessThanMin + amountInDash.isGreaterThan(maxForDashCoinBaseAccount) -> SwapValueErrorType.MoreThanMax.apply { + amount = userAccountOnCoinbaseState.value?.coinbaseAccount?.availableBalance?.value + } + isInputGreaterThanLimit(amountInDash) -> { + SwapValueErrorType.UnAuthorizedValue } + else -> SwapValueErrorType.NOError + } } fun isInputGreaterThanWalletBalance(input: Coin, balanceInWallet: Coin): Boolean { @@ -207,14 +184,14 @@ class TransferDashViewModel @Inject constructor( fun createAddressForAccount() = viewModelScope.launch { _loadingState.value = true - when(val result = coinBaseRepository.createAddress()){ + when (val result = coinBaseRepository.createAddress()) { is ResponseResource.Success -> { - if (result.value.isEmpty()){ + if (result.value.isEmpty()) { onAddressCreationFailedCallback.call() } else { - result.value?.let{ - observeCoinbaseAddressState.value = it - } + result.value?.let { + observeCoinbaseAddressState.value = it + } } _loadingState.value = false } @@ -230,26 +207,26 @@ class TransferDashViewModel @Inject constructor( } suspend fun estimateNetworkFee(value: Coin, emptyWallet: Boolean): SendPaymentService.TransactionDetails? { - try { - return sendPaymentService.estimateNetworkFee(dashAddress, value, emptyWallet) + try { + return sendPaymentService.estimateNetworkFee(dashAddress, value, emptyWallet) } catch (exception: Exception) { - - when (exception) { - is DustySendRequested -> { - _sendDashToCoinbaseError.value = NetworkFeeExceptionState(R.string.send_coins_error_dusty_send) - } - is InsufficientMoneyException -> { - _sendDashToCoinbaseError.value =NetworkFeeExceptionState( R.string.send_coins_error_insufficient_money) - } - is CouldNotAdjustDownwards -> { - _sendDashToCoinbaseError.value =NetworkFeeExceptionState( R.string.send_coins_error_dusty_send) - - } - else -> { - _sendDashToCoinbaseError.value =NetworkFeeExceptionState( exceptionMessage =exception.toString()) - } - } - return null + when (exception) { + is DustySendRequested -> { + _sendDashToCoinbaseError.value = NetworkFeeExceptionState(R.string.send_coins_error_dusty_send) + } + is InsufficientMoneyException -> { + _sendDashToCoinbaseError.value = NetworkFeeExceptionState( + R.string.send_coins_error_insufficient_money + ) + } + is CouldNotAdjustDownwards -> { + _sendDashToCoinbaseError.value = NetworkFeeExceptionState(R.string.send_coins_error_dusty_send) + } + else -> { + _sendDashToCoinbaseError.value = NetworkFeeExceptionState(exceptionMessage = exception.toString()) + } + } + return null } } @@ -257,10 +234,11 @@ class TransferDashViewModel @Inject constructor( coin: Coin, isEmptyWallet: Boolean, checkConditions: Boolean - ): SendDashResponseState{ + ): SendDashResponseState { return try { val transaction = sendPaymentService.sendCoins( - dashAddress, coin, + dashAddress, + coin, emptyWallet = isEmptyWallet, checkBalanceConditions = checkConditions ) @@ -269,12 +247,12 @@ class TransferDashViewModel @Inject constructor( ServiceName.Coinbase ) SendDashResponseState.SuccessState(transaction.isPending) - } catch(e: LeftoverBalanceException) { + } catch (e: LeftoverBalanceException) { throw e } catch (e: InsufficientMoneyException) { e.printStackTrace() SendDashResponseState.InsufficientMoneyState - } catch (e: Exception){ + } catch (e: Exception) { e.printStackTrace() e.message?.let { SendDashResponseState.FailureState(it) @@ -295,16 +273,22 @@ class TransferDashViewModel @Inject constructor( sendTransactionToWalletParams, TransactionType.TransferDash ) - transactionMetadataProvider.markAddressAsTransferInAsync(sendTransactionToWalletParams.to!!, ServiceName.Coinbase) + transactionMetadataProvider.markAddressAsTransferInAsync( + sendTransactionToWalletParams.to!!, + ServiceName.Coinbase + ) } fun logTransfer(isFiatSelected: Boolean) { analyticsService.logEvent(AnalyticsConstants.Coinbase.TRANSFER_CONTINUE, mapOf()) - analyticsService.logEvent(if (isFiatSelected) { - AnalyticsConstants.Coinbase.TRANSFER_ENTER_FIAT - } else { - AnalyticsConstants.Coinbase.TRANSFER_ENTER_DASH - }, mapOf()) + analyticsService.logEvent( + if (isFiatSelected) { + AnalyticsConstants.Coinbase.TRANSFER_ENTER_FIAT + } else { + AnalyticsConstants.Coinbase.TRANSFER_ENTER_DASH + }, + mapOf() + ) } fun logEvent(eventName: String) { @@ -327,12 +311,12 @@ class TransferDashViewModel @Inject constructor( } } - private fun getUserData(){ + private fun getUserData() { viewModelScope.launch { - when(val response = coinBaseRepository.getExchangeRateFromCoinbase()){ + when (val response = coinBaseRepository.getExchangeRateFromCoinbase()) { is ResponseResource.Success -> { val userData = response.value - if (userData == CoinbaseToDashExchangeRateUIModel.EMPTY){ + if (userData == CoinbaseToDashExchangeRateUIModel.EMPTY) { onFetchUserDataOnCoinbaseFailedCallback.call() } else { _userAccountDataWithExchangeRate.value = userData @@ -343,17 +327,22 @@ class TransferDashViewModel @Inject constructor( } is ResponseResource.Failure -> { - _loadingState.value = false - onFetchUserDataOnCoinbaseFailedCallback.call() + _loadingState.value = false + onFetchUserDataOnCoinbaseFailedCallback.call() } } } } - val dashAddress: Address - get() = Address.fromString(walletDataProvider.networkParameters, (observeCoinbaseAddressState.value ?: observeCoinbaseUserAccountAddress.value?:"").trim { it <= ' ' }) + private val dashAddress: Address + get() = Address.fromString( + walletDataProvider.networkParameters, + (observeCoinbaseAddressState.value ?: observeCoinbaseUserAccountAddress.value ?: "").trim { + it <= ' ' + } + ) } -sealed class SendDashResponseState{ +sealed class SendDashResponseState { data class SuccessState(val isTransactionPending: Boolean): SendDashResponseState() object InsufficientMoneyState: SendDashResponseState() data class FailureState(val failureMessage: String): SendDashResponseState() @@ -363,4 +352,4 @@ sealed class SendDashResponseState{ data class NetworkFeeExceptionState( @StringRes val exceptionMessageResource: Int? = null, val exceptionMessage: String? = null -) \ No newline at end of file +) diff --git a/integrations/coinbase/src/main/res/layout/content_review_buy_order_coinbase.xml b/integrations/coinbase/src/main/res/layout/content_review_buy_order_coinbase.xml index 284afc2fd9..baa22eaae1 100644 --- a/integrations/coinbase/src/main/res/layout/content_review_buy_order_coinbase.xml +++ b/integrations/coinbase/src/main/res/layout/content_review_buy_order_coinbase.xml @@ -36,6 +36,7 @@ android:layout_height="wrap_content" android:layout_marginHorizontal="4dp" android:gravity="end" + android:textAlignment="gravity" android:maxLines="1" app:layout_constraintBottom_toBottomOf="@+id/pay_label" app:layout_constraintEnd_toStartOf="@id/account" diff --git a/integrations/coinbase/src/main/res/layout/convert_view.xml b/integrations/coinbase/src/main/res/layout/convert_view.xml index 3327b0c7b9..17194a8202 100644 --- a/integrations/coinbase/src/main/res/layout/convert_view.xml +++ b/integrations/coinbase/src/main/res/layout/convert_view.xml @@ -19,7 +19,7 @@ xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="wrap_content" - tools:context="org.dash.wallet.integration.coinbase_integration.ui.convert_currency.ConvertView" + tools:context="org.dash.wallet.integrations.coinbase.ui.convert_currency.ConvertView" tools:parentTag="androidx.constraintlayout.widget.ConstraintLayout"> - - - - - - + + + - - - - + android:id="@+id/coinbaseOrderReviewFragment" + android:name="org.dash.wallet.integrations.coinbase.ui.CoinbaseOrderReviewFragment" + android:label="CoinbaseOrderReviewFragment" + tools:layout="@layout/fragment_coinbase_order_review"> + @@ -90,7 +84,7 @@ @@ -121,7 +115,7 @@ app:destination="@id/coinbaseFeeInfoDialog" /> + app:argType="org.dash.wallet.integrations.coinbase.model.SwapTradeUIModel" /> diff --git a/integrations/coinbase/src/main/res/values-xhdpi/dimens.xml b/integrations/coinbase/src/main/res/values-xhdpi/dimens.xml index d366ee1aad..b3ec8a57d8 100644 --- a/integrations/coinbase/src/main/res/values-xhdpi/dimens.xml +++ b/integrations/coinbase/src/main/res/values-xhdpi/dimens.xml @@ -1,5 +1,5 @@ - 0.23 + 0.20 0.08 \ No newline at end of file diff --git a/integrations/coinbase/src/main/res/values-xxhdpi/dimens.xml b/integrations/coinbase/src/main/res/values-xxhdpi/dimens.xml index 017166fed7..a55f76e7b3 100644 --- a/integrations/coinbase/src/main/res/values-xxhdpi/dimens.xml +++ b/integrations/coinbase/src/main/res/values-xxhdpi/dimens.xml @@ -1,5 +1,5 @@ - 0.25 + 0.22 0.10 \ No newline at end of file diff --git a/integrations/coinbase/src/main/res/values/strings.xml b/integrations/coinbase/src/main/res/values/strings.xml index 8873f47504..75ae84946c 100644 --- a/integrations/coinbase/src/main/res/values/strings.xml +++ b/integrations/coinbase/src/main/res/values/strings.xml @@ -37,7 +37,7 @@ Coinbase Fee Total Order Preview - You will receive %s Dash on your Dash Wallet on this device. Please note that it can take up to 2–3 minutes to complete a transfer. + You will receive ~ %s Dash on your Dash Wallet on this device. Please note that it can take up to 2–3 minutes to complete a transfer. Amount in Dash Cancel Confirm (%ss) @@ -57,11 +57,10 @@ Purchase Failed Transfer Failed Conversion Failed - The Dash was successfully deposited to your Coinbase account. But there was a problem transferring it to Dash Wallet on this device. - Something went wrong + The Dash was successfully deposited to your Coinbase account. But there was a problem transferring it to Dash Wallet on this device. It could take up to 2–3 minutes for the Dash to be transferred to your Dash Wallet on this device. It could take up to 5 minutes to convert %s from Coinbase account to Dash in your Dash Wallet on this device.. - “It could take up to 5 minutes to convert Dash from Dash Wallet on this device to %s in your Coinbase account. + It could take up to 5 minutes to convert Dash from Dash Wallet on this device to %s in your Coinbase account. Contact Coinbase Support Fees in crypto purchases In addition to the displayed Coinbase fee, we include a spread in the price. Cryptocurrency markets are volatile, and this allows us to temporary lock in a price for trade execution. @@ -78,9 +77,9 @@ From Dash wallet on this device You will receive %s DASH - We didn’t find any assets on your Coinbase account. - You don’t own any crypto. Buy some crypto to get started.. - Buy Crypto on Coinbase + We didn’t find any assets on your Coinbase account. + You don’t own any crypto. Buy some crypto to get started. + Buy Crypto on Coinbase You exceeded the authorization limit on Coinbase. Debit money from your account Change this limit @@ -99,11 +98,10 @@ Verify Enter Coinbase 2FA code This extra step shows it’s really you trying to make a transaction. - Need help ? + Need help? Coinbase 2FA code It could be the code from the SMS on your phone. If not, enter the code from the authentication app. The code is incorrect. Please check and try again! - That code was invalid. Please try again Verify You don’t have enough balance Transfer @@ -113,9 +111,12 @@ Transfer successful It could take up to 10 minutes to transfer Dash from Coinbase to the Dash wallet of this device. It could take up to 10 minutes to transfer Dash from Dash Wallet on this device to your Coinbase account. - There was a problem transferring it to Dash Wallet on this device. + There was a problem transferring it to Dash Wallet on this device. The amount of coins in the wallet is too small to transfer. You will receive %s %s %s You will receive %s + Your Coinbase session has expired + Please log in to your Coinbase account + Would you like to make a deposit for your purchase using a linked bank account? https://www.coinbase.com/ \ No newline at end of file diff --git a/integrations/coinbase/src/test/java/org/dash/wallet/integration/coinbase_integration/CoinBaseRepositoryTest.kt b/integrations/coinbase/src/test/java/org/dash/wallet/integrations/coinbase/CoinBaseRepositoryTest.kt similarity index 60% rename from integrations/coinbase/src/test/java/org/dash/wallet/integration/coinbase_integration/CoinBaseRepositoryTest.kt rename to integrations/coinbase/src/test/java/org/dash/wallet/integrations/coinbase/CoinBaseRepositoryTest.kt index 44c7634908..7bf31f653f 100644 --- a/integrations/coinbase/src/test/java/org/dash/wallet/integration/coinbase_integration/CoinBaseRepositoryTest.kt +++ b/integrations/coinbase/src/test/java/org/dash/wallet/integrations/coinbase/CoinBaseRepositoryTest.kt @@ -14,7 +14,7 @@ * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ -package org.dash.wallet.integration.coinbase_integration +package org.dash.wallet.integrations.coinbase import io.mockk.MockKAnnotations import io.mockk.coEvery @@ -24,15 +24,12 @@ import io.mockk.impl.annotations.MockK import io.mockk.mockk import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.runBlocking -import org.dash.wallet.common.Configuration -import org.dash.wallet.integration.coinbase_integration.model.PlaceBuyOrderParams -import org.dash.wallet.integration.coinbase_integration.model.PlaceBuyOrderUIModel -import org.dash.wallet.integration.coinbase_integration.model.SendTransactionToWalletParams +import org.dash.wallet.integrations.coinbase.model.SendTransactionToWalletParams import org.dash.wallet.common.data.ResponseResource -import org.dash.wallet.integration.coinbase_integration.repository.CoinBaseRepository -import org.dash.wallet.integration.coinbase_integration.service.CoinBaseAuthApi -import org.dash.wallet.integration.coinbase_integration.service.CoinBaseServicesApi -import org.dash.wallet.integration.coinbase_integration.utils.CoinbaseConfig +import org.dash.wallet.integrations.coinbase.repository.CoinBaseRepository +import org.dash.wallet.integrations.coinbase.service.CoinBaseAuthApi +import org.dash.wallet.integrations.coinbase.service.CoinBaseServicesApi +import org.dash.wallet.integrations.coinbase.utils.CoinbaseConfig import org.hamcrest.MatcherAssert.assertThat import org.hamcrest.Matchers.`is` import org.junit.Before @@ -42,9 +39,7 @@ class CoinBaseRepositoryTest { @MockK lateinit var coinBaseServicesApi: CoinBaseServicesApi @MockK lateinit var coinBaseAuthApi: CoinBaseAuthApi @MockK lateinit var config: CoinbaseConfig - @MockK lateinit var placeBuyOrderMapper: PlaceBuyOrderMapper @MockK lateinit var swapTradeMapper: SwapTradeMapper - @MockK lateinit var commitBuyOrderMapper: CommitBuyOrderMapper @MockK lateinit var coinbaseAddressMapper: CoinbaseAddressMapper private lateinit var coinBaseRepository: CoinBaseRepository private val accountId = "423095d3-bb89-5cef-b1bc-d1dfe6e13857" @@ -61,10 +56,9 @@ class CoinBaseRepositoryTest { coinBaseAuthApi, config, mockk(), - placeBuyOrderMapper, swapTradeMapper, - commitBuyOrderMapper, - coinbaseAddressMapper + coinbaseAddressMapper, + mockk() ) } @@ -74,28 +68,7 @@ class CoinBaseRepositoryTest { coEvery { coinBaseServicesApi.getActivePaymentMethods() } returns expectedPaymentMethods val actualSuccessResponse = runBlocking { coinBaseRepository.getActivePaymentMethods() } coVerify { coinBaseServicesApi.getActivePaymentMethods() } - assertThat(actualSuccessResponse, `is`(ResponseResource.Success(TestUtils.paymentMethodsData))) - } - - @Test - fun `when placing a buy order, repository returns success with data`() { - val expectedPlaceBuyOrderResponse = TestUtils.placeBuyOrderApiResponse() - val expectedPlaceBuyOrderUIModel = PlaceBuyOrderUIModel( - "5ccb6a4a-6296-5ca6-8fb5-8e66740925ef", - "931aa7a2-6500-505b-bf0b-35f031466711", - "0.99", - "usd" - ) - val expectedPlaceBuyOrderData = TestUtils.buyOrderData - val accountId = "423095d3-bb89-5cef-b1bc-d1dfe6e13857" - val params = PlaceBuyOrderParams( - "0.5", "usd", "931aa7a2-6500-505b-bf0b-35f031466711", - commit = true - ) - coEvery { coinBaseServicesApi.placeBuyOrder(accountId = accountId, placeBuyOrderParams = params) } returns expectedPlaceBuyOrderResponse - coEvery { placeBuyOrderMapper.map(expectedPlaceBuyOrderData) } returns expectedPlaceBuyOrderUIModel - val actualSuccessResponse = runBlocking { coinBaseRepository.placeBuyOrder(params) } - assertThat(actualSuccessResponse, `is`(ResponseResource.Success(expectedPlaceBuyOrderUIModel))) + assertThat(actualSuccessResponse, `is`(TestUtils.paymentMethodsData)) } @Test diff --git a/integrations/coinbase/src/test/java/org/dash/wallet/integration/coinbase_integration/TestUtils.kt b/integrations/coinbase/src/test/java/org/dash/wallet/integrations/coinbase/TestUtils.kt similarity index 72% rename from integrations/coinbase/src/test/java/org/dash/wallet/integration/coinbase_integration/TestUtils.kt rename to integrations/coinbase/src/test/java/org/dash/wallet/integrations/coinbase/TestUtils.kt index a96ef8bba1..0954413395 100644 --- a/integrations/coinbase/src/test/java/org/dash/wallet/integration/coinbase_integration/TestUtils.kt +++ b/integrations/coinbase/src/test/java/org/dash/wallet/integrations/coinbase/TestUtils.kt @@ -14,13 +14,11 @@ * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ -package org.dash.wallet.integration.coinbase_integration +package org.dash.wallet.integrations.coinbase import com.google.gson.GsonBuilder -import org.dash.wallet.integration.coinbase_integration.model.BuyOrderResponse -import org.dash.wallet.integration.coinbase_integration.model.CoinBaseUserAccountInfo -import org.dash.wallet.integration.coinbase_integration.model.PaymentMethodsResponse -import org.dash.wallet.integration.coinbase_integration.model.SendTransactionToWalletResponse +import org.dash.wallet.integrations.coinbase.model.PaymentMethodsResponse +import org.dash.wallet.integrations.coinbase.model.SendTransactionToWalletResponse import java.io.BufferedReader import java.io.IOException import java.io.InputStream @@ -55,11 +53,6 @@ object TestUtils { return gson.fromJson(dataSetAsString, resourceGenerator.type) } - fun getUserAccountApiResponse(): CoinBaseUserAccountInfo { - val apiResponse = readFileWithoutNewLineFromResources("user_accounts.json") - return generateResource(apiResponse, CoinBaseUserAccountInfo::class.java) - } - val paymentMethodsData = getPaymentMethodsApiResponse().data fun getPaymentMethodsApiResponse(): PaymentMethodsResponse { @@ -67,13 +60,6 @@ object TestUtils { return generateResource(apiResponse, PaymentMethodsResponse::class.java) } - fun placeBuyOrderApiResponse(): BuyOrderResponse { - val apiResponse = readFileWithoutNewLineFromResources("place_buy_order.json") - return generateResource(apiResponse, BuyOrderResponse::class.java) - } - - val buyOrderData = placeBuyOrderApiResponse().data - fun sendFundsToWalletApiResponse(): SendTransactionToWalletResponse { val apiResponse = readFileWithoutNewLineFromResources("send_funds_to_wallet.json") return generateResource(apiResponse, SendTransactionToWalletResponse::class.java) diff --git a/integrations/uphold/build.gradle b/integrations/uphold/build.gradle index 0984c98940..a2d39389f7 100644 --- a/integrations/uphold/build.gradle +++ b/integrations/uphold/build.gradle @@ -34,7 +34,7 @@ android { buildFeatures { viewBinding true } - namespace 'org.dash.wallet.integration.uphold' + namespace 'org.dash.wallet.integrations.uphold' } kapt { diff --git a/integrations/uphold/proguard-rules.pro b/integrations/uphold/proguard-rules.pro index 2c264ec25a..6b1cffe2c5 100644 --- a/integrations/uphold/proguard-rules.pro +++ b/integrations/uphold/proguard-rules.pro @@ -1,10 +1,10 @@ -keepattributes Exceptions, InnerClasses --keep class org.dash.wallet.integration.coinbase_integration.** { +-keep class org.dash.wallet.integrations.coinbase_integration.** { public protected private *; } --keep interface org.dash.wallet.integration.coinbase_integration.** {*;} +-keep interface org.dash.wallet.integrations.coinbase_integration.** {*;} --keep class org.dash.wallet.integration.uphold.** { +-keep class org.dash.wallet.integrations.uphold.** { public protected private *; } diff --git a/integrations/uphold/src/main/java/org/dash/wallet/integration/uphold/api/RemoteDataSource.kt b/integrations/uphold/src/main/java/org/dash/wallet/integrations/uphold/api/RemoteDataSource.kt similarity index 96% rename from integrations/uphold/src/main/java/org/dash/wallet/integration/uphold/api/RemoteDataSource.kt rename to integrations/uphold/src/main/java/org/dash/wallet/integrations/uphold/api/RemoteDataSource.kt index 2824428b5b..0bd55e5ccb 100644 --- a/integrations/uphold/src/main/java/org/dash/wallet/integration/uphold/api/RemoteDataSource.kt +++ b/integrations/uphold/src/main/java/org/dash/wallet/integrations/uphold/api/RemoteDataSource.kt @@ -15,7 +15,7 @@ * along with this program. If not, see . */ -package org.dash.wallet.integration.uphold.api +package org.dash.wallet.integrations.uphold.api import com.google.gson.GsonBuilder import okhttp3.OkHttpClient diff --git a/integrations/uphold/src/main/java/org/dash/wallet/integration/uphold/api/TopperClient.kt b/integrations/uphold/src/main/java/org/dash/wallet/integrations/uphold/api/TopperClient.kt similarity index 97% rename from integrations/uphold/src/main/java/org/dash/wallet/integration/uphold/api/TopperClient.kt rename to integrations/uphold/src/main/java/org/dash/wallet/integrations/uphold/api/TopperClient.kt index c22fbf70f3..ce6b8f9f7b 100644 --- a/integrations/uphold/src/main/java/org/dash/wallet/integration/uphold/api/TopperClient.kt +++ b/integrations/uphold/src/main/java/org/dash/wallet/integrations/uphold/api/TopperClient.kt @@ -15,7 +15,7 @@ * along with this program. If not, see . */ -package org.dash.wallet.integration.uphold.api +package org.dash.wallet.integrations.uphold.api import com.google.gson.Gson import io.jsonwebtoken.Jwts @@ -25,7 +25,7 @@ import okhttp3.OkHttpClient import org.bitcoinj.core.Address import org.dash.wallet.common.util.Constants import org.dash.wallet.common.util.get -import org.dash.wallet.integration.uphold.data.SupportedTopperAssets +import org.dash.wallet.integrations.uphold.data.SupportedTopperAssets import org.slf4j.LoggerFactory import org.spongycastle.asn1.ASN1Sequence import org.spongycastle.asn1.pkcs.PrivateKeyInfo diff --git a/integrations/uphold/src/main/java/org/dash/wallet/integration/uphold/api/UpholdClient.java b/integrations/uphold/src/main/java/org/dash/wallet/integrations/uphold/api/UpholdClient.java similarity index 95% rename from integrations/uphold/src/main/java/org/dash/wallet/integration/uphold/api/UpholdClient.java rename to integrations/uphold/src/main/java/org/dash/wallet/integrations/uphold/api/UpholdClient.java index cbc971541e..f84d5d3615 100644 --- a/integrations/uphold/src/main/java/org/dash/wallet/integration/uphold/api/UpholdClient.java +++ b/integrations/uphold/src/main/java/org/dash/wallet/integrations/uphold/api/UpholdClient.java @@ -15,17 +15,17 @@ * along with this program. If not, see . */ -package org.dash.wallet.integration.uphold.api; +package org.dash.wallet.integrations.uphold.api; import android.content.Context; import android.content.SharedPreferences; import com.securepreferences.SecurePreferences; -import org.dash.wallet.integration.uphold.data.UpholdApiException; -import org.dash.wallet.integration.uphold.data.UpholdCard; -import org.dash.wallet.integration.uphold.data.UpholdConstants; -import org.dash.wallet.integration.uphold.data.UpholdTransaction; +import org.dash.wallet.integrations.uphold.data.UpholdApiException; +import org.dash.wallet.integrations.uphold.data.UpholdCard; +import org.dash.wallet.integrations.uphold.data.UpholdConstants; +import org.dash.wallet.integrations.uphold.data.UpholdTransaction; import org.slf4j.Logger; import org.slf4j.LoggerFactory; diff --git a/integrations/uphold/src/main/java/org/dash/wallet/integration/uphold/api/UpholdClientExt.kt b/integrations/uphold/src/main/java/org/dash/wallet/integrations/uphold/api/UpholdClientExt.kt similarity index 96% rename from integrations/uphold/src/main/java/org/dash/wallet/integration/uphold/api/UpholdClientExt.kt rename to integrations/uphold/src/main/java/org/dash/wallet/integrations/uphold/api/UpholdClientExt.kt index 9bf4989d1a..fd41820bdd 100644 --- a/integrations/uphold/src/main/java/org/dash/wallet/integration/uphold/api/UpholdClientExt.kt +++ b/integrations/uphold/src/main/java/org/dash/wallet/integrations/uphold/api/UpholdClientExt.kt @@ -15,14 +15,14 @@ * along with this program. If not, see . */ -package org.dash.wallet.integration.uphold.api +package org.dash.wallet.integrations.uphold.api import android.content.SharedPreferences import org.dash.wallet.common.util.ensureSuccessful -import org.dash.wallet.integration.uphold.data.UpholdCard -import org.dash.wallet.integration.uphold.data.UpholdConstants -import org.dash.wallet.integration.uphold.data.UpholdCryptoCardAddress -import org.dash.wallet.integration.uphold.data.UpholdException +import org.dash.wallet.integrations.uphold.data.UpholdCard +import org.dash.wallet.integrations.uphold.data.UpholdConstants +import org.dash.wallet.integrations.uphold.data.UpholdCryptoCardAddress +import org.dash.wallet.integrations.uphold.data.UpholdException import retrofit2.Call import retrofit2.Callback import retrofit2.Response diff --git a/integrations/uphold/src/main/java/org/dash/wallet/integration/uphold/api/UpholdService.kt b/integrations/uphold/src/main/java/org/dash/wallet/integrations/uphold/api/UpholdService.kt similarity index 86% rename from integrations/uphold/src/main/java/org/dash/wallet/integration/uphold/api/UpholdService.kt rename to integrations/uphold/src/main/java/org/dash/wallet/integrations/uphold/api/UpholdService.kt index 4f232e7bea..80260d915d 100644 --- a/integrations/uphold/src/main/java/org/dash/wallet/integration/uphold/api/UpholdService.kt +++ b/integrations/uphold/src/main/java/org/dash/wallet/integrations/uphold/api/UpholdService.kt @@ -15,13 +15,13 @@ * along with this program. If not, see . */ -package org.dash.wallet.integration.uphold.api +package org.dash.wallet.integrations.uphold.api -import org.dash.wallet.integration.uphold.data.UpholdAccessToken -import org.dash.wallet.integration.uphold.data.UpholdCapability -import org.dash.wallet.integration.uphold.data.UpholdCard -import org.dash.wallet.integration.uphold.data.UpholdCryptoCardAddress -import org.dash.wallet.integration.uphold.data.UpholdTransaction +import org.dash.wallet.integrations.uphold.data.UpholdAccessToken +import org.dash.wallet.integrations.uphold.data.UpholdCapability +import org.dash.wallet.integrations.uphold.data.UpholdCard +import org.dash.wallet.integrations.uphold.data.UpholdCryptoCardAddress +import org.dash.wallet.integrations.uphold.data.UpholdTransaction import retrofit2.Call import retrofit2.Response import retrofit2.http.Body diff --git a/integrations/uphold/src/main/java/org/dash/wallet/integration/uphold/currencyModel/UpholdCurrencyResponse.kt b/integrations/uphold/src/main/java/org/dash/wallet/integrations/uphold/currencyModel/UpholdCurrencyResponse.kt similarity index 87% rename from integrations/uphold/src/main/java/org/dash/wallet/integration/uphold/currencyModel/UpholdCurrencyResponse.kt rename to integrations/uphold/src/main/java/org/dash/wallet/integrations/uphold/currencyModel/UpholdCurrencyResponse.kt index d2ac96f41a..84b1cadd44 100644 --- a/integrations/uphold/src/main/java/org/dash/wallet/integration/uphold/currencyModel/UpholdCurrencyResponse.kt +++ b/integrations/uphold/src/main/java/org/dash/wallet/integrations/uphold/currencyModel/UpholdCurrencyResponse.kt @@ -1,4 +1,4 @@ -package org.dash.wallet.integration.uphold.currencyModel +package org.dash.wallet.integrations.uphold.currencyModel import com.google.gson.annotations.SerializedName diff --git a/integrations/uphold/src/main/java/org/dash/wallet/integration/uphold/data/ForbiddenError.kt b/integrations/uphold/src/main/java/org/dash/wallet/integrations/uphold/data/ForbiddenError.kt similarity index 91% rename from integrations/uphold/src/main/java/org/dash/wallet/integration/uphold/data/ForbiddenError.kt rename to integrations/uphold/src/main/java/org/dash/wallet/integrations/uphold/data/ForbiddenError.kt index 9e4da08bd7..e3ef13f087 100644 --- a/integrations/uphold/src/main/java/org/dash/wallet/integration/uphold/data/ForbiddenError.kt +++ b/integrations/uphold/src/main/java/org/dash/wallet/integrations/uphold/data/ForbiddenError.kt @@ -15,9 +15,9 @@ * along with this program. If not, see . */ -package org.dash.wallet.integration.uphold.data +package org.dash.wallet.integrations.uphold.data -import org.dash.wallet.integration.uphold.R +import org.dash.wallet.integrations.uphold.R object ForbiddenError { val errorToMessageMap = mapOf( diff --git a/integrations/uphold/src/main/java/org/dash/wallet/integration/uphold/data/RequirementsCheckResult.kt b/integrations/uphold/src/main/java/org/dash/wallet/integrations/uphold/data/RequirementsCheckResult.kt similarity index 93% rename from integrations/uphold/src/main/java/org/dash/wallet/integration/uphold/data/RequirementsCheckResult.kt rename to integrations/uphold/src/main/java/org/dash/wallet/integrations/uphold/data/RequirementsCheckResult.kt index 8df0fc4595..e7c0edef18 100644 --- a/integrations/uphold/src/main/java/org/dash/wallet/integration/uphold/data/RequirementsCheckResult.kt +++ b/integrations/uphold/src/main/java/org/dash/wallet/integrations/uphold/data/RequirementsCheckResult.kt @@ -15,7 +15,7 @@ * along with this program. If not, see . */ -package org.dash.wallet.integration.uphold.data +package org.dash.wallet.integrations.uphold.data enum class RequirementsCheckResult { Satisfied, Resolve, DoNothing diff --git a/integrations/uphold/src/main/java/org/dash/wallet/integration/uphold/data/SupportedTopperAssets.kt b/integrations/uphold/src/main/java/org/dash/wallet/integrations/uphold/data/SupportedTopperAssets.kt similarity index 96% rename from integrations/uphold/src/main/java/org/dash/wallet/integration/uphold/data/SupportedTopperAssets.kt rename to integrations/uphold/src/main/java/org/dash/wallet/integrations/uphold/data/SupportedTopperAssets.kt index 44e4e2a385..6a2f5f31cf 100644 --- a/integrations/uphold/src/main/java/org/dash/wallet/integration/uphold/data/SupportedTopperAssets.kt +++ b/integrations/uphold/src/main/java/org/dash/wallet/integrations/uphold/data/SupportedTopperAssets.kt @@ -15,7 +15,7 @@ * along with this program. If not, see . */ -package org.dash.wallet.integration.uphold.data +package org.dash.wallet.integrations.uphold.data data class SupportedTopperAssets( val assets: Assets diff --git a/integrations/uphold/src/main/java/org/dash/wallet/integration/uphold/data/UpholdAccessToken.java b/integrations/uphold/src/main/java/org/dash/wallet/integrations/uphold/data/UpholdAccessToken.java similarity index 95% rename from integrations/uphold/src/main/java/org/dash/wallet/integration/uphold/data/UpholdAccessToken.java rename to integrations/uphold/src/main/java/org/dash/wallet/integrations/uphold/data/UpholdAccessToken.java index 5d8e320530..91ebc33555 100644 --- a/integrations/uphold/src/main/java/org/dash/wallet/integration/uphold/data/UpholdAccessToken.java +++ b/integrations/uphold/src/main/java/org/dash/wallet/integrations/uphold/data/UpholdAccessToken.java @@ -15,7 +15,7 @@ * along with this program. If not, see . */ -package org.dash.wallet.integration.uphold.data; +package org.dash.wallet.integrations.uphold.data; import com.google.gson.annotations.SerializedName; diff --git a/integrations/uphold/src/main/java/org/dash/wallet/integration/uphold/data/UpholdAddress.java b/integrations/uphold/src/main/java/org/dash/wallet/integrations/uphold/data/UpholdAddress.java similarity index 95% rename from integrations/uphold/src/main/java/org/dash/wallet/integration/uphold/data/UpholdAddress.java rename to integrations/uphold/src/main/java/org/dash/wallet/integrations/uphold/data/UpholdAddress.java index 04ae5ae1c7..fbcaef1d92 100644 --- a/integrations/uphold/src/main/java/org/dash/wallet/integration/uphold/data/UpholdAddress.java +++ b/integrations/uphold/src/main/java/org/dash/wallet/integrations/uphold/data/UpholdAddress.java @@ -15,7 +15,7 @@ * along with this program. If not, see . */ -package org.dash.wallet.integration.uphold.data; +package org.dash.wallet.integrations.uphold.data; public class UpholdAddress { diff --git a/integrations/uphold/src/main/java/org/dash/wallet/integration/uphold/data/UpholdApiException.java b/integrations/uphold/src/main/java/org/dash/wallet/integrations/uphold/data/UpholdApiException.java similarity index 99% rename from integrations/uphold/src/main/java/org/dash/wallet/integration/uphold/data/UpholdApiException.java rename to integrations/uphold/src/main/java/org/dash/wallet/integrations/uphold/data/UpholdApiException.java index c57b3c92a8..c541d9c304 100644 --- a/integrations/uphold/src/main/java/org/dash/wallet/integration/uphold/data/UpholdApiException.java +++ b/integrations/uphold/src/main/java/org/dash/wallet/integrations/uphold/data/UpholdApiException.java @@ -1,10 +1,10 @@ -package org.dash.wallet.integration.uphold.data; +package org.dash.wallet.integrations.uphold.data; import android.annotation.SuppressLint; import android.content.Context; -import org.dash.wallet.integration.uphold.R; +import org.dash.wallet.integrations.uphold.R; import org.json.JSONArray; import org.json.JSONException; import org.json.JSONObject; diff --git a/integrations/uphold/src/main/java/org/dash/wallet/integration/uphold/data/UpholdBankAccount.java b/integrations/uphold/src/main/java/org/dash/wallet/integrations/uphold/data/UpholdBankAccount.java similarity index 97% rename from integrations/uphold/src/main/java/org/dash/wallet/integration/uphold/data/UpholdBankAccount.java rename to integrations/uphold/src/main/java/org/dash/wallet/integrations/uphold/data/UpholdBankAccount.java index 2b8341e343..8940c8f25e 100644 --- a/integrations/uphold/src/main/java/org/dash/wallet/integration/uphold/data/UpholdBankAccount.java +++ b/integrations/uphold/src/main/java/org/dash/wallet/integrations/uphold/data/UpholdBankAccount.java @@ -15,7 +15,7 @@ * along with this program. If not, see . */ -package org.dash.wallet.integration.uphold.data; +package org.dash.wallet.integrations.uphold.data; public class UpholdBankAccount { diff --git a/integrations/uphold/src/main/java/org/dash/wallet/integration/uphold/data/UpholdCapability.kt b/integrations/uphold/src/main/java/org/dash/wallet/integrations/uphold/data/UpholdCapability.kt similarity index 95% rename from integrations/uphold/src/main/java/org/dash/wallet/integration/uphold/data/UpholdCapability.kt rename to integrations/uphold/src/main/java/org/dash/wallet/integrations/uphold/data/UpholdCapability.kt index ee36fdd67d..688bbffed2 100644 --- a/integrations/uphold/src/main/java/org/dash/wallet/integration/uphold/data/UpholdCapability.kt +++ b/integrations/uphold/src/main/java/org/dash/wallet/integrations/uphold/data/UpholdCapability.kt @@ -15,7 +15,7 @@ * along with this program. If not, see . */ -package org.dash.wallet.integration.uphold.data +package org.dash.wallet.integrations.uphold.data data class UpholdCapability( var key: String = "", diff --git a/integrations/uphold/src/main/java/org/dash/wallet/integration/uphold/data/UpholdCard.java b/integrations/uphold/src/main/java/org/dash/wallet/integrations/uphold/data/UpholdCard.java similarity index 97% rename from integrations/uphold/src/main/java/org/dash/wallet/integration/uphold/data/UpholdCard.java rename to integrations/uphold/src/main/java/org/dash/wallet/integrations/uphold/data/UpholdCard.java index 72cdcb1e3a..f6f50fcbb3 100644 --- a/integrations/uphold/src/main/java/org/dash/wallet/integration/uphold/data/UpholdCard.java +++ b/integrations/uphold/src/main/java/org/dash/wallet/integrations/uphold/data/UpholdCard.java @@ -15,7 +15,7 @@ * along with this program. If not, see . */ -package org.dash.wallet.integration.uphold.data; +package org.dash.wallet.integrations.uphold.data; import java.util.List; diff --git a/integrations/uphold/src/main/java/org/dash/wallet/integration/uphold/data/UpholdCardAddress.kt b/integrations/uphold/src/main/java/org/dash/wallet/integrations/uphold/data/UpholdCardAddress.kt similarity index 94% rename from integrations/uphold/src/main/java/org/dash/wallet/integration/uphold/data/UpholdCardAddress.kt rename to integrations/uphold/src/main/java/org/dash/wallet/integrations/uphold/data/UpholdCardAddress.kt index 741706ff31..c61aa71376 100644 --- a/integrations/uphold/src/main/java/org/dash/wallet/integration/uphold/data/UpholdCardAddress.kt +++ b/integrations/uphold/src/main/java/org/dash/wallet/integrations/uphold/data/UpholdCardAddress.kt @@ -15,7 +15,7 @@ * along with this program. If not, see . */ -package org.dash.wallet.integration.uphold.data +package org.dash.wallet.integrations.uphold.data import com.google.gson.annotations.SerializedName diff --git a/integrations/uphold/src/main/java/org/dash/wallet/integration/uphold/data/UpholdConstants.kt b/integrations/uphold/src/main/java/org/dash/wallet/integrations/uphold/data/UpholdConstants.kt similarity index 98% rename from integrations/uphold/src/main/java/org/dash/wallet/integration/uphold/data/UpholdConstants.kt rename to integrations/uphold/src/main/java/org/dash/wallet/integrations/uphold/data/UpholdConstants.kt index 08c959df5b..3000bdfc33 100644 --- a/integrations/uphold/src/main/java/org/dash/wallet/integration/uphold/data/UpholdConstants.kt +++ b/integrations/uphold/src/main/java/org/dash/wallet/integrations/uphold/data/UpholdConstants.kt @@ -15,7 +15,7 @@ * along with this program. If not, see . */ -package org.dash.wallet.integration.uphold.data +package org.dash.wallet.integrations.uphold.data object UpholdConstants { lateinit var CLIENT_ID: String diff --git a/integrations/uphold/src/main/java/org/dash/wallet/integration/uphold/data/UpholdCryptoCardAddress.java b/integrations/uphold/src/main/java/org/dash/wallet/integrations/uphold/data/UpholdCryptoCardAddress.java similarity index 95% rename from integrations/uphold/src/main/java/org/dash/wallet/integration/uphold/data/UpholdCryptoCardAddress.java rename to integrations/uphold/src/main/java/org/dash/wallet/integrations/uphold/data/UpholdCryptoCardAddress.java index 3e4886b86d..4925fa7a5a 100644 --- a/integrations/uphold/src/main/java/org/dash/wallet/integration/uphold/data/UpholdCryptoCardAddress.java +++ b/integrations/uphold/src/main/java/org/dash/wallet/integrations/uphold/data/UpholdCryptoCardAddress.java @@ -15,7 +15,7 @@ * along with this program. If not, see . */ -package org.dash.wallet.integration.uphold.data; +package org.dash.wallet.integrations.uphold.data; import com.google.gson.annotations.SerializedName; diff --git a/integrations/uphold/src/main/java/org/dash/wallet/integration/uphold/data/UpholdException.java b/integrations/uphold/src/main/java/org/dash/wallet/integrations/uphold/data/UpholdException.java similarity index 85% rename from integrations/uphold/src/main/java/org/dash/wallet/integration/uphold/data/UpholdException.java rename to integrations/uphold/src/main/java/org/dash/wallet/integrations/uphold/data/UpholdException.java index 87e165ad3f..0cbedb052a 100644 --- a/integrations/uphold/src/main/java/org/dash/wallet/integration/uphold/data/UpholdException.java +++ b/integrations/uphold/src/main/java/org/dash/wallet/integrations/uphold/data/UpholdException.java @@ -1,4 +1,4 @@ -package org.dash.wallet.integration.uphold.data; +package org.dash.wallet.integrations.uphold.data; public class UpholdException extends Exception { diff --git a/integrations/uphold/src/main/java/org/dash/wallet/integration/uphold/data/UpholdTransaction.java b/integrations/uphold/src/main/java/org/dash/wallet/integrations/uphold/data/UpholdTransaction.java similarity index 97% rename from integrations/uphold/src/main/java/org/dash/wallet/integration/uphold/data/UpholdTransaction.java rename to integrations/uphold/src/main/java/org/dash/wallet/integrations/uphold/data/UpholdTransaction.java index 7f0d8597c1..d949af429e 100644 --- a/integrations/uphold/src/main/java/org/dash/wallet/integration/uphold/data/UpholdTransaction.java +++ b/integrations/uphold/src/main/java/org/dash/wallet/integrations/uphold/data/UpholdTransaction.java @@ -15,7 +15,7 @@ * along with this program. If not, see . */ -package org.dash.wallet.integration.uphold.data; +package org.dash.wallet.integrations.uphold.data; import java.math.BigDecimal; diff --git a/integrations/uphold/src/main/java/org/dash/wallet/integration/uphold/ui/UpholdOtpDialog.java b/integrations/uphold/src/main/java/org/dash/wallet/integrations/uphold/ui/UpholdOtpDialog.java similarity index 94% rename from integrations/uphold/src/main/java/org/dash/wallet/integration/uphold/ui/UpholdOtpDialog.java rename to integrations/uphold/src/main/java/org/dash/wallet/integrations/uphold/ui/UpholdOtpDialog.java index a60db39cf2..2a53c6c5c6 100644 --- a/integrations/uphold/src/main/java/org/dash/wallet/integration/uphold/ui/UpholdOtpDialog.java +++ b/integrations/uphold/src/main/java/org/dash/wallet/integrations/uphold/ui/UpholdOtpDialog.java @@ -15,7 +15,7 @@ * along with this program. If not, see . */ -package org.dash.wallet.integration.uphold.ui; +package org.dash.wallet.integrations.uphold.ui; import android.app.Activity; import android.app.Dialog; @@ -28,8 +28,8 @@ import androidx.fragment.app.FragmentManager; import org.dash.wallet.common.ui.BaseAlertDialogBuilder; -import org.dash.wallet.integration.uphold.R; -import org.dash.wallet.integration.uphold.api.UpholdClient; +import org.dash.wallet.integrations.uphold.R; +import org.dash.wallet.integrations.uphold.api.UpholdClient; import kotlin.Unit; diff --git a/integrations/uphold/src/main/java/org/dash/wallet/integration/uphold/ui/UpholdPortalFragment.kt b/integrations/uphold/src/main/java/org/dash/wallet/integrations/uphold/ui/UpholdPortalFragment.kt similarity index 97% rename from integrations/uphold/src/main/java/org/dash/wallet/integration/uphold/ui/UpholdPortalFragment.kt rename to integrations/uphold/src/main/java/org/dash/wallet/integrations/uphold/ui/UpholdPortalFragment.kt index 05a111b127..5f924c4c55 100644 --- a/integrations/uphold/src/main/java/org/dash/wallet/integration/uphold/ui/UpholdPortalFragment.kt +++ b/integrations/uphold/src/main/java/org/dash/wallet/integrations/uphold/ui/UpholdPortalFragment.kt @@ -15,7 +15,7 @@ * along with this program. If not, see . */ -package org.dash.wallet.integration.uphold.ui +package org.dash.wallet.integrations.uphold.ui import android.animation.ObjectAnimator import android.content.BroadcastReceiver @@ -42,9 +42,9 @@ import org.dash.wallet.common.ui.viewBinding import org.dash.wallet.common.util.observe import org.dash.wallet.common.util.openCustomTab import org.dash.wallet.common.util.toFormattedString -import org.dash.wallet.integration.uphold.R -import org.dash.wallet.integration.uphold.data.RequirementsCheckResult -import org.dash.wallet.integration.uphold.data.UpholdConstants +import org.dash.wallet.integrations.uphold.R +import org.dash.wallet.integrations.uphold.data.RequirementsCheckResult +import org.dash.wallet.integrations.uphold.data.UpholdConstants @AndroidEntryPoint class UpholdPortalFragment : Fragment(R.layout.fragment_integration_portal) { diff --git a/integrations/uphold/src/main/java/org/dash/wallet/integration/uphold/ui/UpholdTransferActivity.kt b/integrations/uphold/src/main/java/org/dash/wallet/integrations/uphold/ui/UpholdTransferActivity.kt similarity index 95% rename from integrations/uphold/src/main/java/org/dash/wallet/integration/uphold/ui/UpholdTransferActivity.kt rename to integrations/uphold/src/main/java/org/dash/wallet/integrations/uphold/ui/UpholdTransferActivity.kt index 7ce50b3444..09b509c759 100644 --- a/integrations/uphold/src/main/java/org/dash/wallet/integration/uphold/ui/UpholdTransferActivity.kt +++ b/integrations/uphold/src/main/java/org/dash/wallet/integrations/uphold/ui/UpholdTransferActivity.kt @@ -15,7 +15,7 @@ * along with this program. If not, see . */ -package org.dash.wallet.integration.uphold.ui +package org.dash.wallet.integrations.uphold.ui import android.content.Context import android.content.Intent @@ -43,11 +43,11 @@ import org.dash.wallet.common.services.TransactionMetadataProvider import org.dash.wallet.common.ui.enter_amount.EnterAmountFragment import org.dash.wallet.common.ui.enter_amount.EnterAmountViewModel import org.dash.wallet.common.util.openCustomTab -import org.dash.wallet.integration.uphold.R -import org.dash.wallet.integration.uphold.data.RequirementsCheckResult -import org.dash.wallet.integration.uphold.data.UpholdConstants -import org.dash.wallet.integration.uphold.data.UpholdTransaction -import org.dash.wallet.integration.uphold.ui.UpholdWithdrawalHelper.OnTransferListener +import org.dash.wallet.integrations.uphold.R +import org.dash.wallet.integrations.uphold.data.RequirementsCheckResult +import org.dash.wallet.integrations.uphold.data.UpholdConstants +import org.dash.wallet.integrations.uphold.data.UpholdTransaction +import org.dash.wallet.integrations.uphold.ui.UpholdWithdrawalHelper.OnTransferListener import java.math.BigDecimal import javax.inject.Inject diff --git a/integrations/uphold/src/main/java/org/dash/wallet/integration/uphold/ui/UpholdViewModel.kt b/integrations/uphold/src/main/java/org/dash/wallet/integrations/uphold/ui/UpholdViewModel.kt similarity index 91% rename from integrations/uphold/src/main/java/org/dash/wallet/integration/uphold/ui/UpholdViewModel.kt rename to integrations/uphold/src/main/java/org/dash/wallet/integrations/uphold/ui/UpholdViewModel.kt index 47433625dd..6a4cac87b9 100644 --- a/integrations/uphold/src/main/java/org/dash/wallet/integration/uphold/ui/UpholdViewModel.kt +++ b/integrations/uphold/src/main/java/org/dash/wallet/integrations/uphold/ui/UpholdViewModel.kt @@ -15,7 +15,7 @@ * along with this program. If not, see . */ -package org.dash.wallet.integration.uphold.ui +package org.dash.wallet.integrations.uphold.ui import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope @@ -42,16 +42,16 @@ import org.dash.wallet.common.services.ExchangeRatesProvider import org.dash.wallet.common.services.analytics.AnalyticsConstants import org.dash.wallet.common.services.analytics.AnalyticsService import org.dash.wallet.common.util.toCoin -import org.dash.wallet.integration.uphold.api.TopperClient -import org.dash.wallet.integration.uphold.api.UpholdClient -import org.dash.wallet.integration.uphold.api.checkCapabilities -import org.dash.wallet.integration.uphold.api.getAccessToken -import org.dash.wallet.integration.uphold.api.getDashBalance -import org.dash.wallet.integration.uphold.api.isAuthenticated -import org.dash.wallet.integration.uphold.api.preferences -import org.dash.wallet.integration.uphold.api.revokeAccessToken -import org.dash.wallet.integration.uphold.data.UpholdConstants -import org.dash.wallet.integration.uphold.data.UpholdException +import org.dash.wallet.integrations.uphold.api.TopperClient +import org.dash.wallet.integrations.uphold.api.UpholdClient +import org.dash.wallet.integrations.uphold.api.checkCapabilities +import org.dash.wallet.integrations.uphold.api.getAccessToken +import org.dash.wallet.integrations.uphold.api.getDashBalance +import org.dash.wallet.integrations.uphold.api.isAuthenticated +import org.dash.wallet.integrations.uphold.api.preferences +import org.dash.wallet.integrations.uphold.api.revokeAccessToken +import org.dash.wallet.integrations.uphold.data.UpholdConstants +import org.dash.wallet.integrations.uphold.data.UpholdException import org.slf4j.LoggerFactory import retrofit2.HttpException import javax.inject.Inject diff --git a/integrations/uphold/src/main/java/org/dash/wallet/integration/uphold/ui/UpholdWithdrawalHelper.java b/integrations/uphold/src/main/java/org/dash/wallet/integrations/uphold/ui/UpholdWithdrawalHelper.java similarity index 93% rename from integrations/uphold/src/main/java/org/dash/wallet/integration/uphold/ui/UpholdWithdrawalHelper.java rename to integrations/uphold/src/main/java/org/dash/wallet/integrations/uphold/ui/UpholdWithdrawalHelper.java index 2fcbc14805..fdd5d184f0 100644 --- a/integrations/uphold/src/main/java/org/dash/wallet/integration/uphold/ui/UpholdWithdrawalHelper.java +++ b/integrations/uphold/src/main/java/org/dash/wallet/integrations/uphold/ui/UpholdWithdrawalHelper.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package org.dash.wallet.integration.uphold.ui; +package org.dash.wallet.integrations.uphold.ui; import android.annotation.SuppressLint; import android.app.ProgressDialog; @@ -26,14 +26,14 @@ import org.dash.wallet.common.ui.BaseAlertDialogBuilder; import org.dash.wallet.common.ui.dialogs.AdaptiveDialog; -import org.dash.wallet.integration.uphold.R; -import org.dash.wallet.integration.uphold.api.UpholdClientExtKt; -import org.dash.wallet.integration.uphold.data.ForbiddenError; -import org.dash.wallet.integration.uphold.data.RequirementsCheckResult; -import org.dash.wallet.integration.uphold.data.UpholdApiException; -import org.dash.wallet.integration.uphold.api.UpholdClient; -import org.dash.wallet.integration.uphold.data.UpholdConstants; -import org.dash.wallet.integration.uphold.data.UpholdTransaction; +import org.dash.wallet.integrations.uphold.R; +import org.dash.wallet.integrations.uphold.api.UpholdClientExtKt; +import org.dash.wallet.integrations.uphold.data.ForbiddenError; +import org.dash.wallet.integrations.uphold.data.RequirementsCheckResult; +import org.dash.wallet.integrations.uphold.data.UpholdApiException; +import org.dash.wallet.integrations.uphold.api.UpholdClient; +import org.dash.wallet.integrations.uphold.data.UpholdConstants; +import org.dash.wallet.integrations.uphold.data.UpholdTransaction; import java.math.BigDecimal; import java.util.List; import java.util.Map; diff --git a/integrations/uphold/src/test/java/org/dash/wallet/integration/uphold/UpholdClientTest.kt b/integrations/uphold/src/test/java/org/dash/wallet/integrations/uphold/UpholdClientTest.kt similarity index 94% rename from integrations/uphold/src/test/java/org/dash/wallet/integration/uphold/UpholdClientTest.kt rename to integrations/uphold/src/test/java/org/dash/wallet/integrations/uphold/UpholdClientTest.kt index 62928784c3..2fe64121c1 100644 --- a/integrations/uphold/src/test/java/org/dash/wallet/integration/uphold/UpholdClientTest.kt +++ b/integrations/uphold/src/test/java/org/dash/wallet/integrations/uphold/UpholdClientTest.kt @@ -15,16 +15,16 @@ * along with this program. If not, see . */ -package org.dash.wallet.integration.uphold +package org.dash.wallet.integrations.uphold import junit.framework.TestCase.assertEquals import kotlinx.coroutines.test.runTest import okhttp3.OkHttpClient import okhttp3.mockwebserver.MockResponse import okhttp3.mockwebserver.MockWebServer -import org.dash.wallet.integration.uphold.api.RemoteDataSource -import org.dash.wallet.integration.uphold.api.UpholdService -import org.dash.wallet.integration.uphold.data.UpholdCapability +import org.dash.wallet.integrations.uphold.api.RemoteDataSource +import org.dash.wallet.integrations.uphold.api.UpholdService +import org.dash.wallet.integrations.uphold.data.UpholdCapability import org.junit.After import org.junit.Before import org.junit.Test diff --git a/integrations/uphold/src/test/java/org/dash/wallet/integration/uphold/UpholdErrorTest.kt b/integrations/uphold/src/test/java/org/dash/wallet/integrations/uphold/UpholdErrorTest.kt similarity index 99% rename from integrations/uphold/src/test/java/org/dash/wallet/integration/uphold/UpholdErrorTest.kt rename to integrations/uphold/src/test/java/org/dash/wallet/integrations/uphold/UpholdErrorTest.kt index 30c4ecbfb8..2bedec5019 100644 --- a/integrations/uphold/src/test/java/org/dash/wallet/integration/uphold/UpholdErrorTest.kt +++ b/integrations/uphold/src/test/java/org/dash/wallet/integrations/uphold/UpholdErrorTest.kt @@ -1,7 +1,7 @@ -package org.dash.wallet.integration.uphold +package org.dash.wallet.integrations.uphold import android.content.Context -import org.dash.wallet.integration.uphold.data.UpholdApiException +import org.dash.wallet.integrations.uphold.data.UpholdApiException import org.junit.Assert.* import org.junit.Test import org.mockito.ArgumentMatchers.anyString diff --git a/wallet/AndroidManifest.xml b/wallet/AndroidManifest.xml index 6baed32d4c..eee828ede5 100644 --- a/wallet/AndroidManifest.xml +++ b/wallet/AndroidManifest.xml @@ -275,7 +275,7 @@ android:theme="@style/LockScreenActivity.Theme" /> @@ -363,12 +363,7 @@ android:value="true" /> - - diff --git a/wallet/res/layout/activity_coinbase.xml b/wallet/res/layout/activity_coinbase.xml deleted file mode 100644 index 6fc45292f9..0000000000 --- a/wallet/res/layout/activity_coinbase.xml +++ /dev/null @@ -1,32 +0,0 @@ - - - - - diff --git a/wallet/res/navigation/nav_home.xml b/wallet/res/navigation/nav_home.xml index 360a19ed2e..89ce440dc2 100644 --- a/wallet/res/navigation/nav_home.xml +++ b/wallet/res/navigation/nav_home.xml @@ -201,15 +201,21 @@ + + @@ -224,11 +230,19 @@ android:name="service" android:defaultValue="COINBASE" app:argType="de.schildbach.wallet.data.ServiceType" /> + + @@ -271,4 +285,5 @@ tools:layout="@layout/fragment_address_input" /> + \ No newline at end of file diff --git a/wallet/res/values/strings.xml b/wallet/res/values/strings.xml index 57c3db9737..a2e954df11 100644 --- a/wallet/res/values/strings.xml +++ b/wallet/res/values/strings.xml @@ -377,9 +377,6 @@ Reason Link your account Buy Dash · No account needed - Your Coinbase session has expired - Please log in to your Coinbase account - Log In Recipient Address Show content in the clipboard diff --git a/wallet/src/de/schildbach/wallet/WalletApplication.java b/wallet/src/de/schildbach/wallet/WalletApplication.java index ad4d77115f..71cb777ce1 100644 --- a/wallet/src/de/schildbach/wallet/WalletApplication.java +++ b/wallet/src/de/schildbach/wallet/WalletApplication.java @@ -62,9 +62,6 @@ import org.bitcoinj.core.VerificationException; import org.bitcoinj.crypto.LinuxSecureRandom; import org.bitcoinj.utils.Threading; -import org.bitcoinj.core.VersionMessage; -import org.bitcoinj.crypto.LinuxSecureRandom; -import org.bitcoinj.utils.Threading; import org.bitcoinj.wallet.AuthenticationKeyChain; import org.bitcoinj.wallet.CoinSelector; import org.bitcoinj.wallet.Protos; @@ -84,13 +81,13 @@ import org.dash.wallet.features.exploredash.ExploreSyncWorker; import org.dash.wallet.common.services.TransactionMetadataProvider; import org.dash.wallet.features.exploredash.utils.DashDirectConstants; -import org.dash.wallet.integration.coinbase_integration.service.CoinBaseClientConstants; +import org.dash.wallet.integrations.coinbase.service.CoinBaseClientConstants; import de.schildbach.wallet.service.PackageInfoProvider; import de.schildbach.wallet.transactions.MasternodeObserver; import de.schildbach.wallet.ui.buy_sell.LiquidClient; -import org.dash.wallet.integration.uphold.api.UpholdClient; -import org.dash.wallet.integration.uphold.data.UpholdConstants; +import org.dash.wallet.integrations.uphold.api.UpholdClient; +import org.dash.wallet.integrations.uphold.data.UpholdConstants; import org.dash.wallet.integrations.crowdnode.utils.CrowdNodeConfig; import org.dash.wallet.integrations.crowdnode.utils.CrowdNodeBalanceCondition; import org.jetbrains.annotations.NotNull; diff --git a/wallet/src/de/schildbach/wallet/di/AppModule.kt b/wallet/src/de/schildbach/wallet/di/AppModule.kt index 76a0d7155e..6498ba910e 100644 --- a/wallet/src/de/schildbach/wallet/di/AppModule.kt +++ b/wallet/src/de/schildbach/wallet/di/AppModule.kt @@ -44,7 +44,7 @@ import org.dash.wallet.common.services.NotificationService import org.dash.wallet.common.services.SendPaymentService import org.dash.wallet.common.services.analytics.AnalyticsService import org.dash.wallet.common.services.analytics.FirebaseAnalyticsServiceImpl -import org.dash.wallet.integration.uphold.api.UpholdClient +import org.dash.wallet.integrations.uphold.api.UpholdClient import javax.inject.Singleton @Module diff --git a/wallet/src/de/schildbach/wallet/di/IntegrationModule.kt b/wallet/src/de/schildbach/wallet/di/IntegrationModule.kt index 54869672b3..7d63cc97d8 100644 --- a/wallet/src/de/schildbach/wallet/di/IntegrationModule.kt +++ b/wallet/src/de/schildbach/wallet/di/IntegrationModule.kt @@ -24,7 +24,7 @@ import dagger.hilt.components.SingletonComponent import de.schildbach.wallet.Constants import de.schildbach.wallet_test.BuildConfig import org.bitcoinj.core.NetworkParameters -import org.dash.wallet.integration.uphold.api.TopperClient +import org.dash.wallet.integrations.uphold.api.TopperClient import javax.inject.Singleton @Module diff --git a/wallet/src/de/schildbach/wallet/ui/LockScreenActivity.kt b/wallet/src/de/schildbach/wallet/ui/LockScreenActivity.kt index 6f6fa059e4..eeacf08663 100644 --- a/wallet/src/de/schildbach/wallet/ui/LockScreenActivity.kt +++ b/wallet/src/de/schildbach/wallet/ui/LockScreenActivity.kt @@ -58,6 +58,7 @@ import org.dash.wallet.common.Configuration import org.dash.wallet.common.SecureActivity import org.dash.wallet.common.WalletDataProvider import org.dash.wallet.common.services.LockScreenBroadcaster +import org.dash.wallet.common.ui.LockScreenAware import org.dash.wallet.common.ui.dialogs.AdaptiveDialog import org.dash.wallet.common.ui.dismissDialog import org.dash.wallet.common.ui.enter_amount.NumericKeyboardView @@ -486,7 +487,7 @@ open class LockScreenActivity : SecureActivity() { alertDialog.dismissDialog() } lockScreenBroadcaster.activatingLockScreen.call() - dismissDialogFragments(supportFragmentManager) + notifyAndDismissFragments(supportFragmentManager) onLockScreenActivated() } @@ -494,16 +495,20 @@ open class LockScreenActivity : SecureActivity() { open fun onLockScreenDeactivated() { } - private fun dismissDialogFragments(fragmentManager: FragmentManager) { + private fun notifyAndDismissFragments(fragmentManager: FragmentManager) { fragmentManager.fragments .takeIf { it.isNotEmpty() } ?.forEach { fragment -> // check to see if the activity is valid and fragment is added to its activity if (fragment.activity != null && fragment.isAdded) { + if (fragment is LockScreenAware) { + fragment.onLockScreenActivated() + } + if (fragment is DialogFragment) { fragment.dismissAllowingStateLoss() } else if (fragment is NavHostFragment) { - dismissDialogFragments(fragment.childFragmentManager) + notifyAndDismissFragments(fragment.childFragmentManager) } } } diff --git a/wallet/src/de/schildbach/wallet/ui/WalletUriHandlerActivity.java b/wallet/src/de/schildbach/wallet/ui/WalletUriHandlerActivity.java index 431f379f42..5b2362444e 100644 --- a/wallet/src/de/schildbach/wallet/ui/WalletUriHandlerActivity.java +++ b/wallet/src/de/schildbach/wallet/ui/WalletUriHandlerActivity.java @@ -30,7 +30,7 @@ import org.bitcoinj.core.Address; import org.bitcoinj.wallet.Wallet; import org.dash.wallet.common.ui.BaseAlertDialogBuilder; -import org.dash.wallet.integration.uphold.ui.UpholdPortalFragment; +import org.dash.wallet.integrations.uphold.ui.UpholdPortalFragment; import org.slf4j.Logger; import org.slf4j.LoggerFactory; diff --git a/wallet/src/de/schildbach/wallet/ui/buy_sell/BuyAndSellIntegrationsFragment.kt b/wallet/src/de/schildbach/wallet/ui/buy_sell/BuyAndSellIntegrationsFragment.kt index 00e34db54c..9be061e2ef 100644 --- a/wallet/src/de/schildbach/wallet/ui/buy_sell/BuyAndSellIntegrationsFragment.kt +++ b/wallet/src/de/schildbach/wallet/ui/buy_sell/BuyAndSellIntegrationsFragment.kt @@ -17,10 +17,8 @@ package de.schildbach.wallet.ui.buy_sell -import android.content.Intent import android.os.Bundle import android.view.View -import androidx.activity.result.contract.ActivityResultContracts import androidx.core.view.isVisible import androidx.fragment.app.Fragment import androidx.fragment.app.viewModels @@ -29,14 +27,12 @@ import androidx.navigation.fragment.findNavController import dagger.hilt.android.AndroidEntryPoint import de.schildbach.wallet.adapter.BuyAndSellDashServicesAdapter import de.schildbach.wallet.data.ServiceType -import de.schildbach.wallet.ui.coinbase.CoinbaseActivity import de.schildbach.wallet_test.R import de.schildbach.wallet_test.databinding.FragmentBuySellIntegrationsBinding import kotlinx.coroutines.launch import org.dash.wallet.common.services.analytics.AnalyticsConstants import org.dash.wallet.common.ui.dialogs.AdaptiveDialog import org.dash.wallet.common.ui.viewBinding -import org.dash.wallet.common.util.Constants import org.dash.wallet.common.util.observe import org.dash.wallet.common.util.openCustomTab import org.dash.wallet.common.util.safeNavigate @@ -61,14 +57,6 @@ class BuyAndSellIntegrationsFragment : Fragment(R.layout.fragment_buy_sell_integ } } - private val coinbaseLauncher = registerForActivityResult( - ActivityResultContracts.StartActivityForResult() - ) { result -> - if (result.resultCode == Constants.RESULT_CODE_GO_HOME) { - findNavController().popBackStack(R.id.walletFragment, false) - } - } - override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) log.info("starting Buy and Sell Dash fragment") @@ -115,7 +103,7 @@ class BuyAndSellIntegrationsFragment : Fragment(R.layout.fragment_buy_sell_integ viewModel.logEnterCoinbase() if (viewModel.isCoinbaseAuthenticated) { - coinbaseLauncher.launch(Intent(requireContext(), CoinbaseActivity::class.java)) + safeNavigate(BuyAndSellIntegrationsFragmentDirections.buySellToCoinbase()) } else { safeNavigate(BuyAndSellIntegrationsFragmentDirections.buySellToOverview(ServiceType.COINBASE)) } diff --git a/wallet/src/de/schildbach/wallet/ui/buy_sell/BuyAndSellViewModel.kt b/wallet/src/de/schildbach/wallet/ui/buy_sell/BuyAndSellViewModel.kt index e7b823373f..1ed1901599 100644 --- a/wallet/src/de/schildbach/wallet/ui/buy_sell/BuyAndSellViewModel.kt +++ b/wallet/src/de/schildbach/wallet/ui/buy_sell/BuyAndSellViewModel.kt @@ -35,19 +35,18 @@ import org.bitcoinj.core.Coin import org.bitcoinj.utils.ExchangeRate import org.dash.wallet.common.Configuration import org.dash.wallet.common.WalletDataProvider -import org.dash.wallet.common.data.ResponseResource import org.dash.wallet.common.data.WalletUIConfig import org.dash.wallet.common.services.ExchangeRatesProvider import org.dash.wallet.common.services.NetworkStateInt import org.dash.wallet.common.services.analytics.AnalyticsConstants import org.dash.wallet.common.services.analytics.AnalyticsService -import org.dash.wallet.integration.coinbase_integration.repository.CoinBaseRepository -import org.dash.wallet.integration.coinbase_integration.utils.CoinbaseConfig -import org.dash.wallet.integration.uphold.api.TopperClient -import org.dash.wallet.integration.uphold.api.UpholdClient -import org.dash.wallet.integration.uphold.api.getDashBalance -import org.dash.wallet.integration.uphold.api.hasValidCredentials -import org.dash.wallet.integration.uphold.api.isAuthenticated +import org.dash.wallet.integrations.coinbase.repository.CoinBaseRepository +import org.dash.wallet.integrations.coinbase.utils.CoinbaseConfig +import org.dash.wallet.integrations.uphold.api.TopperClient +import org.dash.wallet.integrations.uphold.api.UpholdClient +import org.dash.wallet.integrations.uphold.api.getDashBalance +import org.dash.wallet.integrations.uphold.api.hasValidCredentials +import org.dash.wallet.integrations.uphold.api.isAuthenticated import javax.inject.Inject /** @@ -202,16 +201,13 @@ class BuyAndSellViewModel @Inject constructor( private fun updateCoinbaseBalance() { viewModelScope.launch { - when (val response = coinBaseRepository.getUserAccount()) { - is ResponseResource.Success -> { - response.value?.balance?.amount?.let { - coinbaseConfig.set(CoinbaseConfig.LAST_BALANCE, Coin.parseCoin(it).value) - } - showRowBalance(ServiceType.COINBASE, response.value?.balance?.amount ?: coinbaseBalanceString()) - } - is ResponseResource.Failure -> { - showRowBalance(ServiceType.COINBASE, coinbaseBalanceString()) - } + try { + val account = coinBaseRepository.getUserAccount() + val balance = account.availableBalance.value + coinbaseConfig.set(CoinbaseConfig.LAST_BALANCE, Coin.parseCoin(balance).value) + showRowBalance(ServiceType.COINBASE, balance) + } catch (ex: Exception) { + showRowBalance(ServiceType.COINBASE, coinbaseBalanceString()) } } } diff --git a/wallet/src/de/schildbach/wallet/ui/buy_sell/IntegrationOverviewFragment.kt b/wallet/src/de/schildbach/wallet/ui/buy_sell/IntegrationOverviewFragment.kt index 9f3f6129f9..cb68ba1df2 100644 --- a/wallet/src/de/schildbach/wallet/ui/buy_sell/IntegrationOverviewFragment.kt +++ b/wallet/src/de/schildbach/wallet/ui/buy_sell/IntegrationOverviewFragment.kt @@ -27,16 +27,14 @@ import androidx.fragment.app.viewModels import androidx.lifecycle.lifecycleScope import androidx.navigation.fragment.findNavController import dagger.hilt.android.AndroidEntryPoint -import de.schildbach.wallet.ui.coinbase.CoinBaseWebClientActivity -import de.schildbach.wallet.ui.coinbase.CoinbaseActivity +import org.dash.wallet.integrations.coinbase.ui.CoinBaseWebClientActivity import de.schildbach.wallet_test.R import de.schildbach.wallet_test.databinding.FragmentIntegrationOverviewBinding -import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.launch import org.dash.wallet.common.ui.dialogs.AdaptiveDialog import org.dash.wallet.common.ui.viewBinding +import org.dash.wallet.common.util.safeNavigate -@ExperimentalCoroutinesApi @AndroidEntryPoint class IntegrationOverviewFragment : Fragment(R.layout.fragment_integration_overview) { private val binding by viewBinding(FragmentIntegrationOverviewBinding::bind) @@ -95,8 +93,7 @@ class IntegrationOverviewFragment : Fragment(R.layout.fragment_integration_overv } if (success) { - startActivity(Intent(requireContext(), CoinbaseActivity::class.java)) - findNavController().popBackStack() + safeNavigate(IntegrationOverviewFragmentDirections.overviewToCoinbase()) } else { AdaptiveDialog.create( R.drawable.ic_error, diff --git a/wallet/src/de/schildbach/wallet/ui/buy_sell/IntegrationOverviewViewModel.kt b/wallet/src/de/schildbach/wallet/ui/buy_sell/IntegrationOverviewViewModel.kt index 1ecacf4075..ff9f16f3b6 100644 --- a/wallet/src/de/schildbach/wallet/ui/buy_sell/IntegrationOverviewViewModel.kt +++ b/wallet/src/de/schildbach/wallet/ui/buy_sell/IntegrationOverviewViewModel.kt @@ -21,8 +21,8 @@ import androidx.lifecycle.ViewModel import dagger.hilt.android.lifecycle.HiltViewModel import org.dash.wallet.common.data.ResponseResource import org.dash.wallet.common.services.analytics.AnalyticsService -import org.dash.wallet.integration.coinbase_integration.repository.CoinBaseRepositoryInt -import org.dash.wallet.integration.coinbase_integration.utils.CoinbaseConfig +import org.dash.wallet.integrations.coinbase.repository.CoinBaseRepositoryInt +import org.dash.wallet.integrations.coinbase.utils.CoinbaseConfig import org.slf4j.LoggerFactory import javax.inject.Inject diff --git a/wallet/src/de/schildbach/wallet/ui/coinbase/CoinbaseActivity.kt b/wallet/src/de/schildbach/wallet/ui/coinbase/CoinbaseActivity.kt deleted file mode 100644 index 7d30f1276a..0000000000 --- a/wallet/src/de/schildbach/wallet/ui/coinbase/CoinbaseActivity.kt +++ /dev/null @@ -1,143 +0,0 @@ -/* - * Copyright 2021 Dash Core Group. - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -package de.schildbach.wallet.ui.coinbase - -import android.app.Activity -import android.content.Intent -import android.os.Bundle -import androidx.activity.result.contract.ActivityResultContracts -import androidx.activity.viewModels -import androidx.lifecycle.lifecycleScope -import androidx.navigation.NavController -import androidx.navigation.fragment.NavHostFragment -import dagger.hilt.android.AndroidEntryPoint -import de.schildbach.wallet.ui.BaseMenuActivity -import de.schildbach.wallet_test.R -import org.dash.wallet.common.ui.dialogs.AdaptiveDialog -import org.dash.wallet.integration.coinbase_integration.viewmodels.CoinbaseActivityViewModel - -@AndroidEntryPoint -class CoinbaseActivity : BaseMenuActivity() { - private val viewModel: CoinbaseActivityViewModel by viewModels() - private lateinit var navController: NavController - - override fun getLayoutId(): Int { - return R.layout.activity_coinbase - } - - private val coinbaseAuthLauncher = registerForActivityResult( - ActivityResultContracts.StartActivityForResult() - ) { result -> - val data = result.data - - if (result.resultCode == Activity.RESULT_OK) { - data?.extras?.getString(CoinBaseWebClientActivity.RESULT_TEXT)?.let { code -> - handleCoinbaseAuthResult(code) - } - } - } - - private fun handleCoinbaseAuthResult(code: String) { - lifecycleScope.launchWhenResumed { - val success = AdaptiveDialog.withProgress(getString(R.string.loading), this@CoinbaseActivity) { - viewModel.loginToCoinbase(code) - } - - if (success) { - val intent = intent - intent.addFlags(Intent.FLAG_ACTIVITY_NO_ANIMATION) - intent.addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP) - finish() - overridePendingTransition(0, 0) - startActivity(intent) - overridePendingTransition(0, 0) - } else { - AdaptiveDialog.create( - R.drawable.ic_error, - getString(R.string.login_error_title, getString(R.string.coinbase)), - getString(R.string.login_error_message, getString(R.string.coinbase)), - getString(android.R.string.cancel), - getString(R.string.retry) - ).show(this@CoinbaseActivity) { retry -> - if (retry == true) { - handleCoinbaseAuthResult(code) - } - } - } - } - } - - private fun continueCoinbase() { - coinbaseAuthLauncher.launch( - Intent( - this, - CoinBaseWebClientActivity::class.java - ) - ) - } - - private val reLoginDialog: AdaptiveDialog by lazy { - AdaptiveDialog.create( - R.drawable.ic_relogin, - getString(R.string.your_coinbase_session_has_expired), - getString(R.string.please_log_in_to_your_coinbase_account), - getString(R.string.cancel), - getString(R.string.log_in) - ).also { - it.isCancelable = false - } - } - override fun onCreate(savedInstanceState: Bundle?) { - super.onCreate(savedInstanceState) - - navController = setNavigationGraph() - - viewModel.coinbaseLogOutCallback.observe(this) { - if (reLoginDialog.isVisible) { - reLoginDialog.dismiss() - } - if (it == true) { - reLoginDialog.show(this) { login -> - if (login == true) { - continueCoinbase() - } else { - finish() - } - } - } - } - - viewModel.getPaymentMethods() - viewModel.getBaseIdForFaitModel() - } - - private fun setNavigationGraph(): NavController { - val navHostFragment = - supportFragmentManager.findFragmentById(R.id.nav_host_coinbase_fragment) as NavHostFragment - return navHostFragment.navController - } - - override fun onLockScreenActivated() { - if (navController.currentDestination?.id == R.id.enterTwoFaCodeFragment) { - navController.popBackStack( - org.dash.wallet.integration.coinbase_integration.R.id.coinbaseServicesFragment, - false - ) - } - } -} From d2ddd17fe7869a4c3a86a067eb176ade20023724 Mon Sep 17 00:00:00 2001 From: HashEngineering Date: Thu, 5 Oct 2023 08:09:07 -0700 Subject: [PATCH 04/28] fix: address restore masternode keys issues (#1211) * feat: add WalletFactory with tests * feat: use WalletFactory for Onboarding * chore remove obsolete functions and files * chore: remove commented code * fix: check for missing authGroupExt and add comments * fix: add license and remove commented lines --- .../dash/wallet/common/WalletDataProvider.kt | 2 - .../schildbach/wallet/WalletApplication.java | 71 +-- .../src/de/schildbach/wallet/di/AppModule.kt | 3 + .../wallet/service/WalletFactory.kt | 390 ++++++++++++++++ .../wallet/ui/OnboardingViewModel.kt | 26 +- .../ui/RestoreWalletFromFileViewModel.kt | 42 +- .../RestoreWalletFromSeedDialogFragment.java | 12 +- .../ui/RestoreWalletFromSeedViewModel.kt | 7 +- .../ui/backup/BackupWalletDialogFragment.java | 11 +- .../ui/backup/OnRestoreWalletListener.java | 25 ++ .../ui/backup/RestoreFromFileActivity.kt | 12 +- .../ui/backup/RestoreFromFileHelper.java | 171 -------- .../backup/RestoreWalletDialogFragment.java | 135 +----- .../MasternodeKeysViewModel.kt | 1 + .../schildbach/wallet/util/WalletUtils.java | 154 ------- .../wallet/util/WalletUtilsTest.java | 69 --- .../wallet/util/backup-protobuf-testnet | Bin 24647 -> 0 bytes .../util/bitcoin-wallet-backup-testnet-3.50 | 4 - .../bitcoin-wallet-backup-testnet-3.50-crlf | 4 - .../wallet/util/services/WalletFactoryTest.kt | 204 +++++++++ .../util/{ => services}/backup-base58-testnet | 0 .../bitcoin-backup-base58-testnet | 0 .../bitcoin-backup-protobuf-testnet | Bin .../services/dash-wallet-backup-5.17.5-nopin | 411 +++++++++++++++++ .../services/dash-wallet-backup-5.17.5-pin | 415 ++++++++++++++++++ 25 files changed, 1580 insertions(+), 589 deletions(-) create mode 100644 wallet/src/de/schildbach/wallet/service/WalletFactory.kt create mode 100644 wallet/src/de/schildbach/wallet/ui/backup/OnRestoreWalletListener.java delete mode 100644 wallet/src/de/schildbach/wallet/ui/backup/RestoreFromFileHelper.java delete mode 100644 wallet/test/de/schildbach/wallet/util/WalletUtilsTest.java delete mode 100644 wallet/test/de/schildbach/wallet/util/backup-protobuf-testnet delete mode 100644 wallet/test/de/schildbach/wallet/util/bitcoin-wallet-backup-testnet-3.50 delete mode 100644 wallet/test/de/schildbach/wallet/util/bitcoin-wallet-backup-testnet-3.50-crlf create mode 100644 wallet/test/de/schildbach/wallet/util/services/WalletFactoryTest.kt rename wallet/test/de/schildbach/wallet/util/{ => services}/backup-base58-testnet (100%) rename wallet/test/de/schildbach/wallet/util/{ => services}/bitcoin-backup-base58-testnet (100%) rename wallet/test/de/schildbach/wallet/util/{ => services}/bitcoin-backup-protobuf-testnet (100%) create mode 100644 wallet/test/de/schildbach/wallet/util/services/dash-wallet-backup-5.17.5-nopin create mode 100644 wallet/test/de/schildbach/wallet/util/services/dash-wallet-backup-5.17.5-pin diff --git a/common/src/main/java/org/dash/wallet/common/WalletDataProvider.kt b/common/src/main/java/org/dash/wallet/common/WalletDataProvider.kt index b5e7cb6267..c866772e44 100644 --- a/common/src/main/java/org/dash/wallet/common/WalletDataProvider.kt +++ b/common/src/main/java/org/dash/wallet/common/WalletDataProvider.kt @@ -68,6 +68,4 @@ interface WalletDataProvider { fun checkSendingConditions(address: Address?, amount: Coin) fun observeMostRecentTransaction(): Flow - - fun getWalletExtensions(): Array } diff --git a/wallet/src/de/schildbach/wallet/WalletApplication.java b/wallet/src/de/schildbach/wallet/WalletApplication.java index 71cb777ce1..f862bbf39f 100644 --- a/wallet/src/de/schildbach/wallet/WalletApplication.java +++ b/wallet/src/de/schildbach/wallet/WalletApplication.java @@ -84,6 +84,7 @@ import org.dash.wallet.integrations.coinbase.service.CoinBaseClientConstants; import de.schildbach.wallet.service.PackageInfoProvider; +import de.schildbach.wallet.service.WalletFactory; import de.schildbach.wallet.transactions.MasternodeObserver; import de.schildbach.wallet.ui.buy_sell.LiquidClient; import org.dash.wallet.integrations.uphold.api.UpholdClient; @@ -159,7 +160,6 @@ public class WalletApplication extends MultiDexApplication private File walletFile; private Wallet wallet; - private AuthenticationGroupExtension authenticationGroupExtension; public static final String ACTION_WALLET_REFERENCE_CHANGED = WalletApplication.class.getPackage().getName() + ".wallet_reference_changed"; @@ -190,6 +190,8 @@ public class WalletApplication extends MultiDexApplication TransactionMetadataProvider transactionMetadataProvider; @Inject PackageInfoProvider packageInfoProvider; + @Inject + WalletFactory walletFactory; @Override protected void attachBaseContext(Context base) { @@ -209,7 +211,6 @@ public void onCreate() { log.info("WalletApplication.onCreate()"); config = new Configuration(PreferenceManager.getDefaultSharedPreferences(this), getResources()); autoLogout = new AutoLogout(config); - authenticationGroupExtension = new AuthenticationGroupExtension(Constants.NETWORK_PARAMETERS); autoLogout.registerDeviceInteractiveReceiver(this); registerActivityLifecycleCallbacks(new ActivitiesTracker() { @Override @@ -317,28 +318,36 @@ private void initEnvironment() { blockchainServiceIntent = new Intent(this, BlockchainServiceImpl.class); } - public void setWallet(Wallet newWallet) { + // only used by onboarding after creating or restoring a wallet + public void setWallet(Wallet newWallet) throws GeneralSecurityException, IOException { + EnumSet authKeyTypes = EnumSet.of( + AuthenticationKeyChain.KeyChainType.MASTERNODE_OWNER, + AuthenticationKeyChain.KeyChainType.MASTERNODE_VOTING, + AuthenticationKeyChain.KeyChainType.MASTERNODE_OPERATOR, + AuthenticationKeyChain.KeyChainType.MASTERNODE_PLATFORM_OPERATOR + ); this.wallet = newWallet; // TODO: move to a wallet creation class if (!wallet.hasKeyChain(Constants.BIP44_PATH)) { wallet.addKeyChain(Constants.BIP44_PATH); } - if (!authenticationGroupExtension.hasKeyChains()) { - authenticationGroupExtension.addKeyChains( - wallet.getParams(), - wallet.getKeyChainSeed(), - EnumSet.of( - AuthenticationKeyChain.KeyChainType.MASTERNODE_OWNER, - AuthenticationKeyChain.KeyChainType.MASTERNODE_VOTING, - AuthenticationKeyChain.KeyChainType.MASTERNODE_OPERATOR, - AuthenticationKeyChain.KeyChainType.MASTERNODE_PLATFORM_OPERATOR - ) - ); - - authenticationGroupExtension.freshKey(AuthenticationKeyChain.KeyChainType.MASTERNODE_OWNER); - authenticationGroupExtension.freshKey(AuthenticationKeyChain.KeyChainType.MASTERNODE_VOTING); - authenticationGroupExtension.freshKey(AuthenticationKeyChain.KeyChainType.MASTERNODE_OPERATOR); - authenticationGroupExtension.freshKey(AuthenticationKeyChain.KeyChainType.MASTERNODE_PLATFORM_OPERATOR); + + if (wallet.getKeyChainExtensions().containsKey(AuthenticationGroupExtension.EXTENSION_ID)) { + AuthenticationGroupExtension authenticationGroupExtension = (AuthenticationGroupExtension) wallet.getKeyChainExtensions().get(AuthenticationGroupExtension.EXTENSION_ID); + if (authKeyTypes.stream().anyMatch(keyType -> authenticationGroupExtension.getKeyChain(keyType) == null)) { + // if the wallet is encrypted, don't add these keys + if (!wallet.isEncrypted()) { + authenticationGroupExtension.addKeyChains( + wallet.getParams(), + wallet.getKeyChainSeed(), + authKeyTypes + ); + authenticationGroupExtension.freshKey(AuthenticationKeyChain.KeyChainType.MASTERNODE_OWNER); + authenticationGroupExtension.freshKey(AuthenticationKeyChain.KeyChainType.MASTERNODE_VOTING); + authenticationGroupExtension.freshKey(AuthenticationKeyChain.KeyChainType.MASTERNODE_OPERATOR); + authenticationGroupExtension.freshKey(AuthenticationKeyChain.KeyChainType.MASTERNODE_PLATFORM_OPERATOR); + } + } } } @@ -564,7 +573,7 @@ private void loadWalletFromProtobuf() { try { final Stopwatch watch = Stopwatch.createStarted(); walletStream = new FileInputStream(walletFile); - wallet = new WalletProtobufSerializer().readWallet(walletStream, authenticationGroupExtension); + wallet = new WalletProtobufSerializer().readWallet(walletStream, false, walletFactory.getExtensions(Constants.NETWORK_PARAMETERS)); if (!wallet.getParams().equals(Constants.NETWORK_PARAMETERS)) throw new UnreadableWalletException("bad wallet network parameters: " + wallet.getParams().getId()); @@ -611,7 +620,7 @@ private Wallet restoreWalletFromBackup() { try { is = openFileInput(Constants.Files.WALLET_KEY_BACKUP_PROTOBUF); - final Wallet wallet = new WalletProtobufSerializer().readWallet(is, true, getWalletExtensions()); + final Wallet wallet = new WalletProtobufSerializer().readWallet(is, true, walletFactory.getExtensions(Constants.NETWORK_PARAMETERS)); if (!wallet.isConsistent()) throw new Error("inconsistent backup"); @@ -744,7 +753,10 @@ public void resetBlockchainState() { public void resetBlockchain() { // reset the extensions - authenticationGroupExtension.reset(); + if (wallet.getKeyChainExtensions().containsKey(AuthenticationGroupExtension.EXTENSION_ID)) { + AuthenticationGroupExtension authenticationGroupExtension = (AuthenticationGroupExtension) wallet.getKeyChainExtensions().get(AuthenticationGroupExtension.EXTENSION_ID); + authenticationGroupExtension.reset(); + } // implicitly stops blockchain service resetBlockchainState(); Intent blockchainServiceResetBlockchainIntent = new Intent(BlockchainService.ACTION_RESET_BLOCKCHAIN, null, this, @@ -918,11 +930,9 @@ public void finalizeWipe() { } // clear data on wallet reset transactionMetadataProvider.clear(); - authenticationGroupExtension.reset(); // wallet must be null for the OnboardingActivity flow log.info("removing wallet from memory during wipe"); wallet = null; - clearExtensions(); } private void notifyWalletWipe() { @@ -1016,10 +1026,10 @@ public Flow observeWalletChanged() { @NonNull @Override public Flow> observeAuthenticationKeyUsage() { - if (wallet == null) { + if (wallet == null || !wallet.getKeyChainExtensions().containsKey(AuthenticationGroupExtension.EXTENSION_ID)) { return FlowKt.emptyFlow(); } - + AuthenticationGroupExtension authenticationGroupExtension = (AuthenticationGroupExtension) wallet.getKeyChainExtensions().get(AuthenticationGroupExtension.EXTENSION_ID); return new MasternodeObserver(authenticationGroupExtension).observeAuthenticationKeyUsage(); } @@ -1104,13 +1114,4 @@ public void checkSendingConditions( crowdNodeConfig ); } - - public void clearExtensions() { - log.info("clearing extensions: authentication"); - authenticationGroupExtension = new AuthenticationGroupExtension(Constants.NETWORK_PARAMETERS); - } - - public WalletExtension[] getWalletExtensions() { - return new WalletExtension[] {authenticationGroupExtension}; - } } diff --git a/wallet/src/de/schildbach/wallet/di/AppModule.kt b/wallet/src/de/schildbach/wallet/di/AppModule.kt index 6498ba910e..0c7589b33b 100644 --- a/wallet/src/de/schildbach/wallet/di/AppModule.kt +++ b/wallet/src/de/schildbach/wallet/di/AppModule.kt @@ -119,4 +119,7 @@ abstract class AppModule { @Binds @Singleton abstract fun bindNetworkState(networkState: NetworkState) : NetworkStateInt + + @Binds + abstract fun bindWalletFactory(walletFactory: DashWalletFactory) : WalletFactory } diff --git a/wallet/src/de/schildbach/wallet/service/WalletFactory.kt b/wallet/src/de/schildbach/wallet/service/WalletFactory.kt new file mode 100644 index 0000000000..0ef4108f1f --- /dev/null +++ b/wallet/src/de/schildbach/wallet/service/WalletFactory.kt @@ -0,0 +1,390 @@ +/* + * Copyright 2023 Dash Core Group + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package de.schildbach.wallet.service + +import android.net.Uri +import android.text.format.DateUtils +import com.google.common.base.Charsets +import com.google.common.base.Preconditions +import de.schildbach.wallet.Constants +import de.schildbach.wallet.WalletApplication +import de.schildbach.wallet.util.Crypto +import de.schildbach.wallet.util.Io +import de.schildbach.wallet.util.Iso8601Format +import org.bitcoinj.core.AddressFormatException +import org.bitcoinj.core.DumpedPrivateKey +import org.bitcoinj.core.ECKey +import org.bitcoinj.core.NetworkParameters +import org.bitcoinj.script.Script +import org.bitcoinj.wallet.DeterministicKeyChain +import org.bitcoinj.wallet.DeterministicSeed +import org.bitcoinj.wallet.KeyChainGroup +import org.bitcoinj.wallet.UnreadableWalletException +import org.bitcoinj.wallet.Wallet +import org.bitcoinj.wallet.WalletExtension +import org.bitcoinj.wallet.WalletProtobufSerializer +import org.bitcoinj.wallet.authentication.AuthenticationGroupExtension +import org.slf4j.Logger +import org.slf4j.LoggerFactory +import java.io.BufferedReader +import java.io.ByteArrayInputStream +import java.io.File +import java.io.FileInputStream +import java.io.IOException +import java.io.InputStream +import java.io.InputStreamReader +import java.text.ParseException +import java.util.LinkedList +import javax.inject.Inject + +interface WalletFactory { + // Onboarding + fun create(params: NetworkParameters): Wallet + fun restoreFromSeed(params: NetworkParameters, recoveryPhrase: List): Wallet + @Throws(IOException::class) + fun restoreFromFile(params: NetworkParameters, backupUri: Uri, password: String): Pair + + // loading from persistent storage + fun getExtensions(params: NetworkParameters): Array + fun load(params: NetworkParameters, walletFile: File): Wallet + fun restoreFromBackup(params: NetworkParameters, backupFile: String): Wallet +} + +class DashWalletFactory @Inject constructor( + private val walletApplication: WalletApplication +) : WalletFactory { + + companion object { + val log: Logger = LoggerFactory.getLogger(DashWalletFactory::class.java) + fun isUnencryptedStream(inStream: InputStream): Boolean { + return try { + inStream.mark(Constants.BACKUP_MAX_CHARS.toInt()) + WalletProtobufSerializer.isWallet(inStream) + } finally { + try { + inStream.reset() + } catch (x: IOException) { + // swallow + } + } + } + } + + val contentResolver = walletApplication.contentResolver + + // always create new extension objects to prevent using a previously created object + override fun getExtensions(params: NetworkParameters): Array { + return arrayOf(AuthenticationGroupExtension(params)) + } + + private fun addMissingExtensions(wallet: Wallet) { + getExtensions(wallet.params).forEach { + wallet.addOrGetExistingExtension(it) + } + } + + override fun create(params: NetworkParameters): Wallet { + val wallet = Wallet.createDeterministic(params, Script.ScriptType.P2PKH) + addMissingExtensions(wallet) + checkWalletValid(wallet, params) + return wallet + } + + override fun restoreFromSeed(params: NetworkParameters, recoveryPhrase: List): Wallet { + return restoreWalletFromSeed(recoveryPhrase, params) + } + + @Throws(IOException::class) + override fun restoreFromFile(params: NetworkParameters, backupUri: Uri, password: String): Pair { + val inputStream: InputStream = contentResolver.openInputStream(backupUri) ?: throw IOException( + "Cannot open $backupUri" + ) + inputStream.mark(Constants.BACKUP_MAX_CHARS.toInt()) + var fromKeys = false + var walletType = "" + val wallet = if (isUnencryptedStream(inputStream)) { + walletType = "unencrypted" + restoreWalletFromProtobuf(contentResolver.openInputStream(backupUri), params, getExtensions(params)) + } else if (isKeysStream(params, contentResolver.openInputStream(backupUri))) { + walletType = "key file" + fromKeys = true + restorePrivateKeysFromBase58(contentResolver.openInputStream(backupUri), params) + } else if (Crypto.isEncryptedStream(contentResolver.openInputStream(backupUri))) { + walletType = "encrypted" + restoreWalletFromEncrypted(params, backupUri, contentResolver.openInputStream(backupUri), password) + } else { + throw IOException("unknown wallet type: this should not happen") + } + log.info("successfully restored {} wallet from external source", walletType) + return Pair(wallet, fromKeys) + } + + override fun load(params: NetworkParameters, walletFile: File): Wallet { + var walletStream: FileInputStream? = null + try { + walletStream = FileInputStream(walletFile) + val wallet = WalletProtobufSerializer().readWallet(walletStream, false, getExtensions(params)) + + if (wallet.params != params) { + throw UnreadableWalletException( + "bad wallet network parameters: ${wallet.params.id} but expecting ${params.id}" + ) + } + return wallet + } finally { + try { + walletStream?.close() + } catch (x: IOException) { + // swallow + } + } + } + + override fun restoreFromBackup(params: NetworkParameters, backupFile: String): Wallet { + var inputStream: InputStream? = null + + try { + inputStream = walletApplication.openFileInput(backupFile) + + val wallet = WalletProtobufSerializer().readWallet(inputStream, true, getExtensions(params)) + + if (!wallet.isConsistent) throw Error("inconsistent backup") + + // does this work on encrypted backups? + wallet.addKeyChain(Constants.BIP44_PATH) + return wallet + } finally { + try { + inputStream?.close() + } catch (x: IOException) { + // swallow + } + } + } + + @Throws(IOException::class) + private fun restoreWalletFromProtobufOrBase58( + inputStream: InputStream, + expectedNetworkParameters: NetworkParameters + ): Wallet { + return restoreWalletFromProtobufOrBase58( + inputStream, + expectedNetworkParameters, + getExtensions(expectedNetworkParameters) + ) + } + + @Throws(IOException::class) + fun restoreWalletFromProtobufOrBase58( + inputStream: InputStream, + expectedNetworkParameters: NetworkParameters, + walletExtensions: Array + ): Wallet { + return try { + restoreWalletFromProtobuf(inputStream, expectedNetworkParameters, walletExtensions) + } catch (x: IOException) { + try { + inputStream.reset() + return restorePrivateKeysFromBase58(inputStream, expectedNetworkParameters) + } catch (x2: IOException) { + throw IOException( + "cannot read protobuf (" + x.message + ") or base58 (" + x2.message + ")", + x + ) + } + } + } + + @Throws(IOException::class) + fun restoreWalletFromProtobuf( + inputStream: InputStream?, + expectedNetworkParameters: NetworkParameters, + walletExtensions: Array + ): Wallet { + return try { + val wallet = WalletProtobufSerializer().readWallet(inputStream, true, walletExtensions) + if (!wallet.isEncrypted) { + addMissingExtensions(wallet) + } + checkWalletValid(wallet, expectedNetworkParameters, !wallet.isEncrypted) + wallet + } catch (x: UnreadableWalletException) { + throw IOException("unreadable wallet", x) + } finally { + try { + inputStream?.close() + } catch (e: IOException) { + // swallow + } + } + } + + private fun checkWalletValid( + wallet: Wallet, + expectedNetworkParameters: NetworkParameters, + checkAllExtensions: Boolean = true + ) { + if (wallet.params != expectedNetworkParameters) { + throw IOException("bad wallet backup network parameters: " + wallet.params.id) + } + if (!wallet.isConsistent) { + throw IOException("inconsistent wallet backup") + } + if (checkAllExtensions) { + getExtensions(expectedNetworkParameters).forEach { + Preconditions.checkState( + wallet.keyChainExtensions.containsKey(it.walletExtensionID) || + wallet.extensions.containsKey(it.walletExtensionID) + ) + } + } + } + + private fun restoreWalletFromSeed( + words: List, + params: NetworkParameters + ): Wallet { + return try { + val seed = DeterministicSeed(words, null, "", Constants.EARLIEST_HD_SEED_CREATION_TIME) + val group = KeyChainGroup.builder(params) + .fromSeed(seed, Script.ScriptType.P2PKH) + .addChain( + DeterministicKeyChain.builder() + .seed(seed) + .accountPath(Constants.BIP44_PATH) + .build() + ) + .build() + val wallet = Wallet(params, group) + // add extensions + addMissingExtensions(wallet) + + checkWalletValid(wallet, params) + wallet + } finally { + } + } + + @Throws(IOException::class) + private fun restorePrivateKeysFromBase58( + inputStream: InputStream?, + expectedNetworkParameters: NetworkParameters + ): Wallet { + try { + val keyReader = BufferedReader(InputStreamReader(inputStream, Charsets.UTF_8)) + + // create non-HD wallet + val group = KeyChainGroup.builder(expectedNetworkParameters).build() + group.importKeys(readKeys(keyReader, expectedNetworkParameters)) + val wallet = Wallet(expectedNetworkParameters, group) + // this will result in a different HD seed each time + wallet.upgradeToDeterministic(Script.ScriptType.P2PKH, null) + // add the extensions + addMissingExtensions(wallet) + checkWalletValid(wallet, expectedNetworkParameters) + return wallet + } finally { + try { + inputStream?.close() + } catch (e: IOException) { + // swallow + } + } + } + + @Throws(IOException::class) + private fun restoreWalletFromEncrypted( + params: NetworkParameters, + walletUri: Uri, + inFileStream: InputStream?, + password: String + ): Wallet { + try { + val cipherIn = BufferedReader( + InputStreamReader(inFileStream, Charsets.UTF_8) + ) + val cipherText = StringBuilder() + Io.copy(cipherIn, cipherText, Constants.BACKUP_MAX_CHARS) + cipherIn.close() + val plainText = Crypto.decryptBytes(cipherText.toString(), password.toCharArray()) + val inputStream: InputStream = ByteArrayInputStream(plainText) + log.info("successfully restored encrypted wallet: {}", walletUri) + return restoreWalletFromProtobufOrBase58(inputStream, params) + } finally { + try { + inFileStream?.close() + } catch (e: IOException) { + // swallow + } + } + } + + @Throws(IOException::class) + private fun readKeys(inputStream: BufferedReader, expectedNetworkParameters: NetworkParameters?): List? { + return try { + val format = Iso8601Format.newDateTimeFormatT() + val keys: MutableList = LinkedList() + var charCount: Long = 0 + while (true) { + val line = inputStream.readLine() ?: break + // eof + charCount += line.length.toLong() + if (charCount > Constants.BACKUP_MAX_CHARS) { + throw IOException("read more than the limit of " + Constants.BACKUP_MAX_CHARS + " characters") + } + if (line.trim { it <= ' ' }.isEmpty() || line[0] == '#') { + continue + } // skip comment + val parts = line.split(" ".toRegex()).dropLastWhile { it.isEmpty() }.toTypedArray() + val key = DumpedPrivateKey.fromBase58(expectedNetworkParameters, parts[0]).key + key.creationTimeSeconds = + if (parts.size >= 2) format.parse(parts[1]).time / DateUtils.SECOND_IN_MILLIS else 0 + keys.add(key) + } + keys + } catch (x: AddressFormatException) { + throw IOException("cannot read keys", x) + } catch (x: ParseException) { + throw IOException("cannot read keys", x) + } + } + + private fun isKeysStream(params: NetworkParameters, inputStream: InputStream?): Boolean { + var reader: BufferedReader? = null + return try { + reader = BufferedReader(InputStreamReader(inputStream, Charsets.UTF_8)) + readKeys(reader, params) + true + } catch (x: IOException) { + false + } finally { + if (reader != null) { + try { + reader.close() + } catch (x: IOException) { + // swallow + } + } + try { + inputStream?.reset() + } catch (x: IOException) { + // swallow + } + } + } +} diff --git a/wallet/src/de/schildbach/wallet/ui/OnboardingViewModel.kt b/wallet/src/de/schildbach/wallet/ui/OnboardingViewModel.kt index 28f46be989..ef29af0c90 100644 --- a/wallet/src/de/schildbach/wallet/ui/OnboardingViewModel.kt +++ b/wallet/src/de/schildbach/wallet/ui/OnboardingViewModel.kt @@ -17,23 +17,28 @@ package de.schildbach.wallet.ui -import android.app.Application -import androidx.lifecycle.AndroidViewModel +import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope +import dagger.hilt.android.lifecycle.HiltViewModel import de.schildbach.wallet.Constants import de.schildbach.wallet.WalletApplication +import de.schildbach.wallet.service.WalletFactory import de.schildbach.wallet.ui.util.SingleLiveEvent import kotlinx.coroutines.launch import org.bitcoinj.crypto.MnemonicException -import org.bitcoinj.wallet.Wallet +import org.dash.wallet.common.Configuration import org.slf4j.LoggerFactory +import javax.inject.Inject -class OnboardingViewModel(application: Application) : AndroidViewModel(application) { +@HiltViewModel +class OnboardingViewModel @Inject constructor( + private val walletApplication: WalletApplication, + private val walletFactory: WalletFactory, + private val configuration: Configuration +) : ViewModel() { private val log = LoggerFactory.getLogger(OnboardingViewModel::class.java) - private val walletApplication = application as WalletApplication - internal val showToastAction = SingleLiveEvent() internal val showRestoreWalletFailureAction = SingleLiveEvent() internal val finishCreateNewWalletAction = SingleLiveEvent() @@ -41,13 +46,10 @@ class OnboardingViewModel(application: Application) : AndroidViewModel(applicati fun createNewWallet() { walletApplication.initEnvironmentIfNeeded() - val wallet = Wallet(Constants.NETWORK_PARAMETERS) - for (extension in walletApplication.getWalletExtensions()) { - wallet.addExtension(extension) - } + val wallet = walletFactory.create(Constants.NETWORK_PARAMETERS) log.info("successfully created new wallet") walletApplication.setWallet(wallet) - walletApplication.configuration.armBackupSeedReminder() + configuration.armBackupSeedReminder() finishCreateNewWalletAction.call(Unit) } @@ -58,7 +60,7 @@ class OnboardingViewModel(application: Application) : AndroidViewModel(applicati if (!walletApplication.isWalletUpgradedToBIP44) { walletApplication.wallet!!.addKeyChain(Constants.BIP44_PATH) } - walletApplication.configuration.armBackupSeedReminder() + configuration.armBackupSeedReminder() finishUnecryptedWalletUpgradeAction.call(Unit) } diff --git a/wallet/src/de/schildbach/wallet/ui/RestoreWalletFromFileViewModel.kt b/wallet/src/de/schildbach/wallet/ui/RestoreWalletFromFileViewModel.kt index 28c402c59e..9a1247b528 100644 --- a/wallet/src/de/schildbach/wallet/ui/RestoreWalletFromFileViewModel.kt +++ b/wallet/src/de/schildbach/wallet/ui/RestoreWalletFromFileViewModel.kt @@ -16,25 +16,32 @@ package de.schildbach.wallet.ui -import android.app.Application import android.content.Intent import android.net.Uri -import androidx.lifecycle.AndroidViewModel import androidx.lifecycle.MutableLiveData +import androidx.lifecycle.ViewModel +import dagger.hilt.android.lifecycle.HiltViewModel import de.schildbach.wallet.Constants import de.schildbach.wallet.WalletApplication +import de.schildbach.wallet.service.WalletFactory import de.schildbach.wallet.ui.util.SingleLiveEvent import de.schildbach.wallet_test.R import org.bitcoinj.crypto.MnemonicException import org.bitcoinj.wallet.Wallet +import org.dash.wallet.common.Configuration import org.slf4j.LoggerFactory +import java.io.IOException +import javax.inject.Inject -class RestoreWalletFromFileViewModel(application: Application) : AndroidViewModel(application) { +@HiltViewModel +class RestoreWalletFromFileViewModel @Inject constructor( + val walletApplication: WalletApplication, + private val walletFactory: WalletFactory, + private val configuration: Configuration +) : ViewModel() { private val log = LoggerFactory.getLogger(RestoreWalletFromFileViewModel::class.java) - private val walletApplication = application as WalletApplication - internal val showRestoreWalletFailureAction = SingleLiveEvent() internal val showUpgradeWalletAction = SingleLiveEvent() internal val showUpgradeDisclaimerAction = SingleLiveEvent() @@ -42,20 +49,37 @@ class RestoreWalletFromFileViewModel(application: Application) : AndroidViewMode val backupUri = MutableLiveData() val displayName = MutableLiveData() - val showSuccessDialog = SingleLiveEvent() val showFailureDialog = SingleLiveEvent() val restoreWallet = SingleLiveEvent() val retryRequest = SingleLiveEvent() - fun restoreWalletFromFile(wallet: Wallet, password: String?) { + @Throws(IOException::class) + fun restoreWalletFromUri(backupUri: Uri, password: String) : Wallet { + val (wallet, fromKeys) = walletFactory.restoreFromFile(Constants.NETWORK_PARAMETERS, backupUri, password) + if (fromKeys) { + // when loading a keys file, a new recovery phrase is created and is different each time + // The user will need to backup their passphrase + configuration.armBackupReminder() + configuration.armBackupSeedReminder() + } + return wallet + } + + fun restoreWallet(wallet: Wallet, password: String?) { if (!wallet.hasKeyChain(Constants.BIP44_PATH) && wallet.isEncrypted) { showUpgradeWalletAction.call(wallet) } else { walletApplication.setWallet(wallet) log.info("successfully restored wallet from file") walletApplication.resetBlockchainState() - startActivityAction.call(SetPinActivity.createIntent(getApplication(), - R.string.set_pin_restore_wallet, false, password)) + startActivityAction.call( + SetPinActivity.createIntent( + walletApplication, + R.string.set_pin_restore_wallet, + false, + password + ) + ) } } } diff --git a/wallet/src/de/schildbach/wallet/ui/RestoreWalletFromSeedDialogFragment.java b/wallet/src/de/schildbach/wallet/ui/RestoreWalletFromSeedDialogFragment.java index 01c9d3df6e..28c43ebe37 100644 --- a/wallet/src/de/schildbach/wallet/ui/RestoreWalletFromSeedDialogFragment.java +++ b/wallet/src/de/schildbach/wallet/ui/RestoreWalletFromSeedDialogFragment.java @@ -52,8 +52,10 @@ import java.util.LinkedList; import java.util.List; +import dagger.hilt.android.AndroidEntryPoint; import de.schildbach.wallet.Constants; import de.schildbach.wallet.WalletApplication; +import de.schildbach.wallet.service.WalletFactory; import de.schildbach.wallet.ui.main.WalletActivity; import de.schildbach.wallet.util.Crypto; import de.schildbach.wallet.util.MnemonicCodeExt; @@ -63,7 +65,10 @@ import android.annotation.SuppressLint; +import javax.inject.Inject; + +@AndroidEntryPoint public class RestoreWalletFromSeedDialogFragment extends DialogFragment { private static final String FRAGMENT_TAG = RestoreWalletFromSeedDialogFragment.class.getName(); @@ -85,6 +90,9 @@ public static void show(final FragmentManager fm) { WalletApplication application; private Wallet wallet; + @Inject + WalletFactory walletFactory; + @Override public void onAttach(final Activity activity) { super.onAttach(activity); @@ -194,10 +202,10 @@ private void restoreWalletFromSeed(final List words) { final WalletActivity activity = (WalletActivity) this.activity; try { MnemonicCodeExt.getInstance().check(activity, words); - activity.restoreWallet(WalletUtils.restoreWalletFromSeed(words, Constants.NETWORK_PARAMETERS, application.getWalletExtensions())); + activity.restoreWallet(walletFactory.restoreFromSeed(Constants.NETWORK_PARAMETERS, words)); log.info("successfully restored wallet from seed: {}", words.size()); - } catch (final IOException | MnemonicException x) { + } catch (MnemonicException x) { final BaseAlertDialogBuilder restoreWalletFromSeedAlertDialogBuilder = new BaseAlertDialogBuilder(requireContext()); restoreWalletFromSeedAlertDialogBuilder.setTitle(getString( R.string.import_export_keys_dialog_failure_title)); diff --git a/wallet/src/de/schildbach/wallet/ui/RestoreWalletFromSeedViewModel.kt b/wallet/src/de/schildbach/wallet/ui/RestoreWalletFromSeedViewModel.kt index b9b585896d..3bb3d7ac2a 100644 --- a/wallet/src/de/schildbach/wallet/ui/RestoreWalletFromSeedViewModel.kt +++ b/wallet/src/de/schildbach/wallet/ui/RestoreWalletFromSeedViewModel.kt @@ -18,16 +18,18 @@ package de.schildbach.wallet.ui import android.content.Intent import androidx.lifecycle.ViewModel +import androidx.lifecycle.viewModelScope import dagger.hilt.android.lifecycle.HiltViewModel import de.schildbach.wallet.Constants import de.schildbach.wallet.WalletApplication import de.schildbach.wallet.security.SecurityFunctions import de.schildbach.wallet.security.SecurityGuard +import de.schildbach.wallet.service.WalletFactory import de.schildbach.wallet.ui.util.SingleLiveEvent import de.schildbach.wallet.util.MnemonicCodeExt -import de.schildbach.wallet.util.WalletUtils import de.schildbach.wallet_test.R import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.launch import kotlinx.coroutines.withContext import org.bitcoinj.crypto.MnemonicException import org.dash.wallet.common.Configuration @@ -38,6 +40,7 @@ import javax.inject.Inject @HiltViewModel class RestoreWalletFromSeedViewModel @Inject constructor( private val walletApplication: WalletApplication, + private val walletFactory: WalletFactory, private val configuration: Configuration, private val securityFunctions: SecurityFunctions ) : ViewModel() { @@ -73,7 +76,7 @@ class RestoreWalletFromSeedViewModel @Inject constructor( fun restoreWalletFromSeed(words: List) { if (isSeedValid(words)) { - val wallet = WalletUtils.restoreWalletFromSeed(normalize(words), Constants.NETWORK_PARAMETERS, walletApplication.getWalletExtensions()) + val wallet = walletFactory.restoreFromSeed(Constants.NETWORK_PARAMETERS, normalize(words)) walletApplication.setWallet(wallet) log.info("successfully restored wallet from seed") configuration.disarmBackupSeedReminder() diff --git a/wallet/src/de/schildbach/wallet/ui/backup/BackupWalletDialogFragment.java b/wallet/src/de/schildbach/wallet/ui/backup/BackupWalletDialogFragment.java index d055a83662..ccd76473eb 100644 --- a/wallet/src/de/schildbach/wallet/ui/backup/BackupWalletDialogFragment.java +++ b/wallet/src/de/schildbach/wallet/ui/backup/BackupWalletDialogFragment.java @@ -43,6 +43,8 @@ import android.widget.EditText; import android.widget.TextView; +import androidx.activity.result.ActivityResultLauncher; +import androidx.activity.result.contract.ActivityResultContracts; import androidx.annotation.NonNull; import androidx.appcompat.app.AlertDialog; import androidx.fragment.app.DialogFragment; @@ -85,6 +87,7 @@ import de.schildbach.wallet.payments.DeriveKeyTask; import de.schildbach.wallet.security.SecurityFunctions; import de.schildbach.wallet.security.SecurityGuard; +import de.schildbach.wallet.service.WalletFactory; import de.schildbach.wallet.ui.ShowPasswordCheckListener; import de.schildbach.wallet.util.Crypto; import de.schildbach.wallet.util.Iso8601Format; @@ -123,6 +126,7 @@ public static void show(final FragmentActivity activity) { private HandlerThread backgroundThread; private Handler backgroundHandler; private BackupWalletViewModel viewModel; + @Inject WalletFactory walletFactory; private static final int REQUEST_CODE_CREATE_DOCUMENT = 0; @@ -297,11 +301,6 @@ public void onActivityResult(final int requestCode, final int resultCode, final if (requestCode == REQUEST_CODE_CREATE_DOCUMENT) { if (resultCode == Activity.RESULT_OK) { Wallet wallet = walletData.getWallet(); - //walletActivityViewModel.wallet.observe(this, new Observer() { - // @Override - // public void onChanged(final Wallet wallet) { - // walletActivityViewModel.wallet.removeObserver(this); - final Uri targetUri = checkNotNull(intent.getData()); final String targetProvider = WalletUtils.uriToProvider(targetUri); @@ -316,7 +315,7 @@ public void onActivityResult(final int requestCode, final int resultCode, final SecurityGuard securityGuard = new SecurityGuard(); if (wallet.isEncrypted()) { String walletPassword = securityGuard.retrievePassword(); - final Wallet decryptedWallet = new WalletProtobufSerializer().readWallet(Constants.NETWORK_PARAMETERS, walletData.getWalletExtensions(), walletProto); + final Wallet decryptedWallet = new WalletProtobufSerializer().readWallet(Constants.NETWORK_PARAMETERS, walletFactory.getExtensions(Constants.NETWORK_PARAMETERS), walletProto); new DeriveKeyTask(backgroundHandler, securityFunctions.getScryptIterationsTarget()) { @Override protected void onSuccess(KeyParameter encryptionKey, boolean changed) { diff --git a/wallet/src/de/schildbach/wallet/ui/backup/OnRestoreWalletListener.java b/wallet/src/de/schildbach/wallet/ui/backup/OnRestoreWalletListener.java new file mode 100644 index 0000000000..ff4fba793f --- /dev/null +++ b/wallet/src/de/schildbach/wallet/ui/backup/OnRestoreWalletListener.java @@ -0,0 +1,25 @@ +/* + * Copyright 2023 Dash Core Group + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package de.schildbach.wallet.ui.backup; + +import org.bitcoinj.wallet.Wallet; + +public interface OnRestoreWalletListener { + void onRestoreWallet(Wallet wallet); + void onRetryRequest(); +} diff --git a/wallet/src/de/schildbach/wallet/ui/backup/RestoreFromFileActivity.kt b/wallet/src/de/schildbach/wallet/ui/backup/RestoreFromFileActivity.kt index 08197f0f3f..70a6bfec55 100644 --- a/wallet/src/de/schildbach/wallet/ui/backup/RestoreFromFileActivity.kt +++ b/wallet/src/de/schildbach/wallet/ui/backup/RestoreFromFileActivity.kt @@ -21,7 +21,7 @@ import android.app.Activity import android.content.Intent import android.os.Bundle import android.text.TextUtils -import androidx.lifecycle.ViewModelProvider +import androidx.activity.viewModels import de.schildbach.wallet.Constants import de.schildbach.wallet.WalletApplication import de.schildbach.wallet.ui.AbstractPINDialogFragment @@ -32,11 +32,13 @@ import de.schildbach.wallet.ui.SET_PIN_REQUEST_CODE import de.schildbach.wallet.ui.widget.UpgradeWalletDisclaimerDialog import de.schildbach.wallet_test.R import org.bitcoinj.wallet.Wallet +import dagger.hilt.android.AndroidEntryPoint import org.dash.wallet.common.SecureActivity import org.dash.wallet.common.ui.dialogs.AdaptiveDialog @SuppressLint("Registered") +@AndroidEntryPoint open class RestoreFromFileActivity : SecureActivity(), AbstractPINDialogFragment.WalletProvider { companion object { @@ -44,14 +46,14 @@ open class RestoreFromFileActivity : SecureActivity(), AbstractPINDialogFragment const val REQUEST_CODE_RESTORE_WALLET = 1 } - private lateinit var viewModel: RestoreWalletFromFileViewModel + private val viewModel: RestoreWalletFromFileViewModel by viewModels() private lateinit var walletApplication: WalletApplication private lateinit var walletBuffer: Wallet override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) - viewModel = ViewModelProvider(this)[RestoreWalletFromFileViewModel::class.java] + // viewModel = ViewModelProvider(this)[RestoreWalletFromFileViewModel::class.java] walletApplication = (application as WalletApplication) initViewModel() } @@ -88,7 +90,7 @@ open class RestoreFromFileActivity : SecureActivity(), AbstractPINDialogFragment } viewModel.restoreWallet.observe(this) { walletBuffer = it - viewModel.restoreWalletFromFile(wallet, null) + viewModel.restoreWallet(wallet, null) } viewModel.retryRequest.observe(this) { RestoreWalletDialogFragment.showPick(supportFragmentManager) @@ -105,7 +107,7 @@ open class RestoreFromFileActivity : SecureActivity(), AbstractPINDialogFragment } override fun onWalletUpgradeComplete(password: String) { - viewModel.restoreWalletFromFile(walletBuffer, password) + viewModel.restoreWallet(walletBuffer, password) } override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { diff --git a/wallet/src/de/schildbach/wallet/ui/backup/RestoreFromFileHelper.java b/wallet/src/de/schildbach/wallet/ui/backup/RestoreFromFileHelper.java deleted file mode 100644 index f47203958c..0000000000 --- a/wallet/src/de/schildbach/wallet/ui/backup/RestoreFromFileHelper.java +++ /dev/null @@ -1,171 +0,0 @@ -/* - * Copyright 2019 Dash Core Group - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package de.schildbach.wallet.ui.backup; - -import android.annotation.SuppressLint; -import android.app.Activity; -import android.app.Dialog; -import android.net.Uri; - -import androidx.lifecycle.LifecycleOwner; -import androidx.lifecycle.ViewModelStoreOwner; - -import com.google.common.base.Charsets; - -import org.bitcoinj.wallet.Wallet; -import org.bitcoinj.wallet.WalletExtension; -import org.dash.wallet.common.Configuration; -import org.dash.wallet.common.ui.BaseAlertDialogBuilder; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import java.io.BufferedReader; -import java.io.ByteArrayInputStream; -import java.io.IOException; -import java.io.InputStream; -import java.io.InputStreamReader; - -import javax.annotation.Nullable; - -import de.schildbach.wallet.Constants; -import de.schildbach.wallet.WalletApplication; -import de.schildbach.wallet.util.Crypto; -import de.schildbach.wallet.util.Io; -import de.schildbach.wallet.util.WalletUtils; -import de.schildbach.wallet_test.R; -import kotlin.Unit; - -public class RestoreFromFileHelper { - - protected static final Logger log = LoggerFactory.getLogger(RestoreFromFileHelper.class); - - @SuppressLint("StringFormatInvalid") - public static void restoreWalletFromProtobuf(final Activity activity, - final Uri walletUri, final InputStream is, @Nullable final WalletExtension[] walletExtensions, final OnRestoreWalletListener listener) { - try { - listener.onRestoreWallet(WalletUtils.restoreWalletFromProtobuf(is, Constants.NETWORK_PARAMETERS, walletExtensions)); - - log.info("successfully restored unencrypted wallet: {}", walletUri); - } catch (final IOException x) { - final BaseAlertDialogBuilder restoreWalletFromProtobufAlertDialogBuilder = new BaseAlertDialogBuilder(activity); - restoreWalletFromProtobufAlertDialogBuilder.setTitle(activity.getString(R.string.import_export_keys_dialog_failure_title)); - restoreWalletFromProtobufAlertDialogBuilder.setMessage(activity.getString(R.string.import_keys_dialog_failure, x.getMessage())); - restoreWalletFromProtobufAlertDialogBuilder.setPositiveText(activity.getString(R.string.button_dismiss)); - restoreWalletFromProtobufAlertDialogBuilder.setNegativeText(activity.getString(R.string.button_retry)); - restoreWalletFromProtobufAlertDialogBuilder.setNegativeAction( - () -> { - listener.onRetryRequest(); - return Unit.INSTANCE; - } - ); - restoreWalletFromProtobufAlertDialogBuilder.setShowIcon(true); - restoreWalletFromProtobufAlertDialogBuilder.buildAlertDialog().show(); - log.info("problem restoring unencrypted wallet: " + walletUri, x); - } finally { - if (is != null) { - try { - is.close(); - } catch (final IOException x2) { - // swallow - } - } - } - } - - public static void restorePrivateKeysFromBase58(final Activity activity, - ViewModelStoreOwner viewModelStoreOwner, LifecycleOwner lifecycleOwner, - final Uri walletUri, final InputStream is, final OnRestoreWalletListener listener) { - try { - listener.onRestoreWallet(WalletUtils.restorePrivateKeysFromBase58(is, Constants.NETWORK_PARAMETERS)); - //remind user to backup since the key backup file does not have an HD seed - //Each time the user restores this backup file a new HD seed will be generated - Configuration config = ((WalletApplication) activity.getApplication()).getConfiguration(); - config.armBackupReminder(); - config.armBackupSeedReminder(); - log.info("successfully restored unencrypted private keys: {}", walletUri); - } catch (final IOException x) { - final BaseAlertDialogBuilder restorePrivateKeyFailedAlertDialogBuilder = new BaseAlertDialogBuilder(activity); - restorePrivateKeyFailedAlertDialogBuilder.setTitle(activity.getString(R.string.import_export_keys_dialog_failure_title)); - restorePrivateKeyFailedAlertDialogBuilder.setMessage(activity.getString(R.string.import_keys_dialog_failure, x.getMessage())); - restorePrivateKeyFailedAlertDialogBuilder.setPositiveText(activity.getString(R.string.button_dismiss)); - restorePrivateKeyFailedAlertDialogBuilder.setNegativeText(activity.getString(R.string.button_retry)); - restorePrivateKeyFailedAlertDialogBuilder.setNegativeAction( - () -> { - listener.onRetryRequest(); - return Unit.INSTANCE; - } - ); - restorePrivateKeyFailedAlertDialogBuilder.setShowIcon(true); - restorePrivateKeyFailedAlertDialogBuilder.buildAlertDialog().show(); - - log.info("problem restoring private keys: " + walletUri, x); - } finally { - if (is != null) { - try { - is.close(); - } catch (final IOException x2) { - // swallow - } - } - } - } - - public static void restoreWalletFromEncrypted(final Activity activity, - final ViewModelStoreOwner viewModelStoreOwner, - final LifecycleOwner lifecycleOwner, - final Uri walletUri, final InputStream isFile, - final String password, final OnRestoreWalletListener listener) { - try { - final BufferedReader cipherIn = new BufferedReader( - new InputStreamReader(isFile, Charsets.UTF_8)); - final StringBuilder cipherText = new StringBuilder(); - Io.copy(cipherIn, cipherText, Constants.BACKUP_MAX_CHARS); - cipherIn.close(); - - final byte[] plainText = Crypto.decryptBytes(cipherText.toString(), password.toCharArray()); - final InputStream is = new ByteArrayInputStream(plainText); - - listener.onRestoreWallet(WalletUtils.restoreWalletFromProtobufOrBase58(is, Constants.NETWORK_PARAMETERS)); - - log.info("successfully restored encrypted wallet: {}", walletUri); - } catch (final IOException x) { - - final BaseAlertDialogBuilder restoreWalletFromEncryptAlertDialogBuilder = new BaseAlertDialogBuilder(activity); - restoreWalletFromEncryptAlertDialogBuilder.setTitle(activity.getString(R.string.import_export_keys_dialog_failure_title)); - restoreWalletFromEncryptAlertDialogBuilder.setMessage(activity.getString(R.string.import_keys_dialog_failure, x.getMessage())); - restoreWalletFromEncryptAlertDialogBuilder.setPositiveText(activity.getString(R.string.button_dismiss)); - restoreWalletFromEncryptAlertDialogBuilder.setNegativeText(activity.getString(R.string.button_retry)); - restoreWalletFromEncryptAlertDialogBuilder.setNegativeAction( - () -> { - listener.onRetryRequest(); - return Unit.INSTANCE; - } - ); - restoreWalletFromEncryptAlertDialogBuilder.setShowIcon(true); - restoreWalletFromEncryptAlertDialogBuilder.buildAlertDialog().show(); - - log.info("problem restoring wallet: " + walletUri, x); - } - } - - public interface OnRestoreWalletListener { - - void onRestoreWallet(Wallet wallet); - - void onRetryRequest(); - } -} diff --git a/wallet/src/de/schildbach/wallet/ui/backup/RestoreWalletDialogFragment.java b/wallet/src/de/schildbach/wallet/ui/backup/RestoreWalletDialogFragment.java index bd5e419449..0e2718d7de 100644 --- a/wallet/src/de/schildbach/wallet/ui/backup/RestoreWalletDialogFragment.java +++ b/wallet/src/de/schildbach/wallet/ui/backup/RestoreWalletDialogFragment.java @@ -17,6 +17,7 @@ package de.schildbach.wallet.ui.backup; +import android.annotation.SuppressLint; import android.app.Activity; import androidx.appcompat.app.AlertDialog; import android.app.Dialog; @@ -35,6 +36,7 @@ import android.widget.TextView; import androidx.annotation.Nullable; import androidx.fragment.app.DialogFragment; +import androidx.fragment.app.FragmentActivity; import androidx.fragment.app.FragmentManager; import androidx.lifecycle.Observer; import androidx.lifecycle.ViewModelProvider; @@ -53,6 +55,7 @@ import org.bitcoinj.wallet.Wallet; import org.dash.wallet.common.Configuration; import org.dash.wallet.common.ui.BaseAlertDialogBuilder; +import org.dash.wallet.common.ui.dialogs.AdaptiveDialog; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -117,16 +120,11 @@ public void onCreate(final Bundle savedInstanceState) { log.info("opening dialog {}", getClass().getName()); viewModel = new ViewModelProvider(activity).get(RestoreWalletFromFileViewModel.class); - viewModel.getShowSuccessDialog().observe(this, new Observer() { - @Override - public void onChanged(final Boolean showEncryptedMessage) { - SuccessDialogFragment.showDialog(fragmentManager, showEncryptedMessage); - } - }); + viewModel.getShowFailureDialog().observe(this, new Observer() { @Override public void onChanged(final String message) { - FailureDialogFragment.showDialog(fragmentManager, message, viewModel.getBackupUri().getValue()); + showRetryDialog(activity, listener, message); } }); viewModel.getBackupUri().observe(this, uri -> { @@ -239,22 +237,8 @@ private void handleRestore(final String password) { final Uri backupUri = viewModel.getBackupUri().getValue(); if (backupUri != null) { try { - final InputStream is = contentResolver.openInputStream(backupUri); - - if (WalletUtils.isUnencryptedStream(contentResolver.openInputStream(backupUri))) { - RestoreFromFileHelper.restoreWalletFromProtobuf(activity, - backupUri, contentResolver.openInputStream(backupUri), - application.getWalletExtensions(), listener); - } else if (WalletUtils.isKeysStream(contentResolver.openInputStream(backupUri))) { - RestoreFromFileHelper.restorePrivateKeysFromBase58(activity, - this.getActivity(), this.getActivity(), - backupUri, contentResolver.openInputStream(backupUri), listener); - } else if (Crypto.isEncryptedStream(is)) { - RestoreFromFileHelper.restoreWalletFromEncrypted(activity, - this.getActivity(), this.getActivity(), - backupUri, contentResolver.openInputStream(backupUri), password, listener); - } - + Wallet wallet = viewModel.restoreWalletFromUri(backupUri, password); + listener.onRestoreWallet(wallet); log.info("successfully restored wallet from external source"); } catch (final IOException x) { viewModel.getShowFailureDialog().setValue(x.getMessage()); @@ -267,100 +251,10 @@ private void handleRestore(final String password) { } } - public static class SuccessDialogFragment extends DialogFragment { - private static final String FRAGMENT_TAG = SuccessDialogFragment.class.getName(); - private static final String KEY_SHOW_ENCRYPTED_MESSAGE = "show_encrypted_message"; - - private Activity activity; - - public static void showDialog(final FragmentManager fm, final boolean showEncryptedMessage) { - final DialogFragment newFragment = new SuccessDialogFragment(); - final Bundle args = new Bundle(); - args.putBoolean(KEY_SHOW_ENCRYPTED_MESSAGE, showEncryptedMessage); - newFragment.setArguments(args); - newFragment.show(fm, FRAGMENT_TAG); - } - - @Override - public void onAttach(final Context context) { - super.onAttach(context); - this.activity = (Activity) context; - } - - @Override - public Dialog onCreateDialog(final Bundle savedInstanceState) { - final boolean showEncryptedMessage = getArguments().getBoolean(KEY_SHOW_ENCRYPTED_MESSAGE); - final StringBuilder message = new StringBuilder(); - message.append(getString(R.string.restore_wallet_dialog_success)); - message.append("\n\n"); - message.append(getString(R.string.restore_wallet_dialog_success_replay)); - if (showEncryptedMessage) { - message.append("\n\n"); - message.append(getString(R.string.restore_wallet_dialog_success_encrypted)); - } - - final BaseAlertDialogBuilder restoreWalletSuccessAlertDialogBuilder = new BaseAlertDialogBuilder(requireActivity()); - restoreWalletSuccessAlertDialogBuilder.setMessage(message); - restoreWalletSuccessAlertDialogBuilder.setNeutralText(getString(R.string.button_ok)); - restoreWalletSuccessAlertDialogBuilder.setNeutralAction( - () -> { - WalletApplication.getInstance().resetBlockchain(); - activity.finish(); - return Unit.INSTANCE; - } - ); - return restoreWalletSuccessAlertDialogBuilder.buildAlertDialog(); - } - } - - public static class FailureDialogFragment extends DialogFragment { - private static final String FRAGMENT_TAG = FailureDialogFragment.class.getName(); - private static final String KEY_EXCEPTION_MESSAGE = "exception_message"; - private static final String KEY_BACKUP_URI = "backup_uri"; - - private Activity activity; - - public static void showDialog(final FragmentManager fm, final String exceptionMessage, final Uri backupUri) { - final DialogFragment newFragment = new FailureDialogFragment(); - final Bundle args = new Bundle(); - args.putString(KEY_EXCEPTION_MESSAGE, exceptionMessage); - args.putParcelable(KEY_BACKUP_URI, checkNotNull(backupUri)); - newFragment.setArguments(args); - newFragment.show(fm, FRAGMENT_TAG); - } - - @Override - public void onAttach(final Context context) { - super.onAttach(context); - this.activity = (Activity) context; - } - - @Override - public Dialog onCreateDialog(final Bundle savedInstanceState) { - final String exceptionMessage = getArguments().getString(KEY_EXCEPTION_MESSAGE); - final Uri backupUri = checkNotNull((Uri) getArguments().getParcelable(KEY_BACKUP_URI)); - - BaseAlertDialogBuilder restoreWalletFailAlertDialogBuilder = new BaseAlertDialogBuilder(requireActivity()); - restoreWalletFailAlertDialogBuilder.setTitle(getString(R.string.import_export_keys_dialog_failure_title)); - restoreWalletFailAlertDialogBuilder.setMessage(formatString(requireContext(), R.string.import_keys_dialog_failure, exceptionMessage)); - restoreWalletFailAlertDialogBuilder.setPositiveText(getString(R.string.button_dismiss)); - restoreWalletFailAlertDialogBuilder.setNegativeText(getString(R.string.button_retry)); - restoreWalletFailAlertDialogBuilder.setNegativeAction( - () -> { - RestoreWalletDialogFragment.show(getParentFragmentManager(), backupUri); - return Unit.INSTANCE; - } - ); - restoreWalletFailAlertDialogBuilder.setShowIcon(true); - - return restoreWalletFailAlertDialogBuilder.buildAlertDialog(); - } - } - private void maybeFinishActivity() { } - RestoreFromFileHelper.OnRestoreWalletListener listener = new RestoreFromFileHelper.OnRestoreWalletListener() { + OnRestoreWalletListener listener = new OnRestoreWalletListener() { @Override public void onRestoreWallet(Wallet wallet) { @@ -372,4 +266,17 @@ public void onRetryRequest() { viewModel.getRetryRequest().call(null); } }; + + @SuppressLint("StringFormatInvalid") + private static void showRetryDialog(Activity activity, OnRestoreWalletListener listener, String message) { + AdaptiveDialog.create(R.drawable.ic_backup_info, + activity.getString(R.string.import_export_keys_dialog_failure_title), + activity.getString(R.string.import_keys_dialog_failure, message), + activity.getString(R.string.button_dismiss), + activity.getString(R.string.button_retry) + ).show((FragmentActivity) activity, retry -> { + listener.onRetryRequest(); + return Unit.INSTANCE; + }); + } } diff --git a/wallet/src/de/schildbach/wallet/ui/more/masternode_keys/MasternodeKeysViewModel.kt b/wallet/src/de/schildbach/wallet/ui/more/masternode_keys/MasternodeKeysViewModel.kt index 92719ec47a..66b7367d76 100644 --- a/wallet/src/de/schildbach/wallet/ui/more/masternode_keys/MasternodeKeysViewModel.kt +++ b/wallet/src/de/schildbach/wallet/ui/more/masternode_keys/MasternodeKeysViewModel.kt @@ -60,6 +60,7 @@ class MasternodeKeysViewModel @Inject constructor( private val log = LoggerFactory.getLogger(MasternodeKeysViewModel::class.java) } + // this is the only place in the app that should addOrGetExistingExtension besides WalletFactory private val authenticationGroup: AuthenticationGroupExtension = walletData.wallet!!.addOrGetExistingExtension( AuthenticationGroupExtension(walletData.wallet) ) as AuthenticationGroupExtension diff --git a/wallet/src/de/schildbach/wallet/util/WalletUtils.java b/wallet/src/de/schildbach/wallet/util/WalletUtils.java index 1ba66890f1..079a05f9ed 100644 --- a/wallet/src/de/schildbach/wallet/util/WalletUtils.java +++ b/wallet/src/de/schildbach/wallet/util/WalletUtils.java @@ -117,88 +117,6 @@ public static Editable formatHash(@Nullable final String prefix, final String ad return builder; } - public static Wallet restoreWalletFromProtobufOrBase58(final InputStream is, - final NetworkParameters expectedNetworkParameters) throws IOException { - return restoreWalletFromProtobufOrBase58(is, expectedNetworkParameters, null); - } - - public static Wallet restoreWalletFromProtobufOrBase58(final InputStream is, - final NetworkParameters expectedNetworkParameters, @Nullable final WalletExtension[] walletExtensions) throws IOException { - is.mark((int) Constants.BACKUP_MAX_CHARS); - - try { - return restoreWalletFromProtobuf(is, expectedNetworkParameters, walletExtensions); - } catch (final IOException x) { - try { - is.reset(); - Wallet wallet = restorePrivateKeysFromBase58(is, expectedNetworkParameters); - wallet.upgradeToDeterministic(null); //this will result in a different HD seed each time - return wallet; - } catch (final IOException x2) { - throw new IOException( - "cannot read protobuf (" + x.getMessage() + ") or base58 (" + x2.getMessage() + ")", x); - } - } - } - - public static Wallet restoreWalletFromProtobuf(final InputStream is, - final NetworkParameters expectedNetworkParameters, - final WalletExtension[] walletExtensions) throws IOException { - try { - final Wallet wallet = new WalletProtobufSerializer().readWallet(is, true, walletExtensions); - - if (!wallet.getParams().equals(expectedNetworkParameters)) - throw new IOException("bad wallet backup network parameters: " + wallet.getParams().getId()); - if (!wallet.isConsistent()) - throw new IOException("inconsistent wallet backup"); - - return wallet; - } catch (final UnreadableWalletException x) { - throw new IOException("unreadable wallet", x); - } - } - - public static Wallet restoreWalletFromSeed(final List words, - final NetworkParameters expectedNetworkParameters, - WalletExtension[] extensions) throws IOException { - try { - DeterministicSeed seed = new DeterministicSeed(words, null,"", Constants.EARLIEST_HD_SEED_CREATION_TIME); - KeyChainGroup group = KeyChainGroup.builder(Constants.NETWORK_PARAMETERS) - .fromSeed(seed, Script.ScriptType.P2PKH) - .addChain(DeterministicKeyChain.builder() - .seed(seed) - .accountPath(Constants.BIP44_PATH) - .build()) - .build(); - - final Wallet wallet = new Wallet(Constants.NETWORK_PARAMETERS, group); - for (WalletExtension extension : extensions) { - wallet.addExtension(extension); - } - - if (!wallet.getParams().equals(expectedNetworkParameters)) - throw new IOException("bad wallet backup network parameters: " + wallet.getParams().getId()); - if (!wallet.isConsistent()) - throw new IOException("inconsistent wallet backup"); - - return wallet; - } finally { - - } - - } - - public static Wallet restorePrivateKeysFromBase58(final InputStream is, - final NetworkParameters expectedNetworkParameters) throws IOException { - final BufferedReader keyReader = new BufferedReader(new InputStreamReader(is, Charsets.UTF_8)); - - // create non-HD wallet - final KeyChainGroup group = KeyChainGroup.builder(expectedNetworkParameters).build(); - - group.importKeys(WalletUtils.readKeys(keyReader, expectedNetworkParameters)); - return new Wallet(expectedNetworkParameters, group); - } - public static void writeKeys(final Writer out, final List keys) throws IOException { final DateFormat format = Iso8601Format.newDateTimeFormatT(); @@ -214,78 +132,6 @@ public static void writeKeys(final Writer out, final List keys) throws IO } } - public static List readKeys(final BufferedReader in, final NetworkParameters expectedNetworkParameters) - throws IOException { - try { - final DateFormat format = Iso8601Format.newDateTimeFormatT(); - - final List keys = new LinkedList(); - - long charCount = 0; - while (true) { - final String line = in.readLine(); - if (line == null) - break; // eof - charCount += line.length(); - if (charCount > Constants.BACKUP_MAX_CHARS) - throw new IOException("read more than the limit of " + Constants.BACKUP_MAX_CHARS + " characters"); - if (line.trim().isEmpty() || line.charAt(0) == '#') - continue; // skip comment - - final String[] parts = line.split(" "); - - final ECKey key = DumpedPrivateKey.fromBase58(expectedNetworkParameters, parts[0]).getKey(); - key.setCreationTimeSeconds( - parts.length >= 2 ? format.parse(parts[1]).getTime() / DateUtils.SECOND_IN_MILLIS : 0); - - keys.add(key); - } - - return keys; - } catch (final AddressFormatException x) { - throw new IOException("cannot read keys", x); - } catch (final ParseException x) { - throw new IOException("cannot read keys", x); - } - } - - public static Boolean isKeysStream(InputStream is) { - BufferedReader reader = null; - - try { - reader = new BufferedReader(new InputStreamReader(is, Charsets.UTF_8)); - WalletUtils.readKeys(reader, Constants.NETWORK_PARAMETERS); - return true; - } catch (final IOException x) { - return false; - } finally { - if (reader != null) { - try { - reader.close(); - } catch (final IOException x) { - // swallow - } - } - try { - is.reset(); - } catch (IOException x) { - //swallow - } - } - } - - public static Boolean isUnencryptedStream(InputStream is) { - try { - return WalletProtobufSerializer.isWallet(is); - } finally { - try { - is.reset(); - } catch (IOException x) { - //swallow - } - } - } - public static byte[] walletToByteArray(final Wallet wallet) { try { final ByteArrayOutputStream os = new ByteArrayOutputStream(); diff --git a/wallet/test/de/schildbach/wallet/util/WalletUtilsTest.java b/wallet/test/de/schildbach/wallet/util/WalletUtilsTest.java deleted file mode 100644 index 797a0bd98e..0000000000 --- a/wallet/test/de/schildbach/wallet/util/WalletUtilsTest.java +++ /dev/null @@ -1,69 +0,0 @@ -/* - * Copyright 2015 the original author or authors. - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -package de.schildbach.wallet.util; - -import java.io.IOException; - -import org.bitcoinj.core.Context; -import org.bitcoinj.params.MainNetParams; -import org.bitcoinj.params.TestNet3Params; -import org.junit.Before; -import org.junit.Test; - -/** - * @author Andreas Schildbach - */ -public class WalletUtilsTest { - @Before - public void recreateContext() { - // Forcefully re-create context to avoid the context mismatch bug - Context context = new Context(TestNet3Params.get()); - } - - @Test - public void restoreWalletFromProtobufOrBase58() throws Exception { - WalletUtils.restoreWalletFromProtobufOrBase58(getClass().getResourceAsStream("backup-protobuf-testnet"), - TestNet3Params.get()); - WalletUtils.restoreWalletFromProtobufOrBase58(getClass().getResourceAsStream("backup-base58-testnet"), - TestNet3Params.get()); - } - - @Test(expected = IOException.class) - public void restoreWalletFromProtobuf_wrongNetwork() throws Exception { - WalletUtils.restoreWalletFromProtobufOrBase58(getClass().getResourceAsStream("backup-protobuf-testnet"), - MainNetParams.get()); - } - - @Test(expected = IOException.class) - public void restoreWalletFromBase58_wrongNetwork() throws Exception { - WalletUtils.restoreWalletFromProtobufOrBase58(getClass().getResourceAsStream("backup-base58-testnet"), - MainNetParams.get()); - } - - @Test(expected = IOException.class) - public void restoreWalletFromProtobuf_wrongNetwork_bitcoin() throws Exception { - WalletUtils.restoreWalletFromProtobufOrBase58(getClass().getResourceAsStream("bitcoin-backup-protobuf-testnet"), - MainNetParams.get()); - } - - @Test(expected = IOException.class) - public void restoreWalletFromBase58_wrongNetwork_bitcoin() throws Exception { - WalletUtils.restoreWalletFromProtobufOrBase58(getClass().getResourceAsStream("bitcoin-backup-base58-testnet"), - MainNetParams.get()); - } -} diff --git a/wallet/test/de/schildbach/wallet/util/backup-protobuf-testnet b/wallet/test/de/schildbach/wallet/util/backup-protobuf-testnet deleted file mode 100644 index 5a502b45d6a620b33fdf8599d70859a978cb0a68..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 24647 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 I)XOV diff --git a/wallet/test/de/schildbach/wallet/util/bitcoin-wallet-backup-testnet-3.50 b/wallet/test/de/schildbach/wallet/util/bitcoin-wallet-backup-testnet-3.50 deleted file mode 100644 index 7869fdc651..0000000000 --- a/wallet/test/de/schildbach/wallet/util/bitcoin-wallet-backup-testnet-3.50 +++ /dev/null @@ -1,4 +0,0 @@ -U2FsdGVkX19pY3qMQBP4lcdqITcJZLWS3CA99iFfEpwrt3O0f57yDfGDFxpybDZBNEus0OeP0a2m -x9hwj7CGWaec6eU1pFQs/JF3TaGnH/i32VrmLU5TX2ay06+0XcIbommjC+U0Slx5HWS2ARt3UBF7 -dktVryZQrzpdncYVe88Cy8r2RezpwTScVBXyUnxSPpStVwoUy8ogJ4cAakhpHOu5n5qkQeHAy0Cv -iaFVlOc= \ No newline at end of file diff --git a/wallet/test/de/schildbach/wallet/util/bitcoin-wallet-backup-testnet-3.50-crlf b/wallet/test/de/schildbach/wallet/util/bitcoin-wallet-backup-testnet-3.50-crlf deleted file mode 100644 index 55e8fb9d29..0000000000 --- a/wallet/test/de/schildbach/wallet/util/bitcoin-wallet-backup-testnet-3.50-crlf +++ /dev/null @@ -1,4 +0,0 @@ -U2FsdGVkX19pY3qMQBP4lcdqITcJZLWS3CA99iFfEpwrt3O0f57yDfGDFxpybDZBNEus0OeP0a2m -x9hwj7CGWaec6eU1pFQs/JF3TaGnH/i32VrmLU5TX2ay06+0XcIbommjC+U0Slx5HWS2ARt3UBF7 -dktVryZQrzpdncYVe88Cy8r2RezpwTScVBXyUnxSPpStVwoUy8ogJ4cAakhpHOu5n5qkQeHAy0Cv -iaFVlOc= diff --git a/wallet/test/de/schildbach/wallet/util/services/WalletFactoryTest.kt b/wallet/test/de/schildbach/wallet/util/services/WalletFactoryTest.kt new file mode 100644 index 0000000000..c0f293731f --- /dev/null +++ b/wallet/test/de/schildbach/wallet/util/services/WalletFactoryTest.kt @@ -0,0 +1,204 @@ +/* + * Copyright 2023 Dash Core Group + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package de.schildbach.wallet.util.services + +import android.content.ContentResolver +import android.net.Uri +import de.schildbach.wallet.WalletApplication +import de.schildbach.wallet.service.DashWalletFactory +import io.mockk.every +import io.mockk.mockk +import io.mockk.mockkStatic +import io.mockk.unmockkAll +import junit.framework.TestCase.assertEquals +import junit.framework.TestCase.assertFalse +import junit.framework.TestCase.assertTrue +import junit.framework.TestCase.fail +import org.bitcoinj.core.Context +import org.bitcoinj.params.MainNetParams +import org.bitcoinj.params.TestNet3Params +import org.junit.After +import org.junit.Before +import org.junit.Test +import java.io.IOException + +class WalletFactoryTest { + private val contentResolver = mockk() + private val application = mockk() + + @Before + fun setup() { + every { application.contentResolver } returns contentResolver + mockkStatic(Context::class) + } + + @After + fun tearDown() { + // Unmock all after tests + unmockkAll() + } + + @Test + fun createTest() { + val walletFactory = DashWalletFactory(application) + val context = Context(MainNetParams.get()) + walletFactory.create(context.params) + } + + @Test + fun restoreFromSeedTest() { + val walletFactory = DashWalletFactory(application) + val contextMocked = mockk() + every { contextMocked.params } returns MainNetParams.get() + every { Context.getOrCreate(any()) } returns contextMocked + + try { + walletFactory.restoreFromSeed( + contextMocked.params, + "innocent two another top giraffe trigger urban top oyster stove gym danger".split(' ') + ) + } catch (e: Exception) { + println(e.message) + e.printStackTrace() + fail(e.message) + } + } + + @Test + fun restoreFromEncryptedFileTest() { + val walletFactory = DashWalletFactory(application) + val context = Context(MainNetParams.get()) + + val withPinUri = mockk() + val withoutPinUri = mockk() + + every { + contentResolver.openInputStream(withoutPinUri) + } answers { + WalletFactoryTest::class.java.getResourceAsStream("dash-wallet-backup-5.17.5-nopin") + } + + every { + contentResolver.openInputStream(withPinUri) + } answers { + val stream = WalletFactoryTest::class.java.getResourceAsStream("dash-wallet-backup-5.17.5-pin") + stream + } + + val (walletWithPin, newSeed) = walletFactory.restoreFromFile(context.params, withPinUri, "1111") + assertTrue(walletWithPin.isEncrypted) + assertTrue(walletWithPin.checkPassword("1111")) + assertFalse(newSeed) + val decryptedSeed = walletWithPin.keyChainSeed.decrypt( + walletWithPin.keyCrypter, + "", + walletWithPin.keyCrypter?.deriveKey("1111") + ) + assertEquals( + "innocent two another top giraffe trigger urban top oyster stove gym danger".split(' '), + decryptedSeed.mnemonicCode + ) + + val (walletWithoutPin, newSeedWithoutPin) = walletFactory.restoreFromFile(context.params, withoutPinUri, "1111") + assertFalse(walletWithoutPin.isEncrypted) + assertFalse(newSeedWithoutPin) + assertEquals( + "innocent two another top giraffe trigger urban top oyster stove gym danger".split(' '), + walletWithoutPin.keyChainSeed.mnemonicCode + ) + } + + @Test + fun restoreFromKeyFileTest() { + val walletFactory = DashWalletFactory(application) + val context = Context(TestNet3Params.get()) + val keysUri = mockk() + + every { + contentResolver.openInputStream(keysUri) + } answers { + WalletFactoryTest::class.java.getResourceAsStream("backup-base58-testnet") + } + + val (walletFromKeys, newSeed) = walletFactory.restoreFromFile(context.params, keysUri, "") + assertTrue(!walletFromKeys.isEncrypted) + assertTrue(newSeed) + } + + @Test(expected = IOException::class) + fun restoreFromEncryptedFileTest_wrongNetwork() { + val walletFactory = DashWalletFactory(application) + val contextTestnet = Context(TestNet3Params.get()) + + val withoutPinUri = mockk() + + every { + contentResolver.openInputStream(withoutPinUri) + } answers { + WalletFactoryTest::class.java.getResourceAsStream("dash-wallet-backup-5.17.5-nopin") + } + + walletFactory.restoreFromFile(contextTestnet.params, withoutPinUri, "1111") + } + + @Test(expected = IOException::class) + fun restoreFromKeyFileTest_wrongNetwork() { + val walletFactory = DashWalletFactory(application) + val context = Context(MainNetParams.get()) + val keysUri = mockk() + + every { + contentResolver.openInputStream(keysUri) + } answers { + WalletFactoryTest::class.java.getResourceAsStream("backup-base58-testnet") + } + + walletFactory.restoreFromFile(context.params, keysUri, "") + } + + @Test(expected = IOException::class) + fun restoreFromFileTest_wrongCoin() { + val walletFactory = DashWalletFactory(application) + val contextTestnet = Context(TestNet3Params.get()) + + val withoutPinUri = mockk() + + every { + contentResolver.openInputStream(withoutPinUri) + } answers { + WalletFactoryTest::class.java.getResourceAsStream("bitcoin-backup-protobuf-testnet") + } + + walletFactory.restoreFromFile(contextTestnet.params, withoutPinUri, "") + } + + @Test(expected = IOException::class) + fun restoreFromKeyFileTest_wrongCoin() { + val walletFactory = DashWalletFactory(application) + val context = Context(MainNetParams.get()) + val keysUri = mockk() + + every { + contentResolver.openInputStream(keysUri) + } answers { + WalletFactoryTest::class.java.getResourceAsStream("bitcoin-backup-base58-testnet") + } + + walletFactory.restoreFromFile(context.params, keysUri, "") + } +} diff --git a/wallet/test/de/schildbach/wallet/util/backup-base58-testnet b/wallet/test/de/schildbach/wallet/util/services/backup-base58-testnet similarity index 100% rename from wallet/test/de/schildbach/wallet/util/backup-base58-testnet rename to wallet/test/de/schildbach/wallet/util/services/backup-base58-testnet diff --git a/wallet/test/de/schildbach/wallet/util/bitcoin-backup-base58-testnet b/wallet/test/de/schildbach/wallet/util/services/bitcoin-backup-base58-testnet similarity index 100% rename from wallet/test/de/schildbach/wallet/util/bitcoin-backup-base58-testnet rename to wallet/test/de/schildbach/wallet/util/services/bitcoin-backup-base58-testnet diff --git a/wallet/test/de/schildbach/wallet/util/bitcoin-backup-protobuf-testnet b/wallet/test/de/schildbach/wallet/util/services/bitcoin-backup-protobuf-testnet similarity index 100% rename from wallet/test/de/schildbach/wallet/util/bitcoin-backup-protobuf-testnet rename to wallet/test/de/schildbach/wallet/util/services/bitcoin-backup-protobuf-testnet diff --git a/wallet/test/de/schildbach/wallet/util/services/dash-wallet-backup-5.17.5-nopin b/wallet/test/de/schildbach/wallet/util/services/dash-wallet-backup-5.17.5-nopin new file mode 100644 index 0000000000..44673f9861 --- /dev/null +++ b/wallet/test/de/schildbach/wallet/util/services/dash-wallet-backup-5.17.5-nopin @@ -0,0 +1,411 @@ +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 +6FOuLk1zpxA2m9hkWK3Y6ioKlDl1ow== \ No newline at end of file diff --git a/wallet/test/de/schildbach/wallet/util/services/dash-wallet-backup-5.17.5-pin b/wallet/test/de/schildbach/wallet/util/services/dash-wallet-backup-5.17.5-pin new file mode 100644 index 0000000000..9e90529317 --- /dev/null +++ b/wallet/test/de/schildbach/wallet/util/services/dash-wallet-backup-5.17.5-pin @@ -0,0 +1,415 @@ +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 +XR9V0Sn9P7XBpn0V9dT1/6pR \ No newline at end of file From e7e65feea51121fb409d1398be80e328d2a7c332 Mon Sep 17 00:00:00 2001 From: HashEngineering Date: Wed, 18 Oct 2023 14:26:36 -0400 Subject: [PATCH 05/28] feat: support UI to disable battery optimization (#1214) * feat: add prequest ignore battery optimizations permission * fix: add headers and mnlistdiff to activity history in BlockchainServiceImpl * fix: add battery optimization to SettingsActivity * fix: add battery optimization to WalletActivity * feat: update report to include a few more items including battery optimization setting. --- Gemfile | 2 +- Gemfile.lock | 49 +++++++------- wallet/AndroidManifest.xml | 1 + wallet/res/drawable/ic_bolt_border.xml | 51 ++++++++++++++ wallet/res/drawable/ic_receive_payments.xml | 51 ++++++++++++++ .../ic_transaction_received_border.xml | 51 ++++++++++++++ wallet/res/layout/activity_settings.xml | 40 +++++++++++ wallet/res/values/strings.xml | 11 ++++ .../schildbach/wallet/WalletApplication.java | 3 + .../wallet/service/BlockchainServiceImpl.java | 31 +++++++-- .../wallet/service/PackageInfoProvider.kt | 15 +++++ .../wallet/ui/ReportIssueDialogBuilder.java | 6 +- .../de/schildbach/wallet/ui/SetPinActivity.kt | 5 +- .../schildbach/wallet/ui/SettingsActivity.kt | 66 +++++++++++++++++++ .../wallet/ui/main/WalletActivity.java | 40 ++++++++--- .../wallet/ui/more/AboutActivity.kt | 4 +- .../schildbach/wallet/ui/more/MoreFragment.kt | 5 +- .../wallet/ui/more/SettingsViewModel.kt | 33 ++++++++++ .../wallet/ui/staking/StakingActivity.kt | 3 +- .../TransactionDetailsDialogFragment.kt | 5 +- .../transactions/TransactionResultActivity.kt | 3 +- .../schildbach/wallet/util/CrashReporter.java | 22 +++++-- 22 files changed, 443 insertions(+), 54 deletions(-) create mode 100644 wallet/res/drawable/ic_bolt_border.xml create mode 100644 wallet/res/drawable/ic_receive_payments.xml create mode 100644 wallet/res/drawable/ic_transaction_received_border.xml create mode 100644 wallet/src/de/schildbach/wallet/ui/more/SettingsViewModel.kt diff --git a/Gemfile b/Gemfile index 7f831a0a2f..fc76deac4e 100644 --- a/Gemfile +++ b/Gemfile @@ -6,7 +6,7 @@ git_source(:github) { |repo_name| "https://github.com/#{repo_name}" } # gem "rails" -gem "fastlane", "~> 2.214.0" +gem "fastlane", "~> 2.216.0" plugins_path = File.join(File.dirname(__FILE__), 'fastlane', 'Pluginfile') eval_gemfile(plugins_path) if File.exist?(plugins_path) diff --git a/Gemfile.lock b/Gemfile.lock index d0a2d937fe..eebca3a04d 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -3,22 +3,22 @@ GEM specs: CFPropertyList (3.0.6) rexml - addressable (2.8.4) + addressable (2.8.5) public_suffix (>= 2.0.2, < 6.0) artifactory (3.0.15) atomos (0.1.3) aws-eventstream (1.2.0) - aws-partitions (1.793.0) - aws-sdk-core (3.180.0) + aws-partitions (1.830.0) + aws-sdk-core (3.184.0) aws-eventstream (~> 1, >= 1.0.2) aws-partitions (~> 1, >= 1.651.0) aws-sigv4 (~> 1.5) jmespath (~> 1, >= 1.6.1) - aws-sdk-kms (1.71.0) - aws-sdk-core (~> 3, >= 3.177.0) + aws-sdk-kms (1.72.0) + aws-sdk-core (~> 3, >= 3.184.0) aws-sigv4 (~> 1.1) - aws-sdk-s3 (1.132.0) - aws-sdk-core (~> 3, >= 3.179.0) + aws-sdk-s3 (1.136.0) + aws-sdk-core (~> 3, >= 3.181.0) aws-sdk-kms (~> 1) aws-sigv4 (~> 1.6) aws-sigv4 (1.6.0) @@ -36,7 +36,7 @@ GEM unf (>= 0.0.5, < 1.0.0) dotenv (2.8.1) emoji_regex (3.2.3) - excon (0.100.0) + excon (0.103.0) faraday (1.10.3) faraday-em_http (~> 1.0) faraday-em_synchrony (~> 1.0) @@ -66,7 +66,7 @@ GEM faraday_middleware (1.2.0) faraday (~> 1.0) fastimage (2.2.7) - fastlane (2.214.0) + fastlane (2.216.0) CFPropertyList (>= 2.3, < 4.0.0) addressable (>= 2.8, < 3.0.0) artifactory (~> 3.0) @@ -87,6 +87,7 @@ GEM google-apis-playcustomapp_v1 (~> 0.1) google-cloud-storage (~> 1.31) highline (~> 2.0) + http-cookie (~> 1.0.5) json (< 3.0.0) jwt (>= 2.1.0, < 3) mini_magick (>= 4.9.4, < 5.0.0) @@ -98,16 +99,17 @@ GEM security (= 0.1.3) simctl (~> 1.6.3) terminal-notifier (>= 2.0.0, < 3.0.0) - terminal-table (>= 1.4.5, < 2.0.0) + terminal-table (~> 3) tty-screen (>= 0.6.3, < 1.0.0) tty-spinner (>= 0.8.0, < 1.0.0) word_wrap (~> 1.0.0) xcodeproj (>= 1.13.0, < 2.0.0) xcpretty (~> 0.3.0) xcpretty-travis-formatter (>= 0.0.3) - fastlane-plugin-firebase_app_distribution (0.6.1) + fastlane-plugin-firebase_app_distribution (0.7.4) + google-apis-firebaseappdistribution_v1 (~> 0.3.0) gh_inspector (1.1.3) - google-apis-androidpublisher_v3 (0.46.0) + google-apis-androidpublisher_v3 (0.50.0) google-apis-core (>= 0.11.0, < 2.a) google-apis-core (0.11.1) addressable (~> 2.5, >= 2.5.1) @@ -118,6 +120,8 @@ GEM retriable (>= 2.0, < 4.a) rexml webrick + google-apis-firebaseappdistribution_v1 (0.3.0) + google-apis-core (>= 0.11.0, < 2.a) google-apis-iamcredentials_v1 (0.17.0) google-apis-core (>= 0.11.0, < 2.a) google-apis-playcustomapp_v1 (0.13.0) @@ -138,10 +142,9 @@ GEM google-cloud-core (~> 1.6) googleauth (>= 0.16.2, < 2.a) mini_mime (~> 1.0) - googleauth (1.7.0) + googleauth (1.8.1) faraday (>= 0.17.3, < 3.a) jwt (>= 1.4, < 3.0) - memoist (~> 0.16) multi_json (~> 1.11) os (>= 0.9, < 2.0) signet (>= 0.16, < 2.a) @@ -152,9 +155,8 @@ GEM jmespath (1.6.2) json (2.6.3) jwt (2.7.1) - memoist (0.16.2) mini_magick (4.12.0) - mini_mime (1.1.2) + mini_mime (1.1.5) multi_json (1.15.0) multipart-post (2.3.0) nanaimo (0.3.0) @@ -174,7 +176,7 @@ GEM ruby2_keywords (0.0.5) rubyzip (2.3.2) security (0.1.3) - signet (0.17.0) + signet (0.18.0) addressable (~> 2.8) faraday (>= 0.17.5, < 3.a) jwt (>= 1.5, < 3.0) @@ -183,8 +185,8 @@ GEM CFPropertyList naturally terminal-notifier (2.0.0) - terminal-table (1.8.0) - unicode-display_width (~> 1.1, >= 1.1.1) + terminal-table (3.0.2) + unicode-display_width (>= 1.1.1, < 3) trailblazer-option (0.1.2) tty-cursor (0.7.1) tty-screen (0.8.1) @@ -194,10 +196,10 @@ GEM unf (0.1.4) unf_ext unf_ext (0.0.8.2) - unicode-display_width (1.8.0) + unicode-display_width (2.4.2) webrick (1.8.1) word_wrap (1.0.0) - xcodeproj (1.22.0) + xcodeproj (1.23.0) CFPropertyList (>= 2.3.3, < 4.0) atomos (~> 0.1.3) claide (>= 1.0.2, < 2.0) @@ -210,11 +212,12 @@ GEM xcpretty (~> 0.2, >= 0.0.7) PLATFORMS + arm64-darwin-21 arm64-darwin-22 DEPENDENCIES - fastlane (~> 2.214.0) + fastlane (~> 2.216.0) fastlane-plugin-firebase_app_distribution BUNDLED WITH - 2.4.17 + 2.4.20 diff --git a/wallet/AndroidManifest.xml b/wallet/AndroidManifest.xml index eee828ede5..88528a7027 100644 --- a/wallet/AndroidManifest.xml +++ b/wallet/AndroidManifest.xml @@ -14,6 +14,7 @@ + diff --git a/wallet/res/drawable/ic_bolt_border.xml b/wallet/res/drawable/ic_bolt_border.xml new file mode 100644 index 0000000000..c5382e03cf --- /dev/null +++ b/wallet/res/drawable/ic_bolt_border.xml @@ -0,0 +1,51 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/wallet/res/drawable/ic_receive_payments.xml b/wallet/res/drawable/ic_receive_payments.xml new file mode 100644 index 0000000000..d1a14d955e --- /dev/null +++ b/wallet/res/drawable/ic_receive_payments.xml @@ -0,0 +1,51 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/wallet/res/drawable/ic_transaction_received_border.xml b/wallet/res/drawable/ic_transaction_received_border.xml new file mode 100644 index 0000000000..d1a14d955e --- /dev/null +++ b/wallet/res/drawable/ic_transaction_received_border.xml @@ -0,0 +1,51 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/wallet/res/layout/activity_settings.xml b/wallet/res/layout/activity_settings.xml index 99dedcd04d..257f98f59a 100644 --- a/wallet/res/layout/activity_settings.xml +++ b/wallet/res/layout/activity_settings.xml @@ -102,4 +102,44 @@ app:layout_constraintTop_toTopOf="parent" app:srcCompat="@drawable/ic_menu_row_arrow" /> + + + + + + + + + + + + + \ No newline at end of file diff --git a/wallet/res/values/strings.xml b/wallet/res/values/strings.xml index a2e954df11..9aba87665c 100644 --- a/wallet/res/values/strings.xml +++ b/wallet/res/values/strings.xml @@ -383,4 +383,15 @@ Tap the address from the clipboard to paste it Not a valid Dash Address or URL request Send to Address + + + Battery optimization + Optimized + Unrestricted + + Let Dash Wallet run in the background? + This app is configured to stop running after not being visible for some time.\n\nThis will result in not receiving payment notifications until the app returns to the foreground.\n\nWe recommend allowing this app to run in the background by setting battery optimization to \"Not Optimized\" or \"Unrestricted.\"\n\nDon\'t worry, we will always mind your battery usage. + Do you want to receive payments in the background? + This app is configured to continue running after not being visible for some time.\n\nWe suggest allowing this app to always stay up-to-date with the blockchain, even in the background.\n\nDon\'t worry, we will always mind your battery usage. + Open Settings diff --git a/wallet/src/de/schildbach/wallet/WalletApplication.java b/wallet/src/de/schildbach/wallet/WalletApplication.java index f862bbf39f..0cfe198f1f 100644 --- a/wallet/src/de/schildbach/wallet/WalletApplication.java +++ b/wallet/src/de/schildbach/wallet/WalletApplication.java @@ -52,6 +52,9 @@ import androidx.work.WorkManager; import com.google.common.base.Stopwatch; +import com.google.common.hash.HashCode; +import com.google.common.hash.Hasher; +import com.google.common.hash.Hashing; import org.bitcoinj.core.Address; import org.bitcoinj.core.Coin; diff --git a/wallet/src/de/schildbach/wallet/service/BlockchainServiceImpl.java b/wallet/src/de/schildbach/wallet/service/BlockchainServiceImpl.java index e5bbfa3693..f6a43720f5 100644 --- a/wallet/src/de/schildbach/wallet/service/BlockchainServiceImpl.java +++ b/wallet/src/de/schildbach/wallet/service/BlockchainServiceImpl.java @@ -176,6 +176,7 @@ public class BlockchainServiceImpl extends LifecycleService implements Blockchai private Coin notificationAccumulatedAmount = Coin.ZERO; private final List
notificationAddresses = new LinkedList
(); private AtomicInteger transactionsReceived = new AtomicInteger(); + private AtomicInteger mnListDiffsReceived = new AtomicInteger(); private long serviceCreatedAt; private boolean resetBlockchainOnShutdown = false; private boolean deleteWalletFileOnShutdown = false; @@ -187,6 +188,8 @@ public class BlockchainServiceImpl extends LifecycleService implements Blockchai private final static int MINIMUM_PEER_COUNT = 16; private static final int MIN_COLLECT_HISTORY = 2; + private static final int IDLE_HEADER_TIMEOUT_MIN = 2; + private static final int IDLE_MNLIST_TIMEOUT_MIN = 2; private static final int IDLE_BLOCK_TIMEOUT_MIN = 2; private static final int IDLE_TRANSACTION_TIMEOUT_MIN = 9; private static final int MAX_HISTORY_SIZE = Math.max(IDLE_TRANSACTION_TIMEOUT_MIN, IDLE_BLOCK_TIMEOUT_MIN); @@ -516,6 +519,7 @@ public void onMasterNodeListDiffDownloaded(Stage stage, @Nullable SimplifiedMast if(peerGroup != null && peerGroup.getSyncStage() == PeerGroup.SyncStage.MNLIST) { super.onMasterNodeListDiffDownloaded(stage, mnlistdiff); startPreBlockPercent = syncPercentage; + mnListDiffsReceived.incrementAndGet(); postOrPostDelayed(); } } @@ -772,32 +776,42 @@ public void shutdown() { private final static class ActivityHistoryEntry { public final int numTransactionsReceived; public final int numBlocksDownloaded; + public final int numHeadersDownloaded; + public final int numMnListDiffsDownloaded; - public ActivityHistoryEntry(final int numTransactionsReceived, final int numBlocksDownloaded) { + public ActivityHistoryEntry(final int numTransactionsReceived, final int numBlocksDownloaded, + final int numHeadersDownloaded, final int numMnListDiffsDownloaded) { this.numTransactionsReceived = numTransactionsReceived; this.numBlocksDownloaded = numBlocksDownloaded; + this.numHeadersDownloaded = numHeadersDownloaded; + this.numMnListDiffsDownloaded = numMnListDiffsDownloaded; } @Override public String toString() { - return numTransactionsReceived + "/" + numBlocksDownloaded; + return numTransactionsReceived + "/" + numBlocksDownloaded + "/" + numHeadersDownloaded + "/" + numMnListDiffsDownloaded; } } private final BroadcastReceiver tickReceiver = new BroadcastReceiver() { private int lastChainHeight = 0; + private int lastHeaderHeight = 0; private final List activityHistory = new LinkedList(); @Override public void onReceive(final Context context, final Intent intent) { final int chainHeight = blockChain.getBestChainHeight(); + final int headerHeight = headerChain.getBestChainHeight(); - if (lastChainHeight > 0) { + if (lastChainHeight > 0 || lastHeaderHeight > 0) { final int numBlocksDownloaded = chainHeight - lastChainHeight; final int numTransactionsReceived = transactionsReceived.getAndSet(0); + // instead of counting headers, count header messages which contain up to 2000 headers + final int numHeadersDownloaded = headerHeight - lastHeaderHeight; + final int numMnListDiffsDownloaded = mnListDiffsReceived.getAndSet(0); // push history - activityHistory.add(0, new ActivityHistoryEntry(numTransactionsReceived, numBlocksDownloaded)); + activityHistory.add(0, new ActivityHistoryEntry(numTransactionsReceived, numBlocksDownloaded, numHeadersDownloaded, numMnListDiffsDownloaded)); // trim while (activityHistory.size() > MAX_HISTORY_SIZE) @@ -810,7 +824,7 @@ public void onReceive(final Context context, final Intent intent) { builder.append(", "); builder.append(entry); } - log.info("History of transactions/blocks: " + builder); + log.info("History of transactions/blocks/headers/mnlistdiff: " + builder); // determine if block and transaction activity is idling boolean isIdle = false; @@ -821,8 +835,10 @@ public void onReceive(final Context context, final Intent intent) { final boolean blocksActive = entry.numBlocksDownloaded > 0 && i <= IDLE_BLOCK_TIMEOUT_MIN; final boolean transactionsActive = entry.numTransactionsReceived > 0 && i <= IDLE_TRANSACTION_TIMEOUT_MIN; + final boolean headersActive = entry.numHeadersDownloaded > 0 && i <= IDLE_HEADER_TIMEOUT_MIN; + final boolean mnListDiffsActive = entry.numMnListDiffsDownloaded > 0 && i <= IDLE_MNLIST_TIMEOUT_MIN; - if (blocksActive || transactionsActive) { + if (blocksActive || transactionsActive || headersActive || mnListDiffsActive) { isIdle = false; break; } @@ -837,6 +853,7 @@ public void onReceive(final Context context, final Intent intent) { } lastChainHeight = chainHeight; + lastHeaderHeight = headerHeight; } }; @@ -1043,7 +1060,7 @@ private void startForeground() { @Override public void onDestroy() { - log.debug(".onDestroy()"); + log.info(".onDestroy()"); WalletApplication.scheduleStartBlockchainService(this); //disconnect feature diff --git a/wallet/src/de/schildbach/wallet/service/PackageInfoProvider.kt b/wallet/src/de/schildbach/wallet/service/PackageInfoProvider.kt index 3429ee8337..fd4204331a 100644 --- a/wallet/src/de/schildbach/wallet/service/PackageInfoProvider.kt +++ b/wallet/src/de/schildbach/wallet/service/PackageInfoProvider.kt @@ -21,10 +21,14 @@ import android.content.Context import android.content.pm.PackageInfo import android.content.pm.PackageManager import android.os.Build +import com.google.common.hash.HashCode +import com.google.common.hash.Hashing import dagger.hilt.android.qualifiers.ApplicationContext import de.schildbach.wallet.Constants import org.bitcoinj.core.VersionMessage import org.slf4j.LoggerFactory +import java.io.FileInputStream +import java.io.IOException import javax.inject.Inject import javax.inject.Singleton @@ -82,4 +86,15 @@ class PackageInfoProvider @Inject constructor( null } } + + @Throws(IOException::class) + fun apkHash(): HashCode { + val hasher = Hashing.sha256().newHasher() + val inputStream: FileInputStream = FileInputStream(context.packageCodePath) + val buf = ByteArray(4096) + var read: Int + while (-1 != inputStream.read(buf).also { read = it }) hasher.putBytes(buf, 0, read) + inputStream.close() + return hasher.hash() + } } diff --git a/wallet/src/de/schildbach/wallet/ui/ReportIssueDialogBuilder.java b/wallet/src/de/schildbach/wallet/ui/ReportIssueDialogBuilder.java index 4242168907..180ca9d9c3 100644 --- a/wallet/src/de/schildbach/wallet/ui/ReportIssueDialogBuilder.java +++ b/wallet/src/de/schildbach/wallet/ui/ReportIssueDialogBuilder.java @@ -46,6 +46,7 @@ import android.net.Uri; import androidx.core.content.FileProvider; +import android.os.PowerManager; import android.text.Editable; import android.text.TextWatcher; import android.view.LayoutInflater; @@ -226,7 +227,8 @@ public static ReportIssueDialogBuilder createReportIssueDialog( final Activity context, final PackageInfoProvider packageInfoProvider, final Configuration configuration, - final Wallet wallet + final Wallet wallet, + final WalletApplication application ) { return new ReportIssueDialogBuilder(context, R.string.report_issue_dialog_title_issue, R.string.report_issue_dialog_message_issue) { @@ -239,7 +241,7 @@ protected CharSequence subject() { @Override protected CharSequence collectApplicationInfo() throws IOException { final StringBuilder applicationInfo = new StringBuilder(); - CrashReporter.appendApplicationInfo(applicationInfo, packageInfoProvider, configuration, wallet); + CrashReporter.appendApplicationInfo(applicationInfo, packageInfoProvider, configuration, wallet, application.getSystemService(PowerManager.class)); return applicationInfo; } diff --git a/wallet/src/de/schildbach/wallet/ui/SetPinActivity.kt b/wallet/src/de/schildbach/wallet/ui/SetPinActivity.kt index 6223b0ede5..480c5048cb 100644 --- a/wallet/src/de/schildbach/wallet/ui/SetPinActivity.kt +++ b/wallet/src/de/schildbach/wallet/ui/SetPinActivity.kt @@ -21,6 +21,7 @@ import android.content.Context import android.content.Intent import android.os.Bundle import android.os.Handler +import android.os.PowerManager import android.view.MenuItem import android.view.View import android.widget.TextView @@ -30,6 +31,7 @@ import androidx.appcompat.app.AlertDialog import androidx.appcompat.widget.Toolbar import androidx.lifecycle.lifecycleScope import dagger.hilt.android.AndroidEntryPoint +import de.schildbach.wallet.WalletApplication import de.schildbach.wallet.livedata.Status import de.schildbach.wallet.security.SecurityFunctions import de.schildbach.wallet.service.PackageInfoProvider @@ -486,7 +488,8 @@ class SetPinActivity : InteractionAwareActivity() { this, packageInfoProvider, viewModel.configuration, - viewModel.walletData.wallet + viewModel.walletData.wallet, + application as WalletApplication ).buildAlertDialog() alertDialog?.show() } diff --git a/wallet/src/de/schildbach/wallet/ui/SettingsActivity.kt b/wallet/src/de/schildbach/wallet/ui/SettingsActivity.kt index 015fb6a6a4..d394e1ee2b 100644 --- a/wallet/src/de/schildbach/wallet/ui/SettingsActivity.kt +++ b/wallet/src/de/schildbach/wallet/ui/SettingsActivity.kt @@ -16,13 +16,20 @@ package de.schildbach.wallet.ui +import android.Manifest import android.content.Intent +import android.content.pm.PackageManager +import android.net.Uri import android.os.Bundle +import android.provider.Settings +import androidx.activity.viewModels +import androidx.core.content.ContextCompat import androidx.lifecycle.lifecycleScope import dagger.hilt.android.AndroidEntryPoint import de.schildbach.wallet.WalletBalanceWidgetProvider import de.schildbach.wallet.ui.main.WalletActivity import de.schildbach.wallet.ui.more.AboutActivity +import de.schildbach.wallet.ui.more.SettingsViewModel import de.schildbach.wallet_test.R import de.schildbach.wallet_test.databinding.ActivitySettingsBinding import kotlinx.coroutines.flow.filterNotNull @@ -49,6 +56,8 @@ class SettingsActivity : LockScreenActivity() { @Inject lateinit var walletUIConfig: WalletUIConfig + val viewModel by viewModels() + override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) @@ -74,6 +83,48 @@ class SettingsActivity : LockScreenActivity() { binding.rescanBlockchain.setOnClickListener { resetBlockchain() } binding.notifications.setOnClickListener { systemActions.openNotificationSettings() } + binding.batteryOptimization.setOnClickListener { + if (ContextCompat.checkSelfPermission( + walletApplication, + Manifest.permission.REQUEST_IGNORE_BATTERY_OPTIMIZATIONS + ) == PackageManager.PERMISSION_GRANTED && + !viewModel.isIgnoringBatteryOptimizations + ) { + AdaptiveDialog.create( + R.drawable.ic_bolt_border, + getString(R.string.battery_optimization_dialog_optimized_title), + getString(R.string.battery_optimization_dialog_message_optimized), + getString(R.string.permission_deny), + getString(R.string.permission_allow) + ).show(this) { allow -> + if (allow == true) { + startActivity( + Intent( + Settings.ACTION_REQUEST_IGNORE_BATTERY_OPTIMIZATIONS, + Uri.parse("package:$packageName") + ) + ) + } + } + } else { + AdaptiveDialog.create( + R.drawable.ic_transaction_received_border, + getString(R.string.battery_optimization_dialog_unrestricted_title), + getString(R.string.battery_optimization_dialog_message_not_optimized), + getString(R.string.close), + getString(R.string.battery_optimization_dialog_button_change) + ).show(this) { allow -> + if (allow == true) { + // Show the list of non-optimized apps + val intent = Intent(Settings.ACTION_IGNORE_BATTERY_OPTIMIZATION_SETTINGS) + if (intent.resolveActivity(packageManager) != null) { + startActivity(intent) + } + } + } + } + } + setBatteryOptimizationText() walletUIConfig.observe(WalletUIConfig.SELECTED_CURRENCY) .filterNotNull() @@ -81,6 +132,21 @@ class SettingsActivity : LockScreenActivity() { .launchIn(lifecycleScope) } + private fun setBatteryOptimizationText() { + binding.batterySettingsSubtitle.text = getString( + if (viewModel.isIgnoringBatteryOptimizations) { + R.string.battery_optimization_subtitle_unrestricted + } else { + R.string.battery_optimization_subtitle_optimized + } + ) + } + + override fun onResume() { + super.onResume() + setBatteryOptimizationText() + } + private fun resetBlockchain() { AdaptiveDialog.create( null, diff --git a/wallet/src/de/schildbach/wallet/ui/main/WalletActivity.java b/wallet/src/de/schildbach/wallet/ui/main/WalletActivity.java index 30f6dd69fd..f465051c2f 100644 --- a/wallet/src/de/schildbach/wallet/ui/main/WalletActivity.java +++ b/wallet/src/de/schildbach/wallet/ui/main/WalletActivity.java @@ -22,13 +22,14 @@ import android.content.Intent; import android.content.pm.PackageInfo; import android.content.pm.PackageManager; +import android.net.Uri; import android.nfc.NdefMessage; import android.nfc.NfcAdapter; import android.os.Build; import android.os.Bundle; import android.os.Handler; -import android.os.LocaleList; -import android.telephony.TelephonyManager; +import android.os.PowerManager; +import android.provider.Settings; import androidx.activity.result.ActivityResultLauncher; import androidx.activity.result.contract.ActivityResultContracts; @@ -40,19 +41,15 @@ import org.bitcoinj.crypto.ChildNumber; import org.bitcoinj.wallet.Wallet; -import org.dash.wallet.common.data.CurrencyInfo; import org.dash.wallet.common.ui.BaseAlertDialogBuilder; import org.dash.wallet.common.ui.dialogs.AdaptiveDialog; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.io.IOException; -import java.util.Currency; -import java.util.Locale; import dagger.hilt.android.AndroidEntryPoint; import de.schildbach.wallet.Constants; -import de.schildbach.wallet.WalletBalanceWidgetProvider; import de.schildbach.wallet.data.PaymentIntent; import de.schildbach.wallet.ui.AbstractBindServiceActivity; import de.schildbach.wallet.ui.EncryptKeysDialogFragment; @@ -222,7 +219,8 @@ protected CharSequence collectApplicationInfo() throws IOException { applicationInfo, packageInfoProvider, configuration, - walletData.getWallet() + walletData.getWallet(), + walletApplication.getSystemService(PowerManager.class) ); return applicationInfo; } @@ -296,7 +294,8 @@ private void checkWalletEncryptionDialog() { WalletActivity.this, packageInfoProvider, configuration, - walletData.getWallet() + walletData.getWallet(), + walletApplication ).buildAlertDialog(); alertDialog.show(); } else { @@ -376,6 +375,7 @@ public void onLockScreenDeactivated() { private final ActivityResultLauncher requestPermissionLauncher = registerForActivityResult(new ActivityResultContracts.RequestPermission(), granted -> { // do nothing + requestDisableBatteryOptimisation(); }); /** @@ -397,9 +397,31 @@ private void explainPushNotifications() { getString(R.string.button_okay) ); - dialog.show(this, result -> Unit.INSTANCE); + dialog.show(this, result -> { + requestDisableBatteryOptimisation(); + return Unit.INSTANCE; + }); } // only show either the permissions dialog (Android >= 13) or the explainer (Android <= 12) once configuration.setShowNotificationsExplainer(false); } + + private void requestDisableBatteryOptimisation() { + PowerManager powerManager = getSystemService(PowerManager.class); + if (ContextCompat.checkSelfPermission(walletApplication, Manifest.permission.REQUEST_IGNORE_BATTERY_OPTIMIZATIONS) == PackageManager.PERMISSION_GRANTED && + !powerManager.isIgnoringBatteryOptimizations(walletApplication.getPackageName())) { + AdaptiveDialog.create( + R.drawable.ic_bolt_border, + getString(R.string.battery_optimization_dialog_optimized_title), + getString(R.string.battery_optimization_dialog_message_optimized), + getString(R.string.permission_deny), + getString(R.string.permission_allow) + ).show(this, (allow) -> { + if (allow) { + startActivity(new Intent(Settings.ACTION_REQUEST_IGNORE_BATTERY_OPTIMIZATIONS, Uri.parse("package:" + getPackageName()))); + } + return Unit.INSTANCE; + }); + } + } } diff --git a/wallet/src/de/schildbach/wallet/ui/more/AboutActivity.kt b/wallet/src/de/schildbach/wallet/ui/more/AboutActivity.kt index 10467ecb76..ca453d08be 100644 --- a/wallet/src/de/schildbach/wallet/ui/more/AboutActivity.kt +++ b/wallet/src/de/schildbach/wallet/ui/more/AboutActivity.kt @@ -28,6 +28,7 @@ import androidx.core.view.isVisible import androidx.lifecycle.lifecycleScope import dagger.hilt.android.AndroidEntryPoint import de.schildbach.wallet.Constants +import de.schildbach.wallet.WalletApplication import de.schildbach.wallet.ui.LockScreenActivity import de.schildbach.wallet.ui.ReportIssueDialogBuilder import de.schildbach.wallet_test.BuildConfig @@ -150,7 +151,8 @@ class AboutActivity : LockScreenActivity() { this, packageInfoProvider, configuration, - walletData.wallet + walletData.wallet, + application as WalletApplication ).buildAlertDialog() alertDialog.show() } diff --git a/wallet/src/de/schildbach/wallet/ui/more/MoreFragment.kt b/wallet/src/de/schildbach/wallet/ui/more/MoreFragment.kt index 8c62208d01..39821467ed 100644 --- a/wallet/src/de/schildbach/wallet/ui/more/MoreFragment.kt +++ b/wallet/src/de/schildbach/wallet/ui/more/MoreFragment.kt @@ -26,6 +26,7 @@ import androidx.navigation.NavOptions import androidx.navigation.fragment.findNavController import com.google.android.material.transition.MaterialFadeThrough import dagger.hilt.android.AndroidEntryPoint +import de.schildbach.wallet.WalletApplication import de.schildbach.wallet.service.PackageInfoProvider import de.schildbach.wallet.ui.* import de.schildbach.wallet_test.R @@ -45,6 +46,7 @@ class MoreFragment : Fragment(R.layout.fragment_more) { @Inject lateinit var packageInfoProvider: PackageInfoProvider @Inject lateinit var configuration: Configuration @Inject lateinit var walletData: WalletDataProvider + @Inject lateinit var walletApplication: WalletApplication override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) @@ -94,7 +96,8 @@ class MoreFragment : Fragment(R.layout.fragment_more) { requireActivity(), packageInfoProvider, configuration, - walletData.wallet + walletData.wallet, + walletApplication ).buildAlertDialog() (requireActivity() as LockScreenActivity).alertDialog = alertDialog alertDialog.show() diff --git a/wallet/src/de/schildbach/wallet/ui/more/SettingsViewModel.kt b/wallet/src/de/schildbach/wallet/ui/more/SettingsViewModel.kt new file mode 100644 index 0000000000..ac5a6af738 --- /dev/null +++ b/wallet/src/de/schildbach/wallet/ui/more/SettingsViewModel.kt @@ -0,0 +1,33 @@ +/* + * Copyright 2019 Dash Core Group + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package de.schildbach.wallet.ui.more + +import android.os.PowerManager +import androidx.lifecycle.ViewModel +import dagger.hilt.android.lifecycle.HiltViewModel +import de.schildbach.wallet.WalletApplication +import javax.inject.Inject + +@HiltViewModel +class SettingsViewModel @Inject constructor( + private val walletApplication: WalletApplication +) : ViewModel() { + private val powerManager: PowerManager = walletApplication.getSystemService(PowerManager::class.java) + + val isIgnoringBatteryOptimizations: Boolean + get() = powerManager.isIgnoringBatteryOptimizations(walletApplication.packageName) +} diff --git a/wallet/src/de/schildbach/wallet/ui/staking/StakingActivity.kt b/wallet/src/de/schildbach/wallet/ui/staking/StakingActivity.kt index acd3945de2..f12cb1c107 100644 --- a/wallet/src/de/schildbach/wallet/ui/staking/StakingActivity.kt +++ b/wallet/src/de/schildbach/wallet/ui/staking/StakingActivity.kt @@ -88,7 +88,8 @@ class StakingActivity : LockScreenActivity() { this, packageInfoProvider, configuration, - walletData.wallet + walletData.wallet, + walletApplication ).buildAlertDialog() alertDialog.show() } diff --git a/wallet/src/de/schildbach/wallet/ui/transactions/TransactionDetailsDialogFragment.kt b/wallet/src/de/schildbach/wallet/ui/transactions/TransactionDetailsDialogFragment.kt index cd41e954a4..7785a97e1b 100644 --- a/wallet/src/de/schildbach/wallet/ui/transactions/TransactionDetailsDialogFragment.kt +++ b/wallet/src/de/schildbach/wallet/ui/transactions/TransactionDetailsDialogFragment.kt @@ -21,6 +21,7 @@ import android.view.View import androidx.core.os.bundleOf import androidx.fragment.app.viewModels import dagger.hilt.android.AndroidEntryPoint +import de.schildbach.wallet.WalletApplication import de.schildbach.wallet.service.PackageInfoProvider import de.schildbach.wallet.ui.ReportIssueDialogBuilder import de.schildbach.wallet.ui.TransactionResultViewModel @@ -57,6 +58,7 @@ class TransactionDetailsDialogFragment : OffsetDialogFragment(R.layout.transacti @Inject lateinit var configuration: Configuration @Inject lateinit var packageInfoProvider: PackageInfoProvider + @Inject lateinit var walletApplication: WalletApplication override val backgroundStyle = R.style.PrimaryBackground override val forceExpand = true @@ -124,7 +126,8 @@ class TransactionDetailsDialogFragment : OffsetDialogFragment(R.layout.transacti requireActivity(), packageInfoProvider, configuration, - viewModel.walletData.wallet + viewModel.walletData.wallet, + walletApplication ).buildAlertDialog().show() } diff --git a/wallet/src/de/schildbach/wallet/ui/transactions/TransactionResultActivity.kt b/wallet/src/de/schildbach/wallet/ui/transactions/TransactionResultActivity.kt index 913f99e5db..a011aa5ad3 100644 --- a/wallet/src/de/schildbach/wallet/ui/transactions/TransactionResultActivity.kt +++ b/wallet/src/de/schildbach/wallet/ui/transactions/TransactionResultActivity.kt @@ -155,7 +155,8 @@ class TransactionResultActivity : LockScreenActivity() { this, packageInfoProvider, configuration, - viewModel.walletData.wallet + viewModel.walletData.wallet, + walletApplication ).buildAlertDialog().show() } diff --git a/wallet/src/de/schildbach/wallet/util/CrashReporter.java b/wallet/src/de/schildbach/wallet/util/CrashReporter.java index dd498f9037..5871581773 100644 --- a/wallet/src/de/schildbach/wallet/util/CrashReporter.java +++ b/wallet/src/de/schildbach/wallet/util/CrashReporter.java @@ -59,6 +59,8 @@ import android.content.pm.PackageManager; import android.content.res.Resources; import android.os.Build; +import android.os.PowerManager; + import androidx.core.app.ActivityManagerCompat; /** @@ -177,26 +179,32 @@ public int compare(final PackageInfo lhs, final PackageInfo rhs) { } public static void appendApplicationInfo( - final Appendable report, - final PackageInfoProvider packageInfoProvider, - final Configuration configuration, - final Wallet wallet + final Appendable report, + final PackageInfoProvider packageInfoProvider, + final Configuration configuration, + final Wallet wallet, + final PowerManager powerManager ) throws IOException { final PackageInfo pi = packageInfoProvider.getPackageInfo(); final Calendar calendar = new GregorianCalendar(UTC); report.append("Version: " + pi.versionName + " (" + pi.versionCode + ")\n"); + report.append("APK Hash: ").append(packageInfoProvider.apkHash().toString()).append("\n"); report.append("Package: " + pi.packageName + "\n"); String installer = packageInfoProvider.getInstallerPackageName(); report.append("Installer: " + (installer != null ? installer : "manual") + "\n"); report.append("Test/Prod: " + (Constants.IS_PROD_BUILD ? "prod" : "test") + "\n"); report.append("Flavor: " + BuildConfig.FLAVOR + "\n"); + report.append("Build Type: " + BuildConfig.BUILD_TYPE + "\n"); + final boolean isIgnoringBatteryOptimization = + powerManager.isIgnoringBatteryOptimizations(packageInfoProvider.getPackageInfo().packageName); + report.append("Battery optimization: ").append(isIgnoringBatteryOptimization ? "no" : "yes").append("\n"); report.append("Timezone: " + TimeZone.getDefault().getID() + "\n"); calendar.setTimeInMillis(System.currentTimeMillis()); - report.append("Time: " + String.format(Locale.US, "%tF %tT %tZ", calendar, calendar, calendar) + "\n"); + report.append("Current Time: " + String.format(Locale.US, "%tF %tT %tZ", calendar, calendar, calendar) + "\n"); calendar.setTimeInMillis(WalletApplication.TIME_CREATE_APPLICATION); report.append( - "Time of launch: " + String.format(Locale.US, "%tF %tT %tZ", calendar, calendar, calendar) + "\n"); + "Time of app launch: " + String.format(Locale.US, "%tF %tT %tZ", calendar, calendar, calendar) + "\n"); calendar.setTimeInMillis(pi.lastUpdateTime); report.append( "Time of last update: " + String.format(Locale.US, "%tF %tT %tZ", calendar, calendar, calendar) + "\n"); @@ -257,6 +265,8 @@ public static void appendApplicationInfo( final File filesDir = packageInfoProvider.getFilesDir(); report.append("\nContents of FilesDir " + filesDir + ":\n"); appendDir(report, filesDir, 0); + report.append("free/usable space: ").append(Long.toString(filesDir.getFreeSpace() / 1024)) + .append("/").append(Long.toString(filesDir.getUsableSpace() / 1024)).append(" kB\n"); } private static void appendDir(final Appendable report, final File file, final int indent) throws IOException { From c024ad8b3c7b6c411520ded89faea5862cef1ca0 Mon Sep 17 00:00:00 2001 From: Andrei Ashikhmin Date: Thu, 19 Oct 2023 01:33:51 +0700 Subject: [PATCH 06/28] fix: only set tx icon in updateIcon (#1218) --- .../ui/transactions/TransactionResultViewBinder.kt | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/wallet/src/de/schildbach/wallet/ui/transactions/TransactionResultViewBinder.kt b/wallet/src/de/schildbach/wallet/ui/transactions/TransactionResultViewBinder.kt index 8d9a5856f8..3330dd7b45 100644 --- a/wallet/src/de/schildbach/wallet/ui/transactions/TransactionResultViewBinder.kt +++ b/wallet/src/de/schildbach/wallet/ui/transactions/TransactionResultViewBinder.kt @@ -242,7 +242,6 @@ class TransactionResultViewBinder( binding.openTaxCategoryCard.isVisible = false binding.dashAmount.setStrikeThru(true) binding.fiatValue.setStrikeThru(true) - binding.checkIcon.setImageResource(R.drawable.ic_transaction_failed) binding.transactionTitle.text = context.getText(R.string.transaction_failed_details) var rescanText = "" @@ -267,26 +266,16 @@ class TransactionResultViewBinder( } } else { if (tx.getValue(wallet).signum() < 0) { - binding.checkIcon.setImageResource( - if (tx.isEntirelySelf(wallet)) { - R.drawable.ic_internal - } else { - R.drawable.ic_transaction_sent - } - ) - binding.transactionTitle.setTextColor(ContextCompat.getColor(context, R.color.dash_blue)) binding.transactionTitle.text = context.getText(R.string.transaction_details_amount_sent) binding.transactionAmountSignal.text = "-" binding.transactionAmountSignal.isVisible = true } else { - binding.checkIcon.setImageResource(R.drawable.ic_transaction_received) binding.transactionTitle.setTextColor(ContextCompat.getColor(context, R.color.system_green)) binding.transactionTitle.text = context.getText(R.string.transaction_details_amount_received) binding.transactionAmountSignal.isVisible = true binding.transactionAmountSignal.text = "+" } - binding.checkIcon.isVisible = true binding.feeContainer.isVisible = isFeeAvailable(tx.fee) } } From 9d2a47328bc6e99635cacac0bd67c6b0e81be051 Mon Sep 17 00:00:00 2001 From: Andrei Ashikhmin Date: Thu, 2 Nov 2023 11:35:38 +0700 Subject: [PATCH 07/28] feat(crowdnode): increase withdrawal precision (#1220) * chore: apply ktlint * chore: cleanup 7.5.0 handling code * feat: use web API to request withdrawal * fix: catch unknown host errors * fix: apply ktlint * fix: tests * fix: ktlint check on tests --- .editorconfig | 4 +- integrations/crowdnode/build.gradle | 3 +- .../crowdnode/api/CrowdNodeApi.kt | 165 ++++++++---------- .../crowdnode/api/CrowdNodeBlockchainApi.kt | 15 +- .../api/CrowdNodeConfirmationTxHandler.kt | 11 +- .../crowdnode/api/CrowdNodeWebApi.kt | 62 ++++++- .../crowdnode/api/CrowdNodeWorker.kt | 2 +- .../crowdnode/api/RemoteDataSource.kt | 2 +- .../crowdnode/di/CrowdNodeModule.kt | 2 +- .../crowdnode/model/AddressStatus.kt | 4 +- .../integrations/crowdnode/model/ApiCode.kt | 2 +- .../crowdnode/model/ApiStatuses.kt | 6 +- .../crowdnode/model/CrowdNodeBalance.kt | 12 +- .../model/CrowdNodeIsAddressInUse.kt | 8 +- .../crowdnode/model/CrowdNodeTx.kt | 30 ++-- .../crowdnode/model/IsAddressInUse.kt | 8 +- .../crowdnode/model/IsDefaultEmail.kt | 4 +- .../crowdnode/model/MessageStatus.kt | 2 +- .../crowdnode/model/WithdrawalLimit.kt | 2 +- .../CrowdNodeAcceptTermsResponse.kt | 7 +- .../transactions/CrowdNodeAcceptTermsTx.kt | 7 +- .../CrowdNodeDepositReceivedResponse.kt | 7 +- .../transactions/CrowdNodeDepositTx.kt | 8 +- .../transactions/CrowdNodeErrorResponse.kt | 8 +- .../transactions/CrowdNodeSignUpTx.kt | 7 +- .../transactions/CrowdNodeTopUpTx.kt | 9 +- .../CrowdNodeWelcomeToApiResponse.kt | 7 +- .../CrowdNodeWithdrawalDeniedResponse.kt | 7 +- .../CrowdNodeWithdrawalQueueResponse.kt | 7 +- .../CrowdNodeWithdrawalReceivedTx.kt | 4 +- .../transactions/FullCrowdNodeSignUpTxSet.kt | 21 ++- .../PossibleAcceptTermsResponse.kt | 4 +- .../transactions/PossibleWelcomeResponse.kt | 6 +- .../crowdnode/ui/CrowdNodeViewModel.kt | 2 +- .../crowdnode/ui/ResultFragment.kt | 2 +- .../crowdnode/ui/dialogs/StakingDialog.kt | 2 +- .../ui/dialogs/WithdrawalLimitsInfoDialog.kt | 10 +- .../ui/entry_point/EntryPointFragment.kt | 2 +- .../ui/entry_point/FirstTimeInfoFragment.kt | 2 +- .../ui/entry_point/NewAccountFragment.kt | 25 +-- .../ui/online/OnlineAccountEmailFragment.kt | 27 +-- .../ui/online/OnlineAccountInfoFragment.kt | 2 +- .../ui/online/OnlineSignUpFragment.kt | 2 +- .../crowdnode/ui/portal/PortalFragment.kt | 70 ++++---- .../crowdnode/ui/portal/TransferFragment.kt | 64 ++++--- .../crowdnode/utils/CrowdNodeConstants.kt | 15 +- .../crowdnode/CrowdNodeApiAggregatorTest.kt | 31 +--- .../crowdnode/CrowdNodeTxFilterTest.kt | 68 ++++---- 48 files changed, 428 insertions(+), 349 deletions(-) diff --git a/.editorconfig b/.editorconfig index d9a6e9f183..01fbc8d643 100644 --- a/.editorconfig +++ b/.editorconfig @@ -5,7 +5,7 @@ ij_kotlin_allow_trailing_comma_on_call_site = false ij_kotlin_imports_layout = *,java.**,javax.**,kotlin.**,^ max_line_length = 120 ktlint_standard_no-wildcard-imports = disabled -ktlint_disabled_rules = no-wildcard-imports, spacing-between-declarations-with-annotations +ktlint_disabled_rules = no-wildcard-imports, spacing-between-declarations-with-annotations, package-name ktlint_standard_package-name = disabled ktlint_standard_spacing-between-declarations-with-annotations = disabled -ktlint_standard_colon-spacing = disabled \ No newline at end of file +ktlint_standard_colon-spacing = disabled diff --git a/integrations/crowdnode/build.gradle b/integrations/crowdnode/build.gradle index 41c86a48c3..d2c5d1586f 100644 --- a/integrations/crowdnode/build.gradle +++ b/integrations/crowdnode/build.gradle @@ -5,11 +5,10 @@ plugins { id 'androidx.navigation.safeargs.kotlin' id 'dagger.hilt.android.plugin' id 'kotlin-parcelize' + id 'org.jlleitschuh.gradle.ktlint' } android { - compileSdkVersion 33 - defaultConfig { compileSdk 33 minSdkVersion 23 diff --git a/integrations/crowdnode/src/main/java/org/dash/wallet/integrations/crowdnode/api/CrowdNodeApi.kt b/integrations/crowdnode/src/main/java/org/dash/wallet/integrations/crowdnode/api/CrowdNodeApi.kt index acd05fa84a..27a84c9fb9 100644 --- a/integrations/crowdnode/src/main/java/org/dash/wallet/integrations/crowdnode/api/CrowdNodeApi.kt +++ b/integrations/crowdnode/src/main/java/org/dash/wallet/integrations/crowdnode/api/CrowdNodeApi.kt @@ -30,18 +30,17 @@ import org.bitcoinj.core.Address import org.bitcoinj.core.Coin import org.bitcoinj.core.Transaction import org.dash.wallet.common.Configuration -import org.dash.wallet.common.util.Constants import org.dash.wallet.common.WalletDataProvider import org.dash.wallet.common.data.Resource -import org.dash.wallet.common.data.Status import org.dash.wallet.common.data.ServiceName -import org.dash.wallet.common.services.AuthenticationManager +import org.dash.wallet.common.data.Status +import org.dash.wallet.common.data.TaxCategory import org.dash.wallet.common.services.LeftoverBalanceException import org.dash.wallet.common.services.NotificationService import org.dash.wallet.common.services.TransactionMetadataProvider import org.dash.wallet.common.services.analytics.AnalyticsService -import org.dash.wallet.common.data.TaxCategory import org.dash.wallet.common.transactions.TransactionUtils +import org.dash.wallet.common.util.Constants import org.dash.wallet.common.util.TickerFlow import org.dash.wallet.integrations.crowdnode.R import org.dash.wallet.integrations.crowdnode.model.* @@ -50,11 +49,11 @@ import org.dash.wallet.integrations.crowdnode.transactions.CrowdNodeWelcomeToApi import org.dash.wallet.integrations.crowdnode.utils.CrowdNodeConfig import org.dash.wallet.integrations.crowdnode.utils.CrowdNodeConstants import org.slf4j.LoggerFactory +import retrofit2.HttpException import java.io.IOException -import java.net.URLEncoder +import java.net.UnknownHostException import java.util.concurrent.Executors import javax.inject.Inject -import kotlin.math.min import kotlin.math.pow import kotlin.time.Duration import kotlin.time.Duration.Companion.hours @@ -94,10 +93,9 @@ class CrowdNodeApiAggregator @Inject constructor( private val analyticsService: AnalyticsService, private val config: CrowdNodeConfig, private val globalConfig: Configuration, - private val securityFunctions: AuthenticationManager, private val transactionMetadataProvider: TransactionMetadataProvider, @ApplicationContext private val appContext: Context -): CrowdNodeApi { +) : CrowdNodeApi { companion object { private val log = LoggerFactory.getLogger(CrowdNodeApiAggregator::class.java) private const val CONFIRMED_STATUS = "confirmed" @@ -138,7 +136,7 @@ class CrowdNodeApiAggregator @Inject constructor( .onEach { status -> cancelTrackingJob() val initialDelay = if (isOnlineStatusRestored) 0.seconds else 10.seconds - when(status) { + when (status) { OnlineAccountStatus.Linking -> startTrackingLinked(linkingApiAddress!!) OnlineAccountStatus.Validating -> startTrackingValidated(accountAddress!!, initialDelay) OnlineAccountStatus.Confirming -> startTrackingConfirmed(accountAddress!!, initialDelay) @@ -197,10 +195,12 @@ class CrowdNodeApiAggregator @Inject constructor( log.info("CrowdNode persistent sign up") val crowdNodeWorker = OneTimeWorkRequestBuilder() - .setInputData(workDataOf( - CrowdNodeWorker.API_REQUEST to CrowdNodeWorker.SIGNUP_CALL, - CrowdNodeWorker.ACCOUNT_ADDRESS to accountAddress.toBase58() - )) + .setInputData( + workDataOf( + CrowdNodeWorker.API_REQUEST to CrowdNodeWorker.SIGNUP_CALL, + CrowdNodeWorker.ACCOUNT_ADDRESS to accountAddress.toBase58() + ) + ) .build() WorkManager.getInstance(appContext) @@ -294,49 +294,45 @@ class CrowdNodeApiAggregator @Inject constructor( return try { apiError.value = null + val result = webApi.requestWithdrawal(accountAddress, amount) - val maxPermil = ApiCode.WithdrawAll.code - val requestPermil = min(amount.value * maxPermil / balance.value, maxPermil) - val requestValue = CrowdNodeConstants.API_OFFSET + Coin.valueOf(requestPermil) - val topUpTx = blockchainApi.topUpAddress(accountAddress, requestValue + Constants.ECONOMIC_FEE) - log.info("topUpTx id: ${topUpTx.txId}") - val withdrawTx = blockchainApi.requestWithdrawal(accountAddress, requestValue) - log.info("withdrawTx id: ${withdrawTx.txId}") - - responseScope.launch { - try { - val txResponse = blockchainApi.waitForWithdrawalResponse(requestValue) - log.info("got withdrawal queue response: ${txResponse.txId}") - val txWithdrawal = blockchainApi.waitForWithdrawalReceived() - log.info("got withdrawal: ${txWithdrawal.txId}") - } catch (ex: Exception) { - handleError(ex, appContext.getString(R.string.crowdnode_withdraw_error)) - } + if (result.messageStatus.lowercase() == MESSAGE_RECEIVED_STATUS) { + log.info("Withdrawal request sent successfully") refreshBalance(retries = 3, afterWithdrawal = true) + true + } else { + log.info("Withdrawal request not received, status: ${result.messageStatus}. Result: ${result.result}") + apiError.value = MessageStatusException(result.result ?: "") + false } - - return true - } catch (ex: Exception) { + } catch (ex: HttpException) { + log.error("SendMessage error, code: ${ex.code()}, error: ${ex.response()?.errorBody()?.string()}") + handleError(ex, appContext.getString(R.string.crowdnode_withdraw_error)) + false + } catch (ex: UnknownHostException) { + log.error("Withdrawal error: ${ex.message}") handleError(ex, appContext.getString(R.string.crowdnode_withdraw_error)) false } } override suspend fun getWithdrawalLimit(period: WithdrawalLimitPeriod): Coin { - return Coin.valueOf(when(period) { - WithdrawalLimitPeriod.PerTransaction -> { - config.get(CrowdNodeConfig.WITHDRAWAL_LIMIT_PER_TX) ?: - CrowdNodeConstants.WithdrawalLimits.DEFAULT_LIMIT_PER_TX.value - } - WithdrawalLimitPeriod.PerHour -> { - config.get(CrowdNodeConfig.WITHDRAWAL_LIMIT_PER_HOUR) ?: - CrowdNodeConstants.WithdrawalLimits.DEFAULT_LIMIT_PER_HOUR.value - } - WithdrawalLimitPeriod.PerDay -> { - config.get(CrowdNodeConfig.WITHDRAWAL_LIMIT_PER_DAY) ?: - CrowdNodeConstants.WithdrawalLimits.DEFAULT_LIMIT_PER_DAY.value + return Coin.valueOf( + when (period) { + WithdrawalLimitPeriod.PerTransaction -> { + config.get(CrowdNodeConfig.WITHDRAWAL_LIMIT_PER_TX) + ?: CrowdNodeConstants.WithdrawalLimits.DEFAULT_LIMIT_PER_TX.value + } + WithdrawalLimitPeriod.PerHour -> { + config.get(CrowdNodeConfig.WITHDRAWAL_LIMIT_PER_HOUR) + ?: CrowdNodeConstants.WithdrawalLimits.DEFAULT_LIMIT_PER_HOUR.value + } + WithdrawalLimitPeriod.PerDay -> { + config.get(CrowdNodeConfig.WITHDRAWAL_LIMIT_PER_DAY) + ?: CrowdNodeConstants.WithdrawalLimits.DEFAULT_LIMIT_PER_DAY.value + } } - }) + ) } override fun hasAnyDeposits(): Boolean { @@ -373,14 +369,16 @@ class CrowdNodeApiAggregator @Inject constructor( if (!afterWithdrawal) { // balance changed, no need to retry anymore break - } else if (lastBalance - (currentBalance.data?.value?: 0L) >= minimumWithdrawal.value) { + } else if (lastBalance - (currentBalance.data?.value ?: 0L) >= minimumWithdrawal.value) { // balance changed, no need to retry anymore break } } } - balance.value = currentBalance + currentBalance.data?.let { + balance.value = currentBalance + } } } @@ -413,9 +411,7 @@ class CrowdNodeApiAggregator @Inject constructor( requireNotNull(address) { "Account address is null, make sure to sign up" } try { - val signature = securityFunctions.signMessage(address, email) - - if (sendSignedEmailMessage(address, email, signature)) { + if (sendSignedEmailMessage(address, email)) { changeOnlineStatus(OnlineAccountStatus.Creating) } } catch (ex: Exception) { @@ -477,31 +473,25 @@ class CrowdNodeApiAggregator @Inject constructor( .launchIn(statusScope) } - @Suppress("BlockingMethodInNonBlockingContext") - private suspend fun sendSignedEmailMessage( - address: Address, - email: String, - signature: String - ): Boolean { + private suspend fun sendSignedEmailMessage(address: Address, email: String): Boolean { log.info("Sending signed email message") - val encodedSignature = URLEncoder.encode(signature, "utf-8") - val result = webApi.sendSignedMessage(address.toBase58(), email, encodedSignature) - - if (result.isSuccessful && result.body()!!.messageStatus.lowercase() == MESSAGE_RECEIVED_STATUS) { - log.info("Signed email sent successfully") - config.set(CrowdNodeConfig.SIGNED_EMAIL_MESSAGE_ID, result.body()!!.id) - return true - } + val result = webApi.registerEmail(address, email) - if (result.isSuccessful) { - log.info("SendMessage not received, status: ${result.body()?.messageStatus ?: "null"}. Result: ${result.body()?.result}") - apiError.value = MessageStatusException(result.body()?.result ?: "") + return try { + if (result.messageStatus.lowercase() == MESSAGE_RECEIVED_STATUS) { + log.info("Signed email sent successfully") + config.set(CrowdNodeConfig.SIGNED_EMAIL_MESSAGE_ID, result.id) + true + } else { + log.info("SendMessage not received, status: ${result.messageStatus}. Result: ${result.result}") + apiError.value = MessageStatusException(result.result ?: "") + false + } + } catch (ex: HttpException) { + log.info("SendMessage error, code: ${ex.code()}, error: ${ex.response()?.errorBody()?.string()}") + apiError.value = MessageStatusException(ex.response()?.errorBody()?.string() ?: "") return false } - - log.info("SendMessage error, code: ${result.code()}, error: ${result.errorBody()?.string()}") - apiError.value = MessageStatusException(result.errorBody()?.string() ?: "") - return false } override suspend fun reset() { @@ -618,25 +608,22 @@ class CrowdNodeApiAggregator @Inject constructor( when (status) { OnlineAccountStatus.None -> {} OnlineAccountStatus.Linking -> { - log.info("found linking online account in progress, account: ${address.toBase58()}, primary: $primaryAddressStr") + log.info( + "found linking online account in progress, " + + "account: ${address.toBase58()}, primary: $primaryAddressStr" + ) checkIfAddressIsInUse(address) } OnlineAccountStatus.Creating, OnlineAccountStatus.SigningUp -> { - if (status == OnlineAccountStatus.Creating && globalConfig.crowdNodePrimaryAddress.isNotEmpty()) { - // The bug from 7.5.0 -> 7.5.1 upgrade scenario. - // The actual state is Done, there is a linked account. - // TODO: remove when there is no 7.5.0 in the wild - log.info("found 7.5.0 -> 7.5.1 upgrade bug, resolving") - changeOnlineStatus(OnlineAccountStatus.Done, save = true) - log.info("found online account, status: ${OnlineAccountStatus.Done}, account: ${address.toBase58()}, primary: $primaryAddressStr") - } else { - // This should not happen - this method is reachable only for a linked account case - throw IllegalStateException("Invalid state found in tryRestoreLinkedOnlineAccount: $status") - } + // This should not happen - this method is reachable only for a linked account case + throw IllegalStateException("Invalid state found in tryRestoreLinkedOnlineAccount: $status") } else -> { changeOnlineStatus(status, save = false) - log.info("found online account, status: ${status.name}, account: ${address.toBase58()}, primary: $primaryAddressStr") + log.info( + "found online account, status: ${status.name}, " + + "account: ${address.toBase58()}, primary: $primaryAddressStr" + ) } } } @@ -673,8 +660,10 @@ class CrowdNodeApiAggregator @Inject constructor( } private fun restoreCreatedOnlineAccount(address: Address) { - val statusOrdinal = runBlocking { config.get(CrowdNodeConfig.ONLINE_ACCOUNT_STATUS) - ?: OnlineAccountStatus.None.ordinal } + val statusOrdinal = runBlocking { + config.get(CrowdNodeConfig.ONLINE_ACCOUNT_STATUS) + ?: OnlineAccountStatus.None.ordinal + } when (val status = OnlineAccountStatus.values()[statusOrdinal]) { OnlineAccountStatus.None -> statusScope.launch { checkIfEmailRegistered(address) } @@ -862,4 +851,4 @@ class CrowdNodeApiAggregator @Inject constructor( ) } } -} \ No newline at end of file +} diff --git a/integrations/crowdnode/src/main/java/org/dash/wallet/integrations/crowdnode/api/CrowdNodeBlockchainApi.kt b/integrations/crowdnode/src/main/java/org/dash/wallet/integrations/crowdnode/api/CrowdNodeBlockchainApi.kt index 988c39b246..83bb1f1224 100644 --- a/integrations/crowdnode/src/main/java/org/dash/wallet/integrations/crowdnode/api/CrowdNodeBlockchainApi.kt +++ b/integrations/crowdnode/src/main/java/org/dash/wallet/integrations/crowdnode/api/CrowdNodeBlockchainApi.kt @@ -129,7 +129,9 @@ open class CrowdNodeBlockchainApi @Inject constructor( val selector = ByAddressCoinSelector(accountAddress) return paymentService.sendCoins( - crowdNodeAddress, requestValue, selector, + crowdNodeAddress, + requestValue, + selector, emptyWallet = false, checkBalanceConditions = false ) @@ -145,7 +147,6 @@ open class CrowdNodeBlockchainApi @Inject constructor( errorResponse ).first() - if (deniedResponse.matches(tx) || errorResponse.matches(tx)) { throw CrowdNodeException(CrowdNodeException.WITHDRAWAL_ERROR) } @@ -156,8 +157,8 @@ open class CrowdNodeBlockchainApi @Inject constructor( suspend fun waitForSignUpResponse(): Transaction { val acceptFilter = CrowdNodeAcceptTermsResponse(params) val errorFilter = CrowdNodeErrorResponse(params, CrowdNodeSignUpTx.SIGNUP_REQUEST_CODE) - val tx = walletData.getTransactions(acceptFilter, errorFilter).firstOrNull() ?: - walletData.observeTransactions(true, acceptFilter, errorFilter).first() + val tx = walletData.getTransactions(acceptFilter, errorFilter).firstOrNull() + ?: walletData.observeTransactions(true, acceptFilter, errorFilter).first() if (errorFilter.matches(tx)) { throw CrowdNodeException("SignUp request returned an error") @@ -170,8 +171,8 @@ open class CrowdNodeBlockchainApi @Inject constructor( val welcomeFilter = CrowdNodeWelcomeToApiResponse(params) val errorFilter = CrowdNodeErrorResponse(params, CrowdNodeAcceptTermsTx.ACCEPT_TERMS_REQUEST_CODE) - val tx = walletData.getTransactions(welcomeFilter, errorFilter).firstOrNull() ?: - walletData.observeTransactions(true, welcomeFilter, errorFilter).first() + val tx = walletData.getTransactions(welcomeFilter, errorFilter).firstOrNull() + ?: walletData.observeTransactions(true, welcomeFilter, errorFilter).first() if (errorFilter.matches(tx)) { throw CrowdNodeException("AcceptTerms request returned an error") @@ -264,4 +265,4 @@ open class CrowdNodeBlockchainApi @Inject constructor( return Coin.valueOf(withdrawals.sumOf { it.getValue(walletData.transactionBag).value }) } -} \ No newline at end of file +} diff --git a/integrations/crowdnode/src/main/java/org/dash/wallet/integrations/crowdnode/api/CrowdNodeConfirmationTxHandler.kt b/integrations/crowdnode/src/main/java/org/dash/wallet/integrations/crowdnode/api/CrowdNodeConfirmationTxHandler.kt index 63033498be..c846cfc2fc 100644 --- a/integrations/crowdnode/src/main/java/org/dash/wallet/integrations/crowdnode/api/CrowdNodeConfirmationTxHandler.kt +++ b/integrations/crowdnode/src/main/java/org/dash/wallet/integrations/crowdnode/api/CrowdNodeConfirmationTxHandler.kt @@ -37,7 +37,7 @@ import java.util.concurrent.Executors class CrowdNodeAPIConfirmationForwarded( params: NetworkParameters -): CoinsToAddressTxFilter( +) : CoinsToAddressTxFilter( CrowdNodeConstants.getCrowdNodeAddress(params), CrowdNodeConstants.API_CONFIRMATION_DASH_AMOUNT, includeFee = true @@ -45,7 +45,7 @@ class CrowdNodeAPIConfirmationForwarded( open class CrowdNodeAPIConfirmationTx( address: Address -): CoinsToAddressTxFilter(address, CrowdNodeConstants.API_CONFIRMATION_DASH_AMOUNT) +) : CoinsToAddressTxFilter(address, CrowdNodeConstants.API_CONFIRMATION_DASH_AMOUNT) class CrowdNodeAPIConfirmationHandler( private val apiAddress: Address, @@ -55,7 +55,7 @@ class CrowdNodeAPIConfirmationHandler( private val crowdNodeConfig: CrowdNodeConfig, private val resources: Resources, private val intent: Intent? -): CrowdNodeAPIConfirmationTx(apiAddress) { +) : CrowdNodeAPIConfirmationTx(apiAddress) { companion object { private val log = LoggerFactory.getLogger(CrowdNodeAPIConfirmationHandler::class.java) } @@ -68,7 +68,8 @@ class CrowdNodeAPIConfirmationHandler( log.info("Handling confirmation tx: ${tx.txId}") handlerScope.launch { - val statusOrdinal = crowdNodeConfig.get(CrowdNodeConfig.ONLINE_ACCOUNT_STATUS) ?: OnlineAccountStatus.None.ordinal + val statusOrdinal = crowdNodeConfig.get(CrowdNodeConfig.ONLINE_ACCOUNT_STATUS) + ?: OnlineAccountStatus.None.ordinal if (statusOrdinal == OnlineAccountStatus.Done.ordinal) { log.info("API address already confirmed") @@ -97,4 +98,4 @@ class CrowdNodeAPIConfirmationHandler( intent = intent ) } -} \ No newline at end of file +} diff --git a/integrations/crowdnode/src/main/java/org/dash/wallet/integrations/crowdnode/api/CrowdNodeWebApi.kt b/integrations/crowdnode/src/main/java/org/dash/wallet/integrations/crowdnode/api/CrowdNodeWebApi.kt index a14a232f63..4f39052c80 100644 --- a/integrations/crowdnode/src/main/java/org/dash/wallet/integrations/crowdnode/api/CrowdNodeWebApi.kt +++ b/integrations/crowdnode/src/main/java/org/dash/wallet/integrations/crowdnode/api/CrowdNodeWebApi.kt @@ -17,24 +17,39 @@ package org.dash.wallet.integrations.crowdnode.api +import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.delay +import kotlinx.coroutines.withContext import org.bitcoinj.core.Address import org.bitcoinj.core.AddressFormatException import org.bitcoinj.core.Coin import org.bitcoinj.core.Transaction import org.dash.wallet.common.data.Resource +import org.dash.wallet.common.services.AuthenticationManager import org.dash.wallet.common.services.analytics.AnalyticsService +import org.dash.wallet.common.util.ensureSuccessful import org.dash.wallet.integrations.crowdnode.model.* import org.slf4j.LoggerFactory +import retrofit2.HttpException import retrofit2.Response import retrofit2.http.* import java.io.IOException import java.math.BigDecimal import java.math.RoundingMode +import java.net.URLEncoder import javax.inject.Inject import kotlin.math.pow import kotlin.time.Duration.Companion.seconds +enum class CrowdNodeMessageType(val value: Int) { + RegisterEmail(1), + Withdrawal(4); + + override fun toString(): String { + return value.toString() + } +} + interface CrowdNodeEndpoint { @GET("odata/apifundings/GetFunds(address='{address}')") suspend fun getTransactions( @@ -66,11 +81,15 @@ interface CrowdNodeEndpoint { @Path("address") address: String ): Response - @GET("odata/apimessages/SendMessage(address='{address}',message='{message}',signature='{signature}',messagetype=1)") + @GET( + "odata/apimessages/SendMessage(address='{address}',message='{message}'," + + "signature='{signature}',messagetype={messagetype})" + ) suspend fun sendSignedMessage( @Path("address") address: String, @Path("message") message: String, - @Path("signature") signature: String + @Path("signature") signature: String, + @Path("messagetype") messageType: CrowdNodeMessageType ): Response @GET("odata/apimessages/GetMessages(address='{address}')") @@ -81,6 +100,7 @@ interface CrowdNodeEndpoint { open class CrowdNodeWebApi @Inject constructor( private val endpoint: CrowdNodeEndpoint, + private val securityFunctions: AuthenticationManager, private val analyticsService: AnalyticsService ) { companion object { @@ -90,11 +110,43 @@ open class CrowdNodeWebApi @Inject constructor( private const val MAX_PER_24H_KEY = "AmountApiWithdrawal24hMax" } - // TODO: these methods are just mappers right now. Move more logic in here from the aggregator class - suspend fun sendSignedMessage(address: String, email: String, encodedSignature: String): Response { - return endpoint.sendSignedMessage(address, email, encodedSignature) + suspend fun registerEmail(address: Address, email: String): MessageStatus { + val signature = securityFunctions.signMessage(address, email) + val encodedSignature = withContext(Dispatchers.IO) { + URLEncoder.encode(signature, "utf-8") + } + val response = endpoint.sendSignedMessage( + address.toBase58(), + email, + encodedSignature, + CrowdNodeMessageType.RegisterEmail + ) + response.ensureSuccessful() + + return response.body() ?: throw HttpException(response) } + suspend fun requestWithdrawal(address: Address, amount: Coin): MessageStatus { + val amountStr = amount.value.toString() + val signature = securityFunctions.signMessage(address, amountStr) + val encodedSignature = withContext(Dispatchers.IO) { + URLEncoder.encode(signature, "utf-8") + } + val response = endpoint.sendSignedMessage( + address.toBase58(), + amountStr, + encodedSignature, + CrowdNodeMessageType.Withdrawal + ) + response.ensureSuccessful() + + val message = response.body() + requireNotNull(message) + + return message + } + + // TODO: these methods are just mappers right now. Move more logic in here from the aggregator class suspend fun getMessages(address: String): Response> { return endpoint.getMessages(address) } diff --git a/integrations/crowdnode/src/main/java/org/dash/wallet/integrations/crowdnode/api/CrowdNodeWorker.kt b/integrations/crowdnode/src/main/java/org/dash/wallet/integrations/crowdnode/api/CrowdNodeWorker.kt index a755d9ffc7..3811f83af2 100644 --- a/integrations/crowdnode/src/main/java/org/dash/wallet/integrations/crowdnode/api/CrowdNodeWorker.kt +++ b/integrations/crowdnode/src/main/java/org/dash/wallet/integrations/crowdnode/api/CrowdNodeWorker.kt @@ -39,7 +39,7 @@ class CrowdNodeWorker @AssistedInject constructor( private val walletDataProvider: WalletDataProvider, private val notificationService: NotificationService, private val analytics: AnalyticsService -): CoroutineWorker(appContext, workerParams) { +) : CoroutineWorker(appContext, workerParams) { companion object { private val log = LoggerFactory.getLogger(CrowdNodeWorker::class.java) diff --git a/integrations/crowdnode/src/main/java/org/dash/wallet/integrations/crowdnode/api/RemoteDataSource.kt b/integrations/crowdnode/src/main/java/org/dash/wallet/integrations/crowdnode/api/RemoteDataSource.kt index 9951d75ebb..a746d6e9cb 100644 --- a/integrations/crowdnode/src/main/java/org/dash/wallet/integrations/crowdnode/api/RemoteDataSource.kt +++ b/integrations/crowdnode/src/main/java/org/dash/wallet/integrations/crowdnode/api/RemoteDataSource.kt @@ -47,4 +47,4 @@ class RemoteDataSource @Inject constructor() { } }.build() } -} \ No newline at end of file +} diff --git a/integrations/crowdnode/src/main/java/org/dash/wallet/integrations/crowdnode/di/CrowdNodeModule.kt b/integrations/crowdnode/src/main/java/org/dash/wallet/integrations/crowdnode/di/CrowdNodeModule.kt index e55d21afdb..fb1db9502c 100644 --- a/integrations/crowdnode/src/main/java/org/dash/wallet/integrations/crowdnode/di/CrowdNodeModule.kt +++ b/integrations/crowdnode/src/main/java/org/dash/wallet/integrations/crowdnode/di/CrowdNodeModule.kt @@ -50,4 +50,4 @@ abstract class CrowdNodeModule { @Binds @Singleton abstract fun bindCrowdNodeApi(crowdNodeApi: CrowdNodeApiAggregator): CrowdNodeApi -} \ No newline at end of file +} diff --git a/integrations/crowdnode/src/main/java/org/dash/wallet/integrations/crowdnode/model/AddressStatus.kt b/integrations/crowdnode/src/main/java/org/dash/wallet/integrations/crowdnode/model/AddressStatus.kt index 3df35c029f..ec500b3b77 100644 --- a/integrations/crowdnode/src/main/java/org/dash/wallet/integrations/crowdnode/model/AddressStatus.kt +++ b/integrations/crowdnode/src/main/java/org/dash/wallet/integrations/crowdnode/model/AddressStatus.kt @@ -21,5 +21,5 @@ import com.google.gson.annotations.SerializedName class AddressStatus( @SerializedName("Status") - val status : String -) \ No newline at end of file + val status: String +) diff --git a/integrations/crowdnode/src/main/java/org/dash/wallet/integrations/crowdnode/model/ApiCode.kt b/integrations/crowdnode/src/main/java/org/dash/wallet/integrations/crowdnode/model/ApiCode.kt index 01422d056b..f26f9fe909 100644 --- a/integrations/crowdnode/src/main/java/org/dash/wallet/integrations/crowdnode/model/ApiCode.kt +++ b/integrations/crowdnode/src/main/java/org/dash/wallet/integrations/crowdnode/model/ApiCode.kt @@ -27,4 +27,4 @@ enum class ApiCode(val code: Long, val isRequest: Boolean = false) { SignUp(131072, true), AcceptTerms(65536, true), MaxCode(SignUp.code, SignUp.isRequest) -} \ No newline at end of file +} diff --git a/integrations/crowdnode/src/main/java/org/dash/wallet/integrations/crowdnode/model/ApiStatuses.kt b/integrations/crowdnode/src/main/java/org/dash/wallet/integrations/crowdnode/model/ApiStatuses.kt index f91ae4bad7..4a3b6f9f8e 100644 --- a/integrations/crowdnode/src/main/java/org/dash/wallet/integrations/crowdnode/model/ApiStatuses.kt +++ b/integrations/crowdnode/src/main/java/org/dash/wallet/integrations/crowdnode/model/ApiStatuses.kt @@ -22,12 +22,14 @@ package org.dash.wallet.integrations.crowdnode.model // that maps to the old order value (see ApiCode) enum class SignUpStatus { NotStarted, + // Create New Account FundingWallet, SigningUp, AcceptingTerms, Finished, Error, + // Link Existing Account LinkedOnline } @@ -45,7 +47,7 @@ enum class OnlineAccountStatus { Done } -open class CrowdNodeException(message: String): Exception(message) { +open class CrowdNodeException(message: String) : Exception(message) { companion object { const val DEPOSIT_ERROR = "deposit_error" const val CONFIRMATION_ERROR = "confirmation_error" @@ -54,4 +56,4 @@ open class CrowdNodeException(message: String): Exception(message) { } } -class MessageStatusException(details: String) : CrowdNodeException(details) \ No newline at end of file +class MessageStatusException(details: String) : CrowdNodeException(details) diff --git a/integrations/crowdnode/src/main/java/org/dash/wallet/integrations/crowdnode/model/CrowdNodeBalance.kt b/integrations/crowdnode/src/main/java/org/dash/wallet/integrations/crowdnode/model/CrowdNodeBalance.kt index 5a4a43de4c..0192d0b2b1 100644 --- a/integrations/crowdnode/src/main/java/org/dash/wallet/integrations/crowdnode/model/CrowdNodeBalance.kt +++ b/integrations/crowdnode/src/main/java/org/dash/wallet/integrations/crowdnode/model/CrowdNodeBalance.kt @@ -19,13 +19,13 @@ package org.dash.wallet.integrations.crowdnode.model import com.google.gson.annotations.SerializedName -data class CrowdNodeBalance ( +data class CrowdNodeBalance( @SerializedName("DashAddress") - val dashAddress : String, + val dashAddress: String, @SerializedName("TotalBalance") - val totalBalance : Double, + val totalBalance: Double, @SerializedName("TotalActiveBalance") - val totalActiveBalance : Double, + val totalActiveBalance: Double, @SerializedName("TotalDividend") - val totalDividend : Double -) \ No newline at end of file + val totalDividend: Double +) diff --git a/integrations/crowdnode/src/main/java/org/dash/wallet/integrations/crowdnode/model/CrowdNodeIsAddressInUse.kt b/integrations/crowdnode/src/main/java/org/dash/wallet/integrations/crowdnode/model/CrowdNodeIsAddressInUse.kt index 8a33c64cb6..e53301c549 100644 --- a/integrations/crowdnode/src/main/java/org/dash/wallet/integrations/crowdnode/model/CrowdNodeIsAddressInUse.kt +++ b/integrations/crowdnode/src/main/java/org/dash/wallet/integrations/crowdnode/model/CrowdNodeIsAddressInUse.kt @@ -19,9 +19,9 @@ package org.dash.wallet.integrations.crowdnode.model import com.google.gson.annotations.SerializedName -data class CrowdNodeIsAddressInUse ( +data class CrowdNodeIsAddressInUse( @SerializedName("inUse") - val isInUse : Boolean, + val isInUse: Boolean, @SerializedName("DashAddress") - val primaryAddress : String? -) \ No newline at end of file + val primaryAddress: String? +) diff --git a/integrations/crowdnode/src/main/java/org/dash/wallet/integrations/crowdnode/model/CrowdNodeTx.kt b/integrations/crowdnode/src/main/java/org/dash/wallet/integrations/crowdnode/model/CrowdNodeTx.kt index eaecef7cd5..21fc7edb51 100644 --- a/integrations/crowdnode/src/main/java/org/dash/wallet/integrations/crowdnode/model/CrowdNodeTx.kt +++ b/integrations/crowdnode/src/main/java/org/dash/wallet/integrations/crowdnode/model/CrowdNodeTx.kt @@ -19,31 +19,31 @@ package org.dash.wallet.integrations.crowdnode.model import com.google.gson.annotations.SerializedName -data class CrowdNodeTx ( +data class CrowdNodeTx( @SerializedName("FundingType") - val fundingType : String, + val fundingType: String, @SerializedName("Amount") - val amount : Double, + val amount: Double, @SerializedName("Time") - val time : Int, + val time: Int, @SerializedName("TimeReceived") - val timeReceived : Int, + val timeReceived: Int, @SerializedName("TxId") - val txId : String, + val txId: String, @SerializedName("PortalUserId") - val portalUserId : Int, + val portalUserId: Int, @SerializedName("Status") - val status : String, + val status: String, @SerializedName("Comment") - val comment : String, + val comment: String, @SerializedName("TimeUTC") - val timeUTC : String, + val timeUTC: String, @SerializedName("Id") - val id : Int, + val id: Int, @SerializedName("UpdatedOn") - val updatedOn : String, + val updatedOn: String, @SerializedName("SyncFromPrivateOn") - val syncFromPrivateOn : String, + val syncFromPrivateOn: String, @SerializedName("UpdatedInPrivateOn") - val updatedInPrivateOn : String -) \ No newline at end of file + val updatedInPrivateOn: String +) diff --git a/integrations/crowdnode/src/main/java/org/dash/wallet/integrations/crowdnode/model/IsAddressInUse.kt b/integrations/crowdnode/src/main/java/org/dash/wallet/integrations/crowdnode/model/IsAddressInUse.kt index 5b23c9a41b..2b6d6bef5f 100644 --- a/integrations/crowdnode/src/main/java/org/dash/wallet/integrations/crowdnode/model/IsAddressInUse.kt +++ b/integrations/crowdnode/src/main/java/org/dash/wallet/integrations/crowdnode/model/IsAddressInUse.kt @@ -19,9 +19,9 @@ package org.dash.wallet.integrations.crowdnode.model import com.google.gson.annotations.SerializedName -data class IsAddressInUse ( +data class IsAddressInUse( @SerializedName("inUse") - val isInUse : Boolean, + val isInUse: Boolean, @SerializedName("DashAddress") - val primaryAddress : String? -) \ No newline at end of file + val primaryAddress: String? +) diff --git a/integrations/crowdnode/src/main/java/org/dash/wallet/integrations/crowdnode/model/IsDefaultEmail.kt b/integrations/crowdnode/src/main/java/org/dash/wallet/integrations/crowdnode/model/IsDefaultEmail.kt index 4076760f47..24f0c85ee8 100644 --- a/integrations/crowdnode/src/main/java/org/dash/wallet/integrations/crowdnode/model/IsDefaultEmail.kt +++ b/integrations/crowdnode/src/main/java/org/dash/wallet/integrations/crowdnode/model/IsDefaultEmail.kt @@ -21,5 +21,5 @@ import com.google.gson.annotations.SerializedName class IsDefaultEmail( @SerializedName("value") - val isDefault : Boolean -) \ No newline at end of file + val isDefault: Boolean +) diff --git a/integrations/crowdnode/src/main/java/org/dash/wallet/integrations/crowdnode/model/MessageStatus.kt b/integrations/crowdnode/src/main/java/org/dash/wallet/integrations/crowdnode/model/MessageStatus.kt index e9bb1129db..6f42cca724 100644 --- a/integrations/crowdnode/src/main/java/org/dash/wallet/integrations/crowdnode/model/MessageStatus.kt +++ b/integrations/crowdnode/src/main/java/org/dash/wallet/integrations/crowdnode/model/MessageStatus.kt @@ -34,4 +34,4 @@ class MessageStatus( val executedTime: String, @SerializedName("Result") val result: String? -) \ No newline at end of file +) diff --git a/integrations/crowdnode/src/main/java/org/dash/wallet/integrations/crowdnode/model/WithdrawalLimit.kt b/integrations/crowdnode/src/main/java/org/dash/wallet/integrations/crowdnode/model/WithdrawalLimit.kt index 45b4abd298..6ca0241033 100644 --- a/integrations/crowdnode/src/main/java/org/dash/wallet/integrations/crowdnode/model/WithdrawalLimit.kt +++ b/integrations/crowdnode/src/main/java/org/dash/wallet/integrations/crowdnode/model/WithdrawalLimit.kt @@ -36,4 +36,4 @@ enum class WithdrawalLimitPeriod { data class WithdrawalLimitsException( val amount: Coin, val period: WithdrawalLimitPeriod -): Exception() \ No newline at end of file +) : Exception() diff --git a/integrations/crowdnode/src/main/java/org/dash/wallet/integrations/crowdnode/transactions/CrowdNodeAcceptTermsResponse.kt b/integrations/crowdnode/src/main/java/org/dash/wallet/integrations/crowdnode/transactions/CrowdNodeAcceptTermsResponse.kt index 443fb98e32..6cc5d165ed 100644 --- a/integrations/crowdnode/src/main/java/org/dash/wallet/integrations/crowdnode/transactions/CrowdNodeAcceptTermsResponse.kt +++ b/integrations/crowdnode/src/main/java/org/dash/wallet/integrations/crowdnode/transactions/CrowdNodeAcceptTermsResponse.kt @@ -24,11 +24,12 @@ import org.dash.wallet.integrations.crowdnode.model.ApiCode import org.dash.wallet.integrations.crowdnode.utils.CrowdNodeConstants // TODO: consider making sure that `toAddress` matches our account address -class CrowdNodeAcceptTermsResponse(networkParams: NetworkParameters): CoinsFromAddressTxFilter( - CrowdNodeConstants.getCrowdNodeAddress(networkParams), ACCEPT_TERMS_RESPONSE_CODE +class CrowdNodeAcceptTermsResponse(networkParams: NetworkParameters) : CoinsFromAddressTxFilter( + CrowdNodeConstants.getCrowdNodeAddress(networkParams), + ACCEPT_TERMS_RESPONSE_CODE ) { companion object { val ACCEPT_TERMS_RESPONSE_CODE: Coin = CrowdNodeConstants.API_OFFSET + Coin.valueOf(ApiCode.PleaseAcceptTerms.code) } -} \ No newline at end of file +} diff --git a/integrations/crowdnode/src/main/java/org/dash/wallet/integrations/crowdnode/transactions/CrowdNodeAcceptTermsTx.kt b/integrations/crowdnode/src/main/java/org/dash/wallet/integrations/crowdnode/transactions/CrowdNodeAcceptTermsTx.kt index c7a868129f..04f3125ce9 100644 --- a/integrations/crowdnode/src/main/java/org/dash/wallet/integrations/crowdnode/transactions/CrowdNodeAcceptTermsTx.kt +++ b/integrations/crowdnode/src/main/java/org/dash/wallet/integrations/crowdnode/transactions/CrowdNodeAcceptTermsTx.kt @@ -24,8 +24,9 @@ import org.dash.wallet.common.transactions.filters.CoinsToAddressTxFilter import org.dash.wallet.integrations.crowdnode.model.ApiCode import org.dash.wallet.integrations.crowdnode.utils.CrowdNodeConstants -class CrowdNodeAcceptTermsTx(networkParams: NetworkParameters): CoinsToAddressTxFilter( - CrowdNodeConstants.getCrowdNodeAddress(networkParams), ACCEPT_TERMS_REQUEST_CODE +class CrowdNodeAcceptTermsTx(networkParams: NetworkParameters) : CoinsToAddressTxFilter( + CrowdNodeConstants.getCrowdNodeAddress(networkParams), + ACCEPT_TERMS_REQUEST_CODE ) { companion object { val ACCEPT_TERMS_REQUEST_CODE: Coin = @@ -35,4 +36,4 @@ class CrowdNodeAcceptTermsTx(networkParams: NetworkParameters): CoinsToAddressTx override fun matches(tx: Transaction): Boolean { return super.matches(tx) && fromAddresses.size == 1 } -} \ No newline at end of file +} diff --git a/integrations/crowdnode/src/main/java/org/dash/wallet/integrations/crowdnode/transactions/CrowdNodeDepositReceivedResponse.kt b/integrations/crowdnode/src/main/java/org/dash/wallet/integrations/crowdnode/transactions/CrowdNodeDepositReceivedResponse.kt index a0af315f83..5ad02af01c 100644 --- a/integrations/crowdnode/src/main/java/org/dash/wallet/integrations/crowdnode/transactions/CrowdNodeDepositReceivedResponse.kt +++ b/integrations/crowdnode/src/main/java/org/dash/wallet/integrations/crowdnode/transactions/CrowdNodeDepositReceivedResponse.kt @@ -23,11 +23,12 @@ import org.dash.wallet.common.transactions.filters.CoinsFromAddressTxFilter import org.dash.wallet.integrations.crowdnode.model.ApiCode import org.dash.wallet.integrations.crowdnode.utils.CrowdNodeConstants -class CrowdNodeDepositReceivedResponse(networkParams: NetworkParameters): CoinsFromAddressTxFilter( - CrowdNodeConstants.getCrowdNodeAddress(networkParams), DEPOSIT_RECEIVED_RESPONSE_CODE +class CrowdNodeDepositReceivedResponse(networkParams: NetworkParameters) : CoinsFromAddressTxFilter( + CrowdNodeConstants.getCrowdNodeAddress(networkParams), + DEPOSIT_RECEIVED_RESPONSE_CODE ) { companion object { val DEPOSIT_RECEIVED_RESPONSE_CODE: Coin = CrowdNodeConstants.API_OFFSET + Coin.valueOf(ApiCode.DepositReceived.code) } -} \ No newline at end of file +} diff --git a/integrations/crowdnode/src/main/java/org/dash/wallet/integrations/crowdnode/transactions/CrowdNodeDepositTx.kt b/integrations/crowdnode/src/main/java/org/dash/wallet/integrations/crowdnode/transactions/CrowdNodeDepositTx.kt index 783f81dc13..a0d952a3ed 100644 --- a/integrations/crowdnode/src/main/java/org/dash/wallet/integrations/crowdnode/transactions/CrowdNodeDepositTx.kt +++ b/integrations/crowdnode/src/main/java/org/dash/wallet/integrations/crowdnode/transactions/CrowdNodeDepositTx.kt @@ -25,7 +25,7 @@ import org.dash.wallet.common.transactions.filters.TransactionFilter import org.dash.wallet.integrations.crowdnode.model.ApiCode import org.dash.wallet.integrations.crowdnode.utils.CrowdNodeConstants -class CrowdNodeDepositTx(private val accountAddress: Address): TransactionFilter { +class CrowdNodeDepositTx(private val accountAddress: Address) : TransactionFilter { override fun matches(tx: Transaction): Boolean { val networkParams = accountAddress.parameters val crowdNodeAddress = CrowdNodeConstants.getCrowdNodeAddress(networkParams) @@ -34,8 +34,8 @@ class CrowdNodeDepositTx(private val accountAddress: Address): TransactionFilter val script = it.outpoint.connectedOutput?.scriptPubKey script != null && - (ScriptPattern.isP2PKH(script) || ScriptPattern.isP2SH(script)) && - script.getToAddress(networkParams) == accountAddress + (ScriptPattern.isP2PKH(script) || ScriptPattern.isP2SH(script)) && + script.getToAddress(networkParams) == accountAddress } if (!allFromAccount) { @@ -60,4 +60,4 @@ class CrowdNodeDepositTx(private val accountAddress: Address): TransactionFilter return toCheck <= ApiCode.MaxCode.code } -} \ No newline at end of file +} diff --git a/integrations/crowdnode/src/main/java/org/dash/wallet/integrations/crowdnode/transactions/CrowdNodeErrorResponse.kt b/integrations/crowdnode/src/main/java/org/dash/wallet/integrations/crowdnode/transactions/CrowdNodeErrorResponse.kt index ed23f6e856..b70e199687 100644 --- a/integrations/crowdnode/src/main/java/org/dash/wallet/integrations/crowdnode/transactions/CrowdNodeErrorResponse.kt +++ b/integrations/crowdnode/src/main/java/org/dash/wallet/integrations/crowdnode/transactions/CrowdNodeErrorResponse.kt @@ -29,7 +29,7 @@ import org.dash.wallet.integrations.crowdnode.utils.CrowdNodeConstants class CrowdNodeErrorResponse( private val networkParams: NetworkParameters, private val requestValue: Coin -): CoinsFromAddressTxFilter( +) : CoinsFromAddressTxFilter( CrowdNodeConstants.getCrowdNodeAddress(networkParams), requestValue, includeFee = true @@ -41,12 +41,12 @@ class CrowdNodeErrorResponse( private fun isChangeSentBackToCrowdNode(tx: Transaction): Boolean { val crowdNodeAddress = CrowdNodeConstants.getCrowdNodeAddress(networkParams) return tx.outputs.size > 2 && - tx.outputs.first().value + (tx.fee ?: Coin.ZERO) == requestValue && - tx.outputs.drop(1).any { addressMatch(it.scriptPubKey, crowdNodeAddress) } + tx.outputs.first().value + (tx.fee ?: Coin.ZERO) == requestValue && + tx.outputs.drop(1).any { addressMatch(it.scriptPubKey, crowdNodeAddress) } } private fun addressMatch(script: Script, address: Address): Boolean { return (ScriptPattern.isP2PKH(script) || ScriptPattern.isP2SH(script)) && script.getToAddress(address.parameters) == address } -} \ No newline at end of file +} diff --git a/integrations/crowdnode/src/main/java/org/dash/wallet/integrations/crowdnode/transactions/CrowdNodeSignUpTx.kt b/integrations/crowdnode/src/main/java/org/dash/wallet/integrations/crowdnode/transactions/CrowdNodeSignUpTx.kt index cdf1dee090..1fc8443382 100644 --- a/integrations/crowdnode/src/main/java/org/dash/wallet/integrations/crowdnode/transactions/CrowdNodeSignUpTx.kt +++ b/integrations/crowdnode/src/main/java/org/dash/wallet/integrations/crowdnode/transactions/CrowdNodeSignUpTx.kt @@ -24,8 +24,9 @@ import org.dash.wallet.common.transactions.filters.CoinsToAddressTxFilter import org.dash.wallet.integrations.crowdnode.model.ApiCode import org.dash.wallet.integrations.crowdnode.utils.CrowdNodeConstants -class CrowdNodeSignUpTx(networkParams: NetworkParameters): CoinsToAddressTxFilter( - CrowdNodeConstants.getCrowdNodeAddress(networkParams), SIGNUP_REQUEST_CODE +class CrowdNodeSignUpTx(networkParams: NetworkParameters) : CoinsToAddressTxFilter( + CrowdNodeConstants.getCrowdNodeAddress(networkParams), + SIGNUP_REQUEST_CODE ) { companion object { val SIGNUP_REQUEST_CODE: Coin = @@ -35,4 +36,4 @@ class CrowdNodeSignUpTx(networkParams: NetworkParameters): CoinsToAddressTxFilte override fun matches(tx: Transaction): Boolean { return super.matches(tx) && fromAddresses.size == 1 } -} \ No newline at end of file +} diff --git a/integrations/crowdnode/src/main/java/org/dash/wallet/integrations/crowdnode/transactions/CrowdNodeTopUpTx.kt b/integrations/crowdnode/src/main/java/org/dash/wallet/integrations/crowdnode/transactions/CrowdNodeTopUpTx.kt index f97780b7fa..8965ed537b 100644 --- a/integrations/crowdnode/src/main/java/org/dash/wallet/integrations/crowdnode/transactions/CrowdNodeTopUpTx.kt +++ b/integrations/crowdnode/src/main/java/org/dash/wallet/integrations/crowdnode/transactions/CrowdNodeTopUpTx.kt @@ -20,17 +20,18 @@ package org.dash.wallet.integrations.crowdnode.transactions import org.bitcoinj.core.Address import org.bitcoinj.core.Transaction import org.bitcoinj.core.TransactionBag -import org.dash.wallet.common.transactions.filters.CoinsToAddressTxFilter import org.dash.wallet.common.transactions.TransactionUtils.isEntirelySelf +import org.dash.wallet.common.transactions.filters.CoinsToAddressTxFilter import org.dash.wallet.integrations.crowdnode.utils.CrowdNodeConstants class CrowdNodeTopUpTx( accountAddress: Address, private val bag: TransactionBag -): CoinsToAddressTxFilter( - accountAddress, CrowdNodeConstants.REQUIRED_FOR_SIGNUP +) : CoinsToAddressTxFilter( + accountAddress, + CrowdNodeConstants.REQUIRED_FOR_SIGNUP ) { override fun matches(tx: Transaction): Boolean { return super.matches(tx) && tx.isEntirelySelf(bag) } -} \ No newline at end of file +} diff --git a/integrations/crowdnode/src/main/java/org/dash/wallet/integrations/crowdnode/transactions/CrowdNodeWelcomeToApiResponse.kt b/integrations/crowdnode/src/main/java/org/dash/wallet/integrations/crowdnode/transactions/CrowdNodeWelcomeToApiResponse.kt index 7492b98abc..0a6c14789c 100644 --- a/integrations/crowdnode/src/main/java/org/dash/wallet/integrations/crowdnode/transactions/CrowdNodeWelcomeToApiResponse.kt +++ b/integrations/crowdnode/src/main/java/org/dash/wallet/integrations/crowdnode/transactions/CrowdNodeWelcomeToApiResponse.kt @@ -24,11 +24,12 @@ import org.dash.wallet.integrations.crowdnode.model.ApiCode import org.dash.wallet.integrations.crowdnode.utils.CrowdNodeConstants // TODO: consider making sure that `toAddress` matches our account address -class CrowdNodeWelcomeToApiResponse(networkParams: NetworkParameters): CoinsFromAddressTxFilter( - CrowdNodeConstants.getCrowdNodeAddress(networkParams), WELCOME_TO_API_RESPONSE_CODE +class CrowdNodeWelcomeToApiResponse(networkParams: NetworkParameters) : CoinsFromAddressTxFilter( + CrowdNodeConstants.getCrowdNodeAddress(networkParams), + WELCOME_TO_API_RESPONSE_CODE ) { companion object { val WELCOME_TO_API_RESPONSE_CODE: Coin = CrowdNodeConstants.API_OFFSET + Coin.valueOf(ApiCode.WelcomeToApi.code) } -} \ No newline at end of file +} diff --git a/integrations/crowdnode/src/main/java/org/dash/wallet/integrations/crowdnode/transactions/CrowdNodeWithdrawalDeniedResponse.kt b/integrations/crowdnode/src/main/java/org/dash/wallet/integrations/crowdnode/transactions/CrowdNodeWithdrawalDeniedResponse.kt index 086733a684..5e7091c9e4 100644 --- a/integrations/crowdnode/src/main/java/org/dash/wallet/integrations/crowdnode/transactions/CrowdNodeWithdrawalDeniedResponse.kt +++ b/integrations/crowdnode/src/main/java/org/dash/wallet/integrations/crowdnode/transactions/CrowdNodeWithdrawalDeniedResponse.kt @@ -23,11 +23,12 @@ import org.dash.wallet.common.transactions.filters.CoinsFromAddressTxFilter import org.dash.wallet.integrations.crowdnode.model.ApiCode import org.dash.wallet.integrations.crowdnode.utils.CrowdNodeConstants -class CrowdNodeWithdrawalDeniedResponse(networkParams: NetworkParameters): CoinsFromAddressTxFilter( - CrowdNodeConstants.getCrowdNodeAddress(networkParams), WITHDRAWAL_DENIED_RESPONSE_CODE +class CrowdNodeWithdrawalDeniedResponse(networkParams: NetworkParameters) : CoinsFromAddressTxFilter( + CrowdNodeConstants.getCrowdNodeAddress(networkParams), + WITHDRAWAL_DENIED_RESPONSE_CODE ) { companion object { val WITHDRAWAL_DENIED_RESPONSE_CODE: Coin = CrowdNodeConstants.API_OFFSET + Coin.valueOf(ApiCode.WithdrawalDenied.code) } -} \ No newline at end of file +} diff --git a/integrations/crowdnode/src/main/java/org/dash/wallet/integrations/crowdnode/transactions/CrowdNodeWithdrawalQueueResponse.kt b/integrations/crowdnode/src/main/java/org/dash/wallet/integrations/crowdnode/transactions/CrowdNodeWithdrawalQueueResponse.kt index dd2b17e5ff..45fa8e35b9 100644 --- a/integrations/crowdnode/src/main/java/org/dash/wallet/integrations/crowdnode/transactions/CrowdNodeWithdrawalQueueResponse.kt +++ b/integrations/crowdnode/src/main/java/org/dash/wallet/integrations/crowdnode/transactions/CrowdNodeWithdrawalQueueResponse.kt @@ -23,11 +23,12 @@ import org.dash.wallet.common.transactions.filters.CoinsFromAddressTxFilter import org.dash.wallet.integrations.crowdnode.model.ApiCode import org.dash.wallet.integrations.crowdnode.utils.CrowdNodeConstants -class CrowdNodeWithdrawalQueueResponse(networkParams: NetworkParameters): CoinsFromAddressTxFilter( - CrowdNodeConstants.getCrowdNodeAddress(networkParams), WITHDRAWAL_QUEUE_RESPONSE_CODE +class CrowdNodeWithdrawalQueueResponse(networkParams: NetworkParameters) : CoinsFromAddressTxFilter( + CrowdNodeConstants.getCrowdNodeAddress(networkParams), + WITHDRAWAL_QUEUE_RESPONSE_CODE ) { companion object { val WITHDRAWAL_QUEUE_RESPONSE_CODE: Coin = CrowdNodeConstants.API_OFFSET + Coin.valueOf(ApiCode.WithdrawalQueue.code) } -} \ No newline at end of file +} diff --git a/integrations/crowdnode/src/main/java/org/dash/wallet/integrations/crowdnode/transactions/CrowdNodeWithdrawalReceivedTx.kt b/integrations/crowdnode/src/main/java/org/dash/wallet/integrations/crowdnode/transactions/CrowdNodeWithdrawalReceivedTx.kt index 234fb25118..6b1117cd6a 100644 --- a/integrations/crowdnode/src/main/java/org/dash/wallet/integrations/crowdnode/transactions/CrowdNodeWithdrawalReceivedTx.kt +++ b/integrations/crowdnode/src/main/java/org/dash/wallet/integrations/crowdnode/transactions/CrowdNodeWithdrawalReceivedTx.kt @@ -27,7 +27,7 @@ import org.dash.wallet.integrations.crowdnode.utils.CrowdNodeConstants class CrowdNodeWithdrawalReceivedTx( private val networkParams: NetworkParameters -): TransactionFilter { +) : TransactionFilter { private val joinedFilters = mutableListOf() override fun matches(tx: Transaction): Boolean { @@ -66,4 +66,4 @@ class CrowdNodeWithdrawalReceivedTx( private fun isPowerOfTwo(number: Long): Boolean { return number and number - 1 == 0L } -} \ No newline at end of file +} diff --git a/integrations/crowdnode/src/main/java/org/dash/wallet/integrations/crowdnode/transactions/FullCrowdNodeSignUpTxSet.kt b/integrations/crowdnode/src/main/java/org/dash/wallet/integrations/crowdnode/transactions/FullCrowdNodeSignUpTxSet.kt index 5020ae2e1c..e0d1d3e400 100644 --- a/integrations/crowdnode/src/main/java/org/dash/wallet/integrations/crowdnode/transactions/FullCrowdNodeSignUpTxSet.kt +++ b/integrations/crowdnode/src/main/java/org/dash/wallet/integrations/crowdnode/transactions/FullCrowdNodeSignUpTxSet.kt @@ -19,15 +19,14 @@ package org.dash.wallet.integrations.crowdnode.transactions import org.bitcoinj.core.* import org.dash.wallet.common.transactions.TransactionComparator -import org.dash.wallet.common.transactions.filters.TransactionFilter -import org.dash.wallet.common.transactions.TransactionUtils import org.dash.wallet.common.transactions.TransactionUtils.isEntirelySelf import org.dash.wallet.common.transactions.TransactionWrapper +import org.dash.wallet.common.transactions.filters.TransactionFilter open class FullCrowdNodeSignUpTxSet( networkParams: NetworkParameters, private val bag: TransactionBag -): TransactionWrapper { +) : TransactionWrapper { private val signUpFilter = CrowdNodeSignUpTx(networkParams) private val crowdNodeTxFilters = mutableListOf( signUpFilter, @@ -45,13 +44,21 @@ open class FullCrowdNodeSignUpTxSet( get() = matchedFilters.filterIsInstance().firstOrNull() open val possibleAcceptTermsResponse: PossibleAcceptTermsResponse? - get() = matchedFilters.filterIsInstance().firstOrNull { didSignUpFromAddress(it.toAddress) } + get() = matchedFilters.filterIsInstance().firstOrNull { + didSignUpFromAddress( + it.toAddress + ) + } open val welcomeToApiResponse: CrowdNodeWelcomeToApiResponse? get() = matchedFilters.filterIsInstance().firstOrNull() open val possibleWelcomeToApiResponse: PossibleWelcomeResponse? - get() = matchedFilters.filterIsInstance().firstOrNull { didSignUpFromAddress(it.toAddress) } + get() = matchedFilters.filterIsInstance().firstOrNull { + didSignUpFromAddress( + it.toAddress + ) + } override fun tryInclude(tx: Transaction): Boolean { if (transactions.any { it.txId == tx.txId }) { @@ -70,7 +77,7 @@ open class FullCrowdNodeSignUpTxSet( } } } - + val matchedFilter = crowdNodeTxFilters.firstOrNull { it.matches(tx) } if (matchedFilter != null) { @@ -101,4 +108,4 @@ open class FullCrowdNodeSignUpTxSet( val signUpTxs = matchedFilters.filterIsInstance() return signUpTxs.any { it.fromAddresses.first() == toAddress } } -} \ No newline at end of file +} diff --git a/integrations/crowdnode/src/main/java/org/dash/wallet/integrations/crowdnode/transactions/PossibleAcceptTermsResponse.kt b/integrations/crowdnode/src/main/java/org/dash/wallet/integrations/crowdnode/transactions/PossibleAcceptTermsResponse.kt index 29049665ac..ff70a82073 100644 --- a/integrations/crowdnode/src/main/java/org/dash/wallet/integrations/crowdnode/transactions/PossibleAcceptTermsResponse.kt +++ b/integrations/crowdnode/src/main/java/org/dash/wallet/integrations/crowdnode/transactions/PossibleAcceptTermsResponse.kt @@ -25,7 +25,7 @@ import org.dash.wallet.common.transactions.filters.CoinsReceivedTxFilter class PossibleAcceptTermsResponse( bag: TransactionBag, private val accountAddress: Address? -): CoinsReceivedTxFilter( +) : CoinsReceivedTxFilter( bag, CrowdNodeAcceptTermsResponse.ACCEPT_TERMS_RESPONSE_CODE ) { @@ -41,4 +41,4 @@ class PossibleAcceptTermsResponse( return matches } -} \ No newline at end of file +} diff --git a/integrations/crowdnode/src/main/java/org/dash/wallet/integrations/crowdnode/transactions/PossibleWelcomeResponse.kt b/integrations/crowdnode/src/main/java/org/dash/wallet/integrations/crowdnode/transactions/PossibleWelcomeResponse.kt index 5cf10a3e94..d2f0f02c24 100644 --- a/integrations/crowdnode/src/main/java/org/dash/wallet/integrations/crowdnode/transactions/PossibleWelcomeResponse.kt +++ b/integrations/crowdnode/src/main/java/org/dash/wallet/integrations/crowdnode/transactions/PossibleWelcomeResponse.kt @@ -25,7 +25,7 @@ import org.dash.wallet.common.transactions.filters.CoinsReceivedTxFilter class PossibleWelcomeResponse( bag: TransactionBag, private val accountAddress: Address? -): CoinsReceivedTxFilter( +) : CoinsReceivedTxFilter( bag, CrowdNodeWelcomeToApiResponse.WELCOME_TO_API_RESPONSE_CODE ) { @@ -33,7 +33,7 @@ class PossibleWelcomeResponse( private set override fun matches(tx: Transaction): Boolean { - val matches = super.matches(tx) && (accountAddress == null || super.toAddress == accountAddress) + val matches = super.matches(tx) && (accountAddress == null || super.toAddress == accountAddress) if (matches) { transaction = tx @@ -41,4 +41,4 @@ class PossibleWelcomeResponse( return matches } -} \ No newline at end of file +} diff --git a/integrations/crowdnode/src/main/java/org/dash/wallet/integrations/crowdnode/ui/CrowdNodeViewModel.kt b/integrations/crowdnode/src/main/java/org/dash/wallet/integrations/crowdnode/ui/CrowdNodeViewModel.kt index 1b04c13596..d15dc3c9e6 100644 --- a/integrations/crowdnode/src/main/java/org/dash/wallet/integrations/crowdnode/ui/CrowdNodeViewModel.kt +++ b/integrations/crowdnode/src/main/java/org/dash/wallet/integrations/crowdnode/ui/CrowdNodeViewModel.kt @@ -412,7 +412,7 @@ class CrowdNodeViewModel @Inject constructor( } } - fun getCrowdNodeAPY() : Double { + fun getCrowdNodeAPY(): Double { return 0.85 * getMasternodeAPY() } } diff --git a/integrations/crowdnode/src/main/java/org/dash/wallet/integrations/crowdnode/ui/ResultFragment.kt b/integrations/crowdnode/src/main/java/org/dash/wallet/integrations/crowdnode/ui/ResultFragment.kt index e9d3cd915a..1845c8d994 100644 --- a/integrations/crowdnode/src/main/java/org/dash/wallet/integrations/crowdnode/ui/ResultFragment.kt +++ b/integrations/crowdnode/src/main/java/org/dash/wallet/integrations/crowdnode/ui/ResultFragment.kt @@ -155,4 +155,4 @@ class ResultFragment : Fragment(R.layout.fragment_result) { findNavController().popBackStack(R.id.crowdNodePortalFragment, false) } } -} \ No newline at end of file +} diff --git a/integrations/crowdnode/src/main/java/org/dash/wallet/integrations/crowdnode/ui/dialogs/StakingDialog.kt b/integrations/crowdnode/src/main/java/org/dash/wallet/integrations/crowdnode/ui/dialogs/StakingDialog.kt index a45a41a50d..b9bda164e0 100644 --- a/integrations/crowdnode/src/main/java/org/dash/wallet/integrations/crowdnode/ui/dialogs/StakingDialog.kt +++ b/integrations/crowdnode/src/main/java/org/dash/wallet/integrations/crowdnode/ui/dialogs/StakingDialog.kt @@ -57,4 +57,4 @@ class StakingDialog : OffsetDialogFragment(R.layout.dialog_staking) { Toast.makeText(requireContext(), R.string.crowdnode_staking_toast_address_copied, Toast.LENGTH_SHORT).show() } } -} \ No newline at end of file +} diff --git a/integrations/crowdnode/src/main/java/org/dash/wallet/integrations/crowdnode/ui/dialogs/WithdrawalLimitsInfoDialog.kt b/integrations/crowdnode/src/main/java/org/dash/wallet/integrations/crowdnode/ui/dialogs/WithdrawalLimitsInfoDialog.kt index f52bd88267..43a4afa688 100644 --- a/integrations/crowdnode/src/main/java/org/dash/wallet/integrations/crowdnode/ui/dialogs/WithdrawalLimitsInfoDialog.kt +++ b/integrations/crowdnode/src/main/java/org/dash/wallet/integrations/crowdnode/ui/dialogs/WithdrawalLimitsInfoDialog.kt @@ -28,8 +28,8 @@ import org.bitcoinj.utils.MonetaryFormat import org.dash.wallet.common.ui.dialogs.AdaptiveDialog import org.dash.wallet.common.ui.viewBinding import org.dash.wallet.integrations.crowdnode.R -import org.dash.wallet.integrations.crowdnode.model.WithdrawalLimitPeriod import org.dash.wallet.integrations.crowdnode.databinding.DialogWithdrawalLimitsBinding +import org.dash.wallet.integrations.crowdnode.model.WithdrawalLimitPeriod class WithdrawalLimitsInfoDialog( private val limitPerTx: Coin, @@ -37,18 +37,18 @@ class WithdrawalLimitsInfoDialog( private val limitPerDay: Coin, private val highlightedLimit: WithdrawalLimitPeriod? = null, private val okButtonText: String? = null -): AdaptiveDialog(R.layout.dialog_withdrawal_limits) { +) : AdaptiveDialog(R.layout.dialog_withdrawal_limits) { private val limitFormat = MonetaryFormat.BTC.minDecimals(0).optionalDecimals(0).noCode() private val binding by viewBinding(DialogWithdrawalLimitsBinding::bind) override fun onViewCreated(view: View, savedInstanceState: Bundle?) { arguments = if (okButtonText.isNullOrEmpty()) { - bundleOf ( + bundleOf( NEG_BUTTON_ARG to getString(android.R.string.ok) ) } else { binding.withdrawOnlineText.isVisible = true - bundleOf ( + bundleOf( NEG_BUTTON_ARG to getString(R.string.button_close), POS_BUTTON_ARG to okButtonText ) @@ -84,4 +84,4 @@ class WithdrawalLimitsInfoDialog( super.onViewCreated(view, savedInstanceState) } -} \ No newline at end of file +} diff --git a/integrations/crowdnode/src/main/java/org/dash/wallet/integrations/crowdnode/ui/entry_point/EntryPointFragment.kt b/integrations/crowdnode/src/main/java/org/dash/wallet/integrations/crowdnode/ui/entry_point/EntryPointFragment.kt index 64bd1bf7db..e079418b7f 100644 --- a/integrations/crowdnode/src/main/java/org/dash/wallet/integrations/crowdnode/ui/entry_point/EntryPointFragment.kt +++ b/integrations/crowdnode/src/main/java/org/dash/wallet/integrations/crowdnode/ui/entry_point/EntryPointFragment.kt @@ -121,4 +121,4 @@ class EntryPointFragment : Fragment(R.layout.fragment_entry_point) { viewModel.hasEnoughBalance.value ?: false ) } -} \ No newline at end of file +} diff --git a/integrations/crowdnode/src/main/java/org/dash/wallet/integrations/crowdnode/ui/entry_point/FirstTimeInfoFragment.kt b/integrations/crowdnode/src/main/java/org/dash/wallet/integrations/crowdnode/ui/entry_point/FirstTimeInfoFragment.kt index 1d566c0e8f..5e64dcab8d 100644 --- a/integrations/crowdnode/src/main/java/org/dash/wallet/integrations/crowdnode/ui/entry_point/FirstTimeInfoFragment.kt +++ b/integrations/crowdnode/src/main/java/org/dash/wallet/integrations/crowdnode/ui/entry_point/FirstTimeInfoFragment.kt @@ -48,4 +48,4 @@ class FirstTimeInfoFragment : Fragment(R.layout.fragment_first_time_info) { viewModel.setInfoShown(true) } -} \ No newline at end of file +} diff --git a/integrations/crowdnode/src/main/java/org/dash/wallet/integrations/crowdnode/ui/entry_point/NewAccountFragment.kt b/integrations/crowdnode/src/main/java/org/dash/wallet/integrations/crowdnode/ui/entry_point/NewAccountFragment.kt index a7b9021576..78672098cf 100644 --- a/integrations/crowdnode/src/main/java/org/dash/wallet/integrations/crowdnode/ui/entry_point/NewAccountFragment.kt +++ b/integrations/crowdnode/src/main/java/org/dash/wallet/integrations/crowdnode/ui/entry_point/NewAccountFragment.kt @@ -129,7 +129,8 @@ class NewAccountFragment : Fragment(R.layout.fragment_new_account) { getString(R.string.crowdnode_login), args[CrowdNodeViewModel.URL_ARG]!!, true - )) + ) + ) } } @@ -164,19 +165,23 @@ class NewAccountFragment : Fragment(R.layout.fragment_new_account) { val clickOnTerms = object : ClickableSpan() { override fun onClick(widget: View) { - safeNavigate(NewAccountFragmentDirections.newAccountToWebView( - getString(R.string.terms_of_use), - getString(R.string.crowdnode_terms_of_use_url) - )) + safeNavigate( + NewAccountFragmentDirections.newAccountToWebView( + getString(R.string.terms_of_use), + getString(R.string.crowdnode_terms_of_use_url) + ) + ) } } val clickOnPrivacy = object : ClickableSpan() { override fun onClick(widget: View) { - safeNavigate(NewAccountFragmentDirections.newAccountToWebView( - getString(R.string.privacy_policy), - getString(R.string.crowdnode_privacy_policy_url) - )) + safeNavigate( + NewAccountFragmentDirections.newAccountToWebView( + getString(R.string.privacy_policy), + getString(R.string.crowdnode_privacy_policy_url) + ) + ) } } @@ -196,4 +201,4 @@ class NewAccountFragment : Fragment(R.layout.fragment_new_account) { val title = getString(R.string.crowdnode_signup_error) safeNavigate(NewAccountFragmentDirections.newAccountToResult(true, title, "")) } -} \ No newline at end of file +} diff --git a/integrations/crowdnode/src/main/java/org/dash/wallet/integrations/crowdnode/ui/online/OnlineAccountEmailFragment.kt b/integrations/crowdnode/src/main/java/org/dash/wallet/integrations/crowdnode/ui/online/OnlineAccountEmailFragment.kt index ac0fc9f43b..b20d6382e7 100644 --- a/integrations/crowdnode/src/main/java/org/dash/wallet/integrations/crowdnode/ui/online/OnlineAccountEmailFragment.kt +++ b/integrations/crowdnode/src/main/java/org/dash/wallet/integrations/crowdnode/ui/online/OnlineAccountEmailFragment.kt @@ -74,7 +74,6 @@ class OnlineAccountEmailFragment : Fragment(R.layout.fragment_online_account_ema } } - binding.continueBtn.setOnClickListener { continueAction() } @@ -94,10 +93,12 @@ class OnlineAccountEmailFragment : Fragment(R.layout.fragment_online_account_ema } viewModel.onlineAccountRequest.observe(viewLifecycleOwner) { args -> - safeNavigate(OnlineAccountEmailFragmentDirections.onlineAccountEmailToSignUp( - args[CrowdNodeViewModel.URL_ARG]!!, - args[CrowdNodeViewModel.EMAIL_ARG] ?: "" - )) + safeNavigate( + OnlineAccountEmailFragmentDirections.onlineAccountEmailToSignUp( + args[CrowdNodeViewModel.URL_ARG]!!, + args[CrowdNodeViewModel.EMAIL_ARG] ?: "" + ) + ) } viewModel.networkError.observe(viewLifecycleOwner) { @@ -110,11 +111,13 @@ class OnlineAccountEmailFragment : Fragment(R.layout.fragment_online_account_ema viewModel.observeCrowdNodeError().observe(viewLifecycleOwner) { if (it != null) { - safeNavigate(OnlineAccountEmailFragmentDirections.onlineAccountEmailToResult( - true, - getString(R.string.crowdnode_signup_error), - "" - )) + safeNavigate( + OnlineAccountEmailFragmentDirections.onlineAccountEmailToResult( + true, + getString(R.string.crowdnode_signup_error), + "" + ) + ) } } @@ -130,6 +133,6 @@ class OnlineAccountEmailFragment : Fragment(R.layout.fragment_online_account_ema private fun isEmail(text: CharSequence?): Boolean { return !text.isNullOrEmpty() && - android.util.Patterns.EMAIL_ADDRESS.matcher(text).matches() + android.util.Patterns.EMAIL_ADDRESS.matcher(text).matches() } -} \ No newline at end of file +} diff --git a/integrations/crowdnode/src/main/java/org/dash/wallet/integrations/crowdnode/ui/online/OnlineAccountInfoFragment.kt b/integrations/crowdnode/src/main/java/org/dash/wallet/integrations/crowdnode/ui/online/OnlineAccountInfoFragment.kt index 2aed6474e1..4409443fea 100644 --- a/integrations/crowdnode/src/main/java/org/dash/wallet/integrations/crowdnode/ui/online/OnlineAccountInfoFragment.kt +++ b/integrations/crowdnode/src/main/java/org/dash/wallet/integrations/crowdnode/ui/online/OnlineAccountInfoFragment.kt @@ -42,4 +42,4 @@ class OnlineAccountInfoFragment : Fragment(R.layout.fragment_online_account_info safeNavigate(OnlineAccountInfoFragmentDirections.onlineAccountInfoToEmail()) } } -} \ No newline at end of file +} diff --git a/integrations/crowdnode/src/main/java/org/dash/wallet/integrations/crowdnode/ui/online/OnlineSignUpFragment.kt b/integrations/crowdnode/src/main/java/org/dash/wallet/integrations/crowdnode/ui/online/OnlineSignUpFragment.kt index d5339863bf..f17f4910d2 100644 --- a/integrations/crowdnode/src/main/java/org/dash/wallet/integrations/crowdnode/ui/online/OnlineSignUpFragment.kt +++ b/integrations/crowdnode/src/main/java/org/dash/wallet/integrations/crowdnode/ui/online/OnlineSignUpFragment.kt @@ -85,4 +85,4 @@ class OnlineSignUpFragment : WebViewFragment() { previousUrl = url } -} \ No newline at end of file +} diff --git a/integrations/crowdnode/src/main/java/org/dash/wallet/integrations/crowdnode/ui/portal/PortalFragment.kt b/integrations/crowdnode/src/main/java/org/dash/wallet/integrations/crowdnode/ui/portal/PortalFragment.kt index 4ea8efdcf0..1e27397d93 100644 --- a/integrations/crowdnode/src/main/java/org/dash/wallet/integrations/crowdnode/ui/portal/PortalFragment.kt +++ b/integrations/crowdnode/src/main/java/org/dash/wallet/integrations/crowdnode/ui/portal/PortalFragment.kt @@ -37,7 +37,6 @@ import org.dash.wallet.common.services.analytics.AnalyticsConstants import org.dash.wallet.common.ui.blinkAnimator import org.dash.wallet.common.ui.dialogs.AdaptiveDialog import org.dash.wallet.common.ui.viewBinding -import org.dash.wallet.common.util.GenericUtils import org.dash.wallet.common.util.safeNavigate import org.dash.wallet.common.util.toFormattedString import org.dash.wallet.integrations.crowdnode.R @@ -64,13 +63,13 @@ class PortalFragment : Fragment(R.layout.fragment_portal) { private val isConfirmed: Boolean get() = viewModel.signUpStatus === SignUpStatus.Finished || - viewModel.onlineAccountStatus == OnlineAccountStatus.Done + viewModel.onlineAccountStatus == OnlineAccountStatus.Done private val isLinkingInProgress: Boolean get() = viewModel.onlineAccountStatus != OnlineAccountStatus.None && - viewModel.onlineAccountStatus != OnlineAccountStatus.Creating && - viewModel.onlineAccountStatus != OnlineAccountStatus.SigningUp && - viewModel.onlineAccountStatus != OnlineAccountStatus.Done + viewModel.onlineAccountStatus != OnlineAccountStatus.Creating && + viewModel.onlineAccountStatus != OnlineAccountStatus.SigningUp && + viewModel.onlineAccountStatus != OnlineAccountStatus.Done override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) @@ -79,11 +78,13 @@ class PortalFragment : Fragment(R.layout.fragment_portal) { viewModel.observeCrowdNodeError().observe(viewLifecycleOwner) { error -> error?.let { - safeNavigate(PortalFragmentDirections.portalToResult( - true, - getErrorMessage(it), - "" - )) + safeNavigate( + PortalFragmentDirections.portalToResult( + true, + getErrorMessage(it), + "" + ) + ) } } @@ -117,9 +118,10 @@ class PortalFragment : Fragment(R.layout.fragment_portal) { viewModel.onlineAccountRequest.observe(viewLifecycleOwner) { args -> safeNavigate( PortalFragmentDirections.portalToSignUp( - args[CrowdNodeViewModel.URL_ARG]!!, - args[CrowdNodeViewModel.EMAIL_ARG] ?: "" - )) + args[CrowdNodeViewModel.URL_ARG]!!, + args[CrowdNodeViewModel.EMAIL_ARG] ?: "" + ) + ) } } @@ -260,18 +262,22 @@ class PortalFragment : Fragment(R.layout.fragment_portal) { binding.onlineAccountBtn.isClickable = !isLinkingInProgress binding.onlineNavIcon.isVisible = !isLinkingInProgress - binding.onlineAccountStatus.text = getText(when (status) { - OnlineAccountStatus.Done -> R.string.crowdnode_online_synced - OnlineAccountStatus.None -> R.string.secure_online_account - OnlineAccountStatus.SigningUp -> R.string.crowdnode_signup_to_finish - else -> R.string.crowdnode_in_process - }) + binding.onlineAccountStatus.text = getText( + when (status) { + OnlineAccountStatus.Done -> R.string.crowdnode_online_synced + OnlineAccountStatus.None -> R.string.secure_online_account + OnlineAccountStatus.SigningUp -> R.string.crowdnode_signup_to_finish + else -> R.string.crowdnode_in_process + } + ) - binding.onlineAccountTitle.text = getText(if (status == OnlineAccountStatus.None) { - R.string.online_account_create - } else { - R.string.online_account - }) + binding.onlineAccountTitle.text = getText( + if (status == OnlineAccountStatus.None) { + R.string.online_account_create + } else { + R.string.online_account + } + ) binding.addressStatusWarning.isVisible = status == OnlineAccountStatus.Validating || @@ -327,12 +333,14 @@ class PortalFragment : Fragment(R.layout.fragment_portal) { return getString(R.string.crowdnode_signup_error) } - return getString(when(exception.message) { - CrowdNodeException.WITHDRAWAL_ERROR -> R.string.crowdnode_withdraw_error - CrowdNodeException.DEPOSIT_ERROR -> R.string.crowdnode_deposit_error - CrowdNodeException.CONFIRMATION_ERROR -> R.string.crowdnode_bad_confirmation - else -> R.string.crowdnode_transfer_error - }) + return getString( + when (exception.message) { + CrowdNodeException.WITHDRAWAL_ERROR -> R.string.crowdnode_withdraw_error + CrowdNodeException.DEPOSIT_ERROR -> R.string.crowdnode_deposit_error + CrowdNodeException.CONFIRMATION_ERROR -> R.string.crowdnode_bad_confirmation + else -> R.string.crowdnode_transfer_error + } + ) } private fun showInfoDialog() { @@ -391,4 +399,4 @@ class PortalFragment : Fragment(R.layout.fragment_portal) { super.onDestroy() this.balanceAnimator = null } -} \ No newline at end of file +} diff --git a/integrations/crowdnode/src/main/java/org/dash/wallet/integrations/crowdnode/ui/portal/TransferFragment.kt b/integrations/crowdnode/src/main/java/org/dash/wallet/integrations/crowdnode/ui/portal/TransferFragment.kt index e0fa6f2aeb..c50884fc33 100644 --- a/integrations/crowdnode/src/main/java/org/dash/wallet/integrations/crowdnode/ui/portal/TransferFragment.kt +++ b/integrations/crowdnode/src/main/java/org/dash/wallet/integrations/crowdnode/ui/portal/TransferFragment.kt @@ -44,7 +44,6 @@ import org.dash.wallet.common.ui.enter_amount.EnterAmountFragment import org.dash.wallet.common.ui.enter_amount.EnterAmountViewModel import org.dash.wallet.common.ui.viewBinding import org.dash.wallet.common.ui.wiggle -import org.dash.wallet.common.util.GenericUtils import org.dash.wallet.common.util.safeNavigate import org.dash.wallet.common.util.toFormattedString import org.dash.wallet.integrations.crowdnode.R @@ -115,15 +114,19 @@ class TransferFragment : Fragment(R.layout.fragment_transfer) { viewModel.observeCrowdNodeError().observe(viewLifecycleOwner) { error -> error?.let { - safeNavigate(TransferFragmentDirections.transferToResult( - true, - getString(if (args.withdraw) { - R.string.crowdnode_withdraw_error - } else { - R.string.crowdnode_deposit_error - }), - "" - )) + safeNavigate( + TransferFragmentDirections.transferToResult( + true, + getString( + if (args.withdraw) { + R.string.crowdnode_withdraw_error + } else { + R.string.crowdnode_deposit_error + } + ), + "" + ) + ) } } @@ -233,22 +236,25 @@ class TransferFragment : Fragment(R.layout.fragment_transfer) { } } - if (isSuccess) { if (isWithdraw) { viewModel.logEvent(AnalyticsConstants.CrowdNode.WITHDRAWAL_REQUESTED) - safeNavigate(TransferFragmentDirections.transferToResult( - false, - getString(R.string.withdrawal_requested), - getString(R.string.withdrawal_requested_message) - )) + safeNavigate( + TransferFragmentDirections.transferToResult( + false, + getString(R.string.withdrawal_requested), + getString(R.string.withdrawal_requested_message) + ) + ) } else { viewModel.logEvent(AnalyticsConstants.CrowdNode.DEPOSIT_REQUESTED) - safeNavigate(TransferFragmentDirections.transferToResult( - false, - getString(R.string.deposit_sent), - getString(R.string.deposit_sent_message) - )) + safeNavigate( + TransferFragmentDirections.transferToResult( + false, + getString(R.string.deposit_sent), + getString(R.string.deposit_sent_message) + ) + ) } } } @@ -304,8 +310,10 @@ class TransferFragment : Fragment(R.layout.fragment_transfer) { binding.balanceText.text = when { dashToFiat -> getString(R.string.available_balance, balance.toFriendlyString()) - rate != null -> getString(R.string.available_balance, - rate.coinToFiat(balance).toFormattedString()) + rate != null -> getString( + R.string.available_balance, + rate.coinToFiat(balance).toFormattedString() + ) else -> "" } } @@ -319,7 +327,9 @@ class TransferFragment : Fragment(R.layout.fragment_transfer) { if (viewModel.shouldShowWithdrawalLimitsInfo()) { val limits = viewModel.getWithdrawalLimits() val result = WithdrawalLimitsInfoDialog( - limits[0], limits[1], limits[2] + limits[0], + limits[1], + limits[2] ).showAsync(requireActivity()) if (result == false) { @@ -341,7 +351,9 @@ class TransferFragment : Fragment(R.layout.fragment_transfer) { } val doAction = WithdrawalLimitsInfoDialog( - limits[0], limits[1], limits[2], + limits[0], + limits[1], + limits[2], highlightedLimit = period, okButtonText = okButtonText ).showAsync(requireActivity()) @@ -359,4 +371,4 @@ class TransferFragment : Fragment(R.layout.fragment_transfer) { val browserIntent = Intent(Intent.ACTION_VIEW, Uri.parse(getString(R.string.crowdnode_withdrawal_policy))) startActivity(browserIntent) } -} \ No newline at end of file +} diff --git a/integrations/crowdnode/src/main/java/org/dash/wallet/integrations/crowdnode/utils/CrowdNodeConstants.kt b/integrations/crowdnode/src/main/java/org/dash/wallet/integrations/crowdnode/utils/CrowdNodeConstants.kt index 23d10a3442..5571febe49 100644 --- a/integrations/crowdnode/src/main/java/org/dash/wallet/integrations/crowdnode/utils/CrowdNodeConstants.kt +++ b/integrations/crowdnode/src/main/java/org/dash/wallet/integrations/crowdnode/utils/CrowdNodeConstants.kt @@ -52,11 +52,14 @@ object CrowdNodeConstants { } fun getCrowdNodeAddress(params: NetworkParameters): Address { - return Address.fromBase58(params, if (params == MainNetParams.get()) { - CROWDNODE_MAINNET_ADDRESS - } else { - CROWDNODE_TESTNET_ADDRESS - }) + return Address.fromBase58( + params, + if (params == MainNetParams.get()) { + CROWDNODE_MAINNET_ADDRESS + } else { + CROWDNODE_TESTNET_ADDRESS + } + ) } fun getCrowdNodeBaseUrl(params: NetworkParameters): String { @@ -86,4 +89,4 @@ object CrowdNodeConstants { TESTNET_LOGIN_URL } } -} \ No newline at end of file +} diff --git a/integrations/crowdnode/src/test/java/org/dash/wallet/integrations/crowdnode/CrowdNodeApiAggregatorTest.kt b/integrations/crowdnode/src/test/java/org/dash/wallet/integrations/crowdnode/CrowdNodeApiAggregatorTest.kt index ef664e05b8..7421e42c08 100644 --- a/integrations/crowdnode/src/test/java/org/dash/wallet/integrations/crowdnode/CrowdNodeApiAggregatorTest.kt +++ b/integrations/crowdnode/src/test/java/org/dash/wallet/integrations/crowdnode/CrowdNodeApiAggregatorTest.kt @@ -93,7 +93,7 @@ class CrowdNodeApiAggregatorTest { localConfig.stub { onBlocking { get(CrowdNodeConfig.ONLINE_ACCOUNT_STATUS) } doReturn OnlineAccountStatus.Linking.ordinal } - val api = CrowdNodeApiAggregator(webApi, blockchainApi, walletData, mock(), mock(), localConfig, globalConfig, mock(), mock(), mock()) // ktlint-disable max-line-length + val api = CrowdNodeApiAggregator(webApi, blockchainApi, walletData, mock(), mock(), localConfig, globalConfig, mock(), mock()) // ktlint-disable max-line-length api.restoreStatus() api.stopTrackingLinked() @@ -112,7 +112,7 @@ class CrowdNodeApiAggregatorTest { } val api = CrowdNodeApiAggregator( mock(), blockchainApi, walletData, mock(), mock(), - localConfig, globalConfig, mock(), mock(), mock() + localConfig, globalConfig, mock(), mock() ) api.restoreStatus() api.stopTrackingLinked() @@ -139,7 +139,7 @@ class CrowdNodeApiAggregatorTest { blockchainApi.stub { on { getFullSignUpTxSet() } doReturn mockFullSet } - val api = CrowdNodeApiAggregator(webApi, blockchainApi, walletData, mock(), mock(), localConfig, globalConfig, mock(), mock(), mock()) // ktlint-disable max-line-length + val api = CrowdNodeApiAggregator(webApi, blockchainApi, walletData, mock(), mock(), localConfig, globalConfig, mock(), mock()) // ktlint-disable max-line-length api.restoreStatus() assertEquals(SignUpStatus.Finished, api.signUpStatus.value) assertEquals(OnlineAccountStatus.None, api.onlineAccountStatus.value) @@ -159,7 +159,7 @@ class CrowdNodeApiAggregatorTest { } val api = CrowdNodeApiAggregator( webApi, blockchainApi, walletData, mock(), mock(), - localConfig, globalConfig, mock(), mock(), mock() + localConfig, globalConfig, mock(), mock() ) api.restoreStatus() api.refreshBalance() @@ -177,24 +177,7 @@ class CrowdNodeApiAggregatorTest { } val api = CrowdNodeApiAggregator( webApi, blockchainApi, walletData, mock(), mock(), - localConfig, globalConfig, mock(), mock(), mock() - ) - api.restoreStatus() - assertEquals(SignUpStatus.LinkedOnline, api.signUpStatus.value) - assertEquals(OnlineAccountStatus.Done, api.onlineAccountStatus.value) - } - } - - // TODO: remove when there is no 7.5.0 in the wild - @Test - fun oldOnlineStatusDoneValue_restoredCorrectly() { - runBlocking { - localConfig.stub { - onBlocking { get(CrowdNodeConfig.ONLINE_ACCOUNT_STATUS) } doReturn 4 - } - val api = CrowdNodeApiAggregator( - webApi, blockchainApi, walletData, mock(), mock(), - localConfig, globalConfig, mock(), mock(), mock() + localConfig, globalConfig, mock(), mock() ) api.restoreStatus() assertEquals(SignUpStatus.LinkedOnline, api.signUpStatus.value) @@ -221,7 +204,7 @@ class CrowdNodeApiAggregatorTest { } val api = CrowdNodeApiAggregator( webApi, blockchainApi, walletData, mock(), mock(), - localConfig, globalConfig, mock(), mock(), mock() + localConfig, globalConfig, mock(), mock() ) api.restoreStatus() assertEquals(SignUpStatus.Finished, api.signUpStatus.value) @@ -255,7 +238,7 @@ class CrowdNodeApiAggregatorTest { val api = CrowdNodeApiAggregator( webApi, blockchainApi, walletData, mock(), mock(), - localConfig, globalConfig, mock(), mock(), mock() + localConfig, globalConfig, mock(), mock() ) api.restoreStatus() diff --git a/integrations/crowdnode/src/test/java/org/dash/wallet/integrations/crowdnode/CrowdNodeTxFilterTest.kt b/integrations/crowdnode/src/test/java/org/dash/wallet/integrations/crowdnode/CrowdNodeTxFilterTest.kt index 1241a77f65..7c840ddb95 100644 --- a/integrations/crowdnode/src/test/java/org/dash/wallet/integrations/crowdnode/CrowdNodeTxFilterTest.kt +++ b/integrations/crowdnode/src/test/java/org/dash/wallet/integrations/crowdnode/CrowdNodeTxFilterTest.kt @@ -32,33 +32,33 @@ import org.mockito.kotlin.stub class CrowdNodeTxFilterTest { private val networkParams = TestNet3Params.get() - private val signUpData = "0100000001f15743be9d858f7e6213bca8262bda38b9b59d44747051899533764cd3ca6606000000006a4730440220412fb2a56090bc271d25fd66a095749bc8c9d4b8200716ed452e91870dddc43702206c3b42a0f3ef11da3a563960c9ac208b6d4da02bafecb06eb7a28d29f8b7cb0f012103691a23ea571114b17de25c310e2b5f978551e1547d1fa465fdb6bb72d17ba3adffffffff02204e0200000000001976a9140d5bcbeeb459af40f97fcb4a98e9d1ed13e904c888ac9d6c0b00000000001976a914f57766c540e7e165092e739e115383bd04d2c21888ac00000000" + private val signUpData = "0100000001f15743be9d858f7e6213bca8262bda38b9b59d44747051899533764cd3ca6606000000006a4730440220412fb2a56090bc271d25fd66a095749bc8c9d4b8200716ed452e91870dddc43702206c3b42a0f3ef11da3a563960c9ac208b6d4da02bafecb06eb7a28d29f8b7cb0f012103691a23ea571114b17de25c310e2b5f978551e1547d1fa465fdb6bb72d17ba3adffffffff02204e0200000000001976a9140d5bcbeeb459af40f97fcb4a98e9d1ed13e904c888ac9d6c0b00000000001976a914f57766c540e7e165092e739e115383bd04d2c21888ac00000000" // ktlint-disable max-line-length private val signUpRequestTx = Transaction(networkParams, Utils.HEX.decode(signUpData)) - private val acceptTermsData = "010000000299378f2db43315876e11a9433c139ba8259181c20c321b64c35328a0867655d8000000006b483045022100cadd63226d6dbf0711d2abe6dab4d6ae8275dc1d273b3b9b8b6435459d81bce102206b2816298e66c78d5e3e8aa02721c06e12ce860ef1e356ee7faf19954204f8720121027c974ed291479646948719c1889aee27bc4e29af2a6c7236a92555d2b3348b97ffffffff8064383dc40802e7bc4dfb9962f930e663ee2bfbe120755f9c7f67230d8cdcf2010000006a47304402207c1c9716c01a72f61983692e5e24125048c23bf2366aec6ececaa848acdb3174022037b07e46e8e04f050c1898d6f343179ffd7bcbf30138b0d380e67ea85f1c1e030121027c974ed291479646948719c1889aee27bc4e29af2a6c7236a92555d2b3348b97ffffffff02204e0100000000001976a9140d5bcbeeb459af40f97fcb4a98e9d1ed13e904c888ac276b0a00000000001976a914c80f91cf7031ad1520661e2a6f9ff3b176bcf96588ac00000000" + private val acceptTermsData = "010000000299378f2db43315876e11a9433c139ba8259181c20c321b64c35328a0867655d8000000006b483045022100cadd63226d6dbf0711d2abe6dab4d6ae8275dc1d273b3b9b8b6435459d81bce102206b2816298e66c78d5e3e8aa02721c06e12ce860ef1e356ee7faf19954204f8720121027c974ed291479646948719c1889aee27bc4e29af2a6c7236a92555d2b3348b97ffffffff8064383dc40802e7bc4dfb9962f930e663ee2bfbe120755f9c7f67230d8cdcf2010000006a47304402207c1c9716c01a72f61983692e5e24125048c23bf2366aec6ececaa848acdb3174022037b07e46e8e04f050c1898d6f343179ffd7bcbf30138b0d380e67ea85f1c1e030121027c974ed291479646948719c1889aee27bc4e29af2a6c7236a92555d2b3348b97ffffffff02204e0100000000001976a9140d5bcbeeb459af40f97fcb4a98e9d1ed13e904c888ac276b0a00000000001976a914c80f91cf7031ad1520661e2a6f9ff3b176bcf96588ac00000000" // ktlint-disable max-line-length private val acceptTermsRequestTx = Transaction(networkParams, Utils.HEX.decode(acceptTermsData)) - private val welcomeData = "020000000263779831af3973f7f8f1c390c363c3eae19bcc60c0296852ecea832e16022769010000006a473044022042dcb3849c7018cc99879bcea881284c3a5848ae5caf4c7d1390a9cbde812e780220557da9f91b088c5a59db6ed82ea34e5f5a4f5d1bf10fa5ccff920c4a461ecb4a012102bf7c36100b0d394e79a1704b8bf9e030a62e139a293f5da891671c56d555f732feffffff90bd741046ab7e68d532ac0466920729f3070b1184a4703ac620601ff594d0ff000000006a47304402202b512d7a20279a1aed12dd05619a2951412ff3d0ded96327a43ddd02dba0c1ad0220337f62aafb3721575e2ccd3c6bac97591186e6cca92f54896e08c1cd529e7be6012102bf7c36100b0d394e79a1704b8bf9e030a62e139a293f5da891671c56d555f732feffffff02244e0000000000001976a914f57766c540e7e165092e739e115383bd04d2c21888ac5c1dc4680a0000001976a9140d5bcbeeb459af40f97fcb4a98e9d1ed13e904c888acfbdc0a00" + private val welcomeData = "020000000263779831af3973f7f8f1c390c363c3eae19bcc60c0296852ecea832e16022769010000006a473044022042dcb3849c7018cc99879bcea881284c3a5848ae5caf4c7d1390a9cbde812e780220557da9f91b088c5a59db6ed82ea34e5f5a4f5d1bf10fa5ccff920c4a461ecb4a012102bf7c36100b0d394e79a1704b8bf9e030a62e139a293f5da891671c56d555f732feffffff90bd741046ab7e68d532ac0466920729f3070b1184a4703ac620601ff594d0ff000000006a47304402202b512d7a20279a1aed12dd05619a2951412ff3d0ded96327a43ddd02dba0c1ad0220337f62aafb3721575e2ccd3c6bac97591186e6cca92f54896e08c1cd529e7be6012102bf7c36100b0d394e79a1704b8bf9e030a62e139a293f5da891671c56d555f732feffffff02244e0000000000001976a914f57766c540e7e165092e739e115383bd04d2c21888ac5c1dc4680a0000001976a9140d5bcbeeb459af40f97fcb4a98e9d1ed13e904c888acfbdc0a00" // ktlint-disable max-line-length private val welcomeResponseTx = Transaction(networkParams, Utils.HEX.decode(welcomeData)) - private val receivedData = "0200000001eb1543c0d57f5baa4f841b9017b7c9e056784b35c3771cbe3dbc7dc3d1f64f8e010000006b483045022100805c75979e175e7ed621ef7d22fe830a21137271e0f6bae52bb42a7386cf936102207770ccac9e5be357731fd01d205a7aec792413145ab966266a2e2f1df7182732012102bf7c36100b0d394e79a1704b8bf9e030a62e139a293f5da891671c56d555f732feffffff02250fad02000000001976a914f57766c540e7e165092e739e115383bd04d2c21888ac10c9e24c000000001976a9140d5bcbeeb459af40f97fcb4a98e9d1ed13e904c888acdbde0a00" + private val receivedData = "0200000001eb1543c0d57f5baa4f841b9017b7c9e056784b35c3771cbe3dbc7dc3d1f64f8e010000006b483045022100805c75979e175e7ed621ef7d22fe830a21137271e0f6bae52bb42a7386cf936102207770ccac9e5be357731fd01d205a7aec792413145ab966266a2e2f1df7182732012102bf7c36100b0d394e79a1704b8bf9e030a62e139a293f5da891671c56d555f732feffffff02250fad02000000001976a914f57766c540e7e165092e739e115383bd04d2c21888ac10c9e24c000000001976a9140d5bcbeeb459af40f97fcb4a98e9d1ed13e904c888acdbde0a00" // ktlint-disable max-line-length private val receivedTx = Transaction(networkParams, Utils.HEX.decode(receivedData)) @Before fun setup() { - val signupConnected = "0100000001a14301088210333bd5d0959624b153d6d0dcfd5a67813ec11df4879808b1b6ac000000006a473044022000c5bb339303d916de5765446a93a1513d151407c598f0f7aba79ea62795892b022012f97f16d00876b08d8e55a8c99ad4b009615789c6e4322cdb760f4a649d3ed5012103626557ad8e11b2bf7004683e2bd634579329b00902e67177c768bfa76fc3796bffffffff02a0bb0d00000000001976a914f57766c540e7e165092e739e115383bd04d2c21888acfdd98a00000000001976a914bd065b89a786f96a4529a4dacc0bb4a14268147088ac00000000" + val signupConnected = "0100000001a14301088210333bd5d0959624b153d6d0dcfd5a67813ec11df4879808b1b6ac000000006a473044022000c5bb339303d916de5765446a93a1513d151407c598f0f7aba79ea62795892b022012f97f16d00876b08d8e55a8c99ad4b009615789c6e4322cdb760f4a649d3ed5012103626557ad8e11b2bf7004683e2bd634579329b00902e67177c768bfa76fc3796bffffffff02a0bb0d00000000001976a914f57766c540e7e165092e739e115383bd04d2c21888acfdd98a00000000001976a914bd065b89a786f96a4529a4dacc0bb4a14268147088ac00000000" // ktlint-disable max-line-length val signupConnectedTx = Transaction(networkParams, Utils.HEX.decode(signupConnected)) signUpRequestTx.inputs[0].connect(signupConnectedTx.outputs[0]) - var acceptTermsConnected = "020000000580a3d49f19aa295f88f3cac50a7b8b03aa9827613194b343d1b4619154765037010000006a47304402205eff3b09ae40a57d647f390da127dfbc5d60def439b654818f5b3b7d7b6bc75902201af302957b373fa1478d9c8d4990ed682e25c8a65ce908dc098f7fd93731e5cb012102bf7c36100b0d394e79a1704b8bf9e030a62e139a293f5da891671c56d555f732feffffff7bb77f9e2029037811ae12e113139c1acfa94b43e889f49f51ac3ca092878e7a010000006a473044022034105004d1cd96f4e464d1735ef5c97f931a838d53f59e29a59f0c1b5fdae22c02204a157f4305dad527274c44f76dc560352e36bde13d9948d81e4606eaa3f18b9e012102bf7c36100b0d394e79a1704b8bf9e030a62e139a293f5da891671c56d555f732feffffff3b9ae62e792bdd6f79dce865951bff5e8f881c43f9bd405481f7e9fd4f7ad897010000006a4730440220623abe3320ce5d504c7128f05b0d8a6551726806cea5e4a29cdc41bf6310be1b0220396659b3636f4be5179324bcb8288218a35850b2004efa3a00074f2348441244012102bf7c36100b0d394e79a1704b8bf9e030a62e139a293f5da891671c56d555f732feffffffd90fc886050e5676fb588af6982461bbab46f28032a613b8af4a47b80affb4ee010000006a473044022020da20cac78787d18a5eb89449b9386d5d66300a509a5e56904dbbf010ee3883022021aed84acbd7f1e572a85e9964709fa33332f36924ceb6d2b1547b8df0369c3e012102bf7c36100b0d394e79a1704b8bf9e030a62e139a293f5da891671c56d555f732feffffff8064383dc40802e7bc4dfb9962f930e663ee2bfbe120755f9c7f67230d8cdcf2000000006a47304402202db504962921331f6dfbe5b8ad9253ac692a9a104cd8dbf2fea8609cc268d1f402207c77072b00ada37e9ef1ca3e0bd9fe3ac962ff21e0c5475ffe2045b8d8c50c87012102bf7c36100b0d394e79a1704b8bf9e030a62e139a293f5da891671c56d555f732feffffff02224e0000000000001976a914c80f91cf7031ad1520661e2a6f9ff3b176bcf96588ac5c757038180000001976a9140d5bcbeeb459af40f97fcb4a98e9d1ed13e904c888ac9f470b00" + var acceptTermsConnected = "020000000580a3d49f19aa295f88f3cac50a7b8b03aa9827613194b343d1b4619154765037010000006a47304402205eff3b09ae40a57d647f390da127dfbc5d60def439b654818f5b3b7d7b6bc75902201af302957b373fa1478d9c8d4990ed682e25c8a65ce908dc098f7fd93731e5cb012102bf7c36100b0d394e79a1704b8bf9e030a62e139a293f5da891671c56d555f732feffffff7bb77f9e2029037811ae12e113139c1acfa94b43e889f49f51ac3ca092878e7a010000006a473044022034105004d1cd96f4e464d1735ef5c97f931a838d53f59e29a59f0c1b5fdae22c02204a157f4305dad527274c44f76dc560352e36bde13d9948d81e4606eaa3f18b9e012102bf7c36100b0d394e79a1704b8bf9e030a62e139a293f5da891671c56d555f732feffffff3b9ae62e792bdd6f79dce865951bff5e8f881c43f9bd405481f7e9fd4f7ad897010000006a4730440220623abe3320ce5d504c7128f05b0d8a6551726806cea5e4a29cdc41bf6310be1b0220396659b3636f4be5179324bcb8288218a35850b2004efa3a00074f2348441244012102bf7c36100b0d394e79a1704b8bf9e030a62e139a293f5da891671c56d555f732feffffffd90fc886050e5676fb588af6982461bbab46f28032a613b8af4a47b80affb4ee010000006a473044022020da20cac78787d18a5eb89449b9386d5d66300a509a5e56904dbbf010ee3883022021aed84acbd7f1e572a85e9964709fa33332f36924ceb6d2b1547b8df0369c3e012102bf7c36100b0d394e79a1704b8bf9e030a62e139a293f5da891671c56d555f732feffffff8064383dc40802e7bc4dfb9962f930e663ee2bfbe120755f9c7f67230d8cdcf2000000006a47304402202db504962921331f6dfbe5b8ad9253ac692a9a104cd8dbf2fea8609cc268d1f402207c77072b00ada37e9ef1ca3e0bd9fe3ac962ff21e0c5475ffe2045b8d8c50c87012102bf7c36100b0d394e79a1704b8bf9e030a62e139a293f5da891671c56d555f732feffffff02224e0000000000001976a914c80f91cf7031ad1520661e2a6f9ff3b176bcf96588ac5c757038180000001976a9140d5bcbeeb459af40f97fcb4a98e9d1ed13e904c888ac9f470b00" // ktlint-disable max-line-length var acceptTermsConnectedTx = Transaction(networkParams, Utils.HEX.decode(acceptTermsConnected)) acceptTermsRequestTx.inputs[0].connect(acceptTermsConnectedTx.outputs[0]) - acceptTermsConnected = "0100000001f592b906b367e3bca0cfe7da733747baad88f17092cbbd03cb19bdeaddbe8d03010000006b4830450221008851ab86e3690334a4b17b842ccf5279af5a5048182faa2cd2cc1ea27bf4507202206576fe169513ffd05513244a23a97f44cb9359181877393d0dff05714818e7ab0121027c974ed291479646948719c1889aee27bc4e29af2a6c7236a92555d2b3348b97ffffffff02204e0200000000001976a9140d5bcbeeb459af40f97fcb4a98e9d1ed13e904c888ac9d6c0b00000000001976a914c80f91cf7031ad1520661e2a6f9ff3b176bcf96588ac00000000" + acceptTermsConnected = "0100000001f592b906b367e3bca0cfe7da733747baad88f17092cbbd03cb19bdeaddbe8d03010000006b4830450221008851ab86e3690334a4b17b842ccf5279af5a5048182faa2cd2cc1ea27bf4507202206576fe169513ffd05513244a23a97f44cb9359181877393d0dff05714818e7ab0121027c974ed291479646948719c1889aee27bc4e29af2a6c7236a92555d2b3348b97ffffffff02204e0200000000001976a9140d5bcbeeb459af40f97fcb4a98e9d1ed13e904c888ac9d6c0b00000000001976a914c80f91cf7031ad1520661e2a6f9ff3b176bcf96588ac00000000" // ktlint-disable max-line-length acceptTermsConnectedTx = Transaction(networkParams, Utils.HEX.decode(acceptTermsConnected)) acceptTermsRequestTx.inputs[1].connect(acceptTermsConnectedTx.outputs[0]) - val welcomeConnected = "0200000002f7f6beb8d49ec4639394a663cd3ae08d9382ecfbb38e9cb85deaf835b74ad1be000000006a47304402202b467d0ae5f40633500096b01dbc5952efca40d50143739dc92f2b9fc8cf479c02206d3a04f11538ce4ff664b168abad0b02d135b3ec571b390616ac76f888e0ecaf012102bf7c36100b0d394e79a1704b8bf9e030a62e139a293f5da891671c56d555f732feffffffd979b8815c9a17e956011b7b9767dcb1237501832392a1f5d2ed2e0d785754c2010000006a473044022079f2e9dd53d838978fe82a6034894c062381ac32bce190e0d862efff7e4a5ef002200eee7a5bd39f084cd8dc73d50d1fc1388e998750db3fadbd74ff2537bef0cd8e012102bf7c36100b0d394e79a1704b8bf9e030a62e139a293f5da891671c56d555f732feffffff02224e0000000000001976a914f57766c540e7e165092e739e115383bd04d2c21888acf91ec3680a0000001976a9140d5bcbeeb459af40f97fcb4a98e9d1ed13e904c888acfadc0a00" + val welcomeConnected = "0200000002f7f6beb8d49ec4639394a663cd3ae08d9382ecfbb38e9cb85deaf835b74ad1be000000006a47304402202b467d0ae5f40633500096b01dbc5952efca40d50143739dc92f2b9fc8cf479c02206d3a04f11538ce4ff664b168abad0b02d135b3ec571b390616ac76f888e0ecaf012102bf7c36100b0d394e79a1704b8bf9e030a62e139a293f5da891671c56d555f732feffffffd979b8815c9a17e956011b7b9767dcb1237501832392a1f5d2ed2e0d785754c2010000006a473044022079f2e9dd53d838978fe82a6034894c062381ac32bce190e0d862efff7e4a5ef002200eee7a5bd39f084cd8dc73d50d1fc1388e998750db3fadbd74ff2537bef0cd8e012102bf7c36100b0d394e79a1704b8bf9e030a62e139a293f5da891671c56d555f732feffffff02224e0000000000001976a914f57766c540e7e165092e739e115383bd04d2c21888acf91ec3680a0000001976a9140d5bcbeeb459af40f97fcb4a98e9d1ed13e904c888acfadc0a00" // ktlint-disable max-line-length val welcomeConnectedTx = Transaction(networkParams, Utils.HEX.decode(welcomeConnected)) welcomeResponseTx.inputs[0].connect(welcomeConnectedTx.outputs[0]) - val receivedConnected = "020000000110ae7a5bff9016348ea2babb8b614ccf498cc89b2dd00273b009874d33b5735a010000006b483045022100a8a27fd8a0a9579fccabb171604947a75223d27eb5ae28cc348ae9c682d24b2002205d27b13f124816789e940df33c78bf3dc3ef35ee1928cebb55ec32ed095f3e8f012102bf7c36100b0d394e79a1704b8bf9e030a62e139a293f5da891671c56d555f732feffffff02304e0000000000001976a914f57766c540e7e165092e739e115383bd04d2c21888ac2cd98f4f000000001976a9140d5bcbeeb459af40f97fcb4a98e9d1ed13e904c888acdbde0a00" + val receivedConnected = "020000000110ae7a5bff9016348ea2babb8b614ccf498cc89b2dd00273b009874d33b5735a010000006b483045022100a8a27fd8a0a9579fccabb171604947a75223d27eb5ae28cc348ae9c682d24b2002205d27b13f124816789e940df33c78bf3dc3ef35ee1928cebb55ec32ed095f3e8f012102bf7c36100b0d394e79a1704b8bf9e030a62e139a293f5da891671c56d555f732feffffff02304e0000000000001976a914f57766c540e7e165092e739e115383bd04d2c21888ac2cd98f4f000000001976a9140d5bcbeeb459af40f97fcb4a98e9d1ed13e904c888acdbde0a00" // ktlint-disable max-line-length val receivedConnectedTx = Transaction(networkParams, Utils.HEX.decode(receivedConnected)) receivedTx.inputs[0].connect(receivedConnectedTx.outputs[0]) } @@ -71,7 +71,7 @@ class CrowdNodeTxFilterTest { assertEquals(1, filter.fromAddresses.size) assertEquals("yihMSMoesHX1JhbntTiV5Nptf5NLrmFMCu", filter.fromAddresses.first().toBase58()) - var notSignUpData = "01000000033f90cbc2d751c77358b3ff37efd72936b389a17b9ec72bdec4678394814cfe2d000000006a473044022050d2f3b6f097f1973b29bb5a0e98f307f6fc338bb8d29e4a7eb257eebd147ccd022055f88aa06cf90aec97991db9c351fd622fa60fe2cb6bbe6df2ecfef03ca047fa012102d336120a91d7d3497056715f6078e36c56e84c41038cf630260ef3245f6ba39effffffff94cae0fa480e004218a66ea7eae8c0a1a39dbd8ebba966004ddfdcac1e11f089000000006b483045022100ed1fbe54b90c8d69e616b79ba5e03e192bdee6b26f66d40d9da14ae7c7e64a9c022062c54fb1635937a38f3b43b504777c9faf357734cad6f53130870f7e980a3be60121037c4c4205eceb06bbf1e4894e52ecddcf700e1a699e2a4cbee9fd7ed748fb7a59ffffffff3e2611f35c7a2fefadce6b115ce8e14b31b627667af9c04909c0ddcceb8294a3000000006a473044022036bed2e8600ed1a715618ca398553254c14fcea824b77ed784cee5f5b23b84df022041c4821e6e639169ddc891e4d6b4e146e5f4684e5687daf5fcce2fd1f73392230121037c4c4205eceb06bbf1e4894e52ecddcf700e1a699e2a4cbee9fd7ed748fb7a59ffffffff0260182300000000001976a9140205411ec940f9139ea72e3a999d21fceff671e688ac4dc27200000000001976a91425b2b9126bf32e6115a813d019e72b7b9106211b88ac00000000" + var notSignUpData = "01000000033f90cbc2d751c77358b3ff37efd72936b389a17b9ec72bdec4678394814cfe2d000000006a473044022050d2f3b6f097f1973b29bb5a0e98f307f6fc338bb8d29e4a7eb257eebd147ccd022055f88aa06cf90aec97991db9c351fd622fa60fe2cb6bbe6df2ecfef03ca047fa012102d336120a91d7d3497056715f6078e36c56e84c41038cf630260ef3245f6ba39effffffff94cae0fa480e004218a66ea7eae8c0a1a39dbd8ebba966004ddfdcac1e11f089000000006b483045022100ed1fbe54b90c8d69e616b79ba5e03e192bdee6b26f66d40d9da14ae7c7e64a9c022062c54fb1635937a38f3b43b504777c9faf357734cad6f53130870f7e980a3be60121037c4c4205eceb06bbf1e4894e52ecddcf700e1a699e2a4cbee9fd7ed748fb7a59ffffffff3e2611f35c7a2fefadce6b115ce8e14b31b627667af9c04909c0ddcceb8294a3000000006a473044022036bed2e8600ed1a715618ca398553254c14fcea824b77ed784cee5f5b23b84df022041c4821e6e639169ddc891e4d6b4e146e5f4684e5687daf5fcce2fd1f73392230121037c4c4205eceb06bbf1e4894e52ecddcf700e1a699e2a4cbee9fd7ed748fb7a59ffffffff0260182300000000001976a9140205411ec940f9139ea72e3a999d21fceff671e688ac4dc27200000000001976a91425b2b9126bf32e6115a813d019e72b7b9106211b88ac00000000" // ktlint-disable max-line-length var notSignUpTx = Transaction(networkParams, Utils.HEX.decode(notSignUpData)) assertEquals( notSignUpTx.txId.toString(), @@ -79,7 +79,7 @@ class CrowdNodeTxFilterTest { ) assertFalse("Tx matches but should not", filter.matches(notSignUpTx)) - notSignUpData = "02000000024b86656e0590d048c666970225930d5806f746646eea0982be81fb354114e60d010000006a4730440220318c122e24d780b6123f001eb7fb006eda71a17067f25c96f067261da2fab4290220351d6a75c278d780550f0a5494082c749ed7737b4905c4383a206154ab4b7f94012102bf7c36100b0d394e79a1704b8bf9e030a62e139a293f5da891671c56d555f732feffffff476e14bb4fa20abc1fd23ef0ad17c2b65a6cf8959f51cfc412656a1a773c9249000000006a47304402201ebad0b1f3a2df05e9368d94a91970334283a5812537e34302260e8b6e124e180220141defc2b70fbd45ac4bb968ce9d51dc219d33709d2cc9fae2c73d61afe9f654012102bf7c36100b0d394e79a1704b8bf9e030a62e139a293f5da891671c56d555f732feffffff02244e0000000000001976a9140a5d65dba28a8a9b50b2f0d50da31f24990856fb88ace3b180290a0000001976a9140d5bcbeeb459af40f97fcb4a98e9d1ed13e904c888aca2dc0a00" + notSignUpData = "02000000024b86656e0590d048c666970225930d5806f746646eea0982be81fb354114e60d010000006a4730440220318c122e24d780b6123f001eb7fb006eda71a17067f25c96f067261da2fab4290220351d6a75c278d780550f0a5494082c749ed7737b4905c4383a206154ab4b7f94012102bf7c36100b0d394e79a1704b8bf9e030a62e139a293f5da891671c56d555f732feffffff476e14bb4fa20abc1fd23ef0ad17c2b65a6cf8959f51cfc412656a1a773c9249000000006a47304402201ebad0b1f3a2df05e9368d94a91970334283a5812537e34302260e8b6e124e180220141defc2b70fbd45ac4bb968ce9d51dc219d33709d2cc9fae2c73d61afe9f654012102bf7c36100b0d394e79a1704b8bf9e030a62e139a293f5da891671c56d555f732feffffff02244e0000000000001976a9140a5d65dba28a8a9b50b2f0d50da31f24990856fb88ace3b180290a0000001976a9140d5bcbeeb459af40f97fcb4a98e9d1ed13e904c888aca2dc0a00" // ktlint-disable max-line-length notSignUpTx = Transaction(networkParams, Utils.HEX.decode(notSignUpData)) assertFalse("Tx matches but should not", filter.matches(notSignUpTx)) } @@ -91,9 +91,9 @@ class CrowdNodeTxFilterTest { assertFalse("Tx matches but should not", filter.matches(welcomeResponseTx)) assertTrue("Tx doesn't match", filter.matches(receivedTx)) - val receivedData = "02000000016041b60434fd4353aad02bd3e8067f67b5cb26751c93154bedab79d65e2826c20100000069463043021f7ac8a5f56eb3ec847f891e20799e408d2079f4e671dff5383e688bf86d543e0220412ba4935e84ec7f5d90850aecc5b447b3f6bed82615bba0af52b6d29637cb28012102bf7c36100b0d394e79a1704b8bf9e030a62e139a293f5da891671c56d555f732feffffff02e5250000000000001976a914f57766c540e7e165092e739e115383bd04d2c21888ac13548d180c0000001976a9140d5bcbeeb459af40f97fcb4a98e9d1ed13e904c888acb4de0a00" + val receivedData = "02000000016041b60434fd4353aad02bd3e8067f67b5cb26751c93154bedab79d65e2826c20100000069463043021f7ac8a5f56eb3ec847f891e20799e408d2079f4e671dff5383e688bf86d543e0220412ba4935e84ec7f5d90850aecc5b447b3f6bed82615bba0af52b6d29637cb28012102bf7c36100b0d394e79a1704b8bf9e030a62e139a293f5da891671c56d555f732feffffff02e5250000000000001976a914f57766c540e7e165092e739e115383bd04d2c21888ac13548d180c0000001976a9140d5bcbeeb459af40f97fcb4a98e9d1ed13e904c888acb4de0a00" // ktlint-disable max-line-length val receivedTx = Transaction(networkParams, Utils.HEX.decode(receivedData)) - val connectedData = "0200000002b069bf6ca5f28203f090396e23da59d271397c8978da359362bc6e38990a2631010000006a47304402204c31b657e983e3ea4abbb362fa5fd050085e92cffb723a12c714a625e87d6a0e0220299bf472f12eb54627339ab6b720aed1acc4f238790441deefc29d0ec44f3e10012102bf7c36100b0d394e79a1704b8bf9e030a62e139a293f5da891671c56d555f732feffffff472666d8c072985cb6bbf5ed749ee61d484e080e39817f03f33939f1697b449f000000006b483045022100ed26d531cf0ec33e36e086abfd3b6abad1bbb856b57298d7606bb24db6dbb37002203017a9a7b9ba93606b1047719616adc72456d24cd4ad1e50dee10c9cc961e2d8012102bf7c36100b0d394e79a1704b8bf9e030a62e139a293f5da891671c56d555f732feffffff02304e0000000000001976a914f57766c540e7e165092e739e115383bd04d2c21888acef7a8d180c0000001976a9140d5bcbeeb459af40f97fcb4a98e9d1ed13e904c888ac86de0a00" + val connectedData = "0200000002b069bf6ca5f28203f090396e23da59d271397c8978da359362bc6e38990a2631010000006a47304402204c31b657e983e3ea4abbb362fa5fd050085e92cffb723a12c714a625e87d6a0e0220299bf472f12eb54627339ab6b720aed1acc4f238790441deefc29d0ec44f3e10012102bf7c36100b0d394e79a1704b8bf9e030a62e139a293f5da891671c56d555f732feffffff472666d8c072985cb6bbf5ed749ee61d484e080e39817f03f33939f1697b449f000000006b483045022100ed26d531cf0ec33e36e086abfd3b6abad1bbb856b57298d7606bb24db6dbb37002203017a9a7b9ba93606b1047719616adc72456d24cd4ad1e50dee10c9cc961e2d8012102bf7c36100b0d394e79a1704b8bf9e030a62e139a293f5da891671c56d555f732feffffff02304e0000000000001976a914f57766c540e7e165092e739e115383bd04d2c21888acef7a8d180c0000001976a9140d5bcbeeb459af40f97fcb4a98e9d1ed13e904c888ac86de0a00" // ktlint-disable max-line-length val connectedTx = Transaction(networkParams, Utils.HEX.decode(connectedData)) receivedTx.inputs[0].connect(connectedTx.outputs[0]) @@ -108,10 +108,10 @@ class CrowdNodeTxFilterTest { assertFalse("Tx matches but should not", filter.matches(welcomeResponseTx)) assertFalse("Tx matches but should not", filter.matches(receivedTx)) - val depositData = "010000000380171b155fae80a1015407f0ba29a40006ff2fbd2e81d71969e865610d36b05e010000006a473044022029b38ae385dfc481de21f82f96d528d2da08927e6a779a7d518b05ca47cd707a02205ddfeae5953695550acf017efca2338d9aa1caedf32960b7f82bf34bf99b8646012103691a23ea571114b17de25c310e2b5f978551e1547d1fa465fdb6bb72d17ba3adffffffff558815361e94ba890231e567190dd10ff599dc5d51d4893a1a81b71da6223576000000006a473044022065be5bfd08ee9960be4c1ce82e951432c2437e76291ec4c18cfb971203befc4e02202f5fdda35868916b0ce7f297e7e6605ccfbee83e0d188e35a76b2dd6dc8e9fcd012103691a23ea571114b17de25c310e2b5f978551e1547d1fa465fdb6bb72d17ba3adffffffff6e413a0400d210f391582b050fbbfea66472e705d6a9c6dbcbb25038bd06428b000000006b483045022100d2b7de552ca945e13b8754ab51499ce732f94cf9b5f4d1730d18270d91d648bc022065ff38acb907f0580a0b9eae80e6836e5e7e2e4466b986567f9e1eb8250ca6aa012103691a23ea571114b17de25c310e2b5f978551e1547d1fa465fdb6bb72d17ba3adffffffff0210530000000000001976a914f57766c540e7e165092e739e115383bd04d2c21888acc0d8a700000000001976a9140d5bcbeeb459af40f97fcb4a98e9d1ed13e904c888ac00000000" - val dataConnected0 = "0100000001f09160ffa383d3aaf4cd8f8943b4d9050132448a4ba718d0462238456fd60535010000006a47304402200af78214fe9a60a8615080f3aef89b0a6129bbb5ca70a7c843fb4dd87a2d6b44022056bec0e4279377f82deed94e28607c75a4cf029c01c00506fb5226a9a3d5a164012103cc0bd3cb81e08fd09ee2a6f407dbe4020f12579f8197818d189b6c41318564ecffffffff02865d2d00000000001976a914e1cd0e852b0ea6a9b7d9c58881a4b3ff3842506d88aca8dca700000000001976a914f57766c540e7e165092e739e115383bd04d2c21888ac00000000" - val dataConnected1 = "02000000026e413a0400d210f391582b050fbbfea66472e705d6a9c6dbcbb25038bd06428b010000006a47304402205e703cd097bb50aedb0013f4ed60cb7a4fb928414480d56121e93e5ee943f7890220711a681aa134a5385fe3692411a2f6ae447715f171e38f3e083e2090899e55b8012102bf7c36100b0d394e79a1704b8bf9e030a62e139a293f5da891671c56d555f732feffffff79ac9f85e66f787269d85dcafb4b7a132d04d8476f15432523adf3f3bbf851e1010000006a47304402205fb26b67d38ea82667cad0b7e8c5b6b8dc5244446326c2aed8b26c769eb9f46202206f88386025cf9cb30bdfdca20b9881db0751e19bbc9ec047fc674ced83e4656f012102bf7c36100b0d394e79a1704b8bf9e030a62e139a293f5da891671c56d555f732feffffff02304e0000000000001976a914f57766c540e7e165092e739e115383bd04d2c21888ac905c2309010000001976a9140d5bcbeeb459af40f97fcb4a98e9d1ed13e904c888ac50df0a00" - val dataConnected2 = "01000000019858b2a28ca42a04e315cbede431104bef6060b5a7c6d526461f76e7a8f9ae2c010000006a47304402204629c835770e35d836fea256bf70addc680445e75161eee1662deea2ac3081910220230d9d573fedf568dd40badcec74890d54de88b9d3f7710572138425441fab55012103691a23ea571114b17de25c310e2b5f978551e1547d1fa465fdb6bb72d17ba3adffffffff0205030000000000001976a914f57766c540e7e165092e739e115383bd04d2c21888ac08520000000000001976a9140d5bcbeeb459af40f97fcb4a98e9d1ed13e904c888ac00000000" + val depositData = "010000000380171b155fae80a1015407f0ba29a40006ff2fbd2e81d71969e865610d36b05e010000006a473044022029b38ae385dfc481de21f82f96d528d2da08927e6a779a7d518b05ca47cd707a02205ddfeae5953695550acf017efca2338d9aa1caedf32960b7f82bf34bf99b8646012103691a23ea571114b17de25c310e2b5f978551e1547d1fa465fdb6bb72d17ba3adffffffff558815361e94ba890231e567190dd10ff599dc5d51d4893a1a81b71da6223576000000006a473044022065be5bfd08ee9960be4c1ce82e951432c2437e76291ec4c18cfb971203befc4e02202f5fdda35868916b0ce7f297e7e6605ccfbee83e0d188e35a76b2dd6dc8e9fcd012103691a23ea571114b17de25c310e2b5f978551e1547d1fa465fdb6bb72d17ba3adffffffff6e413a0400d210f391582b050fbbfea66472e705d6a9c6dbcbb25038bd06428b000000006b483045022100d2b7de552ca945e13b8754ab51499ce732f94cf9b5f4d1730d18270d91d648bc022065ff38acb907f0580a0b9eae80e6836e5e7e2e4466b986567f9e1eb8250ca6aa012103691a23ea571114b17de25c310e2b5f978551e1547d1fa465fdb6bb72d17ba3adffffffff0210530000000000001976a914f57766c540e7e165092e739e115383bd04d2c21888acc0d8a700000000001976a9140d5bcbeeb459af40f97fcb4a98e9d1ed13e904c888ac00000000" // ktlint-disable max-line-length + val dataConnected0 = "0100000001f09160ffa383d3aaf4cd8f8943b4d9050132448a4ba718d0462238456fd60535010000006a47304402200af78214fe9a60a8615080f3aef89b0a6129bbb5ca70a7c843fb4dd87a2d6b44022056bec0e4279377f82deed94e28607c75a4cf029c01c00506fb5226a9a3d5a164012103cc0bd3cb81e08fd09ee2a6f407dbe4020f12579f8197818d189b6c41318564ecffffffff02865d2d00000000001976a914e1cd0e852b0ea6a9b7d9c58881a4b3ff3842506d88aca8dca700000000001976a914f57766c540e7e165092e739e115383bd04d2c21888ac00000000" // ktlint-disable max-line-length + val dataConnected1 = "02000000026e413a0400d210f391582b050fbbfea66472e705d6a9c6dbcbb25038bd06428b010000006a47304402205e703cd097bb50aedb0013f4ed60cb7a4fb928414480d56121e93e5ee943f7890220711a681aa134a5385fe3692411a2f6ae447715f171e38f3e083e2090899e55b8012102bf7c36100b0d394e79a1704b8bf9e030a62e139a293f5da891671c56d555f732feffffff79ac9f85e66f787269d85dcafb4b7a132d04d8476f15432523adf3f3bbf851e1010000006a47304402205fb26b67d38ea82667cad0b7e8c5b6b8dc5244446326c2aed8b26c769eb9f46202206f88386025cf9cb30bdfdca20b9881db0751e19bbc9ec047fc674ced83e4656f012102bf7c36100b0d394e79a1704b8bf9e030a62e139a293f5da891671c56d555f732feffffff02304e0000000000001976a914f57766c540e7e165092e739e115383bd04d2c21888ac905c2309010000001976a9140d5bcbeeb459af40f97fcb4a98e9d1ed13e904c888ac50df0a00" // ktlint-disable max-line-length + val dataConnected2 = "01000000019858b2a28ca42a04e315cbede431104bef6060b5a7c6d526461f76e7a8f9ae2c010000006a47304402204629c835770e35d836fea256bf70addc680445e75161eee1662deea2ac3081910220230d9d573fedf568dd40badcec74890d54de88b9d3f7710572138425441fab55012103691a23ea571114b17de25c310e2b5f978551e1547d1fa465fdb6bb72d17ba3adffffffff0205030000000000001976a914f57766c540e7e165092e739e115383bd04d2c21888ac08520000000000001976a9140d5bcbeeb459af40f97fcb4a98e9d1ed13e904c888ac00000000" // ktlint-disable max-line-length val depositTx = Transaction(networkParams, Utils.HEX.decode(depositData)) val depositConnectedTx0 = Transaction(networkParams, Utils.HEX.decode(dataConnected0)) depositTx.inputs[0].connect(depositConnectedTx0.outputs[0]) @@ -130,7 +130,7 @@ class CrowdNodeTxFilterTest { assertTrue("AcceptTerms tx doesn't match", filter.matches(acceptTermsRequestTx)) assertEquals("yeZGeMwoGgqseGVnxDbrce4sSpzFsbQf7k", filter.fromAddresses.first().toBase58()) - var notAcceptTermsData = "01000000033f90cbc2d751c77358b3ff37efd72936b389a17b9ec72bdec4678394814cfe2d000000006a473044022050d2f3b6f097f1973b29bb5a0e98f307f6fc338bb8d29e4a7eb257eebd147ccd022055f88aa06cf90aec97991db9c351fd622fa60fe2cb6bbe6df2ecfef03ca047fa012102d336120a91d7d3497056715f6078e36c56e84c41038cf630260ef3245f6ba39effffffff94cae0fa480e004218a66ea7eae8c0a1a39dbd8ebba966004ddfdcac1e11f089000000006b483045022100ed1fbe54b90c8d69e616b79ba5e03e192bdee6b26f66d40d9da14ae7c7e64a9c022062c54fb1635937a38f3b43b504777c9faf357734cad6f53130870f7e980a3be60121037c4c4205eceb06bbf1e4894e52ecddcf700e1a699e2a4cbee9fd7ed748fb7a59ffffffff3e2611f35c7a2fefadce6b115ce8e14b31b627667af9c04909c0ddcceb8294a3000000006a473044022036bed2e8600ed1a715618ca398553254c14fcea824b77ed784cee5f5b23b84df022041c4821e6e639169ddc891e4d6b4e146e5f4684e5687daf5fcce2fd1f73392230121037c4c4205eceb06bbf1e4894e52ecddcf700e1a699e2a4cbee9fd7ed748fb7a59ffffffff0260182300000000001976a9140205411ec940f9139ea72e3a999d21fceff671e688ac4dc27200000000001976a91425b2b9126bf32e6115a813d019e72b7b9106211b88ac00000000" + var notAcceptTermsData = "01000000033f90cbc2d751c77358b3ff37efd72936b389a17b9ec72bdec4678394814cfe2d000000006a473044022050d2f3b6f097f1973b29bb5a0e98f307f6fc338bb8d29e4a7eb257eebd147ccd022055f88aa06cf90aec97991db9c351fd622fa60fe2cb6bbe6df2ecfef03ca047fa012102d336120a91d7d3497056715f6078e36c56e84c41038cf630260ef3245f6ba39effffffff94cae0fa480e004218a66ea7eae8c0a1a39dbd8ebba966004ddfdcac1e11f089000000006b483045022100ed1fbe54b90c8d69e616b79ba5e03e192bdee6b26f66d40d9da14ae7c7e64a9c022062c54fb1635937a38f3b43b504777c9faf357734cad6f53130870f7e980a3be60121037c4c4205eceb06bbf1e4894e52ecddcf700e1a699e2a4cbee9fd7ed748fb7a59ffffffff3e2611f35c7a2fefadce6b115ce8e14b31b627667af9c04909c0ddcceb8294a3000000006a473044022036bed2e8600ed1a715618ca398553254c14fcea824b77ed784cee5f5b23b84df022041c4821e6e639169ddc891e4d6b4e146e5f4684e5687daf5fcce2fd1f73392230121037c4c4205eceb06bbf1e4894e52ecddcf700e1a699e2a4cbee9fd7ed748fb7a59ffffffff0260182300000000001976a9140205411ec940f9139ea72e3a999d21fceff671e688ac4dc27200000000001976a91425b2b9126bf32e6115a813d019e72b7b9106211b88ac00000000" // ktlint-disable max-line-length var notAcceptTermsTx = Transaction(networkParams, Utils.HEX.decode(notAcceptTermsData)) assertEquals( notAcceptTermsTx.txId.toString(), @@ -138,7 +138,7 @@ class CrowdNodeTxFilterTest { ) assertFalse("Tx matches but should not", filter.matches(notAcceptTermsTx)) - notAcceptTermsData = "02000000024b86656e0590d048c666970225930d5806f746646eea0982be81fb354114e60d010000006a4730440220318c122e24d780b6123f001eb7fb006eda71a17067f25c96f067261da2fab4290220351d6a75c278d780550f0a5494082c749ed7737b4905c4383a206154ab4b7f94012102bf7c36100b0d394e79a1704b8bf9e030a62e139a293f5da891671c56d555f732feffffff476e14bb4fa20abc1fd23ef0ad17c2b65a6cf8959f51cfc412656a1a773c9249000000006a47304402201ebad0b1f3a2df05e9368d94a91970334283a5812537e34302260e8b6e124e180220141defc2b70fbd45ac4bb968ce9d51dc219d33709d2cc9fae2c73d61afe9f654012102bf7c36100b0d394e79a1704b8bf9e030a62e139a293f5da891671c56d555f732feffffff02244e0000000000001976a9140a5d65dba28a8a9b50b2f0d50da31f24990856fb88ace3b180290a0000001976a9140d5bcbeeb459af40f97fcb4a98e9d1ed13e904c888aca2dc0a00" + notAcceptTermsData = "02000000024b86656e0590d048c666970225930d5806f746646eea0982be81fb354114e60d010000006a4730440220318c122e24d780b6123f001eb7fb006eda71a17067f25c96f067261da2fab4290220351d6a75c278d780550f0a5494082c749ed7737b4905c4383a206154ab4b7f94012102bf7c36100b0d394e79a1704b8bf9e030a62e139a293f5da891671c56d555f732feffffff476e14bb4fa20abc1fd23ef0ad17c2b65a6cf8959f51cfc412656a1a773c9249000000006a47304402201ebad0b1f3a2df05e9368d94a91970334283a5812537e34302260e8b6e124e180220141defc2b70fbd45ac4bb968ce9d51dc219d33709d2cc9fae2c73d61afe9f654012102bf7c36100b0d394e79a1704b8bf9e030a62e139a293f5da891671c56d555f732feffffff02244e0000000000001976a9140a5d65dba28a8a9b50b2f0d50da31f24990856fb88ace3b180290a0000001976a9140d5bcbeeb459af40f97fcb4a98e9d1ed13e904c888aca2dc0a00" // ktlint-disable max-line-length notAcceptTermsTx = Transaction(networkParams, Utils.HEX.decode(notAcceptTermsData)) assertFalse("Tx matches but should not", filter.matches(notAcceptTermsTx)) } @@ -154,11 +154,11 @@ class CrowdNodeTxFilterTest { ) assertFalse("Tx matches but should not", filter.matches(receivedTx)) - val topUpData = "0100000001fcb93a5a93588ece9b4b9b8ece83a7afa4e9d2ffd5da0b76ed30c8dff07498ba000000006a47304402203c0226cb59e0b512cea751cf0d44a52a1bb07c9628b26a58221d27665a48eccf0220107831d8e72ef41822bfaac8f5082a18e575f178fa581cc310c9591aefea8dd90121028b14bfe13b4e77af8d2b7e1da61b034bec0e36f9fd4ddea2c02537027ddec68dffffffff02bd850100000000001976a9148b6743bde3b5b5778220891e8572d2475c1c9e0d88aca0bb0d00000000001976a9144a37287587b5c58c704ccdee322ab43521d3ecd288ac00000000" + val topUpData = "0100000001fcb93a5a93588ece9b4b9b8ece83a7afa4e9d2ffd5da0b76ed30c8dff07498ba000000006a47304402203c0226cb59e0b512cea751cf0d44a52a1bb07c9628b26a58221d27665a48eccf0220107831d8e72ef41822bfaac8f5082a18e575f178fa581cc310c9591aefea8dd90121028b14bfe13b4e77af8d2b7e1da61b034bec0e36f9fd4ddea2c02537027ddec68dffffffff02bd850100000000001976a9148b6743bde3b5b5778220891e8572d2475c1c9e0d88aca0bb0d00000000001976a9144a37287587b5c58c704ccdee322ab43521d3ecd288ac00000000" // ktlint-disable max-line-length val topUpTx = Transaction(networkParams, Utils.HEX.decode(topUpData)) - val connectedData = "0100000001e0ab1c7b601baebdccf03bc55787ad3957d8d13ba9a0a5e4d39c161302194aad000000006a473044022074266bfc5df38745604753ee0a48fa9ed7729c305b97a8816fa1c286da53bf0c02204ae1b458a3a956b8ad1a20c8c4f6c48dfe4ef05169afa77106a1816d07fb5c030121027c974ed291479646948719c1889aee27bc4e29af2a6c7236a92555d2b3348b97ffffffff0240420f00000000001976a91428fd6a3abc9633389c146b44f59243ac1ec3caac88ac629cd223000000001976a9140817e5a5adce5731e83f318fb725bd0e339effef88ac00000000" + val connectedData = "0100000001e0ab1c7b601baebdccf03bc55787ad3957d8d13ba9a0a5e4d39c161302194aad000000006a473044022074266bfc5df38745604753ee0a48fa9ed7729c305b97a8816fa1c286da53bf0c02204ae1b458a3a956b8ad1a20c8c4f6c48dfe4ef05169afa77106a1816d07fb5c030121027c974ed291479646948719c1889aee27bc4e29af2a6c7236a92555d2b3348b97ffffffff0240420f00000000001976a91428fd6a3abc9633389c146b44f59243ac1ec3caac88ac629cd223000000001976a9140817e5a5adce5731e83f318fb725bd0e339effef88ac00000000" // ktlint-disable max-line-length val connectedTx = Transaction(networkParams, Utils.HEX.decode(connectedData)) - val spentByData = "0100000001728d9b2f7080e4d404a0d5f1d6f1a1e5e8c08441934d65ceb70c8a0082f006da010000006a473044022054401d60d62e97d7f4ab5e1c826d520dde0282d8519806ff9ad42642dc12930002200e9d850b926191662d6faf3c9d01489d037710d3f43d1f01063dabfa31b4beec012102c9ec4d5cd8547b6811c0ecb9f18a6970443fa103ff30846ec369b5bf06a252a3ffffffff02204e0200000000001976a9140d5bcbeeb459af40f97fcb4a98e9d1ed13e904c888ac9d6c0b00000000001976a9144a37287587b5c58c704ccdee322ab43521d3ecd288ac00000000" + val spentByData = "0100000001728d9b2f7080e4d404a0d5f1d6f1a1e5e8c08441934d65ceb70c8a0082f006da010000006a473044022054401d60d62e97d7f4ab5e1c826d520dde0282d8519806ff9ad42642dc12930002200e9d850b926191662d6faf3c9d01489d037710d3f43d1f01063dabfa31b4beec012102c9ec4d5cd8547b6811c0ecb9f18a6970443fa103ff30846ec369b5bf06a252a3ffffffff02204e0200000000001976a9140d5bcbeeb459af40f97fcb4a98e9d1ed13e904c888ac9d6c0b00000000001976a9144a37287587b5c58c704ccdee322ab43521d3ecd288ac00000000" // ktlint-disable max-line-length val spentByTx = Transaction(networkParams, Utils.HEX.decode(spentByData)) topUpTx.inputs[0].connect(connectedTx.outputs[0]) topUpTx.outputs[1].markAsSpent(spentByTx.inputs[0]) @@ -168,9 +168,9 @@ class CrowdNodeTxFilterTest { @Test fun pleaseAcceptTermsTxFilter_correctMatch() { - val txData = "02000000042607763cf6eceb2478060ead38fbb3151b7676b6a243e78b58c420a4ad99cb05010000006a47304402201f95f3a194bd51c521adcd46173d3d5c9bd2dd148004dd1da72e686fd6d946e4022020e34d85cd817aff0663b133915ca2eda5ecd5d5a93fba33f2e9644f1d1513a3012102bf7c36100b0d394e79a1704b8bf9e030a62e139a293f5da891671c56d555f732feffffffe27ecbb210e98a5d2dba6e3bfa0732b8f6371155c3f8bd0420027d2eb3d24a7d010000006b483045022100c7d5c710ebdf8a2526389347823c3de83b3da498eeac5d1e9001e2e86f4cd0d002200e91ee98abc4f5fb5a78e8e80ed6fd17697a706e7118f87e545d8fdad65a845b012102bf7c36100b0d394e79a1704b8bf9e030a62e139a293f5da891671c56d555f732feffffff70a65da4b8d4438058c2e8f36811577cdb244d33c7973644386259135e3635a3010000006b483045022100d1c279574bdb0a4c72b6a11247f2945746b50f3a847c9c6925f0badfa8f5827a0220059884f1e9099fcfbb4966cced355e764ddf18bc60a3e03a3804c0c9b20618a4012102bf7c36100b0d394e79a1704b8bf9e030a62e139a293f5da891671c56d555f732feffffff4605e08cc9758029e89705c41872f063854684b5abf2020e56aca53f161b3fea000000006b483045022100f5afc8c1e722b25532b0a3561f0c37cf80bcd288a40fa0ced53d9a137f06dbc8022067c8ad28484b4a504f74cc7ad754ab4b87f0fbb46a4725e915b625eb000be8fd012102bf7c36100b0d394e79a1704b8bf9e030a62e139a293f5da891671c56d555f732feffffff02224e0000000000001976a914b889fb3449a36530c85d9689c4773c5cd1ba223388ac51844c8c060000001976a9140d5bcbeeb459af40f97fcb4a98e9d1ed13e904c888acb1f80a00" + val txData = "02000000042607763cf6eceb2478060ead38fbb3151b7676b6a243e78b58c420a4ad99cb05010000006a47304402201f95f3a194bd51c521adcd46173d3d5c9bd2dd148004dd1da72e686fd6d946e4022020e34d85cd817aff0663b133915ca2eda5ecd5d5a93fba33f2e9644f1d1513a3012102bf7c36100b0d394e79a1704b8bf9e030a62e139a293f5da891671c56d555f732feffffffe27ecbb210e98a5d2dba6e3bfa0732b8f6371155c3f8bd0420027d2eb3d24a7d010000006b483045022100c7d5c710ebdf8a2526389347823c3de83b3da498eeac5d1e9001e2e86f4cd0d002200e91ee98abc4f5fb5a78e8e80ed6fd17697a706e7118f87e545d8fdad65a845b012102bf7c36100b0d394e79a1704b8bf9e030a62e139a293f5da891671c56d555f732feffffff70a65da4b8d4438058c2e8f36811577cdb244d33c7973644386259135e3635a3010000006b483045022100d1c279574bdb0a4c72b6a11247f2945746b50f3a847c9c6925f0badfa8f5827a0220059884f1e9099fcfbb4966cced355e764ddf18bc60a3e03a3804c0c9b20618a4012102bf7c36100b0d394e79a1704b8bf9e030a62e139a293f5da891671c56d555f732feffffff4605e08cc9758029e89705c41872f063854684b5abf2020e56aca53f161b3fea000000006b483045022100f5afc8c1e722b25532b0a3561f0c37cf80bcd288a40fa0ced53d9a137f06dbc8022067c8ad28484b4a504f74cc7ad754ab4b87f0fbb46a4725e915b625eb000be8fd012102bf7c36100b0d394e79a1704b8bf9e030a62e139a293f5da891671c56d555f732feffffff02224e0000000000001976a914b889fb3449a36530c85d9689c4773c5cd1ba223388ac51844c8c060000001976a9140d5bcbeeb459af40f97fcb4a98e9d1ed13e904c888acb1f80a00" // ktlint-disable max-line-length val tx = Transaction(networkParams, Utils.HEX.decode(txData)) - val connectedTxData = "0100000001fc44931460fcb2a3b366f4b967fb4bde573667c6bcee2eaae198e3c8ed1faff5000000006b483045022100832d93353b7651d8bcf38d9d450de4234e9dc3bd243199ab06fa775cc9096c9502200f7d574aaa4b52ac254aeaf372efa7833f245acefb4e9ae2b81a1faeffcd9016012103f5ca44dde27d2a4219ad6e66617ef2bfbeb11021e761e835021e781505650915ffffffff02204e0200000000001976a9140d5bcbeeb459af40f97fcb4a98e9d1ed13e904c888ac9d6c0b00000000001976a914b889fb3449a36530c85d9689c4773c5cd1ba223388ac00000000" + val connectedTxData = "0100000001fc44931460fcb2a3b366f4b967fb4bde573667c6bcee2eaae198e3c8ed1faff5000000006b483045022100832d93353b7651d8bcf38d9d450de4234e9dc3bd243199ab06fa775cc9096c9502200f7d574aaa4b52ac254aeaf372efa7833f245acefb4e9ae2b81a1faeffcd9016012103f5ca44dde27d2a4219ad6e66617ef2bfbeb11021e761e835021e781505650915ffffffff02204e0200000000001976a9140d5bcbeeb459af40f97fcb4a98e9d1ed13e904c888ac9d6c0b00000000001976a914b889fb3449a36530c85d9689c4773c5cd1ba223388ac00000000" // ktlint-disable max-line-length val connectedTx = Transaction(networkParams, Utils.HEX.decode(connectedTxData)) tx.inputs[3].connect(connectedTx.outputs[0]) @@ -196,15 +196,21 @@ class CrowdNodeTxFilterTest { on { isPubKeyHashMine(any(), any()) } doReturn true } - val possibleAcceptFilter = PossibleAcceptTermsResponse(bagMock, Address.fromBase58(networkParams, "yVQr2XQ6eWduZmyQgPfQiBA3uwvPaRWpxo")) - var txData = "0200000002c67bbdfacfb02f7729ec60c47b85e20f898871b3b96c5f344180b08e189f9250010000006a47304402200d74f07333ad9bb5fa813ddc9e0082b6cdd8893e7e9ed6fcbf596c9ed238dede02206f5a875334990d29ae8ae3c15f2256f5f236d9ae9e2e10135afc36bfc624b466012102bf7c36100b0d394e79a1704b8bf9e030a62e139a293f5da891671c56d555f732feffffff4749fa6a4c09d2f6091a7cb191c6d2533986e0517991cc68af5ae42ca66e7bf9000000006a47304402207009df8e1e8ad59f44e6dc84c6e99677454d42491907d8d993bc1d1bc3da2ef8022050c09ea66986d61ec813263b71c3dc7df327b24c2c3b9c528b719326cce298e8012102bf7c36100b0d394e79a1704b8bf9e030a62e139a293f5da891671c56d555f732feffffff02224e0000000000001976a91463be8f527ae6e7c9ce4148bdfda835074062db2288aca025d9be170000001976a9140d5bcbeeb459af40f97fcb4a98e9d1ed13e904c888acab7c0b00" + val possibleAcceptFilter = PossibleAcceptTermsResponse( + bagMock, + Address.fromBase58(networkParams, "yVQr2XQ6eWduZmyQgPfQiBA3uwvPaRWpxo") + ) + var txData = "0200000002c67bbdfacfb02f7729ec60c47b85e20f898871b3b96c5f344180b08e189f9250010000006a47304402200d74f07333ad9bb5fa813ddc9e0082b6cdd8893e7e9ed6fcbf596c9ed238dede02206f5a875334990d29ae8ae3c15f2256f5f236d9ae9e2e10135afc36bfc624b466012102bf7c36100b0d394e79a1704b8bf9e030a62e139a293f5da891671c56d555f732feffffff4749fa6a4c09d2f6091a7cb191c6d2533986e0517991cc68af5ae42ca66e7bf9000000006a47304402207009df8e1e8ad59f44e6dc84c6e99677454d42491907d8d993bc1d1bc3da2ef8022050c09ea66986d61ec813263b71c3dc7df327b24c2c3b9c528b719326cce298e8012102bf7c36100b0d394e79a1704b8bf9e030a62e139a293f5da891671c56d555f732feffffff02224e0000000000001976a91463be8f527ae6e7c9ce4148bdfda835074062db2288aca025d9be170000001976a9140d5bcbeeb459af40f97fcb4a98e9d1ed13e904c888acab7c0b00" // ktlint-disable max-line-length val acceptTx = Transaction(networkParams, Utils.HEX.decode(txData)) assertTrue("Transaction doesn't match", possibleAcceptFilter.matches(acceptTx)) assertFalse("Tx matches but should not", possibleAcceptFilter.matches(acceptTermsRequestTx)) - txData = "0200000001b7407f1686f90c705dee1266c60e4174812f3f771b4eb09522591b7e7e284c38010000006a4730440220081ba8f1eaaee49d8e4e7dabac4e222e91cf2125d99ab2f1d6922362f1827e2b0220547d9b0c6d1938172ebbb1dc337f8bfe8425c8cd3238f055f6c48201847b4300012102bf7c36100b0d394e79a1704b8bf9e030a62e139a293f5da891671c56d555f732feffffff02244e0000000000001976a91463be8f527ae6e7c9ce4148bdfda835074062db2288acbe1e7ce8190000001976a9140d5bcbeeb459af40f97fcb4a98e9d1ed13e904c888acb17d0b00" + txData = "0200000001b7407f1686f90c705dee1266c60e4174812f3f771b4eb09522591b7e7e284c38010000006a4730440220081ba8f1eaaee49d8e4e7dabac4e222e91cf2125d99ab2f1d6922362f1827e2b0220547d9b0c6d1938172ebbb1dc337f8bfe8425c8cd3238f055f6c48201847b4300012102bf7c36100b0d394e79a1704b8bf9e030a62e139a293f5da891671c56d555f732feffffff02244e0000000000001976a91463be8f527ae6e7c9ce4148bdfda835074062db2288acbe1e7ce8190000001976a9140d5bcbeeb459af40f97fcb4a98e9d1ed13e904c888acb17d0b00" // ktlint-disable max-line-length val welcomeTx = Transaction(networkParams, Utils.HEX.decode(txData)) - val possibleWelcomeFilter = PossibleWelcomeResponse(bagMock, Address.fromBase58(networkParams, "yVQr2XQ6eWduZmyQgPfQiBA3uwvPaRWpxo")) + val possibleWelcomeFilter = PossibleWelcomeResponse( + bagMock, + Address.fromBase58(networkParams, "yVQr2XQ6eWduZmyQgPfQiBA3uwvPaRWpxo") + ) assertTrue("Transaction doesn't match", possibleWelcomeFilter.matches(welcomeTx)) assertFalse("Tx matches but should not", possibleWelcomeFilter.matches(signUpRequestTx)) @@ -218,9 +224,9 @@ class CrowdNodeTxFilterTest { @Test fun crowdNodeAPIConfirmationForwarded_correctMatch() { - val txData = "01000000016fdb75611fd8892d8d19707f0d0958da5b930c635750f2c8c5bf5a48458a8ffd000000006b483045022100ff77055377b33afb8fc2f622fda59816394a567c50e98569fe8ab68d797948b802204b05504b3f837e80f4d0d8c3c6d98107e770572a8eaae66ddd14a660fe9674f101210275ab1f1c864e594c5e075ac45fbd01a45285701e429b842f8d0a1c872cf3a7baffffffff0149d00000000000001976a9140d5bcbeeb459af40f97fcb4a98e9d1ed13e904c888ac00000000" + val txData = "01000000016fdb75611fd8892d8d19707f0d0958da5b930c635750f2c8c5bf5a48458a8ffd000000006b483045022100ff77055377b33afb8fc2f622fda59816394a567c50e98569fe8ab68d797948b802204b05504b3f837e80f4d0d8c3c6d98107e770572a8eaae66ddd14a660fe9674f101210275ab1f1c864e594c5e075ac45fbd01a45285701e429b842f8d0a1c872cf3a7baffffffff0149d00000000000001976a9140d5bcbeeb459af40f97fcb4a98e9d1ed13e904c888ac00000000" // ktlint-disable max-line-length val forwardedTx = Transaction(networkParams, Utils.HEX.decode(txData)) - val connected = "01000000017a4c4461b44d14a3866bc89482bdd35c800961879d65fc4e0542a377232f124b000000006a47304402206beee5884010d44501dee9094d9922af0c22b63f3afc1f3aa5c43bfcf59f38070220676e5bbeb712cbbbadbde6959cdee3367d1578bd06204178630cb1ba729073a20121025e25aa5f744bdd42f386a11efc584fa25afdaa299477038d2b86ac655287305effffffff0231d40000000000001976a914dcd48b7080eafd7b4db3bac7ea8480ccc3b1093388ac7d54a750000000001976a9149701a96ac4f8bb1a627be9928ccbc717ccce485788ac00000000" + val connected = "01000000017a4c4461b44d14a3866bc89482bdd35c800961879d65fc4e0542a377232f124b000000006a47304402206beee5884010d44501dee9094d9922af0c22b63f3afc1f3aa5c43bfcf59f38070220676e5bbeb712cbbbadbde6959cdee3367d1578bd06204178630cb1ba729073a20121025e25aa5f744bdd42f386a11efc584fa25afdaa299477038d2b86ac655287305effffffff0231d40000000000001976a914dcd48b7080eafd7b4db3bac7ea8480ccc3b1093388ac7d54a750000000001976a9149701a96ac4f8bb1a627be9928ccbc717ccce485788ac00000000" // ktlint-disable max-line-length val connectedTx = Transaction(networkParams, Utils.HEX.decode(connected)) forwardedTx.inputs[0].connect(connectedTx.outputs[0]) @@ -228,4 +234,4 @@ class CrowdNodeTxFilterTest { assertTrue("Transaction doesn't match", filter.matches(forwardedTx)) assertFalse("Tx matches but should not", filter.matches(signUpRequestTx)) } -} \ No newline at end of file +} From bc4b98860af165c3fb148bafe0bc8a9d41352b81 Mon Sep 17 00:00:00 2001 From: HashEngineering Date: Thu, 2 Nov 2023 12:22:04 -0700 Subject: [PATCH 08/28] chore: add x86_64-linux to Gemfile.lock (#1223) --- Gemfile.lock | 1 + 1 file changed, 1 insertion(+) diff --git a/Gemfile.lock b/Gemfile.lock index eebca3a04d..05106894ee 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -214,6 +214,7 @@ GEM PLATFORMS arm64-darwin-21 arm64-darwin-22 + x86_64-linux DEPENDENCIES fastlane (~> 2.216.0) From 8d858f7980cacfd79fd49724febc7f4141076f7a Mon Sep 17 00:00:00 2001 From: HashEngineering Date: Tue, 7 Nov 2023 20:55:44 -0800 Subject: [PATCH 09/28] fix: correct grammar on How CrowdNode staking works dialog (#1226) --- .../crowdnode/src/main/res/values/strings-crowdnode.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/integrations/crowdnode/src/main/res/values/strings-crowdnode.xml b/integrations/crowdnode/src/main/res/values/strings-crowdnode.xml index 8d42a96c1b..d4464a1098 100644 --- a/integrations/crowdnode/src/main/res/values/strings-crowdnode.xml +++ b/integrations/crowdnode/src/main/res/values/strings-crowdnode.xml @@ -94,7 +94,7 @@ Deposit %s to start earning First deposit should be more than %s How CrowdNode staking works - The Dash Network is driven by a number of Masternodes which is an essential part of facilitating payments. + The Dash Network is driven by a number of Masternodes which are an essential part of facilitating payments. A Masternode needs 1000 Dash as collateral and each Masternode is currently rewarded approximately %s%% per year. Current APY is %s%% You start earning if your CrowdNode account has more than %s @@ -107,7 +107,7 @@ Receiving rewards You will receive fractional payments automatically and they will by default be reinvested, however, it is also easy to set up automatic withdrawals to receive recurring payouts. First Minimum Deposit - As most people do not have exactly 1000 Dash at hand, CrowdNode has made a service where they by pooling deposits from members can achieve the benefits of owning a Masternode. + As most people do not have exactly 1000 Dash at hand, CrowdNode has made a service where, by pooling deposits from members, they can achieve the benefits of owning a Masternode. Joining the pool CrowdNode benefits Connected Dash address From abf09e4f72789aa35cd84f0d59b796903156e67e Mon Sep 17 00:00:00 2001 From: Andrei Ashikhmin Date: Fri, 10 Nov 2023 02:56:27 +0700 Subject: [PATCH 10/28] fix: update explore menu subtitle (#1225) --- wallet/res/values/strings-extra.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/wallet/res/values/strings-extra.xml b/wallet/res/values/strings-extra.xml index 1b76bd8f30..ebb8eca799 100644 --- a/wallet/res/values/strings-extra.xml +++ b/wallet/res/values/strings-extra.xml @@ -270,7 +270,7 @@ Contact Support Report an issue Explore - Shop with DASH at over 155,000 merchants + Find merchants that accept DASH Address Book Import Private Key From 78d39a74ce0227a699cea37b395cd2e93b8943b9 Mon Sep 17 00:00:00 2001 From: HashEngineering Date: Wed, 15 Nov 2023 07:55:18 -0800 Subject: [PATCH 11/28] chore: update translations (crowdnode, battery optimization, etc) (#1227) --- common/src/main/res/values-ar/strings.xml | 11 +++++++++- common/src/main/res/values-cs/strings.xml | 2 +- common/src/main/res/values-de/strings.xml | 1 + common/src/main/res/values-el/strings.xml | 1 + common/src/main/res/values-es/strings.xml | 1 + common/src/main/res/values-fa/strings.xml | 1 + common/src/main/res/values-fil/strings.xml | 1 + common/src/main/res/values-fr/strings.xml | 1 + common/src/main/res/values-id/strings.xml | 2 +- common/src/main/res/values-it/strings.xml | 1 + common/src/main/res/values-ja/strings.xml | 1 + common/src/main/res/values-ko/strings.xml | 1 + common/src/main/res/values-nl/strings.xml | 1 + common/src/main/res/values-pl/strings.xml | 1 + common/src/main/res/values-pt/strings.xml | 1 + common/src/main/res/values-ru/strings.xml | 1 + common/src/main/res/values-sk/strings.xml | 1 + common/src/main/res/values-th/strings.xml | 1 + common/src/main/res/values-tr/strings.xml | 1 + common/src/main/res/values-uk/strings.xml | 1 + common/src/main/res/values-zh-rTW/strings.xml | 1 + common/src/main/res/values-zh/strings.xml | 1 + .../res/values-ar/strings-explore-dash.xml | 10 +++++++++ .../res/values-el/strings-explore-dash.xml | 10 +++++++++ .../res/values-es/strings-explore-dash.xml | 2 +- .../res/values-fil/strings-explore-dash.xml | 10 +++++++++ .../res/values-it/strings-explore-dash.xml | 2 +- .../res/values-sk/strings-explore-dash.xml | 10 +++++++++ .../res/values-th/strings-explore-dash.xml | 9 ++++++++ .../res/values-tr/strings-explore-dash.xml | 10 +++++++++ .../res/values-uk/strings-explore-dash.xml | 10 +++++++++ .../res/values-zh/strings-explore-dash.xml | 10 +++++++++ .../src/main/res/values-ar/strings.xml | 13 +++-------- .../src/main/res/values-de/strings.xml | 14 ++++-------- .../src/main/res/values-el/strings.xml | 19 ++++++++-------- .../src/main/res/values-es/strings.xml | 19 ++++++++-------- .../src/main/res/values-fa/strings.xml | 13 +++-------- .../src/main/res/values-fil/strings.xml | 15 +++++++------ .../src/main/res/values-fr/strings.xml | 19 ++++++++-------- .../src/main/res/values-id/strings.xml | 11 +--------- .../src/main/res/values-it/strings.xml | 22 ++++++++++--------- .../src/main/res/values-ja/strings.xml | 21 +++++++++--------- .../src/main/res/values-ko/strings.xml | 19 ++++++++-------- .../src/main/res/values-nl/strings.xml | 17 +++++++------- .../src/main/res/values-pl/strings.xml | 19 ++++++++-------- .../src/main/res/values-pt/strings.xml | 15 +++++++------ .../src/main/res/values-ru/strings.xml | 13 ++++++----- .../src/main/res/values-sk/strings.xml | 17 +++++++------- .../src/main/res/values-th/strings.xml | 14 ++++-------- .../src/main/res/values-tr/strings.xml | 13 +++-------- .../src/main/res/values-uk/strings.xml | 13 ++++++----- .../src/main/res/values-zh-rTW/strings.xml | 15 +++++++------ .../src/main/res/values-zh/strings.xml | 15 +++++++------ .../main/res/values-ar/strings-crowdnode.xml | 2 -- .../main/res/values-de/strings-crowdnode.xml | 2 -- .../main/res/values-el/strings-crowdnode.xml | 2 +- .../main/res/values-es/strings-crowdnode.xml | 4 ++-- .../main/res/values-fa/strings-crowdnode.xml | 2 -- .../main/res/values-fr/strings-crowdnode.xml | 4 ++-- .../main/res/values-id/strings-crowdnode.xml | 2 -- .../main/res/values-it/strings-crowdnode.xml | 4 ++-- .../main/res/values-ja/strings-crowdnode.xml | 4 ++-- .../main/res/values-ko/strings-crowdnode.xml | 4 ++-- .../main/res/values-nl/strings-crowdnode.xml | 4 ++-- .../main/res/values-pl/strings-crowdnode.xml | 4 ++-- .../main/res/values-pt/strings-crowdnode.xml | 3 +-- .../main/res/values-ru/strings-crowdnode.xml | 2 +- .../main/res/values-sk/strings-crowdnode.xml | 4 ++-- .../main/res/values-th/strings-crowdnode.xml | 2 -- .../main/res/values-tr/strings-crowdnode.xml | 2 -- .../main/res/values-zh/strings-crowdnode.xml | 2 +- market/market-description-bg.txt | 2 +- wallet/res/values-ar/strings-extra.xml | 3 +-- wallet/res/values-ar/strings.xml | 7 +++--- wallet/res/values-bg/strings.xml | 3 ++- wallet/res/values-cs/strings-extra.xml | 2 -- wallet/res/values-cs/strings.xml | 3 ++- wallet/res/values-de/strings-extra.xml | 2 -- wallet/res/values-de/strings.xml | 6 ++--- wallet/res/values-el/strings-extra.xml | 3 ++- wallet/res/values-el/strings.xml | 15 ++++++++++--- wallet/res/values-es/strings-extra.xml | 2 +- wallet/res/values-es/strings.xml | 14 +++++++++--- wallet/res/values-fa/strings-extra.xml | 2 -- wallet/res/values-fa/strings.xml | 6 ++--- wallet/res/values-fil/strings-extra.xml | 3 ++- wallet/res/values-fil/strings.xml | 15 ++++++++++--- wallet/res/values-fr/strings-extra.xml | 2 +- wallet/res/values-fr/strings.xml | 14 +++++++++--- wallet/res/values-id/strings-extra.xml | 2 -- wallet/res/values-id/strings.xml | 3 ++- wallet/res/values-it/strings-extra.xml | 2 +- wallet/res/values-it/strings.xml | 16 ++++++++++---- wallet/res/values-ja/strings-extra.xml | 2 +- wallet/res/values-ja/strings.xml | 14 +++++++++--- wallet/res/values-ko/strings-extra.xml | 2 +- wallet/res/values-ko/strings.xml | 14 +++++++++--- wallet/res/values-nl/strings-extra.xml | 2 +- wallet/res/values-nl/strings.xml | 14 +++++++++--- wallet/res/values-pl/strings-extra.xml | 2 +- wallet/res/values-pl/strings.xml | 14 +++++++++--- wallet/res/values-pt/strings-extra.xml | 2 -- wallet/res/values-pt/strings.xml | 6 ++--- wallet/res/values-ru/strings-extra.xml | 2 +- wallet/res/values-ru/strings.xml | 14 +++++++++--- wallet/res/values-sk/strings-extra.xml | 3 ++- wallet/res/values-sk/strings.xml | 15 ++++++++++--- wallet/res/values-th/strings-extra.xml | 2 -- wallet/res/values-th/strings.xml | 7 +++--- wallet/res/values-tr/strings-extra.xml | 3 +-- wallet/res/values-tr/strings.xml | 7 +++--- wallet/res/values-uk/strings-extra.xml | 3 +-- wallet/res/values-uk/strings.xml | 15 ++++++++++--- wallet/res/values-vi/strings.xml | 3 ++- wallet/res/values-zh-rTW/strings-extra.xml | 2 +- wallet/res/values-zh-rTW/strings.xml | 14 +++++++++--- wallet/res/values-zh/strings-extra.xml | 3 +-- wallet/res/values-zh/strings.xml | 15 ++++++++++--- 118 files changed, 490 insertions(+), 323 deletions(-) diff --git a/common/src/main/res/values-ar/strings.xml b/common/src/main/res/values-ar/strings.xml index 1b972d59ab..89ac54fe86 100644 --- a/common/src/main/res/values-ar/strings.xml +++ b/common/src/main/res/values-ar/strings.xml @@ -58,6 +58,7 @@ شارك العنوان تحقق حدد المبلغ + إنشاء حساب بحث @@ -75,12 +76,19 @@ اختر العملة المبلغ صغير جدا بحيث لا يمكن إرساله. رصيد غير كاف + يتم المزامنة لا يوجد اتصال انترنت غير متاح + يبدو أنك تقوم بإفراغ محفظة داش يرجى ملاحظة أنك لن تتمكن من سحب أموالك من CrowdNode إلى هذه المحفظة حتى تقوم بزيادة رصيدك إلى 0.0003 داش. - يتم المزامنة + + + لم يتم الإعتراف بالدفع + خطأ في الدفع + تعذر على الخادم معالجة دفعتك ، يرجى الاستفسار من التاجر + تعذر العثور على سعر الصرف. استلم مباشرةً إلى محفظة داش @@ -89,4 +97,5 @@ تم قطع الاتصال متاح غير متاح + تسجيل الدخول \ No newline at end of file diff --git a/common/src/main/res/values-cs/strings.xml b/common/src/main/res/values-cs/strings.xml index f212b2b3ad..71b257f814 100644 --- a/common/src/main/res/values-cs/strings.xml +++ b/common/src/main/res/values-cs/strings.xml @@ -68,4 +68,4 @@ Odpojeno k dispozici Není dostupný - \ No newline at end of file + \ No newline at end of file diff --git a/common/src/main/res/values-de/strings.xml b/common/src/main/res/values-de/strings.xml index 3bbbe33a37..59b512fe7d 100644 --- a/common/src/main/res/values-de/strings.xml +++ b/common/src/main/res/values-de/strings.xml @@ -97,4 +97,5 @@ Verbindung getrennt verfügbar Nicht verfügbar + Einloggen \ No newline at end of file diff --git a/common/src/main/res/values-el/strings.xml b/common/src/main/res/values-el/strings.xml index 078962ec77..24f440c49e 100644 --- a/common/src/main/res/values-el/strings.xml +++ b/common/src/main/res/values-el/strings.xml @@ -97,4 +97,5 @@ Αποσυνδεδεμένο διαθέσιμο Μη διαθέσιμο + Είσοδος \ No newline at end of file diff --git a/common/src/main/res/values-es/strings.xml b/common/src/main/res/values-es/strings.xml index e521cc80b6..aae7a5ca86 100644 --- a/common/src/main/res/values-es/strings.xml +++ b/common/src/main/res/values-es/strings.xml @@ -97,4 +97,5 @@ Desconectado disponible No disponible + Iniciar sesión \ No newline at end of file diff --git a/common/src/main/res/values-fa/strings.xml b/common/src/main/res/values-fa/strings.xml index fe9741cc0c..b88ea4f46e 100644 --- a/common/src/main/res/values-fa/strings.xml +++ b/common/src/main/res/values-fa/strings.xml @@ -97,4 +97,5 @@ عدم اتصال به شبکه موجود در دسترس نیست + ورود \ No newline at end of file diff --git a/common/src/main/res/values-fil/strings.xml b/common/src/main/res/values-fil/strings.xml index c2c8072278..5644356894 100644 --- a/common/src/main/res/values-fil/strings.xml +++ b/common/src/main/res/values-fil/strings.xml @@ -97,4 +97,5 @@ Idiskonekta magagamit Hindi available + Mag log in \ No newline at end of file diff --git a/common/src/main/res/values-fr/strings.xml b/common/src/main/res/values-fr/strings.xml index 4485413ddd..4da664ba06 100644 --- a/common/src/main/res/values-fr/strings.xml +++ b/common/src/main/res/values-fr/strings.xml @@ -97,4 +97,5 @@ Déconnecté disponible Non disponible + Se connecter \ No newline at end of file diff --git a/common/src/main/res/values-id/strings.xml b/common/src/main/res/values-id/strings.xml index a554598adc..9840a23b59 100644 --- a/common/src/main/res/values-id/strings.xml +++ b/common/src/main/res/values-id/strings.xml @@ -96,4 +96,4 @@ Terputus tersedia Tak tersedia - \ No newline at end of file + \ No newline at end of file diff --git a/common/src/main/res/values-it/strings.xml b/common/src/main/res/values-it/strings.xml index fc7ee35a44..a067f375e2 100644 --- a/common/src/main/res/values-it/strings.xml +++ b/common/src/main/res/values-it/strings.xml @@ -97,4 +97,5 @@ Disconnesso disponibile Non disponibile + Accedi \ No newline at end of file diff --git a/common/src/main/res/values-ja/strings.xml b/common/src/main/res/values-ja/strings.xml index 042413a98b..d5f76befb7 100644 --- a/common/src/main/res/values-ja/strings.xml +++ b/common/src/main/res/values-ja/strings.xml @@ -97,4 +97,5 @@ 切断されました ご利用可能 ご利用できません + ログイン \ No newline at end of file diff --git a/common/src/main/res/values-ko/strings.xml b/common/src/main/res/values-ko/strings.xml index 5c01e7ccfb..a5e0fb64e4 100644 --- a/common/src/main/res/values-ko/strings.xml +++ b/common/src/main/res/values-ko/strings.xml @@ -97,4 +97,5 @@ 연결 해제 가능 이용할 수 없음 + 로그인 \ No newline at end of file diff --git a/common/src/main/res/values-nl/strings.xml b/common/src/main/res/values-nl/strings.xml index 02c252a093..5b0097ad88 100644 --- a/common/src/main/res/values-nl/strings.xml +++ b/common/src/main/res/values-nl/strings.xml @@ -97,4 +97,5 @@ Verbinding verbroken beschikbaar Niet beschikbaar + Log in \ No newline at end of file diff --git a/common/src/main/res/values-pl/strings.xml b/common/src/main/res/values-pl/strings.xml index b5dd13a184..cad7c56b10 100644 --- a/common/src/main/res/values-pl/strings.xml +++ b/common/src/main/res/values-pl/strings.xml @@ -97,4 +97,5 @@ Rozłączony dostępne Niedostępne + Zaloguj się \ No newline at end of file diff --git a/common/src/main/res/values-pt/strings.xml b/common/src/main/res/values-pt/strings.xml index 50161b1794..404ea23c70 100644 --- a/common/src/main/res/values-pt/strings.xml +++ b/common/src/main/res/values-pt/strings.xml @@ -97,4 +97,5 @@ Desconectado disponível Não disponível + Entrar \ No newline at end of file diff --git a/common/src/main/res/values-ru/strings.xml b/common/src/main/res/values-ru/strings.xml index 254fc4d80f..3c7d71c0d4 100644 --- a/common/src/main/res/values-ru/strings.xml +++ b/common/src/main/res/values-ru/strings.xml @@ -97,4 +97,5 @@ Подключение отсутствует доступно Недоступно + Войти \ No newline at end of file diff --git a/common/src/main/res/values-sk/strings.xml b/common/src/main/res/values-sk/strings.xml index c6e4379adc..a67ac8b67a 100644 --- a/common/src/main/res/values-sk/strings.xml +++ b/common/src/main/res/values-sk/strings.xml @@ -97,4 +97,5 @@ Odpojený dostupné Nie je k dispozícií + Prihlásiť sa \ No newline at end of file diff --git a/common/src/main/res/values-th/strings.xml b/common/src/main/res/values-th/strings.xml index 14735de8d0..fc4b097c62 100644 --- a/common/src/main/res/values-th/strings.xml +++ b/common/src/main/res/values-th/strings.xml @@ -97,4 +97,5 @@ ตัดการเชื่อมต่อ ที่สามารถใช้ได้ ไม่สามารถใช้ได้ + เข้าสู่ระบบ \ No newline at end of file diff --git a/common/src/main/res/values-tr/strings.xml b/common/src/main/res/values-tr/strings.xml index f855f8ddcc..13cebb4810 100644 --- a/common/src/main/res/values-tr/strings.xml +++ b/common/src/main/res/values-tr/strings.xml @@ -97,4 +97,5 @@ Bağlantı kesildi mevcut Mevcut değil + Giriş Yap \ No newline at end of file diff --git a/common/src/main/res/values-uk/strings.xml b/common/src/main/res/values-uk/strings.xml index b193d6e09a..9b7c63ed57 100644 --- a/common/src/main/res/values-uk/strings.xml +++ b/common/src/main/res/values-uk/strings.xml @@ -97,4 +97,5 @@ Disconnected доступно Недоступний + Увійти \ No newline at end of file diff --git a/common/src/main/res/values-zh-rTW/strings.xml b/common/src/main/res/values-zh-rTW/strings.xml index 474f530e7f..526d9b0b8a 100644 --- a/common/src/main/res/values-zh-rTW/strings.xml +++ b/common/src/main/res/values-zh-rTW/strings.xml @@ -97,4 +97,5 @@ 斷開連接 可用的 無法使用 + 登入 \ No newline at end of file diff --git a/common/src/main/res/values-zh/strings.xml b/common/src/main/res/values-zh/strings.xml index fc3d633715..70f0080e01 100644 --- a/common/src/main/res/values-zh/strings.xml +++ b/common/src/main/res/values-zh/strings.xml @@ -97,4 +97,5 @@ 连接断开 可用 不可用 + 登录 \ No newline at end of file diff --git a/features/exploredash/src/main/res/values-ar/strings-explore-dash.xml b/features/exploredash/src/main/res/values-ar/strings-explore-dash.xml index ff4d157319..ff5fae7bce 100644 --- a/features/exploredash/src/main/res/values-ar/strings-explore-dash.xml +++ b/features/exploredash/src/main/res/values-ar/strings-explore-dash.xml @@ -18,6 +18,7 @@ اكتشف داش اكتشف + ابحث عن التجار الذين يقبلون الداش، وأماكن شرائها وكيفية كسب الدخل منها. أين تنفق؟ ابحث عن التجار الذين يقبلون داش كوسيلة للدفع. أجهزة الصراف الآلي @@ -83,6 +84,7 @@ طريقة الدفع داش كرت هدية + بطاقات الهدايا القطر الموقع الموقع الحالي @@ -167,5 +169,13 @@ شراء بطاقة هدية مع داش. استرد بطاقة الهدايا الخاصة بك عبر الإنترنت في غضون ثوانٍ أو عند الصراف. + + المزامنة جارية... قد لا تكون النتائج كاملة. + المزامنة جارية... حدث خطأ. + تم إلغاء المزامنة... سيتم إجراء محاولة أخرى لاحقًا. + خطأ في الإنشاء: قاعدة البيانات غير موجودة! + تمت مقاطعة المزامنة... الشبكة غير متصلة بالإنترنت أو لا يمكن الوصول إليها + + \ No newline at end of file diff --git a/features/exploredash/src/main/res/values-el/strings-explore-dash.xml b/features/exploredash/src/main/res/values-el/strings-explore-dash.xml index 5642c6d1ac..0b3a939ee2 100644 --- a/features/exploredash/src/main/res/values-el/strings-explore-dash.xml +++ b/features/exploredash/src/main/res/values-el/strings-explore-dash.xml @@ -18,6 +18,7 @@ Εξερευνήστε το Dash Εξερευνήστε + Βρείτε τους εμπόρους που δέχονται Dash, πού να αγοράσετε και πώς να βγάλετε εισόδημα με αυτό. Πού να ξοδέψετε; Βρείτε εμπόρους που δέχονται το Dash ως πληρωμή ATM @@ -75,6 +76,7 @@ Μέθοδος πληρωμής Dash Δωροκάρτα + Δωροκάρτες Ακτίνα Τοποθεσία Τρέχουσα τοποθεσία @@ -151,5 +153,13 @@ Αγοράστε μια δωροκάρτα με Dash. Εξαργυρώστε τη δωροκάρτα σας online μέσα σε λίγα δευτερόλεπτα ή σε ταμείο. + + Συγχρονισμός σε εξέλιξη... Τα αποτελέσματα μπορεί να μην είναι πλήρη. + Συγχρονισμός σε εξέλιξη... Υπήρξε ένα σφάλμα. + Ο συγχρονισμός ακυρώθηκε... Θα γίνει νέα προσπάθεια αργότερα. + Σφάλμα Build: Η βάση δεδομένων δεν βρέθηκε! + Ο συγχρονισμός διακόπηκε... Το δίκτυο είναι εκτός σύνδεσης ή μη προσβάσιμο + + \ No newline at end of file diff --git a/features/exploredash/src/main/res/values-es/strings-explore-dash.xml b/features/exploredash/src/main/res/values-es/strings-explore-dash.xml index 1863454ec2..268b513f88 100644 --- a/features/exploredash/src/main/res/values-es/strings-explore-dash.xml +++ b/features/exploredash/src/main/res/values-es/strings-explore-dash.xml @@ -24,7 +24,7 @@ Cajeros automáticos Encuentra dónde comprar o vender DASH y otras criptomonedas por dinero en efectivo. Haz fácilmente staking de tus Dash y obtén ingresos pasivos con unos pocos clics. - Rendimiento anual aproximado actual = %s%% + APY actual = %s%% Buscar resultados diff --git a/features/exploredash/src/main/res/values-fil/strings-explore-dash.xml b/features/exploredash/src/main/res/values-fil/strings-explore-dash.xml index fb49862cbc..a731100dee 100644 --- a/features/exploredash/src/main/res/values-fil/strings-explore-dash.xml +++ b/features/exploredash/src/main/res/values-fil/strings-explore-dash.xml @@ -18,6 +18,7 @@ Galugarin ang Dash Galugarin + Maghanap ng mga merchant na tumatanggap ng Dash, kung saan ito bibilhin at kung paano kumita gamit ito Saan Gagastos? Maghanap ng mga merchant na tumatanggap ng Dash bilang bayad. Mga ATM @@ -75,6 +76,7 @@ Paraan ng Pagbayad Dash Gift card + Mga gift card Radius Lokasyon Kasalukuyang Lokasyon @@ -151,5 +153,13 @@ Bumili ng gift card gamit ang iyong Dash I-redeem ang iyong gift card online sa loob ng ilang segundo o sa cashier. + + Kasalukuyang isinasagawa ang pag-sync... Maaaring hindi kumpleto ang resulta. + Kasalukuyang isinasagawa ang pag-sync... Nagkaroon ng error. + Kinansela ang pag-sync... Isa pang pagtatangka ang gagawin sa ibang pagkakataon. + Error sa Pagbuo: Hindi nakita ang database! + Naantala ang pag-sync... Ang network ay offline o hindi maabot + + \ No newline at end of file diff --git a/features/exploredash/src/main/res/values-it/strings-explore-dash.xml b/features/exploredash/src/main/res/values-it/strings-explore-dash.xml index 36b85f5d9d..ebac26208c 100644 --- a/features/exploredash/src/main/res/values-it/strings-explore-dash.xml +++ b/features/exploredash/src/main/res/values-it/strings-explore-dash.xml @@ -50,7 +50,7 @@ %d commercianti %d - %d + %d commercianti diff --git a/features/exploredash/src/main/res/values-sk/strings-explore-dash.xml b/features/exploredash/src/main/res/values-sk/strings-explore-dash.xml index 911ceb7315..4db90d2f30 100644 --- a/features/exploredash/src/main/res/values-sk/strings-explore-dash.xml +++ b/features/exploredash/src/main/res/values-sk/strings-explore-dash.xml @@ -18,6 +18,7 @@ Objavte Dash Objaviť + Nájdite obchodníkov, ktorí akceptujú Dash, kde ho kúpiť a ako s ním zarobiť. Kde minúť? Nájsť obchodníkov, ktorí akceptujú Dash ako platbu. Bankomaty @@ -79,6 +80,7 @@ Spôsob platby Dash Darčeková karta + Darčekové karty Okruh Miesto Súčasná poloha @@ -159,5 +161,13 @@ Kúpte si darčekovú kartu s Dashom. Uplatnite svoju darčekovú kartu online v priebehu niekoľkých sekúnd alebo pri pokladni. + + Prebieha synchronizácia… Výsledky nemusia byť úplné. + Prebieha synchronizácia… Vyskytla sa chyba. + Synchronizácia bola zrušená… Ďalší pokus sa uskutoční neskôr. + Chyba zostavy: Databáza sa nenašla! + Synchronizácia bola prerušená… Sieť je offline alebo nedostupná + + \ No newline at end of file diff --git a/features/exploredash/src/main/res/values-th/strings-explore-dash.xml b/features/exploredash/src/main/res/values-th/strings-explore-dash.xml index 4cc8340ef7..e3c7217b3d 100644 --- a/features/exploredash/src/main/res/values-th/strings-explore-dash.xml +++ b/features/exploredash/src/main/res/values-th/strings-explore-dash.xml @@ -74,6 +74,7 @@ วิธีการชำระเงิน Dash บัตรของขวัญ + บัตรของขวัญ รัศมี ตำแหน่ง ตำแหน่งปัจจุบัน @@ -148,5 +149,13 @@ ซื้อบัตรของขวัญด้วย Dash แลกบัตรของขวัญของคุณทางออนไลน์ภายในไม่กี่วินาทีหรือที่แคชเชียร์ + + กำลังซิงค์... ผลลัพธ์อาจไม่สมบูรณ์ + กำลังซิงค์... มีข้อผิดพลาด + การซิงค์ถูกยกเลิก... จะพยายามดำเนินการอีกครั้งในภายหลัง + สร้างข้อผิดพลาด: ไม่พบฐานข้อมูล! + การซิงค์ถูกขัดจังหวะ... เครือข่ายออฟไลน์หรือไม่สามารถเข้าถึงได้ + + \ No newline at end of file diff --git a/features/exploredash/src/main/res/values-tr/strings-explore-dash.xml b/features/exploredash/src/main/res/values-tr/strings-explore-dash.xml index 5c9543d0b7..f4ece8a649 100644 --- a/features/exploredash/src/main/res/values-tr/strings-explore-dash.xml +++ b/features/exploredash/src/main/res/values-tr/strings-explore-dash.xml @@ -18,6 +18,7 @@ Dash\'i Keşfet Keşfet + Dash\'i kabul eden satıcıları bulun. Nereden satın alınır ve onunla nasıl gelir elde edilir. Nerede Harcamalı? Dash\'i ödeme olarak kabul eden satıcıları bulun. ATMler @@ -75,6 +76,7 @@ Ödeme Şekli Dash Hediye Kartı + Hediye Kartları Yarıçap Konum Mevcut Konum @@ -151,5 +153,13 @@ Dash ile bir hediye kartı satın alın. Hediye kartınızı saniyeler içinde çevrimiçi olarak veya kasiyerde kullanın. + + Senkronizasyon devam ediyor... Sonuçlar tamamlanmayabilir. + Senkronizasyon devam ediyor… Bir hata oluştu. + Senkronizasyon iptal edildi… Daha sonra başka bir deneme yapılacak. + Derleme Hatası: Veritabanı bulunamadı! + Senkronizasyon kesintiye uğradı… Ağ çevrimdışı veya ulaşılamıyor + + \ No newline at end of file diff --git a/features/exploredash/src/main/res/values-uk/strings-explore-dash.xml b/features/exploredash/src/main/res/values-uk/strings-explore-dash.xml index e402a62a84..8e0766da78 100644 --- a/features/exploredash/src/main/res/values-uk/strings-explore-dash.xml +++ b/features/exploredash/src/main/res/values-uk/strings-explore-dash.xml @@ -18,6 +18,7 @@ Дослідити Dash Дослідити + Знайти торговців, які приймають Dash, де їх купити та як з ними заробити. Де витратити? Знайдіть місця де приймають DASH в якості оплати. Банкомати @@ -79,6 +80,7 @@ Спосіб оплати Dash Подарункова карта + Подарункові карти Радіус Місцезнаходження Поточне місцезнаходження @@ -159,5 +161,13 @@ Купити подарункову картку за допомогою Dash Отримайте свою подарункову картку онлайн за лічені секунди або в касі. + + Триває синхронізація... Результати можуть бути неповними. + Триває синхронізація... Сталася помилка. + Синхронізацію скасовано... Ще одна спроба буде зроблена пізніше. + Помилка збірки: база даних не знайдена! + Синхронізацію перервано... Мережа офлайн або недоступна + + \ No newline at end of file diff --git a/features/exploredash/src/main/res/values-zh/strings-explore-dash.xml b/features/exploredash/src/main/res/values-zh/strings-explore-dash.xml index 3b852d295e..fca0a40e4f 100644 --- a/features/exploredash/src/main/res/values-zh/strings-explore-dash.xml +++ b/features/exploredash/src/main/res/values-zh/strings-explore-dash.xml @@ -18,6 +18,7 @@ 探索Dash 探索 + 寻找接受 Dash 的商家, 可以在哪里购买 Dash 以及如何通过它获得收益. 在哪里消费? 查找接受Dash支付方式的商户. 自动提款机 @@ -73,6 +74,7 @@ 支付方式 Dash 礼品卡 + 礼品卡 半径 位置 当前位置 @@ -147,5 +149,13 @@ 使用Dash购买礼品卡. 在线上或收银台几秒内兑换您的礼品卡. + + 同步中… 结果可能不完整. + 同步中... 出现一个错误. + 同步已取消... 稍后将进行另一次尝试. + 构建错误: 未找到数据库! + 同步中断... 网络未连接或无法访问 + + \ No newline at end of file diff --git a/integrations/coinbase/src/main/res/values-ar/strings.xml b/integrations/coinbase/src/main/res/values-ar/strings.xml index a59e951086..52fa89d67e 100644 --- a/integrations/coinbase/src/main/res/values-ar/strings.xml +++ b/integrations/coinbase/src/main/res/values-ar/strings.xml @@ -37,7 +37,6 @@ رسوم كوين بيس المجموع معاينة الطلب - ستتلقى %s داش على محفظة داش الخاصة بك على هذا الجهاز. يرجى ملاحظة أن الأمر قد يستغرق ما يصل إلى 2-3 دقائق لإكمال التحويل. المبلغ في داش إلغاء تأكيد (%s) @@ -57,11 +56,8 @@ الشراء لم ينجح فشل التحويل فشل تحويل - تم إيداع الداش بنجاح في حساب كوين بيس الخاص بك. ولكن كانت هناك مشكلة في نقلها إلى محفظة داش على هذا الجهاز. - هناك خطأ ما قد يستغرق الأمر ما يصل إلى 2-3 دقائق حتى يتم نقل داش إلى محفظة داش على هذا الجهاز. قد يستغرق تحويل %s من حساب كوين بيس إلى داش في محفظة داش على هذا الجهاز ما يصل إلى 5 دقائق .. - \"قد يستغرق تحويل داش من محفظة داش على هذا الجهاز إلى %s حساب كوين بيس الخاص بك ما يصل إلى 5 دقائق. اتصل بدعم كوين بيس رسوم مشتريات العملات المشفرة بالإضافة إلى رسوم كوين بيس المعروضة ، نقوم بتضمين فروق الأسعار في السعر. أسواق العملات المشفرة متقلبة ، وهذا يسمح لنا بتثبيت السعر مؤقتًا لتنفيذ التجارة. @@ -77,9 +73,7 @@ إلى حساب كوين بيس الخاص بك من محفظة داش على هذا الجهاز ستستلم - لم نعثر على أي أصول في حساب كوين بيس الخاص بك. - أنت لا تملك أي عملة مشفرة. قم بشراء بعض العملات المشفرة لتبدأ .. - شراء عملات على كوين بيس + شراء عملات على كوين بيس لقد تجاوزت حد التفويض على كوين بيس. خصم الأموال من حسابك قم بتغيير هذا الحد @@ -98,11 +92,9 @@ تحقق أدخل كود كوين بيس 2FA توضح هذه الخطوة الإضافية أنك تحاول حقًا إجراء معاملة. - تحتاج مساعدة ؟ كود كوين بيس 2FA قد يكون الرمز من الرسائل القصيرة على هاتفك. إذا لم يكن كذلك ، أدخل الرمز من تطبيق المصادقة. الرمز غير صحيح. يرجى مراجعة وحاول مرة أخرى! - كان هذا الرمز غير صالح. حاول مرة اخرى تحقق ليس لديك رصيد كافي نقل @@ -112,8 +104,9 @@ تم التحويل بنجاح قد يستغرق نقل داش من كوين بيس إلى محفظة داش لهذا الجهاز ما يصل إلى 10 دقائق. قد يستغرق الأمر ما يصل إلى 10 دقائق لنقل داش من محفظة داش على هذا الجهاز إلى حساب كوين بيس الخاص بك. - حدثت مشكلة في نقلها إلى محفظة داش على هذا الجهاز. كمية العملات في المحفظة صغيرة جدًا بحيث لا يمكن تحويلها. ستستلم %s%s%s سوف تستلم %s + تم انتهاء صلاحية جلسة اتصالك بكوين اكس + الرجاء تسجيل الدخول الى حساب كوين اكس \ No newline at end of file diff --git a/integrations/coinbase/src/main/res/values-de/strings.xml b/integrations/coinbase/src/main/res/values-de/strings.xml index ef87ed8b3f..e212f3c83b 100644 --- a/integrations/coinbase/src/main/res/values-de/strings.xml +++ b/integrations/coinbase/src/main/res/values-de/strings.xml @@ -37,7 +37,6 @@ Coinbase Gebühr Gesamt Vorschau bestellen - Du erhältst %s Dash auf die Dash Wallet dieses Gerätes. Bitte beachte, dass es 2-3 Minuten dauern kann bis der Transfer vollzogen ist. Betrag in Dash Abbrechen Bestätigen (%ss) @@ -57,11 +56,8 @@ Kauf fehlgeschlagen Übertragung fehlgeschlagen Konvertierung fehlgeschlagen - Die Dash wurden deinem Coinbase Konto erfolgreich gutgeschrieben. Es gab aber ein Problem beim Übertragen auf die Dash Wallet dieses Gerätes. - Es ist etwas schiefgelaufen Es kann 2-3 Minuten dauern, bis die Dash auf die Dash Wallet dieses Gerätes transferiert werden. Es kann bis zu 5 Minuten dauern, um die %s von deinem Coinbase Konto in Dash auf die Dash Wallet deines Gerätes umzuwandeln. - Es kann bis zu 5 Minuten dauern, um die Dash von der Dash Wallet dieses Gerätes in %s auf deinem Coinbase Konto umzuwandeln. Coinbase Support kontaktieren Gebühren bei Kryptowährungskäufen Zusätzlich zu der angezeigten Coinbase-Gebühr schließen wir einen Spread in den Preis ein. Kryptowährungsmärkte sind volatil, und dies ermöglicht es uns, einen Preis für die Handelsausführung vorübergehend festzulegen. @@ -77,9 +73,7 @@ auf dein Coinbase Konto Von der Dash Wallet auf diesem Gerät Du erhälst - Es befindet sich kein Guthaben auf deinem Coinbase Konto. - Du besitzt keine Kryptowährung. Kaufe welche um loszulegen.. - Kauf Kryptowährung auf Coinbase + Kauf Kryptowährung auf Coinbase Du hast den Berechtigungsrahmen von Coinbase überschritten. Geld von deinem Konto abbuchen Ändere dieses Limit @@ -98,11 +92,9 @@ Verifizieren Coinbase 2FA Code eingeben Dieser zusätzliche Schritt beweist, dass es wirklich du bist der die Transaktion ausführt. - Benötigst du Hilfe? Coinbase 2FA Code Es könnte ein Code aus einer SMS auf deinem Telefon sein. Wenn nicht, trage den Code von deiner Authentifizierung-App ein. Diese Code ist falsch. Bitte kontrollieren und erneut versuchen! - Diese Code ist ungültig. Bitte erneut versuchen Verifizieren Du hast nicht genug Guthaben Transferieren @@ -112,8 +104,10 @@ Transfer erfolgreich Es kann bis zu 10 Minuten dauern bis Dash von Coinbase auf die Dash Wallet dieses Gerätes übertragen werden. Es kann bis zu 10 Minuten dauern bis Dash von der Dash Wallet dieses Gerätes  auf Coinbase übertragen werden. - Es gab ein Problem bei dem Übertragen auf die Dash Wallet dieses Gerätes. Die Menge an Coins in der Wallet ist zu klein um sie zu versenden. Du erhältst %s %s %s Du wirst %s erhalten. + Deine Coinbase Session ist abgelaufen. + Bitte logge dich in deinen Coinbase Account ein. + Möchtest du eine Einzahlung mit dem hinterlegten Bankkonto durchführen? \ No newline at end of file diff --git a/integrations/coinbase/src/main/res/values-el/strings.xml b/integrations/coinbase/src/main/res/values-el/strings.xml index ecadf160eb..dc680f809b 100644 --- a/integrations/coinbase/src/main/res/values-el/strings.xml +++ b/integrations/coinbase/src/main/res/values-el/strings.xml @@ -37,7 +37,7 @@ Προμήθεια Coinbase Σύνολο Προεπισκόπηση παραγγελίας - Θα λάβετε %s Dash στο Dash Wallet σας σε αυτή τη συσκευή. Λάβετε υπόψη ότι μπορεί να χρειαστούν έως και 2-3 λεπτά για να ολοκληρωθεί η μεταφορά. + Θα λάβετε ~ %s Dash στο πορτοφόλι σας σε αυτή τη συσκευή. Λάβετε υπόψη ότι μπορεί να χρειαστούν έως και 2-3 λεπτά για να ολοκληρωθεί η μεταφορά. Ποσό σε Dash Ακύρωση Επιβεβαίωση(%ss) @@ -57,11 +57,10 @@ Η αγορά απέτυχε Η μεταφορά απέτυχε Η μετατροπή απέτυχε - Τα Dash κατατέθηκαν επιτυχώς στο λογαριασμό σας στην Coinbase. Υπήρξε όμως πρόβλημα στη μεταφορά τους στο Dash Wallet σε αυτή τη συσκευή. -  Κάτι πήγε στραβά + Το Dash κατατέθηκε με επιτυχία στο λογαριασμό σας στην Coinbase. Υπήρξε όμως πρόβλημα στη μεταφορά του στο Dash Wallet σε αυτή τη συσκευή. Μπορεί να χρειαστούν έως και 2-3 λεπτά για να μεταφερθούν τα Dash στο Dash Wallet σας σε αυτή τη συσκευή. Μπορεί να χρειαστούν έως και 5 λεπτά για να μετατρέψετε %s από τον λογαριασμό στην Coinbase σε Dash στο Dash Wallet σας σε αυτή τη συσκευή.. - \"Μπορεί να χρειαστούν έως και 5 λεπτά για να μετατρέψετε Dash από το Dash Wallet σε αυτή τη συσκευή σε %s στο λογαριασμό σας στην Coinbase. + Μπορεί να χρειαστούν έως και 5 λεπτά για τη μετατροπή των Dash από το Dash Wallet σε αυτή τη συσκευή σε %s στο λογαριασμό σας Coinbase. Επικοινωνία με την υποστήριξη της Coinbase Προμήθειες σε αγορές κρυπτονομισμάτων Εκτός από την εμφανιζόμενη προμήθεια της Coinbase, συμπεριλαμβάνουμε στην τιμή ένα περιθώριο. Οι αγορές κρυπτονομισμάτων είναι ευμετάβλητες και αυτό μας επιτρέπει να κλειδώσουμε προσωρινά μια τιμή για την εκτέλεση των συναλλαγών. @@ -77,9 +76,9 @@ στο λογαριασμό σας στην Coinbase Στο πορτοφόλι Dash σε αυτή τη συσκευή θα λάβετε - Δεν βρήκαμε κανένα περιουσιακό στοιχείο στο λογαριασμό σας στην Coinbase. -  Δεν έχετε στην κατοχή σας κανένα κρυπτονόμισμα. Αγοράστε μερικά κρυπτονομίσματα για να ξεκινήσετε.. - Αγοράστε κρυπτονομίσματα στην Coinbase + Δεν βρήκαμε κανένα κεφάλαιο στο λογαριασμό σας Coinbase. + Δεν έχετε στην κατοχή σας κανένα κρυπτονόμισμα. Αγοράστε μερικά κρυπτονομίσματα για να ξεκινήσετε. + Αγοράστε κρυπτονομίσματα στην Coinbase Υπερβήκατε το όριο εξουσιοδότησης στην Coinbase. Χρέωση χρημάτων από το λογαριασμό σας Αλλαγή αυτού του ορίου @@ -102,7 +101,6 @@ Κωδικός Coinbase 2FA Θα μπορούσε να είναι ο κωδικός από το SMS στο τηλέφωνό σας. Εάν όχι, εισαγάγετε τον κωδικό από την εφαρμογή ελέγχου ταυτότητας. Ο κωδικός είναι λανθασμένος. Παρακαλώ ελέγξτε και δοκιμάστε ξανά! - Αυτός ο κωδικός ήταν άκυρος. Παρακαλώ δοκιμάστε ξανά Επιβεβαίωση Δεν έχετε αρκετό υπόλοιπο Μεταφορά @@ -112,8 +110,11 @@ Επιτυχής μεταφορά Η μεταφορά Dash από την Coinbase στο πορτοφόλι Dash αυτής της συσκευής μπορεί να διαρκέσει έως και 10 λεπτά. Μπορεί να χρειαστούν έως και 10 λεπτά για να μεταφέρετε Dash από το Dash Wallet σε αυτή τη συσκευή στο λογαριασμό σας στην Coinbase. -  Υπήρξε πρόβλημα με τη μεταφορά στο Dash Wallet σε αυτή τη συσκευή. + Υπήρξε πρόβλημα με τη μεταφορά στο Dash Wallet σε αυτή τη συσκευή. Το ποσό των νομισμάτων στο πορτοφόλι είναι πολύ μικρό για μεταφορά. Θα λάβετε %s %s %s θα λάβετε %s + Η συνεδρία σας στην Coinbase έχει λήξει + Συνδεθείτε στο λογαριασμό σας στην Coinbase + Θα θέλατε να κάνετε μια κατάθεση για την πληρωμή της αγοράς σας χρησιμοποιώντας έναν συνδεδεμένο τραπεζικό λογαριασμό; \ No newline at end of file diff --git a/integrations/coinbase/src/main/res/values-es/strings.xml b/integrations/coinbase/src/main/res/values-es/strings.xml index 16172709ee..cedf3afb9e 100644 --- a/integrations/coinbase/src/main/res/values-es/strings.xml +++ b/integrations/coinbase/src/main/res/values-es/strings.xml @@ -37,7 +37,7 @@ Comisión de Coinbase Total Vista previa del pedido - Tu recibirás %s Dash en tu billetera de Dash en este dispositivo. Ten en cuenta que puede tomar hasta 2-3 minutos completar una transferencia. + Tu vas a recibir ~ %s Dash en tu billetera de Dash en este dispositivo. Ten en cuenta que completar una transferencia puede tardar entre 2 y 3 minutos. Monto en Dash Cancelar Confirmar (%ss) @@ -57,11 +57,10 @@ Compra fallida Transacción fallida Conversión fallida - Los Dash se depositaron con éxito en tu cuenta de Coinbase. Pero hubo un problema al transferirlo a la billetera de Dash en este dispositivo. - Algo salió mal + Los Dash se depositaron exitosamente en tu cuenta Coinbase. Pero hubo un problema al transferirlo a la billetera de Dash en este dispositivo. Los Dash podrían demorar entre 2-3 minutos en transferirse a tu billetera de Dash en este dispositivo. Podría tomar hasta 5 minutos convertir %s desde la cuenta de Coinbase a Dash en tu billetera de Dash en este dispositivo.. - “Podría tomar hasta 5 minutos convertir Dash de la billetera de Dash en este dispositivo a %s en tu cuenta de Coinbase. + Podría tomar hasta 5 minutos convertir Dash de la billetera de Dash en este dispositivo a %s en tu cuenta de Coinbase. Ponte en contacto con el soporte de Coinbase Comisiones en compras de criptomonedas Además de la tarifa de Coinbase mostrada, incluimos un diferencial en el precio. Los mercados de criptomonedas son volátiles y esto nos permite fijar temporalmente un precio para la ejecución de operaciones. @@ -77,9 +76,9 @@ a tu cuenta de Coinbase Desde la billetera de Dash en este dispositivo Tu recibirás - No encontramos ningún activo en tu cuenta de Coinbase. - No posees ninguna criptomoneda. Compra algunas criptomonedas para comenzar. - Comprar criptomonedas en Coinbase + No encontramos ningún activo en tu cuenta de Coinbase. + No posees ninguna cripto. Compra algunas criptomonedas para comenzar. + Comprar criptomonedas en Coinbase Excediste el límite de autorización en Coinbase. Debitar dinero de tu cuenta Cambiar este límite @@ -98,11 +97,10 @@ Verificar Ingresa el código 2FA para Coinbase Este paso adicional muestra que realmente eres tú quien intenta realizar una transacción. - Necesitas ayuda ? + Necesitas ayuda? Código 2FA para Coinbase Podría ser el código del SMS en tu teléfono. De lo contrario, ingresa el código de la aplicación de autenticación. El código es incorrecto. ¡Por favor revisa e intenta de nuevo! - Ese código no era válido. Inténtalo de nuevo Verificar No tienes saldo suficiente Transferir @@ -116,4 +114,7 @@ La cantidad de monedas en la billetera es demasiado pequeña para transferir. Tu recibirás %s %s %s Vas a recibir %s + Tu sesión de Coinbase ha expirado + Inicia sesión en tu cuenta de Coinbase + ¿Te gustaría realizar un depósito para tu compra utilizando una cuenta bancaria vinculada? \ No newline at end of file diff --git a/integrations/coinbase/src/main/res/values-fa/strings.xml b/integrations/coinbase/src/main/res/values-fa/strings.xml index 5504367b8c..5db3b1a666 100644 --- a/integrations/coinbase/src/main/res/values-fa/strings.xml +++ b/integrations/coinbase/src/main/res/values-fa/strings.xml @@ -37,7 +37,6 @@ کارمزد کوین‌بیس مجموع پیش‌نمایش سفارش - شما %sدش در دش والت‌تان روی همین دستگاه دریافت خواهید کرد. لطفا توجه داشته باشید که تکمیل عملیات انتقال این مبلغ می‌تواند بین ۲ تا ۳ دقیقه زمان ببرد. مبلغ بر حسب دش لغو تائید (%sثانیه) @@ -57,11 +56,8 @@ عملیات خرید ناموفق بود عملیات انتقال ناموفق بود عملیات تبدیل انجام نشد - دش با موفقیت به حساب کوین‌بیس شما انتقال یافت. اما انتقال دش به دش والت روی این دستگاه موفق نبود. - مشکلی روی داده است عملیات انتقال دش به این دش والت روی این دستگاه ممکن است ۲ تا ۳ دقیقه زمان ببرد. تا حداکثر ۵ دقیقه زمان لازم است تا %s از حساب کوین‌بیس به دش تبدیل شده و به دش والت در دستگاه‌تان انتقال یابد. - تا حداکثر ۵ دقیقه زمان لازم است تا %s از دش والت روی این دستگاه به حساب کوین‌بیس شما تبدیل شده و انتقال یابد. با پشتیبانی کوین‌بیس تماس بگیرید کارمزد خرید رمزارز علاوه بر کارمزد نمایش‌داده‌شده در کوین‌بیس، ما مبلغی را به اقیمت اضافه کردیم. بازار رمزارزها نوسانات زیادی دارد و با این کار می‌توانیم قیمت را به صورت موقت برای اجرای تراکنش قفل کنیم. @@ -77,9 +73,7 @@ به حساب کوین‌بیس از دش والت روی این دستگاه دریافت خواهید کرد - هیچ دارایی در حساب کوین‌بیس شما یافت نشد. - هیچ رمزارزی ندارید. برای شروع مقداری رمزارز بخرید - خرید رمزارز در کوین‌بیس + خرید رمزارز در کوین‌بیس از حد تعریف‌شده در کوین‌بیس فراتر رفتید. واریز از حساب‌تان تغییر این محدوده @@ -98,11 +92,9 @@ تائید کد 2FA کوین‌بیس را وارد کنید این گام اضافه نشان می‌دهد واقعا خودتان قصد انجام این تراکنش را دارید. - نیاز به کمک دارید؟ کد 2FA کوین‌بیس این کد ممکن است به صورت پیامک به گوشی شما بیاید. در عیر این صورت، از کد اپلیکیشن اعتبارسنجی استفاده کنید. کد وارد شده درست نیست. لطفا دوباره آن را بررسی کرده و وارد کنید! - کد نامعتبر بود. لطفا دوباره امتحان کنید تائید موجودی کافی ندارید انتقال @@ -112,8 +104,9 @@ تراکنش موفق انتقال دش از کوین‌بیس به کیف پول دش روی این دستگاه شاید تا ۱۰ دقیقه زمان ببرد. تا حداکثر ۱۰ دقیقه زمان لازم است تا دش والت شما روی این دستگاه به حساب‌تان در کوین‌بیس انتقال یابد. - انتقال دش از دش والت به این دستگاه با مشکل روبرو شد. مقدار دش در کیف‌ پول‌تان آن قدر نیست که بتوانید انتقال دهید. شما این مقدار دریافت کردید: %s%s%s شما %s دریافت خواهید کرد + حساب کوینبیس‌ شما منقضی شده است + لطفا وارد حساب کوینبیس‌تان شوید \ No newline at end of file diff --git a/integrations/coinbase/src/main/res/values-fil/strings.xml b/integrations/coinbase/src/main/res/values-fil/strings.xml index 25c63da8d4..b0f35f0a0d 100644 --- a/integrations/coinbase/src/main/res/values-fil/strings.xml +++ b/integrations/coinbase/src/main/res/values-fil/strings.xml @@ -37,7 +37,7 @@ Bayad sa Coinbase Kabuuan Preview ng Order - Makakatanggap ka ng %s Dash sa iyong Dash Wallet sa device na ito. Pakitandaan na maaaring tumagal ng hanggang 2-3 minuto upang makumpleto ang isang paglipat. + Makakatanggap ka ng ~ %s Dash sa iyong Dash Wallet sa device na ito. Pakitandaan na maaaring tumagal ng hanggang 2-3 minuto upang makumpleto ang isang paglipat. Halaga sa Dash Kanselahin Kumpirmahin ang (%ss) @@ -58,10 +58,9 @@ Nabigo ang Paglipat Nabigo ang Conversion Ang Dash ay matagumpay na nadeposito sa iyong Coinbase account. Ngunit nagkaroon ng problema sa paglilipat nito sa Dash Wallet sa device na ito. - May maling nangyari Maaaring tumagal nang hanggang 2-3 minuto bago mailipat ang Dash sa iyong Dash Wallet sa device na ito. Maaaring tumagal nang hanggang 5 minuto upang ma-convert %s mula sa Coinbase account patungo sa Dash sa iyong Dash Wallet sa device na ito.. - “Maaaring tumagal nang hanggang 5 minuto upang ma-convert ang Dash mula sa Dash Wallet sa device na ito sa %s sa iyong Coinbase account. + Maaaring tumagal nang hanggang 5 minuto upang ma-convert ang Dash mula sa Dash Wallet sa device na ito sa %s sa iyong Coinbase account. Makipag-ugnayan sa Suporta sa Coinbase Mga bayarin sa mga pagbili ng crypto Bilang karagdagan sa ipinapakitang bayad sa Coinbase, nagsasama kami ng spread sa presyo. Ang mga merkado ng Cryptocurrency ay pabagu-bago, at nagbibigay-daan ito sa amin na pansamantalang i-lock ang isang presyo para sa pagpapatupad ng kalakalan. @@ -77,9 +76,9 @@ sa iyong Coinbase account Mula sa Dash wallet sa device na ito Matatanggap mo - Wala kaming nakitang anumang asset sa iyong Coinbase account. - Wala kang anumang crypto. Bumili ng ilang crypto para makapagsimula.. - Bumili ng Crypto sa Coinbase + Wala kaming nakitang anumang asset sa iyong Coinbase account. + Wala kang anumang crypto. Bumili ng ilang crypto para makapagsimula.. + Bumili ng Crypto sa Coinbase Lumampas ka sa limitasyon ng awtorisasyon sa Coinbase. Mag-debit ng pera mula sa iyong account Baguhin ang limitasyong ito @@ -102,7 +101,6 @@ Coinbase 2FA code Maaaring ito ay ang code mula sa SMS sa iyong telepono. Kung hindi, ilagay ang code mula sa authentication app. Mali ang code. Pakisuri at subukang muli! - Di-wasto ang code na iyon. Pakisubukang muli Kumpirmahin Wala kang sapat na balanse Ipadala @@ -116,4 +114,7 @@ Ang halaga ng mga barya sa wallet ay masyadong maliit upang ilipat. Matatanggap mo %s %s %s Matatanggap mo %s + Ang iyong session sa Coinbase ay nag-expire na + Mangyaring mag-log in sa iyong Coinbase account + Gusto mo bang magdeposito para sa iyong pagbili gamit ang isang naka-link na bank account? \ No newline at end of file diff --git a/integrations/coinbase/src/main/res/values-fr/strings.xml b/integrations/coinbase/src/main/res/values-fr/strings.xml index 346983d6bc..cb746b8d93 100644 --- a/integrations/coinbase/src/main/res/values-fr/strings.xml +++ b/integrations/coinbase/src/main/res/values-fr/strings.xml @@ -37,7 +37,7 @@ Frais Coinbase Total Prévisualisation de la commande - Vous recevrez %s dashs dans votre Dash Wallet sur cet appareil. Veuillez noter qu\'un transfert peut prendre jusqu\'à 2 à 3 minutes. + Vous recevrez ~ %s dashs sur votre Dash Wallet sur cet appareil. Veuillez noter qu\'un transfert peut prendre jusqu\'à 2 à 3 minutes. Montant en dashs Annuler Confirmer (%ss) @@ -57,11 +57,10 @@ Échec de l\'achat Échec du transfert Échec de la conversion - Les dashs ont été déposés avec succès dans votre compte Coinbase. Mais il y a un problème pour les transférer vers le portefeuille Dash sur cet appareil. - Quelque chose s\'est mal passé + Les dashs ont été déposés avec succès sur votre compte Coinbase. Mais il y a eu un problème pour le transférer sur le Dash Wallet sur cet appareil. Le transfert des dashs sur votre Dash Wallet sur cet appareil peut prendre jusqu\'à 2 à 3 minutes. Jusqu\'à 5 minutes peuvent être nécessaires pour convertir %s depuis le compte Coinbase vers Dash Wallet sur cet appareil. - Jusqu\'à 5 minutes peuvent être nécessaires pour convertir des dashs de Dash Wallet sur cet appareil en %s sur votre compte Coinbase. + La conversion des dashs sur le Dash Wallet de cet appareil en %s sur votre compte Coinbase peut prendre jusqu\'à 5 minutes. Contacter le service de soutien Coinbase Frais dans les achats de crypto En plus des frais Coinbase affichés, nous incluons un écart dans le cours. Les marchés cryptomonétaires sont volatiles, et cela nous permet de verrouiller temporairement un prix pour l\'exécution de la transaction. @@ -77,9 +76,9 @@ vers votre compte Coinbase Depuis le portefeuille Dash sur cet appareil Vous recevrez - Nous n\'avons trouvé aucune valeur sur votre compte Coinbase. - Vous ne possédez pas de crypto. Achetez de la crypto pour démarrer. - Acheter de la crypto sur Coinbase + Nous n\'avons pas détecté de fonds dans votre compte Coinbase. + Vous ne possédez pas de cryptos. Veuillez acquérir un peu de cryptos pour commencer. + Acheter de la crypto sur Coinbase Vous avez dépassé la limite d\'autorisation sur Coinbase. Débit d\'argent depuis votre compte Modifier cette limite @@ -102,7 +101,6 @@ Code 2FA Coinbase Cela pourrait être le code sur SMS de votre téléphone. Si ce n\'est pas le cas, saisissez le code de l\'application d\'authentification. Ce code est incorrect. Veuillez vérifier et réessayer ! - Ce code était invalide. Veuillez réessayer Vérifier Vous n\'avez pas un solde suffisant Transférer @@ -112,8 +110,11 @@ Transfert réussi Transférer les dashs depuis Coinbase vers le portefeuille Dash de cet appareil peut prendre jusqu\'à 10 minutes. 10 minutes peuvent être nécessaires pour transférer des dashs depuis Dash Wallet sur cet appareil vers votre compte Coinbase. - Il y a eu un problème de transfert vers le portefeuille Dash sur cet appareil. + Il y a eu un problème pour le transférer sur le Dash Wallet de cet appareil. La quantité dans le portefeuille est trop petite pour être transférée. Vous recevrez %s %s %s Vous recevrez %s + Votre session Coinbase a expiré + Veuillez vous connecter à votre compte Coinbase + Souhaitez-vous faire un dépôt pour votre achat en utilisant un compte bancaire lié ? \ No newline at end of file diff --git a/integrations/coinbase/src/main/res/values-id/strings.xml b/integrations/coinbase/src/main/res/values-id/strings.xml index 7860d6c5fa..a62325df0f 100644 --- a/integrations/coinbase/src/main/res/values-id/strings.xml +++ b/integrations/coinbase/src/main/res/values-id/strings.xml @@ -37,7 +37,6 @@ Biaya Coinbase Total Pratinjau Pesanan - Anda akan menerima %s Dash di Dash Wallet Anda di perangkat ini. Harap perhatikan bahwa perlu waktu hingga 2–3 menit untuk menyelesaikan transfer. Jumlah dalam Dash Batal Konfirmasi (%ss) @@ -57,11 +56,8 @@ Pembelian gagal Transfer Gagal Konversi Gagal - Dash berhasil disetorkan ke akun Coinbase Anda. Namun ada masalah saat mentransfernya ke Dash Wallet di perangkat ini. - Ada yang salah Diperlukan waktu hingga 2–3 menit agar Dash ditransfer ke Dash Wallet Anda di perangkat ini. Diperlukan waktu hingga 5 menit untuk mengonversi %s dari akun Coinbase ke Dash di Dash Wallet Anda di perangkat ini.. - “Diperlukan waktu hingga 5 menit untuk mengonversi %s Dash dari Dash Wallet di perangkat ini ke akun Coinbase Anda. Hubungi Dukungan Coinbase Biaya dalam pembelian kripto Selain biaya Coinbase yang ditampilkan, kami menyertakan spread dalam harga. Pasar Cryptocurrency tidak stabil, dan ini memungkinkan kami untuk mengunci harga sementara untuk eksekusi perdagangan. @@ -76,9 +72,7 @@ ke akun Coinbase Anda Dari dompet Dash di perangkat ini Anda akan menerima - Kami tidak menemukan aset apa pun di akun Coinbase Anda. - Anda tidak memiliki kripto apa pun. Beli beberapa krypto untuk memulai.. - Beli kripto di Coinbase + Beli kripto di Coinbase Anda melampaui batas otorisasi di Coinbase. Debit uang dari akun Anda Ubah batas ini @@ -97,11 +91,9 @@ Memeriksa Masukkan kode Coinbase 2FA Langkah ekstra ini menunjukkan bahwa Anda benar-benar mencoba melakukan transaksi. - Butuh bantuan ? Kode 2FA Coinbase Bisa jadi kode dari SMS di ponsel Anda. Jika tidak, masukkan kode dari aplikasi otentikasi. Kode tidak benar. Silakan periksa dan coba lagi! - Kode itu tidak valid. Silakan coba lagi Memeriksa Anda tidak memiliki cukup saldo Transfer @@ -111,7 +103,6 @@ Transfer berhasil Diperlukan waktu hingga 10 menit untuk mentransfer Dash dari Coinbase ke dompet Dash perangkat ini. Diperlukan waktu hingga 10 menit untuk mentransfer Dash dari Dash Wallet di perangkat ini ke akun Coinbase Anda. - Terjadi masalah saat mentransfernya ke Dompet Dash di perangkat ini. Jumlah koin di dompet terlalu kecil untuk ditransfer. Anda akan menerima %s %s %s \ No newline at end of file diff --git a/integrations/coinbase/src/main/res/values-it/strings.xml b/integrations/coinbase/src/main/res/values-it/strings.xml index 3aff671c0d..64b2821f62 100644 --- a/integrations/coinbase/src/main/res/values-it/strings.xml +++ b/integrations/coinbase/src/main/res/values-it/strings.xml @@ -37,7 +37,7 @@ Commissione Coinbase Totale Anteprima dell\'ordine - Riceverai %s Dash sul tuo portafoglio Dash su questo dispositivo. Tieni presente che possono essere necessari fino a 2-3 minuti per completare un trasferimento. + Riceverai ~%s Dash nel tuo Dash Wallet su questo dispositivo. Tieni presente che potrebbero essere necessari fino a 2-3 minuti per completare un trasferimento. Importo in Dash Annulla Conferma (%ss) @@ -57,11 +57,10 @@ Acquisto fallito Trasferimento fallito Conversione fallita - I Dash sono stati depositati con successo sul tuo account Coinbase, ma si è verificato un problema durante il trasferimento nel Dash Wallet su questo dispositivo. - Qualcosa è andato storto + Dash depositati con successo sul tuo conto Coinbase. Ma si è verificato un problema durante il trasferimento su Dash Wallet su questo dispositivo. Il trasferimento del Dash al tuo Dash Wallet su questo dispositivo potrebbe richiedere fino a 2-3 minuti. La conversione potrebbe richiedere fino a 5 minuti%sdall\'account Coinbase a Dash nel tuo Dash Wallet su questo dispositivo.. - \"Potrebbero essere necessari fino a 5 minuti per convertire Dash da Dash Wallet su questo dispositivo a %s nel tuo account Coinbase. + Potrebbero essere necessari fino a 5 minuti per convertire Dash da Dash Wallet su questo dispositivo a %s nel tuo account Coinbase. Contatta il supporto di Coinbase Commissioni negli acquisti di criptovalute Oltre alla commissione Coinbase visualizzata, includiamo uno spread nel prezzo. I mercati delle criptovalute sono volatili e questo ci consente di bloccare temporaneamente un prezzo per l\'esecuzione degli scambi. @@ -77,9 +76,9 @@ al tuo account Coinbase Dal Dash Wallet su questo dispositivo Tu riceverai - Non abbiamo trovato alcun asset sul tuo account Coinbase. - Non possiedi alcuna criptovaluta. Acquista delle criptovalute per iniziare.. - Acquista criptovalute su Coinbase + Non abbiamo trovato alcuna risorsa sul tuo account Coinbase. + Non possiedi alcuna criptovaluta. Acquista delle criptovalute per iniziare. + Acquista criptovalute su Coinbase Hai superato il limite di autorizzazione su Coinbase. Addebita denaro dal tuo account Modifica questo limite @@ -98,11 +97,11 @@ Verifica Inserisci il codice Coinbase 2FA Questo passaggio aggiuntivo mostra che stai davvero cercando di effettuare una transazione. - Hai bisogno di aiuto? + Hai bisogno di aiuto? +  Codice Coinbase 2FA Potrebbe essere il codice tramite SMS sul tuo telefono. In caso contrario, inserisci il codice dall\'app di autenticazione. Il codice non è corretto. Si prega di verificare e riprovare! - Quel codice non era valido. Per favore riprova Verifica Non hai Saldo sufficiente Trasferimento @@ -112,8 +111,11 @@ Trasferimento riuscito Potrebbero essere necessari fino a 10 minuti per trasferire Dash da Coinbase al Dash Wallet di questo dispositivo. Potrebbero essere necessari fino a 10 minuti per trasferire Dash da Dash Wallet su questo dispositivo al tuo account Coinbase. - Si è verificato un problema durante il trasferimento al Dash Wallet su questo dispositivo. + Si è verificato un problema durante il trasferimento su Dash Wallet su questo dispositivo. La quantità di coins nel portafoglio è troppo piccola per essere trasferita. Riceverai %s %s %s Riceverai %s + Your Coinbase session has expired + Accedi al tuo account Coinbase + Desideri effettuare un deposito per il tuo acquisto utilizzando un conto bancario collegato? \ No newline at end of file diff --git a/integrations/coinbase/src/main/res/values-ja/strings.xml b/integrations/coinbase/src/main/res/values-ja/strings.xml index d49b2d3a4d..fe48b826d7 100644 --- a/integrations/coinbase/src/main/res/values-ja/strings.xml +++ b/integrations/coinbase/src/main/res/values-ja/strings.xml @@ -37,7 +37,7 @@ Coinbaseの手数料 合計 注文プレビュー - この端末のDashウォレットで%sDashを受け取ります。送金完了まで2-3分ほどかかることがあります。 + この端末のDashウォレットで~ %sDashを受け取ります。送金が完了するまでに2~3分かかる場合があります。 金額(Dash) キャンセル 確認 (%s秒) @@ -57,11 +57,10 @@ 購入に失敗しました 取引に失敗しました 変換に失敗しました - Dashはお客様のCoinbaseアカウントに正常に入金されました。しかし、この端末でDashウォレットに送金する際に問題が発生しました。 - 問題が発生しました + Dashは正常にお客様のCoinbaseアカウントに入金されました。しかし、この端末のDashウォレットへの送金には問題がありました。 この端末のDashウォレットにDashが送金されるのに、2-3分かかることがあります。 この端末では、Coinbaseのアカウント内の%sからDashウォレット内のDashに変換するのに最大5分かかることがあります。 - この端末では、Dashウォレット内のDashからCoinbaseのアカウント内の%sに変換するのに最大で5分かかることがあります。 + この端末上のDashウォレットからお客様のCoinbaseアカウントの%sにDashを変換するには、最大5分かかります。 Coinbaseサポートに問い合わせる 仮想通貨購入の手数料 表示されているCoinbaseの手数料に加え、スプレッドもその価格に含まれています。仮想通貨市場は変動が激しいため、このようにすることで取引執行のための価格を一時的に固定することができます。 @@ -77,9 +76,9 @@ Coinbaseのアカウントへ この端末のDashウォレットから 入金できます - お客様のCoinbaseアカウントに資金が見つかりませんでした。 - お客様は仮想通貨をお持ちではありません。仮想通貨を購入して始めましょう。 - Coinbaseで仮想通貨を購入する + お客様のCoinbaseアカウントに資産は見つかりませんでした。 + お客様は仮想通貨をお持ちではありません。仮想通貨を買って始めましょう。 + Coinbaseで仮想通貨を購入する Coinbaseの認証制限を超えてしまいました。 口座からお金を引き落とす この制限を変更する @@ -98,11 +97,10 @@ 検証する Coinbaseの2FAコードを入力する この追加ステップは、お客様が本当に取引を行おうとしていることを証明するために行われます。 - お困りですか? + 何かお困りですか。 Coinbaseの2FAコード これは、お使いの携帯電話のSMSに記載されたコードだと思われます。該当しない場合は、認証アプリからコードを入力してください。 コードが正しくありません。ご確認の上、再度ご入力ください。 - そのコードは無効になっています。もう一度お試しください。 検証する 残高が不足しています 送金する @@ -112,8 +110,11 @@ 送金に成功しました Coinbaseからこの端末のDashウォレットにDashを送金するのに、最長10分かかる場合があります。 この端末のDashウォレットからCoinbaseのアカウントにDashを転送するのに、最大10分かかることがあります。 - この端末のDashウォレットに送金する際に問題が発生しました。 + この端末のDashウォレットへの送金中に問題がありました。 ウォレット内の通貨の金額が不足しているため、送金できません。 %s %s %sを入金できます %sを入金します + お客様のCoinbaseのセッションは有効期限切れです + お客様のCoinbaseアカウントにログインしてください + リンクされた銀行口座を使用して購入代金を入金しますか。 \ No newline at end of file diff --git a/integrations/coinbase/src/main/res/values-ko/strings.xml b/integrations/coinbase/src/main/res/values-ko/strings.xml index d266aaf6df..8cc320b647 100644 --- a/integrations/coinbase/src/main/res/values-ko/strings.xml +++ b/integrations/coinbase/src/main/res/values-ko/strings.xml @@ -37,7 +37,7 @@ 코인베이스 수수료 총합 주문 미리보기 - 이 기기의 대시 지갑에 %s대시를 받게 됩니다. 송금이 완료되는 데에는 최대 2-3분이 소요될 수 있습니다. + 이 기기의 대시 지갑에 ~ %s대시를 받게 됩니다. 송금이 완료되는 데에는 최대 2-3분이 소요될 수 있습니다. 대시 금액 취소 확인 (%ss) @@ -57,11 +57,10 @@ 매수에 실패하였습니다 송금에 실패하였습니다 환전에 실패하였습니다 - 당신의 코인베이스 계정에 대시가 성공적으로 입금되었으나, 이 기기의 대시 지갑에 전송하는 과정에서 문제가 발생하였습니다. - 오류가 발생하였습니다 + 당신의 코인베이스 계정에 대시가 성공적으로 입금되었습니다. 그러나, 이 기기의 대시 지갑에 전송하는 과정에서 문제가 발생하였습니다. 이 기기 내 대시 지갑으로 해당 대시를 송금하는 데에는 최대 2-3분이 소요될 수 있습니다. 코인베이스 계정의 %s를 대시로 환전하여 이 기기의 대시 지갑으로 받는 데에는 최대 5분이 소요될 수 있습니다.. - 이 기기의 대시 지갑 내 대시를 귀하의 %s로 환전하여 코인베이스 계정으로 받는 데에는 최대 5분이 소요될 수 있습니다. + 이 기기의 대시 지갑으로부터 당신의 코인베이스 계정의 %s로 대시를 전환하는 데에는 최대 5분이 소요될 수 있습니다. 코인베이스 지원센터에 문의하기 암호화폐 매수 수수료 해당 가격에는 표시된 코인베이스 수수료 외에도 차액이 포함됩니다. 암호화폐 시장은 변동성이 높으며, 이로 인해 거래를 완료하기 위해서는 일시적으로 가격을 잠글 수 있습니다. @@ -77,9 +76,9 @@ 코앤 베이스 계정으로 이 기기의 대시 지갑으로부터 다음을 통해 보낸 금액을 받게 됩니다 - 귀하의 코인베이스 계정에서 어떤 자산도 찾지 못했습니다. - 보유하고 있는 암호화폐가 없습니다. 암호화폐를 구매하셔서 시작하세요... - 코인베이스에서 암호화폐를 구입하세요 + 귀하의 코인베이스 계정에서 어떤 자산도 찾지 못했습니다. + 보유하고 있는 암호화폐가 없습니다. 시작하시려면 암호화폐를 구입하세요. + 코인베이스에서 암호화폐를 구입하세요 코인베이스 인증 제한을 초과하였습니다. 당신의 계정으로부터 돈을 인출합니다 이 제한을 변경합니다 @@ -102,7 +101,6 @@ 코인베이스 2FA 코드 휴대폰 SMS로 전송된 코드일 수 있습니다. 아니라면, 인증 어플의 코드를 입력하십시오. 잘못된 코드입니다. 확인 후 다시 시도해주십시오! - 유효하지 않은 코드입니다. 다시 시도해주십시오. 인증하기 잔액이 충분하지 않습니다 송금하기 @@ -112,8 +110,11 @@ 성공적으로 송금되었습니다 코인베이스로부터 이 기기의 대시 지갑으로 전송하는 데에는 최대 10분 가량 소요될 수 있습니다. 이 기기의 대시 지갑 내 대시를 코인베이스 계정으로 전송하는 데에는 최대 10분이 소요될 수 있습니다. - 이 기기의 대시 지갑에 전송하는 데 문제가 발생하였습니다. + 이 기기의 대시 지갑에 송금하는 데 문제가 발생하였습니다. 지갑 내 코인의 양이 너무 적어 송금할 수 없습니다. %s%s%s을 받습니다. %s을 받습니다. + 코인베이스 세션이 만료되었습니다 + 코인베이스 계정에 로그인 하십시오 + 연결된 은행 계좌를 이용해 구매를 위한 입금 처리를 완료하시겠습니까? \ No newline at end of file diff --git a/integrations/coinbase/src/main/res/values-nl/strings.xml b/integrations/coinbase/src/main/res/values-nl/strings.xml index e7fc0a831a..93454c26c5 100644 --- a/integrations/coinbase/src/main/res/values-nl/strings.xml +++ b/integrations/coinbase/src/main/res/values-nl/strings.xml @@ -37,7 +37,7 @@ Coinbase vergoeding Totaal Voorbeeld bestelling - Je ontvangt %s Dash in je Dash portemonnee op dit apparaat. Houd er rekening mee dat het tot 2 à 3 minuten kan duren om een overdracht te voltooien. + Je ontvangt ~ %s Dash in je Dash portemonnee op dit apparaat. Houd er rekening mee dat het 2 à 3 minuten kan duren om een overdracht te voltooien. Bedrag in Dash Annuleer Bevestig (%ss) @@ -57,11 +57,10 @@ Aankoop mislukt Overdracht mislukt Conversie mislukt - De Dash is succesvol gestort op je Coinbase account. Maar er was een probleem bij het overzetten naar de Dash portemonnee op dit apparaat. - Er is iets verkeerd gegaan + Dash is succesvol gestort op je Coinbase account, maar er was een probleem bij het overzetten hiervan naar de Dash portemonnee op dit apparaat. Het kan tot 2 à 3 minuten duren voordat de Dash is verzonden naar je Dash portemonnee op dit apparaat. Het kan tot 5 minuten duren om %s van je Coinbase-account naar Dash in je Dash portemonnee te converteren. - “Het kan tot 5 minuten duren om Dash van je Dash portemonnee naar %s in je Coinbase-account te converteren. + Het kan tot 5 minuten duren om Dash van je Dash portemonnee naar %s op je Coinbase account te converteren. Neem contact op met Coinbase Kosten bij crypto aankopen Naast de weergegeven Coinbase vergoeding, nemen we een spread in de prijs. Cryptocurrency markten zijn volatiel en dit stelt ons in staat om tijdelijk een prijs vast te leggen voor de uitvoering van transacties. @@ -77,9 +76,9 @@ naar je Coinbase account Van Dash portemonnee op dit apparaat Je ontvangt - We hebben geen activa gevonden op je Coinbase account. - Je bezit geen cryptovaluta. Koop wat crypto om te beginnen.. - Koop crypto op Coinbase + We hebben geen activa gevonden op je Coinbase account. + Je bezit geen crypto valuta. Koop wat crypto om te beginnen.. + Koop crypto op Coinbase Je hebt de autorisatielimiet op Coinbase overschreden. Geld van uw rekening afschrijven Wijzig dit limiet @@ -102,7 +101,6 @@ Coinbase 2FA code Het kan de code van de SMS op je telefoon zijn. Zo niet, voer dan de code van de authenticatie app in. De code is onjuist. Controleer en probeer het opnieuw. - Die code was ongeldig. Probeer het opnieuw Verifiëren Je hebt niet genoeg saldo Maak over @@ -116,4 +114,7 @@ Het aantal munten in de portemonnee is te klein om over te dragen. Je ontvangt %s%s%s Je zal %s ontvangen + Je Coinbase sessie is verlopen + Log in bij je Coinbase-account + Wil je de aankoop storten via een gekoppelde bankrekening? \ No newline at end of file diff --git a/integrations/coinbase/src/main/res/values-pl/strings.xml b/integrations/coinbase/src/main/res/values-pl/strings.xml index 7f066ee5a5..eaaa24bffc 100644 --- a/integrations/coinbase/src/main/res/values-pl/strings.xml +++ b/integrations/coinbase/src/main/res/values-pl/strings.xml @@ -57,11 +57,10 @@ Zakup się nie udał Transfer nie zoastała wysłana Konwersja nie udała się - Dash został pomyślnie przelany na twoje konto Coinbase, ale wystąpił błąd podczas transferu do twojego portfela Dash na tym urządzeniu. - Coś poszło nie tak + Dash został pomyślnie przesłany na twoje konto Coinbase, ale wystąpił błąd podczas transferu do twojego portfela Dash na tym urządzeniu. Transfer Dash do porfela na tym urządzeniu może zająć do 2-3 minut. Przekonwertowanie %s środków z konta na Coinbase do portfela Dash może zająć do 5 minut. - \"Przekonwertowanie Dash z twojego portfela, na %s konto Coinbase, może zająć do 5 minut. + Przekonwertowanie Dash z twojego portfela, na %s konto Coinbase, może zająć do 5 minut. Skontaktuj się z pomocą Coibase Opłaty w kupowaniu krypto Oprócz pokazanej opłaty Coinbase, wliczamy również mały margines błędu. Rynki Kryptowalut są dosyć zmienne, więc ten margines pozwala nam na gwarancje ceny podczas transakcji. @@ -77,9 +76,9 @@ Na twoje konto Coinbase Z portfela Dash na tym urządzeniu Otrzymasz - Nie znaleźliśmy żadnych aktywów na twoim koncie Coinbase - Nie posiadasz żadnych krypto monet. Kup trochę aby rozpocząć przygodę z krypto.. - Kup Krypto na Coinbase + Nie znaleźliśmy żadnych środków na twoim koncie Coinbase + Nie posiadasz żadnych krypto-monet. Kup trochę aby rozpocząć przygodę z krypto. + Kup Krypto na Coinbase Przekroczyłeś limit autoryzacji na Coinbase. Obciąż swoje konto Zmień ten limit @@ -98,11 +97,10 @@ Zweryfikuj Wprowadź kod 2FA Ten dodatkowy krok potwierdza, że to naprawdę ty dokonujesz tej transakcji. - Potrzebujesz Pomocy? + Potrzebujesz pomocy? Kod 2FA Może to być kod z SMS-a na Twoim telefonie. Jeśli nie, wprowadź kod z aplikacji uwierzytelniającej. Kod jest nieprawidłowy. Spróbuj ponownie! - Ten kod był nieprawidłowy. Proszę spróbuj ponownie Zweryfikuj Nie masz wystarczająco środków Przelej @@ -112,8 +110,11 @@ Transfer został dokonany pomyślnie Wysłanie Dash z Coinbase do portfela Dash na tym urządzeniu może potrwać do 10 minut. Przesłanie Dash z twojego portfela na konto Coinbase może zająć do 10 minut. - Wystąpił problem podczas transferu Dash. + Wystąpił problem podczas transferu Dash do portfela na tym urządzeniu. Niewystarczająca ilość monet do przelania. Otrzymasz %s %s %s Otrzymasz %s + Twoja sesja Coinbase wygasła + Zaloguj się na konto Coinbase + Czy chcesz dokonać depozytu na twój zakup z połączonego konta bankowego? \ No newline at end of file diff --git a/integrations/coinbase/src/main/res/values-pt/strings.xml b/integrations/coinbase/src/main/res/values-pt/strings.xml index 85055b668f..1e965a6953 100644 --- a/integrations/coinbase/src/main/res/values-pt/strings.xml +++ b/integrations/coinbase/src/main/res/values-pt/strings.xml @@ -37,7 +37,7 @@ Taxa Coinbase Total Visualização do pedido - Você receberá %s Dash em sua Carteira Dash neste dispositivo. Por favor, observe que pode levar de 2 a 3 minutos para completar a transferência. + Você receberá ~ %s Dash em sua Carteira Dash neste dispositivo. Por favor, observe que pode levar de 2 a 3 minutos para completar a transferência. Quantidade em Dash Cancelar Confirmar (%ss) @@ -58,10 +58,9 @@ A transferência falhou A conversão falhou O Dash foi depositado com sucesso na sua conta Coinbase. Mas houve um problema ao transferi-lo para a Carteira Dash neste dispositivo. - Algo deu errado Pode levar de 2 a 3 minutos para que o Dash seja transferido para sua Carteira Dash neste dispositivo. Pode levar até 5 minutos para converter %s da conta Coinbase para o Dash em sua Carteira Dash neste dispositivo. - “Pode levar até 5 minutos para converter o DASH da Dash Wallet neste dispositivo para %s na sua conta Coinbase. + Pode levar até 5 minutos para converter o Dash da Carteira Dash neste dispositivo para %s na sua conta Coinbase. Contato com o Suporte Coinbase Taxas em compras de criptomoedas Além da taxa Coinbase exibida, nós incluímos um spread no preço. Os mercados de criptomoedas são voláteis e isso nos permite bloquear temporariamente um preço para a execução da negociação. @@ -77,9 +76,9 @@ para a sua conta Coinbase Da sua Carteira Dash neste dispositivo Você receberá - Nós não encontramos nenhum ativo em sua conta Coinbase. - Você não possui nenhuma criptomoeda. Compre algumas criptomoedas para começar. - Compre Crypto na Coinbase + Nós não encontramos nenhum ativo em sua conta Coinbase. + Você não possui nenhuma criptomoeda. Compre algumas criptomoedas para começar. + Compre Crypto na Coinbase Você excedeu o limite de autorização na Coinbase. Debitar dinheiro da sua conta Alterar este limite @@ -102,7 +101,6 @@ Código 2FA da Coinbase Pode ser o código do SMS no seu telefone. Caso contrário, insira o código do aplicativo de autenticação. Código incorreto. Por favor, verifique e tente novamente! - Código inválido. Por favor, tente novamente Verificar Você não tem saldo suficiente Transferir @@ -116,4 +114,7 @@ A quantidade de moedas na carteira é muito pequena para transferir. Você receberá %s %s %s Você receberá %s + Sua sessão na Coinbase expirou + Por favor, faça login em sua conta Coinbase + Você gostaria de fazer um depósito para a sua compra usando uma conta bancária vinculada? \ No newline at end of file diff --git a/integrations/coinbase/src/main/res/values-ru/strings.xml b/integrations/coinbase/src/main/res/values-ru/strings.xml index dcccd4659f..afc81168dd 100644 --- a/integrations/coinbase/src/main/res/values-ru/strings.xml +++ b/integrations/coinbase/src/main/res/values-ru/strings.xml @@ -37,7 +37,7 @@ Комиссия Coinbase Всего Просмотр заказа - Вы получите %sDash на Dash Wallet на этом устройстве. Пожалуйста, обратите внимание, что перевод займёт 2-3 минуты. + Вы получите ~ %sDash на Dash Wallet на этом устройстве. Пожалуйста, обратите внимание, что перевод займёт 2-3 минуты. Сумма в Dash Отмена Подтвердить (%s) @@ -58,7 +58,6 @@ Ошибка при переводе Ошибка при обмене Dash был успешно зачислен на ваш аккаунт Coinbase. Но при переводе его на Dash Wallet на этом устройстве произошла проблема. - Что-то пошло не так Перевод Dash на Dash Wallet на этом устройстве может занять 2-3 минуты. Обмен %s с Coinbase в Dash на Dash Wallet на этом устройстве может занять до 5 минут. Обмен Dash из Dash Wallet на этом устройстве на %s в вашем Coinbase аккаунте может занять до 5 минут. @@ -77,9 +76,9 @@ на аккаунт Coinbase С Dash Wallet на этом устройстве Вы получите - Мы не нашли никаких активов на вашем аккаунте Coinbase - У вас нет криптовалюты. Для начала приобретите немного криптовалюты. - Купить криптовалюту на Coinbase + Мы не нашли никаких активов на вашем аккаунте Coinbase + У вас нет криптовалюты. Для начала приобретите немного криптовалюты. + Купить криптовалюту на Coinbase Превышен лимит авторизаций на Coinbase. Списать деньги с аккаунта Изменить лимит @@ -102,7 +101,6 @@ 2FA код Coinbase Это может быть SMS-код. Или код из приложения для аутентификации. Неверный код. Попробуйте ещё раз! - Неправильный код. Пожалуйста, попробуйте ещё раз Подтвердить Средств на балансе недостаточно Перевести @@ -116,4 +114,7 @@ В этом кошельке слишком мало монет для перевода. Вы получите %s%s%s Вы получите %s + Время сессии Coinbase истекло + Пожалуйста, войдите в свой аккаунт Coinbase + Хотите внести депозит за покупку, используя привязанный банковский счет? \ No newline at end of file diff --git a/integrations/coinbase/src/main/res/values-sk/strings.xml b/integrations/coinbase/src/main/res/values-sk/strings.xml index 226e96ca72..3fce3b89c8 100644 --- a/integrations/coinbase/src/main/res/values-sk/strings.xml +++ b/integrations/coinbase/src/main/res/values-sk/strings.xml @@ -37,7 +37,7 @@ Poplatok Coinbase Celkovo Ukážka objednávky - Na tomto zariadení dostanete %s Dash do svojej peňaženky Dash. Upozorňujeme, že dokončenie prevodu môže trvať 2 až 3 minúty. + Do svojej peňaženky Dash na tomto zariadení dostanete zhruba %s Dash. Upozorňujeme, že dokončenie prevodu môže trvať 2 až 3 minúty Suma v Dash Zrušiť Potvrďte (%ss) @@ -57,11 +57,10 @@ Nákup zlyhal Prenos zlyhal Konverzia zlyhala - Dash bol úspešne vložený na váš účet Coinbase. Pri prenose do Dash peňaženky v tomto zariadení sa však vyskytol problém. - Niečo sa pokazilo + Dash bol úspešne vložený na váš Coinbase účet. Pri prenose do Dash Wallet na tomto zariadení sa však vyskytol problém. Prenos Dash do vašej peňaženky na tomto zariadení môže trvať 2 až 3 minúty. Prevod %s z Coinbase účtu na Dash vo vašej Dash peňaženke na tomto zariadení, môže trvať až 5 minút. - Prevod %s na váš Coinbase účet z Dash vo vašej Dash peňaženke na tomto zariadení môže trvať až 5 minút. + Prevod Dash z Dash Wallet na tomto zariadení na váš účet Coinbase %s môže trvať až 5 minút. Kontaktujte podporu Coinbase Poplatky pri nákupoch kryptomien Okrem zobrazeného poplatku za Coinbase započítavame aj rozpätie v cene. Trhy s kryptomenami sú volatilné, čo nám umožňuje dočasne zablokovať cenu za realizáciu obchodu. @@ -77,9 +76,9 @@ na váš Coinbase účet Z peňaženky Dash na tomto zariadení Dostanete - Na vašom účte Coinbase sme nenašli žiadne aktíva. - Nevlastníte žiadne kryptomeny. Pre začiatok si kúpte nejaké kryptomeny. - Kúpte si kryptomeny na Coinbase + Na vašom Coinbase účte sme nenašli žiadne aktíva. + Nevlastníte žiadne kryptomeny. Ak chcete začať, kúpte si nejaké kryptomeny. + Kúpte si kryptomeny na Coinbase Prekročili ste autorizačný limit na Coinbase. Zaplatiť z vášho účtu Zmeniť tento limit @@ -102,7 +101,6 @@ Coinbase 2FA kód Môže to byť kód z SMS vo vašom telefóne. Ak nie, zadajte kód z overovacej aplikácie. Kód je nesprávny. Skontrolujte a skúste to znova! - Kód bol neplatný. Prosím skúste znova Overiť Váš zostatok je nedostatočný Presun @@ -116,4 +114,7 @@ Na prevod je množstvo mincí v peňaženke príliš malé. Dostanete %s %s%s Dostanete %s + Vaša relácia s Coinbase vypršala + Prihláste sa do svojho Coinbase účtu + Chceli by ste vložiť zálohu za svoj nákup pomocou prepojeného bankového účtu? \ No newline at end of file diff --git a/integrations/coinbase/src/main/res/values-th/strings.xml b/integrations/coinbase/src/main/res/values-th/strings.xml index a2cbcc3e3f..21dc3c67c7 100644 --- a/integrations/coinbase/src/main/res/values-th/strings.xml +++ b/integrations/coinbase/src/main/res/values-th/strings.xml @@ -37,7 +37,6 @@ ค่าธรรมเนียม Coinbase ทั้งหมด คำสั่งซื้อตัวอย่าง - คุณจะได้รับ %s Dash ในกระเป๋าหน้าเงินของคุณบนอุปกรณ์นี้ โปรดทราบว่าอาจใช้เวลานานถึง 2-3 นาทีในการโอน จำนวนเงินใน Dash ยกเลิก ยืนยัน (%ss) @@ -57,11 +56,8 @@ การซื้อไม่สำเร็จ การโอนไม่สำเร็จ การแปลงไม่สำเร็จ - Dash ถูกฝากเข้าบัญชี Coinbase ของคุณเรียบร้อยแล้ว แต่มีปัญหาในการโอนไปยังกระเป๋าเงินสำหรับอุปกรณ์นี้ - บางอย่างผิดพลาด อาจใช้เวลานานถึง 2-3 นาทีในการโอนไปยังกระเป๋าเงิน Dash ของคุณบนอุปกรณ์นี้ อาจใช้เวลานานถึง 5 นาทีในการแปลง %s จากบัญชี Coinbase เป็น Dash ในกระเป๋าเงิน Dash ของคุณบนอุปกรณ์นี้.. - อาจใช้เวลานานถึง 5 นาทีในการแปลง Dash จาก Dash Wallet บนอุปกรณ์นี้เป็น %s ในบัญชี Coinbase ของคุณ ติดต่อฝ่ายสนับสนุน Coinbase ค่าธรรมเนียมในการซื้อคริปโต นอกเหนือจากค่าธรรมเนียม Coinbase ที่แสดงแล้วเรายังรวมถึงสเปรดในราคาตลาด คริปปโตมีความผันผวนและสิ่งนี้ช่วยให้เราสามารถล็อคไว้ชั่วคราวในราคาสำหรับการดำเนินการเทรด @@ -77,9 +73,7 @@ ถึงบัญชี Coinbase ของคุณ จากกระเป๋าเงิน Dash ในอุปกรณ์นี้ คุณจะได้รับ - เราไม่พบสินทรัพย์ใด ๆ ในบัญชี Coinbase ของคุณ - คุณไม่ได้เป็นเจ้าของคริปโตใด ๆ ซื้อคริปโตเลยเพื่อเริ่มต้น .. - ซื้อคริปโตบน Coinbase + ซื้อคริปโตบน Coinbase คุณทำรายการเกินขีดจำกัดการอนุญาตของ Coinbase หักเงินจากบัญชีของคุณ เปลี่ยนข้อจำกัดนี้ @@ -98,11 +92,9 @@ ตรวจสอบ ป้อนรหัส 2FA ของ Coinbase ขั้นตอนพิเศษนี้แสดงให้เห็นว่าคุณพยายามทำธุรกรรม - ต้องการความช่วยเหลือ? รหัส 2FA ของ Coinbase อาจเป็นรหัสจาก SMS บนโทรศัพท์ของคุณ หรืออาจเป็นรหัสจากแอพ authentication รหัสไม่ถูกต้อง กรุณาตรวจสอบและลองอีกครั้ง! - รหัสนั้นไม่ถูกต้อง กรุณาลองอีกครั้ง ตรวจสอบ คุณมียอดคงเหลือไม่เพียงพอ โอน @@ -112,8 +104,10 @@ โอนสำเร็จ อาจใช้เวลานานถึง 10 นาทีในการโอน Dash จาก Coinbase ไปยังกระเป๋าเงิน Dash บนอุปกรณ์นี้ อาจใช้เวลานานถึง 10 นาทีในการถ่ายโอน Dash จากกระเป๋าเงิน Dash บนอุปกรณ์นี้ไปยังบัญชี Coinbase ของคุณ - มีปัญหาในการโอน Dash ไปยังกระเป๋าเงิน Dash บนอุปกรณ์นี้ จำนวนเหรียญในกระเป๋าเงินนั้นน้อยเกินในการโอน คุณจะได้รับ %s %s %s คุณจะได้รับ %s + เซสชั่น Coinbase ของคุณหมดอายุแล้ว + กรุณาเข้าสู่ระบบบัญชี Coinbase ของคุณ + คุณต้องการฝากเงินสำหรับการซื้อของคุณโดยใช้บัญชีธนาคารที่เชื่อมโยงหรือไม่? \ No newline at end of file diff --git a/integrations/coinbase/src/main/res/values-tr/strings.xml b/integrations/coinbase/src/main/res/values-tr/strings.xml index c72c77a875..ea8bf9eb3b 100644 --- a/integrations/coinbase/src/main/res/values-tr/strings.xml +++ b/integrations/coinbase/src/main/res/values-tr/strings.xml @@ -37,7 +37,6 @@ Coinbase Ücreti Toplam Sipariş Önizleme - Bu cihazdaki Dash Cüzdanınızda %s Dash alacaksınız. Bir transferin tamamlanması 2-3 dakikayı bulabileceğini lütfen unutmayın. Dash\'teki Tutar İptal et Onaylayın (%ss) @@ -57,11 +56,8 @@ Satın Alma Başarısız Aktarım Başarısız Dönüştürülemedi - Dash başarıyla Coinbase hesabınıza yatırıldı. Ancak bu cihazda Dash Cüzdan\'a aktarılırken bir sorun oluştu. - Bir şeyler yanlış gitti Dash\'in bu cihazdaki Dash Cüzdanınıza aktarılması 2-3 dakika kadar sürebilir. Coinbase hesabınızdaki %s\'i Dash Cüzdanınızdaki Dash\'e dönüştürmek 5 dakika kadar sürebilir.. - \"Bu cihazda bulunan Dash Cüzdanınızdaki Dash\'i Coinbase hesabınıza %s olarak dönüştürmek 5 dakika kadar sürebilir. Coinbase Destek Ekibii ile İletişime Geçin Kripto alımlarında ücretler Görüntülenen Coinbase ücretine ek olarak, fiyata bir makas daha dahil ediyoruz. Kripto para piyasaları değişkendir ve bu, ticari işlem için bir fiyatta geçici olarak kilitlememize olanak tanır. @@ -77,9 +73,7 @@ Coinbase hesabınıza Bu cihazdaki Dash Cüzdanınızdan Alacağınız - Coinbase hesabınızda herhangi bir varlık bulamadık. - Herhangi bir kripto paranız yok. Başlamak için kripto satın alın.. - Coinbase\'de Kripto Satın Alın + Coinbase\'de Kripto Satın Alın Coinbase\'deki yetki sınırını aştınız. Hesabınızdan para çekme Bu sınırı değiştir @@ -98,11 +92,9 @@ Onayla Coinbase 2FA kodunu girin Bu ekstra adım, gerçekten bir işlem yapmaya çalıştığınızı gösterir. - Yardıma mı ihtiyacınız var? Coinbase 2FA kodu Telefonunuzdaki SMS\'den gelen kod olabilir. Değilse, kimlik doğrulama uygulamasından kodu girin. Kod yanlış. Lütfen kontrol edip tekrar deneyin! - Girilen kod geçersiz. Lütfen tekrar deneyin Onayla Yeterli bakiyeniz yok Transfer @@ -112,8 +104,9 @@ Aktarım başarılı Dash\'i Coinbase\'den bu cihazdaki Dash cüzdanına aktarmak 10 dakika kadar sürebilir. Dash\'i bu cihazdaki Dash Cüzdanından Coinbase hesabınıza aktarmak 10 dakika kadar sürebilir. - Bu cihazdaki Dash Cüzdanınaa aktarılırken bir sorun oluştu. Cüzdandaki jeton miktarı transfer edilemeyecek kadar az. %s %s %s alacaksınız %s Alacaksınız + Coinbase oturumunuzun süresi doldu + Lütfen Coinbase hesabınıza giriş yapın \ No newline at end of file diff --git a/integrations/coinbase/src/main/res/values-uk/strings.xml b/integrations/coinbase/src/main/res/values-uk/strings.xml index f3ef958526..f78c7682a4 100644 --- a/integrations/coinbase/src/main/res/values-uk/strings.xml +++ b/integrations/coinbase/src/main/res/values-uk/strings.xml @@ -58,10 +58,9 @@ Переказ не вдався Конвертація не вдалась Dash успішно внесено на ваш обліковий запис Coinbase. Але виникла проблема під час перенесення його в Dash Wallet на цьому пристрої. - Щось пішло не так Перенесення Dash у ваш Dash Wallet на цьому пристрої може зайняти 2-3 хвилини. Перетворення %s з облікового запису Coinbase на Dash у вашому Dash Wallet на цьому пристрої може зайняти до 5 хвилин. - «Конвертація Dash з Dash Wallet на цьому пристрої в %s у вашому обліковому записі Coinbase може зайняти до 5 хвилин. + Конвертація Dash з Dash Wallet на цьому пристрої в %s у вашому обліковому записі Coinbase може зайняти до 5 хвилин. Звернутися до служби підтримки Coinbase Комісії за покупки криптовалюти На додаток до відображеної комісії Coinbase ми включаємо спред у ціну. Криптовалютні ринки нестабільні, і це дозволяє нам тимчасово фіксувати ціну для здійснення угод. @@ -77,9 +76,9 @@ На акаунт Coinbase З Dash Wallet на цьому пристрої Ви отримаєте - Ми не знайшли активів у вашому обліковому записі Coinbase. - У вас немає ніякої криптовалюти. Щоб почати, купіть криптовалюту. - Купити криптовалюту на Coinbase + Ми не знайшли активів у вашому обліковому записі Coinbase. + У вас немає ніякої криптовалюти. Щоб почати, купіть криптовалюту. + Купити криптовалюту на Coinbase Ви перевищили ліміт авторизації на Coinbase. Списати гроші з вашого рахунку Змінити ліміт @@ -102,7 +101,6 @@ Код 2FA від Coinbase Це може бути код із SMS на вашому телефоні. Якщо ні, введіть код із програми автентифікації. Код не правильний. Перевірте та повторіть спробу! - Цей код був недійсним. Будь ласка спробуйте ще раз Підтвердити Ваш баланс недостатній Переказати @@ -116,4 +114,7 @@ Кількість монет у гаманці замала для переказу. Ви отримаєте %s %s %s Ви отримаєте %s + Ваш сеанс у Coinbase закінчився + Увійдіть у свій обліковий запис Coinbase + Бажаєте внести депозит за свою покупку за допомогою прив’язаного банківського рахунку? \ No newline at end of file diff --git a/integrations/coinbase/src/main/res/values-zh-rTW/strings.xml b/integrations/coinbase/src/main/res/values-zh-rTW/strings.xml index fe43ee39a6..98dd1f52fd 100644 --- a/integrations/coinbase/src/main/res/values-zh-rTW/strings.xml +++ b/integrations/coinbase/src/main/res/values-zh-rTW/strings.xml @@ -37,7 +37,7 @@ Coinbase 費用 總共 訂單預覽 - 您將在此設備上的達世錢包中收到 %s Dash。請注意,完成轉帳最多可能需要 2-3 分鐘。 + 您將在此設備上的達世錢包中收到~ %s Dash。請注意,完成轉帳最多可能需要 2-3 分鐘。 達世幣金額 取消 確認 (%ss) @@ -58,10 +58,9 @@ 轉帳失敗 兌換失敗 達世幣已成功存入您的 Coinbase 帳戶。但是把達世幣轉賬到此設備上的達世幣錢包時出現問題。 - 出了些問題 最多可能需要 2-3 分鐘才能將 Dash 轉帳到您在此設備上的達世幣錢包。 從Coinbase 帳戶中的 %s 轉換為這設備上達世幣錢包中的達世幣最多可能需要 5 分鐘。 - “最多可能需要 5 分鐘才能將達世幣從這設備上的達世幣錢包轉換為您在 Coinbase 帳戶中的 %s 。 + 最多可能需要 5 分鐘才能將達世幣從這設備上的達世幣錢包轉換為您在 Coinbase 帳戶中的 %s 。 聯繫 Coinbase 支援服務 購買加密貨幣的費用 除了顯示的 Coinbase 費用外,我們還包括價格差價。加密貨幣市場是不穩定的,這使我們能夠暫時鎖定交易執行的價格。 @@ -77,9 +76,9 @@ 到你的 Coinbase 賬戶 從此設備上的達世幣錢包 您將會收到 - 我們沒有在您的 Coinbase 帳戶中找到任何資產。 - 你未擁有任何加密貨幣。購買一些加密貨幣來開始.. - 在 Coinbase 上購買加密貨幣 + 我們沒有在您的 Coinbase 帳戶中找到任何資產。 + 你未擁有任何加密貨幣。購買一些加密貨幣來開始。 + 在 Coinbase 上購買加密貨幣 你超出了 Coinbase 的授權限制。 從您的帳戶中扣款 更改此限制 @@ -102,7 +101,6 @@ Coinbase 兩步認證代碼 它可能是您手機上短信中的代碼。如果沒有,請輸入身份驗證應用程序中的代碼。 代碼不正確。請檢查並重試! - 該代碼無效。請再試一次 檢查 你沒有足夠的餘額 轉帳 @@ -116,4 +114,7 @@ 錢包中餘額不足,因此無法進行轉賬。 您將會收到 %s %s %s 您將會收到 %s + 您的 Coinbase 工作階段已過期 + 請登錄您的 Coinbase 賬戶 + 您想在關聯的銀行帳戶所存入一筆資金並使用它來為您的購買埋單嗎? \ No newline at end of file diff --git a/integrations/coinbase/src/main/res/values-zh/strings.xml b/integrations/coinbase/src/main/res/values-zh/strings.xml index b3c8f5cfb7..694dffda90 100644 --- a/integrations/coinbase/src/main/res/values-zh/strings.xml +++ b/integrations/coinbase/src/main/res/values-zh/strings.xml @@ -37,7 +37,7 @@ Coinbase 费用 总计 订单预览 - 您将会在此设备的钱包中收到%sDash. 请注意, 完成转账最多可能需要2–3分钟. + 您将会在此设备的钱包中收到 ~ %sDash. 请注意, 完成转账最多可能需要2–3分钟. Dash 金额 取消 确认 (%ss) @@ -58,10 +58,9 @@ 转账失败 兑换失败 Dash已成功存入您的 Coinbase账户. 但是把 Dash转账到此设备上的 Dash钱包时出错. - 出错了 最多可能需要2–3分钟才能把 Dash转账到此设备的 Dash钱包. 从 Coinbase账户中的%s转换为此设备 Dash钱包中的 Dash最多可能需要5分钟... - “最多可能需要 5 分钟才能将 Dash从此设备上的 Dash钱包转换为您在 Coinbase账户中的 %s. + 最多可能需要 5 分钟才能将 Dash从此设备上的 Dash钱包转换为您在 Coinbase账户中的 %s. 联系 Coinbase客服 购买加密货币的费用 除了显示的 Coinbase费用外, 我们还包括价格差. 加密货币市场是不稳定的, 这使我们能够暂时锁定交易执行的价格. @@ -77,9 +76,9 @@ 到您的 Coinbase账户 从此设备的 Dash钱包 您将收到 - 我们没有在您的 Coinbase账户中找到任何资产. - 您未拥有任何加密货币. 购买一些加密货币以开始... - 在 Coinbase上购买加密货币 + 我们没有在您的 Coinbase账户中找到任何资产. + 您未拥有任何加密货币. 购买一些加密货币以开始. + 在 Coinbase上购买加密货币 您超出了 Coinbase的授权限制. 从您的账户中借记 更改此限制 @@ -102,7 +101,6 @@ Coinbase 2次验证码 它可能是您手机短信中的验证码. 如果没有, 请输入身份验证应用程序中的验证码. 验证码不正确. 请检查并重试! - 该验证码无效. 请再试一次 验证 您没有足够的余额 转账 @@ -116,4 +114,7 @@ 钱包余额不足, 因此无法进行转账. 您将会收到%s%s%s 您将会收到 %s + 您的Coinbase对话已过期 + 请登录您的 Coinbase 账户 + 您是否想使用关联的银行账户进行存款已完成您的购买? \ No newline at end of file diff --git a/integrations/crowdnode/src/main/res/values-ar/strings-crowdnode.xml b/integrations/crowdnode/src/main/res/values-ar/strings-crowdnode.xml index f38edf5b02..f633cae2a0 100644 --- a/integrations/crowdnode/src/main/res/values-ar/strings-crowdnode.xml +++ b/integrations/crowdnode/src/main/res/values-ar/strings-crowdnode.xml @@ -94,7 +94,6 @@ قم بإيداع %s لبدء الكسب يجب أن يكون الإيداع الأول أكثر من %s كيف يعمل CrowdNode Staking - يتم تشغيل شبكة داش بواسطة عدد من Masternodes والتي تعد جزءًا أساسيًا من تسهيل المدفوعات. يحتاج Masternode إلى 1000 داش كضمان ويتم حاليًا مكافأة كل Masternode %s%% سنويًا تقريبًا. APY الحالي هو %s%% تبدأ في الكسب إذا كان حساب CrowdNode الخاص بك يحتوي على أكثر من %s @@ -107,7 +106,6 @@ استلام المكافآت ستتلقى مدفوعات جزئية تلقائيًا وسيتم إعادة استثمارها افتراضيًا ، ومع ذلك ، من السهل أيضًا إعداد عمليات سحب تلقائية لتلقي دفعات متكررة. الحد الأدنى للإيداع الأول - نظرًا لأن معظم الأشخاص ليس لديهم 1000 داش بالضبط في متناول اليد ، فقد قدم CrowdNode خدمة حيث يمكنهم من خلال تجميع الودائع من الأعضاء تحقيق فوائد امتلاك Masternode. الانضمام إلى المجموعة فوائد CrowdNode عنوان داش متصل diff --git a/integrations/crowdnode/src/main/res/values-de/strings-crowdnode.xml b/integrations/crowdnode/src/main/res/values-de/strings-crowdnode.xml index 8c5aa07812..1ed0692741 100644 --- a/integrations/crowdnode/src/main/res/values-de/strings-crowdnode.xml +++ b/integrations/crowdnode/src/main/res/values-de/strings-crowdnode.xml @@ -94,7 +94,6 @@ %s einzahlen um Erträge zu erhalten Die erste Einzahlung sollte größer als %s sein Wie CrowdNode Staking funktioniert - Das Dash Netzwerk wird von einer Vielzahl von Masternodes betrieben, welche einen großen Anteil an der Zahlungsabwicklung besitzen. Ein Masternode benötigt 1000 Dash als Einlage und jeder Masternode erhält derzeit rund %s %% pro Jahr. Derzeitige APY ist %s %% Du erhältst Erträge, wenn dein CrowdNode Konto mehr als %s aufweist @@ -107,7 +106,6 @@ Rewards erhalten Du erhältst automatisch Teilzahlungen, die standardmäßig reinvestiert werden. Du kannst jedoch auch automatische Entnahme einrichten, um wiederkehrende Auszahlungen zu erhalten. Erste Mindesteinlage - Da die meisten Menschen nicht über 1000 Dash verfügen, hat CrowdNode einen Service erstellt der die Einzahlungen von Teilnehmer zusammenfügt um einen ganzen Masternode zu betreiben. Eintritt in den Pool CrowdNode Leistungen Dash Adresse verknüpfen diff --git a/integrations/crowdnode/src/main/res/values-el/strings-crowdnode.xml b/integrations/crowdnode/src/main/res/values-el/strings-crowdnode.xml index 627bb83c2d..1f12c84df5 100644 --- a/integrations/crowdnode/src/main/res/values-el/strings-crowdnode.xml +++ b/integrations/crowdnode/src/main/res/values-el/strings-crowdnode.xml @@ -94,7 +94,7 @@ Καταθέστε %s για να αρχίσετε να κερδίζετε Η πρώτη κατάθεση πρέπει να είναι μεγαλύτερη από %s Πώς λειτουργεί το staking στο CrowdNode  - Το δίκτυο Dash καθοδηγείται από έναν αριθμό Masternodes, ο οποίος αποτελεί ενα βασικό μέρος της διεκπεραίωσης των πληρωμών. + Το δίκτυο Dash διευθύνεται από έναν αριθμό Masternodes, οι οποίοι αποτελούν βασικό μέρος της διεξαγωγής των πληρωμών. Ένα Masternode χρειάζεται 1000 Dash ως εγγύηση και κάθε Masternode ανταμείβεται προς το παρόν με περίπου %s%% ετησίως. Το τρέχον APY είναι %s%% Αρχίζετε να κερδίζετε αν ο λογαριασμός σας στο CrowdNode έχει πάνω από %s diff --git a/integrations/crowdnode/src/main/res/values-es/strings-crowdnode.xml b/integrations/crowdnode/src/main/res/values-es/strings-crowdnode.xml index 7adddd03f3..f56305b6b7 100644 --- a/integrations/crowdnode/src/main/res/values-es/strings-crowdnode.xml +++ b/integrations/crowdnode/src/main/res/values-es/strings-crowdnode.xml @@ -94,7 +94,7 @@ Deposita %s para empezar a ganar El primer depósito debe ser más de %s Cómo funciona el staking de CrowdNode - La red de Dash está impulsada por una serie de Masternodes, que es una parte esencial para facilitar los pagos. + Dash Network está impulsada por una serie de Masternodes que son una parte esencial para facilitar los pagos. Un Masternode necesita 1000 Dash como garantía y cada Masternode actualmente es recompensado aproximadamente %s%% por año. El rendimiento anual aproximado actual es %s%% Empiezas a ganar si tu cuenta de CrowdNode tiene más de %s @@ -107,7 +107,7 @@ Recibir recompensas Recibirás pagos fraccionados automáticamente y se reinvertirán de manera predeterminada; sin embargo, también es fácil configurar retiros automáticos para recibir pagos recurrentes. Primer Depósito Mínimo - Como la mayoría de las personas no tienen exactamente 1000 Dash a mano, CrowdNode ha creado un servicio en el que al agrupar los depósitos de los miembros, estos pueden obtener los beneficios de poseer un Masternode. + Como la mayoría de las personas no tienen exactamente 1000 Dash a mano, CrowdNode ha creado un servicio donde, al agrupar los depósitos de los miembros, pueden lograr los beneficios de poseer un Masternode. Entrar al grupo Beneficios de CrowdNode Dirección de Dash conectada diff --git a/integrations/crowdnode/src/main/res/values-fa/strings-crowdnode.xml b/integrations/crowdnode/src/main/res/values-fa/strings-crowdnode.xml index 8b4d0c33d8..4be1143406 100644 --- a/integrations/crowdnode/src/main/res/values-fa/strings-crowdnode.xml +++ b/integrations/crowdnode/src/main/res/values-fa/strings-crowdnode.xml @@ -94,7 +94,6 @@ سپرده‌گذاری %s برای آغاز دریافت پاداش اولین واریزی باید بیشتر از %s باشد. عملکرد استیکینگ کراونود - شبکه دش توسط تعدادی مسترنود اداره می‌شود که بخشی اساسی در تسهیل پرداخت‌ها هستند. هر مسترنود باید هزار دش به عنوان وثیقه داشته باشد و هر مسترنود هر سال تقریبا %s%% پاداش می‌گیرد. بازدهی سالانه کنونی %s%% در صورتی می‌توانید کسب درآمد کنید که موجودی حساب کراودنود شما بیش از %s باشد. @@ -107,7 +106,6 @@ دریافت پاداش مبلغی جزئی را به صورت خودکار دریافت خواهید کرد و به صورت خودکار دوباره سرمایه‌گذاری می‌شود. با این وجود، امکان تنظیم برداشت خودکار برای دریافت پرداخت‌های مکرر به سادگی امکان‌پذیر است. اولین حداقل واریزی - از آنجا که بیشتر کاربران هزار دش در اختیار ندارند، کراودنود این خدمت را فراهم کرده تا با در کنار هم قرار گرفتن موجودی‌ کاربران مختلف، همه آنها بنوانند از مزایای در اختیار داشتن یک مسترنود بهره‌مند شوند. پیوستن به مجموعه مزایای کراودنود اتصال به نشانی دش برقرار است diff --git a/integrations/crowdnode/src/main/res/values-fr/strings-crowdnode.xml b/integrations/crowdnode/src/main/res/values-fr/strings-crowdnode.xml index ddcecf09e3..a64ec4cdef 100644 --- a/integrations/crowdnode/src/main/res/values-fr/strings-crowdnode.xml +++ b/integrations/crowdnode/src/main/res/values-fr/strings-crowdnode.xml @@ -94,7 +94,7 @@ Transférez %s pour recevoir des récompenses Le premier transfert doit être supérieur à %s Comment marche l\'épargne CrowdNode (staking) - Le réseau Dash est généré par un certain nombre de masternodes, qui sont une fonction essentielle pour faciliter les paiements. + Le réseau Dash est opéré par un certain nombre de masternodes, qui sont un facteur essentiel pour faciliter les paiements. Un masternode a besoin de 1000 dashs comme caution, et chaque masternode est actuellement rémunéré à hauteur de %s%% par an. L\'APY actuel est %s%% Vous commencerez à percevoir si votre compte CrowdNode détient plus de %s @@ -107,7 +107,7 @@ Recevoir les paiements Vous recevrez automatiquement les paiements partiels, et ils seront réinvestis par défaut. Cependant, il est également simple d\'opter pour des retraits automatiques pour recevoir les paiements récurrents. Premier dépôt minimal - Comme la plupart des gens ne possèdent pas exactement 1000 dashs, CrowdNode a créé un service où ils rassemblent les dépôts faits par les membres, et leur permettent de bénéficier des gains d\'un masternode. + Comme beaucoup de gens n\'ont pas 1000 dashs sous la main, CrowdNode a bâti un service qui permet à ses membres, par la réunion de leurs dépôts, de bénéficier des revenus de la propriété d\'un masternode. Rejoindre une équipe Bénéfices CrowdNode Adresse Dash connectée diff --git a/integrations/crowdnode/src/main/res/values-id/strings-crowdnode.xml b/integrations/crowdnode/src/main/res/values-id/strings-crowdnode.xml index dbeb146245..d244cda2f1 100644 --- a/integrations/crowdnode/src/main/res/values-id/strings-crowdnode.xml +++ b/integrations/crowdnode/src/main/res/values-id/strings-crowdnode.xml @@ -94,7 +94,6 @@ Setor %s untuk mulai menghasilkan Setoran pertama harus lebih dari %s Cara kerja staking CrowdNode - Dash Network digerakkan oleh sejumlah Masternode yang merupakan bagian penting dalam memfasilitasi pembayaran. Masternode membutuhkan 1000 Dash sebagai jaminan dan setiap Masternode saat ini dihargai sekitar %s%% per tahun. APY saat ini adalah %s%% Anda mulai menghasilkan jika akun CrowdNode Anda memiliki lebih dari %s @@ -107,7 +106,6 @@ Menerima imbalan Anda akan menerima pembayaran pecahan secara otomatis dan secara default akan diinvestasikan kembali, namun,mudah juga untuk mengatur penarikan otomatis untuk menerima pembayaran berulang. Setoran Minimum Pertama - Karena kebanyakan orang tidak memiliki 1000 Dash, CrowdNode telah membuat layanan di mana mereka dengan mengumpulkan deposit dari anggota sehingga dapat mencapai manfaat memiliki Masternode. Bergabung ke pool Manfaat CrowdNode Alamat Dash terhubung diff --git a/integrations/crowdnode/src/main/res/values-it/strings-crowdnode.xml b/integrations/crowdnode/src/main/res/values-it/strings-crowdnode.xml index 2547e66d1b..618c579e35 100644 --- a/integrations/crowdnode/src/main/res/values-it/strings-crowdnode.xml +++ b/integrations/crowdnode/src/main/res/values-it/strings-crowdnode.xml @@ -94,7 +94,7 @@ Deposita %s per iniziare a guadagnare Il primo deposito deve essere almeno di %s Come funziona lo staking di CrowdNode - La Dash Network è guidata da una serie di Masternodes che sono una parte essenziale per facilitare i pagamenti. + La rete Dash è guidata da una serie di Masternode che sono una parte essenziale per facilitare i pagamenti. Un Masternode ha bisogno di 1000 Dash come garanzia e ogni Masternode viene attualmente ricompensato all\'incirca %s %% all\'anno. L\'APY attuale è %s %% Inizi a guadagnare se il tuo account CrowdNode ha più di %s @@ -107,7 +107,7 @@ Ricevi le ricompense Riceverai automaticamente pagamenti frazionari e per impostazione predefinita verranno reinvestiti, tuttavia, è anche facile impostare prelievi automatici per ricevere pagamenti ricorrenti. Primo deposito minimo - Poiché la maggior parte delle persone non hanno esattamente 1000 Dash a portata di mano, CrowdNode ha realizzato un servizio in cui unendo i depositi dei membri possono ottenere i vantaggi del possedere un Masternode. + Poiché la maggior parte delle persone non ha esattamente 1000 Dash a portata di mano, CrowdNode ha creato un servizio in cui, mettendo in comune i depositi dei membri, è possibile ottenere i vantaggi di possedere un Masternode. Unisciti alla pool Vantaggi di CrowNode Indirizzo Dash connesso diff --git a/integrations/crowdnode/src/main/res/values-ja/strings-crowdnode.xml b/integrations/crowdnode/src/main/res/values-ja/strings-crowdnode.xml index 7d693dc26d..1410710cc2 100644 --- a/integrations/crowdnode/src/main/res/values-ja/strings-crowdnode.xml +++ b/integrations/crowdnode/src/main/res/values-ja/strings-crowdnode.xml @@ -94,7 +94,7 @@ %s入金して収益化を始める 初回の入金には、%s以上が必要になります CrowdNodeのステーキングの仕組み - Dashネットワークは多くのマスターノードによって稼働しており、決済を円滑に行うために不可欠な要素となっています。 + Dashネットワークは多くのマスターノードで駆動され、決済を促進するのに不可欠な役割を担っています。 マスターノードには1000Dashの担保金が必要で、現在各マスターノードには年に%s%%回程度の報酬が支払われます。 現在のAPYは、%s%%です お客様のCrowdNodeのアカウントに%s以上あれば、収益が発生します。 @@ -107,7 +107,7 @@ 報酬の受取 端数の支払いは自動的に受け取れ、デフォルトでは再投資されますが、定期的な支払いを受け取るための自動出金の設定も簡単に行うことができます。 初回最低入金額 - 手元に1000Dashもない人がほとんどなため、CrowdNodeはサービスを作り、会員からの預金をプールすることで、そのような人々がマスターノードを所有する特典を得られるようにしました。 + 大半の人が手元に1000Dashをきっちり持っているわけではないため、CrowdNodeは会員からの預金をプールしてマスターノードを所有するメリットを得ることができるサービスを作りました。 プールの参加 CrowdNodeの特典 連結されたDashアドレス diff --git a/integrations/crowdnode/src/main/res/values-ko/strings-crowdnode.xml b/integrations/crowdnode/src/main/res/values-ko/strings-crowdnode.xml index ceb4570713..115b4dcf20 100644 --- a/integrations/crowdnode/src/main/res/values-ko/strings-crowdnode.xml +++ b/integrations/crowdnode/src/main/res/values-ko/strings-crowdnode.xml @@ -94,7 +94,7 @@ 보증금 %s로 리워드를 받으세요 첫 보증금은 %s 이상이어야 합니다 크라우드노드 스테이킹이 이루어지는 방식 - 대시 네트워크는 지불이 이루어지도록 도와주는 중요 부분인 마스터노드에 의해 운영됩니다. + 대시 네트워크는 지불이 이루어지는 데 중요한 역할을 하는 다수의 마스터노드에 의해 운영됩니다. 마스터노드는 보증금으로 1000대시가 필요하며, 각 마스터노드는 현재 연간 약 %s%%을 보상받고 있습니다. 현재 APY는 %s%% 입니다 당신의 크라우드노드 계정에 %s이상 보유하면 보상을 받기 시작합니다. @@ -107,7 +107,7 @@ 보상 받기 당신은 분할된 지불금을 자동적으로 받게 되며, 이 금액은 디폴트로 다시 투자됩니다. 그러나, 쉬운 설정을 통해 지속적으로 이 수익금을 받아 자동 출금할 수도 있습니다. 첫 번째 최소 보증금 - 대부분의 사람들이 당장 손에 1000대시를 쥐고 있지 않은 만큼, 크라우드노드는 멤버들의 보증금을 풀로 모아 마스터노드 소유의 이점을 누릴 수 있도록 하는 서비스를 개설하였습니다. + 대부분의 사람들이 당장 1000대시를 지니고 있지 않은 만큼, 크라우드노드는 구성원들의 보증금을 모아 마스터노드 소유의 장점을 누릴 수 있도록 하는 서비스를 개설하였습니다. 풀 가입하기 크라우드노드의 이점 연결된 대시 주소 diff --git a/integrations/crowdnode/src/main/res/values-nl/strings-crowdnode.xml b/integrations/crowdnode/src/main/res/values-nl/strings-crowdnode.xml index d7189c4aa1..b9cd43668d 100644 --- a/integrations/crowdnode/src/main/res/values-nl/strings-crowdnode.xml +++ b/integrations/crowdnode/src/main/res/values-nl/strings-crowdnode.xml @@ -94,7 +94,7 @@ Stort %s om te beginnen met verdienen Eerste storting moet meer zijn dan %s Hoe staking bij CrowdNode werkt - Het Dash Netwerk wordt aangedreven door \'Masternodes\', wat een essentieel onderdeel is voor het faciliteren van betalingen. + Het Dash netwerk wordt aangedreven door een aantal Masternodes, welke een essentieel onderdeel zijn voor het faciliteren van betalingen. Een Masternode heeft 1000 Dash nodig als onderpand. Elke Masternode wordt momenteel ongeveer met %s%% per jaar beloond. Huidig jaarlijks rendement = %s%% Je begint te verdienen als je CrowdNode account meer heeft dan %s @@ -107,7 +107,7 @@ Beloningen ontvangen U ontvangt automatisch fractionele betalingen en deze worden standaard opnieuw geïnvesteerd. Het is ook mogelijk om automatische opnames in te stellen om terugkerende uitbetalingen te ontvangen. Eerste minimale storting - Omdat de meeste mensen geen 1000 Dash bij de hand hebben, heeft CrowdNode een service gemaakt waarbij ze door stortingen van leden te bundelen de voordelen van het bezitten van een Masternode kunnen bereiken. + Omdat de meeste mensen geen 1000 Dash bezitten, biedt CrowdNode een service waarbij de stortingen van leden worden gebundeld tot masternodes, om zo de voordelen van het bezitten hiervan te kunnen bereiken. Deelnemen aan de pool CrowdNode voordelen Verbonden Dash adres diff --git a/integrations/crowdnode/src/main/res/values-pl/strings-crowdnode.xml b/integrations/crowdnode/src/main/res/values-pl/strings-crowdnode.xml index d6d1e98fda..87cf502e33 100644 --- a/integrations/crowdnode/src/main/res/values-pl/strings-crowdnode.xml +++ b/integrations/crowdnode/src/main/res/values-pl/strings-crowdnode.xml @@ -94,7 +94,7 @@ Wpłać %saby zacząć zarabiać Pierwsza wpłata powinna być większa niż %s Jak działa lokata procentowa na CrowdNode - Sieć Dash jest kierowana przez Masternody które są niezbędne do wysyłania transakcji. + Sieć Dash jest kierowana przez Masternody które są niezbędną częscią infrastruktury przeznaczonej do dokonywania transakcji. Jeden Masternode musi mieć 1000 Dash jako zabezpieczenie, oraz jest za to wynagradzany około %s%% na rok. Obecne oprocentowanie w skali roku jest %s%% Zaczniesz otrzymywać wynagrodznie jeśli twoje konto na CrowdNode będzię miało więcej niż %s @@ -107,7 +107,7 @@ Otrzymywanie wynagrodzenia Będziesz otrzymywać częściowe płatności automatycznie i domyślnie będą one reinwestowane, jednak jęsli chcesz możesz też ustawić automatyczne wypłaty. Pierwszy Minimalna Wielkość Depozytu - Ponieważ większość ludzi nie ma pod ręką dokładnie 1000 Dash, CrowdNode stworzył usługę, w której wszyscy mogą czerpać korzyści z posiadania Masternode poprzez kimulacje mniejszych kwot od wielu użytkowników. + Ponieważ większość ludzi nie ma pod ręką dokładnie 1000 Dash, CrowdNode stworzył usługę, w której wszyscy mogą czerpać korzyści z posiadania Masternode poprzez kumulacje mniejszych kwot od wielu użytkowników. Dołączanie do Puli CrowdNode Korzyści CrowdNode Połączony adres Dash diff --git a/integrations/crowdnode/src/main/res/values-pt/strings-crowdnode.xml b/integrations/crowdnode/src/main/res/values-pt/strings-crowdnode.xml index f0d0eaf9e2..ce503d7244 100644 --- a/integrations/crowdnode/src/main/res/values-pt/strings-crowdnode.xml +++ b/integrations/crowdnode/src/main/res/values-pt/strings-crowdnode.xml @@ -94,7 +94,7 @@ Deposite %s para começar a ganhar O primeiro depósito deve ser superior a %s Como funciona o staking no CrowdNode - A Rede Dash é impulsionada por vários Masternodes, o que é uma parte essencial para facilitar os pagamentos. + A Rede Dash é impulsionada por vários Masternodes, que são uma parte essencial para facilitar os pagamentos. Um Masternode precisa de 1000 Dash como garantia e cada Masternode é atualmente recompensado aproximadamente por %s %% por ano. O APY atual é %s %% Você começa a ganhar se sua conta CrowdNode tiver mais de %s @@ -107,7 +107,6 @@ Recebendo recompensas Você receberá pagamentos fracionados automaticamente e eles serão reinvestidos por padrão, no entanto, também é fácil configurar saques automáticos para receber pagamentos recorrentes. Primeiro Depósito Mínimo - Como a maioria das pessoas não tem exatamente 1000 Dash em mãos, o CrowdNode criou um serviço em que, ao reunir depósitos de membros, podem obter os benefícios de possuir um Masternode. Entrando no \"pool\" Benefícios CrowdNode Endereço Dash conectado diff --git a/integrations/crowdnode/src/main/res/values-ru/strings-crowdnode.xml b/integrations/crowdnode/src/main/res/values-ru/strings-crowdnode.xml index 84b887a48e..d96dcb43a7 100644 --- a/integrations/crowdnode/src/main/res/values-ru/strings-crowdnode.xml +++ b/integrations/crowdnode/src/main/res/values-ru/strings-crowdnode.xml @@ -24,7 +24,7 @@ Аккаунт создан Привязать уже существующий аккаунт Войти в CrowdNode - Подписаться на новости CrowdNode + Создать CrowdNode аккаунт Привязать уже существующий аккаунт CrowdNode Необходимо создать резервную копию кодовой фразы Dash Wallet Создать резервную копию кодовой фразы diff --git a/integrations/crowdnode/src/main/res/values-sk/strings-crowdnode.xml b/integrations/crowdnode/src/main/res/values-sk/strings-crowdnode.xml index 51a21789ad..57601943b3 100644 --- a/integrations/crowdnode/src/main/res/values-sk/strings-crowdnode.xml +++ b/integrations/crowdnode/src/main/res/values-sk/strings-crowdnode.xml @@ -94,7 +94,7 @@ Aby ste mohli začať zarábať, vložte %s Prvý vklad by mal byť vyšší ako %s Ako funguje stávkovanie na CrowdNode - Sieť Dash je riadená množstvom Masternódov, čo je nevyhnutná súčasť pre uskutočnenie platieb. + Sieť Dash je riadená množstvom Masternodov, ktoré sú nevyhnutnou súčasťou uskutočnenia platieb. Masternód potrebuje 1000 Dash ako záruku a každý Masternód je v súčasnosti odmeňovaný približne %s%% ročne. Aktuálny APY je %s%% Zarábať začnete, ak má váš účet CrowdNode viac ako %s @@ -107,7 +107,7 @@ Prijímané odmeny Čiastkové platby budete dostávať automaticky a štandardne budú reinvestované, ale je tiež jednoduché nastaviť automatické výbery pre prijímanie opakujúcich sa platieb. Prvý minimálny vklad - Keďže väčšina ľudí nemá po ruke presne 1000 Dash, CrowdNode vytvoril službu, kde môžete združovaním vkladov od členov dosiahnuť výhody vlastníctva Masternódu. + Keďže väčšina ľudí nemá po ruke presne 1000 Dash, CrowdNode vytvoril službu, kde môžu združovaním vkladov od členov dosiahnuť výhody vlastníctva Masternode. Pripojenie k členstvu Výhody CrowdNode Pripojená Dash adresa diff --git a/integrations/crowdnode/src/main/res/values-th/strings-crowdnode.xml b/integrations/crowdnode/src/main/res/values-th/strings-crowdnode.xml index c5a7ca488d..c438a58aff 100644 --- a/integrations/crowdnode/src/main/res/values-th/strings-crowdnode.xml +++ b/integrations/crowdnode/src/main/res/values-th/strings-crowdnode.xml @@ -94,7 +94,6 @@ ฝากเงิน %s เพื่อเริ่มต้นหารายได้ เงินฝากครั้งแรกควรมากกว่า %s การ staking CrowdNode ทำงานอย่างไร - เครือข่าย Dash นั้นขับเคลื่อนด้วยมาสเตอร์โหนดจำนวนมากซึ่งเป็นส่วนสำคัญของการชำระเงินที่อำนวยความสะดวก มาสเตอร์โหนดต้องการ 1000 Dash เป็นหลักประกันและแต่ละ มาสเตอร์โหนดต้องการจะได้รับรางวัลประมาณ %s%%ต่อปี APY ปัจจุบันคือ %s%% คุณเริ่มมีรายได้หากบัญชี CrowdNode ของคุณมีมากกว่า %s @@ -107,7 +106,6 @@ การรับรีวอร์ด คุณจะได้รับการชำระเงินแบบเศษส่วนโดยอัตโนมัติและโดยค่าเริ่มต้นจะถูกนำกลับมาลงทุนใหม่อย่างไรก็ตามมันเป็นเรื่องง่ายที่จะตั้งค่าการถอนอัตโนมัติเพื่อรับการจ่ายเงินที่เกิดขึ้นซ้ำ เงินฝากขั้นต่ำขั้นแรก - เนื่องจากคนส่วนใหญ่ไม่ได้มี 1000 Dash ในมือ CrowdNode ได้ให้บริการที่พวกเขามีการรวมเงินฝากจากสมาชิกสามารถได้รับประโยชน์จากการเป็นเจ้าของมาสเตอร์โหนด เข้าร่วม pool ผลประโยชน์ของ CrowdNode ที่อยู่ Dash ที่เชื่อมต่อ diff --git a/integrations/crowdnode/src/main/res/values-tr/strings-crowdnode.xml b/integrations/crowdnode/src/main/res/values-tr/strings-crowdnode.xml index 891fe152ad..b0e44d983b 100644 --- a/integrations/crowdnode/src/main/res/values-tr/strings-crowdnode.xml +++ b/integrations/crowdnode/src/main/res/values-tr/strings-crowdnode.xml @@ -94,7 +94,6 @@ Kazanmaya başlamak için %s yatırın İlk depozito %s\'den fazla olmalıdır CrowdNode staking nasıl çalışır? - Dash Ağı, ödemeleri kolaylaştırmanın önemli bir parçası olan bir dizi Ana Düğüm tarafından yönetilir. Bir Ana Düğüm teminat olarak 1000 Dash\'a ihtiyaç duyar ve her Ana Düğüm şu anda yılda yaklaşık %s%% ödüllendirilir. Güncel YFG: %s%% CrowdNode hesabınızda %s\'den fazla varsa kazanmaya başlarsınız @@ -107,7 +106,6 @@ Ödüller alınıyor Kısmı ödemeleri otomatik olarak alacaksınız ve bunlar yeniden yatırım için kullanılacak. Tekrarlayan ödemeleri almak için otomatik para çekme işlemlerini ayarlamak kolaydır. İlk Asgari Para Yatırma - Çoğu insanın elinde tam olarak 1000 Dash bulunmadığından, CrowdNode, üyelerden gelen mevduatları bir araya getirerek bir Ana Düğüm sahibi olmanın avantajlarını elde edebilecekleri bir hizmet yaptı. Havuza katılmak CrowdNode avantajları Bağlantılı Dash adresleri diff --git a/integrations/crowdnode/src/main/res/values-zh/strings-crowdnode.xml b/integrations/crowdnode/src/main/res/values-zh/strings-crowdnode.xml index c96c9a520b..25314635ca 100644 --- a/integrations/crowdnode/src/main/res/values-zh/strings-crowdnode.xml +++ b/integrations/crowdnode/src/main/res/values-zh/strings-crowdnode.xml @@ -94,7 +94,7 @@ 存入 %s 来开始赚取收益 首次存款应该超过 %s CrowdNode 质押的运作原理 - Dash网络由众多主节点驱动, 这是令支付更便捷的重要组成部分. + Dash 网络由众多主节点驱动, 这是令支付更便捷的重要组成部分. 一个主节点需要 1000 Dash作为抵押, 每个主节点目前每年获得大约%s%%的奖励. 目前的年利率是%s%% 如果您的CrowdNode账户拥有超过%s就会开始赚取收益 diff --git a/market/market-description-bg.txt b/market/market-description-bg.txt index 4d8e7c9e01..42822ec9d1 100644 --- a/market/market-description-bg.txt +++ b/market/market-description-bg.txt @@ -1,4 +1,4 @@ -MAIN DESCRIPTION +ОСНОВНО ОПИСАНИЕ Dash Wallet е референтният Android портфейл, поддържан от Dash Core Group за Dash - Digital Cash - иновативна алтернатива на Bitcoin, която е изрично предназначена за плащания. diff --git a/wallet/res/values-ar/strings-extra.xml b/wallet/res/values-ar/strings-extra.xml index 1ddef19afa..8cb5fbf29a 100644 --- a/wallet/res/values-ar/strings-extra.xml +++ b/wallet/res/values-ar/strings-extra.xml @@ -279,8 +279,6 @@ اتصل بالدعم بلغ عن خطأ اكتشف - تسوق مع داش لدى أكثر من 155000 تاجر - دليل العناوين استيراد مفتاح خاص مراقب الشبكة @@ -407,6 +405,7 @@ مفاتيح المالك مفاتيح التصويت مفاتيح المشغل + المعرف الشخصي التابع لعقدة Evolution %dمفاتيح %dمستعملة %dزوج المفتاح diff --git a/wallet/res/values-ar/strings.xml b/wallet/res/values-ar/strings.xml index 9d1a4155b6..46bf47a5fe 100644 --- a/wallet/res/values-ar/strings.xml +++ b/wallet/res/values-ar/strings.xml @@ -378,12 +378,11 @@ سبب قم بايصال حسابك قم بشراء داش - لا حساب لازم - تم انتهاء صلاحية جلسة اتصالك بكوين اكس - الرجاء تسجيل الدخول الى حساب كوين اكس - تسجيل الدخول + عنوان المستلم اظهر المحتوى في لوحة الملاحظات اضغط على العنوان من لوحة الملاحظات للصقه ليس عنوان داش صالح او الاتصال عبر الرابط غير صالح إرسال إلى عنوان - + + diff --git a/wallet/res/values-bg/strings.xml b/wallet/res/values-bg/strings.xml index 20ecff417b..f0309f119a 100644 --- a/wallet/res/values-bg/strings.xml +++ b/wallet/res/values-bg/strings.xml @@ -359,4 +359,5 @@ Филтър Изпрати до адрес - + + diff --git a/wallet/res/values-cs/strings-extra.xml b/wallet/res/values-cs/strings-extra.xml index c636438932..24b99cdc8b 100644 --- a/wallet/res/values-cs/strings-extra.xml +++ b/wallet/res/values-cs/strings-extra.xml @@ -251,8 +251,6 @@ Podpora Nahlásit problém Prozkoumat - Nakupujte s DASH u více než 155 000 obchodníků - Adresář Importovat Privátní Klíč Monitor Sítě diff --git a/wallet/res/values-cs/strings.xml b/wallet/res/values-cs/strings.xml index 3759b0af33..690edc5c27 100644 --- a/wallet/res/values-cs/strings.xml +++ b/wallet/res/values-cs/strings.xml @@ -372,4 +372,5 @@ Filtr Zrušit filtr Odeslat na adresu - + + diff --git a/wallet/res/values-de/strings-extra.xml b/wallet/res/values-de/strings-extra.xml index 11f30af801..a5f8ac5e0e 100644 --- a/wallet/res/values-de/strings-extra.xml +++ b/wallet/res/values-de/strings-extra.xml @@ -267,8 +267,6 @@ Support kontaktieren Ein Problem melden Explore - Bei über 155.000 Händlern mit DASH einkaufen - Adressbuch Privaten Schlüssel importieren Netzwerk-Monitor diff --git a/wallet/res/values-de/strings.xml b/wallet/res/values-de/strings.xml index 9f1f3be12b..63e9533e19 100644 --- a/wallet/res/values-de/strings.xml +++ b/wallet/res/values-de/strings.xml @@ -378,13 +378,11 @@ Ursache Konto verknüpfen Dash kaufen. Es wird kein Konto benötigt. - Deine Coinbase Session ist abgelaufen. - Bitte logge dich in deinen Coinbase Account ein. - Einloggen Adresse des Empfängers Inhalt der Zwischenablage anzeigen Tippe auf die Adresse in der Zwischenablage, um sie einzufügen. Keine gültige Dash Adresse oder URL-Anfrage An Adresse senden - + + diff --git a/wallet/res/values-el/strings-extra.xml b/wallet/res/values-el/strings-extra.xml index 0b05cc6752..8720bf212f 100644 --- a/wallet/res/values-el/strings-extra.xml +++ b/wallet/res/values-el/strings-extra.xml @@ -267,7 +267,7 @@ Επικοινωνήστε με την υποστήριξη Αναφέρετε ένα πρόβλημα Εξερευνήστε - Αγοράστε με το DASH σε πάνω από 155.000 εμπόρους + Βρείτε εμπόρους που δέχονται DASH Βιβλίο Διευθύνσεων Εισάγετε το ιδιωτικό κλειδί @@ -395,6 +395,7 @@ Κλειδιά Κατόχου Κλειδιά ψήφου Κλειδιά Χειριστή + Κλειδιά Evolution Node ID %d κλειδιά %d χρησιμοποιείται Ζεύγος κλειδιών %d diff --git a/wallet/res/values-el/strings.xml b/wallet/res/values-el/strings.xml index 5c19ba5792..d094015f99 100644 --- a/wallet/res/values-el/strings.xml +++ b/wallet/res/values-el/strings.xml @@ -377,12 +377,21 @@ Λόγος Σύνδεση του λογαριασμού σας Αγοράστε Dash - Δεν απαιτείται λογαριασμός - Η συνεδρία σας στην Coinbase έχει λήξει - Συνδεθείτε στο λογαριασμό σας στην Coinbase - Είσοδος + Διεύθυνση παραλήπτη Εμφάνιση περιεχομένου στο πρόχειρο Πατήστε τη διεύθυνση από το πρόχειρο για να την επικολλήσετε Δεν είναι μια έγκυρη διεύθυνση Dash ή URL Αποστολή στη διεύθυνση + + + Βελτιστοποίηση μπαταρίας + Βελτιστοποίηση + Απεριόριστα + + Θα επιτρέψετε στο Dash Wallet να εκτελείται στο παρασκήνιο; + Αυτή η εφαρμογή έχει ρυθμιστεί ώστε να σταματά να εκτελείται όταν δεν είναι ορατή για κάποιο χρονικό διάστημα.\n\nΑυτό θα έχει ως αποτέλεσμα να μην λαμβάνετε ειδοποιήσεις πληρωμής έως ότου η εφαρμογή επιστρέψει στο προσκήνιο.\n\nΣυνιστούμε να επιτρέψετε σε αυτή την εφαρμογή να εκτελείται στο παρασκήνιο, ρυθμίζοντας τη βελτιστοποίηση της μπαταρίας σε \"Μη βελτιστοποιημένη\" ή \"Χωρίς περιορισμούς\".\n\nΜην ανησυχείτε, θα προσέχουμε πάντα τη χρήση της μπαταρίας σας. + Θέλετε να λαμβάνετε πληρωμές στο παρασκήνιο; + Αυτή η εφαρμογή έχει ρυθμιστεί ώστε να συνεχίζει να εκτελείται ακόμη και όταν δεν είναι ορατή για κάποιο χρονικό διάστημα.\n\nΠροτείνουμε να επιτρέψετε σε αυτή την εφαρμογή να παραμένει πάντα ενημερωμένη με TO blockchain, ακόμη και στο παρασκήνιο.\n\nΜην ανησυχείτε, θα προσέχουμε πάντα τη χρήση της μπαταρίας σας. + Άνοιγμα Ρυθμίσεων diff --git a/wallet/res/values-es/strings-extra.xml b/wallet/res/values-es/strings-extra.xml index 6aa54ae7e9..49816ffe7b 100644 --- a/wallet/res/values-es/strings-extra.xml +++ b/wallet/res/values-es/strings-extra.xml @@ -270,7 +270,7 @@ Contactar Soporte Reportar un problema Explorar - Compra con DASH en más de 155,000 comerciantes + Encuentra comerciantes que acepten DASH Libreta de direcciones Barrer billetera de papel diff --git a/wallet/res/values-es/strings.xml b/wallet/res/values-es/strings.xml index 7ce0fd0c40..7590205563 100644 --- a/wallet/res/values-es/strings.xml +++ b/wallet/res/values-es/strings.xml @@ -377,13 +377,21 @@ Razón Vincula tu cuenta Compra Dash. No se necesita una cuenta - Tu sesión de Coinbase ha expirado - Inicia sesión en tu cuenta de Coinbase - Iniciar sesión Dirección de la destinataria Mostrar contenido en el portapapeles Toca la dirección del portapapeles para pegarla. No es una dirección de Dash válida o una solicitud de URL Enviar a Dirección + + + Optimización de la batería + Optimizado + Irrestricto + + ¿Dejar que la billetera de Dash se ejecute en segundo plano? + Esta aplicación está configurada para dejar de ejecutarse después de no estar visible durante algún tiempo.\n\nEsto provocará que no recibas notificaciones de pago hasta que la aplicación vuelva al primer plano.\n\nRecomendamos permitir que esta aplicación se ejecute en segundo plano configurando la batería optimización a \"No optimizado\" o \"Sin restricciones\".\n\nNo te preocupes, lo haremos siempre teniendo en cuenta el uso de la batería. + ¿Quieres recibir pagos en segundo plano? + Esta aplicación está configurada para continuar ejecutándose después de no estar visible durante algún tiempo.\n\nSugerimos permitir que esta aplicación se mantenga siempre actualizada con la cadena de bloques, incluso en segundo plano.\n\nNo te preocupes, lo haremos siempre teniendo en cuenta el uso de la batería. + Abrir Configuración diff --git a/wallet/res/values-fa/strings-extra.xml b/wallet/res/values-fa/strings-extra.xml index b7f8531411..527bb44aee 100644 --- a/wallet/res/values-fa/strings-extra.xml +++ b/wallet/res/values-fa/strings-extra.xml @@ -269,8 +269,6 @@ تماس با پشتیبانی گزارش مشکل کاوش - از بیش از ۱۵۵،۰۰۰ فروشنده با دش خرید کنید - دفترچه نشانی‌ها وارد کردن کلید خصوصی پایش شبکه diff --git a/wallet/res/values-fa/strings.xml b/wallet/res/values-fa/strings.xml index a61d6817ac..56a5ac48e1 100644 --- a/wallet/res/values-fa/strings.xml +++ b/wallet/res/values-fa/strings.xml @@ -378,13 +378,11 @@ دلیل به حساب‌تان متصل کنید دش بخرید - بدون نیاز به حساب - حساب کوینبیس‌ شما منقضی شده است - لطفا وارد حساب کوینبیس‌تان شوید - ورود نشانی دریافت‌کننده نشان دادن اطلاعات در کلیپ‌برد روی نشانی موجود در کلیپ‌برد لمس کنید تا چسبانده شود نشانی دش و یا نشانی وب درخواستی معتبر نیست ارسال به نشانی - + + diff --git a/wallet/res/values-fil/strings-extra.xml b/wallet/res/values-fil/strings-extra.xml index 5b900aaf4c..005a25475c 100644 --- a/wallet/res/values-fil/strings-extra.xml +++ b/wallet/res/values-fil/strings-extra.xml @@ -267,7 +267,7 @@ Contact Support I-report ang Isyu Galugarin - Mamili gamit ang DASH sa mahigit 155,000 merchant + Maghanap ng mga merchant na tumatanggap ng DASH Adresbuk I-import ang private key @@ -395,6 +395,7 @@ Susi ng may-ari Mga susi ng pagboto Operator na mga Susi + Mga key ng Evolution Node ID %d mga susi %d ginamit Keypair %d diff --git a/wallet/res/values-fil/strings.xml b/wallet/res/values-fil/strings.xml index bcdb57f690..a7dd47c748 100644 --- a/wallet/res/values-fil/strings.xml +++ b/wallet/res/values-fil/strings.xml @@ -377,12 +377,21 @@ Dahilan I-link ang iyong account Bumili ng Dash · Walang kinakailangang account - Ang iyong session sa Coinbase ay nag-expire na - Mangyaring mag-log in sa iyong Coinbase account - Mag log in + Address ng Tatanggap Ipakita ang nilalaman sa clipboard I-tap ang address mula sa clipboard para i-paste ito Hindi wastong Dash Address o kahilingan sa URL Ipadala sa address + + + Pag-optimize ng baterya + Na-optimize + Hindi pinaghihigpitan + + Hayaang tumakbo ang Dash Wallet sa background? + Ang app na ito ay na-configure na huminto sa pagtakbo pagkatapos na hindi makita sa loob ng ilang oras.\n\nMagreresulta ito sa hindi pagtanggap ng mga notification sa pagbabayad hanggang sa bumalik ang app sa foreground.\n\nInirerekomenda naming payagan ang app na ito na tumakbo sa background sa pamamagitan ng pagtatakda ng pag-optimize baterya sa \"Not Optimized\" o \"Unrestricted.\"\n\nHuwag mag-alala, lagi naming iisipin ang iyong paggamit ng baterya. + Gusto mo bang makatanggap ng mga pagbabayad sa background? + Ang app na ito ay na-configure upang magpatuloy sa pagtakbo pagkatapos na hindi makita sa loob ng ilang oras.\n\nIminumungkahi namin na payagan ang app na ito na laging manatiling up-to-date sa blockchain, kahit na nasa background.\n\nHuwag mag-alala, gagawin namin laging isipin ang iyong paggamit ng baterya. + Buksan ang settings diff --git a/wallet/res/values-fr/strings-extra.xml b/wallet/res/values-fr/strings-extra.xml index 8ec43077d2..0753f88ebe 100644 --- a/wallet/res/values-fr/strings-extra.xml +++ b/wallet/res/values-fr/strings-extra.xml @@ -270,7 +270,7 @@ Contacter le service d\'aide Signaler un problème Explorer - Achetez avec Dash chez plus de 155 000 vendeurs + Trouver des vendeurs qui acceptent Dash Carnet d\'adresses Importer une clé privée diff --git a/wallet/res/values-fr/strings.xml b/wallet/res/values-fr/strings.xml index 200e85782a..659ab15acd 100644 --- a/wallet/res/values-fr/strings.xml +++ b/wallet/res/values-fr/strings.xml @@ -377,13 +377,21 @@ Raison Relier votre compte Achetez des dashs · Aucun compte requis - Votre session Coinbase a expiré - Veuillez vous connecter à votre compte Coinbase - Se connecter Adresse du destinataire Voir le contenu du presse-papiers Touchez l\'adresse du presse-papiers pour la coller Adresse Dash ou requête URL non valides Envoyer à l\'adresse + + + Optimisation de la batterie + Optimisée + Sans restriction + + Autoriser Dash Wallet à fonctionner en arrière-plan ? + Cette application est configurée pour arrêter de fonctionner si elle n\'est pas visible et consultée pendant un certain temps.\n\nCela empêchera de recevoir des notifications de paiement avant que l\'application ne soit à nouveau consultée.\n\nNous conseillons d\'autoriser cette application à fonctionner en arrière-plan, en réglant l\'optimisation de batterie sur \"Non optimisée\" ou \"Sans restriction\".\n\nNe vous inquiétez pas, nous prendrons soin de l\'utilisation de la batterie. + Souhaitez-vous pouvoir recevoir des paiements en arrière-plan ? + Cette application est configurée pour continuer à fonctionner après n\'avoir pas été visible ou consultée pendant un certain temps.\n\nNous conseillons d\'autoriser cette application à toujours rester à jour de la blockchain, même en arrière-plan.\n\nNe vous inquiétez pas, nous prendrons soin de l\'utilisation de la batterie. + Ouvrir les Réglages diff --git a/wallet/res/values-id/strings-extra.xml b/wallet/res/values-id/strings-extra.xml index 55d5fff009..3c67d93b03 100644 --- a/wallet/res/values-id/strings-extra.xml +++ b/wallet/res/values-id/strings-extra.xml @@ -252,8 +252,6 @@ Hubungi Dukungan Laporkan masalah Jelajahi - Berbelanja dengan DASH di lebih dari 155.000 pedagang - Buku alamat Impor kunci pribadi Monitor jaringan diff --git a/wallet/res/values-id/strings.xml b/wallet/res/values-id/strings.xml index 2741f78123..c894b7c1f2 100644 --- a/wallet/res/values-id/strings.xml +++ b/wallet/res/values-id/strings.xml @@ -376,4 +376,5 @@ Alasan Tautkan akun Anda Kirim ke alamat - + + diff --git a/wallet/res/values-it/strings-extra.xml b/wallet/res/values-it/strings-extra.xml index 1e0e1dd746..4d62e34661 100644 --- a/wallet/res/values-it/strings-extra.xml +++ b/wallet/res/values-it/strings-extra.xml @@ -270,7 +270,7 @@ Contatta il Supporto Segnala un problema Esplora - Acquista con DASH presso oltre 155.000 commercianti + Trova commercianti che accettano DASH Rubrica degli indirizzi Importa chiave privata diff --git a/wallet/res/values-it/strings.xml b/wallet/res/values-it/strings.xml index baad9fc814..650ee44e26 100644 --- a/wallet/res/values-it/strings.xml +++ b/wallet/res/values-it/strings.xml @@ -358,7 +358,7 @@ Conferme Immediato - Privato - Sicuro - Portafoglio + Wallet Eseguire il backup del portafoglio su file Visualizza la frase di recupero @@ -377,13 +377,21 @@ Motivo Collega il tuo account Acquista Dash · Nessun account necessario - Your Coinbase session has expired - Accedi al tuo account Coinbase - Accedi Indirizzo del destinatario Mostra il contenuto negli appunti Tocca l\'indirizzo dagli appunti per incollarlo Indirizzo Dash o richiesta URL non validi Invia all\'indirizzo + + + Ottimizzazione della batteria + Ottimizzata + Senza restrizioni + + Lasciare che Dash Wallet funzioni in background? + Questa app è configurata per interrompere l\'esecuzione dopo non essere stata visibile per un po\' di tempo.\n\nCiò comporterà la mancata ricezione delle notifiche di pagamento finché l\'app non torna in primo piano.\n\nSi consiglia di consentire l\'esecuzione di questa app in background spuntando ottimizzazione batteria su \"Non ottimizzato\" o \"Illimitato\".\n\nMa non preoccuparti, ci occuperemo sempre di monitorare il consumo della batteria. + Vuoi ricevere pagamenti in background? + Questa app è configurata per continuare a funzionare anche dopo non essere stata visibile per un po\' di tempo.\n\nSuggeriamo di consentire a questa app di rimanere sempre aggiornata con la blockchain, anche in background.\n\nNon preoccuparti, faremo sempre attenzione al consumo della batteria. + Apri Impostazioni diff --git a/wallet/res/values-ja/strings-extra.xml b/wallet/res/values-ja/strings-extra.xml index 2e9f621118..e39143c38d 100644 --- a/wallet/res/values-ja/strings-extra.xml +++ b/wallet/res/values-ja/strings-extra.xml @@ -266,7 +266,7 @@ サポートへ連絡する 問題を報告する 探求する - 155,000以上の加盟店でDASHでショッピング + DASHが使える加盟店を探す アドレスブック 秘密鍵をインポート diff --git a/wallet/res/values-ja/strings.xml b/wallet/res/values-ja/strings.xml index ebb8d818e3..c4c0476222 100644 --- a/wallet/res/values-ja/strings.xml +++ b/wallet/res/values-ja/strings.xml @@ -377,13 +377,21 @@ 理由 お客様のアカウントをリンク Dashを購入 · アカウント不要 - お客様のCoinbaseのセッションは有効期限切れです - お客様のCoinbaseアカウントにログインしてください - ログイン 受取人のアドレス クリップボードのコンテンツを表示する クリップボードからアドレスをタップしてペーストする 無効なDashアドレスやURLリクエスト アドレスに送金する + + + バッテリーの最適化 + 最適化済み + 制限なし + + Dashウォレットをバックグラウンドで起動しますか。 + このアプリは、しばらく表示されないと動作が停止するように設定されています。\n\nこのため、アプリがフォアグラウンドに戻るまで、支払い通知を受け取ることができないようになります。\n\nバッテリー最適化を「最適化しない」または「制限なし」に設定し、このアプリをバックグラウンドで動作させることをお勧めします。\n\nバッテリーの使用量は常に管理していますのでご安心ください。 + バックグラウンドで支払いを受け取りたいですか。 + このアプリは、しばらく表示されなくても動作し続けるように設定されています。\n\nバックグラウンドでも、このアプリが常にブロックチェーンの最新情報を取得できるようにすることをお勧めします。\n\nバッテリーの使用量は常に管理していますのでご安心ください。 + 設定を開く diff --git a/wallet/res/values-ko/strings-extra.xml b/wallet/res/values-ko/strings-extra.xml index 1df2a507d7..f83438f622 100644 --- a/wallet/res/values-ko/strings-extra.xml +++ b/wallet/res/values-ko/strings-extra.xml @@ -264,7 +264,7 @@ 고객 지원부에 문의하기 오류 보고하기 탐색하기 - 155,000개 이상의 상점에서 대시로 쇼핑하세요 + 대시 거래를 허용하는 판매자 찾기 주소록 프라이빗 키 가져오기 diff --git a/wallet/res/values-ko/strings.xml b/wallet/res/values-ko/strings.xml index 0005041dcc..0f7b19c681 100644 --- a/wallet/res/values-ko/strings.xml +++ b/wallet/res/values-ko/strings.xml @@ -377,13 +377,21 @@ 이유 당신의 계정을 연결하세요 대시 구매하기 · 계정이 필요하지 않습니다 - 코인베이스 세션이 만료되었습니다 - 코인베이스 계정에 로그인 하십시오 - 로그인 수신자 주소 클립보드 내용 보이기 클립보드의 주소를 탭하여 붙여넣기 대시 주소 혹은 URL 요청이 유효하지 않습니다 주소로 전송합니다 + + + 배터리 최적화 + 최적화됨 + 제한이 해제됨 + + 대시 지갑을 백그라운드에서 실행하시겠습니까? + 이 어플은 일정 시간동안 화면에 보이지 않은 경우 실행을 중단하도록 설정되었습니다.\n\n이로써 해당 어플이 전면에 실행되기 전까지 지불 알림을 받지 않게 됩니다.\n\n우리는 배터리 최적화를 \"최적화 하지 않음\" 혹은 \"제한 해제\"로 설정하여 이 어플이 백그라운드에서 실행되도록 할 것을 권장합니다.\n\n걱정마세요, 언제나 당신의 배터리 사용량을 신경쓸게요. + 백그라운드에서 지불을 받으시겠습니까? + 이 어플은 일정 시간동안 화면에 보이지 않은 경우에도 게속 실행되도록 설정되었습니다.\n\n우리는 백그라운드에서도 이 어플이 언제나 블록체인의 최신 상태에 맞도록 유지할 것을 권장합니다\n\n걱정마세요, 언제나 당신의 배터리 사용량을 신경쓸게요. + 설정 열기 diff --git a/wallet/res/values-nl/strings-extra.xml b/wallet/res/values-nl/strings-extra.xml index 73a0d3955d..7e1c8587bc 100644 --- a/wallet/res/values-nl/strings-extra.xml +++ b/wallet/res/values-nl/strings-extra.xml @@ -267,7 +267,7 @@ Neem contact op Meld een probleem Verken - Winkel met DASH bij meer dan 155.000 acceptanten + Vind winkeliers die Dash accepteren Adresboek Importeer persoonlijke sleutel diff --git a/wallet/res/values-nl/strings.xml b/wallet/res/values-nl/strings.xml index fbe93bc621..ce1ecabda3 100644 --- a/wallet/res/values-nl/strings.xml +++ b/wallet/res/values-nl/strings.xml @@ -377,13 +377,21 @@ Reden Koppel je rekening Koop Dash · Geen rekening nodig - Je Coinbase sessie is verlopen - Log in bij je Coinbase-account - Log in Adres van ontvanger Toon inhoud op het klembord Tik op het adres van het klembord om het te plakken Geen geldig Dash adres of URL verzoek Versturen naar adres + + + Batterij optimalisatie + Geoptimaliseerd + Onbeperkt + + Dash portemonnee op de achtergrond laten draaien? + Deze app is zo geconfigureerd dat deze niet meer actief is nadat deze enige tijd niet zichtbaar is geweest.\n\nDit heeft tot gevolg dat er geen betalingsmeldingen worden ontvangen totdat de app terugkeert naar de voorgrond.\n\nWe raden aan deze app op de achtergrond te laten draaien door bij batterij instellingen voor \'Niet geoptimaliseerd\' of \'Onbeperkt\' te kiezen.\n\nMaak je geen zorgen, we houden rekening met je batterijgebruik. + Wilt je betalingen op de achtergrond ontvangen? + Deze app is geconfigureerd om actief te blijven nadat hij enige tijd niet zichtbaar is geweest.\n\nWe raden aan deze app altijd up-to-date te laten blijven met de blockchain, zelfs op de achtergrond.\n\nMaak je geen zorgen, we houden rekening met je batterijgebruik. + Open instellingen diff --git a/wallet/res/values-pl/strings-extra.xml b/wallet/res/values-pl/strings-extra.xml index 458e82d04c..e2ace1fcae 100644 --- a/wallet/res/values-pl/strings-extra.xml +++ b/wallet/res/values-pl/strings-extra.xml @@ -273,7 +273,7 @@ Skontaktuj się z pomocą techniczną Zgłoś problem Odkryj - Używaj DASH do robienia zakupów w ponad 155,000 sklepach + Znajdź sprzedawców, którzy akceptują DASH Książka Adresowa Zaimportuj Klucz Prywatny diff --git a/wallet/res/values-pl/strings.xml b/wallet/res/values-pl/strings.xml index e1f162f619..1e426bc552 100644 --- a/wallet/res/values-pl/strings.xml +++ b/wallet/res/values-pl/strings.xml @@ -377,13 +377,21 @@ Powód Połącz swoje konto Kup Dash · Konto nie jest potrzebne - Twoja sesja Coinbase wygasła - Zaloguj się na konto Coinbase - Zaloguj się Adres odbiorcy Pokaz zawartość schowka Naciśnij, adres ze schowka, aby go wkleić Nieprawidłowy adres Dash lub żądanie adresu URL Zapłać na Adres + + + Optymalizacja baterii + Zoptymalizowana + Bez ograniczeń + + Pozwolić, aby Dash Wallet działał w tle? + Ta aplikacja jest skonfigurowana tak, aby przestała działać, gdy nie będzie widoczna przez pewien czas.\n\nSpowoduje to, że powiadomienia o płatnościach nie będą pokazywane, dopóki aplikacja nie powróci na pierwszy plan.\n\nZalecamy zezwolenie tej aplikacji na działanie w tle poprzez ustawienie baterii optymalizacja na „Niezoptymalizowana” lub „Bez ograniczeń”.\n\nNie martw się, zawsze będziemy zwracać uwagę na Twoje zużycie baterii. + Chcesz otrzymywać płatności w tle? + Ta aplikacja jest skonfigurowana tak, aby nadal działać, gdy nie będzie widoczna przez pewien czas.\n\nSugerujemy zezwolenie tej aplikacji na ciągłe aktualizowanie łańcucha bloków, nawet w tle.\n\nNie martw się, zawsze będziemy uważali na poziom zużycia baterii. + Otwórz Ustawienia diff --git a/wallet/res/values-pt/strings-extra.xml b/wallet/res/values-pt/strings-extra.xml index b596bf6920..115c24fe7e 100644 --- a/wallet/res/values-pt/strings-extra.xml +++ b/wallet/res/values-pt/strings-extra.xml @@ -270,8 +270,6 @@ Contato com o Suporte Reportar um problema Explorador - Compre com DASH em mais de 155.000 comércios - Livro de endereços Importar Chave Privada Monitor de rede diff --git a/wallet/res/values-pt/strings.xml b/wallet/res/values-pt/strings.xml index 78784a0b3b..f1c485e4a1 100644 --- a/wallet/res/values-pt/strings.xml +++ b/wallet/res/values-pt/strings.xml @@ -377,13 +377,11 @@ Razão Conectar sua conta Compre Dash · Não é necessário ter uma conta - Sua sessão na Coinbase expirou - Por favor, faça login em sua conta Coinbase - Entrar Endereço do Destinatário Exibir conteúdo na área de transferência  Toque no endereço da área de transferência para colá-lo Endereço Dash inválido ou solicitação de URL inválida Enviar para Endereço - + + diff --git a/wallet/res/values-ru/strings-extra.xml b/wallet/res/values-ru/strings-extra.xml index e6336155f5..9206808cbc 100644 --- a/wallet/res/values-ru/strings-extra.xml +++ b/wallet/res/values-ru/strings-extra.xml @@ -273,7 +273,7 @@ Связаться с поддержкой Сообщить о проблеме Исследовать - Покупайте за DASH в более чем 155 000 магазинах + Найти магазины, где принимают DASH Адресная книга Импортировать приватный ключ diff --git a/wallet/res/values-ru/strings.xml b/wallet/res/values-ru/strings.xml index 722df54336..1b2db1ccbb 100644 --- a/wallet/res/values-ru/strings.xml +++ b/wallet/res/values-ru/strings.xml @@ -377,13 +377,21 @@ Причина Привязать аккаунт Купить Dash · Без учётной записи - Время сессии Coinbase истекло - Пожалуйста, войдите в свой аккаунт Coinbase - Войти Адрес получателя Показать содержимое буфера обмена Чтобы вставить адрес из буфера обмена, нажмите на него Некорректный адрес Dash или URL запрос Отправить на адрес + + + Оптимизация расхода батареи + Оптимизировано + Не ограничено + + Разрешить Dash Wallet работать в фоновом режиме? + Это приложение настроено на автоматическое завершение работы спустя некоторое время после сворачивания.\n\nТаким образом, вы не будете получать платёжные уведомления, пока приложение не вернется к работе в активном режиме.\n\nМы рекомендуем разрешить приложению работу в фоновом режиме, переключив настройку режима питания на \"Не оптимизировано\" или \"Не ограничено\".\n\nНе волнуйтесь, мы всегда помним о расходе батареи. + Хотите получать переводы в фоновом режиме? + Это приложение настроено на продолжение работы спустя некоторое время после сворачивания.\n\nМы рекомендуем разрешить приложению всегда проверять обновления блокчейна, даже в при работе в фоновом режиме.\n\nНе волнуйтесь, мы всегда помним о расходе батареи. + Открыть настройки diff --git a/wallet/res/values-sk/strings-extra.xml b/wallet/res/values-sk/strings-extra.xml index a2fb55bd01..cea96ecd17 100644 --- a/wallet/res/values-sk/strings-extra.xml +++ b/wallet/res/values-sk/strings-extra.xml @@ -273,7 +273,7 @@ Kontaktovať podporu Nahlásiť problém Objaviť - Nakupujte s DASH u viac ako 155 000 obchodníkov + Nájdite obchodníkov, ktorí akceptujú DASH Adresár Importovať privátny Kľúč @@ -401,6 +401,7 @@ Kľúče vlastníka Hlasovacie kľúče Kľúče operátora + Identifikačné kľúče uzla Evolution %d kľúče %d použité Kľúčový pár %d diff --git a/wallet/res/values-sk/strings.xml b/wallet/res/values-sk/strings.xml index 59c6b366eb..436fb6f1eb 100644 --- a/wallet/res/values-sk/strings.xml +++ b/wallet/res/values-sk/strings.xml @@ -377,12 +377,21 @@ Dôvod Pripojiť váš účet Kúpiť Dash · Nie je potrebný žiadny účet - Vaša relácia s Coinbase vypršala - Prihláste sa do svojho Coinbase účtu - Prihlásiť sa + Adresa príjemcu Zobraziť obsah v schránke Klepnutím na adresu zo schránky ju použijete Neplatná Dash adresa alebo URL žiadosť Poslať na adresu + + + Optimalizácia batérie + Optimalizované + Neobmedzené + + Nechať Dash Wallet bežať na pozadí? + Táto aplikácia je nakonfigurovaná tak, aby sa zastavila, keď nebude nejaký čas viditeľná.\n\nV dôsledku toho nebudete dostávať upozornenia na platby, kým sa aplikácia nevráti do popredia.\n\nOdporúčame povoliť tejto aplikácii spustenie na pozadí nastavením batérie optimalizácia na „Neoptimalizované“ alebo „Neobmedzené.“\n\nNebojte sa, vždy nám bude záležať na spotrebe batérie. + Chcete dostávať platby na pozadí? + Táto aplikácia je nakonfigurovaná tak, aby bola spustená aj vtedy, keď nebude nejaký čas viditeľná.\n\nOdporúčame tejto aplikácii povoliť, aby mala vždy aktuálne informácie o blockchaine, a to aj na pozadí.\n\nNebojte sa, vždy nám bude záležať na spotrebe batérie. + Otvoriť nastavenia diff --git a/wallet/res/values-th/strings-extra.xml b/wallet/res/values-th/strings-extra.xml index 5f70aeece5..730d6cc11c 100644 --- a/wallet/res/values-th/strings-extra.xml +++ b/wallet/res/values-th/strings-extra.xml @@ -264,8 +264,6 @@ ติดต่อฝ่ายสนับสนุน รายงานปัญหา สำรวจ - ซื้อสินค้าด้วย Dash ที่มีร้านค้ามากกว่า 155,000 ร้าน - สมุดที่อยู่ นำเข้าคีย์ส่วนตัว จอภาพเครือข่าย diff --git a/wallet/res/values-th/strings.xml b/wallet/res/values-th/strings.xml index 58ed4019ea..57ae217d60 100644 --- a/wallet/res/values-th/strings.xml +++ b/wallet/res/values-th/strings.xml @@ -377,12 +377,11 @@ เหตุผล เชื่อมโยงบัญชีของคุณ ซื้อ Dash · ไม่ต้องใช้บัญชี - เซสชั่น Coinbase ของคุณหมดอายุแล้ว - กรุณาเข้าสู่ระบบบัญชี Coinbase ของคุณ - เข้าสู่ระบบ + ที่อยู่ผู้รับ แสดงเนื้อหาในคลิปบอร์ด แตะที่อยู่จากคลิปบอร์ดเพื่อวาง ไม่ใช่ที่อยู่ Dash หรือคำขอ URL ที่ถูกต้อง ส่งไปยังที่อยู่ - + + diff --git a/wallet/res/values-tr/strings-extra.xml b/wallet/res/values-tr/strings-extra.xml index 46268a98c6..8c5b824dd3 100644 --- a/wallet/res/values-tr/strings-extra.xml +++ b/wallet/res/values-tr/strings-extra.xml @@ -267,8 +267,6 @@ Destekle İletişime Geçin Sorun rapor et Keşfet - 155.000\'den fazla satıcıda DASH ile alışveriş yapın - Adres Rehberi Özel anahtar içeri al Ağ monitörü @@ -395,6 +393,7 @@ Sahip Anahtarları Oy Verme Anahtarları Operatör Anahtarları + Evolution Node Kimlik Anahtarları %d anahtarları %d kullanıldı Tuş Çifti %d diff --git a/wallet/res/values-tr/strings.xml b/wallet/res/values-tr/strings.xml index 051bde3646..b0844907b6 100644 --- a/wallet/res/values-tr/strings.xml +++ b/wallet/res/values-tr/strings.xml @@ -377,12 +377,11 @@ Sebep Hesabınızı bağlayın Dash Satın Al · Hesap gerekmez - Coinbase oturumunuzun süresi doldu - Lütfen Coinbase hesabınıza giriş yapın - Giriş Yap + Alıcı adresi İçeriği panoda göster Yapıştırmak için panodaki adresi seçin Geçerli bir Dash Adresi veya URL isteği değil Adrese Gönder - + + diff --git a/wallet/res/values-uk/strings-extra.xml b/wallet/res/values-uk/strings-extra.xml index 11f0ec2ac4..3535247973 100644 --- a/wallet/res/values-uk/strings-extra.xml +++ b/wallet/res/values-uk/strings-extra.xml @@ -273,8 +273,6 @@ Служба підтримки Повідомити про проблему Дослідити - Купуйте з DASH у понад 155 000 магазинів - Адресна книга Імпорт приватного ключа Мережевий монітор @@ -401,6 +399,7 @@ Ключі власника Ключі голосування Ключі оператора + Ключі ідентифікатора вузла Evolution %d ключів %dвикористано Ключі %d diff --git a/wallet/res/values-uk/strings.xml b/wallet/res/values-uk/strings.xml index 0a4b1be439..36a7af2b38 100644 --- a/wallet/res/values-uk/strings.xml +++ b/wallet/res/values-uk/strings.xml @@ -377,12 +377,21 @@ Причина Прив\'язати ваш акаунт Купити Dash · Обліковий запис не потрібен - Ваш сеанс у Coinbase закінчився - Увійдіть у свій обліковий запис Coinbase - Увійти + Адреса отримувача Показати вміст у буфері обміну Натисніть на адресу з буфера обміну, щоб вставити її Некоректна адреса Dash або URL запит Відправити на адресу + + + Оптимізація батареї + Оптимізовано + Необмежений + + Дозволити Dash Wallet працювати у фоновому режимі? + Цю програму налаштовано так, щоб вона припиняла працювати, якщо деякий час її не видно.\n\nЦе призведе до того, що сповіщення про платежі не надходитимуть, доки програма не повернеться на передній план.\n\nМи рекомендуємо дозволити цій програмі працювати у фоновому режимі, налаштувавши акумулятор оптимізація на «Не оптимізовано» або «Необмежено».\n\nНе хвилюйтеся, ми завжди будемо звертати увагу на використання акумулятора. + Ви хочете отримувати платежі у фоновому режимі? + Ця програма налаштована на продовження роботи після того, як деякий час її не видно.\n\nМи радимо дозволити цій програмі завжди оновлюватися з блокчейном, навіть у фоновому режимі.\n\nНе хвилюйтеся, ми будемо завжди звертати увагу на використання акумулятора. + Відкрити налаштування diff --git a/wallet/res/values-vi/strings.xml b/wallet/res/values-vi/strings.xml index 40e0496639..5a8496bf05 100644 --- a/wallet/res/values-vi/strings.xml +++ b/wallet/res/values-vi/strings.xml @@ -359,4 +359,5 @@ Lọc Gửi đến địa chỉ - + + diff --git a/wallet/res/values-zh-rTW/strings-extra.xml b/wallet/res/values-zh-rTW/strings-extra.xml index 8bad55017c..dea2266a2c 100644 --- a/wallet/res/values-zh-rTW/strings-extra.xml +++ b/wallet/res/values-zh-rTW/strings-extra.xml @@ -264,7 +264,7 @@ 聯繫支援服務 報告問題 瀏覽 - 在超過 155,000 家商家處使用達世幣購物 + 尋找接受達世幣付款的商家 地址簿 導入私鑰 diff --git a/wallet/res/values-zh-rTW/strings.xml b/wallet/res/values-zh-rTW/strings.xml index 0c765ae3c6..5782371c51 100644 --- a/wallet/res/values-zh-rTW/strings.xml +++ b/wallet/res/values-zh-rTW/strings.xml @@ -377,13 +377,21 @@ 理由 連結您的帳戶 購買達世幣 · 無需賬戶 - 您的 Coinbase 工作階段已過期 - 請登錄您的 Coinbase 賬戶 - 登入 收款者位址 顯示剪貼板中的內容 點擊剪貼板中的地址進行粘貼 不是有效的達世幣位址或 URL 請求 發送到位址 + + + 電池優化 + 最佳化 + 無限制 + + 讓達世幣錢包在後台運行嗎? + 此應用程式被設置為在後台運行一段時間後停止運行。\n\n這將導致在應用程式返回前台之前無法接收付款通知。\n\n我們建議透過設定優化電池來允許此應用程式在背景運行優化為「未優化」或「無限制」。\n\n不用擔心,我們將始終提醒您的電池使用情況。 + 您想在後台接收付款嗎? + 此應用程式被設置為在後台運行後繼續運行。\n\n我們建議允許此應用程式始終與區塊鏈保持最新狀態,即使在後台也是如此。\n\n不用擔心,我們會的經常提醒你電池的使用情況。 + 開啟設定 diff --git a/wallet/res/values-zh/strings-extra.xml b/wallet/res/values-zh/strings-extra.xml index d0044f0178..e83b4560a4 100644 --- a/wallet/res/values-zh/strings-extra.xml +++ b/wallet/res/values-zh/strings-extra.xml @@ -264,8 +264,6 @@ 联系支持 报告问题 探索 - 在超过155,000家商户使用Dash购物 - 地址簿 导入私钥 网络监视器 @@ -392,6 +390,7 @@ 所有者私钥 投票者密钥 运营者私钥 + Evolution 节点 ID 私钥 %d 密钥 %d 已使用 密钥对 %d diff --git a/wallet/res/values-zh/strings.xml b/wallet/res/values-zh/strings.xml index 38397629eb..3c06efad04 100644 --- a/wallet/res/values-zh/strings.xml +++ b/wallet/res/values-zh/strings.xml @@ -377,12 +377,21 @@ 理由 关联您的账户 购买Dash · 无需账户 - 您的Coinbase对话已过期 - 请登录您的 Coinbase 账户 - 登录 + 收款人地址 显示剪切板中的内容 点击剪切板中的地址进行粘贴 不是有效的Dash地址或 URL 请求 发送至地址 + + + 电池优化 + 已优化 + 无限制 + + 允许 Dash Wallet 后台运行? + 此 app 被设置为在后台运行一段时间后则停止运行.\n\n这使切换到程序前将无法接收到支付通知.\n\n我们推荐允许此 app 以“未优化” 或 “无限制”的电池优化设置在后台运行.\n\n无需担心, 我们将经常提醒您电池用量. + 您想要在后台接收支付推送么? + 此 app 被设置为在后台运行一段时间后仍继续运行.\n\n我们推荐允许此 app 一直保持更新最新状态, 即便是在后台运行.\n\n无需担心, 我们将经常提醒您电池用量. + 打开设置 From 4b72a8327ba65717a296298d0b78fa8ed4bd631c Mon Sep 17 00:00:00 2001 From: HashEngineering Date: Thu, 16 Nov 2023 11:30:52 -0800 Subject: [PATCH 12/28] v9.1.1 --- wallet/CHANGES | 9 +++++++++ wallet/build.gradle | 4 ++-- 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/wallet/CHANGES b/wallet/CHANGES index 46bd40ee65..405878fe0d 100644 --- a/wallet/CHANGES +++ b/wallet/CHANGES @@ -1,4 +1,13 @@ Dash Wallet +v9.1.1 +* Import Private Key Image Crash Fix +* Add support to change battery optimization +* Decrease shortcut fonts for some languages +* Fix bug where MN keys remained from previous wallet +* Update Coinbase Buy API +* CrowdNode: Increase precision of Withdrawals +* CrowdNode: Fix grammar on How staking works page + v9.1.0 * Add Topper Integration diff --git a/wallet/build.gradle b/wallet/build.gradle index 057632f134..a39cf89474 100644 --- a/wallet/build.gradle +++ b/wallet/build.gradle @@ -189,8 +189,8 @@ android { compileSdk 33 minSdkVersion 23 targetSdkVersion 33 - versionCode project.hasProperty('versionCode') ? project.property('versionCode') as int : 90100 - versionName project.hasProperty('versionName') ? project.property('versionName') : "9.1.0" + versionCode project.hasProperty('versionCode') ? project.property('versionCode') as int : 90110 + versionName project.hasProperty('versionName') ? project.property('versionName') : "9.1.1" multiDexEnabled true generatedDensities = ['hdpi', 'xhdpi'] vectorDrawables.useSupportLibrary = true From fdb53978e37ec5167635785ecd945adda49699b8 Mon Sep 17 00:00:00 2001 From: HashEngineering Date: Wed, 22 Nov 2023 22:15:55 -0800 Subject: [PATCH 13/28] refactor: move explainer dialogs to WalletActivityExt (#1228) * fix crash when request disable battery opt dialog is cancelled --- .../wallet/ui/main/WalletActivity.java | 66 +---------------- .../wallet/ui/main/WalletActivityExt.kt | 70 +++++++++++++++++++ 2 files changed, 72 insertions(+), 64 deletions(-) diff --git a/wallet/src/de/schildbach/wallet/ui/main/WalletActivity.java b/wallet/src/de/schildbach/wallet/ui/main/WalletActivity.java index f465051c2f..3dece8fd6a 100644 --- a/wallet/src/de/schildbach/wallet/ui/main/WalletActivity.java +++ b/wallet/src/de/schildbach/wallet/ui/main/WalletActivity.java @@ -17,24 +17,15 @@ package de.schildbach.wallet.ui.main; -import android.Manifest; import android.content.Context; import android.content.Intent; import android.content.pm.PackageInfo; -import android.content.pm.PackageManager; -import android.net.Uri; import android.nfc.NdefMessage; import android.nfc.NfcAdapter; -import android.os.Build; import android.os.Bundle; import android.os.Handler; import android.os.PowerManager; -import android.provider.Settings; - -import androidx.activity.result.ActivityResultLauncher; -import androidx.activity.result.contract.ActivityResultContracts; import androidx.core.app.ActivityCompat; -import androidx.core.content.ContextCompat; import androidx.lifecycle.ViewModelProvider; import com.google.common.collect.ImmutableList; @@ -125,7 +116,7 @@ public void onStart() { super.onStart(); if (!getLockScreenDisplayed() && configuration.getShowNotificationsExplainer()) { - explainPushNotifications(); + WalletActivityExt.INSTANCE.explainPushNotifications(this); } } @@ -368,60 +359,7 @@ public void onNewKeyChainEncrypted() { @Override public void onLockScreenDeactivated() { if (configuration.getShowNotificationsExplainer()) { - explainPushNotifications(); - } - } - - private final ActivityResultLauncher requestPermissionLauncher = - registerForActivityResult(new ActivityResultContracts.RequestPermission(), granted -> { - // do nothing - requestDisableBatteryOptimisation(); - }); - - /** - * Android 13 - Show system dialog to get notification permission from user, if not granted - * ask again with each app upgrade if not granted. This logic is handled by - * {@link #onLockScreenDeactivated} and {@link #onStart}. - * Android 12 and below - show a explainer dialog once only. - */ - private void explainPushNotifications() { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU && - ContextCompat.checkSelfPermission(this, Manifest.permission.POST_NOTIFICATIONS) != PackageManager.PERMISSION_GRANTED) { - requestPermissionLauncher.launch(Manifest.permission.POST_NOTIFICATIONS); - } else if (configuration.getShowNotificationsExplainer()) { - AdaptiveDialog dialog = AdaptiveDialog.create( - R.drawable.ic_info_blue, - getString(R.string.notification_explainer_title), - getString(R.string.notification_explainer_message), - "", - getString(R.string.button_okay) - ); - - dialog.show(this, result -> { - requestDisableBatteryOptimisation(); - return Unit.INSTANCE; - }); - } - // only show either the permissions dialog (Android >= 13) or the explainer (Android <= 12) once - configuration.setShowNotificationsExplainer(false); - } - - private void requestDisableBatteryOptimisation() { - PowerManager powerManager = getSystemService(PowerManager.class); - if (ContextCompat.checkSelfPermission(walletApplication, Manifest.permission.REQUEST_IGNORE_BATTERY_OPTIMIZATIONS) == PackageManager.PERMISSION_GRANTED && - !powerManager.isIgnoringBatteryOptimizations(walletApplication.getPackageName())) { - AdaptiveDialog.create( - R.drawable.ic_bolt_border, - getString(R.string.battery_optimization_dialog_optimized_title), - getString(R.string.battery_optimization_dialog_message_optimized), - getString(R.string.permission_deny), - getString(R.string.permission_allow) - ).show(this, (allow) -> { - if (allow) { - startActivity(new Intent(Settings.ACTION_REQUEST_IGNORE_BATTERY_OPTIMIZATIONS, Uri.parse("package:" + getPackageName()))); - } - return Unit.INSTANCE; - }); + WalletActivityExt.INSTANCE.explainPushNotifications(this); } } } diff --git a/wallet/src/de/schildbach/wallet/ui/main/WalletActivityExt.kt b/wallet/src/de/schildbach/wallet/ui/main/WalletActivityExt.kt index 278994c2de..4063c389d6 100644 --- a/wallet/src/de/schildbach/wallet/ui/main/WalletActivityExt.kt +++ b/wallet/src/de/schildbach/wallet/ui/main/WalletActivityExt.kt @@ -17,13 +17,20 @@ package de.schildbach.wallet.ui.main +import android.Manifest import android.content.Intent import android.content.IntentFilter +import android.content.pm.PackageManager +import android.net.Uri import android.os.Build import android.os.Bundle +import android.os.PowerManager import android.os.storage.StorageManager import android.provider.Settings import android.view.MenuItem +import androidx.activity.result.ActivityResultLauncher +import androidx.activity.result.contract.ActivityResultContracts +import androidx.core.content.ContextCompat import androidx.core.content.getSystemService import androidx.core.view.isVisible import androidx.lifecycle.lifecycleScope @@ -36,6 +43,7 @@ import de.schildbach.wallet_test.R import kotlinx.coroutines.launch import org.dash.wallet.common.services.analytics.AnalyticsConstants import org.dash.wallet.common.ui.dialogs.AdaptiveDialog +import org.dash.wallet.common.ui.dialogs.AdaptiveDialog.Companion.create import org.dash.wallet.common.util.openCustomTab object WalletActivityExt { @@ -194,4 +202,66 @@ object WalletActivityExt { } } } + + private val WalletActivity.requestPermissionLauncher: ActivityResultLauncher + get() = registerForActivityResult(ActivityResultContracts.RequestPermission()) { + requestDisableBatteryOptimisation() + } + + /** + * Android 13 - Show system dialog to get notification permission from user, if not granted + * ask again with each app upgrade if not granted. This logic is handled by + * [.onLockScreenDeactivated] and [.onStart]. + * Android 12 and below - show a explainer dialog once only. + */ + fun WalletActivity.explainPushNotifications() { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU && + ContextCompat.checkSelfPermission( + this, + Manifest.permission.POST_NOTIFICATIONS + ) != PackageManager.PERMISSION_GRANTED + ) { + requestPermissionLauncher.launch(Manifest.permission.POST_NOTIFICATIONS) + } else if (configuration.showNotificationsExplainer) { + val dialog = create( + R.drawable.ic_info_blue, + getString(R.string.notification_explainer_title), + getString(R.string.notification_explainer_message), + "", + getString(R.string.button_okay) + ) + dialog.show(this) { + requestDisableBatteryOptimisation() + } + } + // only show either the permissions dialog (Android >= 13) or the explainer (Android <= 12) once + configuration.showNotificationsExplainer = false + } + + private fun WalletActivity.requestDisableBatteryOptimisation() { + val powerManager: PowerManager = getSystemService(PowerManager::class.java) + if (ContextCompat.checkSelfPermission( + walletApplication, + Manifest.permission.REQUEST_IGNORE_BATTERY_OPTIMIZATIONS + ) == PackageManager.PERMISSION_GRANTED && + !powerManager.isIgnoringBatteryOptimizations(walletApplication.packageName) + ) { + create( + R.drawable.ic_bolt_border, + getString(R.string.battery_optimization_dialog_optimized_title), + getString(R.string.battery_optimization_dialog_message_optimized), + getString(R.string.permission_deny), + getString(R.string.permission_allow) + ).show(this) { allow: Boolean? -> + if (allow == true) { + startActivity( + Intent( + Settings.ACTION_REQUEST_IGNORE_BATTERY_OPTIMIZATIONS, + Uri.parse("package:$packageName") + ) + ) + } + } + } + } } From d9ba8e717260e6d63e1bcd4ff264536ad2c295b0 Mon Sep 17 00:00:00 2001 From: HashEngineering Date: Tue, 28 Nov 2023 07:08:49 -0800 Subject: [PATCH 14/28] feat(crowdnode): limit withdrawals to once per block (#1229) * feat(crowdnode): limit withdrawals to once per block * fix: eliminate progress dialog after withdraw error (per block) * refactor: remove LiveData from BlockchainStateDao * tests: fix CrowdNodeApiAggregatorTest * chore: replace hard coded text with string resources --- .../common/data/entity/BlockchainState.kt | 2 +- .../crowdnode/api/CrowdNodeApi.kt | 12 +++ .../crowdnode/model/WithdrawalLimit.kt | 3 +- .../ui/dialogs/WithdrawalLimitsInfoDialog.kt | 4 + .../crowdnode/ui/portal/TransferFragment.kt | 51 +++++++------ .../crowdnode/utils/CrowdNodeConfig.kt | 1 + .../src/main/res/values/strings-crowdnode.xml | 2 + .../crowdnode/CrowdNodeApiAggregatorTest.kt | 14 ++-- .../schildbach/wallet/WalletApplication.java | 29 +------- .../wallet/database/dao/BlockchainStateDao.kt | 13 +--- .../wallet/service/BlockchainServiceImpl.java | 48 +++--------- .../service/BlockchainStateDataProvider.kt | 74 ++++++++++++++++++- .../wallet/ui/more/ToolsFragment.kt | 1 + .../wallet/ui/more/ToolsViewModel.kt | 2 +- 14 files changed, 153 insertions(+), 103 deletions(-) diff --git a/common/src/main/java/org/dash/wallet/common/data/entity/BlockchainState.kt b/common/src/main/java/org/dash/wallet/common/data/entity/BlockchainState.kt index f8f1dc97b3..16a3794d6d 100644 --- a/common/src/main/java/org/dash/wallet/common/data/entity/BlockchainState.kt +++ b/common/src/main/java/org/dash/wallet/common/data/entity/BlockchainState.kt @@ -25,7 +25,7 @@ import java.util.* data class BlockchainState(var bestChainDate: Date?, var bestChainHeight: Int, var replaying: Boolean, - var impediments: Set, + var impediments: MutableSet, var chainlockHeight: Int, var mnlistHeight: Int, var percentageSync: Int) { diff --git a/integrations/crowdnode/src/main/java/org/dash/wallet/integrations/crowdnode/api/CrowdNodeApi.kt b/integrations/crowdnode/src/main/java/org/dash/wallet/integrations/crowdnode/api/CrowdNodeApi.kt index 27a84c9fb9..d2990d0475 100644 --- a/integrations/crowdnode/src/main/java/org/dash/wallet/integrations/crowdnode/api/CrowdNodeApi.kt +++ b/integrations/crowdnode/src/main/java/org/dash/wallet/integrations/crowdnode/api/CrowdNodeApi.kt @@ -35,6 +35,7 @@ import org.dash.wallet.common.data.Resource import org.dash.wallet.common.data.ServiceName import org.dash.wallet.common.data.Status import org.dash.wallet.common.data.TaxCategory +import org.dash.wallet.common.services.BlockchainStateProvider import org.dash.wallet.common.services.LeftoverBalanceException import org.dash.wallet.common.services.NotificationService import org.dash.wallet.common.services.TransactionMetadataProvider @@ -94,6 +95,7 @@ class CrowdNodeApiAggregator @Inject constructor( private val config: CrowdNodeConfig, private val globalConfig: Configuration, private val transactionMetadataProvider: TransactionMetadataProvider, + private val blockchainStateProvider: BlockchainStateProvider, @ApplicationContext private val appContext: Context ) : CrowdNodeApi { companion object { @@ -299,6 +301,8 @@ class CrowdNodeApiAggregator @Inject constructor( if (result.messageStatus.lowercase() == MESSAGE_RECEIVED_STATUS) { log.info("Withdrawal request sent successfully") refreshBalance(retries = 3, afterWithdrawal = true) + val currentBlockHeight = blockchainStateProvider.getState()?.bestChainHeight ?: -1 + config.set(CrowdNodeConfig.LAST_WITHDRAWAL_BLOCK, currentBlockHeight) true } else { log.info("Withdrawal request not received, status: ${result.messageStatus}. Result: ${result.result}") @@ -331,6 +335,9 @@ class CrowdNodeApiAggregator @Inject constructor( config.get(CrowdNodeConfig.WITHDRAWAL_LIMIT_PER_DAY) ?: CrowdNodeConstants.WithdrawalLimits.DEFAULT_LIMIT_PER_DAY.value } + else -> { + 0L + } } ) } @@ -824,6 +831,11 @@ class CrowdNodeApiAggregator @Inject constructor( if (withdrawalsLast24h.add(value) > perDayLimit) { throw WithdrawalLimitsException(perDayLimit, WithdrawalLimitPeriod.PerDay) } + val lastWithdrawBlock = config.get(CrowdNodeConfig.LAST_WITHDRAWAL_BLOCK) + val currentBlockHeight = blockchainStateProvider.getState()?.bestChainHeight ?: -1 + if (lastWithdrawBlock != null && lastWithdrawBlock >= currentBlockHeight) { + throw WithdrawalLimitsException(value, WithdrawalLimitPeriod.PerBlock) + } } private fun refreshWithdrawalLimits() { diff --git a/integrations/crowdnode/src/main/java/org/dash/wallet/integrations/crowdnode/model/WithdrawalLimit.kt b/integrations/crowdnode/src/main/java/org/dash/wallet/integrations/crowdnode/model/WithdrawalLimit.kt index 6ca0241033..c769e601b2 100644 --- a/integrations/crowdnode/src/main/java/org/dash/wallet/integrations/crowdnode/model/WithdrawalLimit.kt +++ b/integrations/crowdnode/src/main/java/org/dash/wallet/integrations/crowdnode/model/WithdrawalLimit.kt @@ -30,7 +30,8 @@ data class WithdrawalLimit( enum class WithdrawalLimitPeriod { PerTransaction, PerHour, - PerDay + PerDay, + PerBlock } data class WithdrawalLimitsException( diff --git a/integrations/crowdnode/src/main/java/org/dash/wallet/integrations/crowdnode/ui/dialogs/WithdrawalLimitsInfoDialog.kt b/integrations/crowdnode/src/main/java/org/dash/wallet/integrations/crowdnode/ui/dialogs/WithdrawalLimitsInfoDialog.kt index 43a4afa688..eee4898e8e 100644 --- a/integrations/crowdnode/src/main/java/org/dash/wallet/integrations/crowdnode/ui/dialogs/WithdrawalLimitsInfoDialog.kt +++ b/integrations/crowdnode/src/main/java/org/dash/wallet/integrations/crowdnode/ui/dialogs/WithdrawalLimitsInfoDialog.kt @@ -30,6 +30,7 @@ import org.dash.wallet.common.ui.viewBinding import org.dash.wallet.integrations.crowdnode.R import org.dash.wallet.integrations.crowdnode.databinding.DialogWithdrawalLimitsBinding import org.dash.wallet.integrations.crowdnode.model.WithdrawalLimitPeriod +import java.lang.IllegalArgumentException class WithdrawalLimitsInfoDialog( private val limitPerTx: Coin, @@ -79,6 +80,9 @@ class WithdrawalLimitsInfoDialog( binding.perDayLimit.setTextColor(warningColor) TextViewCompat.setCompoundDrawableTintList(binding.perDayLimit, colorStateLit) } + else -> { + throw IllegalArgumentException("highlightedLimit $highlightedLimit not supported") + } } } diff --git a/integrations/crowdnode/src/main/java/org/dash/wallet/integrations/crowdnode/ui/portal/TransferFragment.kt b/integrations/crowdnode/src/main/java/org/dash/wallet/integrations/crowdnode/ui/portal/TransferFragment.kt index c50884fc33..0de83152a3 100644 --- a/integrations/crowdnode/src/main/java/org/dash/wallet/integrations/crowdnode/ui/portal/TransferFragment.kt +++ b/integrations/crowdnode/src/main/java/org/dash/wallet/integrations/crowdnode/ui/portal/TransferFragment.kt @@ -339,30 +339,39 @@ class TransferFragment : Fragment(R.layout.fragment_transfer) { } private suspend fun showWithdrawalLimitsError(period: WithdrawalLimitPeriod) { - val limits = viewModel.getWithdrawalLimits() - val okButtonText = if (period == WithdrawalLimitPeriod.PerTransaction) { - if (viewModel.onlineAccountStatus == OnlineAccountStatus.Done) { - getString(R.string.read_withdrawal_policy) + if (period == WithdrawalLimitPeriod.PerBlock) { + AdaptiveDialog.create( + R.drawable.ic_warning, + getString(R.string.crowdnode_withdrawal_limits_per_block_title), + getString(R.string.crowdnode_withdrawal_limits_per_block_message), + getString(R.string.button_okay) + ).showAsync(requireActivity()) + } else { + val limits = viewModel.getWithdrawalLimits() + val okButtonText = if (period == WithdrawalLimitPeriod.PerTransaction) { + if (viewModel.onlineAccountStatus == OnlineAccountStatus.Done) { + getString(R.string.read_withdrawal_policy) + } else { + getString(R.string.online_account_create) + } } else { - getString(R.string.online_account_create) + "" } - } else { - "" - } - val doAction = WithdrawalLimitsInfoDialog( - limits[0], - limits[1], - limits[2], - highlightedLimit = period, - okButtonText = okButtonText - ).showAsync(requireActivity()) - - if (doAction == true) { - if (viewModel.onlineAccountStatus == OnlineAccountStatus.Done) { - openWithdrawalPolicy() - } else { - safeNavigate(TransferFragmentDirections.transferToOnlineAccountEmail()) + val doAction = WithdrawalLimitsInfoDialog( + limits[0], + limits[1], + limits[2], + highlightedLimit = period, + okButtonText = okButtonText + ).showAsync(requireActivity()) + + if (doAction == true) { + if (viewModel.onlineAccountStatus == OnlineAccountStatus.Done) { + openWithdrawalPolicy() + } else { + safeNavigate(TransferFragmentDirections.transferToOnlineAccountEmail()) + } } } } diff --git a/integrations/crowdnode/src/main/java/org/dash/wallet/integrations/crowdnode/utils/CrowdNodeConfig.kt b/integrations/crowdnode/src/main/java/org/dash/wallet/integrations/crowdnode/utils/CrowdNodeConfig.kt index c31475aaf2..8cb8cabc5e 100644 --- a/integrations/crowdnode/src/main/java/org/dash/wallet/integrations/crowdnode/utils/CrowdNodeConfig.kt +++ b/integrations/crowdnode/src/main/java/org/dash/wallet/integrations/crowdnode/utils/CrowdNodeConfig.kt @@ -46,5 +46,6 @@ open class CrowdNodeConfig @Inject constructor( val WITHDRAWAL_LIMIT_PER_TX = longPreferencesKey("withdrawal_limit_per_tx") val WITHDRAWAL_LIMIT_PER_HOUR = longPreferencesKey("withdrawal_limit_per_hour") val WITHDRAWAL_LIMIT_PER_DAY = longPreferencesKey("withdrawal_limit_per_day") + val LAST_WITHDRAWAL_BLOCK = intPreferencesKey("last_withdrawal_block") } } diff --git a/integrations/crowdnode/src/main/res/values/strings-crowdnode.xml b/integrations/crowdnode/src/main/res/values/strings-crowdnode.xml index d4464a1098..09a8b869ed 100644 --- a/integrations/crowdnode/src/main/res/values/strings-crowdnode.xml +++ b/integrations/crowdnode/src/main/res/values/strings-crowdnode.xml @@ -143,6 +143,8 @@ Due to CrowdNode’s terms of service users can withdraw no more than: Withdraw without limits with an online account on CrowdNode website. This error is most likely due to exceeding CrowdNode withdrawal limits. Try again later. + Please wait before initiating the next withdrawal + Please wait 5 minutes before initiating another withdrawal. per transaction per hour per 24 hours diff --git a/integrations/crowdnode/src/test/java/org/dash/wallet/integrations/crowdnode/CrowdNodeApiAggregatorTest.kt b/integrations/crowdnode/src/test/java/org/dash/wallet/integrations/crowdnode/CrowdNodeApiAggregatorTest.kt index 7421e42c08..eb029ae18f 100644 --- a/integrations/crowdnode/src/test/java/org/dash/wallet/integrations/crowdnode/CrowdNodeApiAggregatorTest.kt +++ b/integrations/crowdnode/src/test/java/org/dash/wallet/integrations/crowdnode/CrowdNodeApiAggregatorTest.kt @@ -93,7 +93,7 @@ class CrowdNodeApiAggregatorTest { localConfig.stub { onBlocking { get(CrowdNodeConfig.ONLINE_ACCOUNT_STATUS) } doReturn OnlineAccountStatus.Linking.ordinal } - val api = CrowdNodeApiAggregator(webApi, blockchainApi, walletData, mock(), mock(), localConfig, globalConfig, mock(), mock()) // ktlint-disable max-line-length + val api = CrowdNodeApiAggregator(webApi, blockchainApi, walletData, mock(), mock(), localConfig, globalConfig, mock(), mock(), mock()) // ktlint-disable max-line-length api.restoreStatus() api.stopTrackingLinked() @@ -112,7 +112,7 @@ class CrowdNodeApiAggregatorTest { } val api = CrowdNodeApiAggregator( mock(), blockchainApi, walletData, mock(), mock(), - localConfig, globalConfig, mock(), mock() + localConfig, globalConfig, mock(), mock(), mock() ) api.restoreStatus() api.stopTrackingLinked() @@ -139,7 +139,7 @@ class CrowdNodeApiAggregatorTest { blockchainApi.stub { on { getFullSignUpTxSet() } doReturn mockFullSet } - val api = CrowdNodeApiAggregator(webApi, blockchainApi, walletData, mock(), mock(), localConfig, globalConfig, mock(), mock()) // ktlint-disable max-line-length + val api = CrowdNodeApiAggregator(webApi, blockchainApi, walletData, mock(), mock(), localConfig, globalConfig, mock(), mock(), mock()) // ktlint-disable max-line-length api.restoreStatus() assertEquals(SignUpStatus.Finished, api.signUpStatus.value) assertEquals(OnlineAccountStatus.None, api.onlineAccountStatus.value) @@ -159,7 +159,7 @@ class CrowdNodeApiAggregatorTest { } val api = CrowdNodeApiAggregator( webApi, blockchainApi, walletData, mock(), mock(), - localConfig, globalConfig, mock(), mock() + localConfig, globalConfig, mock(), mock(), mock() ) api.restoreStatus() api.refreshBalance() @@ -177,7 +177,7 @@ class CrowdNodeApiAggregatorTest { } val api = CrowdNodeApiAggregator( webApi, blockchainApi, walletData, mock(), mock(), - localConfig, globalConfig, mock(), mock() + localConfig, globalConfig, mock(), mock(), mock() ) api.restoreStatus() assertEquals(SignUpStatus.LinkedOnline, api.signUpStatus.value) @@ -204,7 +204,7 @@ class CrowdNodeApiAggregatorTest { } val api = CrowdNodeApiAggregator( webApi, blockchainApi, walletData, mock(), mock(), - localConfig, globalConfig, mock(), mock() + localConfig, globalConfig, mock(), mock(), mock() ) api.restoreStatus() assertEquals(SignUpStatus.Finished, api.signUpStatus.value) @@ -238,7 +238,7 @@ class CrowdNodeApiAggregatorTest { val api = CrowdNodeApiAggregator( webApi, blockchainApi, walletData, mock(), mock(), - localConfig, globalConfig, mock(), mock() + localConfig, globalConfig, mock(), mock(), mock() ) api.restoreStatus() diff --git a/wallet/src/de/schildbach/wallet/WalletApplication.java b/wallet/src/de/schildbach/wallet/WalletApplication.java index 0cfe198f1f..baf255e3cc 100644 --- a/wallet/src/de/schildbach/wallet/WalletApplication.java +++ b/wallet/src/de/schildbach/wallet/WalletApplication.java @@ -30,7 +30,6 @@ import android.content.ComponentName; import android.content.Context; import android.content.Intent; -import android.database.sqlite.SQLiteException; import android.media.AudioAttributes; import android.net.Uri; import android.os.Build; @@ -52,9 +51,6 @@ import androidx.work.WorkManager; import com.google.common.base.Stopwatch; -import com.google.common.hash.HashCode; -import com.google.common.hash.Hasher; -import com.google.common.hash.Hashing; import org.bitcoinj.core.Address; import org.bitcoinj.core.Coin; @@ -70,7 +66,6 @@ import org.bitcoinj.wallet.Protos; import org.bitcoinj.wallet.UnreadableWalletException; import org.bitcoinj.wallet.Wallet; -import org.bitcoinj.wallet.WalletExtension; import org.bitcoinj.wallet.WalletProtobufSerializer; import org.bitcoinj.wallet.authentication.AuthenticationGroupExtension; import org.bitcoinj.wallet.authentication.AuthenticationKeyUsage; @@ -86,6 +81,7 @@ import org.dash.wallet.features.exploredash.utils.DashDirectConstants; import org.dash.wallet.integrations.coinbase.service.CoinBaseClientConstants; +import de.schildbach.wallet.service.BlockchainStateDataProvider; import de.schildbach.wallet.service.PackageInfoProvider; import de.schildbach.wallet.service.WalletFactory; import de.schildbach.wallet.transactions.MasternodeObserver; @@ -123,8 +119,6 @@ import ch.qos.logback.core.rolling.RollingFileAppender; import ch.qos.logback.core.rolling.TimeBasedRollingPolicy; import dagger.hilt.android.HiltAndroidApp; -import org.dash.wallet.common.data.entity.BlockchainState; -import de.schildbach.wallet.database.dao.BlockchainStateDao; import de.schildbach.wallet.service.BlockchainService; import de.schildbach.wallet.service.BlockchainServiceImpl; import de.schildbach.wallet.service.BlockchainSyncJobService; @@ -186,7 +180,7 @@ public class WalletApplication extends MultiDexApplication @Inject HiltWorkerFactory workerFactory; @Inject - BlockchainStateDao blockchainStateDao; + BlockchainStateDataProvider blockchainStateDataProvider; @Inject CrowdNodeConfig crowdNodeConfig; @Inject @@ -749,9 +743,7 @@ public void stopBlockchainService() { } public void resetBlockchainState() { - Executors.newSingleThreadExecutor().execute(() -> { - blockchainStateDao.save(new BlockchainState(true)); - }); + blockchainStateDataProvider.resetBlockchainState(); } public void resetBlockchain() { @@ -768,20 +760,7 @@ public void resetBlockchain() { } private void resetBlockchainSyncProgress() { - Executors.newSingleThreadExecutor().execute(() -> { - BlockchainState blockchainState; - - try { - blockchainState = blockchainStateDao.loadSync(); - } catch (SQLiteException ex) { - blockchainState = null; - } - - if (blockchainState != null) { - blockchainState.setPercentageSync(0); - blockchainStateDao.save(blockchainState); - } - }); + blockchainStateDataProvider.resetBlockchainSyncProgress(); } public void replaceWallet(final Wallet newWallet) { diff --git a/wallet/src/de/schildbach/wallet/database/dao/BlockchainStateDao.kt b/wallet/src/de/schildbach/wallet/database/dao/BlockchainStateDao.kt index 1e588d45fa..c447f88a7a 100644 --- a/wallet/src/de/schildbach/wallet/database/dao/BlockchainStateDao.kt +++ b/wallet/src/de/schildbach/wallet/database/dao/BlockchainStateDao.kt @@ -17,7 +17,6 @@ package de.schildbach.wallet.database.dao -import androidx.lifecycle.LiveData import androidx.room.Dao import androidx.room.Insert import androidx.room.OnConflictStrategy @@ -32,9 +31,9 @@ import org.dash.wallet.common.data.entity.BlockchainState abstract class BlockchainStateDao { @Insert(onConflict = OnConflictStrategy.REPLACE) - protected abstract fun insert(blockchainState: BlockchainState) + protected abstract suspend fun insert(blockchainState: BlockchainState) - fun save(blockchainState: BlockchainState) { + suspend fun saveState(blockchainState: BlockchainState) { if (blockchainState.replaying && blockchainState.percentageSync == 100) { blockchainState.replaying = false } @@ -42,13 +41,7 @@ abstract class BlockchainStateDao { } @Query("SELECT * FROM blockchain_state LIMIT 1") - abstract fun load(): LiveData - - @Query("SELECT * FROM blockchain_state LIMIT 1") - abstract fun loadSync(): BlockchainState? - - @Query("SELECT * FROM blockchain_state LIMIT 1") - abstract suspend fun get(): BlockchainState? + abstract suspend fun getState(): BlockchainState? @Query("SELECT * FROM blockchain_state LIMIT 1") abstract fun observeState(): Flow diff --git a/wallet/src/de/schildbach/wallet/service/BlockchainServiceImpl.java b/wallet/src/de/schildbach/wallet/service/BlockchainServiceImpl.java index f6a43720f5..722da2ed39 100644 --- a/wallet/src/de/schildbach/wallet/service/BlockchainServiceImpl.java +++ b/wallet/src/de/schildbach/wallet/service/BlockchainServiceImpl.java @@ -87,6 +87,7 @@ import org.dash.wallet.common.transactions.filters.TransactionFilter; import org.dash.wallet.common.services.TransactionMetadataProvider; import org.dash.wallet.common.transactions.TransactionUtils; +import org.dash.wallet.common.util.FlowExtKt; import org.dash.wallet.integrations.crowdnode.api.CrowdNodeAPIConfirmationHandler; import org.dash.wallet.integrations.crowdnode.api.CrowdNodeBlockchainApi; import org.dash.wallet.integrations.crowdnode.transactions.CrowdNodeDepositReceivedResponse; @@ -132,6 +133,9 @@ import de.schildbach.wallet.util.CrashReporter; import de.schildbach.wallet.util.ThrottlingWalletChangeListener; import de.schildbach.wallet_test.R; +import kotlin.Unit; +import kotlin.coroutines.Continuation; +import kotlinx.coroutines.flow.FlowCollector; import static org.dash.wallet.common.util.Constants.PREFIX_ALMOST_EQUAL_TO; @@ -148,6 +152,7 @@ public class BlockchainServiceImpl extends LifecycleService implements Blockchai @Inject CrowdNodeBlockchainApi crowdNodeBlockchainApi; @Inject CrowdNodeConfig crowdNodeConfig; @Inject BlockchainStateDao blockchainStateDao; + @Inject BlockchainStateDataProvider blockchainStateDataProvider; @Inject ExchangeRatesDao exchangeRatesDao; @Inject TransactionMetadataProvider transactionMetadataProvider; @Inject PackageInfoProvider packageInfoProvider; @@ -976,7 +981,10 @@ public void onCreate() { peerDiscoveryList.add(dnsDiscovery); updateAppWidget(); - blockchainStateDao.load().observe(this, this::handleBlockchainStateNotification); + FlowExtKt.observe(blockchainStateDao.observeState(), this, (blockchainState, continuation) -> { + handleBlockchainStateNotification((BlockchainState) blockchainState); + return null; + }); registerCrowdNodeConfirmedAddressFilter(); } @@ -1154,47 +1162,15 @@ private Notification createNetworkSyncNotification(BlockchainState blockchainSta } private void updateBlockchainStateImpediments() { - executor.execute(() -> { - BlockchainState blockchainState = blockchainStateDao.loadSync(); - if (blockchainState != null) { - blockchainState.getImpediments().clear(); - blockchainState.getImpediments().addAll(impediments); - blockchainStateDao.save(blockchainState); - } - }); + blockchainStateDataProvider.updateImpediments(impediments); } private void updateBlockchainState() { - executor.execute(() -> { - BlockchainState blockchainState = blockchainStateDao.loadSync(); - if (blockchainState == null) { - blockchainState = new BlockchainState(); - } - - StoredBlock chainHead = blockChain.getChainHead(); - StoredBlock block = application.getWallet().getContext().chainLockHandler.getBestChainLockBlock(); - int chainLockHeight = block != null ? block.getHeight() : 0; - int mnListHeight = (int) application.getWallet().getContext().masternodeListManager.getListAtChainTip().getHeight(); - - blockchainState.setBestChainDate(chainHead.getHeader().getTime()); - blockchainState.setBestChainHeight(chainHead.getHeight()); - blockchainState.setImpediments(EnumSet.copyOf(impediments)); - blockchainState.setChainlockHeight(chainLockHeight); - blockchainState.setMnlistHeight(mnListHeight); - blockchainState.setPercentageSync(percentageSync()); - - blockchainStateDao.save(blockchainState); - }); + blockchainStateDataProvider.updateBlockchainState(blockChain, impediments, percentageSync()); } public void setBlockchainDownloaded() { - executor.execute(() -> { - BlockchainState blockchainState = blockchainStateDao.loadSync(); - if (blockchainState != null && blockchainState.getPercentageSync() != 100) { - blockchainState.setPercentageSync(100); - blockchainStateDao.save(blockchainState); - } - }); + blockchainStateDataProvider.setBlockchainDownloaded(); } @Override diff --git a/wallet/src/de/schildbach/wallet/service/BlockchainStateDataProvider.kt b/wallet/src/de/schildbach/wallet/service/BlockchainStateDataProvider.kt index 4d45daf1c1..02015ed411 100644 --- a/wallet/src/de/schildbach/wallet/service/BlockchainStateDataProvider.kt +++ b/wallet/src/de/schildbach/wallet/service/BlockchainStateDataProvider.kt @@ -18,12 +18,17 @@ package de.schildbach.wallet.service import android.content.Context +import android.database.sqlite.SQLiteException import dagger.hilt.android.qualifiers.ApplicationContext import de.schildbach.wallet.Constants import de.schildbach.wallet.database.dao.BlockchainStateDao +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.distinctUntilChanged +import kotlinx.coroutines.launch import org.bitcoinj.core.Block +import org.bitcoinj.core.BlockChain import org.bitcoinj.core.CheckpointManager import org.bitcoinj.core.Coin import org.bitcoinj.core.NetworkParameters @@ -32,10 +37,12 @@ import org.bitcoinj.store.BlockStoreException import org.dash.wallet.common.Configuration import org.dash.wallet.common.WalletDataProvider import org.dash.wallet.common.data.entity.BlockchainState +import org.dash.wallet.common.data.entity.BlockchainState.Impediment import org.dash.wallet.common.services.BlockchainStateProvider import java.io.IOException import java.io.InputStream import java.math.BigInteger +import java.util.EnumSet import javax.inject.Inject import kotlin.math.min @@ -67,14 +74,79 @@ class BlockchainStateDataProvider @Inject constructor( const val MASTERNODE_COUNT = 3800 } + private val coroutineScope = CoroutineScope(Dispatchers.IO) + override suspend fun getState(): BlockchainState? { - return blockchainStateDao.loadSync() + return blockchainStateDao.getState() } override fun observeState(): Flow { return blockchainStateDao.observeState().distinctUntilChanged() } + fun updateImpediments(impediments: Set) { + coroutineScope.launch { + val blockchainState = blockchainStateDao.getState() + if (blockchainState != null) { + blockchainState.impediments.clear() + blockchainState.impediments.addAll(impediments) + blockchainStateDao.saveState(blockchainState) + } + } + } + + fun updateBlockchainState(blockChain: BlockChain, impediments: Set, percentageSync: Int) { + coroutineScope.launch { + var blockchainState = blockchainStateDao.getState() + if (blockchainState == null) { + blockchainState = BlockchainState() + } + val chainHead: StoredBlock = blockChain.chainHead + val chainLockHeight = walletDataProvider.wallet!!.context.chainLockHandler.bestChainLockBlockHeight + val mnListHeight: Int = + walletDataProvider.wallet!!.context.masternodeListManager.listAtChainTip.height.toInt() + blockchainState.bestChainDate = chainHead.header.time + blockchainState.bestChainHeight = chainHead.height + blockchainState.impediments = EnumSet.copyOf(impediments) + blockchainState.chainlockHeight = chainLockHeight + blockchainState.mnlistHeight = mnListHeight + blockchainState.percentageSync = percentageSync + blockchainStateDao.saveState(blockchainState) + } + } + + fun setBlockchainDownloaded() { + coroutineScope.launch { + val blockchainState = blockchainStateDao.getState() + if (blockchainState != null && blockchainState.percentageSync != 100) { + blockchainState.percentageSync = 100 + blockchainStateDao.saveState(blockchainState) + } + } + } + + fun resetBlockchainState() { + coroutineScope.launch { + blockchainStateDao.saveState( + BlockchainState(true) + ) + } + } + + fun resetBlockchainSyncProgress() { + coroutineScope.launch { + val blockchainState: BlockchainState? = try { + blockchainStateDao.getState() + } catch (ex: SQLiteException) { + null + } + if (blockchainState != null) { + blockchainState.percentageSync = 0 + blockchainStateDao.saveState(blockchainState) + } + } + } + override fun getLastMasternodeAPY(): Double { val apy = configuration.prefsKeyCrowdNodeStakingApy.toDouble() return if (apy != 0.0) { diff --git a/wallet/src/de/schildbach/wallet/ui/more/ToolsFragment.kt b/wallet/src/de/schildbach/wallet/ui/more/ToolsFragment.kt index 332c3818fa..d86842f0e3 100644 --- a/wallet/src/de/schildbach/wallet/ui/more/ToolsFragment.kt +++ b/wallet/src/de/schildbach/wallet/ui/more/ToolsFragment.kt @@ -46,6 +46,7 @@ import org.dash.wallet.common.ui.BaseAlertDialogBuilder import org.dash.wallet.common.ui.dialogs.AdaptiveDialog import org.dash.wallet.common.ui.viewBinding import org.dash.wallet.common.util.Qr +import org.dash.wallet.common.util.observe import org.slf4j.LoggerFactory import javax.inject.Inject diff --git a/wallet/src/de/schildbach/wallet/ui/more/ToolsViewModel.kt b/wallet/src/de/schildbach/wallet/ui/more/ToolsViewModel.kt index c0a2aa99f2..578fa05275 100644 --- a/wallet/src/de/schildbach/wallet/ui/more/ToolsViewModel.kt +++ b/wallet/src/de/schildbach/wallet/ui/more/ToolsViewModel.kt @@ -42,7 +42,7 @@ class ToolsViewModel @Inject constructor( val blockchainStateDao: BlockchainStateDao, ) : ViewModel() { - val blockchainState = blockchainStateDao.load() + val blockchainState = blockchainStateDao.observeState() val xpub: String val xpubWithCreationDate: String From ad8c81be43d040caef6024acbee592522361793c Mon Sep 17 00:00:00 2001 From: HashEngineering Date: Fri, 1 Dec 2023 11:22:11 -0500 Subject: [PATCH 15/28] chore: update crowdnode text for per block withdrawal limits (#1231) --- .../crowdnode/src/main/res/values/strings-crowdnode.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/integrations/crowdnode/src/main/res/values/strings-crowdnode.xml b/integrations/crowdnode/src/main/res/values/strings-crowdnode.xml index 09a8b869ed..2d4f44d749 100644 --- a/integrations/crowdnode/src/main/res/values/strings-crowdnode.xml +++ b/integrations/crowdnode/src/main/res/values/strings-crowdnode.xml @@ -144,7 +144,7 @@ Withdraw without limits with an online account on CrowdNode website. This error is most likely due to exceeding CrowdNode withdrawal limits. Try again later. Please wait before initiating the next withdrawal - Please wait 5 minutes before initiating another withdrawal. + You need to wait 5 minutes before initiating another withdrawal per transaction per hour per 24 hours From 84a72ddc149a9d7d9d9041cd8fdd42d20ea7d430 Mon Sep 17 00:00:00 2001 From: HashEngineering Date: Mon, 4 Dec 2023 12:49:47 -0500 Subject: [PATCH 16/28] feat(coinbase): hide buy swap from coinbase info and portal screens (#1233) --- .../integrations/coinbase/ui/CoinbaseServicesFragment.kt | 3 +++ .../wallet/ui/buy_sell/IntegrationOverviewFragment.kt | 5 +++++ 2 files changed, 8 insertions(+) diff --git a/integrations/coinbase/src/main/java/org/dash/wallet/integrations/coinbase/ui/CoinbaseServicesFragment.kt b/integrations/coinbase/src/main/java/org/dash/wallet/integrations/coinbase/ui/CoinbaseServicesFragment.kt index 47ef6c962f..6d76ab36ab 100644 --- a/integrations/coinbase/src/main/java/org/dash/wallet/integrations/coinbase/ui/CoinbaseServicesFragment.kt +++ b/integrations/coinbase/src/main/java/org/dash/wallet/integrations/coinbase/ui/CoinbaseServicesFragment.kt @@ -87,6 +87,9 @@ class CoinbaseServicesFragment : Fragment(R.layout.fragment_integration_portal) safeNavigate(CoinbaseServicesFragmentDirections.servicesToBuyDash()) } + // the convert or buy swap feature should be hidden + // as there are not enough supported currencies with the v3 API + binding.convertBtn.isVisible = false binding.convertBtn.setOnClickListener { viewModel.logEvent(AnalyticsConstants.Coinbase.CONVERT_DASH) safeNavigate(CoinbaseServicesFragmentDirections.servicesToConvertCrypto(true)) diff --git a/wallet/src/de/schildbach/wallet/ui/buy_sell/IntegrationOverviewFragment.kt b/wallet/src/de/schildbach/wallet/ui/buy_sell/IntegrationOverviewFragment.kt index cb68ba1df2..2f7d1c298f 100644 --- a/wallet/src/de/schildbach/wallet/ui/buy_sell/IntegrationOverviewFragment.kt +++ b/wallet/src/de/schildbach/wallet/ui/buy_sell/IntegrationOverviewFragment.kt @@ -22,6 +22,7 @@ import android.content.Intent import android.os.Bundle import android.view.View import androidx.activity.result.contract.ActivityResultContracts +import androidx.core.view.isVisible import androidx.fragment.app.Fragment import androidx.fragment.app.viewModels import androidx.lifecycle.lifecycleScope @@ -62,6 +63,10 @@ class IntegrationOverviewFragment : Fragment(R.layout.fragment_integration_overv binding.continueBtn.setOnClickListener { continueCoinbase() } + // the convert or buy swap feature should be hidden + // as there are not enough supported currencies with the v3 API + binding.buyConvertText.isVisible = false + binding.buyConvertIc.isVisible = false } private fun continueCoinbase() { From 623d4eaf6a346c1d7f36f8a57b8f26ea936fb999 Mon Sep 17 00:00:00 2001 From: HashEngineering Date: Fri, 8 Dec 2023 09:48:44 -0600 Subject: [PATCH 17/28] chore: update translations for v10.0.0 (#1238) --- common/src/main/res/values-id/strings.xml | 4 ++- .../res/values-id/strings-explore-dash.xml | 10 ++++++ .../src/main/res/values-de/strings.xml | 7 +++++ .../src/main/res/values-fa/strings.xml | 8 +++++ .../src/main/res/values-id/strings.xml | 12 +++++++ .../src/main/res/values-th/strings.xml | 7 +++++ .../src/main/res/values-tr/strings.xml | 8 +++++ .../main/res/values-de/strings-crowdnode.xml | 4 +++ .../main/res/values-el/strings-crowdnode.xml | 2 ++ .../main/res/values-es/strings-crowdnode.xml | 2 ++ .../main/res/values-fa/strings-crowdnode.xml | 2 ++ .../main/res/values-fil/strings-crowdnode.xml | 2 ++ .../main/res/values-fr/strings-crowdnode.xml | 2 ++ .../main/res/values-id/strings-crowdnode.xml | 2 ++ .../main/res/values-it/strings-crowdnode.xml | 2 ++ .../main/res/values-ja/strings-crowdnode.xml | 2 ++ .../main/res/values-nl/strings-crowdnode.xml | 2 ++ .../main/res/values-pl/strings-crowdnode.xml | 2 ++ .../main/res/values-pt/strings-crowdnode.xml | 3 ++ .../main/res/values-ru/strings-crowdnode.xml | 2 ++ .../main/res/values-sk/strings-crowdnode.xml | 2 ++ .../main/res/values-th/strings-crowdnode.xml | 2 ++ .../main/res/values-tr/strings-crowdnode.xml | 2 ++ .../main/res/values-uk/strings-crowdnode.xml | 2 ++ .../res/values-zh-rTW/strings-crowdnode.xml | 2 ++ .../main/res/values-zh/strings-crowdnode.xml | 2 ++ .../src/main/res/values-id/strings-uphold.xml | 7 +++++ wallet/README.md | 3 ++ wallet/res/values-de/strings-extra.xml | 2 ++ wallet/res/values-de/strings.xml | 12 ++++++- wallet/res/values-fa/strings-extra.xml | 2 ++ wallet/res/values-fa/strings.xml | 17 +++++++++- wallet/res/values-id/strings-extra.xml | 31 ++++++++++++++++++- wallet/res/values-id/strings.xml | 19 +++++++++++- wallet/res/values-pt/strings-extra.xml | 2 ++ wallet/res/values-pt/strings.xml | 12 ++++++- wallet/res/values-th/strings-extra.xml | 2 ++ wallet/res/values-th/strings.xml | 12 ++++++- wallet/res/values-tr/strings-extra.xml | 2 ++ wallet/res/values-tr/strings.xml | 12 ++++++- wallet/res/values-uk/strings-extra.xml | 2 ++ wallet/res/values-zh/strings-extra.xml | 2 ++ 42 files changed, 228 insertions(+), 8 deletions(-) diff --git a/common/src/main/res/values-id/strings.xml b/common/src/main/res/values-id/strings.xml index 9840a23b59..e4e96ec961 100644 --- a/common/src/main/res/values-id/strings.xml +++ b/common/src/main/res/values-id/strings.xml @@ -77,6 +77,7 @@ Jumlah yang terlalu kecil untuk dikirim Dana tidak mencukupi Menyingkronkan: + Tidak ada koneksi internet Tak tersedia @@ -96,4 +97,5 @@ Terputus tersedia Tak tersedia - \ No newline at end of file + masuk + \ No newline at end of file diff --git a/features/exploredash/src/main/res/values-id/strings-explore-dash.xml b/features/exploredash/src/main/res/values-id/strings-explore-dash.xml index a6f4913ccb..f4379dd0a4 100644 --- a/features/exploredash/src/main/res/values-id/strings-explore-dash.xml +++ b/features/exploredash/src/main/res/values-id/strings-explore-dash.xml @@ -18,6 +18,7 @@ Jelajahi Dash Jelajahi + Temukan pedagang yang menerima Dash, tempat membelinya, dan cara memperoleh penghasilan dengannya. Dimana harus Belanja? Temukan pedagang yang menerima Dash sebagai pembayaran. ATM @@ -73,6 +74,7 @@ Metode Pembayaran Dash Kartu Hadiah + Kartu Hadiah Radius Lokasi Lokasi Saat ini @@ -147,5 +149,13 @@ Beli kartu hadiah dengan Dash. Tukarkan kartu hadiah Anda secara online dalam hitungan detik atau di kasir. + + Sinkronisasi sedang berlangsung... Hasil mungkin tidak lengkap. + Sinkronisasi sedang berlangsung... Terjadi kesalahan. + Sinkronisasi dibatalkan... Upaya lain akan dilakukan nanti. + Kesalahan Pembuatan: Basis data tidak ditemukan! + Sinkronisasi terputus... Jaringan sedang offline atau tidak dapat dijangkau + + \ No newline at end of file diff --git a/integrations/coinbase/src/main/res/values-de/strings.xml b/integrations/coinbase/src/main/res/values-de/strings.xml index e212f3c83b..076654b59a 100644 --- a/integrations/coinbase/src/main/res/values-de/strings.xml +++ b/integrations/coinbase/src/main/res/values-de/strings.xml @@ -37,6 +37,7 @@ Coinbase Gebühr Gesamt Vorschau bestellen + Du wirst ~ %s Dash auf der Dash Wallet auf diesem Gerät erhalten. Bitte beachte, dass es 2-3 Minuten dauern kann, bis der Transfer abgeschlossen ist. Betrag in Dash Abbrechen Bestätigen (%ss) @@ -56,8 +57,10 @@ Kauf fehlgeschlagen Übertragung fehlgeschlagen Konvertierung fehlgeschlagen + Die Dash wurden erfolgreich in dein Coinbase Konto eingezahlt. Aber es entstand ein Problem diese auf die Dash Wallet auf diesem Gerät zu transferieren. Es kann 2-3 Minuten dauern, bis die Dash auf die Dash Wallet dieses Gerätes transferiert werden. Es kann bis zu 5 Minuten dauern, um die %s von deinem Coinbase Konto in Dash auf die Dash Wallet deines Gerätes umzuwandeln. + Es kann bis zu 5 Minuten dauern, damit die Dash von der Dash Wallet auf diesem Gerät zu %s auf dein Coinbase Konto übertragen sind. Coinbase Support kontaktieren Gebühren bei Kryptowährungskäufen Zusätzlich zu der angezeigten Coinbase-Gebühr schließen wir einen Spread in den Preis ein. Kryptowährungsmärkte sind volatil, und dies ermöglicht es uns, einen Preis für die Handelsausführung vorübergehend festzulegen. @@ -73,6 +76,8 @@ auf dein Coinbase Konto Von der Dash Wallet auf diesem Gerät Du erhälst + Wir konnten kein Guthaben auf deinem Coinbase Konto entdecken. + Du besitzt keine Kryptowährung. Leg los und kaufe welche. Kauf Kryptowährung auf Coinbase Du hast den Berechtigungsrahmen von Coinbase überschritten. Geld von deinem Konto abbuchen @@ -92,6 +97,7 @@ Verifizieren Coinbase 2FA Code eingeben Dieser zusätzliche Schritt beweist, dass es wirklich du bist der die Transaktion ausführt. + Benötigst du Hilfe? Coinbase 2FA Code Es könnte ein Code aus einer SMS auf deinem Telefon sein. Wenn nicht, trage den Code von deiner Authentifizierung-App ein. Diese Code ist falsch. Bitte kontrollieren und erneut versuchen! @@ -104,6 +110,7 @@ Transfer erfolgreich Es kann bis zu 10 Minuten dauern bis Dash von Coinbase auf die Dash Wallet dieses Gerätes übertragen werden. Es kann bis zu 10 Minuten dauern bis Dash von der Dash Wallet dieses Gerätes  auf Coinbase übertragen werden. + Es entstand ein Problem bei dem Überweisen auf die Dash Wallet dieses Gerätes. Die Menge an Coins in der Wallet ist zu klein um sie zu versenden. Du erhältst %s %s %s Du wirst %s erhalten. diff --git a/integrations/coinbase/src/main/res/values-fa/strings.xml b/integrations/coinbase/src/main/res/values-fa/strings.xml index 5db3b1a666..55d60a0509 100644 --- a/integrations/coinbase/src/main/res/values-fa/strings.xml +++ b/integrations/coinbase/src/main/res/values-fa/strings.xml @@ -37,6 +37,7 @@ کارمزد کوین‌بیس مجموع پیش‌نمایش سفارش + شما %sدش در دش والت‌تان روی همین دستگاه دریافت خواهید کرد. لطفا توجه داشته باشید که تکمیل عملیات انتقال این مبلغ می‌تواند بین ۲ تا ۳ دقیقه زمان ببرد. مبلغ بر حسب دش لغو تائید (%sثانیه) @@ -56,8 +57,10 @@ عملیات خرید ناموفق بود عملیات انتقال ناموفق بود عملیات تبدیل انجام نشد + دش با موفقیت به حساب کوین‌بیس شما انتقال یافت. اما انتقال دش به دش والت روی این دستگاه موفق نبود. عملیات انتقال دش به این دش والت روی این دستگاه ممکن است ۲ تا ۳ دقیقه زمان ببرد. تا حداکثر ۵ دقیقه زمان لازم است تا %s از حساب کوین‌بیس به دش تبدیل شده و به دش والت در دستگاه‌تان انتقال یابد. + تا حداکثر ۵ دقیقه زمان لازم است تا دش از دش والت روی این دستگاه به %sدر حساب کوین‌بیس شما تبدیل شود. با پشتیبانی کوین‌بیس تماس بگیرید کارمزد خرید رمزارز علاوه بر کارمزد نمایش‌داده‌شده در کوین‌بیس، ما مبلغی را به اقیمت اضافه کردیم. بازار رمزارزها نوسانات زیادی دارد و با این کار می‌توانیم قیمت را به صورت موقت برای اجرای تراکنش قفل کنیم. @@ -73,6 +76,8 @@ به حساب کوین‌بیس از دش والت روی این دستگاه دریافت خواهید کرد + هیچ دارایی در حساب کوین‌بیس شما یافت نشد. + هیچ رمزارزی ندارید. برای شروع مقداری رمزارز بخرید خرید رمزارز در کوین‌بیس از حد تعریف‌شده در کوین‌بیس فراتر رفتید. واریز از حساب‌تان @@ -92,6 +97,7 @@ تائید کد 2FA کوین‌بیس را وارد کنید این گام اضافه نشان می‌دهد واقعا خودتان قصد انجام این تراکنش را دارید. + نیاز به کمک دارید؟ کد 2FA کوین‌بیس این کد ممکن است به صورت پیامک به گوشی شما بیاید. در عیر این صورت، از کد اپلیکیشن اعتبارسنجی استفاده کنید. کد وارد شده درست نیست. لطفا دوباره آن را بررسی کرده و وارد کنید! @@ -104,9 +110,11 @@ تراکنش موفق انتقال دش از کوین‌بیس به کیف پول دش روی این دستگاه شاید تا ۱۰ دقیقه زمان ببرد. تا حداکثر ۱۰ دقیقه زمان لازم است تا دش والت شما روی این دستگاه به حساب‌تان در کوین‌بیس انتقال یابد. + انتقال دش از دش والت به این دستگاه با مشکل روبرو شد. مقدار دش در کیف‌ پول‌تان آن قدر نیست که بتوانید انتقال دهید. شما این مقدار دریافت کردید: %s%s%s شما %s دریافت خواهید کرد حساب کوینبیس‌ شما منقضی شده است لطفا وارد حساب کوینبیس‌تان شوید + آیا مایلید مقداری موجودی برای خریدتان با استفاده از حساب بانکی متصل‌شده فراهم کنید؟ \ No newline at end of file diff --git a/integrations/coinbase/src/main/res/values-id/strings.xml b/integrations/coinbase/src/main/res/values-id/strings.xml index a62325df0f..b3fa79cf7e 100644 --- a/integrations/coinbase/src/main/res/values-id/strings.xml +++ b/integrations/coinbase/src/main/res/values-id/strings.xml @@ -37,6 +37,7 @@ Biaya Coinbase Total Pratinjau Pesanan + Anda akan menerima ~ %s Dash di dompet anda pada perangkat ini. Catat, ini mungkin memerlukan sampai 2-3 menit untuk menyelesaikan transfer. Jumlah dalam Dash Batal Konfirmasi (%ss) @@ -56,14 +57,17 @@ Pembelian gagal Transfer Gagal Konversi Gagal + Dash berhasil disetorkan ke akun Coinbase Anda. Namun ada masalah saat mentransfernya ke Dash Wallet di perangkat ini. Diperlukan waktu hingga 2–3 menit agar Dash ditransfer ke Dash Wallet Anda di perangkat ini. Diperlukan waktu hingga 5 menit untuk mengonversi %s dari akun Coinbase ke Dash di Dash Wallet Anda di perangkat ini.. + Diperlukan waktu hingga 5 menit untuk mengonversi Dash dari Dash Wallet di perangkat ini menjadi %s di akun Coinbase anda. Hubungi Dukungan Coinbase Biaya dalam pembelian kripto Selain biaya Coinbase yang ditampilkan, kami menyertakan spread dalam harga. Pasar Cryptocurrency tidak stabil, dan ini memungkinkan kami untuk mengunci harga sementara untuk eksekusi perdagangan. Pelajari lebih lanjut Ada yang salah! Silakan coba lagi nanti atau hubungi pusat dukungan kami untuk mendapatkan bantuan + Antara Dompet Dash dan Coinbase Pratinjau Konversi Anda mengirim Dari akun Coinbase Anda @@ -72,6 +76,8 @@ ke akun Coinbase Anda Dari dompet Dash di perangkat ini Anda akan menerima + Kami tidak menemukan aset apapun di akun Coinbase anda. + Anda tidak mempunyai crypto apapun. Beli beberapa crypto untuk memulai. Beli kripto di Coinbase Anda melampaui batas otorisasi di Coinbase. Debit uang dari akun Anda @@ -91,6 +97,7 @@ Memeriksa Masukkan kode Coinbase 2FA Langkah ekstra ini menunjukkan bahwa Anda benar-benar mencoba melakukan transaksi. + Butuh bantuan? Kode 2FA Coinbase Bisa jadi kode dari SMS di ponsel Anda. Jika tidak, masukkan kode dari aplikasi otentikasi. Kode tidak benar. Silakan periksa dan coba lagi! @@ -103,6 +110,11 @@ Transfer berhasil Diperlukan waktu hingga 10 menit untuk mentransfer Dash dari Coinbase ke dompet Dash perangkat ini. Diperlukan waktu hingga 10 menit untuk mentransfer Dash dari Dash Wallet di perangkat ini ke akun Coinbase Anda. + Ada masalah saat mentransfernya ke Dash Wallet di perangkat ini. Jumlah koin di dompet terlalu kecil untuk ditransfer. Anda akan menerima %s %s %s + Anda akan menerima %s + Sesi Coinbase anda telah kadaluwarsa + Silahkan masuk ke akun Coinbase anda + Apakah Anda ingin melakukan deposit untuk pembelian Anda menggunakan rekening bank yang tertaut? \ No newline at end of file diff --git a/integrations/coinbase/src/main/res/values-th/strings.xml b/integrations/coinbase/src/main/res/values-th/strings.xml index 21dc3c67c7..d087960309 100644 --- a/integrations/coinbase/src/main/res/values-th/strings.xml +++ b/integrations/coinbase/src/main/res/values-th/strings.xml @@ -37,6 +37,7 @@ ค่าธรรมเนียม Coinbase ทั้งหมด คำสั่งซื้อตัวอย่าง + คุณจะได้รับ ~ %s Dash บน Dash Wallet ของคุณบนอุปกรณ์นี้ โปรดทราบว่าอาจใช้เวลาถึง 2-3 นาทีในการโอนให้เสร็จสมบูรณ์ จำนวนเงินใน Dash ยกเลิก ยืนยัน (%ss) @@ -56,8 +57,10 @@ การซื้อไม่สำเร็จ การโอนไม่สำเร็จ การแปลงไม่สำเร็จ + ฝาก Dash ไปยังบัญชี Coinbase ของคุณเรียบร้อยแล้ว แต่มีปัญหาในการโอนไปยัง Dash Wallet บนอุปกรณ์นี้ อาจใช้เวลานานถึง 2-3 นาทีในการโอนไปยังกระเป๋าเงิน Dash ของคุณบนอุปกรณ์นี้ อาจใช้เวลานานถึง 5 นาทีในการแปลง %s จากบัญชี Coinbase เป็น Dash ในกระเป๋าเงิน Dash ของคุณบนอุปกรณ์นี้.. + อาจใช้เวลาถึง 5 นาทีในการแปลง Dash จาก Dash Wallet บนอุปกรณ์นี้เป็น %s ในบัญชี Coinbase ของคุณ ติดต่อฝ่ายสนับสนุน Coinbase ค่าธรรมเนียมในการซื้อคริปโต นอกเหนือจากค่าธรรมเนียม Coinbase ที่แสดงแล้วเรายังรวมถึงสเปรดในราคาตลาด คริปปโตมีความผันผวนและสิ่งนี้ช่วยให้เราสามารถล็อคไว้ชั่วคราวในราคาสำหรับการดำเนินการเทรด @@ -73,6 +76,8 @@ ถึงบัญชี Coinbase ของคุณ จากกระเป๋าเงิน Dash ในอุปกรณ์นี้ คุณจะได้รับ + เราไม่พบสินทรัพย์ใดๆ ในบัญชี Coinbase ของคุณ + คุณไม่ได้เป็นเจ้าของคริปโตใด ๆ ซื้อคริปโตเพื่อเริ่มต้น ซื้อคริปโตบน Coinbase คุณทำรายการเกินขีดจำกัดการอนุญาตของ Coinbase หักเงินจากบัญชีของคุณ @@ -92,6 +97,7 @@ ตรวจสอบ ป้อนรหัส 2FA ของ Coinbase ขั้นตอนพิเศษนี้แสดงให้เห็นว่าคุณพยายามทำธุรกรรม + ต้องการความช่วยเหลือ? รหัส 2FA ของ Coinbase อาจเป็นรหัสจาก SMS บนโทรศัพท์ของคุณ หรืออาจเป็นรหัสจากแอพ authentication รหัสไม่ถูกต้อง กรุณาตรวจสอบและลองอีกครั้ง! @@ -104,6 +110,7 @@ โอนสำเร็จ อาจใช้เวลานานถึง 10 นาทีในการโอน Dash จาก Coinbase ไปยังกระเป๋าเงิน Dash บนอุปกรณ์นี้ อาจใช้เวลานานถึง 10 นาทีในการถ่ายโอน Dash จากกระเป๋าเงิน Dash บนอุปกรณ์นี้ไปยังบัญชี Coinbase ของคุณ + เกิดปัญหาในการโอนไปยัง Dash Wallet บนอุปกรณ์นี้ จำนวนเหรียญในกระเป๋าเงินนั้นน้อยเกินในการโอน คุณจะได้รับ %s %s %s คุณจะได้รับ %s diff --git a/integrations/coinbase/src/main/res/values-tr/strings.xml b/integrations/coinbase/src/main/res/values-tr/strings.xml index ea8bf9eb3b..0cdcd3208b 100644 --- a/integrations/coinbase/src/main/res/values-tr/strings.xml +++ b/integrations/coinbase/src/main/res/values-tr/strings.xml @@ -37,6 +37,7 @@ Coinbase Ücreti Toplam Sipariş Önizleme + Bu cihazdaki Dash Cüzdanınızda ~ %s Dash alacaksınız. Transferin tamamlanması 2-3 dakika sürebileceğini lütfen unutmayın. Dash\'teki Tutar İptal et Onaylayın (%ss) @@ -56,8 +57,10 @@ Satın Alma Başarısız Aktarım Başarısız Dönüştürülemedi + Dash başarıyla Coinbase hesabınıza yatırıldı. Ancak bu cihazdaki Dash Cüzdan\'ına aktarılırken bir sorun oluştu. Dash\'in bu cihazdaki Dash Cüzdanınıza aktarılması 2-3 dakika kadar sürebilir. Coinbase hesabınızdaki %s\'i Dash Cüzdanınızdaki Dash\'e dönüştürmek 5 dakika kadar sürebilir.. + Dash\'i bu cihazdaki Dash Cüzdan’ınızdan Coinbase hesabınızdaki %s\'e dönüştürmek 5 dakika kadar sürebilir. Coinbase Destek Ekibii ile İletişime Geçin Kripto alımlarında ücretler Görüntülenen Coinbase ücretine ek olarak, fiyata bir makas daha dahil ediyoruz. Kripto para piyasaları değişkendir ve bu, ticari işlem için bir fiyatta geçici olarak kilitlememize olanak tanır. @@ -73,6 +76,8 @@ Coinbase hesabınıza Bu cihazdaki Dash Cüzdanınızdan Alacağınız + Coinbase hesabınızda herhangi bir varlık bulamadık. + Herhangi bir kriptoya sahip değilsiniz. Başlamak için biraz kripto satın alın. Coinbase\'de Kripto Satın Alın Coinbase\'deki yetki sınırını aştınız. Hesabınızdan para çekme @@ -92,6 +97,7 @@ Onayla Coinbase 2FA kodunu girin Bu ekstra adım, gerçekten bir işlem yapmaya çalıştığınızı gösterir. + Yardıma mı ihtiyacınız var? Coinbase 2FA kodu Telefonunuzdaki SMS\'den gelen kod olabilir. Değilse, kimlik doğrulama uygulamasından kodu girin. Kod yanlış. Lütfen kontrol edip tekrar deneyin! @@ -104,9 +110,11 @@ Aktarım başarılı Dash\'i Coinbase\'den bu cihazdaki Dash cüzdanına aktarmak 10 dakika kadar sürebilir. Dash\'i bu cihazdaki Dash Cüzdanından Coinbase hesabınıza aktarmak 10 dakika kadar sürebilir. + Bu cihazda Dash Wallet\'a aktarılırken bir sorun oluştu. Cüzdandaki jeton miktarı transfer edilemeyecek kadar az. %s %s %s alacaksınız %s Alacaksınız Coinbase oturumunuzun süresi doldu Lütfen Coinbase hesabınıza giriş yapın + Bağlı bir banka hesabını kullanarak satın alma işleminiz için para yatırmak ister misiniz? \ No newline at end of file diff --git a/integrations/crowdnode/src/main/res/values-de/strings-crowdnode.xml b/integrations/crowdnode/src/main/res/values-de/strings-crowdnode.xml index 1ed0692741..3f950ddf97 100644 --- a/integrations/crowdnode/src/main/res/values-de/strings-crowdnode.xml +++ b/integrations/crowdnode/src/main/res/values-de/strings-crowdnode.xml @@ -94,6 +94,7 @@ %s einzahlen um Erträge zu erhalten Die erste Einzahlung sollte größer als %s sein Wie CrowdNode Staking funktioniert + Das Dash Netzwerk wird durch eine Reihe von Masternodes gesteuert, die eine wesentliche Rolle bei der Erleichterung von Zahlungen spielen. Ein Masternode benötigt 1000 Dash als Einlage und jeder Masternode erhält derzeit rund %s %% pro Jahr. Derzeitige APY ist %s %% Du erhältst Erträge, wenn dein CrowdNode Konto mehr als %s aufweist @@ -106,6 +107,7 @@ Rewards erhalten Du erhältst automatisch Teilzahlungen, die standardmäßig reinvestiert werden. Du kannst jedoch auch automatische Entnahme einrichten, um wiederkehrende Auszahlungen zu erhalten. Erste Mindesteinlage + Da die meisten nicht genau 1000 Dash zur Hand haben, hat CrowdNode einen Service entwickelt der Einlagen von Mitgliedern zusammenlegt, damit diese die Vorteile, die durch das Besitzen eines Masternodes entstehen, nutzen können. Eintritt in den Pool CrowdNode Leistungen Dash Adresse verknüpfen @@ -141,6 +143,8 @@ Aufgrund der CrowdNode AGB können Nutzer nicht mehr als diesen Betrag abheben: Abheben ohne Grenzen mit einem Online Konto auf der CrowdNode Website. Dieser Fehler ist höchstwahrscheinlich darauf zurückzuführen, dass die Auszahlungsgrenzen von CrowdNode überschritten wurden. Versuche es später noch einmal. + Warte bitte, bis du die nächste Auszahlung startest. + Du musst 5 Minuten warten, bevor du die nächste Auszahlung beginnen kannst. pro Transaktion pro Stunde pro 24 Stunden diff --git a/integrations/crowdnode/src/main/res/values-el/strings-crowdnode.xml b/integrations/crowdnode/src/main/res/values-el/strings-crowdnode.xml index 1f12c84df5..74d4dcecf1 100644 --- a/integrations/crowdnode/src/main/res/values-el/strings-crowdnode.xml +++ b/integrations/crowdnode/src/main/res/values-el/strings-crowdnode.xml @@ -143,6 +143,8 @@ Σύμφωνα με τους όρους παροχής υπηρεσιών της CrowdNode οι χρήστες δεν μπορούν να αποσύρουν περισσότερα από: Κάντε ανάληψη χωρίς όρια με έναν online λογαριασμό στον ιστότοπο της CrowdNode. Αυτό το σφάλμα οφείλεται πιθανότατα στην υπέρβαση των ορίων ανάληψης της CrowdNode. Δοκιμάστε ξανά αργότερα. + Παρακαλώ περιμένετε πριν ξεκινήσετε την επόμενη ανάληψη + Πρέπει να περιμένετε 5 λεπτά πριν ξεκινήσετε άλλη ανάληψη. ανά συναλλαγή ανά ώρα ανά 24 ώρες diff --git a/integrations/crowdnode/src/main/res/values-es/strings-crowdnode.xml b/integrations/crowdnode/src/main/res/values-es/strings-crowdnode.xml index f56305b6b7..0a0ae7cf3e 100644 --- a/integrations/crowdnode/src/main/res/values-es/strings-crowdnode.xml +++ b/integrations/crowdnode/src/main/res/values-es/strings-crowdnode.xml @@ -143,6 +143,8 @@ Debido a los términos de servicio de CrowdNode, los usuarios no pueden retirar más de: Retira sin límites con una cuenta en línea en el sitio web de CrowdNode. Lo más probable es que este error se deba a que se excedieron los límites de retiro de CrowdNode. Vuelve a intentarlo más tarde. + Espera antes de iniciar el próximo retiro. + Debes esperar 5 minutos antes de iniciar otro retiro. por transacción por hora por 24 horas diff --git a/integrations/crowdnode/src/main/res/values-fa/strings-crowdnode.xml b/integrations/crowdnode/src/main/res/values-fa/strings-crowdnode.xml index 4be1143406..8b4d0c33d8 100644 --- a/integrations/crowdnode/src/main/res/values-fa/strings-crowdnode.xml +++ b/integrations/crowdnode/src/main/res/values-fa/strings-crowdnode.xml @@ -94,6 +94,7 @@ سپرده‌گذاری %s برای آغاز دریافت پاداش اولین واریزی باید بیشتر از %s باشد. عملکرد استیکینگ کراونود + شبکه دش توسط تعدادی مسترنود اداره می‌شود که بخشی اساسی در تسهیل پرداخت‌ها هستند. هر مسترنود باید هزار دش به عنوان وثیقه داشته باشد و هر مسترنود هر سال تقریبا %s%% پاداش می‌گیرد. بازدهی سالانه کنونی %s%% در صورتی می‌توانید کسب درآمد کنید که موجودی حساب کراودنود شما بیش از %s باشد. @@ -106,6 +107,7 @@ دریافت پاداش مبلغی جزئی را به صورت خودکار دریافت خواهید کرد و به صورت خودکار دوباره سرمایه‌گذاری می‌شود. با این وجود، امکان تنظیم برداشت خودکار برای دریافت پرداخت‌های مکرر به سادگی امکان‌پذیر است. اولین حداقل واریزی + از آنجا که بیشتر کاربران هزار دش در اختیار ندارند، کراودنود این خدمت را فراهم کرده تا با در کنار هم قرار گرفتن موجودی‌ کاربران مختلف، همه آنها بنوانند از مزایای در اختیار داشتن یک مسترنود بهره‌مند شوند. پیوستن به مجموعه مزایای کراودنود اتصال به نشانی دش برقرار است diff --git a/integrations/crowdnode/src/main/res/values-fil/strings-crowdnode.xml b/integrations/crowdnode/src/main/res/values-fil/strings-crowdnode.xml index a4e3b79f1f..82e7571e7d 100644 --- a/integrations/crowdnode/src/main/res/values-fil/strings-crowdnode.xml +++ b/integrations/crowdnode/src/main/res/values-fil/strings-crowdnode.xml @@ -143,6 +143,8 @@ Dahil sa mga tuntunin ng serbisyo ng CrowdNode, ang mga user ay maaaring mag-withdraw ng hindi hihigit sa: Mag-withdraw nang walang limitasyon gamit ang isang online na account sa website ng CrowdNode. Ang error na ito ay malamang na dahil sa paglampas sa mga limitasyon sa pag-withdraw ng CrowdNode. Subukan ulit mamaya. + Mangyaring maghintay bago simulan ang susunod na withdrawal + Kailangan mong maghintay ng 5 minuto bago simulan ang isa pang withdrawal bawat transaksyon bawat oras bawat 24 na oras diff --git a/integrations/crowdnode/src/main/res/values-fr/strings-crowdnode.xml b/integrations/crowdnode/src/main/res/values-fr/strings-crowdnode.xml index a64ec4cdef..710f471912 100644 --- a/integrations/crowdnode/src/main/res/values-fr/strings-crowdnode.xml +++ b/integrations/crowdnode/src/main/res/values-fr/strings-crowdnode.xml @@ -143,6 +143,8 @@ En raison des conditions d\'utilisation CrowdNode, les utilisateurs ne peuvent pas retirer plus de : Retirer sans limite avec un compte en ligne sur le site web CrowdNode. Cette erreur est sans doute due au dépassement des limites de retrait CrowdNode. Veuillez réessayer plus tard. + Veuillez attendre avant de lancer le retrait suivant + Vous devez attendre 5 minutes avant de lancer un autre retrait par transaction par heure par 24 heures diff --git a/integrations/crowdnode/src/main/res/values-id/strings-crowdnode.xml b/integrations/crowdnode/src/main/res/values-id/strings-crowdnode.xml index d244cda2f1..3b362d31ab 100644 --- a/integrations/crowdnode/src/main/res/values-id/strings-crowdnode.xml +++ b/integrations/crowdnode/src/main/res/values-id/strings-crowdnode.xml @@ -94,6 +94,7 @@ Setor %s untuk mulai menghasilkan Setoran pertama harus lebih dari %s Cara kerja staking CrowdNode + Dash Network digerakkan oleh sejumlah Masternode yang merupakan bagian penting dalam memfasilitasi pembayaran. Masternode membutuhkan 1000 Dash sebagai jaminan dan setiap Masternode saat ini dihargai sekitar %s%% per tahun. APY saat ini adalah %s%% Anda mulai menghasilkan jika akun CrowdNode Anda memiliki lebih dari %s @@ -106,6 +107,7 @@ Menerima imbalan Anda akan menerima pembayaran pecahan secara otomatis dan secara default akan diinvestasikan kembali, namun,mudah juga untuk mengatur penarikan otomatis untuk menerima pembayaran berulang. Setoran Minimum Pertama + Karena kebanyakan orang tidak memiliki 1000 Dash, CrowdNode telah membuat layanan di mana, dengan mengumpulkan simpanan dari anggota, mereka dapat memperoleh keuntungan dari memiliki Masternode. Bergabung ke pool Manfaat CrowdNode Alamat Dash terhubung diff --git a/integrations/crowdnode/src/main/res/values-it/strings-crowdnode.xml b/integrations/crowdnode/src/main/res/values-it/strings-crowdnode.xml index 618c579e35..6f1775bc96 100644 --- a/integrations/crowdnode/src/main/res/values-it/strings-crowdnode.xml +++ b/integrations/crowdnode/src/main/res/values-it/strings-crowdnode.xml @@ -143,6 +143,8 @@ A causa dei termini di servizio di CrowdNode, gli utenti possono recedere non più di: Preleva senza limiti con un account online sul sito web di CrowdNode. Questo errore è molto probabilmente dovuto al superamento dei limiti di prelievo di CrowdNode. Riprovare più tardi. + Si prega di attendere prima di avviare il prossimo prelievo + Devi attendere 5 minuti prima di avviare un altro prelievo per transazione all\'ora ogni 24 ore diff --git a/integrations/crowdnode/src/main/res/values-ja/strings-crowdnode.xml b/integrations/crowdnode/src/main/res/values-ja/strings-crowdnode.xml index 1410710cc2..25624b79f1 100644 --- a/integrations/crowdnode/src/main/res/values-ja/strings-crowdnode.xml +++ b/integrations/crowdnode/src/main/res/values-ja/strings-crowdnode.xml @@ -143,6 +143,8 @@ CrowdNodeの利用規約により、ユーザーはこれ以上出金できません。 CrowdNodeのウェブサイトからオンラインアカウントで無制限に出金することができます。 このエラーは、CrowdNodeの出金限度額を超えているためと考えられます。後でもう一度お試しください。 + 次の出金を開始する前に少々お待ちください + 5分間待ってから次の出金を開始しましょう 1取引あたり 1時間あたり 24時間あたり diff --git a/integrations/crowdnode/src/main/res/values-nl/strings-crowdnode.xml b/integrations/crowdnode/src/main/res/values-nl/strings-crowdnode.xml index b9cd43668d..f66422bed5 100644 --- a/integrations/crowdnode/src/main/res/values-nl/strings-crowdnode.xml +++ b/integrations/crowdnode/src/main/res/values-nl/strings-crowdnode.xml @@ -143,6 +143,8 @@ Vanwege de servicevoorwaarden van CrowdNode kunnen gebruikers niet meer opnemen dan: Neem onbeperkt geld op met een online account via de CrowdNode website. Deze fout is hoogstwaarschijnlijk te wijten aan het overschrijden van het opnamelimiet van CrowdNode. Probeer het later opnieuw. + Wacht voordat je de volgende opname uitvoert + Je moet 5 minuten wachten voordat je een nieuwe opname kunt uitvoeren per transactie per uur per 24 uur diff --git a/integrations/crowdnode/src/main/res/values-pl/strings-crowdnode.xml b/integrations/crowdnode/src/main/res/values-pl/strings-crowdnode.xml index 87cf502e33..a733531b35 100644 --- a/integrations/crowdnode/src/main/res/values-pl/strings-crowdnode.xml +++ b/integrations/crowdnode/src/main/res/values-pl/strings-crowdnode.xml @@ -143,6 +143,8 @@ Zgodnie z warunkami korzystania z usługi CrowdNode użytkownicy mogą wypłacić nie więcej niż: Wypłacaj bez ograniczeń za pomocą konta online na stronie CrowdNode. Ten błąd jest najprawdopodobniej spowodowany przekroczeniem limitów wypłat CrowdNode. Spróbuj ponownie później. + Proszę poczekać przed rozpoczęciem kolejnej wypłaty + Musisz odczekać 5 minut przed rozpoczęciem kolejnej wypłaty w jednej transakcji na godzinę na 24 godzin diff --git a/integrations/crowdnode/src/main/res/values-pt/strings-crowdnode.xml b/integrations/crowdnode/src/main/res/values-pt/strings-crowdnode.xml index ce503d7244..9125759387 100644 --- a/integrations/crowdnode/src/main/res/values-pt/strings-crowdnode.xml +++ b/integrations/crowdnode/src/main/res/values-pt/strings-crowdnode.xml @@ -107,6 +107,7 @@ Recebendo recompensas Você receberá pagamentos fracionados automaticamente e eles serão reinvestidos por padrão, no entanto, também é fácil configurar saques automáticos para receber pagamentos recorrentes. Primeiro Depósito Mínimo + Como a maioria das pessoas não possui exatamente 1000 Dash em mãos, o CrowdNode criou um serviço no qual, reunindo os depósitos dos membros, eles podem obter os benefícios de possuir um Masternode. Entrando no \"pool\" Benefícios CrowdNode Endereço Dash conectado @@ -142,6 +143,8 @@ Devido aos termos de serviço do CrowdNode, os usuários não podem retirar mais do que: Saque sem limites com uma conta online no site do CrowdNode. Este erro ocorre provavelmente devido a exceder os limites de retirada do CrowdNode. Tente mais tarde. + Por favor, aguarde antes de iniciar o próximo saque + Você precisa aguardar 5 minutos antes de iniciar outro saque por transação por hora por 24 horas diff --git a/integrations/crowdnode/src/main/res/values-ru/strings-crowdnode.xml b/integrations/crowdnode/src/main/res/values-ru/strings-crowdnode.xml index d96dcb43a7..ebee242f66 100644 --- a/integrations/crowdnode/src/main/res/values-ru/strings-crowdnode.xml +++ b/integrations/crowdnode/src/main/res/values-ru/strings-crowdnode.xml @@ -143,6 +143,8 @@ Из-за условий использования CrowdNode пользователи не могут вывести больше, чем: Вывести без ограничений в онлайн аккаунте на сайте CrowdNode. Скорее всего, ошибка произошла из-за превышения лимита CrowdNode на вывод. Попробуйте ещё раз позже. + Пожалуйста, подождите, прежде чем запустить следующий вывод средств + Вы должны подождать 5 минут, прежде чем вывести средства ещё раз за транзакцию в час в 24 часа diff --git a/integrations/crowdnode/src/main/res/values-sk/strings-crowdnode.xml b/integrations/crowdnode/src/main/res/values-sk/strings-crowdnode.xml index 57601943b3..590665283c 100644 --- a/integrations/crowdnode/src/main/res/values-sk/strings-crowdnode.xml +++ b/integrations/crowdnode/src/main/res/values-sk/strings-crowdnode.xml @@ -143,6 +143,8 @@ Vzhľadom na zmluvné podmienky CrowdNode môžu používatelia vybrať maximálne: Výber bez obmedzení s online účtom na webe CrowdNode. Táto chyba je s najväčšou pravdepodobnosťou spôsobená prekročením limitov výberu CrowdNode. Skúste to neskôr. + Pred spustením ďalšieho výberu prosím počkajte + Pred ďalším výberom musíte počkať 5 minút za transakciu za hodinu za 24 hodín diff --git a/integrations/crowdnode/src/main/res/values-th/strings-crowdnode.xml b/integrations/crowdnode/src/main/res/values-th/strings-crowdnode.xml index c438a58aff..66f5ec9b15 100644 --- a/integrations/crowdnode/src/main/res/values-th/strings-crowdnode.xml +++ b/integrations/crowdnode/src/main/res/values-th/strings-crowdnode.xml @@ -94,6 +94,7 @@ ฝากเงิน %s เพื่อเริ่มต้นหารายได้ เงินฝากครั้งแรกควรมากกว่า %s การ staking CrowdNode ทำงานอย่างไร + Dash Network ขับเคลื่อนโดย Masternodes จำนวนหนึ่งซึ่งเป็นส่วนสำคัญในการอำนวยความสะดวกในการชำระเงิน มาสเตอร์โหนดต้องการ 1000 Dash เป็นหลักประกันและแต่ละ มาสเตอร์โหนดต้องการจะได้รับรางวัลประมาณ %s%%ต่อปี APY ปัจจุบันคือ %s%% คุณเริ่มมีรายได้หากบัญชี CrowdNode ของคุณมีมากกว่า %s @@ -106,6 +107,7 @@ การรับรีวอร์ด คุณจะได้รับการชำระเงินแบบเศษส่วนโดยอัตโนมัติและโดยค่าเริ่มต้นจะถูกนำกลับมาลงทุนใหม่อย่างไรก็ตามมันเป็นเรื่องง่ายที่จะตั้งค่าการถอนอัตโนมัติเพื่อรับการจ่ายเงินที่เกิดขึ้นซ้ำ เงินฝากขั้นต่ำขั้นแรก + เนื่องจากคนส่วนใหญ่ไม่มี 1,000 Dash อยู่ในมือ CrowdNode ได้ให้บริการโดยรวบรวมเงินฝากจากสมาชิก พวกเขาสามารถบรรลุผลประโยชน์ของการเป็นเจ้าของ Masternode เข้าร่วม pool ผลประโยชน์ของ CrowdNode ที่อยู่ Dash ที่เชื่อมต่อ diff --git a/integrations/crowdnode/src/main/res/values-tr/strings-crowdnode.xml b/integrations/crowdnode/src/main/res/values-tr/strings-crowdnode.xml index b0e44d983b..47fe945cac 100644 --- a/integrations/crowdnode/src/main/res/values-tr/strings-crowdnode.xml +++ b/integrations/crowdnode/src/main/res/values-tr/strings-crowdnode.xml @@ -94,6 +94,7 @@ Kazanmaya başlamak için %s yatırın İlk depozito %s\'den fazla olmalıdır CrowdNode staking nasıl çalışır? + Dash Ağı, ödemeleri kolaylaştırmanın önemli bir parçası olan bir dizi Ana Düğüm tarafından yönlendirilmektedir. Bir Ana Düğüm teminat olarak 1000 Dash\'a ihtiyaç duyar ve her Ana Düğüm şu anda yılda yaklaşık %s%% ödüllendirilir. Güncel YFG: %s%% CrowdNode hesabınızda %s\'den fazla varsa kazanmaya başlarsınız @@ -106,6 +107,7 @@ Ödüller alınıyor Kısmı ödemeleri otomatik olarak alacaksınız ve bunlar yeniden yatırım için kullanılacak. Tekrarlayan ödemeleri almak için otomatik para çekme işlemlerini ayarlamak kolaydır. İlk Asgari Para Yatırma + Çoğu insanın elinde 1000 Dash bulunmadığından Crowdnode üyelerinden gelen yatırma işlemlerini bir havuzda toplayarak ana düğüm sahibi olmanın avantajlarından yararlanabilecekleri bir hizmet sunmuştur. Havuza katılmak CrowdNode avantajları Bağlantılı Dash adresleri diff --git a/integrations/crowdnode/src/main/res/values-uk/strings-crowdnode.xml b/integrations/crowdnode/src/main/res/values-uk/strings-crowdnode.xml index 83c5f49f32..0501605969 100644 --- a/integrations/crowdnode/src/main/res/values-uk/strings-crowdnode.xml +++ b/integrations/crowdnode/src/main/res/values-uk/strings-crowdnode.xml @@ -143,6 +143,8 @@ Відповідно до умов обслуговування CrowdNode користувачі можуть знімати не більше ніж: Знімайте без обмежень за допомогою онлайн-облікового запису на веб-сайті CrowdNode. Ця помилка, швидше за все, пов’язана з перевищенням обмежень на зняття з CrowdNode. Спробуйте ще раз пізніше. + Будь ласка, зачекайте перш ніж починати наступне виведення + Вам потрібно зачекати 5 хвилин, перш ніж почати наступне виведення за транзакцію за годину за 24 години diff --git a/integrations/crowdnode/src/main/res/values-zh-rTW/strings-crowdnode.xml b/integrations/crowdnode/src/main/res/values-zh-rTW/strings-crowdnode.xml index b9bc1dfc6b..4abf162036 100644 --- a/integrations/crowdnode/src/main/res/values-zh-rTW/strings-crowdnode.xml +++ b/integrations/crowdnode/src/main/res/values-zh-rTW/strings-crowdnode.xml @@ -143,6 +143,8 @@ 由於 CrowdNode 的服務條款,用戶的提款額不能超過: 使用 CrowdNode 網站上的在線帳戶可以無限制地提款。 此錯誤很可能是由於超出了 CrowdNode 提款上限。請稍後再試。 + 請稍候片刻然後再開始下次提款 + 您需要等待5分鐘才能再次發起提款 每筆交易 每小時 每 24 小時 diff --git a/integrations/crowdnode/src/main/res/values-zh/strings-crowdnode.xml b/integrations/crowdnode/src/main/res/values-zh/strings-crowdnode.xml index 25314635ca..7722ffb35f 100644 --- a/integrations/crowdnode/src/main/res/values-zh/strings-crowdnode.xml +++ b/integrations/crowdnode/src/main/res/values-zh/strings-crowdnode.xml @@ -143,6 +143,8 @@ 由于 CrowdNode的服务条款, 用户的提款额不能超过: 使用 CrowdNode网站上的在线账户可以无限制地提款. 此错误很可能是由于超出了 CrowdNode提款上限. 请稍后再试. + 请稍等片刻再开始下次提现 + 您需要等待5分钟才能再次发起提现 每笔交易 每小时 每24小时 diff --git a/integrations/uphold/src/main/res/values-id/strings-uphold.xml b/integrations/uphold/src/main/res/values-id/strings-uphold.xml index 77ebf45c74..7d20f5acd2 100644 --- a/integrations/uphold/src/main/res/values-id/strings-uphold.xml +++ b/integrations/uphold/src/main/res/values-id/strings-uphold.xml @@ -2,13 +2,18 @@ Uphold + Puncak Tautkan akun Uphold anda + Didukung oleh Topper + Didukung oleh Uphold Dari Uphold ke Dompet Dash anda Beli/Jual Dash dengan Uphold Beli Dash dengan akun Uphold Beli Dash Transfer dari Uphold Tautkan Akun Uphold + Saldo Dash di Uphold + Dari Uphold ke Dompet Dash Biaya akan dipotong dari jumlah yang diminta. Masukkan jumlah yang akan ditransfer Berhasil @@ -17,10 +22,12 @@ Transfer Lihat di Uphold Keluar dari Uphold + Putuskan akun Uphold Keluar Anda juga harus keluar dari situs web Uphold menggunakan browser Anda Pergi ke Situs Web Uphold Error + Dompet Dash tidak lagi terhubung ke akun Uphold anda. Layanan Uphold tidak tersedia. Silakan coba lagi nanti. Silakan laporkan masalah ini ke tim dukungan Dash Wallet menggunakan "Laporkan Masalah" dari menu utama. diff --git a/wallet/README.md b/wallet/README.md index 1221caadb4..ba3e7d20ad 100644 --- a/wallet/README.md +++ b/wallet/README.md @@ -227,6 +227,9 @@ As soon as a translation is ready, it can be pulled: # pull translation from Transifex tx pull -f -l + # pull all translations > 50% complete from Transifex + tx pull -f --minimum-perc=50 + Note that after pulling, any bugs introduced by either translators or Transifex itself need to be corrected manually. diff --git a/wallet/res/values-de/strings-extra.xml b/wallet/res/values-de/strings-extra.xml index a5f8ac5e0e..407d8e461c 100644 --- a/wallet/res/values-de/strings-extra.xml +++ b/wallet/res/values-de/strings-extra.xml @@ -267,6 +267,8 @@ Support kontaktieren Ein Problem melden Explore + Finde Händler die DASH akzeptieren + Adressbuch Privaten Schlüssel importieren Netzwerk-Monitor diff --git a/wallet/res/values-de/strings.xml b/wallet/res/values-de/strings.xml index 63e9533e19..dc53c8aac0 100644 --- a/wallet/res/values-de/strings.xml +++ b/wallet/res/values-de/strings.xml @@ -385,4 +385,14 @@ Keine gültige Dash Adresse oder URL-Anfrage An Adresse senden - + + Akku-Optimierung + Optimiert + Unbeschränkt + + Dash Wallet im Hintergrund laufen lassen? + Diese App wurde so eingestellt, dass sie endet, wenn sie seit langem nicht mehr angeschaut wurde.\n\nDies führt dazu, dass Zahlungsbenachrichtigungen nicht erhalten werden bis die App wieder im Vordergrund erscheint.\n\nWir empfehlen diese App im Hintergrund laufen zu lassen und die Akkuoptimierung zu \"Nicht optimiert\" oder \"Unbeschränkt\" zu ändern.\n\nMach dir keine Sorgen. Wir achten immer auf ausgewogene Akkunutzung. + Möchtest du Zahlungen im Hintergrund erhalten? + Diese App wurde so eingestellt, dass sie endet, wenn sie seit langem nicht mehr angeschaut wurde.\n\nWir empfehlen, diese App immer up-to-date mit der Blockchain zu halten, selbst wenn sie im Hintergrund läuft.\n\nMach dir keine Sorgen. Wir achten immer auf ausgewogene Akkunutzung. + Einstellungen öffnen + diff --git a/wallet/res/values-fa/strings-extra.xml b/wallet/res/values-fa/strings-extra.xml index 527bb44aee..8a2027e647 100644 --- a/wallet/res/values-fa/strings-extra.xml +++ b/wallet/res/values-fa/strings-extra.xml @@ -269,6 +269,8 @@ تماس با پشتیبانی گزارش مشکل کاوش + فروشنده‌ای بیابید که دش می‌پذیرد + دفترچه نشانی‌ها وارد کردن کلید خصوصی پایش شبکه diff --git a/wallet/res/values-fa/strings.xml b/wallet/res/values-fa/strings.xml index 56a5ac48e1..baf728b4e1 100644 --- a/wallet/res/values-fa/strings.xml +++ b/wallet/res/values-fa/strings.xml @@ -385,4 +385,19 @@ نشانی دش و یا نشانی وب درخواستی معتبر نیست ارسال به نشانی - + + بهینه‌سازی مصرف باتری + بهینه‌شده + بدون محدودیت + + اجازه می‌دهید دش والت در پس‌زمینه فعال باشد؟ + این اپلیکیشن به شکلی تنظیم شده که پس از مدتی که در دید نبود، دیگر کار نکند. +این تنظیم سبب می‌شود اطلاعیه‌های واریزی تا زمانی که اپلیکیشن از پس‌زمینه فراخوانده نشده، برایتان نمایش داده نشود +پیشنهاد می‌کنیم اجازه دهید این اپلیکیشن در پس‌زمینه همچنان کار کند. برای این تنظیم کافی است بهینه‌سازی باتری را به حالت «بهینه‌نشده» یا «بدون محدودیت» تغییر دهید. +نگران نباشید! ما همیشه به مقدار مصرف باتری شما اهمیت می‌دهیم. + آیا می‌خواهید در پس‌زمینه،واریزی‌هایتان را دریافت کنید؟ + این اپلیکیشن به شکلی تنظیم شده که پس از مدتی که در دید نبود، همچنان به کارکرد ادامه دهد. +پیشنهاد می‌کنیم اجازه دهید این اپلیکیشن همچنان با بلاکچین هماهنگ باشد، حتی در مواقعی که در پس‌زمینه فعال است. +نگران نباشید! ما همیشه به مقدار مصرف باتری شما اهمیت می‌دهیم. + باز کردن تنظیمات + diff --git a/wallet/res/values-id/strings-extra.xml b/wallet/res/values-id/strings-extra.xml index 3c67d93b03..d8d643eb00 100644 --- a/wallet/res/values-id/strings-extra.xml +++ b/wallet/res/values-id/strings-extra.xml @@ -26,6 +26,8 @@ Biaya jaringan Total + + Cadangan Pindai @@ -198,6 +200,12 @@ Teruskan Cadangkan Dompet Tampilkan Frasa pemulihan + Cadangkan frasa pemulihan anda + Anda memerlukan frasa pemulihan ini untuk mengakses dana Anda jika perangkat ini hilang, rusak, atau jika Dash Wallet dihapus dari perangkat ini. + Dash Core Group TIDAK menyimpan frasa pemulihan ini + Siapa saja yang mempunyai frasa pemulihan anda dapat mengakses dana anda. + Anda TIDAK akan bisa memulihkan dompet tanpa frasa pemulihan + Tulis itu pada tempat yang aman dan jangan tunjukkan pada siapapun. Ketuk kata-kata dari frasa pemulihan Anda dalam urutan yang benar Berhasil Diverifikasi Dompet Anda aman sekarang. Anda dapat menggunakan frasa pemulihan kapan saja untuk memulihkan akun di perangkat lain. @@ -231,6 +239,10 @@ Pindah dari Pindah ke internal Transaksi gagal + Hal ini dapat diatasi dengan + memindai ulang blockchain + Hal ini dimungkinkan jika dompet dipasang pada dua perangkat. + Membuktikan Konfirmasikan sidik jari untuk melanjutkan Masukkan PIN Anda untuk melanjutkan @@ -252,6 +264,8 @@ Hubungi Dukungan Laporkan masalah Jelajahi + Temukan toko yang menerima Dash + Buku alamat Impor kunci pribadi Monitor jaringan @@ -261,6 +275,7 @@ Tinjau dan beri peringkat aplikasi Hubungi Dukungan Ini adalah aplikasi open source yang bercabang dari Bitcoin Wallet + Hak Cipta © 2023 Dash Core Group GNU General Public License v3.0 Dash Wallet %s Sinkronisasi perangkat Explore Terakhir @@ -342,6 +357,7 @@ Kemarin Menyingkronkan: + Menyinkronkan Saldo %dtransaksi Semua Diterima @@ -372,10 +388,23 @@ secara default semua transaksi ke atau dari akun tertaut Anda (pertukaran, staking, dll) akan ditandai sebagai transfer. Pilih transaksi apa pun dari beranda dan ubah kategori di halaman detail transaksi. + Kunci Masternode Kunci Pemilik Kunci pemungutan suara Kunci Operator + Kunci Evolution Node ID + %d kunci + %d digunakan + Keypair %d Alamat + Id Kunci + Kunci publik + Kunci publik (legacy) + Kunci pribadi + Kunci pribadi WIF + IP: %s Tidak diketahui + Tidak digunakan Dicabut - \ No newline at end of file + Kunci private / publik (base64) + \ No newline at end of file diff --git a/wallet/res/values-id/strings.xml b/wallet/res/values-id/strings.xml index c894b7c1f2..2f504e2505 100644 --- a/wallet/res/values-id/strings.xml +++ b/wallet/res/values-id/strings.xml @@ -370,11 +370,28 @@ Dana tidak mencukupi Anda tidak memiliki dana yang cukup umtuk memyelesaikan pembayaran ini + Pilih layanan untuk membeli, menjual, menukar dan mentransfer Dash Pilih Layanan Menyaring Hapus Filter Alasan Tautkan akun Anda + Beli Dash · Tidak memerlukan akun + + Alamat penerima + Tunjukkan konten dalam clipboard + Ketuk alamat dari clipboard untuk menempelkannya + Bukan Alamat Dash atau permintaan URL yang valid. Kirim ke alamat - + + Pengoptimalan baterai + Optimal + Tidak dibatasi + + Biarkan Dompet Dash berjalan di latar belakang? + Aplikasi ini dikonfigurasi untuk berhenti berjalan setelah tidak terlihat selama beberapa waktu.\n\nHal ini akan mengakibatkan tidak menerima notifikasi pembayaran hingga aplikasi kembali ke latar depan.\n\nSebaiknya izinkan aplikasi ini berjalan di latar belakang dengan menyetel baterai pengoptimalan menjadi \"Tidak Dioptimalkan\" atau \"Tidak Dibatasi\".\n\nJangan khawatir, kami akan selalu memperhatikan penggunaan baterai Anda. + Apakah anda ingin menerima pembayaran di latar belakang? + Aplikasi ini dikonfigurasi untuk terus berjalan setelah tidak terlihat selama beberapa waktu.\n\nKami menyarankan agar aplikasi ini selalu mengikuti perkembangan blockchain, bahkan di latar belakang.\n\nJangan khawatir, kami akan melakukannya selalu perhatikan penggunaan baterai Anda. + Buka Pengaturan + diff --git a/wallet/res/values-pt/strings-extra.xml b/wallet/res/values-pt/strings-extra.xml index 115c24fe7e..18d712cae7 100644 --- a/wallet/res/values-pt/strings-extra.xml +++ b/wallet/res/values-pt/strings-extra.xml @@ -270,6 +270,8 @@ Contato com o Suporte Reportar um problema Explorador + Encontrar comerciantes que aceitam DASH + Livro de endereços Importar Chave Privada Monitor de rede diff --git a/wallet/res/values-pt/strings.xml b/wallet/res/values-pt/strings.xml index f1c485e4a1..441bd38744 100644 --- a/wallet/res/values-pt/strings.xml +++ b/wallet/res/values-pt/strings.xml @@ -384,4 +384,14 @@ Endereço Dash inválido ou solicitação de URL inválida Enviar para Endereço - + + Otimização de bateria + Otimizado + Sem restrições + + Permitir que a Carteira Dash seja executada em segundo plano? + Este aplicativo está configurado para parar de ser executado após algum tempo sem estar visível.\n\nIsso resultará na falta de recebimento de notificações de pagamento até que o aplicativo retorne ao primeiro plano.\n\nRecomendamos permitir que este aplicativo seja executado em segundo plano, configurando a otimização de bateria para \"Não Otimizado\" ou \"Sem Restrições\".\n\nNão se preocupe, sempre cuidaremos do uso da sua bateria. + Você deseja receber pagamentos em segundo plano?  + Este aplicativo está configurado para continuar em execução mesmo após algum tempo sem estar visível.\n\nSugerimos permitir que este aplicativo esteja sempre atualizado com a blockchain, mesmo em segundo plano.\n\nNão se preocupe, sempre cuidaremos do uso da sua bateria. + Abrir Configurações + diff --git a/wallet/res/values-th/strings-extra.xml b/wallet/res/values-th/strings-extra.xml index 730d6cc11c..135112de45 100644 --- a/wallet/res/values-th/strings-extra.xml +++ b/wallet/res/values-th/strings-extra.xml @@ -264,6 +264,8 @@ ติดต่อฝ่ายสนับสนุน รายงานปัญหา สำรวจ + ค้นหาร้านค้าที่ยอมรับ DASH + สมุดที่อยู่ นำเข้าคีย์ส่วนตัว จอภาพเครือข่าย diff --git a/wallet/res/values-th/strings.xml b/wallet/res/values-th/strings.xml index 57ae217d60..f7ed8df89c 100644 --- a/wallet/res/values-th/strings.xml +++ b/wallet/res/values-th/strings.xml @@ -384,4 +384,14 @@ ไม่ใช่ที่อยู่ Dash หรือคำขอ URL ที่ถูกต้อง ส่งไปยังที่อยู่ - + + การเพิ่มประสิทธิภาพแบตเตอรี่ + ปรับให้เหมาะสม + ไม่จำกัด + + ให้ Dash Wallet ทำงานอยู่เบื้องหลังใช่ไหม + แอปนี้ได้รับการกำหนดค่าให้หยุดทำงานหลังจากไม่ปรากฏให้เห็นมาระยะหนึ่งแล้ว \n\n ซึ่งจะทำให้ไม่ได้รับการแจ้งเตือนการชำระเงินจนกว่าแอปจะกลับสู่ส่วนหน้า \n\n เราขอแนะนำให้อนุญาตให้แอปนี้ทำงานในพื้นหลังโดยการตั้งค่าแบตเตอรี่ การเพิ่มประสิทธิภาพเป็น \"ไม่เพิ่มประสิทธิภาพ\" หรือ \"ไม่จำกัด\" \n\n ไม่ต้องกังวล เราจะคำนึงถึงการใช้งานแบตเตอรี่ของคุณเสมอ + คุณต้องการรับการชำระเงินในเบื้องหลังหรือไม่? + แอปนี้ได้รับการกำหนดค่าให้ทำงานต่อไปหลังจากที่ไม่ปรากฏให้เห็นมาระยะหนึ่งแล้ว \n\n เราขอแนะนำให้อนุญาตให้แอปนี้อัปเดตบล็อกเชนอยู่เสมอ แม้จะอยู่เบื้องหลังก็ตาม \n\n อย่ากังวล เราจะ คำนึงถึงการใช้งานแบตเตอรี่ของคุณเสมอ + เปิดการตั้งค่า + diff --git a/wallet/res/values-tr/strings-extra.xml b/wallet/res/values-tr/strings-extra.xml index 8c5b824dd3..232a550230 100644 --- a/wallet/res/values-tr/strings-extra.xml +++ b/wallet/res/values-tr/strings-extra.xml @@ -267,6 +267,8 @@ Destekle İletişime Geçin Sorun rapor et Keşfet + DASH\'i kabul eden satıcıları bulun + Adres Rehberi Özel anahtar içeri al Ağ monitörü diff --git a/wallet/res/values-tr/strings.xml b/wallet/res/values-tr/strings.xml index b0844907b6..0770306ddb 100644 --- a/wallet/res/values-tr/strings.xml +++ b/wallet/res/values-tr/strings.xml @@ -384,4 +384,14 @@ Geçerli bir Dash Adresi veya URL isteği değil Adrese Gönder - + + Pil iyileştirmesi + Optimize edilmiş + Sınırsız + + Dash Cüzdan\'ının arka planda çalışmasına izin verilsin mi? + Bu uygulama, bir süre görünür olmadığında çalışmayı durduracak şekilde yapılandırılmıştır.\n\nBu durum, uygulama ön plana dönene kadar ödeme bildirimlerinin alınmamasına neden olacaktır.\n\nPil ayarını yaparak bu uygulamanın arka planda çalışmasına izin vermenizi öneririz. Optimizasyonunu \"Optimize Edilmemiş\" veya \"Kısıtlanmamış\" olarak ayarlayın.\n\nMerak etmeyin, pil kullanımınıza her zaman önem vereceğiz. + Arka planda ödeme almak ister misiniz? + Bu uygulama bir süre görünmez kaldıktan sonra çalışmaya devam edecek şekilde yapılandırılmıştır.\n\nBu uygulamanın arka planda bile her zaman blockchain ile güncel kalmasına izin vermenizi öneririz.\n\nMerak etmeyin, pil kullanımınıza her zaman önem vereceğiz. + Ayarları aç + diff --git a/wallet/res/values-uk/strings-extra.xml b/wallet/res/values-uk/strings-extra.xml index 3535247973..2221c01c20 100644 --- a/wallet/res/values-uk/strings-extra.xml +++ b/wallet/res/values-uk/strings-extra.xml @@ -273,6 +273,8 @@ Служба підтримки Повідомити про проблему Дослідити + Знайдіть продавців, які приймають DASH + Адресна книга Імпорт приватного ключа Мережевий монітор diff --git a/wallet/res/values-zh/strings-extra.xml b/wallet/res/values-zh/strings-extra.xml index e83b4560a4..0b6925ff02 100644 --- a/wallet/res/values-zh/strings-extra.xml +++ b/wallet/res/values-zh/strings-extra.xml @@ -264,6 +264,8 @@ 联系支持 报告问题 探索 + 查找接受 DASH 支付的商家 + 地址簿 导入私钥 网络监视器 From b999576e22695a4878df586143f8e7117918cd42 Mon Sep 17 00:00:00 2001 From: HashEngineering Date: Mon, 11 Dec 2023 18:18:20 -0800 Subject: [PATCH 18/28] feat: support Dash Core 20 (#1234) * chore: update to dashj 20.0.0-SNAPSHOT to support Core v20 (#1204) * chore: upgrade to dashj 20.0.0-SNAPSHOT * fix: use Ouzo for the devnet flavor * fix: change calculation for masternode rewards after v20 activation (#1221) * chore: update dashj to 20.0.0 (#1236) * chore: update checkpoints --- build.gradle | 2 +- wallet/assets/checkpoints-testnet.txt | 199 +++++++++++++++++- wallet/assets/checkpoints.txt | 75 ++++++- .../src/de/schildbach/wallet/Constants.java | 4 +- .../service/BlockchainStateDataProvider.kt | 8 + 5 files changed, 282 insertions(+), 6 deletions(-) diff --git a/build.gradle b/build.gradle index bb42ae4331..9fe63658b6 100644 --- a/build.gradle +++ b/build.gradle @@ -3,7 +3,7 @@ buildscript { kotlin_version = '1.8.22' coroutinesVersion = '1.6.4' ok_http_version = '4.9.1' - dashjVersion = '19.0.6' + dashjVersion = '20.0.0' hiltVersion = '2.45' hiltWorkVersion = '1.0.0' workRuntimeVersion='2.7.1' diff --git a/wallet/assets/checkpoints-testnet.txt b/wallet/assets/checkpoints-testnet.txt index c6adeff3eb..f72e7d24ed 100644 --- a/wallet/assets/checkpoints-testnet.txt +++ b/wallet/assets/checkpoints-testnet.txt @@ -1,6 +1,6 @@ TXT CHECKPOINTS 1 0 -1380 +1576 AAAAAAAAAAAkECOYAAACQAIAAAC2vSBlF+IPfT99SBfBEbKlchhK3afBQPjYS9KqDAgAABE5bCPb4Xjjx3Gzp7s/CezmJlWPYawKPXVKt9/lIy/754vXVP//Dx4QcwEA AAAAAAAAAABIQCXnAAAEgAIAAACawFgULuwvdEP4oo4cVUCbItDd/zHYV2j0GgTfFwcAAIazSWKA2SoCbNJR3AqymqvWS9x6Ahsmd9McS5pQk6w23pTXVP//Ax68GQoA AAAAAAAAAADZAErXAAAGwAIAAAADyAGIUUymlSwxz+Kn1VWKDgT/31Nmomb5tsx0aAMAAOzHCNcqCWULtDmL4vMjNlvFgwfCDixx4kSCOB3xH8ZU3qXXVP//AB5amgAA @@ -1381,4 +1381,199 @@ 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 diff --git a/wallet/assets/checkpoints.txt b/wallet/assets/checkpoints.txt index bad055f4e4..ca931444a1 100644 --- a/wallet/assets/checkpoints.txt +++ b/wallet/assets/checkpoints.txt @@ -1,6 +1,6 @@ TXT CHECKPOINTS 1 0 -3342 +3415 AAAAAAAAAAAkQCRAAAACQAIAAAAVH6nAAsNe/Kh1ihi+qsNyRhKz5QGUAyybpU/1KQIAADKEYBnXENihFvOEAHUsytpbQJf7QFGTQ3pFVWiHHeQnSlDbUvz/Ax7nQAIA AAAAAAAAAAC1ALUAAAAEgAIAAACPa0nYq+iMh51WsXwHxbWdw7esglfgOjOuEwrcgAIAACtgKaPRFHJ2L5fqDdPmUkCemb3cyIOqM23tbAGNYbkFjlrbUv//AB6YIQEA AAAAAAAAAAL4AvgAAAAGwAIAAAB8a2OpvV2j+WYn2Yyknb5gxQk+BlaGM2E/Sash6wAAAM2CiycIZqtxwBQJoBUSohXNRj+SoILtbjzEaFoHJvUD5WfbUsD/Px028AMA @@ -3343,3 +3343,76 @@ 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 diff --git a/wallet/src/de/schildbach/wallet/Constants.java b/wallet/src/de/schildbach/wallet/Constants.java index 38a75dca30..1ca5af5c4a 100644 --- a/wallet/src/de/schildbach/wallet/Constants.java +++ b/wallet/src/de/schildbach/wallet/Constants.java @@ -29,7 +29,7 @@ import org.bitcoinj.core.NetworkParameters; import org.bitcoinj.crypto.ChildNumber; import org.bitcoinj.params.MainNetParams; -import org.bitcoinj.params.ScrewDriverDevNetParams; +import org.bitcoinj.params.OuzoDevNetParams; import org.bitcoinj.params.TestNet3Params; import org.bitcoinj.utils.MonetaryFormat; import org.bitcoinj.wallet.DeterministicKeyChain; @@ -86,7 +86,7 @@ public final class Constants { case "devnet": { // Devnet BIP44_PATH = DeterministicKeyChain.BIP44_ACCOUNT_ZERO_PATH_TESTNET; - NETWORK_PARAMETERS = ScrewDriverDevNetParams.get(); + NETWORK_PARAMETERS = OuzoDevNetParams.get(); // TODO: remove this next line when Platform Supports Core 0.18 DNS_SEED = NETWORK_PARAMETERS.getDnsSeeds(); IS_PROD_BUILD = false; diff --git a/wallet/src/de/schildbach/wallet/service/BlockchainStateDataProvider.kt b/wallet/src/de/schildbach/wallet/service/BlockchainStateDataProvider.kt index 02015ed411..dfb3cb1dfd 100644 --- a/wallet/src/de/schildbach/wallet/service/BlockchainStateDataProvider.kt +++ b/wallet/src/de/schildbach/wallet/service/BlockchainStateDataProvider.kt @@ -278,6 +278,14 @@ class BlockchainStateDataProvider @Inject constructor( // Activated but we have to wait for the next cycle to start realocation, nothing to do return ret } + + if (Constants.NETWORK_PARAMETERS.isV20Active(height)) { + // Once MNRewardReallocated activates, block reward is 80% of block subsidy (+ tx fees) since treasury is 20% + // Since the MN reward needs to be equal to 60% of the block subsidy (according to the proposal), MN reward is set to 75% of the block reward. + // Previous reallocation periods are dropped. + return blockValue * 3 / 4 + } + val reallocCycle = superblockCycle * 3 val nCurrentPeriod: Int = min((height - reallocStart) / reallocCycle, periods.size - 1) From 03ec62c4c090318facc958d447886419ff85e580 Mon Sep 17 00:00:00 2001 From: HashEngineering Date: Mon, 11 Dec 2023 18:22:41 -0800 Subject: [PATCH 19/28] v10.0.0 --- wallet/CHANGES | 7 +++++++ wallet/build.gradle | 4 ++-- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/wallet/CHANGES b/wallet/CHANGES index 405878fe0d..974142356c 100644 --- a/wallet/CHANGES +++ b/wallet/CHANGES @@ -1,4 +1,11 @@ Dash Wallet +v10.0.0 +* Support Dash Core v20 network protocol +* Update translations +* Coinbase: hide buy swap features and information +* Crowdnode: add per block withdrawal limit +* Fix crash when dismissing Battery Optimization info dialog + v9.1.1 * Import Private Key Image Crash Fix * Add support to change battery optimization diff --git a/wallet/build.gradle b/wallet/build.gradle index a39cf89474..7250b542a0 100644 --- a/wallet/build.gradle +++ b/wallet/build.gradle @@ -189,8 +189,8 @@ android { compileSdk 33 minSdkVersion 23 targetSdkVersion 33 - versionCode project.hasProperty('versionCode') ? project.property('versionCode') as int : 90110 - versionName project.hasProperty('versionName') ? project.property('versionName') : "9.1.1" + versionCode project.hasProperty('versionCode') ? project.property('versionCode') as int : 100000 + versionName project.hasProperty('versionName') ? project.property('versionName') : "10.0.0" multiDexEnabled true generatedDensities = ['hdpi', 'xhdpi'] vectorDrawables.useSupportLibrary = true From 2c2839aae33f8db4f964b7b4bb3400eaf9437511 Mon Sep 17 00:00:00 2001 From: HashEngineering Date: Wed, 13 Dec 2023 10:45:01 -0800 Subject: [PATCH 20/28] fix: prevent crash when showing inactivity notification when there is no wallet (#1237) --- .../src/de/schildbach/wallet/service/BootstrapReceiver.java | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/wallet/src/de/schildbach/wallet/service/BootstrapReceiver.java b/wallet/src/de/schildbach/wallet/service/BootstrapReceiver.java index a838ca129b..1fbccccea9 100644 --- a/wallet/src/de/schildbach/wallet/service/BootstrapReceiver.java +++ b/wallet/src/de/schildbach/wallet/service/BootstrapReceiver.java @@ -155,6 +155,11 @@ private void maybeShowInactivityNotification() { return; final Wallet wallet = walletDataProvider.getWallet(); + if (wallet == null) { + // with version 7.0 and above it is possible to have the app installed without a wallet + log.info("wallet does not exist, not showing inactivity warning"); + return; + } final Coin estimatedBalance = wallet.getBalance(Wallet.BalanceType.ESTIMATED_SPENDABLE); if (!estimatedBalance.isPositive()) return; From 929ecab9a64fb8e7ce199032a07305185d01cc74 Mon Sep 17 00:00:00 2001 From: HashEngineering Date: Fri, 15 Dec 2023 07:30:29 -0800 Subject: [PATCH 21/28] fix: refactor WalletActivity permission launcher to avoid InvalidStateException (#1239) --- .../src/de/schildbach/wallet/ui/main/WalletActivity.java | 3 +++ .../src/de/schildbach/wallet/ui/main/WalletActivityExt.kt | 7 +------ 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/wallet/src/de/schildbach/wallet/ui/main/WalletActivity.java b/wallet/src/de/schildbach/wallet/ui/main/WalletActivity.java index 3dece8fd6a..e9589e8fb5 100644 --- a/wallet/src/de/schildbach/wallet/ui/main/WalletActivity.java +++ b/wallet/src/de/schildbach/wallet/ui/main/WalletActivity.java @@ -25,6 +25,8 @@ import android.os.Bundle; import android.os.Handler; import android.os.PowerManager; +import androidx.activity.result.ActivityResultLauncher; +import androidx.activity.result.contract.ActivityResultContracts; import androidx.core.app.ActivityCompat; import androidx.lifecycle.ViewModelProvider; @@ -76,6 +78,7 @@ public static Intent createIntent(Context context) { private BaseAlertDialogBuilder baseAlertDialogBuilder; private MainViewModel viewModel; + ActivityResultLauncher requestPermissionLauncher = registerForActivityResult(new ActivityResultContracts.RequestPermission(), result -> WalletActivityExt.INSTANCE.requestDisableBatteryOptimisation(WalletActivity.this)); @Override protected void onCreate(final Bundle savedInstanceState) { diff --git a/wallet/src/de/schildbach/wallet/ui/main/WalletActivityExt.kt b/wallet/src/de/schildbach/wallet/ui/main/WalletActivityExt.kt index 4063c389d6..63267827eb 100644 --- a/wallet/src/de/schildbach/wallet/ui/main/WalletActivityExt.kt +++ b/wallet/src/de/schildbach/wallet/ui/main/WalletActivityExt.kt @@ -203,11 +203,6 @@ object WalletActivityExt { } } - private val WalletActivity.requestPermissionLauncher: ActivityResultLauncher - get() = registerForActivityResult(ActivityResultContracts.RequestPermission()) { - requestDisableBatteryOptimisation() - } - /** * Android 13 - Show system dialog to get notification permission from user, if not granted * ask again with each app upgrade if not granted. This logic is handled by @@ -238,7 +233,7 @@ object WalletActivityExt { configuration.showNotificationsExplainer = false } - private fun WalletActivity.requestDisableBatteryOptimisation() { + fun WalletActivity.requestDisableBatteryOptimisation() { val powerManager: PowerManager = getSystemService(PowerManager::class.java) if (ContextCompat.checkSelfPermission( walletApplication, From 3b0d1ea0cd68d5a44219fd402e7a9d8aedfc9a97 Mon Sep 17 00:00:00 2001 From: HashEngineering Date: Fri, 15 Dec 2023 08:42:20 -0800 Subject: [PATCH 22/28] v10.0.1 --- wallet/CHANGES | 4 ++++ wallet/build.gradle | 4 ++-- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/wallet/CHANGES b/wallet/CHANGES index 974142356c..c8eef84203 100644 --- a/wallet/CHANGES +++ b/wallet/CHANGES @@ -1,4 +1,8 @@ Dash Wallet +v10.0.1 +* Fix crash with InactivityService with no wallet +* Fix crash on Android 13 and 14 when requesting notification permissions + v10.0.0 * Support Dash Core v20 network protocol * Update translations diff --git a/wallet/build.gradle b/wallet/build.gradle index 7250b542a0..0704d4ec63 100644 --- a/wallet/build.gradle +++ b/wallet/build.gradle @@ -189,8 +189,8 @@ android { compileSdk 33 minSdkVersion 23 targetSdkVersion 33 - versionCode project.hasProperty('versionCode') ? project.property('versionCode') as int : 100000 - versionName project.hasProperty('versionName') ? project.property('versionName') : "10.0.0" + versionCode project.hasProperty('versionCode') ? project.property('versionCode') as int : 100010 + versionName project.hasProperty('versionName') ? project.property('versionName') : "10.0.1" multiDexEnabled true generatedDensities = ['hdpi', 'xhdpi'] vectorDrawables.useSupportLibrary = true From 42846fac87d06d538a4ee08bb796df1368e47057 Mon Sep 17 00:00:00 2001 From: HashEngineering Date: Mon, 18 Dec 2023 12:07:16 -0800 Subject: [PATCH 23/28] fix: eliminate race condition when updating BlockchainState and update CrowdNode APY (#1240) * fix: eliminate race condition when updating BlockchainState that results in "Syncing..." for a long time * fix: set the weight for EvoNodes to 4x for masternode APY * chore: update to dashj 20.0.1-SNAPSHOT --- build.gradle | 2 +- .../wallet/service/BlockchainServiceImpl.java | 11 ++++---- .../service/BlockchainStateDataProvider.kt | 27 +++++++++---------- 3 files changed, 18 insertions(+), 22 deletions(-) diff --git a/build.gradle b/build.gradle index 9fe63658b6..1317492a79 100644 --- a/build.gradle +++ b/build.gradle @@ -3,7 +3,7 @@ buildscript { kotlin_version = '1.8.22' coroutinesVersion = '1.6.4' ok_http_version = '4.9.1' - dashjVersion = '20.0.0' + dashjVersion = '20.0.1-SNAPSHOT' hiltVersion = '2.45' hiltWorkVersion = '1.0.0' workRuntimeVersion='2.7.1' diff --git a/wallet/src/de/schildbach/wallet/service/BlockchainServiceImpl.java b/wallet/src/de/schildbach/wallet/service/BlockchainServiceImpl.java index 722da2ed39..9d7ed65583 100644 --- a/wallet/src/de/schildbach/wallet/service/BlockchainServiceImpl.java +++ b/wallet/src/de/schildbach/wallet/service/BlockchainServiceImpl.java @@ -501,6 +501,7 @@ public void run() { protected void progress(double pct, int blocksLeft, Date date) { super.progress(pct, blocksLeft, date); syncPercentage = pct > 0.0 ? (int)pct : 0; + log.info("progress {}", syncPercentage); if (syncPercentage > 100) { syncPercentage = 100; } @@ -513,9 +514,11 @@ protected void progress(double pct, int blocksLeft, Date date) { @Override protected void doneDownload() { super.doneDownload(); - updateBlockchainState(); + log.info("DoneDownload {}", syncPercentage); + // if the chain is already synced from a previous session, then syncPercentage = 0 + // set to 100% so that observers will see that sync is completed syncPercentage = 100; - setBlockchainDownloaded(); + updateBlockchainState(); } @Override @@ -1169,10 +1172,6 @@ private void updateBlockchainState() { blockchainStateDataProvider.updateBlockchainState(blockChain, impediments, percentageSync()); } - public void setBlockchainDownloaded() { - blockchainStateDataProvider.setBlockchainDownloaded(); - } - @Override public List getConnectedPeers() { if (peerGroup != null) diff --git a/wallet/src/de/schildbach/wallet/service/BlockchainStateDataProvider.kt b/wallet/src/de/schildbach/wallet/service/BlockchainStateDataProvider.kt index dfb3cb1dfd..4feb381101 100644 --- a/wallet/src/de/schildbach/wallet/service/BlockchainStateDataProvider.kt +++ b/wallet/src/de/schildbach/wallet/service/BlockchainStateDataProvider.kt @@ -23,7 +23,7 @@ import dagger.hilt.android.qualifiers.ApplicationContext import de.schildbach.wallet.Constants import de.schildbach.wallet.database.dao.BlockchainStateDao import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.asCoroutineDispatcher import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.distinctUntilChanged import kotlinx.coroutines.launch @@ -39,10 +39,12 @@ import org.dash.wallet.common.WalletDataProvider import org.dash.wallet.common.data.entity.BlockchainState import org.dash.wallet.common.data.entity.BlockchainState.Impediment import org.dash.wallet.common.services.BlockchainStateProvider +import org.slf4j.LoggerFactory import java.io.IOException import java.io.InputStream import java.math.BigInteger import java.util.EnumSet +import java.util.concurrent.Executors import javax.inject.Inject import kotlin.math.min @@ -74,7 +76,8 @@ class BlockchainStateDataProvider @Inject constructor( const val MASTERNODE_COUNT = 3800 } - private val coroutineScope = CoroutineScope(Dispatchers.IO) + // this coroutineScope should execute all jobs sequentially + private val coroutineScope = CoroutineScope(Executors.newSingleThreadExecutor().asCoroutineDispatcher()) override suspend fun getState(): BlockchainState? { return blockchainStateDao.getState() @@ -95,8 +98,8 @@ class BlockchainStateDataProvider @Inject constructor( } } - fun updateBlockchainState(blockChain: BlockChain, impediments: Set, percentageSync: Int) { - coroutineScope.launch { + fun updateBlockchainState(blockChain: BlockChain, impediments: Set, percentageSync: Int) { + coroutineScope.launch { var blockchainState = blockchainStateDao.getState() if (blockchainState == null) { blockchainState = BlockchainState() @@ -115,16 +118,6 @@ class BlockchainStateDataProvider @Inject constructor( } } - fun setBlockchainDownloaded() { - coroutineScope.launch { - val blockchainState = blockchainStateDao.getState() - if (blockchainState != null && blockchainState.percentageSync != 100) { - blockchainState.percentageSync = 100 - blockchainStateDao.saveState(blockchainState) - } - } - } - fun resetBlockchainState() { coroutineScope.launch { blockchainStateDao.saveState( @@ -173,7 +166,11 @@ class BlockchainStateDataProvider @Inject constructor( } val validMNsCount = if (mnlist.size() != 0) { - mnlist.validMNsCount + var virtualMNCount = 0 + mnlist.forEachMN(true) { entry -> + virtualMNCount += if (entry.isHPMN) 4 else 1 + } + virtualMNCount } else { MASTERNODE_COUNT } From a05ba53666a4a8ef8e310eec611428caffc51a93 Mon Sep 17 00:00:00 2001 From: HashEngineering Date: Fri, 22 Dec 2023 18:10:52 -0800 Subject: [PATCH 24/28] fix: prevent rescan blockchain failure on old wallets (#1241) * only rescan transactions for masternode key usage on upgrade --- build.gradle | 2 +- wallet/src/de/schildbach/wallet/service/BootstrapReceiver.java | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/build.gradle b/build.gradle index 1317492a79..1ed612867f 100644 --- a/build.gradle +++ b/build.gradle @@ -3,7 +3,7 @@ buildscript { kotlin_version = '1.8.22' coroutinesVersion = '1.6.4' ok_http_version = '4.9.1' - dashjVersion = '20.0.1-SNAPSHOT' + dashjVersion = '20.0.2' hiltVersion = '2.45' hiltWorkVersion = '1.0.0' workRuntimeVersion='2.7.1' diff --git a/wallet/src/de/schildbach/wallet/service/BootstrapReceiver.java b/wallet/src/de/schildbach/wallet/service/BootstrapReceiver.java index 1fbccccea9..ad7d9983e9 100644 --- a/wallet/src/de/schildbach/wallet/service/BootstrapReceiver.java +++ b/wallet/src/de/schildbach/wallet/service/BootstrapReceiver.java @@ -138,7 +138,7 @@ private void maybeUpgradeWallet(final Wallet wallet) { WalletExtension extension = extensions.get(AuthenticationGroupExtension.EXTENSION_ID); if (extension != null) { // reset will rescan existing transactions and rebuild the authentication usage info - ((AuthenticationGroupExtension) extension).reset(); + ((AuthenticationGroupExtension) extension).rescanWallet(); } // Maybe upgrade wallet to secure chain From 078b4558a67f7eb23247c5a6722b6b65d55cbfd0 Mon Sep 17 00:00:00 2001 From: HashEngineering Date: Fri, 22 Dec 2023 18:11:26 -0800 Subject: [PATCH 25/28] v10.0.2 --- wallet/CHANGES | 6 ++++++ wallet/build.gradle | 4 ++-- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/wallet/CHANGES b/wallet/CHANGES index c8eef84203..e584bffebe 100644 --- a/wallet/CHANGES +++ b/wallet/CHANGES @@ -1,4 +1,10 @@ Dash Wallet +v10.0.2 +* Fix Syncing... issue +* Fix CrowdNode and Masternode APY calculation +* Update to dashj 20.0.2 (block reward fix) +* Fix Rescan Blockchain failure on older wallets + v10.0.1 * Fix crash with InactivityService with no wallet * Fix crash on Android 13 and 14 when requesting notification permissions diff --git a/wallet/build.gradle b/wallet/build.gradle index 0704d4ec63..8d4c94edbe 100644 --- a/wallet/build.gradle +++ b/wallet/build.gradle @@ -189,8 +189,8 @@ android { compileSdk 33 minSdkVersion 23 targetSdkVersion 33 - versionCode project.hasProperty('versionCode') ? project.property('versionCode') as int : 100010 - versionName project.hasProperty('versionName') ? project.property('versionName') : "10.0.1" + versionCode project.hasProperty('versionCode') ? project.property('versionCode') as int : 100021 + versionName project.hasProperty('versionName') ? project.property('versionName') : "10.0.2" multiDexEnabled true generatedDensities = ['hdpi', 'xhdpi'] vectorDrawables.useSupportLibrary = true From 11cc3494b294cd2558ae7033e10163989e855ced Mon Sep 17 00:00:00 2001 From: HashEngineering Date: Sat, 30 Dec 2023 21:38:15 -0600 Subject: [PATCH 26/28] fix: repair compile issues from master merge --- .../wallet/database/DatabaseMigrationTest.kt | 2 +- .../schildbach/wallet/WalletApplication.java | 4 +- .../wallet/service/BlockchainServiceImpl.java | 5 +- .../wallet/ui/DashPayUserActivity.kt | 1 + .../wallet/ui/OnboardingViewModel.kt | 2 +- .../wallet/ui/SearchUserActivity.kt | 1 + .../schildbach/wallet/ui/SettingsActivity.kt | 2 - .../ui/coinjoin/CoinJoinLevelFragment.kt | 4 +- .../wallet/ui/dashpay/ContactsFragment.kt | 1 + .../wallet/ui/dashpay/DashPayViewModel.kt | 14 ++--- .../ui/dashpay/NotificationsFragment.kt | 1 + .../wallet/ui/dashpay/PlatformRepo.kt | 6 +-- .../wallet/ui/invite/CreateInviteViewModel.kt | 9 ++-- .../ui/invite/InvitationFragmentViewModel.kt | 2 +- .../wallet/ui/invite/InviteFriendActivity.kt | 2 +- .../schildbach/wallet/ui/main/MainActivity.kt | 19 ++++--- .../wallet/ui/main/WalletActivityExt.kt | 4 +- .../wallet/ui/more/SettingsViewModel.kt | 52 ++++++++++++++++++- 18 files changed, 94 insertions(+), 37 deletions(-) diff --git a/wallet/androidTest/de/schildbach/wallet/database/DatabaseMigrationTest.kt b/wallet/androidTest/de/schildbach/wallet/database/DatabaseMigrationTest.kt index 91c14446f2..8cd4fb607d 100644 --- a/wallet/androidTest/de/schildbach/wallet/database/DatabaseMigrationTest.kt +++ b/wallet/androidTest/de/schildbach/wallet/database/DatabaseMigrationTest.kt @@ -153,7 +153,7 @@ open class DatabaseMigrationTest { // Check that data is valid runBlocking { - val savedBlockchainState = db.blockchainStateDao().loadSync() + val savedBlockchainState = db.blockchainStateDao().getState() assert(savedBlockchainState!!.bestChainHeight == BLOCKCHAIN_HEIGHT) val savedRate = db.exchangeRatesDao().getRateSync("ARS") diff --git a/wallet/src/de/schildbach/wallet/WalletApplication.java b/wallet/src/de/schildbach/wallet/WalletApplication.java index ceaa04f8c2..ecb1fcb0f4 100644 --- a/wallet/src/de/schildbach/wallet/WalletApplication.java +++ b/wallet/src/de/schildbach/wallet/WalletApplication.java @@ -368,7 +368,7 @@ public void setWallet(Wallet newWallet) throws GeneralSecurityException, IOExcep AuthenticationKeyChain.KeyChainType.MASTERNODE_OWNER, AuthenticationKeyChain.KeyChainType.MASTERNODE_VOTING, AuthenticationKeyChain.KeyChainType.MASTERNODE_OPERATOR, - AuthenticationKeyChain.KeyChainType.MASTERNODE_PLATFORM_OPERATOR + AuthenticationKeyChain.KeyChainType.MASTERNODE_PLATFORM_OPERATOR, AuthenticationKeyChain.KeyChainType.BLOCKCHAIN_IDENTITY, AuthenticationKeyChain.KeyChainType.BLOCKCHAIN_IDENTITY_FUNDING, AuthenticationKeyChain.KeyChainType.BLOCKCHAIN_IDENTITY_TOPUP, @@ -405,7 +405,7 @@ public void setWallet(Wallet newWallet) throws GeneralSecurityException, IOExcep WalletEx walletEx = (WalletEx) wallet; if (walletEx.getCoinJoin() != null) { // this wallet is not encrypted yet - walletEx.initializeCoinJoin(null); + walletEx.initializeCoinJoin(null, 0); } } } diff --git a/wallet/src/de/schildbach/wallet/service/BlockchainServiceImpl.java b/wallet/src/de/schildbach/wallet/service/BlockchainServiceImpl.java index 060ac1f964..311f7d988a 100644 --- a/wallet/src/de/schildbach/wallet/service/BlockchainServiceImpl.java +++ b/wallet/src/de/schildbach/wallet/service/BlockchainServiceImpl.java @@ -164,7 +164,6 @@ public class BlockchainServiceImpl extends LifecycleService implements Blockchai @Inject CrowdNodeBlockchainApi crowdNodeBlockchainApi; @Inject CrowdNodeConfig crowdNodeConfig; @Inject BlockchainStateDao blockchainStateDao; - @Inject BlockchainStateDataProvider blockchainStateDataProvider; @Inject ExchangeRatesDao exchangeRatesDao; @Inject TransactionMetadataProvider transactionMetadataProvider; @Inject PlatformSyncService platformSyncService; @@ -882,7 +881,7 @@ public void onReceive(final Context context, final Intent intent) { builder.append(", "); builder.append(entry); } - log.info("History of transactions/blocks/headers/mnlistdiff: " + builder); + log.info("History of transactions/blocks/headers/mnlistdiff: " + (mixingStatus == MixingStatus.MIXING ? "[mixing] " : "") + builder); // determine if block and transaction activity is idling @@ -1043,7 +1042,7 @@ public void onCreate() { updateAppWidget(); FlowExtKt.observe(blockchainStateDao.observeState(), this, (blockchainState, continuation) -> { - handleBlockchainStateNotification((BlockchainState) blockchainState); + handleBlockchainStateNotification((BlockchainState) blockchainState, mixingStatus); return null; }); registerCrowdNodeConfirmedAddressFilter(); diff --git a/wallet/src/de/schildbach/wallet/ui/DashPayUserActivity.kt b/wallet/src/de/schildbach/wallet/ui/DashPayUserActivity.kt index 71e0fa68d4..66c9705789 100644 --- a/wallet/src/de/schildbach/wallet/ui/DashPayUserActivity.kt +++ b/wallet/src/de/schildbach/wallet/ui/DashPayUserActivity.kt @@ -45,6 +45,7 @@ import org.dash.wallet.common.data.entity.BlockchainState import org.dash.wallet.common.services.analytics.AnalyticsConstants import org.dash.wallet.common.ui.avatar.ProfilePictureDisplay import org.dash.wallet.common.ui.dialogs.AdaptiveDialog +import org.dash.wallet.common.util.observe import javax.inject.Inject @AndroidEntryPoint diff --git a/wallet/src/de/schildbach/wallet/ui/OnboardingViewModel.kt b/wallet/src/de/schildbach/wallet/ui/OnboardingViewModel.kt index bc1113af34..cdac2c7dea 100644 --- a/wallet/src/de/schildbach/wallet/ui/OnboardingViewModel.kt +++ b/wallet/src/de/schildbach/wallet/ui/OnboardingViewModel.kt @@ -65,7 +65,7 @@ class OnboardingViewModel @Inject constructor( if (onboardingInvite != null) { analytics.logEvent(AnalyticsConstants.Invites.NEW_WALLET, mapOf()) - startActivityAction.call(AcceptInviteActivity.createIntent(getApplication(), onboardingInvite, true)) + startActivityAction.call(AcceptInviteActivity.createIntent(walletApplication, onboardingInvite, true)) } else { finishCreateNewWalletAction.call(Unit) } diff --git a/wallet/src/de/schildbach/wallet/ui/SearchUserActivity.kt b/wallet/src/de/schildbach/wallet/ui/SearchUserActivity.kt index 3adf1d5004..2a7f50f656 100644 --- a/wallet/src/de/schildbach/wallet/ui/SearchUserActivity.kt +++ b/wallet/src/de/schildbach/wallet/ui/SearchUserActivity.kt @@ -50,6 +50,7 @@ import de.schildbach.wallet.ui.invite.InvitesHistoryActivity import de.schildbach.wallet_test.R import de.schildbach.wallet_test.databinding.ActivitySearchDashpayProfileRootBinding import kotlinx.coroutines.launch +import org.dash.wallet.common.util.observe @AndroidEntryPoint class SearchUserActivity : LockScreenActivity(), ContactViewHolder.OnItemClickListener, diff --git a/wallet/src/de/schildbach/wallet/ui/SettingsActivity.kt b/wallet/src/de/schildbach/wallet/ui/SettingsActivity.kt index 17c8e15d73..4fc3ef36b6 100644 --- a/wallet/src/de/schildbach/wallet/ui/SettingsActivity.kt +++ b/wallet/src/de/schildbach/wallet/ui/SettingsActivity.kt @@ -62,8 +62,6 @@ class SettingsActivity : LockScreenActivity() { @Inject lateinit var walletUIConfig: WalletUIConfig - val viewModel by viewModels() - override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) diff --git a/wallet/src/de/schildbach/wallet/ui/coinjoin/CoinJoinLevelFragment.kt b/wallet/src/de/schildbach/wallet/ui/coinjoin/CoinJoinLevelFragment.kt index 3c3ff2486f..f1a88adbd9 100644 --- a/wallet/src/de/schildbach/wallet/ui/coinjoin/CoinJoinLevelFragment.kt +++ b/wallet/src/de/schildbach/wallet/ui/coinjoin/CoinJoinLevelFragment.kt @@ -83,7 +83,7 @@ class CoinJoinLevelFragment : Fragment(R.layout.fragment_coinjoin_level) { private fun showConnectionWaringDialog(mode: CoinJoinMode) { AdaptiveDialog.create( - org.dash.wallet.integration.coinbase_integration.R.drawable.ic_warning, + R.drawable.ic_warning, getString( if (mode == CoinJoinMode.INTERMEDIATE) { R.string.Intermediate_level_WIFI_Warning @@ -92,7 +92,7 @@ class CoinJoinLevelFragment : Fragment(R.layout.fragment_coinjoin_level) { } ), getString(R.string.privcay_level_WIFI_warning_desc), - getString(org.dash.wallet.integration.coinbase_integration.R.string.cancel), + getString(R.string.cancel), getString(R.string.continue_anyway) ).show(requireActivity()) { if (it == true) { diff --git a/wallet/src/de/schildbach/wallet/ui/dashpay/ContactsFragment.kt b/wallet/src/de/schildbach/wallet/ui/dashpay/ContactsFragment.kt index 0b3a0ea12e..806f53a79b 100644 --- a/wallet/src/de/schildbach/wallet/ui/dashpay/ContactsFragment.kt +++ b/wallet/src/de/schildbach/wallet/ui/dashpay/ContactsFragment.kt @@ -58,6 +58,7 @@ import org.dash.wallet.common.ui.dialogs.AdaptiveDialog import org.dash.wallet.common.ui.observeOnDestroy import org.dash.wallet.common.ui.viewBinding import org.dash.wallet.common.util.KeyboardUtil +import org.dash.wallet.common.util.observe import org.dash.wallet.common.util.safeNavigate enum class ContactsScreenMode { diff --git a/wallet/src/de/schildbach/wallet/ui/dashpay/DashPayViewModel.kt b/wallet/src/de/schildbach/wallet/ui/dashpay/DashPayViewModel.kt index a0ccf8e3fd..e242dc3bca 100644 --- a/wallet/src/de/schildbach/wallet/ui/dashpay/DashPayViewModel.kt +++ b/wallet/src/de/schildbach/wallet/ui/dashpay/DashPayViewModel.kt @@ -17,9 +17,9 @@ package de.schildbach.wallet.ui.dashpay import androidx.lifecycle.LiveData import androidx.lifecycle.MutableLiveData -import androidx.lifecycle.Transformations import androidx.lifecycle.asLiveData import androidx.lifecycle.liveData +import androidx.lifecycle.switchMap import androidx.lifecycle.viewModelScope import dagger.hilt.android.lifecycle.HiltViewModel import de.schildbach.wallet.WalletApplication @@ -82,7 +82,7 @@ open class DashPayViewModel @Inject constructor( platformSyncService, viewModelScope ) - val blockchainStateData = blockchainState.load() + val blockchainStateData = blockchainState.observeState() private val contactRequestLiveData = MutableLiveData>() @@ -119,7 +119,7 @@ open class DashPayViewModel @Inject constructor( ) ) } - val getUsernameLiveData = Transformations.switchMap(usernameLiveData) { username -> + val getUsernameLiveData = usernameLiveData.switchMap { username -> getUsernameJob.cancel() getUsernameJob = Job() liveData(context = getUsernameJob + Dispatchers.IO) { @@ -146,7 +146,7 @@ open class DashPayViewModel @Inject constructor( // Search Usernames that start with "text". Results are a list of documents for names // starting with text. If no results are found then an empty list is returned. // - val searchUsernamesLiveData = Transformations.switchMap(userSearchLiveData) { search: UserSearch -> + val searchUsernamesLiveData = userSearchLiveData.switchMap { search: UserSearch -> searchUsernamesJob.cancel() searchUsernamesJob = Job() liveData(context = searchUsernamesJob + Dispatchers.IO) { @@ -190,7 +190,7 @@ open class DashPayViewModel @Inject constructor( // // Search (established contacts) Usernames and Display Names that contain "text". // - val searchContactsLiveData = Transformations.switchMap(contactsLiveData) { usernameSearch: UsernameSearch -> + val searchContactsLiveData = contactsLiveData.switchMap { usernameSearch: UsernameSearch -> searchContactsJob.cancel() searchContactsJob = Job() liveData(context = searchContactsJob + Dispatchers.IO) { @@ -230,7 +230,7 @@ open class DashPayViewModel @Inject constructor( .enqueue() } - val getContactRequestLiveData = Transformations.switchMap(contactRequestLiveData) { + val getContactRequestLiveData = contactRequestLiveData.switchMap { liveData(context = contactRequestJob + Dispatchers.IO) { if (it.second != null) { emit(Resource.loading(null)) @@ -246,7 +246,7 @@ open class DashPayViewModel @Inject constructor( } } - val getContactLiveData = Transformations.switchMap(contactUserIdLiveData) { userId -> + val getContactLiveData = contactUserIdLiveData.switchMap { userId -> getContactJob.cancel() getContactJob = Job() liveData(context = getContactJob + Dispatchers.IO) { diff --git a/wallet/src/de/schildbach/wallet/ui/dashpay/NotificationsFragment.kt b/wallet/src/de/schildbach/wallet/ui/dashpay/NotificationsFragment.kt index dc01813244..8372bd418f 100644 --- a/wallet/src/de/schildbach/wallet/ui/dashpay/NotificationsFragment.kt +++ b/wallet/src/de/schildbach/wallet/ui/dashpay/NotificationsFragment.kt @@ -42,6 +42,7 @@ import kotlinx.coroutines.launch import org.dash.wallet.common.WalletDataProvider import org.dash.wallet.common.services.analytics.AnalyticsConstants import org.dash.wallet.common.ui.viewBinding +import org.dash.wallet.common.util.observe import org.slf4j.LoggerFactory import java.util.* import javax.inject.Inject diff --git a/wallet/src/de/schildbach/wallet/ui/dashpay/PlatformRepo.kt b/wallet/src/de/schildbach/wallet/ui/dashpay/PlatformRepo.kt index 33c13f1c7e..f88394d089 100644 --- a/wallet/src/de/schildbach/wallet/ui/dashpay/PlatformRepo.kt +++ b/wallet/src/de/schildbach/wallet/ui/dashpay/PlatformRepo.kt @@ -538,7 +538,7 @@ class PlatformRepo @Inject constructor( withContext(Dispatchers.IO) { val wallet = walletApplication.wallet as WalletEx // this will initialize any missing key chains - wallet.initializeCoinJoin(keyParameter) + wallet.initializeCoinJoin(keyParameter, 0) var authenticationGroupExtension = AuthenticationGroupExtension(wallet) authenticationGroupExtension = wallet.addOrGetExistingExtension(authenticationGroupExtension) as AuthenticationGroupExtension @@ -553,7 +553,7 @@ class PlatformRepo @Inject constructor( suspend fun createCreditFundingTransactionAsync(blockchainIdentity: BlockchainIdentity, keyParameter: KeyParameter?, useCoinJoin: Boolean) { withContext(Dispatchers.IO) { Context.propagate(walletApplication.wallet!!.context) - val cftx = blockchainIdentity.createCreditFundingTransaction(Constants.DASH_PAY_FEE, keyParameter, useCoinJoin) + val cftx = blockchainIdentity.createCreditFundingTransaction(Constants.DASH_PAY_FEE, keyParameter, useCoinJoin, true) blockchainIdentity.initializeCreditFundingTransaction(cftx) } } @@ -1019,7 +1019,7 @@ class PlatformRepo @Inject constructor( // dashj Context does not work with coroutines well, so we need to call Context.propogate // in each suspend method that uses the dashj Context Context.propagate(walletApplication.wallet!!.context) - val cftx = blockchainIdentity.createInviteFundingTransaction(Constants.DASH_PAY_FEE, keyParameter, useCoinJoin = false) + val cftx = blockchainIdentity.createInviteFundingTransaction(Constants.DASH_PAY_FEE, keyParameter, useCoinJoin = false, returnChange = true) val invitation = Invitation(cftx.creditBurnIdentityIdentifier.toStringBase58(), cftx.txId, System.currentTimeMillis()) // update database diff --git a/wallet/src/de/schildbach/wallet/ui/invite/CreateInviteViewModel.kt b/wallet/src/de/schildbach/wallet/ui/invite/CreateInviteViewModel.kt index fd6b7b7555..97ea8001d8 100644 --- a/wallet/src/de/schildbach/wallet/ui/invite/CreateInviteViewModel.kt +++ b/wallet/src/de/schildbach/wallet/ui/invite/CreateInviteViewModel.kt @@ -17,8 +17,10 @@ package de.schildbach.wallet.ui.invite +import androidx.lifecycle.LiveData import androidx.lifecycle.MediatorLiveData import androidx.lifecycle.asLiveData +import androidx.lifecycle.viewModelScope import dagger.hilt.android.lifecycle.HiltViewModel import de.schildbach.wallet.Constants import de.schildbach.wallet.database.dao.BlockchainStateDao @@ -30,6 +32,7 @@ import de.schildbach.wallet.ui.dashpay.BaseProfileViewModel import org.dash.wallet.common.WalletDataProvider import org.dash.wallet.common.data.entity.BlockchainState import org.dash.wallet.common.services.analytics.AnalyticsService +import org.dash.wallet.common.util.observe import javax.inject.Inject @HiltViewModel @@ -42,9 +45,9 @@ class CreateInviteViewModel @Inject constructor( dashPayProfileDao: DashPayProfileDao ) : BaseProfileViewModel(blockchainIdentityDataDao, dashPayProfileDao) { - val blockchainStateData = blockchainStateDao.load() - val blockchainState: BlockchainState? - get() = blockchainStateData.value + val blockchainStateData = blockchainStateDao.observeState().asLiveData(viewModelScope.coroutineContext) + //val blockchainState: LiveData + // get() = blockchainStateData.asLiveData(viewModelScope) val isAbleToCreateInviteLiveData = MediatorLiveData().apply { addSource(blockchainStateData) { diff --git a/wallet/src/de/schildbach/wallet/ui/invite/InvitationFragmentViewModel.kt b/wallet/src/de/schildbach/wallet/ui/invite/InvitationFragmentViewModel.kt index 8aeb76329b..8556ab619f 100644 --- a/wallet/src/de/schildbach/wallet/ui/invite/InvitationFragmentViewModel.kt +++ b/wallet/src/de/schildbach/wallet/ui/invite/InvitationFragmentViewModel.kt @@ -132,7 +132,7 @@ open class InvitationFragmentViewModel @Inject constructor( } } - val invitationLiveData = Transformations.switchMap(identityIdLiveData) { + val invitationLiveData = identityIdLiveData.switchMap { liveData(Dispatchers.IO) { emit(invitationDao.loadByUserId(it)!!) } diff --git a/wallet/src/de/schildbach/wallet/ui/invite/InviteFriendActivity.kt b/wallet/src/de/schildbach/wallet/ui/invite/InviteFriendActivity.kt index 32ad9666d2..0ef02e83cf 100644 --- a/wallet/src/de/schildbach/wallet/ui/invite/InviteFriendActivity.kt +++ b/wallet/src/de/schildbach/wallet/ui/invite/InviteFriendActivity.kt @@ -65,7 +65,7 @@ class InviteFriendActivity : LockScreenActivity() { } } - fun createIntentExistingInvite(context: Context, userId: String, inviteIndex: Int, startedByHistory: Boolean = true): Intent? { + fun createIntentExistingInvite(context: Context, userId: String, inviteIndex: Int, startedByHistory: Boolean = true): Intent { val intent = Intent(context, InviteFriendActivity::class.java) intent.putExtra(ARG_IDENTITY_ID, userId) intent.putExtra(ARG_STARTED_BY_HISTORY, startedByHistory) diff --git a/wallet/src/de/schildbach/wallet/ui/main/MainActivity.kt b/wallet/src/de/schildbach/wallet/ui/main/MainActivity.kt index c4cf0410e8..ce524f3c57 100644 --- a/wallet/src/de/schildbach/wallet/ui/main/MainActivity.kt +++ b/wallet/src/de/schildbach/wallet/ui/main/MainActivity.kt @@ -18,7 +18,6 @@ package de.schildbach.wallet.ui.main import android.Manifest -import android.app.Dialog import android.content.Context import android.content.Intent import android.content.IntentFilter @@ -27,6 +26,7 @@ import android.nfc.NdefMessage import android.nfc.NfcAdapter import android.os.Build import android.os.Bundle +import android.os.PowerManager import android.provider.Settings import android.view.MenuItem import android.view.WindowManager @@ -52,6 +52,7 @@ import de.schildbach.wallet.ui.invite.InviteHandler import de.schildbach.wallet.ui.invite.InviteSendContactRequestDialog import de.schildbach.wallet.ui.main.WalletActivityExt.checkTimeSkew import de.schildbach.wallet.ui.main.WalletActivityExt.handleFirebaseAction +import de.schildbach.wallet.ui.main.WalletActivityExt.requestDisableBatteryOptimisation import de.schildbach.wallet.ui.main.WalletActivityExt.setupBottomNavigation import de.schildbach.wallet.ui.main.WalletActivityExt.showFiatCurrencyChangeDetectedDialog import de.schildbach.wallet.ui.util.InputParser @@ -106,6 +107,10 @@ class MainActivity : AbstractBindServiceActivity(), ActivityCompat.OnRequestPerm private var retryCreationIfInProgress = true private var pendingInvite: InvitationLinkData? = null + val requestPermissionLauncher = registerForActivityResult(ActivityResultContracts.RequestPermission()) { _ -> + requestDisableBatteryOptimisation() + }; + override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) window.addFlags(WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS) @@ -297,7 +302,7 @@ class MainActivity : AbstractBindServiceActivity(), ActivityCompat.OnRequestPerm } override fun onNewKeyChainEncrypted() { - //TODO: can we remove this? + // TODO: can we remove this? } private fun showRestoreWalletFromSeedDialog() { @@ -399,7 +404,8 @@ class MainActivity : AbstractBindServiceActivity(), ActivityCompat.OnRequestPerm applicationInfo, packageInfoProvider, configuration, - walletData.wallet + walletData.wallet, + walletApplication.getSystemService(PowerManager::class.java) ) return applicationInfo } @@ -453,7 +459,8 @@ class MainActivity : AbstractBindServiceActivity(), ActivityCompat.OnRequestPerm this@MainActivity, packageInfoProvider, configuration, - walletData.wallet + walletData.wallet, + walletApplication ).buildAlertDialog() alertDialog.show() } else { @@ -567,10 +574,6 @@ class MainActivity : AbstractBindServiceActivity(), ActivityCompat.OnRequestPerm } } - private val requestPermissionLauncher = registerForActivityResult(ActivityResultContracts.RequestPermission()) { granted -> - // do nothing - }; - /** * Android 13 - Show system dialog to get notification permission from user, if not granted * ask again with each app upgrade if not granted. This logic is handled by diff --git a/wallet/src/de/schildbach/wallet/ui/main/WalletActivityExt.kt b/wallet/src/de/schildbach/wallet/ui/main/WalletActivityExt.kt index 93998c33b1..b3277a840a 100644 --- a/wallet/src/de/schildbach/wallet/ui/main/WalletActivityExt.kt +++ b/wallet/src/de/schildbach/wallet/ui/main/WalletActivityExt.kt @@ -219,7 +219,7 @@ object WalletActivityExt { * [.onLockScreenDeactivated] and [.onStart]. * Android 12 and below - show a explainer dialog once only. */ - fun WalletActivity.explainPushNotifications() { + fun MainActivity.explainPushNotifications() { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU && ContextCompat.checkSelfPermission( this, @@ -243,7 +243,7 @@ object WalletActivityExt { configuration.showNotificationsExplainer = false } - fun WalletActivity.requestDisableBatteryOptimisation() { + fun MainActivity.requestDisableBatteryOptimisation() { val powerManager: PowerManager = getSystemService(PowerManager::class.java) if (ContextCompat.checkSelfPermission( walletApplication, diff --git a/wallet/src/de/schildbach/wallet/ui/more/SettingsViewModel.kt b/wallet/src/de/schildbach/wallet/ui/more/SettingsViewModel.kt index ac5a6af738..50015cacb6 100644 --- a/wallet/src/de/schildbach/wallet/ui/more/SettingsViewModel.kt +++ b/wallet/src/de/schildbach/wallet/ui/more/SettingsViewModel.kt @@ -18,16 +18,66 @@ package de.schildbach.wallet.ui.more import android.os.PowerManager import androidx.lifecycle.ViewModel +import androidx.lifecycle.viewModelScope import dagger.hilt.android.lifecycle.HiltViewModel import de.schildbach.wallet.WalletApplication +import de.schildbach.wallet.data.CoinJoinConfig +import de.schildbach.wallet.service.CoinJoinMode +import de.schildbach.wallet.service.CoinJoinService +import de.schildbach.wallet.service.MixingStatus +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.launchIn +import kotlinx.coroutines.flow.onEach +import kotlinx.coroutines.launch +import org.bitcoinj.wallet.Wallet +import org.bitcoinj.wallet.WalletEx +import org.dash.wallet.common.WalletDataProvider +import org.dash.wallet.common.data.WalletUIConfig +import org.dash.wallet.common.util.toBigDecimal +import java.text.DecimalFormat import javax.inject.Inject @HiltViewModel class SettingsViewModel @Inject constructor( - private val walletApplication: WalletApplication + private val walletApplication: WalletApplication, + private val walletUIConfig: WalletUIConfig, + private val coinJoinConfig: CoinJoinConfig, + private val coinJoinService: CoinJoinService, + private val walletDataProvider: WalletDataProvider ) : ViewModel() { private val powerManager: PowerManager = walletApplication.getSystemService(PowerManager::class.java) val isIgnoringBatteryOptimizations: Boolean get() = powerManager.isIgnoringBatteryOptimizations(walletApplication.packageName) + val voteDashPayIsEnabled = walletUIConfig.observe(WalletUIConfig.VOTE_DASH_PAY_ENABLED) + val coinJoinMixingMode: Flow + get() = coinJoinConfig.observeMode() + + var coinJoinMixingStatus: MixingStatus = MixingStatus.NOT_STARTED + init { + coinJoinService.observeMixingState() + .onEach { coinJoinMixingStatus = it } + .launchIn(viewModelScope) + } + + var decimalFormat: DecimalFormat = DecimalFormat("0.000") + val walletBalance: String + get() = decimalFormat.format(walletDataProvider.wallet!!.getBalance(Wallet.BalanceType.ESTIMATED).toBigDecimal()) + + val mixedBalance: String + get() = decimalFormat.format((walletDataProvider.wallet as WalletEx).coinJoinBalance.toBigDecimal()) + + fun setVoteDashPay(isEnabled: Boolean) { + viewModelScope.launch { + walletUIConfig.set(WalletUIConfig.VOTE_DASH_PAY_ENABLED, isEnabled) + } + } + + suspend fun shouldShowCoinJoinInfo(): Boolean { + return coinJoinConfig.get(CoinJoinConfig.FIRST_TIME_INFO_SHOWN) != true + } + + suspend fun setCoinJoinInfoShown() { + coinJoinConfig.set(CoinJoinConfig.FIRST_TIME_INFO_SHOWN, true) + } } From 2a2d4f36fd5e8034b6b5efd5144dcd43e31db649 Mon Sep 17 00:00:00 2001 From: HashEngineering Date: Sun, 31 Dec 2023 17:13:14 -0600 Subject: [PATCH 27/28] fix: use WalletEx in WalletFactory --- wallet/src/de/schildbach/wallet/service/WalletFactory.kt | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/wallet/src/de/schildbach/wallet/service/WalletFactory.kt b/wallet/src/de/schildbach/wallet/service/WalletFactory.kt index 0ef4108f1f..353063b17b 100644 --- a/wallet/src/de/schildbach/wallet/service/WalletFactory.kt +++ b/wallet/src/de/schildbach/wallet/service/WalletFactory.kt @@ -36,6 +36,7 @@ import org.bitcoinj.wallet.DeterministicSeed import org.bitcoinj.wallet.KeyChainGroup import org.bitcoinj.wallet.UnreadableWalletException import org.bitcoinj.wallet.Wallet +import org.bitcoinj.wallet.WalletEx import org.bitcoinj.wallet.WalletExtension import org.bitcoinj.wallet.WalletProtobufSerializer import org.bitcoinj.wallet.authentication.AuthenticationGroupExtension @@ -99,7 +100,7 @@ class DashWalletFactory @Inject constructor( } override fun create(params: NetworkParameters): Wallet { - val wallet = Wallet.createDeterministic(params, Script.ScriptType.P2PKH) + val wallet = WalletEx.createDeterministic(params, Script.ScriptType.P2PKH) addMissingExtensions(wallet) checkWalletValid(wallet, params) return wallet @@ -270,7 +271,7 @@ class DashWalletFactory @Inject constructor( .build() ) .build() - val wallet = Wallet(params, group) + val wallet = WalletEx(params, group) // add extensions addMissingExtensions(wallet) @@ -291,7 +292,7 @@ class DashWalletFactory @Inject constructor( // create non-HD wallet val group = KeyChainGroup.builder(expectedNetworkParameters).build() group.importKeys(readKeys(keyReader, expectedNetworkParameters)) - val wallet = Wallet(expectedNetworkParameters, group) + val wallet = WalletEx(expectedNetworkParameters, group) // this will result in a different HD seed each time wallet.upgradeToDeterministic(Script.ScriptType.P2PKH, null) // add the extensions From 9021f85baaf55454dd69c030b02b7964b4074c7f Mon Sep 17 00:00:00 2001 From: HashEngineering Date: Sun, 31 Dec 2023 17:13:38 -0600 Subject: [PATCH 28/28] chore: update dashpay workflow for Java 17 --- .github/workflows/dashpay.yml | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/.github/workflows/dashpay.yml b/.github/workflows/dashpay.yml index dce99853a5..de06754b05 100644 --- a/.github/workflows/dashpay.yml +++ b/.github/workflows/dashpay.yml @@ -26,7 +26,7 @@ jobs: - name: set up JDK uses: actions/setup-java@v3 with: - java-version: '11' + java-version: '17' distribution: 'adopt' cache: gradle @@ -53,6 +53,7 @@ jobs: - name: Setup fastlane run: | gem install bundler:2.2.26 + gem install google-cloud-storage bundle config path vendor/bundle bundle install --jobs 4 --retry 3 @@ -91,6 +92,6 @@ jobs: if: github.event_name == 'pull_request' run: bundle exec fastlane build flavor:"staging" type:"release" storepass:"${{ secrets.SIGNING_STORE_PASS }}" versioncode:"${{ env.build_number }}" - - name: Build and Firebase Distribution + - name: Staging Build and Firebase Distribution if: github.event_name == 'push' - run: bundle exec fastlane build_distribute flavor:"staging" type:"release" storepass:"${{ secrets.SIGNING_STORE_PASS }}" versioncode:"${{ env.build_number }}" comment:"Up to date DashPay build" appid:"1:1039839682638:android:bbcfa8c9939ee993ea631f" testgroup:"qa" + run: bundle exec fastlane build_distribute flavor:"staging" type:"release" storepass:"${{ secrets.SIGNING_STORE_PASS }}" versioncode:"${{ env.build_number }}" comment:"Up to date Dash Wallet TestNet build" appid:"1:1039839682638:android:3202b16d460a1a88" testgroup:"qa" SUPPORT_EMAIL:"${{ secrets.INTERNAL_SUPPORT_EMAIL }}"