Updated on 2026-08-14

This commit is contained in:
Tangem 2025-03-07 17:48:01 +03:00
parent 2e14534db3
commit 66c6e8e34d
5 changed files with 82 additions and 16 deletions

View file

@ -26,6 +26,7 @@ import com.tangem.utils.coroutines.CoroutineDispatcherProvider
import com.tangem.utils.coroutines.runCatching
import com.tangem.utils.isNullOrZero
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.firstOrNull
import kotlinx.coroutines.withContext
@Suppress("LongParameterList")
@ -134,8 +135,10 @@ internal class DefaultRampManager(
userWalletId: UserWalletId,
cryptoCurrency: CryptoCurrency,
): ExchangeableState {
val asset = expressServiceLoader.getInitializationStatus(userWalletId).firstOrNull()
?: return ExchangeableState.Error
return withContext(dispatchers.io) {
when (val asset = expressServiceLoader.getInitializationStatus(userWalletId).value) {
when (asset) {
is Lce.Error -> ExchangeableState.Error
is Lce.Loading -> ExchangeableState.Loading
is Lce.Content -> {

View file

@ -1,12 +1,23 @@
package com.tangem.datasource.di
import android.content.Context
import androidx.datastore.core.DataStoreFactory
import androidx.datastore.dataStoreFile
import com.squareup.moshi.Moshi
import com.tangem.datasource.api.express.models.response.Asset
import com.tangem.datasource.local.datastore.RuntimeDataStore
import com.tangem.datasource.local.token.DefaultExpressAssetsStore
import com.tangem.datasource.local.token.ExpressAssetsStore
import com.tangem.datasource.utils.MoshiDataStoreSerializer
import com.tangem.datasource.utils.mapWithStringKeyTypes
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
import dagger.Module
import dagger.Provides
import dagger.hilt.InstallIn
import dagger.hilt.android.qualifiers.ApplicationContext
import dagger.hilt.components.SingletonComponent
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.SupervisorJob
import javax.inject.Singleton
@Module
@ -15,7 +26,22 @@ internal object ExpressAssetsStoreModule {
@Provides
@Singleton
fun provideExpressAssetsStore(): ExpressAssetsStore {
return DefaultExpressAssetsStore(dataStore = RuntimeDataStore())
fun provideExpressAssetsStore(
@NetworkMoshi moshi: Moshi,
@ApplicationContext context: Context,
dispatchers: CoroutineDispatcherProvider,
): ExpressAssetsStore {
return DefaultExpressAssetsStore(
persistenceStore = DataStoreFactory.create(
serializer = MoshiDataStoreSerializer(
moshi = moshi,
types = mapWithStringKeyTypes<Asset>(),
defaultValue = emptyMap(),
),
produceFile = { context.dataStoreFile("express_assets") },
scope = CoroutineScope(context = dispatchers.io + SupervisorJob()),
),
runtimeStore = RuntimeDataStore(),
)
}
}

View file

@ -12,7 +12,9 @@ import com.tangem.domain.core.utils.lceError
import com.tangem.domain.core.utils.lceLoading
import com.tangem.domain.wallets.models.UserWalletId
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.flow
import kotlinx.coroutines.flow.update
import kotlinx.coroutines.withContext
import timber.log.Timber
@ -41,8 +43,6 @@ internal class DefaultExpressServiceLoader @Inject constructor(
withContext(dispatchers.io) {
val initializationStatus = getInitializationStatusInternal(userWalletId)
initializationStatus.update { lceLoading() }
try {
if (userTokens.isNotEmpty()) {
val response = tangemExpressApi.getAssets(
@ -54,22 +54,24 @@ internal class DefaultExpressServiceLoader @Inject constructor(
initializationStatus.update { response.lceContent() }
}
} catch (e: Throwable) {
initializationStatus.update { e.lceError() }
if (expressAssetsStore.getSyncOrNull(userWalletId) == null) {
initializationStatus.update { e.lceError() }
}
Timber.e(e, "Unable to fetch assets for: ${userWalletId.stringValue}")
}
}
}
override fun getInitializationStatus(userWalletId: UserWalletId): InitializationStatusFlow {
return getInitializationStatusInternal(userWalletId)
override fun getInitializationStatus(userWalletId: UserWalletId): Flow<Lce<Throwable, List<Asset>>> {
return flow { getInitializationStatusInternal(userWalletId).collect { emit(it) } }
}
private fun getInitializationStatusInternal(userWalletId: UserWalletId): InitializationStatusFlow {
private suspend fun getInitializationStatusInternal(userWalletId: UserWalletId): InitializationStatusFlow {
val initializationStatus = initializationStatuses.value.get(key = userWalletId)
if (initializationStatus != null) return initializationStatus
val default: InitializationStatusFlow = MutableStateFlow(value = lceLoading())
val cached = expressAssetsStore.getSyncOrNull(userWalletId)
val default: InitializationStatusFlow = MutableStateFlow(value = cached?.lceContent() ?: lceLoading())
initializationStatuses.update {
it.toMutableMap().apply {

View file

@ -4,7 +4,7 @@ import com.tangem.datasource.api.express.models.request.LeastTokenInfo
import com.tangem.datasource.api.express.models.response.Asset
import com.tangem.domain.core.lce.Lce
import com.tangem.domain.wallets.models.UserWalletId
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.Flow
/**
* Express service loader
@ -17,5 +17,5 @@ interface ExpressServiceLoader {
suspend fun update(userWalletId: UserWalletId, userTokens: List<LeastTokenInfo>)
/** Get initialization status by [userWalletId] */
fun getInitializationStatus(userWalletId: UserWalletId): StateFlow<Lce<Throwable, List<Asset>>>
fun getInitializationStatus(userWalletId: UserWalletId): Flow<Lce<Throwable, List<Asset>>>
}

View file

@ -1,18 +1,53 @@
package com.tangem.datasource.local.token
import androidx.datastore.core.DataStore
import com.tangem.datasource.api.express.models.response.Asset
import com.tangem.datasource.local.datastore.core.StringKeyDataStore
import com.tangem.domain.wallets.models.UserWalletId
import kotlinx.coroutines.coroutineScope
import kotlinx.coroutines.flow.firstOrNull
import kotlinx.coroutines.launch
internal typealias AssetsByWalletId = Map<String, List<Asset>>
internal class DefaultExpressAssetsStore(
private val dataStore: StringKeyDataStore<List<Asset>>,
private val persistenceStore: DataStore<AssetsByWalletId>,
private val runtimeStore: StringKeyDataStore<List<Asset>>,
) : ExpressAssetsStore {
override suspend fun getSyncOrNull(userWalletId: UserWalletId): List<Asset>? {
return dataStore.getSyncOrNull(userWalletId.stringValue)
val runtimeAssets = runtimeStore.getSyncOrNull(userWalletId.stringValue)
if (runtimeAssets != null) {
return runtimeAssets
}
val cachedAssets = getCachedAssets(userWalletId)
return if (cachedAssets != null) {
runtimeStore.store(userWalletId.stringValue, cachedAssets)
cachedAssets
} else {
null
}
}
private suspend fun getCachedAssets(userWalletId: UserWalletId): List<Asset>? {
return persistenceStore.data.firstOrNull().orEmpty()[userWalletId.stringValue]
}
override suspend fun store(userWalletId: UserWalletId, item: List<Asset>) {
dataStore.store(userWalletId.stringValue, item)
coroutineScope {
launch {
runtimeStore.store(userWalletId.stringValue, item)
}
launch {
persistenceStore.updateData {
it.toMutableMap().apply {
put(userWalletId.stringValue, item)
}
}
}
}
}
}