diff --git a/core/config-toggles/src/main/assets/configs/feature_toggles_config.json b/core/config-toggles/src/main/assets/configs/feature_toggles_config.json
index 4beb689f05..b69abace31 100644
--- a/core/config-toggles/src/main/assets/configs/feature_toggles_config.json
+++ b/core/config-toggles/src/main/assets/configs/feature_toggles_config.json
@@ -126,5 +126,9 @@
{
"name": "AND_15368_VISA_PAY_REDESIGN",
"version": "undefined"
+ },
+ {
+ "name": "AND_15364_VISA_PAY_CARD_CLOSE",
+ "version": "undefined"
}
]
diff --git a/core/res/src/main/res/values/strings.xml b/core/res/src/main/res/values/strings.xml
index 18fff34c69..87d1ef2075 100644
--- a/core/res/src/main/res/values/strings.xml
+++ b/core/res/src/main/res/values/strings.xml
@@ -1681,6 +1681,7 @@
Close card
Go back
Close your card?
+ You can’t close the last card
Deposit
Dispute
Explore transaction
diff --git a/core/ui/src/main/java/com/tangem/core/ui/ds/message/TangemMessage.kt b/core/ui/src/main/java/com/tangem/core/ui/ds/message/TangemMessage.kt
index 282be7cef8..ecda9ea392 100644
--- a/core/ui/src/main/java/com/tangem/core/ui/ds/message/TangemMessage.kt
+++ b/core/ui/src/main/java/com/tangem/core/ui/ds/message/TangemMessage.kt
@@ -56,7 +56,7 @@ fun TangemMessage(
subtitle = messageUM.subtitle,
messageEffect = messageUM.messageEffect,
isCentered = messageUM.isCentered,
- content = {
+ trailingContent = {
if (messageUM.iconUM != null) {
TangemIcon(
tangemIconUM = messageUM.iconUM,
@@ -107,7 +107,7 @@ fun TangemMessage(
title = config.title,
subtitle = config.subtitle,
modifier = modifier,
- content = {
+ trailingContent = {
val iconTint = when (config.iconTint) {
NotificationConfig.IconTint.Unspecified -> null
NotificationConfig.IconTint.Accent -> TangemTheme.colors2.graphic.status.accent
@@ -144,13 +144,14 @@ fun TangemMessage(
* Tangem message component that displays a message with optional title, subtitle, content, and buttons.
* [Message](https://www.figma.com/design/RU7AIgwHtGdMfy83T5UOoR/Core-Library?node-id=8455-81318&m=dev)
*
- * @param modifier Modifier to be applied to the message component.
- * @param title Optional title of the message.
- * @param subtitle Optional subtitle of the message.
- * @param messageEffect Effect to be applied to the message background.
- * @param content Optional composable content to be displayed alongside the title and subtitle.
- * @param buttons Optional composable buttons to be displayed below the message.
- * @param isCentered Flag indicating whether the content should be centered horizontally.
+ * @param modifier Modifier to be applied to the message component.
+ * @param title Optional title of the message.
+ * @param subtitle Optional subtitle of the message.
+ * @param messageEffect Effect to be applied to the message background.
+ * @param leadingContent Optional composable content displayed before the title and subtitle.
+ * @param trailingContent Optional composable content displayed after the title and subtitle.
+ * @param buttons Optional composable buttons to be displayed below the message.
+ * @param isCentered Flag indicating whether the content should be centered horizontally.
*/
@Composable
fun TangemMessage(
@@ -161,7 +162,8 @@ fun TangemMessage(
onCloseClick: (() -> Unit)? = null,
isCentered: Boolean = false,
contentColor: Color = TangemTheme.colors2.surface.level3,
- content: (@Composable RowScope.() -> Unit)? = null,
+ leadingContent: (@Composable RowScope.() -> Unit)? = null,
+ trailingContent: (@Composable RowScope.() -> Unit)? = null,
buttons: (@Composable RowScope.() -> Unit)? = null,
) {
val alignment = if (isCentered) {
@@ -190,7 +192,8 @@ fun TangemMessage(
title = title,
subtitle = subtitle,
alignment = alignment,
- content = content,
+ leadingContent = leadingContent,
+ content = trailingContent,
isCentered = isCentered,
)
if (buttons != null) {
@@ -224,6 +227,7 @@ private fun TangemMessageContent(
subtitle: TextReference? = null,
alignment: Alignment.Horizontal = Alignment.Start,
isCentered: Boolean = false,
+ leadingContent: (@Composable RowScope.() -> Unit)? = null,
content: (@Composable RowScope.() -> Unit)? = null,
) {
val textAlign = if (isCentered) {
@@ -235,6 +239,7 @@ private fun TangemMessageContent(
horizontalArrangement = Arrangement.spacedBy(TangemTheme.dimens2.x2),
modifier = Modifier.padding(TangemTheme.dimens2.x1),
) {
+ leadingContent?.invoke(this)
Column(
modifier = Modifier.weight(1f),
horizontalAlignment = alignment,
@@ -423,7 +428,7 @@ private fun TangemMessage2_Preview() {
subtitle = stringReference("Subtext"),
messageEffect = TangemMessageEffect.Magic,
isCentered = false,
- content = {
+ trailingContent = {
Box(
modifier = Modifier
.size(TangemTheme.dimens2.x7)
diff --git a/domain/visa/src/main/kotlin/com/tangem/domain/tangempay/TangemPayAnalyticsEvents.kt b/domain/visa/src/main/kotlin/com/tangem/domain/tangempay/TangemPayAnalyticsEvents.kt
index 18dadd605d..a955c92974 100644
--- a/domain/visa/src/main/kotlin/com/tangem/domain/tangempay/TangemPayAnalyticsEvents.kt
+++ b/domain/visa/src/main/kotlin/com/tangem/domain/tangempay/TangemPayAnalyticsEvents.kt
@@ -218,6 +218,21 @@ sealed class TangemPayAnalyticsEvents(
event = "Visa Replace Card Confirmed",
)
+ class CloseCardClicked : TangemPayAnalyticsEvents(
+ categoryName = "Visa Card Management",
+ event = "Visa Close Card Clicked",
+ )
+
+ class CloseCardConfirmationPopupOpened : TangemPayAnalyticsEvents(
+ categoryName = "Visa Card Management",
+ event = "Visa Close Card Confirmation Popup Opened",
+ )
+
+ class CloseCardConfirmed : TangemPayAnalyticsEvents(
+ categoryName = "Visa Card Management",
+ event = "Visa Close Card Confirmed",
+ )
+
class LimitChangeClicked : TangemPayAnalyticsEvents(
categoryName = "Visa Card Management",
event = "Visa Daily Limit Change Clicked",
diff --git a/features/tangempay/details/api/src/main/kotlin/com/tangem/features/tangempay/TangemPayFeatureToggles.kt b/features/tangempay/details/api/src/main/kotlin/com/tangem/features/tangempay/TangemPayFeatureToggles.kt
index c2f6f12c37..aa6c289349 100644
--- a/features/tangempay/details/api/src/main/kotlin/com/tangem/features/tangempay/TangemPayFeatureToggles.kt
+++ b/features/tangempay/details/api/src/main/kotlin/com/tangem/features/tangempay/TangemPayFeatureToggles.kt
@@ -2,4 +2,5 @@ package com.tangem.features.tangempay
interface TangemPayFeatureToggles {
val isRedesignEnabled: Boolean
+ val isCloseCardEnabled: Boolean
}
\ No newline at end of file
diff --git a/features/tangempay/details/impl/src/main/kotlin/com/tangem/features/tangempay/DefaultTangemPayFeatureToggles.kt b/features/tangempay/details/impl/src/main/kotlin/com/tangem/features/tangempay/DefaultTangemPayFeatureToggles.kt
index 0ea33aafcd..aa2166183f 100644
--- a/features/tangempay/details/impl/src/main/kotlin/com/tangem/features/tangempay/DefaultTangemPayFeatureToggles.kt
+++ b/features/tangempay/details/impl/src/main/kotlin/com/tangem/features/tangempay/DefaultTangemPayFeatureToggles.kt
@@ -9,4 +9,7 @@ internal class DefaultTangemPayFeatureToggles(
override val isRedesignEnabled: Boolean
get() = featureTogglesManager.isFeatureEnabled(FeatureToggles.AND_15368_VISA_PAY_REDESIGN) &&
featureTogglesManager.isFeatureEnabled(FeatureToggles.APP_REDESIGN_ENABLED)
+
+ override val isCloseCardEnabled: Boolean
+ get() = featureTogglesManager.isFeatureEnabled(FeatureToggles.AND_15364_VISA_PAY_CARD_CLOSE)
}
\ No newline at end of file
diff --git a/features/tangempay/details/impl/src/main/kotlin/com/tangem/features/tangempay/closure/TangemPayCloseCardComponent.kt b/features/tangempay/details/impl/src/main/kotlin/com/tangem/features/tangempay/closure/TangemPayCloseCardComponent.kt
new file mode 100644
index 0000000000..13717caa01
--- /dev/null
+++ b/features/tangempay/details/impl/src/main/kotlin/com/tangem/features/tangempay/closure/TangemPayCloseCardComponent.kt
@@ -0,0 +1,35 @@
+package com.tangem.features.tangempay.closure
+
+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
+
+internal class TangemPayCloseCardComponent(
+ appComponentContext: AppComponentContext,
+ private val params: Params,
+) : ComposableBottomSheetComponent, AppComponentContext by appComponentContext {
+
+ private val model: TangemPayCloseCardModel = getOrCreateModel(params = params)
+
+ override fun dismiss() = model.onDismiss()
+
+ @Composable
+ override fun BottomSheet() {
+ val state by model.state.collectAsStateWithLifecycle()
+ TangemPayCloseCardContent(state = state)
+ }
+
+ data class Params(
+ val listener: CloseCardListener,
+ val userWalletId: UserWalletId,
+ val cardId: String,
+ )
+}
+
+internal interface CloseCardListener {
+ fun onDismissCloseCard()
+}
\ No newline at end of file
diff --git a/features/tangempay/details/impl/src/main/kotlin/com/tangem/features/tangempay/closure/TangemPayCloseCardContent.kt b/features/tangempay/details/impl/src/main/kotlin/com/tangem/features/tangempay/closure/TangemPayCloseCardContent.kt
new file mode 100644
index 0000000000..19032854f9
--- /dev/null
+++ b/features/tangempay/details/impl/src/main/kotlin/com/tangem/features/tangempay/closure/TangemPayCloseCardContent.kt
@@ -0,0 +1,145 @@
+package com.tangem.features.tangempay.closure
+
+import android.content.res.Configuration
+import androidx.compose.foundation.background
+import androidx.compose.foundation.layout.*
+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.bottomsheets.TangemBottomSheet
+import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfig
+import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfigContent
+import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetType
+import com.tangem.core.ui.ds.image.TangemIconUM
+import com.tangem.core.ui.ds.topbar.TangemTopBar
+import com.tangem.core.ui.ds.topbar.TangemTopBarType
+import com.tangem.core.ui.ds2.button.TangemButton
+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.TangemThemePreviewRedesign
+import com.tangem.features.tangempay.details.impl.R
+
+@Composable
+internal fun TangemPayCloseCardContent(state: TangemPayCloseCardUM) {
+ TangemBottomSheet(
+ config = TangemBottomSheetConfig(
+ isShown = true,
+ onDismissRequest = state.onDismissRequest,
+ content = TangemBottomSheetConfigContent.Empty,
+ ),
+ type = TangemBottomSheetType.Modal,
+ containerColor = TangemTheme.colors3.bg.secondary,
+ title = {
+ TangemTopBar(
+ type = TangemTopBarType.BottomSheet,
+ endContent = {
+ TangemButton(
+ iconStart = TangemIconUM.Icon(iconRes = R.drawable.ic_close_24),
+ onClick = state.onDismissRequest,
+ size = TangemButton.Size.X11,
+ variant = TangemButton.Variant.Material,
+ )
+ },
+ )
+ },
+ content = {
+ Content(state)
+ },
+ )
+}
+
+@Composable
+private fun Content(state: TangemPayCloseCardUM) {
+ Column(
+ modifier = Modifier
+ .fillMaxWidth()
+ .padding(horizontal = TangemTheme.dimens2.x4),
+ horizontalAlignment = Alignment.CenterHorizontally,
+ ) {
+ Spacer(modifier = Modifier.height(TangemTheme.dimens2.x6))
+
+ Box(
+ modifier = Modifier
+ .size(80.dp)
+ .clip(CircleShape)
+ .background(TangemTheme.colors3.bg.status.warningSubtle),
+ contentAlignment = Alignment.Center,
+ ) {
+ Icon(
+ painter = painterResource(id = R.drawable.ic_warning_20),
+ contentDescription = null,
+ tint = TangemTheme.colors3.icon.status.warning,
+ modifier = Modifier.size(28.dp),
+ )
+ }
+
+ Spacer(modifier = Modifier.height(TangemTheme.dimens2.x8))
+
+ Text(
+ text = stringResourceSafe(R.string.tangem_pay_close_card_popup_title),
+ style = TangemTheme.typography3.heading.small,
+ color = TangemTheme.colors3.text.primary,
+ textAlign = TextAlign.Center,
+ modifier = Modifier.fillMaxWidth(),
+ )
+
+ Spacer(modifier = Modifier.height(TangemTheme.dimens2.x2))
+
+ Text(
+ text = stringResourceSafe(R.string.tangem_pay_close_card_popup_description),
+ style = TangemTheme.typography3.subheading.medium,
+ color = TangemTheme.colors3.text.secondary,
+ textAlign = TextAlign.Center,
+ modifier = Modifier.fillMaxWidth(),
+ )
+
+ Spacer(modifier = Modifier.height(TangemTheme.dimens2.x12))
+
+ TangemButton(
+ modifier = Modifier.fillMaxWidth(),
+ variant = TangemButton.Variant.Secondary,
+ size = TangemButton.Size.X12,
+ text = resourceReference(R.string.tangem_pay_close_card_popup_secondary_button_title),
+ isEnabled = !state.isClosingInProgress,
+ onClick = state.onDismissRequest,
+ )
+
+ Spacer(modifier = Modifier.height(TangemTheme.dimens2.x2))
+
+ TangemButton(
+ modifier = Modifier.fillMaxWidth(),
+ variant = TangemButton.Variant.Primary,
+ size = TangemButton.Size.X12,
+ text = resourceReference(R.string.tangem_pay_close_card_popup_primary_button_title),
+ isLoading = state.isClosingInProgress,
+ isEnabled = !state.isClosingInProgress,
+ onClick = state.onCloseClick,
+ )
+
+ Spacer(modifier = Modifier.height(TangemTheme.dimens2.x4))
+ }
+}
+
+@Preview(showBackground = true)
+@Preview(uiMode = Configuration.UI_MODE_NIGHT_YES)
+@Composable
+private fun Preview() {
+ TangemThemePreviewRedesign {
+ Content(
+ state = TangemPayCloseCardUM(
+ isClosingInProgress = true,
+ onCloseClick = {},
+ onDismissRequest = {},
+ ),
+ )
+ }
+}
\ No newline at end of file
diff --git a/features/tangempay/details/impl/src/main/kotlin/com/tangem/features/tangempay/closure/TangemPayCloseCardModel.kt b/features/tangempay/details/impl/src/main/kotlin/com/tangem/features/tangempay/closure/TangemPayCloseCardModel.kt
new file mode 100644
index 0000000000..5ca6139ff4
--- /dev/null
+++ b/features/tangempay/details/impl/src/main/kotlin/com/tangem/features/tangempay/closure/TangemPayCloseCardModel.kt
@@ -0,0 +1,68 @@
+package com.tangem.features.tangempay.closure
+
+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.message.SnackbarMessage
+import com.tangem.domain.pay.usecase.CloseTangemPayCardUseCase
+import com.tangem.domain.tangempay.TangemPayAnalyticsEvents
+import com.tangem.features.tangempay.details.impl.R
+import com.tangem.utils.coroutines.CoroutineDispatcherProvider
+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 TangemPayCloseCardModel @Inject constructor(
+ paramsContainer: ParamsContainer,
+ override val dispatchers: CoroutineDispatcherProvider,
+ private val closeTangemPayCardUseCase: CloseTangemPayCardUseCase,
+ private val uiMessageSender: UiMessageSender,
+ private val analytics: AnalyticsEventHandler,
+) : Model() {
+
+ private val params = paramsContainer.require()
+
+ val state: StateFlow
+ field = MutableStateFlow(
+ TangemPayCloseCardUM(
+ isClosingInProgress = false,
+ onCloseClick = ::onConfirm,
+ onDismissRequest = ::onDismiss,
+ ),
+ )
+
+ init {
+ analytics.send(TangemPayAnalyticsEvents.CloseCardConfirmationPopupOpened())
+ }
+
+ fun onDismiss() {
+ if (state.value.isClosingInProgress) return
+ params.listener.onDismissCloseCard()
+ }
+
+ private fun onConfirm() {
+ if (state.value.isClosingInProgress) return
+ analytics.send(TangemPayAnalyticsEvents.CloseCardConfirmed())
+ state.update { it.copy(isClosingInProgress = true) }
+ modelScope.launch {
+ closeTangemPayCardUseCase(
+ userWalletId = params.userWalletId,
+ cardId = params.cardId,
+ ).onLeft {
+ state.update { uiState -> uiState.copy(isClosingInProgress = false) }
+ uiMessageSender.send(SnackbarMessage(resourceReference(R.string.common_something_went_wrong)))
+ params.listener.onDismissCloseCard()
+ }.onRight {
+ params.listener.onDismissCloseCard()
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/features/tangempay/details/impl/src/main/kotlin/com/tangem/features/tangempay/closure/TangemPayCloseCardUM.kt b/features/tangempay/details/impl/src/main/kotlin/com/tangem/features/tangempay/closure/TangemPayCloseCardUM.kt
new file mode 100644
index 0000000000..aa06a1cc86
--- /dev/null
+++ b/features/tangempay/details/impl/src/main/kotlin/com/tangem/features/tangempay/closure/TangemPayCloseCardUM.kt
@@ -0,0 +1,10 @@
+package com.tangem.features.tangempay.closure
+
+import androidx.compose.runtime.Immutable
+
+@Immutable
+internal data class TangemPayCloseCardUM(
+ val isClosingInProgress: Boolean,
+ val onCloseClick: () -> Unit,
+ val onDismissRequest: () -> Unit,
+)
\ No newline at end of file
diff --git a/features/tangempay/details/impl/src/main/kotlin/com/tangem/features/tangempay/components/TangemPayCardPageScreenComponent.kt b/features/tangempay/details/impl/src/main/kotlin/com/tangem/features/tangempay/components/TangemPayCardPageScreenComponent.kt
index 7e5dc2eada..8f34c3f102 100644
--- a/features/tangempay/details/impl/src/main/kotlin/com/tangem/features/tangempay/components/TangemPayCardPageScreenComponent.kt
+++ b/features/tangempay/details/impl/src/main/kotlin/com/tangem/features/tangempay/components/TangemPayCardPageScreenComponent.kt
@@ -17,6 +17,7 @@ import com.tangem.core.ui.components.NavigationBar3ButtonsScrim
import com.tangem.core.ui.decompose.ComposableBottomSheetComponent
import com.tangem.core.ui.decompose.ComposableContentComponent
import com.tangem.core.ui.res.LocalVisaRedesignEnabled
+import com.tangem.features.tangempay.closure.TangemPayCloseCardComponent
import com.tangem.features.tangempay.components.cardDetails.DefaultTangemPayCardDetailsBlockComponent
import com.tangem.features.tangempay.components.cardDetails.TangemPayCardDetailsBlockComponent
import com.tangem.features.tangempay.entity.TangemPayCardNavigation
@@ -90,6 +91,14 @@ internal class TangemPayCardPageScreenComponent(
cardId = params.initialStatus.firstCard().id,
),
)
+ is TangemPayCardNavigation.CloseCard -> TangemPayCloseCardComponent(
+ appComponentContext = context,
+ params = TangemPayCloseCardComponent.Params(
+ listener = model,
+ userWalletId = navigation.userWalletId,
+ cardId = navigation.cardId,
+ ),
+ )
is TangemPayCardNavigation.AddFunds -> TangemPayAddFundsComponent(
appComponentContext = context,
params = TangemPayAddFundsComponent.Params(
diff --git a/features/tangempay/details/impl/src/main/kotlin/com/tangem/features/tangempay/di/TangemPayModelModule.kt b/features/tangempay/details/impl/src/main/kotlin/com/tangem/features/tangempay/di/TangemPayModelModule.kt
index 1766696a25..165d7cb95a 100644
--- a/features/tangempay/details/impl/src/main/kotlin/com/tangem/features/tangempay/di/TangemPayModelModule.kt
+++ b/features/tangempay/details/impl/src/main/kotlin/com/tangem/features/tangempay/di/TangemPayModelModule.kt
@@ -6,6 +6,7 @@ import com.tangem.features.tangempay.model.TangemPayAddFundsModel
import com.tangem.features.tangempay.model.TangemPayAddToWalletModel
import com.tangem.features.tangempay.model.TangemPayCardDetailsBlockModel
import com.tangem.features.tangempay.model.TangemPayCardPageModel
+import com.tangem.features.tangempay.closure.TangemPayCloseCardModel
import com.tangem.features.tangempay.limit.setup.TangemPayCardLimitSetupModel
import com.tangem.features.tangempay.model.TangemPayChangePinModel
import com.tangem.features.tangempay.model.TangemPayDetailsModel
@@ -79,6 +80,11 @@ internal interface TangemPayModelModule {
@ClassKey(TangemPayReissueCardModel::class)
fun bindTangemPayReissueCardModel(model: TangemPayReissueCardModel): Model
+ @Binds
+ @IntoMap
+ @ClassKey(TangemPayCloseCardModel::class)
+ fun bindTangemPayCloseCardModel(model: TangemPayCloseCardModel): Model
+
@Binds
@IntoMap
@ClassKey(TangemPayCardLimitSetupModel::class)
diff --git a/features/tangempay/details/impl/src/main/kotlin/com/tangem/features/tangempay/entity/TangemPayCardNavigation.kt b/features/tangempay/details/impl/src/main/kotlin/com/tangem/features/tangempay/entity/TangemPayCardNavigation.kt
index f972f46a39..eef562c6c5 100644
--- a/features/tangempay/details/impl/src/main/kotlin/com/tangem/features/tangempay/entity/TangemPayCardNavigation.kt
+++ b/features/tangempay/details/impl/src/main/kotlin/com/tangem/features/tangempay/entity/TangemPayCardNavigation.kt
@@ -17,6 +17,12 @@ internal sealed class TangemPayCardNavigation {
@Serializable
data object ReissueCard : TangemPayCardNavigation()
+ @Serializable
+ data class CloseCard(
+ val userWalletId: UserWalletId,
+ val cardId: String,
+ ) : TangemPayCardNavigation()
+
@Serializable
data class AddFunds(
val walletId: UserWalletId,
diff --git a/features/tangempay/details/impl/src/main/kotlin/com/tangem/features/tangempay/entity/TangemPayCardPageUM.kt b/features/tangempay/details/impl/src/main/kotlin/com/tangem/features/tangempay/entity/TangemPayCardPageUM.kt
index cb24f4b42d..406e3c16e6 100644
--- a/features/tangempay/details/impl/src/main/kotlin/com/tangem/features/tangempay/entity/TangemPayCardPageUM.kt
+++ b/features/tangempay/details/impl/src/main/kotlin/com/tangem/features/tangempay/entity/TangemPayCardPageUM.kt
@@ -4,6 +4,7 @@ import androidx.annotation.DrawableRes
import androidx.compose.runtime.Immutable
import com.tangem.core.ui.extensions.TextReference
import com.tangem.core.ui.extensions.resourceReference
+import com.tangem.domain.models.pay.TangemPayCardState
import com.tangem.features.tangempay.details.impl.R
import kotlinx.collections.immutable.ImmutableList
import kotlinx.collections.immutable.persistentListOf
@@ -16,7 +17,7 @@ internal data class TangemPayCardPageUM(
val onBackClick: () -> Unit,
val dailyLimitState: TangemPayDailyLimitBlockState,
val addToWalletBlockState: AddToWalletBlockState? = null,
- val isReissueInProgress: Boolean = false,
+ val cardState: TangemPayCardState = TangemPayCardState.Active,
val menuItems: ImmutableList,
) {
companion object {
@@ -31,7 +32,7 @@ internal data class TangemPayCardPageUM(
TangemPayCardPageSetting(TextReference.Str("Freeze Card")) {},
TangemPayCardPageSetting(TextReference.Str("Reissue Card")) {},
),
- isReissueInProgress: Boolean = false,
+ cardState: TangemPayCardState = TangemPayCardState.Active,
dailyLimitState: TangemPayDailyLimitBlockState = TangemPayDailyLimitBlockState.Content.stub(),
settingsV2: ImmutableList = TangemPayCardPageSettingV2.stubList(),
) = TangemPayCardPageUM(
@@ -39,7 +40,7 @@ internal data class TangemPayCardPageUM(
settings = settings,
settingsV2 = settingsV2,
onBackClick = {},
- isReissueInProgress = isReissueInProgress,
+ cardState = cardState,
dailyLimitState = dailyLimitState,
menuItems = persistentListOf(),
)
diff --git a/features/tangempay/details/impl/src/main/kotlin/com/tangem/features/tangempay/entity/TangemPayDropDownItemUM.kt b/features/tangempay/details/impl/src/main/kotlin/com/tangem/features/tangempay/entity/TangemPayDropDownItemUM.kt
index 71d30e3f2e..904b272672 100644
--- a/features/tangempay/details/impl/src/main/kotlin/com/tangem/features/tangempay/entity/TangemPayDropDownItemUM.kt
+++ b/features/tangempay/details/impl/src/main/kotlin/com/tangem/features/tangempay/entity/TangemPayDropDownItemUM.kt
@@ -7,4 +7,6 @@ internal data class TangemPayDropDownItemUM(
val onClick: () -> Unit,
val title: TextReference,
val icon: TangemIconUM,
+ val subtitle: TextReference? = null,
+ val isEnabled: Boolean = true,
)
\ No newline at end of file
diff --git a/features/tangempay/details/impl/src/main/kotlin/com/tangem/features/tangempay/model/TangemPayCardPageModel.kt b/features/tangempay/details/impl/src/main/kotlin/com/tangem/features/tangempay/model/TangemPayCardPageModel.kt
index 97dcffc0fd..cfdd4ebb6f 100644
--- a/features/tangempay/details/impl/src/main/kotlin/com/tangem/features/tangempay/model/TangemPayCardPageModel.kt
+++ b/features/tangempay/details/impl/src/main/kotlin/com/tangem/features/tangempay/model/TangemPayCardPageModel.kt
@@ -38,6 +38,7 @@ import com.tangem.domain.pay.usecase.ChangeCardFrozenStateUseCase
import com.tangem.domain.tangempay.TangemPayAnalyticsEvents
import com.tangem.features.tangempay.TangemPayFeatureToggles
import com.tangem.features.tangempay.components.AddFundsListener
+import com.tangem.features.tangempay.closure.CloseCardListener
import com.tangem.features.tangempay.components.ReissueCardListener
import com.tangem.features.tangempay.components.TangemPayCardPageComponent
import com.tangem.features.tangempay.components.ViewPinListener
@@ -72,7 +73,7 @@ internal class TangemPayCardPageModel @Inject constructor(
private val changeCardFrozenStateUseCase: ChangeCardFrozenStateUseCase,
private val cardDetailsEventListener: CardDetailsEventListener,
private val tangemPayFeatureToggles: TangemPayFeatureToggles,
-) : Model(), ViewPinListener, ReissueCardListener, AddFundsListener {
+) : Model(), ViewPinListener, ReissueCardListener, AddFundsListener, CloseCardListener {
private val params: TangemPayCardPageComponent.Params = paramsContainer.require()
@@ -94,7 +95,9 @@ internal class TangemPayCardPageModel @Inject constructor(
dailyLimitState = TangemPayDailyLimitBlockState.Loading,
settings = persistentListOf(),
settingsV2 = persistentListOf(),
- menuItems = buildMenuItems(),
+ menuItems = buildMenuItems(
+ isLastCard = params.initialStatus.ifLoadedOrNull { it.cards.isLastCard() } ?: false,
+ ),
),
)
@@ -128,7 +131,8 @@ internal class TangemPayCardPageModel @Inject constructor(
dailyLimitState = dailyLimitState,
settings = buildSettings(card),
settingsV2 = buildSettingsV2(card),
- isReissueInProgress = card.state == TangemPayCardState.Reissuing,
+ menuItems = buildMenuItems(isLastCard = status.cards.isLastCard()),
+ cardState = card.state,
)
}
} else {
@@ -218,19 +222,45 @@ internal class TangemPayCardPageModel @Inject constructor(
)
}
- private fun buildMenuItems(): ImmutableList {
- return persistentListOf(
- TangemPayDropDownItemUM(
- title = TextReference.Res(R.string.tangempay_card_details_reissue_card),
- onClick = ::onClickReissueCard,
- icon = TangemIconUM.Icon(
- imageVector = Icons.ic_arrow_refresh_20,
- tintReference = {
- TangemTheme.colors3.icon.primary
- },
+ private fun buildMenuItems(isLastCard: Boolean): ImmutableList {
+ return buildList {
+ add(
+ TangemPayDropDownItemUM(
+ title = TextReference.Res(R.string.tangempay_card_details_reissue_card),
+ onClick = ::onClickReissueCard,
+ icon = TangemIconUM.Icon(
+ imageVector = Icons.ic_arrow_refresh_20,
+ tintReference = {
+ TangemTheme.colors3.icon.primary
+ },
+ ),
),
- ),
- )
+ )
+ if (tangemPayFeatureToggles.isCloseCardEnabled) {
+ add(
+ TangemPayDropDownItemUM(
+ title = TextReference.Res(R.string.tangem_pay_close_card_popup_primary_button_title),
+ onClick = ::onClickCloseCard,
+ icon = TangemIconUM.Icon(
+ iconRes = CoreUiR.drawable.ic_trash_24,
+ tintReference = {
+ if (isLastCard) {
+ TangemTheme.colors3.icon.tertiary
+ } else {
+ TangemTheme.colors3.icon.primary
+ }
+ },
+ ),
+ subtitle = if (isLastCard) {
+ TextReference.Res(R.string.tangem_pay_close_card_disabled_last_card)
+ } else {
+ null
+ },
+ isEnabled = !isLastCard,
+ ),
+ )
+ }
+ }.toImmutableList()
}
private fun onClickViewDetails() {
@@ -278,6 +308,21 @@ internal class TangemPayCardPageModel @Inject constructor(
bottomSheetNavigation.dismiss()
}
+ override fun onDismissCloseCard() {
+ bottomSheetNavigation.dismiss()
+ }
+
+ private fun onClickCloseCard() {
+ analytics.send(TangemPayAnalyticsEvents.CloseCardClicked())
+ val card = currentStatus.value.findCard(initialCardId, params.initialStatus) ?: return
+ bottomSheetNavigation.activate(
+ TangemPayCardNavigation.CloseCard(
+ userWalletId = userWalletId,
+ cardId = card.id,
+ ),
+ )
+ }
+
override fun onClickAddFunds() {
bottomSheetNavigation.dismiss()
modelScope.launch {
@@ -403,4 +448,6 @@ internal class TangemPayCardPageModel @Inject constructor(
override fun onDismissViewPin() {
bottomSheetNavigation.dismiss()
}
-}
\ No newline at end of file
+}
+
+private fun List.isLastCard(): Boolean = count { it.state == TangemPayCardState.Active } <= 1
\ No newline at end of file
diff --git a/features/tangempay/details/impl/src/main/kotlin/com/tangem/features/tangempay/ui/TangemPayCardPageScreen.kt b/features/tangempay/details/impl/src/main/kotlin/com/tangem/features/tangempay/ui/TangemPayCardPageScreen.kt
index 044a3ea8c2..6bb50a2d87 100644
--- a/features/tangempay/details/impl/src/main/kotlin/com/tangem/features/tangempay/ui/TangemPayCardPageScreen.kt
+++ b/features/tangempay/details/impl/src/main/kotlin/com/tangem/features/tangempay/ui/TangemPayCardPageScreen.kt
@@ -28,9 +28,11 @@ import com.tangem.core.ui.ds.image.TangemIconUM
import com.tangem.core.ui.ds.topbar.TangemTopBar
import com.tangem.core.ui.ds2.button.TangemButton
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.*
import com.tangem.domain.models.pay.TangemPayCardFrozenState
+import com.tangem.domain.models.pay.TangemPayCardState
import com.tangem.features.tangempay.components.cardDetails.PreviewTangemPayCardDetailsBlockComponent
import com.tangem.features.tangempay.components.cardDetails.TangemPayCardDetailsBlockComponent
import com.tangem.features.tangempay.details.impl.R
@@ -82,7 +84,7 @@ internal fun TangemPayCardPageScreen(
state = cardDetailsState,
)
}
- if (isRedesignEnabled && state.settingsV2.isNotEmpty()) {
+ if (isRedesignEnabled && state.settingsV2.isNotEmpty() && state.cardState == TangemPayCardState.Active) {
cardPageItem("Settings buttons") {
TangemPayCardPageSettingsButtonsBlock(
modifier = Modifier.fillMaxWidth(),
@@ -90,28 +92,39 @@ internal fun TangemPayCardPageScreen(
)
}
}
- if (state.isReissueInProgress) {
- cardPageItem(key = "Reissue") {
- TangemPayReplacingCardBlock()
- }
- } else {
- if (state.addToWalletBlockState != null) {
- cardPageItem(key = "GooglePay") {
- TangemPayAddToWalletBlock(state = state.addToWalletBlockState)
- }
- }
- cardPageItem(key = "Limit") {
- TangemPayDailyLimitBlock(state = state.dailyLimitState)
- }
- if (state.dailyLimitState == TangemPayDailyLimitBlockState.Error) {
- cardPageItem(key = "LimitError") {
- TangemPayDailyLimitErrorBlock()
- }
- }
- cardPageItem(key = "Settings") {
- TangemPayCardPageSettingsBlock(settings = state.settings)
+ cardState(state)
+ }
+ }
+}
+
+private fun LazyListScope.cardState(state: TangemPayCardPageUM) {
+ when (state.cardState) {
+ TangemPayCardState.Active -> {
+ if (state.addToWalletBlockState != null) {
+ cardPageItem(key = "GooglePay") {
+ TangemPayAddToWalletBlock(state = state.addToWalletBlockState)
}
}
+ cardPageItem(key = "Limit") {
+ TangemPayDailyLimitBlock(state = state.dailyLimitState)
+ }
+ if (state.dailyLimitState == TangemPayDailyLimitBlockState.Error) {
+ cardPageItem(key = "LimitError") {
+ TangemPayDailyLimitErrorBlock()
+ }
+ }
+ cardPageItem(key = "Settings") {
+ TangemPayCardPageSettingsBlock(settings = state.settings)
+ }
+ }
+ TangemPayCardState.Reissuing -> cardPageItem(key = "Reissue") {
+ TangemPayReplacingCardBlock()
+ }
+ TangemPayCardState.Closing -> cardPageItem(key = "Closing") {
+ TangemPayReplacingCardBlock(
+ title = resourceReference(R.string.tangempay_card_page_closing_banner_title),
+ subtitle = resourceReference(R.string.tangempay_card_page_closing_banner_description),
+ )
}
}
}
diff --git a/features/tangempay/details/impl/src/main/kotlin/com/tangem/features/tangempay/ui/TangemPayReplacingCardBlock.kt b/features/tangempay/details/impl/src/main/kotlin/com/tangem/features/tangempay/ui/TangemPayReplacingCardBlock.kt
index ac1af73851..88a94ec847 100644
--- a/features/tangempay/details/impl/src/main/kotlin/com/tangem/features/tangempay/ui/TangemPayReplacingCardBlock.kt
+++ b/features/tangempay/details/impl/src/main/kotlin/com/tangem/features/tangempay/ui/TangemPayReplacingCardBlock.kt
@@ -1,35 +1,93 @@
package com.tangem.features.tangempay.ui
import android.content.res.Configuration
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.size
+import androidx.compose.material3.Icon
import androidx.compose.runtime.Composable
+import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.ui.Modifier
+import androidx.compose.ui.graphics.vector.ImageVector
+import androidx.compose.ui.res.vectorResource
import androidx.compose.ui.tooling.preview.Preview
+import androidx.compose.ui.unit.dp
+import com.tangem.core.ui.components.SpacerH24
import com.tangem.core.ui.components.notifications.Notification
import com.tangem.core.ui.components.notifications.NotificationConfig
+import com.tangem.core.ui.ds.message.TangemMessage
+import com.tangem.core.ui.extensions.TextReference
import com.tangem.core.ui.extensions.resourceReference
+import com.tangem.core.ui.res.LocalVisaRedesignEnabled
import com.tangem.core.ui.res.TangemTheme
import com.tangem.core.ui.res.TangemThemePreview
+import com.tangem.core.ui.res.TangemThemePreviewRedesign
import com.tangem.features.tangempay.details.impl.R
@Composable
-internal fun TangemPayReplacingCardBlock(modifier: Modifier = Modifier) {
+internal fun TangemPayReplacingCardBlock(
+ modifier: Modifier = Modifier,
+ title: TextReference? = resourceReference(R.string.tangempay_reissue_card_in_progress),
+ subtitle: TextReference = resourceReference(R.string.tangempay_reissue_card_in_progress_description),
+) {
+ if (LocalVisaRedesignEnabled.current) {
+ BlockV2(title = title, subtitle = subtitle, modifier = modifier)
+ } else {
+ BlockV1(title = title, subtitle = subtitle, modifier = modifier)
+ }
+}
+
+@Composable
+private fun BlockV1(
+ modifier: Modifier = Modifier,
+ title: TextReference? = resourceReference(R.string.tangempay_reissue_card_in_progress),
+ subtitle: TextReference = resourceReference(R.string.tangempay_reissue_card_in_progress_description),
+) {
Notification(
modifier = modifier,
config = NotificationConfig(
iconResId = 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),
+ title = title,
+ subtitle = subtitle,
),
containerColor = TangemTheme.colors.background.primary,
)
}
+@Composable
+private fun BlockV2(
+ modifier: Modifier = Modifier,
+ title: TextReference? = resourceReference(R.string.tangempay_reissue_card_in_progress),
+ subtitle: TextReference = resourceReference(R.string.tangempay_reissue_card_in_progress_description),
+) {
+ TangemMessage(
+ modifier = modifier,
+ title = title,
+ subtitle = subtitle,
+ leadingContent = {
+ Icon(
+ modifier = Modifier.size(20.dp),
+ imageVector = ImageVector.vectorResource(R.drawable.ic_clock_24),
+ contentDescription = null,
+ tint = TangemTheme.colors3.icon.primary,
+ )
+ },
+ )
+}
+
@Preview(showBackground = true)
@Preview(uiMode = Configuration.UI_MODE_NIGHT_YES)
@Composable
private fun Preview() {
- TangemThemePreview {
- TangemPayReplacingCardBlock()
+ Column {
+ TangemThemePreview {
+ TangemPayReplacingCardBlock()
+ }
+ SpacerH24()
+ CompositionLocalProvider(LocalVisaRedesignEnabled provides true) {
+ TangemThemePreviewRedesign {
+ TangemPayReplacingCardBlock()
+ }
+ }
}
}
\ No newline at end of file
diff --git a/features/tangempay/details/impl/src/main/kotlin/com/tangem/features/tangempay/ui/components/PayContextMenuBlock.kt b/features/tangempay/details/impl/src/main/kotlin/com/tangem/features/tangempay/ui/components/PayContextMenuBlock.kt
index 88dd7ca342..7e914f42d5 100644
--- a/features/tangempay/details/impl/src/main/kotlin/com/tangem/features/tangempay/ui/components/PayContextMenuBlock.kt
+++ b/features/tangempay/details/impl/src/main/kotlin/com/tangem/features/tangempay/ui/components/PayContextMenuBlock.kt
@@ -1,18 +1,28 @@
package com.tangem.features.tangempay.ui.components
+import android.content.res.Configuration
import androidx.compose.foundation.layout.*
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
+import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
+import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.DpOffset
import androidx.compose.ui.util.fastForEach
import com.tangem.core.ui.ds.contextmenu.TangemContextMenu
import com.tangem.core.ui.ds.image.TangemIcon
+import com.tangem.core.ui.ds.image.TangemIconUM
+import com.tangem.core.ui.extensions.TextReference
import com.tangem.core.ui.extensions.clickableSingle
+import com.tangem.core.ui.extensions.conditional
import com.tangem.core.ui.extensions.resolveReference
import com.tangem.core.ui.res.TangemTheme
+import com.tangem.core.ui.res.TangemThemePreviewRedesign
+import com.tangem.features.tangempay.details.impl.R
import com.tangem.features.tangempay.entity.TangemPayDropDownItemUM
import kotlinx.collections.immutable.ImmutableList
+import kotlinx.collections.immutable.persistentListOf
+import com.tangem.core.ui.R as CoreUiR
@Composable
internal fun PayContextMenuBlock(
@@ -28,30 +38,93 @@ internal fun PayContextMenuBlock(
modifier = modifier,
) {
items.fastForEach { item ->
- Column {
- Row(
- horizontalArrangement = Arrangement.spacedBy(TangemTheme.dimens2.x2),
- modifier = Modifier
- .clickableSingle(
- onClick = {
- item.onClick()
- onMenuDismiss()
- },
- )
- .padding(vertical = TangemTheme.dimens2.x3, horizontal = TangemTheme.dimens2.x4),
- ) {
- TangemIcon(
- modifier = Modifier.size(TangemTheme.dimens2.x5),
- tangemIconUM = item.icon,
+ PayContextMenuItem(item = item, onMenuDismiss = onMenuDismiss)
+ }
+ }
+}
+
+@Composable
+private fun PayContextMenuItem(item: TangemPayDropDownItemUM, onMenuDismiss: () -> Unit) {
+ Row(
+ horizontalArrangement = Arrangement.spacedBy(TangemTheme.dimens2.x2),
+ verticalAlignment = Alignment.CenterVertically,
+ modifier = Modifier
+ .fillMaxWidth()
+ .conditional(
+ condition = item.isEnabled,
+ modifier = {
+ clickableSingle(
+ onClick = {
+ item.onClick()
+ onMenuDismiss()
+ },
)
- Text(
- text = item.title.resolveReference(),
- style = TangemTheme.typography3.body.medium,
- color = TangemTheme.colors3.text.primary,
- maxLines = 1,
- )
- }
+ },
+ )
+ .padding(vertical = TangemTheme.dimens2.x3, horizontal = TangemTheme.dimens2.x4),
+ ) {
+ TangemIcon(
+ modifier = Modifier.size(TangemTheme.dimens2.x5),
+ tangemIconUM = item.icon,
+ )
+ Column(verticalArrangement = Arrangement.spacedBy(TangemTheme.dimens2.x0_5)) {
+ Text(
+ text = item.title.resolveReference(),
+ style = TangemTheme.typography3.body.medium,
+ color = if (item.isEnabled) {
+ TangemTheme.colors3.text.primary
+ } else {
+ TangemTheme.colors3.text.tertiary
+ },
+ maxLines = 1,
+ )
+ item.subtitle?.let { subtitle ->
+ Text(
+ text = subtitle.resolveReference(),
+ style = TangemTheme.typography3.caption.medium,
+ color = TangemTheme.colors3.text.tertiary,
+ maxLines = 2,
+ )
}
}
}
-}
\ No newline at end of file
+}
+
+// region Preview
+
+@Preview(showBackground = true)
+@Preview(uiMode = Configuration.UI_MODE_NIGHT_YES)
+@Composable
+private fun Preview() {
+ TangemThemePreviewRedesign {
+ Column {
+ persistentListOf(
+ previewReplaceItem(),
+ previewCloseItem(),
+ ).fastForEach { item ->
+ PayContextMenuItem(item = item, onMenuDismiss = {})
+ }
+ }
+ }
+}
+
+private fun previewReplaceItem() = TangemPayDropDownItemUM(
+ title = TextReference.Res(R.string.tangempay_card_details_reissue_card),
+ onClick = {},
+ icon = TangemIconUM.Icon(
+ iconRes = CoreUiR.drawable.ic_refresh_24,
+ tintReference = { TangemTheme.colors3.icon.primary },
+ ),
+)
+
+private fun previewCloseItem() = TangemPayDropDownItemUM(
+ title = TextReference.Res(R.string.tangem_pay_close_card_popup_primary_button_title),
+ onClick = {},
+ icon = TangemIconUM.Icon(
+ iconRes = CoreUiR.drawable.ic_trash_24,
+ tintReference = { TangemTheme.colors3.icon.tertiary },
+ ),
+ subtitle = TextReference.Res(R.string.tangem_pay_close_card_disabled_last_card),
+ isEnabled = false,
+)
+// endregion
\ No newline at end of file
diff --git a/features/tangempay/details/impl/src/test/kotlin/com/tangem/features/tangempay/closure/TangemPayCloseCardModelTest.kt b/features/tangempay/details/impl/src/test/kotlin/com/tangem/features/tangempay/closure/TangemPayCloseCardModelTest.kt
new file mode 100644
index 0000000000..03689d3e06
--- /dev/null
+++ b/features/tangempay/details/impl/src/test/kotlin/com/tangem/features/tangempay/closure/TangemPayCloseCardModelTest.kt
@@ -0,0 +1,110 @@
+package com.tangem.features.tangempay.closure
+
+import arrow.core.left
+import arrow.core.right
+import com.google.common.truth.Truth.assertThat
+import com.tangem.core.analytics.api.AnalyticsEventHandler
+import com.tangem.core.decompose.model.MutableParamsContainer
+import com.tangem.core.decompose.ui.UiMessageSender
+import com.tangem.core.ui.message.SnackbarMessage
+import com.tangem.domain.models.wallet.UserWalletId
+import com.tangem.domain.pay.usecase.CloseTangemPayCardUseCase
+import com.tangem.domain.tangempay.TangemPayAnalyticsEvents
+import com.tangem.domain.visa.error.VisaApiError
+import com.tangem.utils.coroutines.TestingCoroutineDispatcherProvider
+import io.mockk.coEvery
+import io.mockk.coVerify
+import io.mockk.mockk
+import io.mockk.verify
+import org.junit.jupiter.api.Nested
+import org.junit.jupiter.api.Test
+
+internal class TangemPayCloseCardModelTest {
+
+ private val cardId = "test_card_id"
+ private val userWalletId = UserWalletId("123")
+
+ private val listener: CloseCardListener = mockk(relaxed = true)
+ private val closeCardUseCase: CloseTangemPayCardUseCase = mockk()
+ private val uiMessageSender: UiMessageSender = mockk(relaxed = true)
+ private val analytics: AnalyticsEventHandler = mockk(relaxed = true)
+
+ private val params = TangemPayCloseCardComponent.Params(
+ listener = listener,
+ userWalletId = userWalletId,
+ cardId = cardId,
+ )
+
+ private fun createModel() = TangemPayCloseCardModel(
+ paramsContainer = MutableParamsContainer(params),
+ dispatchers = TestingCoroutineDispatcherProvider(),
+ closeTangemPayCardUseCase = closeCardUseCase,
+ uiMessageSender = uiMessageSender,
+ analytics = analytics,
+ )
+
+ @Test
+ fun `GIVEN model WHEN created THEN CloseCardConfirmationPopupOpened is sent`() {
+ createModel()
+
+ verify(exactly = 1) {
+ analytics.send(TangemPayAnalyticsEvents.CloseCardConfirmationPopupOpened())
+ }
+ }
+
+ @Test
+ fun `GIVEN not closing WHEN onDismiss THEN listener notified and use case not called`() {
+ val model = createModel()
+
+ model.state.value.onDismissRequest()
+
+ verify(exactly = 1) { listener.onDismissCloseCard() }
+ coVerify(exactly = 0) { closeCardUseCase(any(), any()) }
+ }
+
+ @Nested
+ inner class OnConfirm {
+
+ @Test
+ fun `GIVEN close succeeds WHEN onCloseClick THEN use case invoked and dialog dismissed`() {
+ coEvery { closeCardUseCase(userWalletId, cardId) } returns Unit.right()
+ val model = createModel()
+
+ model.state.value.onCloseClick()
+
+ coVerify(exactly = 1) { closeCardUseCase(userWalletId, cardId) }
+ verify(exactly = 1) { listener.onDismissCloseCard() }
+ }
+
+ @Test
+ fun `GIVEN close succeeds WHEN onCloseClick THEN CloseCardConfirmed is sent`() {
+ coEvery { closeCardUseCase(userWalletId, cardId) } returns Unit.right()
+ val model = createModel()
+
+ model.state.value.onCloseClick()
+
+ verify(exactly = 1) { analytics.send(TangemPayAnalyticsEvents.CloseCardConfirmed()) }
+ }
+
+ @Test
+ fun `GIVEN close fails WHEN onCloseClick THEN snackbar shown and dialog dismissed`() {
+ coEvery { closeCardUseCase(userWalletId, cardId) } returns VisaApiError.Unspecified.left()
+ val model = createModel()
+
+ model.state.value.onCloseClick()
+
+ verify(exactly = 1) { uiMessageSender.send(any()) }
+ verify(exactly = 1) { listener.onDismissCloseCard() }
+ }
+
+ @Test
+ fun `GIVEN close fails WHEN onCloseClick THEN progress is reset`() {
+ coEvery { closeCardUseCase(userWalletId, cardId) } returns VisaApiError.Unspecified.left()
+ val model = createModel()
+
+ model.state.value.onCloseClick()
+
+ assertThat(model.state.value.isClosingInProgress).isFalse()
+ }
+ }
+}
\ No newline at end of file