Updated on 2026-08-14
This commit is contained in:
commit
8fe3e9e3c1
974 changed files with 23162 additions and 21412 deletions
|
|
@ -113,6 +113,12 @@ sealed class AnalyticsParam {
|
|||
|
||||
data object WalletConnect : TxSentFrom("WalletConnect")
|
||||
data object Sell : TxSentFrom("Sell")
|
||||
|
||||
data class NFT(
|
||||
override val blockchain: String,
|
||||
override val token: String,
|
||||
override val feeType: FeeType,
|
||||
) : TxSentFrom("NFT"), TxData
|
||||
}
|
||||
|
||||
sealed interface TxData {
|
||||
|
|
|
|||
|
|
@ -22,9 +22,35 @@ sealed class MainScreenAnalyticsEvent(
|
|||
)
|
||||
|
||||
// region Action Buttons feature
|
||||
data class ButtonBuy(val status: AnalyticsParam.Status) : MainScreenAnalyticsEvent(
|
||||
data class ButtonBuy(
|
||||
val status: AnalyticsParam.Status,
|
||||
val screenType: String? = null,
|
||||
) : MainScreenAnalyticsEvent(
|
||||
event = "Button - Buy",
|
||||
params = mapOf(AnalyticsParam.STATUS to status.value),
|
||||
params = buildMap {
|
||||
put(AnalyticsParam.STATUS, status.value)
|
||||
screenType?.let { put(AnalyticsParam.TYPE, it) }
|
||||
},
|
||||
)
|
||||
|
||||
data object ButtonReceive : MainScreenAnalyticsEvent(
|
||||
event = "Button - Receive",
|
||||
)
|
||||
|
||||
data object LimitsClicked : MainScreenAnalyticsEvent(
|
||||
event = "Limits Clicked",
|
||||
)
|
||||
|
||||
data object NoticeBalancesInfo : MainScreenAnalyticsEvent(
|
||||
event = "Notice - Balances Info",
|
||||
)
|
||||
|
||||
data object NoticeLimitsInfo : MainScreenAnalyticsEvent(
|
||||
event = "Notice - Limits Info",
|
||||
)
|
||||
|
||||
data object ButtonExplore : MainScreenAnalyticsEvent(
|
||||
event = "Button - Explore",
|
||||
)
|
||||
|
||||
data class ButtonSwap(val status: AnalyticsParam.Status) : MainScreenAnalyticsEvent(
|
||||
|
|
@ -83,4 +109,9 @@ sealed class MainScreenAnalyticsEvent(
|
|||
params = mapOf(ERROR_CODE to errorCode),
|
||||
)
|
||||
// endregion
|
||||
|
||||
companion object {
|
||||
const val VISA_TYPE = "Visa"
|
||||
const val WALLET_TYPE = "Wallet"
|
||||
}
|
||||
}
|
||||
|
|
@ -7,14 +7,6 @@
|
|||
"name": "ONRAMP_ENABLED",
|
||||
"version": "undefined"
|
||||
},
|
||||
{
|
||||
"name": "ONBOARDING_CODE_REFACTORING_ENABLED",
|
||||
"version": "5.22.0"
|
||||
},
|
||||
{
|
||||
"name": "NAVIGATION_REFACTORING",
|
||||
"version": "5.23.0"
|
||||
},
|
||||
{
|
||||
"name": "VISA_ONBOARDING_ENABLED",
|
||||
"version": "undefined"
|
||||
|
|
@ -27,6 +19,14 @@
|
|||
"name": "NFT_ENABLED",
|
||||
"version": "undefined"
|
||||
},
|
||||
{
|
||||
"name": "NFT_EVM_ENABLED",
|
||||
"version": "undefined"
|
||||
},
|
||||
{
|
||||
"name": "NFT_SOLANA_ENABLED",
|
||||
"version": "undefined"
|
||||
},
|
||||
{
|
||||
"name": "STAKING_CARDANO_ENABLED",
|
||||
"version": "undefined"
|
||||
|
|
@ -36,21 +36,13 @@
|
|||
"version": "5.22.0"
|
||||
},
|
||||
{
|
||||
"name": "ASK_BIOMETRY_REFACTORING_ENABLED",
|
||||
"version": "5.22.0"
|
||||
},
|
||||
{
|
||||
"name": "NOTE_REFACTORING_ENABLED",
|
||||
"version": "5.23.0"
|
||||
"name": "NEW_ARTWORK_LOADING",
|
||||
"version": "5.24.0"
|
||||
},
|
||||
{
|
||||
"name": "NEW_ATTESTATION_ENABLED",
|
||||
"version": "undefined"
|
||||
},
|
||||
{
|
||||
"name": "TWIN_REFACTORING_ENABLED",
|
||||
"version": "5.23.0"
|
||||
},
|
||||
{
|
||||
"name": "SEND_V2_ENABLED",
|
||||
"version": "5.23.0"
|
||||
|
|
@ -62,5 +54,13 @@
|
|||
{
|
||||
"name": "NETWORKS_LOADING_REFACTORING_ENABLED",
|
||||
"version": "5.23.0"
|
||||
},
|
||||
{
|
||||
"name": "QUOTES_LOADING_REFACTORING_ENABLED",
|
||||
"version": "5.24.0"
|
||||
},
|
||||
{
|
||||
"name": "STAKING_LOADING_REFACTORING_ENABLED",
|
||||
"version": "5.25.0"
|
||||
}
|
||||
]
|
||||
|
|
|
|||
|
|
@ -32,7 +32,7 @@ internal class DevFeatureTogglesManager(
|
|||
|
||||
val savedFeatureToggles = appPreferencesStore.getObjectSyncOrNull<Map<String, Boolean>>(
|
||||
key = PreferencesKeys.FEATURE_TOGGLES_KEY,
|
||||
) ?: emptyMap()
|
||||
) ?: emptyMap<String, Boolean>()
|
||||
|
||||
val localFeatureToggles = localTogglesStorage.toggles
|
||||
.associateToggles(currentVersion = versionProvider.get().orEmpty())
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ package com.tangem.core.configtoggle.manager
|
|||
|
||||
import android.annotation.SuppressLint
|
||||
import com.google.common.truth.Truth
|
||||
import com.squareup.moshi.Moshi
|
||||
import com.tangem.core.configtoggle.feature.impl.DevFeatureTogglesManager
|
||||
import com.tangem.core.configtoggle.feature.impl.FeatureTogglesConstants
|
||||
import com.tangem.core.configtoggle.storage.ConfigToggle
|
||||
|
|
@ -12,6 +13,7 @@ import com.tangem.datasource.local.preferences.AppPreferencesStore
|
|||
import com.tangem.datasource.local.preferences.PreferencesKeys
|
||||
import com.tangem.datasource.local.preferences.utils.getObjectSyncOrNull
|
||||
import com.tangem.datasource.local.preferences.utils.getSyncOrNull
|
||||
import com.tangem.utils.coroutines.TestingCoroutineDispatcherProvider
|
||||
import io.mockk.*
|
||||
import kotlinx.coroutines.test.runTest
|
||||
import org.junit.Test
|
||||
|
|
@ -24,7 +26,11 @@ import kotlin.collections.set
|
|||
internal class DevTogglesManagerTest {
|
||||
|
||||
private val localTogglesStorage = mockk<TogglesStorage>()
|
||||
private val appPreferenceStore = mockk<AppPreferencesStore>(relaxed = true)
|
||||
private val appPreferenceStore = AppPreferencesStore(
|
||||
moshi = Moshi.Builder().build(),
|
||||
dispatchers = TestingCoroutineDispatcherProvider(),
|
||||
preferencesDataStore = mockk(relaxed = true),
|
||||
)
|
||||
private val versionProvider = mockk<VersionProvider>()
|
||||
private val manager = DevFeatureTogglesManager(
|
||||
localTogglesStorage = localTogglesStorage,
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ import com.squareup.moshi.Types
|
|||
import com.tangem.core.configtoggle.feature.impl.FeatureTogglesConstants
|
||||
import com.tangem.datasource.asset.loader.AssetLoader
|
||||
import com.tangem.datasource.asset.reader.AssetReader
|
||||
import com.tangem.utils.coroutines.TestingCoroutineDispatcherProvider
|
||||
import io.mockk.*
|
||||
import kotlinx.coroutines.test.runTest
|
||||
import org.junit.Test
|
||||
|
|
@ -24,7 +25,11 @@ internal class LocalTogglesStorageTest {
|
|||
private val jsonAdapter = mockk<JsonAdapter<List<ConfigToggle>>>()
|
||||
|
||||
// Impossible to mockk AssetLoader because it implement inline functions
|
||||
private val assetLoader = AssetLoader(assetReader = assetReader, moshi = moshi)
|
||||
private val assetLoader = AssetLoader(
|
||||
assetReader = assetReader,
|
||||
moshi = moshi,
|
||||
dispatchers = TestingCoroutineDispatcherProvider(),
|
||||
)
|
||||
|
||||
private val storage = LocalTogglesStorage(assetLoader)
|
||||
|
||||
|
|
|
|||
|
|
@ -72,6 +72,7 @@ dependencies {
|
|||
/** Chucker */
|
||||
debugImplementation(deps.chucker)
|
||||
mockedImplementation(deps.chuckerStub)
|
||||
externalImplementation(deps.chuckerStub)
|
||||
internalImplementation(deps.chuckerStub)
|
||||
releaseImplementation(deps.chuckerStub)
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,13 @@
|
|||
package com.tangem.datasource.api.common.adapter
|
||||
|
||||
import com.squareup.moshi.FromJson
|
||||
import com.squareup.moshi.ToJson
|
||||
import java.math.BigInteger
|
||||
|
||||
internal class BigIntegerAdapter {
|
||||
@FromJson
|
||||
fun fromJson(value: String) = BigInteger(value)
|
||||
|
||||
@ToJson
|
||||
fun toJson(value: BigInteger) = value.toString()
|
||||
}
|
||||
|
|
@ -0,0 +1,21 @@
|
|||
package com.tangem.datasource.api.common.blockaid
|
||||
|
||||
import com.tangem.datasource.api.common.blockaid.models.request.DomainScanRequest
|
||||
import com.tangem.datasource.api.common.blockaid.models.request.EvmTransactionScanRequest
|
||||
import com.tangem.datasource.api.common.blockaid.models.request.SolanaTransactionScanRequest
|
||||
import com.tangem.datasource.api.common.blockaid.models.response.DomainScanResponse
|
||||
import com.tangem.datasource.api.common.blockaid.models.response.TransactionScanResponse
|
||||
import retrofit2.http.Body
|
||||
import retrofit2.http.POST
|
||||
|
||||
interface BlockAidApi {
|
||||
|
||||
@POST("site/scan")
|
||||
suspend fun scanDomain(@Body request: DomainScanRequest): DomainScanResponse
|
||||
|
||||
@POST("evm/json-rpc/scan")
|
||||
suspend fun scanJsonRpc(@Body request: EvmTransactionScanRequest): TransactionScanResponse
|
||||
|
||||
@POST("solana/message/scan")
|
||||
suspend fun scanSolanaMessage(@Body request: SolanaTransactionScanRequest): TransactionScanResponse
|
||||
}
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
package com.tangem.datasource.api.common.blockaid.models.request
|
||||
|
||||
import com.squareup.moshi.Json
|
||||
import com.squareup.moshi.JsonClass
|
||||
|
||||
@JsonClass(generateAdapter = true)
|
||||
data class DomainScanRequest(
|
||||
@Json(name = "url") val url: String,
|
||||
)
|
||||
|
|
@ -0,0 +1,22 @@
|
|||
package com.tangem.datasource.api.common.blockaid.models.request
|
||||
|
||||
import com.squareup.moshi.Json
|
||||
import com.squareup.moshi.JsonClass
|
||||
import com.tangem.datasource.api.common.blockaid.models.response.TransactionMetadata
|
||||
|
||||
@JsonClass(generateAdapter = true)
|
||||
data class EvmTransactionScanRequest(
|
||||
@Json(name = "chain") val chain: String,
|
||||
@Json(name = "account_address") val accountAddress: String,
|
||||
@Json(name = "method") val method: String,
|
||||
@Json(name = "data") val data: RpcData,
|
||||
@Json(name = "options") val options: List<String> = listOf("simulation", "validation"),
|
||||
@Json(name = "metadata") val metadata: TransactionMetadata,
|
||||
)
|
||||
|
||||
@JsonClass(generateAdapter = true)
|
||||
data class RpcData(
|
||||
@Json(name = "jsonrpc") val jsonrpc: String = "2.0",
|
||||
@Json(name = "method") val method: String,
|
||||
@Json(name = "params") val params: String,
|
||||
)
|
||||
|
|
@ -0,0 +1,16 @@
|
|||
package com.tangem.datasource.api.common.blockaid.models.request
|
||||
|
||||
import com.squareup.moshi.Json
|
||||
import com.squareup.moshi.JsonClass
|
||||
import com.tangem.datasource.api.common.blockaid.models.response.TransactionMetadata
|
||||
|
||||
@JsonClass(generateAdapter = true)
|
||||
data class SolanaTransactionScanRequest(
|
||||
@Json(name = "encoding") val encoding: String = "base64",
|
||||
@Json(name = "chain") val chain: String,
|
||||
@Json(name = "method") val method: String,
|
||||
@Json(name = "options") val options: List<String> = listOf("simulation, validation"),
|
||||
@Json(name = "metadata") val metadata: TransactionMetadata,
|
||||
@Json(name = "account_address") val accountAddress: String,
|
||||
@Json(name = "transactions") val transactions: List<String>,
|
||||
)
|
||||
|
|
@ -0,0 +1,10 @@
|
|||
package com.tangem.datasource.api.common.blockaid.models.response
|
||||
|
||||
import com.squareup.moshi.Json
|
||||
import com.squareup.moshi.JsonClass
|
||||
|
||||
@JsonClass(generateAdapter = true)
|
||||
data class AccountSummaryResponse(
|
||||
@Json(name = "assets_diffs") val assetsDiffs: List<AssetDiff>,
|
||||
@Json(name = "exposures") val exposures: List<Exposure>,
|
||||
)
|
||||
|
|
@ -0,0 +1,25 @@
|
|||
package com.tangem.datasource.api.common.blockaid.models.response
|
||||
|
||||
import com.squareup.moshi.Json
|
||||
import com.squareup.moshi.JsonClass
|
||||
|
||||
@JsonClass(generateAdapter = true)
|
||||
data class AssetDiff(
|
||||
@Json(name = "asset_type") val assetType: String,
|
||||
@Json(name = "asset") val asset: Asset,
|
||||
@Json(name = "in") val inTransfer: List<Transfer>? = null,
|
||||
@Json(name = "out") val outTransfer: List<Transfer>? = null,
|
||||
)
|
||||
|
||||
@JsonClass(generateAdapter = true)
|
||||
data class Asset(
|
||||
@Json(name = "chain_id") val chainId: Int? = null,
|
||||
@Json(name = "logo_url") val logoUrl: String? = null,
|
||||
@Json(name = "symbol") val symbol: String,
|
||||
)
|
||||
|
||||
@JsonClass(generateAdapter = true)
|
||||
data class Transfer(
|
||||
@Json(name = "value") val value: String,
|
||||
@Json(name = "raw_value") val rawValue: String,
|
||||
)
|
||||
|
|
@ -0,0 +1,10 @@
|
|||
package com.tangem.datasource.api.common.blockaid.models.response
|
||||
|
||||
import com.squareup.moshi.Json
|
||||
import com.squareup.moshi.JsonClass
|
||||
|
||||
@JsonClass(generateAdapter = true)
|
||||
data class DomainScanResponse(
|
||||
@Json(name = "status") val status: String,
|
||||
@Json(name = "is_malicious") val isMalicious: Boolean?,
|
||||
)
|
||||
|
|
@ -0,0 +1,22 @@
|
|||
package com.tangem.datasource.api.common.blockaid.models.response
|
||||
|
||||
import com.squareup.moshi.Json
|
||||
import com.squareup.moshi.JsonClass
|
||||
|
||||
@JsonClass(generateAdapter = true)
|
||||
data class Exposure(
|
||||
@Json(name = "asset") val asset: Asset,
|
||||
@Json(name = "spenders") val spenders: Map<String, SpenderDetails>,
|
||||
)
|
||||
|
||||
@JsonClass(generateAdapter = true)
|
||||
data class SpenderDetails(
|
||||
@Json(name = "exposure") val exposure: List<ExposureDetail>,
|
||||
@Json(name = "is_approved_for_all") val isApprovedForAll: Boolean? = null,
|
||||
)
|
||||
|
||||
@JsonClass(generateAdapter = true)
|
||||
data class ExposureDetail(
|
||||
@Json(name = "value") val value: String,
|
||||
@Json(name = "raw_value") val rawValue: String,
|
||||
)
|
||||
|
|
@ -0,0 +1,10 @@
|
|||
package com.tangem.datasource.api.common.blockaid.models.response
|
||||
|
||||
import com.squareup.moshi.Json
|
||||
import com.squareup.moshi.JsonClass
|
||||
|
||||
@JsonClass(generateAdapter = true)
|
||||
data class SimulationResponse(
|
||||
@Json(name = "status") val status: String,
|
||||
@Json(name = "account_summary") val accountSummary: AccountSummaryResponse,
|
||||
)
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
package com.tangem.datasource.api.common.blockaid.models.response
|
||||
|
||||
import com.squareup.moshi.Json
|
||||
import com.squareup.moshi.JsonClass
|
||||
|
||||
@JsonClass(generateAdapter = true)
|
||||
data class TransactionMetadata(
|
||||
@Json(name = "domain") val domain: String,
|
||||
)
|
||||
|
|
@ -0,0 +1,10 @@
|
|||
package com.tangem.datasource.api.common.blockaid.models.response
|
||||
|
||||
import com.squareup.moshi.Json
|
||||
import com.squareup.moshi.JsonClass
|
||||
|
||||
@JsonClass(generateAdapter = true)
|
||||
data class TransactionScanResponse(
|
||||
@Json(name = "validation") val validation: ValidationResponse,
|
||||
@Json(name = "simulation") val simulation: SimulationResponse,
|
||||
)
|
||||
|
|
@ -0,0 +1,10 @@
|
|||
package com.tangem.datasource.api.common.blockaid.models.response
|
||||
|
||||
import com.squareup.moshi.Json
|
||||
import com.squareup.moshi.JsonClass
|
||||
|
||||
@JsonClass(generateAdapter = true)
|
||||
data class ValidationResponse(
|
||||
@Json(name = "status") val status: String,
|
||||
@Json(name = "result_type") val resultType: String,
|
||||
)
|
||||
|
|
@ -24,9 +24,9 @@ sealed class ApiConfig {
|
|||
Express,
|
||||
TangemTech,
|
||||
StakeKit,
|
||||
TangemVisaAuth,
|
||||
TangemVisa,
|
||||
TangemCardSdk,
|
||||
BlockAid,
|
||||
}
|
||||
|
||||
private fun initializeId(): ID {
|
||||
|
|
@ -34,9 +34,9 @@ sealed class ApiConfig {
|
|||
is Express -> ID.Express
|
||||
is TangemTech -> ID.TangemTech
|
||||
is StakeKit -> ID.StakeKit
|
||||
is TangemVisaAuth -> ID.TangemVisaAuth
|
||||
is TangemVisa -> ID.TangemVisa
|
||||
is TangemCardSdk -> ID.TangemCardSdk
|
||||
is BlockAid -> ID.BlockAid
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -44,6 +44,7 @@ sealed class ApiConfig {
|
|||
internal const val DEBUG_BUILD_TYPE = "debug"
|
||||
internal const val INTERNAL_BUILD_TYPE = "internal"
|
||||
internal const val MOCKED_BUILD_TYPE = "mocked"
|
||||
internal const val EXTERNAL_BUILD_TYPE = "external"
|
||||
internal const val RELEASE_BUILD_TYPE = "release"
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,33 @@
|
|||
package com.tangem.datasource.api.common.config
|
||||
|
||||
import com.tangem.datasource.local.config.environment.EnvironmentConfigStorage
|
||||
import com.tangem.utils.ProviderSuspend
|
||||
import kotlinx.coroutines.flow.first
|
||||
|
||||
internal class BlockAid(
|
||||
private val configStorage: EnvironmentConfigStorage,
|
||||
) : ApiConfig() {
|
||||
|
||||
override val defaultEnvironment: ApiEnvironment = ApiEnvironment.PROD
|
||||
|
||||
override val environmentConfigs = listOf(
|
||||
createProdEnvironment(),
|
||||
)
|
||||
|
||||
private fun createProdEnvironment(): ApiEnvironmentConfig = ApiEnvironmentConfig(
|
||||
environment = ApiEnvironment.PROD,
|
||||
baseUrl = "https://api.blockaid.io/v0/",
|
||||
headers = buildMap {
|
||||
put(
|
||||
key = "X-API-KEY",
|
||||
value = ProviderSuspend {
|
||||
requireNotNull(
|
||||
configStorage.getConfig().first { !it.blockAidApiKey.isNullOrEmpty() }.blockAidApiKey,
|
||||
)
|
||||
},
|
||||
)
|
||||
put("accept", ProviderSuspend { "application/json" })
|
||||
put("content-type", ProviderSuspend { "application/json" })
|
||||
},
|
||||
)
|
||||
}
|
||||
|
|
@ -48,10 +48,8 @@ internal class Express(
|
|||
|
||||
private fun createHeaders(isProd: Boolean) = buildMap {
|
||||
put(key = "api-key", value = ProviderSuspend { getApiKey(isProd) })
|
||||
put(key = "user-id", value = ProviderSuspend(expressAuthProvider::getUserId))
|
||||
put(key = "session-id", value = ProviderSuspend(expressAuthProvider::getSessionId))
|
||||
putAll(from = RequestHeader.AppVersionPlatformHeaders(appVersionProvider).values)
|
||||
put(key = "refcode", value = ProviderSuspend(expressAuthProvider::getRefCode))
|
||||
}
|
||||
|
||||
private fun getApiKey(isProd: Boolean): String {
|
||||
|
|
@ -73,6 +71,7 @@ internal class Express(
|
|||
INTERNAL_BUILD_TYPE,
|
||||
MOCKED_BUILD_TYPE,
|
||||
-> ApiEnvironment.STAGE
|
||||
EXTERNAL_BUILD_TYPE,
|
||||
RELEASE_BUILD_TYPE,
|
||||
-> ApiEnvironment.PROD
|
||||
else -> error("Unknown build type [${BuildConfig.BUILD_TYPE}]")
|
||||
|
|
|
|||
|
|
@ -7,15 +7,22 @@ internal class TangemVisa(
|
|||
private val appVersionProvider: AppVersionProvider,
|
||||
) : ApiConfig() {
|
||||
|
||||
override val defaultEnvironment: ApiEnvironment = ApiEnvironment.PROD
|
||||
override val defaultEnvironment: ApiEnvironment = ApiEnvironment.DEV
|
||||
|
||||
override val environmentConfigs = listOf(
|
||||
createProdEnvironment(),
|
||||
createDevEnvironment(),
|
||||
)
|
||||
|
||||
private fun createProdEnvironment(): ApiEnvironmentConfig = ApiEnvironmentConfig(
|
||||
environment = ApiEnvironment.PROD,
|
||||
baseUrl = "https://bff.tangem.com/",
|
||||
baseUrl = "https://api.paera.com/bff/",
|
||||
headers = createHeaders(),
|
||||
)
|
||||
|
||||
private fun createDevEnvironment(): ApiEnvironmentConfig = ApiEnvironmentConfig(
|
||||
environment = ApiEnvironment.DEV,
|
||||
baseUrl = "[REDACTED_ENV_URL]",
|
||||
headers = createHeaders(),
|
||||
)
|
||||
|
||||
|
|
|
|||
|
|
@ -1,26 +0,0 @@
|
|||
package com.tangem.datasource.api.common.config
|
||||
|
||||
import com.tangem.utils.ProviderSuspend
|
||||
import com.tangem.utils.version.AppVersionProvider
|
||||
|
||||
internal class TangemVisaAuth(
|
||||
private val appVersionProvider: AppVersionProvider,
|
||||
) : ApiConfig() {
|
||||
|
||||
override val defaultEnvironment: ApiEnvironment = ApiEnvironment.STAGE
|
||||
|
||||
override val environmentConfigs = listOf(
|
||||
createStageEnvironment(),
|
||||
)
|
||||
|
||||
private fun createStageEnvironment(): ApiEnvironmentConfig = ApiEnvironmentConfig(
|
||||
environment = ApiEnvironment.STAGE,
|
||||
baseUrl = "https://api-s.tangem.org/",
|
||||
headers = createHeaders(),
|
||||
)
|
||||
|
||||
private fun createHeaders() = mapOf(
|
||||
"version" to ProviderSuspend { appVersionProvider.versionName },
|
||||
"platform" to ProviderSuspend { "Android" },
|
||||
)
|
||||
}
|
||||
|
|
@ -5,10 +5,7 @@ import com.tangem.datasource.api.express.models.request.AssetsRequestBody
|
|||
import com.tangem.datasource.api.express.models.request.ExchangeSentRequestBody
|
||||
import com.tangem.datasource.api.express.models.request.PairsRequestBody
|
||||
import com.tangem.datasource.api.express.models.response.*
|
||||
import retrofit2.http.Body
|
||||
import retrofit2.http.GET
|
||||
import retrofit2.http.POST
|
||||
import retrofit2.http.Query
|
||||
import retrofit2.http.*
|
||||
|
||||
/**
|
||||
* Interface of Tangem Express API (new swap mechanism)
|
||||
|
|
@ -17,16 +14,29 @@ import retrofit2.http.Query
|
|||
interface TangemExpressApi {
|
||||
|
||||
@POST("assets")
|
||||
suspend fun getAssets(@Body body: AssetsRequestBody): ApiResponse<List<Asset>>
|
||||
suspend fun getAssets(
|
||||
@Header("user-id") userWalletId: String,
|
||||
@Header("refcode") refCode: String,
|
||||
@Body body: AssetsRequestBody,
|
||||
): ApiResponse<List<Asset>>
|
||||
|
||||
@POST("pairs")
|
||||
suspend fun getPairs(@Body body: PairsRequestBody): ApiResponse<List<SwapPair>>
|
||||
suspend fun getPairs(
|
||||
@Header("user-id") userWalletId: String,
|
||||
@Header("refcode") refCode: String,
|
||||
@Body body: PairsRequestBody,
|
||||
): ApiResponse<List<SwapPair>>
|
||||
|
||||
@GET("providers")
|
||||
suspend fun getProviders(): ApiResponse<List<ExchangeProvider>>
|
||||
suspend fun getProviders(
|
||||
@Header("user-id") userWalletId: String,
|
||||
@Header("refcode") refCode: String,
|
||||
): ApiResponse<List<ExchangeProvider>>
|
||||
|
||||
@GET("exchange-quote")
|
||||
suspend fun getExchangeQuote(
|
||||
@Header("user-id") userWalletId: String,
|
||||
@Header("refcode") refCode: String,
|
||||
@Query("fromContractAddress") fromContractAddress: String,
|
||||
@Query("fromNetwork") fromNetwork: String,
|
||||
@Query("toContractAddress") toContractAddress: String,
|
||||
|
|
@ -40,6 +50,8 @@ interface TangemExpressApi {
|
|||
|
||||
@GET("exchange-data")
|
||||
suspend fun getExchangeData(
|
||||
@Header("user-id") userWalletId: String,
|
||||
@Header("refcode") refCode: String,
|
||||
@Query("fromContractAddress") fromContractAddress: String,
|
||||
@Query("fromNetwork") fromNetwork: String,
|
||||
@Query("toContractAddress") toContractAddress: String,
|
||||
|
|
@ -57,8 +69,16 @@ interface TangemExpressApi {
|
|||
): ApiResponse<ExchangeDataResponse>
|
||||
|
||||
@GET("exchange-status")
|
||||
suspend fun getExchangeStatus(@Query("txId") txId: String): ApiResponse<ExchangeStatusResponse>
|
||||
suspend fun getExchangeStatus(
|
||||
@Header("user-id") userWalletId: String,
|
||||
@Header("refcode") refCode: String,
|
||||
@Query("txId") txId: String,
|
||||
): ApiResponse<ExchangeStatusResponse>
|
||||
|
||||
@POST("exchange-sent")
|
||||
suspend fun exchangeSent(@Body body: ExchangeSentRequestBody): ApiResponse<ExchangeSentResponseBody>
|
||||
suspend fun exchangeSent(
|
||||
@Header("user-id") userWalletId: String,
|
||||
@Header("refcode") refCode: String,
|
||||
@Body body: ExchangeSentRequestBody,
|
||||
): ApiResponse<ExchangeSentResponseBody>
|
||||
}
|
||||
|
|
@ -3,37 +3,52 @@ package com.tangem.datasource.api.onramp
|
|||
import com.tangem.datasource.api.common.response.ApiResponse
|
||||
import com.tangem.datasource.api.onramp.models.request.OnrampPairsRequest
|
||||
import com.tangem.datasource.api.onramp.models.response.OnrampDataResponse
|
||||
import com.tangem.datasource.api.onramp.models.response.model.OnrampPairDTO
|
||||
import com.tangem.datasource.api.onramp.models.response.OnrampQuoteResponse
|
||||
import com.tangem.datasource.api.onramp.models.response.OnrampStatusResponse
|
||||
import com.tangem.datasource.api.onramp.models.response.model.OnrampCountryDTO
|
||||
import com.tangem.datasource.api.onramp.models.response.model.OnrampCurrencyDTO
|
||||
import com.tangem.datasource.api.onramp.models.response.model.OnrampPairDTO
|
||||
import com.tangem.datasource.api.onramp.models.response.model.PaymentMethodDTO
|
||||
import retrofit2.http.Body
|
||||
import retrofit2.http.GET
|
||||
import retrofit2.http.POST
|
||||
import retrofit2.http.Query
|
||||
import retrofit2.http.*
|
||||
|
||||
@Suppress("LongParameterList", "LargeClass", "TooManyFunctions")
|
||||
interface OnrampApi {
|
||||
|
||||
@GET("currencies")
|
||||
suspend fun getCurrencies(): ApiResponse<List<OnrampCurrencyDTO>>
|
||||
suspend fun getCurrencies(
|
||||
@Header("user-id") userWalletId: String,
|
||||
@Header("refcode") refCode: String,
|
||||
): ApiResponse<List<OnrampCurrencyDTO>>
|
||||
|
||||
@GET("countries")
|
||||
suspend fun getCountries(): ApiResponse<List<OnrampCountryDTO>>
|
||||
suspend fun getCountries(
|
||||
@Header("user-id") userWalletId: String,
|
||||
@Header("refcode") refCode: String,
|
||||
): ApiResponse<List<OnrampCountryDTO>>
|
||||
|
||||
@GET("country-by-ip")
|
||||
suspend fun getCountryByIp(): ApiResponse<OnrampCountryDTO>
|
||||
suspend fun getCountryByIp(
|
||||
@Header("user-id") userWalletId: String,
|
||||
@Header("refcode") refCode: String,
|
||||
): ApiResponse<OnrampCountryDTO>
|
||||
|
||||
@GET("payment-methods")
|
||||
suspend fun getPaymentMethods(): ApiResponse<List<PaymentMethodDTO>>
|
||||
suspend fun getPaymentMethods(
|
||||
@Header("user-id") userWalletId: String,
|
||||
@Header("refcode") refCode: String,
|
||||
): ApiResponse<List<PaymentMethodDTO>>
|
||||
|
||||
@POST("onramp-pairs")
|
||||
suspend fun getPairs(@Body body: OnrampPairsRequest): ApiResponse<List<OnrampPairDTO>>
|
||||
suspend fun getPairs(
|
||||
@Header("user-id") userWalletId: String,
|
||||
@Header("refcode") refCode: String,
|
||||
@Body body: OnrampPairsRequest,
|
||||
): ApiResponse<List<OnrampPairDTO>>
|
||||
|
||||
@GET("onramp-quote")
|
||||
suspend fun getQuote(
|
||||
@Header("user-id") userWalletId: String,
|
||||
@Header("refcode") refCode: String,
|
||||
@Query("fromCurrencyCode") fromCurrencyCode: String,
|
||||
@Query("fromPrecision") fromPrecision: Int,
|
||||
@Query("toContractAddress") toContractAddress: String,
|
||||
|
|
@ -47,6 +62,8 @@ interface OnrampApi {
|
|||
|
||||
@GET("onramp-data")
|
||||
suspend fun getData(
|
||||
@Header("user-id") userWalletId: String,
|
||||
@Header("refcode") refCode: String?,
|
||||
@Query("fromCurrencyCode") fromCurrencyCode: String,
|
||||
@Query("fromPrecision") fromPrecision: Int,
|
||||
@Query("toContractAddress") toContractAddress: String,
|
||||
|
|
@ -64,5 +81,9 @@ interface OnrampApi {
|
|||
): ApiResponse<OnrampDataResponse>
|
||||
|
||||
@GET("onramp-status")
|
||||
suspend fun getStatus(@Query("txId") txId: String): ApiResponse<OnrampStatusResponse>
|
||||
suspend fun getStatus(
|
||||
@Header("user-id") userWalletId: String,
|
||||
@Header("refcode") refCode: String,
|
||||
@Query("txId") txId: String,
|
||||
): ApiResponse<OnrampStatusResponse>
|
||||
}
|
||||
|
|
@ -32,7 +32,7 @@ data class OnrampDataJson(
|
|||
val requestId: String,
|
||||
|
||||
@Json(name = "externalTxId")
|
||||
val externalTxId: String,
|
||||
val externalTxId: String?,
|
||||
|
||||
@Json(name = "externalTxUrl")
|
||||
val externalTxUrl: String?,
|
||||
|
|
|
|||
|
|
@ -21,7 +21,7 @@ data class OnrampStatusResponse(
|
|||
val failReason: String?,
|
||||
|
||||
@Json(name = "externalTxId")
|
||||
val externalTxId: String,
|
||||
val externalTxId: String?,
|
||||
|
||||
@Json(name = "externalTxUrl")
|
||||
val externalTxUrl: String?,
|
||||
|
|
@ -91,4 +91,10 @@ enum class Status {
|
|||
|
||||
@Json(name = "paused")
|
||||
Paused,
|
||||
|
||||
@Json(name = "refund-in-progress")
|
||||
RefundInProgress,
|
||||
|
||||
@Json(name = "refunded")
|
||||
Refunded,
|
||||
}
|
||||
|
|
@ -140,6 +140,40 @@ interface TangemTechApi {
|
|||
@GET("stories/{story_id}")
|
||||
suspend fun getStoryById(@Path("story_id") storyId: String): ApiResponse<StoryContentResponse>
|
||||
|
||||
// region push notifications
|
||||
@GET("notification/push_notifications_eligible_networks")
|
||||
suspend fun getEligibleNetworksForPushNotifications(): ApiResponse<List<CryptoNetworkResponse>>
|
||||
|
||||
@POST("user-wallets/applications/")
|
||||
suspend fun createApplicationId(
|
||||
@Body
|
||||
body: NotificationApplicationCreateBody,
|
||||
): ApiResponse<NotificationApplicationIdResponse>
|
||||
|
||||
@PATCH("user-wallets/applications/{application_id}")
|
||||
suspend fun updatePushTokenForApplicationId(
|
||||
@Path("application_id") applicationId: String,
|
||||
@Body body: NotificationApplicationCreateBody,
|
||||
): ApiResponse<String>
|
||||
|
||||
@PATCH("user-wallets/wallets/{wallet_id}/notify")
|
||||
suspend fun setNotificationsEnabled(@Path("wallet_id") walletId: String, @Body body: WalletBody): ApiResponse<Unit>
|
||||
// endregion
|
||||
|
||||
// region wallets
|
||||
@PATCH("user-wallets/wallets/{wallet_id}")
|
||||
suspend fun updateWallet(@Path("wallet_id") walletId: String, @Body body: WalletBody): ApiResponse<Unit>
|
||||
|
||||
@POST("user-wallets/wallets/create-and-connect-by-appuid/{application_id}")
|
||||
suspend fun associateApplicationIdWithWallets(
|
||||
@Path("application_id") applicationId: String,
|
||||
@Body body: List<WalletIdBody>,
|
||||
): ApiResponse<Unit>
|
||||
|
||||
@GET("user-wallets/wallets/{wallet_id}")
|
||||
suspend fun getWalletById(@Path("wallet_id") walletId: String): ApiResponse<WalletResponse>
|
||||
// endregion
|
||||
|
||||
companion object {
|
||||
val marketsQuoteFields = listOf(
|
||||
"price",
|
||||
|
|
|
|||
|
|
@ -0,0 +1,11 @@
|
|||
package com.tangem.datasource.api.tangemTech.models
|
||||
|
||||
import com.squareup.moshi.Json
|
||||
import com.squareup.moshi.JsonClass
|
||||
|
||||
@JsonClass(generateAdapter = true)
|
||||
data class CryptoNetworkResponse(
|
||||
@Json(name = "id") val id: Int,
|
||||
@Json(name = "networkId") val networkId: String,
|
||||
@Json(name = "name") val name: String,
|
||||
)
|
||||
|
|
@ -0,0 +1,14 @@
|
|||
package com.tangem.datasource.api.tangemTech.models
|
||||
|
||||
import com.squareup.moshi.Json
|
||||
import com.squareup.moshi.JsonClass
|
||||
|
||||
@JsonClass(generateAdapter = true)
|
||||
data class NotificationApplicationCreateBody(
|
||||
@Json(name = "pushToken") val pushToken: String? = null,
|
||||
@Json(name = "platform") val platform: String? = null,
|
||||
@Json(name = "device") val device: String? = null,
|
||||
@Json(name = "systemVersion") val systemVersion: String? = null,
|
||||
@Json(name = "language") val language: String? = null,
|
||||
@Json(name = "timezone") val timezone: String? = null,
|
||||
)
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
package com.tangem.datasource.api.tangemTech.models
|
||||
|
||||
import com.squareup.moshi.Json
|
||||
import com.squareup.moshi.JsonClass
|
||||
|
||||
@JsonClass(generateAdapter = true)
|
||||
data class NotificationApplicationIdResponse(
|
||||
@Json(name = "uid") val appId: String,
|
||||
)
|
||||
|
|
@ -0,0 +1,12 @@
|
|||
package com.tangem.datasource.api.tangemTech.models
|
||||
|
||||
import com.squareup.moshi.Json
|
||||
import com.squareup.moshi.JsonClass
|
||||
|
||||
@JsonClass(generateAdapter = true)
|
||||
data class NotificationApplicationUpdateBody(
|
||||
@Json(name = "pushToken") val pushToken: String,
|
||||
@Json(name = "systemVersion") val systemVersion: String? = null,
|
||||
@Json(name = "language") val language: String? = null,
|
||||
@Json(name = "timezone") val timezone: String? = null,
|
||||
)
|
||||
|
|
@ -0,0 +1,10 @@
|
|||
package com.tangem.datasource.api.tangemTech.models
|
||||
|
||||
import com.squareup.moshi.Json
|
||||
import com.squareup.moshi.JsonClass
|
||||
|
||||
@JsonClass(generateAdapter = true)
|
||||
data class WalletBody(
|
||||
@Json(name = "notifyStatus") val notifyStatus: Boolean? = null,
|
||||
@Json(name = "name") val name: String? = null,
|
||||
)
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
package com.tangem.datasource.api.tangemTech.models
|
||||
|
||||
import com.squareup.moshi.Json
|
||||
import com.squareup.moshi.JsonClass
|
||||
|
||||
@JsonClass(generateAdapter = true)
|
||||
data class WalletIdBody(
|
||||
@Json(name = "id") val walletId: String,
|
||||
)
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
package com.tangem.datasource.api.tangemTech.models
|
||||
|
||||
import com.squareup.moshi.Json
|
||||
import com.squareup.moshi.JsonClass
|
||||
|
||||
@JsonClass(generateAdapter = true)
|
||||
data class WalletResponse(
|
||||
@Json(name = "notifyStatus") val notifyStatus: Boolean,
|
||||
@Json(name = "id") val id: String,
|
||||
@Json(name = "name") val name: String? = null,
|
||||
)
|
||||
|
|
@ -1,9 +1,17 @@
|
|||
package com.tangem.datasource.api.visa
|
||||
|
||||
import com.tangem.datasource.api.common.response.ApiResponse
|
||||
import com.tangem.datasource.api.utils.ReadTimeout
|
||||
import com.tangem.datasource.api.visa.models.request.ActivationByCardWalletRequest
|
||||
import com.tangem.datasource.api.visa.models.request.ActivationByCustomerWalletRequest
|
||||
import com.tangem.datasource.api.visa.models.request.ActivationStatusRequest
|
||||
import com.tangem.datasource.api.visa.models.request.GenerateNoneByCardIdRequest
|
||||
import com.tangem.datasource.api.visa.models.request.GenerateNoneByCardWalletRequest
|
||||
import com.tangem.datasource.api.visa.models.request.GetAccessTokenByCardIdRequest
|
||||
import com.tangem.datasource.api.visa.models.request.GetAccessTokenByCardWalletRequest
|
||||
import com.tangem.datasource.api.visa.models.request.GetCardWalletAcceptanceRequest
|
||||
import com.tangem.datasource.api.visa.models.request.GetCustomerWalletAcceptanceRequest
|
||||
import com.tangem.datasource.api.visa.models.request.RefreshTokenByCardIdRequest
|
||||
import com.tangem.datasource.api.visa.models.request.RefreshTokenByCardWalletRequest
|
||||
import com.tangem.datasource.api.visa.models.request.SetPinCodeRequest
|
||||
import com.tangem.datasource.api.visa.models.response.*
|
||||
import retrofit2.http.Body
|
||||
|
|
@ -11,65 +19,71 @@ import retrofit2.http.GET
|
|||
import retrofit2.http.Header
|
||||
import retrofit2.http.POST
|
||||
import retrofit2.http.Query
|
||||
import java.util.concurrent.TimeUnit
|
||||
|
||||
interface TangemVisaApi {
|
||||
|
||||
@GET("product_instance/activation_status")
|
||||
// region: auth
|
||||
|
||||
@POST("v1/auth/challenge")
|
||||
suspend fun generateNonceByCardId(@Body request: GenerateNoneByCardIdRequest): GenerateNonceResponse
|
||||
|
||||
@POST("v1/auth/challenge")
|
||||
suspend fun generateNonceByCardWallet(@Body request: GenerateNoneByCardWalletRequest): GenerateNonceResponse
|
||||
|
||||
@POST("v1/auth/token")
|
||||
suspend fun getAccessTokenByCardId(@Body request: GetAccessTokenByCardIdRequest): JWTResponse
|
||||
|
||||
@POST("v1/auth/token")
|
||||
suspend fun getAccessTokenByCardWallet(@Body request: GetAccessTokenByCardWalletRequest): JWTResponse
|
||||
|
||||
@POST("v1/auth/token/refresh")
|
||||
suspend fun refreshCardIdAccessToken(@Body request: RefreshTokenByCardIdRequest): ApiResponse<JWTResponse>
|
||||
|
||||
@POST("v1/auth/token/refresh")
|
||||
suspend fun refreshCardWalletAccessToken(@Body request: RefreshTokenByCardWalletRequest): ApiResponse<JWTResponse>
|
||||
|
||||
// endregion
|
||||
|
||||
// region: activation
|
||||
|
||||
@POST("v1/activation/status")
|
||||
suspend fun getRemoteActivationStatus(
|
||||
@Header("Authorization") authHeader: String,
|
||||
@Query("customer_id") customerId: String,
|
||||
@Query("product_instance_id") productInstanceId: String,
|
||||
@Query("card_id") cardId: String,
|
||||
@Query("card_public_key") cardPublicKey: String,
|
||||
@Body request: ActivationStatusRequest,
|
||||
): ApiResponse<CardActivationRemoteStateResponse>
|
||||
|
||||
@ReadTimeout(duration = 20, TimeUnit.MINUTES)
|
||||
@GET("product_instance/activation_status")
|
||||
suspend fun getRemoteActivationStatusLongPoll(
|
||||
@Header("Authorization") authHeader: String,
|
||||
@Query("customer_id") customerId: String,
|
||||
@Query("product_instance_id") productInstanceId: String,
|
||||
@Query("card_id") cardId: String,
|
||||
@Query("card_public_key") cardPublicKey: String,
|
||||
): ApiResponse<CardActivationRemoteStateResponse>
|
||||
|
||||
@GET("product_instance/card_wallet_acceptance")
|
||||
@POST("v1/activation/acceptance/message")
|
||||
suspend fun getCardWalletAcceptance(
|
||||
@Header("Authorization") authHeader: String,
|
||||
@Query("customer_id") customerId: String,
|
||||
@Query("product_instance_id") productInstanceId: String,
|
||||
@Query("activation_order") activationOrderId: String,
|
||||
@Query("customer_wallet_address") customerWalletAddress: String,
|
||||
): ApiResponse<CardWalletDataToSignResponse>
|
||||
@Body request: GetCardWalletAcceptanceRequest,
|
||||
): ApiResponse<VisaDataToSignResponse>
|
||||
|
||||
@GET("product_instance/customer_wallet_acceptance")
|
||||
@POST("v1/activation/acceptance/message")
|
||||
suspend fun getCustomerWalletAcceptance(
|
||||
@Header("Authorization") authHeader: String,
|
||||
@Query("customer_id") customerId: String,
|
||||
@Query("product_instance_id") productInstanceId: String,
|
||||
@Query("activation_order") activationOrderId: String,
|
||||
@Query("card_wallet_address") cardWalletAddress: String,
|
||||
): ApiResponse<CustomerWalletDataToSignResponse>
|
||||
@Body request: GetCustomerWalletAcceptanceRequest,
|
||||
): ApiResponse<VisaDataToSignResponse>
|
||||
|
||||
@POST("product_instance/activation_by_card_wallet")
|
||||
@POST("v1/activation/data")
|
||||
suspend fun activateByCardWallet(
|
||||
@Header("Authorization") authHeader: String,
|
||||
@Body body: ActivationByCardWalletRequest,
|
||||
): ApiResponse<Unit>
|
||||
|
||||
@POST("product_instance/activation_by_customer_wallet")
|
||||
@POST("v1/activation/data")
|
||||
suspend fun activateByCustomerWallet(
|
||||
@Header("Authorization") authHeader: String,
|
||||
@Body body: ActivationByCustomerWalletRequest,
|
||||
): ApiResponse<Unit>
|
||||
|
||||
@POST("product_instance/issuer_activation")
|
||||
@POST("v1/activation/pin")
|
||||
suspend fun setPinCode(
|
||||
@Header("Authorization") authHeader: String,
|
||||
@Body body: SetPinCodeRequest,
|
||||
): ApiResponse<Unit>
|
||||
|
||||
// endregion
|
||||
|
||||
@GET("customer/info")
|
||||
suspend fun getCustomerInfo(
|
||||
@Header("Authorization") authHeader: String,
|
||||
|
|
|
|||
|
|
@ -1,32 +0,0 @@
|
|||
package com.tangem.datasource.api.visa
|
||||
|
||||
import com.tangem.datasource.api.common.response.ApiResponse
|
||||
import com.tangem.datasource.api.visa.models.response.GenerateNonceResponse
|
||||
import com.tangem.datasource.api.visa.models.response.JWTResponse
|
||||
import retrofit2.http.Field
|
||||
import retrofit2.http.POST
|
||||
import retrofit2.http.Query
|
||||
|
||||
interface TangemVisaAuthApi {
|
||||
|
||||
@POST("auth/card_wallet")
|
||||
suspend fun generateNonceByWalletAddress(
|
||||
@Query("card_wallet_address") cardWalletAddress: String,
|
||||
): GenerateNonceResponse
|
||||
|
||||
@POST("auth/card_id")
|
||||
suspend fun generateNonceByCard(
|
||||
@Query("card_id") cardId: String,
|
||||
@Query("card_public_key") cardPublicKey: String,
|
||||
): GenerateNonceResponse
|
||||
|
||||
@POST("auth/get_token")
|
||||
suspend fun getAccessToken(
|
||||
@Query("session_id") sessionId: String,
|
||||
@Query("signature") signature: String,
|
||||
@Query("salt") salt: String?,
|
||||
): JWTResponse
|
||||
|
||||
@POST("auth/refresh_token")
|
||||
suspend fun refreshAccessToken(@Field("refresh_token") refreshToken: String): ApiResponse<JWTResponse>
|
||||
}
|
||||
|
|
@ -5,22 +5,15 @@ import com.squareup.moshi.JsonClass
|
|||
|
||||
@JsonClass(generateAdapter = true)
|
||||
data class ActivationByCardWalletRequest(
|
||||
@Json(name = "customer_id") val customerId: String,
|
||||
@Json(name = "product_instance_id") val productInstanceId: String,
|
||||
@Json(name = "activation_order_id") val activationOrderId: String,
|
||||
@Json(name = "data") val data: Data,
|
||||
@Json(name = "order_id") val orderId: String,
|
||||
@Json(name = "card_wallet") val cardWallet: CardWallet,
|
||||
@Json(name = "deploy_acceptance_signature") val deployAcceptanceSignature: String,
|
||||
@Json(name = "otp") val otp: Otp,
|
||||
) {
|
||||
@JsonClass(generateAdapter = true)
|
||||
data class Data(
|
||||
@Json(name = "card_wallet") val cardWallet: CardWallet,
|
||||
@Json(name = "otp") val otp: Otp,
|
||||
)
|
||||
|
||||
@JsonClass(generateAdapter = true)
|
||||
data class CardWallet(
|
||||
@Json(name = "address") val address: String,
|
||||
@Json(name = "card_wallet_confirmation") val cardWalletConfirmation: CardWalletConfirmation?,
|
||||
@Json(name = "deploy_acceptance_signature") val deployAcceptanceSignature: String,
|
||||
)
|
||||
|
||||
@JsonClass(generateAdapter = true)
|
||||
|
|
|
|||
|
|
@ -5,19 +5,12 @@ import com.squareup.moshi.JsonClass
|
|||
|
||||
@JsonClass(generateAdapter = true)
|
||||
data class ActivationByCustomerWalletRequest(
|
||||
@Json(name = "customer_id") val customerId: String,
|
||||
@Json(name = "product_instance_id") val productInstanceId: String,
|
||||
@Json(name = "activation_order_id") val activationOrderId: String,
|
||||
@Json(name = "data") val data: Data,
|
||||
@Json(name = "order_id") val orderId: String,
|
||||
@Json(name = "customer_wallet") val customerWallet: CustomerWallet,
|
||||
) {
|
||||
@JsonClass(generateAdapter = true)
|
||||
data class Data(
|
||||
@Json(name = "customer_wallet") val customerWallet: CustomerWallet,
|
||||
)
|
||||
|
||||
@JsonClass(generateAdapter = true)
|
||||
data class CustomerWallet(
|
||||
@Json(name = "address") val address: String,
|
||||
@Json(name = "deploy_acceptance_signature") val deployAcceptanceSignature: String,
|
||||
@Json(name = "address") val customerWalletAddress: String,
|
||||
)
|
||||
}
|
||||
|
|
@ -0,0 +1,10 @@
|
|||
package com.tangem.datasource.api.visa.models.request
|
||||
|
||||
import com.squareup.moshi.Json
|
||||
import com.squareup.moshi.JsonClass
|
||||
|
||||
@JsonClass(generateAdapter = true)
|
||||
data class ActivationStatusRequest(
|
||||
@Json(name = "card_id") val cardId: String,
|
||||
@Json(name = "card_public_key") val cardPublicKey: String,
|
||||
)
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
package com.tangem.datasource.api.visa.models.request
|
||||
|
||||
import com.squareup.moshi.Json
|
||||
import com.squareup.moshi.JsonClass
|
||||
|
||||
@JsonClass(generateAdapter = true)
|
||||
data class GenerateNoneByCardIdRequest(
|
||||
@Json(name = "auth_type") val authType: String = "card_id",
|
||||
@Json(name = "card_id") val cardId: String,
|
||||
@Json(name = "card_public_key") val cardPublicKey: String,
|
||||
)
|
||||
|
|
@ -0,0 +1,10 @@
|
|||
package com.tangem.datasource.api.visa.models.request
|
||||
|
||||
import com.squareup.moshi.Json
|
||||
import com.squareup.moshi.JsonClass
|
||||
|
||||
@JsonClass(generateAdapter = true)
|
||||
data class GenerateNoneByCardWalletRequest(
|
||||
@Json(name = "auth_type") val authType: String = "card_wallet",
|
||||
@Json(name = "card_wallet_address") val cardWalletAddress: String,
|
||||
)
|
||||
|
|
@ -0,0 +1,12 @@
|
|||
package com.tangem.datasource.api.visa.models.request
|
||||
|
||||
import com.squareup.moshi.Json
|
||||
import com.squareup.moshi.JsonClass
|
||||
|
||||
@JsonClass(generateAdapter = true)
|
||||
data class GetAccessTokenByCardIdRequest(
|
||||
@Json(name = "auth_type") val authType: String = "card_id",
|
||||
@Json(name = "session_id") val sessionId: String,
|
||||
@Json(name = "signature") val signature: String,
|
||||
@Json(name = "salt") val salt: String,
|
||||
)
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
package com.tangem.datasource.api.visa.models.request
|
||||
|
||||
import com.squareup.moshi.Json
|
||||
import com.squareup.moshi.JsonClass
|
||||
|
||||
@JsonClass(generateAdapter = true)
|
||||
data class GetAccessTokenByCardWalletRequest(
|
||||
@Json(name = "auth_type") val authType: String = "card_wallet",
|
||||
@Json(name = "session_id") val sessionId: String,
|
||||
@Json(name = "signature") val signature: String,
|
||||
)
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
package com.tangem.datasource.api.visa.models.request
|
||||
|
||||
import com.squareup.moshi.Json
|
||||
import com.squareup.moshi.JsonClass
|
||||
|
||||
@JsonClass(generateAdapter = true)
|
||||
data class GetCardWalletAcceptanceRequest(
|
||||
@Json(name = "type") val type: String = "card_wallet",
|
||||
@Json(name = "customer_wallet_address") val customerWalletAddress: String,
|
||||
@Json(name = "card_wallet_address") val cardWalletAddress: String,
|
||||
)
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
package com.tangem.datasource.api.visa.models.request
|
||||
|
||||
import com.squareup.moshi.Json
|
||||
import com.squareup.moshi.JsonClass
|
||||
|
||||
@JsonClass(generateAdapter = true)
|
||||
data class GetCustomerWalletAcceptanceRequest(
|
||||
@Json(name = "type") val type: String = "customer_wallet",
|
||||
@Json(name = "customer_wallet_address") val customerWalletAddress: String,
|
||||
@Json(name = "card_wallet_address") val cardWalletAddress: String,
|
||||
)
|
||||
|
|
@ -0,0 +1,10 @@
|
|||
package com.tangem.datasource.api.visa.models.request
|
||||
|
||||
import com.squareup.moshi.Json
|
||||
import com.squareup.moshi.JsonClass
|
||||
|
||||
@JsonClass(generateAdapter = true)
|
||||
data class RefreshTokenByCardIdRequest(
|
||||
@Json(name = "auth_type") val authType: String = "card_id",
|
||||
@Json(name = "refresh_token") val refreshToken: String,
|
||||
)
|
||||
|
|
@ -0,0 +1,10 @@
|
|||
package com.tangem.datasource.api.visa.models.request
|
||||
|
||||
import com.squareup.moshi.Json
|
||||
import com.squareup.moshi.JsonClass
|
||||
|
||||
@JsonClass(generateAdapter = true)
|
||||
data class RefreshTokenByCardWalletRequest(
|
||||
@Json(name = "auth_type") val authType: String = "card_wallet",
|
||||
@Json(name = "refresh_token") val refreshToken: String,
|
||||
)
|
||||
|
|
@ -5,14 +5,8 @@ import com.squareup.moshi.JsonClass
|
|||
|
||||
@JsonClass(generateAdapter = true)
|
||||
data class SetPinCodeRequest(
|
||||
@Json(name = "customer_id") val customerId: String,
|
||||
@Json(name = "activation_order_id") val activationOrderId: String,
|
||||
@Json(name = "product_instance_id") val productInstanceId: String,
|
||||
@Json(name = "data") val data: Data,
|
||||
) {
|
||||
data class Data(
|
||||
@Json(name = "session_key") val sessionKey: String,
|
||||
@Json(name = "iv") val iv: String,
|
||||
@Json(name = "encrypted_pin") val encryptedPin: String,
|
||||
)
|
||||
}
|
||||
@Json(name = "order_id") val orderId: String,
|
||||
@Json(name = "session_id") val sessionId: String,
|
||||
@Json(name = "iv") val iv: String,
|
||||
@Json(name = "pin") val pin: String,
|
||||
)
|
||||
|
|
@ -5,12 +5,21 @@ import com.squareup.moshi.JsonClass
|
|||
|
||||
@JsonClass(generateAdapter = true)
|
||||
data class CardActivationRemoteStateResponse(
|
||||
@Json(name = "activation_status") val status: String,
|
||||
@Json(name = "activation_order") val activationOrder: ActivationOrder?,
|
||||
@Json(name = "result") val result: Result,
|
||||
) {
|
||||
@JsonClass(generateAdapter = true)
|
||||
data class Result(
|
||||
@Json(name = "status") val status: String,
|
||||
@Json(name = "order") val activationOrder: ActivationOrder?,
|
||||
@Json(name = "stepChangeCode") val stepChangeCode: Int?,
|
||||
@Json(name = "updatedAt") val updatedAt: String?,
|
||||
)
|
||||
|
||||
@JsonClass(generateAdapter = true)
|
||||
data class ActivationOrder(
|
||||
@Json(name = "id") val id: String,
|
||||
@Json(name = "customer_id") val customerId: String,
|
||||
@Json(name = "customer_wallet_address") val customerWalletAddress: String,
|
||||
@Json(name = "card_wallet_address") val cardWalletAddress: String,
|
||||
)
|
||||
}
|
||||
|
|
@ -1,14 +0,0 @@
|
|||
package com.tangem.datasource.api.visa.models.response
|
||||
|
||||
import com.squareup.moshi.Json
|
||||
import com.squareup.moshi.JsonClass
|
||||
|
||||
@JsonClass(generateAdapter = true)
|
||||
data class CustomerWalletDataToSignResponse(
|
||||
@Json(name = "data_for_customer_wallet") val dataForCardWallet: Data,
|
||||
) {
|
||||
@JsonClass(generateAdapter = true)
|
||||
data class Data(
|
||||
@Json(name = "hash") val hash: String,
|
||||
)
|
||||
}
|
||||
|
|
@ -5,6 +5,11 @@ import com.squareup.moshi.JsonClass
|
|||
|
||||
@JsonClass(generateAdapter = true)
|
||||
data class GenerateNonceResponse(
|
||||
@Json(name = "nonce") val nonce: String,
|
||||
@Json(name = "session_id") val sessionId: String,
|
||||
)
|
||||
@Json(name = "result") val result: Result,
|
||||
) {
|
||||
@JsonClass(generateAdapter = true)
|
||||
data class Result(
|
||||
@Json(name = "nonce") val nonce: String,
|
||||
@Json(name = "session_id") val sessionId: String,
|
||||
)
|
||||
}
|
||||
|
|
@ -5,12 +5,17 @@ import com.squareup.moshi.JsonClass
|
|||
|
||||
@JsonClass(generateAdapter = true)
|
||||
data class JWTResponse(
|
||||
@Json(name = "access_token") val accessToken: String,
|
||||
@Json(name = "expires_in") val expiresIn: Int,
|
||||
@Json(name = "refresh_expires_in") val refreshExpiresIn: Int,
|
||||
@Json(name = "refresh_token") val refreshToken: String,
|
||||
@Json(name = "token_type") val tokenType: String,
|
||||
@Json(name = "not-before-policy") val notBeforePolicy: Int,
|
||||
@Json(name = "session_state") val sessionState: String,
|
||||
@Json(name = "scope") val scope: String,
|
||||
)
|
||||
@Json(name = "result") val result: Result,
|
||||
) {
|
||||
@JsonClass(generateAdapter = true)
|
||||
data class Result(
|
||||
@Json(name = "access_token") val accessToken: String,
|
||||
@Json(name = "expires_in") val expiresIn: Int,
|
||||
@Json(name = "refresh_expires_in") val refreshExpiresIn: Int,
|
||||
@Json(name = "refresh_token") val refreshToken: String,
|
||||
@Json(name = "token_type") val tokenType: String,
|
||||
@Json(name = "not-before-policy") val notBeforePolicy: Int,
|
||||
@Json(name = "session_state") val sessionState: String,
|
||||
@Json(name = "scope") val scope: String,
|
||||
)
|
||||
}
|
||||
|
|
@ -4,11 +4,11 @@ import com.squareup.moshi.Json
|
|||
import com.squareup.moshi.JsonClass
|
||||
|
||||
@JsonClass(generateAdapter = true)
|
||||
data class CardWalletDataToSignResponse(
|
||||
@Json(name = "dataForCardWallet") val dataForCardWallet: Data,
|
||||
data class VisaDataToSignResponse(
|
||||
@Json(name = "result") val result: Result,
|
||||
) {
|
||||
@JsonClass(generateAdapter = true)
|
||||
data class Data(
|
||||
data class Result(
|
||||
@Json(name = "hash") val hash: String,
|
||||
)
|
||||
}
|
||||
|
|
@ -0,0 +1,18 @@
|
|||
package com.tangem.datasource.appcurrency
|
||||
|
||||
import com.tangem.datasource.api.tangemTech.models.CurrenciesResponse
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
|
||||
/**
|
||||
* Store of app currency data model [CurrenciesResponse.Currency]
|
||||
*
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
interface AppCurrencyResponseStore {
|
||||
|
||||
/** Get flow of [CurrenciesResponse.Currency] */
|
||||
fun get(): Flow<CurrenciesResponse.Currency?>
|
||||
|
||||
/** Get [CurrenciesResponse.Currency] synchronously or null */
|
||||
suspend fun getSyncOrNull(): CurrenciesResponse.Currency?
|
||||
}
|
||||
|
|
@ -0,0 +1,28 @@
|
|||
package com.tangem.datasource.appcurrency
|
||||
|
||||
import com.tangem.datasource.api.tangemTech.models.CurrenciesResponse
|
||||
import com.tangem.datasource.local.preferences.AppPreferencesStore
|
||||
import com.tangem.datasource.local.preferences.PreferencesKeys
|
||||
import com.tangem.datasource.local.preferences.utils.getObject
|
||||
import com.tangem.datasource.local.preferences.utils.getObjectSyncOrNull
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
|
||||
/**
|
||||
* Default implementation of [AppCurrencyResponseStore]
|
||||
*
|
||||
* @property appPreferencesStore app preferences store
|
||||
*/
|
||||
internal class DefaultAppCurrencyResponseStore(
|
||||
private val appPreferencesStore: AppPreferencesStore,
|
||||
) : AppCurrencyResponseStore {
|
||||
|
||||
override fun get(): Flow<CurrenciesResponse.Currency?> {
|
||||
return appPreferencesStore.getObject(PreferencesKeys.SELECTED_APP_CURRENCY_KEY)
|
||||
}
|
||||
|
||||
override suspend fun getSyncOrNull(): CurrenciesResponse.Currency? {
|
||||
return appPreferencesStore.getObjectSyncOrNull<CurrenciesResponse.Currency>(
|
||||
PreferencesKeys.SELECTED_APP_CURRENCY_KEY,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -3,8 +3,10 @@ package com.tangem.datasource.asset.loader
|
|||
import com.squareup.moshi.Moshi
|
||||
import com.squareup.moshi.Types
|
||||
import com.squareup.moshi.adapter
|
||||
import com.tangem.utils.coroutines.runCatching
|
||||
import com.tangem.datasource.asset.reader.AssetReader
|
||||
import com.tangem.datasource.di.NetworkMoshi
|
||||
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
||||
import timber.log.Timber
|
||||
import javax.inject.Inject
|
||||
import javax.inject.Singleton
|
||||
|
|
@ -23,69 +25,63 @@ import javax.inject.Singleton
|
|||
class AssetLoader @Inject constructor(
|
||||
val assetReader: AssetReader,
|
||||
@NetworkMoshi val moshi: Moshi,
|
||||
val dispatchers: CoroutineDispatcherProvider,
|
||||
) {
|
||||
|
||||
/** Load content [Content] of asset file [fileName] */
|
||||
@OptIn(ExperimentalStdlibApi::class)
|
||||
suspend inline fun <reified Content> load(fileName: String): Content? {
|
||||
return runCatching {
|
||||
val json = assetReader.read(fullFileName = "$fileName.json")
|
||||
suspend inline fun <reified Content> load(fileName: String): Content? = runCatching(dispatchers.io) {
|
||||
val json = assetReader.read(fullFileName = "$fileName.json")
|
||||
|
||||
moshi.adapter<Content>().fromJson(json)
|
||||
}
|
||||
.fold(
|
||||
onSuccess = { parsedConfig ->
|
||||
if (parsedConfig == null) Timber.e(IllegalStateException("Parsed config [$fileName] is null"))
|
||||
parsedConfig
|
||||
},
|
||||
onFailure = {
|
||||
Timber.e(it, "Failed to load config [$fileName] from assets")
|
||||
null
|
||||
},
|
||||
)
|
||||
moshi.adapter<Content>().fromJson(json)
|
||||
}
|
||||
.fold(
|
||||
onSuccess = { parsedConfig ->
|
||||
if (parsedConfig == null) Timber.e(IllegalStateException("Parsed config [$fileName] is null"))
|
||||
parsedConfig
|
||||
},
|
||||
onFailure = {
|
||||
Timber.e(it, "Failed to load config [$fileName] from assets")
|
||||
null
|
||||
},
|
||||
)
|
||||
|
||||
/** Load list [V] values of asset file [fileName] */
|
||||
suspend inline fun <reified V> loadList(fileName: String): List<V> {
|
||||
return runCatching {
|
||||
val json = assetReader.read(fullFileName = "$fileName.json")
|
||||
suspend inline fun <reified V> loadList(fileName: String): List<V> = runCatching(dispatchers.io) {
|
||||
val json = assetReader.read(fullFileName = "$fileName.json")
|
||||
|
||||
val type = Types.newParameterizedType(List::class.java, V::class.java)
|
||||
val adapter = moshi.adapter<List<V>>(type)
|
||||
val type = Types.newParameterizedType(List::class.java, V::class.java)
|
||||
val adapter = moshi.adapter<List<V>>(type)
|
||||
|
||||
adapter.fromJson(json)
|
||||
}
|
||||
.fold(
|
||||
onSuccess = { parsedConfig ->
|
||||
if (parsedConfig == null) Timber.e(IllegalStateException("Parsed config [$fileName] is null"))
|
||||
parsedConfig.orEmpty()
|
||||
},
|
||||
onFailure = {
|
||||
Timber.e(it, "Failed to load config [$fileName] from assets")
|
||||
emptyList()
|
||||
},
|
||||
)
|
||||
adapter.fromJson(json)
|
||||
}
|
||||
.fold(
|
||||
onSuccess = { parsedConfig ->
|
||||
if (parsedConfig == null) Timber.e(IllegalStateException("Parsed config [$fileName] is null"))
|
||||
parsedConfig.orEmpty()
|
||||
},
|
||||
onFailure = {
|
||||
Timber.e(it, "Failed to load config [$fileName] from assets")
|
||||
emptyList()
|
||||
},
|
||||
)
|
||||
|
||||
/** Load map [String] keys and [V] values of asset file [fileName] */
|
||||
suspend inline fun <reified V> loadMap(fileName: String): Map<String, V> {
|
||||
return runCatching {
|
||||
val json = assetReader.read(fullFileName = "$fileName.json")
|
||||
suspend inline fun <reified V> loadMap(fileName: String): Map<String, V> = runCatching(dispatchers.io) {
|
||||
val json = assetReader.read(fullFileName = "$fileName.json")
|
||||
|
||||
val type = Types.newParameterizedType(Map::class.java, String::class.java, V::class.java)
|
||||
val adapter = moshi.adapter<Map<String, V>>(type)
|
||||
val type = Types.newParameterizedType(Map::class.java, String::class.java, V::class.java)
|
||||
val adapter = moshi.adapter<Map<String, V>>(type)
|
||||
|
||||
adapter.fromJson(json)
|
||||
}
|
||||
.fold(
|
||||
onSuccess = { parsedConfig ->
|
||||
if (parsedConfig == null) Timber.e(IllegalStateException("Parsed config [$fileName] is null"))
|
||||
parsedConfig.orEmpty()
|
||||
},
|
||||
onFailure = {
|
||||
Timber.e(it, "Failed to load config [$fileName] from assets")
|
||||
emptyMap()
|
||||
},
|
||||
)
|
||||
adapter.fromJson(json)
|
||||
}
|
||||
.fold(
|
||||
onSuccess = { parsedConfig ->
|
||||
if (parsedConfig == null) Timber.e(IllegalStateException("Parsed config [$fileName] is null"))
|
||||
parsedConfig.orEmpty()
|
||||
},
|
||||
onFailure = {
|
||||
Timber.e(it, "Failed to load config [$fileName] from assets")
|
||||
emptyMap()
|
||||
},
|
||||
)
|
||||
}
|
||||
|
|
@ -1,23 +1,19 @@
|
|||
package com.tangem.datasource.asset.reader
|
||||
|
||||
import android.content.res.AssetManager
|
||||
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
||||
import kotlinx.coroutines.withContext
|
||||
import java.io.BufferedReader
|
||||
|
||||
/**
|
||||
* Implementation of asset file reader
|
||||
*
|
||||
* @property assetManager asset manager
|
||||
* @property dispatchers dispatchers
|
||||
*/
|
||||
internal class AndroidAssetReader(
|
||||
private val assetManager: AssetManager,
|
||||
private val dispatchers: CoroutineDispatcherProvider,
|
||||
) : AssetReader {
|
||||
|
||||
override suspend fun read(fullFileName: String): String = withContext(dispatchers.io) {
|
||||
assetManager.open(fullFileName).bufferedReader()
|
||||
override suspend fun read(fullFileName: String): String {
|
||||
return assetManager.open(fullFileName).bufferedReader()
|
||||
.use(BufferedReader::readText)
|
||||
}
|
||||
}
|
||||
|
|
@ -39,12 +39,13 @@ internal object ApiConfigsModule {
|
|||
|
||||
@Provides
|
||||
@IntoSet
|
||||
fun provideTangemAuthVisaConfig(appVersionProvider: AppVersionProvider): ApiConfig =
|
||||
TangemVisaAuth(appVersionProvider)
|
||||
fun provideTangemVisaConfig(appVersionProvider: AppVersionProvider): ApiConfig = TangemVisa(appVersionProvider)
|
||||
|
||||
@Provides
|
||||
@IntoSet
|
||||
fun provideTangemVisaConfig(appVersionProvider: AppVersionProvider): ApiConfig = TangemVisa(appVersionProvider)
|
||||
fun provideBlockAidConfig(environmentConfigStorage: EnvironmentConfigStorage): ApiConfig {
|
||||
return BlockAid(environmentConfigStorage)
|
||||
}
|
||||
|
||||
@Provides
|
||||
@IntoSet
|
||||
|
|
|
|||
|
|
@ -1,8 +1,11 @@
|
|||
package com.tangem.datasource.di
|
||||
|
||||
import com.tangem.datasource.appcurrency.AppCurrencyResponseStore
|
||||
import com.tangem.datasource.appcurrency.DefaultAppCurrencyResponseStore
|
||||
import com.tangem.datasource.local.appcurrency.AvailableAppCurrenciesStore
|
||||
import com.tangem.datasource.local.appcurrency.implementation.DefaultAvailableAppCurrenciesStore
|
||||
import com.tangem.datasource.local.datastore.RuntimeDataStore
|
||||
import com.tangem.datasource.local.preferences.AppPreferencesStore
|
||||
import dagger.Module
|
||||
import dagger.Provides
|
||||
import dagger.hilt.InstallIn
|
||||
|
|
@ -18,4 +21,10 @@ internal object AppCurrencyDataModule {
|
|||
fun provideAvailableAppCurrenciesStore(): AvailableAppCurrenciesStore {
|
||||
return DefaultAvailableAppCurrenciesStore(dataStore = RuntimeDataStore())
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideAppCurrencyResponseStore(appPreferencesStore: AppPreferencesStore): AppCurrencyResponseStore {
|
||||
return DefaultAppCurrencyResponseStore(appPreferencesStore)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,21 @@
|
|||
package com.tangem.datasource.di
|
||||
|
||||
import com.tangem.datasource.info.AndroidAppInfoProvider
|
||||
import com.tangem.utils.info.AppInfoProvider
|
||||
import com.tangem.utils.version.AppVersionProvider
|
||||
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 AppInfoModule {
|
||||
|
||||
@Singleton
|
||||
@Provides
|
||||
fun provideAppInfoProvider(appVersionProvider: AppVersionProvider): AppInfoProvider {
|
||||
return AndroidAppInfoProvider(appVersionProvider)
|
||||
}
|
||||
}
|
||||
|
|
@ -2,7 +2,6 @@ package com.tangem.datasource.di
|
|||
|
||||
import android.content.Context
|
||||
import com.squareup.moshi.Moshi
|
||||
import com.tangem.datasource.local.*
|
||||
import com.tangem.datasource.local.preferences.*
|
||||
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
||||
import dagger.Module
|
||||
|
|
@ -26,6 +25,7 @@ internal object AppPreferencesStoreModule {
|
|||
return AppPreferencesStore(
|
||||
preferencesDataStore = PreferencesDataStore.getInstance(context = appContext, dispatcher = dispatchers.io),
|
||||
moshi = moshi,
|
||||
dispatchers = dispatchers,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -3,7 +3,6 @@ package com.tangem.datasource.di
|
|||
import android.content.Context
|
||||
import com.tangem.datasource.asset.reader.AndroidAssetReader
|
||||
import com.tangem.datasource.asset.reader.AssetReader
|
||||
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
||||
import dagger.Module
|
||||
import dagger.Provides
|
||||
import dagger.hilt.InstallIn
|
||||
|
|
@ -17,10 +16,7 @@ internal object AssetReaderModule {
|
|||
|
||||
@Singleton
|
||||
@Provides
|
||||
fun providesAsserReader(
|
||||
@ApplicationContext context: Context,
|
||||
dispatchers: CoroutineDispatcherProvider,
|
||||
): AssetReader {
|
||||
return AndroidAssetReader(context.assets, dispatchers)
|
||||
fun providesAsserReader(@ApplicationContext context: Context): AssetReader {
|
||||
return AndroidAssetReader(context.assets)
|
||||
}
|
||||
}
|
||||
|
|
@ -6,10 +6,7 @@ import com.squareup.moshi.kotlin.reflect.KotlinJsonAdapterFactory
|
|||
import com.tangem.blockchain.nft.models.NFTAsset
|
||||
import com.tangem.blockchain.nft.models.NFTCollection
|
||||
import com.tangem.common.json.MoshiJsonConverter
|
||||
import com.tangem.datasource.api.common.adapter.BigDecimalAdapter
|
||||
import com.tangem.datasource.api.common.adapter.DateTimeAdapter
|
||||
import com.tangem.datasource.api.common.adapter.LocalDateAdapter
|
||||
import com.tangem.datasource.api.common.adapter.addStakeKitEnumFallbackAdapters
|
||||
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.domain.models.scan.serialization.*
|
||||
|
|
@ -38,6 +35,7 @@ class MoshiModule {
|
|||
.withDefaultValue(ProviderModel.UnsupportedType),
|
||||
)
|
||||
.add(BigDecimalAdapter())
|
||||
.add(BigIntegerAdapter())
|
||||
.add(LocalDateAdapter())
|
||||
.add(DateTimeAdapter())
|
||||
.add(VisaActivationRemoteState.jsonAdapter)
|
||||
|
|
@ -51,12 +49,14 @@ class MoshiModule {
|
|||
PolymorphicJsonAdapterFactory.of(NFTCollection.Identifier::class.java, "bc")
|
||||
.withSubtype(NFTCollection.Identifier.EVM::class.java, "evm")
|
||||
.withSubtype(NFTCollection.Identifier.TON::class.java, "ton")
|
||||
.withSubtype(NFTCollection.Identifier.Solana::class.java, "sol")
|
||||
.withDefaultValue(NFTCollection.Identifier.Unknown),
|
||||
)
|
||||
.add(
|
||||
PolymorphicJsonAdapterFactory.of(NFTAsset.Identifier::class.java, "bc")
|
||||
.withSubtype(NFTAsset.Identifier.EVM::class.java, "evm")
|
||||
.withSubtype(NFTAsset.Identifier.TON::class.java, "ton")
|
||||
.withSubtype(NFTAsset.Identifier.Solana::class.java, "sol")
|
||||
.withDefaultValue(NFTAsset.Identifier.Unknown),
|
||||
)
|
||||
.addLast(KotlinJsonAdapterFactory())
|
||||
|
|
@ -71,6 +71,7 @@ class MoshiModule {
|
|||
val adapters = MoshiJsonConverter.getTangemSdkAdapters() +
|
||||
listOf(
|
||||
BigDecimalAdapter(),
|
||||
BigIntegerAdapter(),
|
||||
WalletDerivedKeysMapAdapter(),
|
||||
ScanResponseDerivedKeysMapAdapter(),
|
||||
ByteArrayKeyAdapter(),
|
||||
|
|
@ -98,6 +99,7 @@ class MoshiModule {
|
|||
adapters = MoshiJsonConverter.getTangemSdkAdapters() +
|
||||
listOf(
|
||||
BigDecimalAdapter(),
|
||||
BigIntegerAdapter(),
|
||||
WalletDerivedKeysMapAdapter(),
|
||||
ScanResponseDerivedKeysMapAdapter(),
|
||||
ByteArrayKeyAdapter(),
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ import android.content.Context
|
|||
import com.squareup.moshi.Moshi
|
||||
import com.tangem.core.analytics.api.AnalyticsErrorHandler
|
||||
import com.tangem.datasource.BuildConfig
|
||||
import com.tangem.datasource.api.common.blockaid.BlockAidApi
|
||||
import com.tangem.datasource.api.common.config.ApiConfig
|
||||
import com.tangem.datasource.api.common.config.ApiConfigs
|
||||
import com.tangem.datasource.api.common.config.managers.ApiConfigsManager
|
||||
|
|
@ -17,7 +18,6 @@ import com.tangem.datasource.api.stakekit.StakeKitApi
|
|||
import com.tangem.datasource.api.tangemTech.TangemTechApi
|
||||
import com.tangem.datasource.api.tangemTech.TangemTechApiV2
|
||||
import com.tangem.datasource.api.visa.TangemVisaApi
|
||||
import com.tangem.datasource.api.visa.TangemVisaAuthApi
|
||||
import com.tangem.datasource.local.logs.AppLogsStore
|
||||
import com.tangem.datasource.local.preferences.AppPreferencesStore
|
||||
import com.tangem.datasource.utils.*
|
||||
|
|
@ -191,29 +191,6 @@ internal object NetworkModule {
|
|||
)
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideTangemVisaAuthApi(
|
||||
@NetworkMoshi moshi: Moshi,
|
||||
@ApplicationContext context: Context,
|
||||
analyticsErrorHandler: AnalyticsErrorHandler,
|
||||
apiConfigsManager: ApiConfigsManager,
|
||||
appLogsStore: AppLogsStore,
|
||||
): TangemVisaAuthApi {
|
||||
return createApi<TangemVisaAuthApi>(
|
||||
id = ApiConfig.ID.TangemVisaAuth,
|
||||
moshi = moshi,
|
||||
context = context,
|
||||
apiConfigsManager = apiConfigsManager,
|
||||
analyticsErrorHandler = analyticsErrorHandler,
|
||||
clientBuilder = {
|
||||
addInterceptor(
|
||||
NetworkLogsSaveInterceptor(appLogsStore),
|
||||
).applyTimeoutAnnotations()
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideTangemVisaApi(
|
||||
|
|
@ -282,6 +259,29 @@ internal object NetworkModule {
|
|||
.create(T::class.java)
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideBlockAidApi(
|
||||
@NetworkMoshi moshi: Moshi,
|
||||
@ApplicationContext context: Context,
|
||||
analyticsErrorHandler: AnalyticsErrorHandler,
|
||||
apiConfigsManager: ApiConfigsManager,
|
||||
appLogsStore: AppLogsStore,
|
||||
): BlockAidApi {
|
||||
return createApi<BlockAidApi>(
|
||||
id = ApiConfig.ID.BlockAid,
|
||||
moshi = moshi,
|
||||
context = context,
|
||||
apiConfigsManager = apiConfigsManager,
|
||||
analyticsErrorHandler = analyticsErrorHandler,
|
||||
clientBuilder = {
|
||||
addInterceptor(
|
||||
NetworkLogsSaveInterceptor(appLogsStore),
|
||||
).applyTimeoutAnnotations()
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
private inline fun <reified T> createApi(
|
||||
id: ApiConfig.ID,
|
||||
moshi: Moshi,
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
package com.tangem.datasource.di
|
||||
|
||||
import android.content.Context
|
||||
import androidx.datastore.core.DataStore
|
||||
import androidx.datastore.core.DataStoreFactory
|
||||
import androidx.datastore.dataStoreFile
|
||||
import com.squareup.moshi.Moshi
|
||||
|
|
@ -26,21 +27,27 @@ internal object QuotesStoreModule {
|
|||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideQuotesStore(
|
||||
fun providePersistenceQuotesStore(
|
||||
@NetworkMoshi moshi: Moshi,
|
||||
@ApplicationContext context: Context,
|
||||
dispatchers: CoroutineDispatcherProvider,
|
||||
): QuotesStore {
|
||||
return DefaultQuotesStore(
|
||||
persistenceStore = DataStoreFactory.create(
|
||||
serializer = MoshiDataStoreSerializer(
|
||||
moshi = moshi,
|
||||
types = mapWithStringKeyTypes<QuotesResponse.Quote>(),
|
||||
defaultValue = emptyMap(),
|
||||
),
|
||||
produceFile = { context.dataStoreFile(fileName = "quotes") },
|
||||
scope = CoroutineScope(context = dispatchers.io + SupervisorJob()),
|
||||
): DataStore<Map<String, QuotesResponse.Quote>> {
|
||||
return DataStoreFactory.create(
|
||||
serializer = MoshiDataStoreSerializer(
|
||||
moshi = moshi,
|
||||
types = mapWithStringKeyTypes<QuotesResponse.Quote>(),
|
||||
defaultValue = emptyMap(),
|
||||
),
|
||||
produceFile = { context.dataStoreFile(fileName = "quotes") },
|
||||
scope = CoroutineScope(context = dispatchers.io + SupervisorJob()),
|
||||
)
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideQuotesStore(persistenceStore: DataStore<Map<String, QuotesResponse.Quote>>): QuotesStore {
|
||||
return DefaultQuotesStore(
|
||||
persistenceStore = persistenceStore,
|
||||
runtimeStore = RuntimeSharedStore(),
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
package com.tangem.datasource.di
|
||||
|
||||
import android.content.Context
|
||||
import androidx.datastore.core.DataStore
|
||||
import androidx.datastore.core.DataStoreFactory
|
||||
import androidx.datastore.dataStoreFile
|
||||
import com.squareup.moshi.Moshi
|
||||
|
|
@ -49,21 +50,29 @@ internal object StakingStoreModule {
|
|||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideStakingBalanceStore(
|
||||
fun provideYieldsBalancesPersistenceStore(
|
||||
@NetworkMoshi moshi: Moshi,
|
||||
@ApplicationContext context: Context,
|
||||
dispatchers: CoroutineDispatcherProvider,
|
||||
): DataStore<Map<String, Set<YieldBalanceWrapperDTO>>> {
|
||||
return DataStoreFactory.create(
|
||||
serializer = MoshiDataStoreSerializer(
|
||||
moshi = moshi,
|
||||
types = mapWithStringKeyTypes(valueTypes = setTypes<YieldBalanceWrapperDTO>()),
|
||||
defaultValue = emptyMap(),
|
||||
),
|
||||
produceFile = { context.dataStoreFile(fileName = "yield_balances") },
|
||||
scope = CoroutineScope(context = dispatchers.io + SupervisorJob()),
|
||||
)
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideStakingBalanceStore(
|
||||
persistenceStore: DataStore<Map<String, Set<YieldBalanceWrapperDTO>>>,
|
||||
): StakingBalanceStore {
|
||||
return DefaultStakingBalanceStore(
|
||||
persistenceStore = DataStoreFactory.create(
|
||||
serializer = MoshiDataStoreSerializer(
|
||||
moshi = moshi,
|
||||
types = mapWithStringKeyTypes(valueTypes = setTypes<YieldBalanceWrapperDTO>()),
|
||||
defaultValue = emptyMap(),
|
||||
),
|
||||
produceFile = { context.dataStoreFile(fileName = "yield_balances") },
|
||||
scope = CoroutineScope(context = dispatchers.io + SupervisorJob()),
|
||||
),
|
||||
persistenceStore = persistenceStore,
|
||||
runtimeStore = RuntimeSharedStore(),
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,11 +5,14 @@ import com.tangem.datasource.api.express.TangemExpressApi
|
|||
import com.tangem.datasource.api.express.models.request.AssetsRequestBody
|
||||
import com.tangem.datasource.api.express.models.request.LeastTokenInfo
|
||||
import com.tangem.datasource.api.express.models.response.Asset
|
||||
import com.tangem.datasource.exchangeservice.swap.ExpressUtils.getRefCode
|
||||
import com.tangem.datasource.local.preferences.AppPreferencesStore
|
||||
import com.tangem.datasource.local.token.ExpressAssetsStore
|
||||
import com.tangem.domain.core.lce.Lce
|
||||
import com.tangem.domain.core.utils.lceContent
|
||||
import com.tangem.domain.core.utils.lceError
|
||||
import com.tangem.domain.core.utils.lceLoading
|
||||
import com.tangem.domain.wallets.models.UserWallet
|
||||
import com.tangem.domain.wallets.models.UserWalletId
|
||||
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
|
|
@ -33,31 +36,34 @@ typealias InitializationStatusFlow = MutableStateFlow<Lce<Throwable, List<Asset>
|
|||
internal class DefaultExpressServiceLoader @Inject constructor(
|
||||
private val tangemExpressApi: TangemExpressApi,
|
||||
private val expressAssetsStore: ExpressAssetsStore,
|
||||
private val appPreferencesStore: AppPreferencesStore,
|
||||
private val dispatchers: CoroutineDispatcherProvider,
|
||||
) : ExpressServiceLoader {
|
||||
|
||||
private val initializationStatuses =
|
||||
MutableStateFlow<Map<UserWalletId, InitializationStatusFlow>>(value = emptyMap())
|
||||
|
||||
override suspend fun update(userWalletId: UserWalletId, userTokens: List<LeastTokenInfo>) {
|
||||
override suspend fun update(userWallet: UserWallet, userTokens: List<LeastTokenInfo>) {
|
||||
withContext(dispatchers.io) {
|
||||
val initializationStatus = getInitializationStatusInternal(userWalletId)
|
||||
val initializationStatus = getInitializationStatusInternal(userWallet.walletId)
|
||||
|
||||
try {
|
||||
if (userTokens.isNotEmpty()) {
|
||||
val response = tangemExpressApi.getAssets(
|
||||
userWalletId = userWallet.walletId.stringValue,
|
||||
refCode = getRefCode(userWallet, appPreferencesStore),
|
||||
body = AssetsRequestBody(tokensList = userTokens),
|
||||
).getOrThrow()
|
||||
|
||||
expressAssetsStore.store(userWalletId, response)
|
||||
expressAssetsStore.store(userWallet.walletId, response)
|
||||
|
||||
initializationStatus.update { response.lceContent() }
|
||||
}
|
||||
} catch (e: Throwable) {
|
||||
if (expressAssetsStore.getSyncOrNull(userWalletId) == null) {
|
||||
if (expressAssetsStore.getSyncOrNull(userWallet.walletId) == null) {
|
||||
initializationStatus.update { e.lceError() }
|
||||
}
|
||||
Timber.e(e, "Unable to fetch assets for: ${userWalletId.stringValue}")
|
||||
Timber.e(e, "Unable to fetch assets for: ${userWallet.walletId.stringValue}")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ package com.tangem.datasource.exchangeservice.swap
|
|||
import com.tangem.datasource.api.express.models.request.LeastTokenInfo
|
||||
import com.tangem.datasource.api.express.models.response.Asset
|
||||
import com.tangem.domain.core.lce.Lce
|
||||
import com.tangem.domain.wallets.models.UserWallet
|
||||
import com.tangem.domain.wallets.models.UserWalletId
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
|
||||
|
|
@ -13,8 +14,8 @@ import kotlinx.coroutines.flow.Flow
|
|||
*/
|
||||
interface ExpressServiceLoader {
|
||||
|
||||
/** Update service using [userWalletId] and [userTokens] */
|
||||
suspend fun update(userWalletId: UserWalletId, userTokens: List<LeastTokenInfo>)
|
||||
/** Update service using [userWallet] and [userTokens] */
|
||||
suspend fun update(userWallet: UserWallet, userTokens: List<LeastTokenInfo>)
|
||||
|
||||
/** Get initialization status by [userWalletId] */
|
||||
fun getInitializationStatus(userWalletId: UserWalletId): Flow<Lce<Throwable, List<Asset>>>
|
||||
|
|
|
|||
|
|
@ -0,0 +1,46 @@
|
|||
package com.tangem.datasource.exchangeservice.swap
|
||||
|
||||
import com.tangem.common.CardIdRangeDec
|
||||
import com.tangem.datasource.local.preferences.AppPreferencesStore
|
||||
import com.tangem.datasource.local.preferences.PreferencesKeys
|
||||
import com.tangem.datasource.local.preferences.utils.getSyncOrDefault
|
||||
import com.tangem.domain.wallets.models.UserWallet
|
||||
import kotlinx.coroutines.runBlocking
|
||||
|
||||
object ExpressUtils {
|
||||
private const val BATCH_ID_CHANGENOW = "BB000013"
|
||||
private const val BATCH_ID_PARTNER = "AF990015"
|
||||
|
||||
fun getRefCode(userWallet: UserWallet, appPreferencesStore: AppPreferencesStore): String {
|
||||
return when {
|
||||
isRing(userWallet, appPreferencesStore) -> "ring"
|
||||
isChangeNow(userWallet) -> "ChangeNow"
|
||||
isPartner(userWallet) -> "partner"
|
||||
else -> ""
|
||||
}
|
||||
}
|
||||
|
||||
private fun isRing(selectedUserWallet: UserWallet, appPreferencesStore: AppPreferencesStore): Boolean {
|
||||
val addedWalletsWithRings = runBlocking {
|
||||
appPreferencesStore.getSyncOrDefault(
|
||||
key = PreferencesKeys.ADDED_WALLETS_WITH_RING_KEY,
|
||||
default = emptySet(),
|
||||
)
|
||||
}
|
||||
|
||||
return addedWalletsWithRings.contains(selectedUserWallet.walletId.stringValue)
|
||||
}
|
||||
|
||||
private fun isChangeNow(selectedUserWallet: UserWallet): Boolean {
|
||||
val changeNowRange = CardIdRangeDec(
|
||||
start = "AF99001800554008",
|
||||
end = "AF99001800559994",
|
||||
)
|
||||
val card = selectedUserWallet.scanResponse.card
|
||||
return card.batchId == BATCH_ID_CHANGENOW || changeNowRange?.contains(card.cardId) == true
|
||||
}
|
||||
|
||||
private fun isPartner(selectedUserWallet: UserWallet): Boolean {
|
||||
return selectedUserWallet.scanResponse.card.batchId == BATCH_ID_PARTNER
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,25 @@
|
|||
package com.tangem.datasource.info
|
||||
|
||||
import android.os.Build
|
||||
import com.tangem.utils.SupportedLanguages
|
||||
import com.tangem.utils.info.AppInfoProvider
|
||||
import com.tangem.utils.version.AppVersionProvider
|
||||
import java.util.*
|
||||
import javax.inject.Inject
|
||||
|
||||
internal class AndroidAppInfoProvider @Inject constructor(
|
||||
private val appVersionProvider: AppVersionProvider,
|
||||
) : AppInfoProvider {
|
||||
override val platform: String
|
||||
get() = "Android"
|
||||
override val device: String
|
||||
get() = "${Build.MANUFACTURER} ${Build.MODEL}"
|
||||
override val osVersion: String
|
||||
get() = Build.VERSION.RELEASE
|
||||
override val language: String
|
||||
get() = SupportedLanguages.getCurrentSupportedLanguageCode()
|
||||
override val timezone: String
|
||||
get() = TimeZone.getDefault().id
|
||||
override val appVersion: String
|
||||
get() = appVersionProvider.versionName
|
||||
}
|
||||
|
|
@ -14,4 +14,5 @@ data class EnvironmentConfig(
|
|||
val express: ExpressModel? = null,
|
||||
val devExpress: ExpressModel? = null,
|
||||
val stakeKitApiKey: String? = null,
|
||||
val blockAidApiKey: String? = null,
|
||||
)
|
||||
|
|
@ -23,6 +23,7 @@ internal object EnvironmentConfigConverter : Converter<EnvironmentConfigModel, E
|
|||
express = value.express,
|
||||
devExpress = value.devExpress,
|
||||
stakeKitApiKey = value.stakeKitApiKey,
|
||||
blockAidApiKey = value.blockaidApiKey,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -38,6 +38,7 @@ class EnvironmentConfigModel(
|
|||
@Json(name = "alephiumTangemApiKey") val alephiumTangemApiKey: String?,
|
||||
@Json(name = "moralisApiKey") val moralisApiKey: String?,
|
||||
@Json(name = "nftScanApiKey") val nftScanApiKey: String?,
|
||||
@Json(name = "blockaidApiKey") val blockaidApiKey: String?,
|
||||
)
|
||||
|
||||
@JsonClass(generateAdapter = true)
|
||||
|
|
|
|||
|
|
@ -37,8 +37,12 @@ class AppLogsStore @Inject constructor(
|
|||
private val mutex = Mutex()
|
||||
private val zipMutex = Mutex()
|
||||
|
||||
private val file = File(applicationContext.filesDir, PERMITTED_FILE_NAME)
|
||||
private val fileZip = File(applicationContext.filesDir, PERMITTED_FILE_NAME_ZIP)
|
||||
private val logFile by lazy {
|
||||
File(applicationContext.filesDir, PERMITTED_FILE_NAME)
|
||||
}
|
||||
private val logFileZip by lazy {
|
||||
File(applicationContext.filesDir, PERMITTED_FILE_NAME_ZIP)
|
||||
}
|
||||
|
||||
private val formatter = DateTimeFormatterBuilder()
|
||||
.appendDayOfMonth(2)
|
||||
|
|
@ -55,12 +59,12 @@ class AppLogsStore @Inject constructor(
|
|||
.toFormatter()
|
||||
|
||||
/** Get log file */
|
||||
fun getFile(): File? = if (file.exists()) file else null
|
||||
fun getFile(): File? = if (logFile.exists()) logFile else null
|
||||
|
||||
suspend fun getZipFile(): File? {
|
||||
return zipMutex.withLock {
|
||||
if (file.exists()) {
|
||||
zip(listOf(file), fileZip)
|
||||
if (logFile.exists()) {
|
||||
zip(listOf(logFile), logFileZip)
|
||||
} else {
|
||||
null
|
||||
}
|
||||
|
|
@ -98,8 +102,8 @@ class AppLogsStore @Inject constructor(
|
|||
/** Delete deprecated logs if file size exceeds [maxSize] */
|
||||
fun deleteDeprecatedLogs(maxSize: Int) {
|
||||
launchWithLock {
|
||||
if (file.exists() && file.length() > maxSize) {
|
||||
file.delete()
|
||||
if (logFile.exists() && logFile.length() > maxSize) {
|
||||
logFile.delete()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -117,7 +121,7 @@ class AppLogsStore @Inject constructor(
|
|||
}
|
||||
|
||||
private fun writeMessage(tag: String, vararg messages: String) {
|
||||
BufferedWriter(FileWriter(file, true)).use { writer ->
|
||||
BufferedWriter(FileWriter(logFile, true)).use { writer ->
|
||||
writer.append(formatter.print(DateTime.now()))
|
||||
writer.append(": $tag ")
|
||||
messages.forEach(writer::append)
|
||||
|
|
@ -126,8 +130,8 @@ class AppLogsStore @Inject constructor(
|
|||
}
|
||||
|
||||
private fun createFileIfNotExist() {
|
||||
if (!file.exists()) {
|
||||
runCatching { file.createNewFile() }
|
||||
if (!logFile.exists()) {
|
||||
runCatching { logFile.createNewFile() }
|
||||
.onFailure(Timber::e)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -96,21 +96,33 @@ internal class DefaultNFTRuntimeStore(
|
|||
|
||||
private fun NFTCollections.Content.Collections.mergeWithPrices(prices: Map<NFTAsset.Identifier, NFTSalePrice>) =
|
||||
copy(
|
||||
collections = this.collections?.map { data ->
|
||||
data.copy(
|
||||
assets = when (val assets = data.assets) {
|
||||
collections = this.collections
|
||||
?.map { data ->
|
||||
val assets = when (val assets = data.assets) {
|
||||
is NFTCollection.Assets.Empty,
|
||||
is NFTCollection.Assets.Loading,
|
||||
is NFTCollection.Assets.Failed,
|
||||
-> assets
|
||||
is NFTCollection.Assets.Value -> assets.copy(
|
||||
items = assets.items.map { asset ->
|
||||
asset.mergeWithPrice(prices[asset.id] ?: NFTSalePrice.Empty(asset.id))
|
||||
},
|
||||
items = assets.items
|
||||
.filter { !it.name.isNullOrEmpty() }
|
||||
.sortedBy { it.name }
|
||||
.map { asset ->
|
||||
asset.mergeWithPrice(prices[asset.id] ?: NFTSalePrice.Empty(asset.id))
|
||||
},
|
||||
)
|
||||
},
|
||||
)
|
||||
},
|
||||
}
|
||||
val assetsCount = when (assets) {
|
||||
is NFTCollection.Assets.Value -> assets.items.size
|
||||
else -> data.count
|
||||
}
|
||||
data.copy(
|
||||
assets = assets,
|
||||
count = assetsCount,
|
||||
)
|
||||
}
|
||||
?.filter { it.count > 0 }
|
||||
?.sortedBy { it.name },
|
||||
source = this.source,
|
||||
)
|
||||
|
||||
|
|
|
|||
|
|
@ -4,14 +4,17 @@ import com.tangem.domain.models.StatusSource
|
|||
import com.tangem.domain.nft.models.NFTAsset
|
||||
import com.tangem.domain.nft.models.NFTSalePrice
|
||||
import com.tangem.domain.tokens.model.Network
|
||||
import com.tangem.utils.converter.Converter
|
||||
import com.tangem.utils.converter.TwoWayConverter
|
||||
import com.tangem.blockchain.nft.models.NFTAsset as SdkNFTAsset
|
||||
|
||||
object NFTSdkAssetConverter : Converter<Pair<Network, SdkNFTAsset>, NFTAsset> {
|
||||
object NFTSdkAssetConverter : TwoWayConverter<Pair<Network, SdkNFTAsset>, NFTAsset> {
|
||||
override fun convert(value: Pair<Network, SdkNFTAsset>): NFTAsset {
|
||||
val (network, asset) = value
|
||||
val assetId = NFTSdkAssetIdentifierConverter.convert(asset.identifier)
|
||||
val collectionId = NFTSdkCollectionIdentifierConverter.convert(asset.collectionIdentifier)
|
||||
val salePrice = asset.salePrice?.let { NFTSdkAssetSalePriceConverter(assetId).convert(it) }
|
||||
?: NFTSalePrice.Empty(assetId)
|
||||
|
||||
return NFTAsset(
|
||||
id = assetId,
|
||||
collectionId = collectionId,
|
||||
|
|
@ -20,13 +23,9 @@ object NFTSdkAssetConverter : Converter<Pair<Network, SdkNFTAsset>, NFTAsset> {
|
|||
owner = asset.owner,
|
||||
name = asset.name,
|
||||
description = asset.description,
|
||||
salePrice = asset.salePrice?.let {
|
||||
NFTSalePrice.Value(
|
||||
assetId = assetId,
|
||||
value = it.value,
|
||||
symbol = it.symbol,
|
||||
)
|
||||
} ?: NFTSalePrice.Empty(assetId = assetId),
|
||||
amount = asset.amount,
|
||||
decimals = asset.decimals,
|
||||
salePrice = salePrice,
|
||||
rarity = asset.rarity?.let {
|
||||
NFTAsset.Rarity(
|
||||
rank = it.rank,
|
||||
|
|
@ -48,4 +47,42 @@ object NFTSdkAssetConverter : Converter<Pair<Network, SdkNFTAsset>, NFTAsset> {
|
|||
source = StatusSource.CACHE,
|
||||
)
|
||||
}
|
||||
|
||||
override fun convertBack(value: NFTAsset): Pair<Network, SdkNFTAsset> {
|
||||
val assetId = NFTSdkAssetIdentifierConverter.convertBack(value.id)
|
||||
val collectionId = NFTSdkCollectionIdentifierConverter.convertBack(value.collectionId)
|
||||
val salePrice = (value.salePrice as? NFTSalePrice.Value)?.let {
|
||||
NFTSdkAssetSalePriceConverter(value.id).convertBack(it)
|
||||
}
|
||||
return value.network to SdkNFTAsset(
|
||||
identifier = assetId,
|
||||
collectionIdentifier = collectionId,
|
||||
blockchainId = value.network.id.value,
|
||||
contractType = value.contractType,
|
||||
owner = value.owner,
|
||||
name = value.name,
|
||||
description = value.description,
|
||||
amount = value.amount,
|
||||
decimals = value.decimals,
|
||||
salePrice = salePrice,
|
||||
rarity = value.rarity?.let {
|
||||
SdkNFTAsset.Rarity(
|
||||
rank = it.rank,
|
||||
label = it.label,
|
||||
)
|
||||
},
|
||||
media = value.media?.let {
|
||||
SdkNFTAsset.Media(
|
||||
url = it.url,
|
||||
mimetype = it.mimetype,
|
||||
)
|
||||
},
|
||||
traits = value.traits.map {
|
||||
SdkNFTAsset.Trait(
|
||||
name = it.name,
|
||||
value = it.value,
|
||||
)
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -9,10 +9,15 @@ object NFTSdkAssetIdentifierConverter : TwoWayConverter<SdkNFTAsset.Identifier,
|
|||
is SdkNFTAsset.Identifier.EVM -> NFTAsset.Identifier.EVM(
|
||||
tokenId = value.tokenId,
|
||||
tokenAddress = value.tokenAddress,
|
||||
contractType = NFTAsset.Identifier.EVM.ContractType.valueOf(value.contractType.name),
|
||||
)
|
||||
is SdkNFTAsset.Identifier.TON -> NFTAsset.Identifier.TON(
|
||||
tokenAddress = value.tokenAddress,
|
||||
)
|
||||
is SdkNFTAsset.Identifier.Solana -> NFTAsset.Identifier.Solana(
|
||||
tokenAddress = value.tokenAddress,
|
||||
cnft = value.cnft,
|
||||
)
|
||||
is SdkNFTAsset.Identifier.Unknown -> NFTAsset.Identifier.Unknown
|
||||
}
|
||||
|
||||
|
|
@ -20,10 +25,15 @@ object NFTSdkAssetIdentifierConverter : TwoWayConverter<SdkNFTAsset.Identifier,
|
|||
is NFTAsset.Identifier.EVM -> SdkNFTAsset.Identifier.EVM(
|
||||
tokenId = value.tokenId,
|
||||
tokenAddress = value.tokenAddress,
|
||||
contractType = SdkNFTAsset.Identifier.EVM.ContractType.valueOf(value.contractType.name),
|
||||
)
|
||||
is NFTAsset.Identifier.TON -> SdkNFTAsset.Identifier.TON(
|
||||
tokenAddress = value.tokenAddress,
|
||||
)
|
||||
is NFTAsset.Identifier.Solana -> SdkNFTAsset.Identifier.Solana(
|
||||
tokenAddress = value.tokenAddress,
|
||||
cnft = value.cnft,
|
||||
)
|
||||
is NFTAsset.Identifier.Unknown -> SdkNFTAsset.Identifier.Unknown
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,25 @@
|
|||
package com.tangem.datasource.local.nft.converter
|
||||
|
||||
import com.tangem.domain.nft.models.NFTAsset
|
||||
import com.tangem.domain.nft.models.NFTSalePrice
|
||||
import com.tangem.utils.converter.TwoWayConverter
|
||||
import com.tangem.blockchain.nft.models.NFTAsset.SalePrice as SDKSalePrice
|
||||
|
||||
internal class NFTSdkAssetSalePriceConverter(
|
||||
private val assetId: NFTAsset.Identifier,
|
||||
) : TwoWayConverter<SDKSalePrice, NFTSalePrice.Value> {
|
||||
override fun convert(value: SDKSalePrice): NFTSalePrice.Value {
|
||||
return NFTSalePrice.Value(
|
||||
assetId = assetId,
|
||||
value = value.value,
|
||||
symbol = value.symbol,
|
||||
)
|
||||
}
|
||||
|
||||
override fun convertBack(value: NFTSalePrice.Value): SDKSalePrice {
|
||||
return SDKSalePrice(
|
||||
symbol = value.symbol,
|
||||
value = value.value,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -12,6 +12,9 @@ object NFTSdkCollectionIdentifierConverter : TwoWayConverter<SdkNFTCollection.Id
|
|||
is SdkNFTCollection.Identifier.TON -> NFTCollection.Identifier.TON(
|
||||
contractAddress = value.contractAddress,
|
||||
)
|
||||
is SdkNFTCollection.Identifier.Solana -> NFTCollection.Identifier.Solana(
|
||||
collection = value.collection,
|
||||
)
|
||||
is SdkNFTCollection.Identifier.Unknown -> NFTCollection.Identifier.Unknown
|
||||
}
|
||||
|
||||
|
|
@ -22,6 +25,9 @@ object NFTSdkCollectionIdentifierConverter : TwoWayConverter<SdkNFTCollection.Id
|
|||
is NFTCollection.Identifier.TON -> SdkNFTCollection.Identifier.TON(
|
||||
contractAddress = value.contractAddress,
|
||||
)
|
||||
is NFTCollection.Identifier.Solana -> SdkNFTCollection.Identifier.Solana(
|
||||
collection = value.collection,
|
||||
)
|
||||
is NFTCollection.Identifier.Unknown -> SdkNFTCollection.Identifier.Unknown
|
||||
}
|
||||
}
|
||||
|
|
@ -6,6 +6,7 @@ import androidx.datastore.preferences.core.Preferences
|
|||
import androidx.datastore.preferences.core.edit
|
||||
import com.squareup.moshi.Moshi
|
||||
import com.squareup.moshi.Types
|
||||
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
||||
|
||||
/**
|
||||
* Application preferences store.
|
||||
|
|
@ -19,6 +20,7 @@ import com.squareup.moshi.Types
|
|||
*/
|
||||
class AppPreferencesStore(
|
||||
val moshi: Moshi,
|
||||
val dispatchers: CoroutineDispatcherProvider,
|
||||
private val preferencesDataStore: DataStore<Preferences>,
|
||||
) : DataStore<Preferences> by preferencesDataStore {
|
||||
|
||||
|
|
|
|||
|
|
@ -123,6 +123,12 @@ object PreferencesKeys {
|
|||
|
||||
val WALLETS_NFT_ENABLED_STATES_KEY by lazy { stringPreferencesKey(name = "walletsNftEnabledStates") }
|
||||
|
||||
// region Notifications
|
||||
val NOTIFICATIONS_APPLICATION_ID_KEY by lazy { stringPreferencesKey(name = "notificationsApplicationId") }
|
||||
|
||||
val NOTIFICATIONS_ENABLED_STATES_KEY by lazy { stringPreferencesKey(name = "notificationsEnabledStates") }
|
||||
// endregion
|
||||
|
||||
// region Promo
|
||||
fun getShouldShowStoriesKey(storyId: String) = booleanPreferencesKey("shouldShowStories_$storyId")
|
||||
|
||||
|
|
|
|||
|
|
@ -5,23 +5,25 @@ import androidx.datastore.preferences.core.edit
|
|||
import com.squareup.moshi.JsonDataException
|
||||
import com.squareup.moshi.Types
|
||||
import com.tangem.datasource.local.preferences.AppPreferencesStore
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.distinctUntilChanged
|
||||
import kotlinx.coroutines.flow.firstOrNull
|
||||
import kotlinx.coroutines.flow.map
|
||||
import kotlinx.coroutines.flow.*
|
||||
import kotlinx.coroutines.withContext
|
||||
|
||||
/** Get flow of nullable data [T] by string [key] */
|
||||
inline fun <reified T> AppPreferencesStore.getObject(key: Preferences.Key<String>): Flow<T?> {
|
||||
val adapter = moshi.adapter(T::class.java)
|
||||
return data.map { preferences ->
|
||||
preferences[key]?.let {
|
||||
try {
|
||||
adapter.fromJson(it)
|
||||
} catch (e: JsonDataException) {
|
||||
null
|
||||
}
|
||||
}
|
||||
}.distinctUntilChanged()
|
||||
return flow {
|
||||
val adapter = moshi.adapter(T::class.java)
|
||||
emitAll(
|
||||
data.map { preferences ->
|
||||
preferences[key]?.let {
|
||||
try {
|
||||
adapter.fromJson(it)
|
||||
} catch (e: JsonDataException) {
|
||||
null
|
||||
}
|
||||
}
|
||||
}.distinctUntilChanged(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -32,16 +34,19 @@ inline fun <reified T> AppPreferencesStore.getObject(key: Preferences.Key<String
|
|||
* @see getObjectList
|
||||
* */
|
||||
inline fun <reified T> AppPreferencesStore.getObject(key: Preferences.Key<String>, default: T): Flow<T> {
|
||||
val adapter = moshi.adapter(T::class.java) // TODO: Support parameterized types
|
||||
return data.map {
|
||||
try {
|
||||
it[key]?.let(adapter::fromJson) ?: default
|
||||
} catch (e: JsonDataException) {
|
||||
default
|
||||
}
|
||||
}.distinctUntilChanged()
|
||||
return flow {
|
||||
val adapter = moshi.adapter(T::class.java)
|
||||
emitAll(
|
||||
data.map {
|
||||
try {
|
||||
it[key]?.let(adapter::fromJson) ?: default
|
||||
} catch (e: JsonDataException) {
|
||||
default
|
||||
}
|
||||
}.distinctUntilChanged(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get nullable data [T] by string [key]
|
||||
*
|
||||
|
|
@ -49,26 +54,27 @@ inline fun <reified T> AppPreferencesStore.getObject(key: Preferences.Key<String
|
|||
*
|
||||
* @see getObjectListSync
|
||||
* */
|
||||
suspend inline fun <reified T> AppPreferencesStore.getObjectSyncOrNull(key: Preferences.Key<String>): T? {
|
||||
val adapter = moshi.adapter(T::class.java) // TODO: Support parameterized types
|
||||
return data.firstOrNull()
|
||||
?.get(key)
|
||||
?.let {
|
||||
try {
|
||||
adapter.fromJson(it)
|
||||
} catch (e: JsonDataException) {
|
||||
null
|
||||
suspend inline fun <reified T> AppPreferencesStore.getObjectSyncOrNull(key: Preferences.Key<String>): T? =
|
||||
withContext(dispatchers.io) {
|
||||
val adapter = moshi.adapter(T::class.java) // TODO: Support parameterized types
|
||||
data.firstOrNull()
|
||||
?.get(key)
|
||||
?.let {
|
||||
try {
|
||||
adapter.fromJson(it)
|
||||
} catch (e: JsonDataException) {
|
||||
null
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** Get data [T] by string [key]. If data is not found, it returns [default] */
|
||||
suspend inline fun <reified T> AppPreferencesStore.getObjectSyncOrDefault(
|
||||
key: Preferences.Key<String>,
|
||||
default: T,
|
||||
): T {
|
||||
): T = withContext(dispatchers.io) {
|
||||
val adapter = moshi.adapter(T::class.java)
|
||||
return data.firstOrNull()
|
||||
data.firstOrNull()
|
||||
?.get(key)
|
||||
?.let {
|
||||
try {
|
||||
|
|
@ -87,37 +93,47 @@ suspend inline fun <reified T> AppPreferencesStore.getObjectSyncOrDefault(
|
|||
*
|
||||
* @see storeObjectList
|
||||
* */
|
||||
suspend inline fun <reified T> AppPreferencesStore.storeObject(key: Preferences.Key<String>, value: T) {
|
||||
val adapter = moshi.adapter(T::class.java) // TODO: Support parameterized types
|
||||
edit { it[key] = adapter.toJson(value) }
|
||||
}
|
||||
@Suppress("OptionalUnit")
|
||||
suspend inline fun <reified T> AppPreferencesStore.storeObject(key: Preferences.Key<String>, value: T): Unit =
|
||||
withContext(dispatchers.io) {
|
||||
val adapter = moshi.adapter(T::class.java) // TODO: Support parameterized types
|
||||
edit { it[key] = adapter.toJson(value) }
|
||||
}
|
||||
|
||||
/** Store list of data [value] by string [key] */
|
||||
suspend inline fun <reified T> AppPreferencesStore.storeObjectList(key: Preferences.Key<String>, value: List<T>) {
|
||||
val adapter = moshi.adapter<List<T>>(Types.newParameterizedType(List::class.java, T::class.java))
|
||||
edit { it[key] = adapter.toJson(value) }
|
||||
}
|
||||
suspend inline fun <reified T> AppPreferencesStore.storeObjectList(key: Preferences.Key<String>, value: List<T>) =
|
||||
withContext(dispatchers.io) {
|
||||
val adapter = moshi.adapter<List<T>>(Types.newParameterizedType(List::class.java, T::class.java))
|
||||
edit { it[key] = adapter.toJson(value) }
|
||||
}
|
||||
|
||||
/** Get flow of list of data [T] by string [key]. If data is not found, it returns `null` */
|
||||
inline fun <reified T> AppPreferencesStore.getObjectList(key: Preferences.Key<String>): Flow<List<T>?> {
|
||||
val adapter = moshi.adapter<List<T>>(Types.newParameterizedType(List::class.java, T::class.java))
|
||||
return data.map { it[key]?.let(adapter::fromJson) }.distinctUntilChanged()
|
||||
return flow {
|
||||
val adapter = moshi.adapter<List<T>>(Types.newParameterizedType(List::class.java, T::class.java))
|
||||
emitAll(
|
||||
data.map {
|
||||
it[key]?.let(adapter::fromJson)
|
||||
}.distinctUntilChanged(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/** Get list of data [T] by string [key], or empty if data is not found */
|
||||
suspend inline fun <reified T> AppPreferencesStore.getObjectListSync(key: Preferences.Key<String>): List<T> {
|
||||
val adapter = moshi.adapter<List<T>>(Types.newParameterizedType(List::class.java, T::class.java))
|
||||
return data.firstOrNull()
|
||||
?.get(key)
|
||||
?.let(adapter::fromJson)
|
||||
.orEmpty()
|
||||
}
|
||||
suspend inline fun <reified T> AppPreferencesStore.getObjectListSync(key: Preferences.Key<String>): List<T> =
|
||||
withContext(dispatchers.io) {
|
||||
val adapter = moshi.adapter<List<T>>(Types.newParameterizedType(List::class.java, T::class.java))
|
||||
data.firstOrNull()
|
||||
?.get(key)
|
||||
?.let(adapter::fromJson)
|
||||
.orEmpty()
|
||||
}
|
||||
|
||||
/** Store map with [String] key and value [V] by string [key] */
|
||||
suspend inline fun <reified V> AppPreferencesStore.storeObjectMap(
|
||||
key: Preferences.Key<String>,
|
||||
value: Map<String, V>,
|
||||
) {
|
||||
) = withContext(dispatchers.io) {
|
||||
val type = Types.newParameterizedType(Map::class.java, String::class.java, V::class.java)
|
||||
val adapter = moshi.adapter<Map<String, V>>(type)
|
||||
|
||||
|
|
@ -125,37 +141,47 @@ suspend inline fun <reified V> AppPreferencesStore.storeObjectMap(
|
|||
}
|
||||
|
||||
/** Get map with [String] key and value [V] by string [key], or empty if data is not found */
|
||||
suspend inline fun <reified V> AppPreferencesStore.getObjectMapSync(key: Preferences.Key<String>): Map<String, V> {
|
||||
val type = Types.newParameterizedType(Map::class.java, String::class.java, V::class.java)
|
||||
val adapter = moshi.adapter<Map<String, V>>(type)
|
||||
suspend inline fun <reified V> AppPreferencesStore.getObjectMapSync(key: Preferences.Key<String>): Map<String, V> =
|
||||
withContext(dispatchers.io) {
|
||||
val type = Types.newParameterizedType(Map::class.java, String::class.java, V::class.java)
|
||||
val adapter = moshi.adapter<Map<String, V>>(type)
|
||||
|
||||
return data.firstOrNull()
|
||||
?.get(key)
|
||||
?.let(adapter::fromJson)
|
||||
.orEmpty()
|
||||
}
|
||||
data.firstOrNull()
|
||||
?.get(key)
|
||||
?.let(adapter::fromJson)
|
||||
.orEmpty()
|
||||
}
|
||||
|
||||
/** Get flow of map with [String] key and value [V] by string [key], or empty if data is not found */
|
||||
inline fun <reified V> AppPreferencesStore.getObjectMap(key: Preferences.Key<String>): Flow<Map<String, V>> {
|
||||
val type = Types.newParameterizedType(Map::class.java, String::class.java, V::class.java)
|
||||
val adapter = moshi.adapter<Map<String, V>>(type)
|
||||
return flow {
|
||||
val type = Types.newParameterizedType(Map::class.java, String::class.java, V::class.java)
|
||||
val adapter = moshi.adapter<Map<String, V>>(type)
|
||||
|
||||
return data.map { it[key]?.let(adapter::fromJson) ?: emptyMap() }
|
||||
emitAll(
|
||||
data.map { it[key]?.let(adapter::fromJson) ?: emptyMap() },
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/** Get set of data [T] by string [key], or empty if data is not found */
|
||||
suspend inline fun <reified T> AppPreferencesStore.getObjectSetSync(key: Preferences.Key<String>): Set<T> {
|
||||
val adapter = moshi.adapter<Set<T>>(Types.newParameterizedType(Set::class.java, T::class.java))
|
||||
return data.firstOrNull()
|
||||
?.get(key)
|
||||
?.let(adapter::fromJson)
|
||||
.orEmpty()
|
||||
}
|
||||
suspend inline fun <reified T> AppPreferencesStore.getObjectSetSync(key: Preferences.Key<String>): Set<T> =
|
||||
withContext(dispatchers.io) {
|
||||
val adapter = moshi.adapter<Set<T>>(Types.newParameterizedType(Set::class.java, T::class.java))
|
||||
data.firstOrNull()
|
||||
?.get(key)
|
||||
?.let(adapter::fromJson)
|
||||
.orEmpty()
|
||||
}
|
||||
|
||||
/** Get flow of set of [T] by string [key], or empty if data is not found */
|
||||
inline fun <reified T> AppPreferencesStore.getObjectSet(key: Preferences.Key<String>): Flow<Set<T>> {
|
||||
val adapter = moshi.adapter<Set<T>>(Types.newParameterizedType(Set::class.java, T::class.java))
|
||||
return data.map {
|
||||
it[key]?.let(adapter::fromJson) ?: emptySet()
|
||||
return flow {
|
||||
val adapter = moshi.adapter<Set<T>>(Types.newParameterizedType(Set::class.java, T::class.java))
|
||||
emitAll(
|
||||
data.map {
|
||||
it[key]?.let(adapter::fromJson) ?: emptySet()
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -22,7 +22,9 @@ internal class SharedPreferencesKeyMigration(
|
|||
private val keyName: String,
|
||||
) : DataMigration<Preferences> {
|
||||
|
||||
private val legacyPrefs = context.getSharedPreferences(legacyPrefsName, Context.MODE_PRIVATE)
|
||||
private val legacyPrefs by lazy {
|
||||
context.getSharedPreferences(legacyPrefsName, Context.MODE_PRIVATE)
|
||||
}
|
||||
|
||||
override suspend fun cleanUp() {
|
||||
val sharedPrefsEditor = legacyPrefs.edit()
|
||||
|
|
|
|||
|
|
@ -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<Map.Entry<String, QuotesResponse.Quote>, 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<String, QuotesResponse.Quote>): 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,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -4,6 +4,8 @@ import androidx.datastore.core.DataStore
|
|||
import com.tangem.datasource.api.stakekit.models.response.model.YieldDTO
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.firstOrNull
|
||||
import kotlinx.coroutines.withTimeoutOrNull
|
||||
import kotlin.time.Duration.Companion.seconds
|
||||
|
||||
internal class DefaultStakingYieldsStore(
|
||||
private val dataStore: DataStore<List<YieldDTO>>,
|
||||
|
|
@ -17,9 +19,18 @@ internal class DefaultStakingYieldsStore(
|
|||
return dataStore.data.firstOrNull().orEmpty()
|
||||
}
|
||||
|
||||
override suspend fun getSyncWithTimeout(): List<YieldDTO>? {
|
||||
return withTimeoutOrNull(timeout = YIELDS_WAITING_TIMEOUT, block = { get().firstOrNull() })
|
||||
}
|
||||
|
||||
override suspend fun store(items: List<YieldDTO>) {
|
||||
dataStore.updateData { _ ->
|
||||
items
|
||||
}
|
||||
}
|
||||
|
||||
private companion object {
|
||||
|
||||
val YIELDS_WAITING_TIMEOUT = 15.seconds
|
||||
}
|
||||
}
|
||||
|
|
@ -9,5 +9,7 @@ interface StakingYieldsStore {
|
|||
|
||||
suspend fun getSync(): List<YieldDTO>
|
||||
|
||||
suspend fun getSyncWithTimeout(): List<YieldDTO>?
|
||||
|
||||
suspend fun store(items: List<YieldDTO>)
|
||||
}
|
||||
|
|
@ -7,7 +7,7 @@ import com.tangem.domain.staking.model.stakekit.YieldBalance
|
|||
import com.tangem.domain.staking.model.stakekit.YieldBalanceItem
|
||||
import com.tangem.utils.converter.Converter
|
||||
|
||||
internal class YieldBalanceConverter(
|
||||
class YieldBalanceConverter(
|
||||
private val source: StatusSource,
|
||||
) : Converter<YieldBalanceWrapperDTO, YieldBalance> {
|
||||
|
||||
|
|
|
|||
|
|
@ -13,7 +13,7 @@ interface UserWalletsStore {
|
|||
|
||||
fun getSyncOrNull(key: UserWalletId): UserWallet?
|
||||
|
||||
suspend fun getSyncStrict(key: UserWalletId): UserWallet
|
||||
fun getSyncStrict(key: UserWalletId): UserWallet
|
||||
|
||||
suspend fun getAllSyncOrNull(): List<UserWallet>?
|
||||
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ import com.tangem.datasource.BuildConfig
|
|||
import com.tangem.datasource.api.common.AuthProvider
|
||||
import com.tangem.datasource.api.common.config.*
|
||||
import com.tangem.datasource.api.common.config.ApiConfig.Companion.DEBUG_BUILD_TYPE
|
||||
import com.tangem.datasource.api.common.config.ApiConfig.Companion.EXTERNAL_BUILD_TYPE
|
||||
import com.tangem.datasource.api.common.config.ApiConfig.Companion.INTERNAL_BUILD_TYPE
|
||||
import com.tangem.datasource.api.common.config.ApiConfig.Companion.MOCKED_BUILD_TYPE
|
||||
import com.tangem.datasource.api.common.config.ApiConfig.Companion.RELEASE_BUILD_TYPE
|
||||
|
|
@ -34,7 +35,6 @@ private val API_CONFIGS = setOf(
|
|||
Express(configManager, expressAuthProvider, appVersionProvider),
|
||||
TangemTech(appVersionProvider, appAuthProvider),
|
||||
StakeKit(stakeKitAuthProvider),
|
||||
TangemVisaAuth(appVersionProvider),
|
||||
TangemVisa(appVersionProvider),
|
||||
)
|
||||
|
||||
|
|
@ -49,9 +49,7 @@ internal class ProdApiConfigsManagerTest(private val model: Model) {
|
|||
@Before
|
||||
fun setup() {
|
||||
every { appVersionProvider.versionName } returns VERSION_NAME
|
||||
every { expressAuthProvider.getUserId() } returns EXPRESS_USER_ID
|
||||
every { expressAuthProvider.getSessionId() } returns EXPRESS_SESSION_ID
|
||||
every { expressAuthProvider.getRefCode() } returns EXPRESS_REF_CODE
|
||||
every { stakeKitAuthProvider.getApiKey() } returns STAKE_KIT_API_KEY
|
||||
every { appAuthProvider.getCardId() } returns APP_CARD_ID
|
||||
every { appAuthProvider.getCardPublicKey() } returns APP_CARD_PUBLIC_KEY
|
||||
|
|
@ -87,9 +85,9 @@ internal class ProdApiConfigsManagerTest(private val model: Model) {
|
|||
is Express -> createExpressModel()
|
||||
is TangemTech -> createTangemTechModel()
|
||||
is StakeKit -> createStakeKitModel()
|
||||
is TangemVisaAuth -> createVisaAuthModel()
|
||||
is TangemVisa -> createVisaModel()
|
||||
is TangemCardSdk -> createTangemCardSdkModel()
|
||||
is BlockAid -> createBlockAidSdkModel()
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -100,6 +98,7 @@ internal class ProdApiConfigsManagerTest(private val model: Model) {
|
|||
INTERNAL_BUILD_TYPE,
|
||||
MOCKED_BUILD_TYPE,
|
||||
-> ApiEnvironment.STAGE
|
||||
EXTERNAL_BUILD_TYPE,
|
||||
RELEASE_BUILD_TYPE,
|
||||
-> ApiEnvironment.PROD
|
||||
else -> error("Unknown build type [${BuildConfig.BUILD_TYPE}]")
|
||||
|
|
@ -115,6 +114,7 @@ internal class ProdApiConfigsManagerTest(private val model: Model) {
|
|||
INTERNAL_BUILD_TYPE,
|
||||
MOCKED_BUILD_TYPE,
|
||||
-> "[REDACTED_ENV_URL]"
|
||||
EXTERNAL_BUILD_TYPE,
|
||||
RELEASE_BUILD_TYPE,
|
||||
-> "https://express.tangem.com/v1/"
|
||||
else -> error("Unknown build type [${BuildConfig.BUILD_TYPE}]")
|
||||
|
|
@ -127,9 +127,7 @@ internal class ProdApiConfigsManagerTest(private val model: Model) {
|
|||
MockEnvironmentConfigStorage.EXPRESS_DEV_API_KEY
|
||||
}
|
||||
},
|
||||
"user-id" to ProviderSuspend { EXPRESS_USER_ID },
|
||||
"session-id" to ProviderSuspend { EXPRESS_SESSION_ID },
|
||||
"refcode" to ProviderSuspend { EXPRESS_REF_CODE },
|
||||
"version" to ProviderSuspend { VERSION_NAME },
|
||||
"platform" to ProviderSuspend { "android" },
|
||||
"language" to ProviderSuspend { Locale.getDefault().language.checkHeaderValueOrEmpty() },
|
||||
|
|
@ -177,26 +175,12 @@ internal class ProdApiConfigsManagerTest(private val model: Model) {
|
|||
)
|
||||
}
|
||||
|
||||
private fun createVisaAuthModel(): Model {
|
||||
return Model(
|
||||
id = ApiConfig.ID.TangemVisaAuth,
|
||||
expected = ApiEnvironmentConfig(
|
||||
environment = ApiEnvironment.STAGE,
|
||||
baseUrl = "https://api-s.tangem.org/",
|
||||
headers = mapOf(
|
||||
"version" to ProviderSuspend { VERSION_NAME },
|
||||
"platform" to ProviderSuspend { "Android" },
|
||||
),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
private fun createVisaModel(): Model {
|
||||
return Model(
|
||||
id = ApiConfig.ID.TangemVisa,
|
||||
expected = ApiEnvironmentConfig(
|
||||
environment = ApiEnvironment.PROD,
|
||||
baseUrl = "https://bff.tangem.com/",
|
||||
environment = ApiEnvironment.DEV,
|
||||
baseUrl = "[REDACTED_ENV_URL]",
|
||||
headers = mapOf(
|
||||
"version" to ProviderSuspend { VERSION_NAME },
|
||||
"platform" to ProviderSuspend { "Android" },
|
||||
|
|
@ -215,6 +199,16 @@ internal class ProdApiConfigsManagerTest(private val model: Model) {
|
|||
)
|
||||
}
|
||||
|
||||
private fun createBlockAidSdkModel(): Model {
|
||||
return Model(
|
||||
id = ApiConfig.ID.BlockAid,
|
||||
expected = ApiEnvironmentConfig(
|
||||
environment = ApiEnvironment.PROD,
|
||||
baseUrl = "https://api.blockaid.io/v0/",
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
private fun String.checkHeaderValueOrEmpty(): String {
|
||||
for (i in this.indices) {
|
||||
val c = this[i]
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ import com.squareup.moshi.Types
|
|||
import com.squareup.moshi.adapter
|
||||
import com.tangem.datasource.api.express.models.response.Asset
|
||||
import com.tangem.datasource.asset.reader.AssetReader
|
||||
import com.tangem.utils.coroutines.TestingCoroutineDispatcherProvider
|
||||
import io.mockk.coEvery
|
||||
import io.mockk.coVerifyOrder
|
||||
import io.mockk.every
|
||||
|
|
@ -22,7 +23,11 @@ class AssetLoaderTest {
|
|||
|
||||
private val assetReader = mockk<AssetReader>()
|
||||
private val moshi = mockk<Moshi>()
|
||||
private val assetLoader = AssetLoader(assetReader = assetReader, moshi = moshi)
|
||||
private val assetLoader = AssetLoader(
|
||||
assetReader = assetReader,
|
||||
moshi = moshi,
|
||||
dispatchers = TestingCoroutineDispatcherProvider(),
|
||||
)
|
||||
|
||||
@Test
|
||||
fun load() = runTest {
|
||||
|
|
|
|||
|
|
@ -2,7 +2,6 @@ package com.tangem.datasource.asset.reader
|
|||
|
||||
import android.content.res.AssetManager
|
||||
import com.google.common.truth.Truth
|
||||
import com.tangem.utils.coroutines.TestingCoroutineDispatcherProvider
|
||||
import io.mockk.every
|
||||
import io.mockk.mockk
|
||||
import kotlinx.coroutines.test.runTest
|
||||
|
|
@ -15,7 +14,7 @@ import java.io.IOException
|
|||
internal class AndroidAssetReaderTest {
|
||||
|
||||
private val assetManager = mockk<AssetManager>()
|
||||
private val assetReader = AndroidAssetReader(assetManager, TestingCoroutineDispatcherProvider())
|
||||
private val assetReader = AndroidAssetReader(assetManager)
|
||||
|
||||
@Test
|
||||
fun read_content() = runTest {
|
||||
|
|
|
|||
|
|
@ -3,19 +3,16 @@ package com.tangem.core.deeplink.global
|
|||
import com.tangem.core.deeplink.DeepLink
|
||||
|
||||
class BuyCurrencyDeepLink(
|
||||
val onReceive: (externalTxId: String) -> Unit,
|
||||
val onReceive: () -> Unit,
|
||||
) : DeepLink() {
|
||||
|
||||
override val uri = BUY_REDIRECT_DEEPLINK
|
||||
|
||||
override fun onReceive(params: Map<String, String>) {
|
||||
onReceive(
|
||||
params["merchant_transaction_id"] ?: return,
|
||||
)
|
||||
onReceive()
|
||||
}
|
||||
|
||||
companion object {
|
||||
const val ONRAMP_REDIRECT_DEEPLINK = "https://tangem.com/success?action=dismissBrowser"
|
||||
private const val BUY_REDIRECT_DEEPLINK = "tangem://redirect?action=dismissBrowser"
|
||||
}
|
||||
}
|
||||
|
|
@ -514,6 +514,9 @@
|
|||
<string name="no_account_polkadot">La cuenta de destino no está activa. Envíe %s o más para activar la cuenta.</string>
|
||||
<string name="no_account_send_to_create">Para crear una cuenta, envíe fondos a esta dirección</string>
|
||||
<string name="no_trustline_xlm_asset">La cuenta de destino no tiene una Trustline para el activo que se envía.</string>
|
||||
<string name="notification_referral_promo_button">Únete ahora</string>
|
||||
<string name="notification_referral_promo_text">Comparte tu código y gana 5 USDT por venta. Tu amigo obtiene un 10% de descuento.</string>
|
||||
<string name="notification_referral_promo_title">¡Obtén RECOMPENSAS por cada amigo!</string>
|
||||
<string name="onboarding_access_code_feature_1_description">Deba configurar un único código de acceso para proteger todss sus dispositivos.</string>
|
||||
<string name="onboarding_access_code_feature_1_title">Proteger</string>
|
||||
<string name="onboarding_access_code_feature_2_description">Puede configurar un código de acceso individual en cada tarjeta más adelante</string>
|
||||
|
|
|
|||
|
|
@ -875,15 +875,15 @@
|
|||
<string name="staking_notification_additional_ada_deposit_text">Cardanoネットワークでステーキングする場合、残高全体が使用されます。追加の2ADAは確保され、ステーキング解除後に返却されます。ステーキング中、ADAはロック解除されたままです。</string>
|
||||
<string name="staking_notification_additional_ada_deposit_title">ADAステーキングの詳細</string>
|
||||
<string name="staking_notification_claim_rewards_text">獲得した報酬はあなたのアドレスに直接送られ、すぐ使用可能です。</string>
|
||||
<string name="staking_notification_earn_rewards_text">Tangemで暗号資産をステーキングしよう</string>
|
||||
<string name="staking_notification_earn_rewards_text_daily">Tangemで暗号資産をステーキングしよう</string>
|
||||
<string name="staking_notification_earn_rewards_text_hourly">Tangemで暗号資産をステーキングしよう</string>
|
||||
<string name="staking_notification_earn_rewards_text_monthly">Tangemで暗号資産をステーキングしよう</string>
|
||||
<string name="staking_notification_earn_rewards_text">Tangemでは暗号資産をステーキングできます</string>
|
||||
<string name="staking_notification_earn_rewards_text_daily">Tangemでは暗号資産をステーキングできます</string>
|
||||
<string name="staking_notification_earn_rewards_text_hourly">Tangemでは暗号資産をステーキングできます</string>
|
||||
<string name="staking_notification_earn_rewards_text_monthly">Tangemでは暗号資産をステーキングできます</string>
|
||||
<string name="staking_notification_earn_rewards_text_period_day">ステーキングを行うと%1$sを受け取ることができます。ステーキング報酬は毎日受け取れます。</string>
|
||||
<string name="staking_notification_earn_rewards_text_period_hour">ステーキングを行うと%1$sを受け取ることができます。ステーキング報酬は1時間ごとに受け取れます。</string>
|
||||
<string name="staking_notification_earn_rewards_text_period_month">ステーキングを行うと%1$sを受け取ることができます。ステーキング報酬は毎月受け取れます。</string>
|
||||
<string name="staking_notification_earn_rewards_text_period_week">ステーキングを行うと%1$sを受け取ることができます。ステーキング報酬は毎週受け取れます。</string>
|
||||
<string name="staking_notification_earn_rewards_text_weekly">Tangemで暗号資産をステーキングしよう</string>
|
||||
<string name="staking_notification_earn_rewards_text_weekly">Tangemでは暗号資産をステーキングできます</string>
|
||||
<string name="staking_notification_earn_rewards_title">ステーキング報酬を獲得</string>
|
||||
<string name="staking_notification_low_staked_balance_text">残りのステーキング残高が少なすぎてステーキングを解除できません。最小のステーキング解除残高を満たすには、さらにステーキングする必要があります。</string>
|
||||
<string name="staking_notification_low_staked_balance_title">ステーキング残高が低いです</string>
|
||||
|
|
@ -1044,6 +1044,8 @@
|
|||
<string name="transaction_history_transaction_validator">バリデーター: %s</string>
|
||||
<string name="transfer_min_amount_error">最小%s</string>
|
||||
<string name="transfer_notification_invalid_minimum_transaction_amount_text">最小取引金額は%1$sです。</string>
|
||||
<string name="tron_will_be_send_token_fee_description">Tronネットワークの人気トークンの手数料は高めです。TRXをステーキングすると、より安く、あるいは無料で取引できます。</string>
|
||||
<string name="tron_will_be_send_token_fee_title">Tronネットワーク手数料を節約</string>
|
||||
<string name="try_to_load_data_again_button_title">もう一度やり直してください</string>
|
||||
<string name="twin_error_same_card">同じカードをスキャンしました。ツインウォレットを作成するには、番号%dのカードをスキャンする必要があります。</string>
|
||||
<string name="twin_error_wrong_twin">間違ったツインカードをスキャンしました。別のカードをお試しください。</string>
|
||||
|
|
|
|||
|
|
@ -526,6 +526,9 @@
|
|||
<string name="no_account_polkadot">Обліковий запис одержувача не активовано. Надішліть %s або більше, щоб активувати обліковий запис.</string>
|
||||
<string name="no_account_send_to_create">Для створення акаунту надішліть кошти на цю адресу</string>
|
||||
<string name="no_trustline_xlm_asset">Акаунт одержувача не містить трастлайну для активу, що надсилається.</string>
|
||||
<string name="notification_referral_promo_button">Приєднатися</string>
|
||||
<string name="notification_referral_promo_text">Поділіться промокодом — заробіть 5 USDT з кожної покупки. Ваші друзі отримають знижку 10% на картку Tangem!</string>
|
||||
<string name="notification_referral_promo_title">Отримуй бонуси за кожного друга!</string>
|
||||
<string name="onboarding_access_code_feature_1_description">Налаштуйте єдиний код доступу для захисту всіх ваших карток або кілець</string>
|
||||
<string name="onboarding_access_code_feature_1_title">Захист</string>
|
||||
<string name="onboarding_access_code_feature_2_description">Встановіть індивідуальний код доступу для кожної картки або кільця пізніше.</string>
|
||||
|
|
|
|||
|
|
@ -535,6 +535,13 @@
|
|||
<string name="nft_details_base_information">Base information</string>
|
||||
<string name="nft_details_chain">Chain</string>
|
||||
<string name="nft_details_contract_address">Contract Address</string>
|
||||
<string name="nft_details_info_chain">stub</string>
|
||||
<string name="nft_details_info_contract_address">stub</string>
|
||||
<string name="nft_details_info_rarity_label">stub</string>
|
||||
<string name="nft_details_info_rarity_rank">stub</string>
|
||||
<string name="nft_details_info_token_address">stub</string>
|
||||
<string name="nft_details_info_token_id">stub</string>
|
||||
<string name="nft_details_info_token_standard">stub</string>
|
||||
<string name="nft_details_last_sale_price">Last sale price</string>
|
||||
<string name="nft_details_rarity_label">Rarity label</string>
|
||||
<string name="nft_details_rarity_rank">Rarity rank</string>
|
||||
|
|
@ -1056,6 +1063,8 @@
|
|||
<string name="transaction_history_transaction_validator">validator: %s</string>
|
||||
<string name="transfer_min_amount_error">Minimum %s</string>
|
||||
<string name="transfer_notification_invalid_minimum_transaction_amount_text">The minimum transaction amount is %1$s.</string>
|
||||
<string name="tron_will_be_send_token_fee_description">Tron network fees for popular tokens are higher. Stake some TRX for cheaper or free transactions.</string>
|
||||
<string name="tron_will_be_send_token_fee_title">Save on Tron network fees</string>
|
||||
<string name="try_to_load_data_again_button_title">Try again</string>
|
||||
<string name="twin_error_same_card">You\'ve scanned the same card. To create a twin wallet you need to scan the card with number %d</string>
|
||||
<string name="twin_error_wrong_twin">You\'ve scanned wrong twin card. Please try another one</string>
|
||||
|
|
@ -1308,6 +1317,8 @@
|
|||
<string name="wc_connection_reqeust_would_like">Would like to</string>
|
||||
<string name="wc_connection_request">Connection request</string>
|
||||
<string name="wc_connections">Connections</string>
|
||||
<string name="wc_contents">Contents</string>
|
||||
<string name="wc_copy_data_button_text">Copy data</string>
|
||||
<string name="wc_disconnect_all">Disconnect all</string>
|
||||
<string name="wc_disconnect_all_alert_desc">Text about discnected all dApps</string>
|
||||
<string name="wc_disconnect_all_alert_title">Disconect All dApps</string>
|
||||
|
|
@ -1318,6 +1329,10 @@
|
|||
<string name="wc_no_sessions_title">No sessions</string>
|
||||
<string name="wc_notification_security_risk_subtitle">This domain is flagged as unsafe by multiple security providers. Leave immediately to protect your assets</string>
|
||||
<string name="wc_notification_security_risk_title">Known security risk</string>
|
||||
<string name="wc_request_from">Request from</string>
|
||||
<string name="wc_signature_type">Signature Type</string>
|
||||
<string name="wc_transaction_request">Transaction request</string>
|
||||
<string name="wc_transaction_request_title">Transaction request</string>
|
||||
<string name="wc_wallet_connect">Wallet connect</string>
|
||||
<string name="welcome_interrupted_backup_alert_discard">Discard</string>
|
||||
<string name="welcome_interrupted_backup_alert_message">You have an interrupted backup. Do you want to resume?</string>
|
||||
|
|
|
|||
|
|
@ -0,0 +1,17 @@
|
|||
package com.tangem.core.ui.components.artwork
|
||||
|
||||
import androidx.compose.runtime.Immutable
|
||||
import kotlinx.collections.immutable.ImmutableList
|
||||
import kotlinx.collections.immutable.toImmutableList
|
||||
|
||||
@Immutable
|
||||
data class ArtworkUM(
|
||||
val verifiedArtwork: ImmutableList<Byte>? = null,
|
||||
val defaultUrl: String,
|
||||
) {
|
||||
|
||||
constructor(bytes: ByteArray?, defaultUrl: String) : this(
|
||||
verifiedArtwork = bytes?.toList()?.toImmutableList(),
|
||||
defaultUrl = defaultUrl,
|
||||
)
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue