Updated on 2026-08-14
This commit is contained in:
parent
4f5257e537
commit
777a05d716
8 changed files with 112 additions and 79 deletions
|
|
@ -1,12 +1,20 @@
|
|||
package com.tangem.datasource.di
|
||||
|
||||
import com.tangem.datasource.local.datastore.RuntimeDataStore
|
||||
import android.content.Context
|
||||
import androidx.datastore.core.DataStoreFactory
|
||||
import androidx.datastore.dataStoreFile
|
||||
import com.squareup.moshi.Moshi
|
||||
import com.tangem.datasource.local.quote.DefaultQuotesStore
|
||||
import com.tangem.datasource.local.quote.QuotesStore
|
||||
import com.tangem.datasource.local.quote.utils.QuotesSerializer
|
||||
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,9 +23,17 @@ internal object QuotesStoreModule {
|
|||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideQuotesStore(): QuotesStore {
|
||||
fun provideQuotesStore(
|
||||
@NetworkMoshi moshi: Moshi,
|
||||
@ApplicationContext context: Context,
|
||||
dispatchers: CoroutineDispatcherProvider,
|
||||
): QuotesStore {
|
||||
return DefaultQuotesStore(
|
||||
dataStore = RuntimeDataStore(),
|
||||
dataStore = DataStoreFactory.create(
|
||||
serializer = QuotesSerializer(moshi),
|
||||
produceFile = { context.dataStoreFile(fileName = "quotes") },
|
||||
scope = CoroutineScope(context = dispatchers.io + SupervisorJob()),
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -1,42 +1,55 @@
|
|||
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.core.StringKeyDataStore
|
||||
import com.tangem.datasource.local.quote.model.StoredQuote
|
||||
import com.tangem.datasource.local.quote.model.QuoteDM
|
||||
import com.tangem.datasource.local.quote.model.QuotesDM
|
||||
import com.tangem.domain.tokens.model.CryptoCurrency
|
||||
import com.tangem.utils.extensions.addOrReplace
|
||||
import kotlinx.coroutines.flow.*
|
||||
import com.tangem.domain.tokens.model.Quote
|
||||
import com.tangem.utils.extensions.orZero
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.firstOrNull
|
||||
import kotlinx.coroutines.flow.map
|
||||
|
||||
internal class DefaultQuotesStore(
|
||||
private val dataStore: StringKeyDataStore<StoredQuote>,
|
||||
private val dataStore: DataStore<QuotesDM>,
|
||||
) : QuotesStore {
|
||||
|
||||
override fun get(currenciesIds: Set<CryptoCurrency.RawID>): Flow<Set<StoredQuote>> {
|
||||
return channelFlow {
|
||||
val flows = currenciesIds.map { currencyId -> dataStore.get(currencyId.value) }
|
||||
override fun get(currenciesIds: Set<CryptoCurrency.RawID>): Flow<Set<Quote>> {
|
||||
return dataStore.data
|
||||
.map { quotes -> createQuotes(currenciesIds, quotes) }
|
||||
}
|
||||
|
||||
if (dataStore.isEmpty() || flows.isEmpty()) {
|
||||
send(emptySet())
|
||||
}
|
||||
override suspend fun getSync(currenciesIds: Set<CryptoCurrency.RawID>): Set<Quote> {
|
||||
val quotes = dataStore.data.firstOrNull().orEmpty()
|
||||
|
||||
merge(*flows.toTypedArray())
|
||||
.scan<StoredQuote, Set<StoredQuote>>(emptySet()) { acc, quote ->
|
||||
acc.addOrReplace(quote) { it.rawCurrencyId == quote.rawCurrencyId }
|
||||
return createQuotes(currenciesIds, quotes)
|
||||
}
|
||||
|
||||
private fun createQuotes(currenciesIds: Set<CryptoCurrency.RawID>, quoteEntities: Set<QuoteDM>): Set<Quote> =
|
||||
currenciesIds.mapTo(mutableSetOf()) { id ->
|
||||
quoteEntities.firstOrNull { it.rawCurrencyId == id }
|
||||
?.let { quote ->
|
||||
Quote.Value(
|
||||
rawCurrencyId = id,
|
||||
fiatRate = quote.fiatRate,
|
||||
priceChange = quote.priceChange,
|
||||
)
|
||||
}
|
||||
.filter(Set<StoredQuote>::isNotEmpty)
|
||||
.collect(::send)
|
||||
?: Quote.Empty(id)
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun getSync(currenciesIds: Set<CryptoCurrency.RawID>): Set<StoredQuote> {
|
||||
return currenciesIds.mapNotNull { currencyId -> dataStore.getSyncOrNull(currencyId.value) }.toSet()
|
||||
}
|
||||
|
||||
override suspend fun store(response: QuotesResponse) {
|
||||
val quotes = response.quotes.mapValues { (id, quote) ->
|
||||
StoredQuote(id, quote)
|
||||
val newQuotes = response.quotes.mapTo(mutableSetOf()) { (currencyId, quote) ->
|
||||
QuoteDM(
|
||||
rawCurrencyId = CryptoCurrency.RawID(currencyId),
|
||||
fiatRate = quote.price.orZero(),
|
||||
priceChange = quote.priceChange24h.orZero().movePointLeft(2),
|
||||
)
|
||||
}
|
||||
|
||||
dataStore.store(quotes)
|
||||
dataStore.updateData { storedQuotes ->
|
||||
(newQuotes + storedQuotes).distinctBy { it.rawCurrencyId }.toSet()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,15 +1,15 @@
|
|||
package com.tangem.datasource.local.quote
|
||||
|
||||
import com.tangem.datasource.api.tangemTech.models.QuotesResponse
|
||||
import com.tangem.datasource.local.quote.model.StoredQuote
|
||||
import com.tangem.domain.tokens.model.CryptoCurrency
|
||||
import com.tangem.domain.tokens.model.Quote
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
|
||||
interface QuotesStore {
|
||||
|
||||
fun get(currenciesIds: Set<CryptoCurrency.RawID>): Flow<Set<StoredQuote>>
|
||||
fun get(currenciesIds: Set<CryptoCurrency.RawID>): Flow<Set<Quote>>
|
||||
|
||||
suspend fun getSync(currenciesIds: Set<CryptoCurrency.RawID>): Set<StoredQuote>
|
||||
suspend fun getSync(currenciesIds: Set<CryptoCurrency.RawID>): Set<Quote>
|
||||
|
||||
suspend fun store(response: QuotesResponse)
|
||||
}
|
||||
|
|
@ -0,0 +1,14 @@
|
|||
package com.tangem.datasource.local.quote.model
|
||||
|
||||
import com.squareup.moshi.JsonClass
|
||||
import com.tangem.domain.tokens.model.CryptoCurrency
|
||||
import java.math.BigDecimal
|
||||
|
||||
internal typealias QuotesDM = Set<QuoteDM>
|
||||
|
||||
@JsonClass(generateAdapter = true)
|
||||
internal data class QuoteDM(
|
||||
val rawCurrencyId: CryptoCurrency.RawID,
|
||||
val fiatRate: BigDecimal,
|
||||
val priceChange: BigDecimal,
|
||||
)
|
||||
|
|
@ -1,9 +0,0 @@
|
|||
package com.tangem.datasource.local.quote.model
|
||||
|
||||
import com.squareup.moshi.Json
|
||||
import com.tangem.datasource.api.tangemTech.models.QuotesResponse
|
||||
|
||||
data class StoredQuote(
|
||||
@Json(name = "rawCurrencyId") val rawCurrencyId: String,
|
||||
@Json(name = "quote") val quote: QuotesResponse.Quote,
|
||||
)
|
||||
|
|
@ -0,0 +1,32 @@
|
|||
package com.tangem.datasource.local.quote.utils
|
||||
|
||||
import androidx.datastore.core.Serializer
|
||||
import com.squareup.moshi.Moshi
|
||||
import com.squareup.moshi.Types
|
||||
import com.tangem.datasource.local.quote.model.QuoteDM
|
||||
import com.tangem.datasource.local.quote.model.QuotesDM
|
||||
import java.io.InputStream
|
||||
import java.io.OutputStream
|
||||
|
||||
internal class QuotesSerializer(moshi: Moshi) : Serializer<QuotesDM> {
|
||||
|
||||
private val adapter by lazy {
|
||||
val types = Types.newParameterizedType(Set::class.java, QuoteDM::class.java)
|
||||
|
||||
moshi.adapter<QuotesDM>(types)
|
||||
}
|
||||
|
||||
override val defaultValue: QuotesDM = emptySet()
|
||||
|
||||
override suspend fun readFrom(input: InputStream): QuotesDM {
|
||||
return input.bufferedReader().use { reader ->
|
||||
adapter.fromJson(reader.readText()) ?: defaultValue
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun writeTo(t: QuotesDM, output: OutputStream) {
|
||||
output.bufferedWriter().use { write ->
|
||||
write.write(adapter.toJson(t))
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue