Updated on 2026-08-14

This commit is contained in:
Tangem 2024-08-30 19:04:31 +04:00
parent 91d8932543
commit b37227d533
9 changed files with 144 additions and 40 deletions

View file

@ -92,4 +92,12 @@ internal object CardDomainModule {
fun provideNetworkHasDerivationUseCase(): NetworkHasDerivationUseCase {
return NetworkHasDerivationUseCase()
}
@Provides
@Singleton
fun provideIsRequiredDerivePublicKeysUseCase(
derivationsRepository: DerivationsRepository,
): HasMissedDerivationsUseCase {
return HasMissedDerivationsUseCase(derivationsRepository)
}
}

View file

@ -1,5 +1,7 @@
package com.tangem.tap.domain.card
import com.tangem.blockchain.common.Blockchain
import com.tangem.blockchainsdk.utils.fromNetworkId
import com.tangem.common.CompletionResult
import com.tangem.common.card.EllipticCurve
import com.tangem.common.core.TangemSdkError
@ -8,8 +10,10 @@ import com.tangem.common.doOnSuccess
import com.tangem.common.extensions.ByteArrayKey
import com.tangem.common.extensions.toMapKey
import com.tangem.crypto.hdWallet.DerivationPath
import com.tangem.data.common.currency.getNetwork
import com.tangem.datasource.local.userwallet.UserWalletsStore
import com.tangem.domain.card.repository.DerivationsRepository
import com.tangem.domain.common.util.derivationStyleProvider
import com.tangem.domain.models.scan.ScanResponse
import com.tangem.domain.tokens.model.CryptoCurrency
import com.tangem.domain.tokens.model.Network
@ -52,6 +56,26 @@ internal class DefaultDerivationsRepository(
derivePublicKeys(userWalletId = userWalletId, derivations = derivations)
}
override suspend fun hasMissedDerivations(
userWalletId: UserWalletId,
networksWithDerivationPath: Map<Network.ID, String?>,
): Boolean {
val userWallet = userWalletsStore.getSyncOrNull(userWalletId) ?: error("User wallet not found")
val derivations = MissedDerivationsFinder(scanResponse = userWallet.scanResponse)
.findByNetworks(
networksWithDerivationPath.mapNotNull { (networkId, extraDerivationPath) ->
getNetwork(
blockchain = Blockchain.fromNetworkId(networkId.value) ?: return@mapNotNull null,
extraDerivationPath = extraDerivationPath,
derivationStyleProvider = userWallet.scanResponse.derivationStyleProvider,
)
},
)
return derivations.isNotEmpty()
}
override suspend fun derivePublicKeys(userWalletId: UserWalletId, derivations: Derivations): DerivedKeys {
tangemSdkManager.derivePublicKeys(cardId = null, derivations = derivations)
.doOnSuccess { response ->

View file

@ -0,0 +1,23 @@
package com.tangem.domain.card
import com.tangem.domain.card.repository.DerivationsRepository
import com.tangem.domain.tokens.model.Network
import com.tangem.domain.wallets.models.UserWalletId
/**
* Use case to check if user has missed derivations
*
[REDACTED_AUTHOR]
*/
class HasMissedDerivationsUseCase(
private val derivationsRepository: DerivationsRepository,
) {
/** Check if user [userWalletId] has missed derivations using map of [Network.ID] with extraDerivationPath */
suspend operator fun invoke(
userWalletId: UserWalletId,
networksWithDerivationPath: Map<Network.ID, String?>,
): Boolean {
return derivationsRepository.hasMissedDerivations(userWalletId, networksWithDerivationPath)
}
}

View file

@ -20,4 +20,10 @@ interface DerivationsRepository {
userWalletId: UserWalletId,
derivations: Map<ByteArrayKey, List<DerivationPath>>,
): Map<ByteArrayKey, ExtendedPublicKeysMap>
/** Check if user [userWalletId] has missed derivations using map of [Network.ID] with extraDerivationPath */
suspend fun hasMissedDerivations(
userWalletId: UserWalletId,
networksWithDerivationPath: Map<Network.ID, String?>,
): Boolean
}

View file

@ -21,6 +21,7 @@ dependencies {
implementation(projects.domain.appCurrency.models)
implementation(projects.domain.balanceHiding)
implementation(projects.domain.balanceHiding.models)
implementation(projects.domain.card)
implementation(projects.domain.markets)
implementation(projects.domain.staking.models)
implementation(projects.domain.tokens)

View file

@ -36,21 +36,21 @@ internal class AddToPortfolioBSContentUMFactory(
/**
* Create [TangemBottomSheetConfig]
*
* @param portfolioData portfolio data
* @param portfolioBSVisibilityModel portfolio bottom sheet visibility model
* @param selectedWallet selected wallet
* @param networksWithToggle networks with toggle
* @param isUserChangedNetworks flag indicates if user changed networks
* @param portfolioData portfolio data
* @param portfolioUIData portfolio bottom sheet visibility model
* @param selectedWallet selected wallet
* @param networksWithToggle networks with toggle
* @param isUserChangedNetworks flag indicates if user changed networks
*/
fun create(
portfolioData: PortfolioData,
portfolioBSVisibilityModel: PortfolioBSVisibilityModel,
portfolioUIData: PortfolioUIData,
selectedWallet: UserWallet,
networksWithToggle: Map<TokenMarketInfo.Network, Boolean>,
isUserChangedNetworks: Boolean,
): TangemBottomSheetConfig {
return TangemBottomSheetConfig(
isShow = portfolioBSVisibilityModel.addToPortfolioBSVisibility,
isShow = portfolioUIData.portfolioBSVisibilityModel.addToPortfolioBSVisibility,
onDismissRequest = { onAddToPortfolioVisibilityChange(false) },
content = AddToPortfolioBSContentUM(
selectedWallet = selectedWallet.toSelectedUserWalletItemUM(),
@ -58,11 +58,11 @@ internal class AddToPortfolioBSContentUMFactory(
networksWithToggle = networksWithToggle,
onNetworkSwitchClick = onNetworkSwitchClick,
).convert(value = token),
isScanCardNotificationVisible = false, // TODO [REDACTED_JIRA]
isScanCardNotificationVisible = portfolioUIData.hasMissedDerivations,
continueButtonEnabled = isUserChangedNetworks,
onContinueButtonClick = onContinueClick,
walletSelectorConfig = crateWalletSelectorBSConfig(
isShow = portfolioBSVisibilityModel.walletSelectorBSVisibility,
isShow = portfolioUIData.portfolioBSVisibilityModel.walletSelectorBSVisibility,
portfolioData = portfolioData,
selectedWalletId = selectedWallet.walletId,
),

View file

@ -5,8 +5,10 @@ import arrow.core.getOrElse
import com.tangem.core.decompose.di.ComponentScoped
import com.tangem.core.decompose.model.Model
import com.tangem.core.decompose.model.ParamsContainer
import com.tangem.domain.card.HasMissedDerivationsUseCase
import com.tangem.domain.markets.TokenMarketInfo
import com.tangem.domain.tokens.model.CryptoCurrency
import com.tangem.domain.tokens.model.Network
import com.tangem.domain.wallets.models.UserWalletId
import com.tangem.domain.wallets.usecase.GetSelectedWalletUseCase
import com.tangem.features.markets.portfolio.api.MarketsPortfolioComponent
@ -24,6 +26,7 @@ internal class MarketsPortfolioModel @Inject constructor(
paramsContainer: ParamsContainer,
private val getSelectedWalletUseCase: GetSelectedWalletUseCase,
private val portfolioDataLoader: PortfolioDataLoader,
private val hasMissedDerivationsUseCase: HasMissedDerivationsUseCase,
override val dispatchers: CoroutineDispatcherProvider,
) : Model() {
@ -85,16 +88,45 @@ internal class MarketsPortfolioModel @Inject constructor(
private fun subscribeOnStateUpdates() {
combine(
flow = portfolioDataLoader.load(params.token.id),
flow2 = portfolioBSVisibilityModelFlow,
flow2 = getPortfolioUIDataFlow(),
flow3 = availableNetworksFlow,
flow4 = selectedMultiWalletIdFlow,
flow5 = walletsWithChangedNetworksFlow,
transform = factory::create,
)
.onEach { _state.value = it }
.launchIn(modelScope)
}
private fun getPortfolioUIDataFlow(): Flow<PortfolioUIData> {
return combine(
flow = portfolioBSVisibilityModelFlow,
flow2 = selectedMultiWalletIdFlow,
flow3 = walletsWithChangedNetworksFlow,
transform = { portfolioBSVisibilityModel, selectedWalletId, walletsWithChangedNetworks ->
PortfolioUIData(
portfolioBSVisibilityModel = portfolioBSVisibilityModel,
selectedWalletId = selectedWalletId,
walletsWithChangedNetworks = walletsWithChangedNetworks,
hasMissedDerivations = hasMissedDerivations(selectedWalletId, walletsWithChangedNetworks),
)
},
)
}
private suspend fun hasMissedDerivations(
selectedWalletId: UserWalletId?,
walletsWithChangedNetworks: Map<UserWalletId, List<String>>,
): Boolean {
return if (selectedWalletId != null) {
hasMissedDerivationsUseCase.invoke(
userWalletId = selectedWalletId,
networksWithDerivationPath = walletsWithChangedNetworks[selectedWalletId].orEmpty()
.associate { Network.ID(it) to null },
)
} else {
false
}
}
private fun onTokenItemClick(index: Int, id: CryptoCurrency.ID) {
_state.update {
val state = _state.value as? MyPortfolioUM.Tokens ?: return@update it

View file

@ -5,7 +5,6 @@ import com.tangem.domain.markets.TokenMarketInfo
import com.tangem.domain.tokens.model.CryptoCurrency
import com.tangem.domain.tokens.model.CryptoCurrencyStatus
import com.tangem.domain.wallets.models.UserWallet
import com.tangem.domain.wallets.models.UserWalletId
import com.tangem.features.markets.portfolio.impl.loader.PortfolioData
import com.tangem.features.markets.portfolio.impl.ui.state.MyPortfolioUM
@ -24,17 +23,15 @@ internal class MyPortfolioUMMFactory(
) {
fun create(
portfolioModel: PortfolioData,
portfolioBSVisibilityModel: PortfolioBSVisibilityModel,
portfolioData: PortfolioData,
portfolioUIData: PortfolioUIData,
availableNetworks: List<TokenMarketInfo.Network>?,
selectedWalletId: UserWalletId?,
walletsWithChangedNetworks: Map<UserWalletId, List<String>>,
): MyPortfolioUM {
if (availableNetworks == null) return MyPortfolioUM.Loading
if (availableNetworks.isEmpty()) return MyPortfolioUM.Unavailable
val walletsWithStatuses = portfolioModel.walletsWithCurrencyStatuses
val walletsWithStatuses = portfolioData.walletsWithCurrencyStatuses
.filterAvailableNetworks(networks = availableNetworks)
val isPortfolioEmpty = walletsWithStatuses.flatMap { it.value }.isEmpty()
@ -44,11 +41,9 @@ internal class MyPortfolioUMMFactory(
return if (hasMultiWallets) {
MyPortfolioUM.AddFirstToken(
bsConfig = createAddToPortfolioBSConfig(
portfolioModel = portfolioModel,
selectedWalletId = selectedWalletId,
portfolioData = portfolioData,
portfolioUIData = portfolioUIData,
availableNetworks = availableNetworks,
walletsWithChangedNetworks = walletsWithChangedNetworks,
portfolioBSVisibilityModel = portfolioBSVisibilityModel,
),
onAddClick = onAddClick,
)
@ -58,15 +53,13 @@ internal class MyPortfolioUMMFactory(
}
return TokensPortfolioUMConverter(
appCurrency = portfolioModel.appCurrency,
isBalanceHidden = portfolioModel.isBalanceHidden,
appCurrency = portfolioData.appCurrency,
isBalanceHidden = portfolioData.isBalanceHidden,
isAllAvailableNetworksAdded = walletsWithStatuses.isAllAvailableNetworksAdded(availableNetworks),
bsConfig = createAddToPortfolioBSConfig(
portfolioModel = portfolioModel,
selectedWalletId = selectedWalletId,
portfolioData = portfolioData,
portfolioUIData = portfolioUIData,
availableNetworks = availableNetworks,
portfolioBSVisibilityModel = portfolioBSVisibilityModel,
walletsWithChangedNetworks = walletsWithChangedNetworks,
),
onAddClick = onAddClick,
onTokenItemClick = onTokenItemClick,
@ -75,23 +68,20 @@ internal class MyPortfolioUMMFactory(
}
private fun createAddToPortfolioBSConfig(
portfolioModel: PortfolioData,
selectedWalletId: UserWalletId?,
portfolioData: PortfolioData,
portfolioUIData: PortfolioUIData,
availableNetworks: List<TokenMarketInfo.Network>,
portfolioBSVisibilityModel: PortfolioBSVisibilityModel,
walletsWithChangedNetworks: Map<UserWalletId, List<String>>,
): TangemBottomSheetConfig {
val walletId = requireNotNull(selectedWalletId) {
"Selected wallet must be not null in state with AddToPortfolio bottom sheet"
}
val walletId = portfolioUIData.selectedWalletId
?: portfolioData.walletsWithCurrencyStatuses.keys.firstOrNull { it.isMultiCurrency }?.walletId
val selectedWallet = portfolioModel.walletsWithCurrencyStatuses.keys
val selectedWallet = portfolioData.walletsWithCurrencyStatuses.keys
.firstOrNull { it.walletId == walletId }
?: error("portfolioModel.walletsWithCurrencyStatuses doesn't contain selected wallet: $walletId")
val changedNetworks = walletsWithChangedNetworks[selectedWalletId]
val changedNetworks = portfolioUIData.walletsWithChangedNetworks[portfolioUIData.selectedWalletId]
val alreadyAddedNetworks = requireNotNull(
value = portfolioModel.walletsWithCurrencyStatuses[selectedWallet],
value = portfolioData.walletsWithCurrencyStatuses[selectedWallet],
lazyMessage = {
"portfolioModel.walletsWithCurrencyStatuses doesn't contain selected wallet: $walletId"
},
@ -100,8 +90,8 @@ internal class MyPortfolioUMMFactory(
.map { it.currency.network.backendId }
return addToPortfolioBSContentUMFactory.create(
portfolioData = portfolioModel,
portfolioBSVisibilityModel = portfolioBSVisibilityModel,
portfolioData = portfolioData,
portfolioUIData = portfolioUIData,
selectedWallet = selectedWallet,
networksWithToggle = availableNetworks.associateWithToggle(
changedNetworks = changedNetworks,

View file

@ -0,0 +1,20 @@
package com.tangem.features.markets.portfolio.impl.model
import com.tangem.domain.wallets.models.UserWalletId
/**
* Portfolio UI data. Combined data from all UI flows that required to setup portfolio
*
* @property portfolioBSVisibilityModel portfolio bottom sheet visibility model
* @property selectedWalletId selected wallet id
* @property walletsWithChangedNetworks wallets with changed networks
* @property hasMissedDerivations flag that indicates if user has missed derivations
*
[REDACTED_AUTHOR]
*/
internal data class PortfolioUIData(
val portfolioBSVisibilityModel: PortfolioBSVisibilityModel,
val selectedWalletId: UserWalletId?,
val walletsWithChangedNetworks: Map<UserWalletId, List<String>>,
val hasMissedDerivations: Boolean,
)