diff --git a/core/ui/src/main/java/com/tangem/core/ui/components/buttons/small/TangemIconButton.kt b/core/ui/src/main/java/com/tangem/core/ui/components/buttons/small/TangemIconButton.kt index c0d2dac09e..9cb8a2f121 100644 --- a/core/ui/src/main/java/com/tangem/core/ui/components/buttons/small/TangemIconButton.kt +++ b/core/ui/src/main/java/com/tangem/core/ui/components/buttons/small/TangemIconButton.kt @@ -16,6 +16,7 @@ import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.graphics.vector.rememberVectorPainter import androidx.compose.ui.res.vectorResource import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.dp import com.tangem.core.ui.R import com.tangem.core.ui.res.TangemTheme @@ -30,6 +31,7 @@ import com.tangem.core.ui.res.TangemThemePreview * @param shape icon button shape * @param background background color * @param iconTint icon color + * @param innerPadding icon padding inside background area * * [Show in Figma](https://www.figma.com/design/14ISV23YB1yVW1uNVwqrKv/Android?node-id=4105-1439&t=nnYBX1qCZmUNhBDf-4) */ @@ -41,6 +43,7 @@ fun TangemIconButton( shape: RoundedCornerShape = RoundedCornerShape(24.dp), background: Color = TangemTheme.colors.button.secondary, iconTint: Color = TangemTheme.colors.icon.secondary, + innerPadding: Dp = 4.dp, ) { Icon( painter = rememberVectorPainter(ImageVector.vectorResource(iconRes)), @@ -50,7 +53,7 @@ fun TangemIconButton( .size(24.dp) .clip(shape) .background(background) - .padding(4.dp) + .padding(innerPadding) .clickable( onClick = onClick, ), diff --git a/features/token-recieve/impl/src/main/java/com/tangem/features/tokenreceive/model/TokenReceiveWarningModel.kt b/features/token-recieve/impl/src/main/java/com/tangem/features/tokenreceive/model/TokenReceiveWarningModel.kt index 8c686c07a7..7d57f2d60d 100644 --- a/features/token-recieve/impl/src/main/java/com/tangem/features/tokenreceive/model/TokenReceiveWarningModel.kt +++ b/features/token-recieve/impl/src/main/java/com/tangem/features/tokenreceive/model/TokenReceiveWarningModel.kt @@ -4,7 +4,6 @@ import androidx.compose.runtime.Stable import com.tangem.core.decompose.di.ModelScoped import com.tangem.core.decompose.model.Model import com.tangem.core.decompose.model.ParamsContainer -import com.tangem.core.ui.extensions.iconResId import com.tangem.features.tokenreceive.component.TokenReceiveWarningComponent import com.tangem.features.tokenreceive.ui.state.WarningUM import com.tangem.utils.coroutines.CoroutineDispatcherProvider @@ -27,7 +26,6 @@ internal class TokenReceiveWarningModel @Inject constructor( iconState = params.iconState, onWarningAcknowledged = params.callback::onWarningAcknowledged, network = params.network.name, - networkIcon = params.network.iconResId, ), ) } \ No newline at end of file diff --git a/features/token-recieve/impl/src/main/java/com/tangem/features/tokenreceive/ui/TokenReceiveAssetsContent.kt b/features/token-recieve/impl/src/main/java/com/tangem/features/tokenreceive/ui/TokenReceiveAssetsContent.kt index 73f31d7f8d..0bc0d7a0c7 100644 --- a/features/token-recieve/impl/src/main/java/com/tangem/features/tokenreceive/ui/TokenReceiveAssetsContent.kt +++ b/features/token-recieve/impl/src/main/java/com/tangem/features/tokenreceive/ui/TokenReceiveAssetsContent.kt @@ -230,6 +230,7 @@ private fun AddressItem( TangemIconButton( modifier = Modifier.size(TangemTheme.dimens.size28), + innerPadding = 6.dp, iconRes = R.drawable.ic_qrcode_new_24, onClick = onOpenQrCodeClick, ) @@ -239,6 +240,7 @@ private fun AddressItem( TangemIconButton( modifier = Modifier.size(TangemTheme.dimens.size28), iconRes = R.drawable.ic_copy_new_24, + innerPadding = 6.dp, onClick = onCopyClick, ) } @@ -276,8 +278,9 @@ private fun EnsItem(onCopyClick: () -> Unit, address: String, modifier: Modifier ) TangemIconButton( - modifier = Modifier.size(28.dp), + modifier = Modifier.size(TangemTheme.dimens.size28), iconRes = R.drawable.ic_copy_new_24, + innerPadding = 6.dp, onClick = onCopyClick, ) } diff --git a/features/token-recieve/impl/src/main/java/com/tangem/features/tokenreceive/ui/TokenReceiveContent.kt b/features/token-recieve/impl/src/main/java/com/tangem/features/tokenreceive/ui/TokenReceiveContent.kt index 75ee1fb93f..f4e495de55 100644 --- a/features/token-recieve/impl/src/main/java/com/tangem/features/tokenreceive/ui/TokenReceiveContent.kt +++ b/features/token-recieve/impl/src/main/java/com/tangem/features/tokenreceive/ui/TokenReceiveContent.kt @@ -1,6 +1,7 @@ package com.tangem.features.tokenreceive.ui import androidx.compose.animation.animateContentSize +import androidx.compose.animation.core.tween import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier @@ -83,7 +84,7 @@ internal fun TokenReceiveContent( ) { Children( stack = stackState, - animation = stackAnimation(fade()), + animation = stackAnimation(fade(animationSpec = tween(durationMillis = 100))), modifier = modifier .fillMaxSize() .animateContentSize(), diff --git a/features/token-recieve/impl/src/main/java/com/tangem/features/tokenreceive/ui/TokenReceiveWarningContent.kt b/features/token-recieve/impl/src/main/java/com/tangem/features/tokenreceive/ui/TokenReceiveWarningContent.kt index 2d85ca786c..25c47e9b3c 100644 --- a/features/token-recieve/impl/src/main/java/com/tangem/features/tokenreceive/ui/TokenReceiveWarningContent.kt +++ b/features/token-recieve/impl/src/main/java/com/tangem/features/tokenreceive/ui/TokenReceiveWarningContent.kt @@ -3,15 +3,12 @@ package com.tangem.features.tokenreceive.ui import android.content.res.Configuration import androidx.compose.foundation.background import androidx.compose.foundation.layout.* -import androidx.compose.material3.Icon import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier -import androidx.compose.ui.graphics.Color import androidx.compose.ui.hapticfeedback.HapticFeedbackType import androidx.compose.ui.platform.LocalHapticFeedback -import androidx.compose.ui.res.painterResource import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.tooling.preview.PreviewParameter @@ -47,15 +44,17 @@ internal fun TokenReceiveWarningContent(warningUM: WarningUM) { horizontalAlignment = Alignment.CenterHorizontally, ) { CurrencyIcon( - modifier = Modifier.size(size = 56.dp), + modifier = Modifier + .padding(8.dp) + .size(size = 64.dp), state = warningUM.iconState, - shouldDisplayNetwork = false, + shouldDisplayNetwork = true, iconSize = 56.dp, ) SpacerH24() - WarningBlock(networkIcon = warningUM.networkIcon, networkName = warningUM.network) + WarningBlock(networkName = warningUM.network) SpacerH12() @@ -80,7 +79,7 @@ internal fun TokenReceiveWarningContent(warningUM: WarningUM) { } @Composable -fun WarningBlock(networkName: String, networkIcon: Int, modifier: Modifier = Modifier) { +fun WarningBlock(networkName: String, modifier: Modifier = Modifier) { Column( modifier = modifier.fillMaxWidth(), horizontalAlignment = Alignment.CenterHorizontally, @@ -92,23 +91,14 @@ fun WarningBlock(networkName: String, networkIcon: Int, modifier: Modifier = Mod color = TangemTheme.colors.text.primary1, ) - Row(verticalAlignment = Alignment.CenterVertically) { - Icon( - modifier = Modifier.size(20.dp), - painter = painterResource(id = networkIcon), - tint = Color.Unspecified, - contentDescription = null, - ) + SpacerW6() - SpacerW6() - - Text( - textAlign = TextAlign.Center, - text = stringResourceSafe(R.string.domain_receive_assets_onboarding_network_name, networkName), - style = TangemTheme.typography.h3, - color = TangemTheme.colors.text.primary1, - ) - } + Text( + textAlign = TextAlign.Center, + text = stringResourceSafe(R.string.domain_receive_assets_onboarding_network_name, networkName), + style = TangemTheme.typography.h3, + color = TangemTheme.colors.text.primary1, + ) } } @@ -139,7 +129,6 @@ private class TokenReceiveWarningContentProvider : PreviewParameterProvider Unit, ) \ No newline at end of file diff --git a/features/wallet/impl/src/test/kotlin/com/tangem/feature/wallet/presentation/wallet/deeplink/DefaultPromoDeeplinkHandlerTest.kt b/features/wallet/impl/src/test/kotlin/com/tangem/feature/wallet/presentation/wallet/deeplink/DefaultPromoDeeplinkHandlerTest.kt index b37d43ee93..29a42feb84 100644 --- a/features/wallet/impl/src/test/kotlin/com/tangem/feature/wallet/presentation/wallet/deeplink/DefaultPromoDeeplinkHandlerTest.kt +++ b/features/wallet/impl/src/test/kotlin/com/tangem/feature/wallet/presentation/wallet/deeplink/DefaultPromoDeeplinkHandlerTest.kt @@ -1,3 +1,5 @@ +@file:Suppress("FunctionSignature") + package com.tangem.feature.wallet.presentation.wallet.deeplink import arrow.core.Either @@ -10,8 +12,8 @@ import com.tangem.core.decompose.ui.UiMessageSender import com.tangem.core.ui.extensions.TextReference import com.tangem.core.ui.extensions.resourceReference import com.tangem.core.ui.message.DialogMessage -import com.tangem.domain.models.currency.CryptoCurrency import com.tangem.domain.models.StatusSource +import com.tangem.domain.models.currency.CryptoCurrency import com.tangem.domain.models.network.Network import com.tangem.domain.models.network.NetworkAddress import com.tangem.domain.models.network.NetworkStatus @@ -19,6 +21,7 @@ import com.tangem.domain.models.wallet.UserWallet import com.tangem.domain.models.wallet.UserWalletId import com.tangem.domain.networks.multi.MultiNetworkStatusSupplier import com.tangem.domain.tokens.MultiWalletCryptoCurrenciesSupplier +import com.tangem.domain.wallets.PromoCodeActivationResult import com.tangem.domain.wallets.models.GetUserWalletError import com.tangem.domain.wallets.models.errors.ActivatePromoCodeError import com.tangem.domain.wallets.usecase.ActivateBitcoinPromocodeUseCase @@ -26,15 +29,10 @@ import com.tangem.domain.wallets.usecase.GetSelectedWalletSyncUseCase import com.tangem.feature.wallet.deeplink.DefaultPromoDeeplinkHandler import com.tangem.feature.wallet.deeplink.analytics.PromoActivationAnalytics import com.tangem.feature.wallet.impl.R -import io.mockk.MockKAnnotations -import io.mockk.coEvery -import io.mockk.coVerify -import io.mockk.every +import com.tangem.utils.coroutines.CoroutineDispatcherProvider +import com.tangem.utils.coroutines.TestingCoroutineDispatcherProvider +import io.mockk.* import io.mockk.impl.annotations.MockK -import io.mockk.just -import io.mockk.mockk -import io.mockk.runs -import io.mockk.verify import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.flow.flow @@ -46,9 +44,6 @@ import kotlinx.coroutines.test.runTest import org.junit.Before import org.junit.Test import timber.log.Timber -import com.tangem.domain.wallets.PromoCodeActivationResult -import com.tangem.utils.coroutines.CoroutineDispatcherProvider -import com.tangem.utils.coroutines.TestingCoroutineDispatcherProvider @OptIn(ExperimentalCoroutinesApi::class) class DefaultPromoDeeplinkHandlerTest { @@ -361,80 +356,82 @@ class DefaultPromoDeeplinkHandlerTest { } @Test - fun `GIVEN BTC status but currencies without BTC WHEN findBitcoinAddress THEN no bitcoin address dialog is shown`() = runTest { - val queryParams = mapOf(DeeplinkConst.PROMO_CODE_KEY to "PROMO123") - val userWallet = mockUserWallet("ABCDEF") - every { getSelectedWalletSyncUseCase.invoke() } returns Either.Right(userWallet) - val btcStatus = buildNetworkStatus(rawNetworkId = Blockchain.Bitcoin.id, address = "bc1qxyz") - coEvery { multiNetworkStatusSupplier.invoke(any()) } returns flowOf(setOf(btcStatus)) - val ethOnly = buildCryptoCurrency(rawNetworkId = "ethereum") - coEvery { multiWalletCryptoCurrenciesSupplier.getSyncOrNull(any()) } returns setOf(ethOnly) - val dispatcherProvider = testDispatcherProvider(testScheduler) + fun `GIVEN BTC status but currencies without BTC WHEN findBitcoinAddress THEN no bitcoin address dialog is shown`() = + runTest { + val queryParams = mapOf(DeeplinkConst.PROMO_CODE_KEY to "PROMO123") + val userWallet = mockUserWallet("ABCDEF") + every { getSelectedWalletSyncUseCase.invoke() } returns Either.Right(userWallet) + val btcStatus = buildNetworkStatus(rawNetworkId = Blockchain.Bitcoin.id, address = "bc1qxyz") + coEvery { multiNetworkStatusSupplier.invoke(any()) } returns flowOf(setOf(btcStatus)) + val ethOnly = buildCryptoCurrency(rawNetworkId = "ethereum") + coEvery { multiWalletCryptoCurrenciesSupplier.getSyncOrNull(any()) } returns setOf(ethOnly) + val dispatcherProvider = testDispatcherProvider(testScheduler) - DefaultPromoDeeplinkHandler( - scope = this, - queryParams = queryParams, - uiMessageSender = uiMessageSender, - multiNetworkStatusSupplier = multiNetworkStatusSupplier, - multiWalletCryptoCurrenciesSupplier = multiWalletCryptoCurrenciesSupplier, - activateBitcoinPromocodeUseCase = activateBitcoinPromocodeUseCase, - getSelectedWalletSyncUseCase = getSelectedWalletSyncUseCase, - analyticsEventsHandler = analyticsEventHandler, - dispatchers = dispatcherProvider, - ) + DefaultPromoDeeplinkHandler( + scope = this, + queryParams = queryParams, + uiMessageSender = uiMessageSender, + multiNetworkStatusSupplier = multiNetworkStatusSupplier, + multiWalletCryptoCurrenciesSupplier = multiWalletCryptoCurrenciesSupplier, + activateBitcoinPromocodeUseCase = activateBitcoinPromocodeUseCase, + getSelectedWalletSyncUseCase = getSelectedWalletSyncUseCase, + analyticsEventsHandler = analyticsEventHandler, + dispatchers = dispatcherProvider, + ) - advanceUntilIdle() + advanceUntilIdle() - val sent = messages.last { it is DialogMessage } as DialogMessage - Truth.assertThat(sent.title).isEqualTo(resourceReference(R.string.bitcoin_promo_no_address_title)) - Truth.assertThat(sent.message).isEqualTo(resourceReference(R.string.bitcoin_promo_no_address)) - } + val sent = messages.last { it is DialogMessage } as DialogMessage + Truth.assertThat(sent.title).isEqualTo(resourceReference(R.string.bitcoin_promo_no_address_title)) + Truth.assertThat(sent.message).isEqualTo(resourceReference(R.string.bitcoin_promo_no_address)) + } @Test - fun `GIVEN multiple statuses emissions and currencies without BTC WHEN findBitcoinAddress THEN no bitcoin address dialog is shown`() = runTest { - val promoCode = "PROMO123" - val queryParams = mapOf(DeeplinkConst.PROMO_CODE_KEY to promoCode) - val userWallet = mockUserWallet("ABCDEF") - every { getSelectedWalletSyncUseCase.invoke() } returns Either.Right(userWallet) + fun `GIVEN multiple statuses emissions and currencies without BTC WHEN findBitcoinAddress THEN no bitcoin address dialog is shown`() = + runTest { + val promoCode = "PROMO123" + val queryParams = mapOf(DeeplinkConst.PROMO_CODE_KEY to promoCode) + val userWallet = mockUserWallet("ABCDEF") + every { getSelectedWalletSyncUseCase.invoke() } returns Either.Right(userWallet) - val ethStatus = buildNetworkStatus(rawNetworkId = "ethereum", address = "0x123") - val btcStatus = buildNetworkStatus(rawNetworkId = Blockchain.Bitcoin.id, address = "bc1qxyz") - coEvery { multiNetworkStatusSupplier.invoke(any()) } returns flow { - emit(emptySet()) - emit(setOf(ethStatus)) - emit(setOf(ethStatus, btcStatus)) - } + val ethStatus = buildNetworkStatus(rawNetworkId = "ethereum", address = "0x123") + val btcStatus = buildNetworkStatus(rawNetworkId = Blockchain.Bitcoin.id, address = "bc1qxyz") + coEvery { multiNetworkStatusSupplier.invoke(any()) } returns flow { + emit(emptySet()) + emit(setOf(ethStatus)) + emit(setOf(ethStatus, btcStatus)) + } - val ethOnly = buildCryptoCurrency(rawNetworkId = "ethereum") - coEvery { multiWalletCryptoCurrenciesSupplier.getSyncOrNull(any()) } returns setOf(ethOnly) + val ethOnly = buildCryptoCurrency(rawNetworkId = "ethereum") + coEvery { multiWalletCryptoCurrenciesSupplier.getSyncOrNull(any()) } returns setOf(ethOnly) - val dispatcherProvider = testDispatcherProvider(testScheduler) + val dispatcherProvider = testDispatcherProvider(testScheduler) - DefaultPromoDeeplinkHandler( - scope = this, - queryParams = queryParams, - uiMessageSender = uiMessageSender, - multiNetworkStatusSupplier = multiNetworkStatusSupplier, - multiWalletCryptoCurrenciesSupplier = multiWalletCryptoCurrenciesSupplier, - activateBitcoinPromocodeUseCase = activateBitcoinPromocodeUseCase, - getSelectedWalletSyncUseCase = getSelectedWalletSyncUseCase, - analyticsEventsHandler = analyticsEventHandler, - dispatchers = dispatcherProvider, - ) - - advanceUntilIdle() - - val sent = messages.last { it is DialogMessage } as DialogMessage - Truth.assertThat(sent.title).isEqualTo(resourceReference(R.string.bitcoin_promo_no_address_title)) - Truth.assertThat(sent.message).isEqualTo(resourceReference(R.string.bitcoin_promo_no_address)) - - verify(exactly = 1) { analyticsEventHandler.send(PromoActivationAnalytics.PromoDeepLinkActivationStart) } - verify(exactly = 1) { - analyticsEventHandler.send( - PromoActivationAnalytics.PromoActivation(PromoCodeActivationResult.NoBitcoinAddress), + DefaultPromoDeeplinkHandler( + scope = this, + queryParams = queryParams, + uiMessageSender = uiMessageSender, + multiNetworkStatusSupplier = multiNetworkStatusSupplier, + multiWalletCryptoCurrenciesSupplier = multiWalletCryptoCurrenciesSupplier, + activateBitcoinPromocodeUseCase = activateBitcoinPromocodeUseCase, + getSelectedWalletSyncUseCase = getSelectedWalletSyncUseCase, + analyticsEventsHandler = analyticsEventHandler, + dispatchers = dispatcherProvider, ) + + advanceUntilIdle() + + val sent = messages.last { it is DialogMessage } as DialogMessage + Truth.assertThat(sent.title).isEqualTo(resourceReference(R.string.bitcoin_promo_no_address_title)) + Truth.assertThat(sent.message).isEqualTo(resourceReference(R.string.bitcoin_promo_no_address)) + + verify(exactly = 1) { analyticsEventHandler.send(PromoActivationAnalytics.PromoDeepLinkActivationStart) } + verify(exactly = 1) { + analyticsEventHandler.send( + PromoActivationAnalytics.PromoActivation(PromoCodeActivationResult.NoBitcoinAddress), + ) + } } - } @Test fun `GIVEN two BTC statuses with different derivation AND two BTC currencies WHEN activate THEN activated`() = @@ -492,267 +489,275 @@ class DefaultPromoDeeplinkHandlerTest { } @Test - fun `GIVEN two BTC statuses with different derivation AND one matching BTC currency WHEN activate THEN activated`() = runTest { - val promoCode = "PROMO123" - val queryParams = mapOf(DeeplinkConst.PROMO_CODE_KEY to promoCode) - val userWallet = mockUserWallet("ABCDEF") - every { getSelectedWalletSyncUseCase.invoke() } returns Either.Right(userWallet) + fun `GIVEN two BTC statuses with different derivation AND one matching BTC currency WHEN activate THEN activated`() = + runTest { + val promoCode = "PROMO123" + val queryParams = mapOf(DeeplinkConst.PROMO_CODE_KEY to promoCode) + val userWallet = mockUserWallet("ABCDEF") + every { getSelectedWalletSyncUseCase.invoke() } returns Either.Right(userWallet) - val dpCard = Network.DerivationPath.Card("m/44'/0'/0'") - val dpCustom = Network.DerivationPath.Custom("m/84'/0'/0'") + val dpCard = Network.DerivationPath.Card("m/44'/0'/0'") + val dpCustom = Network.DerivationPath.Custom("m/84'/0'/0'") - val btcStatusCard = buildNetworkStatus( - rawNetworkId = Blockchain.Bitcoin.id, - address = "bc1qcard", - derivationPath = dpCard, - ) - val btcStatusCustom = buildNetworkStatus( - rawNetworkId = Blockchain.Bitcoin.id, - address = "bc1qcustom", - derivationPath = dpCustom, - ) - coEvery { multiNetworkStatusSupplier.invoke(any()) } returns flowOf(setOf(btcStatusCard, btcStatusCustom)) - - val btcCoinCard = buildCryptoCurrency(rawNetworkId = Blockchain.Bitcoin.id, derivationPath = dpCard) - coEvery { multiWalletCryptoCurrenciesSupplier.getSyncOrNull(any()) } returns setOf(btcCoinCard) - - coEvery { activateBitcoinPromocodeUseCase.invoke("bc1qcard", promoCode) } returns Either.Right("ok") - - val dispatcherProvider = testDispatcherProvider(testScheduler) - - DefaultPromoDeeplinkHandler( - scope = this, - queryParams = queryParams, - uiMessageSender = uiMessageSender, - multiNetworkStatusSupplier = multiNetworkStatusSupplier, - multiWalletCryptoCurrenciesSupplier = multiWalletCryptoCurrenciesSupplier, - activateBitcoinPromocodeUseCase = activateBitcoinPromocodeUseCase, - getSelectedWalletSyncUseCase = getSelectedWalletSyncUseCase, - analyticsEventsHandler = analyticsEventHandler, - dispatchers = dispatcherProvider, - ) - - advanceUntilIdle() - - val sent = messages.last { it is DialogMessage } as DialogMessage - Truth.assertThat(sent.title).isEqualTo(resourceReference(R.string.bitcoin_promo_activation_success_title)) - Truth.assertThat(sent.message).isEqualTo(resourceReference(R.string.bitcoin_promo_activation_success)) - - coVerify(exactly = 1) { activateBitcoinPromocodeUseCase.invoke("bc1qcard", promoCode) } - coVerify(exactly = 0) { activateBitcoinPromocodeUseCase.invoke("bc1qcustom", promoCode) } - - verify(exactly = 1) { analyticsEventHandler.send(PromoActivationAnalytics.PromoDeepLinkActivationStart) } - verify(exactly = 1) { - analyticsEventHandler.send( - PromoActivationAnalytics.PromoActivation(PromoCodeActivationResult.Activated), + val btcStatusCard = buildNetworkStatus( + rawNetworkId = Blockchain.Bitcoin.id, + address = "bc1qcard", + derivationPath = dpCard, ) + val btcStatusCustom = buildNetworkStatus( + rawNetworkId = Blockchain.Bitcoin.id, + address = "bc1qcustom", + derivationPath = dpCustom, + ) + coEvery { multiNetworkStatusSupplier.invoke(any()) } returns flowOf(setOf(btcStatusCard, btcStatusCustom)) + + val btcCoinCard = buildCryptoCurrency(rawNetworkId = Blockchain.Bitcoin.id, derivationPath = dpCard) + coEvery { multiWalletCryptoCurrenciesSupplier.getSyncOrNull(any()) } returns setOf(btcCoinCard) + + coEvery { activateBitcoinPromocodeUseCase.invoke("bc1qcard", promoCode) } returns Either.Right("ok") + + val dispatcherProvider = testDispatcherProvider(testScheduler) + + DefaultPromoDeeplinkHandler( + scope = this, + queryParams = queryParams, + uiMessageSender = uiMessageSender, + multiNetworkStatusSupplier = multiNetworkStatusSupplier, + multiWalletCryptoCurrenciesSupplier = multiWalletCryptoCurrenciesSupplier, + activateBitcoinPromocodeUseCase = activateBitcoinPromocodeUseCase, + getSelectedWalletSyncUseCase = getSelectedWalletSyncUseCase, + analyticsEventsHandler = analyticsEventHandler, + dispatchers = dispatcherProvider, + ) + + advanceUntilIdle() + + val sent = messages.last { it is DialogMessage } as DialogMessage + Truth.assertThat(sent.title).isEqualTo(resourceReference(R.string.bitcoin_promo_activation_success_title)) + Truth.assertThat(sent.message).isEqualTo(resourceReference(R.string.bitcoin_promo_activation_success)) + + coVerify(exactly = 1) { activateBitcoinPromocodeUseCase.invoke("bc1qcard", promoCode) } + coVerify(exactly = 0) { activateBitcoinPromocodeUseCase.invoke("bc1qcustom", promoCode) } + + verify(exactly = 1) { analyticsEventHandler.send(PromoActivationAnalytics.PromoDeepLinkActivationStart) } + verify(exactly = 1) { + analyticsEventHandler.send( + PromoActivationAnalytics.PromoActivation(PromoCodeActivationResult.Activated), + ) + } } - } @Test - fun `GIVEN two BTC statuses with different derivation AND no BTC currencies WHEN findBitcoinAddress THEN no bitcoin address dialog is shown`() = runTest { - val promoCode = "PROMO123" - val queryParams = mapOf(DeeplinkConst.PROMO_CODE_KEY to promoCode) - val userWallet = mockUserWallet("ABCDEF") - every { getSelectedWalletSyncUseCase.invoke() } returns Either.Right(userWallet) + fun `GIVEN two BTC statuses with different derivation AND no BTC currencies WHEN findBitcoinAddress THEN no bitcoin address dialog is shown`() = + runTest { + val promoCode = "PROMO123" + val queryParams = mapOf(DeeplinkConst.PROMO_CODE_KEY to promoCode) + val userWallet = mockUserWallet("ABCDEF") + every { getSelectedWalletSyncUseCase.invoke() } returns Either.Right(userWallet) - val dpCard = Network.DerivationPath.Card("m/44'/0'/0'") - val dpCustom = Network.DerivationPath.Custom("m/84'/0'/0'") + val dpCard = Network.DerivationPath.Card("m/44'/0'/0'") + val dpCustom = Network.DerivationPath.Custom("m/84'/0'/0'") - val btcStatusCard = buildNetworkStatus( - rawNetworkId = Blockchain.Bitcoin.id, - address = "bc1qcard", - derivationPath = dpCard, - ) - val btcStatusCustom = buildNetworkStatus( - rawNetworkId = Blockchain.Bitcoin.id, - address = "bc1qcustom", - derivationPath = dpCustom, - ) - coEvery { multiNetworkStatusSupplier.invoke(any()) } returns flowOf(setOf(btcStatusCard, btcStatusCustom)) - - coEvery { multiWalletCryptoCurrenciesSupplier.getSyncOrNull(any()) } returns emptySet() - - val dispatcherProvider = testDispatcherProvider(testScheduler) - - DefaultPromoDeeplinkHandler( - scope = this, - queryParams = queryParams, - uiMessageSender = uiMessageSender, - multiNetworkStatusSupplier = multiNetworkStatusSupplier, - multiWalletCryptoCurrenciesSupplier = multiWalletCryptoCurrenciesSupplier, - activateBitcoinPromocodeUseCase = activateBitcoinPromocodeUseCase, - getSelectedWalletSyncUseCase = getSelectedWalletSyncUseCase, - analyticsEventsHandler = analyticsEventHandler, - dispatchers = dispatcherProvider, - ) - - advanceUntilIdle() - - val sent = messages.last { it is DialogMessage } as DialogMessage - Truth.assertThat(sent.title).isEqualTo(resourceReference(R.string.bitcoin_promo_no_address_title)) - Truth.assertThat(sent.message).isEqualTo(resourceReference(R.string.bitcoin_promo_no_address)) - - coVerify(exactly = 0) { activateBitcoinPromocodeUseCase.invoke(any(), any()) } - - verify(exactly = 1) { analyticsEventHandler.send(PromoActivationAnalytics.PromoDeepLinkActivationStart) } - verify(exactly = 1) { - analyticsEventHandler.send( - PromoActivationAnalytics.PromoActivation(PromoCodeActivationResult.NoBitcoinAddress), + val btcStatusCard = buildNetworkStatus( + rawNetworkId = Blockchain.Bitcoin.id, + address = "bc1qcard", + derivationPath = dpCard, ) + val btcStatusCustom = buildNetworkStatus( + rawNetworkId = Blockchain.Bitcoin.id, + address = "bc1qcustom", + derivationPath = dpCustom, + ) + coEvery { multiNetworkStatusSupplier.invoke(any()) } returns flowOf(setOf(btcStatusCard, btcStatusCustom)) + + coEvery { multiWalletCryptoCurrenciesSupplier.getSyncOrNull(any()) } returns emptySet() + + val dispatcherProvider = testDispatcherProvider(testScheduler) + + DefaultPromoDeeplinkHandler( + scope = this, + queryParams = queryParams, + uiMessageSender = uiMessageSender, + multiNetworkStatusSupplier = multiNetworkStatusSupplier, + multiWalletCryptoCurrenciesSupplier = multiWalletCryptoCurrenciesSupplier, + activateBitcoinPromocodeUseCase = activateBitcoinPromocodeUseCase, + getSelectedWalletSyncUseCase = getSelectedWalletSyncUseCase, + analyticsEventsHandler = analyticsEventHandler, + dispatchers = dispatcherProvider, + ) + + advanceUntilIdle() + + val sent = messages.last { it is DialogMessage } as DialogMessage + Truth.assertThat(sent.title).isEqualTo(resourceReference(R.string.bitcoin_promo_no_address_title)) + Truth.assertThat(sent.message).isEqualTo(resourceReference(R.string.bitcoin_promo_no_address)) + + coVerify(exactly = 0) { activateBitcoinPromocodeUseCase.invoke(any(), any()) } + + verify(exactly = 1) { analyticsEventHandler.send(PromoActivationAnalytics.PromoDeepLinkActivationStart) } + verify(exactly = 1) { + analyticsEventHandler.send( + PromoActivationAnalytics.PromoActivation(PromoCodeActivationResult.NoBitcoinAddress), + ) + } } - } @Test - fun `GIVEN two BTC currencies first derivation mismatched AND one matching status WHEN findBitcoinAddress THEN no bitcoin address dialog is shown`() = runTest { - val promoCode = "PROMO123" - val queryParams = mapOf(DeeplinkConst.PROMO_CODE_KEY to promoCode) - val userWallet = mockUserWallet("ABCDEF") - every { getSelectedWalletSyncUseCase.invoke() } returns Either.Right(userWallet) + fun `GIVEN two BTC currencies first derivation mismatched AND one matching status WHEN findBitcoinAddress THEN no bitcoin address dialog is shown`() = + runTest { + val promoCode = "PROMO123" + val queryParams = mapOf(DeeplinkConst.PROMO_CODE_KEY to promoCode) + val userWallet = mockUserWallet("ABCDEF") + every { getSelectedWalletSyncUseCase.invoke() } returns Either.Right(userWallet) - val dpCard = Network.DerivationPath.Card("m/44'/0'/0'") - val dpCustom = Network.DerivationPath.Custom("m/84'/0'/0'") + val dpCard = Network.DerivationPath.Card("m/44'/0'/0'") + val dpCustom = Network.DerivationPath.Custom("m/84'/0'/0'") - val btcStatusCard = buildNetworkStatus( - rawNetworkId = Blockchain.Bitcoin.id, - address = "bc1qcard", - derivationPath = dpCard, - ) - coEvery { multiNetworkStatusSupplier.invoke(any()) } returns flowOf(setOf(btcStatusCard)) - - val btcCoinCustom = buildCryptoCurrency(rawNetworkId = Blockchain.Bitcoin.id, derivationPath = dpCustom) - val btcCoinCard = buildCryptoCurrency(rawNetworkId = Blockchain.Bitcoin.id, derivationPath = dpCard) - coEvery { multiWalletCryptoCurrenciesSupplier.getSyncOrNull(any()) } returns setOf(btcCoinCustom, btcCoinCard) - - val dispatcherProvider = testDispatcherProvider(testScheduler) - - DefaultPromoDeeplinkHandler( - scope = this, - queryParams = queryParams, - uiMessageSender = uiMessageSender, - multiNetworkStatusSupplier = multiNetworkStatusSupplier, - multiWalletCryptoCurrenciesSupplier = multiWalletCryptoCurrenciesSupplier, - activateBitcoinPromocodeUseCase = activateBitcoinPromocodeUseCase, - getSelectedWalletSyncUseCase = getSelectedWalletSyncUseCase, - analyticsEventsHandler = analyticsEventHandler, - dispatchers = dispatcherProvider, - ) - - advanceUntilIdle() - - val sent = messages.last { it is DialogMessage } as DialogMessage - Truth.assertThat(sent.title).isEqualTo(resourceReference(R.string.bitcoin_promo_no_address_title)) - Truth.assertThat(sent.message).isEqualTo(resourceReference(R.string.bitcoin_promo_no_address)) - - coVerify(exactly = 0) { activateBitcoinPromocodeUseCase.invoke(any(), any()) } - - verify(exactly = 1) { analyticsEventHandler.send(PromoActivationAnalytics.PromoDeepLinkActivationStart) } - verify(exactly = 1) { - analyticsEventHandler.send( - PromoActivationAnalytics.PromoActivation(PromoCodeActivationResult.NoBitcoinAddress), + val btcStatusCard = buildNetworkStatus( + rawNetworkId = Blockchain.Bitcoin.id, + address = "bc1qcard", + derivationPath = dpCard, ) + coEvery { multiNetworkStatusSupplier.invoke(any()) } returns flowOf(setOf(btcStatusCard)) + + val btcCoinCustom = buildCryptoCurrency(rawNetworkId = Blockchain.Bitcoin.id, derivationPath = dpCustom) + val btcCoinCard = buildCryptoCurrency(rawNetworkId = Blockchain.Bitcoin.id, derivationPath = dpCard) + coEvery { multiWalletCryptoCurrenciesSupplier.getSyncOrNull(any()) } returns setOf( + btcCoinCustom, + btcCoinCard, + ) + + val dispatcherProvider = testDispatcherProvider(testScheduler) + + DefaultPromoDeeplinkHandler( + scope = this, + queryParams = queryParams, + uiMessageSender = uiMessageSender, + multiNetworkStatusSupplier = multiNetworkStatusSupplier, + multiWalletCryptoCurrenciesSupplier = multiWalletCryptoCurrenciesSupplier, + activateBitcoinPromocodeUseCase = activateBitcoinPromocodeUseCase, + getSelectedWalletSyncUseCase = getSelectedWalletSyncUseCase, + analyticsEventsHandler = analyticsEventHandler, + dispatchers = dispatcherProvider, + ) + + advanceUntilIdle() + + val sent = messages.last { it is DialogMessage } as DialogMessage + Truth.assertThat(sent.title).isEqualTo(resourceReference(R.string.bitcoin_promo_no_address_title)) + Truth.assertThat(sent.message).isEqualTo(resourceReference(R.string.bitcoin_promo_no_address)) + + coVerify(exactly = 0) { activateBitcoinPromocodeUseCase.invoke(any(), any()) } + + verify(exactly = 1) { analyticsEventHandler.send(PromoActivationAnalytics.PromoDeepLinkActivationStart) } + verify(exactly = 1) { + analyticsEventHandler.send( + PromoActivationAnalytics.PromoActivation(PromoCodeActivationResult.NoBitcoinAddress), + ) + } } - } @Test - fun `GIVEN only BTC status with CUSTOM derivation AND only BTC currency with CARD derivation WHEN findBitcoinAddress THEN no bitcoin address dialog is shown`() = runTest { - val promoCode = "PROMO123" - val queryParams = mapOf(DeeplinkConst.PROMO_CODE_KEY to promoCode) - val userWallet = mockUserWallet("ABCDEF") - every { getSelectedWalletSyncUseCase.invoke() } returns Either.Right(userWallet) + fun `GIVEN only BTC status with CUSTOM derivation AND only BTC currency with CARD derivation WHEN findBitcoinAddress THEN no bitcoin address dialog is shown`() = + runTest { + val promoCode = "PROMO123" + val queryParams = mapOf(DeeplinkConst.PROMO_CODE_KEY to promoCode) + val userWallet = mockUserWallet("ABCDEF") + every { getSelectedWalletSyncUseCase.invoke() } returns Either.Right(userWallet) - val dpCard = Network.DerivationPath.Card("m/44'/0'/0'") - val dpCustom = Network.DerivationPath.Custom("m/84'/0'/0'") + val dpCard = Network.DerivationPath.Card("m/44'/0'/0'") + val dpCustom = Network.DerivationPath.Custom("m/84'/0'/0'") - val btcStatusCustom = buildNetworkStatus( - rawNetworkId = Blockchain.Bitcoin.id, - address = "bc1qcustom", - derivationPath = dpCustom, - ) - coEvery { multiNetworkStatusSupplier.invoke(any()) } returns flowOf(setOf(btcStatusCustom)) - - val btcCoinCard = buildCryptoCurrency(rawNetworkId = Blockchain.Bitcoin.id, derivationPath = dpCard) - coEvery { multiWalletCryptoCurrenciesSupplier.getSyncOrNull(any()) } returns setOf(btcCoinCard) - - val dispatcherProvider = testDispatcherProvider(testScheduler) - - DefaultPromoDeeplinkHandler( - scope = this, - queryParams = queryParams, - uiMessageSender = uiMessageSender, - multiNetworkStatusSupplier = multiNetworkStatusSupplier, - multiWalletCryptoCurrenciesSupplier = multiWalletCryptoCurrenciesSupplier, - activateBitcoinPromocodeUseCase = activateBitcoinPromocodeUseCase, - getSelectedWalletSyncUseCase = getSelectedWalletSyncUseCase, - analyticsEventsHandler = analyticsEventHandler, - dispatchers = dispatcherProvider, - ) - - advanceUntilIdle() - - val sent = messages.last { it is DialogMessage } as DialogMessage - Truth.assertThat(sent.title).isEqualTo(resourceReference(R.string.bitcoin_promo_no_address_title)) - Truth.assertThat(sent.message).isEqualTo(resourceReference(R.string.bitcoin_promo_no_address)) - - coVerify(exactly = 0) { activateBitcoinPromocodeUseCase.invoke(any(), any()) } - - verify(exactly = 1) { analyticsEventHandler.send(PromoActivationAnalytics.PromoDeepLinkActivationStart) } - verify(exactly = 1) { - analyticsEventHandler.send( - PromoActivationAnalytics.PromoActivation(PromoCodeActivationResult.NoBitcoinAddress), + val btcStatusCustom = buildNetworkStatus( + rawNetworkId = Blockchain.Bitcoin.id, + address = "bc1qcustom", + derivationPath = dpCustom, ) + coEvery { multiNetworkStatusSupplier.invoke(any()) } returns flowOf(setOf(btcStatusCustom)) + + val btcCoinCard = buildCryptoCurrency(rawNetworkId = Blockchain.Bitcoin.id, derivationPath = dpCard) + coEvery { multiWalletCryptoCurrenciesSupplier.getSyncOrNull(any()) } returns setOf(btcCoinCard) + + val dispatcherProvider = testDispatcherProvider(testScheduler) + + DefaultPromoDeeplinkHandler( + scope = this, + queryParams = queryParams, + uiMessageSender = uiMessageSender, + multiNetworkStatusSupplier = multiNetworkStatusSupplier, + multiWalletCryptoCurrenciesSupplier = multiWalletCryptoCurrenciesSupplier, + activateBitcoinPromocodeUseCase = activateBitcoinPromocodeUseCase, + getSelectedWalletSyncUseCase = getSelectedWalletSyncUseCase, + analyticsEventsHandler = analyticsEventHandler, + dispatchers = dispatcherProvider, + ) + + advanceUntilIdle() + + val sent = messages.last { it is DialogMessage } as DialogMessage + Truth.assertThat(sent.title).isEqualTo(resourceReference(R.string.bitcoin_promo_no_address_title)) + Truth.assertThat(sent.message).isEqualTo(resourceReference(R.string.bitcoin_promo_no_address)) + + coVerify(exactly = 0) { activateBitcoinPromocodeUseCase.invoke(any(), any()) } + + verify(exactly = 1) { analyticsEventHandler.send(PromoActivationAnalytics.PromoDeepLinkActivationStart) } + verify(exactly = 1) { + analyticsEventHandler.send( + PromoActivationAnalytics.PromoActivation(PromoCodeActivationResult.NoBitcoinAddress), + ) + } } - } @Test - fun `GIVEN only BTC status with CARD derivation AND only BTC currency with CUSTOM derivation WHEN findBitcoinAddress THEN no bitcoin address dialog is shown`() = runTest { - val promoCode = "PROMO123" - val queryParams = mapOf(DeeplinkConst.PROMO_CODE_KEY to promoCode) - val userWallet = mockUserWallet("ABCDEF") - every { getSelectedWalletSyncUseCase.invoke() } returns Either.Right(userWallet) + fun `GIVEN only BTC status with CARD derivation AND only BTC currency with CUSTOM derivation WHEN findBitcoinAddress THEN no bitcoin address dialog is shown`() = + runTest { + val promoCode = "PROMO123" + val queryParams = mapOf(DeeplinkConst.PROMO_CODE_KEY to promoCode) + val userWallet = mockUserWallet("ABCDEF") + every { getSelectedWalletSyncUseCase.invoke() } returns Either.Right(userWallet) - val dpCard = Network.DerivationPath.Card("m/44'/0'/0'") - val dpCustom = Network.DerivationPath.Custom("m/84'/0'/0'") + val dpCard = Network.DerivationPath.Card("m/44'/0'/0'") + val dpCustom = Network.DerivationPath.Custom("m/84'/0'/0'") - val btcStatusCard = buildNetworkStatus( - rawNetworkId = Blockchain.Bitcoin.id, - address = "bc1qcard", - derivationPath = dpCard, - ) - coEvery { multiNetworkStatusSupplier.invoke(any()) } returns flowOf(setOf(btcStatusCard)) - - val btcCoinCustom = buildCryptoCurrency(rawNetworkId = Blockchain.Bitcoin.id, derivationPath = dpCustom) - coEvery { multiWalletCryptoCurrenciesSupplier.getSyncOrNull(any()) } returns setOf(btcCoinCustom) - - val dispatcherProvider = testDispatcherProvider(testScheduler) - - DefaultPromoDeeplinkHandler( - scope = this, - queryParams = queryParams, - uiMessageSender = uiMessageSender, - multiNetworkStatusSupplier = multiNetworkStatusSupplier, - multiWalletCryptoCurrenciesSupplier = multiWalletCryptoCurrenciesSupplier, - activateBitcoinPromocodeUseCase = activateBitcoinPromocodeUseCase, - getSelectedWalletSyncUseCase = getSelectedWalletSyncUseCase, - analyticsEventsHandler = analyticsEventHandler, - dispatchers = dispatcherProvider, - ) - - advanceUntilIdle() - - val sent = messages.last { it is DialogMessage } as DialogMessage - Truth.assertThat(sent.title).isEqualTo(resourceReference(R.string.bitcoin_promo_no_address_title)) - Truth.assertThat(sent.message).isEqualTo(resourceReference(R.string.bitcoin_promo_no_address)) - - coVerify(exactly = 0) { activateBitcoinPromocodeUseCase.invoke(any(), any()) } - - verify(exactly = 1) { analyticsEventHandler.send(PromoActivationAnalytics.PromoDeepLinkActivationStart) } - verify(exactly = 1) { - analyticsEventHandler.send( - PromoActivationAnalytics.PromoActivation(PromoCodeActivationResult.NoBitcoinAddress), + val btcStatusCard = buildNetworkStatus( + rawNetworkId = Blockchain.Bitcoin.id, + address = "bc1qcard", + derivationPath = dpCard, ) + coEvery { multiNetworkStatusSupplier.invoke(any()) } returns flowOf(setOf(btcStatusCard)) + + val btcCoinCustom = buildCryptoCurrency(rawNetworkId = Blockchain.Bitcoin.id, derivationPath = dpCustom) + coEvery { multiWalletCryptoCurrenciesSupplier.getSyncOrNull(any()) } returns setOf(btcCoinCustom) + + val dispatcherProvider = testDispatcherProvider(testScheduler) + + DefaultPromoDeeplinkHandler( + scope = this, + queryParams = queryParams, + uiMessageSender = uiMessageSender, + multiNetworkStatusSupplier = multiNetworkStatusSupplier, + multiWalletCryptoCurrenciesSupplier = multiWalletCryptoCurrenciesSupplier, + activateBitcoinPromocodeUseCase = activateBitcoinPromocodeUseCase, + getSelectedWalletSyncUseCase = getSelectedWalletSyncUseCase, + analyticsEventsHandler = analyticsEventHandler, + dispatchers = dispatcherProvider, + ) + + advanceUntilIdle() + + val sent = messages.last { it is DialogMessage } as DialogMessage + Truth.assertThat(sent.title).isEqualTo(resourceReference(R.string.bitcoin_promo_no_address_title)) + Truth.assertThat(sent.message).isEqualTo(resourceReference(R.string.bitcoin_promo_no_address)) + + coVerify(exactly = 0) { activateBitcoinPromocodeUseCase.invoke(any(), any()) } + + verify(exactly = 1) { analyticsEventHandler.send(PromoActivationAnalytics.PromoDeepLinkActivationStart) } + verify(exactly = 1) { + analyticsEventHandler.send( + PromoActivationAnalytics.PromoActivation(PromoCodeActivationResult.NoBitcoinAddress), + ) + } } - } private fun mockUserWallet(id: String): UserWallet { val userWallet = mockk(relaxed = true) @@ -777,6 +782,7 @@ class DefaultPromoDeeplinkHandlerTest { hasFiatFeeRate = false, canHandleTokens = false, transactionExtrasType = Network.TransactionExtrasType.NONE, + nameResolvingType = Network.NameResolvingType.NONE, ) val value = NetworkStatus.Verified( @@ -807,6 +813,7 @@ class DefaultPromoDeeplinkHandlerTest { hasFiatFeeRate = false, canHandleTokens = false, transactionExtrasType = Network.TransactionExtrasType.NONE, + nameResolvingType = Network.NameResolvingType.NONE, ) val value = NetworkStatus.Unreachable(address = null) @@ -830,6 +837,7 @@ class DefaultPromoDeeplinkHandlerTest { hasFiatFeeRate = false, canHandleTokens = false, transactionExtrasType = Network.TransactionExtrasType.NONE, + nameResolvingType = Network.NameResolvingType.NONE, ) return CryptoCurrency.Coin(