Updated on 2026-08-14

This commit is contained in:
Tangem 2025-02-21 12:25:03 +04:00
parent 9531146e8f
commit 4eb9f62f4c
9 changed files with 156 additions and 55 deletions

View file

@ -2,6 +2,7 @@ package com.tangem.datasource.local.network
import androidx.datastore.core.DataStore
import com.tangem.datasource.local.datastore.RuntimeDataStore
import com.tangem.datasource.local.network.converter.NetworkDerivationPathConverter
import com.tangem.datasource.local.network.converter.NetworkStatusConverter
import com.tangem.datasource.local.network.converter.NetworkStatusDataModelConverter
import com.tangem.datasource.local.network.entity.NetworkStatusDM
@ -13,6 +14,8 @@ import com.tangem.utils.extensions.addOrReplace
import kotlinx.coroutines.coroutineScope
import kotlinx.coroutines.flow.*
import kotlinx.coroutines.launch
import kotlinx.coroutines.sync.Mutex
import kotlinx.coroutines.sync.withLock
private typealias NetworkStatusesByWalletId = Map<String, Set<NetworkStatusDM>>
@ -21,6 +24,8 @@ internal class DefaultNetworksStatusesStore(
private val persistenceDataStore: DataStore<NetworkStatusesByWalletId>,
) : NetworksStatusesStore {
private val mutex = Mutex()
override fun get(key: UserWalletId): Flow<Set<NetworkStatus>> {
return runtimeDataStore.get(provideStringKey(key))
}
@ -28,12 +33,14 @@ internal class DefaultNetworksStatusesStore(
override fun get(key: UserWalletId, networks: Set<Network>): Flow<Set<NetworkStatus>> = channelFlow {
val cachedStatuses = persistenceDataStore.data.firstOrNull()
?.get(key.stringValue)
?.mapNotNullTo(mutableSetOf()) { status ->
val network = networks
.firstOrNull { it.id == status.networkId }
?.mapNotNullTo(mutableSetOf()) { cached ->
val network = networks.firstOrNull {
it.id == cached.networkId &&
it.derivationPath == NetworkDerivationPathConverter.convert(cached.derivationPath)
}
?: return@mapNotNullTo null
NetworkStatusConverter(network = network, isCached = true).convert(value = status)
NetworkStatusConverter(network = network, isCached = true).convert(value = cached)
}
.orEmpty()
@ -71,25 +78,31 @@ internal class DefaultNetworksStatusesStore(
}
override suspend fun storeAll(key: UserWalletId, values: Set<NetworkStatus>) {
coroutineScope {
launch { storeInRuntimeStore(key = key, statuses = values) }
launch { storeInPersistenceStore(userWalletId = key, statuses = values) }
mutex.withLock {
coroutineScope {
launch { storeInRuntimeStore(key = key, statuses = values) }
launch { storeInPersistenceStore(userWalletId = key, statuses = values) }
}
}
}
override suspend fun refresh(key: UserWalletId, networks: Set<Network>) {
val currentStatuses = getSyncOrNull(key).orEmpty()
mutex.withLock {
val currentStatuses = getSyncOrNull(key).orEmpty()
storeInRuntimeStore(
key = key,
statuses = networks.mapNotNullTo(hashSetOf()) { network ->
val status = currentStatuses.firstOrNull { it.network.id == network.id } ?: return@mapNotNullTo null
storeInRuntimeStore(
key = key,
statuses = networks.mapNotNullTo(hashSetOf()) { network ->
val status = currentStatuses.firstOrNull {
it.network.id == network.id && it.network.derivationPath == network.derivationPath
} ?: return@mapNotNullTo null
status.copy(
value = status.value.copySealed(source = StatusSource.CACHE),
)
},
)
status.copy(
value = status.value.copySealed(source = StatusSource.CACHE),
)
},
)
}
}
/**
@ -146,7 +159,9 @@ internal class DefaultNetworksStatusesStore(
persistenceDataStore.updateData { storedStatuses ->
storedStatuses.toMutableMap().apply {
val updatedValues = this[userWalletId.stringValue].orEmpty()
.addOrReplace(newStatuses) { prev, new -> prev.networkId == new.networkId }
.addOrReplace(newStatuses) { prev, new ->
prev.networkId == new.networkId && prev.derivationPath == new.derivationPath
}
this[userWalletId.stringValue] = updatedValues
}

View file

@ -0,0 +1,34 @@
package com.tangem.datasource.local.network.converter
import com.tangem.datasource.local.network.entity.NetworkStatusDM
import com.tangem.datasource.local.network.entity.NetworkStatusDM.DerivationPath.Type
import com.tangem.domain.tokens.model.Network
import com.tangem.utils.converter.TwoWayConverter
/**
* Converter from [NetworkStatusDM.DerivationPath] to [Network.DerivationPath] and vice versa
*
[REDACTED_AUTHOR]
*/
internal object NetworkDerivationPathConverter :
TwoWayConverter<NetworkStatusDM.DerivationPath, Network.DerivationPath> {
override fun convert(value: NetworkStatusDM.DerivationPath): Network.DerivationPath {
return when (value.type) {
Type.CARD -> Network.DerivationPath.Card(value.value)
Type.CUSTOM -> Network.DerivationPath.Custom(value.value)
Type.NONE -> Network.DerivationPath.None
}
}
override fun convertBack(value: Network.DerivationPath): NetworkStatusDM.DerivationPath {
return NetworkStatusDM.DerivationPath(
value = value.value.orEmpty(),
type = when (value) {
is Network.DerivationPath.Card -> Type.CARD
is Network.DerivationPath.Custom -> Type.CUSTOM
Network.DerivationPath.None -> Type.NONE
},
)
}
}

View file

@ -16,6 +16,7 @@ internal object NetworkStatusDataModelConverter : Converter<NetworkStatus, Netwo
is NetworkStatus.Verified -> {
NetworkStatusDM.Verified(
networkId = value.network.id,
derivationPath = NetworkDerivationPathConverter.convertBack(value = value.network.derivationPath),
selectedAddress = status.address.defaultAddress.value,
availableAddresses = NetworkAddressConverter(selectedAddress = status.address.defaultAddress.value)
.convertBack(value = status.address),
@ -25,6 +26,7 @@ internal object NetworkStatusDataModelConverter : Converter<NetworkStatus, Netwo
is NetworkStatus.NoAccount -> {
NetworkStatusDM.NoAccount(
networkId = value.network.id,
derivationPath = NetworkDerivationPathConverter.convertBack(value = value.network.derivationPath),
selectedAddress = status.address.defaultAddress.value,
availableAddresses = NetworkAddressConverter(selectedAddress = status.address.defaultAddress.value)
.convertBack(value = status.address),

View file

@ -9,12 +9,14 @@ import java.math.BigDecimal
internal sealed interface NetworkStatusDM {
val networkId: Network.ID
val derivationPath: DerivationPath
val selectedAddress: String
val availableAddresses: Set<Address>
@NameLabel("amounts")
data class Verified(
@Json(name = "network_id") override val networkId: Network.ID,
@Json(name = "derivation_path") override val derivationPath: DerivationPath,
@Json(name = "selected_address") override val selectedAddress: String,
@Json(name = "available_addresses") override val availableAddresses: Set<Address>,
@Json(name = "amounts") val amounts: Map<String, BigDecimal>,
@ -23,12 +25,33 @@ internal sealed interface NetworkStatusDM {
@NameLabel("amount_to_create_account")
data class NoAccount(
@Json(name = "network_id") override val networkId: Network.ID,
@Json(name = "derivation_path") override val derivationPath: DerivationPath,
@Json(name = "selected_address") override val selectedAddress: String,
@Json(name = "available_addresses") override val availableAddresses: Set<Address>,
@Json(name = "amount_to_create_account") val amountToCreateAccount: BigDecimal,
@Json(name = "error_message") val errorMessage: String,
) : NetworkStatusDM
@JsonClass(generateAdapter = true)
data class DerivationPath(
@Json(name = "value") val value: String,
@Json(name = "type") val type: Type,
) {
@JsonClass(generateAdapter = false)
enum class Type {
@Json(name = "card")
CARD,
@Json(name = "custom")
CUSTOM,
@Json(name = "none")
NONE,
}
}
@JsonClass(generateAdapter = true)
data class Address(
@Json(name = "value") val value: String,