From 8511623421c81dc4e8c1dae13d306b96fc1a7755 Mon Sep 17 00:00:00 2001 From: Tangem Date: Wed, 29 Apr 2026 17:59:40 +0400 Subject: [PATCH] Updated on 2026-08-14 --- .../ui/expressStatus/ExpressStatusItem.kt | 38 ++- .../ui/expressStatus/ExpressStatusItems.kt | 9 +- .../ui/expressStatus/OnrampStatusMapper.kt | 38 +++ .../state/ExpressStatusSubtitleBuilder.kt | 52 ++++ .../state/ExpressTransactionStateUM.kt | 8 +- .../state/ExpressStatusSubtitleBuilderTest.kt | 117 +++++++++ core/ui/build.gradle.kts | 5 + .../com/tangem/core/ui/utils/DateUtils.kt | 15 +- .../core/ui/utils/FormattedDateMapper.kt | 55 ++++ .../core/ui/utils/DateTimeFormattersTest.kt | 17 ++ .../com/tangem/core/ui/utils/DateUtilsTest.kt | 19 ++ .../core/ui/utils/FormattedDateMapperTest.kt | 234 ++++++++++++++++++ .../ShortArticleToArticleConfigUMConverter.kt | 2 +- .../details/converter/NewsDetailsConverter.kt | 38 +-- .../feed/ui/utils/CreatedTimeFormatter.kt | 43 ---- ...nDetailsOnrampTransactionStateConverter.kt | 19 +- ...enDetailsSwapTransactionsStateConverter.kt | 24 ++ .../ExpressStatusBottomSheetStateProvider.kt | 2 + .../SingleWalletOnrampTransactionConverter.kt | 20 +- 19 files changed, 633 insertions(+), 122 deletions(-) create mode 100644 common/ui/src/main/java/com/tangem/common/ui/expressStatus/OnrampStatusMapper.kt create mode 100644 common/ui/src/main/java/com/tangem/common/ui/expressStatus/state/ExpressStatusSubtitleBuilder.kt create mode 100644 common/ui/src/test/kotlin/com/tangem/common/ui/expressStatus/state/ExpressStatusSubtitleBuilderTest.kt create mode 100644 core/ui/src/main/java/com/tangem/core/ui/utils/FormattedDateMapper.kt create mode 100644 core/ui/src/test/kotlin/com/tangem/core/ui/utils/FormattedDateMapperTest.kt delete mode 100644 features/feed/impl/src/main/kotlin/com/tangem/features/feed/ui/utils/CreatedTimeFormatter.kt diff --git a/common/ui/src/main/java/com/tangem/common/ui/expressStatus/ExpressStatusItem.kt b/common/ui/src/main/java/com/tangem/common/ui/expressStatus/ExpressStatusItem.kt index e44c2a673b..88a40df4bd 100644 --- a/common/ui/src/main/java/com/tangem/common/ui/expressStatus/ExpressStatusItem.kt +++ b/common/ui/src/main/java/com/tangem/common/ui/expressStatus/ExpressStatusItem.kt @@ -20,11 +20,13 @@ import androidx.compose.ui.tooling.preview.PreviewParameter import androidx.compose.ui.tooling.preview.PreviewParameterProvider import androidx.constraintlayout.compose.* import com.tangem.common.ui.R +import com.tangem.common.ui.expressStatus.state.buildExpressStatusSubtitle import com.tangem.core.ui.components.atoms.text.EllipsisText import com.tangem.core.ui.components.atoms.text.TextEllipsis import com.tangem.core.ui.components.currency.icon.CurrencyIcon import com.tangem.core.ui.components.currency.icon.CurrencyIconState import com.tangem.core.ui.extensions.TextReference +import com.tangem.core.ui.extensions.isNullOrEmpty import com.tangem.core.ui.extensions.resolveReference import com.tangem.core.ui.extensions.stringReference import com.tangem.core.ui.res.TangemTheme @@ -43,6 +45,7 @@ internal fun ExpressStatusItem( onClick: () -> Unit, modifier: Modifier = Modifier, toAmount: TextReference = TextReference.EMPTY, + subtitle: TextReference = TextReference.EMPTY, @DrawableRes infoIconRes: Int? = null, infoIconTint: Color? = null, ) { @@ -55,7 +58,8 @@ internal fun ExpressStatusItem( .padding(TangemTheme.dimens.spacing12) .testTag(TokenDetailsScreenTestTags.EXPRESS_STATUS_ITEM), ) { - val (titleRef, iconRef, infoIconRef, swapIconRef, fromRef, toRef, fromIconRef, toIconRef) = createRefs() + val (titleRef, subtitleRef, iconRef, infoIconRef, swapIconRef, fromRef, toRef, fromIconRef, toIconRef) = + createRefs() val padding6 = TangemTheme.dimens.spacing6 Text( @@ -66,9 +70,25 @@ internal fun ExpressStatusItem( .constrainAs(titleRef) { start.linkTo(parent.start) top.linkTo(parent.top) + end.linkTo(infoIconRef.start, padding6, padding6) + width = Dimension.preferredWrapContent + horizontalBias = 0f } .testTag(TokenDetailsScreenTestTags.EXPRESS_STATUS_ITEM_TITLE), ) + Text( + text = subtitle.resolveReference(), + style = TangemTheme.typography.body2, + color = TangemTheme.colors.text.tertiary, + modifier = Modifier.constrainAs(subtitleRef) { + start.linkTo(parent.start) + top.linkTo(titleRef.bottom) + end.linkTo(infoIconRef.start, padding6, padding6) + width = Dimension.preferredWrapContent + horizontalBias = 0f + visibility = if (subtitle.isNullOrEmpty()) Visibility.Gone else Visibility.Visible + }, + ) CurrencyIcon( state = fromTokenIconState, shouldDisplayNetwork = false, @@ -76,7 +96,7 @@ internal fun ExpressStatusItem( .size(TangemTheme.dimens.size20) .constrainAs(fromIconRef) { start.linkTo(parent.start) - top.linkTo(titleRef.bottom, padding6) + top.linkTo(subtitleRef.bottom, padding6) bottom.linkTo(parent.bottom) } .testTag(TokenDetailsScreenTestTags.EXPRESS_STATUS_ITEM_FROM_ICON), @@ -89,7 +109,7 @@ internal fun ExpressStatusItem( modifier = Modifier .constrainAs(fromRef) { start.linkTo(fromIconRef.end, padding6) - top.linkTo(titleRef.bottom, padding6) + top.linkTo(subtitleRef.bottom, padding6) end.linkTo(swapIconRef.start) bottom.linkTo(parent.bottom) width = Dimension.fillToConstraints.atMostWrapContent @@ -104,7 +124,7 @@ internal fun ExpressStatusItem( .size(TangemTheme.dimens.size12) .constrainAs(swapIconRef) { start.linkTo(fromRef.end, padding6) - top.linkTo(titleRef.bottom, padding6) + top.linkTo(subtitleRef.bottom, padding6) end.linkTo(toIconRef.start) bottom.linkTo(parent.bottom) } @@ -117,7 +137,7 @@ internal fun ExpressStatusItem( .size(TangemTheme.dimens.size20) .constrainAs(toIconRef) { start.linkTo(swapIconRef.end, padding6) - top.linkTo(titleRef.bottom, padding6) + top.linkTo(subtitleRef.bottom, padding6) end.linkTo(toRef.start) bottom.linkTo(parent.bottom) } @@ -131,7 +151,7 @@ internal fun ExpressStatusItem( modifier = Modifier .constrainAs(toRef) { start.linkTo(toIconRef.end, padding6) - top.linkTo(titleRef.bottom, padding6) + top.linkTo(subtitleRef.bottom, padding6) end.linkTo(infoIconRef.start, padding6, padding6) bottom.linkTo(parent.bottom) width = Dimension.fillToConstraints.atLeastWrapContent @@ -180,13 +200,17 @@ private fun ExpressStatusItemPreview( ) { TangemThemePreview { ExpressStatusItem( - title = stringReference("ChangeNow"), + title = stringReference("Exchange by ChangeHero"), fromTokenIconState = CurrencyIconState.Loading, toTokenIconState = CurrencyIconState.Loading, fromAmount = stringReference(amount), fromSymbol = "USDT", toAmount = stringReference(amount), toSymbol = "USDT", + subtitle = buildExpressStatusSubtitle( + activeStatus = stringReference("Confirming"), + date = stringReference("59 min ago"), + ), onClick = {}, infoIconRes = null, infoIconTint = null, diff --git a/common/ui/src/main/java/com/tangem/common/ui/expressStatus/ExpressStatusItems.kt b/common/ui/src/main/java/com/tangem/common/ui/expressStatus/ExpressStatusItems.kt index 1424805a32..49d77a4633 100644 --- a/common/ui/src/main/java/com/tangem/common/ui/expressStatus/ExpressStatusItems.kt +++ b/common/ui/src/main/java/com/tangem/common/ui/expressStatus/ExpressStatusItems.kt @@ -14,10 +14,10 @@ fun LazyListScope.expressTransactionsItems( ) { items( count = expressTxs.size, - key = { expressTxs[it].info.txId }, - contentType = { expressTxs[it]::class.java }, - ) { - val itemInfo = expressTxs[it].info + key = { index -> expressTxs[index].info.txId }, + contentType = { index -> expressTxs[index]::class.java }, + ) { index -> + val itemInfo = expressTxs[index].info val (iconRes, tint) = when (itemInfo.iconState) { ExpressTransactionStateIconUM.Warning -> { R.drawable.ic_alert_triangle_20 to TangemTheme.colors.icon.attention @@ -36,6 +36,7 @@ fun LazyListScope.expressTransactionsItems( fromSymbol = itemInfo.fromAmountSymbol, toAmount = itemInfo.toAmount, toSymbol = itemInfo.toAmountSymbol, + subtitle = itemInfo.subtitle, onClick = itemInfo.onClick, infoIconRes = iconRes, infoIconTint = tint, diff --git a/common/ui/src/main/java/com/tangem/common/ui/expressStatus/OnrampStatusMapper.kt b/common/ui/src/main/java/com/tangem/common/ui/expressStatus/OnrampStatusMapper.kt new file mode 100644 index 0000000000..a1e1b27b08 --- /dev/null +++ b/common/ui/src/main/java/com/tangem/common/ui/expressStatus/OnrampStatusMapper.kt @@ -0,0 +1,38 @@ +package com.tangem.common.ui.expressStatus + +import com.tangem.common.ui.R +import com.tangem.common.ui.expressStatus.state.ExpressTransactionStateIconUM +import com.tangem.core.ui.extensions.TextReference +import com.tangem.core.ui.extensions.resourceReference +import com.tangem.core.ui.extensions.wrappedList +import com.tangem.domain.onramp.model.OnrampStatus + +fun OnrampStatus.Status.toActiveStatusText(currencyName: String): TextReference = when (this) { + OnrampStatus.Status.Created, + OnrampStatus.Status.WaitingForPayment, + -> resourceReference(R.string.express_exchange_status_receiving_active) + OnrampStatus.Status.PaymentProcessing -> resourceReference(R.string.express_exchange_status_confirming_active) + OnrampStatus.Status.Verifying -> resourceReference(R.string.express_exchange_status_verifying) + OnrampStatus.Status.Paid -> resourceReference(R.string.express_status_buying_active, wrappedList(currencyName)) + OnrampStatus.Status.Sending -> resourceReference( + R.string.express_exchange_status_sending_active, + wrappedList(currencyName), + ) + OnrampStatus.Status.Finished -> resourceReference(R.string.express_status_bought, wrappedList(currencyName)) + OnrampStatus.Status.RefundInProgress -> resourceReference(R.string.express_exchange_status_refunding) + OnrampStatus.Status.Refunded -> resourceReference(R.string.express_exchange_status_refunded) + OnrampStatus.Status.Paused -> resourceReference(R.string.express_exchange_status_paused) + OnrampStatus.Status.Expired, + OnrampStatus.Status.Failed, + -> resourceReference(R.string.express_exchange_status_failed) +} + +fun OnrampStatus.Status.toIconState(): ExpressTransactionStateIconUM = when (this) { + OnrampStatus.Status.Verifying, + OnrampStatus.Status.RefundInProgress, + -> ExpressTransactionStateIconUM.Warning + OnrampStatus.Status.Refunded, + OnrampStatus.Status.Failed, + -> ExpressTransactionStateIconUM.Error + else -> ExpressTransactionStateIconUM.None +} \ No newline at end of file diff --git a/common/ui/src/main/java/com/tangem/common/ui/expressStatus/state/ExpressStatusSubtitleBuilder.kt b/common/ui/src/main/java/com/tangem/common/ui/expressStatus/state/ExpressStatusSubtitleBuilder.kt new file mode 100644 index 0000000000..dadfe910b1 --- /dev/null +++ b/common/ui/src/main/java/com/tangem/common/ui/expressStatus/state/ExpressStatusSubtitleBuilder.kt @@ -0,0 +1,52 @@ +package com.tangem.common.ui.expressStatus.state + +import com.tangem.core.ui.R +import com.tangem.core.ui.extensions.TextReference +import com.tangem.core.ui.extensions.WrappedList +import com.tangem.core.ui.extensions.isNullOrEmpty +import com.tangem.core.ui.extensions.stringReference +import com.tangem.core.ui.extensions.wrappedList + +/** + * Builds express status subtitle by combining [activeStatus] with formatted [date]. + * + * Separator rules: + * - Relative date (MinutesAgo / HoursAgo PluralRes) — " ~ ". + * - Absolute date (Today / FullDate) — " "; Today's leading Res is decapitalized when a status + * prefix is present. + * - Either side empty — the other is returned as is (no modifications). + */ +fun buildExpressStatusSubtitle(activeStatus: TextReference, date: TextReference): TextReference { + val hasStatus = !activeStatus.isNullOrEmpty() + val hasDate = !date.isNullOrEmpty() + return when { + !hasStatus && !hasDate -> TextReference.EMPTY + hasStatus && !hasDate -> activeStatus + !hasStatus && hasDate -> date + else -> combineWithStatus(activeStatus, date) + } +} + +private fun combineWithStatus(status: TextReference, date: TextReference): TextReference { + val shouldUseTilde = date.isRelativeTimeAgo() + val separator = if (shouldUseTilde) stringReference(value = " ~ ") else stringReference(value = " ") + val datePart = if (shouldUseTilde) date else date.decapitalizeToday() + return TextReference.Combined(refs = wrappedList(status, separator, datePart)) +} + +private fun TextReference.isRelativeTimeAgo(): Boolean { + return this is TextReference.PluralRes && + (id == R.plurals.common_minutes_time_ago || id == R.plurals.common_hours_time_ago) +} + +private fun TextReference.decapitalizeToday(): TextReference { + if (this !is TextReference.Combined) return this + val patched = refs.data.map { ref -> + if (ref is TextReference.Res && ref.id == R.string.common_today) { + ref.copy(shouldDecapitalize = true) + } else { + ref + } + } + return TextReference.Combined(refs = WrappedList(data = patched)) +} \ No newline at end of file diff --git a/common/ui/src/main/java/com/tangem/common/ui/expressStatus/state/ExpressTransactionStateUM.kt b/common/ui/src/main/java/com/tangem/common/ui/expressStatus/state/ExpressTransactionStateUM.kt index fb2489e331..2b7035b44d 100644 --- a/common/ui/src/main/java/com/tangem/common/ui/expressStatus/state/ExpressTransactionStateUM.kt +++ b/common/ui/src/main/java/com/tangem/common/ui/expressStatus/state/ExpressTransactionStateUM.kt @@ -28,6 +28,8 @@ data class ExpressTransactionStateInfoUM( val txExternalUrl: String?, val timestamp: Long, val timestampFormatted: TextReference, + val timestampAgoFormatted: TextReference, + val activeStatus: TextReference, val onGoToProviderClick: (String) -> Unit, val onClick: () -> Unit, val onDisposeExpressStatus: () -> Unit, @@ -36,12 +38,14 @@ data class ExpressTransactionStateInfoUM( val toFiatAmount: TextReference?, val toAmountSymbol: String, val toCurrencyIcon: CurrencyIconState, - val fromAmount: TextReference, val fromFiatAmount: TextReference?, val fromAmountSymbol: String, val fromCurrencyIcon: CurrencyIconState, -) +) { + val subtitle: TextReference + get() = buildExpressStatusSubtitle(activeStatus = activeStatus, date = timestampAgoFormatted) +} enum class ExpressTransactionStateIconUM { Warning, diff --git a/common/ui/src/test/kotlin/com/tangem/common/ui/expressStatus/state/ExpressStatusSubtitleBuilderTest.kt b/common/ui/src/test/kotlin/com/tangem/common/ui/expressStatus/state/ExpressStatusSubtitleBuilderTest.kt new file mode 100644 index 0000000000..04870d3d3b --- /dev/null +++ b/common/ui/src/test/kotlin/com/tangem/common/ui/expressStatus/state/ExpressStatusSubtitleBuilderTest.kt @@ -0,0 +1,117 @@ +package com.tangem.common.ui.expressStatus.state + +import com.google.common.truth.Truth.assertThat +import com.tangem.core.ui.R +import com.tangem.core.ui.extensions.TextReference +import com.tangem.core.ui.extensions.WrappedList +import com.tangem.core.ui.extensions.stringReference +import com.tangem.core.ui.extensions.wrappedList +import com.tangem.utils.StringsSigns +import org.junit.jupiter.api.Test + +internal class ExpressStatusSubtitleBuilderTest { + + private val status = stringReference("Confirming") + + private val minutesAgo = TextReference.PluralRes( + id = R.plurals.common_minutes_time_ago, + count = 30, + formatArgs = wrappedList(30), + ) + + private val hoursAgo = TextReference.PluralRes( + id = R.plurals.common_hours_time_ago, + count = 3, + formatArgs = wrappedList(3), + ) + + private val today = TextReference.Combined( + refs = WrappedList( + data = listOf( + TextReference.Res(R.string.common_today), + TextReference.Str(StringsSigns.COMA_SIGN), + TextReference.Str(StringsSigns.WHITE_SPACE), + TextReference.Str("03:00"), + ), + ), + ) + + private val fullDate = TextReference.Str("14 Oct 2025") + + @Test + fun `GIVEN empty activeStatus AND empty date WHEN buildExpressStatusSubtitle THEN return EMPTY`() { + val result = buildExpressStatusSubtitle( + activeStatus = TextReference.EMPTY, + date = TextReference.EMPTY, + ) + + assertThat(result).isEqualTo(TextReference.EMPTY) + } + + @Test + fun `GIVEN non-empty activeStatus AND empty date WHEN buildExpressStatusSubtitle THEN return activeStatus`() { + val result = buildExpressStatusSubtitle(activeStatus = status, date = TextReference.EMPTY) + + assertThat(result).isEqualTo(status) + } + + @Test + fun `GIVEN empty activeStatus AND MinutesAgo date WHEN buildExpressStatusSubtitle THEN return date as is`() { + val result = buildExpressStatusSubtitle(activeStatus = TextReference.EMPTY, date = minutesAgo) + + assertThat(result).isEqualTo(minutesAgo) + } + + @Test + fun `GIVEN status AND MinutesAgo date WHEN buildExpressStatusSubtitle THEN return Combined with tilde separator`() { + val result = buildExpressStatusSubtitle(activeStatus = status, date = minutesAgo) + + assertThat(result).isEqualTo( + TextReference.Combined(refs = wrappedList(status, stringReference(" ~ "), minutesAgo)), + ) + } + + @Test + fun `GIVEN status AND HoursAgo date WHEN buildExpressStatusSubtitle THEN return Combined with tilde separator`() { + val result = buildExpressStatusSubtitle(activeStatus = status, date = hoursAgo) + + assertThat(result).isEqualTo( + TextReference.Combined(refs = wrappedList(status, stringReference(" ~ "), hoursAgo)), + ) + } + + @Test + fun `GIVEN status AND Today date WHEN buildExpressStatusSubtitle THEN return Combined with space AND decapitalized today`() { + val result = buildExpressStatusSubtitle(activeStatus = status, date = today) + + val expectedToday = TextReference.Combined( + refs = WrappedList( + data = listOf( + TextReference.Res(id = R.string.common_today, shouldDecapitalize = true), + TextReference.Str(StringsSigns.COMA_SIGN), + TextReference.Str(StringsSigns.WHITE_SPACE), + TextReference.Str("03:00"), + ), + ), + ) + assertThat(result).isEqualTo( + TextReference.Combined(refs = wrappedList(status, stringReference(" "), expectedToday)), + ) + } + + @Test + fun `GIVEN status AND FullDate date WHEN buildExpressStatusSubtitle THEN return Combined with space separator`() { + val result = buildExpressStatusSubtitle(activeStatus = status, date = fullDate) + + assertThat(result).isEqualTo( + TextReference.Combined(refs = wrappedList(status, stringReference(" "), fullDate)), + ) + } + + @Test + fun `GIVEN empty activeStatus AND Today date WHEN buildExpressStatusSubtitle THEN return Today as is without decapitalize`() { + val result = buildExpressStatusSubtitle(activeStatus = TextReference.EMPTY, date = today) + + assertThat(result).isEqualTo(today) + } +} \ No newline at end of file diff --git a/core/ui/build.gradle.kts b/core/ui/build.gradle.kts index 58c1e0cb0f..10573a0d9d 100644 --- a/core/ui/build.gradle.kts +++ b/core/ui/build.gradle.kts @@ -66,6 +66,10 @@ abstract class VerifyDesignTokensTask : DefaultTask() { } } +tasks.withType().configureEach { + useJUnitPlatform() +} + android { namespace = "com.tangem.core.ui" @@ -149,4 +153,5 @@ dependencies { testImplementation(deps.test.truth) testImplementation(deps.test.junit5) testRuntimeOnly(deps.test.junit5.engine) + testRuntimeOnly(deps.test.junit5.vintage.engine) } \ No newline at end of file diff --git a/core/ui/src/main/java/com/tangem/core/ui/utils/DateUtils.kt b/core/ui/src/main/java/com/tangem/core/ui/utils/DateUtils.kt index 9354511464..6c5be9aecd 100644 --- a/core/ui/src/main/java/com/tangem/core/ui/utils/DateUtils.kt +++ b/core/ui/src/main/java/com/tangem/core/ui/utils/DateUtils.kt @@ -5,6 +5,7 @@ import com.tangem.utils.extensions.isToday import com.tangem.utils.extensions.isYesterday import org.joda.time.DateTime import org.joda.time.DateTimeZone +import org.joda.time.LocalDate import org.joda.time.format.DateTimeFormatter /** @@ -46,16 +47,26 @@ fun Long.formatAsDateTime(formatter: DateTimeFormatter): String { * @param now The current date to compare against. * @return A [FormattedDate] subclass. */ -@Suppress("MagicNumber") fun getFormattedDate(createdAt: String, now: DateTime): FormattedDate { val pastDateUtc = try { DateTime.parse(createdAt) } catch (_: Exception) { return FormattedDate.FullDate(createdAt) } + return getFormattedDate(pastDateUtc = pastDateUtc, now = now) +} +/** + * Compares the given past date to [now] and returns a [FormattedDate] describing the difference. + * + * @param pastDateUtc Past date in UTC. + * @param now The current date to compare against. + */ +@Suppress("MagicNumber") +fun getFormattedDate(pastDateUtc: DateTime, now: DateTime): FormattedDate { val pastDateLocal = pastDateUtc.withZone(DateTimeZone.getDefault()) - val isToday = pastDateLocal.isToday() + val nowLocal = now.withZone(DateTimeZone.getDefault()) + val isToday = LocalDate(pastDateLocal) == LocalDate(nowLocal) val diffInMillis = now.millis - pastDateUtc.millis val diffInMinutes = diffInMillis / (1000 * 60) diff --git a/core/ui/src/main/java/com/tangem/core/ui/utils/FormattedDateMapper.kt b/core/ui/src/main/java/com/tangem/core/ui/utils/FormattedDateMapper.kt new file mode 100644 index 0000000000..ef715af57c --- /dev/null +++ b/core/ui/src/main/java/com/tangem/core/ui/utils/FormattedDateMapper.kt @@ -0,0 +1,55 @@ +package com.tangem.core.ui.utils + +import com.tangem.core.ui.R +import com.tangem.core.ui.extensions.TextReference +import com.tangem.core.ui.extensions.WrappedList +import com.tangem.core.ui.extensions.wrappedList +import com.tangem.utils.StringsSigns +import org.joda.time.DateTime +import org.joda.time.DateTimeZone + +/** + * Maps an ISO 8601 date string into a [TextReference] with a human-friendly "time ago" label. + */ +fun mapFormattedDate(createdAt: String, now: DateTime = DateTime.now()): TextReference { + val formattedDate = runCatching { + getFormattedDate(createdAt = createdAt, now = now) + }.getOrElse { FormattedDate.FullDate(createdAt) } + + return formattedDate.toTextReference() +} + +/** + * Maps an epoch millisecond [timestamp] into a [TextReference] with a human-friendly "time ago" label. + */ +fun mapFormattedDate(timestamp: Long, now: DateTime = DateTime.now()): TextReference { + val formattedDate = runCatching { + getFormattedDate(pastDateUtc = DateTime(timestamp, DateTimeZone.UTC), now = now) + }.getOrElse { FormattedDate.FullDate(timestamp.toString()) } + + return formattedDate.toTextReference() +} + +private fun FormattedDate.toTextReference(): TextReference = when (this) { + is FormattedDate.FullDate -> TextReference.Str(value = date) + is FormattedDate.HoursAgo -> TextReference.PluralRes( + id = R.plurals.common_hours_time_ago, + count = hours, + formatArgs = wrappedList(hours), + ) + is FormattedDate.MinutesAgo -> TextReference.PluralRes( + id = R.plurals.common_minutes_time_ago, + count = minutes, + formatArgs = wrappedList(minutes), + ) + is FormattedDate.Today -> TextReference.Combined( + refs = WrappedList( + data = listOf( + TextReference.Res(R.string.common_today), + TextReference.Str(StringsSigns.COMA_SIGN), + TextReference.Str(StringsSigns.WHITE_SPACE), + TextReference.Str(time), + ), + ), + ) +} \ No newline at end of file diff --git a/core/ui/src/test/kotlin/com/tangem/core/ui/utils/DateTimeFormattersTest.kt b/core/ui/src/test/kotlin/com/tangem/core/ui/utils/DateTimeFormattersTest.kt index 7fa8bcd9ce..0be823f7d9 100644 --- a/core/ui/src/test/kotlin/com/tangem/core/ui/utils/DateTimeFormattersTest.kt +++ b/core/ui/src/test/kotlin/com/tangem/core/ui/utils/DateTimeFormattersTest.kt @@ -1,6 +1,12 @@ package com.tangem.core.ui.utils +import android.text.format.DateFormat import com.google.common.truth.Truth +import io.mockk.every +import io.mockk.mockkStatic +import io.mockk.unmockkStatic +import org.junit.jupiter.api.AfterEach +import org.junit.jupiter.api.BeforeEach import org.junit.jupiter.api.Test import org.junit.jupiter.api.TestInstance @@ -11,6 +17,17 @@ import org.junit.jupiter.api.TestInstance @TestInstance(TestInstance.Lifecycle.PER_CLASS) class DateTimeFormattersTest { + @BeforeEach + fun setUp() { + mockkStatic(DateFormat::class) + every { DateFormat.getBestDateTimePattern(any(), any()) } answers { secondArg() } + } + + @AfterEach + fun tearDown() { + unmockkStatic(DateFormat::class) + } + @Test fun `converts LLLL to MMMM - full standalone month pattern that crashes on Chinese locale`() { // Arrange diff --git a/core/ui/src/test/kotlin/com/tangem/core/ui/utils/DateUtilsTest.kt b/core/ui/src/test/kotlin/com/tangem/core/ui/utils/DateUtilsTest.kt index 6e145b2371..626df6c6b2 100644 --- a/core/ui/src/test/kotlin/com/tangem/core/ui/utils/DateUtilsTest.kt +++ b/core/ui/src/test/kotlin/com/tangem/core/ui/utils/DateUtilsTest.kt @@ -1,8 +1,15 @@ package com.tangem.core.ui.utils +import android.text.format.DateFormat import com.google.common.truth.Truth +import io.mockk.every +import io.mockk.mockkObject +import io.mockk.mockkStatic +import io.mockk.unmockkObject +import io.mockk.unmockkStatic import org.joda.time.DateTime import org.joda.time.DateTimeZone +import org.joda.time.format.DateTimeFormatterBuilder import org.junit.jupiter.api.AfterEach import org.junit.jupiter.api.BeforeEach import org.junit.jupiter.api.Test @@ -19,11 +26,23 @@ class DateUtilsTest { fun setUp() { defaultTimeZone = DateTimeZone.getDefault() DateTimeZone.setDefault(DateTimeZone.forID("Europe/Moscow")) + + mockkStatic(DateFormat::class) + every { DateFormat.getBestDateTimePattern(any(), any()) } answers { secondArg() } + + mockkObject(DateTimeFormatters) + every { DateTimeFormatters.timeFormatter } returns DateTimeFormatterBuilder() + .appendHourOfDay(2) + .appendLiteral(':') + .appendMinuteOfHour(2) + .toFormatter() } @AfterEach fun tearDown() { DateTimeZone.setDefault(defaultTimeZone) + unmockkObject(DateTimeFormatters) + unmockkStatic(DateFormat::class) } @Test diff --git a/core/ui/src/test/kotlin/com/tangem/core/ui/utils/FormattedDateMapperTest.kt b/core/ui/src/test/kotlin/com/tangem/core/ui/utils/FormattedDateMapperTest.kt new file mode 100644 index 0000000000..4e6ebc7f46 --- /dev/null +++ b/core/ui/src/test/kotlin/com/tangem/core/ui/utils/FormattedDateMapperTest.kt @@ -0,0 +1,234 @@ +package com.tangem.core.ui.utils + +import android.text.format.DateFormat +import com.google.common.truth.Truth +import com.tangem.core.ui.R +import com.tangem.core.ui.extensions.TextReference +import com.tangem.core.ui.extensions.WrappedList +import com.tangem.core.ui.extensions.wrappedList +import com.tangem.utils.StringsSigns +import io.mockk.every +import io.mockk.mockkObject +import io.mockk.mockkStatic +import io.mockk.unmockkObject +import io.mockk.unmockkStatic +import org.joda.time.DateTime +import org.joda.time.DateTimeZone +import org.joda.time.format.DateTimeFormatterBuilder +import org.junit.jupiter.api.AfterEach +import org.junit.jupiter.api.BeforeEach +import org.junit.jupiter.api.Test +import org.junit.jupiter.api.TestInstance + +@TestInstance(TestInstance.Lifecycle.PER_CLASS) +class FormattedDateMapperTest { + + private lateinit var defaultTimeZone: DateTimeZone + + private val now = createDateTime(day = 14, hour = 12) + + @BeforeEach + fun setUp() { + defaultTimeZone = DateTimeZone.getDefault() + DateTimeZone.setDefault(DateTimeZone.forID("Europe/Moscow")) + + mockkStatic(DateFormat::class) + every { DateFormat.getBestDateTimePattern(any(), any()) } answers { secondArg() } + + mockkObject(DateTimeFormatters) + every { DateTimeFormatters.timeFormatter } returns DateTimeFormatterBuilder() + .appendHourOfDay(2) + .appendLiteral(':') + .appendMinuteOfHour(2) + .toFormatter() + } + + @AfterEach + fun tearDown() { + DateTimeZone.setDefault(defaultTimeZone) + unmockkObject(DateTimeFormatters) + unmockkStatic(DateFormat::class) + } + + // region String overload + + @Test + fun `GIVEN iso string less than a minute ago WHEN mapFormattedDate THEN return minutes PluralRes with count 1`() { + val createdAt = now.minusSeconds(30).toString() + + val result = mapFormattedDate(createdAt = createdAt, now = now) + + Truth.assertThat(result).isEqualTo( + TextReference.PluralRes( + id = R.plurals.common_minutes_time_ago, + count = 1, + formatArgs = wrappedList(1), + ), + ) + } + + @Test + fun `GIVEN iso string 30 minutes ago WHEN mapFormattedDate THEN return minutes PluralRes with count 30`() { + val createdAt = now.minusMinutes(30).toString() + + val result = mapFormattedDate(createdAt = createdAt, now = now) + + Truth.assertThat(result).isEqualTo( + TextReference.PluralRes( + id = R.plurals.common_minutes_time_ago, + count = 30, + formatArgs = wrappedList(30), + ), + ) + } + + @Test + fun `GIVEN iso string 3 hours ago today WHEN mapFormattedDate THEN return hours PluralRes with count 3`() { + val createdAt = now.minusHours(3).toString() + + val result = mapFormattedDate(createdAt = createdAt, now = now) + + Truth.assertThat(result).isEqualTo( + TextReference.PluralRes( + id = R.plurals.common_hours_time_ago, + count = 3, + formatArgs = wrappedList(3), + ), + ) + } + + @Test + fun `GIVEN iso string today but past 12 hours WHEN mapFormattedDate THEN return Combined with today and local time`() { + val pastDate = createDateTime(day = 14, hour = 0) + val createdAt = pastDate.toString() + val nowInTest = createDateTime(day = 14, hour = 12) + + val result = mapFormattedDate(createdAt = createdAt, now = nowInTest) + + Truth.assertThat(result).isEqualTo( + TextReference.Combined( + refs = WrappedList( + data = listOf( + TextReference.Res(R.string.common_today), + TextReference.Str(StringsSigns.COMA_SIGN), + TextReference.Str(StringsSigns.WHITE_SPACE), + TextReference.Str("03:00"), + ), + ), + ), + ) + } + + @Test + fun `GIVEN iso string from a previous day WHEN mapFormattedDate THEN return Str FullDate`() { + val pastDate = createDateTime(day = 10, hour = 9) + val createdAt = pastDate.toString() + + val result = mapFormattedDate(createdAt = createdAt, now = now) + + Truth.assertThat(result).isInstanceOf(TextReference.Str::class.java) + } + + @Test + fun `GIVEN malformed iso string WHEN mapFormattedDate THEN return Str with original value`() { + val malformed = "2025/10/14T12:00:00.000Z" + + val result = mapFormattedDate(createdAt = malformed, now = now) + + Truth.assertThat(result).isEqualTo(TextReference.Str(value = malformed)) + } + + // endregion + + // region Long overload + + @Test + fun `GIVEN timestamp less than a minute ago WHEN mapFormattedDate THEN return minutes PluralRes with count 1`() { + val timestamp = now.minusSeconds(30).millis + + val result = mapFormattedDate(timestamp = timestamp, now = now) + + Truth.assertThat(result).isEqualTo( + TextReference.PluralRes( + id = R.plurals.common_minutes_time_ago, + count = 1, + formatArgs = wrappedList(1), + ), + ) + } + + @Test + fun `GIVEN timestamp 45 minutes ago WHEN mapFormattedDate THEN return minutes PluralRes with count 45`() { + val timestamp = now.minusMinutes(45).millis + + val result = mapFormattedDate(timestamp = timestamp, now = now) + + Truth.assertThat(result).isEqualTo( + TextReference.PluralRes( + id = R.plurals.common_minutes_time_ago, + count = 45, + formatArgs = wrappedList(45), + ), + ) + } + + @Test + fun `GIVEN timestamp 5 hours ago today WHEN mapFormattedDate THEN return hours PluralRes with count 5`() { + val timestamp = now.minusHours(5).millis + + val result = mapFormattedDate(timestamp = timestamp, now = now) + + Truth.assertThat(result).isEqualTo( + TextReference.PluralRes( + id = R.plurals.common_hours_time_ago, + count = 5, + formatArgs = wrappedList(5), + ), + ) + } + + @Test + fun `GIVEN timestamp today but past 12 hours WHEN mapFormattedDate THEN return Combined with today and local time`() { + val pastDate = createDateTime(day = 14, hour = 0) + val nowInTest = createDateTime(day = 14, hour = 12) + + val result = mapFormattedDate(timestamp = pastDate.millis, now = nowInTest) + + Truth.assertThat(result).isEqualTo( + TextReference.Combined( + refs = WrappedList( + data = listOf( + TextReference.Res(R.string.common_today), + TextReference.Str(StringsSigns.COMA_SIGN), + TextReference.Str(StringsSigns.WHITE_SPACE), + TextReference.Str("03:00"), + ), + ), + ), + ) + } + + @Test + fun `GIVEN timestamp from a previous day WHEN mapFormattedDate THEN return Str FullDate`() { + val pastDate = createDateTime(day = 10, hour = 9) + + val result = mapFormattedDate(timestamp = pastDate.millis, now = now) + + Truth.assertThat(result).isInstanceOf(TextReference.Str::class.java) + } + + // endregion + + private fun createDateTime(day: Int, hour: Int): DateTime { + return DateTime( + /* year = */ 2025, + /* monthOfYear = */ 10, + /* dayOfMonth = */ day, + /* hourOfDay = */ hour, + /* minuteOfHour = */ 0, + /* secondOfMinute = */ 0, + /* millisOfSecond = */ 0, + /* zone = */ DateTimeZone.UTC, + ) + } +} \ No newline at end of file diff --git a/features/feed/impl/src/main/kotlin/com/tangem/features/feed/model/converter/ShortArticleToArticleConfigUMConverter.kt b/features/feed/impl/src/main/kotlin/com/tangem/features/feed/model/converter/ShortArticleToArticleConfigUMConverter.kt index b565dd823c..1fde08923e 100644 --- a/features/feed/impl/src/main/kotlin/com/tangem/features/feed/model/converter/ShortArticleToArticleConfigUMConverter.kt +++ b/features/feed/impl/src/main/kotlin/com/tangem/features/feed/model/converter/ShortArticleToArticleConfigUMConverter.kt @@ -7,7 +7,7 @@ import com.tangem.data.common.currency.getTokenIconUrlFromDefaultHost import com.tangem.domain.models.currency.CryptoCurrency import com.tangem.domain.models.news.ShortArticle import com.tangem.features.feed.ui.feed.components.articles.ArticleConfigUM -import com.tangem.features.feed.ui.utils.mapFormattedDate +import com.tangem.core.ui.utils.mapFormattedDate import com.tangem.utils.converter.Converter import kotlinx.collections.immutable.ImmutableList import kotlinx.collections.immutable.ImmutableSet diff --git a/features/feed/impl/src/main/kotlin/com/tangem/features/feed/model/news/details/converter/NewsDetailsConverter.kt b/features/feed/impl/src/main/kotlin/com/tangem/features/feed/model/news/details/converter/NewsDetailsConverter.kt index a966da27f2..d2cdcd64d7 100644 --- a/features/feed/impl/src/main/kotlin/com/tangem/features/feed/model/news/details/converter/NewsDetailsConverter.kt +++ b/features/feed/impl/src/main/kotlin/com/tangem/features/feed/model/news/details/converter/NewsDetailsConverter.kt @@ -4,23 +4,17 @@ import androidx.compose.runtime.Stable import com.tangem.core.ui.components.label.entity.LabelLeadingContentUM import com.tangem.core.ui.components.label.entity.LabelUM import com.tangem.core.ui.extensions.TextReference -import com.tangem.core.ui.extensions.WrappedList -import com.tangem.core.ui.extensions.wrappedList -import com.tangem.core.ui.utils.FormattedDate -import com.tangem.core.ui.utils.getFormattedDate +import com.tangem.core.ui.utils.mapFormattedDate import com.tangem.data.common.currency.getTokenIconUrlFromDefaultHost import com.tangem.domain.models.currency.CryptoCurrency import com.tangem.domain.models.news.DetailedArticle import com.tangem.domain.models.news.RelatedArticle -import com.tangem.features.feed.impl.R import com.tangem.features.feed.ui.news.details.state.ArticleUM import com.tangem.features.feed.ui.news.details.state.Media import com.tangem.features.feed.ui.news.details.state.RelatedArticleUM -import com.tangem.utils.StringsSigns import com.tangem.utils.converter.Converter import kotlinx.collections.immutable.ImmutableList import kotlinx.collections.immutable.toImmutableList -import org.joda.time.DateTime @Stable internal class NewsDetailsConverter( @@ -74,34 +68,4 @@ internal class NewsDetailsConverter( ) }.toImmutableList() } - - private fun mapFormattedDate(createdAt: String): TextReference { - val formattedDate = getFormattedDate( - createdAt = createdAt, - now = DateTime.now(), - ) - return when (formattedDate) { - is FormattedDate.FullDate -> TextReference.Str(value = formattedDate.date) - is FormattedDate.HoursAgo -> TextReference.PluralRes( - id = R.plurals.news_published_hours_ago, - count = formattedDate.hours, - formatArgs = wrappedList(formattedDate.hours), - ) - is FormattedDate.MinutesAgo -> TextReference.PluralRes( - id = R.plurals.news_published_minutes_ago, - count = formattedDate.minutes, - formatArgs = wrappedList(formattedDate.minutes), - ) - is FormattedDate.Today -> TextReference.Combined( - refs = WrappedList( - data = listOf( - TextReference.Res(R.string.common_today), - TextReference.Str(StringsSigns.COMA_SIGN), - TextReference.Str(StringsSigns.WHITE_SPACE), - TextReference.Str(formattedDate.time), - ), - ), - ) - } - } } \ No newline at end of file diff --git a/features/feed/impl/src/main/kotlin/com/tangem/features/feed/ui/utils/CreatedTimeFormatter.kt b/features/feed/impl/src/main/kotlin/com/tangem/features/feed/ui/utils/CreatedTimeFormatter.kt deleted file mode 100644 index ef92d71b3c..0000000000 --- a/features/feed/impl/src/main/kotlin/com/tangem/features/feed/ui/utils/CreatedTimeFormatter.kt +++ /dev/null @@ -1,43 +0,0 @@ -package com.tangem.features.feed.ui.utils - -import com.tangem.core.ui.extensions.TextReference -import com.tangem.core.ui.extensions.WrappedList -import com.tangem.core.ui.extensions.wrappedList -import com.tangem.core.ui.utils.FormattedDate -import com.tangem.core.ui.utils.getFormattedDate -import com.tangem.features.feed.impl.R -import com.tangem.utils.StringsSigns -import org.joda.time.DateTime - -internal fun mapFormattedDate(createdAt: String): TextReference { - val formattedDate = runCatching { - getFormattedDate( - createdAt = createdAt, - now = DateTime.now(), - ) - }.getOrElse { FormattedDate.FullDate("") } - - return when (formattedDate) { - is FormattedDate.FullDate -> TextReference.Str(value = formattedDate.date) - is FormattedDate.HoursAgo -> TextReference.PluralRes( - id = R.plurals.news_published_hours_ago, - count = formattedDate.hours, - formatArgs = wrappedList(formattedDate.hours), - ) - is FormattedDate.MinutesAgo -> TextReference.PluralRes( - id = R.plurals.news_published_minutes_ago, - count = formattedDate.minutes, - formatArgs = wrappedList(formattedDate.minutes), - ) - is FormattedDate.Today -> TextReference.Combined( - refs = WrappedList( - data = listOf( - TextReference.Res(R.string.common_today), - TextReference.Str(StringsSigns.COMA_SIGN), - TextReference.Str(StringsSigns.WHITE_SPACE), - TextReference.Str(formattedDate.time), - ), - ), - ) - } -} \ No newline at end of file diff --git a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/factory/TokenDetailsOnrampTransactionStateConverter.kt b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/factory/TokenDetailsOnrampTransactionStateConverter.kt index 2181819a15..e2e0af401f 100644 --- a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/factory/TokenDetailsOnrampTransactionStateConverter.kt +++ b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/factory/TokenDetailsOnrampTransactionStateConverter.kt @@ -1,6 +1,8 @@ package com.tangem.feature.tokendetails.presentation.tokendetails.state.factory import com.tangem.common.ui.expressStatus.state.* +import com.tangem.common.ui.expressStatus.toActiveStatusText +import com.tangem.common.ui.expressStatus.toIconState import com.tangem.common.ui.notifications.ExpressNotificationsUM import com.tangem.common.ui.notifications.NotificationUM import com.tangem.core.analytics.api.AnalyticsEventHandler @@ -12,6 +14,7 @@ import com.tangem.core.ui.extensions.wrappedList import com.tangem.core.ui.format.bigdecimal.crypto import com.tangem.core.ui.format.bigdecimal.fiat import com.tangem.core.ui.format.bigdecimal.format +import com.tangem.core.ui.utils.mapFormattedDate import com.tangem.core.ui.utils.toDateFormatWithTodayYesterday import com.tangem.core.ui.utils.toTimeFormat import com.tangem.domain.appcurrency.model.AppCurrency @@ -55,6 +58,8 @@ internal class TokenDetailsOnrampTransactionStateConverter( value.timestamp.toTimeFormat(), ), ), + timestampAgoFormatted = mapFormattedDate(value.timestamp), + activeStatus = value.status.toActiveStatusText(cryptoCurrency.name), toAmount = stringReference( value.toAmount.format { crypto(cryptoCurrency) }, ), @@ -82,7 +87,7 @@ internal class TokenDetailsOnrampTransactionStateConverter( url = value.fromCurrency.image, fallbackResId = R.drawable.ic_currency_24, ), - iconState = getIconState(value.status), + iconState = value.status.toIconState(), onGoToProviderClick = { url -> analyticsEventHandler.send(TokenOnrampAnalyticsEvent.GoToProvider()) clickIntents.onGoToProviderClick(url) @@ -124,18 +129,6 @@ internal class TokenDetailsOnrampTransactionStateConverter( null } - private fun getIconState(status: OnrampStatus.Status): ExpressTransactionStateIconUM { - return when (status) { - OnrampStatus.Status.RefundInProgress, - OnrampStatus.Status.Verifying, - -> ExpressTransactionStateIconUM.Warning - OnrampStatus.Status.Refunded, - OnrampStatus.Status.Failed, - -> ExpressTransactionStateIconUM.Error - else -> ExpressTransactionStateIconUM.None - } - } - private fun convertStatuses(status: OnrampStatus.Status, externalTxUrl: String?): ExpressStatusUM { val statuses = with(status) { persistentListOf( diff --git a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/factory/TokenDetailsSwapTransactionsStateConverter.kt b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/factory/TokenDetailsSwapTransactionsStateConverter.kt index e5d7a1341b..454a03e171 100644 --- a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/factory/TokenDetailsSwapTransactionsStateConverter.kt +++ b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/factory/TokenDetailsSwapTransactionsStateConverter.kt @@ -13,6 +13,7 @@ import com.tangem.core.ui.extensions.wrappedList import com.tangem.core.ui.format.bigdecimal.crypto import com.tangem.core.ui.format.bigdecimal.fiat import com.tangem.core.ui.format.bigdecimal.format +import com.tangem.core.ui.utils.mapFormattedDate import com.tangem.core.ui.utils.toDateFormatWithTodayYesterday import com.tangem.core.ui.utils.toTimeFormat import com.tangem.domain.appcurrency.model.AppCurrency @@ -143,6 +144,7 @@ internal class TokenDetailsSwapTransactionsStateConverter( info = tx.info.copy( txExternalId = statusModel.txExternalId, txExternalUrl = statusModel.txExternalUrl, + activeStatus = getActiveStatusText(statusModel.status), ), ) } @@ -164,6 +166,8 @@ internal class TokenDetailsSwapTransactionsStateConverter( timestampFormatted = stringReference( "${timestamp.toDateFormatWithTodayYesterday()}, ${timestamp.toTimeFormat()}", ), + timestampAgoFormatted = mapFormattedDate(timestamp), + activeStatus = getActiveStatusText(transaction.status?.status), toAmount = getCryptoAmount(transaction.toCryptoAmount, toCryptoCurrency), toFiatAmount = getFiatAmount(toFiatAmount), toCurrencyIcon = iconStateConverter.convert(toCryptoCurrency), @@ -251,6 +255,26 @@ internal class TokenDetailsSwapTransactionsStateConverter( } } + private fun getActiveStatusText(status: ExchangeStatus?): TextReference = when (status) { + ExchangeStatus.New, + ExchangeStatus.Waiting, + -> resourceReference(R.string.express_exchange_status_receiving_active) + ExchangeStatus.WaitingTxHash -> resourceReference(R.string.express_exchange_status_waiting_tx_hash) + ExchangeStatus.Confirming -> resourceReference(R.string.express_exchange_status_confirming_active) + ExchangeStatus.Verifying -> resourceReference(R.string.express_exchange_status_verifying) + ExchangeStatus.Exchanging -> resourceReference(R.string.express_exchange_status_exchanging_active) + ExchangeStatus.Sending -> resourceReference(R.string.express_exchange_status_sending_active) + ExchangeStatus.Finished -> resourceReference(R.string.express_exchange_status_sent) + ExchangeStatus.Refunded -> resourceReference(R.string.express_exchange_status_refunded) + ExchangeStatus.Paused -> resourceReference(R.string.express_exchange_status_paused) + ExchangeStatus.Cancelled -> resourceReference(R.string.express_exchange_status_canceled) + ExchangeStatus.Failed, + ExchangeStatus.TxFailed, + ExchangeStatus.Unknown, + -> resourceReference(R.string.express_exchange_status_failed) + null -> TextReference.EMPTY + } + private fun getIconState(status: ExchangeStatus?): ExpressTransactionStateIconUM { return when (status) { ExchangeStatus.Verifying -> ExpressTransactionStateIconUM.Warning diff --git a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/ui/components/express/ExpressStatusBottomSheetStateProvider.kt b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/ui/components/express/ExpressStatusBottomSheetStateProvider.kt index dfcff712d9..1a0a125cb4 100644 --- a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/ui/components/express/ExpressStatusBottomSheetStateProvider.kt +++ b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/ui/components/express/ExpressStatusBottomSheetStateProvider.kt @@ -42,6 +42,8 @@ class ExpressStatusBottomSheetStateProvider : PreviewParameterProvider analyticsEventHandler.send(TokenOnrampAnalyticsEvent.GoToProvider()) clickIntents.onGoToProviderClick(url) @@ -134,18 +140,6 @@ internal class SingleWalletOnrampTransactionConverter( null } - private fun getIconState(status: OnrampStatus.Status): ExpressTransactionStateIconUM { - return when (status) { - OnrampStatus.Status.Verifying, - OnrampStatus.Status.RefundInProgress, - -> ExpressTransactionStateIconUM.Warning - OnrampStatus.Status.Refunded, - OnrampStatus.Status.Failed, - -> ExpressTransactionStateIconUM.Error - else -> ExpressTransactionStateIconUM.None - } - } - private fun convertStatuses(status: OnrampStatus.Status, externalTxUrl: String?): ExpressStatusUM { val statuses = with(status) { persistentListOf(