Updated on 2026-08-14
This commit is contained in:
parent
ddcb68caf6
commit
782a2ec0f5
21 changed files with 479 additions and 6 deletions
|
|
@ -0,0 +1,86 @@
|
|||
package com.tangem.tap.proxy
|
||||
|
||||
import com.tangem.blockchain.common.Blockchain
|
||||
import com.tangem.blockchain.common.TransactionStatus
|
||||
import com.tangem.blockchain.common.WalletManager
|
||||
import com.tangem.blockchain.common.txhistory.TransactionHistoryItem
|
||||
import com.tangem.blockchain.common.txhistory.TransactionHistoryState
|
||||
import com.tangem.blockchain.extensions.Result
|
||||
import com.tangem.domain.common.BlockchainNetwork
|
||||
import com.tangem.domain.common.extensions.fromNetworkId
|
||||
import com.tangem.lib.crypto.TxHistoryManager
|
||||
import com.tangem.lib.crypto.models.ProxyAmount
|
||||
import com.tangem.lib.crypto.models.txhistory.ProxyTransactionHistoryItem
|
||||
import com.tangem.lib.crypto.models.txhistory.ProxyTransactionHistoryState
|
||||
import com.tangem.lib.crypto.models.txhistory.ProxyTransactionStatus
|
||||
|
||||
class TxHistoryManagerImpl(
|
||||
private val appStateHolder: AppStateHolder,
|
||||
) : TxHistoryManager {
|
||||
|
||||
override suspend fun checkTxHistoryState(networkId: String, derivationPath: String?): ProxyTransactionHistoryState {
|
||||
val blockchain = requireNotNull(Blockchain.fromNetworkId(networkId)) { "blockchain not found" }
|
||||
val walletManager = getActualWalletManager(blockchain, derivationPath)
|
||||
val state = walletManager.getTransactionHistoryState(address = walletManager.wallet.address)
|
||||
return state.mapToProxy()
|
||||
}
|
||||
|
||||
override suspend fun getTxHistoryItems(
|
||||
networkId: String,
|
||||
derivationPath: String?,
|
||||
page: Int,
|
||||
pageSize: Int,
|
||||
): List<ProxyTransactionHistoryItem> {
|
||||
val blockchain = requireNotNull(Blockchain.fromNetworkId(networkId)) { "blockchain not found" }
|
||||
val walletManager = getActualWalletManager(blockchain, derivationPath)
|
||||
val itemsResult = walletManager.getTransactionsHistory(
|
||||
address = walletManager.wallet.address,
|
||||
page = page,
|
||||
pageSize = pageSize,
|
||||
)
|
||||
|
||||
return when (itemsResult) {
|
||||
is Result.Success -> itemsResult.data.map { historyItem -> historyItem.mapToProxy() }
|
||||
is Result.Failure -> error(itemsResult.error.message ?: itemsResult.error.customMessage)
|
||||
}
|
||||
}
|
||||
|
||||
private fun getActualWalletManager(blockchain: Blockchain, derivationPath: String?): WalletManager {
|
||||
val blockchainNetwork = BlockchainNetwork(blockchain, derivationPath, emptyList())
|
||||
val walletManager = appStateHolder.walletState?.getWalletManager(blockchainNetwork)
|
||||
return requireNotNull(walletManager) { "no wallet manager found" }
|
||||
}
|
||||
|
||||
private fun TransactionHistoryState.mapToProxy(): ProxyTransactionHistoryState {
|
||||
return when (this) {
|
||||
TransactionHistoryState.Success.Empty -> ProxyTransactionHistoryState.Success.Empty
|
||||
is TransactionHistoryState.Failed.FetchError -> ProxyTransactionHistoryState.Failed.FetchError(exception)
|
||||
TransactionHistoryState.NotImplemented -> ProxyTransactionHistoryState.NotImplemented
|
||||
is TransactionHistoryState.Success.HasTransactions ->
|
||||
ProxyTransactionHistoryState.Success.HasTransactions(txCount)
|
||||
}
|
||||
}
|
||||
|
||||
private fun TransactionHistoryItem.mapToProxy() = ProxyTransactionHistoryItem(
|
||||
txHash = txHash,
|
||||
timestamp = timestamp,
|
||||
direction = when (val direction = direction) {
|
||||
is TransactionHistoryItem.TransactionDirection.Incoming ->
|
||||
ProxyTransactionHistoryItem.TransactionDirection.Incoming(direction.from)
|
||||
is TransactionHistoryItem.TransactionDirection.Outgoing ->
|
||||
ProxyTransactionHistoryItem.TransactionDirection.Outgoing(direction.to)
|
||||
},
|
||||
status = when (status) {
|
||||
TransactionStatus.Confirmed -> ProxyTransactionStatus.Confirmed
|
||||
TransactionStatus.Unconfirmed -> ProxyTransactionStatus.Unconfirmed
|
||||
},
|
||||
type = when (type) {
|
||||
TransactionHistoryItem.TransactionType.Transfer -> ProxyTransactionHistoryItem.TransactionType.Transfer
|
||||
},
|
||||
amount = ProxyAmount(
|
||||
currencySymbol = amount.currencySymbol,
|
||||
value = requireNotNull(amount.value) { "Amount value must not be null" },
|
||||
decimals = amount.decimals,
|
||||
),
|
||||
)
|
||||
}
|
||||
|
|
@ -8,11 +8,9 @@ import com.tangem.domain.common.util.cardTypesResolver
|
|||
import com.tangem.feature.learn2earn.domain.api.Learn2earnDependencyProvider
|
||||
import com.tangem.lib.crypto.DerivationManager
|
||||
import com.tangem.lib.crypto.TransactionManager
|
||||
import com.tangem.lib.crypto.TxHistoryManager
|
||||
import com.tangem.lib.crypto.UserWalletManager
|
||||
import com.tangem.tap.proxy.AppStateHolder
|
||||
import com.tangem.tap.proxy.DerivationManagerImpl
|
||||
import com.tangem.tap.proxy.TransactionManagerImpl
|
||||
import com.tangem.tap.proxy.UserWalletManagerImpl
|
||||
import com.tangem.tap.proxy.*
|
||||
import dagger.Module
|
||||
import dagger.Provides
|
||||
import dagger.hilt.InstallIn
|
||||
|
|
@ -61,6 +59,12 @@ class ProxyModule {
|
|||
)
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideTxHistoryManager(appStateHolder: AppStateHolder): TxHistoryManager {
|
||||
return TxHistoryManagerImpl(appStateHolder = appStateHolder)
|
||||
}
|
||||
|
||||
// regions FeatureConsumers
|
||||
@Provides
|
||||
@Singleton
|
||||
|
|
|
|||
1
data/txhistory/.gitignore
vendored
Normal file
1
data/txhistory/.gitignore
vendored
Normal file
|
|
@ -0,0 +1 @@
|
|||
/build
|
||||
21
data/txhistory/build.gradle.kts
Normal file
21
data/txhistory/build.gradle.kts
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
plugins {
|
||||
alias(deps.plugins.android.library)
|
||||
alias(deps.plugins.kotlin.android)
|
||||
alias(deps.plugins.kotlin.kapt)
|
||||
id("configuration")
|
||||
}
|
||||
|
||||
android {
|
||||
namespace = "com.tangem.data.txhistory"
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation(projects.domain.txhistory)
|
||||
|
||||
implementation(deps.kotlin.coroutines)
|
||||
implementation(deps.androidx.paging.runtime)
|
||||
implementation(deps.arrow.core)
|
||||
|
||||
implementation(deps.hilt.core)
|
||||
kapt(deps.hilt.kapt)
|
||||
}
|
||||
|
|
@ -0,0 +1,18 @@
|
|||
package com.tangem.data.txhistory.di
|
||||
|
||||
import com.tangem.data.txhistory.repository.MockTxHistoryRepository
|
||||
import com.tangem.domain.txhistory.repository.TxHistoryRepository
|
||||
import dagger.Module
|
||||
import dagger.Provides
|
||||
import dagger.hilt.InstallIn
|
||||
import dagger.hilt.components.SingletonComponent
|
||||
import javax.inject.Singleton
|
||||
|
||||
@Module
|
||||
@InstallIn(SingletonComponent::class)
|
||||
internal object TxHistoryDataModule {
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideTxHistoryRepository(): TxHistoryRepository = MockTxHistoryRepository()
|
||||
}
|
||||
|
|
@ -0,0 +1,70 @@
|
|||
package com.tangem.data.txhistory.mock
|
||||
|
||||
import com.tangem.domain.txhistory.model.TxHistoryItem
|
||||
import java.math.BigDecimal
|
||||
|
||||
internal object MockTxHistoryItems {
|
||||
|
||||
private val txHistoryItem1 = TxHistoryItem(
|
||||
txHash = "noster",
|
||||
timestamp = 1689930746,
|
||||
direction = TxHistoryItem.TransactionDirection.Incoming("address"),
|
||||
status = TxHistoryItem.TxStatus.Confirmed,
|
||||
type = TxHistoryItem.TransactionType.Transfer,
|
||||
amount = BigDecimal("1000000000.5"),
|
||||
)
|
||||
|
||||
private val txHistoryItem2 = TxHistoryItem(
|
||||
txHash = "noster",
|
||||
timestamp = 1689844346,
|
||||
direction = TxHistoryItem.TransactionDirection.Incoming("address2"),
|
||||
status = TxHistoryItem.TxStatus.Unconfirmed,
|
||||
type = TxHistoryItem.TransactionType.Transfer,
|
||||
amount = BigDecimal("1000000000.5"),
|
||||
)
|
||||
|
||||
private val txHistoryItem3 = TxHistoryItem(
|
||||
txHash = "noster",
|
||||
timestamp = 1689757946,
|
||||
direction = TxHistoryItem.TransactionDirection.Outgoing("address3"),
|
||||
status = TxHistoryItem.TxStatus.Confirmed,
|
||||
type = TxHistoryItem.TransactionType.Transfer,
|
||||
amount = BigDecimal("1000000000.5"),
|
||||
)
|
||||
|
||||
private val txHistoryItem4 = TxHistoryItem(
|
||||
txHash = "noster",
|
||||
timestamp = 1689671546,
|
||||
direction = TxHistoryItem.TransactionDirection.Incoming("address4"),
|
||||
status = TxHistoryItem.TxStatus.Confirmed,
|
||||
type = TxHistoryItem.TransactionType.Transfer,
|
||||
amount = BigDecimal("1000000000.5"),
|
||||
)
|
||||
|
||||
private val txHistoryItem5 = TxHistoryItem(
|
||||
txHash = "noster",
|
||||
timestamp = 1689585146,
|
||||
direction = TxHistoryItem.TransactionDirection.Outgoing("address5"),
|
||||
status = TxHistoryItem.TxStatus.Unconfirmed,
|
||||
type = TxHistoryItem.TransactionType.Transfer,
|
||||
amount = BigDecimal("1000000000.5"),
|
||||
)
|
||||
|
||||
private val txHistoryItem6 = TxHistoryItem(
|
||||
txHash = "noster",
|
||||
timestamp = 1689585146,
|
||||
direction = TxHistoryItem.TransactionDirection.Incoming("address6"),
|
||||
status = TxHistoryItem.TxStatus.Confirmed,
|
||||
type = TxHistoryItem.TransactionType.Transfer,
|
||||
amount = BigDecimal("1000000000.5"),
|
||||
)
|
||||
|
||||
val txHistoryItems = listOf(
|
||||
txHistoryItem1,
|
||||
txHistoryItem2,
|
||||
txHistoryItem3,
|
||||
txHistoryItem4,
|
||||
txHistoryItem5,
|
||||
txHistoryItem6,
|
||||
)
|
||||
}
|
||||
|
|
@ -0,0 +1,25 @@
|
|||
package com.tangem.data.txhistory.repository
|
||||
|
||||
import androidx.paging.Pager
|
||||
import androidx.paging.PagingConfig
|
||||
import androidx.paging.PagingData
|
||||
import com.tangem.data.txhistory.repository.paging.TxHistoryPagingSource
|
||||
import com.tangem.domain.txhistory.model.TxHistoryItem
|
||||
import com.tangem.domain.txhistory.repository.TxHistoryRepository
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
|
||||
internal class MockTxHistoryRepository : TxHistoryRepository {
|
||||
|
||||
override suspend fun getTxHistoryItemsCount(networkId: String, derivationPath: String): Int {
|
||||
return 0
|
||||
}
|
||||
|
||||
override fun getTxHistoryItems(networkId: String, pageSize: Int): Flow<PagingData<TxHistoryItem>> {
|
||||
return Pager(
|
||||
config = PagingConfig(
|
||||
pageSize = pageSize,
|
||||
),
|
||||
pagingSourceFactory = { TxHistoryPagingSource() },
|
||||
).flow
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,41 @@
|
|||
package com.tangem.data.txhistory.repository.paging
|
||||
|
||||
import androidx.paging.PagingSource
|
||||
import androidx.paging.PagingState
|
||||
import com.tangem.data.txhistory.mock.MockTxHistoryItems
|
||||
import com.tangem.domain.txhistory.model.TxHistoryItem
|
||||
|
||||
private const val INITIAL_PAGE = 1
|
||||
|
||||
internal class TxHistoryPagingSource : PagingSource<Int, TxHistoryItem>() {
|
||||
|
||||
override fun getRefreshKey(state: PagingState<Int, TxHistoryItem>): Int? {
|
||||
return state.anchorPosition?.let { anchorPosition ->
|
||||
state.closestPageToPosition(anchorPosition)?.prevKey?.plus(other = 1)
|
||||
?: state.closestPageToPosition(anchorPosition)?.nextKey?.minus(other = 1)
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun load(params: LoadParams<Int>): LoadResult<Int, TxHistoryItem> {
|
||||
val currentPage = params.key ?: INITIAL_PAGE
|
||||
return try {
|
||||
// TODO: [REDACTED_JIRA]
|
||||
// val result = txHistoryManager.getTxHistoryItems(
|
||||
// networkId = networkId,
|
||||
// derivationPath = derivationPath,
|
||||
// page = currentPage,
|
||||
// pageSize = params.loadSize,
|
||||
// )
|
||||
val result = MockTxHistoryItems.txHistoryItems
|
||||
|
||||
LoadResult.Page(
|
||||
data = result,
|
||||
prevKey = if (currentPage > INITIAL_PAGE) currentPage.minus(1) else null,
|
||||
// TODO: handle end of reached [REDACTED_JIRA]
|
||||
nextKey = null,
|
||||
)
|
||||
} catch (e: Exception) {
|
||||
LoadResult.Error(e)
|
||||
}
|
||||
}
|
||||
}
|
||||
1
domain/txhistory/.gitignore
vendored
Normal file
1
domain/txhistory/.gitignore
vendored
Normal file
|
|
@ -0,0 +1 @@
|
|||
/build
|
||||
17
domain/txhistory/build.gradle.kts
Normal file
17
domain/txhistory/build.gradle.kts
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
plugins {
|
||||
alias(deps.plugins.android.library)
|
||||
alias(deps.plugins.kotlin.android)
|
||||
id("configuration")
|
||||
}
|
||||
|
||||
android {
|
||||
namespace = "com.tangem.domain.txhistory"
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation(deps.arrow.core)
|
||||
implementation(deps.kotlin.coroutines)
|
||||
implementation(deps.androidx.paging.runtime)
|
||||
|
||||
implementation(projects.core.utils)
|
||||
}
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
package com.tangem.domain.txhistory.error
|
||||
|
||||
sealed class TxHistoryListError : Throwable() {
|
||||
data class DataError(override val cause: Throwable) : TxHistoryListError()
|
||||
}
|
||||
|
|
@ -0,0 +1,10 @@
|
|||
package com.tangem.domain.txhistory.error
|
||||
|
||||
sealed class TxHistoryStateError : Throwable() {
|
||||
|
||||
object TxHistoryNotImplemented : TxHistoryStateError()
|
||||
|
||||
object EmptyTxHistories : TxHistoryStateError()
|
||||
|
||||
data class DataError(override val cause: Throwable) : TxHistoryStateError()
|
||||
}
|
||||
|
|
@ -0,0 +1,23 @@
|
|||
package com.tangem.domain.txhistory.model
|
||||
|
||||
import java.math.BigDecimal
|
||||
|
||||
data class TxHistoryItem(
|
||||
val txHash: String,
|
||||
val timestamp: Long,
|
||||
val direction: TransactionDirection,
|
||||
val status: TxStatus,
|
||||
val type: TransactionType,
|
||||
val amount: BigDecimal,
|
||||
) {
|
||||
sealed interface TransactionDirection {
|
||||
data class Incoming(val from: String) : TransactionDirection
|
||||
data class Outgoing(val to: String) : TransactionDirection
|
||||
}
|
||||
|
||||
sealed interface TransactionType {
|
||||
object Transfer : TransactionType
|
||||
}
|
||||
|
||||
enum class TxStatus { Confirmed, Unconfirmed }
|
||||
}
|
||||
|
|
@ -0,0 +1,16 @@
|
|||
package com.tangem.domain.txhistory.repository
|
||||
|
||||
import androidx.paging.PagingData
|
||||
import com.tangem.domain.txhistory.error.TxHistoryListError
|
||||
import com.tangem.domain.txhistory.error.TxHistoryStateError
|
||||
import com.tangem.domain.txhistory.model.TxHistoryItem
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
|
||||
interface TxHistoryRepository {
|
||||
|
||||
@Throws(TxHistoryStateError::class)
|
||||
suspend fun getTxHistoryItemsCount(networkId: String, derivationPath: String): Int
|
||||
|
||||
@Throws(TxHistoryListError::class)
|
||||
fun getTxHistoryItems(networkId: String, pageSize: Int): Flow<PagingData<TxHistoryItem>>
|
||||
}
|
||||
|
|
@ -0,0 +1,27 @@
|
|||
package com.tangem.domain.txhistory.usecase
|
||||
|
||||
import arrow.core.Either
|
||||
import arrow.core.raise.catch
|
||||
import arrow.core.raise.either
|
||||
import com.tangem.domain.txhistory.error.TxHistoryStateError
|
||||
import com.tangem.domain.txhistory.repository.TxHistoryRepository
|
||||
|
||||
class GetTxHistoryItemsCountUseCase(private val repository: TxHistoryRepository) {
|
||||
|
||||
suspend operator fun invoke(networkId: String, derivationPath: String): Either<TxHistoryStateError, Int> {
|
||||
return either {
|
||||
catch(
|
||||
block = { repository.getTxHistoryItemsCount(networkId, derivationPath) },
|
||||
catch = { e ->
|
||||
raise(
|
||||
when (e) {
|
||||
is TxHistoryStateError.TxHistoryNotImplemented -> e
|
||||
is TxHistoryStateError.EmptyTxHistories -> e
|
||||
else -> TxHistoryStateError.DataError(e)
|
||||
},
|
||||
)
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,49 @@
|
|||
package com.tangem.domain.txhistory.usecase
|
||||
|
||||
import androidx.paging.PagingData
|
||||
import arrow.core.Either
|
||||
import arrow.core.left
|
||||
import arrow.core.raise.Raise
|
||||
import arrow.core.raise.recover
|
||||
import arrow.core.right
|
||||
import com.tangem.domain.txhistory.error.TxHistoryListError
|
||||
import com.tangem.domain.txhistory.model.TxHistoryItem
|
||||
import com.tangem.domain.txhistory.repository.TxHistoryRepository
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.catch
|
||||
import kotlinx.coroutines.flow.channelFlow
|
||||
import kotlinx.coroutines.flow.collect
|
||||
|
||||
private const val DEFAULT_PAGE_SIZE = 20
|
||||
|
||||
class GetTxHistoryItemsUseCase(private val repository: TxHistoryRepository) {
|
||||
|
||||
operator fun invoke(
|
||||
networkId: String,
|
||||
pageSize: Int = DEFAULT_PAGE_SIZE,
|
||||
): Flow<Either<TxHistoryListError,
|
||||
PagingData<TxHistoryItem>,>,> {
|
||||
return channelFlow {
|
||||
recover(
|
||||
block = {
|
||||
getTxHistoryItems(
|
||||
networkId = networkId,
|
||||
pageSize = pageSize,
|
||||
).collect { items ->
|
||||
send(items.right())
|
||||
}
|
||||
},
|
||||
recover = { error -> send(error.left()) },
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun Raise<TxHistoryListError>.getTxHistoryItems(
|
||||
networkId: String,
|
||||
pageSize: Int,
|
||||
): Flow<PagingData<TxHistoryItem>> {
|
||||
return repository
|
||||
.getTxHistoryItems(networkId = networkId, pageSize = pageSize)
|
||||
.catch { raise(TxHistoryListError.DataError(it)) }
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,18 @@
|
|||
package com.tangem.lib.crypto
|
||||
|
||||
import com.tangem.lib.crypto.models.txhistory.ProxyTransactionHistoryItem
|
||||
import com.tangem.lib.crypto.models.txhistory.ProxyTransactionHistoryState
|
||||
|
||||
interface TxHistoryManager {
|
||||
|
||||
@Throws(IllegalStateException::class)
|
||||
suspend fun checkTxHistoryState(networkId: String, derivationPath: String?): ProxyTransactionHistoryState
|
||||
|
||||
@Throws(IllegalStateException::class)
|
||||
suspend fun getTxHistoryItems(
|
||||
networkId: String,
|
||||
derivationPath: String?,
|
||||
page: Int,
|
||||
pageSize: Int,
|
||||
): List<ProxyTransactionHistoryItem>
|
||||
}
|
||||
|
|
@ -0,0 +1,21 @@
|
|||
package com.tangem.lib.crypto.models.txhistory
|
||||
|
||||
import com.tangem.lib.crypto.models.ProxyAmount
|
||||
|
||||
data class ProxyTransactionHistoryItem(
|
||||
val txHash: String,
|
||||
val timestamp: Long,
|
||||
val direction: TransactionDirection,
|
||||
val status: ProxyTransactionStatus,
|
||||
val type: TransactionType,
|
||||
val amount: ProxyAmount,
|
||||
) {
|
||||
sealed interface TransactionDirection {
|
||||
data class Incoming(val from: String) : TransactionDirection
|
||||
data class Outgoing(val to: String) : TransactionDirection
|
||||
}
|
||||
|
||||
sealed interface TransactionType {
|
||||
object Transfer : TransactionType
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,15 @@
|
|||
package com.tangem.lib.crypto.models.txhistory
|
||||
|
||||
sealed class ProxyTransactionHistoryState {
|
||||
|
||||
sealed class Success : ProxyTransactionHistoryState() {
|
||||
object Empty : Success()
|
||||
data class HasTransactions(val txCount: Int) : Success()
|
||||
}
|
||||
|
||||
sealed class Failed : ProxyTransactionHistoryState() {
|
||||
data class FetchError(val exception: Exception) : Failed()
|
||||
}
|
||||
|
||||
object NotImplemented : ProxyTransactionHistoryState()
|
||||
}
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
package com.tangem.lib.crypto.models.txhistory
|
||||
|
||||
enum class ProxyTransactionStatus { Confirmed, Unconfirmed }
|
||||
|
|
@ -83,6 +83,7 @@ include(":domain:settings")
|
|||
include(":domain:tokens")
|
||||
include(":domain:wallets")
|
||||
include(":domain:wallets:models")
|
||||
include(":domain:txhistory")
|
||||
// endregion Domain modules
|
||||
|
||||
// region Data modules
|
||||
|
|
@ -90,5 +91,6 @@ include(":data:common")
|
|||
include(":data:card")
|
||||
include(":data:tokens")
|
||||
include(":data:source:preferences")
|
||||
// endregion Data modules
|
||||
include(":data:settings")
|
||||
include(":data:settings")
|
||||
include(":data:txhistory")
|
||||
// endregion Data modules
|
||||
Loading…
Add table
Add a link
Reference in a new issue