Updated on 2026-08-14

This commit is contained in:
Tangem 2025-06-03 12:25:53 +03:00
commit 7c629b68d1
1179 changed files with 24718 additions and 16703 deletions

View file

@ -109,9 +109,11 @@ internal class AskBiometryModel @Inject constructor(
walletsRepository.saveShouldSaveUserWallets(item = true)
settingsRepository.setShouldSaveAccessCodes(value = true)
cardSdkConfigRepository.setAccessCodeRequestPolicy(
isBiometricsRequestPolicy = userWallet.hasAccessCode,
)
if (userWallet is UserWallet.Cold) {
cardSdkConfigRepository.setAccessCodeRequestPolicy(
isBiometricsRequestPolicy = userWallet.hasAccessCode,
)
}
if (_uiState.value.bottomSheetVariant) {
dismissBSFlow.emit(Unit)

View file

@ -15,6 +15,7 @@ dependencies {
/* Project - API */
implementation(projects.features.details.api)
implementation(projects.features.wallet.api)
implementation(projects.features.disclaimer.api)
implementation(projects.features.tester.api)

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

@ -77,11 +77,12 @@ internal class PreviewUserWalletListComponent : UserWalletListComponent {
}
}
private fun getInformation(cardCount: Int): TextReference {
return TextReference.PluralRes(
private fun getInformation(cardCount: Int): UserWalletItemUM.Information.Loaded {
val text = TextReference.PluralRes(
id = R.plurals.card_label_card_count,
count = cardCount,
formatArgs = wrappedList(cardCount),
)
return UserWalletItemUM.Information.Loaded(text)
}
}

View file

@ -17,6 +17,8 @@ import com.tangem.domain.feedback.models.FeedbackEmailType
import com.tangem.domain.redux.LegacyAction
import com.tangem.domain.redux.ReduxStateHolder
import com.tangem.domain.walletconnect.CheckIsWalletConnectAvailableUseCase
import com.tangem.domain.wallets.models.UserWallet
import com.tangem.domain.wallets.models.requireColdWallet
import com.tangem.domain.wallets.usecase.GetSelectedWalletSyncUseCase
import com.tangem.domain.wallets.usecase.GetWalletsUseCase
import com.tangem.features.details.component.DetailsComponent
@ -80,6 +82,7 @@ internal class DetailsModel @Inject constructor(
items = MutableStateFlow(
itemsBuilder.buildAll(
isWalletConnectAvailable = isWalletConnectAvailable,
userWalletId = params.userWalletId,
onSupportClick = ::sendFeedback,
onBuyClick = ::onBuyClick,
),
@ -110,14 +113,17 @@ internal class DetailsModel @Inject constructor(
modelScope.launch {
val userWallets = getWalletsUseCase.invokeSync()
val scanResponse = getSelectedWalletSyncUseCase().getOrNull()?.scanResponse
?: error("Selected wallet is null")
val scanResponse =
getSelectedWalletSyncUseCase().getOrNull()?.requireColdWallet()?.scanResponse // TODO [REDACTED_TASK_KEY]
?: error("Selected wallet is null")
val cardInfo = getCardInfoUseCase(scanResponse).getOrNull() ?: return@launch
val feedbackType = when {
userWallets.all { it.scanResponse.card.isVisa } -> FeedbackEmailType.Visa.DirectUserRequest(cardInfo)
userWallets.all { it.scanResponse.card.isVisa.not() } -> FeedbackEmailType.DirectUserRequest(cardInfo)
userWallets.all { it is UserWallet.Cold && it.scanResponse.card.isVisa } ->
FeedbackEmailType.Visa.DirectUserRequest(cardInfo)
userWallets.all { it !is UserWallet.Cold || it.scanResponse.card.isVisa.not() } ->
FeedbackEmailType.DirectUserRequest(cardInfo)
else -> {
showFeedbackEmailTypeOptionBS(cardInfo)
return@launch
@ -172,7 +178,9 @@ internal class DetailsModel @Inject constructor(
FeedbackEmailType.DirectUserRequest(selectedCardInfo)
} else {
val scanResponse = getWalletsUseCase.invokeSync()
.firstOrNull { it.scanResponse.card.isVisa.not() }?.scanResponse ?: return@launch
.firstOrNull { it is UserWallet.Cold && it.scanResponse.card.isVisa.not() }
?.requireColdWallet()?.scanResponse ?: return@launch
val cardInfo = getCardInfoUseCase(scanResponse).getOrNull() ?: return@launch
FeedbackEmailType.DirectUserRequest(cardInfo)
}
@ -182,7 +190,8 @@ internal class DetailsModel @Inject constructor(
FeedbackEmailType.Visa.DirectUserRequest(selectedCardInfo)
} else {
val scanResponse = getWalletsUseCase.invokeSync()
.firstOrNull { it.scanResponse.card.isVisa }?.scanResponse ?: return@launch
.firstOrNull { it is UserWallet.Cold && it.scanResponse.card.isVisa }
?.requireColdWallet()?.scanResponse ?: return@launch
val cardInfo = getCardInfoUseCase(scanResponse).getOrNull() ?: return@launch
FeedbackEmailType.Visa.DirectUserRequest(cardInfo)
}

View file

@ -1,15 +1,18 @@
package com.tangem.features.details.model
import com.tangem.common.routing.AppRoute
import com.tangem.common.ui.userwallet.state.UserWalletItemUM
import com.tangem.core.decompose.di.ModelScoped
import com.tangem.core.decompose.model.Model
import com.tangem.core.decompose.navigation.Router
import com.tangem.core.decompose.ui.UiMessageSender
import com.tangem.core.ui.extensions.TextReference
import com.tangem.core.ui.extensions.resourceReference
import com.tangem.domain.wallets.usecase.ShouldSaveUserWalletsUseCase
import com.tangem.features.details.entity.UserWalletListUM
import com.tangem.features.details.impl.R
import com.tangem.features.details.utils.UserWalletSaver
import com.tangem.features.details.utils.UserWalletsFetcher
import com.tangem.features.wallet.utils.UserWalletsFetcher
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
import kotlinx.collections.immutable.ImmutableList
import kotlinx.collections.immutable.persistentListOf
@ -21,13 +24,17 @@ import javax.inject.Inject
@ModelScoped
internal class UserWalletListModel @Inject constructor(
userWalletsFetcher: UserWalletsFetcher,
userWalletsFetcherFactory: UserWalletsFetcher.Factory,
shouldSaveUserWalletsUseCase: ShouldSaveUserWalletsUseCase,
private val router: Router,
private val messageSender: UiMessageSender,
private val userWalletSaver: UserWalletSaver,
override val dispatchers: CoroutineDispatcherProvider,
) : Model() {
private val isWalletSavingInProgress: MutableStateFlow<Boolean> = MutableStateFlow(value = false)
private val userWalletsFetcher = userWalletsFetcherFactory
.create(messageSender) { userWalletId -> router.push(AppRoute.WalletSettings(userWalletId)) }
val state: MutableStateFlow<UserWalletListUM> = MutableStateFlow(
value = UserWalletListUM(

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

@ -20,7 +20,7 @@ import com.tangem.core.ui.message.SnackbarMessage
import com.tangem.domain.card.ScanCardProcessor
import com.tangem.domain.models.scan.ScanResponse
import com.tangem.domain.redux.ReduxStateHolder
import com.tangem.domain.wallets.builder.UserWalletBuilder
import com.tangem.domain.wallets.builder.ColdUserWalletBuilder
import com.tangem.domain.wallets.models.SaveWalletError
import com.tangem.domain.wallets.models.UserWallet
import com.tangem.domain.wallets.usecase.SaveWalletUseCase
@ -37,7 +37,7 @@ import kotlin.coroutines.resume
internal class UserWalletSaver @Inject constructor(
private val scanCardProcessor: ScanCardProcessor,
private val saveWalletUseCase: SaveWalletUseCase,
private val userWalletBuilderFactory: UserWalletBuilder.Factory,
private val coldUserWalletBuilderFactory: ColdUserWalletBuilder.Factory,
private val shouldSaveUserWalletsSyncUseCase: ShouldSaveUserWalletsSyncUseCase,
private val reduxStateHolder: ReduxStateHolder,
private val messageSender: UiMessageSender,
@ -112,7 +112,7 @@ internal class UserWalletSaver @Inject constructor(
}
private suspend fun Raise<Error>.createUserWallet(response: ScanResponse): UserWallet {
val userWallet = userWalletBuilderFactory.create(scanResponse = response).build()
val userWallet = coldUserWalletBuilderFactory.create(scanResponse = response).build()
return ensureNotNull(userWallet) { Error.Unknown }
}

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

@ -0,0 +1,8 @@
package com.tangem.features.markets.deeplink
interface MarketsDeepLinkHandler {
interface Factory {
fun create(): MarketsDeepLinkHandler
}
}

View file

@ -0,0 +1,10 @@
package com.tangem.features.markets.deeplink
import kotlinx.coroutines.CoroutineScope
interface MarketsTokenDetailDeepLinkHandler {
interface Factory {
fun create(coroutineScope: CoroutineScope, params: Map<String, String>): MarketsTokenDetailDeepLinkHandler
}
}

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

@ -0,0 +1,23 @@
package com.tangem.features.markets.tokenlist
import androidx.compose.runtime.Composable
import androidx.compose.runtime.Stable
import androidx.compose.runtime.State
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.Dp
import com.tangem.core.decompose.factory.ComponentFactory
import com.tangem.core.ui.decompose.ComposableContentComponent
import com.tangem.features.markets.entry.BottomSheetState
@Stable
interface MarketsTokenListComponent : ComposableContentComponent {
@Composable
fun BottomSheetContent(
bottomSheetState: State<BottomSheetState>,
onHeaderSizeChange: (Dp) -> Unit,
modifier: Modifier,
)
interface Factory : ComponentFactory<Unit, MarketsTokenListComponent>
}

View file

@ -17,6 +17,9 @@ dependencies {
api(projects.features.onramp.api)
implementation(projects.core.navigation)
/* Data */
implementation(projects.data.common)
/* Domain */
implementation(projects.domain.appCurrency)
implementation(projects.domain.appCurrency.models)

View file

@ -0,0 +1,20 @@
package com.tangem.features.markets.deeplink
import com.tangem.common.routing.AppRoute
import com.tangem.common.routing.AppRouter
import dagger.assisted.AssistedFactory
import dagger.assisted.AssistedInject
internal class DefaultMarketsDeepLinkHandler @AssistedInject constructor(
appRouter: AppRouter,
) : MarketsDeepLinkHandler {
init {
appRouter.push(AppRoute.Markets)
}
@AssistedFactory
interface Factory : MarketsDeepLinkHandler.Factory {
override fun create(): DefaultMarketsDeepLinkHandler
}
}

View file

@ -0,0 +1,79 @@
package com.tangem.features.markets.deeplink
import arrow.core.getOrElse
import com.tangem.common.routing.AppRoute
import com.tangem.common.routing.AppRouter
import com.tangem.data.common.currency.getTokenIconUrlFromDefaultHost
import com.tangem.domain.appcurrency.GetSelectedAppCurrencyUseCase
import com.tangem.domain.appcurrency.model.AppCurrency
import com.tangem.domain.markets.GetTokenMarketInfoUseCase
import com.tangem.domain.markets.TokenMarketParams
import com.tangem.domain.models.currency.CryptoCurrency
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 DefaultMarketsTokenDetailDeepLinkHandler @AssistedInject constructor(
@Assisted scope: CoroutineScope,
@Assisted queryParams: Map<String, String>,
appRouter: AppRouter,
getSelectedAppCurrencyUseCase: GetSelectedAppCurrencyUseCase,
getTokenMarketInfoUseCase: GetTokenMarketInfoUseCase,
) : MarketsTokenDetailDeepLinkHandler {
init {
val tokenId = queryParams[TOKEN_ID_KEY]
val rawTokenId = CryptoCurrency.RawID(tokenId.orEmpty())
scope.launch {
val appCurrency = getSelectedAppCurrencyUseCase.invokeSync().getOrElse {
AppCurrency.Default
}
val tokenInfo = getTokenMarketInfoUseCase(
appCurrency = appCurrency,
tokenId = rawTokenId,
tokenSymbol = TOKEN_SYMBOL_KEY,
).getOrElse {
Timber.e("Failed to get market token info")
return@launch
}
appRouter.push(
AppRoute.MarketsTokenDetails(
token = TokenMarketParams(
id = rawTokenId,
name = tokenInfo.name,
symbol = tokenInfo.symbol,
tokenQuotes = TokenMarketParams.Quotes(
currentPrice = tokenInfo.quotes.currentPrice,
h24Percent = tokenInfo.quotes.h24ChangePercent,
weekPercent = tokenInfo.quotes.weekChangePercent,
monthPercent = tokenInfo.quotes.monthChangePercent,
),
imageUrl = getTokenIconUrlFromDefaultHost(rawTokenId),
),
appCurrency = appCurrency,
showPortfolio = true,
analyticsParams = null,
),
)
}
}
@AssistedFactory
interface Factory : MarketsTokenDetailDeepLinkHandler.Factory {
override fun create(
coroutineScope: CoroutineScope,
queryParams: Map<String, String>,
): DefaultMarketsTokenDetailDeepLinkHandler
}
private companion object {
const val TOKEN_ID_KEY = "token_id"
const val TOKEN_SYMBOL_KEY = "token_symbol"
}
}

View file

@ -0,0 +1,26 @@
package com.tangem.features.markets.deeplink.di
import com.tangem.features.markets.deeplink.DefaultMarketsDeepLinkHandler
import com.tangem.features.markets.deeplink.DefaultMarketsTokenDetailDeepLinkHandler
import com.tangem.features.markets.deeplink.MarketsDeepLinkHandler
import com.tangem.features.markets.deeplink.MarketsTokenDetailDeepLinkHandler
import dagger.Binds
import dagger.Module
import dagger.hilt.InstallIn
import dagger.hilt.components.SingletonComponent
import javax.inject.Singleton
@Module
@InstallIn(SingletonComponent::class)
internal interface MarketsDeepLinkModule {
@Binds
@Singleton
fun bindMarketsDeepLinkHandlerFactory(impl: DefaultMarketsDeepLinkHandler.Factory): MarketsDeepLinkHandler.Factory
@Binds
@Singleton
fun bindMarketsTokenDetailDeepLinkHandlerFactory(
impl: DefaultMarketsTokenDetailDeepLinkHandler.Factory,
): MarketsTokenDetailDeepLinkHandler.Factory
}

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

@ -1,117 +0,0 @@
package com.tangem.features.markets.details.impl.ui.components
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.text.SpanStyle
import androidx.compose.ui.text.buildAnnotatedString
import androidx.compose.ui.text.withStyle
import androidx.compose.ui.tooling.preview.Preview
import com.tangem.core.ui.components.TextShimmer
import com.tangem.core.ui.extensions.TextReference
import com.tangem.core.ui.extensions.resolveReference
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
import com.tangem.core.ui.utils.PreviewShimmerContainer
import com.tangem.features.markets.impl.R
import com.tangem.utils.StringsSigns
@Composable
internal fun Description(
description: TextReference,
hasFullDescription: Boolean,
onReadMoreClick: () -> Unit,
modifier: Modifier = Modifier,
) {
if (hasFullDescription) {
val text = buildAnnotatedString {
withStyle(SpanStyle(color = TangemTheme.colors.text.secondary)) {
append(description.resolveReference())
}
withStyle(SpanStyle(color = TangemTheme.colors.text.accent)) {
append(
" " + stringResourceSafe(R.string.common_read_more).replace(
' ',
StringsSigns.NON_BREAKING_SPACE,
),
)
}
}
Text(
modifier = modifier
.clickable(
interactionSource = null,
indication = null,
onClick = onReadMoreClick,
),
text = text,
style = TangemTheme.typography.body2,
)
} else {
Text(
modifier = modifier,
text = description.resolveReference(),
style = TangemTheme.typography.body2,
color = TangemTheme.colors.text.secondary,
)
}
}
@Composable
internal fun DescriptionPlaceholder(modifier: Modifier = Modifier) {
Column(
modifier = modifier,
) {
TextShimmer(
modifier = Modifier.fillMaxWidth(),
style = TangemTheme.typography.body2,
textSizeHeight = true,
)
TextShimmer(
modifier = Modifier.fillMaxWidth(),
style = TangemTheme.typography.body2,
textSizeHeight = true,
)
TextShimmer(
modifier = Modifier.fillMaxWidth(fraction = 0.8f),
style = TangemTheme.typography.body2,
textSizeHeight = true,
)
}
}
@Preview
@Composable
private fun ContentPreview() {
TangemThemePreview {
Description(
description = stringReference(
"XRP (XRP) is a cryptocurrency launched in January 2009, where the first " +
"genesis block was mined on 9th January 2009",
),
hasFullDescription = true,
onReadMoreClick = {},
)
}
}
@Preview
@Composable
private fun PreviewPlaceholder() {
TangemThemePreview {
PreviewShimmerContainer(
actualContent = {
ContentPreview()
},
shimmerContent = {
DescriptionPlaceholder()
},
)
}
}

View file

@ -10,6 +10,8 @@ import androidx.compose.ui.Modifier
import com.tangem.core.ui.res.TangemTheme
import com.tangem.features.markets.details.impl.ui.state.MarketsTokenDetailsUM
import com.tangem.core.ui.components.UnableToLoadData
import com.tangem.core.ui.components.items.DescriptionItem
import com.tangem.core.ui.components.items.DescriptionPlaceholder
internal fun LazyListScope.tokenMarketDetailsBody(
state: MarketsTokenDetailsUM.Body,
@ -69,7 +71,7 @@ private fun LazyListScope.error(state: MarketsTokenDetailsUM.Body.Error) {
private fun LazyListScope.description(description: MarketsTokenDetailsUM.Description) {
item("description") {
Description(
DescriptionItem(
modifier = Modifier.blockPaddings(),
description = description.shortDescription,
hasFullDescription = description.fullDescription != null,

View file

@ -5,20 +5,15 @@ import androidx.compose.runtime.Stable
import androidx.compose.runtime.State
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.Dp
import com.arkivanov.decompose.ExperimentalDecomposeApi
import com.arkivanov.decompose.extensions.compose.subscribeAsState
import com.arkivanov.decompose.router.stack.ChildStack
import com.arkivanov.decompose.router.stack.StackNavigation
import com.arkivanov.decompose.router.stack.childStack
import com.arkivanov.decompose.router.stack.pushNew
import com.arkivanov.decompose.router.stack.popWhile
import com.arkivanov.decompose.value.Value
import com.tangem.core.decompose.context.AppComponentContext
import com.tangem.core.decompose.context.childByContext
import com.tangem.core.decompose.navigation.Router
import com.tangem.domain.appcurrency.model.AppCurrency
import com.tangem.domain.markets.TokenMarket
import com.tangem.domain.markets.toSerializableParam
import com.tangem.features.markets.details.MarketsTokenDetailsComponent
import com.tangem.core.decompose.navigation.inner.InnerRouter
import com.tangem.features.markets.entry.BottomSheetState
import com.tangem.features.markets.entry.MarketsEntryComponent
import com.tangem.features.markets.entry.impl.MarketsEntryChildFactory.Child
@ -35,7 +30,12 @@ internal class DefaultMarketsEntryComponent @AssistedInject constructor(
private val stackNavigation = StackNavigation<Child>()
val stack: Value<ChildStack<Child, Any>> = childStack(
private val innerRouter = InnerRouter<Child>(
stackNavigation = stackNavigation,
popCallback = { onChildBack() },
)
private val stack: Value<ChildStack<Child, Any>> = childStack(
key = "main",
source = stackNavigation,
serializer = Child.serializer(),
@ -46,9 +46,8 @@ internal class DefaultMarketsEntryComponent @AssistedInject constructor(
child = configuration,
appComponentContext = childByContext(
componentContext = factoryContext,
router = createRouter(configuration),
router = innerRouter,
),
onTokenSelected = ::marketsListTokenSelected,
)
},
)
@ -68,32 +67,9 @@ internal class DefaultMarketsEntryComponent @AssistedInject constructor(
)
}
@OptIn(ExperimentalDecomposeApi::class)
private fun marketsListTokenSelected(token: TokenMarket, appCurrency: AppCurrency) {
stackNavigation.pushNew(
configuration = Child.TokenDetails(
params = MarketsTokenDetailsComponent.Params(
token = token.toSerializableParam(),
appCurrency = appCurrency,
showPortfolio = true,
analyticsParams = MarketsTokenDetailsComponent.AnalyticsParams(
blockchain = null,
source = "Market",
),
),
),
)
}
private fun AppComponentContext.createRouter(child: Child): Router {
return when (child) {
is Child.TokenDetails -> {
MarketTokenDetailsRouter(
contextRouter = this.router,
stackNavigation = stackNavigation,
)
}
else -> this.router
private fun onChildBack() {
if (stack.value.active.configuration !is Child.TokenList) {
stackNavigation.popWhile { it != Child.TokenList }
}
}

View file

@ -1,25 +0,0 @@
package com.tangem.features.markets.entry.impl
import com.arkivanov.decompose.router.stack.StackNavigation
import com.arkivanov.decompose.router.stack.popWhile
import com.tangem.core.decompose.navigation.Route
import com.tangem.core.decompose.navigation.Router
import kotlin.reflect.KClass
internal class MarketTokenDetailsRouter(
private val contextRouter: Router,
private val stackNavigation: StackNavigation<MarketsEntryChildFactory.Child>,
) : Router by contextRouter {
override fun pop(onComplete: (isSuccess: Boolean) -> Unit) {
stackNavigation.popWhile({ it != MarketsEntryChildFactory.Child.TokenList }, onComplete)
}
override fun popTo(route: Route, onComplete: (isSuccess: Boolean) -> Unit) {
/** Not allowed */
}
override fun popTo(routeClass: KClass<out Route>, onComplete: (isSuccess: Boolean) -> Unit) {
/** Not allowed */
}
}

View file

@ -2,11 +2,9 @@ package com.tangem.features.markets.entry.impl
import androidx.compose.runtime.Immutable
import com.tangem.core.decompose.context.AppComponentContext
import com.tangem.domain.appcurrency.model.AppCurrency
import com.tangem.domain.markets.TokenMarket
import com.tangem.core.decompose.navigation.Route
import com.tangem.features.markets.details.MarketsTokenDetailsComponent
import com.tangem.features.markets.entry.impl.MarketsEntryChildFactory.Child
import com.tangem.features.markets.tokenlist.api.MarketsTokenListComponent
import com.tangem.features.markets.tokenlist.MarketsTokenListComponent
import kotlinx.serialization.Serializable
import javax.inject.Inject
@ -17,7 +15,7 @@ internal class MarketsEntryChildFactory @Inject constructor(
@Serializable
@Immutable
sealed interface Child {
sealed interface Child : Route {
@Serializable
@Immutable
@ -28,11 +26,7 @@ internal class MarketsEntryChildFactory @Inject constructor(
data class TokenDetails(val params: MarketsTokenDetailsComponent.Params) : Child
}
fun createChild(
child: Child,
appComponentContext: AppComponentContext,
onTokenSelected: (TokenMarket, AppCurrency) -> Unit,
): Any {
fun createChild(child: Child, appComponentContext: AppComponentContext): Any {
return when (child) {
is Child.TokenDetails -> {
tokenDetailsComponentFactory.create(
@ -43,7 +37,7 @@ internal class MarketsEntryChildFactory @Inject constructor(
is Child.TokenList -> {
tokenListComponentFactory.create(
context = appComponentContext,
onTokenSelected = onTokenSelected,
params = Unit,
)
}
}

View file

@ -20,7 +20,7 @@ import com.tangem.core.ui.res.TangemTheme
import com.tangem.features.markets.details.MarketsTokenDetailsComponent
import com.tangem.features.markets.entry.BottomSheetState
import com.tangem.features.markets.entry.impl.MarketsEntryChildFactory
import com.tangem.features.markets.tokenlist.api.MarketsTokenListComponent
import com.tangem.features.markets.tokenlist.MarketsTokenListComponent
@Composable
internal fun EntryBottomSheetContent(

View file

@ -7,11 +7,11 @@ import com.tangem.domain.balancehiding.GetBalanceHidingSettingsUseCase
import com.tangem.domain.core.lce.Lce
import com.tangem.domain.markets.FilterAvailableNetworksForWalletUseCase
import com.tangem.domain.markets.TokenMarketInfo
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

@ -11,6 +11,7 @@ import com.tangem.domain.models.ArtworkModel
import com.tangem.domain.tokens.model.TotalFiatBalance
import com.tangem.domain.wallets.models.UserWallet
import com.tangem.domain.wallets.models.UserWalletId
import com.tangem.domain.wallets.models.isMultiCurrency
import com.tangem.features.markets.portfolio.impl.loader.PortfolioData
import com.tangem.features.markets.portfolio.impl.ui.state.AddToPortfolioBSContentUM
import com.tangem.features.markets.portfolio.impl.ui.state.WalletSelectorBSContentUM

View file

@ -21,9 +21,11 @@ import com.tangem.domain.managetokens.model.CurrencyUnsupportedState
import com.tangem.domain.markets.SaveMarketTokensUseCase
import com.tangem.domain.markets.TokenMarketInfo
import com.tangem.domain.models.ArtworkModel
import com.tangem.domain.tokens.model.CryptoCurrency
import com.tangem.domain.models.currency.CryptoCurrency
import com.tangem.domain.wallets.models.UserWallet
import com.tangem.domain.wallets.models.UserWalletId
import com.tangem.domain.wallets.models.isMultiCurrency
import com.tangem.domain.wallets.models.requireColdWallet
import com.tangem.domain.wallets.usecase.GetCardImageUseCase
import com.tangem.domain.wallets.usecase.GetSelectedWalletUseCase
import com.tangem.features.markets.impl.R
@ -195,6 +197,7 @@ internal class MarketsPortfolioModel @Inject constructor(
modelScope.launch {
loadArtworksMutex.withLock {
wallets.forEach { wallet ->
wallet.requireColdWallet() // TODO [REDACTED_TASK_KEY]
if (!loadedArtworks.containsKey(wallet.walletId)) {
val artwork = getCardImageUseCase(
cardId = wallet.cardId,

View file

@ -6,6 +6,7 @@ import com.tangem.domain.models.ArtworkModel
import com.tangem.domain.tokens.model.CryptoCurrencyStatus
import com.tangem.domain.wallets.models.UserWallet
import com.tangem.domain.wallets.models.UserWalletId
import com.tangem.domain.wallets.models.isMultiCurrency
import com.tangem.features.markets.portfolio.impl.loader.PortfolioData
import com.tangem.features.markets.portfolio.impl.ui.state.MyPortfolioUM
import com.tangem.features.markets.portfolio.impl.ui.state.MyPortfolioUM.Tokens.AddButtonState

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
@ -59,7 +59,8 @@ internal class TokenActionsHandler @AssistedInject constructor(
cryptoCurrencyData = cryptoCurrencyData,
),
)
if (handleDemoMode(action, cryptoCurrencyData.userWallet)) return
val userWallet = cryptoCurrencyData.userWallet
if (userWallet is UserWallet.Cold && handleDemoMode(action, userWallet)) return
when (action) {
TokenActionsBSContentUM.Action.Buy -> onBuyClick(cryptoCurrencyData)
@ -72,7 +73,7 @@ internal class TokenActionsHandler @AssistedInject constructor(
}
}
private fun handleDemoMode(action: TokenActionsBSContentUM.Action, userWallet: UserWallet): Boolean {
private fun handleDemoMode(action: TokenActionsBSContentUM.Action, userWallet: UserWallet.Cold): Boolean {
val demoCard = isDemoCardUseCase.invoke(userWallet.cardId)
val needShowDemoWarning = demoCard && disabledActionsInDemoMode.contains(action)

View file

@ -4,6 +4,7 @@ import androidx.compose.ui.tooling.preview.PreviewParameterProvider
import com.tangem.common.ui.userwallet.state.UserWalletItemUM
import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfig
import com.tangem.core.ui.components.rows.model.BlockchainRowUM
import com.tangem.core.ui.extensions.TextReference
import com.tangem.core.ui.extensions.stringReference
import com.tangem.domain.wallets.models.UserWalletId
import com.tangem.features.markets.impl.R
@ -25,7 +26,7 @@ internal class PreviewAddToPortfolioBSContentProvider : PreviewParameterProvider
val userWallet = UserWalletItemUM(
id = UserWalletId("1"),
name = stringReference("Wallet 1"),
information = stringReference("3 cards"),
information = UserWalletItemUM.Information.Loaded(TextReference.Str("3 cards")),
balance = UserWalletItemUM.Balance.Loading,
isEnabled = true,
endIcon = UserWalletItemUM.EndIcon.Arrow,

View file

@ -1,29 +0,0 @@
package com.tangem.features.markets.tokenlist.api
import androidx.compose.runtime.Composable
import androidx.compose.runtime.Stable
import androidx.compose.runtime.State
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.Dp
import com.tangem.core.decompose.context.AppComponentContext
import com.tangem.domain.appcurrency.model.AppCurrency
import com.tangem.domain.markets.TokenMarket
import com.tangem.features.markets.entry.BottomSheetState
@Stable
interface MarketsTokenListComponent {
@Composable
fun BottomSheetContent(
bottomSheetState: State<BottomSheetState>,
onHeaderSizeChange: (Dp) -> Unit,
modifier: Modifier,
)
interface Factory {
fun create(
context: AppComponentContext,
onTokenSelected: (TokenMarket, AppCurrency) -> Unit,
): MarketsTokenListComponent
}
}

View file

@ -1,34 +1,58 @@
package com.tangem.features.markets.tokenlist.impl
import androidx.compose.runtime.*
import androidx.compose.foundation.layout.imePadding
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.statusBarsPadding
import androidx.compose.material3.Scaffold
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.State
import androidx.compose.runtime.getValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.Dp
import androidx.lifecycle.compose.LifecycleStartEffect
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import com.tangem.common.routing.AppRoute
import com.tangem.common.routing.AppRoute.MarketsTokenDetails.AnalyticsParams
import com.tangem.core.decompose.context.AppComponentContext
import com.tangem.core.decompose.model.getOrCreateModel
import com.tangem.domain.appcurrency.model.AppCurrency
import com.tangem.domain.markets.TokenMarket
import com.tangem.core.ui.res.TangemTheme
import com.tangem.core.ui.utils.WindowInsetsZero
import com.tangem.domain.markets.toSerializableParam
import com.tangem.features.markets.entry.BottomSheetState
import com.tangem.features.markets.tokenlist.api.MarketsTokenListComponent
import com.tangem.features.markets.tokenlist.MarketsTokenListComponent
import com.tangem.features.markets.tokenlist.impl.model.MarketsListModel
import com.tangem.features.markets.tokenlist.impl.ui.MarketsList
import com.tangem.features.markets.tokenlist.impl.ui.MarketsListWithBack
import dagger.assisted.Assisted
import dagger.assisted.AssistedFactory
import dagger.assisted.AssistedInject
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach
@Suppress("UnusedPrivateMember")
class DefaultMarketsTokenListComponent @AssistedInject constructor(
@Assisted appComponentContext: AppComponentContext,
@Assisted private val onTokenSelected: (TokenMarket, AppCurrency) -> Unit,
@Assisted params: Unit,
) : AppComponentContext by appComponentContext, MarketsTokenListComponent {
private val model: MarketsListModel = getOrCreateModel()
init {
model.tokenSelected
.onEach { onTokenSelected(it.first, it.second) }
.onEach { (token, appCurrency) ->
router.push(
AppRoute.MarketsTokenDetails(
token = token.toSerializableParam(),
appCurrency = appCurrency,
showPortfolio = true,
analyticsParams = AnalyticsParams(
blockchain = null,
source = "Market",
),
),
)
}
.launchIn(componentScope)
}
@ -60,11 +84,35 @@ class DefaultMarketsTokenListComponent @AssistedInject constructor(
)
}
@Composable
override fun Content(modifier: Modifier) {
LifecycleStartEffect(Unit) {
model.isVisibleOnScreen.value = true
onStopOrDispose {
model.isVisibleOnScreen.value = false
}
}
val state by model.state.collectAsStateWithLifecycle()
Scaffold(
contentWindowInsets = WindowInsetsZero,
containerColor = TangemTheme.colors.background.primary,
) {
MarketsListWithBack(
modifier = Modifier
.statusBarsPadding()
.imePadding()
.padding(it),
state = state,
bottomSheetState = BottomSheetState.EXPANDED,
onBackClick = router::pop,
)
}
}
@AssistedFactory
interface Factory : MarketsTokenListComponent.Factory {
override fun create(
context: AppComponentContext,
onTokenSelected: (TokenMarket, AppCurrency) -> Unit,
): DefaultMarketsTokenListComponent
override fun create(context: AppComponentContext, params: Unit): DefaultMarketsTokenListComponent
}
}

View file

@ -1,6 +1,6 @@
package com.tangem.features.markets.tokenlist.impl.di
import com.tangem.features.markets.tokenlist.api.MarketsTokenListComponent
import com.tangem.features.markets.tokenlist.MarketsTokenListComponent
import com.tangem.features.markets.tokenlist.impl.DefaultMarketsTokenListComponent
import dagger.Binds
import dagger.Module

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

@ -3,22 +3,29 @@ package com.tangem.features.markets.tokenlist.impl.ui
import android.content.res.Configuration
import androidx.activity.compose.BackHandler
import androidx.compose.animation.AnimatedVisibility
import androidx.compose.foundation.clickable
import androidx.compose.foundation.interaction.MutableInteractionSource
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.lazy.rememberLazyListState
import androidx.compose.material3.Icon
import androidx.compose.material3.Text
import androidx.compose.material3.ripple
import androidx.compose.runtime.*
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.drawBehind
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.graphics.vector.rememberVectorPainter
import androidx.compose.ui.layout.onGloballyPositioned
import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.platform.LocalFocusManager
import androidx.compose.ui.platform.LocalSoftwareKeyboardController
import androidx.compose.ui.res.vectorResource
import androidx.compose.ui.text.buildAnnotatedString
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp
import androidx.compose.ui.text.buildAnnotatedString
import com.tangem.core.ui.components.Keyboard
import com.tangem.core.ui.components.SpacerH12
import com.tangem.core.ui.components.SpacerH8
@ -38,7 +45,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
@ -62,26 +69,8 @@ internal fun MarketsList(
bottomSheetState: BottomSheetState,
modifier: Modifier = Modifier,
) {
Content(
modifier = modifier,
state = state,
onHeaderSizeChange = onHeaderSizeChange,
)
MarketsListSortByBottomSheet(config = state.sortByBottomSheet)
KeyboardEvents(
isSortByBottomSheetShown = state.sortByBottomSheet.isShown,
bottomSheetState = bottomSheetState,
)
}
@Suppress("LongMethod")
@Composable
private fun Content(state: MarketsListUM, onHeaderSizeChange: (Dp) -> Unit, modifier: Modifier = Modifier) {
val density = LocalDensity.current
val background = LocalMainBottomSheetColor.current.value
val strokeColor = TangemTheme.colors.stroke.primary
val scrolledState = remember { mutableStateOf(false) }
Column(
modifier = modifier
.fillMaxSize()
@ -106,85 +95,148 @@ private fun Content(state: MarketsListUM, onHeaderSizeChange: (Dp) -> Unit, modi
.padding(bottom = 4.dp),
state = state.searchBar,
)
Column(Modifier.padding(horizontal = TangemTheme.dimens.size16)) {
AnimatedVisibility(
visible = scrolledState.value.not(),
) {
Column {
SpacerH8()
Title(isInSearchMode = state.isInSearchMode)
SpacerH12()
}
}
Content(state = state)
}
MarketsListSortByBottomSheet(config = state.sortByBottomSheet)
KeyboardEvents(
isSortByBottomSheetShown = state.sortByBottomSheet.isShown,
bottomSheetState = bottomSheetState,
)
}
@Composable
internal fun MarketsListWithBack(
state: MarketsListUM,
bottomSheetState: BottomSheetState,
onBackClick: () -> Unit,
modifier: Modifier = Modifier,
) {
val background = LocalMainBottomSheetColor.current.value
Column(
modifier = modifier
.fillMaxSize()
.imePadding()
.drawBehind { drawRect(background) },
) {
Row(
verticalAlignment = Alignment.CenterVertically,
) {
Icon(
painter = rememberVectorPainter(
ImageVector.vectorResource(R.drawable.ic_close_24),
),
contentDescription = null,
tint = TangemTheme.colors.icon.primary1,
modifier = Modifier
.padding(16.dp)
.clickable(
interactionSource = remember { MutableInteractionSource() },
indication = ripple(bounded = false),
onClick = onBackClick,
),
)
SearchBar(
modifier = Modifier
.drawBehind { drawRect(background) }
.padding(
end = 16.dp,
),
state = state.searchBar,
)
}
Content(state = state)
}
MarketsListSortByBottomSheet(config = state.sortByBottomSheet)
KeyboardEvents(
isSortByBottomSheetShown = state.sortByBottomSheet.isShown,
bottomSheetState = bottomSheetState,
)
}
@Suppress("LongMethod")
@Composable
private fun ColumnScope.Content(state: MarketsListUM, modifier: Modifier = Modifier) {
val strokeColor = TangemTheme.colors.stroke.primary
val scrolledState = remember { mutableStateOf(false) }
Column(modifier.padding(horizontal = TangemTheme.dimens.size16)) {
AnimatedVisibility(
visible = scrolledState.value.not(),
) {
Column {
AnimatedVisibility(state.isInSearchMode.not()) {
Options(
modifier = Modifier.padding(bottom = TangemTheme.dimens.spacing12),
sortByTypeUM = state.selectedSortBy,
trendInterval = state.selectedInterval,
onIntervalClick = state.onIntervalClick,
onSortByClick = state.onSortByButtonClick,
)
}
AnimatedVisibility(
state.isInSearchMode.not() &&
state.stakingNotificationMaxApy != null &&
state.selectedSortBy != SortByTypeUM.Staking,
) {
val showMore = stringResourceSafe(R.string.common_show_more)
val description = stringResourceSafe(
R.string.markets_staking_banner_description_placeholder,
showMore,
)
val clickableDescription = buildAnnotatedString {
append(description.substringBefore(showMore))
pushStringAnnotation(SHOW_MORE_KEY, "")
appendColored(showMore, TangemTheme.colors.text.accent)
pop()
}
StakingInMarketsPromoNotification(
config = NotificationConfig(
iconResId = R.drawable.img_staking_in_market_notification,
title = resourceReference(
R.string.markets_staking_banner_title,
wrappedList(state.stakingNotificationMaxApy.format { percent() }),
),
subtitle = annotatedReference(clickableDescription),
onClick = state.onStakingNotificationClick,
onCloseClick = state.onStakingNotificationCloseClick,
),
modifier = Modifier.padding(bottom = TangemTheme.dimens.spacing12),
)
}
SpacerH8()
Title(isInSearchMode = state.isInSearchMode)
SpacerH12()
}
}
Column {
AnimatedVisibility(state.isInSearchMode.not()) {
Options(
modifier = Modifier.padding(bottom = TangemTheme.dimens.spacing12),
sortByTypeUM = state.selectedSortBy,
trendInterval = state.selectedInterval,
onIntervalClick = state.onIntervalClick,
onSortByClick = state.onSortByButtonClick,
)
}
AnimatedVisibility(
state.isInSearchMode.not() &&
state.stakingNotificationMaxApy != null &&
state.selectedSortBy != SortByTypeUM.Staking,
) {
val showMore = stringResourceSafe(R.string.common_show_more)
val description = stringResourceSafe(
R.string.markets_staking_banner_description_placeholder,
showMore,
)
val clickableDescription = buildAnnotatedString {
append(description.substringBefore(showMore))
pushStringAnnotation(SHOW_MORE_KEY, "")
appendColored(showMore, TangemTheme.colors.text.accent)
pop()
}
StakingInMarketsPromoNotification(
config = NotificationConfig(
iconResId = R.drawable.img_staking_in_market_notification,
title = resourceReference(
R.string.markets_staking_banner_title,
wrappedList(state.stakingNotificationMaxApy.format { percent() }),
),
subtitle = annotatedReference(clickableDescription),
onClick = state.onStakingNotificationClick,
onCloseClick = state.onStakingNotificationCloseClick,
),
modifier = Modifier.padding(bottom = TangemTheme.dimens.spacing12),
)
}
}
val strokeWidth = TangemTheme.dimens.size0_5
Box(
Modifier
.fillMaxWidth()
.height(strokeWidth)
.drawBehind {
// draw horizontal line
if (scrolledState.value) {
drawLine(
color = strokeColor,
start = Offset(0f, size.height),
end = Offset(size.width, size.height),
strokeWidth = strokeWidth.toPx(),
)
}
},
)
ItemsList(
scrolledState = scrolledState,
isInSearchMode = state.isInSearchMode,
state = state.list,
)
}
val strokeWidth = TangemTheme.dimens.size0_5
Box(
Modifier
.fillMaxWidth()
.height(strokeWidth)
.drawBehind {
// draw horizontal line
if (scrolledState.value) {
drawLine(
color = strokeColor,
start = Offset(0f, size.height),
end = Offset(size.width, size.height),
strokeWidth = strokeWidth.toPx(),
)
}
},
)
ItemsList(
scrolledState = scrolledState,
isInSearchMode = state.isInSearchMode,
state = state.list,
)
}
@Composable

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

@ -4,4 +4,5 @@ interface NFTFeatureToggles {
val isNFTEnabled: Boolean
val isNFTEVMEnabled: Boolean
val isNFTSolanaEnabled: Boolean
val isNFTMediaContentEnabled: Boolean
}

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

@ -13,4 +13,7 @@ internal class DefaultNFTFeatureToggles(
override val isNFTSolanaEnabled: Boolean
get() = featureTogglesManager.isFeatureEnabled(name = "NFT_SOLANA_ENABLED")
override val isNFTMediaContentEnabled: Boolean
get() = featureTogglesManager.isFeatureEnabled(name = "NFT_MEDIA_CONTENT_ENABLED")
}

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

@ -10,7 +10,6 @@ import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.tooling.preview.Preview
import com.tangem.core.ui.components.PrimaryButton
@ -40,14 +39,14 @@ internal fun NFTCollectionsEmpty(state: NFTCollectionsUM.Empty, modifier: Modifi
)
Text(
modifier = Modifier.padding(top = TangemTheme.dimens.spacing24),
text = stringResource(R.string.nft_collections_empty_title),
text = stringResourceSafe(R.string.nft_collections_empty_title),
style = TangemTheme.typography.h3,
color = TangemTheme.colors.text.primary1,
textAlign = TextAlign.Center,
)
Text(
modifier = Modifier.padding(top = TangemTheme.dimens.spacing8),
text = stringResource(R.string.nft_collections_empty_description),
text = stringResourceSafe(R.string.nft_collections_empty_description),
style = TangemTheme.typography.body1,
color = TangemTheme.colors.text.secondary,
textAlign = TextAlign.Center,
@ -55,7 +54,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

@ -118,7 +118,7 @@ internal class OnboardingEntryModel @Inject constructor(
}
}
private fun onMultiWalletOnboardingDone(userWallet: UserWallet) {
private fun onMultiWalletOnboardingDone(userWallet: UserWallet.Cold) {
when {
params.mode == Mode.AddBackupWallet1 -> {
stackNavigation.replaceAll(

View file

@ -24,7 +24,7 @@ sealed class OnboardingRoute : Route {
val scanResponse: ScanResponse,
val withSeedPhraseFlow: Boolean,
val mode: OnboardingMultiWalletComponent.Mode,
val onDone: (UserWallet) -> Unit,
val onDone: (UserWallet.Cold) -> Unit,
) : OnboardingRoute()
data class Visa(

View file

@ -14,7 +14,7 @@ interface OnboardingMultiWalletComponent : ComposableContentComponent, InnerNavi
val scanResponse: ScanResponse,
val withSeedPhraseFlow: Boolean,
val mode: Mode,
val onDone: (UserWallet) -> Unit,
val onDone: (UserWallet.Cold) -> Unit,
)
enum class Mode {

View file

@ -8,7 +8,7 @@ import com.tangem.core.decompose.model.ParamsContainer
import com.tangem.domain.card.repository.CardRepository
import com.tangem.domain.common.TapWorkarounds.canSkipBackup
import com.tangem.domain.models.scan.ScanResponse
import com.tangem.domain.wallets.builder.UserWalletBuilder
import com.tangem.domain.wallets.builder.ColdUserWalletBuilder
import com.tangem.domain.wallets.models.UserWallet
import com.tangem.domain.wallets.usecase.SaveWalletUseCase
import com.tangem.features.onboarding.v2.common.analytics.OnboardingEvent
@ -24,7 +24,7 @@ import javax.inject.Inject
internal class Wallet1ChooseOptionModel @Inject constructor(
override val dispatchers: CoroutineDispatcherProvider,
paramsContainer: ParamsContainer,
private val userWalletBuilderFactory: UserWalletBuilder.Factory,
private val coldUserWalletBuilderFactory: ColdUserWalletBuilder.Factory,
private val cardRepository: CardRepository,
private val saveWalletUseCase: SaveWalletUseCase,
private val analyticsHandler: AnalyticsEventHandler,
@ -64,9 +64,9 @@ internal class Wallet1ChooseOptionModel @Inject constructor(
}
}
private suspend fun createUserWallet(scanResponse: ScanResponse): UserWallet {
private fun createUserWallet(scanResponse: ScanResponse): UserWallet.Cold {
return requireNotNull(
value = userWalletBuilderFactory.create(scanResponse = scanResponse).build(),
value = coldUserWalletBuilderFactory.create(scanResponse = scanResponse).build(),
lazyMessage = { "User wallet not created" },
)
}

View file

@ -13,7 +13,7 @@ import com.tangem.domain.feedback.GetCardInfoUseCase
import com.tangem.domain.feedback.SendFeedbackEmailUseCase
import com.tangem.domain.feedback.models.FeedbackEmailType
import com.tangem.domain.models.scan.ScanResponse
import com.tangem.domain.wallets.builder.UserWalletBuilder
import com.tangem.domain.wallets.builder.ColdUserWalletBuilder
import com.tangem.domain.wallets.models.UserWallet
import com.tangem.domain.wallets.usecase.SaveWalletUseCase
import com.tangem.features.onboarding.v2.common.analytics.OnboardingEvent
@ -42,7 +42,7 @@ internal class MultiWalletCreateWalletModel @Inject constructor(
private val getCardInfoUseCase: GetCardInfoUseCase,
private val cardRepository: CardRepository,
private val analyticsHandler: AnalyticsEventHandler,
private val userWalletBuilderFactory: UserWalletBuilder.Factory,
private val coldUserWalletBuilderFactory: ColdUserWalletBuilder.Factory,
private val saveWalletUseCase: SaveWalletUseCase,
) : Model() {
@ -143,9 +143,9 @@ internal class MultiWalletCreateWalletModel @Inject constructor(
}
}
private suspend fun createUserWallet(scanResponse: ScanResponse): UserWallet {
private suspend fun createUserWallet(scanResponse: ScanResponse): UserWallet.Cold {
return requireNotNull(
value = userWalletBuilderFactory.create(scanResponse = scanResponse).build(),
value = coldUserWalletBuilderFactory.create(scanResponse = scanResponse).build(),
lazyMessage = { "User wallet not created" },
)
}

View file

@ -16,9 +16,11 @@ import com.tangem.domain.models.scan.CardDTO
import com.tangem.domain.models.scan.ScanResponse
import com.tangem.domain.models.scan.isRing
import com.tangem.domain.onboarding.repository.OnboardingRepository
import com.tangem.domain.wallets.builder.UserWalletBuilder
import com.tangem.domain.wallets.builder.ColdUserWalletBuilder
import com.tangem.domain.wallets.legacy.UserWalletsListManager
import com.tangem.domain.wallets.models.UserWallet
import com.tangem.domain.wallets.models.copy
import com.tangem.domain.wallets.models.requireColdWallet
import com.tangem.domain.wallets.repository.WalletsRepository
import com.tangem.features.onboarding.v2.common.ui.CantLeaveBackupDialog
import com.tangem.features.onboarding.v2.impl.R
@ -48,7 +50,7 @@ internal class MultiWalletFinalizeModel @Inject constructor(
private val tangemSdkManager: TangemSdkManager,
private val getCardInfoUseCase: GetCardInfoUseCase,
private val sendFeedbackEmailUseCase: SendFeedbackEmailUseCase,
private val userWalletBuilderFactory: UserWalletBuilder.Factory,
private val coldUserWalletBuilderFactory: ColdUserWalletBuilder.Factory,
private val userWalletsListManager: UserWalletsListManager,
private val cardRepository: CardRepository,
private val onboardingRepository: OnboardingRepository,
@ -240,13 +242,16 @@ internal class MultiWalletFinalizeModel @Inject constructor(
}
OnboardingMultiWalletComponent.Mode.AddBackup -> {
val userWallet = userWalletsListManager.userWallets.first()
.firstOrNull { it.scanResponse.primaryCard?.cardId == scanResponse.primaryCard?.cardId }
.firstOrNull {
it is UserWallet.Cold &&
it.scanResponse.primaryCard?.cardId == scanResponse.primaryCard?.cardId
}
?: userWalletCreated
userWalletsListManager.update(
userWalletId = userWallet.walletId,
update = { wallet ->
wallet.copy(
wallet.requireColdWallet().copy(
scanResponse = scanResponse.updateScanResponseAfterBackup(),
)
},
@ -254,7 +259,7 @@ internal class MultiWalletFinalizeModel @Inject constructor(
userWallet
}
}
}.requireColdWallet()
if (hasRing) {
walletsRepository.setHasWalletsWithRing(userWallet.walletId)
@ -279,9 +284,9 @@ internal class MultiWalletFinalizeModel @Inject constructor(
}
}
private suspend fun createUserWallet(scanResponse: ScanResponse): UserWallet {
private fun createUserWallet(scanResponse: ScanResponse): UserWallet.Cold {
return requireNotNull(
value = userWalletBuilderFactory.create(scanResponse = scanResponse)
value = coldUserWalletBuilderFactory.create(scanResponse = scanResponse)
.backupCardsIds(backupCardIds.toSet())
.hasBackupError(walletHasBackupError)
.build(),

View file

@ -9,7 +9,7 @@ data class OnboardingMultiWalletState(
val isThreeCards: Boolean,
val currentScanResponse: ScanResponse,
val startFromFinalize: FinalizeStage?,
val resultUserWallet: UserWallet?,
val resultUserWallet: UserWallet.Cold?,
) {
enum class FinalizeStage {
ScanPrimaryCard, ScanBackupFirstCard, ScanBackupSecondCard

View file

@ -8,7 +8,7 @@ import com.tangem.core.decompose.model.ParamsContainer
import com.tangem.core.ui.components.artwork.ArtworkUM
import com.tangem.domain.card.repository.CardRepository
import com.tangem.domain.models.scan.ScanResponse
import com.tangem.domain.wallets.builder.UserWalletBuilder
import com.tangem.domain.wallets.builder.ColdUserWalletBuilder
import com.tangem.domain.wallets.models.UserWallet
import com.tangem.domain.wallets.usecase.SaveWalletUseCase
import com.tangem.features.onboarding.v2.common.analytics.OnboardingEvent
@ -28,7 +28,7 @@ internal class OnboardingNoteCreateWalletModel @Inject constructor(
override val dispatchers: CoroutineDispatcherProvider,
private val tangemSdkManager: TangemSdkManager,
private val cardRepository: CardRepository,
private val userWalletBuilderFactory: UserWalletBuilder.Factory,
private val coldUserWalletBuilderFactory: ColdUserWalletBuilder.Factory,
private val saveWalletUseCase: SaveWalletUseCase,
) : Model() {
@ -87,7 +87,7 @@ internal class OnboardingNoteCreateWalletModel @Inject constructor(
private suspend fun createUserWallet(scanResponse: ScanResponse): UserWallet {
return requireNotNull(
value = userWalletBuilderFactory.create(scanResponse = scanResponse).build(),
value = coldUserWalletBuilderFactory.create(scanResponse = scanResponse).build(),
lazyMessage = { "User wallet not created" },
)
}

View file

@ -7,7 +7,6 @@ import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
@ -43,7 +42,8 @@ internal fun OnboardingNoteCreateWallet(state: OnboardingNoteCreateWalletUM, mod
state = WalletArtworksState.Folded,
)
Column(
modifier = Modifier.weight(1 - ALL_STEPS_TOP_CONTAINER_WEIGHT)
modifier = Modifier
.weight(1 - ALL_STEPS_TOP_CONTAINER_WEIGHT)
.fillMaxWidth()
.verticalScroll(rememberScrollState())
.padding(horizontal = 32.dp),
@ -51,7 +51,7 @@ internal fun OnboardingNoteCreateWallet(state: OnboardingNoteCreateWalletUM, mod
) {
SpacerHMax()
Text(
text = stringResource(R.string.onboarding_create_wallet_header),
text = stringResourceSafe(R.string.onboarding_create_wallet_header),
style = TangemTheme.typography.h2,
color = TangemTheme.colors.text.primary1,
textAlign = TextAlign.Center,
@ -59,7 +59,7 @@ internal fun OnboardingNoteCreateWallet(state: OnboardingNoteCreateWalletUM, mod
)
SpacerH16()
Text(
text = stringResource(R.string.onboarding_create_wallet_body),
text = stringResourceSafe(R.string.onboarding_create_wallet_body),
style = TangemTheme.typography.body1,
color = TangemTheme.colors.text.secondary,
textAlign = TextAlign.Center,

View file

@ -13,16 +13,16 @@ 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
import com.tangem.domain.wallets.builder.ColdUserWalletBuilder
import com.tangem.domain.wallets.models.UserWallet
import com.tangem.domain.wallets.usecase.SaveWalletUseCase
import com.tangem.features.onboarding.v2.common.analytics.OnboardingEvent
@ -39,9 +39,9 @@ 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 coldUserWalletBuilderFactory: ColdUserWalletBuilder.Factory,
private val getLegacyTopUpUrlUseCase: GetLegacyTopUpUrlUseCase,
private val urlOpener: UrlOpener,
private val clipboardManager: ClipboardManager,
@ -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) {
@ -237,7 +240,7 @@ internal class OnboardingNoteTopUpModel @Inject constructor(
private suspend fun createAndSaveUserWallet(scanResponse: ScanResponse): UserWallet {
val wallet = requireNotNull(
value = userWalletBuilderFactory.create(scanResponse = scanResponse).build(),
value = coldUserWalletBuilderFactory.create(scanResponse = scanResponse).build(),
lazyMessage = { "User wallet not created" },
)
saveWalletUseCase(wallet, false)

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