Updated on 2026-08-14

This commit is contained in:
Tangem 2025-10-09 14:33:14 +03:00
commit 8eb1a28f29
236 changed files with 3852 additions and 2068 deletions

View file

@ -15,7 +15,7 @@ import retrofit2.http.Query
interface YieldSupplyApi {
@GET("api/v1/yield/markets")
suspend fun getYieldMarkets(@Query("chainId") chainId: Int? = null): ApiResponse<YieldMarketsResponse>
suspend fun getYieldMarkets(@Query("chainId") chainId: String? = null): ApiResponse<YieldMarketsResponse>
@GET("api/v1/yield/token/{chainId}/{tokenAddress}")
suspend fun getYieldTokenStatus(

View file

@ -2,8 +2,37 @@ package com.tangem.datasource.api.tangemTech.models.account
import com.squareup.moshi.Json
import com.squareup.moshi.JsonClass
import com.tangem.datasource.utils.SerializeNulls
@JsonClass(generateAdapter = true)
data class SaveWalletAccountsResponse(
@Json(name = "accounts") val accounts: List<WalletAccountDTO>,
)
@Json(name = "accounts") val accounts: List<AccountDTO>,
) {
@SerializeNulls
@JsonClass(generateAdapter = true)
data class AccountDTO(
@Json(name = "id") val id: String,
@Json(name = "name") val name: String?,
@Json(name = "derivation") val derivationIndex: Int,
@Json(name = "icon") val icon: String,
@Json(name = "iconColor") val iconColor: String,
)
companion object {
operator fun invoke(accounts: List<WalletAccountDTO>): SaveWalletAccountsResponse {
return SaveWalletAccountsResponse(
accounts = accounts.map { accountDto ->
AccountDTO(
id = accountDto.id,
name = accountDto.name,
derivationIndex = accountDto.derivationIndex,
icon = accountDto.icon,
iconColor = accountDto.iconColor,
)
},
)
}
}
}

View file

@ -9,6 +9,7 @@ import com.tangem.common.json.MoshiJsonConverter
import com.tangem.datasource.api.common.adapter.*
import com.tangem.datasource.local.config.providers.models.ProviderModel
import com.tangem.datasource.local.network.entity.NetworkStatusDM
import com.tangem.datasource.utils.SerializeNullsFactory
import com.tangem.domain.models.scan.serialization.*
import com.tangem.domain.visa.model.VisaActivationRemoteState
import com.tangem.domain.visa.model.VisaCardActivationStatus
@ -28,6 +29,7 @@ class MoshiModule {
@NetworkMoshi
fun provideNetworkMoshi(): Moshi {
return Moshi.Builder()
.add(SerializeNullsFactory)
.add(
PolymorphicJsonAdapterFactory.of(ProviderModel::class.java, "type")
.withSubtype(ProviderModel.Public::class.java, "public")

View file

@ -4,11 +4,11 @@ import android.content.Context
import androidx.datastore.core.DataStoreFactory
import androidx.datastore.dataStoreFile
import com.squareup.moshi.Moshi
import com.tangem.datasource.api.tangemTech.models.YieldMarketsResponse
import com.tangem.datasource.local.yieldsupply.DefaultYieldMarketsStore
import com.tangem.datasource.local.yieldsupply.YieldMarketsStore
import com.tangem.datasource.utils.MoshiDataStoreSerializer
import com.tangem.datasource.utils.listTypes
import com.tangem.domain.yield.supply.models.YieldMarketToken
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
import dagger.Module
import dagger.Provides
@ -34,7 +34,7 @@ object YieldSupplyModule {
persistenceStore = DataStoreFactory.create(
serializer = MoshiDataStoreSerializer(
moshi = moshi,
types = listTypes<YieldMarketToken>(),
types = listTypes<YieldMarketsResponse.MarketDto>(),
defaultValue = emptyList(),
),
produceFile = { context.dataStoreFile(fileName = "yield_markets_cache") },

View file

@ -12,5 +12,7 @@ interface TangemPayStorage {
suspend fun getOrderId(customerWalletAddress: String): String?
suspend fun clear(customerWalletAddress: String)
suspend fun clearOrderId(customerWalletAddress: String)
suspend fun clearAll(customerWalletAddress: String)
}

View file

@ -1,21 +1,21 @@
package com.tangem.datasource.local.yieldsupply
import androidx.datastore.core.DataStore
import com.tangem.domain.yield.supply.models.YieldMarketToken
import com.tangem.datasource.api.tangemTech.models.YieldMarketsResponse
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.firstOrNull
internal class DefaultYieldMarketsStore(
private val persistenceStore: DataStore<List<YieldMarketToken>>,
private val persistenceStore: DataStore<List<YieldMarketsResponse.MarketDto>>,
) : YieldMarketsStore {
override fun get(): Flow<List<YieldMarketToken>> = persistenceStore.data
override fun get(): Flow<List<YieldMarketsResponse.MarketDto>> = persistenceStore.data
override suspend fun getSyncOrNull(): List<YieldMarketToken>? {
override suspend fun getSyncOrNull(): List<YieldMarketsResponse.MarketDto>? {
return persistenceStore.data.firstOrNull()
}
override suspend fun store(items: List<YieldMarketToken>) {
override suspend fun store(items: List<YieldMarketsResponse.MarketDto>) {
persistenceStore.updateData { _ -> items }
}
}

View file

@ -1,13 +1,13 @@
package com.tangem.datasource.local.yieldsupply
import com.tangem.domain.yield.supply.models.YieldMarketToken
import com.tangem.datasource.api.tangemTech.models.YieldMarketsResponse
import kotlinx.coroutines.flow.Flow
interface YieldMarketsStore {
fun get(): Flow<List<YieldMarketToken>>
fun get(): Flow<List<YieldMarketsResponse.MarketDto>>
suspend fun getSyncOrNull(): List<YieldMarketToken>?
suspend fun getSyncOrNull(): List<YieldMarketsResponse.MarketDto>?
suspend fun store(items: List<YieldMarketToken>)
suspend fun store(items: List<YieldMarketsResponse.MarketDto>)
}

View file

@ -0,0 +1,5 @@
package com.tangem.datasource.utils
@Target(AnnotationTarget.CLASS)
@Retention(AnnotationRetention.RUNTIME)
annotation class SerializeNulls

View file

@ -0,0 +1,25 @@
package com.tangem.datasource.utils
import com.squareup.moshi.JsonAdapter
import com.squareup.moshi.Moshi
import com.squareup.moshi.Types
import java.lang.reflect.Type
/**
* Factory to serialize nulls in Moshi if the class is annotated with [SerializeNulls].
*
[REDACTED_AUTHOR]
*/
internal object SerializeNullsFactory : JsonAdapter.Factory {
override fun create(type: Type, annotations: MutableSet<out Annotation>, moshi: Moshi): JsonAdapter<*>? {
val rawType = Types.getRawType(type)
if (!rawType.isAnnotationPresent(SerializeNulls::class.java)) {
return null
}
val nextAdapter: JsonAdapter<Any> = moshi.nextAdapter(this, type, annotations)
return nextAdapter.serializeNulls()
}
}

View file

@ -0,0 +1,51 @@
package com.tangem.datasource.utils
import com.google.common.truth.Truth.assertThat
import com.squareup.moshi.JsonClass
import com.squareup.moshi.Moshi
import org.junit.jupiter.api.Test
import org.junit.jupiter.api.TestInstance
// --- DTO ---
@SerializeNulls
@JsonClass(generateAdapter = true)
data class UserWithNulls(val id: String?, val name: String?)
@JsonClass(generateAdapter = true)
data class UserWithoutNulls(val id: String?, val name: String?)
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
class SerializeNullsFactoryTest {
private val moshi = Moshi.Builder()
.add(SerializeNullsFactory)
.build()
@Test
fun `should serialize nulls for annotated class`() {
val adapter = moshi.adapter(UserWithNulls::class.java)
val json = adapter.toJson(UserWithNulls(id = null, name = "John"))
assertThat(json).isEqualTo("""{"id":null,"name":"John"}""")
}
@Test
fun `should skip nulls for non-annotated class`() {
val adapter = moshi.adapter(UserWithoutNulls::class.java)
val json = adapter.toJson(UserWithoutNulls(id = null, name = "John"))
assertThat(json).isEqualTo("""{"name":"John"}""")
}
@Test
fun `should deserialize annotated class correctly`() {
val adapter = moshi.adapter(UserWithNulls::class.java)
val json = """{"id":null,"name":"Jane"}"""
val result = adapter.fromJson(json)
assertThat(result).isEqualTo(UserWithNulls(id = null, name = "Jane"))
}
}