Updated on 2026-08-14

This commit is contained in:
Tangem 2025-09-29 12:34:23 +04:00
parent 46cf9b8b08
commit 210ba5ca59
22 changed files with 303 additions and 90 deletions

View file

@ -18,6 +18,7 @@ import com.tangem.domain.models.wallet.isMultiCurrency
import com.tangem.domain.nft.ObserveAndClearNFTCacheIfNeedUseCase
import com.tangem.domain.notifications.GetIsHuaweiDeviceWithoutGoogleServicesUseCase
import com.tangem.domain.notifications.repository.NotificationsRepository
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
@ -51,6 +52,8 @@ import kotlinx.coroutines.flow.*
import timber.log.Timber
import javax.inject.Inject
private const val TANGEM_PAY_UPDATE_INTERVAL = 60000L
@Suppress("LongParameterList", "LargeClass")
@Stable
@ModelScoped
@ -88,6 +91,7 @@ internal class WalletModel @Inject constructor(
private val hotWalletFeatureToggles: HotWalletFeatureToggles,
private val userWalletsListRepository: UserWalletsListRepository,
private val tangemPayMainScreenCustomerInfoUseCase: TangemPayMainScreenCustomerInfoUseCase,
private val tangemPayIssueOrderUseCase: TangemPayIssueOrderUseCase,
private val tangemPayStateConverter: TangemPayStateConverter,
private val tangemPayFeatureToggles: TangemPayFeatureToggles,
val screenLifecycleProvider: ScreenLifecycleProvider,
@ -105,6 +109,8 @@ internal class WalletModel @Inject constructor(
private var expressTxStatusTaskScheduler = SingleTaskScheduler<Unit>()
private var updateTangemPayJob: Job? = null
init {
analyticsEventsHandler.send(WalletScreenAnalyticsEvent.MainScreen.ScreenOpened)
@ -323,20 +329,44 @@ internal class WalletModel @Inject constructor(
private fun subscribeToTangemPayInfo() {
/**
* Update state each time a user opens/returns to wallet screen
* and every minute while user stays on the main screen
*/
screenLifecycleProvider.isBackgroundState.onEach { isBackground ->
if (!isBackground) { updateTangemPayInfo() }
screenLifecycleProvider.isBackgroundState.onEach { inBackground ->
if (!inBackground && tangemPayFeatureToggles.isTangemPayEnabled) {
updateTangemPayJob = modelScope.launch {
refreshTangemPayInfo()
while (isActive) {
delay(TANGEM_PAY_UPDATE_INTERVAL)
refreshTangemPayInfo()
}
}
} else {
updateTangemPayJob?.cancel()
updateTangemPayJob = null
}
}.launchIn(modelScope)
}
private fun updateTangemPayInfo() {
if (tangemPayFeatureToggles.isTangemPayEnabled) {
modelScope.launch {
tangemPayMainScreenCustomerInfoUseCase()?.let { info ->
stateHolder.update {
it.copy(tangemPayState = tangemPayStateConverter.convert(info))
}
}
private suspend fun refreshTangemPayInfo() {
val newState = withContext(dispatchers.io) {
tangemPayMainScreenCustomerInfoUseCase()?.let { info ->
tangemPayStateConverter.convert(
value = info,
onIssueOrderClick = ::issueOrder,
onContinueKycClick = innerWalletRouter::openTangemPayOnboarding,
)
}
} ?: return
stateHolder.update { it.copy(tangemPayState = newState) }
}
private fun issueOrder() {
modelScope.launch {
stateHolder.update { it.copy(tangemPayState = tangemPayStateConverter.getIssueProgress()) }
withContext(dispatchers.io) {
tangemPayIssueOrderUseCase()
} ?: stateHolder.update {
it.copy(tangemPayState = tangemPayStateConverter.getIssueState(::issueOrder))
}
}
}

View file

@ -107,4 +107,8 @@ internal class DefaultWalletRouter @Inject constructor(
configuration = WalletDialogConfig.TokenReceive(tokenReceiveConfig),
)
}
override fun openTangemPayOnboarding() {
router.push(AppRoute.TangemPayOnboarding(AppRoute.TangemPayOnboarding.Mode.ContinueOnboarding))
}
}

View file

@ -58,4 +58,6 @@ internal interface InnerWalletRouter {
fun openNFT(userWallet: UserWallet)
fun openTokenReceiveBottomSheet(tokenReceiveConfig: TokenReceiveConfig)
fun openTangemPayOnboarding()
}

View file

@ -14,6 +14,7 @@ internal sealed class TangemPayState {
val buttonText: TextReference,
@DrawableRes val iconRes: Int,
val onButtonClick: () -> Unit,
val showProgress: Boolean = false,
) : TangemPayState()
data class Card(

View file

@ -1,48 +1,64 @@
package com.tangem.feature.wallet.presentation.wallet.state.transformers.converter
import com.tangem.common.routing.AppRoute
import com.tangem.common.ui.R
import com.tangem.core.decompose.navigation.Router
import com.tangem.core.ui.extensions.TextReference
import com.tangem.core.ui.format.bigdecimal.fiat
import com.tangem.core.ui.format.bigdecimal.format
import com.tangem.domain.pay.model.CustomerInfo.CardInfo
import com.tangem.domain.pay.model.CustomerInfo
import com.tangem.domain.pay.model.MainScreenCustomerInfo
import com.tangem.domain.pay.model.OrderStatus
import com.tangem.feature.wallet.presentation.wallet.state.model.TangemPayState
import com.tangem.utils.converter.Converter
import com.tangem.feature.wallet.presentation.wallet.state.model.TangemPayState.Progress
import java.util.Currency
import javax.inject.Inject
internal class TangemPayStateConverter @Inject constructor(
private val router: Router,
) : Converter<CustomerInfo, TangemPayState> {
internal class TangemPayStateConverter @Inject constructor() {
override fun convert(value: CustomerInfo): TangemPayState {
val route = AppRoute.TangemPayOnboarding(AppRoute.TangemPayOnboarding.Mode.ContinueOnboarding)
val cardInfo = value.cardInfo
fun convert(
value: MainScreenCustomerInfo,
onIssueOrderClick: () -> Unit,
onContinueKycClick: () -> Unit,
): TangemPayState {
val cardInfo = value.info.cardInfo
return when {
!value.isKycApproved() -> TangemPayState.Progress(
title = TextReference.Res(R.string.tangempay_kyc_in_progress_notification_title),
buttonText = TextReference.Res(R.string.tangempay_kyc_in_progress_notification_button),
iconRes = R.drawable.ic_promo_kyc_36,
onButtonClick = { router.push(route) },
)
!value.isProductInstanceActive() -> TangemPayState.Progress(
title = TextReference.Res(R.string.tangempay_issue_card_notification_title),
buttonText = TextReference.Res(R.string.common_continue),
iconRes = R.drawable.ic_tangem_pay_promo_card_36,
onButtonClick = { router.push(route) },
)
!value.info.isKycApproved() -> {
Progress(
title = TextReference.Res(R.string.tangempay_kyc_in_progress_notification_title),
buttonText = TextReference.Res(R.string.tangempay_kyc_in_progress_notification_button),
iconRes = R.drawable.ic_promo_kyc_36,
onButtonClick = onContinueKycClick,
)
}
value.orderStatus == OrderStatus.NOT_ISSUED || value.orderStatus == OrderStatus.CANCELED -> {
getIssueState(onIssueOrderClick)
}
cardInfo != null -> {
TangemPayState.Card(
lastFourDigits = TextReference.Str("*${cardInfo.lastFourDigits}"),
balanceText = TextReference.Str(getBalanceText(cardInfo)),
)
}
else -> TangemPayState.Empty
else -> {
getIssueProgress()
}
}
}
fun getIssueProgress(): TangemPayState = Progress(
title = TextReference.Res(R.string.tangempay_issue_card_notification_title),
buttonText = TextReference.EMPTY,
iconRes = R.drawable.ic_tangem_pay_promo_card_36,
onButtonClick = {},
showProgress = true,
)
fun getIssueState(onIssueOrderClick: () -> Unit) = Progress(
title = TextReference.Res(R.string.tangempay_issue_card_notification_title),
buttonText = TextReference.Res(R.string.common_continue),
iconRes = R.drawable.ic_tangem_pay_promo_card_36,
onButtonClick = onIssueOrderClick,
)
private fun getBalanceText(cardInfo: CardInfo): String {
val currency = Currency.getInstance(cardInfo.currencyCode)
return cardInfo.balance.format {

View file

@ -1,7 +1,6 @@
package com.tangem.feature.wallet.presentation.wallet.ui.components.visa
import android.content.res.Configuration
import androidx.compose.animation.AnimatedVisibility
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxWidth
@ -16,31 +15,31 @@ import com.tangem.core.ui.extensions.TextReference
import com.tangem.core.ui.res.TangemTheme
import com.tangem.core.ui.res.TangemThemePreview
import com.tangem.feature.wallet.presentation.wallet.state.model.TangemPayState
import com.tangem.feature.wallet.presentation.wallet.state.model.TangemPayState.Progress
import com.tangem.feature.wallet.presentation.wallet.ui.components.singlecurrency.TangemPayCardMainBlock
@Composable
internal fun TangemPayMainScreenBlock(state: TangemPayState, modifier: Modifier = Modifier) {
AnimatedVisibility(state !is TangemPayState.Empty) {
when (state) {
is TangemPayState.Progress -> {
Notification(
modifier = modifier
.fillMaxWidth(),
config = NotificationConfig(
title = state.title,
iconSize = 36.dp,
subtitle = TextReference.EMPTY,
iconResId = state.iconRes,
buttonsState = NotificationConfig.ButtonsState.SecondaryButtonConfig(
text = state.buttonText,
onClick = state.onButtonClick,
),
when (state) {
is Progress -> {
Notification(
modifier = modifier
.fillMaxWidth(),
config = NotificationConfig(
title = state.title,
iconSize = 36.dp,
subtitle = TextReference.EMPTY,
iconResId = state.iconRes,
buttonsState = NotificationConfig.ButtonsState.SecondaryButtonConfig(
text = state.buttonText,
onClick = state.onButtonClick,
showProgress = state.showProgress,
),
)
}
is TangemPayState.Card -> TangemPayCardMainBlock(state, modifier)
is TangemPayState.Empty -> Unit
),
)
}
is TangemPayState.Card -> TangemPayCardMainBlock(state, modifier)
is TangemPayState.Empty -> Unit
}
}
@ -51,7 +50,7 @@ private fun ResetCardScreenPreview() {
TangemThemePreview {
Column(verticalArrangement = Arrangement.spacedBy(TangemTheme.dimens.spacing8)) {
TangemPayMainScreenBlock(
TangemPayState.Progress(
Progress(
title = TextReference.Res(R.string.tangempay_kyc_in_progress_notification_title),
buttonText = TextReference.Res(R.string.tangempay_kyc_in_progress_notification_button),
iconRes = R.drawable.ic_promo_kyc_36,
@ -60,7 +59,7 @@ private fun ResetCardScreenPreview() {
)
TangemPayMainScreenBlock(
TangemPayState.Progress(
Progress(
title = TextReference.Res(R.string.tangempay_issue_card_notification_title),
buttonText = TextReference.Res(R.string.common_continue),
iconRes = R.drawable.ic_tangem_pay_promo_card_36,
@ -68,6 +67,16 @@ private fun ResetCardScreenPreview() {
),
)
TangemPayMainScreenBlock(
Progress(
title = TextReference.Res(R.string.tangempay_issue_card_notification_title),
buttonText = TextReference.EMPTY,
iconRes = R.drawable.ic_tangem_pay_promo_card_36,
onButtonClick = {},
showProgress = true,
),
)
TangemPayMainScreenBlock(
TangemPayState.Card(
lastFourDigits = TextReference.Str("*1234"),