From eff698c6aefd4053ab04e6e896090532f856a8ee Mon Sep 17 00:00:00 2001 From: Andrei Ashikhmin Date: Thu, 22 Feb 2024 18:08:57 +0700 Subject: [PATCH 1/3] fix: coinbase login & coin parse error --- .../coinbase/CoinbaseConstants.kt | 1 + .../coinbase/model/AccountsResponse.kt | 19 ++ .../coinbase/repository/CoinBaseRepository.kt | 28 +-- .../coinbase/service/CoinBaseAuthApi.kt | 3 +- .../coinbase/ui/CoinBaseWebClientActivity.kt | 18 +- .../viewmodels/CoinbaseServicesViewModel.kt | 2 +- .../coinbase/viewmodels/CoinbaseViewModel.kt | 18 +- .../viewmodels/TransferDashViewModel.kt | 7 +- .../wallet/ui/WalletUriHandlerActivity.java | 194 ------------------ .../wallet/ui/WalletUriHandlerActivity.kt | 183 +++++++++++++++++ .../wallet/ui/buy_sell/BuyAndSellViewModel.kt | 5 +- .../buy_sell/IntegrationOverviewFragment.kt | 45 ++-- .../buy_sell/IntegrationOverviewViewModel.kt | 36 ++-- 13 files changed, 286 insertions(+), 273 deletions(-) delete mode 100644 wallet/src/de/schildbach/wallet/ui/WalletUriHandlerActivity.java create mode 100644 wallet/src/de/schildbach/wallet/ui/WalletUriHandlerActivity.kt diff --git a/integrations/coinbase/src/main/java/org/dash/wallet/integrations/coinbase/CoinbaseConstants.kt b/integrations/coinbase/src/main/java/org/dash/wallet/integrations/coinbase/CoinbaseConstants.kt index bd8300462e..3d08d65f30 100644 --- a/integrations/coinbase/src/main/java/org/dash/wallet/integrations/coinbase/CoinbaseConstants.kt +++ b/integrations/coinbase/src/main/java/org/dash/wallet/integrations/coinbase/CoinbaseConstants.kt @@ -36,6 +36,7 @@ object CoinbaseConstants { 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 + const val REDIRECT_URL = "dashwallet://brokers/coinbase/connect" fun getCacheDir(context: Context): File { return File(context.cacheDir, "coinbase") 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 index 551d220934..2753905aaa 100644 --- 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 @@ -19,7 +19,15 @@ package org.dash.wallet.integrations.coinbase.model import android.os.Parcelable import com.google.gson.annotations.SerializedName +import kotlinx.parcelize.IgnoredOnParcel import kotlinx.parcelize.Parcelize +import org.bitcoinj.core.Coin +import org.dash.wallet.common.util.toCoin +import java.lang.Exception +import java.lang.IllegalArgumentException +import java.math.BigDecimal +import java.math.MathContext +import java.math.RoundingMode import java.util.UUID @Parcelize @@ -51,6 +59,17 @@ data class CoinbaseAccount( ready = false ) } + + fun coinBalance(): Coin = try { + Coin.parseCoin(availableBalance.value) + } catch (ex: IllegalArgumentException) { + try { + val rounded = BigDecimal(availableBalance.value).round(MathContext(8, RoundingMode.HALF_UP)) + rounded.toCoin() + } catch (ex: Exception) { + Coin.ZERO + } + } } @Parcelize diff --git a/integrations/coinbase/src/main/java/org/dash/wallet/integrations/coinbase/repository/CoinBaseRepository.kt b/integrations/coinbase/src/main/java/org/dash/wallet/integrations/coinbase/repository/CoinBaseRepository.kt index 05d80a074f..f326e6d72c 100644 --- a/integrations/coinbase/src/main/java/org/dash/wallet/integrations/coinbase/repository/CoinBaseRepository.kt +++ b/integrations/coinbase/src/main/java/org/dash/wallet/integrations/coinbase/repository/CoinBaseRepository.kt @@ -29,6 +29,7 @@ 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.common.util.toCoin import org.dash.wallet.integrations.coinbase.* import org.dash.wallet.integrations.coinbase.model.* import org.dash.wallet.integrations.coinbase.service.CoinBaseAuthApi @@ -37,6 +38,8 @@ 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 java.math.MathContext +import java.math.RoundingMode import javax.inject.Inject interface CoinBaseRepositoryInt { @@ -60,7 +63,7 @@ interface CoinBaseRepositoryInt { ): ResponseResource suspend fun swapTrade(tradesRequest: TradesRequest): ResponseResource suspend fun commitSwapTrade(buyOrderId: String): ResponseResource - suspend fun completeCoinbaseAuthentication(authorizationCode: String): ResponseResource + suspend fun completeCoinbaseAuthentication(authorizationCode: String): Boolean suspend fun refreshWithdrawalLimit() suspend fun getExchangeRateFromCoinbase(): ResponseResource suspend fun getWithdrawalLimitInDash(): Double @@ -98,10 +101,10 @@ class CoinBaseRepository @Inject constructor( 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) - } + config.set(CoinbaseConfig.USER_ACCOUNT_ID, userAccountData.uuid.toString()) + config.set(CoinbaseConfig.LAST_BALANCE, userAccountData.coinBalance().value) + + return userAccountData } override suspend fun getUserAccounts(exchangeCurrencyCode: String): List { @@ -217,15 +220,14 @@ class CoinBaseRepository @Inject constructor( ) } - override suspend fun completeCoinbaseAuthentication(authorizationCode: String) = safeApiCall { - authApi.getToken(code = authorizationCode).also { - it?.let { tokenResponse -> - config.set(CoinbaseConfig.LAST_ACCESS_TOKEN, tokenResponse.accessToken) - config.set(CoinbaseConfig.LAST_REFRESH_TOKEN, tokenResponse.refreshToken) - getUserAccount() - } + override suspend fun completeCoinbaseAuthentication(authorizationCode: String): Boolean { + authApi.getToken(code = authorizationCode)?.let { tokenResponse -> + config.set(CoinbaseConfig.LAST_ACCESS_TOKEN, tokenResponse.accessToken) + config.set(CoinbaseConfig.LAST_REFRESH_TOKEN, tokenResponse.refreshToken) + getUserAccount() } - !config.get(CoinbaseConfig.LAST_ACCESS_TOKEN).isNullOrEmpty() + + return !config.get(CoinbaseConfig.LAST_ACCESS_TOKEN).isNullOrEmpty() } override suspend fun refreshWithdrawalLimit() { diff --git a/integrations/coinbase/src/main/java/org/dash/wallet/integrations/coinbase/service/CoinBaseAuthApi.kt b/integrations/coinbase/src/main/java/org/dash/wallet/integrations/coinbase/service/CoinBaseAuthApi.kt index 7adeeb17ff..715ef977df 100644 --- a/integrations/coinbase/src/main/java/org/dash/wallet/integrations/coinbase/service/CoinBaseAuthApi.kt +++ b/integrations/coinbase/src/main/java/org/dash/wallet/integrations/coinbase/service/CoinBaseAuthApi.kt @@ -16,6 +16,7 @@ */ package org.dash.wallet.integrations.coinbase.service +import org.dash.wallet.integrations.coinbase.CoinbaseConstants import org.dash.wallet.integrations.coinbase.model.TokenResponse import retrofit2.http.Field import retrofit2.http.FormUrlEncoded @@ -26,7 +27,7 @@ interface CoinBaseAuthApi { @POST("oauth/token") suspend fun getToken( @Field("client_id") clientId: String = CoinBaseClientConstants.CLIENT_ID, - @Field("redirect_uri") redirectUri: String = "authhub://oauth-callback", + @Field("redirect_uri") redirectUri: String = CoinbaseConstants.REDIRECT_URL, @Field("grant_type") grant_type: String = "authorization_code", @Field("client_secret") client_secret: String = CoinBaseClientConstants.CLIENT_SECRET, @Field("code") code: String diff --git a/integrations/coinbase/src/main/java/org/dash/wallet/integrations/coinbase/ui/CoinBaseWebClientActivity.kt b/integrations/coinbase/src/main/java/org/dash/wallet/integrations/coinbase/ui/CoinBaseWebClientActivity.kt index 244814bd70..b3e036acdf 100644 --- a/integrations/coinbase/src/main/java/org/dash/wallet/integrations/coinbase/ui/CoinBaseWebClientActivity.kt +++ b/integrations/coinbase/src/main/java/org/dash/wallet/integrations/coinbase/ui/CoinBaseWebClientActivity.kt @@ -66,21 +66,9 @@ class CoinBaseWebClientActivity : InteractionAwareActivity() { binding.webView.settings.allowFileAccess = false WebStorage.getInstance().deleteAllData() - val loginUrl = - "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: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" + - "&meta[send_limit_currency]=USD" + - "&meta[send_limit_period]=month" + - "&account=all" - - binding.webView.loadUrl(loginUrl) + + + binding.webView.loadUrl("") } override fun onDestroy() { diff --git a/integrations/coinbase/src/main/java/org/dash/wallet/integrations/coinbase/viewmodels/CoinbaseServicesViewModel.kt b/integrations/coinbase/src/main/java/org/dash/wallet/integrations/coinbase/viewmodels/CoinbaseServicesViewModel.kt index 18f23f7dd2..eb3ba1c394 100644 --- a/integrations/coinbase/src/main/java/org/dash/wallet/integrations/coinbase/viewmodels/CoinbaseServicesViewModel.kt +++ b/integrations/coinbase/src/main/java/org/dash/wallet/integrations/coinbase/viewmodels/CoinbaseServicesViewModel.kt @@ -100,7 +100,7 @@ class CoinbaseServicesViewModel @Inject constructor( val response = coinBaseRepository.getUserAccount() config.set( CoinbaseConfig.LAST_BALANCE, - Coin.parseCoin(response.availableBalance.value).value + response.coinBalance().value ) } catch (ex: IllegalStateException) { _uiState.value = _uiState.value.copy(error = CoinbaseErrorType.USER_ACCOUNT_ERROR) 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 index f23597831b..a207cc6da8 100644 --- 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 @@ -38,6 +38,7 @@ 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 java.lang.Exception import javax.inject.Inject data class CoinbaseUIState( @@ -79,19 +80,12 @@ class CoinbaseViewModel @Inject constructor( } 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 try { + coinBaseRepository.completeCoinbaseAuthentication(code) + } catch (ex: Exception) { + log.error("Coinbase login error $ex") + false } - - return false } fun getBaseIdForFiatModel() = viewModelScope.launch { _uiState.update { it.copy(baseIdForFiatModel = BaseIdForFiatData.LoadingState) } diff --git a/integrations/coinbase/src/main/java/org/dash/wallet/integrations/coinbase/viewmodels/TransferDashViewModel.kt b/integrations/coinbase/src/main/java/org/dash/wallet/integrations/coinbase/viewmodels/TransferDashViewModel.kt index 40d9997ee8..1099ecabf3 100644 --- a/integrations/coinbase/src/main/java/org/dash/wallet/integrations/coinbase/viewmodels/TransferDashViewModel.kt +++ b/integrations/coinbase/src/main/java/org/dash/wallet/integrations/coinbase/viewmodels/TransferDashViewModel.kt @@ -146,12 +146,7 @@ class TransferDashViewModel @Inject constructor( } private fun calculateCoinbaseMaxAllowedValue(account:CoinbaseToDashExchangeRateUIModel) { - val maxCoinValue = try { - Coin.parseCoin(account.coinbaseAccount.availableBalance.value) - } catch (x: Exception) { - Coin.ZERO - } - maxForDashCoinBaseAccount = maxCoinValue + maxForDashCoinBaseAccount = account.coinbaseAccount.coinBalance() } private suspend fun isInputGreaterThanLimit(amountInDash: Coin): Boolean { diff --git a/wallet/src/de/schildbach/wallet/ui/WalletUriHandlerActivity.java b/wallet/src/de/schildbach/wallet/ui/WalletUriHandlerActivity.java deleted file mode 100644 index 5b2362444e..0000000000 --- a/wallet/src/de/schildbach/wallet/ui/WalletUriHandlerActivity.java +++ /dev/null @@ -1,194 +0,0 @@ -/* - * Copyright 2011-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.ui; - -import android.app.Activity; -import android.content.Intent; -import android.content.pm.ApplicationInfo; -import android.net.Uri; -import android.os.Bundle; - -import androidx.annotation.Nullable; -import androidx.appcompat.app.AppCompatActivity; -import androidx.localbroadcastmanager.content.LocalBroadcastManager; - -import org.bitcoinj.core.Address; -import org.bitcoinj.wallet.Wallet; -import org.dash.wallet.common.ui.BaseAlertDialogBuilder; -import org.dash.wallet.integrations.uphold.ui.UpholdPortalFragment; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import de.schildbach.wallet.Constants; -import de.schildbach.wallet.WalletApplication; -import de.schildbach.wallet.data.PaymentIntent; -import de.schildbach.wallet.integration.android.BitcoinIntegration; -import de.schildbach.wallet.ui.main.WalletActivity; -import de.schildbach.wallet.ui.send.SendCoinsActivity; -import de.schildbach.wallet.ui.util.InputParser; -import de.schildbach.wallet.ui.util.WalletUri; -import de.schildbach.wallet_test.R; -import kotlin.Unit; -import kotlin.jvm.functions.Function0; - -import static org.dash.wallet.common.ui.BaseAlertDialogBuilderKt.formatString; - -/** - * The only purpose of this Activity is to handle all so called Wallet Uris - * providing simple and convenient Inter App Communication. - * It could not be handled directly by WalletActivity, since it is configured - * as a singleTask and doesn't support startActivityForResult(...) pattern. - */ -public final class WalletUriHandlerActivity extends AppCompatActivity { - - private static final int REQUEST_CODE_SEND_FROM_WALLET_URI = 1; - - private Wallet wallet; - private static final Logger log = LoggerFactory.getLogger(WalletUriHandlerActivity.class); - - - @Override - protected void onCreate(@Nullable Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - - - WalletApplication application = (WalletApplication) getApplication(); - wallet = application.getWallet(); - - handleIntent(getIntent()); - } - - @Override - protected void onNewIntent(Intent intent) { - super.onNewIntent(intent); - handleIntent(getIntent()); - } - - private void handleIntent(final Intent intent) { - if (wallet == null) { - setResult(RESULT_CANCELED); - finish(); - return; - } - - final String action = intent.getAction(); - final Uri intentUri = intent.getData(); - final String scheme = intentUri != null ? intentUri.getScheme() : null; - - if (Intent.ACTION_VIEW.equals(action) && Constants.WALLET_URI_SCHEME.equals(scheme)) { - if (intentUri.getHost().equalsIgnoreCase("brokers")) { - if (intentUri.getPath().contains("uphold")) { - Intent activityIntent = new Intent(this, WalletActivity.class); - activityIntent.putExtra("uri", intentUri); - activityIntent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP); - activityIntent.addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP); - activityIntent.setAction(UpholdPortalFragment.AUTH_RESULT_ACTION); - LocalBroadcastManager.getInstance(this).sendBroadcast(activityIntent); - } - finish(); - } else { - new InputParser.WalletUriParser(intentUri) { - @Override - protected void handlePaymentIntent(final PaymentIntent paymentIntent, boolean forceInstantSend) { - SendCoinsActivity.Companion.sendFromWalletUri( - WalletUriHandlerActivity.this, REQUEST_CODE_SEND_FROM_WALLET_URI, paymentIntent); - } - - protected void handleMasterPublicKeyRequest(String sender) { - String confirmationMessage = getString(R.string.wallet_uri_handler_public_key_request_dialog_msg, sender); - showConfirmationDialog(confirmationMessage, positiveBtnClickCreateMasterKey); - } - - protected void handleAddressRequest(String sender) { - String confirmationMessage = getString(R.string.wallet_uri_handler_address_request_dialog_msg, sender); - showConfirmationDialog(confirmationMessage, positiveBtnClickCreateAddress); - } - - @Override - protected void error(Exception x, final int messageResId, final Object... messageArgs) { - BaseAlertDialogBuilder baseAlertDialogBuilder = new BaseAlertDialogBuilder(WalletUriHandlerActivity.this); - baseAlertDialogBuilder.setMessage(formatString(WalletUriHandlerActivity.this, messageResId, messageArgs)); - baseAlertDialogBuilder.setNeutralText(getString(R.string.button_dismiss)); - baseAlertDialogBuilder.setNeutralAction( - () -> { - WalletUriHandlerActivity.this.finish(); - return Unit.INSTANCE; - } - ); - } - }.parse(); - } - } - } - - private String getAppName() { - ApplicationInfo applicationInfo = getApplicationInfo(); - int stringId = applicationInfo.labelRes; - return stringId == 0 ? applicationInfo.nonLocalizedLabel.toString() : getString(stringId); - } - - private void showConfirmationDialog(String message, final Function0 onPositiveButtonClickListener) { - BaseAlertDialogBuilder confirmationAlertDialogBuilder = new BaseAlertDialogBuilder(this); - confirmationAlertDialogBuilder.setTitle(getString(R.string.app_name)); - confirmationAlertDialogBuilder.setMessage(message); - confirmationAlertDialogBuilder.setPositiveText(getString(R.string.button_ok)); - confirmationAlertDialogBuilder.setPositiveAction(onPositiveButtonClickListener); - confirmationAlertDialogBuilder.setNegativeText( getString(R.string.button_cancel)); - confirmationAlertDialogBuilder.setNegativeAction(negativeButtonClickListener); - confirmationAlertDialogBuilder.buildAlertDialog().show(); - } - - @Override - protected void onActivityResult(int requestCode, int resultCode, Intent data) { - super.onActivityResult(requestCode, resultCode, data); - if (requestCode == REQUEST_CODE_SEND_FROM_WALLET_URI) { - Intent result = null; - if (resultCode == Activity.RESULT_OK) { - Uri requestData = getIntent().getData(); - String transactionHash = BitcoinIntegration.transactionHashFromResult(data); - result = WalletUri.createPaymentResult(requestData, transactionHash); - } - setResult(resultCode, result); - finish(); - } - } - - private final Function0 negativeButtonClickListener = () -> { - WalletUriHandlerActivity.this.setResult(RESULT_CANCELED); - WalletUriHandlerActivity.this.finish(); - return Unit.INSTANCE; - }; - - private final Function0 positiveBtnClickCreateMasterKey = () -> { - String watchingKey = wallet.getWatchingKey().serializePubB58(wallet.getNetworkParameters()); - Uri requestData = getIntent().getData(); - Intent result = WalletUri.createMasterPublicKeyResult(requestData, watchingKey, null, getAppName()); - setResult(RESULT_OK, result); - finish(); - return Unit.INSTANCE; - }; - - private final Function0 positiveBtnClickCreateAddress = () -> { - Address address = wallet.freshReceiveAddress(); - Uri requestData = getIntent().getData(); - Intent result = WalletUri.createAddressResult(requestData, address.toString(), getAppName()); - setResult(RESULT_OK, result); - finish(); - return Unit.INSTANCE; - }; -} diff --git a/wallet/src/de/schildbach/wallet/ui/WalletUriHandlerActivity.kt b/wallet/src/de/schildbach/wallet/ui/WalletUriHandlerActivity.kt new file mode 100644 index 0000000000..71cedcd03a --- /dev/null +++ b/wallet/src/de/schildbach/wallet/ui/WalletUriHandlerActivity.kt @@ -0,0 +1,183 @@ +/* + * Copyright 2024 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 + +import android.content.Intent +import android.os.Bundle +import androidx.appcompat.app.AppCompatActivity +import androidx.localbroadcastmanager.content.LocalBroadcastManager +import de.schildbach.wallet.Constants +import de.schildbach.wallet.WalletApplication +import de.schildbach.wallet.data.PaymentIntent +import de.schildbach.wallet.integration.android.BitcoinIntegration +import de.schildbach.wallet.ui.buy_sell.IntegrationOverviewFragment +import de.schildbach.wallet.ui.main.WalletActivity +import de.schildbach.wallet.ui.send.SendCoinsActivity.Companion.sendFromWalletUri +import de.schildbach.wallet.ui.util.InputParser.WalletUriParser +import de.schildbach.wallet.ui.util.WalletUri +import de.schildbach.wallet_test.R +import org.bitcoinj.wallet.Wallet +import org.dash.wallet.common.ui.BaseAlertDialogBuilder +import org.dash.wallet.common.ui.formatString +import org.dash.wallet.integrations.uphold.ui.UpholdPortalFragment + +/** + * The only purpose of this Activity is to handle all so called Wallet Uris + * providing simple and convenient Inter App Communication. + * It could not be handled directly by WalletActivity, since it is configured + * as a singleTask and doesn't support startActivityForResult(...) pattern. + */ +class WalletUriHandlerActivity : AppCompatActivity() { + companion object { + private const val REQUEST_CODE_SEND_FROM_WALLET_URI = 1 + } + + private var wallet: Wallet? = null + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + wallet = (application as WalletApplication).wallet + handleIntent(intent) + } + + override fun onNewIntent(intent: Intent) { + super.onNewIntent(intent) + handleIntent(getIntent()) + } + + private fun handleIntent(intent: Intent) { + if (wallet == null) { + setResult(RESULT_CANCELED) + finish() + return + } + + intent.data?.let { intentUri -> + val action = intent.action + val scheme = intentUri.scheme + + if (Intent.ACTION_VIEW == action && Constants.WALLET_URI_SCHEME == scheme) { + if (intentUri.host.equals("brokers", ignoreCase = true)) { + val activityIntent = Intent(this, WalletActivity::class.java) + activityIntent.putExtra("uri", intentUri) + activityIntent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP) + activityIntent.addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP) + + if (intentUri.path?.contains("uphold") == true) { + activityIntent.setAction(UpholdPortalFragment.AUTH_RESULT_ACTION) + LocalBroadcastManager.getInstance(this).sendBroadcast(activityIntent) + } else if (intentUri.path?.contains("coinbase") == true) { + activityIntent.setAction(IntegrationOverviewFragment.AUTH_RESULT_ACTION) + LocalBroadcastManager.getInstance(this).sendBroadcast(activityIntent) + } + finish() + } else { + object : WalletUriParser(intentUri) { + override fun handlePaymentIntent(paymentIntent: PaymentIntent, forceInstantSend: Boolean) { + sendFromWalletUri( + this@WalletUriHandlerActivity, REQUEST_CODE_SEND_FROM_WALLET_URI, paymentIntent + ) + } + + override fun handleMasterPublicKeyRequest(sender: String) { + val confirmationMessage = + getString(R.string.wallet_uri_handler_public_key_request_dialog_msg, sender) + showConfirmationDialog(confirmationMessage, positiveBtnClickCreateMasterKey) + } + + override fun handleAddressRequest(sender: String) { + val confirmationMessage = + getString(R.string.wallet_uri_handler_address_request_dialog_msg, sender) + showConfirmationDialog(confirmationMessage, positiveBtnClickCreateAddress) + } + + override fun error(x: Exception, messageResId: Int, vararg messageArgs: Any) { + val baseAlertDialogBuilder = BaseAlertDialogBuilder(this@WalletUriHandlerActivity) + baseAlertDialogBuilder.message = + this@WalletUriHandlerActivity.formatString(messageResId, messageArgs) + baseAlertDialogBuilder.neutralText = getString(R.string.button_dismiss) + baseAlertDialogBuilder.neutralAction = { + finish() + } + } + }.parse() + } + } + } + } + + private val appName: String + get() { + val applicationInfo = applicationInfo + val stringId = applicationInfo.labelRes + return if (stringId == 0) applicationInfo.nonLocalizedLabel.toString() else getString(stringId) + } + + private fun showConfirmationDialog(message: String, onPositiveButtonClickListener: Function0) { + val confirmationAlertDialogBuilder = BaseAlertDialogBuilder(this) + confirmationAlertDialogBuilder.title = getString(R.string.app_name) + confirmationAlertDialogBuilder.message = message + confirmationAlertDialogBuilder.positiveText = getString(R.string.button_ok) + confirmationAlertDialogBuilder.positiveAction = onPositiveButtonClickListener + confirmationAlertDialogBuilder.negativeText = getString(R.string.button_cancel) + confirmationAlertDialogBuilder.negativeAction = negativeButtonClickListener + confirmationAlertDialogBuilder.buildAlertDialog().show() + } + + override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { + super.onActivityResult(requestCode, resultCode, data) + + if (requestCode == REQUEST_CODE_SEND_FROM_WALLET_URI) { + var result: Intent? = null + + if (resultCode == RESULT_OK) { + val requestData = intent.data + val transactionHash = BitcoinIntegration.transactionHashFromResult(data) + result = WalletUri.createPaymentResult(requestData, transactionHash) + } + + setResult(resultCode, result) + finish() + } + } + + private val negativeButtonClickListener = { + this@WalletUriHandlerActivity.setResult(RESULT_CANCELED) + finish() + } + + private val positiveBtnClickCreateMasterKey = { + val watchingKey = wallet!!.watchingKey.serializePubB58(wallet!!.networkParameters) + val requestData = intent.data + val result = WalletUri.createMasterPublicKeyResult( + requestData, watchingKey, null, + appName + ) + setResult(RESULT_OK, result) + finish() + } + + private val positiveBtnClickCreateAddress = { + val address = wallet!!.freshReceiveAddress() + val requestData = intent.data + val result = WalletUri.createAddressResult(requestData, address.toString(), appName) + setResult(RESULT_OK, result) + finish() + } +} + 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 1ed1901599..939fa30ada 100644 --- a/wallet/src/de/schildbach/wallet/ui/buy_sell/BuyAndSellViewModel.kt +++ b/wallet/src/de/schildbach/wallet/ui/buy_sell/BuyAndSellViewModel.kt @@ -203,9 +203,8 @@ class BuyAndSellViewModel @Inject constructor( viewModelScope.launch { try { val account = coinBaseRepository.getUserAccount() - val balance = account.availableBalance.value - coinbaseConfig.set(CoinbaseConfig.LAST_BALANCE, Coin.parseCoin(balance).value) - showRowBalance(ServiceType.COINBASE, balance) + coinbaseConfig.set(CoinbaseConfig.LAST_BALANCE, account.coinBalance().value) + showRowBalance(ServiceType.COINBASE, account.availableBalance.value) } 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 2f7d1c298f..8b49557638 100644 --- a/wallet/src/de/schildbach/wallet/ui/buy_sell/IntegrationOverviewFragment.kt +++ b/wallet/src/de/schildbach/wallet/ui/buy_sell/IntegrationOverviewFragment.kt @@ -17,39 +17,47 @@ package de.schildbach.wallet.ui.buy_sell -import android.app.Activity +import android.content.BroadcastReceiver +import android.content.Context import android.content.Intent +import android.content.IntentFilter +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.localbroadcastmanager.content.LocalBroadcastManager import androidx.navigation.fragment.findNavController import dagger.hilt.android.AndroidEntryPoint -import org.dash.wallet.integrations.coinbase.ui.CoinBaseWebClientActivity import de.schildbach.wallet_test.R import de.schildbach.wallet_test.databinding.FragmentIntegrationOverviewBinding 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.openCustomTab import org.dash.wallet.common.util.safeNavigate @AndroidEntryPoint class IntegrationOverviewFragment : Fragment(R.layout.fragment_integration_overview) { + companion object { + const val AUTH_RESULT_ACTION = "IntegrationOverviewFragment.AUTH_RESULT" + } + private val binding by viewBinding(FragmentIntegrationOverviewBinding::bind) private val viewModel by viewModels() - private val coinbaseAuthLauncher = registerForActivityResult( - ActivityResultContracts.StartActivityForResult() - ) { result -> - val data = result.data + private val coinbaseAuthResultReceiver = object : BroadcastReceiver() { + override fun onReceive(context: Context, intent: Intent) { + val uri = intent.extras?.get("uri") as Uri? + val code = uri?.getQueryParameter("code") - if (result.resultCode == Activity.RESULT_OK) { - data?.extras?.getString(CoinBaseWebClientActivity.RESULT_TEXT)?.let { code -> + if (code != null) { handleCoinbaseAuthResult(code) } + + startActivity(intent) } } @@ -67,6 +75,11 @@ class IntegrationOverviewFragment : Fragment(R.layout.fragment_integration_overv // as there are not enough supported currencies with the v3 API binding.buyConvertText.isVisible = false binding.buyConvertIc.isVisible = false + + LocalBroadcastManager.getInstance(requireContext()).registerReceiver( + coinbaseAuthResultReceiver, + IntentFilter(AUTH_RESULT_ACTION) + ) } private fun continueCoinbase() { @@ -81,18 +94,18 @@ class IntegrationOverviewFragment : Fragment(R.layout.fragment_integration_overv if (goodToGo) { viewModel.setCoinbaseInfoPopupShown() - coinbaseAuthLauncher.launch( - Intent( - requireContext(), - CoinBaseWebClientActivity::class.java - ) - ) + linkCoinbaseAccount() } } } + private fun linkCoinbaseAccount() { + val url = viewModel.getCoinbaseLinkAccountUrl() + requireActivity().openCustomTab(url) + } + private fun handleCoinbaseAuthResult(code: String) { - lifecycleScope.launchWhenResumed { + lifecycleScope.launch { val success = AdaptiveDialog.withProgress(getString(R.string.loading), requireActivity()) { viewModel.loginToCoinbase(code) } 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 ff9f16f3b6..c4b22fa9fc 100644 --- a/wallet/src/de/schildbach/wallet/ui/buy_sell/IntegrationOverviewViewModel.kt +++ b/wallet/src/de/schildbach/wallet/ui/buy_sell/IntegrationOverviewViewModel.kt @@ -21,9 +21,13 @@ 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.integrations.coinbase.CoinbaseConstants import org.dash.wallet.integrations.coinbase.repository.CoinBaseRepositoryInt +import org.dash.wallet.integrations.coinbase.service.CoinBaseClientConstants import org.dash.wallet.integrations.coinbase.utils.CoinbaseConfig +import org.dash.wallet.integrations.coinbase.viewmodels.CoinbaseViewModel import org.slf4j.LoggerFactory +import java.lang.Exception import javax.inject.Inject @HiltViewModel @@ -37,19 +41,12 @@ class IntegrationOverviewViewModel @Inject constructor( } 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 try { + coinBaseRepository.completeCoinbaseAuthentication(code) + } catch (ex: Exception) { + log.error("Coinbase login error $ex") + false } - - return false } suspend fun shouldShowCoinbaseInfoPopup(): Boolean { @@ -63,4 +60,19 @@ class IntegrationOverviewViewModel @Inject constructor( fun logEvent(eventName: String) { analyticsService.logEvent(eventName, mapOf()) } + + fun getCoinbaseLinkAccountUrl(): String { + return "https://www.coinbase.com/oauth/authorize?client_id=${CoinBaseClientConstants.CLIENT_ID}" + + "&redirect_uri=${CoinbaseConstants.REDIRECT_URL}&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: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]=10" + + "&meta[send_limit_currency]=USD" + + "&meta[send_limit_period]=month" + + "&account=all" + } } From 3878098823fa83a021e0a241944cee24f7f65590 Mon Sep 17 00:00:00 2001 From: Andrei Ashikhmin Date: Thu, 22 Feb 2024 21:10:27 +0700 Subject: [PATCH 2/3] chore: cleanup --- .../coinbase/ui/CoinBaseWebClientActivity.kt | 129 ------------------ 1 file changed, 129 deletions(-) delete mode 100644 integrations/coinbase/src/main/java/org/dash/wallet/integrations/coinbase/ui/CoinBaseWebClientActivity.kt diff --git a/integrations/coinbase/src/main/java/org/dash/wallet/integrations/coinbase/ui/CoinBaseWebClientActivity.kt b/integrations/coinbase/src/main/java/org/dash/wallet/integrations/coinbase/ui/CoinBaseWebClientActivity.kt deleted file mode 100644 index b3e036acdf..0000000000 --- a/integrations/coinbase/src/main/java/org/dash/wallet/integrations/coinbase/ui/CoinBaseWebClientActivity.kt +++ /dev/null @@ -1,129 +0,0 @@ -/* - * 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.ui - -import android.annotation.SuppressLint -import android.content.Intent -import android.net.Uri -import android.os.Bundle -import android.view.MenuItem -import android.webkit.* -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() { - - private lateinit var binding: FragmentWebviewBinding - - override fun onCreate(savedInstanceState: Bundle?) { - super.onCreate(savedInstanceState) - binding = FragmentWebviewBinding.inflate(layoutInflater) - setContentView(binding.root) - initUI() - turnOffAutoLogout() - } - - @SuppressLint("SetJavaScriptEnabled") - private fun initUI() { - setSupportActionBar(binding.toolbar) - val actionBar = supportActionBar - actionBar?.apply { - setDisplayHomeAsUpEnabled(true) - setDisplayShowHomeEnabled(true) - } - setTitle(R.string.coinbase) - // Clear all the Application Cache, Web SQL Database and the HTML5 Web Storage - WebStorage.getInstance().deleteAllData() - - // Clear all the cookies - CookieManager.getInstance().removeAllCookies(null) - CookieManager.getInstance().flush() - - binding.webView.clearCache(true) - binding.webView.clearFormData() - binding.webView.clearHistory() - binding.webView.clearSslPreferences() - - binding.webView.webViewClient = MyWebViewClient() - binding.webView.webChromeClient = WebChromeClient() - binding.webView.settings.javaScriptEnabled = true - binding.webView.settings.allowFileAccess = false - WebStorage.getInstance().deleteAllData() - - - - binding.webView.loadUrl("") - } - - override fun onDestroy() { - super.onDestroy() - turnOnAutoLogout() - } - - override fun onPause() { - super.onPause() - binding.webView.onPause() - } - - override fun onResume() { - super.onResume() - binding.webView.onResume() - } - - private fun setActivityResult(code: String) { - val intent = Intent() - intent.putExtra(RESULT_TEXT, code) - setResult(RESULT_OK, intent) - finish() - } - - private inner class MyWebViewClient : WebViewClient() { - - override fun shouldOverrideUrlLoading(view: WebView?, url: String?): Boolean { - val uri = Uri.parse(url) - val host = uri.host - if (Uri.parse(url).host == "oauth-callback") { - // This is my web site, so do not override; let my WebView load the page - uri.getQueryParameter("code")?.let { - // viewModel.setLoginToken(it) - setActivityResult(it) - } - - return false - } - - url?.let { view?.loadUrl(it) } - return true - } - } - - override fun onOptionsItemSelected(item: MenuItem): Boolean { - when (item.itemId) { - android.R.id.home -> { - onBackPressed() - return true - } - } - return super.onOptionsItemSelected(item) - } - - companion object { - val RESULT_TEXT = "RESULT_TEXT" - } -} From 047e6727f692bdc0ca7de931bfecf9ea604cc22a Mon Sep 17 00:00:00 2001 From: Andrei Ashikhmin Date: Fri, 23 Feb 2024 14:53:27 +0700 Subject: [PATCH 3/3] feat: replace webview auth in CoinbaseServicesFragment --- .../coinbase/CoinbaseConstants.kt | 14 ++++ .../coinbase/service/CoinBaseAuthApi.kt | 5 +- .../coinbase/ui/CoinbaseServicesFragment.kt | 83 ++++++++++++------- .../wallet/ui/WalletUriHandlerActivity.kt | 3 +- .../buy_sell/IntegrationOverviewFragment.kt | 25 ++++-- .../buy_sell/IntegrationOverviewViewModel.kt | 15 ---- 6 files changed, 89 insertions(+), 56 deletions(-) diff --git a/integrations/coinbase/src/main/java/org/dash/wallet/integrations/coinbase/CoinbaseConstants.kt b/integrations/coinbase/src/main/java/org/dash/wallet/integrations/coinbase/CoinbaseConstants.kt index 3d08d65f30..ccd0315534 100644 --- a/integrations/coinbase/src/main/java/org/dash/wallet/integrations/coinbase/CoinbaseConstants.kt +++ b/integrations/coinbase/src/main/java/org/dash/wallet/integrations/coinbase/CoinbaseConstants.kt @@ -17,6 +17,7 @@ package org.dash.wallet.integrations.coinbase import android.content.Context +import org.dash.wallet.integrations.coinbase.service.CoinBaseClientConstants import java.io.File object CoinbaseConstants { @@ -37,6 +38,19 @@ object CoinbaseConstants { const val BASE_IDS_REQUEST_URL = "v2/assets/prices?filter=holdable&resolution=latest" const val BUY_FEE = 0.006 const val REDIRECT_URL = "dashwallet://brokers/coinbase/connect" + const val AUTH_RESULT_ACTION = "Coinbase.AUTH_RESULT" + val LINK_URL = "https://www.coinbase.com/oauth/authorize?client_id=${CoinBaseClientConstants.CLIENT_ID}" + + "&redirect_uri=${REDIRECT_URL}&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: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]=10" + + "&meta[send_limit_currency]=USD" + + "&meta[send_limit_period]=month" + + "&account=all" fun getCacheDir(context: Context): File { return File(context.cacheDir, "coinbase") diff --git a/integrations/coinbase/src/main/java/org/dash/wallet/integrations/coinbase/service/CoinBaseAuthApi.kt b/integrations/coinbase/src/main/java/org/dash/wallet/integrations/coinbase/service/CoinBaseAuthApi.kt index 715ef977df..16727967a8 100644 --- a/integrations/coinbase/src/main/java/org/dash/wallet/integrations/coinbase/service/CoinBaseAuthApi.kt +++ b/integrations/coinbase/src/main/java/org/dash/wallet/integrations/coinbase/service/CoinBaseAuthApi.kt @@ -28,11 +28,12 @@ interface CoinBaseAuthApi { suspend fun getToken( @Field("client_id") clientId: String = CoinBaseClientConstants.CLIENT_ID, @Field("redirect_uri") redirectUri: String = CoinbaseConstants.REDIRECT_URL, - @Field("grant_type") grant_type: String = "authorization_code", - @Field("client_secret") client_secret: String = CoinBaseClientConstants.CLIENT_SECRET, + @Field("grant_type") grantType: String = "authorization_code", + @Field("client_secret") clientSecret: String = CoinBaseClientConstants.CLIENT_SECRET, @Field("code") code: String ): TokenResponse? + @FormUrlEncoded @POST("oauth/revoke") suspend fun revokeToken(@Field("token") token: String) } 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 6d76ab36ab..0d9c4b99ad 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 @@ -18,7 +18,10 @@ package org.dash.wallet.integrations.coinbase.ui import android.animation.ObjectAnimator import android.app.Activity +import android.content.BroadcastReceiver +import android.content.Context import android.content.Intent +import android.content.IntentFilter import android.net.Uri import android.os.Bundle import android.view.View @@ -27,8 +30,11 @@ import androidx.core.view.isVisible import androidx.fragment.app.Fragment import androidx.fragment.app.viewModels import androidx.lifecycle.lifecycleScope +import androidx.lifecycle.withResumed +import androidx.localbroadcastmanager.content.LocalBroadcastManager import androidx.navigation.fragment.findNavController import dagger.hilt.android.AndroidEntryPoint +import kotlinx.coroutines.launch import org.bitcoinj.core.Coin import org.dash.wallet.common.databinding.FragmentIntegrationPortalBinding import org.dash.wallet.common.services.analytics.AnalyticsConstants @@ -36,8 +42,10 @@ 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.openCustomTab import org.dash.wallet.common.util.safeNavigate import org.dash.wallet.common.util.toFormattedString +import org.dash.wallet.integrations.coinbase.CoinbaseConstants import org.dash.wallet.integrations.coinbase.R import org.dash.wallet.integrations.coinbase.model.CoinbaseErrorType import org.dash.wallet.integrations.coinbase.viewmodels.CoinbaseViewModel @@ -51,17 +59,20 @@ class CoinbaseServicesFragment : Fragment(R.layout.fragment_integration_portal) private val sharedViewModel by coinbaseViewModels() private var balanceAnimator: ObjectAnimator? = null - private val coinbaseAuthLauncher = registerForActivityResult( - ActivityResultContracts.StartActivityForResult() - ) { result -> - val data = result.data + private val coinbaseAuthResultReceiver = object : BroadcastReceiver() { + override fun onReceive(context: Context, intent: Intent) { + val uri = intent.extras?.get("uri") as Uri? + val code = uri?.getQueryParameter("code") - if (result.resultCode == Activity.RESULT_OK) { - data?.extras?.getString(CoinBaseWebClientActivity.RESULT_TEXT)?.let { code -> - lifecycleScope.launchWhenResumed { - handleCoinbaseAuthResult(code) + if (code != null) { + lifecycleScope.launch { + withResumed { + handleCoinbaseAuthResult(code) + } } } + + startActivity(intent) } } @@ -125,9 +136,7 @@ class CoinbaseServicesFragment : Fragment(R.layout.fragment_integration_portal) it.isCancelable = false }.show(requireActivity()) { login -> if (login == true) { - coinbaseAuthLauncher.launch( - Intent(requireContext(), CoinBaseWebClientActivity::class.java) - ) + requireActivity().openCustomTab(CoinbaseConstants.LINK_URL) } else { findNavController().popBackStack() } @@ -170,6 +179,11 @@ class CoinbaseServicesFragment : Fragment(R.layout.fragment_integration_portal) viewModel.refreshBalance() sharedViewModel.getBaseIdForFiatModel() + + LocalBroadcastManager.getInstance(requireContext()).registerReceiver( + coinbaseAuthResultReceiver, + IntentFilter(CoinbaseConstants.AUTH_RESULT_ACTION) + ) } private fun setNetworkState(hasInternet: Boolean) { @@ -186,30 +200,39 @@ class CoinbaseServicesFragment : Fragment(R.layout.fragment_integration_portal) startActivity(defaultBrowser) } - private suspend fun handleCoinbaseAuthResult(code: String) { - val success = AdaptiveDialog.withProgress(getString(R.string.loading), requireActivity()) { - sharedViewModel.loginToCoinbase(code) - } + private fun handleCoinbaseAuthResult(code: String) { + lifecycleScope.launch { + val success = AdaptiveDialog.withProgress(getString(R.string.loading), requireActivity()) { + sharedViewModel.loginToCoinbase(code) + } - if (success) { - return - } + if (success) { + return@launch + } - 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() + 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 onDestroyView() { + super.onDestroyView() + LocalBroadcastManager.getInstance(requireContext()).unregisterReceiver( + coinbaseAuthResultReceiver + ) + } + override fun onDestroy() { super.onDestroy() this.balanceAnimator = null diff --git a/wallet/src/de/schildbach/wallet/ui/WalletUriHandlerActivity.kt b/wallet/src/de/schildbach/wallet/ui/WalletUriHandlerActivity.kt index 71cedcd03a..105eaef167 100644 --- a/wallet/src/de/schildbach/wallet/ui/WalletUriHandlerActivity.kt +++ b/wallet/src/de/schildbach/wallet/ui/WalletUriHandlerActivity.kt @@ -34,6 +34,7 @@ import de.schildbach.wallet_test.R import org.bitcoinj.wallet.Wallet import org.dash.wallet.common.ui.BaseAlertDialogBuilder import org.dash.wallet.common.ui.formatString +import org.dash.wallet.integrations.coinbase.CoinbaseConstants import org.dash.wallet.integrations.uphold.ui.UpholdPortalFragment /** @@ -82,7 +83,7 @@ class WalletUriHandlerActivity : AppCompatActivity() { activityIntent.setAction(UpholdPortalFragment.AUTH_RESULT_ACTION) LocalBroadcastManager.getInstance(this).sendBroadcast(activityIntent) } else if (intentUri.path?.contains("coinbase") == true) { - activityIntent.setAction(IntegrationOverviewFragment.AUTH_RESULT_ACTION) + activityIntent.setAction(CoinbaseConstants.AUTH_RESULT_ACTION) LocalBroadcastManager.getInstance(this).sendBroadcast(activityIntent) } finish() 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 8b49557638..9316481563 100644 --- a/wallet/src/de/schildbach/wallet/ui/buy_sell/IntegrationOverviewFragment.kt +++ b/wallet/src/de/schildbach/wallet/ui/buy_sell/IntegrationOverviewFragment.kt @@ -28,6 +28,7 @@ import androidx.core.view.isVisible import androidx.fragment.app.Fragment import androidx.fragment.app.viewModels import androidx.lifecycle.lifecycleScope +import androidx.lifecycle.withResumed import androidx.localbroadcastmanager.content.LocalBroadcastManager import androidx.navigation.fragment.findNavController import dagger.hilt.android.AndroidEntryPoint @@ -38,13 +39,10 @@ import org.dash.wallet.common.ui.dialogs.AdaptiveDialog import org.dash.wallet.common.ui.viewBinding import org.dash.wallet.common.util.openCustomTab import org.dash.wallet.common.util.safeNavigate +import org.dash.wallet.integrations.coinbase.CoinbaseConstants @AndroidEntryPoint class IntegrationOverviewFragment : Fragment(R.layout.fragment_integration_overview) { - companion object { - const val AUTH_RESULT_ACTION = "IntegrationOverviewFragment.AUTH_RESULT" - } - private val binding by viewBinding(FragmentIntegrationOverviewBinding::bind) private val viewModel by viewModels() @@ -54,7 +52,11 @@ class IntegrationOverviewFragment : Fragment(R.layout.fragment_integration_overv val code = uri?.getQueryParameter("code") if (code != null) { - handleCoinbaseAuthResult(code) + lifecycleScope.launch { + withResumed { + handleCoinbaseAuthResult(code) + } + } } startActivity(intent) @@ -78,7 +80,7 @@ class IntegrationOverviewFragment : Fragment(R.layout.fragment_integration_overv LocalBroadcastManager.getInstance(requireContext()).registerReceiver( coinbaseAuthResultReceiver, - IntentFilter(AUTH_RESULT_ACTION) + IntentFilter(CoinbaseConstants.AUTH_RESULT_ACTION) ) } @@ -100,8 +102,7 @@ class IntegrationOverviewFragment : Fragment(R.layout.fragment_integration_overv } private fun linkCoinbaseAccount() { - val url = viewModel.getCoinbaseLinkAccountUrl() - requireActivity().openCustomTab(url) + requireActivity().openCustomTab(CoinbaseConstants.LINK_URL) } private fun handleCoinbaseAuthResult(code: String) { @@ -127,4 +128,12 @@ class IntegrationOverviewFragment : Fragment(R.layout.fragment_integration_overv } } } + + override fun onDestroyView() { + super.onDestroyView() + + LocalBroadcastManager.getInstance(requireContext()).unregisterReceiver( + coinbaseAuthResultReceiver + ) + } } 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 c4b22fa9fc..034b695e75 100644 --- a/wallet/src/de/schildbach/wallet/ui/buy_sell/IntegrationOverviewViewModel.kt +++ b/wallet/src/de/schildbach/wallet/ui/buy_sell/IntegrationOverviewViewModel.kt @@ -60,19 +60,4 @@ class IntegrationOverviewViewModel @Inject constructor( fun logEvent(eventName: String) { analyticsService.logEvent(eventName, mapOf()) } - - fun getCoinbaseLinkAccountUrl(): String { - return "https://www.coinbase.com/oauth/authorize?client_id=${CoinBaseClientConstants.CLIENT_ID}" + - "&redirect_uri=${CoinbaseConstants.REDIRECT_URL}&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: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]=10" + - "&meta[send_limit_currency]=USD" + - "&meta[send_limit_period]=month" + - "&account=all" - } }