Updated on 2026-08-14
This commit is contained in:
commit
fe2cfac54d
1140 changed files with 23493 additions and 8579 deletions
|
|
@ -52,6 +52,7 @@ dependencies {
|
|||
/** Coroutines */
|
||||
implementation(deps.kotlin.coroutines)
|
||||
implementation(deps.kotlin.coroutines.rx2)
|
||||
implementation(deps.kotlin.datetime)
|
||||
|
||||
/** Logging */
|
||||
implementation(deps.timber)
|
||||
|
|
@ -76,7 +77,7 @@ dependencies {
|
|||
|
||||
/** Chucker */
|
||||
debugImplementation(deps.chucker)
|
||||
mockedImplementation(deps.chuckerStub)
|
||||
mockedImplementation(deps.chucker)
|
||||
externalImplementation(deps.chuckerStub)
|
||||
internalImplementation(deps.chuckerStub)
|
||||
releaseImplementation(deps.chuckerStub)
|
||||
|
|
|
|||
|
|
@ -8,12 +8,12 @@ interface AuthProvider {
|
|||
/**
|
||||
* Returns authToken for tangem tech api
|
||||
*/
|
||||
fun getCardPublicKey(): String
|
||||
suspend fun getCardPublicKey(): String
|
||||
|
||||
fun getCardId(): String
|
||||
suspend fun getCardId(): String
|
||||
|
||||
/**
|
||||
* Returns map where keys(cardId) associated with cardPublicKey
|
||||
*/
|
||||
fun getCardsPublicKeys(): Map<String, String>
|
||||
suspend fun getCardsPublicKeys(): Map<String, String>
|
||||
}
|
||||
|
|
@ -15,12 +15,14 @@ import okio.IOException
|
|||
* Switch api environment [Interceptor]
|
||||
*
|
||||
* @property id api config id [ApiConfig.ID]
|
||||
* @property baseUrls base urls for all api config environments
|
||||
* @property apiConfigsManager api configs manager
|
||||
*
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
internal class SwitchEnvironmentInterceptor(
|
||||
private val id: ApiConfig.ID,
|
||||
private val baseUrls: Set<String>,
|
||||
private val apiConfigsManager: ApiConfigsManager,
|
||||
) : Interceptor {
|
||||
|
||||
|
|
@ -39,10 +41,13 @@ internal class SwitchEnvironmentInterceptor(
|
|||
return chain.proceed(request)
|
||||
}
|
||||
|
||||
private fun HttpUrl.adjustBaseUrl(url: String): HttpUrl {
|
||||
return this.newBuilder()
|
||||
.host(host = url.toHttpUrl().host)
|
||||
.build()
|
||||
private fun HttpUrl.adjustBaseUrl(newBaseUrl: String): HttpUrl {
|
||||
val currentUrl = this.toString()
|
||||
val currentBaseUrl = baseUrls.first { currentUrl.contains(it) }
|
||||
|
||||
return currentUrl
|
||||
.replace(oldValue = currentBaseUrl, newValue = newBaseUrl)
|
||||
.toHttpUrl()
|
||||
}
|
||||
|
||||
private fun Request.Builder.addHeaders(headers: Map<String, ProviderSuspend<String>>): Request.Builder {
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
package com.tangem.datasource.api.common.config
|
||||
|
||||
import com.tangem.datasource.BuildConfig
|
||||
import com.tangem.lib.auth.StakeKitAuthProvider
|
||||
import com.tangem.utils.ProviderSuspend
|
||||
|
||||
|
|
@ -14,20 +15,44 @@ internal class StakeKit(
|
|||
private val stakeKitAuthProvider: StakeKitAuthProvider,
|
||||
) : ApiConfig() {
|
||||
|
||||
override val defaultEnvironment: ApiEnvironment = ApiEnvironment.PROD
|
||||
override val defaultEnvironment: ApiEnvironment = getInitialEnvironment()
|
||||
|
||||
override val environmentConfigs: List<ApiEnvironmentConfig> = listOf(
|
||||
createProdEnvironment(),
|
||||
createMockEnvironment(),
|
||||
)
|
||||
|
||||
private fun getInitialEnvironment(): ApiEnvironment {
|
||||
return when (BuildConfig.BUILD_TYPE) {
|
||||
MOCKED_BUILD_TYPE,
|
||||
-> ApiEnvironment.MOCK
|
||||
DEBUG_BUILD_TYPE,
|
||||
INTERNAL_BUILD_TYPE,
|
||||
EXTERNAL_BUILD_TYPE,
|
||||
RELEASE_BUILD_TYPE,
|
||||
-> ApiEnvironment.PROD
|
||||
else -> error("Unknown build type [${BuildConfig.BUILD_TYPE}]")
|
||||
}
|
||||
}
|
||||
|
||||
private fun createProdEnvironment(): ApiEnvironmentConfig {
|
||||
return ApiEnvironmentConfig(
|
||||
environment = ApiEnvironment.PROD,
|
||||
baseUrl = "https://api.stakek.it/v1/",
|
||||
headers = mapOf(
|
||||
"X-API-KEY" to ProviderSuspend(stakeKitAuthProvider::getApiKey),
|
||||
"accept" to ProviderSuspend { "application/json" },
|
||||
),
|
||||
headers = createHeaders(),
|
||||
)
|
||||
}
|
||||
|
||||
private fun createMockEnvironment(): ApiEnvironmentConfig {
|
||||
return ApiEnvironmentConfig(
|
||||
environment = ApiEnvironment.MOCK,
|
||||
baseUrl = "[REDACTED_ENV_URL]",
|
||||
headers = createHeaders(),
|
||||
)
|
||||
}
|
||||
|
||||
private fun createHeaders() = buildMap {
|
||||
put(key = "X-API-KEY", value = ProviderSuspend(stakeKitAuthProvider::getApiKey))
|
||||
put(key = "accept", value = ProviderSuspend { "application/json" })
|
||||
}
|
||||
}
|
||||
|
|
@ -1,19 +1,7 @@
|
|||
package com.tangem.datasource.api.pay
|
||||
|
||||
import com.tangem.datasource.api.common.response.ApiResponse
|
||||
import com.tangem.datasource.api.pay.models.request.ActivationByCardWalletRequest
|
||||
import com.tangem.datasource.api.pay.models.request.ActivationByCustomerWalletRequest
|
||||
import com.tangem.datasource.api.pay.models.request.ActivationStatusRequest
|
||||
import com.tangem.datasource.api.pay.models.request.ExchangeAccessTokenRequest
|
||||
import com.tangem.datasource.api.pay.models.request.GenerateNoneByCardIdRequest
|
||||
import com.tangem.datasource.api.pay.models.request.GenerateNoneByCardWalletRequest
|
||||
import com.tangem.datasource.api.pay.models.request.GetAccessTokenByCardIdRequest
|
||||
import com.tangem.datasource.api.pay.models.request.GetAccessTokenByCardWalletRequest
|
||||
import com.tangem.datasource.api.pay.models.request.GetCardWalletAcceptanceRequest
|
||||
import com.tangem.datasource.api.pay.models.request.GetCustomerWalletAcceptanceRequest
|
||||
import com.tangem.datasource.api.pay.models.request.RefreshTokenByCardIdRequest
|
||||
import com.tangem.datasource.api.pay.models.request.RefreshTokenByCardWalletRequest
|
||||
import com.tangem.datasource.api.pay.models.request.SetPinCodeRequest
|
||||
import com.tangem.datasource.api.pay.models.request.*
|
||||
import com.tangem.datasource.api.pay.models.response.*
|
||||
import retrofit2.http.Body
|
||||
import retrofit2.http.GET
|
||||
|
|
@ -33,9 +21,17 @@ interface TangemPayApi {
|
|||
@Body request: GenerateNoneByCardWalletRequest,
|
||||
): ApiResponse<GenerateNonceResponse>
|
||||
|
||||
@POST("v1/auth/challenge")
|
||||
suspend fun generateNonceByCustomerWallet(
|
||||
@Body request: GenerateNonceByCustomerWalletRequest,
|
||||
): ApiResponse<GenerateNonceResponse>
|
||||
|
||||
@POST("v1/auth/token")
|
||||
suspend fun getAccessTokenByCardId(@Body request: GetAccessTokenByCardIdRequest): ApiResponse<JWTResponse>
|
||||
|
||||
@POST("v1/auth/token")
|
||||
suspend fun getTokenByCustomerWallet(@Body request: GetTokenByCustomerWalletRequest): ApiResponse<JWTResponse>
|
||||
|
||||
@POST("v1/auth/token")
|
||||
suspend fun getAccessTokenByCardWallet(@Body request: GetAccessTokenByCardWalletRequest): ApiResponse<JWTResponse>
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,10 @@
|
|||
package com.tangem.datasource.api.pay.models.request
|
||||
|
||||
import com.squareup.moshi.Json
|
||||
import com.squareup.moshi.JsonClass
|
||||
|
||||
@JsonClass(generateAdapter = true)
|
||||
data class GenerateNonceByCustomerWalletRequest(
|
||||
@Json(name = "auth_type") val authType: String = "customer_wallet",
|
||||
@Json(name = "customer_wallet_address") val customerWalletAddress: String,
|
||||
)
|
||||
|
|
@ -0,0 +1,12 @@
|
|||
package com.tangem.datasource.api.pay.models.request
|
||||
|
||||
import com.squareup.moshi.Json
|
||||
import com.squareup.moshi.JsonClass
|
||||
|
||||
@JsonClass(generateAdapter = true)
|
||||
data class GetTokenByCustomerWalletRequest(
|
||||
@Json(name = "auth_type") val authType: String = "customer_wallet",
|
||||
@Json(name = "session_id") val sessionId: String,
|
||||
@Json(name = "signature") val signature: String,
|
||||
@Json(name = "message_format") val messageFormat: String,
|
||||
)
|
||||
|
|
@ -5,7 +5,7 @@ import com.squareup.moshi.JsonClass
|
|||
import com.tangem.datasource.api.stakekit.models.request.ConstructTransactionRequestBody.GasArgs
|
||||
import com.tangem.datasource.api.stakekit.models.response.model.BalanceDTO
|
||||
import com.tangem.datasource.api.stakekit.models.response.model.TokenDTO
|
||||
import com.tangem.domain.staking.model.stakekit.action.StakingActionType
|
||||
import com.tangem.domain.models.staking.action.StakingActionType
|
||||
|
||||
@JsonClass(generateAdapter = true)
|
||||
data class PendingActionRequestBody(
|
||||
|
|
|
|||
|
|
@ -1,9 +1,11 @@
|
|||
package com.tangem.datasource.api.tangemTech
|
||||
|
||||
import com.tangem.datasource.api.common.response.ApiResponse
|
||||
import com.tangem.datasource.api.promotion.models.PromotionInfoResponse
|
||||
import com.tangem.datasource.api.promotion.models.StoryContentResponse
|
||||
import com.tangem.datasource.api.tangemTech.models.*
|
||||
import com.tangem.datasource.api.tangemTech.models.account.GetWalletAccountsResponse
|
||||
import com.tangem.datasource.api.tangemTech.models.account.GetWalletArchivedAccountsResponse
|
||||
import com.tangem.datasource.api.tangemTech.models.account.SaveWalletAccountsResponse
|
||||
import com.tangem.datasource.api.utils.ReadTimeout
|
||||
import com.tangem.datasource.local.config.providers.models.ProviderModel
|
||||
import retrofit2.http.*
|
||||
|
|
@ -30,9 +32,6 @@ interface TangemTechApi {
|
|||
@Query("limit") limit: Int? = null,
|
||||
): ApiResponse<CoinsResponse>
|
||||
|
||||
@GET("v1/rates")
|
||||
suspend fun getRates(@Query("currencyId") currencyId: String, @Query("coinIds") coinIds: String): RatesResponse
|
||||
|
||||
@GET("v1/currencies")
|
||||
suspend fun getCurrencyList(
|
||||
@Header("Cache-Control") cacheControl: String = "max-age=600",
|
||||
|
|
@ -68,46 +67,11 @@ interface TangemTechApi {
|
|||
@Query("fields") fields: String,
|
||||
): ApiResponse<QuotesResponse>
|
||||
|
||||
@GET("v1/promotion")
|
||||
suspend fun getPromotionInfo(
|
||||
@Query("programName") name: String,
|
||||
@Header("Cache-Control") cacheControl: String = "max-age=600",
|
||||
): ApiResponse<PromotionInfoResponse>
|
||||
|
||||
@GET("v1/settings/{wallet_id}")
|
||||
suspend fun getUserTokensSettings(@Path("wallet_id") walletId: String): ApiResponse<UserTokensSettingsResponse>
|
||||
|
||||
@PUT("v1/settings/{wallet_id}")
|
||||
suspend fun saveUserTokensSettings(
|
||||
@Path("wallet_id") walletId: String,
|
||||
@Body userTokensSettings: UserTokensSettingsResponse,
|
||||
): ApiResponse<Unit>
|
||||
|
||||
@POST("v1/user-network-account")
|
||||
suspend fun createUserNetworkAccount(
|
||||
@Body body: CreateUserNetworkAccountBody,
|
||||
): ApiResponse<CreateUserNetworkAccountResponse>
|
||||
|
||||
@POST("v1/account")
|
||||
suspend fun createUserTokensAccount(
|
||||
@Body body: CreateUserTokensAccountBody,
|
||||
): ApiResponse<UserTokensAccountResponse>
|
||||
|
||||
@PUT("v1/account/{account_id}")
|
||||
suspend fun updateUserTokensAccount(
|
||||
@Path("account_id") accountId: Int,
|
||||
@Body body: UpdateUserTokensAccountBody,
|
||||
): ApiResponse<UserTokensAccountResponse>
|
||||
|
||||
@PUT("v1/account/{account_id}/archive")
|
||||
suspend fun archiveUserTokensAccount(@Path("account_id") accountId: Int): ApiResponse<UserTokensAccountResponse>
|
||||
|
||||
@PUT("v1/account/{account_id}/unarchive")
|
||||
suspend fun restoreUserTokensAccount(@Path("account_id") accountId: Int): ApiResponse<UserTokensAccountResponse>
|
||||
|
||||
@GET("v1/features")
|
||||
suspend fun getFeatures(): ApiResponse<FeaturesResponse>
|
||||
|
||||
@ReadTimeout(duration = 5, unit = TimeUnit.SECONDS)
|
||||
@GET("v1/networks/providers")
|
||||
suspend fun getBlockchainProviders(): Map<String, List<ProviderModel>>
|
||||
|
|
@ -160,7 +124,7 @@ interface TangemTechApi {
|
|||
suspend fun setNotificationsEnabled(@Path("wallet_id") walletId: String, @Body body: WalletBody): ApiResponse<Unit>
|
||||
// endregion
|
||||
|
||||
// region wallets
|
||||
// region user-wallets
|
||||
@PATCH("v1/user-wallets/wallets/{wallet_id}")
|
||||
suspend fun updateWallet(@Path("wallet_id") walletId: String, @Body body: WalletBody): ApiResponse<Unit>
|
||||
|
||||
|
|
@ -176,4 +140,20 @@ interface TangemTechApi {
|
|||
@GET("v1/user-wallets/wallets/by-app/{app_id}")
|
||||
suspend fun getWallets(@Path("app_id") appId: String): ApiResponse<List<WalletResponse>>
|
||||
// endregion
|
||||
|
||||
// region account
|
||||
@GET("/v1/wallets/{walletId}/accounts")
|
||||
suspend fun getWalletAccounts(@Path("walletId") walletId: String): ApiResponse<GetWalletAccountsResponse>
|
||||
|
||||
@PUT("/v1/wallets/{walletId}/accounts")
|
||||
suspend fun saveWalletAccounts(
|
||||
@Path("walletId") walletId: String,
|
||||
@Header("If-Match") ifMatch: String,
|
||||
): ApiResponse<SaveWalletAccountsResponse>
|
||||
|
||||
@GET("/v1/wallets/{walletId}/accounts/archived")
|
||||
suspend fun getWalletArchivedAccounts(
|
||||
@Path("walletId") walletId: String,
|
||||
): ApiResponse<GetWalletArchivedAccountsResponse>
|
||||
// endregion
|
||||
}
|
||||
|
|
@ -1,17 +0,0 @@
|
|||
package com.tangem.datasource.api.tangemTech
|
||||
|
||||
import com.tangem.datasource.api.common.response.ApiResponse
|
||||
import com.tangem.datasource.api.tangemTech.models.v2.UserTokensResponseV2
|
||||
import retrofit2.http.*
|
||||
|
||||
interface TangemTechApiV2 {
|
||||
|
||||
@GET("user-tokens/{wallet_id}")
|
||||
suspend fun getUserTokens(@Path("wallet_id") walletId: String): ApiResponse<UserTokensResponseV2>
|
||||
|
||||
@PUT("user-tokens/{wallet_id}")
|
||||
suspend fun saveUserTokens(
|
||||
@Path("wallet_id") walletId: String,
|
||||
@Body userTokens: UserTokensResponseV2,
|
||||
): ApiResponse<Unit>
|
||||
}
|
||||
|
|
@ -16,6 +16,7 @@ data class UserTokensResponse(
|
|||
@JsonClass(generateAdapter = true)
|
||||
data class Token(
|
||||
@Json(name = "id") val id: String? = null,
|
||||
@Json(name = "accountId") val accountId: String? = null,
|
||||
@Json(name = "networkId") val networkId: String,
|
||||
@Json(name = "derivationPath") val derivationPath: String? = null,
|
||||
@Json(name = "name") val name: String,
|
||||
|
|
|
|||
|
|
@ -0,0 +1,23 @@
|
|||
package com.tangem.datasource.api.tangemTech.models.account
|
||||
|
||||
import com.squareup.moshi.Json
|
||||
import com.squareup.moshi.JsonClass
|
||||
import com.tangem.datasource.api.tangemTech.models.UserTokensResponse
|
||||
import com.tangem.datasource.api.tangemTech.models.UserTokensResponse.GroupType
|
||||
import com.tangem.datasource.api.tangemTech.models.UserTokensResponse.SortType
|
||||
|
||||
@JsonClass(generateAdapter = true)
|
||||
data class GetWalletAccountsResponse(
|
||||
@Json(name = "wallet") val wallet: Wallet,
|
||||
@Json(name = "accounts") val accounts: List<WalletAccountDTO>,
|
||||
@Json(name = "unassignedTokens") val unassignedTokens: List<UserTokensResponse.Token>,
|
||||
) {
|
||||
|
||||
@JsonClass(generateAdapter = true)
|
||||
data class Wallet(
|
||||
@Json(name = "version") val version: Int,
|
||||
@Json(name = "group") val group: GroupType,
|
||||
@Json(name = "sort") val sort: SortType,
|
||||
@Json(name = "totalAccounts") val totalAccounts: Int,
|
||||
)
|
||||
}
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
package com.tangem.datasource.api.tangemTech.models.account
|
||||
|
||||
import com.squareup.moshi.Json
|
||||
import com.squareup.moshi.JsonClass
|
||||
|
||||
@JsonClass(generateAdapter = true)
|
||||
data class GetWalletArchivedAccountsResponse(
|
||||
@Json(name = "archivedAccounts") val accounts: List<WalletAccountDTO>,
|
||||
)
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
package com.tangem.datasource.api.tangemTech.models.account
|
||||
|
||||
import com.squareup.moshi.Json
|
||||
import com.squareup.moshi.JsonClass
|
||||
|
||||
@JsonClass(generateAdapter = true)
|
||||
data class SaveWalletAccountsResponse(
|
||||
@Json(name = "accounts") val accounts: List<WalletAccountDTO>,
|
||||
)
|
||||
|
|
@ -0,0 +1,17 @@
|
|||
package com.tangem.datasource.api.tangemTech.models.account
|
||||
|
||||
import com.squareup.moshi.Json
|
||||
import com.squareup.moshi.JsonClass
|
||||
import com.tangem.datasource.api.tangemTech.models.UserTokensResponse
|
||||
|
||||
@JsonClass(generateAdapter = true)
|
||||
data class WalletAccountDTO(
|
||||
@Json(name = "id") val id: String,
|
||||
@Json(name = "name") val name: String,
|
||||
@Json(name = "derivation") val derivationIndex: Int,
|
||||
@Json(name = "icon") val icon: String,
|
||||
@Json(name = "iconColor") val iconColor: String,
|
||||
@Json(name = "tokens") val tokens: List<UserTokensResponse.Token>? = null,
|
||||
@Json(name = "totalTokens") val totalTokens: Int? = null,
|
||||
@Json(name = "totalNetworks") val totalNetworks: Int? = null,
|
||||
)
|
||||
|
|
@ -1,26 +0,0 @@
|
|||
package com.tangem.datasource.api.tangemTech.models.v2
|
||||
|
||||
import com.squareup.moshi.Json
|
||||
import com.squareup.moshi.JsonClass
|
||||
import com.tangem.datasource.api.tangemTech.models.UserTokensResponse
|
||||
|
||||
@JsonClass(generateAdapter = true)
|
||||
data class UserTokensResponseV2(
|
||||
@Json(name = "accounts")
|
||||
val accounts: List<TokensAccount>,
|
||||
) {
|
||||
|
||||
@JsonClass(generateAdapter = true)
|
||||
data class TokensAccount(
|
||||
@Json(name = "id")
|
||||
val id: Int,
|
||||
@Json(name = "title")
|
||||
val title: String,
|
||||
@Json(name = "tokens")
|
||||
val tokens: List<UserTokensResponse.Token>? = null,
|
||||
@Json(name = "tokensCount")
|
||||
val tokensCount: Int? = null,
|
||||
@Json(name = "archived")
|
||||
val isArchived: Boolean,
|
||||
)
|
||||
}
|
||||
|
|
@ -1,8 +1,5 @@
|
|||
package com.tangem.datasource.di
|
||||
|
||||
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
|
||||
|
|
@ -12,44 +9,29 @@ import com.tangem.datasource.api.common.config.managers.ApiConfigsManager
|
|||
import com.tangem.datasource.api.common.config.managers.DevApiConfigsManager
|
||||
import com.tangem.datasource.api.common.config.managers.MockApiConfigsManager
|
||||
import com.tangem.datasource.api.common.config.managers.ProdApiConfigsManager
|
||||
import com.tangem.datasource.api.common.response.ApiResponseCallAdapterFactory
|
||||
import com.tangem.datasource.api.express.TangemExpressApi
|
||||
import com.tangem.datasource.api.markets.TangemTechMarketsApi
|
||||
import com.tangem.datasource.api.onramp.OnrampApi
|
||||
import com.tangem.datasource.api.pay.TangemPayApi
|
||||
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.local.logs.AppLogsStore
|
||||
import com.tangem.datasource.di.utils.RetrofitApiBuilder
|
||||
import com.tangem.datasource.di.utils.RetrofitApiBuilder.Timeouts
|
||||
import com.tangem.datasource.local.preferences.AppPreferencesStore
|
||||
import com.tangem.datasource.utils.*
|
||||
import com.tangem.datasource.utils.RequestHeader.AppVersionPlatformHeaders
|
||||
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
||||
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.android.qualifiers.ApplicationContext
|
||||
import dagger.hilt.components.SingletonComponent
|
||||
import okhttp3.OkHttpClient
|
||||
import retrofit2.Retrofit
|
||||
import retrofit2.converter.moshi.MoshiConverterFactory
|
||||
import java.util.concurrent.TimeUnit
|
||||
import javax.inject.Singleton
|
||||
|
||||
@Module
|
||||
@InstallIn(SingletonComponent::class)
|
||||
internal object NetworkModule {
|
||||
|
||||
private const val PROD_V2_TANGEM_TECH_BASE_URL = "https://api.tangem-tech.com/v2/"
|
||||
private const val TANGEM_TECH_MARKETS_SERVICE_TIMEOUT_SECONDS = 60L
|
||||
private const val STAKE_KIT_API_TIMEOUT_SECONDS = 60L
|
||||
|
||||
private val excludedApiForLogging: Set<ApiConfig.ID> = setOf(
|
||||
ApiConfig.ID.StakeKit,
|
||||
)
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideApiConfigManager(
|
||||
|
|
@ -66,286 +48,76 @@ internal object NetworkModule {
|
|||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideExpressApi(
|
||||
@NetworkMoshi moshi: Moshi,
|
||||
@ApplicationContext context: Context,
|
||||
analyticsErrorHandler: AnalyticsErrorHandler,
|
||||
apiConfigsManager: ApiConfigsManager,
|
||||
appLogsStore: AppLogsStore,
|
||||
): TangemExpressApi {
|
||||
return createApi(
|
||||
id = ApiConfig.ID.Express,
|
||||
moshi = moshi,
|
||||
context = context,
|
||||
apiConfigsManager = apiConfigsManager,
|
||||
analyticsErrorHandler = analyticsErrorHandler,
|
||||
clientBuilder = {
|
||||
addInterceptor(
|
||||
NetworkLogsSaveInterceptor(appLogsStore),
|
||||
)
|
||||
},
|
||||
fun provideExpressApi(retrofitApiBuilder: RetrofitApiBuilder): TangemExpressApi {
|
||||
return retrofitApiBuilder.build(
|
||||
apiConfigId = ApiConfig.ID.Express,
|
||||
applyTimeoutAnnotations = false,
|
||||
)
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideStakeKitApi(
|
||||
@NetworkMoshi moshi: Moshi,
|
||||
@ApplicationContext context: Context,
|
||||
apiConfigsManager: ApiConfigsManager,
|
||||
analyticsErrorHandler: AnalyticsErrorHandler,
|
||||
appLogsStore: AppLogsStore,
|
||||
): StakeKitApi {
|
||||
return createApi(
|
||||
id = ApiConfig.ID.StakeKit,
|
||||
moshi = moshi,
|
||||
context = context,
|
||||
apiConfigsManager = apiConfigsManager,
|
||||
analyticsErrorHandler = analyticsErrorHandler,
|
||||
fun provideStakeKitApi(retrofitApiBuilder: RetrofitApiBuilder): StakeKitApi {
|
||||
return retrofitApiBuilder.build(
|
||||
apiConfigId = ApiConfig.ID.StakeKit,
|
||||
applyTimeoutAnnotations = false,
|
||||
timeouts = Timeouts(
|
||||
callTimeoutSeconds = STAKE_KIT_API_TIMEOUT_SECONDS,
|
||||
connectTimeoutSeconds = STAKE_KIT_API_TIMEOUT_SECONDS,
|
||||
readTimeoutSeconds = STAKE_KIT_API_TIMEOUT_SECONDS,
|
||||
writeTimeoutSeconds = STAKE_KIT_API_TIMEOUT_SECONDS,
|
||||
),
|
||||
clientBuilder = {
|
||||
addInterceptor(
|
||||
NetworkLogsSaveInterceptor(appLogsStore),
|
||||
)
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideOnrampApi(
|
||||
@NetworkMoshi moshi: Moshi,
|
||||
@ApplicationContext context: Context,
|
||||
analyticsErrorHandler: AnalyticsErrorHandler,
|
||||
apiConfigsManager: ApiConfigsManager,
|
||||
appLogsStore: AppLogsStore,
|
||||
): OnrampApi {
|
||||
return createApi(
|
||||
id = ApiConfig.ID.Express,
|
||||
moshi = moshi,
|
||||
context = context,
|
||||
apiConfigsManager = apiConfigsManager,
|
||||
analyticsErrorHandler = analyticsErrorHandler,
|
||||
clientBuilder = {
|
||||
addInterceptor(
|
||||
NetworkLogsSaveInterceptor(appLogsStore),
|
||||
)
|
||||
},
|
||||
fun provideOnrampApi(retrofitApiBuilder: RetrofitApiBuilder): OnrampApi {
|
||||
return retrofitApiBuilder.build(
|
||||
apiConfigId = ApiConfig.ID.Express,
|
||||
applyTimeoutAnnotations = false,
|
||||
)
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideTangemTechApi(
|
||||
@NetworkMoshi moshi: Moshi,
|
||||
@ApplicationContext context: Context,
|
||||
analyticsErrorHandler: AnalyticsErrorHandler,
|
||||
apiConfigsManager: ApiConfigsManager,
|
||||
): TangemTechApi {
|
||||
return createApi(
|
||||
id = ApiConfig.ID.TangemTech,
|
||||
moshi = moshi,
|
||||
context = context,
|
||||
apiConfigsManager = apiConfigsManager,
|
||||
analyticsErrorHandler = analyticsErrorHandler,
|
||||
clientBuilder = { applyTimeoutAnnotations() },
|
||||
)
|
||||
}
|
||||
|
||||
// TODO: It will be deleted in the future or refactored using ApiConfig
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideTangemTechApiV2(
|
||||
@NetworkMoshi moshi: Moshi,
|
||||
@ApplicationContext context: Context,
|
||||
analyticsErrorHandler: AnalyticsErrorHandler,
|
||||
appVersionProvider: AppVersionProvider,
|
||||
appInfoProvider: AppInfoProvider,
|
||||
): TangemTechApiV2 {
|
||||
return provideTangemTechApiInternal(
|
||||
moshi = moshi,
|
||||
context = context,
|
||||
appVersionProvider = appVersionProvider,
|
||||
baseUrl = PROD_V2_TANGEM_TECH_BASE_URL,
|
||||
analyticsErrorHandler = analyticsErrorHandler,
|
||||
appInfoProvider = appInfoProvider,
|
||||
fun provideTangemTechApi(retrofitApiBuilder: RetrofitApiBuilder): TangemTechApi {
|
||||
return retrofitApiBuilder.build(
|
||||
apiConfigId = ApiConfig.ID.TangemTech,
|
||||
applyTimeoutAnnotations = true,
|
||||
)
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideTangemTechMarketsApi(
|
||||
@NetworkMoshi moshi: Moshi,
|
||||
@ApplicationContext context: Context,
|
||||
analyticsErrorHandler: AnalyticsErrorHandler,
|
||||
apiConfigsManager: ApiConfigsManager,
|
||||
): TangemTechMarketsApi {
|
||||
return createApi(
|
||||
id = ApiConfig.ID.TangemTech,
|
||||
moshi = moshi,
|
||||
context = context,
|
||||
apiConfigsManager = apiConfigsManager,
|
||||
analyticsErrorHandler = analyticsErrorHandler,
|
||||
clientBuilder = {
|
||||
this.callTimeout(TANGEM_TECH_MARKETS_SERVICE_TIMEOUT_SECONDS, TimeUnit.SECONDS)
|
||||
.connectTimeout(TANGEM_TECH_MARKETS_SERVICE_TIMEOUT_SECONDS, TimeUnit.SECONDS)
|
||||
.readTimeout(TANGEM_TECH_MARKETS_SERVICE_TIMEOUT_SECONDS, TimeUnit.SECONDS)
|
||||
.applyTimeoutAnnotations()
|
||||
},
|
||||
fun provideTangemTechMarketsApi(retrofitApiBuilder: RetrofitApiBuilder): TangemTechMarketsApi {
|
||||
return retrofitApiBuilder.build(
|
||||
apiConfigId = ApiConfig.ID.TangemTech,
|
||||
applyTimeoutAnnotations = false,
|
||||
timeouts = Timeouts(
|
||||
callTimeoutSeconds = TANGEM_TECH_MARKETS_SERVICE_TIMEOUT_SECONDS,
|
||||
connectTimeoutSeconds = TANGEM_TECH_MARKETS_SERVICE_TIMEOUT_SECONDS,
|
||||
readTimeoutSeconds = TANGEM_TECH_MARKETS_SERVICE_TIMEOUT_SECONDS,
|
||||
),
|
||||
logsSaving = false,
|
||||
)
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideTangemVisaApi(
|
||||
@NetworkMoshi moshi: Moshi,
|
||||
@ApplicationContext context: Context,
|
||||
analyticsErrorHandler: AnalyticsErrorHandler,
|
||||
apiConfigsManager: ApiConfigsManager,
|
||||
appLogsStore: AppLogsStore,
|
||||
): TangemPayApi {
|
||||
return createApi<TangemPayApi>(
|
||||
id = ApiConfig.ID.TangemPay,
|
||||
moshi = moshi,
|
||||
context = context,
|
||||
apiConfigsManager = apiConfigsManager,
|
||||
analyticsErrorHandler = analyticsErrorHandler,
|
||||
clientBuilder = {
|
||||
addInterceptor(
|
||||
NetworkLogsSaveInterceptor(appLogsStore),
|
||||
).applyTimeoutAnnotations()
|
||||
},
|
||||
fun provideTangemVisaApi(retrofitApiBuilder: RetrofitApiBuilder): TangemPayApi {
|
||||
return retrofitApiBuilder.build(
|
||||
apiConfigId = ApiConfig.ID.TangemPay,
|
||||
applyTimeoutAnnotations = false,
|
||||
)
|
||||
}
|
||||
|
||||
@Suppress("LongParameterList")
|
||||
@Deprecated("use createApi instead")
|
||||
private inline fun <reified T> provideTangemTechApiInternal(
|
||||
moshi: Moshi,
|
||||
context: Context,
|
||||
appVersionProvider: AppVersionProvider,
|
||||
appInfoProvider: AppInfoProvider,
|
||||
baseUrl: String,
|
||||
analyticsErrorHandler: AnalyticsErrorHandler,
|
||||
timeouts: Timeouts = Timeouts(),
|
||||
requestHeaders: List<RequestHeader> = listOf(AppVersionPlatformHeaders(appVersionProvider, appInfoProvider)),
|
||||
): T {
|
||||
val client = OkHttpClient.Builder()
|
||||
.applyTimeoutAnnotations()
|
||||
.let { builder ->
|
||||
var b = builder
|
||||
if (timeouts.callTimeoutSeconds != null) {
|
||||
b = b.callTimeout(timeouts.callTimeoutSeconds, TimeUnit.SECONDS)
|
||||
}
|
||||
if (timeouts.connectTimeoutSeconds != null) {
|
||||
b = b.connectTimeout(timeouts.connectTimeoutSeconds, TimeUnit.SECONDS)
|
||||
}
|
||||
if (timeouts.readTimeoutSeconds != null) {
|
||||
b = b.readTimeout(timeouts.readTimeoutSeconds, TimeUnit.SECONDS)
|
||||
}
|
||||
if (timeouts.writeTimeoutSeconds != null) {
|
||||
b = b.writeTimeout(timeouts.writeTimeoutSeconds, TimeUnit.SECONDS)
|
||||
}
|
||||
b
|
||||
}
|
||||
.addHeaders(
|
||||
*requestHeaders.toTypedArray(),
|
||||
// TODO("refactor header init") get auth data after biometric auth to avoid race condition
|
||||
// AuthenticationHeader(authProvider),
|
||||
)
|
||||
.addLoggers(context)
|
||||
.build()
|
||||
|
||||
return Retrofit.Builder()
|
||||
.addConverterFactory(MoshiConverterFactory.create(moshi))
|
||||
.addCallAdapterFactory(ApiResponseCallAdapterFactory.create(analyticsErrorHandler))
|
||||
.baseUrl(baseUrl)
|
||||
.client(client)
|
||||
.build()
|
||||
.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()
|
||||
},
|
||||
fun provideBlockAidApi(retrofitApiBuilder: RetrofitApiBuilder): BlockAidApi {
|
||||
return retrofitApiBuilder.build(
|
||||
apiConfigId = ApiConfig.ID.BlockAid,
|
||||
applyTimeoutAnnotations = false,
|
||||
)
|
||||
}
|
||||
|
||||
private inline fun <reified T> createApi(
|
||||
id: ApiConfig.ID,
|
||||
moshi: Moshi,
|
||||
context: Context,
|
||||
apiConfigsManager: ApiConfigsManager,
|
||||
analyticsErrorHandler: AnalyticsErrorHandler,
|
||||
timeouts: Timeouts = Timeouts(),
|
||||
clientBuilder: OkHttpClient.Builder.() -> OkHttpClient.Builder = { this },
|
||||
): T {
|
||||
val environmentConfig = apiConfigsManager.getEnvironmentConfig(id)
|
||||
|
||||
return Retrofit.Builder()
|
||||
.addConverterFactory(MoshiConverterFactory.create(moshi))
|
||||
.addCallAdapterFactory(ApiResponseCallAdapterFactory.create(analyticsErrorHandler))
|
||||
.baseUrl(environmentConfig.baseUrl)
|
||||
.client(
|
||||
OkHttpClient.Builder()
|
||||
.applyApiConfig(id, apiConfigsManager)
|
||||
.applyTimeoutAnnotations()
|
||||
.let { builder ->
|
||||
var b = builder
|
||||
if (timeouts.callTimeoutSeconds != null) {
|
||||
b = b.callTimeout(timeouts.callTimeoutSeconds, TimeUnit.SECONDS)
|
||||
}
|
||||
if (timeouts.connectTimeoutSeconds != null) {
|
||||
b = b.connectTimeout(timeouts.connectTimeoutSeconds, TimeUnit.SECONDS)
|
||||
}
|
||||
if (timeouts.readTimeoutSeconds != null) {
|
||||
b = b.readTimeout(timeouts.readTimeoutSeconds, TimeUnit.SECONDS)
|
||||
}
|
||||
if (timeouts.writeTimeoutSeconds != null) {
|
||||
b = b.writeTimeout(timeouts.writeTimeoutSeconds, TimeUnit.SECONDS)
|
||||
}
|
||||
b
|
||||
}
|
||||
.addLoggers(context = context, id = id)
|
||||
.clientBuilder()
|
||||
.build(),
|
||||
)
|
||||
.build()
|
||||
.create(T::class.java)
|
||||
}
|
||||
|
||||
private fun OkHttpClient.Builder.addLoggers(context: Context, id: ApiConfig.ID): OkHttpClient.Builder {
|
||||
if (id in excludedApiForLogging) return this
|
||||
|
||||
return addLoggers(context)
|
||||
}
|
||||
|
||||
private data class Timeouts(
|
||||
val callTimeoutSeconds: Long? = null,
|
||||
val connectTimeoutSeconds: Long? = null,
|
||||
val readTimeoutSeconds: Long? = null,
|
||||
val writeTimeoutSeconds: Long? = null,
|
||||
)
|
||||
}
|
||||
|
|
@ -0,0 +1,34 @@
|
|||
package com.tangem.datasource.di
|
||||
|
||||
import com.tangem.datasource.local.datastore.RuntimeDataStore
|
||||
import com.tangem.datasource.local.datastore.RuntimeSharedStore
|
||||
import com.tangem.datasource.local.swap.DefaultSwapBestRateAnimationStore
|
||||
import com.tangem.datasource.local.swap.DefaultSwapTransactionStatusStore
|
||||
import com.tangem.datasource.local.swap.SwapBestRateAnimationStore
|
||||
import com.tangem.datasource.local.swap.SwapTransactionStatusStore
|
||||
import dagger.Module
|
||||
import dagger.Provides
|
||||
import dagger.hilt.InstallIn
|
||||
import dagger.hilt.components.SingletonComponent
|
||||
import javax.inject.Singleton
|
||||
|
||||
@Module
|
||||
@InstallIn(SingletonComponent::class)
|
||||
object SwapStoreModule {
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideSwapTransactionStatusStore(): SwapTransactionStatusStore {
|
||||
return DefaultSwapTransactionStatusStore(
|
||||
dataStore = RuntimeDataStore(),
|
||||
)
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideSwapBestRateAnimationStore(): SwapBestRateAnimationStore {
|
||||
return DefaultSwapBestRateAnimationStore(
|
||||
dataStore = RuntimeSharedStore(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -1,23 +0,0 @@
|
|||
package com.tangem.datasource.di
|
||||
|
||||
import com.tangem.datasource.local.datastore.RuntimeDataStore
|
||||
import com.tangem.datasource.local.swaptx.DefaultSwapTransactionStatusStore
|
||||
import com.tangem.datasource.local.swaptx.SwapTransactionStatusStore
|
||||
import dagger.Module
|
||||
import dagger.Provides
|
||||
import dagger.hilt.InstallIn
|
||||
import dagger.hilt.components.SingletonComponent
|
||||
import javax.inject.Singleton
|
||||
|
||||
@Module
|
||||
@InstallIn(SingletonComponent::class)
|
||||
object SwapTransactionStatusStoreModule {
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideSwapTransactionStatusStore(): SwapTransactionStatusStore {
|
||||
return DefaultSwapTransactionStatusStore(
|
||||
dataStore = RuntimeDataStore(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,201 @@
|
|||
package com.tangem.datasource.di.utils
|
||||
|
||||
import android.content.Context
|
||||
import com.chuckerteam.chucker.api.ChuckerInterceptor
|
||||
import com.squareup.moshi.Moshi
|
||||
import com.tangem.core.analytics.api.AnalyticsErrorHandler
|
||||
import com.tangem.datasource.BuildConfig
|
||||
import com.tangem.datasource.api.common.SwitchEnvironmentInterceptor
|
||||
import com.tangem.datasource.api.common.config.ApiConfig
|
||||
import com.tangem.datasource.api.common.config.ApiConfigs
|
||||
import com.tangem.datasource.api.common.config.ApiEnvironmentConfig
|
||||
import com.tangem.datasource.api.common.config.managers.ApiConfigsManager
|
||||
import com.tangem.datasource.api.common.createNetworkLoggingInterceptor
|
||||
import com.tangem.datasource.api.common.response.ApiResponseCallAdapterFactory
|
||||
import com.tangem.datasource.api.utils.ConnectTimeout
|
||||
import com.tangem.datasource.api.utils.ReadTimeout
|
||||
import com.tangem.datasource.api.utils.WriteTimeout
|
||||
import com.tangem.datasource.di.NetworkMoshi
|
||||
import com.tangem.datasource.local.logs.AppLogsStore
|
||||
import com.tangem.datasource.utils.NetworkLogsSaveInterceptor
|
||||
import com.tangem.datasource.utils.addHeaders
|
||||
import dagger.hilt.android.qualifiers.ApplicationContext
|
||||
import okhttp3.Interceptor
|
||||
import okhttp3.OkHttpClient
|
||||
import retrofit2.Invocation
|
||||
import retrofit2.Retrofit
|
||||
import retrofit2.converter.moshi.MoshiConverterFactory
|
||||
import java.util.concurrent.TimeUnit
|
||||
import javax.inject.Inject
|
||||
import javax.inject.Singleton
|
||||
|
||||
/**
|
||||
* A builder class for creating Retrofit API instances
|
||||
*
|
||||
* @property apiConfigsManager manages API configurations for different environments
|
||||
* @property moshi moshi
|
||||
* @property analyticsErrorHandler handles analytics-related errors
|
||||
* @property context application context
|
||||
* @property appLogsStore application logs store
|
||||
*
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
@Singleton
|
||||
internal class RetrofitApiBuilder @Inject constructor(
|
||||
private val apiConfigs: ApiConfigs,
|
||||
private val apiConfigsManager: ApiConfigsManager,
|
||||
@NetworkMoshi private val moshi: Moshi,
|
||||
private val analyticsErrorHandler: AnalyticsErrorHandler,
|
||||
@ApplicationContext private val context: Context,
|
||||
private val appLogsStore: AppLogsStore,
|
||||
) {
|
||||
|
||||
private val configsBaseUrls: Map<ApiConfig.ID, Set<String>> = getConfigsBaseUrls()
|
||||
|
||||
/**
|
||||
* Builds a Retrofit API instance for the specified API configuration ID
|
||||
*
|
||||
* @param apiConfigId the ID of the API configuration to use
|
||||
* @param applyTimeoutAnnotations whether to apply timeout annotations to the requests. See [ReadTimeout], etc.
|
||||
* @param timeouts optional timeouts for the requests
|
||||
* @param logsSaving whether to enable logs saving
|
||||
*
|
||||
* @return an instance [T] of the specified API interface
|
||||
*/
|
||||
inline fun <reified T> build(
|
||||
apiConfigId: ApiConfig.ID,
|
||||
applyTimeoutAnnotations: Boolean,
|
||||
timeouts: Timeouts? = null,
|
||||
logsSaving: Boolean = true,
|
||||
): T {
|
||||
val environmentConfig = apiConfigsManager.getEnvironmentConfig(apiConfigId)
|
||||
|
||||
return Retrofit.Builder()
|
||||
.addConverterFactory(MoshiConverterFactory.create(moshi))
|
||||
.addCallAdapterFactory(ApiResponseCallAdapterFactory.create(analyticsErrorHandler))
|
||||
.baseUrl(environmentConfig.baseUrl)
|
||||
.client(
|
||||
OkHttpClient.Builder()
|
||||
.applyApiConfig(apiConfigId = apiConfigId, environmentConfig = environmentConfig)
|
||||
.let {
|
||||
if (applyTimeoutAnnotations) it.applyTimeoutAnnotations() else it
|
||||
}
|
||||
.applyTimeouts(timeouts = timeouts)
|
||||
.let {
|
||||
if (logsSaving) it.applyLogsSaving() else it
|
||||
}
|
||||
.addLoggers(apiConfigId = apiConfigId, context = context)
|
||||
.build(),
|
||||
)
|
||||
.build()
|
||||
.create(T::class.java)
|
||||
}
|
||||
|
||||
data class Timeouts(
|
||||
val callTimeoutSeconds: Long? = null,
|
||||
val connectTimeoutSeconds: Long? = null,
|
||||
val readTimeoutSeconds: Long? = null,
|
||||
val writeTimeoutSeconds: Long? = null,
|
||||
)
|
||||
|
||||
private fun getConfigsBaseUrls(): Map<ApiConfig.ID, Set<String>> {
|
||||
return apiConfigs.associate { config ->
|
||||
val allBaseUrls = config.environmentConfigs.mapTo(hashSetOf(), ApiEnvironmentConfig::baseUrl)
|
||||
|
||||
config.id to allBaseUrls
|
||||
}
|
||||
}
|
||||
|
||||
private fun OkHttpClient.Builder.applyApiConfig(
|
||||
apiConfigId: ApiConfig.ID,
|
||||
environmentConfig: ApiEnvironmentConfig,
|
||||
): OkHttpClient.Builder {
|
||||
return if (BuildConfig.TESTER_MENU_ENABLED) {
|
||||
addInterceptor(
|
||||
interceptor = SwitchEnvironmentInterceptor(
|
||||
id = apiConfigId,
|
||||
baseUrls = configsBaseUrls[apiConfigId]
|
||||
?: error("Base URLs for ApiConfig with id [$apiConfigId] not found"),
|
||||
apiConfigsManager = apiConfigsManager,
|
||||
),
|
||||
)
|
||||
} else {
|
||||
val headers = environmentConfig.headers
|
||||
|
||||
this.addHeaders(headers)
|
||||
}
|
||||
}
|
||||
|
||||
private fun OkHttpClient.Builder.applyTimeouts(timeouts: Timeouts?): OkHttpClient.Builder {
|
||||
if (timeouts == null) return this
|
||||
|
||||
var b = this
|
||||
|
||||
if (timeouts.callTimeoutSeconds != null) {
|
||||
b = b.callTimeout(timeouts.callTimeoutSeconds, TimeUnit.SECONDS)
|
||||
}
|
||||
if (timeouts.connectTimeoutSeconds != null) {
|
||||
b = b.connectTimeout(timeouts.connectTimeoutSeconds, TimeUnit.SECONDS)
|
||||
}
|
||||
if (timeouts.readTimeoutSeconds != null) {
|
||||
b = b.readTimeout(timeouts.readTimeoutSeconds, TimeUnit.SECONDS)
|
||||
}
|
||||
if (timeouts.writeTimeoutSeconds != null) {
|
||||
b = b.writeTimeout(timeouts.writeTimeoutSeconds, TimeUnit.SECONDS)
|
||||
}
|
||||
|
||||
return b
|
||||
}
|
||||
|
||||
/**
|
||||
* Apply timeout annotations [Interceptor].
|
||||
* Add this [Interceptor] to [OkHttpClient] if use timeout annotations for retrofit requests.
|
||||
*/
|
||||
private fun OkHttpClient.Builder.applyTimeoutAnnotations(): OkHttpClient.Builder {
|
||||
return addInterceptor(
|
||||
Interceptor { chain ->
|
||||
val request = chain.request()
|
||||
val tag = request.tag(Invocation::class.java)
|
||||
val connectionTimeout = tag?.method()?.getAnnotation(ConnectTimeout::class.java)
|
||||
val readTimeout = tag?.method()?.getAnnotation(ReadTimeout::class.java)
|
||||
val writeTimeout = tag?.method()?.getAnnotation(WriteTimeout::class.java)
|
||||
|
||||
chain
|
||||
.run {
|
||||
connectionTimeout?.let { withConnectTimeout(timeout = it.duration, unit = it.unit) } ?: this
|
||||
}
|
||||
.run {
|
||||
readTimeout?.let { withReadTimeout(timeout = it.duration, unit = it.unit) } ?: this
|
||||
}
|
||||
.run {
|
||||
writeTimeout?.let { withWriteTimeout(timeout = it.duration, unit = it.unit) } ?: this
|
||||
}
|
||||
.proceed(request)
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
private fun OkHttpClient.Builder.applyLogsSaving(): OkHttpClient.Builder {
|
||||
return addInterceptor(
|
||||
interceptor = NetworkLogsSaveInterceptor(appLogsStore),
|
||||
)
|
||||
}
|
||||
|
||||
private fun OkHttpClient.Builder.addLoggers(apiConfigId: ApiConfig.ID, context: Context): OkHttpClient.Builder {
|
||||
if (apiConfigId in excludedApiForLogging) return this
|
||||
|
||||
return if (BuildConfig.LOG_ENABLED) {
|
||||
addInterceptor(interceptor = ChuckerInterceptor(context))
|
||||
addInterceptor(interceptor = createNetworkLoggingInterceptor())
|
||||
} else {
|
||||
this
|
||||
}
|
||||
}
|
||||
|
||||
private companion object {
|
||||
|
||||
val excludedApiForLogging: Set<ApiConfig.ID> = setOf(
|
||||
// ApiConfig.ID.StakeKit,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -84,6 +84,10 @@ object PreferencesKeys {
|
|||
|
||||
val SHOULD_SAVE_ACCESS_CODES_KEY by lazy { booleanPreferencesKey(name = "saveAccessCodes") }
|
||||
|
||||
val REQUIRE_ACCESS_CODE_KEY by lazy { booleanPreferencesKey(name = "requireAccessCode") }
|
||||
|
||||
val USE_BIOMETRIC_AUTHENTICATION_KEY by lazy { booleanPreferencesKey(name = "useBiometricAuthentication") }
|
||||
|
||||
val SHOULD_SHOW_MARKETS_TOOLTIP_KEY by lazy { booleanPreferencesKey(name = "shouldShowMarketsTooltip") }
|
||||
|
||||
val MARKETS_STAKING_NOTIFICATION_HIDE_CLICKED_KEY by lazy {
|
||||
|
|
@ -149,6 +153,8 @@ object PreferencesKeys {
|
|||
val TRON_NETWORK_FEE_NOTIFICATION_SHOW_COUNT_KEY by lazy {
|
||||
intPreferencesKey(name = "tronNetworkFeeNotificationShowCount")
|
||||
}
|
||||
|
||||
fun getShouldShowNotificationKey(key: String) = booleanPreferencesKey("showShowNotificationUM_$key")
|
||||
// endregion
|
||||
|
||||
// region Promo
|
||||
|
|
@ -163,6 +169,18 @@ object PreferencesKeys {
|
|||
fun getShouldShowInitialPermissionScreen(permission: String) =
|
||||
booleanPreferencesKey("shouldShowInitialPushPermissionScreen_$permission")
|
||||
// endregion
|
||||
|
||||
// region Hot Wallet unlock attempts
|
||||
|
||||
fun getHotWalletUnlockAttemptsKey(attemptId: String) =
|
||||
intPreferencesKey(name = "hotWalletUnlockAttempts_$attemptId")
|
||||
|
||||
fun getHotWalletUnlockBootKey(attemptId: String) = intPreferencesKey(name = "hotWalletUnlockBootCount_$attemptId")
|
||||
|
||||
fun getHotWalletUnlockDeadlineKey(attemptId: String) =
|
||||
longPreferencesKey(name = "hotWalletUnlockDeadline_$attemptId")
|
||||
|
||||
// endregion
|
||||
}
|
||||
|
||||
/** Preferences keys set that should be migrated from "PreferencesDataSource" to a new DataStore<Preferences> */
|
||||
|
|
|
|||
|
|
@ -0,0 +1,21 @@
|
|||
package com.tangem.datasource.local.swap
|
||||
|
||||
import com.tangem.datasource.local.datastore.RuntimeSharedStore
|
||||
|
||||
internal class DefaultSwapBestRateAnimationStore(
|
||||
private val dataStore: RuntimeSharedStore<Boolean>,
|
||||
) : SwapBestRateAnimationStore, RuntimeSharedStore<Boolean> by dataStore {
|
||||
/**
|
||||
* Returns flag indicating whether should show best rate animation in current session.
|
||||
* Animation should appear once per session
|
||||
*
|
||||
* If true, reset flag to false
|
||||
*/
|
||||
override suspend fun getSyncOrNull(): Boolean {
|
||||
val value = dataStore.getSyncOrNull() ?: true
|
||||
if (value) {
|
||||
dataStore.store(false)
|
||||
}
|
||||
return value
|
||||
}
|
||||
}
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
package com.tangem.datasource.local.swaptx
|
||||
package com.tangem.datasource.local.swap
|
||||
|
||||
import com.tangem.datasource.local.datastore.core.StringKeyDataStore
|
||||
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
package com.tangem.datasource.local.swap
|
||||
|
||||
/**
|
||||
* Stores flag indicating whether should show best rate animation in current session.
|
||||
* Animation should appear once per session
|
||||
*
|
||||
* If true, reset flag to false
|
||||
*/
|
||||
interface SwapBestRateAnimationStore {
|
||||
suspend fun getSyncOrNull(): Boolean
|
||||
}
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
package com.tangem.datasource.local.swaptx
|
||||
package com.tangem.datasource.local.swap
|
||||
|
||||
/**
|
||||
* Runtime cache for storing swap transactions statuses sent to analytics
|
||||
|
|
@ -1,7 +1,7 @@
|
|||
package com.tangem.datasource.local.token.converter
|
||||
|
||||
import com.tangem.datasource.api.stakekit.models.response.model.BalanceDTO.BalanceTypeDTO
|
||||
import com.tangem.domain.staking.model.stakekit.BalanceType
|
||||
import com.tangem.domain.models.staking.BalanceType
|
||||
import com.tangem.utils.converter.Converter
|
||||
|
||||
internal object BalanceTypeConverter : Converter<BalanceTypeDTO, BalanceType> {
|
||||
|
|
|
|||
|
|
@ -1,8 +1,8 @@
|
|||
package com.tangem.datasource.local.token.converter
|
||||
|
||||
import com.tangem.datasource.api.stakekit.models.response.model.BalanceDTO
|
||||
import com.tangem.domain.staking.model.stakekit.PendingAction
|
||||
import com.tangem.domain.staking.model.stakekit.PendingActionConstraints
|
||||
import com.tangem.domain.models.staking.PendingAction
|
||||
import com.tangem.domain.models.staking.PendingActionConstraints
|
||||
import com.tangem.utils.converter.Converter
|
||||
|
||||
internal object PendingActionConstraintsConverter :
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
package com.tangem.datasource.local.token.converter
|
||||
|
||||
import com.tangem.datasource.api.stakekit.models.response.model.BalanceDTO
|
||||
import com.tangem.domain.staking.model.stakekit.PendingAction
|
||||
import com.tangem.domain.models.staking.PendingAction
|
||||
import com.tangem.utils.converter.Converter
|
||||
|
||||
internal object PendingActionConverter : Converter<BalanceDTO.PendingAction, PendingAction> {
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
package com.tangem.datasource.local.token.converter
|
||||
|
||||
import com.tangem.datasource.api.stakekit.models.response.model.action.StakingActionTypeDTO
|
||||
import com.tangem.domain.staking.model.stakekit.action.StakingActionType
|
||||
import com.tangem.domain.models.staking.action.StakingActionType
|
||||
import com.tangem.utils.converter.Converter
|
||||
|
||||
@Suppress("CyclomaticComplexMethod")
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
package com.tangem.datasource.local.token.converter
|
||||
|
||||
import com.tangem.datasource.api.stakekit.models.response.model.NetworkTypeDTO
|
||||
import com.tangem.domain.staking.model.stakekit.NetworkType
|
||||
import com.tangem.domain.models.staking.NetworkType
|
||||
import com.tangem.utils.converter.TwoWayConverter
|
||||
|
||||
@Suppress("CyclomaticComplexMethod", "LongMethod")
|
||||
|
|
|
|||
|
|
@ -1,53 +1,61 @@
|
|||
package com.tangem.datasource.local.token.converter
|
||||
|
||||
import com.tangem.datasource.api.stakekit.models.response.model.BalanceDTO
|
||||
import com.tangem.datasource.api.stakekit.models.response.model.YieldBalanceWrapperDTO
|
||||
import com.tangem.domain.models.StatusSource
|
||||
import com.tangem.domain.staking.model.stakekit.BalanceItem
|
||||
import com.tangem.domain.staking.model.stakekit.YieldBalance
|
||||
import com.tangem.domain.staking.model.stakekit.YieldBalanceItem
|
||||
import com.tangem.domain.models.staking.BalanceItem
|
||||
import com.tangem.domain.models.staking.StakingID
|
||||
import com.tangem.domain.models.staking.YieldBalance
|
||||
import com.tangem.domain.models.staking.YieldBalanceItem
|
||||
import com.tangem.utils.converter.Converter
|
||||
import kotlinx.datetime.Instant
|
||||
|
||||
class YieldBalanceConverter(
|
||||
private val source: StatusSource,
|
||||
) : Converter<YieldBalanceWrapperDTO, YieldBalance> {
|
||||
) : Converter<YieldBalanceWrapperDTO, YieldBalance?> {
|
||||
|
||||
constructor(isCached: Boolean) : this(source = if (isCached) StatusSource.CACHE else StatusSource.ACTUAL)
|
||||
|
||||
override fun convert(value: YieldBalanceWrapperDTO): YieldBalance {
|
||||
override fun convert(value: YieldBalanceWrapperDTO): YieldBalance? {
|
||||
val stakingId = StakingID(
|
||||
integrationId = value.integrationId ?: return null,
|
||||
address = value.addresses.address,
|
||||
)
|
||||
|
||||
return if (value.balances.isEmpty()) {
|
||||
YieldBalance.Empty(
|
||||
integrationId = value.integrationId,
|
||||
address = value.addresses.address,
|
||||
source = source,
|
||||
)
|
||||
YieldBalance.Empty(stakingId = stakingId, source = source)
|
||||
} else {
|
||||
YieldBalance.Data(
|
||||
integrationId = value.integrationId,
|
||||
address = value.addresses.address,
|
||||
stakingId = stakingId,
|
||||
balance = YieldBalanceItem(
|
||||
items = value.balances.map { item ->
|
||||
BalanceItem(
|
||||
groupId = item.groupId,
|
||||
token = TokenConverter.convert(item.tokenDTO),
|
||||
type = BalanceTypeConverter.convert(item.type),
|
||||
amount = item.amount,
|
||||
rawCurrencyId = item.tokenDTO.coinGeckoId,
|
||||
// tron-specific. operates validatorAddresses instead of validatorAddress
|
||||
validatorAddress = item.validatorAddress ?: item.validatorAddresses?.get(0),
|
||||
date = item.date?.toDateTime(),
|
||||
pendingActions = PendingActionConverter
|
||||
.convertList(item.pendingActions)
|
||||
.sortedBy { it.passthrough },
|
||||
pendingActionsConstraints = PendingActionConstraintsConverter
|
||||
.convertList(item.pendingActionConstraints.orEmpty()),
|
||||
isPending = false,
|
||||
)
|
||||
}
|
||||
.sortedWith(compareBy({ it.type }, { it.amount })),
|
||||
items = value.balances
|
||||
.map { item -> item.toBalanceItem() }
|
||||
.sortedWith(comparator = compareBy({ it.type }, { it.amount })),
|
||||
integrationId = value.integrationId,
|
||||
),
|
||||
source = source,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun BalanceDTO.toBalanceItem(): BalanceItem {
|
||||
val item = this
|
||||
|
||||
return BalanceItem(
|
||||
groupId = item.groupId,
|
||||
token = YieldTokenConverter.convert(item.tokenDTO),
|
||||
type = BalanceTypeConverter.convert(item.type),
|
||||
amount = item.amount,
|
||||
rawCurrencyId = item.tokenDTO.coinGeckoId,
|
||||
// tron-specific. operates validatorAddresses instead of validatorAddress
|
||||
validatorAddress = item.validatorAddress ?: item.validatorAddresses?.get(0),
|
||||
date = item.date?.toString()?.let { Instant.parse(it) },
|
||||
pendingActions = PendingActionConverter
|
||||
.convertList(item.pendingActions)
|
||||
.sortedBy { it.passthrough },
|
||||
pendingActionsConstraints = PendingActionConstraintsConverter
|
||||
.convertList(item.pendingActionConstraints.orEmpty()),
|
||||
isPending = false,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -1,13 +1,13 @@
|
|||
package com.tangem.datasource.local.token.converter
|
||||
|
||||
import com.tangem.datasource.api.stakekit.models.response.model.TokenDTO
|
||||
import com.tangem.domain.staking.model.stakekit.Token
|
||||
import com.tangem.domain.models.staking.YieldToken
|
||||
import com.tangem.utils.converter.TwoWayConverter
|
||||
|
||||
object TokenConverter : TwoWayConverter<TokenDTO, Token> {
|
||||
object YieldTokenConverter : TwoWayConverter<TokenDTO, YieldToken> {
|
||||
|
||||
override fun convert(value: TokenDTO): Token {
|
||||
return Token(
|
||||
override fun convert(value: TokenDTO): YieldToken {
|
||||
return YieldToken(
|
||||
name = value.name,
|
||||
network = StakingNetworkTypeConverter.convert(value.network),
|
||||
symbol = value.symbol,
|
||||
|
|
@ -19,7 +19,7 @@ object TokenConverter : TwoWayConverter<TokenDTO, Token> {
|
|||
)
|
||||
}
|
||||
|
||||
override fun convertBack(value: Token): TokenDTO {
|
||||
override fun convertBack(value: YieldToken): TokenDTO {
|
||||
return TokenDTO(
|
||||
name = value.name,
|
||||
network = StakingNetworkTypeConverter.convertBack(value.network),
|
||||
|
|
@ -5,6 +5,10 @@ import com.tangem.domain.models.wallet.UserWallet
|
|||
import com.tangem.domain.models.wallet.UserWalletId
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
|
||||
@Deprecated(
|
||||
message = "Use UserWalletsListRepository instead",
|
||||
replaceWith = ReplaceWith("UserWalletsListRepository"),
|
||||
)
|
||||
interface UserWalletsStore {
|
||||
|
||||
val selectedUserWalletOrNull: UserWallet?
|
||||
|
|
@ -15,8 +19,6 @@ interface UserWalletsStore {
|
|||
|
||||
fun getSyncStrict(key: UserWalletId): UserWallet
|
||||
|
||||
suspend fun getAllSyncOrNull(): List<UserWallet>?
|
||||
|
||||
suspend fun update(
|
||||
userWalletId: UserWalletId,
|
||||
update: suspend (UserWallet) -> UserWallet,
|
||||
|
|
|
|||
|
|
@ -1,21 +1,9 @@
|
|||
package com.tangem.datasource.utils
|
||||
|
||||
import android.content.Context
|
||||
import com.chuckerteam.chucker.api.ChuckerInterceptor
|
||||
import com.tangem.datasource.BuildConfig
|
||||
import com.tangem.datasource.api.common.SwitchEnvironmentInterceptor
|
||||
import com.tangem.datasource.api.common.config.ApiConfig
|
||||
import com.tangem.datasource.api.common.config.ApiConfig.Companion.MOCKED_BUILD_TYPE
|
||||
import com.tangem.datasource.api.common.config.managers.ApiConfigsManager
|
||||
import com.tangem.datasource.api.common.createNetworkLoggingInterceptor
|
||||
import com.tangem.datasource.api.utils.ConnectTimeout
|
||||
import com.tangem.datasource.api.utils.ReadTimeout
|
||||
import com.tangem.datasource.api.utils.WriteTimeout
|
||||
import com.tangem.utils.ProviderSuspend
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import okhttp3.Interceptor
|
||||
import okhttp3.OkHttpClient
|
||||
import retrofit2.Invocation
|
||||
|
||||
/** Extension for adding headers [requestHeaders] to every [OkHttpClient] request */
|
||||
internal fun OkHttpClient.Builder.addHeaders(vararg requestHeaders: RequestHeader): OkHttpClient.Builder {
|
||||
|
|
@ -24,30 +12,6 @@ internal fun OkHttpClient.Builder.addHeaders(vararg requestHeaders: RequestHeade
|
|||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Apply timeout annotations [Interceptor].
|
||||
* Add this [Interceptor] to [OkHttpClient] if use timeout annotations for retrofit requests.
|
||||
*/
|
||||
internal fun OkHttpClient.Builder.applyTimeoutAnnotations(): OkHttpClient.Builder {
|
||||
return addInterceptor(
|
||||
Interceptor { chain ->
|
||||
val request = chain.request()
|
||||
val tag = request.tag(Invocation::class.java)
|
||||
val connectionTimeout = tag?.method()?.getAnnotation(ConnectTimeout::class.java)
|
||||
val readTimeout = tag?.method()?.getAnnotation(ReadTimeout::class.java)
|
||||
val writeTimeout = tag?.method()?.getAnnotation(WriteTimeout::class.java)
|
||||
|
||||
chain.run {
|
||||
connectionTimeout?.let { withConnectTimeout(timeout = it.duration, unit = it.unit) } ?: this
|
||||
}.run {
|
||||
readTimeout?.let { withReadTimeout(timeout = it.duration, unit = it.unit) } ?: this
|
||||
}.run {
|
||||
writeTimeout?.let { withWriteTimeout(timeout = it.duration, unit = it.unit) } ?: this
|
||||
}.proceed(request)
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
/** Extension for adding headers [requestHeaders] to every [OkHttpClient] request */
|
||||
internal fun OkHttpClient.Builder.addHeaders(
|
||||
requestHeaders: Map<String, ProviderSuspend<String>>,
|
||||
|
|
@ -66,44 +30,4 @@ internal fun OkHttpClient.Builder.addHeaders(
|
|||
chain.proceed(request)
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Extension for logging each [OkHttpClient] request
|
||||
*
|
||||
* @param context context
|
||||
*/
|
||||
internal fun OkHttpClient.Builder.addLoggers(context: Context? = null): OkHttpClient.Builder {
|
||||
return if (BuildConfig.LOG_ENABLED) {
|
||||
context?.let {
|
||||
addInterceptor(interceptor = ChuckerInterceptor(it))
|
||||
}
|
||||
addInterceptor(interceptor = createNetworkLoggingInterceptor())
|
||||
} else {
|
||||
this
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Apply api config
|
||||
*
|
||||
* @param id class of [ApiConfig]
|
||||
* @param apiConfigsManager api configs manager
|
||||
*/
|
||||
internal fun OkHttpClient.Builder.applyApiConfig(
|
||||
id: ApiConfig.ID,
|
||||
apiConfigsManager: ApiConfigsManager,
|
||||
): OkHttpClient.Builder {
|
||||
return if (BuildConfig.TESTER_MENU_ENABLED || BuildConfig.BUILD_TYPE == MOCKED_BUILD_TYPE) {
|
||||
addInterceptor(
|
||||
interceptor = SwitchEnvironmentInterceptor(
|
||||
id = id,
|
||||
apiConfigsManager = apiConfigsManager,
|
||||
),
|
||||
)
|
||||
} else {
|
||||
val headers = apiConfigsManager.getEnvironmentConfig(id).headers
|
||||
|
||||
this.addHeaders(headers)
|
||||
}
|
||||
}
|
||||
|
|
@ -18,6 +18,7 @@ import com.tangem.utils.ProviderSuspend
|
|||
import com.tangem.utils.info.AppInfoProvider
|
||||
import com.tangem.utils.version.AppVersionProvider
|
||||
import io.mockk.clearMocks
|
||||
import io.mockk.coEvery
|
||||
import io.mockk.every
|
||||
import io.mockk.mockk
|
||||
import kotlinx.coroutines.runBlocking
|
||||
|
|
@ -55,8 +56,8 @@ internal class ProdApiConfigsManagerTest {
|
|||
every { appVersionProvider.versionName } returns VERSION_NAME
|
||||
every { expressAuthProvider.getSessionId() } returns EXPRESS_SESSION_ID
|
||||
every { stakeKitAuthProvider.getApiKey() } returns STAKE_KIT_API_KEY
|
||||
every { appAuthProvider.getCardId() } returns APP_CARD_ID
|
||||
every { appAuthProvider.getCardPublicKey() } returns APP_CARD_PUBLIC_KEY
|
||||
coEvery { appAuthProvider.getCardId() } returns APP_CARD_ID
|
||||
coEvery { appAuthProvider.getCardPublicKey() } returns APP_CARD_PUBLIC_KEY
|
||||
every { appInfoProvider.osVersion } returns "Android 16"
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue