Updated on 2026-08-14
This commit is contained in:
parent
42f0e3f0fe
commit
0bf2decb83
13 changed files with 244 additions and 109 deletions
|
|
@ -9,6 +9,7 @@ import com.tangem.datasource.api.common.adapter.DateTimeAdapter
|
|||
import com.tangem.datasource.api.common.adapter.LocalDateAdapter
|
||||
import com.tangem.datasource.api.common.adapter.addStakeKitEnumFallbackAdapters
|
||||
import com.tangem.datasource.local.config.providers.models.ProviderModel
|
||||
import com.tangem.datasource.local.network.entity.NetworkStatusDM
|
||||
import com.tangem.domain.models.scan.serialization.*
|
||||
import com.tangem.domain.visa.model.VisaActivationRemoteState
|
||||
import com.tangem.domain.visa.model.VisaCardActivationStatus
|
||||
|
|
@ -16,6 +17,7 @@ import dagger.Module
|
|||
import dagger.Provides
|
||||
import dagger.hilt.InstallIn
|
||||
import dagger.hilt.components.SingletonComponent
|
||||
import dev.onenowy.moshipolymorphicadapter.NamePolymorphicAdapterFactory
|
||||
import javax.inject.Singleton
|
||||
|
||||
@Module
|
||||
|
|
@ -38,6 +40,11 @@ class MoshiModule {
|
|||
.add(DateTimeAdapter())
|
||||
.add(VisaActivationRemoteState.jsonAdapter)
|
||||
.add(VisaCardActivationStatus.jsonAdapter)
|
||||
.add(
|
||||
NamePolymorphicAdapterFactory.of(NetworkStatusDM::class.java)
|
||||
.withSubtype(NetworkStatusDM.Verified::class.java, "amounts")
|
||||
.withSubtype(NetworkStatusDM.NoAccount::class.java, "amount_to_create_account"),
|
||||
)
|
||||
.addLast(KotlinJsonAdapterFactory())
|
||||
.addStakeKitEnumFallbackAdapters()
|
||||
.build()
|
||||
|
|
|
|||
|
|
@ -2,9 +2,9 @@ package com.tangem.datasource.local.network
|
|||
|
||||
import androidx.datastore.core.DataStore
|
||||
import com.tangem.datasource.local.datastore.RuntimeDataStore
|
||||
import com.tangem.datasource.local.network.entity.NetworkStatusesByWalletId
|
||||
import com.tangem.datasource.local.network.utils.toDataModel
|
||||
import com.tangem.datasource.local.network.utils.toDomainModel
|
||||
import com.tangem.datasource.local.network.converter.NetworkStatusConverter
|
||||
import com.tangem.datasource.local.network.converter.NetworkStatusDataModelConverter
|
||||
import com.tangem.datasource.local.network.entity.NetworkStatusDM
|
||||
import com.tangem.domain.tokens.model.Network
|
||||
import com.tangem.domain.tokens.model.NetworkStatus
|
||||
import com.tangem.domain.wallets.models.UserWalletId
|
||||
|
|
@ -14,6 +14,8 @@ import kotlinx.coroutines.flow.*
|
|||
import kotlinx.coroutines.sync.Mutex
|
||||
import kotlinx.coroutines.sync.withLock
|
||||
|
||||
private typealias NetworkStatusesByWalletId = Map<String, Set<NetworkStatusDM>>
|
||||
|
||||
internal class DefaultNetworksStatusesStore(
|
||||
private val runtimeDataStore: RuntimeDataStore<Set<NetworkStatus>>,
|
||||
private val persistenceDataStore: DataStore<NetworkStatusesByWalletId>,
|
||||
|
|
@ -33,7 +35,7 @@ internal class DefaultNetworksStatusesStore(
|
|||
.firstOrNull { it.id == status.networkId }
|
||||
?: return@mapNotNullTo null
|
||||
|
||||
status.toDomainModel(network = network, isCached = true)
|
||||
NetworkStatusConverter(network = network, isCached = true).convert(value = status)
|
||||
}
|
||||
.orEmpty()
|
||||
|
||||
|
|
@ -103,16 +105,14 @@ internal class DefaultNetworksStatusesStore(
|
|||
}
|
||||
|
||||
private suspend fun storeNetworkStatusInPersistence(userWalletId: UserWalletId, networkStatus: NetworkStatus) {
|
||||
val status = networkStatus.value
|
||||
val network = networkStatus.network
|
||||
|
||||
if (status !is NetworkStatus.Verified) return
|
||||
|
||||
persistenceDataStore.updateData { storedStatuses ->
|
||||
val userWalletStatuses = storedStatuses[userWalletId.stringValue] ?: emptySet()
|
||||
val updatedStatuses = userWalletStatuses.addOrReplace(item = status.toDataModel(network)) {
|
||||
it.networkId == network.id
|
||||
}
|
||||
val updatedStatuses = userWalletStatuses.addOrReplace(
|
||||
item = NetworkStatusDataModelConverter.convert(value = networkStatus),
|
||||
predicate = { it.networkId == network.id },
|
||||
)
|
||||
|
||||
storedStatuses.toMutableMap().apply {
|
||||
this[userWalletId.stringValue] = updatedStatuses
|
||||
|
|
|
|||
|
|
@ -0,0 +1,60 @@
|
|||
package com.tangem.datasource.local.network.converter
|
||||
|
||||
import com.tangem.datasource.local.network.entity.NetworkStatusDM
|
||||
import com.tangem.domain.tokens.model.NetworkAddress
|
||||
import com.tangem.utils.converter.TwoWayConverter
|
||||
import timber.log.Timber
|
||||
|
||||
/**
|
||||
* Converter from [Set<NetworkStatusDM.Address>] to [NetworkAddress] and vice versa
|
||||
*
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
internal class NetworkAddressConverter(
|
||||
private val selectedAddress: String,
|
||||
) : TwoWayConverter<Set<NetworkStatusDM.Address>, NetworkAddress> {
|
||||
|
||||
override fun convert(value: Set<NetworkStatusDM.Address>): NetworkAddress {
|
||||
val defaultAddress = value
|
||||
.firstOrNull { it.value == selectedAddress }
|
||||
?.let(::toNetworkAddress)
|
||||
|
||||
requireNotNull(defaultAddress) { "Selected address must not be null" }
|
||||
|
||||
return if (value.size != 1) {
|
||||
NetworkAddress.Selectable(
|
||||
defaultAddress = defaultAddress,
|
||||
availableAddresses = value.mapTo(destination = hashSetOf(), transform = ::toNetworkAddress),
|
||||
)
|
||||
} else {
|
||||
NetworkAddress.Single(defaultAddress = defaultAddress)
|
||||
}
|
||||
}
|
||||
|
||||
override fun convertBack(value: NetworkAddress): Set<NetworkStatusDM.Address> {
|
||||
return value.availableAddresses
|
||||
.map { address ->
|
||||
NetworkStatusDM.Address(
|
||||
value = address.value,
|
||||
type = when (address.type) {
|
||||
NetworkAddress.Address.Type.Primary -> NetworkStatusDM.Address.Type.Primary
|
||||
NetworkAddress.Address.Type.Secondary -> NetworkStatusDM.Address.Type.Secondary
|
||||
},
|
||||
)
|
||||
}
|
||||
.toSet()
|
||||
}
|
||||
|
||||
private fun toNetworkAddress(address: NetworkStatusDM.Address): NetworkAddress.Address {
|
||||
val type = when (address.type) {
|
||||
NetworkStatusDM.Address.Type.Primary -> NetworkAddress.Address.Type.Primary
|
||||
NetworkStatusDM.Address.Type.Secondary -> NetworkAddress.Address.Type.Secondary
|
||||
}
|
||||
|
||||
if (address.value.isBlank()) {
|
||||
Timber.w("Address value is blank")
|
||||
}
|
||||
|
||||
return NetworkAddress.Address(value = address.value, type = type)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,35 @@
|
|||
package com.tangem.datasource.local.network.converter
|
||||
|
||||
import com.tangem.common.extensions.mapNotNullValues
|
||||
import com.tangem.domain.tokens.model.CryptoCurrency
|
||||
import com.tangem.domain.tokens.model.CryptoCurrencyAmountStatus
|
||||
import com.tangem.utils.converter.TwoWayConverter
|
||||
import java.math.BigDecimal
|
||||
|
||||
private typealias AmountsDataModel = Map<String, BigDecimal>
|
||||
private typealias AmountsDomainModel = Map<CryptoCurrency.ID, CryptoCurrencyAmountStatus>
|
||||
|
||||
/**
|
||||
* Converter from [AmountsDataModel] to [AmountsDomainModel] and vice versa
|
||||
*
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
internal object NetworkAmountsConverter : TwoWayConverter<AmountsDataModel, AmountsDomainModel> {
|
||||
|
||||
override fun convert(value: AmountsDataModel): AmountsDomainModel {
|
||||
return value
|
||||
.mapKeys { CryptoCurrency.ID.fromValue(value = it.key) }
|
||||
.mapValues { (_, amount) -> CryptoCurrencyAmountStatus.Loaded(value = amount) }
|
||||
}
|
||||
|
||||
override fun convertBack(value: AmountsDomainModel): AmountsDataModel {
|
||||
return value
|
||||
.mapKeys { (id, _) -> id.value }
|
||||
.mapNotNullValues { (_, amount) ->
|
||||
when (amount) {
|
||||
is CryptoCurrencyAmountStatus.Loaded -> amount.value
|
||||
is CryptoCurrencyAmountStatus.NotFound -> null
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,46 @@
|
|||
package com.tangem.datasource.local.network.converter
|
||||
|
||||
import com.tangem.datasource.local.network.entity.NetworkStatusDM
|
||||
import com.tangem.domain.tokens.model.Network
|
||||
import com.tangem.domain.tokens.model.NetworkStatus
|
||||
import com.tangem.utils.converter.Converter
|
||||
|
||||
/**
|
||||
* Converter from [NetworkStatusDM] to [NetworkStatus]
|
||||
*
|
||||
* @property network network
|
||||
* @property isCached flag that determines whether the status is a cache
|
||||
*
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
internal class NetworkStatusConverter(
|
||||
private val network: Network,
|
||||
private val isCached: Boolean,
|
||||
) : Converter<NetworkStatusDM, NetworkStatus> {
|
||||
|
||||
override fun convert(value: NetworkStatusDM): NetworkStatus {
|
||||
val address = NetworkAddressConverter(selectedAddress = value.selectedAddress)
|
||||
.convert(value = value.availableAddresses)
|
||||
|
||||
val status = when (value) {
|
||||
is NetworkStatusDM.Verified -> {
|
||||
NetworkStatus.Verified(
|
||||
address = address,
|
||||
amounts = NetworkAmountsConverter.convert(value = value.amounts),
|
||||
pendingTransactions = mapOf(),
|
||||
isCached = isCached,
|
||||
)
|
||||
}
|
||||
is NetworkStatusDM.NoAccount -> {
|
||||
NetworkStatus.NoAccount(
|
||||
address = address,
|
||||
amountToCreateAccount = value.amountToCreateAccount,
|
||||
errorMessage = value.errorMessage,
|
||||
isCached = isCached,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
return NetworkStatus(network = network, value = status)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,38 @@
|
|||
package com.tangem.datasource.local.network.converter
|
||||
|
||||
import com.tangem.datasource.local.network.entity.NetworkStatusDM
|
||||
import com.tangem.domain.tokens.model.NetworkStatus
|
||||
import com.tangem.utils.converter.Converter
|
||||
|
||||
/**
|
||||
* Converter from [NetworkStatus] to [NetworkStatusDM]
|
||||
*
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
internal object NetworkStatusDataModelConverter : Converter<NetworkStatus, NetworkStatusDM> {
|
||||
|
||||
override fun convert(value: NetworkStatus): NetworkStatusDM {
|
||||
return when (val status = value.value) {
|
||||
is NetworkStatus.Verified -> {
|
||||
NetworkStatusDM.Verified(
|
||||
networkId = value.network.id,
|
||||
selectedAddress = status.address.defaultAddress.value,
|
||||
availableAddresses = NetworkAddressConverter(selectedAddress = status.address.defaultAddress.value)
|
||||
.convertBack(value = status.address),
|
||||
amounts = NetworkAmountsConverter.convertBack(value = status.amounts),
|
||||
)
|
||||
}
|
||||
is NetworkStatus.NoAccount -> {
|
||||
NetworkStatusDM.NoAccount(
|
||||
networkId = value.network.id,
|
||||
selectedAddress = status.address.defaultAddress.value,
|
||||
availableAddresses = NetworkAddressConverter(selectedAddress = status.address.defaultAddress.value)
|
||||
.convertBack(value = status.address),
|
||||
amountToCreateAccount = status.amountToCreateAccount,
|
||||
errorMessage = status.errorMessage,
|
||||
)
|
||||
}
|
||||
else -> error("Unsupported network status: $value")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -3,17 +3,31 @@ package com.tangem.datasource.local.network.entity
|
|||
import com.squareup.moshi.Json
|
||||
import com.squareup.moshi.JsonClass
|
||||
import com.tangem.domain.tokens.model.Network
|
||||
import dev.onenowy.moshipolymorphicadapter.annotations.NameLabel
|
||||
import java.math.BigDecimal
|
||||
|
||||
internal typealias NetworkStatusesByWalletId = Map<String, Set<NetworkStatusDM>>
|
||||
internal sealed interface NetworkStatusDM {
|
||||
|
||||
@JsonClass(generateAdapter = true)
|
||||
internal data class NetworkStatusDM(
|
||||
@Json(name = "network_id") val networkId: Network.ID,
|
||||
@Json(name = "selected_address") val selectedAddress: String,
|
||||
@Json(name = "available_addresses") val availableAddresses: Set<Address>,
|
||||
@Json(name = "amounts") val amounts: Map<String, BigDecimal>,
|
||||
) {
|
||||
val networkId: Network.ID
|
||||
val selectedAddress: String
|
||||
val availableAddresses: Set<Address>
|
||||
|
||||
@NameLabel("amounts")
|
||||
data class Verified(
|
||||
@Json(name = "network_id") override val networkId: Network.ID,
|
||||
@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>,
|
||||
) : NetworkStatusDM
|
||||
|
||||
@NameLabel("amount_to_create_account")
|
||||
data class NoAccount(
|
||||
@Json(name = "network_id") override val networkId: Network.ID,
|
||||
@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 Address(
|
||||
|
|
|
|||
|
|
@ -1,81 +0,0 @@
|
|||
package com.tangem.datasource.local.network.utils
|
||||
|
||||
import com.tangem.common.extensions.mapNotNullValues
|
||||
import com.tangem.datasource.local.network.entity.NetworkStatusDM
|
||||
import com.tangem.domain.tokens.model.*
|
||||
import timber.log.Timber
|
||||
import java.math.BigDecimal
|
||||
|
||||
internal fun NetworkStatus.Verified.toDataModel(network: Network): NetworkStatusDM {
|
||||
return NetworkStatusDM(
|
||||
networkId = network.id,
|
||||
selectedAddress = address.defaultAddress.value,
|
||||
availableAddresses = address.availableAddresses
|
||||
.map { address ->
|
||||
NetworkStatusDM.Address(
|
||||
value = address.value,
|
||||
type = when (address.type) {
|
||||
NetworkAddress.Address.Type.Primary -> NetworkStatusDM.Address.Type.Primary
|
||||
NetworkAddress.Address.Type.Secondary -> NetworkStatusDM.Address.Type.Secondary
|
||||
},
|
||||
)
|
||||
}
|
||||
.toSet(),
|
||||
amounts = amounts
|
||||
.mapKeys { (id, _) -> id.value }
|
||||
.mapNotNullValues { (_, amount) ->
|
||||
when (amount) {
|
||||
is CryptoCurrencyAmountStatus.Loaded -> amount.value
|
||||
is CryptoCurrencyAmountStatus.NotFound -> null
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
internal fun NetworkStatusDM.toDomainModel(network: Network, isCached: Boolean): NetworkStatus {
|
||||
return NetworkStatus(
|
||||
network = network,
|
||||
value = NetworkStatus.Verified(
|
||||
address = mapToDomainAddress(selectedAddress, availableAddresses),
|
||||
amounts = mapToDomainAmounts(amounts),
|
||||
pendingTransactions = mapOf(),
|
||||
),
|
||||
isCached = isCached,
|
||||
)
|
||||
}
|
||||
|
||||
private fun mapToDomainAmounts(amounts: Map<String, BigDecimal>): Map<CryptoCurrency.ID, CryptoCurrencyAmountStatus> {
|
||||
return amounts
|
||||
.mapKeys { CryptoCurrency.ID.fromValue(it.key) }
|
||||
.mapValues { (_, amount) -> CryptoCurrencyAmountStatus.Loaded(amount) }
|
||||
}
|
||||
|
||||
private fun mapToDomainAddress(
|
||||
selectedAddress: String,
|
||||
availableAddresses: Set<NetworkStatusDM.Address>,
|
||||
): NetworkAddress {
|
||||
val defaultAddress = availableAddresses
|
||||
.firstOrNull { it.value == selectedAddress }
|
||||
?.let(::mapToDomainAddress)
|
||||
|
||||
requireNotNull(defaultAddress) { "Selected address must not be null" }
|
||||
|
||||
return if (availableAddresses.size != 1) {
|
||||
NetworkAddress.Selectable(defaultAddress, availableAddresses.mapTo(hashSetOf(), ::mapToDomainAddress))
|
||||
} else {
|
||||
NetworkAddress.Single(defaultAddress)
|
||||
}
|
||||
}
|
||||
|
||||
private fun mapToDomainAddress(address: NetworkStatusDM.Address): NetworkAddress.Address {
|
||||
val type = when (address.type) {
|
||||
NetworkStatusDM.Address.Type.Primary -> NetworkAddress.Address.Type.Primary
|
||||
NetworkStatusDM.Address.Type.Secondary -> NetworkAddress.Address.Type.Secondary
|
||||
}
|
||||
|
||||
if (address.value.isBlank()) {
|
||||
Timber.w("Address value is blank")
|
||||
}
|
||||
|
||||
return NetworkAddress.Address(address.value, type)
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue