From a398620b0d9331fb95d560872486a446c8a1dd21 Mon Sep 17 00:00:00 2001 From: Tangem Date: Sat, 5 Apr 2025 12:19:37 +0400 Subject: [PATCH] Updated on 2026-08-14 --- common/test/build.gradle.kts | 3 + .../data/quote/MockQuoteResponseFactory.kt | 19 +++ .../test/data/quote/QuoteResponseExt.kt | 14 ++ .../local/quote/converter/QuoteConverter.kt | 17 ++- ...NetworksStatusesStoreInitializationTest.kt | 2 +- data/quotes/.gitignore | 1 + data/quotes/build.gradle.kts | 30 ++++ .../data/quotes/store/DefaultQuotesStoreV2.kt | 89 ++++++++++++ .../tangem/data/quotes/store/QuotesStoreV2.kt | 22 +++ .../quotes/store/QuotesStoreGetMethodTest.kt | 63 +++++++++ .../store/QuotesStoreInitializationTest.kt | 82 +++++++++++ .../store/QuotesStoreUpdateMethodsTest.kt | 131 ++++++++++++++++++ .../com/tangem/domain/tokens/model/Quote.kt | 7 + settings.gradle.kts | 1 + 14 files changed, 477 insertions(+), 4 deletions(-) create mode 100644 common/test/src/main/java/com/tangem/common/test/data/quote/MockQuoteResponseFactory.kt create mode 100644 common/test/src/main/java/com/tangem/common/test/data/quote/QuoteResponseExt.kt create mode 100644 data/quotes/.gitignore create mode 100644 data/quotes/build.gradle.kts create mode 100644 data/quotes/src/main/java/com/tangem/data/quotes/store/DefaultQuotesStoreV2.kt create mode 100644 data/quotes/src/main/java/com/tangem/data/quotes/store/QuotesStoreV2.kt create mode 100644 data/quotes/src/test/java/com/tangem/data/quotes/store/QuotesStoreGetMethodTest.kt create mode 100644 data/quotes/src/test/java/com/tangem/data/quotes/store/QuotesStoreInitializationTest.kt create mode 100644 data/quotes/src/test/java/com/tangem/data/quotes/store/QuotesStoreUpdateMethodsTest.kt diff --git a/common/test/build.gradle.kts b/common/test/build.gradle.kts index 00c1510373..4ab99487fb 100644 --- a/common/test/build.gradle.kts +++ b/common/test/build.gradle.kts @@ -9,6 +9,9 @@ android { } dependencies { + implementation(projects.core.datasource) + implementation(projects.core.utils) + implementation(projects.data.common) implementation(projects.domain.legacy) diff --git a/common/test/src/main/java/com/tangem/common/test/data/quote/MockQuoteResponseFactory.kt b/common/test/src/main/java/com/tangem/common/test/data/quote/MockQuoteResponseFactory.kt new file mode 100644 index 0000000000..2e3f56492e --- /dev/null +++ b/common/test/src/main/java/com/tangem/common/test/data/quote/MockQuoteResponseFactory.kt @@ -0,0 +1,19 @@ +package com.tangem.common.test.data.quote + +import com.tangem.datasource.api.tangemTech.models.QuotesResponse +import java.math.BigDecimal + +/** +[REDACTED_AUTHOR] + */ +object MockQuoteResponseFactory { + + fun createSinglePrice(value: BigDecimal): QuotesResponse.Quote { + return QuotesResponse.Quote( + price = value, + priceChange24h = value, + priceChange1w = value, + priceChange30d = value, + ) + } +} \ No newline at end of file diff --git a/common/test/src/main/java/com/tangem/common/test/data/quote/QuoteResponseExt.kt b/common/test/src/main/java/com/tangem/common/test/data/quote/QuoteResponseExt.kt new file mode 100644 index 0000000000..ac570f6d81 --- /dev/null +++ b/common/test/src/main/java/com/tangem/common/test/data/quote/QuoteResponseExt.kt @@ -0,0 +1,14 @@ +package com.tangem.common.test.data.quote + +import com.tangem.datasource.api.tangemTech.models.QuotesResponse +import com.tangem.datasource.local.quote.converter.QuoteConverter +import com.tangem.domain.models.StatusSource +import com.tangem.domain.tokens.model.Quote + +fun QuotesResponse.Quote.toDomain(rawCurrencyId: String, source: StatusSource = StatusSource.ACTUAL): Quote { + return QuoteConverter(source = source).convert(value = mapOf(rawCurrencyId to this).entries.first()) +} + +fun Pair.toDomain(source: StatusSource = StatusSource.ACTUAL): Quote { + return QuoteConverter(source = source).convert(value = mapOf(this).entries.first()) +} \ No newline at end of file diff --git a/core/datasource/src/main/java/com/tangem/datasource/local/quote/converter/QuoteConverter.kt b/core/datasource/src/main/java/com/tangem/datasource/local/quote/converter/QuoteConverter.kt index 120bea1730..54af10bcb2 100644 --- a/core/datasource/src/main/java/com/tangem/datasource/local/quote/converter/QuoteConverter.kt +++ b/core/datasource/src/main/java/com/tangem/datasource/local/quote/converter/QuoteConverter.kt @@ -10,13 +10,24 @@ import com.tangem.utils.extensions.orZero /** * Converter from [QuotesResponse.Quote] to [Quote.Value] * - * @property isCached flag that determines whether the quote is a cache + * @property source status source * [REDACTED_AUTHOR] */ -internal class QuoteConverter(private val isCached: Boolean) : +class QuoteConverter( + private val source: StatusSource, +) : Converter, Quote.Value> { + /** + * Secondary constructor + * + * @param isCached flag that determines whether the quote is a cache + */ + constructor(isCached: Boolean) : this( + source = if (isCached) StatusSource.CACHE else StatusSource.ACTUAL, + ) + override fun convert(value: Map.Entry): Quote.Value { val (currencyId, quote) = value @@ -24,7 +35,7 @@ internal class QuoteConverter(private val isCached: Boolean) : rawCurrencyId = CryptoCurrency.RawID(currencyId), fiatRate = quote.price.orZero(), priceChange = quote.priceChange24h.orZero().movePointLeft(2), - source = if (isCached) StatusSource.CACHE else StatusSource.ACTUAL, + source = source, ) } } \ No newline at end of file diff --git a/data/networks/src/test/java/com/tangem/data/networks/store/NetworksStatusesStoreInitializationTest.kt b/data/networks/src/test/java/com/tangem/data/networks/store/NetworksStatusesStoreInitializationTest.kt index 08314ee68e..94d819b827 100644 --- a/data/networks/src/test/java/com/tangem/data/networks/store/NetworksStatusesStoreInitializationTest.kt +++ b/data/networks/src/test/java/com/tangem/data/networks/store/NetworksStatusesStoreInitializationTest.kt @@ -31,7 +31,7 @@ internal class NetworksStatusesStoreInitializationTest { DefaultNetworksStatusesStoreV2( runtimeStore = runtimeStore, - persistenceDataStore = persistenceStore, // local mock + persistenceDataStore = persistenceStore, dispatchers = TestingCoroutineDispatcherProvider(), ) diff --git a/data/quotes/.gitignore b/data/quotes/.gitignore new file mode 100644 index 0000000000..42afabfd2a --- /dev/null +++ b/data/quotes/.gitignore @@ -0,0 +1 @@ +/build \ No newline at end of file diff --git a/data/quotes/build.gradle.kts b/data/quotes/build.gradle.kts new file mode 100644 index 0000000000..56a4b96552 --- /dev/null +++ b/data/quotes/build.gradle.kts @@ -0,0 +1,30 @@ +plugins { + alias(deps.plugins.android.library) + alias(deps.plugins.kotlin.android) + alias(deps.plugins.kotlin.kapt) + id("configuration") +} + +android { + namespace = "com.tangem.data.quotes" +} + +dependencies { + implementation(projects.core.datasource) + implementation(projects.core.utils) + + implementation(projects.domain.models) + implementation(projects.domain.tokens.models) + implementation(projects.domain.wallets.models) + + implementation(deps.androidx.datastore) + + implementation(deps.hilt.android) + kapt(deps.hilt.kapt) + + testImplementation(deps.test.coroutine) + testImplementation(deps.test.junit) + testImplementation(deps.test.mockk) + testImplementation(deps.test.truth) + testImplementation(projects.common.test) +} \ No newline at end of file diff --git a/data/quotes/src/main/java/com/tangem/data/quotes/store/DefaultQuotesStoreV2.kt b/data/quotes/src/main/java/com/tangem/data/quotes/store/DefaultQuotesStoreV2.kt new file mode 100644 index 0000000000..2ae06d88e6 --- /dev/null +++ b/data/quotes/src/main/java/com/tangem/data/quotes/store/DefaultQuotesStoreV2.kt @@ -0,0 +1,89 @@ +package com.tangem.data.quotes.store + +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.tokens.model.CryptoCurrency +import com.tangem.domain.tokens.model.Quote +import com.tangem.utils.coroutines.CoroutineDispatcherProvider +import com.tangem.utils.extensions.addOrReplace +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.SupervisorJob +import kotlinx.coroutines.coroutineScope +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.firstOrNull +import kotlinx.coroutines.launch + +internal typealias CurrencyIdWithQuote = Map + +/** + * Default implementation of [QuotesStoreV2] + * + * @property runtimeStore runtime store + * @property persistenceDataStore persistence store + * @param dispatchers dispatchers + */ +internal class DefaultQuotesStoreV2( + private val runtimeStore: RuntimeSharedStore>, + private val persistenceDataStore: DataStore, + dispatchers: CoroutineDispatcherProvider, +) : QuotesStoreV2 { + + private val scope = CoroutineScope(context = SupervisorJob() + dispatchers.io) + + init { + scope.launch { + val cachedStatuses = persistenceDataStore.data.firstOrNull() + + if (cachedStatuses.isNullOrEmpty()) return@launch + + runtimeStore.store( + value = QuoteConverter(isCached = true).convertSet(input = cachedStatuses.entries), + ) + } + } + + override fun get(): Flow> = runtimeStore.get() + + override suspend fun refresh(currenciesIds: Set) { + updateStatusSourceInRuntime(currenciesIds = currenciesIds, source = StatusSource.CACHE) + } + + override suspend fun storeActual(values: Map) { + coroutineScope { + launch { + val quotes = QuoteConverter(isCached = false).convertSet(input = values.entries) + storeInRuntimeStore(values = quotes) + } + launch { storeInPersistenceStore(values = values) } + } + } + + override suspend fun storeError(currenciesIds: Set) { + updateStatusSourceInRuntime(currenciesIds = currenciesIds, source = StatusSource.ONLY_CACHE) + } + + private suspend fun updateStatusSourceInRuntime(currenciesIds: Set, source: StatusSource) { + runtimeStore.update(default = emptySet()) { stored -> + val updatedQuotes = currenciesIds.mapTo(hashSetOf()) { id -> + val quote = stored.firstOrNull { it.rawCurrencyId == id } ?: Quote.Empty(id) + + quote.copySealed(source = source) + } + + stored.addOrReplace(items = updatedQuotes) { old, new -> old.rawCurrencyId == new.rawCurrencyId } + } + } + + private suspend fun storeInRuntimeStore(values: Set) { + runtimeStore.update(default = emptySet()) { saved -> + saved.addOrReplace(items = values) { prev, new -> prev.rawCurrencyId == new.rawCurrencyId } + } + } + + private suspend fun storeInPersistenceStore(values: Map) { + persistenceDataStore.updateData { storedQuotes -> storedQuotes + values } + } +} \ No newline at end of file diff --git a/data/quotes/src/main/java/com/tangem/data/quotes/store/QuotesStoreV2.kt b/data/quotes/src/main/java/com/tangem/data/quotes/store/QuotesStoreV2.kt new file mode 100644 index 0000000000..b530a6559c --- /dev/null +++ b/data/quotes/src/main/java/com/tangem/data/quotes/store/QuotesStoreV2.kt @@ -0,0 +1,22 @@ +package com.tangem.data.quotes.store + +import com.tangem.datasource.api.tangemTech.models.QuotesResponse +import com.tangem.domain.tokens.model.CryptoCurrency +import com.tangem.domain.tokens.model.Quote +import kotlinx.coroutines.flow.Flow + +/** Store of [Quote]'es set */ +internal interface QuotesStoreV2 { + + /** Get flow of quotes */ + fun get(): Flow> + + /** Refresh status of [currenciesIds] */ + suspend fun refresh(currenciesIds: Set) + + /** Store actual map of currency ids and quotes [values] */ + suspend fun storeActual(values: Map) + + /** Store error for [currenciesIds] */ + suspend fun storeError(currenciesIds: Set) +} \ No newline at end of file diff --git a/data/quotes/src/test/java/com/tangem/data/quotes/store/QuotesStoreGetMethodTest.kt b/data/quotes/src/test/java/com/tangem/data/quotes/store/QuotesStoreGetMethodTest.kt new file mode 100644 index 0000000000..ede1171964 --- /dev/null +++ b/data/quotes/src/test/java/com/tangem/data/quotes/store/QuotesStoreGetMethodTest.kt @@ -0,0 +1,63 @@ +package com.tangem.data.quotes.store + +import com.google.common.truth.Truth +import com.tangem.common.test.data.quote.MockQuoteResponseFactory +import com.tangem.common.test.data.quote.toDomain +import com.tangem.common.test.datastore.MockStateDataStore +import com.tangem.common.test.utils.getEmittedValues +import com.tangem.datasource.local.datastore.RuntimeSharedStore +import com.tangem.domain.tokens.model.Quote +import com.tangem.utils.coroutines.TestingCoroutineDispatcherProvider +import kotlinx.coroutines.test.runTest +import org.junit.Test +import java.math.BigDecimal + +/** +[REDACTED_AUTHOR] + */ +internal class QuotesStoreGetMethodTest { + + private val runtimeStore = RuntimeSharedStore>() + private val persistenceStore = MockStateDataStore(default = emptyMap()) + + private val store = DefaultQuotesStoreV2( + runtimeStore = runtimeStore, + persistenceDataStore = persistenceStore, + dispatchers = TestingCoroutineDispatcherProvider(), + ) + + @Test + fun `test get if runtime store is empty`() = runTest { + val actual = store.get() + + val values = backgroundScope.getEmittedValues(testScheduler, actual) + + Truth.assertThat(values).isEqualTo(emptyList>()) + } + + @Test + fun `test get if runtime store contains empty set`() = runTest { + runtimeStore.store(value = emptySet()) + + val actual = store.get() + + val values = backgroundScope.getEmittedValues(testScheduler, actual) + + Truth.assertThat(values).isEqualTo(listOf(emptySet())) + } + + @Test + fun `test get if runtime store is not empty`() = runTest { + val btcQuote = "BTC" to MockQuoteResponseFactory.createSinglePrice(BigDecimal.ZERO) + val ethQuote = "ETH" to MockQuoteResponseFactory.createSinglePrice(BigDecimal.ONE) + + runtimeStore.store(value = setOf(btcQuote.toDomain(), ethQuote.toDomain())) + + val actual = store.get() + + val values = backgroundScope.getEmittedValues(testScheduler, actual) + + Truth.assertThat(values.size).isEqualTo(1) + Truth.assertThat(values).isEqualTo(listOf(setOf(btcQuote.toDomain(), ethQuote.toDomain()))) + } +} \ No newline at end of file diff --git a/data/quotes/src/test/java/com/tangem/data/quotes/store/QuotesStoreInitializationTest.kt b/data/quotes/src/test/java/com/tangem/data/quotes/store/QuotesStoreInitializationTest.kt new file mode 100644 index 0000000000..2452a66004 --- /dev/null +++ b/data/quotes/src/test/java/com/tangem/data/quotes/store/QuotesStoreInitializationTest.kt @@ -0,0 +1,82 @@ +package com.tangem.data.quotes.store + +import androidx.datastore.core.DataStore +import com.google.common.truth.Truth +import com.tangem.common.test.data.quote.MockQuoteResponseFactory +import com.tangem.common.test.data.quote.toDomain +import com.tangem.common.test.datastore.MockStateDataStore +import com.tangem.datasource.local.datastore.RuntimeSharedStore +import com.tangem.domain.models.StatusSource +import com.tangem.domain.tokens.model.Quote +import com.tangem.utils.coroutines.TestingCoroutineDispatcherProvider +import io.mockk.every +import io.mockk.mockk +import kotlinx.coroutines.flow.emptyFlow +import kotlinx.coroutines.test.runTest +import org.junit.Test +import java.math.BigDecimal + +/** +[REDACTED_AUTHOR] + */ +internal class QuotesStoreInitializationTest { + + @Test + fun `test initialization if cache store is empty`() = runTest { + val runtimeStore = RuntimeSharedStore>() + val persistenceStore: DataStore = mockk() + + every { persistenceStore.data } returns emptyFlow() + + DefaultQuotesStoreV2( + runtimeStore = runtimeStore, + persistenceDataStore = persistenceStore, + dispatchers = TestingCoroutineDispatcherProvider(), + ) + + Truth.assertThat(runtimeStore.getSyncOrNull()).isEqualTo(null) + } + + @Test + fun `test initialization if cache store contains empty map`() = runTest { + val runtimeStore = RuntimeSharedStore>() + val persistenceStore = MockStateDataStore(default = emptyMap()) + + DefaultQuotesStoreV2( + runtimeStore = runtimeStore, + persistenceDataStore = persistenceStore, + dispatchers = TestingCoroutineDispatcherProvider(), + ) + + Truth.assertThat(runtimeStore.getSyncOrNull()).isEqualTo(null) + } + + @Test + fun `test initialization if cache store is not empty`() = runTest { + val runtimeStore = RuntimeSharedStore>() + val persistenceStore = MockStateDataStore(default = emptyMap()) + + val btcQuote = "BTC" to MockQuoteResponseFactory.createSinglePrice(BigDecimal.ZERO) + val ethQuote = "ETH" to MockQuoteResponseFactory.createSinglePrice(BigDecimal.ONE) + + persistenceStore.updateData { + it.toMutableMap().apply { + this += btcQuote + this += ethQuote + } + } + + DefaultQuotesStoreV2( + runtimeStore = runtimeStore, + persistenceDataStore = persistenceStore, + dispatchers = TestingCoroutineDispatcherProvider(), + ) + + val expected = setOf( + btcQuote.toDomain(source = StatusSource.CACHE), + ethQuote.toDomain(source = StatusSource.CACHE), + ) + + Truth.assertThat(runtimeStore.getSyncOrNull()).isEqualTo(expected) + } +} \ No newline at end of file diff --git a/data/quotes/src/test/java/com/tangem/data/quotes/store/QuotesStoreUpdateMethodsTest.kt b/data/quotes/src/test/java/com/tangem/data/quotes/store/QuotesStoreUpdateMethodsTest.kt new file mode 100644 index 0000000000..407b640fd9 --- /dev/null +++ b/data/quotes/src/test/java/com/tangem/data/quotes/store/QuotesStoreUpdateMethodsTest.kt @@ -0,0 +1,131 @@ +package com.tangem.data.quotes.store + +import com.google.common.truth.Truth +import com.tangem.common.test.data.quote.MockQuoteResponseFactory +import com.tangem.common.test.data.quote.toDomain +import com.tangem.common.test.datastore.MockStateDataStore +import com.tangem.datasource.local.datastore.RuntimeSharedStore +import com.tangem.domain.models.StatusSource +import com.tangem.domain.tokens.model.CryptoCurrency +import com.tangem.domain.tokens.model.Quote +import com.tangem.utils.coroutines.TestingCoroutineDispatcherProvider +import kotlinx.coroutines.flow.firstOrNull +import kotlinx.coroutines.test.runTest +import org.junit.Test +import java.math.BigDecimal + +/** +[REDACTED_AUTHOR] + */ +internal class QuotesStoreUpdateMethodsTest { + + private val runtimeStore = RuntimeSharedStore>() + private val persistenceStore = MockStateDataStore(default = emptyMap()) + + private val store = DefaultQuotesStoreV2( + runtimeStore = runtimeStore, + persistenceDataStore = persistenceStore, + dispatchers = TestingCoroutineDispatcherProvider(), + ) + + @Test + fun `refresh if runtime store is empty`() = runTest { + val currenciesIds = setOf( + CryptoCurrency.RawID(value = "BTC"), + CryptoCurrency.RawID(value = "ETH"), + ) + + store.refresh(currenciesIds = currenciesIds) + + val runtimeExpected = currenciesIds.map(Quote::Empty).toSet() + + Truth.assertThat(runtimeStore.getSyncOrNull()).isEqualTo(runtimeExpected) + Truth.assertThat(persistenceStore.data.firstOrNull()).isEqualTo(emptyMap>()) + } + + @Test + fun `refresh if runtime store contains quote with this id`() = runTest { + val quote = MockQuoteResponseFactory.createSinglePrice(value = BigDecimal.ONE) + .toDomain(rawCurrencyId = "BTC", source = StatusSource.ACTUAL) + + runtimeStore.store(value = setOf(quote)) + + store.refresh(currenciesIds = setOf(quote.rawCurrencyId)) + + val runtimeExpected = setOf(quote.copySealed(source = StatusSource.CACHE)) + + Truth.assertThat(runtimeStore.getSyncOrNull()).isEqualTo(runtimeExpected) + Truth.assertThat(persistenceStore.data.firstOrNull()).isEqualTo(emptyMap>()) + } + + @Test + fun `store actual if runtime and cache stores contain quotes with this id`() = runTest { + val prevStatus = MockQuoteResponseFactory.createSinglePrice(value = BigDecimal.ONE) + + runtimeStore.store( + value = setOf( + prevStatus.toDomain(rawCurrencyId = "BTC", source = StatusSource.ONLY_CACHE), + ), + ) + + persistenceStore.updateData { + it.toMutableMap().apply { + put("BTC", prevStatus) + } + } + + val newStatus = MockQuoteResponseFactory.createSinglePrice(value = BigDecimal.TEN) + + store.storeActual(values = mapOf("BTC" to newStatus)) + + val runtimeExpected = setOf( + newStatus.toDomain(rawCurrencyId = "BTC", source = StatusSource.ACTUAL), + ) + val persistenceExpected = mapOf("BTC" to newStatus) + + Truth.assertThat(runtimeStore.getSyncOrNull()).isEqualTo(runtimeExpected) + Truth.assertThat(persistenceStore.data.firstOrNull()).isEqualTo(persistenceExpected) + } + + @Test + fun `store error if runtime store is empty`() = runTest { + val currenciesIds = setOf( + CryptoCurrency.RawID(value = "BTC"), + CryptoCurrency.RawID(value = "ETH"), + ) + + store.storeError(currenciesIds = currenciesIds) + + val runtimeExpected = currenciesIds.map(Quote::Empty).toSet() + + Truth.assertThat(runtimeStore.getSyncOrNull()).isEqualTo(runtimeExpected) + Truth.assertThat(persistenceStore.data.firstOrNull()).isEqualTo(emptyMap>()) + } + + @Test + fun `store error if runtime store contains status with this network`() = runTest { + val status = MockQuoteResponseFactory.createSinglePrice(value = BigDecimal.ONE) + + runtimeStore.store( + value = setOf( + status.toDomain(rawCurrencyId = "BTC", source = StatusSource.CACHE), + Quote.Empty(rawCurrencyId = CryptoCurrency.RawID(value = "ETH")), + ), + ) + + store.storeError( + currenciesIds = setOf( + CryptoCurrency.RawID(value = "BTC"), + CryptoCurrency.RawID(value = "ETH"), + ), + ) + + val runtimeExpected = setOf( + status.toDomain(rawCurrencyId = "BTC", source = StatusSource.ONLY_CACHE), + Quote.Empty(rawCurrencyId = CryptoCurrency.RawID(value = "ETH")), + ) + + Truth.assertThat(runtimeStore.getSyncOrNull()).isEqualTo(runtimeExpected) + Truth.assertThat(persistenceStore.data.firstOrNull()).isEqualTo(emptyMap>()) + } +} \ No newline at end of file diff --git a/domain/tokens/models/src/main/java/com/tangem/domain/tokens/model/Quote.kt b/domain/tokens/models/src/main/java/com/tangem/domain/tokens/model/Quote.kt index ebfb2e7525..6e3d9532f0 100644 --- a/domain/tokens/models/src/main/java/com/tangem/domain/tokens/model/Quote.kt +++ b/domain/tokens/models/src/main/java/com/tangem/domain/tokens/model/Quote.kt @@ -7,6 +7,13 @@ sealed interface Quote { val rawCurrencyId: CryptoCurrency.RawID + fun copySealed(source: StatusSource): Quote { + return when (this) { + is Empty -> this + is Value -> copy(source = source) + } + } + /** * Represents unknown financial information for a specific cryptocurrency. * diff --git a/settings.gradle.kts b/settings.gradle.kts index cbcae1d8bb..76403e5965 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -314,4 +314,5 @@ include(":data:manage-tokens") include(":data:networks") include(":data:nft") include(":data:onramp") +include(":data:quotes") // endregion Data modules \ No newline at end of file