Skip to content

Commit

Permalink
feat(deeplinks): AND-6641 part 2 - swap & sell (#4076)
Browse files Browse the repository at this point in the history
  • Loading branch information
dserrano-bc committed Nov 7, 2022
1 parent 2ea5588 commit 2df6593
Show file tree
Hide file tree
Showing 9 changed files with 278 additions and 15 deletions.
Expand Up @@ -31,7 +31,7 @@ interface HomeNavigator {
fun launchOpenExternalEmailApp()
fun launchSetupFingerprintLogin()
fun launchReceive(cryptoTicker: String?)
fun launchSend(/*cryptoTicker: String? = null*/)
fun launchSend()
fun launchBuySell(
viewType: BuySellFragment.BuySellViewType = BuySellFragment.BuySellViewType.TYPE_BUY,
asset: AssetInfo? = null,
Expand Down
31 changes: 23 additions & 8 deletions app/src/main/java/piuk/blockchain/android/ui/home/MainActivity.kt
Expand Up @@ -92,6 +92,7 @@ import piuk.blockchain.android.ui.dashboard.walletmode.WalletModeSelectionBottom
import piuk.blockchain.android.ui.dashboard.walletmode.icon
import piuk.blockchain.android.ui.dashboard.walletmode.title
import piuk.blockchain.android.ui.home.analytics.BuyDefiAnalyticsEvents
import piuk.blockchain.android.ui.home.models.LaunchFlowForAccount
import piuk.blockchain.android.ui.home.models.MainIntent
import piuk.blockchain.android.ui.home.models.MainModel
import piuk.blockchain.android.ui.home.models.MainState
Expand Down Expand Up @@ -705,6 +706,19 @@ class MainActivity :
analytics.logEvent(ReferralAnalyticsEvents.ReferralProgramClicked(Origin.Deeplink))
showReferralBottomSheet(newState.referral.referralInfo)
}
is ViewToLaunch.LaunchTxFlowFromDeepLink -> {
startActivity(
TransactionFlowActivity.newIntent(
this,
action = view.action,
sourceAccount = if (view.account is LaunchFlowForAccount.Account) {
view.account.account
} else {
NullCryptoAccount()
}
)
)
}
}.exhaustive

// once we've completed a loop of render with a view to launch
Expand Down Expand Up @@ -887,12 +901,10 @@ class MainActivity :
is Destination.DashboardDestination -> launchPortfolio(reload = true)
is Destination.WalletConnectDestination -> model.process(MainIntent.StartWCSession(destination.url))
is Destination.AssetReceiveDestination -> launchReceive(destination.networkTicker)
is Destination.AssetSellDestination -> {
// next PR
}
is Destination.AssetSwapDestination -> {
// next PR
}
is Destination.AssetSellDestination ->
model.process(MainIntent.LaunchTransactionFlowFromDeepLink(destination.networkTicker, AssetAction.Sell))
is Destination.AssetSwapDestination ->
model.process(MainIntent.LaunchTransactionFlowFromDeepLink(destination.networkTicker, AssetAction.Swap))
}.exhaustive

model.process(MainIntent.ClearDeepLinkResult)
Expand Down Expand Up @@ -1003,7 +1015,8 @@ class MainActivity :
visible()
animate()
.alpha(1f)
.setStartDelay(500L).duration = resources.getInteger(android.R.integer.config_mediumAnimTime).toLong()
.setStartDelay(500L).duration =
resources.getInteger(android.R.integer.config_mediumAnimTime).toLong()
}
}

Expand Down Expand Up @@ -1046,7 +1059,9 @@ class MainActivity :
}

override fun onSelectNetworkClicked(session: WalletConnectSession) {
model.process(MainIntent.UpdateViewToLaunch(ViewToLaunch.LaunchWalletConnectSessionNetworkSelection(session)))
model.process(
MainIntent.UpdateViewToLaunch(ViewToLaunch.LaunchWalletConnectSessionNetworkSelection(session))
)
}

override fun onNetworkSelected(session: WalletConnectSession, networkInfo: NetworkInfo) {
Expand Down
3 changes: 2 additions & 1 deletion app/src/main/java/piuk/blockchain/android/ui/home/koin.kt
Expand Up @@ -59,7 +59,8 @@ val mainModule = module {
referralPrefs = get(),
referralRepository = get(),
ethDataManager = get(),
stakingAccountFlag = get(stakingAccountFeatureFlag)
stakingAccountFlag = get(stakingAccountFeatureFlag),
coincore = get()
)
}

Expand Down
Expand Up @@ -62,10 +62,10 @@ sealed class MainIntent : MviIntent<MainState> {
override fun reduce(oldState: MainState): MainState = oldState
}

class UpdateViewToLaunch(val nextState: ViewToLaunch) : MainIntent() {
class UpdateViewToLaunch(private val view: ViewToLaunch) : MainIntent() {
override fun reduce(oldState: MainState): MainState =
oldState.copy(
viewToLaunch = nextState
viewToLaunch = view
)
}

Expand Down Expand Up @@ -137,4 +137,8 @@ sealed class MainIntent : MviIntent<MainState> {
override fun reduce(oldState: MainState): MainState =
oldState.copy(isStakingEnabled = isStakingEnabled)
}

class LaunchTransactionFlowFromDeepLink(val cryptoTicker: String, val action: AssetAction) : MainIntent() {
override fun reduce(oldState: MainState): MainState = oldState
}
}
Expand Up @@ -5,6 +5,9 @@ import android.net.Uri
import com.blockchain.banking.BankPaymentApproval
import com.blockchain.coincore.AssetAction
import com.blockchain.coincore.BlockchainAccount
import com.blockchain.coincore.Coincore
import com.blockchain.coincore.impl.CryptoNonCustodialAccount
import com.blockchain.coincore.impl.CustodialTradingAccount
import com.blockchain.core.chains.ethereum.EthDataManager
import com.blockchain.core.referral.ReferralRepository
import com.blockchain.deeplinking.navigation.DeeplinkRedirector
Expand All @@ -25,7 +28,6 @@ import info.blockchain.balance.AssetCatalogue
import info.blockchain.balance.AssetInfo
import io.reactivex.rxjava3.core.Completable
import io.reactivex.rxjava3.core.Single
import java.lang.IllegalStateException
import java.text.SimpleDateFormat
import java.util.Calendar
import java.util.Locale
Expand Down Expand Up @@ -64,7 +66,8 @@ class MainInteractor internal constructor(
private val referralPrefs: ReferralPrefs,
private val referralRepository: ReferralRepository,
private val ethDataManager: EthDataManager,
private val stakingAccountFlag: FeatureFlag
private val stakingAccountFlag: FeatureFlag,
private val coincore: Coincore
) {

fun checkForDeepLinks(intent: Intent): Single<LinkState> =
Expand Down Expand Up @@ -165,4 +168,36 @@ class MainInteractor internal constructor(

fun isStakingEnabled(): Single<Boolean> =
stakingAccountFlag.enabled

fun selectAccountForTxFlow(cryptoTicker: String, action: AssetAction): Single<LaunchFlowForAccount> =
coincore.walletsWithActions(setOf(action)).map { sellAccounts ->
sellAccounts.filter { account ->
account.currency.networkTicker == cryptoTicker
}
}.map { sameAssetAccounts ->
val eligibleTradingAccount = sameAssetAccounts.filterIsInstance<CustodialTradingAccount>()
.firstOrNull { tradingAccount ->
tradingAccount.isFunded
}

when {
eligibleTradingAccount != null -> {
return@map LaunchFlowForAccount.Account(eligibleTradingAccount)
}
else -> {
val eligibleNonCustodialAccount = sameAssetAccounts.filterIsInstance<CryptoNonCustodialAccount>()
.firstOrNull { ncAccount ->
ncAccount.isFunded
}
when {
eligibleNonCustodialAccount != null -> {
return@map LaunchFlowForAccount.Account(eligibleNonCustodialAccount)
}
else -> {
return@map LaunchFlowForAccount.NoAccount
}
}
}
}
}
}
Expand Up @@ -259,6 +259,29 @@ class MainModel(
process(MainIntent.UpdateStakingFlag(false))
}
)
is MainIntent.LaunchTransactionFlowFromDeepLink -> {
interactor.selectAccountForTxFlow(intent.cryptoTicker, intent.action)
.subscribeBy(
onSuccess = { account ->
process(
MainIntent.UpdateViewToLaunch(
ViewToLaunch.LaunchTxFlowFromDeepLink(account, intent.action)
)
)
},
onError = {
Timber.e(
"Error getting default account for TxFlow ${intent.action} deeplink ${it.message}"
)

process(
MainIntent.UpdateViewToLaunch(
ViewToLaunch.LaunchTxFlowFromDeepLink(LaunchFlowForAccount.NoAccount, intent.action)
)
)
}
)
}
is MainIntent.UpdateStakingFlag,
MainIntent.ResetViewState,
is MainIntent.SelectNetworkForWCSession,
Expand Down
Expand Up @@ -63,6 +63,7 @@ sealed class ViewToLaunch {
val walletConnectSession: WalletConnectSession,
val networkInfo: NetworkInfo
) : ViewToLaunch()

class LaunchWalletConnectSessionApproved(val walletConnectSession: WalletConnectSession) : ViewToLaunch()
class LaunchWalletConnectSessionRejected(val walletConnectSession: WalletConnectSession) : ViewToLaunch()
object LaunchSimpleBuyFromDeepLinkApproval : ViewToLaunch()
Expand All @@ -72,4 +73,10 @@ sealed class ViewToLaunch {
class LaunchTransactionFlowWithTargets(val targets: Collection<CryptoTarget>) : ViewToLaunch()
class ShowTargetScanError(val error: QrScanError) : ViewToLaunch()
object ShowReferralSheet : ViewToLaunch()
class LaunchTxFlowFromDeepLink(val account: LaunchFlowForAccount, val action: AssetAction) : ViewToLaunch()
}

sealed class LaunchFlowForAccount {
class Account(val account: BlockchainAccount) : LaunchFlowForAccount()
object NoAccount : LaunchFlowForAccount()
}
@@ -1,6 +1,10 @@
package piuk.blockchain.android.ui.home

import android.content.Intent
import com.blockchain.coincore.AssetAction
import com.blockchain.coincore.Coincore
import com.blockchain.coincore.impl.CryptoNonCustodialAccount
import com.blockchain.coincore.impl.CustodialTradingAccount
import com.blockchain.core.chains.ethereum.EthDataManager
import com.blockchain.core.referral.ReferralRepository
import com.blockchain.deeplinking.navigation.DeeplinkRedirector
Expand Down Expand Up @@ -43,6 +47,7 @@ import piuk.blockchain.android.scan.ScanResult
import piuk.blockchain.android.simplebuy.SimpleBuyState
import piuk.blockchain.android.simplebuy.SimpleBuySyncFactory
import piuk.blockchain.android.ui.auth.newlogin.domain.service.SecureChannelService
import piuk.blockchain.android.ui.home.models.LaunchFlowForAccount
import piuk.blockchain.android.ui.home.models.MainInteractor
import piuk.blockchain.android.ui.home.models.ReferralState
import piuk.blockchain.android.ui.launcher.DeepLinkPersistence
Expand Down Expand Up @@ -71,6 +76,7 @@ class MainInteractorTest {
private val referralRepository: ReferralRepository = mock()
private val ethDataManager: EthDataManager = mock()
private val stakingFF: FeatureFlag = mock()
private val coincore: Coincore = mock()

private val jsonSerializers = module {
single {
Expand Down Expand Up @@ -112,7 +118,8 @@ class MainInteractorTest {
referralPrefs = referralPrefs,
referralRepository = referralRepository,
ethDataManager = ethDataManager,
stakingAccountFlag = stakingFF
stakingAccountFlag = stakingFF,
coincore = coincore
)
}

Expand Down Expand Up @@ -287,4 +294,133 @@ class MainInteractorTest {
.assertValue(ReferralState(referralMock, true))
}
}

@Test
fun `given no wallets with same ticker when function called then NoAccount should be returned`() {
val btcCurrency = mock<AssetInfo> {
on { networkTicker }.thenReturn("BTC")
}
val ethCurrency = mock<AssetInfo> {
on { networkTicker }.thenReturn("ETH")
}
val accountList = listOf<CustodialTradingAccount>(
mock {
on { currency }.thenReturn(btcCurrency)
},
mock {
on { currency }.thenReturn(ethCurrency)
}
)

whenever(coincore.walletsWithActions(setOf(AssetAction.Sell))).thenReturn(Single.just(accountList))

val result = interactor.selectAccountForTxFlow("XLM", AssetAction.Sell).test()
result.assertValue {
it is LaunchFlowForAccount.NoAccount
}
}

@Test
fun `given a funded custodial account when called then it should be returned`() {
val btcCurrency = mock<AssetInfo> {
on { networkTicker }.thenReturn("BTC")
}
val ethCurrency = mock<AssetInfo> {
on { networkTicker }.thenReturn("ETH")
}
val btcAccount = mock<CustodialTradingAccount> {
on { currency }.thenReturn(btcCurrency)
on { isFunded }.thenReturn(true)
}
val ethAccount = mock<CustodialTradingAccount> {
on { currency }.thenReturn(ethCurrency)
on { isFunded }.thenReturn(true)
}
val accountList = listOf(btcAccount, ethAccount)

whenever(coincore.walletsWithActions(setOf(AssetAction.Sell))).thenReturn(Single.just(accountList))

val result = interactor.selectAccountForTxFlow("BTC", AssetAction.Sell).test()
result.assertValue {
it is LaunchFlowForAccount.Account && it.account == btcAccount
}
}

@Test
fun `given no funded custodial account and a funded non-custodial account when called then it should be returned`() {
val btcCurrency = mock<AssetInfo> {
on { networkTicker }.thenReturn("BTC")
}
val ethCurrency = mock<AssetInfo> {
on { networkTicker }.thenReturn("ETH")
}
val btcCustodialAccount = mock<CustodialTradingAccount> {
on { currency }.thenReturn(btcCurrency)
on { isFunded }.thenReturn(false)
}
val ethCustodialAccount = mock<CustodialTradingAccount> {
on { currency }.thenReturn(ethCurrency)
on { isFunded }.thenReturn(true)
}
val btcNonCustodialAccount = mock<CryptoNonCustodialAccount> {
on { currency }.thenReturn(btcCurrency)
on { isFunded }.thenReturn(true)
}
val ethNonCustodialAccount = mock<CryptoNonCustodialAccount> {
on { currency }.thenReturn(ethCurrency)
on { isFunded }.thenReturn(true)
}
val accountList = listOf(
btcCustodialAccount,
ethCustodialAccount,
btcNonCustodialAccount,
ethNonCustodialAccount
)

whenever(coincore.walletsWithActions(setOf(AssetAction.Sell))).thenReturn(Single.just(accountList))

val result = interactor.selectAccountForTxFlow("BTC", AssetAction.Sell).test()
result.assertValue {
it is LaunchFlowForAccount.Account && it.account == btcNonCustodialAccount
}
}

@Test
fun `given no funded custodial or non-custodial accounts when called then NoAccount should be returned`() {
val btcCurrency = mock<AssetInfo> {
on { networkTicker }.thenReturn("BTC")
}
val ethCurrency = mock<AssetInfo> {
on { networkTicker }.thenReturn("ETH")
}
val btcCustodialAccount = mock<CustodialTradingAccount> {
on { currency }.thenReturn(btcCurrency)
on { isFunded }.thenReturn(false)
}
val ethCustodialAccount = mock<CustodialTradingAccount> {
on { currency }.thenReturn(ethCurrency)
on { isFunded }.thenReturn(false)
}
val btcNonCustodialAccount = mock<CryptoNonCustodialAccount> {
on { currency }.thenReturn(btcCurrency)
on { isFunded }.thenReturn(false)
}
val ethNonCustodialAccount = mock<CryptoNonCustodialAccount> {
on { currency }.thenReturn(ethCurrency)
on { isFunded }.thenReturn(false)
}
val accountList = listOf(
btcCustodialAccount,
ethCustodialAccount,
btcNonCustodialAccount,
ethNonCustodialAccount
)

whenever(coincore.walletsWithActions(setOf(AssetAction.Sell))).thenReturn(Single.just(accountList))

val result = interactor.selectAccountForTxFlow("BTC", AssetAction.Sell).test()
result.assertValue {
it is LaunchFlowForAccount.NoAccount
}
}
}

0 comments on commit 2df6593

Please sign in to comment.