Updated on 2026-08-14
This commit is contained in:
parent
1edf995ab0
commit
2dadb60975
26 changed files with 1110 additions and 48 deletions
|
|
@ -1,14 +1,27 @@
|
|||
package com.tangem.datasource.di
|
||||
|
||||
import android.content.Context
|
||||
import androidx.datastore.core.DataStoreFactory
|
||||
import androidx.datastore.core.handlers.ReplaceFileCorruptionHandler
|
||||
import androidx.datastore.dataStoreFile
|
||||
import com.tangem.datasource.local.datastore.RuntimeDataStore
|
||||
import com.tangem.datasource.local.txhistory.DefaultTxHistoryItemsStore
|
||||
import com.tangem.datasource.local.txhistory.TxHistoryItemsStore
|
||||
import com.tangem.datasource.local.visa.DefaultTangemPayTxHistoryItemsStore
|
||||
import com.tangem.datasource.local.visa.TangemPayTxHistoryItemsStore
|
||||
import com.tangem.datasource.local.visa.entity.TangemPayTxHistoryItemDM
|
||||
import com.tangem.datasource.local.visa.entity.TangemPayTxHistoryItemToDMConverter
|
||||
import com.tangem.datasource.local.visa.entity.TangemPayTxHistoryItemToDomainConverter
|
||||
import com.tangem.datasource.utils.KotlinxDataStoreSerializer
|
||||
import com.tangem.utils.coroutines.AppCoroutineScope
|
||||
import dagger.Module
|
||||
import dagger.Provides
|
||||
import dagger.hilt.InstallIn
|
||||
import dagger.hilt.android.qualifiers.ApplicationContext
|
||||
import dagger.hilt.components.SingletonComponent
|
||||
import kotlinx.serialization.builtins.ListSerializer
|
||||
import kotlinx.serialization.builtins.MapSerializer
|
||||
import kotlinx.serialization.builtins.serializer
|
||||
import javax.inject.Singleton
|
||||
|
||||
@Module
|
||||
|
|
@ -25,9 +38,34 @@ internal object TxHistoryItemsStoreModule {
|
|||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideTangemPayTxHistoryItemsStore(): TangemPayTxHistoryItemsStore {
|
||||
fun provideTangemPayTxHistoryItemsStore(
|
||||
@ApplicationContext context: Context,
|
||||
appScope: AppCoroutineScope,
|
||||
toDMConverter: TangemPayTxHistoryItemToDMConverter,
|
||||
toDomainConverter: TangemPayTxHistoryItemToDomainConverter,
|
||||
): TangemPayTxHistoryItemsStore {
|
||||
return DefaultTangemPayTxHistoryItemsStore(
|
||||
dataStore = RuntimeDataStore(),
|
||||
dataStore = DataStoreFactory.create(
|
||||
serializer = KotlinxDataStoreSerializer(
|
||||
defaultValue = emptyMap(),
|
||||
serializer = MapSerializer(
|
||||
keySerializer = String.serializer(),
|
||||
valueSerializer = MapSerializer(
|
||||
keySerializer = String.serializer(),
|
||||
valueSerializer = ListSerializer(TangemPayTxHistoryItemDM.serializer()),
|
||||
),
|
||||
),
|
||||
// TangemPayTxHistoryItemDM.Collateral declares a `type` field that would clash with the
|
||||
// default kotlinx polymorphic class discriminator ("type"), so persist sealed subtypes
|
||||
// under a non-conflicting discriminator key.
|
||||
json = KotlinxDataStoreSerializer.jsonBuilder { classDiscriminator = "__type" },
|
||||
),
|
||||
corruptionHandler = ReplaceFileCorruptionHandler { emptyMap() },
|
||||
produceFile = { context.dataStoreFile(fileName = "tangem_pay_tx_history") },
|
||||
scope = appScope,
|
||||
),
|
||||
toDMConverter = toDMConverter,
|
||||
toDomainConverter = toDomainConverter,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -1,23 +1,37 @@
|
|||
package com.tangem.datasource.local.visa
|
||||
|
||||
import com.tangem.datasource.local.datastore.core.StringKeyDataStore
|
||||
import com.tangem.datasource.local.datastore.core.StringKeyDataStoreDecorator
|
||||
import androidx.datastore.core.DataStore
|
||||
import com.tangem.datasource.local.visa.entity.TangemPayTxHistoryItemDM
|
||||
import com.tangem.datasource.local.visa.entity.TangemPayTxHistoryItemToDMConverter
|
||||
import com.tangem.datasource.local.visa.entity.TangemPayTxHistoryItemToDomainConverter
|
||||
import com.tangem.domain.visa.model.TangemPayTxHistoryItem
|
||||
import kotlinx.coroutines.flow.first
|
||||
|
||||
internal typealias StoredTangemPayTxHistory = Map<String, Map<String, List<TangemPayTxHistoryItemDM>>>
|
||||
|
||||
internal class DefaultTangemPayTxHistoryItemsStore(
|
||||
dataStore: StringKeyDataStore<Map<String, List<TangemPayTxHistoryItem>>>,
|
||||
) : TangemPayTxHistoryItemsStore,
|
||||
StringKeyDataStoreDecorator<String, Map<String, List<TangemPayTxHistoryItem>>>(dataStore) {
|
||||
override fun provideStringKey(key: String): String = key
|
||||
private val dataStore: DataStore<StoredTangemPayTxHistory>,
|
||||
private val toDMConverter: TangemPayTxHistoryItemToDMConverter,
|
||||
private val toDomainConverter: TangemPayTxHistoryItemToDomainConverter,
|
||||
) : TangemPayTxHistoryItemsStore {
|
||||
|
||||
override suspend fun getSyncOrNull(key: String, cursor: String): List<TangemPayTxHistoryItem>? {
|
||||
val storedValue = getSyncOrNull(key)
|
||||
return storedValue?.get(cursor)
|
||||
return dataStore.data.first()[key]?.get(cursor)?.let { toDomainConverter.convertList(it) }
|
||||
}
|
||||
|
||||
override suspend fun store(key: String, cursor: String, value: List<TangemPayTxHistoryItem>) {
|
||||
val oldValue = getSyncOrNull(key).orEmpty()
|
||||
val newValue = oldValue.toMutableMap().apply { put(cursor, value) }
|
||||
store(key, newValue)
|
||||
val page = toDMConverter.convertList(value)
|
||||
dataStore.updateData { stored ->
|
||||
val walletPages = stored[key].orEmpty()
|
||||
stored + (key to walletPages + (cursor to page))
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun remove(key: String) {
|
||||
remove(keys = listOf(key))
|
||||
}
|
||||
|
||||
override suspend fun remove(keys: List<String>) {
|
||||
dataStore.updateData { stored -> stored - keys.toSet() }
|
||||
}
|
||||
}
|
||||
|
|
@ -8,5 +8,7 @@ interface TangemPayTxHistoryItemsStore {
|
|||
|
||||
suspend fun remove(key: String)
|
||||
|
||||
suspend fun remove(keys: List<String>)
|
||||
|
||||
suspend fun store(key: String, cursor: String, value: List<TangemPayTxHistoryItem>)
|
||||
}
|
||||
|
|
@ -0,0 +1,102 @@
|
|||
package com.tangem.datasource.local.visa.entity
|
||||
|
||||
import com.tangem.domain.models.serialization.SerializedBigDecimal
|
||||
import com.tangem.domain.models.serialization.SerializedCurrency
|
||||
import com.tangem.domain.models.serialization.SerializedDateTime
|
||||
import kotlinx.serialization.SerialName
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
@Serializable
|
||||
internal sealed class TangemPayTxHistoryItemDM {
|
||||
|
||||
abstract val id: String
|
||||
abstract val date: SerializedDateTime
|
||||
abstract val amount: SerializedBigDecimal
|
||||
abstract val currency: SerializedCurrency
|
||||
abstract val jsonRepresentation: String
|
||||
|
||||
@Serializable
|
||||
@SerialName("spend")
|
||||
data class Spend(
|
||||
@SerialName("id") override val id: String,
|
||||
@SerialName("json_representation") override val jsonRepresentation: String,
|
||||
@SerialName("date") override val date: SerializedDateTime,
|
||||
@SerialName("amount") override val amount: SerializedBigDecimal,
|
||||
@SerialName("currency") override val currency: SerializedCurrency,
|
||||
@SerialName("authorized_amount") val authorizedAmount: SerializedBigDecimal,
|
||||
@SerialName("local_amount") val localAmount: SerializedBigDecimal?,
|
||||
@SerialName("local_currency") val localCurrency: SerializedCurrency?,
|
||||
@SerialName("enriched_merchant_name") val enrichedMerchantName: String?,
|
||||
@SerialName("merchant_name") val merchantName: String,
|
||||
@SerialName("enriched_merchant_category") val enrichedMerchantCategory: String?,
|
||||
@SerialName("merchant_category_code") val merchantCategoryCode: String?,
|
||||
@SerialName("merchant_category") val merchantCategory: String?,
|
||||
@SerialName("status") val status: Status,
|
||||
@SerialName("enriched_merchant_icon_url") val enrichedMerchantIconUrl: String?,
|
||||
@SerialName("declined_reason") val declinedReason: String?,
|
||||
) : TangemPayTxHistoryItemDM()
|
||||
|
||||
@Serializable
|
||||
@SerialName("payment")
|
||||
data class Payment(
|
||||
@SerialName("id") override val id: String,
|
||||
@SerialName("json_representation") override val jsonRepresentation: String,
|
||||
@SerialName("date") override val date: SerializedDateTime,
|
||||
@SerialName("amount") override val amount: SerializedBigDecimal,
|
||||
@SerialName("currency") override val currency: SerializedCurrency,
|
||||
@SerialName("transaction_hash") val transactionHash: String?,
|
||||
) : TangemPayTxHistoryItemDM()
|
||||
|
||||
@Serializable
|
||||
@SerialName("fee")
|
||||
data class Fee(
|
||||
@SerialName("id") override val id: String,
|
||||
@SerialName("json_representation") override val jsonRepresentation: String,
|
||||
@SerialName("date") override val date: SerializedDateTime,
|
||||
@SerialName("amount") override val amount: SerializedBigDecimal,
|
||||
@SerialName("currency") override val currency: SerializedCurrency,
|
||||
@SerialName("description") val description: String?,
|
||||
) : TangemPayTxHistoryItemDM()
|
||||
|
||||
@Serializable
|
||||
@SerialName("collateral")
|
||||
data class Collateral(
|
||||
@SerialName("id") override val id: String,
|
||||
@SerialName("json_representation") override val jsonRepresentation: String,
|
||||
@SerialName("date") override val date: SerializedDateTime,
|
||||
@SerialName("amount") override val amount: SerializedBigDecimal,
|
||||
@SerialName("currency") override val currency: SerializedCurrency,
|
||||
@SerialName("transaction_hash") val transactionHash: String,
|
||||
@SerialName("type") val type: Type,
|
||||
) : TangemPayTxHistoryItemDM()
|
||||
|
||||
@Serializable
|
||||
enum class Type {
|
||||
@SerialName("deposit")
|
||||
Deposit,
|
||||
|
||||
@SerialName("withdrawal")
|
||||
Withdrawal,
|
||||
}
|
||||
|
||||
@Serializable
|
||||
enum class Status {
|
||||
@SerialName("pending")
|
||||
PENDING,
|
||||
|
||||
@SerialName("reserved")
|
||||
RESERVED,
|
||||
|
||||
@SerialName("completed")
|
||||
COMPLETED,
|
||||
|
||||
@SerialName("declined")
|
||||
DECLINED,
|
||||
|
||||
@SerialName("reversed")
|
||||
REVERSED,
|
||||
|
||||
@SerialName("unknown")
|
||||
UNKNOWN,
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,133 @@
|
|||
package com.tangem.datasource.local.visa.entity
|
||||
|
||||
import com.tangem.domain.visa.model.TangemPayTxHistoryItem
|
||||
import com.tangem.utils.converter.Converter
|
||||
import javax.inject.Inject
|
||||
|
||||
internal class TangemPayTxHistoryItemToDMConverter @Inject constructor() :
|
||||
Converter<TangemPayTxHistoryItem, TangemPayTxHistoryItemDM> {
|
||||
|
||||
override fun convert(value: TangemPayTxHistoryItem): TangemPayTxHistoryItemDM = when (value) {
|
||||
is TangemPayTxHistoryItem.Spend -> TangemPayTxHistoryItemDM.Spend(
|
||||
id = value.id,
|
||||
jsonRepresentation = value.jsonRepresentation,
|
||||
date = value.date,
|
||||
amount = value.amount,
|
||||
currency = value.currency,
|
||||
authorizedAmount = value.authorizedAmount,
|
||||
localAmount = value.localAmount,
|
||||
localCurrency = value.localCurrency,
|
||||
enrichedMerchantName = value.enrichedMerchantName,
|
||||
merchantName = value.merchantName,
|
||||
enrichedMerchantCategory = value.enrichedMerchantCategory,
|
||||
merchantCategoryCode = value.merchantCategoryCode,
|
||||
merchantCategory = value.merchantCategory,
|
||||
status = value.status.toDM(),
|
||||
enrichedMerchantIconUrl = value.enrichedMerchantIconUrl,
|
||||
declinedReason = value.declinedReason,
|
||||
)
|
||||
is TangemPayTxHistoryItem.Payment -> TangemPayTxHistoryItemDM.Payment(
|
||||
id = value.id,
|
||||
jsonRepresentation = value.jsonRepresentation,
|
||||
date = value.date,
|
||||
amount = value.amount,
|
||||
currency = value.currency,
|
||||
transactionHash = value.transactionHash,
|
||||
)
|
||||
is TangemPayTxHistoryItem.Fee -> TangemPayTxHistoryItemDM.Fee(
|
||||
id = value.id,
|
||||
jsonRepresentation = value.jsonRepresentation,
|
||||
date = value.date,
|
||||
amount = value.amount,
|
||||
currency = value.currency,
|
||||
description = value.description,
|
||||
)
|
||||
is TangemPayTxHistoryItem.Collateral -> TangemPayTxHistoryItemDM.Collateral(
|
||||
id = value.id,
|
||||
jsonRepresentation = value.jsonRepresentation,
|
||||
date = value.date,
|
||||
amount = value.amount,
|
||||
currency = value.currency,
|
||||
transactionHash = value.transactionHash,
|
||||
type = value.type.toDM(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
internal class TangemPayTxHistoryItemToDomainConverter @Inject constructor() :
|
||||
Converter<TangemPayTxHistoryItemDM, TangemPayTxHistoryItem> {
|
||||
|
||||
override fun convert(value: TangemPayTxHistoryItemDM): TangemPayTxHistoryItem = when (value) {
|
||||
is TangemPayTxHistoryItemDM.Spend -> TangemPayTxHistoryItem.Spend(
|
||||
id = value.id,
|
||||
jsonRepresentation = value.jsonRepresentation,
|
||||
date = value.date,
|
||||
amount = value.amount,
|
||||
currency = value.currency,
|
||||
authorizedAmount = value.authorizedAmount,
|
||||
localAmount = value.localAmount,
|
||||
localCurrency = value.localCurrency,
|
||||
enrichedMerchantName = value.enrichedMerchantName,
|
||||
merchantName = value.merchantName,
|
||||
enrichedMerchantCategory = value.enrichedMerchantCategory,
|
||||
merchantCategoryCode = value.merchantCategoryCode,
|
||||
merchantCategory = value.merchantCategory,
|
||||
status = value.status.toDomain(),
|
||||
enrichedMerchantIconUrl = value.enrichedMerchantIconUrl,
|
||||
declinedReason = value.declinedReason,
|
||||
)
|
||||
is TangemPayTxHistoryItemDM.Payment -> TangemPayTxHistoryItem.Payment(
|
||||
id = value.id,
|
||||
jsonRepresentation = value.jsonRepresentation,
|
||||
date = value.date,
|
||||
amount = value.amount,
|
||||
currency = value.currency,
|
||||
transactionHash = value.transactionHash,
|
||||
)
|
||||
is TangemPayTxHistoryItemDM.Fee -> TangemPayTxHistoryItem.Fee(
|
||||
id = value.id,
|
||||
jsonRepresentation = value.jsonRepresentation,
|
||||
date = value.date,
|
||||
amount = value.amount,
|
||||
currency = value.currency,
|
||||
description = value.description,
|
||||
)
|
||||
is TangemPayTxHistoryItemDM.Collateral -> TangemPayTxHistoryItem.Collateral(
|
||||
id = value.id,
|
||||
jsonRepresentation = value.jsonRepresentation,
|
||||
date = value.date,
|
||||
amount = value.amount,
|
||||
currency = value.currency,
|
||||
transactionHash = value.transactionHash,
|
||||
type = value.type.toDomain(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun TangemPayTxHistoryItem.Status.toDM(): TangemPayTxHistoryItemDM.Status = when (this) {
|
||||
TangemPayTxHistoryItem.Status.PENDING -> TangemPayTxHistoryItemDM.Status.PENDING
|
||||
TangemPayTxHistoryItem.Status.RESERVED -> TangemPayTxHistoryItemDM.Status.RESERVED
|
||||
TangemPayTxHistoryItem.Status.COMPLETED -> TangemPayTxHistoryItemDM.Status.COMPLETED
|
||||
TangemPayTxHistoryItem.Status.DECLINED -> TangemPayTxHistoryItemDM.Status.DECLINED
|
||||
TangemPayTxHistoryItem.Status.REVERSED -> TangemPayTxHistoryItemDM.Status.REVERSED
|
||||
TangemPayTxHistoryItem.Status.UNKNOWN -> TangemPayTxHistoryItemDM.Status.UNKNOWN
|
||||
}
|
||||
|
||||
private fun TangemPayTxHistoryItemDM.Status.toDomain(): TangemPayTxHistoryItem.Status = when (this) {
|
||||
TangemPayTxHistoryItemDM.Status.PENDING -> TangemPayTxHistoryItem.Status.PENDING
|
||||
TangemPayTxHistoryItemDM.Status.RESERVED -> TangemPayTxHistoryItem.Status.RESERVED
|
||||
TangemPayTxHistoryItemDM.Status.COMPLETED -> TangemPayTxHistoryItem.Status.COMPLETED
|
||||
TangemPayTxHistoryItemDM.Status.DECLINED -> TangemPayTxHistoryItem.Status.DECLINED
|
||||
TangemPayTxHistoryItemDM.Status.REVERSED -> TangemPayTxHistoryItem.Status.REVERSED
|
||||
TangemPayTxHistoryItemDM.Status.UNKNOWN -> TangemPayTxHistoryItem.Status.UNKNOWN
|
||||
}
|
||||
|
||||
private fun TangemPayTxHistoryItem.Type.toDM(): TangemPayTxHistoryItemDM.Type = when (this) {
|
||||
TangemPayTxHistoryItem.Type.Deposit -> TangemPayTxHistoryItemDM.Type.Deposit
|
||||
TangemPayTxHistoryItem.Type.Withdrawal -> TangemPayTxHistoryItemDM.Type.Withdrawal
|
||||
}
|
||||
|
||||
private fun TangemPayTxHistoryItemDM.Type.toDomain(): TangemPayTxHistoryItem.Type = when (this) {
|
||||
TangemPayTxHistoryItemDM.Type.Deposit -> TangemPayTxHistoryItem.Type.Deposit
|
||||
TangemPayTxHistoryItemDM.Type.Withdrawal -> TangemPayTxHistoryItem.Type.Withdrawal
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue