Updated on 2026-08-14

This commit is contained in:
Tangem 2023-10-16 22:45:10 +08:00
parent 49d8e8f51c
commit cfd237fc30
6 changed files with 92 additions and 36 deletions

View file

@ -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)
}
}

View file

@ -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<List<CryptoCurrency>> {
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<List<CryptoCurrency>> {
return userTokensStore.get(userWallet.walletId).map { storedTokens ->
responseCurrenciesFactory.createCurrencies(

View file

@ -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<Either<GetCurrenciesError, List<CryptoCurrency>>> {
return currenciesRepository.getMissedAddressesCryptoCurrencies(userWalletId)
.map(List<CryptoCurrency>::right)
.catch { GetCurrenciesError.DataError(it).left() }
}
}

View file

@ -166,4 +166,6 @@ interface CurrenciesRepository {
* ID provided.
*/
fun isTokensSortedByBalance(userWalletId: UserWalletId): Flow<Boolean>
fun getMissedAddressesCryptoCurrencies(userWalletId: UserWalletId): Flow<List<CryptoCurrency>>
}

View file

@ -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<WalletNotification>.addInformationalNotifications(
cardTypesResolver: CardTypesResolver,
cryptoCurrencyList: List<CryptoCurrencyStatus>,
maybeMissedAddressCurrencies: Either<GetCurrenciesError, List<CryptoCurrency>>,
) {
addIf(
element = WalletNotification.Informational.DemoCard,
condition = isDemoCardUseCase(cardId = cardTypesResolver.getCardId()),
)
addMissingAddressesNotification(cryptoCurrencyList)
addMissingAddressesNotification(maybeMissedAddressCurrencies)
}
private fun MutableList<WalletNotification>.addIf(element: WalletNotification, condition: Boolean) {
@ -156,27 +160,21 @@ internal class WalletNotificationsListFactory(
}
private fun MutableList<WalletNotification>.addMissingAddressesNotification(
cryptoCurrencyList: List<CryptoCurrencyStatus>,
maybeCurrencies: Either<GetCurrenciesError, List<CryptoCurrency>>,
) {
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<CryptoCurrencyStatus>.getMissedAddressCurrencies(): List<CryptoCurrency> {
return this
.filter { it.value is CryptoCurrencyStatus.MissedDerivation }
.map(CryptoCurrencyStatus::currency)
}
private fun List<CryptoCurrencyStatus>.hasUnreachableNetworks(): Boolean {
return any { it.value is CryptoCurrencyStatus.Unreachable }
}

View file

@ -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<ByteArrayKey, MutableList<DerivationPath>> {
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<CryptoCurrency>,
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 =