Updated on 2026-08-14
This commit is contained in:
commit
2f62d482ca
635 changed files with 18802 additions and 9118 deletions
|
|
@ -7,34 +7,51 @@ package com.tangem.datasource.api.common.response
|
|||
*/
|
||||
sealed class ApiResponse<T : Any> {
|
||||
|
||||
/**
|
||||
* Represents a successful response from the API.
|
||||
*
|
||||
* @property data The data returned by the API.
|
||||
*/
|
||||
data class Success<T : Any>(val data: T) : ApiResponse<T>()
|
||||
/** Map of headers (header name with list of values) */
|
||||
abstract val headers: Map<String, List<String>>
|
||||
|
||||
/**
|
||||
* Represents an error response or failure from the API.
|
||||
* Represents a successful response from the API
|
||||
*
|
||||
* @property cause The cause of the error.
|
||||
* @property data the data returned by the API
|
||||
* @property headers the headers returned by the API
|
||||
*/
|
||||
data class Error(val cause: ApiResponseError) : ApiResponse<Nothing>()
|
||||
data class Success<T : Any>(
|
||||
val data: T,
|
||||
override val headers: Map<String, List<String>> = emptyMap(),
|
||||
) : ApiResponse<T>()
|
||||
|
||||
/**
|
||||
* Represents an error response or failure from the API
|
||||
*
|
||||
* @property cause the cause of the error
|
||||
* @property headers the headers returned by the API
|
||||
*/
|
||||
data class Error(
|
||||
val cause: ApiResponseError,
|
||||
override val headers: Map<String, List<String>> = emptyMap(),
|
||||
) : ApiResponse<Nothing>()
|
||||
}
|
||||
|
||||
/**
|
||||
* Wraps data in a [ApiResponse.Success] instance.
|
||||
* Wraps data in a [ApiResponse.Success] instance
|
||||
*
|
||||
* @param data The data to wrap.
|
||||
* @return A [ApiResponse.Success] instance containing the provided data.
|
||||
* @param data the data to wrap
|
||||
* @param headers the headers returned by the API
|
||||
* @return a [ApiResponse.Success] instance containing the provided data
|
||||
*/
|
||||
internal fun <T : Any> apiSuccess(data: T): ApiResponse<T> = ApiResponse.Success(data)
|
||||
internal fun <T : Any> apiSuccess(data: T, headers: Map<String, List<String>>): ApiResponse<T> {
|
||||
return ApiResponse.Success(data, headers)
|
||||
}
|
||||
|
||||
/**
|
||||
* Wraps an [ApiResponseError] in a [ApiResponse.Error] instance.
|
||||
* Wraps an [ApiResponseError] in a [ApiResponse.Error] instance
|
||||
*
|
||||
* @param cause The error to wrap.
|
||||
* @return A [ApiResponse.Error] instance containing the provided error.
|
||||
* @param cause the error to wrap
|
||||
* @param headers the headers returned by the API
|
||||
* @return a [ApiResponse.Error] instance containing the provided error
|
||||
*/
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
internal fun <T : Any> apiError(cause: ApiResponseError): ApiResponse<T> = ApiResponse.Error(cause) as ApiResponse<T>
|
||||
internal fun <T : Any> apiError(cause: ApiResponseError, headers: Map<String, List<String>>): ApiResponse<T> {
|
||||
return ApiResponse.Error(cause, headers) as ApiResponse<T>
|
||||
}
|
||||
|
|
@ -47,7 +47,8 @@ internal class ApiResponseCallDelegate<T : Any>(
|
|||
Timber.e(e, "onFailure UnknownException")
|
||||
ApiResponseError.UnknownException(e)
|
||||
}
|
||||
val safeResponse = apiError<T>(error)
|
||||
|
||||
val safeResponse = apiError<T>(cause = error, headers = emptyMap())
|
||||
|
||||
responseCallback.onResponse(this@ApiResponseCallDelegate, Response.success(safeResponse))
|
||||
}
|
||||
|
|
|
|||
|
|
@ -20,6 +20,8 @@ sealed class ApiResponseError : Exception() {
|
|||
|
||||
// region Error Codes
|
||||
enum class Code(val numericCode: Int) {
|
||||
// 3xx Server Errors
|
||||
NOT_MODIFIED(numericCode = 304),
|
||||
// 4xx Server Errors
|
||||
BAD_REQUEST(numericCode = 400),
|
||||
UNAUTHORIZED(numericCode = 401),
|
||||
|
|
@ -64,10 +66,6 @@ sealed class ApiResponseError : Exception() {
|
|||
;
|
||||
|
||||
override fun toString(): String = "$numericCode - $name"
|
||||
|
||||
companion object {
|
||||
val values = values()
|
||||
}
|
||||
}
|
||||
// endregion Error Codes
|
||||
}
|
||||
|
|
|
|||
|
|
@ -19,4 +19,9 @@ inline fun <T> catchApiResponseError(onError: (ApiResponseError) -> Unit, block:
|
|||
onError(e)
|
||||
throw e
|
||||
}
|
||||
}
|
||||
|
||||
/** Checks if the ApiResponseError is a network error with the specified HTTP status [code] */
|
||||
fun ApiResponseError.isNetworkError(code: ApiResponseError.HttpException.Code): Boolean {
|
||||
return this is ApiResponseError.HttpException && this.code == code
|
||||
}
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
package com.tangem.datasource.api.common.response
|
||||
|
||||
const val IF_NONE_MATCH_HEADER = "IfNoneMatch"
|
||||
|
|
@ -12,12 +12,13 @@ import java.util.concurrent.TimeoutException
|
|||
import javax.net.ssl.SSLHandshakeException
|
||||
|
||||
internal fun <T : Any> Response<T>.toSafeApiResponse(analyticsErrorHandler: AnalyticsErrorHandler): ApiResponse<T> {
|
||||
val headers = headers().toMultimap()
|
||||
val body = body()
|
||||
|
||||
return if (isSuccessful && body != null) {
|
||||
apiSuccess(body)
|
||||
apiSuccess(data = body, headers = headers)
|
||||
} else {
|
||||
val code = ApiResponseError.HttpException.Code.values
|
||||
val code = ApiResponseError.HttpException.Code.entries
|
||||
.firstOrNull { it.numericCode == code() }
|
||||
val e = try {
|
||||
if (code == null) {
|
||||
|
|
@ -33,7 +34,7 @@ internal fun <T : Any> Response<T>.toSafeApiResponse(analyticsErrorHandler: Anal
|
|||
ApiResponseError.UnknownException(e)
|
||||
}
|
||||
|
||||
apiError(e)
|
||||
apiError(e, headers)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -103,4 +103,7 @@ interface TangemPayApi {
|
|||
|
||||
@GET("v1/customer/kyc")
|
||||
suspend fun getKycAccess(@Header("Authorization") authHeader: String): ApiResponse<KycAccessInfoResponse>
|
||||
|
||||
@GET("v1/customer/me")
|
||||
suspend fun getCustomerMe(@Header("Authorization") authHeader: String): ApiResponse<CustomerMeResponse>
|
||||
}
|
||||
|
|
@ -0,0 +1,48 @@
|
|||
package com.tangem.datasource.api.pay.models.response
|
||||
|
||||
import com.squareup.moshi.Json
|
||||
import com.squareup.moshi.JsonClass
|
||||
|
||||
@JsonClass(generateAdapter = true)
|
||||
data class CustomerMeResponse(
|
||||
@Json(name = "result") val result: Result?,
|
||||
@Json(name = "error") val error: String?,
|
||||
) {
|
||||
@JsonClass(generateAdapter = true)
|
||||
data class Result(
|
||||
@Json(name = "id") val id: String,
|
||||
@Json(name = "state") val state: String,
|
||||
@Json(name = "createdAt") val createdAt: String,
|
||||
@Json(name = "product_instance") val productInstance: ProductInstance,
|
||||
@Json(name = "payment_account") val paymentAccount: PaymentAccount,
|
||||
@Json(name = "kyc") val kyc: Kyc,
|
||||
)
|
||||
|
||||
@JsonClass(generateAdapter = true)
|
||||
data class ProductInstance(
|
||||
@Json(name = "id") val id: String,
|
||||
@Json(name = "cid") val cid: String,
|
||||
@Json(name = "card_id") val cardId: String,
|
||||
@Json(name = "card_wallet_address") val cardWalletAddress: String,
|
||||
@Json(name = "status") val status: String,
|
||||
@Json(name = "updated_at") val updatedAt: String,
|
||||
@Json(name = "payment_account_id") val paymentAccountId: String,
|
||||
)
|
||||
|
||||
@JsonClass(generateAdapter = true)
|
||||
data class PaymentAccount(
|
||||
@Json(name = "id") val id: String,
|
||||
@Json(name = "address") val address: String,
|
||||
@Json(name = "customer_wallet_address") val customerWalletAddress: String,
|
||||
)
|
||||
|
||||
@JsonClass(generateAdapter = true)
|
||||
data class Kyc(
|
||||
@Json(name = "id") val id: String,
|
||||
@Json(name = "provider") val provider: String,
|
||||
@Json(name = "status") val status: String,
|
||||
@Json(name = "risk") val risk: String,
|
||||
@Json(name = "review_answer") val reviewAnswer: String,
|
||||
@Json(name = "created_at") val createdAt: String,
|
||||
)
|
||||
}
|
||||
|
|
@ -154,7 +154,8 @@ interface TangemTechApi {
|
|||
suspend fun saveWalletAccounts(
|
||||
@Path("walletId") walletId: String,
|
||||
@Header("If-Match") ifMatch: String,
|
||||
): ApiResponse<SaveWalletAccountsResponse>
|
||||
@Body body: SaveWalletAccountsResponse,
|
||||
): ApiResponse<Unit>
|
||||
|
||||
@GET("/v1/wallets/{walletId}/accounts/archived")
|
||||
suspend fun getWalletArchivedAccounts(
|
||||
|
|
|
|||
|
|
@ -15,7 +15,7 @@ data class GetWalletAccountsResponse(
|
|||
|
||||
@JsonClass(generateAdapter = true)
|
||||
data class Wallet(
|
||||
@Json(name = "version") val version: Int,
|
||||
@Json(name = "version") val version: Int = 0,
|
||||
@Json(name = "group") val group: GroupType,
|
||||
@Json(name = "sort") val sort: SortType,
|
||||
@Json(name = "totalAccounts") val totalAccounts: Int,
|
||||
|
|
|
|||
|
|
@ -14,11 +14,18 @@ interface RuntimeStateStore<T> {
|
|||
/** Get flow of elements [T] */
|
||||
fun get(): StateFlow<T>
|
||||
|
||||
/** Get element [T] synchronously or null */
|
||||
suspend fun getSyncOrNull(): T?
|
||||
|
||||
/** Store [value] */
|
||||
suspend fun store(value: T)
|
||||
|
||||
/** Update current value by [function] */
|
||||
suspend fun update(function: (T) -> T)
|
||||
|
||||
/** Clear stored value */
|
||||
fun clear()
|
||||
|
||||
companion object {
|
||||
|
||||
/**
|
||||
|
|
@ -32,6 +39,8 @@ interface RuntimeStateStore<T> {
|
|||
|
||||
override fun get(): StateFlow<T> = flow
|
||||
|
||||
override suspend fun getSyncOrNull(): T? = flow.value
|
||||
|
||||
override suspend fun store(value: T) {
|
||||
flow.value = value
|
||||
}
|
||||
|
|
@ -39,6 +48,10 @@ interface RuntimeStateStore<T> {
|
|||
override suspend fun update(function: (T) -> T) {
|
||||
flow.update(function)
|
||||
}
|
||||
|
||||
override fun clear() {
|
||||
flow.value = defaultValue
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -7,7 +7,7 @@ import com.tangem.datasource.local.preferences.PreferencesKeys.IS_TANGEM_TOS_ACC
|
|||
import com.tangem.datasource.local.preferences.PreferencesKeys.SAVE_USER_WALLETS_KEY
|
||||
import com.tangem.datasource.local.preferences.PreferencesKeys.SHOULD_OPEN_WELCOME_ON_RESUME_KEY
|
||||
import com.tangem.datasource.local.preferences.PreferencesKeys.SHOULD_SAVE_ACCESS_CODES_KEY
|
||||
import com.tangem.datasource.local.preferences.PreferencesKeys.SHOULD_SHOW_SAVE_USER_WALLET_SCREEN_KEY
|
||||
import com.tangem.datasource.local.preferences.PreferencesKeys.SHOULD_SHOW_ASK_BIOMETRY_KEY
|
||||
import com.tangem.datasource.local.preferences.PreferencesKeys.SHOW_RATING_DIALOG_AT_LAUNCH_COUNT_KEY
|
||||
import com.tangem.datasource.local.preferences.PreferencesKeys.USED_CARDS_INFO_KEY
|
||||
import com.tangem.datasource.local.preferences.PreferencesKeys.USER_WAS_INTERACT_WITH_RATING_KEY
|
||||
|
|
@ -26,7 +26,7 @@ object PreferencesKeys {
|
|||
|
||||
val SAVE_USER_WALLETS_KEY by lazy { booleanPreferencesKey(name = "saveUserWallets") }
|
||||
|
||||
val SHOULD_SHOW_SAVE_USER_WALLET_SCREEN_KEY by lazy { booleanPreferencesKey("saveUserWalletShown") }
|
||||
val SHOULD_SHOW_ASK_BIOMETRY_KEY by lazy { booleanPreferencesKey("saveUserWalletShown") }
|
||||
|
||||
val APP_LAUNCH_COUNT_KEY by lazy { intPreferencesKey(name = "launchCount") }
|
||||
|
||||
|
|
@ -56,10 +56,6 @@ object PreferencesKeys {
|
|||
|
||||
val LAST_SWAPPED_CRYPTOCURRENCY_ID_KEY by lazy { stringPreferencesKey(name = "lastSwappedCryptoCurrency") }
|
||||
|
||||
val FEATURE_TOGGLES_KEY by lazy { stringPreferencesKey(name = "featureToggles") }
|
||||
|
||||
val EXCLUDED_BLOCKCHAINS_KEY by lazy { stringPreferencesKey(name = "excludedBlockchainsV2") }
|
||||
|
||||
val WAS_TWINS_ONBOARDING_SHOWN by lazy { booleanPreferencesKey(name = "twinsOnboardingShown") }
|
||||
|
||||
val IS_TANGEM_TOS_ACCEPTED_KEY by lazy { booleanPreferencesKey(name = "tangem_tos_accepted") }
|
||||
|
|
@ -187,7 +183,7 @@ object PreferencesKeys {
|
|||
internal fun getTapPrefKeysToMigrate(): Set<String> {
|
||||
return setOf(
|
||||
SAVE_USER_WALLETS_KEY,
|
||||
SHOULD_SHOW_SAVE_USER_WALLET_SCREEN_KEY,
|
||||
SHOULD_SHOW_ASK_BIOMETRY_KEY,
|
||||
APP_LAUNCH_COUNT_KEY,
|
||||
SHOW_RATING_DIALOG_AT_LAUNCH_COUNT_KEY,
|
||||
FUNDS_FOUND_DATE_KEY,
|
||||
|
|
|
|||
|
|
@ -39,5 +39,13 @@ internal class DefaultUserTokensResponseStore(
|
|||
)
|
||||
}
|
||||
|
||||
override suspend fun clear(userWalletId: UserWalletId) {
|
||||
appPreferencesStore.updateData { preferences ->
|
||||
val key = createPreferencesKey(userWalletId = userWalletId.stringValue)
|
||||
|
||||
preferences.toMutablePreferences().apply { remove(key) }
|
||||
}
|
||||
}
|
||||
|
||||
private fun createPreferencesKey(userWalletId: String) = stringPreferencesKey(name = "user_tokens_$userWalletId")
|
||||
}
|
||||
|
|
@ -17,4 +17,6 @@ interface UserTokensResponseStore {
|
|||
suspend fun getSyncOrNull(userWalletId: UserWalletId): UserTokensResponse?
|
||||
|
||||
suspend fun store(userWalletId: UserWalletId, response: UserTokensResponse)
|
||||
|
||||
suspend fun clear(userWalletId: UserWalletId)
|
||||
}
|
||||
|
|
@ -0,0 +1,10 @@
|
|||
package com.tangem.datasource.local.visa
|
||||
|
||||
interface TangemPayStorage {
|
||||
|
||||
suspend fun store(authHeader: String)
|
||||
|
||||
suspend fun get(): String?
|
||||
|
||||
suspend fun clear()
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue