Updated on 2026-08-14
This commit is contained in:
parent
08608fa610
commit
8511623421
19 changed files with 633 additions and 122 deletions
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
@ -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))
|
||||
}
|
||||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
@ -66,6 +66,10 @@ abstract class VerifyDesignTokensTask : DefaultTask() {
|
|||
}
|
||||
}
|
||||
|
||||
tasks.withType<Test>().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)
|
||||
}
|
||||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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),
|
||||
),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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),
|
||||
),
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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),
|
||||
),
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -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(
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -42,6 +42,8 @@ class ExpressStatusBottomSheetStateProvider : PreviewParameterProvider<ExpressSt
|
|||
txExternalUrl = "https://example.com/tx/78910",
|
||||
timestamp = System.currentTimeMillis(),
|
||||
timestampFormatted = TextReference.Str("Just now"),
|
||||
timestampAgoFormatted = TextReference.Str("1m ago"),
|
||||
activeStatus = TextReference.Str("Confirming"),
|
||||
onGoToProviderClick = {},
|
||||
onClick = {},
|
||||
onDisposeExpressStatus = {},
|
||||
|
|
|
|||
|
|
@ -1,6 +1,8 @@
|
|||
package com.tangem.feature.wallet.presentation.wallet.state.transformers.converter
|
||||
|
||||
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
|
||||
|
|
@ -36,6 +39,7 @@ internal class SingleWalletOnrampTransactionConverter(
|
|||
private val currency = cryptoCurrencyStatus.currency
|
||||
private val status = cryptoCurrencyStatus.value
|
||||
|
||||
@Suppress("LongMethod")
|
||||
override fun convert(value: OnrampTransaction): ExpressTransactionStateUM.OnrampUM {
|
||||
return ExpressTransactionStateUM.OnrampUM(
|
||||
info = ExpressTransactionStateInfoUM(
|
||||
|
|
@ -56,6 +60,8 @@ internal class SingleWalletOnrampTransactionConverter(
|
|||
value.timestamp.toTimeFormat(),
|
||||
),
|
||||
),
|
||||
timestampAgoFormatted = mapFormattedDate(value.timestamp),
|
||||
activeStatus = value.status.toActiveStatusText(currency.name),
|
||||
toAmount = stringReference(value.toAmount.format { crypto(currency) }),
|
||||
toFiatAmount = stringReference(
|
||||
status.fiatRate?.multiply(value.toAmount).format {
|
||||
|
|
@ -81,7 +87,7 @@ internal class SingleWalletOnrampTransactionConverter(
|
|||
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)
|
||||
|
|
@ -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(
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue