From b37227d5331393f94ef9b59fdea3323712be338e Mon Sep 17 00:00:00 2001 From: Tangem Date: Fri, 30 Aug 2024 19:04:31 +0400 Subject: [PATCH] Updated on 2026-08-14 --- .../tangem/tap/di/domain/CardDomainModule.kt | 8 ++++ .../card/DefaultDerivationsRepository.kt | 24 ++++++++++ .../card/HasMissedDerivationsUseCase.kt | 23 ++++++++++ .../card/repository/DerivationsRepository.kt | 6 +++ features/markets/impl/build.gradle.kts | 1 + .../model/AddToPortfolioBSContentUMFactory.kt | 18 ++++---- .../impl/model/MarketsPortfolioModel.kt | 38 +++++++++++++-- .../impl/model/MyPortfolioUMMFactory.kt | 46 ++++++++----------- .../portfolio/impl/model/PortfolioUIData.kt | 20 ++++++++ 9 files changed, 144 insertions(+), 40 deletions(-) create mode 100644 domain/card/src/main/kotlin/com/tangem/domain/card/HasMissedDerivationsUseCase.kt create mode 100644 features/markets/impl/src/main/kotlin/com/tangem/features/markets/portfolio/impl/model/PortfolioUIData.kt diff --git a/app/src/main/java/com/tangem/tap/di/domain/CardDomainModule.kt b/app/src/main/java/com/tangem/tap/di/domain/CardDomainModule.kt index c74a09521c..ce9ed6e593 100644 --- a/app/src/main/java/com/tangem/tap/di/domain/CardDomainModule.kt +++ b/app/src/main/java/com/tangem/tap/di/domain/CardDomainModule.kt @@ -92,4 +92,12 @@ internal object CardDomainModule { fun provideNetworkHasDerivationUseCase(): NetworkHasDerivationUseCase { return NetworkHasDerivationUseCase() } + + @Provides + @Singleton + fun provideIsRequiredDerivePublicKeysUseCase( + derivationsRepository: DerivationsRepository, + ): HasMissedDerivationsUseCase { + return HasMissedDerivationsUseCase(derivationsRepository) + } } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/domain/card/DefaultDerivationsRepository.kt b/app/src/main/java/com/tangem/tap/domain/card/DefaultDerivationsRepository.kt index a5d6f883b7..4390c3ca9f 100644 --- a/app/src/main/java/com/tangem/tap/domain/card/DefaultDerivationsRepository.kt +++ b/app/src/main/java/com/tangem/tap/domain/card/DefaultDerivationsRepository.kt @@ -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, + ): 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 -> diff --git a/domain/card/src/main/kotlin/com/tangem/domain/card/HasMissedDerivationsUseCase.kt b/domain/card/src/main/kotlin/com/tangem/domain/card/HasMissedDerivationsUseCase.kt new file mode 100644 index 0000000000..5d481fe1ca --- /dev/null +++ b/domain/card/src/main/kotlin/com/tangem/domain/card/HasMissedDerivationsUseCase.kt @@ -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, + ): Boolean { + return derivationsRepository.hasMissedDerivations(userWalletId, networksWithDerivationPath) + } +} \ No newline at end of file diff --git a/domain/card/src/main/kotlin/com/tangem/domain/card/repository/DerivationsRepository.kt b/domain/card/src/main/kotlin/com/tangem/domain/card/repository/DerivationsRepository.kt index 5c6512a557..c4abbb8284 100644 --- a/domain/card/src/main/kotlin/com/tangem/domain/card/repository/DerivationsRepository.kt +++ b/domain/card/src/main/kotlin/com/tangem/domain/card/repository/DerivationsRepository.kt @@ -20,4 +20,10 @@ interface DerivationsRepository { userWalletId: UserWalletId, derivations: Map>, ): Map + + /** Check if user [userWalletId] has missed derivations using map of [Network.ID] with extraDerivationPath */ + suspend fun hasMissedDerivations( + userWalletId: UserWalletId, + networksWithDerivationPath: Map, + ): Boolean } \ No newline at end of file diff --git a/features/markets/impl/build.gradle.kts b/features/markets/impl/build.gradle.kts index 410e6dbd11..3fc641b0da 100644 --- a/features/markets/impl/build.gradle.kts +++ b/features/markets/impl/build.gradle.kts @@ -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) diff --git a/features/markets/impl/src/main/kotlin/com/tangem/features/markets/portfolio/impl/model/AddToPortfolioBSContentUMFactory.kt b/features/markets/impl/src/main/kotlin/com/tangem/features/markets/portfolio/impl/model/AddToPortfolioBSContentUMFactory.kt index 9983cd1bb2..6b8140850f 100644 --- a/features/markets/impl/src/main/kotlin/com/tangem/features/markets/portfolio/impl/model/AddToPortfolioBSContentUMFactory.kt +++ b/features/markets/impl/src/main/kotlin/com/tangem/features/markets/portfolio/impl/model/AddToPortfolioBSContentUMFactory.kt @@ -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, 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, ), diff --git a/features/markets/impl/src/main/kotlin/com/tangem/features/markets/portfolio/impl/model/MarketsPortfolioModel.kt b/features/markets/impl/src/main/kotlin/com/tangem/features/markets/portfolio/impl/model/MarketsPortfolioModel.kt index 219da20190..5d647ae942 100644 --- a/features/markets/impl/src/main/kotlin/com/tangem/features/markets/portfolio/impl/model/MarketsPortfolioModel.kt +++ b/features/markets/impl/src/main/kotlin/com/tangem/features/markets/portfolio/impl/model/MarketsPortfolioModel.kt @@ -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 { + 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>, + ): 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 diff --git a/features/markets/impl/src/main/kotlin/com/tangem/features/markets/portfolio/impl/model/MyPortfolioUMMFactory.kt b/features/markets/impl/src/main/kotlin/com/tangem/features/markets/portfolio/impl/model/MyPortfolioUMMFactory.kt index 91072c3906..109e54ad83 100644 --- a/features/markets/impl/src/main/kotlin/com/tangem/features/markets/portfolio/impl/model/MyPortfolioUMMFactory.kt +++ b/features/markets/impl/src/main/kotlin/com/tangem/features/markets/portfolio/impl/model/MyPortfolioUMMFactory.kt @@ -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?, - selectedWalletId: UserWalletId?, - walletsWithChangedNetworks: Map>, ): 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, - portfolioBSVisibilityModel: PortfolioBSVisibilityModel, - walletsWithChangedNetworks: Map>, ): 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, diff --git a/features/markets/impl/src/main/kotlin/com/tangem/features/markets/portfolio/impl/model/PortfolioUIData.kt b/features/markets/impl/src/main/kotlin/com/tangem/features/markets/portfolio/impl/model/PortfolioUIData.kt new file mode 100644 index 0000000000..fcb0dd86c0 --- /dev/null +++ b/features/markets/impl/src/main/kotlin/com/tangem/features/markets/portfolio/impl/model/PortfolioUIData.kt @@ -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>, + val hasMissedDerivations: Boolean, +) \ No newline at end of file