Updated on 2026-08-14
This commit is contained in:
commit
7b1bbc5d39
20 changed files with 692 additions and 80 deletions
|
|
@ -127,6 +127,10 @@
|
|||
"name": "AND_15368_VISA_PAY_REDESIGN",
|
||||
"version": "undefined"
|
||||
},
|
||||
{
|
||||
"name": "AND_15364_VISA_PAY_CARD_CLOSE",
|
||||
"version": "undefined"
|
||||
},
|
||||
{
|
||||
"name": "TWI_83_ADDRESS_BOOK_ENABLED",
|
||||
"version": "undefined"
|
||||
|
|
|
|||
|
|
@ -1681,6 +1681,7 @@
|
|||
<string name="tangem_pay_close_card_popup_primary_button_title">Close card</string>
|
||||
<string name="tangem_pay_close_card_popup_secondary_button_title">Go back</string>
|
||||
<string name="tangem_pay_close_card_popup_title">Close your card?</string>
|
||||
<string name="tangem_pay_close_card_disabled_last_card">You can’t close the last card</string>
|
||||
<string name="tangem_pay_deposit">Deposit</string>
|
||||
<string name="tangem_pay_dispute">Dispute</string>
|
||||
<string name="tangem_pay_explore_transaction">Explore transaction</string>
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -2,4 +2,5 @@ package com.tangem.features.tangempay
|
|||
|
||||
interface TangemPayFeatureToggles {
|
||||
val isRedesignEnabled: Boolean
|
||||
val isCloseCardEnabled: Boolean
|
||||
}
|
||||
|
|
@ -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)
|
||||
}
|
||||
|
|
@ -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()
|
||||
}
|
||||
|
|
@ -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<TangemBottomSheetConfigContent.Empty>(
|
||||
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 = {},
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -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<TangemPayCloseCardComponent.Params>()
|
||||
|
||||
val state: StateFlow<TangemPayCloseCardUM>
|
||||
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()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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,
|
||||
)
|
||||
|
|
@ -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(
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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<TangemPayDropDownItemUM>,
|
||||
) {
|
||||
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> = TangemPayCardPageSettingV2.stubList(),
|
||||
) = TangemPayCardPageUM(
|
||||
|
|
@ -39,7 +40,7 @@ internal data class TangemPayCardPageUM(
|
|||
settings = settings,
|
||||
settingsV2 = settingsV2,
|
||||
onBackClick = {},
|
||||
isReissueInProgress = isReissueInProgress,
|
||||
cardState = cardState,
|
||||
dailyLimitState = dailyLimitState,
|
||||
menuItems = persistentListOf(),
|
||||
)
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
)
|
||||
|
|
@ -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<TangemPayDropDownItemUM> {
|
||||
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<TangemPayDropDownItemUM> {
|
||||
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()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun List<TangemPayCard>.isLastCard(): Boolean = count { it.state == TangemPayCardState.Active } <= 1
|
||||
|
|
@ -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),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 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
|
||||
|
|
@ -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<SnackbarMessage>()) }
|
||||
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()
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue