Updated on 2026-08-14
This commit is contained in:
parent
08da87d369
commit
e736f86583
9 changed files with 42 additions and 485 deletions
|
|
@ -1,54 +0,0 @@
|
|||
package com.tangem.datasource.di
|
||||
|
||||
import android.content.Context
|
||||
import androidx.datastore.core.DataStore
|
||||
import androidx.datastore.core.DataStoreFactory
|
||||
import androidx.datastore.dataStoreFile
|
||||
import com.squareup.moshi.Moshi
|
||||
import com.tangem.datasource.api.tangemTech.models.QuotesResponse
|
||||
import com.tangem.datasource.local.datastore.RuntimeSharedStore
|
||||
import com.tangem.datasource.local.quote.DefaultQuotesStore
|
||||
import com.tangem.datasource.local.quote.QuotesStore
|
||||
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
|
||||
@InstallIn(SingletonComponent::class)
|
||||
internal object QuotesStoreModule {
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun providePersistenceQuotesStore(
|
||||
@NetworkMoshi moshi: Moshi,
|
||||
@ApplicationContext context: Context,
|
||||
dispatchers: CoroutineDispatcherProvider,
|
||||
): DataStore<Map<String, QuotesResponse.Quote>> {
|
||||
return DataStoreFactory.create(
|
||||
serializer = MoshiDataStoreSerializer(
|
||||
moshi = moshi,
|
||||
types = mapWithStringKeyTypes<QuotesResponse.Quote>(),
|
||||
defaultValue = emptyMap(),
|
||||
),
|
||||
produceFile = { context.dataStoreFile(fileName = "quotes") },
|
||||
scope = CoroutineScope(context = dispatchers.io + SupervisorJob()),
|
||||
)
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideQuotesStore(persistenceStore: DataStore<Map<String, QuotesResponse.Quote>>): QuotesStore {
|
||||
return DefaultQuotesStore(
|
||||
persistenceStore = persistenceStore,
|
||||
runtimeStore = RuntimeSharedStore(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -1,128 +0,0 @@
|
|||
package com.tangem.datasource.local.quote
|
||||
|
||||
import androidx.datastore.core.DataStore
|
||||
import com.tangem.datasource.api.tangemTech.models.QuotesResponse
|
||||
import com.tangem.datasource.local.datastore.RuntimeSharedStore
|
||||
import com.tangem.datasource.local.quote.converter.QuoteConverter
|
||||
import com.tangem.domain.models.StatusSource
|
||||
import com.tangem.domain.models.currency.CryptoCurrency
|
||||
import com.tangem.domain.tokens.model.Quote
|
||||
import com.tangem.utils.extensions.addOrReplace
|
||||
import kotlinx.coroutines.coroutineScope
|
||||
import kotlinx.coroutines.flow.*
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
internal typealias QuotesByCurrencyId = Map<String, QuotesResponse.Quote>
|
||||
|
||||
/**
|
||||
* Default implementation of [QuotesStore]
|
||||
*
|
||||
* @property persistenceStore persistence quotes store
|
||||
* @property runtimeStore runtime quotes store
|
||||
*/
|
||||
internal class DefaultQuotesStore(
|
||||
private val persistenceStore: DataStore<QuotesByCurrencyId>,
|
||||
private val runtimeStore: RuntimeSharedStore<Set<Quote>>,
|
||||
) : QuotesStore {
|
||||
|
||||
override fun get(currenciesIds: Set<CryptoCurrency.RawID>): Flow<Set<Quote>> = channelFlow {
|
||||
val cachedQuotes = getCachedQuotes(currenciesIds = currenciesIds)
|
||||
|
||||
if (cachedQuotes.isNotEmpty()) {
|
||||
send(cachedQuotes)
|
||||
}
|
||||
|
||||
runtimeStore.get()
|
||||
.onEach {
|
||||
val mergedQuotes = mergeQuotes(
|
||||
currenciesIds = currenciesIds,
|
||||
cachedQuotes = cachedQuotes,
|
||||
runtimeQuotes = it,
|
||||
)
|
||||
|
||||
send(element = mergedQuotes)
|
||||
}
|
||||
.launchIn(scope = this)
|
||||
}
|
||||
|
||||
override suspend fun getSync(currenciesIds: Set<CryptoCurrency.RawID>): Set<Quote> {
|
||||
val runtimeQuotes = runtimeStore.getSyncOrNull()
|
||||
val cachedQuotes = getCachedQuotes(currenciesIds = currenciesIds)
|
||||
|
||||
if (runtimeQuotes.isNullOrEmpty() && cachedQuotes.isEmpty()) return emptySet()
|
||||
|
||||
return mergeQuotes(
|
||||
currenciesIds = currenciesIds,
|
||||
cachedQuotes = cachedQuotes,
|
||||
runtimeQuotes = runtimeQuotes.orEmpty(),
|
||||
)
|
||||
}
|
||||
|
||||
override suspend fun store(response: QuotesResponse) {
|
||||
coroutineScope {
|
||||
launch {
|
||||
storeInRuntimeStore(
|
||||
values = QuoteConverter(isCached = false).convertSet(input = response.quotes.entries),
|
||||
)
|
||||
}
|
||||
launch { storeInPersistenceStore(response = response) }
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun storeEmptyQuotes(currenciesIds: Set<CryptoCurrency.RawID>) {
|
||||
storeInRuntimeStore(values = currenciesIds.map(Quote::Empty).toSet())
|
||||
}
|
||||
|
||||
override suspend fun refresh(currenciesIds: Set<CryptoCurrency.RawID>) {
|
||||
val currentStatuses = getSync(currenciesIds)
|
||||
|
||||
storeInRuntimeStore(
|
||||
values = currentStatuses.mapTo(hashSetOf()) { quote ->
|
||||
when (quote) {
|
||||
is Quote.Empty -> quote
|
||||
is Quote.Value -> quote.copy(source = StatusSource.CACHE)
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
private suspend fun getCachedQuotes(currenciesIds: Set<CryptoCurrency.RawID>): Set<Quote.Value> {
|
||||
val ids = currenciesIds.map(CryptoCurrency.RawID::value).toSet()
|
||||
val cachedQuotes = persistenceStore.data.firstOrNull().orEmpty().filterKeys { it in ids }
|
||||
|
||||
return QuoteConverter(isCached = true).convertSet(input = cachedQuotes.entries)
|
||||
}
|
||||
|
||||
private fun mergeQuotes(
|
||||
currenciesIds: Set<CryptoCurrency.RawID>,
|
||||
cachedQuotes: Set<Quote.Value>,
|
||||
runtimeQuotes: Set<Quote>,
|
||||
): Set<Quote> {
|
||||
return currenciesIds
|
||||
.mapTo(hashSetOf()) { currencyId ->
|
||||
val runtimeQuote = runtimeQuotes.firstOrNull { it.rawCurrencyId == currencyId }
|
||||
|
||||
if (runtimeQuote == null || runtimeQuote is Quote.Empty) {
|
||||
getCachedQuoteIfPossible(cachedStatuses = cachedQuotes, currencyId = currencyId)
|
||||
} else {
|
||||
runtimeQuote
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun getCachedQuoteIfPossible(cachedStatuses: Set<Quote.Value>, currencyId: CryptoCurrency.RawID): Quote {
|
||||
return cachedStatuses.firstOrNull { it.rawCurrencyId == currencyId }
|
||||
?.copy(source = StatusSource.ONLY_CACHE)
|
||||
?: Quote.Empty(currencyId)
|
||||
}
|
||||
|
||||
private suspend fun storeInRuntimeStore(values: Set<Quote>) {
|
||||
runtimeStore.update(default = emptySet()) { saved ->
|
||||
saved.addOrReplace(items = values) { prev, new -> prev.rawCurrencyId == new.rawCurrencyId }
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun storeInPersistenceStore(response: QuotesResponse) {
|
||||
persistenceStore.updateData { storedQuotes -> storedQuotes + response.quotes }
|
||||
}
|
||||
}
|
||||
|
|
@ -1,24 +0,0 @@
|
|||
package com.tangem.datasource.local.quote
|
||||
|
||||
import com.tangem.datasource.api.tangemTech.models.QuotesResponse
|
||||
import com.tangem.domain.models.currency.CryptoCurrency
|
||||
import com.tangem.domain.tokens.model.Quote
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
|
||||
/** Quotes store */
|
||||
interface QuotesStore {
|
||||
|
||||
/** Get flow of quotes for [currenciesIds] */
|
||||
fun get(currenciesIds: Set<CryptoCurrency.RawID>): Flow<Set<Quote>>
|
||||
|
||||
/** Get quotes for [currenciesIds] synchronously */
|
||||
suspend fun getSync(currenciesIds: Set<CryptoCurrency.RawID>): Set<Quote>
|
||||
|
||||
/** Store [response] from remote */
|
||||
suspend fun store(response: QuotesResponse)
|
||||
|
||||
/** Store [Quote.Empty] for [currenciesIds] */
|
||||
suspend fun storeEmptyQuotes(currenciesIds: Set<CryptoCurrency.RawID>)
|
||||
|
||||
suspend fun refresh(currenciesIds: Set<CryptoCurrency.RawID>)
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue