Updated on 2026-08-14

This commit is contained in:
Tangem 2024-07-26 11:14:52 +03:00
parent 2cd0164d1d
commit 5016ee56ea
10 changed files with 1798 additions and 1560 deletions

View file

@ -1,38 +1,43 @@
package com.tangem.feature.tokendetails.presentation.tokendetails.state.components
import androidx.compose.runtime.Immutable
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.SpanStyle
import androidx.compose.ui.text.buildAnnotatedString
import androidx.compose.ui.text.withStyle
import com.tangem.core.ui.components.currency.tokenicon.converter.CryptoCurrencyToIconStateConverter
import com.tangem.core.ui.components.notifications.CurrencyNotificationConfig
import com.tangem.core.ui.components.notifications.NotificationConfig
import com.tangem.core.ui.extensions.TextReference
import com.tangem.core.ui.extensions.stringReference
import com.tangem.core.ui.extensions.resourceReference
import com.tangem.core.ui.extensions.wrappedList
import com.tangem.core.ui.res.TangemTheme
import com.tangem.domain.tokens.model.CryptoCurrency
import com.tangem.features.tokendetails.impl.R
@Immutable
internal sealed class ExchangeStatusNotifications(val config: NotificationConfig) {
internal sealed interface ExchangeStatusNotifications {
data class NeedVerification(
val onGoToProviderClick: () -> Unit,
) : ExchangeStatusNotifications(
sealed class CommonNotification(val config: NotificationConfig) : ExchangeStatusNotifications
data class NeedVerification(val onGoToProviderClick: () -> Unit) : CommonNotification(
config = NotificationConfig(
title = TextReference.Res(R.string.express_exchange_notification_verification_title),
subtitle = TextReference.Res(R.string.express_exchange_notification_verification_text),
title = resourceReference(R.string.express_exchange_notification_verification_title),
subtitle = resourceReference(R.string.express_exchange_notification_verification_text),
iconResId = R.drawable.ic_alert_triangle_20,
buttonsState = NotificationConfig.ButtonsState.SecondaryButtonConfig(
text = TextReference.Res(R.string.common_go_to_provider),
text = resourceReference(R.string.common_go_to_provider),
onClick = onGoToProviderClick,
),
),
)
data class Failed(
val onGoToProviderClick: () -> Unit,
) : ExchangeStatusNotifications(
data class Failed(val onGoToProviderClick: () -> Unit) : CommonNotification(
config = NotificationConfig(
title = TextReference.Res(R.string.express_exchange_notification_failed_title),
subtitle = TextReference.Res(R.string.express_exchange_notification_failed_text),
title = resourceReference(R.string.express_exchange_notification_failed_title),
subtitle = resourceReference(R.string.express_exchange_notification_failed_text),
iconResId = R.drawable.ic_alert_circle_24,
buttonsState = NotificationConfig.ButtonsState.SecondaryButtonConfig(
text = TextReference.Res(R.string.common_go_to_provider),
text = resourceReference(R.string.common_go_to_provider),
onClick = onGoToProviderClick,
),
),
@ -40,16 +45,48 @@ internal sealed class ExchangeStatusNotifications(val config: NotificationConfig
data class TokenRefunded(
val cryptoCurrency: CryptoCurrency,
val onReadMoreClick: () -> Unit,
val onGoToTokenClick: () -> Unit,
) : ExchangeStatusNotifications(
config = NotificationConfig(
title = stringReference("TITLE FOR TOKEN REFUND"),
subtitle = stringReference("SUBTITLE FOR TOKEN REFUND"),
iconResId = R.drawable.ic_alert_triangle_20,
) : ExchangeStatusNotifications {
val config = CurrencyNotificationConfig(
title = resourceReference(
id = R.string.express_exchange_notification_refund_title,
formatArgs = wrappedList(cryptoCurrency.symbol, cryptoCurrency.network.name),
),
subtitle = CurrencyNotificationConfig.AnnotatedSubtitle(
valueProvider = {
val linkText = stringResource(R.string.common_read_more)
val fullString = stringResource(
R.string.express_exchange_notification_refund_text,
cryptoCurrency.symbol,
linkText,
)
val linkTextPosition = fullString.length - linkText.length
buildAnnotatedString {
withStyle(SpanStyle(TangemTheme.colors.text.tertiary)) {
append(fullString.substring(0, linkTextPosition))
}
withStyle(SpanStyle(TangemTheme.colors.text.accent)) {
append(fullString.substring(linkTextPosition, fullString.length))
}
}
},
onClick = { value, position ->
val readMoreStyle = requireNotNull(value.spanStyles.getOrNull(1))
if (position in readMoreStyle.start..readMoreStyle.end) {
onReadMoreClick()
}
},
),
tokenIconState = CryptoCurrencyToIconStateConverter().convert(cryptoCurrency),
buttonsState = NotificationConfig.ButtonsState.SecondaryButtonConfig(
text = stringReference("Go to token"),
text = resourceReference(R.string.common_go_to_token),
onClick = onGoToTokenClick,
),
),
)
)
}
}

View file

@ -25,6 +25,7 @@ import kotlinx.collections.immutable.PersistentList
import kotlinx.collections.immutable.persistentListOf
import kotlinx.collections.immutable.toPersistentList
import java.math.BigDecimal
import java.util.Locale
internal class TokenDetailsSwapTransactionsStateConverter(
private val clickIntents: TokenDetailsClickIntents,
@ -163,9 +164,11 @@ internal class TokenDetailsSwapTransactionsStateConverter(
if (refundToken == null) {
null
} else {
ExchangeStatusNotifications.TokenRefunded(refundToken) {
clickIntents.onGoToRefundedTokenClick(refundToken)
}
ExchangeStatusNotifications.TokenRefunded(
cryptoCurrency = refundToken,
onReadMoreClick = { clickIntents.onOpenUrlClick(url = getAboutCrossChainBridgesLink()) },
onGoToTokenClick = { clickIntents.onGoToRefundedTokenClick(refundToken) },
)
}
}
else -> null
@ -316,4 +319,12 @@ internal class TokenDetailsSwapTransactionsStateConverter(
isDone = isSendingDone,
)
}
private fun getAboutCrossChainBridgesLink(): String {
return if (Locale.getDefault().country == "RU") {
"https://tangem.com/ru/blog/post/an-overview-of-cross-chain-bridges/"
} else {
"https://tangem.com/en/blog/post/an-overview-of-cross-chain-bridges/"
}
}
}

View file

@ -17,11 +17,12 @@ import com.tangem.core.ui.components.SpacerH24
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.notifications.Notification
import com.tangem.core.ui.components.notifications.CurrencyNotification
import com.tangem.core.ui.extensions.TextReference
import com.tangem.core.ui.res.TangemTheme
import com.tangem.feature.swap.domain.models.domain.ExchangeStatus
import com.tangem.feature.tokendetails.presentation.tokendetails.state.SwapTransactionsState
import com.tangem.feature.tokendetails.presentation.tokendetails.state.components.ExchangeStatusNotifications
@Composable
internal fun ExchangeStatusBottomSheet(config: TangemBottomSheetConfig) {
@ -29,16 +30,13 @@ internal fun ExchangeStatusBottomSheet(config: TangemBottomSheetConfig) {
config = config,
containerColor = TangemTheme.colors.background.tertiary,
) { content: ExchangeStatusBottomSheetConfig ->
ExchangeStatusBottomSheetContent(content = content)
ExchangeStatusBottomSheetContent(config = content.value)
}
}
@Composable
private fun ExchangeStatusBottomSheetContent(content: ExchangeStatusBottomSheetConfig) {
val config = content.value
Column(
modifier = Modifier.padding(horizontal = TangemTheme.dimens.spacing16),
) {
private fun ExchangeStatusBottomSheetContent(config: SwapTransactionsState) {
Column(modifier = Modifier.padding(horizontal = TangemTheme.dimens.spacing16)) {
SpacerH10()
Text(
text = stringResource(id = R.string.express_exchange_status_title),
@ -80,25 +78,39 @@ private fun ExchangeStatusBottomSheetContent(content: ExchangeStatusBottomSheetC
showLink = config.showProviderLink,
onClick = { config.onGoToProviderClick(config.txUrl.orEmpty()) },
)
AnimatedContent(
targetState = config.notification,
label = "Exchange Status Notification Change",
) {
it?.let {
val tint = when (config.activeStatus) {
ExchangeStatus.Verifying -> TangemTheme.colors.icon.attention
ExchangeStatus.Failed -> TangemTheme.colors.icon.warning
else -> null
}
Notification(
config = it.config,
iconTint = tint,
if (config.notification != null) {
Notification(state = config.notification, activeStatus = config.activeStatus)
}
SpacerH24()
}
}
@Composable
private fun Notification(state: ExchangeStatusNotifications, activeStatus: ExchangeStatus?) {
AnimatedContent(
targetState = state,
modifier = Modifier.padding(top = TangemTheme.dimens.spacing12),
label = "Exchange Status Notification Change",
) { notification ->
when (notification) {
is ExchangeStatusNotifications.CommonNotification -> {
com.tangem.core.ui.components.notifications.Notification(
config = notification.config,
iconTint = when (activeStatus) {
ExchangeStatus.Verifying -> TangemTheme.colors.icon.attention
ExchangeStatus.Failed -> TangemTheme.colors.icon.warning
else -> null
},
containerColor = TangemTheme.colors.background.action,
)
}
is ExchangeStatusNotifications.TokenRefunded -> {
CurrencyNotification(
config = notification.config,
containerColor = TangemTheme.colors.background.action,
modifier = Modifier.padding(top = TangemTheme.dimens.spacing12),
)
}
}
SpacerH24()
}
}

View file

@ -57,4 +57,6 @@ interface TokenDetailsClickIntents {
fun onAssociateClick()
fun onGoToRefundedTokenClick(cryptoCurrency: CryptoCurrency)
fun onOpenUrlClick(url: String)
}

View file

@ -726,6 +726,10 @@ internal class TokenDetailsViewModel @Inject constructor(
router.openTokenDetails(userWalletId, cryptoCurrency)
}
override fun onOpenUrlClick(url: String) {
router.openUrl(url)
}
override fun onSwapPromoDismiss() {
viewModelScope.launch(dispatchers.main) {
shouldShowSwapPromoTokenUseCase.neverToShow()