Updated on 2026-08-14
This commit is contained in:
parent
9aa3bf7541
commit
3ea0fbb8a3
32 changed files with 618 additions and 421 deletions
|
|
@ -0,0 +1,23 @@
|
|||
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.TangemPayCardFrozenStateStore
|
||||
import dagger.Module
|
||||
import dagger.Provides
|
||||
import dagger.hilt.InstallIn
|
||||
import dagger.hilt.components.SingletonComponent
|
||||
import javax.inject.Singleton
|
||||
|
||||
@Module
|
||||
@InstallIn(SingletonComponent::class)
|
||||
internal object TangemPayStoresModule {
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideTangemPayCardFrozenStateStore(): TangemPayCardFrozenStateStore {
|
||||
return DefaultTangemPayCardFrozenStateStore(
|
||||
dataStore = RuntimeDataStore(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,21 @@
|
|||
package com.tangem.datasource.local.visa
|
||||
|
||||
import com.tangem.datasource.local.datastore.core.StringKeyDataStore
|
||||
import com.tangem.domain.visa.model.TangemPayCardFrozenState
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
|
||||
internal class DefaultTangemPayCardFrozenStateStore(
|
||||
private val dataStore: StringKeyDataStore<TangemPayCardFrozenState>,
|
||||
) : TangemPayCardFrozenStateStore {
|
||||
override suspend fun getSyncOrNull(key: String): TangemPayCardFrozenState? {
|
||||
return dataStore.getSyncOrNull(key)
|
||||
}
|
||||
|
||||
override fun get(key: String): Flow<TangemPayCardFrozenState> {
|
||||
return dataStore.get(key)
|
||||
}
|
||||
|
||||
override suspend fun store(key: String, value: TangemPayCardFrozenState) {
|
||||
dataStore.store(key, value)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,13 @@
|
|||
package com.tangem.datasource.local.visa
|
||||
|
||||
import com.tangem.domain.visa.model.TangemPayCardFrozenState
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
|
||||
interface TangemPayCardFrozenStateStore {
|
||||
|
||||
suspend fun getSyncOrNull(key: String): TangemPayCardFrozenState?
|
||||
|
||||
fun get(key: String): Flow<TangemPayCardFrozenState>
|
||||
|
||||
suspend fun store(key: String, value: TangemPayCardFrozenState)
|
||||
}
|
||||
BIN
core/ui/src/main/res/drawable/img_frozen_tangem_pay_visa.webp
Normal file
BIN
core/ui/src/main/res/drawable/img_frozen_tangem_pay_visa.webp
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 2.8 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 2.1 KiB After Width: | Height: | Size: 2 KiB |
|
|
@ -14,6 +14,7 @@ object StringsSigns {
|
|||
const val NON_BREAKING_SPACE = '\u00A0'
|
||||
const val PERCENT = "%"
|
||||
const val THREE_STARS = "\u2217\u2217\u2217"
|
||||
const val ASTERISK = "\u2217"
|
||||
const val PASSWORD_VISUAL_CHAR = '\u2022'
|
||||
const val APPROXIMATE = "≈"
|
||||
const val WHITE_SPACE = " "
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ import com.tangem.datasource.api.pay.TangemPayApi
|
|||
import com.tangem.datasource.api.pay.models.request.DeeplinkValidityRequest
|
||||
import com.tangem.datasource.api.pay.models.request.OrderRequest
|
||||
import com.tangem.datasource.api.pay.models.response.CustomerMeResponse
|
||||
import com.tangem.datasource.local.visa.TangemPayCardFrozenStateStore
|
||||
import com.tangem.datasource.local.visa.TangemPayStorage
|
||||
import com.tangem.domain.pay.datasource.TangemPayAuthDataSource
|
||||
import com.tangem.domain.pay.model.CustomerInfo
|
||||
|
|
@ -16,6 +17,7 @@ import com.tangem.domain.pay.model.CustomerInfo.ProductInstance
|
|||
import com.tangem.domain.pay.model.MainScreenCustomerInfo
|
||||
import com.tangem.domain.pay.model.OrderStatus
|
||||
import com.tangem.domain.pay.repository.OnboardingRepository
|
||||
import com.tangem.domain.visa.model.TangemPayCardFrozenState
|
||||
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
||||
import kotlinx.coroutines.withContext
|
||||
import javax.inject.Inject
|
||||
|
|
@ -24,6 +26,7 @@ private const val VALID_STATUS = "valid"
|
|||
private const val APPROVED_KYC_STATUS = "APPROVED"
|
||||
private const val TAG = "TangemPay: OnboardingRepository"
|
||||
|
||||
@Suppress("LongParameterList")
|
||||
internal class DefaultOnboardingRepository @Inject constructor(
|
||||
private val dispatcherProvider: CoroutineDispatcherProvider,
|
||||
private val tangemPayApi: TangemPayApi,
|
||||
|
|
@ -31,6 +34,7 @@ internal class DefaultOnboardingRepository @Inject constructor(
|
|||
private val tangemPayStorage: TangemPayStorage,
|
||||
private val authDataSource: TangemPayAuthDataSource,
|
||||
private val tangemPayWalletsManager: TangemPayWalletsManager,
|
||||
private val cardFrozenStateStore: TangemPayCardFrozenStateStore,
|
||||
) : OnboardingRepository {
|
||||
|
||||
override suspend fun validateDeeplink(link: String): Either<UniversalError, Boolean> {
|
||||
|
|
@ -122,7 +126,7 @@ internal class DefaultOnboardingRepository @Inject constructor(
|
|||
}
|
||||
}
|
||||
|
||||
private fun getCustomerInfo(response: CustomerMeResponse.Result?): CustomerInfo {
|
||||
private suspend fun getCustomerInfo(response: CustomerMeResponse.Result?): CustomerInfo {
|
||||
val card = response?.card
|
||||
val balance = response?.balance
|
||||
val paymentAccount = response?.paymentAccount
|
||||
|
|
@ -138,6 +142,13 @@ internal class DefaultOnboardingRepository @Inject constructor(
|
|||
null
|
||||
}
|
||||
val productInstance = response?.productInstance?.let { instance ->
|
||||
cardFrozenStateStore.store(
|
||||
key = instance.cardId,
|
||||
value = when (instance.status) {
|
||||
CustomerMeResponse.ProductInstance.Status.ACTIVE -> TangemPayCardFrozenState.Unfrozen
|
||||
else -> TangemPayCardFrozenState.Frozen
|
||||
},
|
||||
)
|
||||
ProductInstance(
|
||||
id = instance.id,
|
||||
cardId = instance.cardId,
|
||||
|
|
|
|||
|
|
@ -12,18 +12,19 @@ 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.TangemPayCardFrozenStateStore
|
||||
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.TangemPayCardDetailsRepository
|
||||
import com.tangem.domain.visa.error.VisaApiError
|
||||
import timber.log.Timber
|
||||
import com.tangem.domain.visa.model.TangemPayCardFrozenState
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import javax.inject.Inject
|
||||
|
||||
private const val TAG = "TangemPay: CardDetailsRepository"
|
||||
|
||||
@Suppress("OptionalUnit")
|
||||
@Suppress("LongParameterList")
|
||||
internal class DefaultTangemPayCardDetailsRepository @Inject constructor(
|
||||
private val tangemPayApi: TangemPayApi,
|
||||
private val requestHelper: TangemPayRequestPerformer,
|
||||
|
|
@ -31,6 +32,7 @@ internal class DefaultTangemPayCardDetailsRepository @Inject constructor(
|
|||
private val apiConfigsManager: ApiConfigsManager,
|
||||
private val rainCryptoUtil: RainCryptoUtil,
|
||||
private val storage: TangemPayStorage,
|
||||
private val cardFrozenStateStore: TangemPayCardFrozenStateStore,
|
||||
) : TangemPayCardDetailsRepository {
|
||||
|
||||
override suspend fun getCardBalance(): Either<UniversalError, TangemPayCardBalance> {
|
||||
|
|
@ -117,34 +119,58 @@ internal class DefaultTangemPayCardDetailsRepository @Inject constructor(
|
|||
}
|
||||
}
|
||||
|
||||
override suspend fun freezeCard(cardId: String): Either<UniversalError, Unit> {
|
||||
override suspend fun freezeCard(cardId: String): Either<UniversalError, TangemPayCardFrozenState> {
|
||||
cardFrozenStateStore.store(cardId, TangemPayCardFrozenState.Pending)
|
||||
return requestHelper.makeSafeRequest {
|
||||
tangemPayApi.freezeCard(authHeader = it, body = FreezeUnfreezeCardRequest(cardId = cardId))
|
||||
}.onLeft {
|
||||
cardFrozenStateStore.store(cardId, TangemPayCardFrozenState.Unfrozen)
|
||||
}.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)
|
||||
val state = when (response.result?.status) {
|
||||
FreezeUnfreezeCardResponse.Status.COMPLETED -> TangemPayCardFrozenState.Frozen
|
||||
FreezeUnfreezeCardResponse.Status.NEW,
|
||||
FreezeUnfreezeCardResponse.Status.PROCESSING,
|
||||
-> TangemPayCardFrozenState.Pending
|
||||
FreezeUnfreezeCardResponse.Status.CANCELED,
|
||||
null,
|
||||
-> TangemPayCardFrozenState.Unfrozen
|
||||
}
|
||||
cardFrozenStateStore.store(cardId, state)
|
||||
|
||||
state
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun unfreezeCard(cardId: String): Either<UniversalError, Unit> {
|
||||
override suspend fun unfreezeCard(cardId: String): Either<UniversalError, TangemPayCardFrozenState> {
|
||||
cardFrozenStateStore.store(cardId, TangemPayCardFrozenState.Pending)
|
||||
return requestHelper.makeSafeRequest {
|
||||
tangemPayApi.unfreezeCard(authHeader = it, body = FreezeUnfreezeCardRequest(cardId = cardId))
|
||||
}.onLeft {
|
||||
cardFrozenStateStore.store(cardId, TangemPayCardFrozenState.Frozen)
|
||||
}.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)
|
||||
val state = when (response.result?.status) {
|
||||
FreezeUnfreezeCardResponse.Status.COMPLETED -> TangemPayCardFrozenState.Unfrozen
|
||||
FreezeUnfreezeCardResponse.Status.NEW,
|
||||
FreezeUnfreezeCardResponse.Status.PROCESSING,
|
||||
-> TangemPayCardFrozenState.Pending
|
||||
FreezeUnfreezeCardResponse.Status.CANCELED,
|
||||
null,
|
||||
-> TangemPayCardFrozenState.Frozen
|
||||
}
|
||||
cardFrozenStateStore.store(cardId, state)
|
||||
|
||||
state
|
||||
}
|
||||
}
|
||||
|
||||
override fun cardFrozenState(cardId: String): Flow<TangemPayCardFrozenState> {
|
||||
return cardFrozenStateStore.get(cardId)
|
||||
}
|
||||
|
||||
override suspend fun cardFrozenStateSync(cardId: String): TangemPayCardFrozenState? {
|
||||
return cardFrozenStateStore.getSyncOrNull(cardId)
|
||||
}
|
||||
|
||||
private suspend fun getPublicKeyBase64(): String {
|
||||
val env = apiConfigsManager.getEnvironmentConfig(ApiConfig.ID.TangemPay).environment
|
||||
return when (env) {
|
||||
|
|
|
|||
|
|
@ -1,11 +1,12 @@
|
|||
package com.tangem.domain.pay
|
||||
|
||||
import com.tangem.domain.visa.model.TangemPayCardFrozenState
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
@Serializable
|
||||
data class TangemPayDetailsConfig(
|
||||
val cardId: String,
|
||||
val isCardFrozen: Boolean,
|
||||
val cardFrozenState: TangemPayCardFrozenState,
|
||||
val customerWalletAddress: String,
|
||||
val cardNumberEnd: String,
|
||||
val chainId: Int,
|
||||
|
|
|
|||
|
|
@ -0,0 +1,10 @@
|
|||
package com.tangem.domain.visa.model
|
||||
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
@Serializable
|
||||
sealed class TangemPayCardFrozenState {
|
||||
data object Pending : TangemPayCardFrozenState()
|
||||
data object Frozen : TangemPayCardFrozenState()
|
||||
data object Unfrozen : TangemPayCardFrozenState()
|
||||
}
|
||||
|
|
@ -5,6 +5,8 @@ import com.tangem.core.error.UniversalError
|
|||
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.visa.model.TangemPayCardFrozenState
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
|
||||
interface TangemPayCardDetailsRepository {
|
||||
|
||||
|
|
@ -18,6 +20,8 @@ interface TangemPayCardDetailsRepository {
|
|||
|
||||
suspend fun setAddToWalletAsDone(): Either<UniversalError, Unit>
|
||||
|
||||
suspend fun freezeCard(cardId: String): Either<UniversalError, Unit>
|
||||
suspend fun unfreezeCard(cardId: String): Either<UniversalError, Unit>
|
||||
suspend fun freezeCard(cardId: String): Either<UniversalError, TangemPayCardFrozenState>
|
||||
suspend fun unfreezeCard(cardId: String): Either<UniversalError, TangemPayCardFrozenState>
|
||||
fun cardFrozenState(cardId: String): Flow<TangemPayCardFrozenState>
|
||||
suspend fun cardFrozenStateSync(cardId: String): TangemPayCardFrozenState?
|
||||
}
|
||||
|
|
@ -6,7 +6,7 @@ import com.tangem.core.decompose.context.AppComponentContext
|
|||
import com.tangem.core.decompose.model.getOrCreateModel
|
||||
import com.tangem.features.tangempay.entity.TangemPayCardDetailsUM
|
||||
import com.tangem.features.tangempay.model.TangemPayCardDetailsBlockModel
|
||||
import com.tangem.features.tangempay.ui.CardDetailsItem
|
||||
import com.tangem.features.tangempay.ui.TangemPayCard
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
|
||||
internal class DefaultTangemPayCardDetailsBlockComponent(
|
||||
|
|
@ -19,6 +19,6 @@ internal class DefaultTangemPayCardDetailsBlockComponent(
|
|||
|
||||
@Composable
|
||||
override fun CardDetailsBlockContent(state: TangemPayCardDetailsUM, modifier: Modifier) {
|
||||
CardDetailsItem(state, modifier)
|
||||
TangemPayCard(state, modifier)
|
||||
}
|
||||
}
|
||||
|
|
@ -3,7 +3,7 @@ package com.tangem.features.tangempay.components.cardDetails
|
|||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import com.tangem.features.tangempay.entity.TangemPayCardDetailsUM
|
||||
import com.tangem.features.tangempay.ui.CardDetailsItem
|
||||
import com.tangem.features.tangempay.ui.TangemPayCard
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
|
||||
|
|
@ -15,6 +15,6 @@ internal class PreviewTangemPayCardDetailsBlockComponent(
|
|||
|
||||
@Composable
|
||||
override fun CardDetailsBlockContent(state: TangemPayCardDetailsUM, modifier: Modifier) {
|
||||
CardDetailsItem(state, modifier)
|
||||
TangemPayCard(state, modifier)
|
||||
}
|
||||
}
|
||||
|
|
@ -1,11 +1,13 @@
|
|||
package com.tangem.features.tangempay.components.cardDetails
|
||||
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.Stable
|
||||
import androidx.compose.ui.Modifier
|
||||
import com.tangem.features.tangempay.components.TangemPayDetailsContainerComponent
|
||||
import com.tangem.features.tangempay.entity.TangemPayCardDetailsUM
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
|
||||
@Stable
|
||||
internal interface TangemPayCardDetailsBlockComponent {
|
||||
val state: StateFlow<TangemPayCardDetailsUM>
|
||||
|
||||
|
|
|
|||
|
|
@ -1,33 +1,24 @@
|
|||
package com.tangem.features.tangempay.entity
|
||||
|
||||
import com.tangem.core.ui.extensions.TextReference
|
||||
import com.tangem.core.ui.extensions.resourceReference
|
||||
import com.tangem.domain.visa.model.TangemPayCardFrozenState
|
||||
import com.tangem.features.tangempay.details.impl.R
|
||||
import com.tangem.features.tangempay.utils.CardDetailsFormatUtil
|
||||
import com.tangem.utils.StringsSigns
|
||||
|
||||
private const val CARD_NUMBER_SART_DIGITS_COUNT = 12
|
||||
private const val DATE_PART_LENGTH = 2
|
||||
private const val CVV_LENGTH = 3
|
||||
|
||||
internal class TangemPayCardDetailsBlockStateFactory(
|
||||
private val cardNumberEnd: String,
|
||||
private val onReveal: () -> Unit,
|
||||
private val onCopy: (String) -> Unit,
|
||||
) {
|
||||
|
||||
private val cardStartMasked = maskedBlock(CARD_NUMBER_SART_DIGITS_COUNT)
|
||||
private val dateMasked = maskedBlock(DATE_PART_LENGTH)
|
||||
private val cvvMasked = maskedBlock(CVV_LENGTH)
|
||||
|
||||
fun getInitialState() = TangemPayCardDetailsUM(
|
||||
number = CardDetailsFormatUtil.formatCardNumber(cardNumber = "$cardStartMasked$cardNumberEnd"),
|
||||
expiry = CardDetailsFormatUtil.formatDate(month = dateMasked, year = dateMasked),
|
||||
cvv = cvvMasked,
|
||||
buttonText = TextReference.Res(R.string.tangempay_card_details_reveal_text),
|
||||
number = "${StringsSigns.ASTERISK}$cardNumberEnd",
|
||||
expiry = "",
|
||||
cvv = "",
|
||||
buttonText = resourceReference(R.string.tangempay_card_details_reveal_text),
|
||||
onClick = onReveal,
|
||||
onCopy = onCopy,
|
||||
isHidden = true,
|
||||
cardFrozenState = TangemPayCardFrozenState.Unfrozen,
|
||||
)
|
||||
|
||||
private fun maskedBlock(count: Int) = StringsSigns.DOT.repeat(count)
|
||||
}
|
||||
|
|
@ -6,20 +6,25 @@ 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.domain.visa.model.TangemPayCardFrozenState
|
||||
import com.tangem.features.tangempay.details.impl.R
|
||||
import com.tangem.features.tangempay.model.transformers.TangemPayCardFrozenStateConverter
|
||||
import com.tangem.features.tangempay.utils.TangemPayDetailIntents
|
||||
import kotlinx.collections.immutable.persistentListOf
|
||||
import kotlinx.collections.immutable.toPersistentList
|
||||
|
||||
@Suppress("LongParameterList")
|
||||
internal class TangemPayDetailsStateFactory(
|
||||
private val onBack: () -> Unit,
|
||||
private val intents: TangemPayDetailIntents,
|
||||
private val isCardFrozen: Boolean,
|
||||
private val cardFrozenState: TangemPayCardFrozenState,
|
||||
private val converter: TangemPayCardFrozenStateConverter,
|
||||
) {
|
||||
|
||||
fun getInitialState(): TangemPayDetailsUM {
|
||||
val cardFrozenStateItem = if (isCardFrozen) {
|
||||
TangemPayDetailsTopBarMenuItem(
|
||||
val cardFrozenStateItem = when (cardFrozenState) {
|
||||
is TangemPayCardFrozenState.Pending -> null
|
||||
is TangemPayCardFrozenState.Frozen -> TangemPayDetailsTopBarMenuItem(
|
||||
type = TangemPayDetailsTopBarMenuItemType.UnfreezeCard,
|
||||
dropdownItem = TangemDropdownMenuItem(
|
||||
title = resourceReference(R.string.tangempay_card_details_unfreeze_card),
|
||||
|
|
@ -27,8 +32,7 @@ internal class TangemPayDetailsStateFactory(
|
|||
onClick = intents::onClickUnfreezeCard,
|
||||
),
|
||||
)
|
||||
} else {
|
||||
TangemPayDetailsTopBarMenuItem(
|
||||
is TangemPayCardFrozenState.Unfrozen -> TangemPayDetailsTopBarMenuItem(
|
||||
type = TangemPayDetailsTopBarMenuItemType.FreezeCard,
|
||||
dropdownItem = TangemDropdownMenuItem(
|
||||
title = resourceReference(R.string.tangempay_card_details_freeze_card),
|
||||
|
|
@ -41,7 +45,7 @@ internal class TangemPayDetailsStateFactory(
|
|||
return TangemPayDetailsUM(
|
||||
topBarConfig = TangemPayDetailsTopBarConfig(
|
||||
onBackClick = onBack,
|
||||
items = persistentListOf(
|
||||
items = listOfNotNull(
|
||||
TangemPayDetailsTopBarMenuItem(
|
||||
type = TangemPayDetailsTopBarMenuItemType.ChangePin,
|
||||
dropdownItem = TangemDropdownMenuItem(
|
||||
|
|
@ -59,7 +63,7 @@ internal class TangemPayDetailsStateFactory(
|
|||
),
|
||||
),
|
||||
cardFrozenStateItem,
|
||||
),
|
||||
).toPersistentList(),
|
||||
),
|
||||
pullToRefreshConfig = PullToRefreshConfig(
|
||||
isRefreshing = false,
|
||||
|
|
@ -73,15 +77,11 @@ internal class TangemPayDetailsStateFactory(
|
|||
onClick = intents::onClickAddFunds,
|
||||
),
|
||||
),
|
||||
frozenState = if (isCardFrozen) {
|
||||
CardFrozenState.Frozen(intents::onClickUnfreezeCard)
|
||||
} else {
|
||||
CardFrozenState.Unfrozen
|
||||
},
|
||||
),
|
||||
addToWalletBlockState = null,
|
||||
isBalanceHidden = false,
|
||||
addFundsEnabled = true,
|
||||
cardFrozenState = converter.convert(cardFrozenState),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -3,6 +3,7 @@ package com.tangem.features.tangempay.entity
|
|||
import com.tangem.core.ui.components.buttons.actions.ActionButtonConfig
|
||||
import com.tangem.core.ui.components.containers.pullToRefresh.PullToRefreshConfig
|
||||
import com.tangem.core.ui.extensions.TextReference
|
||||
import com.tangem.domain.visa.model.TangemPayCardFrozenState
|
||||
import kotlinx.collections.immutable.ImmutableList
|
||||
|
||||
internal data class TangemPayDetailsUM(
|
||||
|
|
@ -12,6 +13,7 @@ internal data class TangemPayDetailsUM(
|
|||
val addToWalletBlockState: AddToWalletBlockState?,
|
||||
val isBalanceHidden: Boolean,
|
||||
val addFundsEnabled: Boolean,
|
||||
val cardFrozenState: CardFrozenState,
|
||||
)
|
||||
|
||||
internal data class TangemPayCardDetailsUM(
|
||||
|
|
@ -23,21 +25,19 @@ internal data class TangemPayCardDetailsUM(
|
|||
val onCopy: (String) -> Unit = {},
|
||||
val isHidden: Boolean = true,
|
||||
val isLoading: Boolean = false,
|
||||
val cardFrozenState: TangemPayCardFrozenState,
|
||||
)
|
||||
|
||||
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,
|
||||
|
|
@ -45,7 +45,6 @@ internal sealed class TangemPayDetailsBalanceBlockState {
|
|||
|
||||
data class Error(
|
||||
override val actionButtons: ImmutableList<ActionButtonConfig>,
|
||||
override val frozenState: CardFrozenState,
|
||||
) : TangemPayDetailsBalanceBlockState()
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -21,12 +21,10 @@ import com.tangem.features.tangempay.model.transformers.DetailsRevealedStateTran
|
|||
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
||||
import com.tangem.utils.coroutines.JobHolder
|
||||
import com.tangem.utils.coroutines.saveIn
|
||||
import com.tangem.utils.transformer.update
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
import kotlinx.coroutines.flow.collectLatest
|
||||
import kotlinx.coroutines.flow.*
|
||||
import kotlinx.coroutines.launch
|
||||
import javax.inject.Inject
|
||||
import com.tangem.utils.transformer.update as transformerUpdate
|
||||
|
||||
@Stable
|
||||
@ModelScoped
|
||||
|
|
@ -53,6 +51,7 @@ internal class TangemPayCardDetailsBlockModel @Inject constructor(
|
|||
private val revealCardDetailsJobHolder = JobHolder()
|
||||
|
||||
init {
|
||||
subscribeToCardFrozenState()
|
||||
modelScope.launch {
|
||||
cardDetailsEventListener.event.collectLatest { event ->
|
||||
when (event) {
|
||||
|
|
@ -63,22 +62,29 @@ internal class TangemPayCardDetailsBlockModel @Inject constructor(
|
|||
}
|
||||
}
|
||||
|
||||
private fun subscribeToCardFrozenState() {
|
||||
cardDetailsRepository
|
||||
.cardFrozenState(params.params.config.cardId)
|
||||
.onEach { uiState.update { state -> state.copy(cardFrozenState = it) } }
|
||||
.launchIn(modelScope)
|
||||
}
|
||||
|
||||
private fun revealCardDetails() {
|
||||
modelScope.launch {
|
||||
uiState.update(
|
||||
uiState.transformerUpdate(
|
||||
transformer = DetailsRevealProgressStateTransformer(onClickHide = ::hideCardDetails),
|
||||
)
|
||||
cardDetailsRepository.revealCardDetails()
|
||||
.onRight {
|
||||
uiState.update(
|
||||
.onRight { cardDetails ->
|
||||
uiState.transformerUpdate(
|
||||
transformer = DetailsRevealedStateTransformer(
|
||||
details = it,
|
||||
details = cardDetails,
|
||||
onClickHide = ::hideCardDetails,
|
||||
),
|
||||
)
|
||||
}
|
||||
.onLeft {
|
||||
uiState.update(transformer = DetailsHiddenStateTransformer(stateFactory))
|
||||
uiState.transformerUpdate(transformer = DetailsHiddenStateTransformer(stateFactory))
|
||||
showError()
|
||||
}
|
||||
}.saveIn(revealCardDetailsJobHolder)
|
||||
|
|
@ -87,7 +93,7 @@ internal class TangemPayCardDetailsBlockModel @Inject constructor(
|
|||
fun hideCardDetails() {
|
||||
modelScope.launch {
|
||||
revealCardDetailsJobHolder.cancel()
|
||||
uiState.update(transformer = DetailsHiddenStateTransformer(stateFactory))
|
||||
uiState.transformerUpdate(transformer = DetailsHiddenStateTransformer(stateFactory))
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -20,6 +20,7 @@ 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.TangemPayCardDetailsRepository
|
||||
import com.tangem.domain.visa.model.TangemPayCardFrozenState
|
||||
import com.tangem.domain.visa.model.TangemPayTxHistoryItem
|
||||
import com.tangem.features.tangempay.TangemPayConstants
|
||||
import com.tangem.features.tangempay.components.AddFundsListener
|
||||
|
|
@ -42,8 +43,10 @@ import com.tangem.utils.coroutines.JobHolder
|
|||
import com.tangem.utils.coroutines.saveIn
|
||||
import com.tangem.utils.transformer.update
|
||||
import kotlinx.coroutines.Job
|
||||
import kotlinx.coroutines.flow.*
|
||||
import kotlinx.coroutines.flow.update
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
import kotlinx.coroutines.flow.launchIn
|
||||
import kotlinx.coroutines.flow.onEach
|
||||
import kotlinx.coroutines.launch
|
||||
import javax.inject.Inject
|
||||
|
||||
|
|
@ -64,10 +67,12 @@ internal class TangemPayDetailsModel @Inject constructor(
|
|||
|
||||
private val params: TangemPayDetailsContainerComponent.Params = paramsContainer.require()
|
||||
|
||||
private val cardFrozenStateConverter = TangemPayCardFrozenStateConverter(onUnfreezeClick = ::onClickUnfreezeCard)
|
||||
private val stateFactory = TangemPayDetailsStateFactory(
|
||||
onBack = router::pop,
|
||||
intents = this,
|
||||
isCardFrozen = params.config.isCardFrozen,
|
||||
cardFrozenState = params.config.cardFrozenState,
|
||||
converter = cardFrozenStateConverter,
|
||||
)
|
||||
|
||||
val uiState: StateFlow<TangemPayDetailsUM>
|
||||
|
|
@ -85,6 +90,23 @@ internal class TangemPayDetailsModel @Inject constructor(
|
|||
handleBalanceHiding()
|
||||
fetchAddToWalletBanner()
|
||||
fetchBalance()
|
||||
subscribeToCardFrozenState()
|
||||
}
|
||||
|
||||
private fun subscribeToCardFrozenState() {
|
||||
cardDetailsRepository
|
||||
.cardFrozenState(params.config.cardId)
|
||||
.onEach { state ->
|
||||
uiState.update(
|
||||
TangemPayFreezeUnfreezeStateTransformer(
|
||||
cardFrozenState = state,
|
||||
onFreezeClick = ::onClickFreezeCard,
|
||||
onUnfreezeClick = ::onClickUnfreezeCard,
|
||||
converter = cardFrozenStateConverter,
|
||||
),
|
||||
)
|
||||
}
|
||||
.launchIn(modelScope)
|
||||
}
|
||||
|
||||
override fun onClickChangePin() {
|
||||
|
|
@ -101,54 +123,64 @@ internal class TangemPayDetailsModel @Inject constructor(
|
|||
|
||||
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,
|
||||
),
|
||||
)
|
||||
.onRight { state ->
|
||||
when (state) {
|
||||
TangemPayCardFrozenState.Frozen -> {
|
||||
uiMessageSender.send(
|
||||
SnackbarMessage(resourceReference(R.string.tangem_pay_freeze_card_success)),
|
||||
)
|
||||
uiState.update(
|
||||
TangemPayFreezeUnfreezeStateTransformer(
|
||||
cardFrozenState = state,
|
||||
onFreezeClick = ::onClickFreezeCard,
|
||||
onUnfreezeClick = ::onClickUnfreezeCard,
|
||||
converter = cardFrozenStateConverter,
|
||||
),
|
||||
)
|
||||
}
|
||||
TangemPayCardFrozenState.Unfrozen -> {
|
||||
uiMessageSender.send(
|
||||
SnackbarMessage(resourceReference(R.string.tangem_pay_freeze_card_failed)),
|
||||
)
|
||||
}
|
||||
TangemPayCardFrozenState.Pending -> Unit // TODO [REDACTED_JIRA]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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,
|
||||
),
|
||||
)
|
||||
.onRight { state ->
|
||||
when (state) {
|
||||
TangemPayCardFrozenState.Unfrozen -> {
|
||||
uiMessageSender.send(
|
||||
SnackbarMessage(resourceReference(R.string.tangem_pay_unfreeze_card_success)),
|
||||
)
|
||||
uiState.update(
|
||||
TangemPayFreezeUnfreezeStateTransformer(
|
||||
cardFrozenState = state,
|
||||
onFreezeClick = ::onClickFreezeCard,
|
||||
onUnfreezeClick = ::onClickUnfreezeCard,
|
||||
converter = cardFrozenStateConverter,
|
||||
),
|
||||
)
|
||||
}
|
||||
TangemPayCardFrozenState.Frozen -> {
|
||||
uiMessageSender.send(
|
||||
SnackbarMessage(resourceReference(R.string.tangem_pay_unfreeze_card_failed)),
|
||||
)
|
||||
}
|
||||
TangemPayCardFrozenState.Pending -> Unit // TODO [REDACTED_JIRA]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -18,10 +18,7 @@ internal class DetailsBalanceTransformer(
|
|||
override fun transform(prevState: TangemPayDetailsUM): TangemPayDetailsUM {
|
||||
val balance = when (balance) {
|
||||
is Either.Left<UniversalError> -> {
|
||||
TangemPayDetailsBalanceBlockState.Error(
|
||||
actionButtons = persistentListOf(),
|
||||
frozenState = prevState.balanceBlockState.frozenState,
|
||||
)
|
||||
TangemPayDetailsBalanceBlockState.Error(actionButtons = persistentListOf())
|
||||
}
|
||||
is Either.Right<TangemPayCardBalance> -> {
|
||||
TangemPayDetailsBalanceBlockState.Content(
|
||||
|
|
@ -30,7 +27,6 @@ internal class DetailsBalanceTransformer(
|
|||
// TODO [REDACTED_TASK_KEY]: Add crypto balance when the BFF is ready
|
||||
cryptoBalance = "",
|
||||
actionButtons = prevState.balanceBlockState.actionButtons,
|
||||
frozenState = prevState.balanceBlockState.frozenState,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,26 +1,39 @@
|
|||
package com.tangem.features.tangempay.model.transformers
|
||||
|
||||
import com.tangem.core.ui.extensions.TextReference
|
||||
import com.tangem.core.ui.extensions.resourceReference
|
||||
import com.tangem.domain.pay.model.TangemPayCardDetails
|
||||
import com.tangem.features.tangempay.details.impl.R
|
||||
import com.tangem.features.tangempay.entity.TangemPayCardDetailsUM
|
||||
import com.tangem.features.tangempay.utils.CardDetailsFormatUtil
|
||||
import com.tangem.utils.transformer.Transformer
|
||||
|
||||
private const val CARD_NUMBER_CHUNK_LENGTH = 4
|
||||
private const val DATE_LENGTH = 2
|
||||
|
||||
internal class DetailsRevealedStateTransformer(
|
||||
private val details: TangemPayCardDetails,
|
||||
private val onClickHide: (() -> Unit),
|
||||
) : Transformer<TangemPayCardDetailsUM> {
|
||||
|
||||
override fun transform(prevState: TangemPayCardDetailsUM): TangemPayCardDetailsUM {
|
||||
return TangemPayCardDetailsUM(
|
||||
number = CardDetailsFormatUtil.formatCardNumber(cardNumber = details.pan),
|
||||
expiry = CardDetailsFormatUtil.formatDate(month = details.expirationMonth, year = details.expirationYear),
|
||||
return prevState.copy(
|
||||
number = formatCardNumber(cardNumber = details.pan),
|
||||
expiry = formatDate(month = details.expirationMonth, year = details.expirationYear),
|
||||
cvv = details.cvv,
|
||||
onClick = onClickHide,
|
||||
buttonText = TextReference.Res(R.string.tangempay_card_details_hide_text),
|
||||
onCopy = prevState.onCopy,
|
||||
buttonText = resourceReference(R.string.tangempay_card_details_hide_text),
|
||||
isHidden = false,
|
||||
)
|
||||
}
|
||||
|
||||
private fun formatCardNumber(cardNumber: String): String {
|
||||
return cardNumber
|
||||
.filterNot { it.isWhitespace() }
|
||||
.chunked(CARD_NUMBER_CHUNK_LENGTH).joinToString(" ")
|
||||
}
|
||||
|
||||
private fun formatDate(month: String, year: String): String {
|
||||
val monthFormatted = month.padStart(DATE_LENGTH, '0')
|
||||
val yearFormatted = year.takeLast(DATE_LENGTH)
|
||||
return "$monthFormatted/$yearFormatted"
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,18 @@
|
|||
package com.tangem.features.tangempay.model.transformers
|
||||
|
||||
import com.tangem.domain.visa.model.TangemPayCardFrozenState
|
||||
import com.tangem.features.tangempay.entity.CardFrozenState
|
||||
import com.tangem.utils.converter.Converter
|
||||
|
||||
internal class TangemPayCardFrozenStateConverter(
|
||||
private val onUnfreezeClick: () -> Unit,
|
||||
) : Converter<TangemPayCardFrozenState, CardFrozenState> {
|
||||
|
||||
override fun convert(value: TangemPayCardFrozenState): CardFrozenState {
|
||||
return when (value) {
|
||||
TangemPayCardFrozenState.Unfrozen -> CardFrozenState.Unfrozen
|
||||
TangemPayCardFrozenState.Frozen -> CardFrozenState.Frozen(onUnfreezeClick)
|
||||
TangemPayCardFrozenState.Pending -> CardFrozenState.Pending
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,21 +0,0 @@
|
|||
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)
|
||||
}
|
||||
}
|
||||
|
|
@ -4,31 +4,39 @@ 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.domain.visa.model.TangemPayCardFrozenState
|
||||
import com.tangem.features.tangempay.details.impl.R
|
||||
import com.tangem.features.tangempay.entity.*
|
||||
import com.tangem.features.tangempay.entity.TangemPayDetailsTopBarMenuItem
|
||||
import com.tangem.features.tangempay.entity.TangemPayDetailsTopBarMenuItemType.FreezeCard
|
||||
import com.tangem.features.tangempay.entity.TangemPayDetailsTopBarMenuItemType.UnfreezeCard
|
||||
import com.tangem.features.tangempay.entity.TangemPayDetailsUM
|
||||
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 cardFrozenState: TangemPayCardFrozenState,
|
||||
private val onFreezeClick: () -> Unit,
|
||||
private val onUnfreezeClick: () -> Unit,
|
||||
private val converter: TangemPayCardFrozenStateConverter,
|
||||
) : 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
|
||||
private val isCardFrozen: Boolean = when (cardFrozenState) {
|
||||
is TangemPayCardFrozenState.Frozen -> true
|
||||
is TangemPayCardFrozenState.Unfrozen, is TangemPayCardFrozenState.Pending -> false
|
||||
}
|
||||
|
||||
override fun transform(prevState: TangemPayDetailsUM): TangemPayDetailsUM {
|
||||
val dropdownMenuItems = createUpdatedMenuItems(prevState.topBarConfig.items)
|
||||
val newBalanceBlockState = createUpdatedBalanceState(prevState.balanceBlockState)
|
||||
val dropdownMenuItems = when (cardFrozenState) {
|
||||
TangemPayCardFrozenState.Frozen,
|
||||
TangemPayCardFrozenState.Unfrozen,
|
||||
-> createUpdatedMenuItems(prevState.topBarConfig.items)
|
||||
TangemPayCardFrozenState.Pending -> prevState.topBarConfig.items
|
||||
}
|
||||
|
||||
return prevState.copy(
|
||||
topBarConfig = prevState.topBarConfig.copy(items = dropdownMenuItems),
|
||||
balanceBlockState = newBalanceBlockState,
|
||||
cardFrozenState = converter.convert(cardFrozenState),
|
||||
)
|
||||
}
|
||||
|
||||
|
|
@ -36,38 +44,25 @@ internal class TangemPayFreezeUnfreezeStateTransformer(
|
|||
items: ImmutableList<TangemPayDetailsTopBarMenuItem>?,
|
||||
): ImmutableList<TangemPayDetailsTopBarMenuItem>? {
|
||||
return items
|
||||
?.filter { it.type != removeType }
|
||||
?.filterNot { it.type == UnfreezeCard || it.type == FreezeCard }
|
||||
?.plus(createMenuItemToAdd())
|
||||
?.toPersistentList()
|
||||
}
|
||||
|
||||
private fun createMenuItemToAdd(): TangemPayDetailsTopBarMenuItem {
|
||||
return TangemPayDetailsTopBarMenuItem(
|
||||
type = addType,
|
||||
type = if (isCardFrozen) UnfreezeCard else FreezeCard,
|
||||
dropdownItem = TangemDropdownMenuItem(
|
||||
title = resourceReference(
|
||||
id = if (frozen) {
|
||||
id = if (isCardFrozen) {
|
||||
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,
|
||||
onClick = if (isCardFrozen) 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,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,19 +0,0 @@
|
|||
package com.tangem.features.tangempay.ui
|
||||
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
import com.tangem.features.tangempay.entity.TangemPayCardDetailsUM
|
||||
|
||||
@Composable
|
||||
internal fun CardDetailsItem(state: TangemPayCardDetailsUM, modifier: Modifier = Modifier) {
|
||||
TangemPayCardDetailsBlock(
|
||||
modifier = modifier
|
||||
.padding(top = TangemTheme.dimens.spacing12)
|
||||
.padding(horizontal = TangemTheme.dimens.spacing16)
|
||||
.fillMaxWidth(),
|
||||
state = state,
|
||||
)
|
||||
}
|
||||
|
|
@ -28,6 +28,7 @@ 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.domain.visa.model.TangemPayCardFrozenState
|
||||
import com.tangem.features.tangempay.components.cardDetails.PreviewTangemPayCardDetailsBlockComponent
|
||||
import com.tangem.features.tangempay.components.cardDetails.TangemPayCardDetailsBlockComponent
|
||||
import com.tangem.features.tangempay.details.impl.R
|
||||
|
|
@ -64,8 +65,11 @@ internal fun TangemPayAddToWalletScreen(
|
|||
state = listState,
|
||||
contentPadding = PaddingValues(bottom = TangemTheme.dimens.spacing16 + bottomBarHeight),
|
||||
) {
|
||||
item(key = TangemPayCardDetailsUM::class.java) {
|
||||
TangemPayCardDetailsBlockItem(component = cardDetailsBlockComponent, state = cardDetailsState)
|
||||
item(TangemPayCardDetailsUM::class.java) {
|
||||
cardDetailsBlockComponent.CardDetailsBlockContent(
|
||||
modifier = Modifier.padding(horizontal = 16.dp).padding(top = 8.dp),
|
||||
state = cardDetailsState,
|
||||
)
|
||||
}
|
||||
|
||||
item(key = TangemPayAddToWalletUM::class.java) {
|
||||
|
|
@ -185,6 +189,7 @@ private fun PreviewTangemPayAddToWalletScreen() {
|
|||
onCopy = {},
|
||||
onClick = {},
|
||||
buttonText = TextReference.Res(R.string.tangempay_card_details_hide_text),
|
||||
cardFrozenState = TangemPayCardFrozenState.Unfrozen,
|
||||
),
|
||||
),
|
||||
)
|
||||
|
|
|
|||
|
|
@ -1,164 +1,306 @@
|
|||
package com.tangem.features.tangempay.ui
|
||||
|
||||
import androidx.compose.animation.core.LinearEasing
|
||||
import androidx.compose.animation.core.RepeatMode
|
||||
import androidx.compose.animation.core.animateFloat
|
||||
import androidx.compose.animation.core.infiniteRepeatable
|
||||
import androidx.compose.animation.core.rememberInfiniteTransition
|
||||
import androidx.compose.animation.core.EaseInOut
|
||||
import androidx.compose.animation.core.animateFloatAsState
|
||||
import androidx.compose.animation.core.tween
|
||||
import androidx.compose.foundation.Image
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.Spacer
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.heightIn
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.IconButton
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.material3.*
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.clip
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.graphics.graphicsLayer
|
||||
import androidx.compose.ui.res.painterResource
|
||||
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 com.tangem.core.ui.extensions.resolveReference
|
||||
import com.tangem.core.ui.components.PrimaryButton
|
||||
import com.tangem.core.ui.components.SpacerWMax
|
||||
import com.tangem.core.ui.components.buttons.common.TangemButtonSize
|
||||
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.visa.model.TangemPayCardFrozenState
|
||||
import com.tangem.features.tangempay.details.impl.R
|
||||
import com.tangem.features.tangempay.entity.TangemPayCardDetailsUM
|
||||
|
||||
private const val REVEAL_ANIMATION_MILLIS = 500
|
||||
@Suppress("MagicNumber")
|
||||
@Composable
|
||||
internal fun TangemPayCard(state: TangemPayCardDetailsUM, modifier: Modifier = Modifier) {
|
||||
val isCardFlipped = !state.isHidden
|
||||
val animDuration = 900
|
||||
val zAxisDistance = 50f // distance between camera and Card
|
||||
|
||||
// rotate Y-axis with animation
|
||||
val rotateCardY by animateFloatAsState(
|
||||
targetValue = if (isCardFlipped) 180f else 0f,
|
||||
animationSpec = tween(durationMillis = animDuration, easing = EaseInOut),
|
||||
label = "",
|
||||
)
|
||||
|
||||
// Determine which side to show based on the rotation angle
|
||||
val shouldShowDetails = rotateCardY > 90f
|
||||
|
||||
Box(
|
||||
modifier = modifier
|
||||
.fillMaxWidth()
|
||||
.aspectRatio(204f / 130f) // size of img_tangem_pay_visa
|
||||
.graphicsLayer {
|
||||
rotationY = rotateCardY
|
||||
cameraDistance = zAxisDistance
|
||||
}
|
||||
.clip(RoundedCornerShape(16.dp))
|
||||
.clickable(onClick = state.onClick)
|
||||
.background(Color(red = 18, green = 21, blue = 31)),
|
||||
) {
|
||||
if (shouldShowDetails) {
|
||||
TangemPayCardDetailsShownBlock(
|
||||
cardNumber = state.number,
|
||||
expiry = state.expiry,
|
||||
cvv = state.cvv,
|
||||
onCopyCardNumber = { state.onCopy(state.number) },
|
||||
onCopyCvv = { state.onCopy(state.cvv) },
|
||||
onCopyExpiry = { state.onCopy(state.expiry) },
|
||||
onHideDetails = state.onClick,
|
||||
modifier = Modifier.graphicsLayer { rotationY = 180f },
|
||||
)
|
||||
} else {
|
||||
TangemPayCardDetailsHiddenBlock(
|
||||
cardFrozenState = state.cardFrozenState,
|
||||
isLoading = state.isLoading,
|
||||
shortCardNumber = state.number,
|
||||
onShowDetails = state.onClick,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
internal fun TangemPayCardDetailsBlock(state: TangemPayCardDetailsUM, modifier: Modifier = Modifier) {
|
||||
val alpha = if (state.isLoading) {
|
||||
val infiniteTransition = rememberInfiniteTransition()
|
||||
val animation by infiniteTransition.animateFloat(
|
||||
initialValue = 1f,
|
||||
targetValue = 0.3f,
|
||||
animationSpec = infiniteRepeatable(
|
||||
animation = tween(REVEAL_ANIMATION_MILLIS, easing = LinearEasing),
|
||||
repeatMode = RepeatMode.Reverse,
|
||||
),
|
||||
)
|
||||
animation
|
||||
} else {
|
||||
1f
|
||||
}
|
||||
Column(
|
||||
modifier = modifier
|
||||
.background(
|
||||
color = TangemTheme.colors.background.primary,
|
||||
shape = TangemTheme.shapes.roundedCornersMedium,
|
||||
),
|
||||
) {
|
||||
CardDetailsBlockHeader(state)
|
||||
CardDetailsTextContainer(
|
||||
modifier = Modifier
|
||||
.graphicsLayer { this.alpha = alpha }
|
||||
.fillMaxWidth()
|
||||
.padding(start = 12.dp, end = 12.dp, top = 4.dp),
|
||||
text = state.number,
|
||||
onCopy = { state.onCopy(state.number) },
|
||||
isHidden = state.isHidden,
|
||||
private fun TangemPayCardDetailsHiddenBlock(
|
||||
shortCardNumber: String,
|
||||
cardFrozenState: TangemPayCardFrozenState,
|
||||
isLoading: Boolean,
|
||||
onShowDetails: () -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
Box(modifier = modifier.fillMaxSize()) {
|
||||
val imageResId = when (cardFrozenState) {
|
||||
is TangemPayCardFrozenState.Frozen -> R.drawable.img_frozen_tangem_pay_visa
|
||||
else -> R.drawable.img_tangem_pay_visa
|
||||
}
|
||||
Image(
|
||||
modifier = Modifier.fillMaxSize(),
|
||||
painter = painterResource(id = imageResId),
|
||||
contentDescription = null,
|
||||
)
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.align(Alignment.BottomCenter)
|
||||
.padding(horizontal = 16.dp)
|
||||
.fillMaxWidth(),
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
) {
|
||||
Text(
|
||||
text = shortCardNumber,
|
||||
style = TangemTheme.typography.subtitle2,
|
||||
color = TangemTheme.colors.text.constantWhite,
|
||||
)
|
||||
when (cardFrozenState) {
|
||||
is TangemPayCardFrozenState.Frozen -> Icon(
|
||||
modifier = Modifier
|
||||
.padding(start = 4.dp)
|
||||
.size(16.dp),
|
||||
painter = painterResource(id = R.drawable.ic_snow_24),
|
||||
contentDescription = null,
|
||||
tint = TangemTheme.colors.icon.constant,
|
||||
)
|
||||
TangemPayCardFrozenState.Pending -> CircularProgressIndicator(
|
||||
modifier = Modifier
|
||||
.padding(start = 4.dp)
|
||||
.size(16.dp),
|
||||
color = TangemTheme.colors.text.constantWhite,
|
||||
strokeWidth = 1.dp,
|
||||
)
|
||||
TangemPayCardFrozenState.Unfrozen -> Unit
|
||||
}
|
||||
SpacerWMax()
|
||||
PrimaryButton(
|
||||
text = stringResourceSafe(id = R.string.tangempay_card_details_show_details),
|
||||
showProgress = isLoading,
|
||||
onClick = onShowDetails,
|
||||
size = TangemButtonSize.Small,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Suppress("MagicNumber", "LongParameterList")
|
||||
@Composable
|
||||
private fun TangemPayCardDetailsShownBlock(
|
||||
cardNumber: String,
|
||||
expiry: String,
|
||||
cvv: String,
|
||||
onCopyCardNumber: () -> Unit,
|
||||
onCopyExpiry: () -> Unit,
|
||||
onCopyCvv: () -> Unit,
|
||||
onHideDetails: () -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
Column(
|
||||
modifier = modifier
|
||||
.padding(8.dp)
|
||||
.fillMaxSize(),
|
||||
) {
|
||||
CardDetailsTextContainer(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(start = 12.dp, end = 12.dp, bottom = 12.dp, top = 8.dp),
|
||||
.weight(1f),
|
||||
title = stringResourceSafe(R.string.tangempay_card_details_card_number),
|
||||
text = cardNumber,
|
||||
onCopy = onCopyCardNumber,
|
||||
)
|
||||
HorizontalDivider(thickness = 1.dp, color = Color(0x1F828282)) // Hardcode since background is an image
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.weight(1f)
|
||||
.padding(top = 8.dp),
|
||||
horizontalArrangement = Arrangement.spacedBy(12.dp),
|
||||
) {
|
||||
CardDetailsTextContainer(
|
||||
modifier = Modifier
|
||||
.graphicsLayer { this.alpha = alpha }
|
||||
.weight(1f),
|
||||
text = state.expiry,
|
||||
onCopy = { state.onCopy(state.expiry) },
|
||||
isHidden = state.isHidden,
|
||||
.weight(1f)
|
||||
.wrapContentHeight(),
|
||||
title = stringResourceSafe(R.string.tangempay_card_details_expiry),
|
||||
text = expiry,
|
||||
onCopy = onCopyExpiry,
|
||||
)
|
||||
VerticalDivider(thickness = 1.dp, color = Color(0x1F828282)) // Hardcode since background is an image
|
||||
CardDetailsTextContainer(
|
||||
modifier = Modifier
|
||||
.graphicsLayer { this.alpha = alpha }
|
||||
.weight(1f),
|
||||
text = state.cvv,
|
||||
onCopy = { state.onCopy(state.cvv) },
|
||||
isHidden = state.isHidden,
|
||||
.weight(1f)
|
||||
.wrapContentHeight(),
|
||||
title = stringResourceSafe(R.string.tangempay_card_details_cvc),
|
||||
text = cvv,
|
||||
onCopy = onCopyCvv,
|
||||
)
|
||||
}
|
||||
Row {
|
||||
SpacerWMax()
|
||||
PrimaryButton(
|
||||
text = stringResourceSafe(id = R.string.tangempay_card_details_hide_details),
|
||||
onClick = onHideDetails,
|
||||
size = TangemButtonSize.Small,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun CardDetailsBlockHeader(state: TangemPayCardDetailsUM, modifier: Modifier = Modifier) {
|
||||
private fun CardDetailsTextContainer(title: String, text: String, onCopy: () -> Unit, modifier: Modifier = Modifier) {
|
||||
Row(
|
||||
modifier = modifier
|
||||
.fillMaxWidth(),
|
||||
modifier = modifier.padding(12.dp),
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
horizontalArrangement = Arrangement.SpaceBetween,
|
||||
) {
|
||||
Text(
|
||||
Column(verticalArrangement = Arrangement.spacedBy(8.dp)) {
|
||||
Text(
|
||||
text = title,
|
||||
style = TangemTheme.typography.subtitle2,
|
||||
color = TangemTheme.colors.text.tertiary,
|
||||
)
|
||||
Text(
|
||||
text = text,
|
||||
style = TangemTheme.typography.body2,
|
||||
color = TangemTheme.colors.text.constantWhite,
|
||||
)
|
||||
}
|
||||
IconButton(
|
||||
modifier = Modifier
|
||||
.padding(start = 12.dp, top = 12.dp),
|
||||
text = stringResourceSafe(R.string.tangempay_card_details_title),
|
||||
color = TangemTheme.colors.text.tertiary,
|
||||
style = TangemTheme.typography.subtitle2,
|
||||
)
|
||||
Text(
|
||||
modifier = Modifier
|
||||
.padding(top = 8.dp, end = 4.dp)
|
||||
.clip(TangemTheme.shapes.roundedCornersMedium)
|
||||
.clickable(onClick = state.onClick)
|
||||
.padding(horizontal = 8.dp, vertical = 4.dp),
|
||||
text = state.buttonText.resolveReference(),
|
||||
color = TangemTheme.colors.text.accent,
|
||||
style = TangemTheme.typography.body2,
|
||||
)
|
||||
.padding(end = TangemTheme.dimens.spacing12)
|
||||
.size(TangemTheme.dimens.size32),
|
||||
onClick = onCopy,
|
||||
) {
|
||||
Icon(
|
||||
modifier = Modifier.size(TangemTheme.dimens.size24),
|
||||
painter = painterResource(id = R.drawable.ic_copy_new_24),
|
||||
tint = TangemTheme.colors.icon.informative,
|
||||
contentDescription = null,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Preview(widthDp = 400, heightDp = 700, showBackground = true)
|
||||
@Composable
|
||||
private fun CardDetailsTextContainer(
|
||||
text: String,
|
||||
isHidden: Boolean,
|
||||
onCopy: () -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
private fun TangemPayCardDetailsBlockV2Preview(
|
||||
@PreviewParameter(TangemPayCardDetailsUMProvider::class) state: TangemPayCardDetailsUM,
|
||||
) {
|
||||
Row(
|
||||
modifier = modifier
|
||||
.heightIn(min = 48.dp)
|
||||
.background(color = TangemTheme.colors.field.primary, shape = RoundedCornerShape(16.dp)),
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
) {
|
||||
Text(
|
||||
modifier = Modifier.padding(vertical = 4.dp, horizontal = 12.dp),
|
||||
text = text,
|
||||
maxLines = 1,
|
||||
color = if (isHidden) TangemTheme.colors.text.disabled else TangemTheme.colors.text.primary1,
|
||||
style = TangemTheme.typography.body2,
|
||||
)
|
||||
|
||||
if (!isHidden) {
|
||||
Spacer(modifier = Modifier.weight(1f))
|
||||
|
||||
IconButton(
|
||||
TangemThemePreview {
|
||||
Box(modifier = Modifier.fillMaxSize()) {
|
||||
TangemPayCard(
|
||||
state = state,
|
||||
modifier = Modifier
|
||||
.padding(end = TangemTheme.dimens.spacing8)
|
||||
.size(TangemTheme.dimens.size32),
|
||||
onClick = onCopy,
|
||||
) {
|
||||
Icon(
|
||||
modifier = Modifier.size(TangemTheme.dimens.size24),
|
||||
painter = painterResource(id = R.drawable.ic_copy_new_24),
|
||||
tint = TangemTheme.colors.icon.informative,
|
||||
contentDescription = null,
|
||||
)
|
||||
}
|
||||
.padding(horizontal = 16.dp)
|
||||
.padding(top = 12.dp),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private class TangemPayCardDetailsUMProvider : CollectionPreviewParameterProvider<TangemPayCardDetailsUM>(
|
||||
collection = listOf(
|
||||
TangemPayCardDetailsUM(
|
||||
isLoading = false,
|
||||
onClick = {},
|
||||
number = "*1245",
|
||||
expiry = "",
|
||||
cvv = "",
|
||||
buttonText = resourceReference(R.string.tangempay_card_details_show_details),
|
||||
onCopy = {},
|
||||
isHidden = true,
|
||||
cardFrozenState = TangemPayCardFrozenState.Frozen,
|
||||
),
|
||||
TangemPayCardDetailsUM(
|
||||
isLoading = false,
|
||||
onClick = {},
|
||||
number = "*1245",
|
||||
expiry = "",
|
||||
cvv = "",
|
||||
buttonText = resourceReference(R.string.tangempay_card_details_show_details),
|
||||
onCopy = {},
|
||||
isHidden = true,
|
||||
cardFrozenState = TangemPayCardFrozenState.Unfrozen,
|
||||
),
|
||||
TangemPayCardDetailsUM(
|
||||
isLoading = false,
|
||||
onClick = {},
|
||||
number = "*1245",
|
||||
expiry = "",
|
||||
cvv = "",
|
||||
buttonText = resourceReference(R.string.tangempay_card_details_show_details),
|
||||
onCopy = {},
|
||||
isHidden = true,
|
||||
cardFrozenState = TangemPayCardFrozenState.Pending,
|
||||
),
|
||||
TangemPayCardDetailsUM(
|
||||
isLoading = false,
|
||||
onClick = {},
|
||||
buttonText = resourceReference(R.string.tangempay_card_details_hide_details),
|
||||
onCopy = {},
|
||||
isHidden = false,
|
||||
number = "1234 5678 9012 3456",
|
||||
expiry = "12/34",
|
||||
cvv = "123",
|
||||
cardFrozenState = TangemPayCardFrozenState.Unfrozen,
|
||||
),
|
||||
),
|
||||
)
|
||||
|
|
@ -1,15 +0,0 @@
|
|||
package com.tangem.features.tangempay.ui
|
||||
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import com.tangem.features.tangempay.components.cardDetails.TangemPayCardDetailsBlockComponent
|
||||
import com.tangem.features.tangempay.entity.TangemPayCardDetailsUM
|
||||
|
||||
@Composable
|
||||
internal fun TangemPayCardDetailsBlockItem(
|
||||
component: TangemPayCardDetailsBlockComponent,
|
||||
state: TangemPayCardDetailsUM,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
component.CardDetailsBlockContent(state = state, modifier = modifier)
|
||||
}
|
||||
|
|
@ -7,11 +7,12 @@ import androidx.compose.foundation.layout.*
|
|||
import androidx.compose.foundation.lazy.LazyColumn
|
||||
import androidx.compose.foundation.lazy.rememberLazyListState
|
||||
import androidx.compose.material3.*
|
||||
import androidx.compose.runtime.*
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.saveable.rememberSaveable
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.runtime.setValue
|
||||
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
|
||||
|
|
@ -35,10 +36,10 @@ 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
|
||||
import com.tangem.domain.visa.model.TangemPayCardFrozenState
|
||||
import com.tangem.features.tangempay.components.cardDetails.PreviewTangemPayCardDetailsBlockComponent
|
||||
import com.tangem.features.tangempay.components.cardDetails.TangemPayCardDetailsBlockComponent
|
||||
import com.tangem.features.tangempay.components.txHistory.PreviewTangemPayTxHistoryComponent
|
||||
|
|
@ -48,6 +49,7 @@ import com.tangem.features.tangempay.entity.*
|
|||
import com.tangem.utils.StringsSigns.DASH_SIGN
|
||||
import kotlinx.collections.immutable.persistentListOf
|
||||
|
||||
@Suppress("LongMethod")
|
||||
@Composable
|
||||
internal fun TangemPayDetailsScreen(
|
||||
state: TangemPayDetailsUM,
|
||||
|
|
@ -77,12 +79,33 @@ internal fun TangemPayDetailsScreen(
|
|||
bottom = TangemTheme.dimens.spacing16 + bottomBarHeight,
|
||||
),
|
||||
) {
|
||||
item(TangemPayCardDetailsUM::class.java) {
|
||||
cardDetailsBlockComponent.CardDetailsBlockContent(
|
||||
modifier = Modifier.padding(horizontal = 16.dp).padding(top = 8.dp),
|
||||
state = cardDetailsState,
|
||||
)
|
||||
}
|
||||
when (state.cardFrozenState) {
|
||||
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 = state.cardFrozenState.onUnfreeze,
|
||||
)
|
||||
}
|
||||
else -> Unit
|
||||
}
|
||||
item(
|
||||
key = TangemPayDetailsBalanceBlockState::class.java,
|
||||
content = {
|
||||
TangemPayDetailsBalanceBlock(
|
||||
modifier = modifier
|
||||
.padding(horizontal = TangemTheme.dimens.spacing16)
|
||||
.padding(top = 12.dp)
|
||||
.fillMaxWidth(),
|
||||
state = state.balanceBlockState,
|
||||
isBalanceHidden = state.isBalanceHidden,
|
||||
|
|
@ -102,23 +125,6 @@ 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) }
|
||||
}
|
||||
}
|
||||
|
|
@ -140,20 +146,12 @@ internal fun TangemPayDetailsBalanceBlock(
|
|||
)
|
||||
.padding(vertical = 12.dp),
|
||||
) {
|
||||
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)
|
||||
}
|
||||
Text(
|
||||
modifier = Modifier.padding(start = 12.dp),
|
||||
text = stringResourceSafe(R.string.tangempay_title),
|
||||
color = TangemTheme.colors.text.tertiary,
|
||||
style = TangemTheme.typography.subtitle2,
|
||||
)
|
||||
FiatBalance(
|
||||
modifier = Modifier.padding(start = 12.dp, top = 8.dp),
|
||||
state = state,
|
||||
|
|
@ -176,36 +174,6 @@ 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,
|
||||
|
|
@ -224,30 +192,18 @@ private fun FiatBalance(
|
|||
text = state.fiatBalance.orMaskWithStars(isBalanceHidden),
|
||||
style = TangemTheme.typography.h2.applyBladeBrush(
|
||||
isEnabled = state.isBalanceFlickering,
|
||||
textColor = getTextColor(
|
||||
isCardFrozen = state.frozenState is CardFrozenState.Frozen,
|
||||
defaultColor = TangemTheme.colors.text.primary1,
|
||||
),
|
||||
textColor = TangemTheme.colors.text.primary1,
|
||||
),
|
||||
)
|
||||
is TangemPayDetailsBalanceBlockState.Error -> Text(
|
||||
modifier = modifier,
|
||||
text = DASH_SIGN.orMaskWithStars(isBalanceHidden),
|
||||
style = TangemTheme.typography.h2,
|
||||
color = getTextColor(
|
||||
isCardFrozen = state.frozenState is CardFrozenState.Frozen,
|
||||
defaultColor = TangemTheme.colors.text.primary1,
|
||||
),
|
||||
color = 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(
|
||||
|
|
@ -351,6 +307,7 @@ private fun TangemPayDetailsScreenPreview(
|
|||
onCopy = {},
|
||||
onClick = {},
|
||||
buttonText = TextReference.Res(R.string.tangempay_card_details_hide_text),
|
||||
cardFrozenState = TangemPayCardFrozenState.Unfrozen,
|
||||
),
|
||||
),
|
||||
)
|
||||
|
|
@ -373,22 +330,20 @@ private class TangemPayDetailsUMProvider : CollectionPreviewParameterProvider<Ta
|
|||
cryptoBalance = "1234.56 USDT",
|
||||
fiatBalance = "$1234.56",
|
||||
isBalanceFlickering = false,
|
||||
frozenState = CardFrozenState.Frozen(onUnfreeze = {}),
|
||||
),
|
||||
addToWalletBlockState = AddToWalletBlockState({}, {}),
|
||||
isBalanceHidden = false,
|
||||
addFundsEnabled = true,
|
||||
cardFrozenState = CardFrozenState.Frozen(onUnfreeze = {}),
|
||||
),
|
||||
TangemPayDetailsUM(
|
||||
topBarConfig = TangemPayDetailsTopBarConfig(onBackClick = {}, items = null),
|
||||
pullToRefreshConfig = PullToRefreshConfig(isRefreshing = false, onRefresh = {}),
|
||||
balanceBlockState = TangemPayDetailsBalanceBlockState.Loading(
|
||||
actionButtons = persistentListOf(),
|
||||
frozenState = CardFrozenState.Pending,
|
||||
),
|
||||
balanceBlockState = TangemPayDetailsBalanceBlockState.Loading(actionButtons = persistentListOf()),
|
||||
addToWalletBlockState = AddToWalletBlockState({}, {}),
|
||||
isBalanceHidden = false,
|
||||
addFundsEnabled = true,
|
||||
cardFrozenState = CardFrozenState.Unfrozen,
|
||||
),
|
||||
),
|
||||
)
|
||||
|
|
@ -410,6 +365,7 @@ private fun TangemPayDetailsTxHistoryScreenPreview(
|
|||
onCopy = {},
|
||||
onClick = {},
|
||||
buttonText = TextReference.Res(R.string.tangempay_card_details_hide_text),
|
||||
cardFrozenState = TangemPayCardFrozenState.Unfrozen,
|
||||
),
|
||||
),
|
||||
)
|
||||
|
|
|
|||
|
|
@ -1,19 +0,0 @@
|
|||
package com.tangem.features.tangempay.utils
|
||||
|
||||
internal object CardDetailsFormatUtil {
|
||||
|
||||
private const val CARD_NUMBER_CHUNK_LENGTH = 4
|
||||
private const val DATE_LENGTH = 2
|
||||
|
||||
fun formatCardNumber(cardNumber: String): String {
|
||||
return cardNumber
|
||||
.filterNot { it.isWhitespace() }
|
||||
.chunked(CARD_NUMBER_CHUNK_LENGTH).joinToString(" ")
|
||||
}
|
||||
|
||||
fun formatDate(month: String, year: String): String {
|
||||
val monthFormatted = month.padStart(DATE_LENGTH, '0')
|
||||
val yearFormatted = year.takeLast(DATE_LENGTH)
|
||||
return "$monthFormatted/$yearFormatted"
|
||||
}
|
||||
}
|
||||
|
|
@ -20,10 +20,12 @@ import com.tangem.domain.nft.ObserveAndClearNFTCacheIfNeedUseCase
|
|||
import com.tangem.domain.notifications.GetIsHuaweiDeviceWithoutGoogleServicesUseCase
|
||||
import com.tangem.domain.notifications.repository.NotificationsRepository
|
||||
import com.tangem.domain.pay.repository.OnboardingRepository
|
||||
import com.tangem.domain.pay.repository.TangemPayCardDetailsRepository
|
||||
import com.tangem.domain.pay.usecase.TangemPayIssueOrderUseCase
|
||||
import com.tangem.domain.pay.usecase.TangemPayMainScreenCustomerInfoUseCase
|
||||
import com.tangem.domain.settings.*
|
||||
import com.tangem.domain.tokens.RefreshMultiCurrencyWalletQuotesUseCase
|
||||
import com.tangem.domain.visa.model.TangemPayCardFrozenState
|
||||
import com.tangem.domain.wallets.usecase.*
|
||||
import com.tangem.domain.yield.supply.usecase.YieldSupplyApyUpdateUseCase
|
||||
import com.tangem.feature.wallet.child.wallet.model.intents.WalletClickIntents
|
||||
|
|
@ -98,6 +100,7 @@ internal class WalletModel @Inject constructor(
|
|||
private val tangemPayOnboardingRepository: OnboardingRepository,
|
||||
private val yieldSupplyFeatureToggles: YieldSupplyFeatureToggles,
|
||||
private val accountsFeatureToggles: AccountsFeatureToggles,
|
||||
private val cardDetailsRepository: TangemPayCardDetailsRepository,
|
||||
val screenLifecycleProvider: ScreenLifecycleProvider,
|
||||
val innerWalletRouter: InnerWalletRouter,
|
||||
) : Model() {
|
||||
|
|
@ -376,9 +379,13 @@ internal class WalletModel @Inject constructor(
|
|||
private suspend fun refreshTangemPayInfo() {
|
||||
val info = tangemPayMainScreenCustomerInfoUseCase()
|
||||
if (info != null) {
|
||||
val cardFrozenState =
|
||||
info.info.productInstance?.cardId?.let { cardDetailsRepository.cardFrozenStateSync(it) }
|
||||
?: TangemPayCardFrozenState.Unfrozen
|
||||
stateHolder.update(
|
||||
transformer = TangemPayInitialStateTransformer(
|
||||
value = info,
|
||||
cardFrozenState = cardFrozenState,
|
||||
onClickIssue = ::issueOrder,
|
||||
onClickKyc = innerWalletRouter::openTangemPayOnboarding,
|
||||
openDetails = { config ->
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ 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
|
||||
import com.tangem.domain.visa.model.TangemPayCardFrozenState
|
||||
import com.tangem.feature.wallet.presentation.wallet.state.model.TangemPayState
|
||||
import com.tangem.feature.wallet.presentation.wallet.state.model.WalletScreenState
|
||||
import com.tangem.feature.wallet.presentation.wallet.state.util.TangemPayStateCreator.createIssueAvailableState
|
||||
|
|
@ -24,6 +25,7 @@ private const val POLYGON_CHAIN_ID = 137
|
|||
|
||||
internal class TangemPayInitialStateTransformer(
|
||||
private val value: MainScreenCustomerInfo? = null,
|
||||
private val cardFrozenState: TangemPayCardFrozenState,
|
||||
private val onClickIssue: () -> Unit = {},
|
||||
private val onClickKyc: () -> Unit = {},
|
||||
private val openDetails: (config: TangemPayDetailsConfig) -> Unit = {},
|
||||
|
|
@ -54,10 +56,7 @@ internal class TangemPayInitialStateTransformer(
|
|||
openDetails(
|
||||
TangemPayDetailsConfig(
|
||||
cardId = productInstance.cardId,
|
||||
isCardFrozen = when (productInstance.status) {
|
||||
ProductInstance.Status.ACTIVE -> false
|
||||
ProductInstance.Status.INACTIVE -> true
|
||||
},
|
||||
cardFrozenState = cardFrozenState,
|
||||
customerWalletAddress = cardInfo.customerWalletAddress,
|
||||
cardNumberEnd = cardInfo.lastFourDigits,
|
||||
chainId = POLYGON_CHAIN_ID,
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue