diff --git a/core/datasource/src/main/java/com/tangem/datasource/api/express/models/response/ExpressErrorResponse.kt b/core/datasource/src/main/java/com/tangem/datasource/api/express/models/response/ExpressErrorResponse.kt
index c0c39b0fb2..ea8a552360 100644
--- a/core/datasource/src/main/java/com/tangem/datasource/api/express/models/response/ExpressErrorResponse.kt
+++ b/core/datasource/src/main/java/com/tangem/datasource/api/express/models/response/ExpressErrorResponse.kt
@@ -21,7 +21,7 @@ data class ExpressError(
data class ExpressErrorValue(
@Json(name = "minAmount")
- val minAmount: BigDecimal?,
+ val minAmount: String?,
@Json(name = "decimals")
val decimals: Int?,
diff --git a/core/res/src/main/res/values-ru/strings.xml b/core/res/src/main/res/values-ru/strings.xml
index 194da3dfb2..8805ebe16d 100644
--- a/core/res/src/main/res/values-ru/strings.xml
+++ b/core/res/src/main/res/values-ru/strings.xml
@@ -201,6 +201,7 @@
Операция не выполнена провайдером
Посетите сайт провайдера для проверки
Провайдер: требуется верификация
+ Список токенов в вашем кошельке
Получение наилучших курсов...
Провайдер
Лучший курс
diff --git a/core/res/src/main/res/values/strings.xml b/core/res/src/main/res/values/strings.xml
index 8d4ff0b882..be887dc4ea 100644
--- a/core/res/src/main/res/values/strings.xml
+++ b/core/res/src/main/res/values/strings.xml
@@ -215,6 +215,7 @@
Exchange status
Verified
Verification required
+ List of all tokens added to your wallet
Fetching best rates...
Floating rate
Go to provider
diff --git a/core/ui/src/main/java/com/tangem/core/ui/components/MiddleEllipsisText.kt b/core/ui/src/main/java/com/tangem/core/ui/components/MiddleEllipsisText.kt
index 9bdff5f987..e67fbf3de2 100644
--- a/core/ui/src/main/java/com/tangem/core/ui/components/MiddleEllipsisText.kt
+++ b/core/ui/src/main/java/com/tangem/core/ui/components/MiddleEllipsisText.kt
@@ -6,7 +6,6 @@ import androidx.compose.runtime.Composable
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.ui.Modifier
-import androidx.compose.ui.geometry.Rect
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.layout.SubcomposeLayout
import androidx.compose.ui.text.TextLayoutResult
@@ -18,10 +17,12 @@ import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.text.style.TextDecoration
import androidx.compose.ui.unit.Constraints
import androidx.compose.ui.unit.TextUnit
+import com.tangem.core.ui.components.atoms.text.BoundCounter
/**
* https://stackoverflow.com/questions/69083061/how-to-make-middle-ellipsis-in-text-with-jetpack-compose
*/
+@Deprecated("Use EllipsisText with TextEllipsis.Middle ellipsis instead")
@Suppress("LongMethod")
@Composable
fun MiddleEllipsisText(
@@ -138,38 +139,4 @@ fun MiddleEllipsisText(
private const val ELLIPSIS_CHARACTERS_COUNT = 3
private const val ELLIPSIS_CHARACTER = '.'
-private val ellipsisText = List(ELLIPSIS_CHARACTERS_COUNT) { ELLIPSIS_CHARACTER }.joinToString(separator = "")
-
-private class BoundCounter(
- private val text: String,
- private val textLayoutResult: TextLayoutResult,
- private val charPosition: (Int) -> Int,
-) {
- var string = ""
- private set
- var width = 0f
- private set
-
- private var _nextCharWidth: Float? = null
- private var invalidCharsCount = 0
-
- fun widthWithNextChar(): Float = width + nextCharWidth()
-
- private fun nextCharWidth(): Float = _nextCharWidth ?: run {
- var boundingBox: Rect
- // invalidCharsCount fixes this bug: https://issuetracker.google.com/issues/197146630
- invalidCharsCount--
- do {
- boundingBox = textLayoutResult
- .getBoundingBox(charPosition(string.count() + ++invalidCharsCount))
- } while (boundingBox.right == 0f)
- _nextCharWidth = boundingBox.width
- boundingBox.width
- }
-
- fun addNextChar() {
- string += text[charPosition(string.count())]
- width += nextCharWidth()
- _nextCharWidth = null
- }
-}
\ No newline at end of file
+private val ellipsisText = List(ELLIPSIS_CHARACTERS_COUNT) { ELLIPSIS_CHARACTER }.joinToString(separator = "")
\ No newline at end of file
diff --git a/core/ui/src/main/java/com/tangem/core/ui/components/atoms/text/BoundCounter.kt b/core/ui/src/main/java/com/tangem/core/ui/components/atoms/text/BoundCounter.kt
new file mode 100644
index 0000000000..6a97038b3d
--- /dev/null
+++ b/core/ui/src/main/java/com/tangem/core/ui/components/atoms/text/BoundCounter.kt
@@ -0,0 +1,38 @@
+package com.tangem.core.ui.components.atoms.text
+
+import androidx.compose.ui.geometry.Rect
+import androidx.compose.ui.text.TextLayoutResult
+
+internal class BoundCounter(
+ private val text: String,
+ private val textLayoutResult: TextLayoutResult,
+ private val charPosition: (Int) -> Int,
+) {
+ var string = ""
+ private set
+ var width = 0f
+ private set
+
+ private var _nextCharWidth: Float? = null
+ private var invalidCharsCount = 0
+
+ fun widthWithNextChar(): Float = width + nextCharWidth()
+
+ private fun nextCharWidth(): Float = _nextCharWidth ?: run {
+ var boundingBox: Rect
+ // invalidCharsCount fixes this bug: https://issuetracker.google.com/issues/197146630
+ invalidCharsCount--
+ do {
+ boundingBox = textLayoutResult
+ .getBoundingBox(charPosition(string.count() + ++invalidCharsCount))
+ } while (boundingBox.right == 0f)
+ _nextCharWidth = boundingBox.width
+ boundingBox.width
+ }
+
+ fun addNextChar() {
+ string += text[charPosition(string.count())]
+ width += nextCharWidth()
+ _nextCharWidth = null
+ }
+}
\ No newline at end of file
diff --git a/core/ui/src/main/java/com/tangem/core/ui/components/atoms/text/EllipsisText.kt b/core/ui/src/main/java/com/tangem/core/ui/components/atoms/text/EllipsisText.kt
new file mode 100644
index 0000000000..d95b813ee1
--- /dev/null
+++ b/core/ui/src/main/java/com/tangem/core/ui/components/atoms/text/EllipsisText.kt
@@ -0,0 +1,242 @@
+package com.tangem.core.ui.components.atoms.text
+
+import androidx.compose.foundation.background
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.material.LocalTextStyle
+import androidx.compose.material.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.remember
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.layout.SubcomposeLayout
+import androidx.compose.ui.text.TextLayoutResult
+import androidx.compose.ui.text.TextStyle
+import androidx.compose.ui.text.font.FontFamily
+import androidx.compose.ui.text.font.FontStyle
+import androidx.compose.ui.text.font.FontWeight
+import androidx.compose.ui.text.style.TextAlign
+import androidx.compose.ui.text.style.TextDecoration
+import androidx.compose.ui.tooling.preview.Preview
+import androidx.compose.ui.tooling.preview.PreviewParameter
+import androidx.compose.ui.tooling.preview.PreviewParameterProvider
+import androidx.compose.ui.unit.Constraints
+import androidx.compose.ui.unit.TextUnit
+import com.tangem.core.ui.res.TangemTheme
+
+sealed class TextEllipsis {
+
+ object Middle : TextEllipsis()
+
+ object End : TextEllipsis()
+
+ data class OffsetEnd(
+ val offsetEnd: Int = 0,
+ val hasSeparator: Boolean = true,
+ ) : TextEllipsis()
+}
+
+/**
+ * https://stackoverflow.com/questions/69083061/how-to-make-middle-ellipsis-in-text-with-jetpack-compose
+ *
+ * Customized Text with ellipsis. Ellipsis can be placed in: Middle, End or OffsetEnd (OffsetEnd with separator).
+ *
+ * * OffsetEnd can be useful to display big amounts with currency symbol. OffsetEnd 0 is equal to End.
+ */
+@Suppress("LongMethod")
+@Composable
+fun EllipsisText(
+ text: String,
+ modifier: Modifier = Modifier,
+ color: Color = Color.Unspecified,
+ fontSize: TextUnit = TextUnit.Unspecified,
+ fontStyle: FontStyle? = null,
+ fontWeight: FontWeight? = null,
+ fontFamily: FontFamily? = null,
+ letterSpacing: TextUnit = TextUnit.Unspecified,
+ textDecoration: TextDecoration? = null,
+ textAlign: TextAlign? = null,
+ lineHeight: TextUnit = TextUnit.Unspecified,
+ softWrap: Boolean = true,
+ onTextLayout: (TextLayoutResult) -> Unit = {},
+ style: TextStyle = LocalTextStyle.current,
+ ellipsis: TextEllipsis = TextEllipsis.End,
+) {
+ val ellipsisText = remember(text) {
+ if (ellipsis is TextEllipsis.OffsetEnd && ellipsis.hasSeparator) {
+ ELLIPSIS_TEXT_WITH_SEPARATOR
+ } else {
+ ELLIPSIS_TEXT
+ }
+ }
+
+ // some letters, like "r", will have less width when placed right before "."
+ // adding a space to prevent such case
+ val layoutText = remember(text) { "$text $ellipsisText" }
+ val textLayoutResultState = remember(layoutText) {
+ mutableStateOf(null)
+ }
+ SubcomposeLayout(modifier) { constraints ->
+ // result is ignored - we only need to fill our textLayoutResult
+ subcompose("measure") {
+ Text(
+ text = layoutText,
+ color = color,
+ fontSize = fontSize,
+ fontStyle = fontStyle,
+ fontWeight = fontWeight,
+ fontFamily = fontFamily,
+ letterSpacing = letterSpacing,
+ textDecoration = textDecoration,
+ textAlign = textAlign,
+ lineHeight = lineHeight,
+ softWrap = softWrap,
+ maxLines = 1,
+ onTextLayout = { textLayoutResultState.value = it },
+ style = style,
+ )
+ }.first().measure(Constraints())
+ // to allow smart cast
+ val textLayoutResult = textLayoutResultState.value
+ ?: // shouldn't happen - onTextLayout is called before subcompose finishes
+ return@SubcomposeLayout layout(0, 0) {}
+ val placeable = subcompose("visible") {
+ val finalText = remember(text, textLayoutResult, constraints.maxWidth) {
+ if (
+ text.isEmpty() ||
+ textLayoutResult.getBoundingBox(text.indices.last).right <= constraints.maxWidth
+ ) {
+ // text not including ellipsis fits on the first line.
+ return@remember text
+ }
+
+ var ellipsisWidth = 0f
+ layoutText.indices.toList()
+ .takeLast(ellipsisText.length)
+ .forEach widthLet@{
+ ellipsisWidth += textLayoutResult.getBoundingBox(it).width
+ }
+
+ val availableWidth = constraints.maxWidth - ellipsisWidth
+ val startCounter = BoundCounter(text, textLayoutResult) { it }
+ val endCounter = BoundCounter(text, textLayoutResult) { text.indices.last - it }
+
+ when (ellipsis) {
+ TextEllipsis.Middle -> {
+ middleEllipsisText(
+ availableWidth,
+ startCounter,
+ endCounter,
+ )
+ }
+ TextEllipsis.End -> {
+ offsetEndEllipsisText(
+ availableWidth = availableWidth,
+ startCounter = startCounter,
+ endCounter = endCounter,
+ )
+ }
+ is TextEllipsis.OffsetEnd -> {
+ offsetEndEllipsisText(
+ availableWidth = availableWidth,
+ startCounter = startCounter,
+ endCounter = endCounter,
+ offsetEnd = ellipsis.offsetEnd,
+ withSeparator = ellipsis.hasSeparator,
+ )
+ }
+ }
+ }
+ Text(
+ text = finalText,
+ color = color,
+ fontSize = fontSize,
+ fontStyle = fontStyle,
+ fontWeight = fontWeight,
+ fontFamily = fontFamily,
+ letterSpacing = letterSpacing,
+ textDecoration = textDecoration,
+ textAlign = textAlign,
+ lineHeight = lineHeight,
+ softWrap = softWrap,
+ onTextLayout = onTextLayout,
+ style = style,
+ )
+ }[0].measure(constraints)
+ layout(placeable.width, placeable.height) {
+ placeable.place(0, 0)
+ }
+ }
+}
+
+private const val ELLIPSIS_SEPARATOR = " "
+private const val ELLIPSIS_TEXT = "..."
+private const val ELLIPSIS_TEXT_WITH_SEPARATOR = ELLIPSIS_TEXT.plus(ELLIPSIS_SEPARATOR)
+
+private fun middleEllipsisText(availableWidth: Float, startCounter: BoundCounter, endCounter: BoundCounter): String {
+ while (availableWidth - startCounter.width - endCounter.width > 0) {
+ val possibleEndWidth = endCounter.widthWithNextChar()
+ if (
+ startCounter.width >= possibleEndWidth &&
+ availableWidth - startCounter.width - possibleEndWidth >= 0
+ ) {
+ endCounter.addNextChar()
+ } else if (availableWidth - startCounter.widthWithNextChar() - endCounter.width >= 0) {
+ startCounter.addNextChar()
+ } else {
+ break
+ }
+ }
+ return startCounter.string.trimEnd() + ELLIPSIS_TEXT + endCounter.string.reversed().trimStart()
+}
+
+private fun offsetEndEllipsisText(
+ availableWidth: Float,
+ startCounter: BoundCounter,
+ endCounter: BoundCounter,
+ offsetEnd: Int = 0,
+ withSeparator: Boolean = false,
+): String {
+ while (availableWidth - startCounter.width - endCounter.width > 0) {
+ val possibleEndWidth = endCounter.widthWithNextChar()
+ if (
+ offsetEnd > endCounter.string.length &&
+ availableWidth - startCounter.width - possibleEndWidth >= 0
+ ) {
+ endCounter.addNextChar()
+ } else if (availableWidth - startCounter.widthWithNextChar() - endCounter.width >= 0) {
+ startCounter.addNextChar()
+ } else {
+ break
+ }
+ }
+ val ellipsis = if (withSeparator) ELLIPSIS_TEXT_WITH_SEPARATOR else ELLIPSIS_TEXT
+
+ return startCounter.string.trimEnd() + ellipsis + endCounter.string.reversed().trimStart()
+}
+
+//region Preview
+@Preview(widthDp = 200)
+@Composable
+private fun EllipsisTexPreview(@PreviewParameter(EllipsisTexPreviewParameterProvider::class) ellipsis: TextEllipsis) {
+ TangemTheme {
+ EllipsisText(
+ text = "11111111111111111111111111111111111111111111111111 END",
+ ellipsis = ellipsis,
+ modifier = Modifier
+ .background(TangemTheme.colors.background.primary)
+ .fillMaxWidth(),
+ )
+ }
+}
+
+private class EllipsisTexPreviewParameterProvider : PreviewParameterProvider {
+ override val values: Sequence
+ get() = sequenceOf(
+ TextEllipsis.Middle,
+ TextEllipsis.End,
+ TextEllipsis.OffsetEnd("TEXT".length),
+ TextEllipsis.OffsetEnd("TEXT".length, false),
+ )
+}
+//endregion
\ No newline at end of file
diff --git a/core/ui/src/main/java/com/tangem/core/ui/components/inputrow/InputRowApprox.kt b/core/ui/src/main/java/com/tangem/core/ui/components/inputrow/InputRowApprox.kt
index 3b085833a3..bd69b6feb9 100644
--- a/core/ui/src/main/java/com/tangem/core/ui/components/inputrow/InputRowApprox.kt
+++ b/core/ui/src/main/java/com/tangem/core/ui/components/inputrow/InputRowApprox.kt
@@ -3,13 +3,13 @@ package com.tangem.core.ui.components.inputrow
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.*
import androidx.compose.material3.Icon
-import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.tooling.preview.Preview
import com.tangem.core.ui.R
+import com.tangem.core.ui.components.atoms.text.EllipsisText
import com.tangem.core.ui.components.currency.tokenicon.TokenIcon
import com.tangem.core.ui.components.currency.tokenicon.TokenIconState
import com.tangem.core.ui.components.inputrow.inner.DividerContainer
@@ -56,6 +56,7 @@ fun InputRowApprox(
iconState = leftIcon,
title = leftTitle,
subtitle = leftSubtitle,
+ modifier = Modifier.weight(1f),
)
Icon(
painter = painterResource(id = R.drawable.ic_approx_24),
@@ -71,6 +72,7 @@ fun InputRowApprox(
iconState = rightIcon,
title = rightTitle,
subtitle = rightSubtitle,
+ modifier = Modifier.weight(1f),
)
}
}
@@ -97,12 +99,12 @@ private fun InputRowApproxItem(
start = TangemTheme.dimens.spacing12,
),
) {
- Text(
+ EllipsisText(
text = title.resolveReference(),
style = TangemTheme.typography.body2,
color = TangemTheme.colors.text.primary1,
)
- Text(
+ EllipsisText(
text = subtitle.resolveReference(),
style = TangemTheme.typography.caption2,
color = TangemTheme.colors.text.tertiary,
@@ -123,7 +125,7 @@ private fun InputRowApproxPreview_Light() {
leftTitle = TextReference.Str("Left title"),
leftSubtitle = TextReference.Str("Left subtitle"),
rightIcon = TokenIconState.Loading,
- rightTitle = TextReference.Str("Right title"),
+ rightTitle = TextReference.Str("Right title Right title Right title Right title Right title"),
rightSubtitle = TextReference.Str("Right subtitle"),
modifier = Modifier
.background(TangemTheme.colors.background.action),
@@ -137,7 +139,7 @@ private fun InputRowApproxPreview_Dark() {
TangemTheme(isDark = true) {
InputRowApprox(
leftIcon = TokenIconState.Loading,
- leftTitle = TextReference.Str("Left title"),
+ leftTitle = TextReference.Str("Left title Left title Left title Left title Left title"),
leftSubtitle = TextReference.Str("Left subtitle"),
rightIcon = TokenIconState.Loading,
rightTitle = TextReference.Str("Right title"),
diff --git a/domain/tokens/src/test/kotlin/com/tangem/domain/tokens/repository/MockQuotesRepository.kt b/domain/tokens/src/test/kotlin/com/tangem/domain/tokens/repository/MockQuotesRepository.kt
index 6d1c1e1a27..ae960f191a 100644
--- a/domain/tokens/src/test/kotlin/com/tangem/domain/tokens/repository/MockQuotesRepository.kt
+++ b/domain/tokens/src/test/kotlin/com/tangem/domain/tokens/repository/MockQuotesRepository.kt
@@ -20,4 +20,9 @@ internal class MockQuotesRepository(
override suspend fun getQuotesSync(currenciesIds: Set, refresh: Boolean): Set {
return getQuotesUpdates(currenciesIds).first()
}
+
+ override suspend fun getQuoteSync(currencyId: CryptoCurrency.ID): Quote {
+ return quotes.map { it.getOrElse { e -> throw e } }.first()
+ .first { it.rawCurrencyId == currencyId.rawCurrencyId }
+ }
}
\ No newline at end of file
diff --git a/features/swap/data/src/main/java/com/tangem/feature/swap/converters/ErrorsDataConverter.kt b/features/swap/data/src/main/java/com/tangem/feature/swap/converters/ErrorsDataConverter.kt
index 5330219b1e..2068fc982a 100644
--- a/features/swap/data/src/main/java/com/tangem/feature/swap/converters/ErrorsDataConverter.kt
+++ b/features/swap/data/src/main/java/com/tangem/feature/swap/converters/ErrorsDataConverter.kt
@@ -3,7 +3,7 @@ package com.tangem.feature.swap.converters
import com.squareup.moshi.JsonAdapter
import com.tangem.datasource.api.express.models.response.ExpressErrorResponse
import com.tangem.feature.swap.domain.models.DataError
-import com.tangem.feature.swap.domain.models.SwapAmount
+import com.tangem.feature.swap.domain.models.createFromAmountWithOffset
import com.tangem.utils.converter.Converter
internal class ErrorsDataConverter(
@@ -23,7 +23,7 @@ internal class ErrorsDataConverter(
2240 -> DataError.ExchangeNotPossibleError(code = error.code)
2250 -> DataError.ExchangeTooSmallAmountError(
code = error.code,
- amount = SwapAmount(
+ amount = createFromAmountWithOffset(
requireNotNull(error.value?.minAmount),
requireNotNull(error.value?.decimals),
),
diff --git a/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/SwapInteractorImpl.kt b/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/SwapInteractorImpl.kt
index c219a29944..8669d31ece 100644
--- a/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/SwapInteractorImpl.kt
+++ b/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/SwapInteractorImpl.kt
@@ -716,7 +716,14 @@ internal class SwapInteractorImpl @Inject constructor(
}
}
} else {
- return SwapState.SwapError(quoteDataModel.error)
+ val rates = getQuotes(fromToken.currency.id)
+ val fromTokenSwapInfo = TokenSwapInfo(
+ tokenAmount = amount,
+ amountFiat = rates[fromToken.currency.id]?.fiatRate?.multiply(amount.value)
+ ?: BigDecimal.ZERO,
+ cryptoCurrencyStatus = fromToken,
+ )
+ return SwapState.SwapError(fromTokenSwapInfo, quoteDataModel.error)
}
}
@@ -796,7 +803,17 @@ internal class SwapInteractorImpl @Inject constructor(
),
)
} else {
- return SwapState.SwapError(it.error)
+ val rates = getQuotes(fromToken.currency.id)
+ val fromTokenSwapInfo = TokenSwapInfo(
+ tokenAmount = amount,
+ amountFiat = rates[fromToken.currency.id]?.fiatRate?.multiply(amount.value)
+ ?: BigDecimal.ZERO,
+ cryptoCurrencyStatus = fromToken,
+ )
+ return SwapState.SwapError(
+ fromTokenSwapInfo,
+ it.error,
+ )
}
}
}
diff --git a/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/models/ui/SwapState.kt b/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/models/ui/SwapState.kt
index 0c98986dc1..b49765d79d 100644
--- a/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/models/ui/SwapState.kt
+++ b/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/models/ui/SwapState.kt
@@ -31,7 +31,10 @@ sealed interface SwapState {
val zeroAmountEquivalent: String,
) : SwapState
- data class SwapError(val error: DataError) : SwapState
+ data class SwapError(
+ val fromTokenInfo: TokenSwapInfo,
+ val error: DataError,
+ ) : SwapState
}
sealed class PermissionDataState {
diff --git a/features/swap/presentation/src/main/java/com/tangem/feature/swap/models/SwapStateHolder.kt b/features/swap/presentation/src/main/java/com/tangem/feature/swap/models/SwapStateHolder.kt
index 37ef409ce5..9fc0d8a9c1 100644
--- a/features/swap/presentation/src/main/java/com/tangem/feature/swap/models/SwapStateHolder.kt
+++ b/features/swap/presentation/src/main/java/com/tangem/feature/swap/models/SwapStateHolder.kt
@@ -123,6 +123,7 @@ sealed interface SwapWarning {
data class HighPriceImpact(val priceImpact: Int, val notificationConfig: NotificationConfig) : SwapWarning
data class TooSmallAmountWarning(val notificationConfig: NotificationConfig) : SwapWarning
data class UnableToCoverFeeWarning(val notificationConfig: NotificationConfig) : SwapWarning
+ data class GeneralWarning(val notificationConfig: NotificationConfig) : SwapWarning
}
enum class GenericWarningType {
diff --git a/features/swap/presentation/src/main/java/com/tangem/feature/swap/ui/StateBuilder.kt b/features/swap/presentation/src/main/java/com/tangem/feature/swap/ui/StateBuilder.kt
index 3d230f08a8..a13a37110c 100644
--- a/features/swap/presentation/src/main/java/com/tangem/feature/swap/ui/StateBuilder.kt
+++ b/features/swap/presentation/src/main/java/com/tangem/feature/swap/ui/StateBuilder.kt
@@ -13,6 +13,7 @@ import com.tangem.domain.tokens.model.CryptoCurrency
import com.tangem.domain.tokens.model.CryptoCurrencyStatus
import com.tangem.feature.swap.converters.TokensDataConverter
import com.tangem.feature.swap.domain.models.DataError
+import com.tangem.feature.swap.domain.models.SwapAmount
import com.tangem.feature.swap.domain.models.domain.NetworkInfo
import com.tangem.feature.swap.domain.models.domain.SwapProvider
import com.tangem.feature.swap.domain.models.formatToUIRepresentation
@@ -24,6 +25,7 @@ import kotlinx.collections.immutable.ImmutableList
import kotlinx.collections.immutable.toImmutableList
import java.math.BigDecimal
import java.math.RoundingMode
+import kotlin.math.min
/**
* State builder creates a specific states for SwapScreen
@@ -175,6 +177,7 @@ internal class StateBuilder(
balance = if (!canSelectReceiveToken) uiStateHolder.receiveCardData.balance else "",
isBalanceHidden = isBalanceHiddenProvider(),
),
+ warnings = emptyList(),
fee = FeeItemState.Empty,
swapButton = SwapButton(enabled = false, loading = true, onClick = {}),
providerState = ProviderState.Loading(),
@@ -293,6 +296,109 @@ internal class StateBuilder(
)
}
+ fun createQuotesErrorState(
+ uiStateHolder: SwapStateHolder,
+ swapProvider: SwapProvider,
+ fromToken: TokenSwapInfo,
+ toToken: CryptoCurrencyStatus?,
+ dataError: DataError,
+ ): SwapStateHolder {
+ if (uiStateHolder.sendCardData !is SwapCardState.SwapCardData) return uiStateHolder
+ if (uiStateHolder.receiveCardData !is SwapCardState.SwapCardData) return uiStateHolder
+ val warning = getWarningForError(dataError, fromToken.cryptoCurrencyStatus.currency)
+ val providerState = getProviderStateForError(
+ swapProvider = swapProvider,
+ fromToken = fromToken.cryptoCurrencyStatus.currency,
+ dataError = dataError,
+ selectionType = ProviderState.SelectionType.CLICK,
+ )
+ val receiveCardData = toToken?.let {
+ SwapCardState.SwapCardData(
+ type = TransactionCardType.ReceiveCard(),
+ amountTextFieldValue = TextFieldValue(
+ text = "0",
+ ),
+ amountEquivalent = "0 ${appCurrencyProvider.invoke().symbol}",
+ token = toToken,
+ tokenIconUrl = uiStateHolder.receiveCardData.tokenIconUrl,
+ coinId = toToken.currency.network.backendId,
+ isNotNativeToken = uiStateHolder.receiveCardData.isNotNativeToken,
+ tokenCurrency = uiStateHolder.receiveCardData.tokenCurrency,
+ canSelectAnotherToken = uiStateHolder.receiveCardData.canSelectAnotherToken,
+ balance = toToken.getFormattedAmount(),
+ isBalanceHidden = isBalanceHiddenProvider(),
+ )
+ } ?: SwapCardState.Empty(
+ type = TransactionCardType.ReceiveCard(),
+ amountEquivalent = "0 ${appCurrencyProvider.invoke().symbol}",
+ amountTextFieldValue = TextFieldValue(
+ text = "0",
+ ),
+ canSelectAnotherToken = true,
+ )
+ return uiStateHolder.copy(
+ sendCardData = uiStateHolder.sendCardData.copy(
+ amountEquivalent = getFormattedFiatAmount(fromToken.amountFiat),
+ ),
+ receiveCardData = receiveCardData,
+ warnings = listOf(warning),
+ permissionState = SwapPermissionState.Empty,
+ fee = FeeItemState.Empty,
+ swapButton = SwapButton(
+ enabled = false,
+ loading = false,
+ onClick = actions.onSwapClick,
+ ),
+ updateInProgress = false,
+ providerState = providerState,
+ )
+ }
+
+ private fun getProviderStateForError(
+ swapProvider: SwapProvider,
+ fromToken: CryptoCurrency,
+ dataError: DataError,
+ selectionType: ProviderState.SelectionType,
+ ): ProviderState {
+ return when (dataError) {
+ is DataError.ExchangeTooSmallAmountError -> {
+ swapProvider.convertToUnavailableProviderState(
+ alertText = resourceReference(
+ R.string.express_provider_min_amount,
+ wrappedList(dataError.amount.getFormattedCryptoAmount(fromToken)),
+ ),
+ selectionType = selectionType,
+ onProviderClick = actions.onProviderClick,
+ )
+ }
+ else -> {
+ ProviderState.Empty()
+ }
+ }
+ }
+
+ private fun getWarningForError(dataError: DataError, fromToken: CryptoCurrency): SwapWarning {
+ return when (dataError) {
+ is DataError.ExchangeTooSmallAmountError -> SwapWarning.TooSmallAmountWarning(
+ notificationConfig = NotificationConfig(
+ title = resourceReference(
+ id = R.string.warning_express_too_minimal_amount_title,
+ formatArgs = wrappedList(dataError.amount.getFormattedCryptoAmount(fromToken)),
+ ),
+ subtitle = resourceReference(R.string.warning_express_too_minimal_amount_description),
+ iconResId = R.drawable.ic_alert_circle_24,
+ ),
+ )
+ else -> SwapWarning.GeneralWarning(
+ notificationConfig = NotificationConfig(
+ title = resourceReference(R.string.common_error),
+ subtitle = resourceReference(R.string.generic_error_code, wrappedList(dataError.code.toString())),
+ iconResId = R.drawable.ic_alert_circle_24,
+ ),
+ )
+ }
+ }
+
fun createQuotesEmptyAmountState(
uiStateHolder: SwapStateHolder,
emptyAmountState: SwapState.EmptyAmountState,
@@ -507,16 +613,6 @@ internal class StateBuilder(
)
}
- fun mapError(uiState: SwapStateHolder, error: DataError, onClick: () -> Unit): SwapStateHolder {
- return when (error) {
- // todo use if needed later
- // DataError.InsufficientLiquidity -> TODO()
- // DataError.NoError -> TODO()
- is DataError.ExchangeTooSmallAmountError -> addWarning(uiState, error.amount.toString(), true, onClick)
- else -> addWarning(uiState, null, false) {}
- }
- }
-
fun addAlert(uiState: SwapStateHolder, onClick: () -> Unit): SwapStateHolder {
return uiState.copy(
alert = SwapWarning.GenericWarning(
@@ -735,11 +831,11 @@ internal class StateBuilder(
onProviderClick = onProviderSelect,
selectionType = ProviderState.SelectionType.SELECT,
)
- // todo handle error
- is SwapState.SwapError -> provider.convertToUnavailableProviderState(
- alertText = resourceReference(R.string.express_provider_min_amount, wrappedList("10")),
- selectionType = ProviderState.SelectionType.NONE,
- onProviderClick = onProviderSelect,
+ is SwapState.SwapError -> getProviderStateForError(
+ swapProvider = provider,
+ fromToken = state.fromTokenInfo.cryptoCurrencyStatus.currency,
+ dataError = state.error,
+ selectionType = ProviderState.SelectionType.SELECT,
)
}
}
@@ -802,10 +898,9 @@ internal class StateBuilder(
selectionType: ProviderState.SelectionType,
onProviderClick: (String) -> Unit,
): ProviderState {
- val rate = toTokenInfo.tokenAmount.value.divide(
+ val rate = toTokenInfo.tokenAmount.value.calculateRate(
fromTokenInfo.tokenAmount.value,
toTokenInfo.cryptoCurrencyStatus.currency.decimals,
- RoundingMode.HALF_UP,
)
val fromCurrencySymbol = fromTokenInfo.cryptoCurrencyStatus.currency.symbol
val toCurrencySymbol = toTokenInfo.cryptoCurrencyStatus.currency.symbol
@@ -831,10 +926,9 @@ internal class StateBuilder(
): ProviderState {
val fromTokenInfo = state.fromTokenInfo
val toTokenInfo = state.toTokenInfo
- val rate = toTokenInfo.tokenAmount.value.divide(
+ val rate = toTokenInfo.tokenAmount.value.calculateRate(
fromTokenInfo.tokenAmount.value,
toTokenInfo.cryptoCurrencyStatus.currency.decimals,
- RoundingMode.HALF_UP,
)
val fromCurrencySymbol = fromTokenInfo.cryptoCurrencyStatus.currency.symbol
val toCurrencySymbol = toTokenInfo.cryptoCurrencyStatus.currency.symbol
@@ -895,6 +989,14 @@ internal class StateBuilder(
return BigDecimalFormatter.formatFiatAmount(amount, appCurrency.code, appCurrency.symbol)
}
+ private fun SwapAmount.getFormattedCryptoAmount(token: CryptoCurrency): String {
+ return "${this.formatToUIRepresentation()} ${token.network.currencySymbol}"
+ }
+
+ private fun BigDecimal.calculateRate(to: BigDecimal, decimals: Int): BigDecimal {
+ return this.divide(to, min(decimals, MAX_DECIMALS_TO_SHOW), RoundingMode.HALF_UP)
+ }
+
private companion object {
const val ADDRESS_MIN_LENGTH = 11
const val ADDRESS_FIRST_PART_LENGTH = 7
@@ -902,5 +1004,6 @@ internal class StateBuilder(
private const val PRICE_IMPACT_THRESHOLD = 0.1
private const val HUNDRED_PERCENTS = 100
private const val UNKNOWN_AMOUNT_SIGN = "—"
+ private const val MAX_DECIMALS_TO_SHOW = 8
}
}
\ No newline at end of file
diff --git a/features/swap/presentation/src/main/java/com/tangem/feature/swap/ui/SwapSelectTokenScreen.kt b/features/swap/presentation/src/main/java/com/tangem/feature/swap/ui/SwapSelectTokenScreen.kt
index 6e89f78c3a..cde932bf0d 100644
--- a/features/swap/presentation/src/main/java/com/tangem/feature/swap/ui/SwapSelectTokenScreen.kt
+++ b/features/swap/presentation/src/main/java/com/tangem/feature/swap/ui/SwapSelectTokenScreen.kt
@@ -43,7 +43,7 @@ fun SwapSelectTokenScreen(state: SwapSelectTokenStateHolder, onBack: () -> Unit)
placeholderSearchText = stringResource(id = R.string.common_search_tokens),
onSearchChange = state.onSearchEntered,
onSearchDisplayClose = { state.onSearchEntered("") },
- subtitle = "", // todo add title
+ subtitle = stringResource(id = R.string.express_exchange_token_list_subtitle),
)
},
)
@@ -55,7 +55,7 @@ private fun ListOfTokens(state: SwapSelectTokenStateHolder, modifier: Modifier =
LazyColumn(
modifier = modifier
.background(color = screenBackgroundColor)
- .fillMaxWidth(),
+ .fillMaxSize(),
horizontalAlignment = Alignment.CenterHorizontally,
) {
item { SpacerH8() }
diff --git a/features/swap/presentation/src/main/java/com/tangem/feature/swap/viewmodels/SwapViewModel.kt b/features/swap/presentation/src/main/java/com/tangem/feature/swap/viewmodels/SwapViewModel.kt
index 9c25f78b14..d1dcdef1c0 100644
--- a/features/swap/presentation/src/main/java/com/tangem/feature/swap/viewmodels/SwapViewModel.kt
+++ b/features/swap/presentation/src/main/java/com/tangem/feature/swap/viewmodels/SwapViewModel.kt
@@ -286,9 +286,13 @@ internal class SwapViewModel @Inject constructor(
)
}
is SwapState.SwapError -> {
- Timber.e("SwapError when loading quotes ${state.error}")
- // todo handle when change token and error
- uiState = stateBuilder.mapError(uiState, state.error) { startLoadingQuotesFromLastState() }
+ uiState = stateBuilder.createQuotesErrorState(
+ uiStateHolder = uiState,
+ swapProvider = provider,
+ fromToken = state.fromTokenInfo,
+ toToken = dataState.toCryptoCurrency,
+ dataError = state.error,
+ )
}
}
}
diff --git a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/TokenDetailsPreviewData.kt b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/TokenDetailsPreviewData.kt
index 2a25900715..458e547586 100644
--- a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/TokenDetailsPreviewData.kt
+++ b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/TokenDetailsPreviewData.kt
@@ -101,6 +101,7 @@ internal object TokenDetailsPreviewData {
),
dialogConfig = null,
pendingTxs = persistentListOf(),
+ swapTxs = persistentListOf(),
pullToRefreshConfig = pullToRefreshConfig,
bottomSheetConfig = null,
isBalanceHidden = false,
diff --git a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/SwapTransactionsState.kt b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/SwapTransactionsState.kt
index 8b4fb5c8a5..9941c1bbda 100644
--- a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/SwapTransactionsState.kt
+++ b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/SwapTransactionsState.kt
@@ -2,21 +2,25 @@ package com.tangem.feature.tokendetails.presentation.tokendetails.state
import com.tangem.core.ui.components.currency.tokenicon.TokenIconState
import com.tangem.feature.swap.domain.models.domain.ExchangeStatus
+import com.tangem.feature.swap.domain.models.domain.SwapProvider
+import com.tangem.feature.tokendetails.presentation.tokendetails.state.components.ExchangeStatusNotifications
import kotlinx.collections.immutable.PersistentList
-import java.math.BigDecimal
internal data class SwapTransactionsState(
val txId: String,
- val providerId: Int,
+ val provider: SwapProvider,
val txUrl: String? = null,
- val rate: BigDecimal,
val timestamp: Long,
- val status: PersistentList,
+ val statuses: PersistentList,
val activeStatus: ExchangeStatus?,
+ val fiatSymbol: String,
+ val notification: ExchangeStatusNotifications? = null,
val toCryptoAmount: String,
+ val toCryptoSymbol: String,
val toFiatAmount: String,
val toCurrencyIcon: TokenIconState,
val fromCryptoAmount: String,
+ val fromCryptoSymbol: String,
val fromFiatAmount: String,
val fromCurrencyIcon: TokenIconState,
val onClick: () -> Unit,
@@ -25,7 +29,6 @@ internal data class SwapTransactionsState(
internal class ExchangeStatusState(
val status: ExchangeStatus,
- val text: String,
val isActive: Boolean,
val isDone: Boolean,
)
\ No newline at end of file
diff --git a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/TokenDetailsState.kt b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/TokenDetailsState.kt
index 15f290adde..f9f5dfcabd 100644
--- a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/TokenDetailsState.kt
+++ b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/TokenDetailsState.kt
@@ -19,6 +19,7 @@ internal data class TokenDetailsState(
val marketPriceBlockState: MarketPriceBlockState,
val notifications: ImmutableList,
val pendingTxs: PersistentList,
+ val swapTxs: PersistentList,
val txHistoryState: TxHistoryState,
val dialogConfig: TokenDetailsDialogConfig?,
val pullToRefreshConfig: TokenDetailsPullToRefreshConfig,
diff --git a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/components/ExchangeStatusNotifications.kt b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/components/ExchangeStatusNotifications.kt
new file mode 100644
index 0000000000..1da8fd0672
--- /dev/null
+++ b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/components/ExchangeStatusNotifications.kt
@@ -0,0 +1,38 @@
+package com.tangem.feature.tokendetails.presentation.tokendetails.state.components
+
+import androidx.compose.runtime.Immutable
+import com.tangem.core.ui.components.notifications.NotificationConfig
+import com.tangem.core.ui.extensions.TextReference
+import com.tangem.features.tokendetails.impl.R
+
+@Immutable
+internal sealed class ExchangeStatusNotifications(val config: NotificationConfig) {
+
+ data class NeedVerification(
+ val onGoToProviderClick: () -> Unit,
+ ) : ExchangeStatusNotifications(
+ config = NotificationConfig(
+ title = TextReference.Res(R.string.express_exchange_notification_verification_title),
+ subtitle = TextReference.Res(R.string.express_exchange_notification_verification_text),
+ iconResId = R.drawable.ic_alert_triangle_20,
+ buttonsState = NotificationConfig.ButtonsState.SecondaryButtonConfig(
+ text = TextReference.Res(R.string.express_go_to_provider),
+ onClick = onGoToProviderClick,
+ ),
+ ),
+ )
+
+ data class Failed(
+ val onGoToProviderClick: () -> Unit,
+ ) : ExchangeStatusNotifications(
+ config = NotificationConfig(
+ title = TextReference.Res(R.string.express_exchange_notification_failed_title),
+ subtitle = TextReference.Res(R.string.express_exchange_notification_failed_text),
+ iconResId = R.drawable.ic_alert_circle_24,
+ buttonsState = NotificationConfig.ButtonsState.SecondaryButtonConfig(
+ text = TextReference.Res(R.string.express_go_to_provider),
+ onClick = onGoToProviderClick,
+ ),
+ ),
+ )
+}
\ No newline at end of file
diff --git a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/factory/TokenDetailsSkeletonStateConverter.kt b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/factory/TokenDetailsSkeletonStateConverter.kt
index e92142e90c..30d410450e 100644
--- a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/factory/TokenDetailsSkeletonStateConverter.kt
+++ b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/factory/TokenDetailsSkeletonStateConverter.kt
@@ -45,6 +45,7 @@ internal class TokenDetailsSkeletonStateConverter(
marketPriceBlockState = MarketPriceBlockState.Loading(value.symbol),
notifications = persistentListOf(),
pendingTxs = persistentListOf(),
+ swapTxs = persistentListOf(),
txHistoryState = TxHistoryState.Content(
contentItems = MutableStateFlow(
value = TxHistoryState.getDefaultLoadingTransactions(clickIntents::onExploreClick),
diff --git a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/ui/TokenDetailsScreen.kt b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/ui/TokenDetailsScreen.kt
index 9b4e73ef9a..57f5e40688 100644
--- a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/ui/TokenDetailsScreen.kt
+++ b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/ui/TokenDetailsScreen.kt
@@ -2,7 +2,9 @@ package com.tangem.feature.tokendetails.presentation.tokendetails.ui
import androidx.activity.compose.BackHandler
import androidx.compose.foundation.ExperimentalFoundationApi
-import androidx.compose.foundation.layout.*
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items
import androidx.compose.material.ExperimentalMaterialApi
@@ -40,6 +42,9 @@ import com.tangem.feature.tokendetails.presentation.tokendetails.ui.components.T
import com.tangem.feature.tokendetails.presentation.tokendetails.ui.components.TokenDetailsDialogs
import com.tangem.feature.tokendetails.presentation.tokendetails.ui.components.TokenDetailsTopAppBar
import com.tangem.feature.tokendetails.presentation.tokendetails.ui.components.TokenInfoBlock
+import com.tangem.feature.tokendetails.presentation.tokendetails.ui.components.exchange.ExchangeStatusBottomSheet
+import com.tangem.feature.tokendetails.presentation.tokendetails.ui.components.exchange.ExchangeStatusBottomSheetConfig
+import com.tangem.feature.tokendetails.presentation.tokendetails.ui.components.exchange.swapTransactionsItems
// TODO: Split to blocks [REDACTED_JIRA]
@Suppress("LongMethod")
@@ -117,6 +122,11 @@ internal fun TokenDetailsScreen(state: TokenDetailsState) {
)
}
+ swapTransactionsItems(
+ state.swapTxs,
+ itemModifier,
+ )
+
txHistoryItems(
state = state.txHistoryState,
isBalanceHidden = state.isBalanceHidden,
@@ -141,6 +151,9 @@ internal fun TokenDetailsScreen(state: TokenDetailsState) {
is ChooseAddressBottomSheetConfig -> {
ChooseAddressBottomSheet(config = config)
}
+ is ExchangeStatusBottomSheetConfig -> {
+ ExchangeStatusBottomSheet(config = config)
+ }
}
}
diff --git a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/ui/components/exchange/ExchangeStatusBlock.kt b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/ui/components/exchange/ExchangeStatusBlock.kt
index a583d1cb56..b9d7dd77ee 100644
--- a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/ui/components/exchange/ExchangeStatusBlock.kt
+++ b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/ui/components/exchange/ExchangeStatusBlock.kt
@@ -26,7 +26,7 @@ import kotlinx.collections.immutable.PersistentList
@Composable
internal fun ExchangeStatusBlock(
- status: PersistentList,
+ statuses: PersistentList,
onClick: () -> Unit,
modifier: Modifier = Modifier,
) {
@@ -70,10 +70,10 @@ internal fun ExchangeStatusBlock(
}
}
- status.forEachIndexed { index, item ->
+ statuses.forEachIndexed { index, item ->
ExchangeStatusStep(
stepStatus = item,
- isLast = index == status.lastIndex,
+ isLast = index == statuses.lastIndex,
)
}
}
@@ -128,7 +128,7 @@ private fun ExchangeStatusStepText(stepStatus: ExchangeStatusState) {
}
Text(
- text = stepStatus.text,
+ text = getStatusText(stepStatus)?.let { stringResource(it) }.orEmpty(),
style = TangemTheme.typography.body2,
color = textColor,
modifier = Modifier
@@ -206,4 +206,34 @@ private fun ExchangeStepSeparator() {
shape = CircleShape,
),
)
+}
+
+private fun getStatusText(stepStatus: ExchangeStatusState) = when (stepStatus.status) {
+ ExchangeStatus.Failed -> R.string.express_exchange_status_failed
+ ExchangeStatus.Verifying -> if (stepStatus.isDone) {
+ R.string.express_exchange_status_verified
+ } else {
+ R.string.express_exchange_status_verifying
+ }
+ ExchangeStatus.New, ExchangeStatus.Waiting -> if (stepStatus.isDone) {
+ R.string.express_exchange_status_received
+ } else {
+ R.string.express_exchange_status_receiving
+ }
+ ExchangeStatus.Confirming -> if (stepStatus.isDone) {
+ R.string.express_exchange_status_confirmed
+ } else {
+ R.string.express_exchange_status_confirming
+ }
+ ExchangeStatus.Exchanging -> if (stepStatus.isDone) {
+ R.string.express_exchange_status_exchanged
+ } else {
+ R.string.express_exchange_status_exchanging
+ }
+ ExchangeStatus.Sending -> if (stepStatus.isDone) {
+ R.string.express_exchange_status_sent
+ } else {
+ R.string.express_exchange_status_sending
+ }
+ else -> null
}
\ No newline at end of file
diff --git a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/ui/components/exchange/ExchangeStatusBottomSheet.kt b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/ui/components/exchange/ExchangeStatusBottomSheet.kt
index 5705f00a63..b32c69f48e 100644
--- a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/ui/components/exchange/ExchangeStatusBottomSheet.kt
+++ b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/ui/components/exchange/ExchangeStatusBottomSheet.kt
@@ -1,5 +1,6 @@
package com.tangem.feature.tokendetails.presentation.tokendetails.ui.components.exchange
+import androidx.compose.animation.AnimatedContent
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.padding
import androidx.compose.material3.Text
@@ -15,6 +16,7 @@ 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.extensions.TextReference
import com.tangem.core.ui.res.TangemTheme
import com.tangem.core.ui.utils.toDateFormat
@@ -64,17 +66,25 @@ private fun ExchangeStatusBottomSheetContent(content: ExchangeStatusBottomSheetC
toFiatAmount = TextReference.Str(config.toFiatAmount),
)
SpacerH12()
- // todo replace with real provider data
ExchangeProvider(
- providerName = TextReference.Str(config.providerId.toString()),
- providerType = TextReference.Str("CEX"),
- imageUrl = "https://s3.eu-central-1.amazonaws.com/tangem.api/express/changenow_512.png",
+ providerName = TextReference.Str(config.provider.name),
+ providerType = TextReference.Str(config.provider.type.name),
+ imageUrl = config.provider.imageLarge,
)
SpacerH12()
ExchangeStatusBlock(
- status = config.status,
+ statuses = config.statuses,
onClick = config.onGoToProviderClick,
)
+ AnimatedContent(
+ targetState = config.notification,
+ label = "Exchange Status Notification Change",
+ ) {
+ it?.let {
+ SpacerH12()
+ Notification(config = it.config)
+ }
+ }
SpacerH24()
}
}
diff --git a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/ui/components/exchange/ExchangeStatusItems.kt b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/ui/components/exchange/ExchangeStatusItems.kt
index 95ac42480e..4ea9a31b08 100644
--- a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/ui/components/exchange/ExchangeStatusItems.kt
+++ b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/ui/components/exchange/ExchangeStatusItems.kt
@@ -17,7 +17,10 @@ import androidx.compose.ui.graphics.Color
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource
import androidx.constraintlayout.compose.ConstraintLayout
+import androidx.constraintlayout.compose.Dimension
import androidx.constraintlayout.compose.Visibility
+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.tokenicon.TokenIcon
import com.tangem.core.ui.components.currency.tokenicon.TokenIconState
import com.tangem.core.ui.res.TangemTheme
@@ -46,11 +49,13 @@ internal fun LazyListScope.swapTransactionsItems(
}
ExchangeStatusItem(
- providerName = item.providerId.toString(),
+ providerName = item.provider.name,
fromTokenIconState = item.fromCurrencyIcon,
toTokenIconState = item.toCurrencyIcon,
fromAmount = item.fromCryptoAmount,
+ fromSymbol = item.fromCryptoSymbol,
toAmount = item.toCryptoAmount,
+ toSymbol = item.toCryptoSymbol,
onClick = item.onClick,
infoIconRes = iconRes,
infoIconTint = tint,
@@ -67,7 +72,9 @@ private fun ExchangeStatusItem(
fromTokenIconState: TokenIconState,
toTokenIconState: TokenIconState,
fromAmount: String,
+ fromSymbol: String,
toAmount: String,
+ toSymbol: String,
onClick: () -> Unit,
modifier: Modifier = Modifier,
@DrawableRes infoIconRes: Int? = null,
@@ -104,14 +111,17 @@ private fun ExchangeStatusItem(
bottom.linkTo(parent.bottom)
},
)
- Text(
+ EllipsisText(
text = fromAmount,
style = TangemTheme.typography.body2,
color = TangemTheme.colors.text.primary1,
+ ellipsis = TextEllipsis.OffsetEnd(fromSymbol.length),
modifier = Modifier.constrainAs(fromRef) {
start.linkTo(fromIconRef.end, padding6)
top.linkTo(titleRef.bottom, padding6)
+ end.linkTo(swapIconRef.start)
bottom.linkTo(parent.bottom)
+ width = Dimension.fillToConstraints
},
)
Icon(
@@ -123,6 +133,7 @@ private fun ExchangeStatusItem(
.constrainAs(swapIconRef) {
start.linkTo(fromRef.end, padding6)
top.linkTo(titleRef.bottom, padding6)
+ end.linkTo(toIconRef.start)
bottom.linkTo(parent.bottom)
},
)
@@ -134,17 +145,21 @@ private fun ExchangeStatusItem(
.constrainAs(toIconRef) {
start.linkTo(swapIconRef.end, padding6)
top.linkTo(titleRef.bottom, padding6)
+ end.linkTo(toRef.start)
bottom.linkTo(parent.bottom)
},
)
- Text(
+ EllipsisText(
text = toAmount,
style = TangemTheme.typography.body2,
color = TangemTheme.colors.text.primary1,
+ ellipsis = TextEllipsis.OffsetEnd(toSymbol.length),
modifier = Modifier.constrainAs(toRef) {
start.linkTo(toIconRef.end, padding6)
top.linkTo(titleRef.bottom, padding6)
+ end.linkTo(infoIconRef.start, padding6)
bottom.linkTo(parent.bottom)
+ width = Dimension.fillToConstraints
},
)
Icon(