From c637c9a194aa8ace05ddb7450ed7272aee2089c2 Mon Sep 17 00:00:00 2001 From: Tangem Date: Fri, 5 Jun 2026 07:22:30 +0200 Subject: [PATCH] Updated on 2026-08-14 --- core/res/src/main/res/values/strings.xml | 4 +- .../components/TangemPayCardPageComponent.kt | 8 +- .../components/TangemPayChangePinComponent.kt | 16 +- .../TangemPayChangePinSuccessComponent.kt | 8 +- .../components/TangemPayViewPinComponent.kt | 7 +- .../model/TangemPayChangePinModel.kt | 10 + .../tangempay/model/TangemPayViewPinModel.kt | 4 + .../TangemPayChangePinCodeSuccessScreenV2.kt | 98 +++++++ .../ui/TangemPayChangePinScreenV2.kt | 239 ++++++++++++++++++ .../tangempay/ui/TangemPayViewPinContentV2.kt | 221 ++++++++++++++++ 10 files changed, 607 insertions(+), 8 deletions(-) create mode 100644 features/tangempay/details/impl/src/main/kotlin/com/tangem/features/tangempay/ui/TangemPayChangePinCodeSuccessScreenV2.kt create mode 100644 features/tangempay/details/impl/src/main/kotlin/com/tangem/features/tangempay/ui/TangemPayChangePinScreenV2.kt create mode 100644 features/tangempay/details/impl/src/main/kotlin/com/tangem/features/tangempay/ui/TangemPayViewPinContentV2.kt diff --git a/core/res/src/main/res/values/strings.xml b/core/res/src/main/res/values/strings.xml index 87d1ef2075..e41b7491e8 100644 --- a/core/res/src/main/res/values/strings.xml +++ b/core/res/src/main/res/values/strings.xml @@ -1876,6 +1876,8 @@ Service temporarily unavailable The service is currently unreachable. Please try again later. Set \nPIN code + Set up new PIN + Set PIN Account closed Replacing your card Use your card or ring to renew session @@ -1888,7 +1890,7 @@ Send USDC Polygon to your account’s address From another wallet or exchange Use crypto from your wallet to top up your payment account - Swap from Tangem Wallet + From your Tangem Wallet USDC on Polygon network Funds from refunded purchases won’t be returned your on-chain Polygon balance or be available for withdrawal, but will stay on your card balance for purchases Please note diff --git a/features/tangempay/details/impl/src/main/kotlin/com/tangem/features/tangempay/components/TangemPayCardPageComponent.kt b/features/tangempay/details/impl/src/main/kotlin/com/tangem/features/tangempay/components/TangemPayCardPageComponent.kt index 44ded62254..ccae6d4b0d 100644 --- a/features/tangempay/details/impl/src/main/kotlin/com/tangem/features/tangempay/components/TangemPayCardPageComponent.kt +++ b/features/tangempay/details/impl/src/main/kotlin/com/tangem/features/tangempay/components/TangemPayCardPageComponent.kt @@ -17,6 +17,7 @@ import com.tangem.core.decompose.factory.ComponentFactory import com.tangem.core.decompose.navigation.inner.InnerRouter import com.tangem.core.ui.decompose.ComposableContentComponent import com.tangem.domain.models.account.AccountStatus +import com.tangem.features.tangempay.TangemPayFeatureToggles import com.tangem.features.tangempay.limit.setup.TangemPayCardLimitSetupComponent import com.tangem.features.tangempay.limit.setup.TangemPayCardLimitSetupSuccessComponent import com.tangem.features.tangempay.navigation.TangemPayCardDetailsInnerRoute @@ -29,6 +30,7 @@ internal class TangemPayCardPageComponent @AssistedInject constructor( @Assisted private val appComponentContext: AppComponentContext, @Assisted private val params: Params, private val tokenReceiveComponentFactory: TokenReceiveComponent.Factory, + private val tangemPayFeatureToggles: TangemPayFeatureToggles, ) : ComposableContentComponent, AppComponentContext by appComponentContext { private val stackNavigation = StackNavigation() @@ -72,7 +74,11 @@ internal class TangemPayCardPageComponent @AssistedInject constructor( params = TangemPayDetailsContainerComponent.Params(initialStatus = params.initialStatus), ) TangemPayCardDetailsInnerRoute.ChangePINSuccess -> TangemPayChangePinSuccessComponent( - appComponentContext = childByContext(componentContext = componentContext, router = innerRouter), + appComponentContext = childByContext( + componentContext = componentContext, + router = innerRouter, + ), + isRedesignEnabled = tangemPayFeatureToggles.isRedesignEnabled, ) TangemPayCardDetailsInnerRoute.AddToWallet -> TangemPayAddToWalletComponent( appComponentContext = childByContext(componentContext = componentContext, router = innerRouter), diff --git a/features/tangempay/details/impl/src/main/kotlin/com/tangem/features/tangempay/components/TangemPayChangePinComponent.kt b/features/tangempay/details/impl/src/main/kotlin/com/tangem/features/tangempay/components/TangemPayChangePinComponent.kt index 7b3ecc0c87..220fb0ccd2 100644 --- a/features/tangempay/details/impl/src/main/kotlin/com/tangem/features/tangempay/components/TangemPayChangePinComponent.kt +++ b/features/tangempay/details/impl/src/main/kotlin/com/tangem/features/tangempay/components/TangemPayChangePinComponent.kt @@ -11,6 +11,7 @@ import com.tangem.core.ui.decompose.ComposableContentComponent import com.tangem.core.ui.security.DisableScreenshotsDisposableEffect import com.tangem.features.tangempay.model.TangemPayChangePinModel import com.tangem.features.tangempay.ui.TangemPayChangePinScreen +import com.tangem.features.tangempay.ui.TangemPayChangePinScreenV2 internal class TangemPayChangePinComponent( private val appComponentContext: AppComponentContext, @@ -24,9 +25,16 @@ internal class TangemPayChangePinComponent( val state by model.uiState.collectAsStateWithLifecycle() BackHandler(onBack = router::pop) DisableScreenshotsDisposableEffect() - TangemPayChangePinScreen( - state = state, - onBackClick = router::pop, - ) + if (model.isRedesignEnabled()) { + TangemPayChangePinScreenV2( + state = state, + onBackClick = router::pop, + ) + } else { + TangemPayChangePinScreen( + state = state, + onBackClick = router::pop, + ) + } } } \ No newline at end of file diff --git a/features/tangempay/details/impl/src/main/kotlin/com/tangem/features/tangempay/components/TangemPayChangePinSuccessComponent.kt b/features/tangempay/details/impl/src/main/kotlin/com/tangem/features/tangempay/components/TangemPayChangePinSuccessComponent.kt index eccf167f98..292202be67 100644 --- a/features/tangempay/details/impl/src/main/kotlin/com/tangem/features/tangempay/components/TangemPayChangePinSuccessComponent.kt +++ b/features/tangempay/details/impl/src/main/kotlin/com/tangem/features/tangempay/components/TangemPayChangePinSuccessComponent.kt @@ -7,15 +7,21 @@ import com.tangem.core.decompose.context.AppComponentContext import com.tangem.core.ui.decompose.ComposableContentComponent import com.tangem.features.tangempay.navigation.TangemPayCardDetailsInnerRoute import com.tangem.features.tangempay.ui.TangemPayChangePinCodeSuccessScreen +import com.tangem.features.tangempay.ui.TangemPayChangePinCodeSuccessScreenV2 internal class TangemPayChangePinSuccessComponent( private val appComponentContext: AppComponentContext, + private val isRedesignEnabled: Boolean, ) : AppComponentContext by appComponentContext, ComposableContentComponent { @Composable override fun Content(modifier: Modifier) { BackHandler(onBack = ::backToDetails) - TangemPayChangePinCodeSuccessScreen(onClick = ::backToDetails) + if (isRedesignEnabled) { + TangemPayChangePinCodeSuccessScreenV2(onClose = ::backToDetails) + } else { + TangemPayChangePinCodeSuccessScreen(onClick = ::backToDetails) + } } private fun backToDetails() { diff --git a/features/tangempay/details/impl/src/main/kotlin/com/tangem/features/tangempay/components/TangemPayViewPinComponent.kt b/features/tangempay/details/impl/src/main/kotlin/com/tangem/features/tangempay/components/TangemPayViewPinComponent.kt index f12eb316e9..48f67173fe 100644 --- a/features/tangempay/details/impl/src/main/kotlin/com/tangem/features/tangempay/components/TangemPayViewPinComponent.kt +++ b/features/tangempay/details/impl/src/main/kotlin/com/tangem/features/tangempay/components/TangemPayViewPinComponent.kt @@ -11,6 +11,7 @@ import com.tangem.core.ui.security.DisableScreenshotsDisposableEffect import com.tangem.domain.models.wallet.UserWalletId import com.tangem.features.tangempay.model.TangemPayViewPinModel import com.tangem.features.tangempay.ui.TangemPayViewPinContent +import com.tangem.features.tangempay.ui.TangemPayViewPinContentV2 internal class TangemPayViewPinComponent( appComponentContext: AppComponentContext, @@ -28,7 +29,11 @@ internal class TangemPayViewPinComponent( val state by model.uiState.collectAsStateWithLifecycle() BackHandler(onBack = ::dismiss) DisableScreenshotsDisposableEffect() - TangemPayViewPinContent(state = state) + if (model.isRedesignEnabled()) { + TangemPayViewPinContentV2(state = state) + } else { + TangemPayViewPinContent(state = state) + } } data class Params( diff --git a/features/tangempay/details/impl/src/main/kotlin/com/tangem/features/tangempay/model/TangemPayChangePinModel.kt b/features/tangempay/details/impl/src/main/kotlin/com/tangem/features/tangempay/model/TangemPayChangePinModel.kt index 4194b5f69d..f8d8643db7 100644 --- a/features/tangempay/details/impl/src/main/kotlin/com/tangem/features/tangempay/model/TangemPayChangePinModel.kt +++ b/features/tangempay/details/impl/src/main/kotlin/com/tangem/features/tangempay/model/TangemPayChangePinModel.kt @@ -12,6 +12,7 @@ import com.tangem.core.ui.message.ToastMessage import com.tangem.domain.pay.model.SetPinResult import com.tangem.domain.pay.repository.TangemPayCardDetailsRepository import com.tangem.domain.tangempay.TangemPayAnalyticsEvents +import com.tangem.features.tangempay.TangemPayFeatureToggles import com.tangem.features.tangempay.components.TangemPayDetailsContainerComponent import com.tangem.features.tangempay.details.impl.R import com.tangem.features.tangempay.entity.TangemPayChangePinUM @@ -27,6 +28,7 @@ import kotlinx.coroutines.flow.update import kotlinx.coroutines.launch import javax.inject.Inject +@Suppress("LongParameterList") @Stable @ModelScoped internal class TangemPayChangePinModel @Inject constructor( @@ -36,6 +38,7 @@ internal class TangemPayChangePinModel @Inject constructor( private val router: Router, private val cardDetailsRepository: TangemPayCardDetailsRepository, private val analytics: AnalyticsEventHandler, + private val featureToggles: TangemPayFeatureToggles, ) : Model() { private val params: TangemPayDetailsContainerComponent.Params = paramsContainer.require() @@ -46,8 +49,15 @@ internal class TangemPayChangePinModel @Inject constructor( analytics.send(TangemPayAnalyticsEvents.ChangePinScreenShown()) } + fun isRedesignEnabled(): Boolean = featureToggles.isRedesignEnabled + private fun onPinCodeChange(pin: String) { uiState.update(transformer = PinCodeChangeTransformer(newPin = pin)) + val state = uiState.value + // In the redesign there is no submit button: a valid full PIN is submitted automatically. + if (featureToggles.isRedesignEnabled && state.submitButtonEnabled && !state.submitButtonLoading) { + onClickSubmit() + } } private fun onClickSubmit() { diff --git a/features/tangempay/details/impl/src/main/kotlin/com/tangem/features/tangempay/model/TangemPayViewPinModel.kt b/features/tangempay/details/impl/src/main/kotlin/com/tangem/features/tangempay/model/TangemPayViewPinModel.kt index a610d66541..9b02ad1ed5 100644 --- a/features/tangempay/details/impl/src/main/kotlin/com/tangem/features/tangempay/model/TangemPayViewPinModel.kt +++ b/features/tangempay/details/impl/src/main/kotlin/com/tangem/features/tangempay/model/TangemPayViewPinModel.kt @@ -7,6 +7,7 @@ import com.tangem.core.decompose.model.Model import com.tangem.core.decompose.model.ParamsContainer import com.tangem.domain.pay.repository.TangemPayCardDetailsRepository import com.tangem.domain.tangempay.TangemPayAnalyticsEvents +import com.tangem.features.tangempay.TangemPayFeatureToggles import com.tangem.features.tangempay.components.TangemPayViewPinComponent import com.tangem.features.tangempay.entity.TangemPayViewPinUM import com.tangem.features.tangempay.model.transformers.TangemPayViewPinErrorStateTransformer @@ -25,6 +26,7 @@ internal class TangemPayViewPinModel @Inject constructor( override val dispatchers: CoroutineDispatcherProvider, private val cardDetailsRepository: TangemPayCardDetailsRepository, private val analytics: AnalyticsEventHandler, + private val featureToggles: TangemPayFeatureToggles, ) : Model() { private val params = paramsContainer.require() @@ -37,6 +39,8 @@ internal class TangemPayViewPinModel @Inject constructor( getCardPin() } + fun isRedesignEnabled(): Boolean = featureToggles.isRedesignEnabled + private fun getCardPin() { modelScope.launch { cardDetailsRepository.getPin(userWalletId = params.walletId, cardId = params.cardId) diff --git a/features/tangempay/details/impl/src/main/kotlin/com/tangem/features/tangempay/ui/TangemPayChangePinCodeSuccessScreenV2.kt b/features/tangempay/details/impl/src/main/kotlin/com/tangem/features/tangempay/ui/TangemPayChangePinCodeSuccessScreenV2.kt new file mode 100644 index 0000000000..6f0c1d5c40 --- /dev/null +++ b/features/tangempay/details/impl/src/main/kotlin/com/tangem/features/tangempay/ui/TangemPayChangePinCodeSuccessScreenV2.kt @@ -0,0 +1,98 @@ +package com.tangem.features.tangempay.ui + +import androidx.compose.foundation.layout.* +import androidx.compose.material3.Icon +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.blur +import androidx.compose.ui.draw.drawBehind +import androidx.compose.ui.geometry.Offset +import androidx.compose.ui.graphics.Brush +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.TileMode +import androidx.compose.ui.platform.testTag +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import com.tangem.core.ui.components.SpacerH +import com.tangem.core.ui.components.SpacerHMax +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.core.ui.res.generated.icons.Icons +import com.tangem.core.ui.res.generated.icons.ic_success_24 +import com.tangem.core.ui.test.TangemPayTestTags +import com.tangem.features.tangempay.details.impl.R + +private const val BG_YELLOW_COLOR = 0x52DFAF12 + +@Suppress("MagicNumber") +@Composable +internal fun TangemPayChangePinCodeSuccessScreenV2(onClose: () -> Unit, modifier: Modifier = Modifier) { + Box(modifier = modifier.fillMaxSize()) { + Box( + modifier = Modifier + .matchParentSize() + .blur(192.dp) + .drawBehind { + val w = size.width + drawRect( + brush = Brush.radialGradient( + colors = listOf( + Color(BG_YELLOW_COLOR), + Color.Transparent, + ), + center = Offset(w / 2f, -w * .1f), + radius = w + w * .2f, + tileMode = TileMode.Clamp, + ), + ) + }, + ) + Column( + modifier = Modifier + .fillMaxSize() + .windowInsetsPadding(WindowInsets.systemBars) + .padding(top = 72.dp, start = 24.dp, end = 24.dp), + ) { + Icon( + modifier = Modifier.size(28.dp), + imageVector = Icons.ic_success_24, + tint = TangemTheme.colors3.icon.primary, + contentDescription = null, + ) + SpacerH(TangemTheme.dimens2.x4) + Text( + modifier = Modifier.testTag(TangemPayTestTags.PIN_SUCCESS_TITLE), + text = stringResourceSafe(R.string.tangempay_card_details_change_pin_success_title), + style = TangemTheme.typography3.heading.medium, + color = TangemTheme.colors3.text.primary, + ) + Text( + modifier = Modifier.testTag(TangemPayTestTags.PIN_SUCCESS_DESCRIPTION), + text = stringResourceSafe(R.string.tangempay_card_details_change_pin_success_description), + style = TangemTheme.typography3.heading.medium, + color = TangemTheme.colors3.text.secondary, + ) + SpacerHMax() + TangemButton( + modifier = Modifier + .fillMaxWidth() + .padding(vertical = TangemTheme.dimens2.x3), + onClick = onClose, + size = TangemButton.Size.X12, + text = resourceReference(R.string.common_close), + ) + } + } +} + +@Preview(showBackground = true) +@Composable +private fun Preview() { + TangemThemePreviewRedesign { + TangemPayChangePinCodeSuccessScreenV2(onClose = {}) + } +} \ No newline at end of file diff --git a/features/tangempay/details/impl/src/main/kotlin/com/tangem/features/tangempay/ui/TangemPayChangePinScreenV2.kt b/features/tangempay/details/impl/src/main/kotlin/com/tangem/features/tangempay/ui/TangemPayChangePinScreenV2.kt new file mode 100644 index 0000000000..26c4737f33 --- /dev/null +++ b/features/tangempay/details/impl/src/main/kotlin/com/tangem/features/tangempay/ui/TangemPayChangePinScreenV2.kt @@ -0,0 +1,239 @@ +package com.tangem.features.tangempay.ui + +import androidx.compose.animation.AnimatedVisibility +import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.* +import androidx.compose.foundation.text.BasicTextField +import androidx.compose.foundation.text.KeyboardActions +import androidx.compose.foundation.text.KeyboardOptions +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.remember +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.focus.FocusRequester +import androidx.compose.ui.focus.focusRequester +import androidx.compose.ui.graphics.Color.Companion.Transparent +import androidx.compose.ui.graphics.SolidColor +import androidx.compose.ui.platform.LocalSoftwareKeyboardController +import androidx.compose.ui.platform.testTag +import androidx.compose.ui.text.input.ImeAction +import androidx.compose.ui.text.input.KeyboardType +import androidx.compose.ui.text.style.TextAlign +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.tooling.preview.PreviewParameter +import androidx.compose.ui.tooling.preview.datasource.CollectionPreviewParameterProvider +import com.tangem.core.ui.components.SpacerH +import com.tangem.core.ui.components.SpacerH4 +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.ds2.loader.TangemLoader +import com.tangem.core.ui.extensions.resolveReference +import com.tangem.core.ui.extensions.resourceReference +import com.tangem.core.ui.extensions.stringResourceSafe +import com.tangem.core.ui.res.TangemTheme +import com.tangem.core.ui.res.TangemThemePreview +import com.tangem.core.ui.res.generated.icons.Icons +import com.tangem.core.ui.res.generated.icons.ic_cross_20 +import com.tangem.core.ui.test.TangemPayTestTags +import com.tangem.features.tangempay.details.impl.R +import com.tangem.features.tangempay.entity.TangemPayChangePinUM +import kotlinx.coroutines.delay + +@Composable +internal fun TangemPayChangePinScreenV2( + state: TangemPayChangePinUM, + onBackClick: () -> Unit, + modifier: Modifier = Modifier, +) { + Column( + modifier = modifier + .fillMaxSize() + .statusBarsPadding(), + ) { + TangemTopBar( + title = resourceReference(R.string.tangempay_set_pin_title), + endContent = { + TangemButton( + iconStart = TangemIconUM.Icon(imageVector = Icons.ic_cross_20), + onClick = onBackClick, + size = TangemButton.Size.X11, + variant = TangemButton.Variant.Material, + ) + }, + ) + SpacerH(TangemTheme.dimens2.x4) + Column( + modifier = Modifier + .fillMaxWidth() + .padding(top = TangemTheme.dimens2.x12) + .padding(horizontal = TangemTheme.dimens2.x9), + horizontalAlignment = Alignment.CenterHorizontally, + ) { + Text( + text = stringResourceSafe(R.string.tangempay_set_pin_header), + style = TangemTheme.typography3.body.medium, + color = TangemTheme.colors3.text.secondary, + textAlign = TextAlign.Center, + modifier = Modifier.testTag(TangemPayTestTags.PIN_SCREEN_TITLE), + ) + SpacerH(TangemTheme.dimens2.x6) + PinCodeSection(state) + SpacerH(TangemTheme.dimens2.x6) + AnimatedVisibility(state.submitButtonLoading) { + TangemLoader() + } + } + } +} + +@Composable +private fun PinCodeSection(state: TangemPayChangePinUM, modifier: Modifier = Modifier) { + val focusRequester = remember { FocusRequester() } + Column(horizontalAlignment = Alignment.CenterHorizontally, modifier = modifier) { + PinCode( + value = state.pinCode, + onValueChange = state.onPinCodeChange, + focusRequester = focusRequester, + readOnly = state.submitButtonLoading, + ) + + AnimatedVisibility( + visible = state.error != null, + ) { + val error = remember(this) { requireNotNull(state.error) } + Column { + SpacerH4() + Text( + text = error.resolveReference(), + style = TangemTheme.typography3.caption.medium, + color = TangemTheme.colors3.text.status.warning, + textAlign = TextAlign.Center, + modifier = Modifier.testTag(TangemPayTestTags.PIN_ERROR_MESSAGE), + ) + } + } + } + + LaunchedEffect(Unit) { + delay(timeMillis = 300) + focusRequester.requestFocus() + } +} + +@Composable +private fun PinCode( + value: String, + onValueChange: (String) -> Unit, + modifier: Modifier = Modifier, + numbersCount: Int = 4, + readOnly: Boolean = false, + focusRequester: FocusRequester = remember { FocusRequester() }, +) { + val keyboardController = LocalSoftwareKeyboardController.current + + // Hide the keyboard while the PIN is being submitted so it can't be edited mid-request. + LaunchedEffect(readOnly) { + if (readOnly) keyboardController?.hide() + } + + BasicTextField( + value = value, + onValueChange = { text -> + if (text.length <= numbersCount && !readOnly) { + onValueChange(text) + } + }, + readOnly = readOnly, + modifier = modifier + .focusRequester(focusRequester) + .clickable(enabled = !readOnly) { + focusRequester.requestFocus() + keyboardController?.show() + } + .testTag(TangemPayTestTags.PIN_INPUT_FIELD), + textStyle = TangemTheme.typography3.heading.medium.copy(color = Transparent), + keyboardOptions = KeyboardOptions( + keyboardType = KeyboardType.NumberPassword, + imeAction = ImeAction.Done, + ), + keyboardActions = KeyboardActions(onDone = { keyboardController?.hide() }), + cursorBrush = SolidColor(Transparent), + decorationBox = { innerTextField -> + Box( + modifier = Modifier.fillMaxWidth(), + contentAlignment = Alignment.Center, + ) { + Row( + horizontalArrangement = Arrangement.spacedBy(TangemTheme.dimens2.x2), + verticalAlignment = Alignment.CenterVertically, + ) { + repeat(numbersCount) { index -> + val digit = value.getOrNull(index)?.toString() + val isActive = !readOnly && index == value.length + PinDigitBox( + modifier = Modifier.size( + width = TangemTheme.dimens2.x14, + height = TangemTheme.dimens2.x16, + ), + digit = digit, + backgroundColor = TangemTheme.colors3.bg.opaque.primary, + borderColor = if (isActive) { + TangemTheme.colors3.border.status.info + } else { + TangemTheme.colors3.border.secondary + }, + textColor = TangemTheme.colors3.text.primary, + textStyle = TangemTheme.typography3.heading.medium, + ) + } + } + innerTextField() + } + }, + ) +} + +@Preview(showBackground = true) +@Composable +private fun TangemPayChangePinScreenV2Preview( + @PreviewParameter(TangemPayChangePinUMPreviewProvider::class) state: TangemPayChangePinUM, +) { + TangemThemePreview { + TangemPayChangePinScreenV2( + state = state, + onBackClick = {}, + ) + } +} + +private class TangemPayChangePinUMPreviewProvider : CollectionPreviewParameterProvider( + collection = listOf( + TangemPayChangePinUM( + pinCode = "", + error = null, + onPinCodeChange = {}, + submitButtonLoading = false, + submitButtonEnabled = false, + onSubmitClick = {}, + ), + TangemPayChangePinUM( + pinCode = "1111", + error = resourceReference(R.string.visa_onboarding_pin_validation_error_message), + onPinCodeChange = {}, + submitButtonLoading = false, + submitButtonEnabled = false, + onSubmitClick = {}, + ), + TangemPayChangePinUM( + pinCode = "2580", + error = null, + onPinCodeChange = {}, + submitButtonLoading = false, + submitButtonEnabled = true, + onSubmitClick = {}, + ), + ), +) \ No newline at end of file diff --git a/features/tangempay/details/impl/src/main/kotlin/com/tangem/features/tangempay/ui/TangemPayViewPinContentV2.kt b/features/tangempay/details/impl/src/main/kotlin/com/tangem/features/tangempay/ui/TangemPayViewPinContentV2.kt new file mode 100644 index 0000000000..b8d79d40e1 --- /dev/null +++ b/features/tangempay/details/impl/src/main/kotlin/com/tangem/features/tangempay/ui/TangemPayViewPinContentV2.kt @@ -0,0 +1,221 @@ +package com.tangem.features.tangempay.ui + +import android.content.res.Configuration +import androidx.compose.animation.AnimatedContent +import androidx.compose.animation.fadeIn +import androidx.compose.animation.fadeOut +import androidx.compose.animation.togetherWith +import androidx.compose.foundation.layout.* +import androidx.compose.foundation.text.BasicTextField +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color.Companion.Transparent +import androidx.compose.ui.graphics.SolidColor +import androidx.compose.ui.text.style.TextAlign +import androidx.compose.ui.tooling.preview.Preview +import com.tangem.core.ui.components.SpacerH +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.components.bottomsheets.message.MessageBottomSheetContent +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.ds2.loader.TangemLoader +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 +import com.tangem.features.tangempay.entity.TangemPayViewPinUM + +@Composable +internal fun TangemPayViewPinContentV2(state: TangemPayViewPinUM) { + TangemBottomSheet( + config = TangemBottomSheetConfig( + isShown = true, + onDismissRequest = state.onDismiss, + content = TangemBottomSheetConfigContent.Empty, + ), + onBack = state.onDismiss, + 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.onDismiss, + size = TangemButton.Size.X11, + variant = TangemButton.Variant.Material, + ) + }, + ) + }, + content = { + AnimatedContent( + targetState = state, + contentKey = { um -> um::class.java }, + transitionSpec = { fadeIn() togetherWith fadeOut() }, + ) { animatedState -> + when (animatedState) { + is TangemPayViewPinUM.Content -> { + PinSuccessContent(animatedState) + } + is TangemPayViewPinUM.Error -> { + MessageBottomSheetContent(animatedState.errorMessage) + } + is TangemPayViewPinUM.Loading -> { + PinLoadingContent() + } + } + } + }, + ) +} + +@Composable +private fun PinSuccessContent(state: TangemPayViewPinUM.Content, modifier: Modifier = Modifier) { + Column( + modifier = modifier + .padding(horizontal = TangemTheme.dimens2.x4) + .padding(top = TangemTheme.dimens2.x12) + .fillMaxWidth(), + horizontalAlignment = Alignment.CenterHorizontally, + ) { + Text( + text = stringResourceSafe(R.string.tangempay_card_details_view_pin_code_title), + style = TangemTheme.typography3.heading.small, + color = TangemTheme.colors3.text.primary, + textAlign = TextAlign.Center, + ) + + SpacerH(TangemTheme.dimens2.x2) + + Text( + text = stringResourceSafe(R.string.tangempay_card_details_view_pin_code_description), + style = TangemTheme.typography3.subheading.medium, + color = TangemTheme.colors3.text.secondary, + textAlign = TextAlign.Center, + ) + + SpacerH(TangemTheme.dimens2.x8) + + PinCode(value = state.pin) + + SpacerH(TangemTheme.dimens2.x12) + + TangemButton( + modifier = Modifier + .fillMaxWidth() + .padding(vertical = TangemTheme.dimens2.x4), + size = TangemButton.Size.X12, + text = resourceReference(R.string.tangempay_change_pin_code), + onClick = state.onClickChangePin, + ) + } +} + +@Composable +private fun PinLoadingContent(modifier: Modifier = Modifier) { + Column( + modifier = modifier + .padding(horizontal = TangemTheme.dimens2.x4) + .padding(top = TangemTheme.dimens2.x12) + .fillMaxWidth(), + horizontalAlignment = Alignment.CenterHorizontally, + ) { + Text( + text = stringResourceSafe(R.string.tangempay_card_details_view_pin_code_title), + style = TangemTheme.typography3.heading.small, + color = TangemTheme.colors3.text.primary, + textAlign = TextAlign.Center, + ) + + SpacerH(TangemTheme.dimens2.x2) + + Text( + text = stringResourceSafe(R.string.tangempay_card_details_view_pin_code_description), + style = TangemTheme.typography3.subheading.medium, + color = TangemTheme.colors3.text.secondary, + textAlign = TextAlign.Center, + ) + + SpacerH(TangemTheme.dimens2.x8) + + TangemLoader( + modifier = Modifier.padding(vertical = TangemTheme.dimens2.x5), + ) + + SpacerH(TangemTheme.dimens2.x12) + + TangemButton( + modifier = Modifier + .fillMaxWidth() + .padding(vertical = TangemTheme.dimens2.x4), + size = TangemButton.Size.X12, + text = resourceReference(R.string.tangempay_change_pin_code), + onClick = {}, + isEnabled = false, + ) + } +} + +@Composable +private fun PinCode(value: String, modifier: Modifier = Modifier, numbersCount: Int = 4) { + BasicTextField( + enabled = false, + value = value, + onValueChange = {}, + modifier = modifier, + textStyle = TangemTheme.typography3.heading.medium.copy(color = Transparent), + cursorBrush = SolidColor(Transparent), + decorationBox = { innerTextField -> + Box( + modifier = Modifier.fillMaxWidth(), + contentAlignment = Alignment.Center, + ) { + Row( + horizontalArrangement = Arrangement.spacedBy(TangemTheme.dimens2.x2), + verticalAlignment = Alignment.CenterVertically, + ) { + repeat(numbersCount) { index -> + val digit = value.getOrNull(index)?.toString() + PinDigitBox( + modifier = Modifier.size( + width = TangemTheme.dimens2.x14, + height = TangemTheme.dimens2.x16, + ), + digit = digit, + backgroundColor = TangemTheme.colors3.bg.opaque.primary, + borderColor = TangemTheme.colors3.border.secondary, + textColor = TangemTheme.colors3.text.primary, + textStyle = TangemTheme.typography3.heading.medium, + ) + } + } + innerTextField() + } + }, + ) +} + +@Preview(showBackground = true) +@Preview(uiMode = Configuration.UI_MODE_NIGHT_YES) +@Composable +private fun TangemPayViewPinContentPreview() { + TangemThemePreviewRedesign { + TangemPayViewPinContentV2( + state = TangemPayViewPinUM.Content( + pin = "1234", + onClickChangePin = {}, + onDismiss = {}, + ), + ) + } +} \ No newline at end of file