Updated on 2026-08-14
This commit is contained in:
parent
8a9008dbe8
commit
9e535d8240
14 changed files with 212 additions and 41 deletions
|
|
@ -350,6 +350,7 @@
|
|||
<string name="swapping_success_view_title">In progress</string>
|
||||
<string name="swapping_swap">Swap</string>
|
||||
<string name="swapping_swap_of_to">Swap of %s to</string>
|
||||
<string name="swapping_tangem_fee_disclaimer">Additionally, quotes include Tangem fee of %s. This helps us deliver a top-of-the-line product.</string>
|
||||
<string name="swapping_token_list_other_tokens">Other tokens</string>
|
||||
<string name="swapping_token_list_title">Choose token</string>
|
||||
<string name="swapping_token_list_your_tokens">Your tokens</string>
|
||||
|
|
|
|||
|
|
@ -350,6 +350,7 @@
|
|||
<string name="swapping_success_view_title">In progress</string>
|
||||
<string name="swapping_swap">Swap</string>
|
||||
<string name="swapping_swap_of_to">Swap of %s to</string>
|
||||
<string name="swapping_tangem_fee_disclaimer">Additionally, quotes include Tangem fee of %s. This helps us deliver a top-of-the-line product.</string>
|
||||
<string name="swapping_token_list_other_tokens">Other tokens</string>
|
||||
<string name="swapping_token_list_title">Choose token</string>
|
||||
<string name="swapping_token_list_your_tokens">Your tokens</string>
|
||||
|
|
|
|||
|
|
@ -350,6 +350,7 @@
|
|||
<string name="swapping_success_view_title">In progress</string>
|
||||
<string name="swapping_swap">Swap</string>
|
||||
<string name="swapping_swap_of_to">Swap of %s to</string>
|
||||
<string name="swapping_tangem_fee_disclaimer">Additionally, quotes include Tangem fee of %s. This helps us deliver a top-of-the-line product.</string>
|
||||
<string name="swapping_token_list_other_tokens">Other tokens</string>
|
||||
<string name="swapping_token_list_title">Choose token</string>
|
||||
<string name="swapping_token_list_your_tokens">Your tokens</string>
|
||||
|
|
|
|||
|
|
@ -350,6 +350,7 @@
|
|||
<string name="swapping_success_view_title">В процессе</string>
|
||||
<string name="swapping_swap">Обмен</string>
|
||||
<string name="swapping_swap_of_to">Обмен %s на</string>
|
||||
<string name="swapping_tangem_fee_disclaimer">Кроме того, котировки включают комиссию Tangem в размере %s. Это помогает нам предоставлять первоклассный продукт.</string>
|
||||
<string name="swapping_token_list_other_tokens">Другие токены</string>
|
||||
<string name="swapping_token_list_title">Выберите токен</string>
|
||||
<string name="swapping_token_list_your_tokens">Ваши токены</string>
|
||||
|
|
|
|||
|
|
@ -350,6 +350,7 @@
|
|||
<string name="swapping_success_view_title">進行中</string>
|
||||
<string name="swapping_swap">交換</string>
|
||||
<string name="swapping_swap_of_to">交易 %s 至</string>
|
||||
<string name="swapping_tangem_fee_disclaimer">Additionally, quotes include Tangem fee of %s. This helps us deliver a top-of-the-line product.</string>
|
||||
<string name="swapping_token_list_other_tokens">其他代幣</string>
|
||||
<string name="swapping_token_list_title">選擇代幣</string>
|
||||
<string name="swapping_token_list_your_tokens">您的代幣</string>
|
||||
|
|
|
|||
|
|
@ -350,6 +350,7 @@
|
|||
<string name="swapping_success_view_title">In progress</string>
|
||||
<string name="swapping_swap">Swap</string>
|
||||
<string name="swapping_swap_of_to">Swap of %s to</string>
|
||||
<string name="swapping_tangem_fee_disclaimer">Additionally, quotes include Tangem fee of %s. This helps us deliver a top-of-the-line product.</string>
|
||||
<string name="swapping_token_list_other_tokens">Other tokens</string>
|
||||
<string name="swapping_token_list_title">Choose token</string>
|
||||
<string name="swapping_token_list_your_tokens">Your tokens</string>
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ package com.tangem.core.ui.components
|
|||
|
||||
import androidx.compose.foundation.Image
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.Column
|
||||
|
|
@ -20,10 +21,13 @@ import androidx.compose.material.Icon
|
|||
import androidx.compose.material.Surface
|
||||
import androidx.compose.material.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.res.painterResource
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.unit.Dp
|
||||
import com.tangem.core.ui.R
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
import com.valentinilk.shimmer.shimmer
|
||||
|
|
@ -53,6 +57,39 @@ fun SmallInfoCard(
|
|||
startText = startText,
|
||||
endText = endText,
|
||||
isLoading = isLoading,
|
||||
disclaimer = null,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Small card with text information attached to the edges
|
||||
*
|
||||
* @param startText text information attached to the left edge
|
||||
* @param endText text information attached to the right edge
|
||||
* @param disclaimer closable description text
|
||||
* @param isLoading if true, shimmer is shown instead of [startText] and [endText]
|
||||
*
|
||||
* @see <a href = "https://www.figma.com/file/14ISV23YB1yVW1uNVwqrKv/Android?node-id=1007%3A709&t=X19ZWlYP0rnmdcDO-4"
|
||||
* >Figma component</a>
|
||||
*/
|
||||
@Composable
|
||||
fun SmallInfoCardWithDisclaimer(
|
||||
startText: String,
|
||||
endText: String,
|
||||
disclaimer: String,
|
||||
isLoading: Boolean = false,
|
||||
) {
|
||||
Surface(
|
||||
shape = RoundedCornerShape(TangemTheme.dimens.radius12),
|
||||
color = TangemTheme.colors.background.primary,
|
||||
elevation = TangemTheme.dimens.elevation2,
|
||||
) {
|
||||
CardInfoBox(
|
||||
startText = startText,
|
||||
endText = endText,
|
||||
isLoading = isLoading,
|
||||
disclaimer = disclaimer,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -63,6 +100,7 @@ fun SmallInfoCard(
|
|||
* @param startText text information attached to the left edge
|
||||
* @param endText text information attached to the right edge
|
||||
* @param warningText text of the warning
|
||||
* @param disclaimer closable description text
|
||||
*
|
||||
* @see <a href = "https://www.figma.com/file/14ISV23YB1yVW1uNVwqrKv/Android?node-id=515%3A1541&t=qYGQJdtf1eeUPdkR-1"
|
||||
* >Figma component</a>
|
||||
|
|
@ -72,6 +110,7 @@ fun SmallInfoCardWithWarning(
|
|||
startText: String,
|
||||
endText: String,
|
||||
warningText: String,
|
||||
disclaimer: String? = null,
|
||||
) {
|
||||
Surface(
|
||||
shape = RoundedCornerShape(TangemTheme.dimens.size12),
|
||||
|
|
@ -83,7 +122,7 @@ fun SmallInfoCardWithWarning(
|
|||
verticalArrangement = Arrangement.Center,
|
||||
horizontalAlignment = Alignment.CenterHorizontally,
|
||||
) {
|
||||
CardInfoBox(startText = startText, endText = endText)
|
||||
CardInfoBox(startText = startText, endText = endText, disclaimer = disclaimer)
|
||||
|
||||
Divider(
|
||||
color = TangemTheme.colors.stroke.primary,
|
||||
|
|
@ -238,44 +277,117 @@ fun IconWithTitleAndDescription(
|
|||
private fun CardInfoBox(
|
||||
startText: String,
|
||||
endText: String,
|
||||
disclaimer: String?,
|
||||
isLoading: Boolean = false,
|
||||
) {
|
||||
val isOpened = remember { mutableStateOf(false) }
|
||||
Column {
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.heightIn(TangemTheme.dimens.size48)
|
||||
.clickable { isOpened.value = !isOpened.value }
|
||||
.padding(
|
||||
horizontal = TangemTheme.dimens.spacing16,
|
||||
vertical = TangemTheme.dimens.spacing12,
|
||||
),
|
||||
horizontalArrangement = Arrangement.SpaceBetween,
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
) {
|
||||
Text(
|
||||
text = startText,
|
||||
color = TangemTheme.colors.text.tertiary,
|
||||
maxLines = 1,
|
||||
style = TangemTheme.typography.subtitle2,
|
||||
)
|
||||
|
||||
if (isLoading) {
|
||||
ShimmerItem(width = TangemTheme.dimens.size40, height = TangemTheme.dimens.size12)
|
||||
} else {
|
||||
Row {
|
||||
Text(
|
||||
text = endText,
|
||||
color = TangemTheme.colors.text.primary1,
|
||||
maxLines = 1,
|
||||
style = TangemTheme.typography.body2,
|
||||
)
|
||||
if (disclaimer != null) {
|
||||
val chevronIcon = if (isOpened.value) {
|
||||
painterResource(id = R.drawable.ic_chevron_up_24)
|
||||
} else {
|
||||
painterResource(id = R.drawable.ic_chevron_24)
|
||||
}
|
||||
Icon(
|
||||
modifier = Modifier.size(TangemTheme.dimens.size20),
|
||||
painter = chevronIcon,
|
||||
tint = TangemTheme.colors.icon.primary1,
|
||||
contentDescription = null,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (isOpened.value && disclaimer != null) {
|
||||
DisclaimerItem(disclaimer)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun ShimmerItem(width: Dp, height: Dp) {
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.size(width = width, height = height)
|
||||
.shimmer()
|
||||
.background(
|
||||
color = TangemTheme.colors.button.secondary,
|
||||
shape = RoundedCornerShape(TangemTheme.dimens.radius3),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun DisclaimerItem(disclaimer: String) {
|
||||
Divider(
|
||||
color = TangemTheme.colors.stroke.primary,
|
||||
thickness = TangemTheme.dimens.size0_5,
|
||||
modifier = Modifier.padding(horizontal = TangemTheme.dimens.spacing12),
|
||||
)
|
||||
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.heightIn(TangemTheme.dimens.size48)
|
||||
.padding(
|
||||
horizontal = TangemTheme.dimens.spacing16,
|
||||
vertical = TangemTheme.dimens.spacing12,
|
||||
top = TangemTheme.dimens.spacing14,
|
||||
bottom = TangemTheme.dimens.spacing16,
|
||||
start = TangemTheme.dimens.spacing16,
|
||||
end = TangemTheme.dimens.spacing16,
|
||||
),
|
||||
horizontalArrangement = Arrangement.SpaceBetween,
|
||||
horizontalArrangement = Arrangement.Center,
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
) {
|
||||
Text(
|
||||
text = startText,
|
||||
color = TangemTheme.colors.text.tertiary,
|
||||
maxLines = 1,
|
||||
style = TangemTheme.typography.subtitle2,
|
||||
)
|
||||
|
||||
if (isLoading) {
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.size(width = TangemTheme.dimens.size40, height = TangemTheme.dimens.size12)
|
||||
.shimmer()
|
||||
.background(
|
||||
color = TangemTheme.colors.button.secondary,
|
||||
shape = RoundedCornerShape(TangemTheme.dimens.radius3),
|
||||
),
|
||||
)
|
||||
} else {
|
||||
Text(
|
||||
text = endText,
|
||||
color = TangemTheme.colors.text.primary1,
|
||||
maxLines = 1,
|
||||
style = TangemTheme.typography.body2,
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.background(
|
||||
color = TangemTheme.colors.background.secondary,
|
||||
shape = CircleShape,
|
||||
)
|
||||
.height(TangemTheme.dimens.size40)
|
||||
.width(TangemTheme.dimens.size40),
|
||||
contentAlignment = Alignment.Center,
|
||||
) {
|
||||
Icon(
|
||||
painter = painterResource(id = R.drawable.ic_heart_24),
|
||||
tint = TangemTheme.colors.icon.secondary,
|
||||
contentDescription = null,
|
||||
)
|
||||
}
|
||||
SpacerW12()
|
||||
Text(
|
||||
text = disclaimer,
|
||||
color = TangemTheme.colors.text.tertiary,
|
||||
style = TangemTheme.typography.body2,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -287,7 +399,19 @@ private fun CardInfoBox(
|
|||
@Composable
|
||||
private fun CardsPreview() {
|
||||
Column(modifier = Modifier.fillMaxWidth()) {
|
||||
SmallInfoCard(startText = "Balance", endText = "0.4405434 BTC")
|
||||
SmallInfoCard(
|
||||
startText = "Balance",
|
||||
endText = "0.4405434 BTC",
|
||||
)
|
||||
|
||||
SpacerH32()
|
||||
|
||||
SmallInfoCardWithDisclaimer(
|
||||
startText = "Balance",
|
||||
endText = "0.4405434 BTC",
|
||||
disclaimer = "Not enough funds for fee on your Polygon wallet to create a transaction." +
|
||||
"Top up your Polygon wallet first.",
|
||||
)
|
||||
|
||||
SpacerH32()
|
||||
|
||||
|
|
@ -296,6 +420,8 @@ private fun CardsPreview() {
|
|||
endText = "0.4405434 BTC",
|
||||
warningText = "Not enough funds for fee on your Polygon wallet to create a transaction. " +
|
||||
"Top up your Polygon wallet first.",
|
||||
disclaimer = "Not enough funds for fee on your Polygon wallet to create a transaction." +
|
||||
"Top up your Polygon wallet first.",
|
||||
)
|
||||
|
||||
SpacerH32()
|
||||
|
|
@ -335,6 +461,7 @@ private fun Preview_InfoCardWithWarning_InDarkTheme() {
|
|||
private fun Preview_SimpleInfoCard_InLightTheme() {
|
||||
TangemTheme(isDark = false) {
|
||||
SmallInfoCard(startText = "Balance", endText = "0.4405434 BTC")
|
||||
SmallInfoCardWithDisclaimer(startText = "Balance", endText = "0.4405434 BTC", disclaimer = "test")
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -343,6 +470,7 @@ private fun Preview_SimpleInfoCard_InLightTheme() {
|
|||
private fun Preview_SimpleInfoCard_InDarkTheme() {
|
||||
TangemTheme(isDark = true) {
|
||||
SmallInfoCard(startText = "Balance", endText = "0.4405434 BTC")
|
||||
SmallInfoCardWithDisclaimer(startText = "Balance", endText = "0.4405434 BTC", disclaimer = "test")
|
||||
}
|
||||
}
|
||||
// endregion Preview
|
||||
9
core/ui/src/main/res/drawable/ic_chevron_up_24.xml
Normal file
9
core/ui/src/main/res/drawable/ic_chevron_up_24.xml
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
<path
|
||||
android:pathData="M7.41,16L12,11.41L16.59,16L18,14.58L12,8.58L6,14.58L7.41,16Z"
|
||||
android:fillColor="#000000"/>
|
||||
</vector>
|
||||
9
core/ui/src/main/res/drawable/ic_heart_24.xml
Normal file
9
core/ui/src/main/res/drawable/ic_heart_24.xml
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
<path
|
||||
android:pathData="M12,21.35L10.55,20.03C5.4,15.36 2,12.27 2,8.5C2,5.41 4.42,3 7.5,3C9.24,3 10.91,3.81 12,5.08C13.09,3.81 14.76,3 16.5,3C19.58,3 22,5.41 22,8.5C22,12.27 18.6,15.36 13.45,20.03L12,21.35Z"
|
||||
android:fillColor="#000000"/>
|
||||
</vector>
|
||||
|
|
@ -239,6 +239,10 @@ internal class SwapInteractorImpl @Inject constructor(
|
|||
return ONE_INCH_SUPPORTED_NETWORKS.contains(networkId)
|
||||
}
|
||||
|
||||
private fun getTangemFee(): Double {
|
||||
return repository.getTangemFee()
|
||||
}
|
||||
|
||||
fun getTokenDecimals(token: Currency): Int {
|
||||
return if (token is Currency.NonNativeToken) {
|
||||
token.decimalCount
|
||||
|
|
@ -495,6 +499,7 @@ internal class SwapInteractorImpl @Inject constructor(
|
|||
networkCurrency = userWalletManager.getNetworkCurrency(networkId),
|
||||
preparedSwapConfigState = preparedSwapConfigState,
|
||||
swapDataModel = swapDataModel,
|
||||
tangemFee = getTangemFee(),
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -16,12 +16,13 @@ sealed interface SwapState {
|
|||
val preparedSwapConfigState: PreparedSwapConfigState,
|
||||
val permissionState: PermissionDataState = PermissionDataState.Empty,
|
||||
val swapDataModel: SwapDataModel? = null,
|
||||
val tangemFee: Double,
|
||||
) : SwapState
|
||||
|
||||
data class EmptyAmountState(
|
||||
val fromTokenWalletBalance: String,
|
||||
val toTokenWalletBalance: String,
|
||||
val zeroAmountEquivalent: String
|
||||
val zeroAmountEquivalent: String,
|
||||
) : SwapState
|
||||
|
||||
data class SwapError(val error: DataError) : SwapState
|
||||
|
|
|
|||
|
|
@ -48,17 +48,18 @@ data class SwapButton(
|
|||
val onClick: () -> Unit,
|
||||
)
|
||||
|
||||
sealed interface FeeState {
|
||||
sealed class FeeState(open val tangemFee: Double) {
|
||||
|
||||
object Empty : FeeState
|
||||
object Empty : FeeState(0.0)
|
||||
|
||||
data class Loaded(
|
||||
override val tangemFee: Double,
|
||||
val fee: String = "",
|
||||
) : FeeState
|
||||
) : FeeState(tangemFee)
|
||||
|
||||
object Loading : FeeState
|
||||
object Loading : FeeState(0.0)
|
||||
|
||||
data class NotEnoughFundsWarning(val fee: String) : FeeState
|
||||
data class NotEnoughFundsWarning(override val tangemFee: Double, val fee: String) : FeeState(tangemFee)
|
||||
}
|
||||
|
||||
sealed interface TransactionCardType {
|
||||
|
|
|
|||
|
|
@ -125,9 +125,9 @@ internal class StateBuilder(val actions: UiActions) {
|
|||
warnings.add(SwapWarning.InsufficientFunds)
|
||||
}
|
||||
val feeState = if (quoteModel.preparedSwapConfigState.isFeeEnough) {
|
||||
FeeState.Loaded(quoteModel.fee)
|
||||
FeeState.Loaded(tangemFee = quoteModel.tangemFee, fee = quoteModel.fee)
|
||||
} else {
|
||||
FeeState.NotEnoughFundsWarning(quoteModel.fee)
|
||||
FeeState.NotEnoughFundsWarning(tangemFee = quoteModel.tangemFee, fee = quoteModel.fee)
|
||||
}
|
||||
return uiStateHolder.copy(
|
||||
sendCardData = SwapCardData(
|
||||
|
|
|
|||
|
|
@ -37,7 +37,7 @@ import com.tangem.core.ui.components.PrimaryButton
|
|||
import com.tangem.core.ui.components.PrimaryButtonIconRight
|
||||
import com.tangem.core.ui.components.RefreshableWaringCard
|
||||
import com.tangem.core.ui.components.SimpleOkDialog
|
||||
import com.tangem.core.ui.components.SmallInfoCard
|
||||
import com.tangem.core.ui.components.SmallInfoCardWithDisclaimer
|
||||
import com.tangem.core.ui.components.SmallInfoCardWithWarning
|
||||
import com.tangem.core.ui.components.WarningCard
|
||||
import com.tangem.core.ui.components.appbar.AppBarWithBackButton
|
||||
|
|
@ -244,17 +244,29 @@ private fun SwapButton(state: SwapStateHolder, modifier: Modifier = Modifier) {
|
|||
@Composable
|
||||
private fun FeeItem(feeState: FeeState, currency: String) {
|
||||
val titleString = stringResource(id = R.string.send_fee_label)
|
||||
val disclaimer = stringResource(id = R.string.swapping_tangem_fee_disclaimer, "${feeState.tangemFee}%")
|
||||
when (feeState) {
|
||||
is FeeState.Loaded -> {
|
||||
SmallInfoCard(startText = titleString, endText = feeState.fee, isLoading = false)
|
||||
SmallInfoCardWithDisclaimer(
|
||||
startText = titleString,
|
||||
endText = feeState.fee,
|
||||
disclaimer = disclaimer,
|
||||
isLoading = false,
|
||||
)
|
||||
}
|
||||
FeeState.Loading -> {
|
||||
SmallInfoCard(startText = titleString, endText = "", isLoading = true)
|
||||
SmallInfoCardWithDisclaimer(
|
||||
startText = titleString,
|
||||
endText = "",
|
||||
disclaimer = disclaimer,
|
||||
isLoading = true,
|
||||
)
|
||||
}
|
||||
is FeeState.NotEnoughFundsWarning -> {
|
||||
SmallInfoCardWithWarning(
|
||||
startText = titleString,
|
||||
endText = feeState.fee,
|
||||
disclaimer = disclaimer,
|
||||
warningText = stringResource(
|
||||
id = R.string.swapping_not_enough_funds_for_fee,
|
||||
currency,
|
||||
|
|
@ -387,7 +399,7 @@ private val state = SwapStateHolder(
|
|||
networkId = "ethereum",
|
||||
sendCardData = sendCard,
|
||||
receiveCardData = receiveCard,
|
||||
fee = FeeState.Loaded(fee = "0.155 MATIC (0.14 $)"),
|
||||
fee = FeeState.Loaded(fee = "0.155 MATIC (0.14 $)", tangemFee = 0.0),
|
||||
warnings = listOf(SwapWarning.PermissionNeeded("DAI")),
|
||||
networkCurrency = "MATIC",
|
||||
swapButton = SwapButton(enabled = true, loading = false, onClick = {}),
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue