Updated on 2026-08-14
This commit is contained in:
parent
ebe84a667e
commit
eb2c1d765a
31 changed files with 1034 additions and 123 deletions
|
|
@ -85,6 +85,18 @@ interface TangemPayApi {
|
|||
@Body body: FreezeUnfreezeCardRequest,
|
||||
): ApiResponse<FreezeUnfreezeCardResponse>
|
||||
|
||||
@GET("v1/fees/{type}")
|
||||
suspend fun getFee(
|
||||
@Header("Authorization") authHeader: String,
|
||||
@Path("type") type: String,
|
||||
): ApiResponse<FeeResponse>
|
||||
|
||||
@POST("v1/customer/card/reissue")
|
||||
suspend fun reissueCard(
|
||||
@Header("Authorization") authHeader: String,
|
||||
@Body body: ReissueCardRequest,
|
||||
): ApiResponse<ReissueCardResponse>
|
||||
|
||||
@POST("v1/customer/card/withdraw/data")
|
||||
suspend fun getWithdrawData(
|
||||
@Header("Authorization") authHeader: String,
|
||||
|
|
|
|||
|
|
@ -0,0 +1,9 @@
|
|||
package com.tangem.datasource.api.pay.models.request
|
||||
|
||||
import com.squareup.moshi.Json
|
||||
import com.squareup.moshi.JsonClass
|
||||
|
||||
@JsonClass(generateAdapter = true)
|
||||
data class ReissueCardRequest(
|
||||
@Json(name = "card_id") val cardId: String,
|
||||
)
|
||||
|
|
@ -0,0 +1,17 @@
|
|||
package com.tangem.datasource.api.pay.models.response
|
||||
|
||||
import com.squareup.moshi.Json
|
||||
import com.squareup.moshi.JsonClass
|
||||
|
||||
@JsonClass(generateAdapter = true)
|
||||
data class FeeResponse(
|
||||
@Json(name = "result") val result: Result,
|
||||
) {
|
||||
@JsonClass(generateAdapter = true)
|
||||
data class Result(
|
||||
@Json(name = "type") val type: String,
|
||||
@Json(name = "amount") val amount: String,
|
||||
@Json(name = "currency") val currency: String,
|
||||
@Json(name = "description") val description: String,
|
||||
)
|
||||
}
|
||||
|
|
@ -0,0 +1,15 @@
|
|||
package com.tangem.datasource.api.pay.models.response
|
||||
|
||||
import com.squareup.moshi.Json
|
||||
import com.squareup.moshi.JsonClass
|
||||
|
||||
@JsonClass(generateAdapter = true)
|
||||
data class ReissueCardResponse(
|
||||
@Json(name = "result") val result: Result,
|
||||
) {
|
||||
@JsonClass(generateAdapter = true)
|
||||
data class Result(
|
||||
@Json(name = "order_id") val orderId: String,
|
||||
@Json(name = "status") val status: String,
|
||||
)
|
||||
}
|
||||
|
|
@ -2,7 +2,9 @@ package com.tangem.datasource.di
|
|||
|
||||
import com.tangem.datasource.local.datastore.RuntimeDataStore
|
||||
import com.tangem.datasource.local.visa.DefaultTangemPayCardFrozenStateStore
|
||||
import com.tangem.datasource.local.visa.DefaultTangemPayReissueCardStore
|
||||
import com.tangem.datasource.local.visa.TangemPayCardFrozenStateStore
|
||||
import com.tangem.datasource.local.visa.TangemPayReissueCardStore
|
||||
import dagger.Module
|
||||
import dagger.Provides
|
||||
import dagger.hilt.InstallIn
|
||||
|
|
@ -20,4 +22,12 @@ internal object TangemPayStoresModule {
|
|||
dataStore = RuntimeDataStore(),
|
||||
)
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideTangemPayReissueCardStore(): TangemPayReissueCardStore {
|
||||
return DefaultTangemPayReissueCardStore(
|
||||
feeStore = RuntimeDataStore(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,30 @@
|
|||
package com.tangem.datasource.local.visa
|
||||
|
||||
import com.tangem.datasource.local.datastore.RuntimeDataStore
|
||||
import com.tangem.domain.models.TangemPayReissueCardFee
|
||||
import com.tangem.domain.models.wallet.UserWalletId
|
||||
|
||||
internal class DefaultTangemPayReissueCardStore(
|
||||
private val feeStore: RuntimeDataStore<TangemPayReissueCardFee>,
|
||||
) : TangemPayReissueCardStore {
|
||||
|
||||
override suspend fun storeReissueFee(
|
||||
userWalletId: UserWalletId,
|
||||
tangemPayReissueCardFee: TangemPayReissueCardFee,
|
||||
) {
|
||||
feeStore.store(userWalletId.stringValue, tangemPayReissueCardFee)
|
||||
}
|
||||
|
||||
override suspend fun getReissueFee(userWalletId: UserWalletId): TangemPayReissueCardFee? {
|
||||
return feeStore.getSyncOrNull(userWalletId.stringValue)
|
||||
}
|
||||
|
||||
override suspend fun storeReissueOrderId(cardId: String, orderId: String) {
|
||||
// TODO v_rodionov: #[REDACTED_TASK_KEY] store orderId in app prefs
|
||||
}
|
||||
|
||||
override suspend fun getOrderId(cardId: String): String? {
|
||||
// TODO v_rodionov: #[REDACTED_TASK_KEY] store orderId in app prefs
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,15 @@
|
|||
package com.tangem.datasource.local.visa
|
||||
|
||||
import com.tangem.domain.models.TangemPayReissueCardFee
|
||||
import com.tangem.domain.models.wallet.UserWalletId
|
||||
|
||||
interface TangemPayReissueCardStore {
|
||||
|
||||
suspend fun storeReissueFee(userWalletId: UserWalletId, tangemPayReissueCardFee: TangemPayReissueCardFee)
|
||||
|
||||
suspend fun getReissueFee(userWalletId: UserWalletId): TangemPayReissueCardFee?
|
||||
|
||||
suspend fun storeReissueOrderId(cardId: String, orderId: String)
|
||||
|
||||
suspend fun getOrderId(cardId: String): String?
|
||||
}
|
||||
|
|
@ -1556,6 +1556,17 @@
|
|||
<string name="tangem_pay_explore_transaction">Explore transaction</string>
|
||||
<string name="tangem_pay_fee_subtitle">Service fees</string>
|
||||
<string name="tangem_pay_fee_title">Fee</string>
|
||||
<string name="tangempay_card_details_reissue_card">Replace card</string>
|
||||
<string name="tangempay_reissue_card_title">Replace your card?</string>
|
||||
<string name="tangempay_reissue_card_description">This generates a new set of card details. Your old details will stop working. You can\'t undo this.</string>
|
||||
<string name="tangempay_reissue_card_fee_label">Replacement fee</string>
|
||||
<string name="tangempay_reissue_card_confirm">Replace card</string>
|
||||
<string name="tangempay_reissue_card_in_progress">Replacing your digital card</string>
|
||||
<string name="tangempay_reissue_card_in_progress_description">Usually takes up to 5 minutes. In rare cases, up to 48 hours.</string>
|
||||
<string name="tangempay_reissue_card_insufficient_funds">Insufficient funds to replace the card</string>
|
||||
<string name="tangempay_reissue_card_insufficient_funds_title">Unable to cover fee</string>
|
||||
<string name="tangempay_reissue_card_insufficient_funds_subtitle">Deposit USDC to payment account to cover the issuing fee</string>
|
||||
<string name="tangempay_reissue_card_fee_unreachable_error_title">Replacement fee info unreachable</string>
|
||||
<string name="tangem_pay_freeze_card_alert_body">Keep your money safe. 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>
|
||||
|
|
|
|||
9
core/ui/src/main/res/drawable/ic_update_32.xml
Normal file
9
core/ui/src/main/res/drawable/ic_update_32.xml
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="32dp"
|
||||
android:height="32dp"
|
||||
android:viewportWidth="32"
|
||||
android:viewportHeight="32">
|
||||
<path
|
||||
android:pathData="M11.888,8.087C11.238,7.437 11.382,6.343 12.241,6.015C12.558,5.894 12.878,5.789 13.201,5.7C14.09,5.456 15.001,5.333 15.934,5.333C18.912,5.333 21.445,6.367 23.534,8.433C25.623,10.5 26.667,13.022 26.667,16V16.233L27.868,15.033C28.383,14.518 29.219,14.518 29.734,15.033C30.25,15.549 30.25,16.385 29.734,16.9L26.401,20.233C25.812,20.823 24.857,20.823 24.267,20.233L20.934,16.9C20.419,16.385 20.419,15.549 20.934,15.033C21.45,14.518 22.285,14.518 22.801,15.033L24.001,16.233V16C24.001,13.778 23.218,11.889 21.651,10.333C20.084,8.778 18.179,8 15.934,8C15.356,8 14.79,8.067 14.234,8.2C13.997,8.257 13.762,8.326 13.528,8.407C12.96,8.605 12.314,8.513 11.888,8.087ZM2.267,16.967C1.752,16.451 1.752,15.616 2.267,15.1L6.075,11.293C6.402,10.965 6.933,10.965 7.26,11.293L11.068,15.1C11.583,15.616 11.583,16.451 11.068,16.967C10.552,17.482 9.716,17.482 9.201,16.967L8.001,15.767V16C8.001,18.222 8.784,20.111 10.351,21.667C11.917,23.222 13.823,24 16.067,24C16.645,24 17.212,23.933 17.767,23.8C18.005,23.743 18.24,23.674 18.473,23.593C19.042,23.395 19.688,23.487 20.114,23.913C20.763,24.563 20.619,25.657 19.761,25.985C19.444,26.106 19.124,26.211 18.801,26.3C17.912,26.545 17.001,26.667 16.067,26.667C13.09,26.667 10.556,25.633 8.467,23.567C6.379,21.5 5.334,18.978 5.334,16V15.767L4.134,16.967C3.619,17.482 2.783,17.482 2.267,16.967Z"
|
||||
android:fillColor="#000000"/>
|
||||
</vector>
|
||||
18
core/ui/src/main/res/drawable/img_usdc_16.xml
Normal file
18
core/ui/src/main/res/drawable/img_usdc_16.xml
Normal file
|
|
@ -0,0 +1,18 @@
|
|||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="16dp"
|
||||
android:height="16dp"
|
||||
android:viewportWidth="16"
|
||||
android:viewportHeight="16">
|
||||
<path
|
||||
android:pathData="M8,16c4.418,0 8,-3.582 8,-8 0,-4.418 -3.582,-8 -8,-8C3.582,0 0,3.582 0,8c0,4.418 3.582,8 8,8Z"
|
||||
android:strokeWidth="0.5"
|
||||
android:fillColor="#3e73c4"/>
|
||||
<path
|
||||
android:pathData="M10.011,9.062c0,-1.062 -0.64,-1.426 -1.92,-1.578 -0.914,-0.122 -1.097,-0.364 -1.097,-0.789 0,-0.425 0.305,-0.698 0.914,-0.698 0.549,0 0.854,0.182 1.005,0.637 0.016,0.044 0.045,0.082 0.083,0.109 0.038,0.027 0.084,0.042 0.131,0.042h0.488c0.028,0.001 0.056,-0.004 0.082,-0.015 0.026,-0.01 0.05,-0.026 0.07,-0.046 0.02,-0.02 0.036,-0.044 0.046,-0.07 0.011,-0.026 0.016,-0.054 0.015,-0.082v-0.03c-0.06,-0.33 -0.226,-0.63 -0.474,-0.855 -0.248,-0.225 -0.563,-0.362 -0.897,-0.389V4.571c0,-0.122 -0.091,-0.213 -0.243,-0.243h-0.458c-0.122,0 -0.213,0.091 -0.243,0.243V5.269c-0.914,0.121 -1.493,0.728 -1.493,1.487 0,1.001 0.609,1.395 1.889,1.548 0.854,0.152 1.128,0.334 1.128,0.82 0,0.485 -0.426,0.819 -1.005,0.819 -0.793,0 -1.066,-0.333 -1.158,-0.789 -0.03,-0.121 -0.122,-0.182 -0.213,-0.182h-0.518c-0.028,-0.001 -0.056,0.004 -0.082,0.015 -0.026,0.01 -0.05,0.026 -0.07,0.046 -0.02,0.02 -0.036,0.044 -0.046,0.07 -0.01,0.026 -0.016,0.054 -0.015,0.082v0.03c0.122,0.759 0.609,1.305 1.615,1.457v0.729c0,0.121 0.091,0.213 0.243,0.243h0.458c0.122,0 0.213,-0.091 0.243,-0.243V10.67c0.914,-0.152 1.523,-0.789 1.523,-1.609v0.001Z"
|
||||
android:strokeWidth="0.5"
|
||||
android:fillColor="#ffffff"/>
|
||||
<path
|
||||
android:pathData="M6.446,12.248c-2.377,-0.85 -3.596,-3.49 -2.712,-5.826 0.457,-1.275 1.462,-2.246 2.712,-2.701 0.122,-0.06 0.183,-0.152 0.183,-0.303v-0.425c0,-0.121 -0.06,-0.212 -0.183,-0.243 -0.031,0 -0.091,0 -0.122,0.03 -0.686,0.214 -1.322,0.562 -1.873,1.023 -0.551,0.461 -1.005,1.027 -1.336,1.664 -0.331,0.637 -0.533,1.334 -0.594,2.05 -0.061,0.716 0.02,1.437 0.239,2.121 0.548,1.7 1.859,3.005 3.565,3.551 0.122,0.06 0.244,0 0.274,-0.122 0.031,-0.03 0.031,-0.061 0.031,-0.122v-0.425c0,-0.091 -0.091,-0.212 -0.183,-0.273ZM9.676,2.78c-0.122,-0.061 -0.244,0 -0.274,0.121 -0.031,0.031 -0.031,0.061 -0.031,0.122v0.425c0,0.122 0.091,0.243 0.183,0.303 2.377,0.85 3.596,3.49 2.712,5.826 -0.457,1.275 -1.462,2.246 -2.712,2.701 -0.122,0.06 -0.183,0.152 -0.183,0.303v0.425c0,0.121 0.06,0.212 0.183,0.243 0.031,0 0.091,0 0.122,-0.03 0.686,-0.214 1.322,-0.562 1.873,-1.023 0.551,-0.461 1.005,-1.027 1.336,-1.664 0.331,-0.637 0.533,-1.334 0.594,-2.05 0.061,-0.716 -0.02,-1.437 -0.239,-2.121 -0.548,-1.73 -1.889,-3.035 -3.565,-3.581Z"
|
||||
android:strokeWidth="0.5"
|
||||
android:fillColor="#ffffff"/>
|
||||
</vector>
|
||||
|
|
@ -10,6 +10,7 @@ import com.tangem.data.pay.converter.PaymentAccountStatusValueDMConverter
|
|||
import com.tangem.data.pay.flow.DefaultPaymentAccountStatusFetcher
|
||||
import com.tangem.data.pay.flow.DefaultPaymentAccountStatusProducer
|
||||
import com.tangem.data.pay.repository.*
|
||||
import com.tangem.domain.pay.repository.TangemPayReissueCardRepository
|
||||
import com.tangem.data.pay.store.PaymentAccountStatusesStore
|
||||
import com.tangem.data.pay.usecase.DefaultGetTangemPayCurrencyStatusUseCase
|
||||
import com.tangem.data.pay.usecase.DefaultGetTangemPayCustomerIdUseCase
|
||||
|
|
@ -71,6 +72,10 @@ internal interface TangemPayDataModule {
|
|||
@Singleton
|
||||
fun bindCustomerOrderRepository(repository: DefaultCustomerOrderRepository): CustomerOrderRepository
|
||||
|
||||
@Binds
|
||||
@Singleton
|
||||
fun bindReissueCardRepository(repository: DefaultReissueCardRepository): TangemPayReissueCardRepository
|
||||
|
||||
@Binds
|
||||
@Singleton
|
||||
fun bindTangemPayCryptoCurrencyFactory(
|
||||
|
|
|
|||
|
|
@ -0,0 +1,104 @@
|
|||
package com.tangem.data.pay.repository
|
||||
|
||||
import arrow.core.Either
|
||||
import arrow.core.raise.either
|
||||
import arrow.core.right
|
||||
import com.tangem.core.error.UniversalError
|
||||
import com.tangem.datasource.api.pay.TangemPayApi
|
||||
import com.tangem.datasource.api.pay.models.request.ReissueCardRequest
|
||||
import com.tangem.datasource.api.pay.models.response.OrderResponse
|
||||
import com.tangem.datasource.local.visa.TangemPayReissueCardStore
|
||||
import com.tangem.domain.models.TangemPayReissueCardFee
|
||||
import com.tangem.domain.models.wallet.UserWalletId
|
||||
import com.tangem.domain.pay.model.OrderStatus
|
||||
import com.tangem.domain.pay.model.TangemPayReissueOrderInfo
|
||||
import com.tangem.domain.pay.repository.TangemPayReissueCardRepository
|
||||
import com.tangem.domain.visa.error.VisaApiError
|
||||
import com.tangem.utils.coroutines.runSuspendCatching
|
||||
import javax.inject.Inject
|
||||
|
||||
internal class DefaultReissueCardRepository @Inject constructor(
|
||||
private val tangemPayApi: TangemPayApi,
|
||||
private val requestHelper: TangemPayRequestPerformer,
|
||||
private val tangemPayReissueCardStore: TangemPayReissueCardStore,
|
||||
) : TangemPayReissueCardRepository {
|
||||
|
||||
override suspend fun getReissueCardFee(userWalletId: UserWalletId): Either<VisaApiError, TangemPayReissueCardFee> =
|
||||
either {
|
||||
runSuspendCatching {
|
||||
tangemPayReissueCardStore.getReissueFee(userWalletId)?.let { return Either.Right(it) }
|
||||
}
|
||||
|
||||
val response = requestHelper.performRequest(userWalletId) { authHeader ->
|
||||
tangemPayApi.getFee(
|
||||
authHeader = authHeader,
|
||||
type = CARD_REPLACEMENT_FEE_TYPE,
|
||||
)
|
||||
}.bind()
|
||||
|
||||
val result = response.result
|
||||
val fee = TangemPayReissueCardFee(
|
||||
amount = result.amount.toBigDecimal(),
|
||||
currencyCode = result.currency,
|
||||
)
|
||||
|
||||
runSuspendCatching {
|
||||
tangemPayReissueCardStore.storeReissueFee(userWalletId, fee)
|
||||
}
|
||||
|
||||
fee
|
||||
}
|
||||
|
||||
override suspend fun reissueCard(
|
||||
userWalletId: UserWalletId,
|
||||
cardId: String,
|
||||
): Either<VisaApiError, TangemPayReissueOrderInfo> = either {
|
||||
val response = requestHelper.performRequest(userWalletId) { authHeader ->
|
||||
tangemPayApi.reissueCard(
|
||||
authHeader = authHeader,
|
||||
body = ReissueCardRequest(cardId = cardId),
|
||||
)
|
||||
}.bind()
|
||||
|
||||
TangemPayReissueOrderInfo(response.result.orderId, OrderStatus.fromString(response.result.status))
|
||||
}
|
||||
|
||||
override suspend fun storeReissueOrderId(cardId: String, orderId: String): Either<UniversalError, Unit> =
|
||||
runSuspendCatching {
|
||||
tangemPayReissueCardStore.storeReissueOrderId(cardId, orderId)
|
||||
}.fold(
|
||||
onSuccess = { Unit.right() },
|
||||
onFailure = { Either.Left(VisaApiError.Unspecified) },
|
||||
)
|
||||
|
||||
override suspend fun getReissueOrderInfo(
|
||||
userWalletId: UserWalletId,
|
||||
cardId: String,
|
||||
): Either<UniversalError, TangemPayReissueOrderInfo?> = either {
|
||||
val orderId = runSuspendCatching { tangemPayReissueCardStore.getOrderId(cardId) }.getOrNull()
|
||||
|
||||
if (orderId == null) {
|
||||
return null.right()
|
||||
}
|
||||
|
||||
val order = requestHelper.performRequest(userWalletId) { authHeader ->
|
||||
tangemPayApi.getOrder(authHeader, orderId)
|
||||
}.bind()
|
||||
|
||||
val result = order.result ?: raise(VisaApiError.Unspecified)
|
||||
|
||||
TangemPayReissueOrderInfo(
|
||||
orderId = result.id,
|
||||
orderStatus = when (result.status) {
|
||||
OrderResponse.Result.Status.NEW -> OrderStatus.NEW
|
||||
OrderResponse.Result.Status.PROCESSING -> OrderStatus.PROCESSING
|
||||
OrderResponse.Result.Status.COMPLETED -> OrderStatus.COMPLETED
|
||||
OrderResponse.Result.Status.CANCELED -> OrderStatus.CANCELED
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
private companion object {
|
||||
const val CARD_REPLACEMENT_FEE_TYPE = "CARD_REPLACEMENT"
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
package com.tangem.domain.models
|
||||
|
||||
import java.math.BigDecimal
|
||||
|
||||
data class TangemPayReissueCardFee(
|
||||
val amount: BigDecimal,
|
||||
val currencyCode: String,
|
||||
)
|
||||
|
|
@ -1,9 +1,22 @@
|
|||
package com.tangem.domain.pay.model
|
||||
|
||||
import java.util.Locale
|
||||
|
||||
enum class OrderStatus {
|
||||
UNKNOWN, // TODO remove it after TangemPay accounts refactor TANGEM_PAY_ACCOUNTS_REFACTOR_ENABLED
|
||||
NEW,
|
||||
PROCESSING,
|
||||
COMPLETED,
|
||||
CANCELED,
|
||||
;
|
||||
|
||||
companion object {
|
||||
fun fromString(value: String) = when (value.uppercase(Locale.US)) {
|
||||
"NEW" -> NEW
|
||||
"PROCESSING" -> PROCESSING
|
||||
"COMPLETED" -> COMPLETED
|
||||
"CANCELED" -> CANCELED
|
||||
else -> UNKNOWN
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,6 @@
|
|||
package com.tangem.domain.pay.model
|
||||
|
||||
data class TangemPayReissueOrderInfo(
|
||||
val orderId: String,
|
||||
val orderStatus: OrderStatus,
|
||||
)
|
||||
|
|
@ -0,0 +1,22 @@
|
|||
package com.tangem.domain.pay.repository
|
||||
|
||||
import arrow.core.Either
|
||||
import com.tangem.core.error.UniversalError
|
||||
import com.tangem.domain.models.wallet.UserWalletId
|
||||
import com.tangem.domain.models.TangemPayReissueCardFee
|
||||
import com.tangem.domain.pay.model.TangemPayReissueOrderInfo
|
||||
import com.tangem.domain.visa.error.VisaApiError
|
||||
|
||||
interface TangemPayReissueCardRepository {
|
||||
|
||||
suspend fun getReissueCardFee(userWalletId: UserWalletId): Either<VisaApiError, TangemPayReissueCardFee>
|
||||
|
||||
suspend fun reissueCard(userWalletId: UserWalletId, cardId: String): Either<VisaApiError, TangemPayReissueOrderInfo>
|
||||
|
||||
suspend fun storeReissueOrderId(cardId: String, orderId: String): Either<UniversalError, Unit>
|
||||
|
||||
suspend fun getReissueOrderInfo(
|
||||
userWalletId: UserWalletId,
|
||||
cardId: String,
|
||||
): Either<UniversalError, TangemPayReissueOrderInfo?>
|
||||
}
|
||||
|
|
@ -192,6 +192,21 @@ sealed class TangemPayAnalyticsEvents(
|
|||
event = "Visa KYC Canceled",
|
||||
)
|
||||
|
||||
class ReplaceCardClicked : TangemPayAnalyticsEvents(
|
||||
categoryName = "Visa Screen",
|
||||
event = "Visa Replace Card Clicked",
|
||||
)
|
||||
|
||||
class ReplaceCardConfirmationPopupOpened : TangemPayAnalyticsEvents(
|
||||
categoryName = "Visa Screen",
|
||||
event = "Visa Replace Card Confirmation Popup Opened",
|
||||
)
|
||||
|
||||
class ReplaceCardConfirmed : TangemPayAnalyticsEvents(
|
||||
categoryName = "Visa Screen",
|
||||
event = "Visa Replace Card Confirmed",
|
||||
)
|
||||
|
||||
class MainVisaPermanentBannerClicked : TangemPayAnalyticsEvents(
|
||||
categoryName = "Visa Onboarding",
|
||||
event = "Visa Permanent Banner Clicked",
|
||||
|
|
|
|||
|
|
@ -14,6 +14,7 @@ import com.tangem.core.decompose.context.childByContext
|
|||
import com.tangem.core.decompose.navigation.inner.InnerRouter
|
||||
import com.tangem.core.ui.decompose.ComposableContentComponent
|
||||
import com.tangem.features.tangempay.navigation.TangemPayDetailsInnerRoute
|
||||
import com.tangem.features.tokenreceive.TokenReceiveComponent
|
||||
import dagger.assisted.Assisted
|
||||
import dagger.assisted.AssistedFactory
|
||||
import dagger.assisted.AssistedInject
|
||||
|
|
@ -21,6 +22,7 @@ import dagger.assisted.AssistedInject
|
|||
internal class DefaultTangemPayCardPageComponent @AssistedInject constructor(
|
||||
@Assisted private val appComponentContext: AppComponentContext,
|
||||
@Assisted private val params: TangemPayCardPageComponent.Params,
|
||||
private val tokenReceiveComponentFactory: TokenReceiveComponent.Factory,
|
||||
) : AppComponentContext by appComponentContext, TangemPayCardPageComponent {
|
||||
|
||||
private val stackNavigation = StackNavigation<TangemPayDetailsInnerRoute>()
|
||||
|
|
@ -56,6 +58,7 @@ internal class DefaultTangemPayCardPageComponent @AssistedInject constructor(
|
|||
TangemPayDetailsInnerRoute.Details -> TangemPayCardPageScreenComponent(
|
||||
appComponentContext = childByContext(componentContext = componentContext, router = innerRouter),
|
||||
params = params,
|
||||
tokenReceiveComponentFactory = tokenReceiveComponentFactory,
|
||||
)
|
||||
TangemPayDetailsInnerRoute.ChangePIN -> TangemPayChangePinComponent(
|
||||
appComponentContext = childByContext(componentContext = componentContext, router = innerRouter),
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
|||
import com.arkivanov.decompose.ComponentContext
|
||||
import com.arkivanov.decompose.extensions.compose.subscribeAsState
|
||||
import com.arkivanov.decompose.router.slot.childSlot
|
||||
import com.arkivanov.decompose.router.slot.dismiss
|
||||
import com.tangem.core.decompose.context.AppComponentContext
|
||||
import com.tangem.core.decompose.context.child
|
||||
import com.tangem.core.decompose.context.childByContext
|
||||
|
|
@ -19,10 +20,12 @@ import com.tangem.features.tangempay.components.cardDetails.TangemPayCardDetails
|
|||
import com.tangem.features.tangempay.entity.TangemPayDetailsNavigation
|
||||
import com.tangem.features.tangempay.model.TangemPayCardPageModel
|
||||
import com.tangem.features.tangempay.ui.TangemPayCardPageScreen
|
||||
import com.tangem.features.tokenreceive.TokenReceiveComponent
|
||||
|
||||
internal class TangemPayCardPageScreenComponent(
|
||||
private val appComponentContext: AppComponentContext,
|
||||
private val params: TangemPayCardPageComponent.Params,
|
||||
private val tokenReceiveComponentFactory: TokenReceiveComponent.Factory,
|
||||
) : AppComponentContext by appComponentContext, ComposableContentComponent {
|
||||
|
||||
private val model: TangemPayCardPageModel = getOrCreateModel(params = params)
|
||||
|
|
@ -57,7 +60,9 @@ internal class TangemPayCardPageScreenComponent(
|
|||
TangemPayCardPageScreen(
|
||||
state = state,
|
||||
cardDetailsBlockComponent = cardDetailsBlockComponent,
|
||||
cardDetailsState = cardDetailsState,
|
||||
cardDetailsState = cardDetailsState.copy(
|
||||
isActive = !state.isReissueInProgress,
|
||||
),
|
||||
modifier = modifier,
|
||||
)
|
||||
bottomSheet.child?.instance?.BottomSheet()
|
||||
|
|
@ -77,6 +82,32 @@ internal class TangemPayCardPageScreenComponent(
|
|||
listener = model,
|
||||
),
|
||||
)
|
||||
is TangemPayDetailsNavigation.ReissueCard -> TangemPayReissueCardComponent(
|
||||
appComponentContext = context,
|
||||
params = TangemPayReissueCardComponent.Params(
|
||||
listener = model,
|
||||
userWalletId = params.userWalletId,
|
||||
cardId = params.config.cardId,
|
||||
),
|
||||
)
|
||||
is TangemPayDetailsNavigation.AddFunds -> TangemPayAddFundsComponent(
|
||||
appComponentContext = context,
|
||||
params = TangemPayAddFundsComponent.Params(
|
||||
listener = model,
|
||||
walletId = navigation.walletId,
|
||||
cryptoBalance = navigation.cryptoBalance,
|
||||
fiatBalance = navigation.fiatBalance,
|
||||
depositAddress = navigation.depositAddress,
|
||||
chainId = navigation.chainId,
|
||||
),
|
||||
)
|
||||
is TangemPayDetailsNavigation.Receive -> tokenReceiveComponentFactory.create(
|
||||
context = context,
|
||||
params = TokenReceiveComponent.Params(
|
||||
config = navigation.config,
|
||||
onDismiss = model.bottomSheetNavigation::dismiss,
|
||||
),
|
||||
)
|
||||
else -> error("Unsupported bottom sheet navigation: $navigation")
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -130,6 +130,7 @@ internal class TangemPayDetailsComponent(
|
|||
listener = model,
|
||||
),
|
||||
)
|
||||
else -> error("Unsupported bottom sheet navigation: $navigation")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,40 @@
|
|||
package com.tangem.features.tangempay.components
|
||||
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||
import com.tangem.core.decompose.context.AppComponentContext
|
||||
import com.tangem.core.decompose.model.getOrCreateModel
|
||||
import com.tangem.core.ui.decompose.ComposableBottomSheetComponent
|
||||
import com.tangem.domain.models.wallet.UserWalletId
|
||||
import com.tangem.domain.pay.model.TangemPayReissueOrderInfo
|
||||
import com.tangem.features.tangempay.model.TangemPayReissueCardModel
|
||||
import com.tangem.features.tangempay.ui.TangemPayReissueCardContent
|
||||
|
||||
internal class TangemPayReissueCardComponent(
|
||||
appComponentContext: AppComponentContext,
|
||||
private val params: Params,
|
||||
) : ComposableBottomSheetComponent, AppComponentContext by appComponentContext {
|
||||
|
||||
private val model: TangemPayReissueCardModel = getOrCreateModel(params = params)
|
||||
|
||||
override fun dismiss() = model.onDismiss()
|
||||
|
||||
@Composable
|
||||
override fun BottomSheet() {
|
||||
val state by model.state.collectAsStateWithLifecycle()
|
||||
TangemPayReissueCardContent(state = state)
|
||||
}
|
||||
|
||||
data class Params(
|
||||
val listener: ReissueCardListener,
|
||||
val userWalletId: UserWalletId,
|
||||
val cardId: String,
|
||||
)
|
||||
}
|
||||
|
||||
internal interface ReissueCardListener {
|
||||
fun onReissueOrderCreate(order: TangemPayReissueOrderInfo)
|
||||
fun onDismissReissueCard()
|
||||
fun onClickAddFunds()
|
||||
}
|
||||
|
|
@ -11,6 +11,7 @@ import com.tangem.features.tangempay.model.TangemPayDetailsModel
|
|||
import com.tangem.features.tangempay.model.TangemPayEditDisplayNameModel
|
||||
import com.tangem.features.tangempay.model.TangemPayTxHistoryDetailsModel
|
||||
import com.tangem.features.tangempay.model.TangemPayTxHistoryModel
|
||||
import com.tangem.features.tangempay.model.TangemPayReissueCardModel
|
||||
import com.tangem.features.tangempay.model.TangemPayViewPinModel
|
||||
import dagger.Binds
|
||||
import dagger.Module
|
||||
|
|
@ -71,4 +72,9 @@ internal interface TangemPayModelModule {
|
|||
@IntoMap
|
||||
@ClassKey(TangemPayEditDisplayNameModel::class)
|
||||
fun bindTangemPayEditDisplayNameModel(model: TangemPayEditDisplayNameModel): Model
|
||||
|
||||
@Binds
|
||||
@IntoMap
|
||||
@ClassKey(TangemPayReissueCardModel::class)
|
||||
fun bindTangemPayReissueCardModel(model: TangemPayReissueCardModel): Model
|
||||
}
|
||||
|
|
@ -1,39 +1,37 @@
|
|||
package com.tangem.features.tangempay.entity
|
||||
|
||||
import androidx.compose.runtime.Immutable
|
||||
import com.tangem.core.ui.extensions.TextReference
|
||||
import kotlinx.collections.immutable.ImmutableList
|
||||
import kotlinx.collections.immutable.persistentListOf
|
||||
|
||||
@Immutable
|
||||
internal data class TangemPayCardPageUM(
|
||||
val addToWalletBlockState: AddToWalletBlockState? = null,
|
||||
val settings: ImmutableList<TangemPayCardPageSetting> = persistentListOf(
|
||||
TangemPayCardPageSetting.ChangePIN,
|
||||
TangemPayCardPageSetting.FreezeCard,
|
||||
),
|
||||
val settings: ImmutableList<TangemPayCardPageSetting>,
|
||||
val onBackClick: () -> Unit,
|
||||
val onSettingClick: (TangemPayCardPageSetting) -> Unit,
|
||||
val addToWalletBlockState: AddToWalletBlockState? = null,
|
||||
val isReissueInProgress: Boolean = false,
|
||||
) {
|
||||
companion object {
|
||||
fun stub(
|
||||
addToWalletBlockState: AddToWalletBlockState? = AddToWalletBlockState(onClick = {}, onClickClose = {}),
|
||||
settings: ImmutableList<TangemPayCardPageSetting> = persistentListOf(
|
||||
TangemPayCardPageSetting.ChangePIN,
|
||||
TangemPayCardPageSetting.FreezeCard,
|
||||
TangemPayCardPageSetting.ReplaceCard,
|
||||
TangemPayCardPageSetting(TextReference.Str("Pin Code")) {},
|
||||
TangemPayCardPageSetting(TextReference.Str("Freeze Card")) {},
|
||||
TangemPayCardPageSetting(TextReference.Str("Reissue Card")) {},
|
||||
),
|
||||
isReissueInProgress: Boolean = false,
|
||||
) = TangemPayCardPageUM(
|
||||
addToWalletBlockState = addToWalletBlockState,
|
||||
settings = settings,
|
||||
onBackClick = {},
|
||||
onSettingClick = {},
|
||||
isReissueInProgress = isReissueInProgress,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Immutable
|
||||
internal sealed class TangemPayCardPageSetting {
|
||||
data object ChangePIN : TangemPayCardPageSetting()
|
||||
data object FreezeCard : TangemPayCardPageSetting()
|
||||
data object ReplaceCard : TangemPayCardPageSetting()
|
||||
}
|
||||
internal data class TangemPayCardPageSetting(
|
||||
val title: TextReference,
|
||||
val onSettingClick: () -> Unit,
|
||||
)
|
||||
|
|
@ -32,4 +32,7 @@ internal sealed class TangemPayDetailsNavigation {
|
|||
val userWalletId: UserWalletId,
|
||||
val cardId: String,
|
||||
) : TangemPayDetailsNavigation()
|
||||
|
||||
@Serializable
|
||||
data object ReissueCard : TangemPayDetailsNavigation()
|
||||
}
|
||||
|
|
@ -31,6 +31,7 @@ internal data class TangemPayCardDetailsUM(
|
|||
val isLoading: Boolean = false,
|
||||
val cardFrozenState: TangemPayCardFrozenState,
|
||||
val displayNameState: DisplayNameState?,
|
||||
val isActive: Boolean = true,
|
||||
)
|
||||
|
||||
internal sealed interface DisplayNameState {
|
||||
|
|
|
|||
|
|
@ -0,0 +1,37 @@
|
|||
package com.tangem.features.tangempay.entity
|
||||
|
||||
import androidx.compose.runtime.Immutable
|
||||
|
||||
@Immutable
|
||||
internal data class TangemPayReissueCardUM(
|
||||
val feeAmount: String,
|
||||
val isFeeLoading: Boolean,
|
||||
val isReissuingInProgress: Boolean,
|
||||
val error: TangemPayReissueCardError?,
|
||||
val onConfirmClick: () -> Unit,
|
||||
val onRetryFee: () -> Unit,
|
||||
val onAddFundsClick: () -> Unit,
|
||||
val onDismissRequest: () -> Unit,
|
||||
) {
|
||||
companion object {
|
||||
fun stub(
|
||||
feeAmount: String = "$4.25",
|
||||
isFeeLoading: Boolean = false,
|
||||
error: TangemPayReissueCardError = TangemPayReissueCardError.InitialDataLoading,
|
||||
isReissuingInProgress: Boolean = false,
|
||||
) = TangemPayReissueCardUM(
|
||||
feeAmount = feeAmount,
|
||||
isFeeLoading = isFeeLoading,
|
||||
error = error,
|
||||
isReissuingInProgress = isReissuingInProgress,
|
||||
onConfirmClick = {},
|
||||
onRetryFee = {},
|
||||
onAddFundsClick = {},
|
||||
onDismissRequest = {},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
internal enum class TangemPayReissueCardError {
|
||||
InsufficientFunds, InitialDataLoading
|
||||
}
|
||||
|
|
@ -4,15 +4,27 @@ import androidx.compose.runtime.Stable
|
|||
import com.arkivanov.decompose.router.slot.SlotNavigation
|
||||
import com.arkivanov.decompose.router.slot.activate
|
||||
import com.arkivanov.decompose.router.slot.dismiss
|
||||
import com.tangem.common.routing.AppRoute
|
||||
import com.tangem.core.analytics.api.AnalyticsEventHandler
|
||||
import com.tangem.core.analytics.models.AnalyticsParam
|
||||
import com.tangem.core.decompose.di.ModelScoped
|
||||
import com.tangem.core.decompose.model.Model
|
||||
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.extensions.TextReference
|
||||
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.model.OrderStatus
|
||||
import com.tangem.domain.pay.model.TangemPayReissueOrderInfo
|
||||
import com.tangem.domain.pay.model.TangemPayTopUpData
|
||||
import com.tangem.domain.pay.repository.TangemPayCardDetailsRepository
|
||||
import com.tangem.domain.pay.repository.TangemPayReissueCardRepository
|
||||
import com.tangem.domain.tangempay.TangemPayAnalyticsEvents
|
||||
import com.tangem.domain.visa.model.TangemPayCardFrozenState
|
||||
import com.tangem.features.tangempay.components.AddFundsListener
|
||||
import com.tangem.features.tangempay.components.ReissueCardListener
|
||||
import com.tangem.features.tangempay.components.TangemPayCardPageComponent
|
||||
import com.tangem.features.tangempay.components.ViewPinListener
|
||||
import com.tangem.features.tangempay.details.impl.R
|
||||
|
|
@ -25,6 +37,7 @@ import com.tangem.features.tangempay.utils.TangemPayMessagesFactory
|
|||
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
||||
import com.tangem.utils.coroutines.JobHolder
|
||||
import com.tangem.utils.coroutines.saveIn
|
||||
import kotlinx.collections.immutable.persistentListOf
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
import kotlinx.coroutines.flow.launchIn
|
||||
|
|
@ -33,43 +46,55 @@ import kotlinx.coroutines.flow.update
|
|||
import kotlinx.coroutines.launch
|
||||
import javax.inject.Inject
|
||||
|
||||
@Suppress("LongParameterList")
|
||||
@Stable
|
||||
@ModelScoped
|
||||
internal class TangemPayCardPageModel @Inject constructor(
|
||||
paramsContainer: ParamsContainer,
|
||||
override val dispatchers: CoroutineDispatcherProvider,
|
||||
private val router: Router,
|
||||
private val analytics: AnalyticsEventHandler,
|
||||
private val cardDetailsRepository: TangemPayCardDetailsRepository,
|
||||
private val uiMessageSender: UiMessageSender,
|
||||
) : Model(), ViewPinListener {
|
||||
private val reissueCardRepository: TangemPayReissueCardRepository,
|
||||
) : Model(), ViewPinListener, ReissueCardListener, AddFundsListener {
|
||||
|
||||
private val params: TangemPayCardPageComponent.Params = paramsContainer.require()
|
||||
|
||||
private var currentFrozenState: TangemPayCardFrozenState = params.config.cardFrozenState
|
||||
|
||||
private val addToWalletBannerJobHolder = JobHolder()
|
||||
private val addFundsJobHolder = JobHolder()
|
||||
|
||||
val uiState: StateFlow<TangemPayCardPageUM>
|
||||
field = MutableStateFlow(
|
||||
TangemPayCardPageUM(
|
||||
onBackClick = router::pop,
|
||||
onSettingClick = ::onSettingClick,
|
||||
settings = persistentListOf(
|
||||
TangemPayCardPageSetting(
|
||||
title = TextReference.Res(R.string.tangempay_card_details_change_pin),
|
||||
onSettingClick = ::onClickChangePIN,
|
||||
),
|
||||
TangemPayCardPageSetting(
|
||||
title = TextReference.Res(R.string.tangempay_card_details_freeze_card),
|
||||
onSettingClick = ::onClickFreezeOrUnfreezeCard,
|
||||
),
|
||||
TangemPayCardPageSetting(
|
||||
title = TextReference.Res(R.string.tangempay_card_details_reissue_card),
|
||||
onSettingClick = ::onClickReissueCard,
|
||||
),
|
||||
),
|
||||
),
|
||||
)
|
||||
|
||||
val bottomSheetNavigation: SlotNavigation<TangemPayDetailsNavigation> = SlotNavigation()
|
||||
|
||||
init {
|
||||
// TODO v_rodionov: #[REDACTED_TASK_KEY] check reissue order state before card details are showed
|
||||
fetchAddToWalletBanner()
|
||||
subscribeToCardFrozenState()
|
||||
}
|
||||
|
||||
private fun onSettingClick(setting: TangemPayCardPageSetting) = when (setting) {
|
||||
TangemPayCardPageSetting.ChangePIN -> onClickChangePIN()
|
||||
TangemPayCardPageSetting.FreezeCard -> onClickFreezeOrUnfreezeCard()
|
||||
TangemPayCardPageSetting.ReplaceCard -> Unit // TODO v_rodionov #[REDACTED_TASK_KEY]
|
||||
}
|
||||
|
||||
private fun onClickChangePIN() {
|
||||
if (!params.config.isPinSet) {
|
||||
router.push(TangemPayDetailsInnerRoute.ChangePIN)
|
||||
|
|
@ -94,6 +119,82 @@ internal class TangemPayCardPageModel @Inject constructor(
|
|||
}
|
||||
}
|
||||
|
||||
private fun onClickReissueCard() {
|
||||
analytics.send(TangemPayAnalyticsEvents.ReplaceCardClicked())
|
||||
bottomSheetNavigation.activate(TangemPayDetailsNavigation.ReissueCard)
|
||||
}
|
||||
|
||||
override fun onReissueOrderCreate(order: TangemPayReissueOrderInfo) {
|
||||
bottomSheetNavigation.dismiss()
|
||||
onReissueOrderStatusReceived(order.orderStatus)
|
||||
if (order.orderStatus != OrderStatus.CANCELED) {
|
||||
modelScope.launch {
|
||||
reissueCardRepository.storeReissueOrderId(params.config.cardId, order.orderId)
|
||||
}
|
||||
} else {
|
||||
uiMessageSender.send(SnackbarMessage(resourceReference(R.string.common_something_went_wrong)))
|
||||
}
|
||||
}
|
||||
|
||||
override fun onDismissReissueCard() {
|
||||
bottomSheetNavigation.dismiss()
|
||||
}
|
||||
|
||||
override fun onClickAddFunds() {
|
||||
bottomSheetNavigation.dismiss()
|
||||
modelScope.launch {
|
||||
val balance = cardDetailsRepository.getCardBalance(params.userWalletId).getOrNull()
|
||||
val depositAddress = balance?.depositAddress
|
||||
if (balance == null || depositAddress == null) {
|
||||
uiMessageSender.send(SnackbarMessage(resourceReference(R.string.common_error)))
|
||||
return@launch
|
||||
}
|
||||
bottomSheetNavigation.activate(
|
||||
TangemPayDetailsNavigation.AddFunds(
|
||||
walletId = params.userWalletId,
|
||||
fiatBalance = balance.fiatBalance,
|
||||
cryptoBalance = balance.cryptoBalance,
|
||||
depositAddress = depositAddress,
|
||||
chainId = params.config.chainId,
|
||||
),
|
||||
)
|
||||
}.saveIn(addFundsJobHolder)
|
||||
}
|
||||
|
||||
override fun onClickReceive(data: TangemPayTopUpData) {
|
||||
bottomSheetNavigation.dismiss()
|
||||
val config = TokenReceiveConfig(
|
||||
shouldShowWarning = true,
|
||||
cryptoCurrency = data.currency,
|
||||
userWalletId = data.walletId,
|
||||
showMemoDisclaimer = false,
|
||||
receiveAddress = data.receiveAddress,
|
||||
)
|
||||
bottomSheetNavigation.activate(TangemPayDetailsNavigation.Receive(config))
|
||||
}
|
||||
|
||||
override fun onClickSwap(data: TangemPayTopUpData) {
|
||||
bottomSheetNavigation.dismiss()
|
||||
router.push(
|
||||
AppRoute.Swap(
|
||||
currencyFrom = data.currency,
|
||||
userWalletId = data.walletId,
|
||||
isInitialReverseOrder = true,
|
||||
screenSource = AnalyticsParam.ScreensSources.TangemPay.value,
|
||||
tangemPayInput = AppRoute.Swap.TangemPayInput(
|
||||
cryptoAmount = data.cryptoBalance,
|
||||
fiatAmount = data.fiatBalance,
|
||||
depositAddress = data.depositAddress,
|
||||
isWithdrawal = false,
|
||||
),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
override fun onDismissAddFunds() {
|
||||
bottomSheetNavigation.dismiss()
|
||||
}
|
||||
|
||||
private fun freezeCard() {
|
||||
modelScope.launch {
|
||||
cardDetailsRepository.freezeCard(
|
||||
|
|
@ -174,4 +275,18 @@ internal class TangemPayCardPageModel @Inject constructor(
|
|||
override fun onDismissViewPin() {
|
||||
bottomSheetNavigation.dismiss()
|
||||
}
|
||||
|
||||
private fun onReissueOrderStatusReceived(orderStatus: OrderStatus) {
|
||||
when (orderStatus) {
|
||||
OrderStatus.NEW, OrderStatus.PROCESSING, OrderStatus.COMPLETED, OrderStatus.UNKNOWN -> {
|
||||
uiState.update { state ->
|
||||
state.copy(
|
||||
addToWalletBlockState = null,
|
||||
isReissueInProgress = true,
|
||||
)
|
||||
}
|
||||
}
|
||||
OrderStatus.CANCELED -> Unit
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,119 @@
|
|||
package com.tangem.features.tangempay.model
|
||||
|
||||
import androidx.compose.runtime.Stable
|
||||
import com.tangem.core.analytics.api.AnalyticsEventHandler
|
||||
import com.tangem.core.decompose.di.ModelScoped
|
||||
import com.tangem.core.decompose.model.Model
|
||||
import com.tangem.core.decompose.model.ParamsContainer
|
||||
import com.tangem.core.decompose.ui.UiMessageSender
|
||||
import com.tangem.core.ui.extensions.resourceReference
|
||||
import com.tangem.core.ui.format.bigdecimal.fiat
|
||||
import com.tangem.core.ui.format.bigdecimal.format
|
||||
import com.tangem.core.ui.format.bigdecimal.getJavaCurrencyByCode
|
||||
import com.tangem.core.ui.message.SnackbarMessage
|
||||
import com.tangem.domain.pay.repository.TangemPayCardDetailsRepository
|
||||
import com.tangem.domain.pay.repository.TangemPayReissueCardRepository
|
||||
import com.tangem.domain.tangempay.TangemPayAnalyticsEvents
|
||||
import com.tangem.features.tangempay.components.TangemPayReissueCardComponent
|
||||
import com.tangem.features.tangempay.details.impl.R
|
||||
import com.tangem.features.tangempay.entity.TangemPayReissueCardError
|
||||
import com.tangem.features.tangempay.entity.TangemPayReissueCardUM
|
||||
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
||||
import com.tangem.utils.coroutines.JobHolder
|
||||
import com.tangem.utils.coroutines.saveIn
|
||||
import kotlinx.coroutines.async
|
||||
import kotlinx.coroutines.coroutineScope
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
import kotlinx.coroutines.flow.update
|
||||
import kotlinx.coroutines.launch
|
||||
import javax.inject.Inject
|
||||
|
||||
@Stable
|
||||
@ModelScoped
|
||||
internal class TangemPayReissueCardModel @Inject constructor(
|
||||
paramsContainer: ParamsContainer,
|
||||
override val dispatchers: CoroutineDispatcherProvider,
|
||||
private val cardDetailsRepository: TangemPayCardDetailsRepository,
|
||||
private val reissueCardRepository: TangemPayReissueCardRepository,
|
||||
private val uiMessageSender: UiMessageSender,
|
||||
private val analytics: AnalyticsEventHandler,
|
||||
) : Model() {
|
||||
|
||||
private val params = paramsContainer.require<TangemPayReissueCardComponent.Params>()
|
||||
private val reissueJobHolder = JobHolder()
|
||||
private val loadDataJobHolder = JobHolder()
|
||||
|
||||
val state: StateFlow<TangemPayReissueCardUM>
|
||||
field = MutableStateFlow(
|
||||
TangemPayReissueCardUM(
|
||||
feeAmount = "",
|
||||
isFeeLoading = true,
|
||||
error = null,
|
||||
isReissuingInProgress = false,
|
||||
onConfirmClick = ::onConfirm,
|
||||
onRetryFee = ::loadData,
|
||||
onAddFundsClick = { params.listener.onClickAddFunds() },
|
||||
onDismissRequest = ::onDismiss,
|
||||
),
|
||||
)
|
||||
|
||||
init {
|
||||
analytics.send(TangemPayAnalyticsEvents.ReplaceCardConfirmationPopupOpened())
|
||||
loadData()
|
||||
}
|
||||
|
||||
fun onDismiss() {
|
||||
reissueJobHolder.cancel()
|
||||
params.listener.onDismissReissueCard()
|
||||
}
|
||||
|
||||
private fun onConfirm() {
|
||||
analytics.send(TangemPayAnalyticsEvents.ReplaceCardConfirmed())
|
||||
state.update { it.copy(isReissuingInProgress = true) }
|
||||
modelScope.launch {
|
||||
reissueCardRepository.reissueCard(
|
||||
userWalletId = params.userWalletId,
|
||||
cardId = params.cardId,
|
||||
).onLeft {
|
||||
uiMessageSender.send(SnackbarMessage(resourceReference(R.string.common_something_went_wrong)))
|
||||
onDismiss()
|
||||
}.onRight { order ->
|
||||
params.listener.onReissueOrderCreate(order)
|
||||
}
|
||||
}.saveIn(reissueJobHolder)
|
||||
}
|
||||
|
||||
private fun loadData() {
|
||||
state.update { it.copy(isFeeLoading = true, error = null) }
|
||||
|
||||
modelScope.launch {
|
||||
val (cardBalance, fee) = coroutineScope {
|
||||
val balanceDeferred = async { cardDetailsRepository.getCardBalance(params.userWalletId).getOrNull() }
|
||||
val feeDeferred = async { reissueCardRepository.getReissueCardFee(params.userWalletId).getOrNull() }
|
||||
balanceDeferred.await() to feeDeferred.await()
|
||||
}
|
||||
|
||||
val error = if (fee == null || cardBalance == null) {
|
||||
TangemPayReissueCardError.InitialDataLoading
|
||||
} else if (cardBalance.availableForWithdrawal < fee.amount) {
|
||||
TangemPayReissueCardError.InsufficientFunds
|
||||
} else {
|
||||
null
|
||||
}
|
||||
|
||||
state.update { state ->
|
||||
state.copy(
|
||||
feeAmount = fee?.let {
|
||||
fee.amount.format {
|
||||
val symbol = getJavaCurrencyByCode(fee.currencyCode).symbol
|
||||
fiat(fee.currencyCode, symbol)
|
||||
}
|
||||
}.orEmpty(),
|
||||
isFeeLoading = false,
|
||||
error = error,
|
||||
)
|
||||
}
|
||||
}.saveIn(loadDataJobHolder)
|
||||
}
|
||||
}
|
||||
|
|
@ -12,12 +12,27 @@ import androidx.compose.foundation.background
|
|||
import androidx.compose.foundation.border
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.interaction.MutableInteractionSource
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.Spacer
|
||||
import androidx.compose.foundation.layout.aspectRatio
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.foundation.layout.width
|
||||
import androidx.compose.foundation.layout.wrapContentHeight
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.foundation.text.BasicTextField
|
||||
import androidx.compose.foundation.text.KeyboardActions
|
||||
import androidx.compose.foundation.text.KeyboardOptions
|
||||
import androidx.compose.material3.*
|
||||
import androidx.compose.material3.ButtonColors
|
||||
import androidx.compose.material3.CircularProgressIndicator
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.IconButton
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.getValue
|
||||
|
|
@ -27,8 +42,6 @@ import androidx.compose.runtime.setValue
|
|||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.clip
|
||||
import androidx.constraintlayout.compose.ConstraintLayout
|
||||
import androidx.constraintlayout.compose.Dimension
|
||||
import androidx.compose.ui.focus.FocusRequester
|
||||
import androidx.compose.ui.focus.focusRequester
|
||||
import androidx.compose.ui.graphics.Brush
|
||||
|
|
@ -45,6 +58,8 @@ import androidx.compose.ui.tooling.preview.Preview
|
|||
import androidx.compose.ui.tooling.preview.PreviewParameter
|
||||
import androidx.compose.ui.tooling.preview.datasource.CollectionPreviewParameterProvider
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.constraintlayout.compose.ConstraintLayout
|
||||
import androidx.constraintlayout.compose.Dimension
|
||||
import com.tangem.core.ui.components.SpacerWMax
|
||||
import com.tangem.core.ui.components.buttons.common.TangemButton
|
||||
import com.tangem.core.ui.components.buttons.common.TangemButtonIconPosition
|
||||
|
|
@ -53,12 +68,12 @@ import com.tangem.core.ui.extensions.resourceReference
|
|||
import com.tangem.core.ui.extensions.stringResourceSafe
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
import com.tangem.core.ui.res.TangemThemePreview
|
||||
import com.tangem.domain.models.account.CardDisplayName
|
||||
import com.tangem.domain.visa.model.TangemPayCardFrozenState
|
||||
import com.tangem.features.tangempay.details.impl.R
|
||||
import com.tangem.features.tangempay.entity.DisplayNameState
|
||||
import com.tangem.features.tangempay.entity.TangemPayCardDetailsUM
|
||||
import com.tangem.features.tangempay.model.CardDataType
|
||||
import com.tangem.domain.models.account.CardDisplayName
|
||||
|
||||
private const val ICON_FADE_DURATION_MS = 300
|
||||
private val CustomCardBlockColor = Color(0x1F828282)
|
||||
|
|
@ -101,7 +116,7 @@ internal fun TangemPayCard(state: TangemPayCardDetailsUM, modifier: Modifier = M
|
|||
shape = RoundedCornerShape(16.dp),
|
||||
),
|
||||
) {
|
||||
if (shouldShowDetails) {
|
||||
if (shouldShowDetails && state.isActive) {
|
||||
TangemPayCardDetailsShownBlock(
|
||||
cardNumber = state.number,
|
||||
expiry = state.expiry,
|
||||
|
|
@ -114,11 +129,7 @@ internal fun TangemPayCard(state: TangemPayCardDetailsUM, modifier: Modifier = M
|
|||
)
|
||||
} else {
|
||||
TangemPayCardDetailsHiddenBlock(
|
||||
cardFrozenState = state.cardFrozenState,
|
||||
isLoading = state.isLoading,
|
||||
shortCardNumber = state.numberShort,
|
||||
onShowDetails = state.onClick,
|
||||
displayNameState = state.displayNameState,
|
||||
state = state,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -126,16 +137,9 @@ internal fun TangemPayCard(state: TangemPayCardDetailsUM, modifier: Modifier = M
|
|||
|
||||
@Suppress("LongMethod", "DestructuringDeclarationWithTooManyEntries")
|
||||
@Composable
|
||||
private fun TangemPayCardDetailsHiddenBlock(
|
||||
shortCardNumber: String,
|
||||
cardFrozenState: TangemPayCardFrozenState,
|
||||
isLoading: Boolean,
|
||||
onShowDetails: () -> Unit,
|
||||
displayNameState: DisplayNameState?,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
private fun TangemPayCardDetailsHiddenBlock(state: TangemPayCardDetailsUM, modifier: Modifier = Modifier) {
|
||||
Box(modifier = modifier.fillMaxSize()) {
|
||||
val imageResId = when (cardFrozenState) {
|
||||
val imageResId = when (state.cardFrozenState) {
|
||||
is TangemPayCardFrozenState.Frozen -> R.drawable.img_tangem_pay_visa_frozen
|
||||
else -> R.drawable.img_tangem_pay_visa
|
||||
}
|
||||
|
|
@ -144,75 +148,78 @@ private fun TangemPayCardDetailsHiddenBlock(
|
|||
painter = painterResource(id = imageResId),
|
||||
contentDescription = null,
|
||||
)
|
||||
ConstraintLayout(
|
||||
modifier = Modifier
|
||||
.align(Alignment.BottomCenter)
|
||||
.padding(horizontal = 16.dp)
|
||||
.padding(bottom = 8.dp)
|
||||
.fillMaxWidth(),
|
||||
) {
|
||||
val (displayNameRef, cardNumberRef, frozenIconRef, buttonRef) = createRefs()
|
||||
|
||||
if (displayNameState != null) {
|
||||
CardDisplayName(
|
||||
state = displayNameState,
|
||||
modifier = Modifier.constrainAs(displayNameRef) {
|
||||
start.linkTo(parent.start)
|
||||
bottom.linkTo(cardNumberRef.top)
|
||||
width = Dimension.wrapContent
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
Text(
|
||||
text = shortCardNumber,
|
||||
style = TangemTheme.typography.subtitle2,
|
||||
color = TangemTheme.colors.text.constantWhite,
|
||||
if (state.isActive) {
|
||||
ConstraintLayout(
|
||||
modifier = Modifier
|
||||
.constrainAs(cardNumberRef) {
|
||||
start.linkTo(parent.start)
|
||||
bottom.linkTo(parent.bottom)
|
||||
}
|
||||
.padding(bottom = 8.dp),
|
||||
)
|
||||
when (cardFrozenState) {
|
||||
is TangemPayCardFrozenState.Frozen -> Icon(
|
||||
modifier = Modifier
|
||||
.constrainAs(frozenIconRef) {
|
||||
start.linkTo(cardNumberRef.end, margin = 4.dp)
|
||||
top.linkTo(cardNumberRef.top)
|
||||
bottom.linkTo(cardNumberRef.bottom)
|
||||
}
|
||||
.padding(bottom = 8.dp)
|
||||
.size(16.dp),
|
||||
painter = painterResource(id = R.drawable.ic_snow_24),
|
||||
contentDescription = null,
|
||||
tint = TangemTheme.colors.icon.constant,
|
||||
)
|
||||
TangemPayCardFrozenState.Pending -> CircularProgressIndicator(
|
||||
modifier = Modifier
|
||||
.constrainAs(frozenIconRef) {
|
||||
start.linkTo(cardNumberRef.end, margin = 4.dp)
|
||||
top.linkTo(cardNumberRef.top)
|
||||
bottom.linkTo(cardNumberRef.bottom)
|
||||
}
|
||||
.padding(bottom = 8.dp)
|
||||
.size(16.dp),
|
||||
color = TangemTheme.colors.text.constantWhite,
|
||||
strokeWidth = 1.dp,
|
||||
)
|
||||
TangemPayCardFrozenState.Unfrozen -> Unit
|
||||
}
|
||||
.align(Alignment.BottomCenter)
|
||||
.padding(horizontal = 16.dp)
|
||||
.padding(bottom = 8.dp)
|
||||
.fillMaxWidth(),
|
||||
) {
|
||||
val (displayNameRef, cardNumberRef, frozenIconRef, buttonRef) = createRefs()
|
||||
|
||||
TangemPayCardDetailsCustomButton(
|
||||
modifier = Modifier.constrainAs(buttonRef) {
|
||||
end.linkTo(parent.end)
|
||||
bottom.linkTo(parent.bottom)
|
||||
},
|
||||
text = stringResourceSafe(id = R.string.tangempay_card_details_show_details),
|
||||
onClick = onShowDetails,
|
||||
showProgress = isLoading,
|
||||
)
|
||||
if (state.displayNameState != null) {
|
||||
CardDisplayName(
|
||||
state = state.displayNameState,
|
||||
modifier = Modifier.constrainAs(displayNameRef) {
|
||||
start.linkTo(parent.start)
|
||||
bottom.linkTo(cardNumberRef.top)
|
||||
width = Dimension.wrapContent
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
Text(
|
||||
text = state.numberShort,
|
||||
style = TangemTheme.typography.subtitle2,
|
||||
color = TangemTheme.colors.text.constantWhite,
|
||||
modifier = Modifier
|
||||
.constrainAs(cardNumberRef) {
|
||||
start.linkTo(parent.start)
|
||||
bottom.linkTo(parent.bottom)
|
||||
}
|
||||
.padding(bottom = 8.dp),
|
||||
)
|
||||
when (state.cardFrozenState) {
|
||||
is TangemPayCardFrozenState.Frozen -> Icon(
|
||||
modifier = Modifier
|
||||
.constrainAs(frozenIconRef) {
|
||||
start.linkTo(cardNumberRef.end, margin = 4.dp)
|
||||
top.linkTo(cardNumberRef.top)
|
||||
bottom.linkTo(cardNumberRef.bottom)
|
||||
}
|
||||
.padding(bottom = 8.dp)
|
||||
.size(16.dp),
|
||||
painter = painterResource(id = R.drawable.ic_snow_24),
|
||||
contentDescription = null,
|
||||
tint = TangemTheme.colors.icon.constant,
|
||||
)
|
||||
TangemPayCardFrozenState.Pending -> CircularProgressIndicator(
|
||||
modifier = Modifier
|
||||
.constrainAs(frozenIconRef) {
|
||||
start.linkTo(cardNumberRef.end, margin = 4.dp)
|
||||
top.linkTo(cardNumberRef.top)
|
||||
bottom.linkTo(cardNumberRef.bottom)
|
||||
}
|
||||
.padding(bottom = 8.dp)
|
||||
.size(16.dp),
|
||||
color = TangemTheme.colors.text.constantWhite,
|
||||
strokeWidth = 1.dp,
|
||||
)
|
||||
TangemPayCardFrozenState.Unfrozen -> Unit
|
||||
}
|
||||
|
||||
TangemPayCardDetailsCustomButton(
|
||||
modifier = Modifier.constrainAs(buttonRef) {
|
||||
end.linkTo(parent.end)
|
||||
bottom.linkTo(parent.bottom)
|
||||
},
|
||||
text = stringResourceSafe(id = R.string.tangempay_card_details_show_details),
|
||||
onClick = state.onClick,
|
||||
showProgress = state.isLoading,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -32,6 +32,10 @@ import androidx.compose.ui.platform.LocalDensity
|
|||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.util.fastForEach
|
||||
import com.tangem.core.ui.components.appbar.AppBarWithBackButton
|
||||
import com.tangem.core.ui.components.notifications.Notification
|
||||
import com.tangem.core.ui.components.notifications.NotificationConfig
|
||||
import com.tangem.core.ui.extensions.resolveReference
|
||||
import com.tangem.core.ui.extensions.resourceReference
|
||||
import com.tangem.core.ui.extensions.stringResourceSafe
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
import com.tangem.core.ui.res.TangemThemePreview
|
||||
|
|
@ -46,12 +50,6 @@ import com.tangem.features.tangempay.entity.TangemPayCardPageUM
|
|||
import kotlinx.collections.immutable.ImmutableList
|
||||
|
||||
private const val CONTENT_FADE_DURATION_MS = 300
|
||||
private val TangemPayCardPageSetting.titleRes
|
||||
get() = when (this) {
|
||||
TangemPayCardPageSetting.ChangePIN -> R.string.tangempay_card_details_change_pin
|
||||
TangemPayCardPageSetting.FreezeCard -> R.string.tangempay_card_details_freeze_card
|
||||
TangemPayCardPageSetting.ReplaceCard -> R.string.common_error // TODO v_rodionov #[REDACTED_TASK_KEY]
|
||||
}
|
||||
|
||||
@Composable
|
||||
internal fun TangemPayCardPageScreen(
|
||||
|
|
@ -110,20 +108,35 @@ internal fun TangemPayCardPageScreen(
|
|||
enter = fadeIn(animationSpec = tween(CONTENT_FADE_DURATION_MS)),
|
||||
exit = fadeOut(animationSpec = tween(CONTENT_FADE_DURATION_MS)),
|
||||
) {
|
||||
TangemPayCardPageSettingsBlock(
|
||||
settings = state.settings,
|
||||
onSettingClick = state.onSettingClick,
|
||||
)
|
||||
if (state.isReissueInProgress) {
|
||||
TangemPayReplacingCardBlock()
|
||||
} else {
|
||||
TangemPayCardPageSettingsBlock(
|
||||
settings = state.settings,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun TangemPayReplacingCardBlock(modifier: Modifier = Modifier) {
|
||||
Notification(
|
||||
modifier = modifier,
|
||||
config = NotificationConfig(
|
||||
iconResId = com.tangem.core.ui.R.drawable.ic_update_32,
|
||||
iconTint = NotificationConfig.IconTint.Accent,
|
||||
title = resourceReference(R.string.tangempay_reissue_card_in_progress),
|
||||
subtitle = resourceReference(R.string.tangempay_reissue_card_in_progress_description),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun TangemPayCardPageSettingsBlock(
|
||||
settings: ImmutableList<TangemPayCardPageSetting>,
|
||||
onSettingClick: (TangemPayCardPageSetting) -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
Column(
|
||||
|
|
@ -147,7 +160,7 @@ private fun TangemPayCardPageSettingsBlock(
|
|||
settings.fastForEach { item ->
|
||||
TangemPayCardPageSettingRow(
|
||||
item = item,
|
||||
onClick = { onSettingClick(item) },
|
||||
onClick = item.onSettingClick,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -167,7 +180,7 @@ private fun TangemPayCardPageSettingRow(
|
|||
contentAlignment = Alignment.CenterStart,
|
||||
) {
|
||||
Text(
|
||||
text = stringResourceSafe(item.titleRes),
|
||||
text = item.title.resolveReference(),
|
||||
style = TangemTheme.typography.subtitle1,
|
||||
color = TangemTheme.colors.text.primary1,
|
||||
)
|
||||
|
|
|
|||
|
|
@ -0,0 +1,218 @@
|
|||
package com.tangem.features.tangempay.ui
|
||||
|
||||
import android.content.res.Configuration
|
||||
import androidx.compose.animation.AnimatedContent
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.foundation.shape.CircleShape
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.clip
|
||||
import androidx.compose.ui.res.painterResource
|
||||
import androidx.compose.ui.text.style.TextAlign
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.tangem.core.ui.components.PrimaryButton
|
||||
import com.tangem.core.ui.components.SpacerH16
|
||||
import com.tangem.core.ui.components.SpacerH24
|
||||
import com.tangem.core.ui.components.SpacerH8
|
||||
import com.tangem.core.ui.components.TextShimmer
|
||||
import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfig
|
||||
import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfigContent
|
||||
import com.tangem.core.ui.components.bottomsheets.modal.TangemModalBottomSheet
|
||||
import com.tangem.core.ui.components.bottomsheets.modal.TangemModalBottomSheetTitle
|
||||
import com.tangem.core.ui.components.notifications.Notification
|
||||
import com.tangem.core.ui.components.notifications.NotificationConfig
|
||||
import com.tangem.core.ui.extensions.TextReference
|
||||
import com.tangem.core.ui.extensions.resourceReference
|
||||
import com.tangem.core.ui.extensions.stringResourceSafe
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
import com.tangem.core.ui.res.TangemThemePreview
|
||||
import com.tangem.features.tangempay.details.impl.R
|
||||
import com.tangem.features.tangempay.entity.TangemPayReissueCardError
|
||||
import com.tangem.features.tangempay.entity.TangemPayReissueCardUM
|
||||
|
||||
@Composable
|
||||
internal fun TangemPayReissueCardContent(state: TangemPayReissueCardUM) {
|
||||
TangemModalBottomSheet<TangemBottomSheetConfigContent.Empty>(
|
||||
config = TangemBottomSheetConfig(
|
||||
isShown = true,
|
||||
onDismissRequest = state.onDismissRequest,
|
||||
content = TangemBottomSheetConfigContent.Empty,
|
||||
),
|
||||
containerColor = TangemTheme.colors.background.tertiary,
|
||||
onBack = state.onDismissRequest,
|
||||
title = {
|
||||
TangemModalBottomSheetTitle(
|
||||
title = TextReference.EMPTY,
|
||||
endIconRes = R.drawable.ic_close_24,
|
||||
onEndClick = state.onDismissRequest,
|
||||
)
|
||||
},
|
||||
) {
|
||||
Content(state)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun Content(state: TangemPayReissueCardUM) {
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(horizontal = TangemTheme.dimens.spacing16),
|
||||
horizontalAlignment = Alignment.CenterHorizontally,
|
||||
) {
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.size(TangemTheme.dimens.spacing56)
|
||||
.clip(CircleShape)
|
||||
.background(TangemTheme.colors.icon.accent.copy(alpha = 0.1f)),
|
||||
contentAlignment = Alignment.Center,
|
||||
) {
|
||||
Icon(
|
||||
painter = painterResource(id = R.drawable.ic_update_32),
|
||||
contentDescription = null,
|
||||
tint = TangemTheme.colors.icon.accent,
|
||||
modifier = Modifier.size(32.dp),
|
||||
)
|
||||
}
|
||||
|
||||
SpacerH24()
|
||||
|
||||
Text(
|
||||
text = stringResourceSafe(R.string.tangempay_reissue_card_title),
|
||||
style = TangemTheme.typography.h3,
|
||||
color = TangemTheme.colors.text.primary1,
|
||||
textAlign = TextAlign.Center,
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(horizontal = 16.dp),
|
||||
)
|
||||
|
||||
SpacerH8()
|
||||
|
||||
Text(
|
||||
text = stringResourceSafe(R.string.tangempay_reissue_card_description),
|
||||
style = TangemTheme.typography.body2,
|
||||
color = TangemTheme.colors.text.secondary,
|
||||
textAlign = TextAlign.Center,
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(horizontal = 16.dp),
|
||||
)
|
||||
|
||||
SpacerH24()
|
||||
|
||||
FeeBlock(state)
|
||||
|
||||
if (state.error != null) {
|
||||
SpacerH16()
|
||||
ErrorBlock(
|
||||
error = state.error,
|
||||
onRetryFee = state.onRetryFee,
|
||||
onAddFundsClick = state.onAddFundsClick,
|
||||
)
|
||||
}
|
||||
|
||||
SpacerH24()
|
||||
|
||||
PrimaryButton(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
text = stringResourceSafe(R.string.tangempay_reissue_card_confirm),
|
||||
enabled = state.error == null && !state.isFeeLoading,
|
||||
showProgress = state.isReissuingInProgress,
|
||||
onClick = state.onConfirmClick,
|
||||
)
|
||||
|
||||
SpacerH16()
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun FeeBlock(state: TangemPayReissueCardUM) {
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.background(
|
||||
color = TangemTheme.colors.background.action,
|
||||
shape = TangemTheme.shapes.roundedCornersMedium,
|
||||
)
|
||||
.padding(TangemTheme.dimens.spacing12),
|
||||
horizontalArrangement = Arrangement.SpaceBetween,
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
) {
|
||||
Text(
|
||||
text = stringResourceSafe(R.string.tangempay_reissue_card_fee_label),
|
||||
style = TangemTheme.typography.body1,
|
||||
color = TangemTheme.colors.text.primary1,
|
||||
)
|
||||
AnimatedContent(
|
||||
targetState = when {
|
||||
state.isFeeLoading -> null
|
||||
state.error == TangemPayReissueCardError.InitialDataLoading -> "—"
|
||||
else -> state.feeAmount
|
||||
},
|
||||
) { fee ->
|
||||
if (fee != null) {
|
||||
Text(
|
||||
text = fee,
|
||||
style = TangemTheme.typography.body1,
|
||||
color = TangemTheme.colors.text.tertiary,
|
||||
)
|
||||
} else {
|
||||
TextShimmer(style = TangemTheme.typography.body1, text = "$0.00")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun ErrorBlock(error: TangemPayReissueCardError, onAddFundsClick: () -> Unit, onRetryFee: () -> Unit) {
|
||||
when (error) {
|
||||
TangemPayReissueCardError.InsufficientFunds -> Notification(
|
||||
config = NotificationConfig(
|
||||
title = resourceReference(R.string.tangempay_reissue_card_insufficient_funds_title),
|
||||
subtitle = resourceReference(R.string.tangempay_reissue_card_insufficient_funds_subtitle),
|
||||
iconResId = R.drawable.img_usdc_16,
|
||||
buttonsState = NotificationConfig.ButtonsState.SecondaryButtonConfig(
|
||||
text = resourceReference(R.string.tangempay_card_details_add_funds),
|
||||
iconResId = R.drawable.ic_plus_24,
|
||||
onClick = onAddFundsClick,
|
||||
),
|
||||
),
|
||||
containerColor = TangemTheme.colors.background.action,
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
)
|
||||
TangemPayReissueCardError.InitialDataLoading -> Notification(
|
||||
config = NotificationConfig(
|
||||
title = resourceReference(R.string.tangempay_reissue_card_fee_unreachable_error_title),
|
||||
subtitle = resourceReference(R.string.send_fee_unreachable_error_text),
|
||||
iconResId = R.drawable.img_attention_20,
|
||||
buttonsState = NotificationConfig.ButtonsState.SecondaryButtonConfig(
|
||||
text = resourceReference(R.string.warning_button_refresh),
|
||||
onClick = onRetryFee,
|
||||
),
|
||||
),
|
||||
containerColor = TangemTheme.colors.background.action,
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Preview(showBackground = true)
|
||||
@Preview(uiMode = Configuration.UI_MODE_NIGHT_YES)
|
||||
@Composable
|
||||
private fun preview() = TangemThemePreview {
|
||||
TangemPayReissueCardContent(
|
||||
state = TangemPayReissueCardUM.stub(),
|
||||
)
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue