diff --git a/app/src/main/java/com/tangem/tap/di/domain/TokensDomainModule.kt b/app/src/main/java/com/tangem/tap/di/domain/TokensDomainModule.kt index 026bf203bb..e9a9087e06 100644 --- a/app/src/main/java/com/tangem/tap/di/domain/TokensDomainModule.kt +++ b/app/src/main/java/com/tangem/tap/di/domain/TokensDomainModule.kt @@ -16,6 +16,7 @@ import dagger.hilt.android.scopes.ViewModelScoped @Module @InstallIn(ViewModelComponent::class) +@Suppress("TooManyFunctions") internal object TokensDomainModule { @Provides @@ -212,4 +213,12 @@ internal object TokensDomainModule { networksRepository = networksRepository, ) } + + @Provides + @ViewModelScoped + fun provideHasMissedAddressesCryptoCurrenciesUseCase( + currenciesRepository: CurrenciesRepository, + ): GetMissedAddressesCryptoCurrenciesUseCase { + return GetMissedAddressesCryptoCurrenciesUseCase(currenciesRepository = currenciesRepository) + } } \ No newline at end of file diff --git a/data/tokens/src/main/kotlin/com/tangem/data/tokens/repository/DefaultCurrenciesRepository.kt b/data/tokens/src/main/kotlin/com/tangem/data/tokens/repository/DefaultCurrenciesRepository.kt index 01f5379e8f..fcfa4ecd88 100644 --- a/data/tokens/src/main/kotlin/com/tangem/data/tokens/repository/DefaultCurrenciesRepository.kt +++ b/data/tokens/src/main/kotlin/com/tangem/data/tokens/repository/DefaultCurrenciesRepository.kt @@ -13,6 +13,7 @@ import com.tangem.datasource.local.userwallet.UserWalletsStore import com.tangem.domain.common.extensions.toCoinId import com.tangem.domain.common.extensions.toNetworkId import com.tangem.domain.common.util.derivationStyleProvider +import com.tangem.domain.common.util.hasDerivation import com.tangem.domain.core.error.DataError import com.tangem.domain.demo.DemoConfig import com.tangem.domain.tokens.model.CryptoCurrency @@ -274,6 +275,24 @@ internal class DefaultCurrenciesRepository( }.cancellable() } + override fun getMissedAddressesCryptoCurrencies(userWalletId: UserWalletId): Flow> { + return channelFlow { + ensureIsCorrectUserWallet(userWalletId, isMultiCurrencyWalletExpected = true) + + val userWallet = getUserWallet(userWalletId = userWalletId) + getMultiCurrencyWalletCurrencies(userWallet = userWallet) + .map { + it.filter { currency -> + val blockchain = Blockchain.fromId(id = currency.network.id.value) + val derivationPath = currency.network.derivationPath.value + + derivationPath != null && !userWallet.scanResponse.hasDerivation(blockchain, derivationPath) + } + } + .collectLatest(::send) + } + } + private fun getMultiCurrencyWalletCurrencies(userWallet: UserWallet): Flow> { return userTokensStore.get(userWallet.walletId).map { storedTokens -> responseCurrenciesFactory.createCurrencies( diff --git a/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/GetMissedAddressesCryptoCurrenciesUseCase.kt b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/GetMissedAddressesCryptoCurrenciesUseCase.kt new file mode 100644 index 0000000000..7d0a2e41ee --- /dev/null +++ b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/GetMissedAddressesCryptoCurrenciesUseCase.kt @@ -0,0 +1,21 @@ +package com.tangem.domain.tokens + +import arrow.core.Either +import arrow.core.left +import arrow.core.right +import com.tangem.domain.tokens.error.GetCurrenciesError +import com.tangem.domain.tokens.model.CryptoCurrency +import com.tangem.domain.tokens.repository.CurrenciesRepository +import com.tangem.domain.wallets.models.UserWalletId +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.catch +import kotlinx.coroutines.flow.map + +class GetMissedAddressesCryptoCurrenciesUseCase(private val currenciesRepository: CurrenciesRepository) { + + operator fun invoke(userWalletId: UserWalletId): Flow>> { + return currenciesRepository.getMissedAddressesCryptoCurrencies(userWalletId) + .map(List::right) + .catch { GetCurrenciesError.DataError(it).left() } + } +} \ No newline at end of file diff --git a/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/repository/CurrenciesRepository.kt b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/repository/CurrenciesRepository.kt index de719ff8c0..13e3c83950 100644 --- a/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/repository/CurrenciesRepository.kt +++ b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/repository/CurrenciesRepository.kt @@ -166,4 +166,6 @@ interface CurrenciesRepository { * ID provided. */ fun isTokensSortedByBalance(userWalletId: UserWalletId): Flow + + fun getMissedAddressesCryptoCurrencies(userWalletId: UserWalletId): Flow> } \ No newline at end of file diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/viewmodels/WalletNotificationsListFactory.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/viewmodels/WalletNotificationsListFactory.kt index bb6830dc17..28676c50c8 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/viewmodels/WalletNotificationsListFactory.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/viewmodels/WalletNotificationsListFactory.kt @@ -1,9 +1,12 @@ package com.tangem.feature.wallet.presentation.wallet.viewmodels +import arrow.core.Either import com.tangem.domain.card.WasCardScannedUseCase import com.tangem.domain.common.CardTypesResolver import com.tangem.domain.demo.IsDemoCardUseCase import com.tangem.domain.settings.IsReadyToShowRateAppUseCase +import com.tangem.domain.tokens.GetMissedAddressesCryptoCurrenciesUseCase +import com.tangem.domain.tokens.error.GetCurrenciesError import com.tangem.domain.tokens.model.CryptoCurrency import com.tangem.domain.tokens.model.CryptoCurrencyStatus import com.tangem.domain.wallets.models.UserWalletId @@ -13,7 +16,6 @@ import kotlinx.collections.immutable.ImmutableList import kotlinx.collections.immutable.toImmutableList import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.combine -import kotlin.collections.count /** * Wallet notifications list factory @@ -31,6 +33,7 @@ internal class WalletNotificationsListFactory( private val isReadyToShowRateAppUseCase: IsReadyToShowRateAppUseCase, private val wasCardScannedUseCase: WasCardScannedUseCase, private val isNeedToBackupUseCase: IsNeedToBackupUseCase, + private val getMissedAddressCryptoCurrenciesUseCase: GetMissedAddressesCryptoCurrenciesUseCase, private val clickIntents: WalletClickIntents, ) { @@ -45,12 +48,13 @@ internal class WalletNotificationsListFactory( flow = wasCardScannedUseCase(cardTypesResolver.getCardId()), flow2 = isReadyToShowRateAppUseCase(), flow3 = isNeedToBackupUseCase(selectedWalletId), - ) { wasCardScanned, isReadyToShowRating, isNeedToBackup -> + flow4 = getMissedAddressCryptoCurrenciesUseCase(selectedWalletId), + ) { wasCardScanned, isReadyToShowRating, isNeedToBackup, maybeMissedAddressCurrencies -> readyForRateAppNotification = true buildList { addCriticalNotifications(cardTypesResolver) - addInformationalNotifications(cardTypesResolver, cryptoCurrencyList) + addInformationalNotifications(cardTypesResolver, maybeMissedAddressCurrencies) addWarningNotifications(cardTypesResolver, cryptoCurrencyList, wasCardScanned, isNeedToBackup) @@ -136,14 +140,14 @@ internal class WalletNotificationsListFactory( private fun MutableList.addInformationalNotifications( cardTypesResolver: CardTypesResolver, - cryptoCurrencyList: List, + maybeMissedAddressCurrencies: Either>, ) { addIf( element = WalletNotification.Informational.DemoCard, condition = isDemoCardUseCase(cardId = cardTypesResolver.getCardId()), ) - addMissingAddressesNotification(cryptoCurrencyList) + addMissingAddressesNotification(maybeMissedAddressCurrencies) } private fun MutableList.addIf(element: WalletNotification, condition: Boolean) { @@ -156,27 +160,21 @@ internal class WalletNotificationsListFactory( } private fun MutableList.addMissingAddressesNotification( - cryptoCurrencyList: List, + maybeCurrencies: Either>, ) { - val missedAddressCurrencies = cryptoCurrencyList.getMissedAddressCurrencies() + val missingAddressCurrencies = (maybeCurrencies as? Either.Right)?.value ?: return addIf( element = WalletNotification.Informational.MissingAddresses( - missingAddressesCount = missedAddressCurrencies.count(), + missingAddressesCount = missingAddressCurrencies.count(), onGenerateClick = { - clickIntents.onGenerateMissedAddressesClick(missedAddressCurrencies = missedAddressCurrencies) + clickIntents.onGenerateMissedAddressesClick(missedAddressCurrencies = missingAddressCurrencies) }, ), - condition = missedAddressCurrencies.isNotEmpty(), + condition = missingAddressCurrencies.isNotEmpty(), ) } - private fun List.getMissedAddressCurrencies(): List { - return this - .filter { it.value is CryptoCurrencyStatus.MissedDerivation } - .map(CryptoCurrencyStatus::currency) - } - private fun List.hasUnreachableNetworks(): Boolean { return any { it.value is CryptoCurrencyStatus.Unreachable } } diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/viewmodels/WalletViewModel.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/viewmodels/WalletViewModel.kt index 1017b5ed27..1abc88cba9 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/viewmodels/WalletViewModel.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/viewmodels/WalletViewModel.kt @@ -134,6 +134,7 @@ internal class WalletViewModel @Inject constructor( isReadyToShowRateAppUseCase: IsReadyToShowRateAppUseCase, isDemoCardUseCase: IsDemoCardUseCase, isNeedToBackupUseCase: IsNeedToBackupUseCase, + getMissedAddressesCryptoCurrenciesUseCase: GetMissedAddressesCryptoCurrenciesUseCase, // endregion Parameters ) : ViewModel(), DefaultLifecycleObserver, WalletClickIntents { @@ -147,6 +148,7 @@ internal class WalletViewModel @Inject constructor( isReadyToShowRateAppUseCase = isReadyToShowRateAppUseCase, isDemoCardUseCase = isDemoCardUseCase, isNeedToBackupUseCase = isNeedToBackupUseCase, + getMissedAddressCryptoCurrenciesUseCase = getMissedAddressesCryptoCurrenciesUseCase, clickIntents = this, ) @@ -333,13 +335,21 @@ internal class WalletViewModel @Inject constructor( val config = CardConfig.createConfig(scanResponse.card) val derivationDataList = currencyList.mapNotNull { config.primaryCurve(blockchain = Blockchain.fromId(it.network.id.value))?.let { curve -> - getNewDerivations(curve, scanResponse, currencyList) + getNewDerivations(curve, scanResponse, it) } } - val derivations = derivationDataList - .associate(DerivationData::derivations) - .ifEmpty { return } + val derivations = buildMap> { + derivationDataList.forEach { + val current = this[it.derivations.first] + if (current != null) { + current.addAll(it.derivations.second) + current.distinct() + } else { + this[it.derivations.first] = it.derivations.second.toMutableList() + } + } + }.ifEmpty { return } viewModelScope.launch(dispatchers.io) { derivePublicKeysUseCase(cardId = scanResponse.card.cardId, derivations = derivations) @@ -364,30 +374,27 @@ internal class WalletViewModel @Inject constructor( private fun getNewDerivations( curve: EllipticCurve, scanResponse: ScanResponse, - currencyList: List, + currency: CryptoCurrency, ): DerivationData? { val wallet = scanResponse.card.wallets.firstOrNull { it.curve == curve } ?: return null - val manageTokensCandidates = currencyList - .map { Blockchain.fromId(it.network.id.value) } - .distinct() - .filter { it.getSupportedCurves().contains(curve) } - .mapNotNull { it.derivationPath(scanResponse.derivationStyleProvider.getDerivationStyle()) } + val blockchain = Blockchain.fromId(currency.network.id.value) + val supportedCurves = blockchain.getSupportedCurves() + val path = blockchain.derivationPath(scanResponse.derivationStyleProvider.getDerivationStyle()) + .takeIf { supportedCurves.contains(curve) } - val customTokensCandidates = currencyList - .filter { Blockchain.fromId(it.network.id.value).getSupportedCurves().contains(curve) } - .mapNotNull { it.network.derivationPath.value } - .map(::DerivationPath) + val customPath = currency.network.derivationPath.value?.let { + DerivationPath(it) + }.takeIf { supportedCurves.contains(curve) } - val bothCandidates = (manageTokensCandidates + customTokensCandidates).distinct().toMutableList() + val bothCandidates = listOfNotNull(path, customPath).distinct().toMutableList() if (bothCandidates.isEmpty()) return null - currencyList.find { it is CryptoCurrency.Coin && Blockchain.fromId(it.network.id.value) == Blockchain.Cardano } - ?.let { currency -> - currency.network.derivationPath.value?.let { - bothCandidates.add(CardanoUtils.extendedDerivationPath(DerivationPath(it))) - } + if (currency is CryptoCurrency.Coin && blockchain == Blockchain.Cardano) { + currency.network.derivationPath.value?.let { + bothCandidates.add(CardanoUtils.extendedDerivationPath(DerivationPath(it))) } + } val mapKeyOfWalletPublicKey = wallet.publicKey.toMapKey() val alreadyDerivedKeys: ExtendedPublicKeysMap =