Updated on 2026-08-14

This commit is contained in:
Tangem 2025-05-23 11:16:28 +03:00
commit ca49abf2b1
892 changed files with 17979 additions and 6555 deletions

View file

@ -5,6 +5,7 @@ import androidx.compose.ui.Modifier
import com.tangem.core.decompose.navigation.DummyRouter
import com.tangem.core.navigation.url.DummyUrlOpener
import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfig
import com.tangem.domain.wallets.models.UserWalletId
import com.tangem.features.details.component.DetailsComponent
import com.tangem.features.details.entity.DetailsFooterUM
import com.tangem.features.details.entity.DetailsUM
@ -18,7 +19,12 @@ internal class PreviewDetailsComponent : DetailsComponent {
private val previewBlocks = runBlocking {
ItemsBuilder(
router = DummyRouter(),
).buildAll(isWalletConnectAvailable = true, onSupportClick = {}, onBuyClick = {})
).buildAll(
isWalletConnectAvailable = true,
userWalletId = UserWalletId(""),
onSupportClick = {},
onBuyClick = {},
)
}
private val previewFooter = DetailsFooterUM(

View file

@ -80,6 +80,7 @@ internal class DetailsModel @Inject constructor(
items = MutableStateFlow(
itemsBuilder.buildAll(
isWalletConnectAvailable = isWalletConnectAvailable,
userWalletId = params.userWalletId,
onSupportClick = ::sendFeedback,
onBuyClick = ::onBuyClick,
),

View file

@ -6,6 +6,7 @@ import com.tangem.core.decompose.navigation.Router
import com.tangem.core.ui.components.block.model.BlockUM
import com.tangem.core.ui.extensions.resourceReference
import com.tangem.core.ui.extensions.stringReference
import com.tangem.domain.wallets.models.UserWalletId
import com.tangem.features.details.entity.DetailsItemUM
import com.tangem.features.details.impl.BuildConfig
import com.tangem.features.details.impl.R
@ -19,20 +20,21 @@ internal class ItemsBuilder @Inject constructor(private val router: Router) {
fun buildAll(
isWalletConnectAvailable: Boolean,
userWalletId: UserWalletId,
onSupportClick: () -> Unit,
onBuyClick: () -> Unit,
): ImmutableList<DetailsItemUM> = buildList {
buildWalletConnectBlock(isWalletConnectAvailable)?.let(::add)
buildWalletConnectBlock(isWalletConnectAvailable, userWalletId)?.let(::add)
buildUserWalletListBlock().let(::add)
buildShopBlock(onBuyClick).let(::add)
buildSettingsBlock().let(::add)
buildSupportBlock(onSupportClick).let(::add)
}.toImmutableList()
private fun buildWalletConnectBlock(isWalletConnectAvailable: Boolean): DetailsItemUM? {
private fun buildWalletConnectBlock(isWalletConnectAvailable: Boolean, userWalletId: UserWalletId): DetailsItemUM? {
return if (isWalletConnectAvailable) {
DetailsItemUM.WalletConnect(
onClick = { router.push(AppRoute.WalletConnectSessions) },
onClick = { router.push(AppRoute.WalletConnectSessions(userWalletId)) },
)
} else {
null

View file

@ -14,7 +14,7 @@ import com.tangem.core.decompose.context.AppComponentContext
import com.tangem.core.decompose.context.childByContext
import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfig
import com.tangem.core.ui.decompose.ComposableContentComponent
import com.tangem.domain.tokens.model.Network
import com.tangem.domain.models.network.Network
import com.tangem.features.managetokens.analytics.CustomTokenAnalyticsEvent
import com.tangem.features.managetokens.component.AddCustomTokenComponent
import com.tangem.features.managetokens.component.CustomTokenFormComponent

View file

@ -9,7 +9,7 @@ import com.tangem.core.decompose.context.AppComponentContext
import com.tangem.core.ui.extensions.resourceReference
import com.tangem.domain.managetokens.ValidateDerivationPathUseCase
import com.tangem.domain.managetokens.model.exceptoin.DerivationPathValidationException
import com.tangem.domain.tokens.model.Network
import com.tangem.domain.models.network.Network
import com.tangem.features.managetokens.component.CustomTokenDerivationInputComponent
import com.tangem.features.managetokens.entity.customtoken.CustomDerivationInputUM
import com.tangem.features.managetokens.entity.customtoken.SelectedDerivationPath

View file

@ -3,7 +3,7 @@ package com.tangem.features.managetokens.component.preview
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import com.tangem.core.ui.extensions.stringReference
import com.tangem.domain.tokens.model.Network
import com.tangem.domain.models.network.Network
import com.tangem.domain.wallets.models.UserWalletId
import com.tangem.features.managetokens.component.CustomTokenSelectorComponent
import com.tangem.features.managetokens.component.CustomTokenSelectorComponent.Params
@ -26,17 +26,19 @@ internal class PreviewCustomTokenSelectorComponent(
) : CustomTokenSelectorComponent {
private val previewItems = List(size = itemsSize) { index ->
val derivationPath = Network.DerivationPath.Card("m/44'/0'/0'/0/$index")
when (params) {
is Params.DerivationPathSelector -> {
val d = SelectedDerivationPath(
id = Network.ID(index.toString()),
value = Network.DerivationPath.Card("m/44'/0'/0'/0/$index"),
id = Network.ID(value = index.toString(), derivationPath = derivationPath),
value = derivationPath,
name = "Network $index",
isDefault = false,
)
DerivationPathUM(
id = d.id?.value ?: "",
id = d.id?.rawId?.value ?: "",
value = d.value.value.orEmpty(),
networkName = stringReference(d.name),
isSelected = d.value == params.selectedDerivationPath?.value,
@ -45,16 +47,16 @@ internal class PreviewCustomTokenSelectorComponent(
}
is Params.NetworkSelector -> {
val n = SelectedNetwork(
id = Network.ID(index.toString()),
id = Network.ID(value = index.toString(), derivationPath = derivationPath),
name = "Network $index",
derivationPath = Network.DerivationPath.Card("m/44'/0'/0'/0/$index"),
derivationPath = derivationPath,
canHandleTokens = false,
)
CurrencyNetworkUM(
network = Network(
id = n.id,
backendId = n.id.value,
backendId = n.id.rawId.value,
name = "Network $index",
currencySymbol = "N$index",
derivationPath = Network.DerivationPath.Card(""),
@ -76,7 +78,7 @@ internal class PreviewCustomTokenSelectorComponent(
}
}.toImmutableList()
val previewState = CustomTokenSelectorUM(
private val previewState = CustomTokenSelectorUM(
header = when (params) {
is Params.DerivationPathSelector -> CustomTokenSelectorUM.HeaderUM.CustomDerivationButton(
value = null,

View file

@ -11,7 +11,7 @@ import com.tangem.core.ui.components.currency.icon.CurrencyIconState
import com.tangem.core.ui.components.fields.entity.SearchBarUM
import com.tangem.core.ui.extensions.resourceReference
import com.tangem.domain.managetokens.model.ManagedCryptoCurrency
import com.tangem.domain.tokens.model.Network
import com.tangem.domain.models.network.Network
import com.tangem.features.managetokens.component.ManageTokensComponent
import com.tangem.features.managetokens.entity.item.CurrencyItemUM
import com.tangem.features.managetokens.entity.item.CurrencyNetworkUM
@ -27,7 +27,7 @@ import kotlinx.coroutines.flow.update
internal class PreviewManageTokensComponent(
private val isLoading: Boolean,
private val showTangemIcon: Boolean,
showTangemIcon: Boolean,
params: ManageTokensComponent.Params,
) : ManageTokensComponent {
@ -150,13 +150,14 @@ internal class PreviewManageTokensComponent(
)
private fun getCurrencyNetworks(currencyIndex: Int) = List(size = 3) { networkIndex ->
val derivationPath = Network.DerivationPath.Card("")
CurrencyNetworkUM(
network = Network(
id = Network.ID(networkIndex.toString()),
id = Network.ID(value = networkIndex.toString(), derivationPath = derivationPath),
backendId = networkIndex.toString(),
name = "Network $networkIndex",
currencySymbol = "N$networkIndex",
derivationPath = Network.DerivationPath.Card(""),
derivationPath = derivationPath,
isTestnet = false,
standardType = Network.StandardType.ERC20,
hasFiatFeeRate = false,

View file

@ -7,7 +7,7 @@ import com.tangem.core.ui.components.currency.icon.CurrencyIconState
import com.tangem.core.ui.components.fields.entity.SearchBarUM
import com.tangem.core.ui.extensions.resourceReference
import com.tangem.domain.managetokens.model.ManagedCryptoCurrency
import com.tangem.domain.tokens.model.Network
import com.tangem.domain.models.network.Network
import com.tangem.features.managetokens.component.OnboardingManageTokensComponent
import com.tangem.features.managetokens.entity.item.CurrencyItemUM
import com.tangem.features.managetokens.entity.item.CurrencyNetworkUM
@ -90,13 +90,15 @@ internal class PreviewOnboardingManageTokensComponent(
)
private fun getCurrencyNetworks() = List(size = 3) { networkIndex ->
val derivationPath = Network.DerivationPath.Card("")
CurrencyNetworkUM(
network = Network(
id = Network.ID(networkIndex.toString()),
id = Network.ID(value = networkIndex.toString(), derivationPath = derivationPath),
backendId = networkIndex.toString(),
name = "Network $networkIndex",
currencySymbol = "N$networkIndex",
derivationPath = Network.DerivationPath.Card(""),
derivationPath = derivationPath,
isTestnet = false,
standardType = Network.StandardType.ERC20,
hasFiatFeeRate = false,

View file

@ -1,6 +1,6 @@
package com.tangem.features.managetokens.entity.customtoken
import com.tangem.domain.tokens.model.Network
import com.tangem.domain.models.network.Network
import com.tangem.domain.wallets.models.UserWalletId
import kotlinx.serialization.Serializable

View file

@ -1,6 +1,6 @@
package com.tangem.features.managetokens.entity.item
import com.tangem.domain.tokens.model.Network
import com.tangem.domain.models.network.Network
internal data class CurrencyNetworkUM(
val network: Network,
@ -13,5 +13,5 @@ internal data class CurrencyNetworkUM(
override val onSelectedStateChange: (Boolean) -> Unit,
) : SelectableItemUM {
override val id: String = network.id.value
override val id: String = network.rawId
}

View file

@ -12,9 +12,9 @@ import com.tangem.core.ui.message.DialogMessage
import com.tangem.domain.card.DerivePublicKeysUseCase
import com.tangem.domain.card.HasMissedDerivationsUseCase
import com.tangem.domain.managetokens.model.exceptoin.CustomTokenFormValidationException
import com.tangem.domain.models.currency.CryptoCurrency
import com.tangem.domain.models.network.Network
import com.tangem.domain.tokens.AddCryptoCurrenciesUseCase
import com.tangem.domain.tokens.model.CryptoCurrency
import com.tangem.domain.tokens.model.Network
import com.tangem.features.managetokens.analytics.CustomTokenAnalyticsEvent
import com.tangem.features.managetokens.component.CustomTokenFormComponent
import com.tangem.features.managetokens.entity.customtoken.ClickableFieldUM

View file

@ -10,7 +10,7 @@ import com.tangem.core.decompose.ui.UiMessageSender
import com.tangem.core.ui.extensions.resourceReference
import com.tangem.core.ui.message.SnackbarMessage
import com.tangem.domain.managetokens.GetSupportedNetworksUseCase
import com.tangem.domain.tokens.model.Network
import com.tangem.domain.models.network.Network
import com.tangem.domain.wallets.models.UserWalletId
import com.tangem.features.managetokens.component.CustomTokenSelectorComponent
import com.tangem.features.managetokens.component.CustomTokenSelectorComponent.Params.DerivationPathSelector

View file

@ -12,11 +12,11 @@ import androidx.compose.ui.tooling.preview.PreviewParameterProvider
import com.tangem.core.ui.components.appbar.TangemTopAppBar
import com.tangem.core.ui.components.appbar.TangemTopAppBarHeight
import com.tangem.core.ui.components.appbar.models.TopAppBarButtonUM
import com.tangem.core.ui.components.bottomsheets.sheet.TangemBottomSheet
import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfig
import com.tangem.core.ui.components.bottomsheets.sheet.TangemBottomSheet
import com.tangem.core.ui.res.TangemTheme
import com.tangem.core.ui.res.TangemThemePreview
import com.tangem.domain.tokens.model.Network
import com.tangem.domain.models.network.Network
import com.tangem.domain.wallets.models.UserWalletId
import com.tangem.features.managetokens.component.AddCustomTokenComponent
import com.tangem.features.managetokens.component.preview.PreviewAddCustomTokenComponent
@ -76,7 +76,7 @@ private class AddCustomTokenComponentPreviewProvider : PreviewParameterProvider<
userWalletId = UserWalletId(stringValue = "321"),
step = AddCustomTokenConfig.Step.FORM,
selectedNetwork = SelectedNetwork(
id = Network.ID(value = "1"),
id = Network.ID(value = "1", derivationPath = Network.DerivationPath.None),
name = "Ethereum",
derivationPath = Network.DerivationPath.None,
canHandleTokens = false,
@ -88,7 +88,7 @@ private class AddCustomTokenComponentPreviewProvider : PreviewParameterProvider<
userWalletId = UserWalletId(stringValue = "321"),
step = AddCustomTokenConfig.Step.NETWORK_SELECTOR,
selectedNetwork = SelectedNetwork(
id = Network.ID(value = "0"),
id = Network.ID(value = "0", derivationPath = Network.DerivationPath.None),
name = "Ethereum",
derivationPath = Network.DerivationPath.None,
canHandleTokens = false,
@ -100,7 +100,7 @@ private class AddCustomTokenComponentPreviewProvider : PreviewParameterProvider<
userWalletId = UserWalletId(stringValue = "321"),
step = AddCustomTokenConfig.Step.DERIVATION_PATH_SELECTOR,
selectedDerivationPath = SelectedDerivationPath(
id = Network.ID(value = "0"),
id = Network.ID(value = "0", derivationPath = Network.DerivationPath.None),
value = Network.DerivationPath.None,
name = "Ethereum",
isDefault = false,

View file

@ -30,7 +30,7 @@ import com.tangem.core.ui.extensions.resolveReference
import com.tangem.core.ui.extensions.stringResourceSafe
import com.tangem.core.ui.res.TangemTheme
import com.tangem.core.ui.res.TangemThemePreview
import com.tangem.domain.tokens.model.Network
import com.tangem.domain.models.network.Network
import com.tangem.domain.wallets.models.UserWalletId
import com.tangem.features.managetokens.component.CustomTokenSelectorComponent
import com.tangem.features.managetokens.component.preview.PreviewCustomTokenSelectorComponent
@ -266,20 +266,23 @@ private fun Preview_CustomTokenNetworkSelectorContent(
private class CustomTokenNetworkSelectorComponentPreviewProvider :
PreviewParameterProvider<CustomTokenSelectorComponent> {
private val derivationPath = Network.DerivationPath.Card("m/44'/0'/0'/0/0")
override val values: Sequence<CustomTokenSelectorComponent>
get() = sequenceOf(
PreviewCustomTokenSelectorComponent(
params = CustomTokenSelectorComponent.Params.DerivationPathSelector(
userWalletId = UserWalletId(stringValue = "321"),
selectedNetwork = SelectedNetwork(
id = Network.ID(value = "0"),
id = Network.ID(value = "0", derivationPath = derivationPath),
name = "Ethereum",
derivationPath = Network.DerivationPath.Card("m/44'/0'/0'/0/0"),
derivationPath = derivationPath,
canHandleTokens = true,
),
selectedDerivationPath = SelectedDerivationPath(
id = Network.ID(value = "0"),
value = Network.DerivationPath.Card("m/44'/0'/0'/0/0"),
id = Network.ID(value = "0", derivationPath = derivationPath),
value = derivationPath,
name = "",
isDefault = false,
),
@ -290,9 +293,9 @@ private class CustomTokenNetworkSelectorComponentPreviewProvider :
params = CustomTokenSelectorComponent.Params.NetworkSelector(
userWalletId = UserWalletId(stringValue = "321"),
selectedNetwork = SelectedNetwork(
id = Network.ID(value = "0"),
id = Network.ID(value = "0", derivationPath = derivationPath),
name = "Ethereum",
derivationPath = Network.DerivationPath.Card("m/44'/0'/0'/0/0"),
derivationPath = derivationPath,
canHandleTokens = true,
),
onNetworkSelected = {},

View file

@ -9,8 +9,8 @@ import com.tangem.domain.managetokens.ValidateTokenFormUseCase
import com.tangem.domain.managetokens.model.AddCustomTokenForm
import com.tangem.domain.managetokens.model.exceptoin.CustomTokenFormValidationException
import com.tangem.domain.managetokens.model.exceptoin.FindTokenException
import com.tangem.domain.tokens.model.CryptoCurrency
import com.tangem.domain.tokens.model.Network
import com.tangem.domain.models.currency.CryptoCurrency
import com.tangem.domain.models.network.Network
import com.tangem.domain.wallets.models.UserWalletId
import com.tangem.utils.coroutines.JobHolder
import com.tangem.utils.coroutines.saveInAndJoin

View file

@ -1,7 +1,7 @@
package com.tangem.features.managetokens.utils.list
import com.tangem.domain.managetokens.model.ManagedCryptoCurrency
import com.tangem.domain.tokens.model.Network
import com.tangem.domain.models.network.Network
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.update

View file

@ -10,7 +10,7 @@ import com.tangem.core.ui.extensions.stringReference
import com.tangem.core.ui.message.SnackbarMessage
import com.tangem.domain.managetokens.*
import com.tangem.domain.managetokens.model.*
import com.tangem.domain.tokens.model.Network
import com.tangem.domain.models.network.Network
import com.tangem.domain.wallets.models.UserWalletId
import com.tangem.features.managetokens.analytics.ManageTokensAnalyticEvent
import com.tangem.features.managetokens.component.ManageTokensSource

View file

@ -2,7 +2,7 @@ package com.tangem.features.managetokens.utils.list
import com.tangem.domain.managetokens.model.CurrencyUnsupportedState
import com.tangem.domain.managetokens.model.ManagedCryptoCurrency
import com.tangem.domain.tokens.model.Network
import com.tangem.domain.models.network.Network
import com.tangem.domain.wallets.models.UserWalletId
internal interface ManageTokensUiActions {

View file

@ -10,7 +10,7 @@ import com.tangem.core.ui.message.EventMessageAction
import com.tangem.core.ui.message.SnackbarMessage
import com.tangem.domain.managetokens.model.CurrencyUnsupportedState
import com.tangem.domain.managetokens.model.ManagedCryptoCurrency
import com.tangem.domain.tokens.model.Network
import com.tangem.domain.models.network.Network
import com.tangem.features.managetokens.component.ManageTokensSource
import com.tangem.features.managetokens.entity.item.CurrencyItemUM
import com.tangem.features.managetokens.impl.R

View file

@ -2,7 +2,7 @@ package com.tangem.features.managetokens.utils.mapper
import com.tangem.domain.managetokens.model.ManagedCryptoCurrency
import com.tangem.domain.managetokens.model.ManagedCryptoCurrency.SourceNetwork
import com.tangem.domain.tokens.model.Network
import com.tangem.domain.models.network.Network
import com.tangem.features.managetokens.entity.item.CurrencyItemUM.Basic.NetworksUM
import com.tangem.features.managetokens.entity.item.CurrencyNetworkUM
import com.tangem.features.managetokens.utils.ui.getIconRes

View file

@ -2,7 +2,7 @@ package com.tangem.features.managetokens.utils.mapper
import com.tangem.core.ui.extensions.resourceReference
import com.tangem.core.ui.extensions.stringReference
import com.tangem.domain.tokens.model.Network
import com.tangem.domain.models.network.Network
import com.tangem.features.managetokens.entity.customtoken.SelectedNetwork
import com.tangem.features.managetokens.entity.item.DerivationPathUM
import com.tangem.features.managetokens.impl.R
@ -12,7 +12,7 @@ internal fun Network.toDerivationPathModel(
onSelectedStateChange: (Boolean) -> Unit,
): DerivationPathUM? {
return DerivationPathUM(
id = id.value,
id = rawId,
value = derivationPath.value ?: return null,
networkName = stringReference(name),
isSelected = isSelected,
@ -25,7 +25,7 @@ internal fun SelectedNetwork.toDerivationPathModel(
onSelectedStateChange: (Boolean) -> Unit,
): DerivationPathUM? {
return DerivationPathUM(
id = id.value,
id = id.rawId.value,
value = derivationPath.value ?: return null,
networkName = resourceReference(R.string.custom_token_derivation_path_default),
isSelected = isSelected,

View file

@ -3,7 +3,7 @@ package com.tangem.features.managetokens.utils.ui
import androidx.annotation.DrawableRes
import com.tangem.core.ui.extensions.getActiveIconRes
import com.tangem.core.ui.extensions.getGreyedOutIconRes
import com.tangem.domain.tokens.model.Network
import com.tangem.domain.models.network.Network
import com.tangem.features.managetokens.entity.item.CurrencyNetworkUM
internal fun CurrencyNetworkUM.select(isSelected: Boolean): CurrencyNetworkUM {
@ -15,7 +15,7 @@ internal fun CurrencyNetworkUM.select(isSelected: Boolean): CurrencyNetworkUM {
@DrawableRes
internal fun Network.ID.getIconRes(isColored: Boolean): Int = if (isColored) {
getActiveIconRes(value)
getActiveIconRes(rawId.value)
} else {
getGreyedOutIconRes(value)
getGreyedOutIconRes(rawId.value)
}

View file

@ -5,7 +5,7 @@ import com.tangem.core.ui.extensions.resourceReference
import com.tangem.core.ui.extensions.wrappedList
import com.tangem.domain.managetokens.ValidateTokenFormUseCase
import com.tangem.domain.managetokens.model.exceptoin.CustomTokenFormValidationException
import com.tangem.domain.tokens.model.CryptoCurrency
import com.tangem.domain.models.currency.CryptoCurrency
import com.tangem.features.managetokens.entity.customtoken.CustomTokenFormUM
import com.tangem.features.managetokens.entity.customtoken.CustomTokenFormUM.TokenFormUM.Field
import com.tangem.features.managetokens.impl.R

View file

@ -3,7 +3,7 @@ package com.tangem.features.markets.token.block
import androidx.compose.runtime.Stable
import com.tangem.core.decompose.context.AppComponentContext
import com.tangem.core.ui.decompose.ComposableContentComponent
import com.tangem.domain.tokens.model.CryptoCurrency
import com.tangem.domain.models.currency.CryptoCurrency
import kotlinx.serialization.Serializable
@Stable

View file

@ -13,7 +13,7 @@ import com.tangem.core.decompose.context.child
import com.tangem.core.decompose.model.getOrCreateModel
import com.tangem.core.ui.res.LocalMainBottomSheetColor
import com.tangem.core.ui.res.TangemTheme
import com.tangem.domain.tokens.model.CryptoCurrency
import com.tangem.domain.models.currency.CryptoCurrency
import com.tangem.features.markets.details.MarketsTokenDetailsComponent
import com.tangem.features.markets.details.MarketsTokenDetailsComponent.Params
import com.tangem.features.markets.details.impl.analytics.MarketDetailsAnalyticsEvent

View file

@ -5,11 +5,11 @@ import com.tangem.domain.appcurrency.GetSelectedAppCurrencyUseCase
import com.tangem.domain.appcurrency.model.AppCurrency
import com.tangem.domain.balancehiding.GetBalanceHidingSettingsUseCase
import com.tangem.domain.core.lce.Lce
import com.tangem.domain.models.currency.CryptoCurrency
import com.tangem.domain.tokens.GetAllWalletsCryptoCurrencyStatusesUseCase
import com.tangem.domain.tokens.GetCryptoCurrencyActionsUseCase
import com.tangem.domain.tokens.GetWalletTotalBalanceUseCase
import com.tangem.domain.tokens.error.TokenListError
import com.tangem.domain.tokens.model.CryptoCurrency
import com.tangem.domain.tokens.model.TotalFiatBalance
import com.tangem.domain.wallets.models.UserWallet
import com.tangem.domain.wallets.models.UserWalletId

View file

@ -14,10 +14,10 @@ import com.tangem.core.ui.message.DialogMessage
import com.tangem.core.ui.message.SnackbarMessage
import com.tangem.domain.appcurrency.model.AppCurrency
import com.tangem.domain.demo.IsDemoCardUseCase
import com.tangem.domain.models.network.Network
import com.tangem.domain.onramp.model.OnrampSource
import com.tangem.domain.redux.ReduxStateHolder
import com.tangem.domain.tokens.legacy.TradeCryptoAction
import com.tangem.domain.tokens.model.Network
import com.tangem.domain.tokens.model.TokenActionsState
import com.tangem.domain.wallets.models.UserWallet
import com.tangem.features.markets.impl.R

View file

@ -11,12 +11,12 @@ import com.tangem.domain.appcurrency.model.AppCurrency
import com.tangem.domain.markets.GetMarketsTokenListFlowUseCase
import com.tangem.domain.markets.GetStakingNotificationMaxApyUseCase
import com.tangem.domain.markets.TokenMarket
import com.tangem.domain.models.currency.CryptoCurrency
import com.tangem.domain.promo.PromoRepository
import com.tangem.domain.settings.usercountry.GetUserCountryUseCase
import com.tangem.domain.settings.usercountry.models.UserCountry
import com.tangem.domain.settings.usercountry.models.UserCountryError
import com.tangem.domain.settings.usercountry.models.needApplyFCARestrictions
import com.tangem.domain.tokens.model.CryptoCurrency
import com.tangem.features.markets.entry.BottomSheetState
import com.tangem.features.markets.tokenlist.impl.analytics.MarketsListAnalyticsEvent
import com.tangem.features.markets.tokenlist.impl.model.statemanager.MarketsListBatchFlowManager

View file

@ -2,7 +2,7 @@ package com.tangem.features.markets.tokenlist.impl.model.statemanager
import com.tangem.domain.appcurrency.model.AppCurrency
import com.tangem.domain.markets.*
import com.tangem.domain.tokens.model.CryptoCurrency
import com.tangem.domain.models.currency.CryptoCurrency
import com.tangem.features.markets.tokenlist.impl.model.converters.MarketsTokenItemConverter
import com.tangem.features.markets.tokenlist.impl.model.utils.logAction
import com.tangem.features.markets.tokenlist.impl.model.utils.logStatus

View file

@ -6,7 +6,7 @@ import com.tangem.core.ui.components.fields.entity.SearchBarUM
import com.tangem.core.ui.event.consumedEvent
import com.tangem.core.ui.event.triggeredEvent
import com.tangem.core.ui.extensions.resourceReference
import com.tangem.domain.tokens.model.CryptoCurrency
import com.tangem.domain.models.currency.CryptoCurrency
import com.tangem.features.markets.impl.R
import com.tangem.features.markets.tokenlist.impl.ui.state.*
import com.tangem.utils.Provider

View file

@ -38,7 +38,7 @@ import com.tangem.core.ui.format.bigdecimal.percent
import com.tangem.core.ui.res.LocalMainBottomSheetColor
import com.tangem.core.ui.res.TangemTheme
import com.tangem.core.ui.res.TangemThemePreview
import com.tangem.domain.tokens.model.CryptoCurrency
import com.tangem.domain.models.currency.CryptoCurrency
import com.tangem.features.markets.entry.BottomSheetState
import com.tangem.features.markets.impl.R
import com.tangem.features.markets.tokenlist.impl.ui.components.MarketsListLazyColumn

View file

@ -18,7 +18,7 @@ import com.tangem.core.ui.event.EventEffect
import com.tangem.core.ui.extensions.resourceReference
import com.tangem.core.ui.extensions.stringResourceSafe
import com.tangem.core.ui.res.TangemTheme
import com.tangem.domain.tokens.model.CryptoCurrency
import com.tangem.domain.models.currency.CryptoCurrency
import com.tangem.features.markets.impl.R
import com.tangem.features.markets.tokenlist.impl.ui.state.ListUM
import kotlinx.coroutines.launch

View file

@ -1,11 +1,12 @@
@file:Suppress("MagicNumber")
package com.tangem.features.markets.tokenlist.impl.ui.preview
import androidx.compose.ui.tooling.preview.datasource.CollectionPreviewParameterProvider
import com.tangem.common.ui.charts.state.MarketChartRawData
import com.tangem.core.ui.components.marketprice.PriceChangeType
import com.tangem.core.ui.extensions.stringReference
import com.tangem.domain.tokens.model.CryptoCurrency
import com.tangem.domain.models.currency.CryptoCurrency
import com.tangem.features.markets.tokenlist.impl.ui.state.MarketsListItemUM
import kotlinx.collections.immutable.persistentListOf

View file

@ -5,7 +5,7 @@ import com.tangem.common.ui.charts.state.MarketChartLook
import com.tangem.common.ui.charts.state.MarketChartRawData
import com.tangem.core.ui.components.marketprice.PriceChangeType
import com.tangem.core.ui.extensions.TextReference
import com.tangem.domain.tokens.model.CryptoCurrency
import com.tangem.domain.models.currency.CryptoCurrency
@Immutable
data class MarketsListItemUM(

View file

@ -6,7 +6,7 @@ import com.tangem.core.ui.components.fields.entity.SearchBarUM
import com.tangem.core.ui.event.StateEvent
import com.tangem.core.ui.extensions.TextReference
import com.tangem.core.ui.extensions.resourceReference
import com.tangem.domain.tokens.model.CryptoCurrency
import com.tangem.domain.models.currency.CryptoCurrency
import com.tangem.features.markets.impl.R
import kotlinx.collections.immutable.ImmutableList
import java.math.BigDecimal

View file

@ -0,0 +1,8 @@
package com.tangem.features.nft.entity
/**
* Trigger on success nft send
*/
interface NFTSendSuccessTrigger {
suspend fun triggerSuccessNFTSend()
}

View file

@ -28,6 +28,7 @@ dependencies {
/** Domain modules */
implementation(projects.domain.appCurrency.models)
implementation(projects.domain.appCurrency)
implementation(projects.domain.models)
implementation(projects.domain.nft)
implementation(projects.domain.nft.models)

View file

@ -5,7 +5,7 @@ import com.tangem.core.ui.extensions.TextReference
internal data class NFTCollectionUM(
val id: String,
val name: String?,
val name: String,
@DrawableRes val networkIconId: Int,
val logoUrl: String?,
val description: TextReference,

View file

@ -1,10 +1,11 @@
package com.tangem.features.nft.collections.entity
import androidx.compose.runtime.Immutable
import com.tangem.core.ui.extensions.TextReference
@Immutable
internal sealed class NFTSalePriceUM {
data object Loading : NFTSalePriceUM()
data object Failed : NFTSalePriceUM()
data class Content(val price: String) : NFTSalePriceUM()
data class Content(val price: TextReference) : NFTSalePriceUM()
}

View file

@ -2,13 +2,13 @@ package com.tangem.features.nft.collections.entity.transformer
import com.tangem.core.ui.components.fields.entity.SearchBarUM
import com.tangem.core.ui.components.notifications.NotificationConfig
import com.tangem.core.ui.extensions.TextReference
import com.tangem.core.ui.extensions.getActiveIconRes
import com.tangem.core.ui.extensions.resourceReference
import com.tangem.core.ui.extensions.wrappedList
import com.tangem.core.ui.extensions.*
import com.tangem.core.ui.format.bigdecimal.crypto
import com.tangem.core.ui.format.bigdecimal.format
import com.tangem.domain.nft.models.*
import com.tangem.features.nft.collections.entity.*
import com.tangem.features.nft.impl.R
import com.tangem.utils.StringsSigns.DASH_SIGN
import com.tangem.utils.transformer.Transformer
import kotlinx.collections.immutable.ImmutableList
import kotlinx.collections.immutable.toPersistentList
@ -16,7 +16,6 @@ import kotlinx.collections.immutable.toPersistentList
@Suppress("LongParameterList")
internal class UpdateDataStateTransformer(
private val nftCollections: List<NFTCollections>,
private val searchQuery: String,
private val onReceiveClick: () -> Unit,
private val onRetryClick: () -> Unit,
private val onExpandCollectionClick: (NFTCollection) -> Unit,
@ -56,7 +55,7 @@ internal class UpdateDataStateTransformer(
.map { it.content }
.asSequence()
.filterIsInstance<NFTCollections.Content.Collections>()
.map { it.collections.orEmpty().transform(prevState, searchQuery) }
.map { it.collections.orEmpty().transform(prevState) }
.flatten()
.toPersistentList(),
warnings = transformNotifications(),
@ -75,48 +74,23 @@ internal class UpdateDataStateTransformer(
)
}
private fun List<NFTCollection>.transform(
state: NFTCollectionsStateUM,
query: String,
): ImmutableList<NFTCollectionUM> = mapNotNull {
val assetsFulfillQuery = if (query.isEmpty()) {
true
} else {
when (val assets = it.assets) {
is NFTCollection.Assets.Empty,
is NFTCollection.Assets.Failed,
is NFTCollection.Assets.Loading,
-> false
is NFTCollection.Assets.Value -> {
assets.items.any { asset ->
asset.name?.lowercase()?.contains(query.lowercase()) == true
}
}
}
}
val collectionFulfillQuery = query.isEmpty() || it.name?.lowercase()?.contains(query.lowercase()) == true
if (collectionFulfillQuery || assetsFulfillQuery) {
NFTCollectionUM(
id = it.collectionIdProvider(),
networkIconId = getActiveIconRes(it.network.id.value),
name = it.name,
description = TextReference.PluralRes(
R.plurals.nft_collections_count,
it.count,
wrappedList(it.count),
),
logoUrl = it.logoUrl,
assets = it.transformAssets(),
onExpandClick = {
onExpandCollectionClick(it)
},
isExpanded = it.isExpanded(state),
)
} else {
null
}
private fun List<NFTCollection>.transform(state: NFTCollectionsStateUM): ImmutableList<NFTCollectionUM> = map {
NFTCollectionUM(
id = it.collectionIdProvider(),
networkIconId = getActiveIconRes(it.network.rawId),
name = it.name.orEmpty(),
description = TextReference.PluralRes(
R.plurals.nft_collections_count,
it.count,
wrappedList(it.count),
),
logoUrl = it.logoUrl,
assets = it.transformAssets(),
onExpandClick = {
onExpandCollectionClick(it)
},
isExpanded = it.isExpanded(state),
)
}.toPersistentList()
private fun transformNotifications(): ImmutableList<NFTCollectionsWarningUM> = buildList {
@ -146,20 +120,31 @@ internal class UpdateDataStateTransformer(
)
}
private fun NFTAsset.transform(collectionName: String): NFTCollectionAssetUM = NFTCollectionAssetUM(
id = id.toString(),
name = name.orEmpty(),
imageUrl = media?.url,
price = when (val salePrice = salePrice) {
is NFTSalePrice.Empty -> NFTSalePriceUM.Failed
is NFTSalePrice.Loading -> NFTSalePriceUM.Loading
is NFTSalePrice.Error -> NFTSalePriceUM.Failed
is NFTSalePrice.Value -> NFTSalePriceUM.Content(salePrice.value.toString())
},
onItemClick = {
onAssetClick(this, collectionName)
},
)
private fun NFTAsset.transform(collectionName: String): NFTCollectionAssetUM {
return NFTCollectionAssetUM(
id = id.toString(),
name = name ?: DASH_SIGN,
imageUrl = media?.url,
price = when (val salePrice = salePrice) {
is NFTSalePrice.Empty -> NFTSalePriceUM.Failed
is NFTSalePrice.Loading -> NFTSalePriceUM.Loading
is NFTSalePrice.Error -> NFTSalePriceUM.Failed
is NFTSalePrice.Value -> NFTSalePriceUM.Content(
price = stringReference(
salePrice.value.format {
crypto(
symbol = salePrice.symbol,
decimals = salePrice.decimals,
)
},
),
)
},
onItemClick = {
onAssetClick(this, collectionName)
},
)
}
private fun NFTCollection.isExpanded(state: NFTCollectionsStateUM): Boolean =
(state.content as? NFTCollectionsUM.Content)

View file

@ -11,14 +11,11 @@ import com.tangem.domain.nft.FetchNFTCollectionAssetsUseCase
import com.tangem.domain.nft.GetNFTCollectionsUseCase
import com.tangem.domain.nft.RefreshAllNFTUseCase
import com.tangem.domain.nft.models.NFTCollection
import com.tangem.domain.nft.models.NFTCollections
import com.tangem.features.nft.collections.NFTCollectionsComponent
import com.tangem.features.nft.collections.entity.NFTCollectionsStateUM
import com.tangem.features.nft.collections.entity.NFTCollectionsUM
import com.tangem.features.nft.collections.entity.transformer.*
import com.tangem.features.nft.collections.entity.transformer.ChangeCollectionExpandedStateTransformer
import com.tangem.features.nft.collections.entity.transformer.ToggleSearchBarTransformer
import com.tangem.features.nft.collections.entity.transformer.UpdateDataStateTransformer
import com.tangem.features.nft.collections.entity.transformer.UpdateSearchQueryTransformer
import com.tangem.features.nft.impl.R
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
import kotlinx.coroutines.flow.*
@ -75,8 +72,7 @@ internal class NFTCollectionsModel @Inject constructor(
) { nftCollections, query ->
_state.update {
UpdateDataStateTransformer(
nftCollections = nftCollections,
searchQuery = query,
nftCollections = nftCollections.filter(query),
onReceiveClick = {
params.onReceiveClick()
},
@ -95,6 +91,38 @@ internal class NFTCollectionsModel @Inject constructor(
.launchIn(modelScope)
}
private fun List<NFTCollections>.filter(query: String): List<NFTCollections> = map {
it.copy(
content = when (val content = it.content) {
is NFTCollections.Content.Collections -> content.copy(
collections = content.collections.orEmpty().filter {
val assetsFulfillQuery = if (query.isEmpty()) {
true
} else {
when (val assets = it.assets) {
is NFTCollection.Assets.Empty,
is NFTCollection.Assets.Failed,
is NFTCollection.Assets.Loading,
-> false
is NFTCollection.Assets.Value -> {
assets.items.any { asset ->
asset.name?.lowercase()?.contains(query.lowercase()) == true
}
}
}
}
val collectionFulfillQuery =
query.isEmpty() || it.name?.lowercase()?.contains(query.lowercase()) == true
collectionFulfillQuery || assetsFulfillQuery
},
)
is NFTCollections.Content.Error -> it.content
},
)
}
private fun onRefresh() {
modelScope.launch {
_state.update { ChangeRefreshingStateTransformer(true).transform(it) }

View file

@ -18,7 +18,6 @@ import androidx.compose.ui.tooling.preview.PreviewParameter
import androidx.compose.ui.tooling.preview.datasource.CollectionPreviewParameterProvider
import com.tangem.core.ui.extensions.TextReference
import com.tangem.core.ui.extensions.resolveReference
import com.tangem.core.ui.extensions.stringResourceSafe
import com.tangem.core.ui.res.TangemTheme
import com.tangem.core.ui.res.TangemThemePreview
import com.tangem.features.nft.collections.entity.NFTCollectionAssetsListUM
@ -90,7 +89,7 @@ private fun RowScope.Text(state: NFTCollectionUM) {
) {
Text(
modifier = Modifier,
text = state.name.takeUnless { it.isNullOrEmpty() } ?: stringResourceSafe(R.string.nft_no_collection),
text = state.name,
style = TangemTheme.typography.subtitle2,
color = TangemTheme.colors.text.primary1,
maxLines = 1,

View file

@ -24,6 +24,7 @@ import coil.request.ImageRequest
import com.tangem.core.ui.components.RectangleShimmer
import com.tangem.core.ui.components.SpacerH12
import com.tangem.core.ui.components.SpacerH2
import com.tangem.core.ui.extensions.stringReference
import com.tangem.core.ui.res.TangemTheme
import com.tangem.core.ui.res.TangemThemePreview
import com.tangem.features.nft.collections.entity.NFTCollectionAssetUM
@ -116,7 +117,9 @@ private class NFTCollectionAssetProvider : CollectionPreviewParameterProvider<NF
id = "item3",
name = "Nethers #0855",
imageUrl = "img",
price = NFTSalePriceUM.Content("0.05 ETH"),
price = NFTSalePriceUM.Content(
price = stringReference("0.05 ETH"),
),
onItemClick = { },
),
),

View file

@ -25,6 +25,7 @@ import com.tangem.core.ui.components.fields.entity.SearchBarUM
import com.tangem.core.ui.components.notifications.NotificationConfig
import com.tangem.core.ui.extensions.TextReference
import com.tangem.core.ui.extensions.resourceReference
import com.tangem.core.ui.extensions.stringReference
import com.tangem.core.ui.extensions.stringResourceSafe
import com.tangem.core.ui.res.TangemTheme
import com.tangem.core.ui.res.TangemThemePreview
@ -338,7 +339,9 @@ private fun Preview_NFTCollectionsContent() {
id = "item1",
name = "Nethers #0854",
imageUrl = "img",
price = NFTSalePriceUM.Content("0.05 ETH"),
price = NFTSalePriceUM.Content(
price = stringReference("0.05 ETH"),
),
onItemClick = { },
),
NFTCollectionAssetUM(

View file

@ -55,7 +55,7 @@ internal fun NFTCollectionsEmpty(state: NFTCollectionsUM.Empty, modifier: Modifi
PrimaryButton(
modifier = Modifier
.padding(top = TangemTheme.dimens.spacing48)
.wrapContentWidth(),
.widthIn(min = TangemTheme.dimens.size158),
text = stringResourceSafe(R.string.nft_collections_receive),
onClick = state.onReceiveClick,
)

View file

@ -6,6 +6,7 @@ import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.text.style.TextOverflow
import com.tangem.core.ui.components.TextShimmer
import com.tangem.core.ui.extensions.resolveReference
import com.tangem.core.ui.res.TangemTheme
import com.tangem.features.nft.collections.entity.NFTSalePriceUM
@ -15,7 +16,7 @@ internal fun NFTSalePrice(state: NFTSalePriceUM, modifier: Modifier = Modifier)
is NFTSalePriceUM.Content -> {
Text(
modifier = modifier,
text = state.price,
text = state.price.resolveReference(),
style = TangemTheme.typography.caption2,
color = TangemTheme.colors.text.tertiary,
maxLines = 1,

View file

@ -19,18 +19,22 @@ import com.tangem.features.nft.common.ui.NFTContent
import com.tangem.features.nft.component.NFTComponent
import com.tangem.features.nft.details.NFTDetailsComponent
import com.tangem.features.nft.details.info.NFTDetailsInfoComponent
import com.tangem.features.nft.entity.NFTSendSuccessListener
import com.tangem.features.nft.receive.NFTReceiveComponent
import com.tangem.features.nft.traits.NFTAssetTraitsComponent
import dagger.assisted.Assisted
import dagger.assisted.AssistedFactory
import dagger.assisted.AssistedInject
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.launch
internal class DefaultNFTComponent @AssistedInject constructor(
@Assisted appComponentContext: AppComponentContext,
@Assisted private val params: NFTComponent.Params,
private val nftDetailsInfoComponentFactory: NFTDetailsInfoComponent.Factory,
nftSendSuccessListener: NFTSendSuccessListener,
) : NFTComponent, AppComponentContext by appComponentContext {
private val stackNavigation = StackNavigation<NFTRoute>()
@ -69,6 +73,11 @@ internal class DefaultNFTComponent @AssistedInject constructor(
currentRoute.emit(stack.active.configuration)
}
}
nftSendSuccessListener.nftSendSuccessFlow
.onEach {
innerRouter.popTo(NFTRoute.Collections(userWalletId = params.userWalletId))
}
.launchIn(componentScope)
}
@Composable

View file

@ -22,7 +22,7 @@ class DefaultNFTDetailsBlockComponent @AssistedInject constructor(
assetName = stringReference(params.nftAsset.name.orEmpty()),
collectionName = stringReference(params.nftCollectionName),
assetImage = params.nftAsset.media?.url,
networkIconRes = getActiveIconRes(params.nftAsset.network.id.value),
networkIconRes = getActiveIconRes(params.nftAsset.network.rawId),
)
}

View file

@ -3,9 +3,7 @@ package com.tangem.features.nft.details.entity
import androidx.compose.runtime.Immutable
import com.tangem.core.ui.components.atoms.text.TextEllipsis
import com.tangem.core.ui.extensions.TextReference
import com.tangem.domain.appcurrency.model.AppCurrency
import kotlinx.collections.immutable.ImmutableList
import java.math.BigDecimal
data class NFTAssetUM(
val name: String,
@ -41,11 +39,9 @@ data class NFTAssetUM(
data object Loading : SalePrice()
data object Empty : SalePrice()
data class Content(
val value: BigDecimal,
val symbol: String,
val decimals: Int,
val rate: BigDecimal?,
val appCurrency: AppCurrency,
val isFlickering: Boolean,
val cryptoPrice: TextReference,
val fiatPrice: TextReference,
) : SalePrice()
}

View file

@ -1,7 +1,10 @@
package com.tangem.features.nft.details.entity
import com.tangem.core.ui.components.containers.pullToRefresh.PullToRefreshConfig
internal data class NFTDetailsUM(
val nftAsset: NFTAssetUM,
val pullToRefreshConfig: PullToRefreshConfig,
val onBackClick: () -> Unit,
val onReadMoreClick: () -> Unit,
val onSeeAllTraitsClick: () -> Unit,

View file

@ -1,9 +1,14 @@
package com.tangem.features.nft.details.entity.factory
import com.tangem.core.ui.components.atoms.text.TextEllipsis
import com.tangem.core.ui.components.containers.pullToRefresh.PullToRefreshConfig
import com.tangem.core.ui.extensions.TextReference
import com.tangem.core.ui.extensions.resourceReference
import com.tangem.core.ui.extensions.stringReference
import com.tangem.core.ui.format.bigdecimal.crypto
import com.tangem.core.ui.format.bigdecimal.fiat
import com.tangem.core.ui.format.bigdecimal.format
import com.tangem.domain.appcurrency.model.AppCurrency
import com.tangem.domain.nft.models.NFTAsset
import com.tangem.domain.nft.models.NFTSalePrice
import com.tangem.features.nft.details.entity.NFTAssetUM
@ -12,17 +17,24 @@ import com.tangem.features.nft.impl.R
import kotlinx.collections.immutable.persistentListOf
import kotlinx.collections.immutable.toImmutableList
@Suppress("LongParameterList")
internal class NFTDetailsUMFactory(
private val appCurrency: AppCurrency,
private val onBackClick: () -> Unit,
private val onReadMoreClick: () -> Unit,
private val onSeeAllTraitsClick: () -> Unit,
private val onExploreClick: () -> Unit,
private val onSendClick: () -> Unit,
private val onRefresh: () -> Unit,
private val onInfoBlockClick: (title: TextReference, text: TextReference) -> Unit,
) {
fun getInitialState(nftAsset: NFTAsset): NFTDetailsUM = NFTDetailsUM(
nftAsset = nftAsset.transform(),
pullToRefreshConfig = PullToRefreshConfig(
isRefreshing = false,
onRefresh = { onRefresh() },
),
onBackClick = onBackClick,
onReadMoreClick = onReadMoreClick,
onSeeAllTraitsClick = onSeeAllTraitsClick,
@ -52,7 +64,7 @@ internal class NFTDetailsUMFactory(
} else {
null
},
salePrice = NFTAssetUM.SalePrice.Empty,
salePrice = toSalePrice(),
description = description,
rarity = if (rarity != null) {
NFTAssetUM.Rarity.Content(
@ -91,6 +103,32 @@ internal class NFTDetailsUMFactory(
private fun NFTAsset.hasSalePrice() = salePrice !is NFTSalePrice.Empty && salePrice !is NFTSalePrice.Error
private fun NFTAsset.toSalePrice() = when (val price = salePrice) {
is NFTSalePrice.Empty,
is NFTSalePrice.Error,
-> NFTAssetUM.SalePrice.Empty
is NFTSalePrice.Loading -> NFTAssetUM.SalePrice.Loading
is NFTSalePrice.Value -> NFTAssetUM.SalePrice.Content(
isFlickering = false,
cryptoPrice = stringReference(
price.value.format {
crypto(
symbol = price.symbol,
decimals = price.decimals,
)
},
),
fiatPrice = stringReference(
price.fiatValue.format {
fiat(
fiatCurrencyCode = appCurrency.code,
fiatCurrencySymbol = appCurrency.symbol,
)
},
),
)
}
@Suppress("LongMethod")
private fun NFTAsset.buildBaseInfoItems() = when (val id = id) {
is NFTAsset.Identifier.EVM -> persistentListOf(

View file

@ -0,0 +1,53 @@
package com.tangem.features.nft.details.entity.transformer
import com.tangem.core.ui.extensions.stringReference
import com.tangem.core.ui.format.bigdecimal.crypto
import com.tangem.core.ui.format.bigdecimal.fiat
import com.tangem.core.ui.format.bigdecimal.format
import com.tangem.domain.appcurrency.model.AppCurrency
import com.tangem.domain.nft.models.NFTSalePrice
import com.tangem.features.nft.details.entity.NFTAssetUM
import com.tangem.features.nft.details.entity.NFTDetailsUM
import com.tangem.utils.transformer.Transformer
internal class NFTPriceChangeTransformer(
private val appCurrency: AppCurrency,
private val nftSalePrice: NFTSalePrice,
) : Transformer<NFTDetailsUM> {
override fun transform(prevState: NFTDetailsUM): NFTDetailsUM {
val topInfo = prevState.nftAsset.topInfo as? NFTAssetUM.TopInfo.Content ?: return prevState
return prevState.copy(
nftAsset = prevState.nftAsset.copy(
topInfo = topInfo.copy(
salePrice = when (nftSalePrice) {
is NFTSalePrice.Empty,
is NFTSalePrice.Error,
-> NFTAssetUM.SalePrice.Empty
is NFTSalePrice.Loading -> NFTAssetUM.SalePrice.Loading
is NFTSalePrice.Value -> NFTAssetUM.SalePrice.Content(
isFlickering = false,
cryptoPrice = stringReference(
nftSalePrice.value.format {
crypto(
symbol = nftSalePrice.symbol,
decimals = nftSalePrice.decimals,
)
},
),
fiatPrice = stringReference(
nftSalePrice.fiatValue.format {
fiat(
fiatCurrencyCode = appCurrency.code,
fiatCurrencySymbol = appCurrency.symbol,
)
},
),
)
},
),
),
)
}
}

View file

@ -0,0 +1,22 @@
package com.tangem.features.nft.details.entity.transformer
import com.tangem.features.nft.details.entity.NFTAssetUM
import com.tangem.features.nft.details.entity.NFTDetailsUM
import com.tangem.utils.transformer.Transformer
internal object NFTPriceUpdatingTransformer : Transformer<NFTDetailsUM> {
override fun transform(prevState: NFTDetailsUM): NFTDetailsUM {
val topInfo = prevState.nftAsset.topInfo as? NFTAssetUM.TopInfo.Content ?: return prevState
val salePrice = topInfo.salePrice as? NFTAssetUM.SalePrice.Content
return prevState.copy(
nftAsset = prevState.nftAsset.copy(
topInfo = topInfo.copy(
salePrice = salePrice?.copy(
isFlickering = true,
) ?: topInfo.salePrice,
),
),
)
}
}

View file

@ -1,5 +1,6 @@
package com.tangem.features.nft.details.model
import arrow.core.getOrElse
import com.arkivanov.decompose.router.slot.SlotNavigation
import com.arkivanov.decompose.router.slot.activate
import com.tangem.common.routing.AppRoute
@ -12,20 +13,31 @@ import com.tangem.core.navigation.url.UrlOpener
import com.tangem.core.ui.extensions.TextReference
import com.tangem.core.ui.extensions.resourceReference
import com.tangem.core.ui.extensions.stringReference
import com.tangem.domain.appcurrency.GetSelectedAppCurrencyUseCase
import com.tangem.domain.appcurrency.model.AppCurrency
import com.tangem.domain.nft.FetchNFTCollectionAssetsUseCase
import com.tangem.domain.nft.FetchNFTPriceUseCase
import com.tangem.domain.nft.GetNFTExploreUrlUseCase
import com.tangem.domain.nft.GetNFTPriceUseCase
import com.tangem.domain.nft.analytics.NFTAnalyticsEvent
import com.tangem.features.nft.details.NFTDetailsComponent
import com.tangem.features.nft.details.entity.NFTAssetUM
import com.tangem.features.nft.details.entity.NFTDetailsBottomSheetConfig
import com.tangem.features.nft.details.entity.NFTDetailsUM
import com.tangem.features.nft.details.entity.factory.NFTDetailsUMFactory
import com.tangem.features.nft.details.entity.transformer.NFTPriceChangeTransformer
import com.tangem.features.nft.details.entity.transformer.NFTPriceUpdatingTransformer
import com.tangem.features.nft.impl.R
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import com.tangem.utils.transformer.update
import kotlinx.coroutines.async
import kotlinx.coroutines.awaitAll
import kotlinx.coroutines.flow.*
import kotlinx.coroutines.launch
import timber.log.Timber
import javax.inject.Inject
@Suppress("LongParameterList")
@ModelScoped
internal class NFTDetailsModel @Inject constructor(
override val dispatchers: CoroutineDispatcherProvider,
@ -33,6 +45,10 @@ internal class NFTDetailsModel @Inject constructor(
private val analyticsEventHandler: AnalyticsEventHandler,
private val urlOpener: UrlOpener,
private val getNFTExploreUrlUseCase: GetNFTExploreUrlUseCase,
private val getSelectedAppCurrencyUseCase: GetSelectedAppCurrencyUseCase,
private val getNFTPriceUseCase: GetNFTPriceUseCase,
private val fetchNFTCollectionAssetsUseCase: FetchNFTCollectionAssetsUseCase,
private val fetchNFTPriceUseCase: FetchNFTPriceUseCase,
paramsContainer: ParamsContainer,
) : Model() {
@ -40,23 +56,88 @@ internal class NFTDetailsModel @Inject constructor(
val state: StateFlow<NFTDetailsUM> get() = _state
private var appCurrency: AppCurrency = AppCurrency.Default
private val stateFactory: NFTDetailsUMFactory = NFTDetailsUMFactory(
appCurrency = appCurrency,
onBackClick = { params.onBackClick() },
onReadMoreClick = ::onReadMoreClick,
onSeeAllTraitsClick = { params.onAllTraitsClick() },
onExploreClick = ::onExploreClick,
onSendClick = ::onSendClick,
onInfoBlockClick = ::onInfoBlockClick,
onRefresh = ::onRefresh,
)
private val _state = MutableStateFlow(
value = stateFactory.getInitialState(params.nftAsset),
)
private val _state by lazy {
MutableStateFlow(
value = stateFactory.getInitialState(params.nftAsset),
)
}
val bottomSheetNavigation: SlotNavigation<NFTDetailsBottomSheetConfig> = SlotNavigation()
init {
analyticsEventHandler.send(NFTAnalyticsEvent.Details.ScreenOpened(params.nftAsset.network.name))
initAppCurrency()
subscribeToPriceChanges()
}
private fun initAppCurrency() {
modelScope.launch {
appCurrency = getSelectedAppCurrencyUseCase.invokeSync().getOrElse { AppCurrency.Default }
}
}
private fun subscribeToPriceChanges() {
_state.update(NFTPriceUpdatingTransformer)
modelScope.launch {
getNFTPriceUseCase(params.userWalletId, params.nftAsset)
.fold(
ifLeft = {
Timber.w(it)
},
ifRight = { quoteFlow ->
quoteFlow
.distinctUntilChanged()
.onEach { salePrice ->
_state.update(
NFTPriceChangeTransformer(
appCurrency = appCurrency,
nftSalePrice = salePrice,
),
)
}.launchIn(modelScope)
},
)
}
}
private fun onRefresh() {
_state.update {
it.copy(pullToRefreshConfig = it.pullToRefreshConfig.copy(isRefreshing = true))
}
_state.update(NFTPriceUpdatingTransformer)
modelScope.launch {
awaitAll(
async {
fetchNFTCollectionAssetsUseCase(
userWalletId = params.userWalletId,
network = params.nftAsset.network,
collectionId = params.nftAsset.collectionId,
)
},
async {
fetchNFTPriceUseCase(
network = params.nftAsset.network,
appCurrencyId = null,
)
},
)
_state.update {
it.copy(pullToRefreshConfig = it.pullToRefreshConfig.copy(isRefreshing = false))
}
}
}
private fun onInfoBlockClick(title: TextReference, text: TextReference) {

View file

@ -1,6 +1,7 @@
package com.tangem.features.nft.details.ui
import androidx.activity.compose.BackHandler
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.statusBarsPadding
@ -11,6 +12,7 @@ import androidx.compose.ui.Modifier
import com.tangem.core.ui.components.PrimaryButton
import com.tangem.core.ui.components.appbar.TangemTopAppBar
import com.tangem.core.ui.components.appbar.models.TopAppBarButtonUM
import com.tangem.core.ui.components.containers.pullToRefresh.TangemPullToRefreshContainer
import com.tangem.core.ui.extensions.stringResourceSafe
import com.tangem.core.ui.res.TangemTheme
import com.tangem.features.nft.details.entity.NFTDetailsUM
@ -34,14 +36,19 @@ internal fun NFTDetails(state: NFTDetailsUM, modifier: Modifier = Modifier) {
)
},
content = { innerPadding ->
NFTDetailsAsset(
state = state.nftAsset,
onReadMoreClick = state.onReadMoreClick,
onSeeAllTraitsClick = state.onSeeAllTraitsClick,
onExploreClick = state.onExploreClick,
TangemPullToRefreshContainer(
config = state.pullToRefreshConfig,
modifier = Modifier
.padding(innerPadding),
)
.padding(innerPadding)
.fillMaxSize(),
) {
NFTDetailsAsset(
state = state.nftAsset,
onReadMoreClick = state.onReadMoreClick,
onSeeAllTraitsClick = state.onSeeAllTraitsClick,
onExploreClick = state.onExploreClick,
)
}
},
floatingActionButtonPosition = FabPosition.Center,
floatingActionButton = {

View file

@ -16,11 +16,9 @@ import com.tangem.core.ui.extensions.resourceReference
import com.tangem.core.ui.extensions.stringReference
import com.tangem.core.ui.res.TangemTheme
import com.tangem.core.ui.res.TangemThemePreview
import com.tangem.domain.appcurrency.model.AppCurrency
import com.tangem.features.nft.details.entity.NFTAssetUM
import com.tangem.features.nft.impl.R
import kotlinx.collections.immutable.persistentListOf
import java.math.BigDecimal
@Composable
internal fun NFTDetailsAsset(
@ -127,11 +125,9 @@ private class NFTAssetProvider : CollectionPreviewParameterProvider<NFTAssetUM>(
topInfo = NFTAssetUM.TopInfo.Content(
title = resourceReference(R.string.nft_details_last_sale_price),
salePrice = NFTAssetUM.SalePrice.Content(
value = BigDecimal("18.75"),
symbol = "ETH",
decimals = 18,
rate = BigDecimal("1"),
appCurrency = AppCurrency.Default,
cryptoPrice = stringReference("ETH 18.75"),
fiatPrice = stringReference("$ 1"),
isFlickering = true,
),
description = "Base edition by Piux. An illustration of Crypto Robot #7804".repeat(3),
rarity = NFTAssetUM.Rarity.Content(

View file

@ -1,7 +1,10 @@
package com.tangem.features.nft.details.ui
import android.content.res.Configuration
import androidx.compose.animation.*
import androidx.compose.animation.AnimatedVisibility
import androidx.compose.animation.animateContentSize
import androidx.compose.animation.fadeIn
import androidx.compose.animation.fadeOut
import androidx.compose.foundation.clickable
import androidx.compose.foundation.interaction.MutableInteractionSource
import androidx.compose.foundation.layout.*
@ -14,21 +17,17 @@ import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.tooling.preview.PreviewParameter
import androidx.compose.ui.tooling.preview.datasource.CollectionPreviewParameterProvider
import androidx.compose.ui.unit.dp
import com.tangem.common.ui.amountScreen.utils.getFiatString
import com.tangem.core.ui.components.TextShimmer
import com.tangem.core.ui.components.atoms.text.ReadMoreText
import com.tangem.core.ui.components.block.information.InformationBlock
import com.tangem.core.ui.components.flicker
import com.tangem.core.ui.extensions.resolveReference
import com.tangem.core.ui.extensions.resourceReference
import com.tangem.core.ui.extensions.stringReference
import com.tangem.core.ui.format.bigdecimal.crypto
import com.tangem.core.ui.format.bigdecimal.format
import com.tangem.core.ui.res.TangemTheme
import com.tangem.core.ui.res.TangemThemePreview
import com.tangem.domain.appcurrency.model.AppCurrency
import com.tangem.features.nft.details.entity.NFTAssetUM
import com.tangem.features.nft.impl.R
import java.math.BigDecimal
private const val READ_MORE_MAX_LINES = 3
@ -68,7 +67,9 @@ internal fun NFTDetailsInfoGroup(
@Composable
private fun SalePrice(state: NFTAssetUM.SalePrice, modifier: Modifier = Modifier) {
AnimatedVisibility(
modifier = Modifier.fillMaxWidth().animateContentSize(),
modifier = Modifier
.fillMaxWidth()
.animateContentSize(),
visible = state !is NFTAssetUM.SalePrice.Empty,
enter = fadeIn(),
exit = fadeOut(),
@ -108,19 +109,15 @@ private fun SalePrice(state: NFTAssetUM.SalePrice, modifier: Modifier = Modifier
verticalArrangement = Arrangement.SpaceAround,
) {
Text(
text = state.value.format {
crypto(
symbol = state.symbol,
decimals = state.decimals,
)
},
text = state.cryptoPrice.resolveReference(),
style = TangemTheme.typography.head,
color = TangemTheme.colors.text.primary1,
)
Text(
modifier = Modifier
.padding(top = TangemTheme.dimens.spacing4),
text = getFiatString(state.value, state.rate, state.appCurrency),
.padding(top = TangemTheme.dimens.spacing4)
.flicker(state.isFlickering),
text = state.fiatPrice.resolveReference(),
style = TangemTheme.typography.caption2,
color = TangemTheme.colors.text.tertiary,
)
@ -237,11 +234,9 @@ private class NFTAssetInfoProvider : CollectionPreviewParameterProvider<NFTAsset
NFTAssetUM.TopInfo.Content(
title = resourceReference(R.string.nft_details_last_sale_price),
salePrice = NFTAssetUM.SalePrice.Content(
value = BigDecimal("18.75"),
symbol = "ETH",
decimals = 18,
rate = BigDecimal("1"),
appCurrency = AppCurrency.Default,
cryptoPrice = stringReference("ETH 18.75"),
fiatPrice = stringReference("$ 1"),
isFlickering = false,
),
description = "Base edition by Piux. An illustration of Crypto Robot #7804".repeat(3),
rarity = NFTAssetUM.Rarity.Content(
@ -267,11 +262,9 @@ private class NFTAssetInfoProvider : CollectionPreviewParameterProvider<NFTAsset
NFTAssetUM.TopInfo.Content(
title = resourceReference(R.string.nft_details_last_sale_price),
salePrice = NFTAssetUM.SalePrice.Content(
value = BigDecimal("18.75"),
symbol = "ETH",
decimals = 18,
rate = BigDecimal("1"),
appCurrency = AppCurrency.Default,
cryptoPrice = stringReference("ETH 18.75"),
fiatPrice = stringReference("$ 1"),
isFlickering = true,
),
description = null,
rarity = NFTAssetUM.Rarity.Content(
@ -285,11 +278,9 @@ private class NFTAssetInfoProvider : CollectionPreviewParameterProvider<NFTAsset
NFTAssetUM.TopInfo.Content(
title = resourceReference(R.string.nft_details_last_sale_price),
salePrice = NFTAssetUM.SalePrice.Content(
value = BigDecimal("18.75"),
symbol = "ETH",
decimals = 18,
rate = BigDecimal("1"),
appCurrency = AppCurrency.Default,
cryptoPrice = stringReference("ETH 18.75"),
fiatPrice = stringReference("$ 1"),
isFlickering = true,
),
description = "Base edition by Piux. An illustration of Crypto Robot #7804".repeat(3),
rarity = NFTAssetUM.Rarity.Empty,
@ -297,11 +288,9 @@ private class NFTAssetInfoProvider : CollectionPreviewParameterProvider<NFTAsset
NFTAssetUM.TopInfo.Content(
title = resourceReference(R.string.nft_details_last_sale_price),
salePrice = NFTAssetUM.SalePrice.Content(
value = BigDecimal("18.75"),
symbol = "ETH",
decimals = 18,
rate = BigDecimal("1"),
appCurrency = AppCurrency.Default,
cryptoPrice = stringReference("ETH 18.75"),
fiatPrice = stringReference("$ 1"),
isFlickering = true,
),
description = null,
rarity = NFTAssetUM.Rarity.Empty,

View file

@ -11,6 +11,9 @@ import com.tangem.features.nft.details.block.DefaultNFTDetailsBlockComponent
import com.tangem.features.nft.details.info.DefaultNFTDetailsInfoComponent
import com.tangem.features.nft.details.info.NFTDetailsInfoComponent
import com.tangem.features.nft.details.model.NFTDetailsModel
import com.tangem.features.nft.entity.DefaultNFTSendSuccessTrigger
import com.tangem.features.nft.entity.NFTSendSuccessListener
import com.tangem.features.nft.entity.NFTSendSuccessTrigger
import com.tangem.features.nft.receive.model.NFTReceiveModel
import com.tangem.features.nft.traits.model.NFTAssetTraitsModel
import dagger.Binds
@ -71,4 +74,12 @@ internal interface NFTFeatureModuleBinds {
@IntoMap
@ClassKey(NFTAssetTraitsModel::class)
fun bindNFTTraitsModel(model: NFTAssetTraitsModel): Model
@Binds
@Singleton
fun bindNFTSendSuccessTrigger(impl: DefaultNFTSendSuccessTrigger): NFTSendSuccessTrigger
@Binds
@Singleton
fun bindNFTSendSuccessListener(impl: DefaultNFTSendSuccessTrigger): NFTSendSuccessListener
}

View file

@ -0,0 +1,25 @@
package com.tangem.features.nft.entity
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.SharedFlow
import javax.inject.Inject
import javax.inject.Singleton
/**
* Listens to success nft send trigger
*/
interface NFTSendSuccessListener {
val nftSendSuccessFlow: Flow<Unit>
}
@Singleton
internal class DefaultNFTSendSuccessTrigger @Inject constructor() : NFTSendSuccessTrigger, NFTSendSuccessListener {
override val nftSendSuccessFlow: SharedFlow<Unit>
field = MutableSharedFlow<Unit>()
override suspend fun triggerSuccessNFTSend() {
nftSendSuccessFlow.emit(Unit)
}
}

View file

@ -2,8 +2,8 @@ package com.tangem.features.nft.receive.entity.transformer
import com.tangem.common.ui.bottomsheet.receive.TokenReceiveBottomSheetConfig
import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfig
import com.tangem.domain.tokens.model.Network
import com.tangem.domain.tokens.model.NetworkAddress
import com.tangem.domain.models.network.Network
import com.tangem.domain.models.network.NetworkAddress
import com.tangem.features.nft.receive.entity.NFTReceiveUM
import com.tangem.utils.transformer.Transformer

View file

@ -3,8 +3,8 @@ package com.tangem.features.nft.receive.entity.transformer
import com.tangem.core.ui.components.currency.icon.CurrencyIconState
import com.tangem.core.ui.components.rows.model.ChainRowUM
import com.tangem.core.ui.extensions.getActiveIconRes
import com.tangem.domain.models.network.Network
import com.tangem.domain.nft.models.NFTNetworks
import com.tangem.domain.tokens.model.Network
import com.tangem.features.nft.receive.entity.NFTNetworkUM
import com.tangem.features.nft.receive.entity.NFTReceiveUM
import com.tangem.utils.transformer.Transformer
@ -33,13 +33,13 @@ internal class UpdateDataStateTransformer(
private fun Network.transform(enabled: Boolean): NFTNetworkUM {
val custom = derivationPath is Network.DerivationPath.Custom
return NFTNetworkUM(
id = id.value + derivationPath.value,
id = rawId + derivationPath.value,
chainRowUM = ChainRowUM(
name = name,
type = "",
icon = CurrencyIconState.CoinIcon(
url = null,
fallbackResId = getActiveIconRes(id.value),
fallbackResId = getActiveIconRes(rawId),
isGrayscale = !enabled,
showCustomBadge = custom,
),

View file

@ -12,12 +12,12 @@ import com.tangem.core.ui.components.fields.entity.SearchBarUM
import com.tangem.core.ui.extensions.resourceReference
import com.tangem.core.ui.extensions.wrappedList
import com.tangem.core.ui.message.DialogMessage
import com.tangem.domain.models.network.Network
import com.tangem.domain.models.network.NetworkStatus
import com.tangem.domain.nft.FilterNFTAvailableNetworksUseCase
import com.tangem.domain.nft.GetNFTNetworksUseCase
import com.tangem.domain.nft.GetNFTNetworkStatusUseCase
import com.tangem.domain.nft.GetNFTNetworksUseCase
import com.tangem.domain.nft.analytics.NFTAnalyticsEvent
import com.tangem.domain.tokens.model.Network
import com.tangem.domain.tokens.model.NetworkStatus
import com.tangem.features.nft.impl.R
import com.tangem.features.nft.receive.NFTReceiveComponent
import com.tangem.features.nft.receive.entity.NFTReceiveUM

View file

@ -2,7 +2,7 @@ package com.tangem.features.onboarding.v2.common.analytics
import com.tangem.core.analytics.models.AnalyticsEvent
import com.tangem.core.analytics.models.AnalyticsParam
import com.tangem.domain.tokens.model.CryptoCurrency
import com.tangem.domain.models.currency.CryptoCurrency
sealed class OnboardingEvent(
category: String,

View file

@ -13,13 +13,13 @@ import com.tangem.core.ui.format.bigdecimal.crypto
import com.tangem.core.ui.format.bigdecimal.format
import com.tangem.domain.card.repository.CardRepository
import com.tangem.domain.exchange.RampStateManager
import com.tangem.domain.models.network.Network
import com.tangem.domain.models.network.NetworkAddress
import com.tangem.domain.models.scan.ScanResponse
import com.tangem.domain.onramp.GetLegacyTopUpUrlUseCase
import com.tangem.domain.tokens.FetchCurrencyStatusUseCase
import com.tangem.domain.tokens.GetPrimaryCurrencyStatusUpdatesUseCase
import com.tangem.domain.tokens.GetSingleCryptoCurrencyStatusUseCase
import com.tangem.domain.tokens.model.CryptoCurrencyStatus
import com.tangem.domain.tokens.model.Network
import com.tangem.domain.tokens.model.NetworkAddress
import com.tangem.domain.tokens.model.ScenarioUnavailabilityReason
import com.tangem.domain.tokens.model.analytics.TokenReceiveAnalyticsEvent
import com.tangem.domain.wallets.builder.UserWalletBuilder
@ -39,7 +39,7 @@ import javax.inject.Inject
internal class OnboardingNoteTopUpModel @Inject constructor(
paramsContainer: ParamsContainer,
override val dispatchers: CoroutineDispatcherProvider,
private val getPrimaryCurrencyStatusUpdatesUseCase: GetPrimaryCurrencyStatusUpdatesUseCase,
private val getSingleCryptoCurrencyStatusUseCase: GetSingleCryptoCurrencyStatusUseCase,
private val fetchCurrencyStatusUseCase: FetchCurrencyStatusUseCase,
private val userWalletBuilderFactory: UserWalletBuilder.Factory,
private val getLegacyTopUpUrlUseCase: GetLegacyTopUpUrlUseCase,
@ -137,8 +137,11 @@ internal class OnboardingNoteTopUpModel @Inject constructor(
private fun observeCryptoCurrencyStatus() {
val userWalletId = userWallet?.walletId ?: return
getPrimaryCurrencyStatusUpdatesUseCase(userWalletId = userWalletId).map { it.getOrNull() }.filterNotNull()
.onEach(::applyCryptoCurrencyStatusToState).launchIn(modelScope)
getSingleCryptoCurrencyStatusUseCase.invokeSingleWallet(userWalletId = userWalletId)
.map { it.getOrNull() }
.filterNotNull()
.onEach(::applyCryptoCurrencyStatusToState)
.launchIn(modelScope)
}
private fun applyCryptoCurrencyStatusToState(status: CryptoCurrencyStatus) {

View file

@ -7,18 +7,18 @@ import com.tangem.common.core.TangemError
import com.tangem.common.core.TangemSdkError
import com.tangem.common.extensions.hexToBytes
import com.tangem.common.extensions.toHexString
import com.tangem.common.ui.bottomsheet.receive.TokenReceiveBottomSheetConfig
import com.tangem.core.analytics.Analytics
import com.tangem.core.analytics.api.AnalyticsEventHandler
import com.tangem.core.analytics.models.AnalyticsParam
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.decompose.ui.UiMessageSender
import com.tangem.core.navigation.url.UrlOpener
import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfig
import com.tangem.common.ui.bottomsheet.receive.TokenReceiveBottomSheetConfig
import com.tangem.core.analytics.Analytics
import com.tangem.core.navigation.share.ShareManager
import com.tangem.core.navigation.url.UrlOpener
import com.tangem.core.ui.clipboard.ClipboardManager
import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfig
import com.tangem.core.ui.extensions.resourceReference
import com.tangem.core.ui.extensions.toWrappedList
import com.tangem.core.ui.format.bigdecimal.crypto
@ -30,13 +30,13 @@ import com.tangem.domain.common.getTwinCardNumber
import com.tangem.domain.common.util.twinsIsTwinned
import com.tangem.domain.feedback.SendFeedbackEmailUseCase
import com.tangem.domain.feedback.models.FeedbackEmailType
import com.tangem.domain.models.network.Network
import com.tangem.domain.models.scan.ScanResponse
import com.tangem.domain.onboarding.SaveTwinsOnboardingShownUseCase
import com.tangem.domain.onramp.GetLegacyTopUpUrlUseCase
import com.tangem.domain.tokens.FetchCurrencyStatusUseCase
import com.tangem.domain.tokens.GetPrimaryCurrencyStatusUpdatesUseCase
import com.tangem.domain.tokens.GetSingleCryptoCurrencyStatusUseCase
import com.tangem.domain.tokens.model.CryptoCurrencyStatus
import com.tangem.domain.tokens.model.Network
import com.tangem.domain.tokens.model.analytics.TokenReceiveAnalyticsEvent
import com.tangem.domain.wallets.builder.UserWalletBuilder
import com.tangem.domain.wallets.builder.UserWalletIdBuilder
@ -77,7 +77,7 @@ internal class OnboardingTwinModel @Inject constructor(
private val tangemSdkManager: TangemSdkManager,
private val issuersConfigStorage: IssuersConfigStorage,
private val cardRepository: CardRepository,
private val getPrimaryCurrencyStatusUpdatesUseCase: GetPrimaryCurrencyStatusUpdatesUseCase,
private val getSingleCryptoCurrencyStatusUseCase: GetSingleCryptoCurrencyStatusUseCase,
private val fetchCurrencyStatusUseCase: FetchCurrencyStatusUseCase,
private val getLegacyTopUpUrlUseCase: GetLegacyTopUpUrlUseCase,
private val urlOpener: UrlOpener,
@ -337,16 +337,16 @@ internal class OnboardingTwinModel @Inject constructor(
setLoading(false)
}
val cryptoCurrencyStatus =
getPrimaryCurrencyStatusUpdatesUseCase.invoke(userWallet.walletId).firstOrNull()?.getOrNull()
?: run {
setLoading(false)
Timber.e("Unable to get currency status")
return@coroutineScope
}
val cryptoCurrencyStatus = getSingleCryptoCurrencyStatusUseCase.invokeSingleWallet(userWallet.walletId)
.firstOrNull()?.getOrNull()
?: run {
setLoading(false)
Timber.e("Unable to get currency status")
return@coroutineScope
}
launch {
getPrimaryCurrencyStatusUpdatesUseCase.invoke(userWallet.walletId)
getSingleCryptoCurrencyStatusUseCase.invokeSingleWallet(userWallet.walletId)
.collect {
it.onRight { status ->
applyCryptoCurrencyStatusToState(status)

View file

@ -2,6 +2,7 @@ package com.tangem.features.onboarding.v2.visa.impl.child.accesscode.model
import androidx.compose.runtime.Stable
import androidx.compose.ui.text.input.TextFieldValue
import arrow.core.getOrElse
import com.tangem.common.CompletionResult
import com.tangem.common.extensions.toHexString
import com.tangem.core.analytics.api.AnalyticsEventHandler
@ -9,10 +10,9 @@ 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.decompose.ui.UiMessageSender
import com.tangem.core.error.UniversalError
import com.tangem.core.error.ext.universalError
import com.tangem.core.ui.utils.showErrorDialog
import com.tangem.domain.visa.error.VisaAPIError
import com.tangem.domain.visa.error.VisaAuthorizationAPIError
import com.tangem.domain.visa.model.VisaCardActivationStatus
import com.tangem.domain.visa.model.VisaCardId
import com.tangem.domain.visa.model.VisaCustomerWalletDataToSignRequest
@ -153,14 +153,11 @@ internal class OnboardingVisaAccessCodeModel @Inject constructor(
loading(true)
modelScope.launch {
val challengeToSign = runCatching {
visaAuthRepository.getCardAuthChallenge(
cardId = activationInput.cardId,
cardPublicKey = activationInput.cardPublicKey,
)
}.getOrElse {
loading(false)
uiMessageSender.showErrorDialog(VisaAuthorizationAPIError)
val challengeToSign = visaAuthRepository.getCardAuthChallenge(
cardId = activationInput.cardId,
cardPublicKey = activationInput.cardPublicKey,
).getOrElse {
onError(it)
return@launch
}
@ -174,23 +171,17 @@ internal class OnboardingVisaAccessCodeModel @Inject constructor(
val resultData = when (result) {
is CompletionResult.Failure -> {
loading(false)
uiMessageSender.showErrorDialog(result.error.universalError)
analyticsEventsHandler.send(
VisaAnalyticsEvent.ErrorOnboarding(result.error.universalError),
)
onError(result.error.universalError)
return@launch
}
is CompletionResult.Success -> result.data
}
runCatching {
visaActivationRepository.activateCard(resultData.signedActivationData)
}.onFailure {
loading(false)
uiMessageSender.showErrorDialog(VisaAPIError)
return@launch
}
visaActivationRepository.activateCard(resultData.signedActivationData)
.onLeft {
onError(it)
return@launch
}
val targetAddress =
result.data.signedActivationData.dataToSign.request.activationOrderInfo.customerWalletAddress
@ -213,6 +204,12 @@ internal class OnboardingVisaAccessCodeModel @Inject constructor(
}
}
private fun onError(error: UniversalError) {
loading(false)
uiMessageSender.showErrorDialog(error)
analyticsEventsHandler.send(VisaAnalyticsEvent.ErrorOnboarding(error))
}
private fun loading(state: Boolean) {
_uiState.update { it.copy(buttonLoading = state) }
}

View file

@ -1,6 +1,7 @@
package com.tangem.features.onboarding.v2.visa.impl.child.approve.model
import androidx.compose.runtime.Stable
import arrow.core.getOrElse
import com.tangem.common.CompletionResult
import com.tangem.common.extensions.toHexString
import com.tangem.core.analytics.api.AnalyticsEventHandler
@ -8,9 +9,9 @@ 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.decompose.ui.UiMessageSender
import com.tangem.core.error.UniversalError
import com.tangem.core.error.ext.universalError
import com.tangem.core.ui.utils.showErrorDialog
import com.tangem.domain.visa.error.VisaAPIError
import com.tangem.domain.visa.model.VisaCardId
import com.tangem.domain.visa.model.VisaDataForApprove
import com.tangem.domain.visa.repository.VisaActivationRepository
@ -67,13 +68,12 @@ internal class OnboardingVisaApproveModel @Inject constructor(
analyticsEventHandler.send(OnboardingVisaAnalyticsEvent.ButtonApprove)
modelScope.launch {
val dataToSign = runCatching {
val dataToSign =
visaActivationRepository.getCustomerWalletAcceptanceData(params.preparationDataForApprove.request)
}.getOrElse {
loading(false)
uiMessageSender.showErrorDialog(VisaAPIError)
return@launch
}
.getOrElse {
onError(it)
return@launch
}
val result = tangemSdkManager.visaCustomerWalletApprove(
visaDataForApprove = VisaDataForApprove(
@ -85,26 +85,28 @@ internal class OnboardingVisaApproveModel @Inject constructor(
val resultData = when (result) {
is CompletionResult.Failure -> {
loading(false)
uiMessageSender.showErrorDialog(result.error.universalError)
analyticsEventHandler.send(VisaAnalyticsEvent.ErrorOnboarding(result.error.universalError))
onError(result.error.universalError)
return@launch
}
is CompletionResult.Success -> result.data
}
runCatching {
visaActivationRepository.approveByCustomerWallet(resultData)
}.onFailure {
loading(false)
uiMessageSender.showErrorDialog(VisaAPIError)
return@launch
}
visaActivationRepository.approveByCustomerWallet(resultData)
.onLeft {
onError(it)
return@launch
}
onDone.emit(Unit)
}
}
private fun onError(error: UniversalError) {
loading(false)
uiMessageSender.showErrorDialog(error)
analyticsEventHandler.send(VisaAnalyticsEvent.ErrorOnboarding(error))
}
private fun loading(state: Boolean) {
_uiState.update { it.copy(approveButtonLoading = state) }
}

View file

@ -1,6 +1,7 @@
package com.tangem.features.onboarding.v2.visa.impl.child.inprogress.model
import androidx.compose.runtime.Stable
import arrow.core.getOrElse
import com.tangem.common.extensions.toHexString
import com.tangem.core.analytics.api.AnalyticsEventHandler
import com.tangem.core.decompose.di.ModelScoped
@ -12,8 +13,10 @@ import com.tangem.datasource.local.visa.VisaAuthTokenStorage
import com.tangem.datasource.local.visa.VisaOTPStorage
import com.tangem.domain.models.scan.ScanResponse
import com.tangem.domain.visa.error.VisaActivationError
import com.tangem.domain.visa.error.VisaAuthorizationAPIError
import com.tangem.domain.visa.model.*
import com.tangem.domain.visa.model.VisaActivationRemoteState
import com.tangem.domain.visa.model.VisaAuthTokens
import com.tangem.domain.visa.model.VisaCardActivationStatus
import com.tangem.domain.visa.model.VisaCardId
import com.tangem.domain.visa.repository.VisaActivationRepository
import com.tangem.domain.visa.repository.VisaAuthRepository
import com.tangem.domain.wallets.builder.UserWalletBuilder
@ -64,9 +67,13 @@ internal class OnboardingVisaInProgressModel @Inject constructor(
private fun runShortPolling() {
modelScope.launch {
while (true) {
val result = runCatching {
visaActivationRepository.getActivationRemoteState()
}.getOrNull() ?: continue
val result = visaActivationRepository.getActivationRemoteState()
.getOrElse {
uiMessageSender.showErrorDialog(it)
delay(timeMillis = 2000)
runShortPolling()
return@launch
}
when (result) {
is VisaActivationRemoteState.CardWalletSignatureRequired,
@ -156,12 +163,11 @@ internal class OnboardingVisaInProgressModel @Inject constructor(
val authTokens = visaAuthTokenStorage.get(params.scanResponse.card.cardId)
?: error("Auth tokens are not found. This should not happen.")
val newTokens = runCatching {
visaAuthRepository.refreshAccessTokens(authTokens.refreshToken)
}.getOrElse {
uiMessageSender.showErrorDialog(VisaAuthorizationAPIError)
return
}
val newTokens = visaAuthRepository.refreshAccessTokens(authTokens.refreshToken)
.getOrElse {
uiMessageSender.showErrorDialog(it)
return
}
val userWallet = createUserWallet(params.scanResponse, newTokens)
userWalletsListManager.save(userWallet)

View file

@ -6,8 +6,10 @@ import com.tangem.core.analytics.api.AnalyticsEventHandler
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.decompose.ui.UiMessageSender
import com.tangem.core.navigation.share.ShareManager
import com.tangem.core.navigation.url.UrlOpener
import com.tangem.core.ui.utils.showErrorDialog
import com.tangem.domain.visa.model.VisaActivationOrderInfo
import com.tangem.domain.visa.model.VisaActivationRemoteState
import com.tangem.domain.visa.model.VisaCardId
@ -15,6 +17,7 @@ import com.tangem.domain.visa.repository.VisaActivationRepository
import com.tangem.features.onboarding.v2.visa.impl.child.otherwallet.OnboardingVisaOtherWalletComponent
import com.tangem.features.onboarding.v2.visa.impl.child.otherwallet.ui.state.OnboardingVisaOtherWalletUM
import com.tangem.features.onboarding.v2.visa.impl.child.welcome.model.analytics.OnboardingVisaAnalyticsEvent
import com.tangem.features.onboarding.v2.visa.impl.child.welcome.model.analytics.VisaAnalyticsEvent
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.MutableSharedFlow
@ -25,6 +28,7 @@ import javax.inject.Inject
@Stable
@ModelScoped
@Suppress("LongParameterList")
internal class OnboardingVisaOtherWalletModel @Inject constructor(
paramsContainer: ParamsContainer,
visaActivationRepositoryFactory: VisaActivationRepository.Factory,
@ -32,6 +36,7 @@ internal class OnboardingVisaOtherWalletModel @Inject constructor(
private val urlOpener: UrlOpener,
private val shareManager: ShareManager,
private val analyticsEventHandler: AnalyticsEventHandler,
private val uiMessageSender: UiMessageSender,
) : Model() {
private val config = paramsContainer.require<OnboardingVisaOtherWalletComponent.Config>()
@ -50,14 +55,18 @@ internal class OnboardingVisaOtherWalletModel @Inject constructor(
analyticsEventHandler.send(OnboardingVisaAnalyticsEvent.GoToWebsiteOpened)
modelScope.launch {
while (true) {
val result = runCatching {
visaActivationRepository.getActivationRemoteState()
}.getOrNull()
if (result is VisaActivationRemoteState.AwaitingPinCode) {
onDone.emit(result.activationOrderInfo)
break
}
visaActivationRepository.getActivationRemoteState()
.onLeft {
uiMessageSender.showErrorDialog(it)
analyticsEventHandler.send(VisaAnalyticsEvent.ErrorOnboarding(it))
delay(timeMillis = 60_000)
}
.onRight {
if (it is VisaActivationRemoteState.AwaitingPinCode) {
onDone.emit(it.activationOrderInfo)
return@launch
}
}
delay(timeMillis = 2000)
}

View file

@ -10,7 +10,6 @@ import com.tangem.core.decompose.ui.UiMessageSender
import com.tangem.core.ui.extensions.resourceReference
import com.tangem.core.ui.utils.showErrorDialog
import com.tangem.domain.visa.SetVisaPinCodeUseCase
import com.tangem.domain.visa.error.VisaAuthorizationAPIError
import com.tangem.domain.visa.model.VisaCardId
import com.tangem.features.onboarding.v2.impl.R
import com.tangem.features.onboarding.v2.visa.impl.child.pincode.OnboardingVisaPinCodeComponent
@ -95,7 +94,7 @@ internal class OnboardingVisaPinCodeModel @Inject constructor(
activationOrderId = params.activationOrderInfo.orderId,
).onLeft {
loading(false)
uiMessageSender.showErrorDialog(VisaAuthorizationAPIError)
uiMessageSender.showErrorDialog(it)
return@launch
}

View file

@ -1,5 +1,6 @@
package com.tangem.features.onboarding.v2.visa.impl.child.welcome.model
import arrow.core.getOrElse
import com.tangem.common.CompletionResult
import com.tangem.common.extensions.toHexString
import com.tangem.core.analytics.api.AnalyticsEventHandler
@ -7,9 +8,9 @@ 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.decompose.ui.UiMessageSender
import com.tangem.core.error.UniversalError
import com.tangem.core.error.ext.universalError
import com.tangem.core.ui.utils.showErrorDialog
import com.tangem.domain.visa.error.VisaAPIError
import com.tangem.domain.visa.model.VisaCardId
import com.tangem.domain.visa.model.VisaCustomerWalletDataToSignRequest
import com.tangem.domain.visa.repository.VisaActivationRepository
@ -78,13 +79,11 @@ internal class OnboardingVisaWelcomeModel @Inject constructor(
loading(true)
modelScope.launch {
val dataToSignByCardWallet = runCatching {
visaActivationRepository.getCardWalletAcceptanceData(params.dataToSignRequest)
}.getOrElse {
loading(false)
uiMessageSender.showErrorDialog(VisaAPIError)
return@launch
}
val dataToSignByCardWallet = visaActivationRepository.getCardWalletAcceptanceData(params.dataToSignRequest)
.getOrElse {
onError(it)
return@launch
}
val result = tangemSdkManager.activateVisaCard(
mode = VisaCardActivationTaskMode.SignOnly(dataToSignByCardWallet = dataToSignByCardWallet),
@ -93,21 +92,17 @@ internal class OnboardingVisaWelcomeModel @Inject constructor(
val resultData = when (result) {
is CompletionResult.Failure -> {
loading(false)
uiMessageSender.showErrorDialog(result.error.universalError)
analyticsEventsHandler.send(VisaAnalyticsEvent.ErrorOnboarding(result.error.universalError))
onError(result.error.universalError)
return@launch
}
is CompletionResult.Success -> result.data
}
runCatching {
visaActivationRepository.activateCard(resultData.signedActivationData)
}.onFailure {
loading(false)
uiMessageSender.showErrorDialog(VisaAPIError)
return@launch
}
visaActivationRepository.activateCard(resultData.signedActivationData)
.onLeft {
onError(it)
return@launch
}
val request = result.data.signedActivationData.dataToSign.request
val targetAddress = request.activationOrderInfo.customerWalletAddress
@ -132,6 +127,12 @@ internal class OnboardingVisaWelcomeModel @Inject constructor(
}
}
private fun onError(error: UniversalError) {
loading(false)
uiMessageSender.showErrorDialog(error)
analyticsEventsHandler.send(VisaAnalyticsEvent.ErrorOnboarding(error))
}
private fun loading(state: Boolean) {
_uiState.update { it.copy(continueButtonLoading = state) }
}

View file

@ -2,8 +2,8 @@ package com.tangem.features.onramp.component
import com.tangem.core.decompose.factory.ComponentFactory
import com.tangem.core.ui.decompose.ComposableContentComponent
import com.tangem.domain.models.currency.CryptoCurrency
import com.tangem.domain.onramp.model.OnrampSource
import com.tangem.domain.tokens.model.CryptoCurrency
import com.tangem.domain.wallets.models.UserWalletId
interface OnrampComponent : ComposableContentComponent {

View file

@ -0,0 +1,10 @@
package com.tangem.features.onramp.deeplink
import kotlinx.coroutines.CoroutineScope
interface BuyDeepLinkHandler {
interface Factory {
fun create(coroutineScope: CoroutineScope): BuyDeepLinkHandler
}
}

View file

@ -3,10 +3,18 @@ package com.tangem.features.onramp.deeplink
import com.tangem.core.deeplink.DeepLink
import kotlinx.coroutines.CoroutineScope
@Deprecated("Use OnrampDeepLinkHandler")
abstract class OnrampDeepLink : DeepLink() {
override val uri = "tangem://onramp"
interface Factory {
fun create(coroutineScope: CoroutineScope): OnrampDeepLink
}
}
interface OnrampDeepLinkHandler {
interface Factory {
fun create(coroutineScope: CoroutineScope, queryParams: Map<String, String>): OnrampDeepLinkHandler
}
}

View file

@ -2,8 +2,8 @@ package com.tangem.features.onramp.confirmresidency
import com.tangem.core.decompose.factory.ComponentFactory
import com.tangem.core.ui.decompose.ComposableBottomSheetComponent
import com.tangem.domain.models.currency.CryptoCurrency
import com.tangem.domain.onramp.model.OnrampCountry
import com.tangem.domain.tokens.model.CryptoCurrency
import com.tangem.domain.wallets.models.UserWalletId
internal interface ConfirmResidencyComponent : ComposableBottomSheetComponent {

View file

@ -0,0 +1,48 @@
package com.tangem.features.onramp.deeplink
import arrow.core.getOrElse
import com.tangem.core.analytics.api.AnalyticsEventHandler
import com.tangem.domain.tokens.GetCryptoCurrencyUseCase
import com.tangem.domain.tokens.model.analytics.TokenScreenAnalyticsEvent
import com.tangem.domain.wallets.usecase.GetSelectedWalletSyncUseCase
import com.tangem.features.onramp.OnrampFeatureToggles
import dagger.assisted.Assisted
import dagger.assisted.AssistedFactory
import dagger.assisted.AssistedInject
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.launch
import timber.log.Timber
internal class DefaultBuyDeepLinkHandler @AssistedInject constructor(
@Assisted scope: CoroutineScope,
onrampFeatureToggles: OnrampFeatureToggles,
getSelectedWalletSyncUseCase: GetSelectedWalletSyncUseCase,
getCryptoCurrencyUseCase: GetCryptoCurrencyUseCase,
analyticsEventHandler: AnalyticsEventHandler,
) : BuyDeepLinkHandler {
init {
// It is okay here, we are navigating from outside, and there is no other way to getting UserWallet
getSelectedWalletSyncUseCase().fold(
ifLeft = {
Timber.e("Error on getting user wallet: $it")
},
ifRight = { userWallet ->
if (!onrampFeatureToggles.isFeatureEnabled && !userWallet.isMultiCurrency) {
scope.launch {
val cryptoCurrency = getCryptoCurrencyUseCase(userWallet.walletId).getOrElse {
Timber.e("Error on getting cryptoCurrency: $it")
return@launch
}
analyticsEventHandler.send(TokenScreenAnalyticsEvent.Bought(cryptoCurrency.symbol))
}
}
},
)
}
@AssistedFactory
interface Factory : BuyDeepLinkHandler.Factory {
override fun create(coroutineScope: CoroutineScope): DefaultBuyDeepLinkHandler
}
}

View file

@ -0,0 +1,61 @@
package com.tangem.features.onramp.deeplink
import com.tangem.common.routing.AppRoute
import com.tangem.common.routing.AppRouter
import com.tangem.features.onramp.success.OnrampSuccessScreenTrigger
import dagger.assisted.Assisted
import dagger.assisted.AssistedFactory
import dagger.assisted.AssistedInject
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.launch
import timber.log.Timber
internal class DefaultOnrampDeepLinkHandler @AssistedInject constructor(
@Assisted scope: CoroutineScope,
@Assisted queryParams: Map<String, String>,
appRouter: AppRouter,
onrampSuccessScreenTrigger: OnrampSuccessScreenTrigger,
) : OnrampDeepLinkHandler {
init {
val txId = queryParams[TX_ID_KEY]
val result = OnrampRedirectResult.getResult(queryParams[RESULT_KEY])
when {
!txId.isNullOrEmpty() -> {
// finish current onramp flow and show onramp success screen
val replaceOnrampScreens = appRouter.stack
.filterNot { it is AppRoute.Onramp || it is AppRoute.OnrampSuccess }
.toMutableList() + AppRoute.OnrampSuccess(txId)
appRouter.replaceAll(*replaceOnrampScreens.toTypedArray())
}
result != OnrampRedirectResult.Unknown -> {
scope.launch {
onrampSuccessScreenTrigger.triggerOnrampSuccess(result == OnrampRedirectResult.Success)
}
}
else -> {
Timber.e(
"""
Invalid parameters for ONRAMP deeplink
|- Params: $queryParams
""".trimIndent(),
)
}
}
}
@AssistedFactory
interface Factory : OnrampDeepLinkHandler.Factory {
override fun create(
coroutineScope: CoroutineScope,
queryParams: Map<String, String>,
): DefaultOnrampDeepLinkHandler
}
private companion object {
const val TX_ID_KEY = "tx_id"
const val RESULT_KEY = "result"
}
}

View file

@ -1,7 +1,6 @@
package com.tangem.features.onramp.deeplink.di
import com.tangem.features.onramp.deeplink.DefaultOnrampDeepLink
import com.tangem.features.onramp.deeplink.OnrampDeepLink
import com.tangem.features.onramp.deeplink.*
import dagger.Binds
import dagger.Module
import dagger.hilt.InstallIn
@ -15,4 +14,12 @@ internal interface OnrampDeeplinkModule {
@Binds
@Singleton
fun bindFactory(impl: DefaultOnrampDeepLink.Factory): OnrampDeepLink.Factory
@Binds
@Singleton
fun bindOnrampDeepLinkHandlerFactory(impl: DefaultOnrampDeepLinkHandler.Factory): OnrampDeepLinkHandler.Factory
@Binds
@Singleton
fun bindBuyDeepLinkHandler(impl: DefaultBuyDeepLinkHandler.Factory): BuyDeepLinkHandler.Factory
}

View file

@ -12,10 +12,10 @@ import com.tangem.core.ui.components.tokenlist.state.TokensListItemUM
import com.tangem.core.ui.extensions.resourceReference
import com.tangem.domain.appcurrency.GetSelectedAppCurrencyUseCase
import com.tangem.domain.appcurrency.model.AppCurrency
import com.tangem.domain.models.currency.CryptoCurrency
import com.tangem.domain.onramp.GetHotCryptoUseCase
import com.tangem.domain.onramp.model.HotCryptoCurrency
import com.tangem.domain.tokens.GetCryptoCurrencyStatusSyncUseCase
import com.tangem.domain.tokens.model.CryptoCurrency
import com.tangem.domain.tokens.GetSingleCryptoCurrencyStatusUseCase
import com.tangem.features.onramp.hottokens.HotCryptoComponent
import com.tangem.features.onramp.hottokens.converter.HotTokenItemStateConverter
import com.tangem.features.onramp.hottokens.entity.HotCryptoUM
@ -45,7 +45,7 @@ internal class HotCryptoModel @Inject constructor(
paramsContainer: ParamsContainer,
getHotCryptoUseCase: GetHotCryptoUseCase,
getSelectedAppCurrencyUseCase: GetSelectedAppCurrencyUseCase,
private val getCryptoCurrencyStatusSyncUseCase: GetCryptoCurrencyStatusSyncUseCase,
private val getSingleCryptoCurrencyStatusUseCase: GetSingleCryptoCurrencyStatusUseCase,
override val dispatchers: CoroutineDispatcherProvider,
) : Model() {
@ -103,7 +103,10 @@ internal class HotCryptoModel @Inject constructor(
private fun onSuccessAdding(id: CryptoCurrency.ID) {
modelScope.launch {
getCryptoCurrencyStatusSyncUseCase(userWalletId = params.userWalletId, cryptoCurrencyId = id)
getSingleCryptoCurrencyStatusUseCase.invokeMultiWalletSync(
userWalletId = params.userWalletId,
cryptoCurrencyId = id,
)
.onRight {
bottomSheetNavigation.dismiss()
params.onTokenClick(it)

View file

@ -3,7 +3,7 @@ package com.tangem.features.onramp.hottokens.portfolio
import com.tangem.core.decompose.factory.ComponentFactory
import com.tangem.core.ui.components.currency.icon.CurrencyIconState
import com.tangem.core.ui.decompose.ComposableBottomSheetComponent
import com.tangem.domain.tokens.model.CryptoCurrency
import com.tangem.domain.models.currency.CryptoCurrency
import com.tangem.domain.wallets.models.UserWalletId
/**

View file

@ -1,7 +1,7 @@
package com.tangem.features.onramp.hottokens.portfolio.entity
import com.tangem.core.ui.components.currency.icon.CurrencyIconState
import com.tangem.domain.tokens.model.CryptoCurrency
import com.tangem.domain.models.currency.CryptoCurrency
internal sealed class OnrampAddToPortfolioBSConfig {

View file

@ -2,9 +2,9 @@ package com.tangem.features.onramp.main
import com.tangem.core.decompose.factory.ComponentFactory
import com.tangem.core.ui.decompose.ComposableContentComponent
import com.tangem.domain.models.currency.CryptoCurrency
import com.tangem.domain.onramp.model.OnrampProviderWithQuote
import com.tangem.domain.onramp.model.OnrampSource
import com.tangem.domain.tokens.model.CryptoCurrency
import com.tangem.domain.wallets.models.UserWalletId
internal interface OnrampMainComponent : ComposableContentComponent {

View file

@ -8,11 +8,11 @@ import com.tangem.common.ui.amountScreen.models.AmountFieldModel
import com.tangem.common.ui.notifications.NotificationUM
import com.tangem.core.ui.extensions.TextReference
import com.tangem.core.ui.extensions.resourceReference
import com.tangem.domain.models.currency.CryptoCurrency
import com.tangem.domain.onramp.model.OnrampCurrency
import com.tangem.domain.onramp.model.error.OnrampError
import com.tangem.domain.tokens.model.Amount
import com.tangem.domain.tokens.model.AmountType
import com.tangem.domain.tokens.model.CryptoCurrency
import com.tangem.domain.tokens.model.convertToAmount
import com.tangem.features.onramp.impl.R
import com.tangem.features.onramp.main.entity.*

View file

@ -8,13 +8,13 @@ import com.tangem.core.ui.extensions.wrappedList
import com.tangem.core.ui.format.bigdecimal.crypto
import com.tangem.core.ui.format.bigdecimal.fiat
import com.tangem.core.ui.format.bigdecimal.format
import com.tangem.domain.models.currency.CryptoCurrency
import com.tangem.domain.onramp.analytics.OnrampAnalyticsEvent
import com.tangem.domain.onramp.model.OnrampCurrency
import com.tangem.domain.onramp.model.OnrampProviderWithQuote
import com.tangem.domain.onramp.model.OnrampQuote
import com.tangem.domain.onramp.model.error.OnrampError
import com.tangem.domain.tokens.model.AmountType
import com.tangem.domain.tokens.model.CryptoCurrency
import com.tangem.features.onramp.impl.R
import com.tangem.features.onramp.main.entity.*
import com.tangem.features.onramp.providers.entity.SelectProviderResult

View file

@ -2,8 +2,8 @@ package com.tangem.features.onramp.providers
import com.tangem.core.decompose.factory.ComponentFactory
import com.tangem.core.ui.decompose.ComposableBottomSheetComponent
import com.tangem.domain.models.currency.CryptoCurrency
import com.tangem.domain.onramp.model.OnrampPaymentMethod
import com.tangem.domain.tokens.model.CryptoCurrency
import com.tangem.features.onramp.providers.entity.SelectProviderResult
internal interface SelectProviderComponent : ComposableBottomSheetComponent {

View file

@ -2,8 +2,8 @@ package com.tangem.features.onramp.redirect
import com.tangem.core.decompose.factory.ComponentFactory
import com.tangem.core.ui.decompose.ComposableContentComponent
import com.tangem.domain.models.currency.CryptoCurrency
import com.tangem.domain.onramp.model.OnrampProviderWithQuote
import com.tangem.domain.tokens.model.CryptoCurrency
import com.tangem.domain.wallets.models.UserWalletId
internal interface OnrampRedirectComponent : ComposableContentComponent {

View file

@ -1,7 +1,7 @@
package com.tangem.features.onramp.root.entity
import com.tangem.domain.models.currency.CryptoCurrency
import com.tangem.domain.onramp.model.OnrampProviderWithQuote
import com.tangem.domain.tokens.model.CryptoCurrency
import kotlinx.serialization.Serializable
@Serializable

View file

@ -2,7 +2,7 @@ package com.tangem.features.onramp.selectcountry
import com.tangem.core.decompose.factory.ComponentFactory
import com.tangem.core.ui.decompose.ComposableBottomSheetComponent
import com.tangem.domain.tokens.model.CryptoCurrency
import com.tangem.domain.models.currency.CryptoCurrency
import com.tangem.domain.wallets.models.UserWalletId
internal interface SelectCountryComponent : ComposableBottomSheetComponent {

View file

@ -2,7 +2,7 @@ package com.tangem.features.onramp.selectcurrency
import com.tangem.core.decompose.factory.ComponentFactory
import com.tangem.core.ui.decompose.ComposableBottomSheetComponent
import com.tangem.domain.tokens.model.CryptoCurrency
import com.tangem.domain.models.currency.CryptoCurrency
import com.tangem.domain.wallets.models.UserWallet
internal interface SelectCurrencyComponent : ComposableBottomSheetComponent {

View file

@ -2,7 +2,7 @@ package com.tangem.features.onramp.settings
import com.tangem.core.decompose.factory.ComponentFactory
import com.tangem.core.ui.decompose.ComposableContentComponent
import com.tangem.domain.tokens.model.CryptoCurrency
import com.tangem.domain.models.currency.CryptoCurrency
import com.tangem.domain.wallets.models.UserWalletId
internal interface OnrampSettingsComponent : ComposableContentComponent {

View file

@ -12,9 +12,9 @@ import com.tangem.core.ui.extensions.wrappedList
import com.tangem.core.ui.format.bigdecimal.crypto
import com.tangem.core.ui.format.bigdecimal.fiat
import com.tangem.core.ui.format.bigdecimal.format
import com.tangem.domain.models.currency.CryptoCurrency
import com.tangem.domain.onramp.model.OnrampStatus
import com.tangem.domain.onramp.model.cache.OnrampTransaction
import com.tangem.domain.tokens.model.CryptoCurrency
import com.tangem.features.onramp.impl.R
import com.tangem.features.onramp.success.entity.OnrampSuccessComponentUM
import com.tangem.utils.converter.Converter

View file

@ -11,6 +11,7 @@ import com.tangem.core.ui.clipboard.ClipboardManager
import com.tangem.core.ui.extensions.resourceReference
import com.tangem.core.ui.extensions.wrappedList
import com.tangem.core.ui.message.DialogMessage
import com.tangem.domain.models.currency.CryptoCurrency
import com.tangem.domain.onramp.GetOnrampStatusUseCase
import com.tangem.domain.onramp.GetOnrampTransactionUseCase
import com.tangem.domain.onramp.OnrampRemoveTransactionUseCase
@ -19,7 +20,6 @@ import com.tangem.domain.onramp.model.OnrampStatus
import com.tangem.domain.onramp.model.cache.OnrampTransaction
import com.tangem.domain.onramp.model.error.OnrampError
import com.tangem.domain.tokens.GetCryptoCurrencyUseCase
import com.tangem.domain.tokens.model.CryptoCurrency
import com.tangem.domain.tokens.model.analytics.TokenOnrampAnalyticsEvent
import com.tangem.domain.wallets.models.UserWallet
import com.tangem.domain.wallets.usecase.GetUserWalletUseCase
@ -181,7 +181,9 @@ internal class OnrampSuccessComponentModel @Inject constructor(
} else {
resourceReference(R.string.express_error_code, wrappedList(errorCode))
},
onDismissRequest = router::pop,
firstActionBuilder = {
okAction(router::pop)
},
)
messageSender.send(message)

View file

@ -15,8 +15,8 @@ import com.tangem.domain.core.utils.getOrElse
import com.tangem.domain.core.utils.lceContent
import com.tangem.domain.core.utils.lceError
import com.tangem.domain.core.utils.lceLoading
import com.tangem.domain.models.currency.CryptoCurrency
import com.tangem.domain.tokens.GetTokenListUseCase
import com.tangem.domain.tokens.model.CryptoCurrency
import com.tangem.domain.tokens.model.CryptoCurrencyStatus
import com.tangem.domain.tokens.model.TokenList
import com.tangem.domain.wallets.usecase.GetWalletsUseCase

View file

@ -17,4 +17,7 @@ dependencies {
/* Project - Domain */
implementation(projects.domain.wallets.models)
/* Compose */
implementation(deps.compose.runtime)
}

View file

@ -0,0 +1,8 @@
package com.tangem.feature.referral.api.deeplink
interface ReferralDeepLinkHandler {
interface Factory {
fun create(): ReferralDeepLinkHandler
}
}

View file

@ -9,7 +9,7 @@ import com.tangem.datasource.api.common.response.getOrThrow
import com.tangem.datasource.api.tangemTech.TangemTechApi
import com.tangem.datasource.api.tangemTech.models.StartReferralBody
import com.tangem.datasource.local.userwallet.UserWalletsStore
import com.tangem.domain.tokens.model.CryptoCurrency
import com.tangem.domain.models.currency.CryptoCurrency
import com.tangem.domain.wallets.models.UserWalletId
import com.tangem.feature.referral.converters.ReferralConverter
import com.tangem.feature.referral.domain.ReferralRepository

View file

@ -20,6 +20,7 @@ dependencies {
/** Domain modules */
implementation(projects.domain.card)
implementation(projects.domain.models)
implementation(projects.domain.tokens)
implementation(projects.domain.tokens.models)
implementation(projects.domain.wallets)

View file

@ -1,6 +1,6 @@
package com.tangem.feature.referral.domain
import com.tangem.domain.tokens.model.CryptoCurrency
import com.tangem.domain.models.currency.CryptoCurrency
import com.tangem.domain.wallets.models.UserWalletId
import com.tangem.feature.referral.domain.models.ReferralData
import com.tangem.feature.referral.domain.models.TokenData

View file

@ -39,12 +39,14 @@ dependencies {
/** Domain */
implementation(projects.domain.demo)
implementation(projects.domain.wallets)
implementation(projects.domain.legacy)
implementation(projects.domain.wallets.models)
implementation(projects.features.referral.domain)
/** Other libraries */
implementation(deps.compose.shimmer)
implementation(deps.compose.accompanist.systemUiController)
implementation(deps.timber)
/** DI */
implementation(deps.hilt.android)

View file

@ -0,0 +1,37 @@
package com.tangem.feature.referral.deeplink
import com.tangem.common.routing.AppRoute
import com.tangem.common.routing.AppRouter
import com.tangem.domain.common.util.cardTypesResolver
import com.tangem.domain.wallets.usecase.GetSelectedWalletSyncUseCase
import com.tangem.feature.referral.api.deeplink.ReferralDeepLinkHandler
import dagger.assisted.AssistedFactory
import dagger.assisted.AssistedInject
import timber.log.Timber
internal class DefaultReferralDeepLinkHandler @AssistedInject constructor(
appRouter: AppRouter,
getSelectedWalletSyncUseCase: GetSelectedWalletSyncUseCase,
) : ReferralDeepLinkHandler {
init {
// It is okay here, we are navigating from outside, and there is no other way to getting UserWallet
getSelectedWalletSyncUseCase().fold(
ifLeft = {
Timber.e("Error on getting user wallet: $it")
},
ifRight = { userWallet ->
if (userWallet.cardTypesResolver.isTangemWallet()) {
appRouter.push(
AppRoute.ReferralProgram(userWalletId = userWallet.walletId),
)
}
},
)
}
@AssistedFactory
interface Factory : ReferralDeepLinkHandler.Factory {
override fun create(): DefaultReferralDeepLinkHandler
}
}

Some files were not shown because too many files have changed in this diff Show more