Updated on 2026-08-14

This commit is contained in:
Tangem 2025-03-12 23:43:40 +05:00
parent 296df28f0a
commit bc70ed5327
8 changed files with 57 additions and 41 deletions

View file

@ -9,7 +9,6 @@ import com.tangem.domain.nft.models.NFTSalePrice
import com.tangem.domain.tokens.model.Network
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.firstOrNull
import kotlinx.coroutines.flow.map
internal class DefaultNFTRuntimeStore(
@ -25,9 +24,7 @@ internal class DefaultNFTRuntimeStore(
override fun getCollections(): Flow<NFTCollections> = collectionsRuntimeStore
.get()
.combine(pricesRuntimeStore.get(), ::Pair)
.map {
val (collectionsData, prices) = it
.combine(pricesRuntimeStore.get()) { collectionsData, prices ->
collectionsData.mergeWithPrices(prices)
}
@ -48,9 +45,7 @@ internal class DefaultNFTRuntimeStore(
override fun getAsset(collectionId: NFTCollection.Identifier, assetId: NFTAsset.Identifier): Flow<NFTAsset> =
collectionsRuntimeStore
.get()
.combine(getSalePrice(assetId), ::Pair)
.map {
val (collectionsData, price) = it
.combine(getSalePrice(assetId)) { collectionsData, price ->
collectionsData
.getCollection(collectionId)
?.getAsset(assetId)

View file

@ -26,6 +26,17 @@ class NFTPersistenceStoreFactory @Inject constructor(
) {
fun provide(network: Network): NFTPersistenceStore {
val networkStringIdentifier = listOfNotNull(
network.id.value,
network.derivationPath.value,
).joinToString("_") {
// remove all non-alphanumeric characters
it
.toCharArray()
.filter(Char::isLetterOrDigit)
.joinToString("")
.lowercase()
}
return DefaultNFTPersistenceStore(
collectionsPersistenceStore = DataStoreFactory.create(
serializer = MoshiDataStoreSerializer(
@ -34,7 +45,7 @@ class NFTPersistenceStoreFactory @Inject constructor(
defaultValue = emptyList(),
),
produceFile = {
context.dataStoreFile(fileName = "nft_${network.name}_${network.derivationPath}_collections")
context.dataStoreFile(fileName = "nft_${networkStringIdentifier}_collections")
},
scope = CoroutineScope(context = dispatchers.io + SupervisorJob()),
),
@ -45,7 +56,7 @@ class NFTPersistenceStoreFactory @Inject constructor(
defaultValue = emptyMap(),
),
produceFile = {
context.dataStoreFile(fileName = "nft_${network.name}_${network.derivationPath}_prices")
context.dataStoreFile(fileName = "nft_${networkStringIdentifier}_prices")
},
scope = CoroutineScope(context = dispatchers.io + SupervisorJob()),
),

View file

@ -4,7 +4,7 @@ import com.tangem.domain.nft.models.NFTAsset
import com.tangem.utils.converter.TwoWayConverter
import com.tangem.blockchain.nft.models.NFTAsset as SdkNFTAsset
class NFTSdkAssetIdentifierConverter : TwoWayConverter<SdkNFTAsset.Identifier, NFTAsset.Identifier> {
object NFTSdkAssetIdentifierConverter : TwoWayConverter<SdkNFTAsset.Identifier, NFTAsset.Identifier> {
override fun convert(value: SdkNFTAsset.Identifier): NFTAsset.Identifier = when (value) {
is SdkNFTAsset.Identifier.EVM -> NFTAsset.Identifier.EVM(
tokenId = value.tokenId,
@ -13,7 +13,7 @@ class NFTSdkAssetIdentifierConverter : TwoWayConverter<SdkNFTAsset.Identifier, N
is SdkNFTAsset.Identifier.TON -> NFTAsset.Identifier.TON(
tokenAddress = value.tokenAddress,
)
is SdkNFTAsset.Identifier.Unknown -> error("unknown asset id")
is SdkNFTAsset.Identifier.Unknown -> NFTAsset.Identifier.Unknown
}
override fun convertBack(value: NFTAsset.Identifier): SdkNFTAsset.Identifier = when (value) {
@ -24,5 +24,6 @@ class NFTSdkAssetIdentifierConverter : TwoWayConverter<SdkNFTAsset.Identifier, N
is NFTAsset.Identifier.TON -> SdkNFTAsset.Identifier.TON(
tokenAddress = value.tokenAddress,
)
is NFTAsset.Identifier.Unknown -> SdkNFTAsset.Identifier.Unknown
}
}

View file

@ -1,5 +1,6 @@
package com.tangem.datasource.local.nft.converter
import com.tangem.domain.nft.models.NFTAsset
import com.tangem.domain.nft.models.NFTCollection
import com.tangem.domain.tokens.model.Network
import com.tangem.utils.converter.Converter
@ -19,9 +20,13 @@ class NFTSdkCollectionConverter(
description = collection.description,
logoUrl = collection.logoUrl,
count = collection.count,
assets = collection.assets.map { asset ->
nftSdkAssetConverter.convert(network to asset)
},
assets = collection.assets
.map { asset ->
nftSdkAssetConverter.convert(network to asset)
}
.filter {
it.id !is NFTAsset.Identifier.Unknown
},
)
}
}

View file

@ -4,7 +4,7 @@ import com.tangem.domain.nft.models.NFTCollection
import com.tangem.utils.converter.TwoWayConverter
import com.tangem.blockchain.nft.models.NFTCollection as SdkNFTCollection
class NFTSdkCollectionIdentifierConverter : TwoWayConverter<SdkNFTCollection.Identifier, NFTCollection.Identifier> {
object NFTSdkCollectionIdentifierConverter : TwoWayConverter<SdkNFTCollection.Identifier, NFTCollection.Identifier> {
override fun convert(value: SdkNFTCollection.Identifier): NFTCollection.Identifier = when (value) {
is SdkNFTCollection.Identifier.EVM -> NFTCollection.Identifier.EVM(
tokenAddress = value.tokenAddress,
@ -12,7 +12,7 @@ class NFTSdkCollectionIdentifierConverter : TwoWayConverter<SdkNFTCollection.Ide
is SdkNFTCollection.Identifier.TON -> NFTCollection.Identifier.TON(
contractAddress = value.contractAddress,
)
is SdkNFTCollection.Identifier.Unknown -> error("unknown collection id")
is SdkNFTCollection.Identifier.Unknown -> NFTCollection.Identifier.Unknown
}
override fun convertBack(value: NFTCollection.Identifier): SdkNFTCollection.Identifier = when (value) {
@ -22,5 +22,6 @@ class NFTSdkCollectionIdentifierConverter : TwoWayConverter<SdkNFTCollection.Ide
is NFTCollection.Identifier.TON -> SdkNFTCollection.Identifier.TON(
contractAddress = value.contractAddress,
)
is NFTCollection.Identifier.Unknown -> SdkNFTCollection.Identifier.Unknown
}
}

View file

@ -1,7 +1,6 @@
package com.tangem.data.nft
import arrow.core.Either
import com.tangem.blockchain.nft.models.NFTCollection
import com.tangem.datasource.local.nft.NFTPersistenceStore
import com.tangem.datasource.local.nft.NFTPersistenceStoreFactory
import com.tangem.datasource.local.nft.NFTRuntimeStore
@ -11,6 +10,7 @@ import com.tangem.datasource.local.nft.converter.NFTSdkAssetIdentifierConverter
import com.tangem.datasource.local.nft.converter.NFTSdkCollectionConverter
import com.tangem.datasource.local.nft.converter.NFTSdkCollectionIdentifierConverter
import com.tangem.domain.models.StatusSource
import com.tangem.domain.nft.models.NFTCollection
import com.tangem.domain.nft.models.NFTCollections
import com.tangem.domain.nft.models.NFTSalePrice
import com.tangem.domain.nft.repository.NFTRepository
@ -20,11 +20,8 @@ import com.tangem.domain.wallets.models.UserWalletId
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
import com.tangem.utils.coroutines.JobHolder
import com.tangem.utils.coroutines.saveIn
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.SupervisorJob
import kotlinx.coroutines.*
import kotlinx.coroutines.flow.*
import kotlinx.coroutines.joinAll
import kotlinx.coroutines.launch
import javax.inject.Inject
import com.tangem.blockchain.nft.models.NFTCollection as SdkNFTCollection
@ -32,24 +29,20 @@ internal class DefaultNFTRepository @Inject constructor(
private val nftPersistenceStoreFactory: NFTPersistenceStoreFactory,
private val nftRuntimeStoreFactory: NFTRuntimeStoreFactory,
private val walletManagersFacade: WalletManagersFacade,
dispatchers: CoroutineDispatcherProvider,
private val dispatchers: CoroutineDispatcherProvider,
) : NFTRepository {
private val scope = CoroutineScope(dispatchers.io + SupervisorJob())
private val jobs = mutableMapOf<Network, JobHolder>()
private val nftRuntimeStores = mutableMapOf<Network, NFTRuntimeStore>()
private val nftPersistenceStores = mutableMapOf<Network, NFTPersistenceStore>()
private val nftSdkAssetIdentifierConverter = NFTSdkAssetIdentifierConverter()
private val nftSdkCollectionIdentifierConverter = NFTSdkCollectionIdentifierConverter()
private val nftSdkAssetConverter = NFTSdkAssetConverter(
nftSdkAssetIdentifierConverter = nftSdkAssetIdentifierConverter,
nftSdkCollectionIdentifierConverter = nftSdkCollectionIdentifierConverter,
nftSdkAssetIdentifierConverter = NFTSdkAssetIdentifierConverter,
nftSdkCollectionIdentifierConverter = NFTSdkCollectionIdentifierConverter,
)
private val collectionConverter = NFTSdkCollectionConverter(
nftSdkCollectionIdentifierConverter = nftSdkCollectionIdentifierConverter,
nftSdkCollectionIdentifierConverter = NFTSdkCollectionIdentifierConverter,
nftSdkAssetConverter = nftSdkAssetConverter,
)
@ -64,12 +57,11 @@ internal class DefaultNFTRepository @Inject constructor(
refreshCollections(userWalletId, networks)
}
override suspend fun refreshCollections(userWalletId: UserWalletId, networks: List<Network>) {
override suspend fun refreshCollections(userWalletId: UserWalletId, networks: List<Network>) = coroutineScope {
networks.map { network ->
scope.launch {
expireCollections(network)
launch(dispatchers.io) {
Either.catch {
expireCollections(network)
walletManagersFacade.getNFTCollections(userWalletId, network)
}.onLeft {
saveFailedStateInRuntime(
@ -115,9 +107,13 @@ internal class DefaultNFTRepository @Inject constructor(
NFTCollections(
network = network,
content = NFTCollections.Content.Collections(
collections = collections.map { collection ->
collectionConverter.convert(network to collection)
},
collections = collections
.map { collection ->
collectionConverter.convert(network to collection)
}
.filter {
it.id !is NFTCollection.Identifier.Unknown
},
source = StatusSource.ACTUAL,
),
),
@ -169,9 +165,13 @@ internal class DefaultNFTRepository @Inject constructor(
NFTCollections(
network = network,
content = NFTCollections.Content.Collections(
collections = it?.map { collection ->
collectionConverter.convert(network to collection)
},
collections = it
?.map { collection ->
collectionConverter.convert(network to collection)
}
?.filter {
it.id !is NFTCollection.Identifier.Unknown
},
source = StatusSource.CACHE,
),
)
@ -184,7 +184,7 @@ internal class DefaultNFTRepository @Inject constructor(
prices
.mapKeys {
val (assetId, _) = it
nftSdkAssetIdentifierConverter.convert(assetId)
NFTSdkAssetIdentifierConverter.convert(assetId)
}
.mapValues {
val (assetId, price) = it
@ -202,7 +202,7 @@ internal class DefaultNFTRepository @Inject constructor(
getNFTPersistenceStore(network)
.getCollectionsSync()
.orEmpty()
.associateBy(NFTCollection::identifier)
.associateBy(SdkNFTCollection::identifier)
return this.map { updatedCollection ->
if (!storedCollections.containsKey(updatedCollection.identifier)) {

View file

@ -48,5 +48,7 @@ sealed class NFTAsset {
) : Identifier()
data class TON(val tokenAddress: String) : Identifier()
data object Unknown : Identifier()
}
}

View file

@ -14,5 +14,6 @@ data class NFTCollection(
sealed class Identifier {
data class EVM(val tokenAddress: String) : Identifier()
data class TON(val contractAddress: String?) : Identifier()
data object Unknown : Identifier()
}
}