Updated on 2026-08-14
This commit is contained in:
parent
e037233aa5
commit
804e4eca23
31 changed files with 635 additions and 147 deletions
|
|
@ -153,4 +153,16 @@ interface TangemPayApi {
|
|||
@Header("Authorization") authHeader: String,
|
||||
@Body body: SetPinRequest,
|
||||
): ApiResponse<SetPinResponse>
|
||||
|
||||
@POST("v1/customer/card/freeze")
|
||||
suspend fun freezeCard(
|
||||
@Header("Authorization") authHeader: String,
|
||||
@Body body: FreezeUnfreezeCardRequest,
|
||||
): ApiResponse<FreezeUnfreezeCardResponse>
|
||||
|
||||
@POST("v1/customer/card/unfreeze")
|
||||
suspend fun unfreezeCard(
|
||||
@Header("Authorization") authHeader: String,
|
||||
@Body body: FreezeUnfreezeCardRequest,
|
||||
): ApiResponse<FreezeUnfreezeCardResponse>
|
||||
}
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
package com.tangem.datasource.api.pay.models.request
|
||||
|
||||
import com.squareup.moshi.Json
|
||||
import com.squareup.moshi.JsonClass
|
||||
|
||||
@JsonClass(generateAdapter = true)
|
||||
data class FreezeUnfreezeCardRequest(@Json(name = "card_id") val cardId: String)
|
||||
|
|
@ -28,10 +28,49 @@ data class CustomerMeResponse(
|
|||
@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 = "status") val status: Status,
|
||||
@Json(name = "updated_at") val updatedAt: String,
|
||||
@Json(name = "payment_account_id") val paymentAccountId: String,
|
||||
)
|
||||
) {
|
||||
@JsonClass(generateAdapter = false)
|
||||
enum class Status {
|
||||
@Json(name = "new")
|
||||
NEW,
|
||||
|
||||
@Json(name = "ready_for_manufacturing")
|
||||
READY_FOR_MANUFACTURING,
|
||||
|
||||
@Json(name = "manufacturing")
|
||||
MANUFACTURING,
|
||||
|
||||
@Json(name = "sent_to_delivery")
|
||||
SENT_TO_DELIVERY,
|
||||
|
||||
@Json(name = "delivered")
|
||||
DELIVERED,
|
||||
|
||||
@Json(name = "activating")
|
||||
ACTIVATING,
|
||||
|
||||
@Json(name = "active")
|
||||
ACTIVE,
|
||||
|
||||
@Json(name = "blocked")
|
||||
BLOCKED,
|
||||
|
||||
@Json(name = "deactivating")
|
||||
DEACTIVATING,
|
||||
|
||||
@Json(name = "deactivated")
|
||||
DEACTIVATED,
|
||||
|
||||
@Json(name = "canceled")
|
||||
CANCELED,
|
||||
|
||||
@Json(name = "unknown")
|
||||
UNKNOWN,
|
||||
}
|
||||
}
|
||||
|
||||
@JsonClass(generateAdapter = true)
|
||||
data class PaymentAccount(
|
||||
|
|
|
|||
|
|
@ -0,0 +1,31 @@
|
|||
package com.tangem.datasource.api.pay.models.response
|
||||
|
||||
import com.squareup.moshi.Json
|
||||
import com.squareup.moshi.JsonClass
|
||||
|
||||
@JsonClass(generateAdapter = true)
|
||||
class FreezeUnfreezeCardResponse(
|
||||
@Json(name = "result") val result: Result?,
|
||||
@Json(name = "error") val error: String?,
|
||||
) {
|
||||
@JsonClass(generateAdapter = true)
|
||||
data class Result(
|
||||
@Json(name = "order_id") val orderId: String,
|
||||
@Json(name = "status") val status: Status,
|
||||
)
|
||||
|
||||
@JsonClass(generateAdapter = false)
|
||||
enum class Status {
|
||||
@Json(name = "NEW")
|
||||
NEW,
|
||||
|
||||
@Json(name = "PROCESSING")
|
||||
PROCESSING,
|
||||
|
||||
@Json(name = "COMPLETED")
|
||||
COMPLETED,
|
||||
|
||||
@Json(name = "CANCELED")
|
||||
CANCELED,
|
||||
}
|
||||
}
|
||||
|
|
@ -1357,6 +1357,7 @@
|
|||
<string name="swapping_to_title">You receive</string>
|
||||
<string name="swapping_token_list_title">Choose token</string>
|
||||
<string name="swapping_token_not_available">not available</string>
|
||||
<string name="tangem_pay_card_frozen">Card frozen</string>
|
||||
<string name="tangem_pay_deposit">Deposit</string>
|
||||
<string name="tangem_pay_dispute">Dispute</string>
|
||||
<string name="tangem_pay_explore_transaction">Explore transaction</string>
|
||||
|
|
@ -1364,7 +1365,9 @@
|
|||
<string name="tangem_pay_fee_title">Fee</string>
|
||||
<string name="tangem_pay_freeze_card_alert_body">Keep your money safe if your card is lost or stolen. You can unfreeze anytime.</string>
|
||||
<string name="tangem_pay_freeze_card_alert_title">Freeze your card?</string>
|
||||
<string name="tangem_pay_freeze_card_failed">Failed to freeze the card. Try again later.</string>
|
||||
<string name="tangem_pay_freeze_card_freeze">Freeze</string>
|
||||
<string name="tangem_pay_freeze_card_success">Your card is frozen.</string>
|
||||
<string name="tangem_pay_get_help">Get Help</string>
|
||||
<string name="tangem_pay_status_completed">Completed</string>
|
||||
<string name="tangem_pay_status_declined">Declined</string>
|
||||
|
|
@ -1373,6 +1376,8 @@
|
|||
<string name="tangem_pay_transaction_fee_notification_text">This fee goes to cover the cost of handling your transfer.</string>
|
||||
<string name="tangem_pay_unfreeze_card_alert_body">Keep using your money. You can freeze anytime.</string>
|
||||
<string name="tangem_pay_unfreeze_card_alert_title">Unfreeze your card?</string>
|
||||
<string name="tangem_pay_unfreeze_card_failed">Failed to unfreeze the card. Try again later.</string>
|
||||
<string name="tangem_pay_unfreeze_card_success">Your card is unfrozen.</string>
|
||||
<string name="tangem_pay_withdrawal">Withdrawal</string>
|
||||
<string name="tangempay_card_details_add_funds">Add funds</string>
|
||||
<string name="tangempay_card_details_add_funds_subtitle">Top-up options</string>
|
||||
|
|
|
|||
12
core/ui/src/main/res/drawable/ic_snow_24.xml
Normal file
12
core/ui/src/main/res/drawable/ic_snow_24.xml
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:autoMirrored="true"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
|
||||
<path
|
||||
android:fillColor="#1E1E1E"
|
||||
android:pathData="M6.876,18.737C6.365,18.704 6.11,18.305 6.176,17.805L6.31,16.284L4.634,17.261C4.167,17.528 3.679,17.417 3.413,16.962C3.157,16.517 3.302,16.007 3.768,15.752L5.433,14.786L4.056,14.131C3.59,13.942 3.379,13.521 3.601,13.076C3.801,12.677 4.245,12.521 4.689,12.766L6.854,13.976L7.841,13.398L10.705,12L7.841,10.601L6.854,10.035L4.7,11.234C4.245,11.478 3.79,11.323 3.601,10.934C3.379,10.479 3.59,10.057 4.056,9.869L5.433,9.214L3.757,8.248C3.302,7.993 3.146,7.504 3.413,7.049C3.668,6.594 4.178,6.483 4.634,6.75L6.299,7.715L6.176,6.183C6.121,5.695 6.376,5.296 6.876,5.262C7.298,5.24 7.664,5.551 7.675,6.061L7.719,8.526L8.707,9.103L11.349,10.89L11.127,7.693V6.561L9.018,5.284C8.574,5.018 8.485,4.563 8.729,4.197C9.007,3.775 9.473,3.753 9.873,4.063L11.127,4.929V2.998C11.127,2.465 11.482,2.088 11.993,2.088C12.526,2.088 12.87,2.454 12.87,2.998V4.929L14.113,4.063C14.524,3.753 14.99,3.775 15.267,4.197C15.5,4.563 15.423,5.018 14.979,5.284L12.87,6.561V7.693L12.648,10.879L15.289,9.103L16.277,8.537L16.322,6.061C16.333,5.551 16.699,5.251 17.132,5.262C17.62,5.296 17.876,5.695 17.809,6.195L17.687,7.715L19.363,6.75C19.829,6.472 20.329,6.594 20.584,7.049C20.85,7.504 20.706,7.971 20.229,8.248L18.564,9.214L19.94,9.869C20.395,10.057 20.617,10.479 20.395,10.923C20.195,11.323 19.752,11.478 19.296,11.234L17.143,10.035L16.155,10.601L13.291,12L16.155,13.398L17.143,13.976L19.308,12.777C19.752,12.521 20.184,12.688 20.395,13.076C20.617,13.521 20.395,13.942 19.94,14.131L18.553,14.786L20.229,15.752C20.684,16.007 20.828,16.517 20.573,16.962C20.318,17.417 19.829,17.528 19.363,17.261L17.687,16.284L17.82,17.805C17.876,18.305 17.62,18.704 17.121,18.737C16.688,18.76 16.333,18.46 16.322,17.938L16.277,15.474L15.289,14.897L12.648,13.132L12.87,16.295V17.439L14.979,18.715C15.423,18.982 15.511,19.437 15.267,19.803C14.99,20.225 14.524,20.247 14.113,19.936L12.87,19.07V21.002C12.87,21.546 12.526,21.912 11.993,21.912C11.482,21.912 11.127,21.535 11.127,21.002V19.07L9.873,19.936C9.473,20.236 8.996,20.225 8.729,19.803C8.485,19.437 8.574,18.982 9.018,18.715L11.127,17.439V16.295L11.349,13.121L8.707,14.897L7.719,15.474L7.675,17.938C7.664,18.449 7.309,18.76 6.876,18.737Z" />
|
||||
|
||||
</vector>
|
||||
|
|
@ -1,6 +1,7 @@
|
|||
package com.tangem.data.pay
|
||||
|
||||
import arrow.core.Either
|
||||
import arrow.core.Either.Companion.catch
|
||||
import com.squareup.moshi.Moshi
|
||||
import com.tangem.blockchain.blockchains.ethereum.Chain
|
||||
import com.tangem.blockchainsdk.utils.ExcludedBlockchains
|
||||
|
|
@ -16,7 +17,6 @@ import com.tangem.domain.models.currency.CryptoCurrency
|
|||
import com.tangem.domain.models.wallet.UserWallet
|
||||
import com.tangem.domain.pay.TangemPayTopUpData
|
||||
import com.tangem.domain.pay.TangemPayTopUpDataFactory
|
||||
import kotlinx.coroutines.CancellationException
|
||||
import timber.log.Timber
|
||||
import java.math.BigDecimal
|
||||
import javax.inject.Inject
|
||||
|
|
@ -68,10 +68,10 @@ internal class DefaultTangemPayTopUpDataFactory @Inject constructor(
|
|||
cryptoBalance: BigDecimal,
|
||||
fiatBalance: BigDecimal,
|
||||
): Either<UniversalError, TangemPayTopUpData> {
|
||||
return try {
|
||||
return catch {
|
||||
val wallet = tangemPayWalletsManager.getDefaultWalletForTangemPayBlocking()
|
||||
val currency = getCurrency(wallet, chainId)
|
||||
val result = TangemPayTopUpData(
|
||||
TangemPayTopUpData(
|
||||
currency = currency,
|
||||
walletId = wallet.walletId,
|
||||
cryptoBalance = cryptoBalance,
|
||||
|
|
@ -79,18 +79,9 @@ internal class DefaultTangemPayTopUpDataFactory @Inject constructor(
|
|||
depositAddress = depositAddress,
|
||||
receiveAddress = listOf(ReceiveAddressModel(nameService = NameService.Default, value = depositAddress)),
|
||||
)
|
||||
|
||||
Either.Right(result)
|
||||
} catch (exception: Exception) {
|
||||
when (exception) {
|
||||
is CancellationException -> {
|
||||
throw exception
|
||||
}
|
||||
else -> {
|
||||
Timber.tag(TAG).e(exception)
|
||||
Either.Left(errorConverter.convert(exception))
|
||||
}
|
||||
}
|
||||
}.mapLeft { exception ->
|
||||
Timber.tag(TAG).e(exception)
|
||||
errorConverter.convert(exception)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,13 +1,13 @@
|
|||
package com.tangem.data.pay.di
|
||||
|
||||
import com.tangem.data.pay.DefaultTangemPayTopUpDataFactory
|
||||
import com.tangem.data.pay.repository.DefaultCardDetailsRepository
|
||||
import com.tangem.data.pay.repository.DefaultTangemPayCardDetailsRepository
|
||||
import com.tangem.data.pay.repository.DefaultKycRepository
|
||||
import com.tangem.data.pay.repository.DefaultTangemPayTxHistoryRepository
|
||||
import com.tangem.data.pay.repository.DefaultOnboardingRepository
|
||||
import com.tangem.data.pay.usecase.DefaultGetTangemPayCurrencyStatusUseCase
|
||||
import com.tangem.domain.pay.TangemPayTopUpDataFactory
|
||||
import com.tangem.domain.pay.repository.CardDetailsRepository
|
||||
import com.tangem.domain.pay.repository.TangemPayCardDetailsRepository
|
||||
import com.tangem.domain.pay.repository.KycRepository
|
||||
import com.tangem.domain.pay.repository.OnboardingRepository
|
||||
import com.tangem.domain.pay.usecase.ProduceTangemPayInitialDataUseCase
|
||||
|
|
@ -40,7 +40,7 @@ internal interface TangemPayDataModule {
|
|||
|
||||
@Binds
|
||||
@Singleton
|
||||
fun bindCardDetailsRepository(repository: DefaultCardDetailsRepository): CardDetailsRepository
|
||||
fun bindCardDetailsRepository(repository: DefaultTangemPayCardDetailsRepository): TangemPayCardDetailsRepository
|
||||
|
||||
@Binds
|
||||
@Singleton
|
||||
|
|
|
|||
|
|
@ -137,8 +137,18 @@ internal class DefaultOnboardingRepository @Inject constructor(
|
|||
} else {
|
||||
null
|
||||
}
|
||||
val productInstance = response?.productInstance?.let { instance ->
|
||||
ProductInstance(
|
||||
id = instance.id,
|
||||
cardId = instance.cardId,
|
||||
status = when (instance.status) {
|
||||
CustomerMeResponse.ProductInstance.Status.ACTIVE -> ProductInstance.Status.ACTIVE
|
||||
else -> ProductInstance.Status.INACTIVE
|
||||
},
|
||||
)
|
||||
}
|
||||
return CustomerInfo(
|
||||
productInstance = response?.productInstance?.let { ProductInstance(id = it.id, status = it.status) },
|
||||
productInstance = productInstance,
|
||||
isKycApproved = response?.kyc?.status == APPROVED_KYC_STATUS,
|
||||
cardInfo = cardInfo,
|
||||
)
|
||||
|
|
|
|||
|
|
@ -9,24 +9,29 @@ import com.tangem.datasource.api.common.config.ApiEnvironment
|
|||
import com.tangem.datasource.api.common.config.managers.ApiConfigsManager
|
||||
import com.tangem.datasource.api.pay.TangemPayApi
|
||||
import com.tangem.datasource.api.pay.models.request.CardDetailsRequest
|
||||
import com.tangem.datasource.api.pay.models.request.FreezeUnfreezeCardRequest
|
||||
import com.tangem.datasource.api.pay.models.request.SetPinRequest
|
||||
import com.tangem.datasource.api.pay.models.response.FreezeUnfreezeCardResponse
|
||||
import com.tangem.datasource.local.visa.TangemPayStorage
|
||||
import com.tangem.domain.pay.model.SetPinResult
|
||||
import com.tangem.domain.pay.model.TangemPayCardBalance
|
||||
import com.tangem.domain.pay.model.TangemPayCardDetails
|
||||
import com.tangem.domain.pay.repository.CardDetailsRepository
|
||||
import com.tangem.domain.pay.repository.TangemPayCardDetailsRepository
|
||||
import com.tangem.domain.visa.error.VisaApiError
|
||||
import timber.log.Timber
|
||||
import javax.inject.Inject
|
||||
|
||||
private const val TAG = "TangemPay: CardDetailsRepository"
|
||||
|
||||
internal class DefaultCardDetailsRepository @Inject constructor(
|
||||
@Suppress("OptionalUnit")
|
||||
internal class DefaultTangemPayCardDetailsRepository @Inject constructor(
|
||||
private val tangemPayApi: TangemPayApi,
|
||||
private val requestHelper: TangemPayRequestPerformer,
|
||||
private val visaLibLoader: VisaLibLoader,
|
||||
private val apiConfigsManager: ApiConfigsManager,
|
||||
private val rainCryptoUtil: RainCryptoUtil,
|
||||
private val storage: TangemPayStorage,
|
||||
) : CardDetailsRepository {
|
||||
) : TangemPayCardDetailsRepository {
|
||||
|
||||
override suspend fun getCardBalance(): Either<UniversalError, TangemPayCardBalance> {
|
||||
return requestHelper.runWithErrorLogs(TAG) {
|
||||
|
|
@ -112,6 +117,34 @@ internal class DefaultCardDetailsRepository @Inject constructor(
|
|||
}
|
||||
}
|
||||
|
||||
override suspend fun freezeCard(cardId: String): Either<UniversalError, Unit> {
|
||||
return requestHelper.makeSafeRequest {
|
||||
tangemPayApi.freezeCard(authHeader = it, body = FreezeUnfreezeCardRequest(cardId = cardId))
|
||||
}.map { response ->
|
||||
val result = response.result
|
||||
if (result != null && result.status == FreezeUnfreezeCardResponse.Status.COMPLETED) {
|
||||
Either.Right(Unit)
|
||||
} else {
|
||||
Timber.e("Card freeze failed with status: ${response.result?.status}")
|
||||
Either.Left(VisaApiError.UnknownWithoutCode)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun unfreezeCard(cardId: String): Either<UniversalError, Unit> {
|
||||
return requestHelper.makeSafeRequest {
|
||||
tangemPayApi.unfreezeCard(authHeader = it, body = FreezeUnfreezeCardRequest(cardId = cardId))
|
||||
}.map { response ->
|
||||
val result = response.result
|
||||
if (result != null && result.status == FreezeUnfreezeCardResponse.Status.COMPLETED) {
|
||||
Either.Right(Unit)
|
||||
} else {
|
||||
Timber.e("Card unfreeze failed with status: ${response.result?.status}")
|
||||
Either.Left(VisaApiError.UnknownWithoutCode)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun getPublicKeyBase64(): String {
|
||||
val env = apiConfigsManager.getEnvironmentConfig(ApiConfig.ID.TangemPay).environment
|
||||
return when (env) {
|
||||
|
|
@ -1,6 +1,7 @@
|
|||
package com.tangem.data.pay.repository
|
||||
|
||||
import arrow.core.Either
|
||||
import arrow.core.Either.Companion.catch
|
||||
import com.squareup.moshi.Moshi
|
||||
import com.tangem.core.error.UniversalError
|
||||
import com.tangem.data.pay.util.TangemPayErrorConverter
|
||||
|
|
@ -11,6 +12,7 @@ import com.tangem.datasource.api.common.response.getOrThrow
|
|||
import com.tangem.datasource.di.NetworkMoshi
|
||||
import com.tangem.datasource.local.visa.TangemPayStorage
|
||||
import com.tangem.domain.pay.datasource.TangemPayAuthDataSource
|
||||
import com.tangem.domain.visa.error.VisaApiError
|
||||
import com.tangem.domain.visa.model.VisaAuthTokens
|
||||
import com.tangem.domain.visa.model.getAuthHeader
|
||||
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
||||
|
|
@ -21,7 +23,6 @@ import kotlinx.coroutines.sync.withLock
|
|||
import timber.log.Timber
|
||||
import javax.inject.Inject
|
||||
|
||||
@Suppress("LongParameterList")
|
||||
internal class TangemPayRequestPerformer @Inject constructor(
|
||||
@NetworkMoshi moshi: Moshi,
|
||||
private val dispatchers: CoroutineDispatcherProvider,
|
||||
|
|
@ -54,6 +55,16 @@ internal class TangemPayRequestPerformer @Inject constructor(
|
|||
}
|
||||
}
|
||||
|
||||
suspend fun <T : Any> makeSafeRequest(
|
||||
requestBlock: suspend (header: String) -> ApiResponse<T>,
|
||||
): Either<VisaApiError, T> {
|
||||
return catch { request(requestBlock) }
|
||||
.mapLeft { exception ->
|
||||
Timber.tag("TangemPayRequestPerformer").e(exception)
|
||||
errorConverter.convert(exception)
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun <T : Any> request(requestBlock: suspend (header: String) -> ApiResponse<T>): T =
|
||||
withContext(dispatchers.io) {
|
||||
performRequest(
|
||||
|
|
|
|||
|
|
@ -1,17 +1,16 @@
|
|||
package com.tangem.data.pay.util
|
||||
|
||||
import com.squareup.moshi.Moshi
|
||||
import com.tangem.core.error.UniversalError
|
||||
import com.tangem.datasource.api.common.response.ApiResponseError
|
||||
import com.tangem.datasource.api.pay.models.response.VisaErrorResponseJsonAdapter
|
||||
import com.tangem.datasource.api.pay.models.response.VisaErrorResponse
|
||||
import com.tangem.domain.visa.error.VisaApiError
|
||||
import com.tangem.utils.converter.Converter
|
||||
|
||||
class TangemPayErrorConverter(moshi: Moshi) : Converter<Throwable, UniversalError> {
|
||||
class TangemPayErrorConverter(moshi: Moshi) : Converter<Throwable, VisaApiError> {
|
||||
|
||||
private val visaErrorAdapter = VisaErrorResponseJsonAdapter(moshi)
|
||||
private val visaErrorAdapter by lazy { moshi.adapter(VisaErrorResponse::class.java) }
|
||||
|
||||
override fun convert(value: Throwable): UniversalError {
|
||||
override fun convert(value: Throwable): VisaApiError {
|
||||
return if (value is ApiResponseError.HttpException) {
|
||||
val errorBody = value.errorBody ?: return VisaApiError.UnknownWithoutCode
|
||||
return runCatching {
|
||||
|
|
|
|||
|
|
@ -4,6 +4,8 @@ import kotlinx.serialization.Serializable
|
|||
|
||||
@Serializable
|
||||
data class TangemPayDetailsConfig(
|
||||
val cardId: String,
|
||||
val isCardFrozen: Boolean,
|
||||
val customerWalletAddress: String,
|
||||
val cardNumberEnd: String,
|
||||
val chainId: Int,
|
||||
|
|
|
|||
|
|
@ -15,8 +15,14 @@ data class CustomerInfo(
|
|||
|
||||
data class ProductInstance(
|
||||
val id: String,
|
||||
val status: String,
|
||||
)
|
||||
val cardId: String,
|
||||
val status: Status,
|
||||
) {
|
||||
enum class Status {
|
||||
ACTIVE,
|
||||
INACTIVE,
|
||||
}
|
||||
}
|
||||
|
||||
data class CardInfo(
|
||||
val lastFourDigits: String,
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ import com.tangem.domain.pay.model.SetPinResult
|
|||
import com.tangem.domain.pay.model.TangemPayCardBalance
|
||||
import com.tangem.domain.pay.model.TangemPayCardDetails
|
||||
|
||||
interface CardDetailsRepository {
|
||||
interface TangemPayCardDetailsRepository {
|
||||
|
||||
suspend fun getCardBalance(): Either<UniversalError, TangemPayCardBalance>
|
||||
|
||||
|
|
@ -17,4 +17,7 @@ interface CardDetailsRepository {
|
|||
suspend fun isAddToWalletDone(): Either<UniversalError, Boolean>
|
||||
|
||||
suspend fun setAddToWalletAsDone(): Either<UniversalError, Unit>
|
||||
|
||||
suspend fun freezeCard(cardId: String): Either<UniversalError, Unit>
|
||||
suspend fun unfreezeCard(cardId: String): Either<UniversalError, Unit>
|
||||
}
|
||||
|
|
@ -107,8 +107,8 @@ class YieldSupplyGetRewardsBalanceUseCaseTest {
|
|||
chainId = 1,
|
||||
apy = BigDecimal.ZERO,
|
||||
isActive = true,
|
||||
maxFeeNative = "0",
|
||||
maxFeeUSD = "0",
|
||||
maxFeeNative = BigDecimal.ZERO,
|
||||
maxFeeUSD = BigDecimal.ZERO,
|
||||
backendId = "id",
|
||||
),
|
||||
)
|
||||
|
|
@ -152,8 +152,8 @@ class YieldSupplyGetRewardsBalanceUseCaseTest {
|
|||
chainId = 1,
|
||||
apy = apy,
|
||||
isActive = true,
|
||||
maxFeeNative = "0",
|
||||
maxFeeUSD = "0",
|
||||
maxFeeNative = BigDecimal.ZERO,
|
||||
maxFeeUSD = BigDecimal.ZERO,
|
||||
backendId = "id",
|
||||
),
|
||||
)
|
||||
|
|
|
|||
|
|
@ -4,49 +4,80 @@ import com.tangem.core.ui.components.buttons.actions.ActionButtonConfig
|
|||
import com.tangem.core.ui.components.containers.pullToRefresh.PullToRefreshConfig
|
||||
import com.tangem.core.ui.components.containers.pullToRefresh.PullToRefreshConfig.ShowRefreshState
|
||||
import com.tangem.core.ui.components.dropdownmenu.TangemDropdownMenuItem
|
||||
import com.tangem.core.ui.extensions.TextReference
|
||||
import com.tangem.core.ui.extensions.resourceReference
|
||||
import com.tangem.core.ui.extensions.themedColor
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
import com.tangem.features.tangempay.details.impl.R
|
||||
import kotlinx.collections.immutable.persistentListOf
|
||||
|
||||
@Suppress("LongParameterList")
|
||||
internal class TangemPayDetailsStateFactory(
|
||||
private val onBack: () -> Unit,
|
||||
private val onRefresh: (ShowRefreshState) -> Unit,
|
||||
private val onAddFunds: () -> Unit,
|
||||
private val onClickChangePin: () -> Unit,
|
||||
private val isCardFrozen: Boolean,
|
||||
private val onClickFreezeCard: () -> Unit,
|
||||
private val onClickUnfreezeCard: () -> Unit,
|
||||
) {
|
||||
|
||||
fun getInitialState() = TangemPayDetailsUM(
|
||||
topBarConfig = TangemPayDetailsTopBarConfig(
|
||||
onBackClick = onBack,
|
||||
items = persistentListOf(
|
||||
TangemDropdownMenuItem(
|
||||
title = TextReference.Res(R.string.tangempay_card_details_change_pin),
|
||||
fun getInitialState(): TangemPayDetailsUM {
|
||||
val cardFrozenStateItem = if (isCardFrozen) {
|
||||
TangemPayDetailsTopBarMenuItem(
|
||||
type = TangemPayDetailsTopBarMenuItemType.UnfreezeCard,
|
||||
dropdownItem = TangemDropdownMenuItem(
|
||||
title = resourceReference(R.string.tangempay_card_details_unfreeze_card),
|
||||
textColor = themedColor { TangemTheme.colors.text.primary1 },
|
||||
onClick = onClickChangePin,
|
||||
onClick = onClickUnfreezeCard,
|
||||
),
|
||||
TangemDropdownMenuItem(
|
||||
title = TextReference.Res(R.string.tangempay_card_details_freeze_card),
|
||||
)
|
||||
} else {
|
||||
TangemPayDetailsTopBarMenuItem(
|
||||
type = TangemPayDetailsTopBarMenuItemType.FreezeCard,
|
||||
dropdownItem = TangemDropdownMenuItem(
|
||||
title = resourceReference(R.string.tangempay_card_details_freeze_card),
|
||||
textColor = themedColor { TangemTheme.colors.text.primary1 },
|
||||
onClick = onClickFreezeCard,
|
||||
),
|
||||
),
|
||||
),
|
||||
pullToRefreshConfig = PullToRefreshConfig(isRefreshing = false, onRefresh = onRefresh),
|
||||
balanceBlockState = TangemPayDetailsBalanceBlockState.Loading(
|
||||
actionButtons = persistentListOf(
|
||||
ActionButtonConfig(
|
||||
text = resourceReference(id = R.string.tangempay_card_details_add_funds),
|
||||
iconResId = R.drawable.ic_arrow_down_24,
|
||||
onClick = onAddFunds,
|
||||
)
|
||||
}
|
||||
|
||||
return TangemPayDetailsUM(
|
||||
topBarConfig = TangemPayDetailsTopBarConfig(
|
||||
onBackClick = onBack,
|
||||
items = persistentListOf(
|
||||
TangemPayDetailsTopBarMenuItem(
|
||||
type = TangemPayDetailsTopBarMenuItemType.ChangePin,
|
||||
dropdownItem = TangemDropdownMenuItem(
|
||||
title = resourceReference(R.string.tangempay_card_details_change_pin),
|
||||
textColor = themedColor { TangemTheme.colors.text.primary1 },
|
||||
onClick = onClickChangePin,
|
||||
),
|
||||
),
|
||||
cardFrozenStateItem,
|
||||
),
|
||||
),
|
||||
),
|
||||
addToWalletBlockState = null,
|
||||
isBalanceHidden = false,
|
||||
addFundsEnabled = true,
|
||||
)
|
||||
pullToRefreshConfig = PullToRefreshConfig(
|
||||
isRefreshing = false,
|
||||
onRefresh = onRefresh,
|
||||
),
|
||||
balanceBlockState = TangemPayDetailsBalanceBlockState.Loading(
|
||||
actionButtons = persistentListOf(
|
||||
ActionButtonConfig(
|
||||
text = resourceReference(id = R.string.tangempay_card_details_add_funds),
|
||||
iconResId = R.drawable.ic_arrow_down_24,
|
||||
onClick = onAddFunds,
|
||||
),
|
||||
),
|
||||
frozenState = if (isCardFrozen) {
|
||||
CardFrozenState.Frozen(onClickUnfreezeCard)
|
||||
} else {
|
||||
CardFrozenState.Unfrozen
|
||||
},
|
||||
),
|
||||
addToWalletBlockState = null,
|
||||
isBalanceHidden = false,
|
||||
addFundsEnabled = true,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -5,5 +5,16 @@ import kotlinx.collections.immutable.ImmutableList
|
|||
|
||||
internal data class TangemPayDetailsTopBarConfig(
|
||||
val onBackClick: () -> Unit,
|
||||
val items: ImmutableList<TangemDropdownMenuItem>?,
|
||||
)
|
||||
val items: ImmutableList<TangemPayDetailsTopBarMenuItem>?,
|
||||
)
|
||||
|
||||
internal data class TangemPayDetailsTopBarMenuItem(
|
||||
val type: TangemPayDetailsTopBarMenuItemType,
|
||||
val dropdownItem: TangemDropdownMenuItem,
|
||||
)
|
||||
|
||||
internal enum class TangemPayDetailsTopBarMenuItemType {
|
||||
ChangePin,
|
||||
FreezeCard,
|
||||
UnfreezeCard,
|
||||
}
|
||||
|
|
@ -15,9 +15,9 @@ internal data class TangemPayDetailsUM(
|
|||
)
|
||||
|
||||
internal data class TangemPayCardDetailsUM(
|
||||
val number: String = "",
|
||||
val expiry: String = "",
|
||||
val cvv: String = "",
|
||||
val number: String,
|
||||
val expiry: String,
|
||||
val cvv: String,
|
||||
val buttonText: TextReference = TextReference.EMPTY,
|
||||
val onClick: () -> Unit = {},
|
||||
val onCopy: (String) -> Unit = {},
|
||||
|
|
@ -28,13 +28,16 @@ internal data class TangemPayCardDetailsUM(
|
|||
internal sealed class TangemPayDetailsBalanceBlockState {
|
||||
|
||||
abstract val actionButtons: ImmutableList<ActionButtonConfig>
|
||||
abstract val frozenState: CardFrozenState
|
||||
|
||||
data class Loading(
|
||||
override val actionButtons: ImmutableList<ActionButtonConfig>,
|
||||
override val frozenState: CardFrozenState,
|
||||
) : TangemPayDetailsBalanceBlockState()
|
||||
|
||||
data class Content(
|
||||
override val actionButtons: ImmutableList<ActionButtonConfig>,
|
||||
override val frozenState: CardFrozenState,
|
||||
val cryptoBalance: String,
|
||||
val fiatBalance: String,
|
||||
val isBalanceFlickering: Boolean,
|
||||
|
|
@ -42,9 +45,16 @@ internal sealed class TangemPayDetailsBalanceBlockState {
|
|||
|
||||
data class Error(
|
||||
override val actionButtons: ImmutableList<ActionButtonConfig>,
|
||||
override val frozenState: CardFrozenState,
|
||||
) : TangemPayDetailsBalanceBlockState()
|
||||
}
|
||||
|
||||
sealed class CardFrozenState {
|
||||
data object Pending : CardFrozenState()
|
||||
data class Frozen(val onUnfreeze: () -> Unit) : CardFrozenState()
|
||||
data object Unfrozen : CardFrozenState()
|
||||
}
|
||||
|
||||
internal data class AddToWalletBlockState(
|
||||
val onClick: () -> Unit,
|
||||
val onClickClose: () -> Unit,
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ import com.tangem.core.decompose.ui.UiMessageSender
|
|||
import com.tangem.core.ui.clipboard.ClipboardManager
|
||||
import com.tangem.core.ui.extensions.TextReference
|
||||
import com.tangem.core.ui.message.SnackbarMessage
|
||||
import com.tangem.domain.pay.repository.CardDetailsRepository
|
||||
import com.tangem.domain.pay.repository.TangemPayCardDetailsRepository
|
||||
import com.tangem.features.tangempay.components.cardDetails.TangemPayCardDetailsBlockComponent
|
||||
import com.tangem.features.tangempay.details.impl.R
|
||||
import com.tangem.features.tangempay.entity.TangemPayCardDetailsBlockStateFactory
|
||||
|
|
@ -33,7 +33,7 @@ import javax.inject.Inject
|
|||
internal class TangemPayCardDetailsBlockModel @Inject constructor(
|
||||
paramsContainer: ParamsContainer,
|
||||
override val dispatchers: CoroutineDispatcherProvider,
|
||||
private val cardDetailsRepository: CardDetailsRepository,
|
||||
private val cardDetailsRepository: TangemPayCardDetailsRepository,
|
||||
private val clipboardManager: ClipboardManager,
|
||||
private val uiMessageSender: UiMessageSender,
|
||||
private val cardDetailsEventListener: CardDetailsEventListener,
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ import com.tangem.core.decompose.di.ModelScoped
|
|||
import com.tangem.core.decompose.model.Model
|
||||
import com.tangem.core.decompose.navigation.Router
|
||||
import com.tangem.domain.pay.model.SetPinResult
|
||||
import com.tangem.domain.pay.repository.CardDetailsRepository
|
||||
import com.tangem.domain.pay.repository.TangemPayCardDetailsRepository
|
||||
import com.tangem.features.tangempay.entity.TangemPayChangePinUM
|
||||
import kotlinx.coroutines.flow.*
|
||||
import com.tangem.features.tangempay.model.transformers.PinCodeChangeTransformer
|
||||
|
|
@ -22,7 +22,7 @@ import javax.inject.Inject
|
|||
internal class TangemPayChangePinModel @Inject constructor(
|
||||
override val dispatchers: CoroutineDispatcherProvider,
|
||||
private val router: Router,
|
||||
private val cardDetailsRepository: CardDetailsRepository,
|
||||
private val cardDetailsRepository: TangemPayCardDetailsRepository,
|
||||
) : Model() {
|
||||
|
||||
val uiState: StateFlow<TangemPayChangePinUM>
|
||||
|
|
|
|||
|
|
@ -12,24 +12,25 @@ import com.tangem.core.decompose.model.ParamsContainer
|
|||
import com.tangem.core.decompose.navigation.Router
|
||||
import com.tangem.core.decompose.ui.UiMessageSender
|
||||
import com.tangem.core.ui.components.containers.pullToRefresh.PullToRefreshConfig.ShowRefreshState
|
||||
import com.tangem.core.ui.extensions.resourceReference
|
||||
import com.tangem.core.ui.message.SnackbarMessage
|
||||
import com.tangem.domain.models.TokenReceiveConfig
|
||||
import com.tangem.domain.pay.TangemPayTopUpData
|
||||
import com.tangem.domain.pay.model.TangemPayCardBalance
|
||||
import com.tangem.domain.pay.repository.CardDetailsRepository
|
||||
import com.tangem.domain.pay.repository.TangemPayCardDetailsRepository
|
||||
import com.tangem.domain.visa.model.TangemPayTxHistoryItem
|
||||
import com.tangem.features.tangempay.components.AddFundsListener
|
||||
import com.tangem.features.tangempay.components.TangemPayDetailsContainerComponent
|
||||
import com.tangem.features.tangempay.details.impl.R
|
||||
import com.tangem.features.tangempay.entity.TangemPayDetailsErrorType
|
||||
import com.tangem.features.tangempay.entity.TangemPayDetailsNavigation
|
||||
import com.tangem.features.tangempay.entity.TangemPayDetailsStateFactory
|
||||
import com.tangem.features.tangempay.entity.TangemPayDetailsUM
|
||||
import com.tangem.features.tangempay.model.listener.CardDetailsEvent
|
||||
import com.tangem.features.tangempay.model.listener.CardDetailsEventListener
|
||||
import com.tangem.features.tangempay.model.transformers.DetailsAddToWalletBannerTransformer
|
||||
import com.tangem.features.tangempay.model.transformers.DetailsBalanceTransformer
|
||||
import com.tangem.features.tangempay.model.transformers.TangemPayDetailsRefreshTransformer
|
||||
import com.tangem.features.tangempay.model.transformers.*
|
||||
import com.tangem.features.tangempay.navigation.TangemPayDetailsInnerRoute
|
||||
import com.tangem.features.tangempay.utils.TangemPayErrorMessageFactory
|
||||
import com.tangem.features.tangempay.utils.TangemPayMessagesFactory
|
||||
import com.tangem.features.tangempay.utils.TangemPayTxHistoryUiActions
|
||||
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
||||
import com.tangem.utils.coroutines.JobHolder
|
||||
|
|
@ -48,7 +49,7 @@ internal class TangemPayDetailsModel @Inject constructor(
|
|||
paramsContainer: ParamsContainer,
|
||||
override val dispatchers: CoroutineDispatcherProvider,
|
||||
private val router: Router,
|
||||
private val cardDetailsRepository: CardDetailsRepository,
|
||||
private val cardDetailsRepository: TangemPayCardDetailsRepository,
|
||||
private val uiMessageSender: UiMessageSender,
|
||||
private val cardDetailsEventListener: CardDetailsEventListener,
|
||||
) : Model(), TangemPayTxHistoryUiActions, AddFundsListener {
|
||||
|
|
@ -60,7 +61,9 @@ internal class TangemPayDetailsModel @Inject constructor(
|
|||
onRefresh = ::onRefreshSwipe,
|
||||
onAddFunds = ::onClickAddFunds,
|
||||
onClickChangePin = ::onClickChangePin,
|
||||
isCardFrozen = params.config.isCardFrozen,
|
||||
onClickFreezeCard = ::onClickFreezeCard,
|
||||
onClickUnfreezeCard = ::onClickUnfreezeCard,
|
||||
)
|
||||
|
||||
val uiState: StateFlow<TangemPayDetailsUM>
|
||||
|
|
@ -84,7 +87,65 @@ internal class TangemPayDetailsModel @Inject constructor(
|
|||
}
|
||||
|
||||
private fun onClickFreezeCard() {
|
||||
// TODO [REDACTED_JIRA]
|
||||
uiMessageSender.send(TangemPayMessagesFactory.createFreezeCardMessage(onFreezeClicked = ::freezeCard))
|
||||
}
|
||||
|
||||
private fun onClickUnfreezeCard() {
|
||||
uiMessageSender.send(TangemPayMessagesFactory.createUnfreezeCardMessage(onUnfreezeClicked = ::unfreezeCard))
|
||||
}
|
||||
|
||||
private fun freezeCard() {
|
||||
modelScope.launch {
|
||||
uiState.update(TangemPayFreezeUnfreezeProcessTransformer)
|
||||
cardDetailsRepository.freezeCard(cardId = params.config.cardId)
|
||||
.onLeft {
|
||||
uiMessageSender.send(SnackbarMessage(resourceReference(R.string.tangem_pay_freeze_card_failed)))
|
||||
uiState.update(
|
||||
TangemPayFreezeUnfreezeStateTransformer(
|
||||
frozen = false,
|
||||
onFreezeClick = ::onClickFreezeCard,
|
||||
onUnfreezeClick = ::onClickUnfreezeCard,
|
||||
),
|
||||
)
|
||||
}
|
||||
.onRight {
|
||||
uiMessageSender.send(SnackbarMessage(resourceReference(R.string.tangem_pay_freeze_card_success)))
|
||||
uiState.update(
|
||||
TangemPayFreezeUnfreezeStateTransformer(
|
||||
frozen = true,
|
||||
onFreezeClick = ::onClickFreezeCard,
|
||||
onUnfreezeClick = ::onClickUnfreezeCard,
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun unfreezeCard() {
|
||||
modelScope.launch {
|
||||
uiState.update(TangemPayFreezeUnfreezeProcessTransformer)
|
||||
cardDetailsRepository.unfreezeCard(cardId = params.config.cardId)
|
||||
.onLeft {
|
||||
uiMessageSender.send(SnackbarMessage(resourceReference(R.string.tangem_pay_unfreeze_card_failed)))
|
||||
uiState.update(
|
||||
TangemPayFreezeUnfreezeStateTransformer(
|
||||
frozen = true,
|
||||
onFreezeClick = ::onClickFreezeCard,
|
||||
onUnfreezeClick = ::onClickUnfreezeCard,
|
||||
),
|
||||
)
|
||||
}
|
||||
.onRight {
|
||||
uiMessageSender.send(SnackbarMessage(resourceReference(R.string.tangem_pay_unfreeze_card_success)))
|
||||
uiState.update(
|
||||
TangemPayFreezeUnfreezeStateTransformer(
|
||||
frozen = false,
|
||||
onFreezeClick = ::onClickFreezeCard,
|
||||
onUnfreezeClick = ::onClickUnfreezeCard,
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun onClickAddFunds() {
|
||||
|
|
@ -188,6 +249,6 @@ internal class TangemPayDetailsModel @Inject constructor(
|
|||
}
|
||||
|
||||
private fun showBottomSheetError(type: TangemPayDetailsErrorType) {
|
||||
uiMessageSender.send(message = TangemPayErrorMessageFactory.createErrorMessage(type = type))
|
||||
uiMessageSender.send(message = TangemPayMessagesFactory.createErrorMessage(type = type))
|
||||
}
|
||||
}
|
||||
|
|
@ -18,7 +18,10 @@ internal class DetailsBalanceTransformer(
|
|||
override fun transform(prevState: TangemPayDetailsUM): TangemPayDetailsUM {
|
||||
val balance = when (balance) {
|
||||
is Either.Left<UniversalError> -> {
|
||||
TangemPayDetailsBalanceBlockState.Error(persistentListOf())
|
||||
TangemPayDetailsBalanceBlockState.Error(
|
||||
actionButtons = persistentListOf(),
|
||||
frozenState = prevState.balanceBlockState.frozenState,
|
||||
)
|
||||
}
|
||||
is Either.Right<TangemPayCardBalance> -> {
|
||||
TangemPayDetailsBalanceBlockState.Content(
|
||||
|
|
@ -27,6 +30,7 @@ internal class DetailsBalanceTransformer(
|
|||
// TODO [REDACTED_TASK_KEY]: Add crypto balance when the BFF is ready
|
||||
cryptoBalance = "",
|
||||
actionButtons = prevState.balanceBlockState.actionButtons,
|
||||
frozenState = prevState.balanceBlockState.frozenState,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ import com.tangem.features.tangempay.details.impl.R
|
|||
import com.tangem.features.tangempay.entity.TangemPayAddFundsItemUM
|
||||
import com.tangem.features.tangempay.entity.TangemPayAddFundsUM
|
||||
import com.tangem.features.tangempay.entity.TangemPayDetailsErrorType
|
||||
import com.tangem.features.tangempay.utils.TangemPayErrorMessageFactory
|
||||
import com.tangem.features.tangempay.utils.TangemPayMessagesFactory
|
||||
import com.tangem.utils.converter.Converter
|
||||
import kotlinx.collections.immutable.persistentListOf
|
||||
|
||||
|
|
@ -20,7 +20,7 @@ internal class TangemPayAddFundsUMConverter(
|
|||
TangemPayAddFundsUM(
|
||||
items = persistentListOf(),
|
||||
dismiss = listener::onDismissAddFunds,
|
||||
errorMessage = TangemPayErrorMessageFactory.createErrorMessage(
|
||||
errorMessage = TangemPayMessagesFactory.createErrorMessage(
|
||||
type = TangemPayDetailsErrorType.Receive,
|
||||
).messageBottomSheetUMV2,
|
||||
)
|
||||
|
|
|
|||
|
|
@ -0,0 +1,21 @@
|
|||
package com.tangem.features.tangempay.model.transformers
|
||||
|
||||
import com.tangem.features.tangempay.entity.CardFrozenState
|
||||
import com.tangem.features.tangempay.entity.TangemPayDetailsBalanceBlockState
|
||||
import com.tangem.features.tangempay.entity.TangemPayDetailsUM
|
||||
import com.tangem.utils.transformer.Transformer
|
||||
|
||||
internal object TangemPayFreezeUnfreezeProcessTransformer : Transformer<TangemPayDetailsUM> {
|
||||
override fun transform(prevState: TangemPayDetailsUM): TangemPayDetailsUM {
|
||||
val newBalanceBlockState = when (val balanceState = prevState.balanceBlockState) {
|
||||
is TangemPayDetailsBalanceBlockState.Error -> balanceState.copy(frozenState = CardFrozenState.Pending)
|
||||
is TangemPayDetailsBalanceBlockState.Loading -> balanceState.copy(frozenState = CardFrozenState.Pending)
|
||||
is TangemPayDetailsBalanceBlockState.Content -> balanceState.copy(
|
||||
isBalanceFlickering = true,
|
||||
frozenState = CardFrozenState.Pending,
|
||||
)
|
||||
}
|
||||
|
||||
return prevState.copy(balanceBlockState = newBalanceBlockState)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,73 @@
|
|||
package com.tangem.features.tangempay.model.transformers
|
||||
|
||||
import com.tangem.core.ui.components.dropdownmenu.TangemDropdownMenuItem
|
||||
import com.tangem.core.ui.extensions.resourceReference
|
||||
import com.tangem.core.ui.extensions.themedColor
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
import com.tangem.features.tangempay.details.impl.R
|
||||
import com.tangem.features.tangempay.entity.*
|
||||
import com.tangem.utils.transformer.Transformer
|
||||
import kotlinx.collections.immutable.ImmutableList
|
||||
import kotlinx.collections.immutable.toPersistentList
|
||||
|
||||
internal class TangemPayFreezeUnfreezeStateTransformer(
|
||||
private val frozen: Boolean,
|
||||
private val onFreezeClick: () -> Unit,
|
||||
private val onUnfreezeClick: () -> Unit,
|
||||
) : Transformer<TangemPayDetailsUM> {
|
||||
|
||||
private val cardFrozenState = if (frozen) CardFrozenState.Frozen(onUnfreezeClick) else CardFrozenState.Unfrozen
|
||||
private val removeType =
|
||||
if (frozen) TangemPayDetailsTopBarMenuItemType.FreezeCard else TangemPayDetailsTopBarMenuItemType.UnfreezeCard
|
||||
private val addType =
|
||||
if (frozen) TangemPayDetailsTopBarMenuItemType.UnfreezeCard else TangemPayDetailsTopBarMenuItemType.FreezeCard
|
||||
|
||||
override fun transform(prevState: TangemPayDetailsUM): TangemPayDetailsUM {
|
||||
val dropdownMenuItems = createUpdatedMenuItems(prevState.topBarConfig.items)
|
||||
val newBalanceBlockState = createUpdatedBalanceState(prevState.balanceBlockState)
|
||||
|
||||
return prevState.copy(
|
||||
topBarConfig = prevState.topBarConfig.copy(items = dropdownMenuItems),
|
||||
balanceBlockState = newBalanceBlockState,
|
||||
)
|
||||
}
|
||||
|
||||
private fun createUpdatedMenuItems(
|
||||
items: ImmutableList<TangemPayDetailsTopBarMenuItem>?,
|
||||
): ImmutableList<TangemPayDetailsTopBarMenuItem>? {
|
||||
return items
|
||||
?.filter { it.type != removeType }
|
||||
?.plus(createMenuItemToAdd())
|
||||
?.toPersistentList()
|
||||
}
|
||||
|
||||
private fun createMenuItemToAdd(): TangemPayDetailsTopBarMenuItem {
|
||||
return TangemPayDetailsTopBarMenuItem(
|
||||
type = addType,
|
||||
dropdownItem = TangemDropdownMenuItem(
|
||||
title = resourceReference(
|
||||
id = if (frozen) {
|
||||
R.string.tangempay_card_details_unfreeze_card
|
||||
} else {
|
||||
R.string.tangempay_card_details_freeze_card
|
||||
},
|
||||
),
|
||||
textColor = themedColor { TangemTheme.colors.text.primary1 },
|
||||
onClick = if (frozen) onUnfreezeClick else onFreezeClick,
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
private fun createUpdatedBalanceState(
|
||||
balanceState: TangemPayDetailsBalanceBlockState,
|
||||
): TangemPayDetailsBalanceBlockState {
|
||||
return when (balanceState) {
|
||||
is TangemPayDetailsBalanceBlockState.Error -> balanceState.copy(frozenState = cardFrozenState)
|
||||
is TangemPayDetailsBalanceBlockState.Loading -> balanceState.copy(frozenState = cardFrozenState)
|
||||
is TangemPayDetailsBalanceBlockState.Content -> balanceState.copy(
|
||||
isBalanceFlickering = false,
|
||||
frozenState = cardFrozenState,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -16,6 +16,7 @@ import androidx.compose.ui.graphics.vector.ImageVector
|
|||
import androidx.compose.ui.res.vectorResource
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.util.fastForEach
|
||||
import com.tangem.core.ui.components.SpacerH16
|
||||
import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfig
|
||||
import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfigContent
|
||||
|
|
@ -70,7 +71,7 @@ internal fun TangemPayAddFundsContent(state: TangemPayAddFundsUM) {
|
|||
color = TangemTheme.colors.text.tertiary,
|
||||
style = TangemTheme.typography.subtitle2,
|
||||
)
|
||||
state.items.forEach {
|
||||
state.items.fastForEach {
|
||||
key(it.title) {
|
||||
TangemPayTopUpItem(state = it)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -9,7 +9,9 @@ import androidx.compose.foundation.lazy.rememberLazyListState
|
|||
import androidx.compose.material3.*
|
||||
import androidx.compose.runtime.*
|
||||
import androidx.compose.runtime.saveable.rememberSaveable
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.platform.LocalDensity
|
||||
import androidx.compose.ui.platform.testTag
|
||||
import androidx.compose.ui.res.painterResource
|
||||
|
|
@ -20,6 +22,7 @@ import androidx.compose.ui.tooling.preview.datasource.CollectionPreviewParameter
|
|||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.util.fastForEach
|
||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||
import com.tangem.core.ui.components.PrimaryButton
|
||||
import com.tangem.core.ui.components.RectangleShimmer
|
||||
import com.tangem.core.ui.components.buttons.HorizontalActionChips
|
||||
import com.tangem.core.ui.components.buttons.actions.ActionButtonConfig
|
||||
|
|
@ -27,9 +30,12 @@ import com.tangem.core.ui.components.containers.pullToRefresh.PullToRefreshConfi
|
|||
import com.tangem.core.ui.components.containers.pullToRefresh.TangemPullToRefreshContainer
|
||||
import com.tangem.core.ui.components.dropdownmenu.TangemDropdownItem
|
||||
import com.tangem.core.ui.components.dropdownmenu.TangemDropdownMenu
|
||||
import com.tangem.core.ui.components.snackbar.TangemSnackbarHost
|
||||
import com.tangem.core.ui.components.text.applyBladeBrush
|
||||
import com.tangem.core.ui.extensions.*
|
||||
import com.tangem.core.ui.extensions.TextReference
|
||||
import com.tangem.core.ui.extensions.orMaskWithStars
|
||||
import com.tangem.core.ui.extensions.resourceReference
|
||||
import com.tangem.core.ui.extensions.stringResourceSafe
|
||||
import com.tangem.core.ui.res.TangemColorPalette
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
import com.tangem.core.ui.res.TangemThemePreview
|
||||
import com.tangem.core.ui.test.TokenDetailsTopBarTestTags
|
||||
|
|
@ -48,19 +54,12 @@ internal fun TangemPayDetailsScreen(
|
|||
txHistoryComponent: TangemPayTxHistoryComponent,
|
||||
cardDetailsBlockComponent: TangemPayCardDetailsBlockComponent,
|
||||
modifier: Modifier = Modifier,
|
||||
snackbarHostState: SnackbarHostState = remember { SnackbarHostState() },
|
||||
) {
|
||||
Scaffold(
|
||||
modifier = modifier,
|
||||
topBar = { TangemPayDetailsTopAppBar(config = state.topBarConfig) },
|
||||
contentWindowInsets = ScaffoldDefaults.contentWindowInsets.exclude(WindowInsets.navigationBars),
|
||||
containerColor = TangemTheme.colors.background.secondary,
|
||||
snackbarHost = {
|
||||
TangemSnackbarHost(
|
||||
modifier = Modifier.padding(start = 16.dp, end = 16.dp, bottom = 16.dp),
|
||||
hostState = snackbarHostState,
|
||||
)
|
||||
},
|
||||
) { scaffoldPaddings ->
|
||||
val listState = rememberLazyListState()
|
||||
val bottomBarHeight = with(LocalDensity.current) { WindowInsets.systemBars.getBottom(this).toDp() }
|
||||
|
|
@ -106,6 +105,20 @@ internal fun TangemPayDetailsScreen(
|
|||
item(TangemPayCardDetailsUM::class.java) {
|
||||
TangemPayCardDetailsBlockItem(component = cardDetailsBlockComponent, state = cardDetailsState)
|
||||
}
|
||||
when (val cardFrozenState = state.balanceBlockState.frozenState) {
|
||||
is CardFrozenState.Frozen -> item(CardFrozenState.Frozen::class.java) {
|
||||
PrimaryButton(
|
||||
modifier = Modifier
|
||||
.animateItem()
|
||||
.padding(horizontal = 16.dp)
|
||||
.padding(top = 12.dp)
|
||||
.fillMaxWidth(),
|
||||
text = stringResourceSafe(R.string.tangempay_card_details_unfreeze_card),
|
||||
onClick = cardFrozenState.onUnfreeze,
|
||||
)
|
||||
}
|
||||
else -> Unit
|
||||
}
|
||||
with(txHistoryComponent) { txHistoryContent(listState = listState, state = txHistoryState) }
|
||||
}
|
||||
}
|
||||
|
|
@ -127,14 +140,22 @@ internal fun TangemPayDetailsBalanceBlock(
|
|||
)
|
||||
.padding(vertical = 12.dp),
|
||||
) {
|
||||
Text(
|
||||
modifier = Modifier.padding(start = 12.dp),
|
||||
text = stringResourceSafe(R.string.tangempay_title),
|
||||
color = TangemTheme.colors.text.tertiary,
|
||||
style = TangemTheme.typography.subtitle2,
|
||||
)
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(horizontal = 12.dp),
|
||||
horizontalArrangement = Arrangement.SpaceBetween,
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
) {
|
||||
Text(
|
||||
text = stringResourceSafe(R.string.tangempay_title),
|
||||
color = TangemTheme.colors.text.tertiary,
|
||||
style = TangemTheme.typography.subtitle2,
|
||||
)
|
||||
TangemPayCardStatus(state = state.frozenState)
|
||||
}
|
||||
FiatBalance(
|
||||
modifier = Modifier.padding(start = 12.dp, top = 4.dp),
|
||||
modifier = Modifier.padding(start = 12.dp, top = 8.dp),
|
||||
state = state,
|
||||
isBalanceHidden = isBalanceHidden,
|
||||
)
|
||||
|
|
@ -155,6 +176,36 @@ internal fun TangemPayDetailsBalanceBlock(
|
|||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun TangemPayCardStatus(state: CardFrozenState, modifier: Modifier = Modifier) {
|
||||
when (state) {
|
||||
is CardFrozenState.Unfrozen -> Unit
|
||||
is CardFrozenState.Pending -> CircularProgressIndicator(
|
||||
modifier = modifier.size(18.dp),
|
||||
trackColor = Color.Transparent,
|
||||
color = TangemColorPalette.Azure,
|
||||
strokeWidth = 2.dp,
|
||||
)
|
||||
is CardFrozenState.Frozen -> Row(
|
||||
modifier = modifier,
|
||||
horizontalArrangement = Arrangement.spacedBy(4.dp),
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
) {
|
||||
Icon(
|
||||
painter = painterResource(id = R.drawable.ic_snow_24),
|
||||
tint = TangemTheme.colors.text.disabled,
|
||||
contentDescription = null,
|
||||
modifier = Modifier.size(16.dp),
|
||||
)
|
||||
Text(
|
||||
text = stringResourceSafe(R.string.tangem_pay_card_frozen),
|
||||
color = TangemTheme.colors.text.disabled,
|
||||
style = TangemTheme.typography.body2,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun FiatBalance(
|
||||
state: TangemPayDetailsBalanceBlockState,
|
||||
|
|
@ -173,18 +224,30 @@ private fun FiatBalance(
|
|||
text = state.fiatBalance.orMaskWithStars(isBalanceHidden),
|
||||
style = TangemTheme.typography.h2.applyBladeBrush(
|
||||
isEnabled = state.isBalanceFlickering,
|
||||
textColor = TangemTheme.colors.text.primary1,
|
||||
textColor = getTextColor(
|
||||
isCardFrozen = state.frozenState is CardFrozenState.Frozen,
|
||||
defaultColor = TangemTheme.colors.text.primary1,
|
||||
),
|
||||
),
|
||||
)
|
||||
is TangemPayDetailsBalanceBlockState.Error -> Text(
|
||||
modifier = modifier,
|
||||
text = DASH_SIGN.orMaskWithStars(isBalanceHidden),
|
||||
style = TangemTheme.typography.h2,
|
||||
color = TangemTheme.colors.text.primary1,
|
||||
color = getTextColor(
|
||||
isCardFrozen = state.frozenState is CardFrozenState.Frozen,
|
||||
defaultColor = TangemTheme.colors.text.primary1,
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@ReadOnlyComposable
|
||||
@Composable
|
||||
private fun getTextColor(isCardFrozen: Boolean, defaultColor: Color): Color {
|
||||
return if (isCardFrozen) TangemTheme.colors.text.tertiary else defaultColor
|
||||
}
|
||||
|
||||
@Suppress("UnusedPrivateMember")
|
||||
@Composable
|
||||
private fun CryptoBalance(
|
||||
|
|
@ -252,7 +315,7 @@ private fun TangemPayDetailsTopAppBar(config: TangemPayDetailsTopBarConfig, modi
|
|||
content = {
|
||||
config.items?.fastForEach {
|
||||
TangemDropdownItem(
|
||||
item = it,
|
||||
item = it.dropdownItem,
|
||||
dismissParent = { showDropdownMenu = false },
|
||||
)
|
||||
}
|
||||
|
|
@ -310,6 +373,7 @@ private class TangemPayDetailsUMProvider : CollectionPreviewParameterProvider<Ta
|
|||
cryptoBalance = "1234.56 USDT",
|
||||
fiatBalance = "$1234.56",
|
||||
isBalanceFlickering = false,
|
||||
frozenState = CardFrozenState.Frozen(onUnfreeze = {}),
|
||||
),
|
||||
addToWalletBlockState = AddToWalletBlockState({}, {}),
|
||||
isBalanceHidden = false,
|
||||
|
|
@ -318,7 +382,10 @@ private class TangemPayDetailsUMProvider : CollectionPreviewParameterProvider<Ta
|
|||
TangemPayDetailsUM(
|
||||
topBarConfig = TangemPayDetailsTopBarConfig(onBackClick = {}, items = null),
|
||||
pullToRefreshConfig = PullToRefreshConfig(isRefreshing = false, onRefresh = {}),
|
||||
balanceBlockState = TangemPayDetailsBalanceBlockState.Loading(actionButtons = persistentListOf()),
|
||||
balanceBlockState = TangemPayDetailsBalanceBlockState.Loading(
|
||||
actionButtons = persistentListOf(),
|
||||
frozenState = CardFrozenState.Pending,
|
||||
),
|
||||
addToWalletBlockState = AddToWalletBlockState({}, {}),
|
||||
isBalanceHidden = false,
|
||||
addFundsEnabled = true,
|
||||
|
|
|
|||
|
|
@ -1,30 +0,0 @@
|
|||
package com.tangem.features.tangempay.utils
|
||||
|
||||
import com.tangem.core.ui.R
|
||||
import com.tangem.core.ui.components.bottomsheets.message.*
|
||||
import com.tangem.core.ui.extensions.TextReference
|
||||
import com.tangem.core.ui.extensions.resourceReference
|
||||
import com.tangem.core.ui.message.BottomSheetMessageV2
|
||||
import com.tangem.core.ui.message.bottomSheetMessage
|
||||
import com.tangem.features.tangempay.entity.TangemPayDetailsErrorType
|
||||
|
||||
internal object TangemPayErrorMessageFactory {
|
||||
|
||||
fun createErrorMessage(type: TangemPayDetailsErrorType): BottomSheetMessageV2 {
|
||||
return when (type) {
|
||||
TangemPayDetailsErrorType.Receive -> bottomSheetMessage {
|
||||
infoBlock {
|
||||
icon(R.drawable.img_attention_20) {
|
||||
backgroundType = MessageBottomSheetUMV2.Icon.BackgroundType.Attention
|
||||
}
|
||||
title = TextReference.Res(R.string.tangempay_card_details_receive_error_title)
|
||||
body = TextReference.Res(R.string.tangempay_card_details_receive_error_description)
|
||||
}
|
||||
primaryButton {
|
||||
text = resourceReference(R.string.common_got_it)
|
||||
onClick { closeBs() }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,70 @@
|
|||
package com.tangem.features.tangempay.utils
|
||||
|
||||
import com.tangem.core.ui.R
|
||||
import com.tangem.core.ui.components.bottomsheets.message.*
|
||||
import com.tangem.core.ui.extensions.TextReference
|
||||
import com.tangem.core.ui.extensions.resourceReference
|
||||
import com.tangem.core.ui.message.BottomSheetMessageV2
|
||||
import com.tangem.core.ui.message.bottomSheetMessage
|
||||
import com.tangem.features.tangempay.entity.TangemPayDetailsErrorType
|
||||
|
||||
internal object TangemPayMessagesFactory {
|
||||
|
||||
fun createErrorMessage(type: TangemPayDetailsErrorType): BottomSheetMessageV2 {
|
||||
return when (type) {
|
||||
TangemPayDetailsErrorType.Receive -> bottomSheetMessage {
|
||||
infoBlock {
|
||||
icon(R.drawable.img_attention_20) {
|
||||
backgroundType = MessageBottomSheetUMV2.Icon.BackgroundType.Attention
|
||||
}
|
||||
title = TextReference.Res(R.string.tangempay_card_details_receive_error_title)
|
||||
body = TextReference.Res(R.string.tangempay_card_details_receive_error_description)
|
||||
}
|
||||
primaryButton {
|
||||
text = resourceReference(R.string.common_got_it)
|
||||
onClick { closeBs() }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun createFreezeCardMessage(onFreezeClicked: () -> Unit): BottomSheetMessageV2 {
|
||||
return bottomSheetMessage {
|
||||
infoBlock {
|
||||
icon(R.drawable.ic_snow_24) {
|
||||
type = MessageBottomSheetUMV2.Icon.Type.Accent
|
||||
backgroundType = MessageBottomSheetUMV2.Icon.BackgroundType.Accent
|
||||
}
|
||||
title = TextReference.Res(R.string.tangem_pay_freeze_card_alert_title)
|
||||
body = TextReference.Res(R.string.tangem_pay_freeze_card_alert_body)
|
||||
}
|
||||
primaryButton {
|
||||
text = resourceReference(R.string.tangem_pay_freeze_card_freeze)
|
||||
onClick {
|
||||
onFreezeClicked()
|
||||
closeBs()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun createUnfreezeCardMessage(onUnfreezeClicked: () -> Unit): BottomSheetMessageV2 {
|
||||
return bottomSheetMessage {
|
||||
infoBlock {
|
||||
icon(R.drawable.ic_snow_24) {
|
||||
type = MessageBottomSheetUMV2.Icon.Type.Accent
|
||||
backgroundType = MessageBottomSheetUMV2.Icon.BackgroundType.Accent
|
||||
}
|
||||
title = TextReference.Res(R.string.tangem_pay_unfreeze_card_alert_title)
|
||||
body = TextReference.Res(R.string.tangem_pay_unfreeze_card_alert_body)
|
||||
}
|
||||
primaryButton {
|
||||
text = resourceReference(R.string.tangempay_card_details_unfreeze_card)
|
||||
onClick {
|
||||
onUnfreezeClicked()
|
||||
closeBs()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -5,6 +5,7 @@ import com.tangem.core.ui.format.bigdecimal.fiat
|
|||
import com.tangem.core.ui.format.bigdecimal.format
|
||||
import com.tangem.domain.pay.TangemPayDetailsConfig
|
||||
import com.tangem.domain.pay.model.CustomerInfo.CardInfo
|
||||
import com.tangem.domain.pay.model.CustomerInfo.ProductInstance
|
||||
import com.tangem.domain.pay.model.MainScreenCustomerInfo
|
||||
import com.tangem.domain.pay.model.OrderStatus.CANCELED
|
||||
import com.tangem.domain.pay.model.OrderStatus.UNKNOWN
|
||||
|
|
@ -35,29 +36,36 @@ internal class TangemPayInitialStateTransformer(
|
|||
|
||||
private fun createInitialState(): TangemPayState {
|
||||
val cardInfo = value?.info?.cardInfo
|
||||
val productInstance = value?.info?.productInstance
|
||||
return when {
|
||||
value == null -> TangemPayState.Empty
|
||||
!value.info.isKycApproved -> createKycInProgressState(onClickKyc)
|
||||
cardInfo != null -> getCardInfoState(cardInfo)
|
||||
cardInfo != null && productInstance != null -> getCardInfoState(cardInfo, productInstance)
|
||||
value.orderStatus == UNKNOWN || value.orderStatus == CANCELED -> createIssueAvailableState(onClickIssue)
|
||||
else -> createIssueProgressState()
|
||||
}
|
||||
}
|
||||
|
||||
private fun getCardInfoState(cardInfo: CardInfo): TangemPayState = TangemPayState.Card(
|
||||
lastFourDigits = TextReference.Str("*${cardInfo.lastFourDigits}"),
|
||||
balanceText = TextReference.Str(getBalanceText(cardInfo)),
|
||||
onClick = {
|
||||
openDetails(
|
||||
TangemPayDetailsConfig(
|
||||
customerWalletAddress = cardInfo.customerWalletAddress,
|
||||
cardNumberEnd = cardInfo.lastFourDigits,
|
||||
chainId = POLYGON_CHAIN_ID,
|
||||
depositAddress = cardInfo.depositAddress,
|
||||
),
|
||||
)
|
||||
},
|
||||
)
|
||||
private fun getCardInfoState(cardInfo: CardInfo, productInstance: ProductInstance): TangemPayState =
|
||||
TangemPayState.Card(
|
||||
lastFourDigits = TextReference.Str("*${cardInfo.lastFourDigits}"),
|
||||
balanceText = TextReference.Str(getBalanceText(cardInfo)),
|
||||
onClick = {
|
||||
openDetails(
|
||||
TangemPayDetailsConfig(
|
||||
cardId = productInstance.cardId,
|
||||
isCardFrozen = when (productInstance.status) {
|
||||
ProductInstance.Status.ACTIVE -> false
|
||||
ProductInstance.Status.INACTIVE -> true
|
||||
},
|
||||
customerWalletAddress = cardInfo.customerWalletAddress,
|
||||
cardNumberEnd = cardInfo.lastFourDigits,
|
||||
chainId = POLYGON_CHAIN_ID,
|
||||
depositAddress = cardInfo.depositAddress,
|
||||
),
|
||||
)
|
||||
},
|
||||
)
|
||||
|
||||
private fun getBalanceText(cardInfo: CardInfo): String {
|
||||
val currency = Currency.getInstance(cardInfo.currencyCode)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue