diff --git a/common/ui/build.gradle.kts b/common/ui/build.gradle.kts
index e57cd07bd1..40a779c416 100644
--- a/common/ui/build.gradle.kts
+++ b/common/ui/build.gradle.kts
@@ -18,6 +18,7 @@ dependencies {
implementation(deps.compose.ui.tooling)
implementation(deps.compose.navigation)
implementation(deps.compose.navigation.hilt)
+ implementation(deps.compose.coil)
/** Deps */
implementation(deps.kotlin.immutable.collections)
diff --git a/common/ui/src/main/java/com/tangem/common/ui/userwallet/UserWalletItem.kt b/common/ui/src/main/java/com/tangem/common/ui/userwallet/UserWalletItem.kt
new file mode 100644
index 0000000000..baf96fd3cf
--- /dev/null
+++ b/common/ui/src/main/java/com/tangem/common/ui/userwallet/UserWalletItem.kt
@@ -0,0 +1,198 @@
+package com.tangem.common.ui.userwallet
+
+import androidx.compose.animation.AnimatedContent
+import androidx.compose.foundation.Image
+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.draw.clip
+import androidx.compose.ui.graphics.vector.ImageVector
+import androidx.compose.ui.platform.LocalContext
+import androidx.compose.ui.platform.LocalDensity
+import androidx.compose.ui.res.vectorResource
+import androidx.compose.ui.text.style.TextOverflow
+import androidx.compose.ui.tooling.preview.Preview
+import androidx.compose.ui.util.fastForEach
+import coil.compose.SubcomposeAsyncImage
+import coil.request.ImageRequest
+import com.tangem.common.ui.userwallet.state.UserWalletItemUM
+import com.tangem.core.ui.components.RectangleShimmer
+import com.tangem.core.ui.components.block.BlockCard
+import com.tangem.core.ui.extensions.TextReference
+import com.tangem.core.ui.extensions.resolveReference
+import com.tangem.core.ui.res.TangemTheme
+import com.tangem.common.ui.R
+import com.tangem.core.ui.coil.RotationTransformation
+import com.tangem.core.ui.extensions.stringReference
+import com.tangem.core.ui.extensions.wrappedList
+import com.tangem.core.ui.res.TangemThemePreview
+import com.tangem.domain.wallets.models.UserWalletId
+import kotlinx.collections.immutable.persistentListOf
+
+@Composable
+fun UserWalletItem(state: UserWalletItemUM, modifier: Modifier = Modifier) {
+ BlockCard(
+ modifier = modifier,
+ onClick = state.onClick,
+ enabled = state.isEnabled,
+ ) {
+ Row(
+ modifier = Modifier
+ .fillMaxWidth()
+ .heightIn(min = TangemTheme.dimens.size68)
+ .padding(all = TangemTheme.dimens.spacing12),
+ verticalAlignment = Alignment.CenterVertically,
+ horizontalArrangement = Arrangement.spacedBy(TangemTheme.dimens.spacing12),
+ ) {
+ CardImage(imageUrl = state.imageUrl)
+ NameAndInfo(
+ modifier = Modifier.weight(1f),
+ name = state.name,
+ information = state.information,
+ )
+
+ when (state.endIcon) {
+ UserWalletItemUM.EndIcon.None -> {}
+ UserWalletItemUM.EndIcon.Arrow -> {
+ Icon(
+ imageVector = ImageVector.vectorResource(R.drawable.ic_chevron_right_24),
+ tint = TangemTheme.colors.icon.informative,
+ contentDescription = null,
+ )
+ }
+ UserWalletItemUM.EndIcon.Checkmark -> {
+ Icon(
+ imageVector = ImageVector.vectorResource(R.drawable.ic_check_24),
+ tint = TangemTheme.colors.icon.accent,
+ contentDescription = null,
+ )
+ }
+ }
+ }
+ }
+}
+
+@Composable
+private fun NameAndInfo(name: TextReference, information: TextReference, modifier: Modifier = Modifier) {
+ Column(
+ modifier = modifier.heightIn(min = TangemTheme.dimens.size40),
+ horizontalAlignment = Alignment.Start,
+ verticalArrangement = Arrangement.SpaceEvenly,
+ ) {
+ Text(
+ text = name.resolveReference(),
+ style = TangemTheme.typography.subtitle1,
+ color = TangemTheme.colors.text.primary1,
+ maxLines = 1,
+ overflow = TextOverflow.Ellipsis,
+ )
+
+ AnimatedContent(
+ targetState = information.resolveReference(),
+ label = "User wallet information",
+ ) { information ->
+ Text(
+ text = information,
+ style = TangemTheme.typography.caption2,
+ color = TangemTheme.colors.text.tertiary,
+ maxLines = 1,
+ overflow = TextOverflow.Ellipsis,
+ )
+ }
+ }
+}
+
+@Composable
+private fun CardImage(imageUrl: String, modifier: Modifier = Modifier) {
+ val imageModifier = modifier
+ .width(TangemTheme.dimens.size24)
+ .height(TangemTheme.dimens.size36)
+ .clip(TangemTheme.shapes.roundedCornersSmall)
+
+ SubcomposeAsyncImage(
+ modifier = imageModifier,
+ model = ImageRequest.Builder(LocalContext.current)
+ .transformations(RotationTransformation(angle = 90f))
+ .size(
+ width = with(LocalDensity.current) { TangemTheme.dimens.size36.roundToPx() },
+ height = with(LocalDensity.current) { TangemTheme.dimens.size24.roundToPx() },
+ )
+ .data(imageUrl)
+ .crossfade(enable = true)
+ .allowHardware(enable = false)
+ .build(),
+ loading = {
+ RectangleShimmer(
+ modifier = imageModifier,
+ radius = TangemTheme.dimens.size2,
+ )
+ },
+ error = {
+ Image(
+ modifier = imageModifier,
+ imageVector = ImageVector.vectorResource(R.drawable.img_card_wallet_2_gray_22_36),
+ contentDescription = null,
+ )
+ },
+ contentDescription = null,
+ )
+}
+
+@Preview
+@Composable
+private fun Preview() {
+ TangemThemePreview {
+ val list = persistentListOf(
+ UserWalletItemUM(
+ id = UserWalletId("user_wallet_1".encodeToByteArray()),
+ name = stringReference("My Wallet"),
+ information = getInformation(3, "4 496,75 $"),
+ imageUrl = "",
+ isEnabled = true,
+ onClick = {},
+ ),
+ UserWalletItemUM(
+ id = UserWalletId("user_wallet_2".encodeToByteArray()),
+ name = stringReference("Old wallet"),
+ information = getInformation(3, "4 496,75 $"),
+ imageUrl = "",
+ isEnabled = true,
+ onClick = {},
+ endIcon = UserWalletItemUM.EndIcon.Arrow,
+ ),
+ UserWalletItemUM(
+ id = UserWalletId("user_wallet_3".encodeToByteArray()),
+ name = stringReference("Multi Card"),
+ information = getInformation(3, "4 496,75 $"),
+ imageUrl = "",
+ isEnabled = false,
+ endIcon = UserWalletItemUM.EndIcon.Checkmark,
+ onClick = {},
+ ),
+ )
+
+ Column {
+ list.fastForEach { userWalletItemUM ->
+ UserWalletItem(
+ modifier = Modifier.fillMaxWidth(),
+ state = userWalletItemUM,
+ )
+ }
+ }
+ }
+}
+
+private fun getInformation(cardCount: Int, totalBalance: String): TextReference {
+ val t1 = TextReference.PluralRes(
+ id = R.plurals.card_label_card_count,
+ count = cardCount,
+ formatArgs = wrappedList(cardCount),
+ )
+ val divider = stringReference(value = " • ")
+ val t2 = stringReference(totalBalance)
+
+ return TextReference.Combined(wrappedList(t1, divider, t2))
+}
\ No newline at end of file
diff --git a/common/ui/src/main/java/com/tangem/common/ui/userwallet/state/UserWalletItemUM.kt b/common/ui/src/main/java/com/tangem/common/ui/userwallet/state/UserWalletItemUM.kt
new file mode 100644
index 0000000000..f8e361a6f2
--- /dev/null
+++ b/common/ui/src/main/java/com/tangem/common/ui/userwallet/state/UserWalletItemUM.kt
@@ -0,0 +1,22 @@
+package com.tangem.common.ui.userwallet.state
+
+import com.tangem.core.ui.extensions.TextReference
+import com.tangem.domain.wallets.models.UserWalletId
+import javax.annotation.concurrent.Immutable
+
+@Immutable
+data class UserWalletItemUM(
+ val id: UserWalletId,
+ val name: TextReference,
+ val information: TextReference,
+ val imageUrl: String,
+ val isEnabled: Boolean,
+ val endIcon: EndIcon = EndIcon.None,
+ val onClick: () -> Unit,
+) {
+ enum class EndIcon {
+ None,
+ Arrow,
+ Checkmark,
+ }
+}
\ No newline at end of file
diff --git a/core/res/src/main/res/values-de/strings.xml b/core/res/src/main/res/values-de/strings.xml
index 1e9ed93914..21258cdee1 100644
--- a/core/res/src/main/res/values-de/strings.xml
+++ b/core/res/src/main/res/values-de/strings.xml
@@ -835,6 +835,8 @@
Wallet-Einstellungen
Tangem
Verwende %s oder scanne eine Karte, um den Zugriff auf deine Wallet freizuschalten.
+ Die Genehmigung des Swaps ist im Gange und wird in Kürze abgeschlossen sein.
+ Genehmigung in Arbeit
Es scheint, dass die Aktivierung der Karte nicht korrekt abgeschlossen wurde. Dies kann an einem Problem mit dem NFC-Modul deines Gerätes oder an einem falschen Tippen der Karte auf dein Gerät liegen. Bitte wende dich an unser Support-Team, um Unterstützung zu erhalten.
Aktivierungsfehler
Laut den Entwicklern des BNB-Netzes wird die Unterstützung für den BEP-2-Standard im Juni 2024 enden. Um den Verlust von Vermögenswerten mit diesem Standard zu vermeiden, konvertiere bitte in den BEP-20 Standard. Nutze gerne unseren Swap-Service, um sie auf das BNB Smart Chain Netzwerk zu übertragen.
@@ -852,8 +854,6 @@
Netzwerk erfordert eine Mindesteinzahlung
Der Swap wird nach Abschluss der Transaktion %s verfügbar sein.
Du hast aktive Transaktion
- Die Genehmigung des Swaps ist im Gange und wird in Kürze abgeschlossen sein.
- Genehmigung in Arbeit
Der Mindestbetrag für den Tausch beträgt %1$s. Bitte stelle sicher, dass der Restsaldo nach dem Swap nicht unter %2$s liegt.
Du hast keine %s Coins in deiner Liste
Keine Token zum Tauschen verfügbar
diff --git a/core/res/src/main/res/values-ja/strings.xml b/core/res/src/main/res/values-ja/strings.xml
index e5c43474a1..046d0156bc 100644
--- a/core/res/src/main/res/values-ja/strings.xml
+++ b/core/res/src/main/res/values-ja/strings.xml
@@ -823,6 +823,8 @@
ウォレット設定
Tangem
%sを使用するか、カードをスキャンしてウォレットにアクセスしてください
+ スワップの承認は現在進行中で、まもなく完了する予定です。
+ 承認が進行中
カードのアクティベーションが正しく完了しませんでした。デバイスの NFCモジュールに問題があるか、カードをデバイスに正しくタップしていないことが原因かもしれません。サポートチームにお問い合わせください。
アクティベーションに失敗しました
BNBネットワーク開発者によると、BEP-2規格のサポートは2024年6月に終了します。この規格の資産を失わないために、BEP-20規格に変換してください。BNBスマートチェーンネットワークへ移行するには、Tangemのスワップサービスをご利用ください。
@@ -840,8 +842,6 @@
ネットワークには最低残高が必要です
スワップは、%s の取引完了後に利用可能となります。
アクティブな取引があります
- スワップの承認は現在進行中で、まもなく完了する予定です。
- 承認が進行中
最低のスワップ金額は%1$s です。スワップ後の残金が%2$s を下回らないようにしてください。
あなたのリストには、交換可能な %s トークンがありません。
スワップ可能なトークンがありません
diff --git a/core/res/src/main/res/values-ru/strings.xml b/core/res/src/main/res/values-ru/strings.xml
index e89bb61d95..fe433aaed9 100644
--- a/core/res/src/main/res/values-ru/strings.xml
+++ b/core/res/src/main/res/values-ru/strings.xml
@@ -859,8 +859,6 @@
Для работы с сетью необходим депозит
Обмен будет доступен после завершения %s транзакции
У вас есть активная транзакция
- Разрешение обмена в процессе и будет скоро завершено
- Разрешение в процессе
Минимальная сумма обмена - %1$s. Пожалуйста, убедитесь, что остаток после обмена также не будет меньше %2$s.
У вас в списке нет монет доступных для обмена с %s
Нет доступных для обмена токенов
diff --git a/core/res/src/main/res/values-uk-rUA/strings.xml b/core/res/src/main/res/values-uk-rUA/strings.xml
index d352a7be3c..62d3baa6e6 100644
--- a/core/res/src/main/res/values-uk-rUA/strings.xml
+++ b/core/res/src/main/res/values-uk-rUA/strings.xml
@@ -844,6 +844,8 @@
Налаштування гаманця
Tangem
Використовуйте %s або відскануйте картку, щоб розблокувати доступ до гаманця
+ Затвердження обміну триває і незабаром буде завершено
+ Затвердження в процесі
Схоже, що активація картки була виконана неправильно. Це може бути пов\'язано з проблемою з модулем NFC вашого пристрою або неправильним прикладанням картки до пристрою. Зверніться за допомогою до нашої служби підтримки.
Помилка активації
За рішенням розробників мережі BNB стандарт BEP-2 перестане підтримуватись у червні 2024 року. Щоб не втратити свої активи, їх необхідно конвертувати у стандарт BEP-20. Використовуйте функцію обміну, щоб перевести їх у мережу BNB Smart Chain.
@@ -861,8 +863,6 @@
Для роботи з мережею вимагається депозит
Обмін буде доступний після завершення %s транзакції
У вас є активна транзакція
- Затвердження обміну триває і незабаром буде завершено
- Затвердження в процесі
Мінімальна сума обміну становить - %1$s. Будь ласка, переконайтеся, що залишок на рахунку після обміну буде не менше за %2$s.
У вашому списку немає доступних монет для обміну %s
Немає доступних токенів для обміну
diff --git a/core/ui/build.gradle.kts b/core/ui/build.gradle.kts
index cb8f7f15ee..4758911a25 100644
--- a/core/ui/build.gradle.kts
+++ b/core/ui/build.gradle.kts
@@ -1,3 +1,5 @@
+import com.android.ide.common.resources.generateLocaleList
+
plugins {
alias(deps.plugins.android.library)
alias(deps.plugins.kotlin.android)
diff --git a/core/ui/src/main/java/com/tangem/core/ui/coil/RotationTransformation.kt b/core/ui/src/main/java/com/tangem/core/ui/coil/RotationTransformation.kt
new file mode 100644
index 0000000000..6a8019af1e
--- /dev/null
+++ b/core/ui/src/main/java/com/tangem/core/ui/coil/RotationTransformation.kt
@@ -0,0 +1,22 @@
+package com.tangem.core.ui.coil
+
+import android.graphics.Bitmap
+import android.graphics.Matrix
+import coil.size.Size
+import coil.transform.Transformation
+
+class RotationTransformation(private val angle: Float) : Transformation {
+
+ override val cacheKey: String = "rotate:$angle"
+
+ override suspend fun transform(input: Bitmap, size: Size): Bitmap {
+ val matrix = Matrix().apply {
+ val centerX = input.width / 2f
+ val centerY = input.height / 2f
+
+ postRotate(angle, centerX, centerY)
+ }
+
+ return Bitmap.createBitmap(input, 0, 0, input.width, input.height, matrix, true)
+ }
+}
\ No newline at end of file
diff --git a/core/ui/src/main/java/com/tangem/core/ui/components/Shimmers.kt b/core/ui/src/main/java/com/tangem/core/ui/components/Shimmers.kt
index 5d09690136..c0f97514dc 100644
--- a/core/ui/src/main/java/com/tangem/core/ui/components/Shimmers.kt
+++ b/core/ui/src/main/java/com/tangem/core/ui/components/Shimmers.kt
@@ -17,6 +17,7 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.graphics.BlendMode
import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.graphics.Shape
import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.tooling.preview.Preview
@@ -101,7 +102,11 @@ fun TextShimmer(
* Height and min width will be set automatically
*/
@Composable
-fun SmallButtonShimmer(modifier: Modifier = Modifier, withIcon: Boolean = false) {
+fun SmallButtonShimmer(
+ modifier: Modifier = Modifier,
+ shape: Shape = RoundedCornerShape(size = TangemTheme.dimens.radius16),
+ withIcon: Boolean = false,
+) {
PrimarySmallButton(
config = SmallButtonConfig(
text = stringReference("B"),
@@ -113,7 +118,7 @@ fun SmallButtonShimmer(modifier: Modifier = Modifier, withIcon: Boolean = false)
},
),
modifier = modifier
- .clip(RoundedCornerShape(size = TangemTheme.dimens.radius16))
+ .clip(shape)
.shimmer(LocalTangemShimmer.current),
)
}
diff --git a/core/ui/src/main/java/com/tangem/core/ui/components/block/information/InformationBlock.kt b/core/ui/src/main/java/com/tangem/core/ui/components/block/information/InformationBlock.kt
index 900aafe401..fb2230889c 100644
--- a/core/ui/src/main/java/com/tangem/core/ui/components/block/information/InformationBlock.kt
+++ b/core/ui/src/main/java/com/tangem/core/ui/components/block/information/InformationBlock.kt
@@ -10,6 +10,7 @@ import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.tooling.preview.Preview
+import androidx.compose.ui.unit.Dp
import com.tangem.core.ui.R
import com.tangem.core.ui.components.buttons.SecondarySmallButton
import com.tangem.core.ui.components.buttons.SmallButtonConfig
@@ -29,6 +30,7 @@ class InformationBlockContentScope(val scope: BoxScope) : BoxScope by scope
fun InformationBlock(
title: @Composable BoxScope.() -> Unit,
modifier: Modifier = Modifier,
+ contentHorizontalPadding: Dp = TangemTheme.dimens.spacing12,
action: (@Composable BoxScope.() -> Unit)? = null,
content: (@Composable InformationBlockContentScope.() -> Unit)? = null,
) {
@@ -72,7 +74,7 @@ fun InformationBlock(
if (content != null) {
Box(
modifier = Modifier
- .padding(horizontal = TangemTheme.dimens.spacing12)
+ .padding(horizontal = contentHorizontalPadding)
.fillMaxWidth(),
) {
val scope = InformationBlockContentScope(scope = this)
diff --git a/core/ui/src/main/java/com/tangem/core/ui/components/buttons/SmallButton.kt b/core/ui/src/main/java/com/tangem/core/ui/components/buttons/SmallButton.kt
index e02ae97109..195fae0923 100644
--- a/core/ui/src/main/java/com/tangem/core/ui/components/buttons/SmallButton.kt
+++ b/core/ui/src/main/java/com/tangem/core/ui/components/buttons/SmallButton.kt
@@ -33,6 +33,7 @@ data class SmallButtonConfig(
val text: TextReference,
val onClick: () -> Unit,
val icon: TangemButtonIconPosition = TangemButtonIconPosition.None,
+ val enabled: Boolean = true,
)
/**
@@ -57,6 +58,7 @@ fun SecondarySmallButton(config: SmallButtonConfig, modifier: Modifier = Modifie
SmallButton(config = config, isPrimary = false, modifier = modifier)
}
+@Suppress("LongMethod")
@Composable
private fun SmallButton(config: SmallButtonConfig, isPrimary: Boolean, modifier: Modifier = Modifier) {
val shape = RoundedCornerShape(size = TangemTheme.dimens.radius16)
@@ -77,7 +79,7 @@ private fun SmallButton(config: SmallButtonConfig, isPrimary: Boolean, modifier:
color = backgroundColor,
shape = shape,
)
- .clickable(enabled = true, onClick = config.onClick)
+ .clickable(enabled = config.enabled, onClick = config.onClick)
.padding(
paddingValues = when (config.icon) {
is TangemButtonIconPosition.None -> PaddingValues(
@@ -100,7 +102,11 @@ private fun SmallButton(config: SmallButtonConfig, isPrimary: Boolean, modifier:
iconPosition = config.icon,
text = {
val textColor by animateColorAsState(
- targetValue = if (isPrimary) TangemTheme.colors.text.primary2 else TangemTheme.colors.text.primary1,
+ targetValue = when {
+ !config.enabled -> TangemTheme.colors.text.disabled
+ isPrimary -> TangemTheme.colors.text.primary2
+ else -> TangemTheme.colors.text.primary1
+ },
label = "Update text color",
)
@@ -116,7 +122,11 @@ private fun SmallButton(config: SmallButtonConfig, isPrimary: Boolean, modifier:
Icon(
modifier = Modifier.size(TangemTheme.dimens.size16),
painter = painterResource(id = iconResId),
- tint = TangemTheme.colors.icon.secondary,
+ tint = if (config.enabled) {
+ TangemTheme.colors.icon.secondary
+ } else {
+ TangemTheme.colors.icon.inactive
+ },
contentDescription = null,
)
},
@@ -174,5 +184,12 @@ private fun ButtonsSample() {
icon = TangemButtonIconPosition.Start(iconResId = R.drawable.ic_plus_24),
),
)
+ SecondarySmallButton(
+ config = config.copy(
+ text = TextReference.Str(value = "Add token"),
+ icon = TangemButtonIconPosition.Start(iconResId = R.drawable.ic_plus_24),
+ enabled = false,
+ ),
+ )
}
}
\ No newline at end of file
diff --git a/core/ui/src/main/java/com/tangem/core/ui/components/rows/ArrowRow.kt b/core/ui/src/main/java/com/tangem/core/ui/components/rows/ArrowRow.kt
index 0e594fbd91..67ef1ac47d 100644
--- a/core/ui/src/main/java/com/tangem/core/ui/components/rows/ArrowRow.kt
+++ b/core/ui/src/main/java/com/tangem/core/ui/components/rows/ArrowRow.kt
@@ -13,6 +13,7 @@ import androidx.compose.ui.graphics.drawscope.Stroke
import androidx.compose.ui.graphics.drawscope.drawIntoCanvas
import androidx.compose.ui.layout.onSizeChanged
import androidx.compose.ui.platform.LocalDensity
+import androidx.compose.ui.platform.LocalLayoutDirection
import androidx.compose.ui.unit.*
import com.tangem.core.ui.res.TangemTheme
import com.tangem.core.ui.utils.*
@@ -68,6 +69,7 @@ private class ChildArrowScope(
@Composable
fun ChildArrow(childHeight: Dp, isLastChild: Boolean) {
+ val isLtr = LocalLayoutDirection.current == LayoutDirection.Ltr
val figureWidth = TangemTheme.dimens.size40
val strokeColor = TangemTheme.colors.stroke.secondary
@@ -86,18 +88,31 @@ fun ChildArrow(childHeight: Dp, isLastChild: Boolean) {
)
val arrowHeadRectDp = DpRect(
origin = DpOffset(
- x = figureWidth - arrowHeadSize.width,
+ x = if (isLtr) {
+ figureWidth - arrowHeadSize.width
+ } else {
+ 0.dp
+ },
y = figureRectDp.size.center.y - arrowHeadSize.center.y,
),
size = arrowHeadSize,
)
- val curvedArrowRectDp = DpRect(
- top = figureRectDp.top,
- left = TangemTheme.dimens.size18,
- right = figureRectDp.right - arrowHeadRectDp.width,
- bottom = figureRectDp.size.center.y,
- )
+ val curvedArrowRectDp = if (isLtr) {
+ DpRect(
+ top = figureRectDp.top,
+ left = TangemTheme.dimens.size18,
+ right = figureRectDp.right - arrowHeadRectDp.width,
+ bottom = figureRectDp.size.center.y,
+ )
+ } else {
+ DpRect(
+ top = figureRectDp.top,
+ left = arrowHeadRectDp.width,
+ right = TangemTheme.dimens.size18 + arrowHeadRectDp.width,
+ bottom = figureRectDp.size.center.y,
+ )
+ }
Canvas(
modifier = Modifier
@@ -114,20 +129,26 @@ fun ChildArrow(childHeight: Dp, isLastChild: Boolean) {
drawScope = this,
)
- scope.drawCurveArrow()
- scope.drawArrowHead()
+ scope.drawCurveArrow(isLtr)
+ scope.drawArrowHead(isLtr)
if (!isLastChild) {
- scope.drawArrowLine()
+ scope.drawArrowLine(isLtr)
}
}
}
-private fun ChildArrowScope.drawArrowHead() {
+private fun ChildArrowScope.drawArrowHead(isLtr: Boolean) {
val arrowHeadPath = Path().apply {
- moveTo(arrowHeadRect.centerRight)
- lineTo(arrowHeadRect.topLeft)
- lineTo(arrowHeadRect.bottomLeft)
+ if (isLtr) {
+ moveTo(arrowHeadRect.centerRight)
+ lineTo(arrowHeadRect.topLeft)
+ lineTo(arrowHeadRect.bottomLeft)
+ } else {
+ moveTo(arrowHeadRect.centerLeft)
+ lineTo(arrowHeadRect.topRight)
+ lineTo(arrowHeadRect.bottomRight)
+ }
close()
}
val paint = Paint().apply {
@@ -143,13 +164,21 @@ private fun ChildArrowScope.drawArrowHead() {
}
}
-private fun ChildArrowScope.drawCurveArrow() {
+private fun ChildArrowScope.drawCurveArrow(isLtr: Boolean) {
val curveArrowPath = Path().apply {
- moveTo(curvedArrowRect.topLeft)
- quadraticBezierTo(
- control = curvedArrowRect.bottomLeft,
- end = curvedArrowRect.bottomRight,
- )
+ if (isLtr) {
+ moveTo(curvedArrowRect.topLeft)
+ quadraticBezierTo(
+ control = curvedArrowRect.bottomLeft,
+ end = curvedArrowRect.bottomRight,
+ )
+ } else {
+ moveTo(curvedArrowRect.topRight)
+ quadraticBezierTo(
+ control = curvedArrowRect.bottomRight,
+ end = curvedArrowRect.bottomLeft,
+ )
+ }
}
drawPath(
path = curveArrowPath,
@@ -158,11 +187,20 @@ private fun ChildArrowScope.drawCurveArrow() {
)
}
-private fun ChildArrowScope.drawArrowLine() {
- drawLine(
- color = strokeColor,
- start = curvedArrowRect.topLeft,
- end = Offset(curvedArrowRect.left, figureRect.bottom),
- strokeWidth = arrowStrokeWidth,
- )
+private fun ChildArrowScope.drawArrowLine(isLtr: Boolean) {
+ if (isLtr) {
+ drawLine(
+ color = strokeColor,
+ start = curvedArrowRect.topLeft,
+ end = Offset(curvedArrowRect.left, figureRect.bottom),
+ strokeWidth = arrowStrokeWidth,
+ )
+ } else {
+ drawLine(
+ color = strokeColor,
+ start = curvedArrowRect.topRight,
+ end = Offset(curvedArrowRect.right, figureRect.bottom),
+ strokeWidth = arrowStrokeWidth,
+ )
+ }
}
\ No newline at end of file
diff --git a/core/ui/src/main/java/com/tangem/core/ui/components/rows/BlockchainRow.kt b/core/ui/src/main/java/com/tangem/core/ui/components/rows/BlockchainRow.kt
index 4f8edf75c4..035be68d2e 100644
--- a/core/ui/src/main/java/com/tangem/core/ui/components/rows/BlockchainRow.kt
+++ b/core/ui/src/main/java/com/tangem/core/ui/components/rows/BlockchainRow.kt
@@ -29,8 +29,9 @@ fun BlockchainRow(model: BlockchainRowUM, action: @Composable BoxScope.() -> Uni
modifier = modifier
.heightIn(min = TangemTheme.dimens.size52)
.padding(
- vertical = TangemTheme.dimens.spacing8,
- horizontal = TangemTheme.dimens.spacing8,
+ top = TangemTheme.dimens.spacing8,
+ bottom = TangemTheme.dimens.spacing8,
+ start = TangemTheme.dimens.spacing8,
),
icon = {
RowIcon(
@@ -125,7 +126,7 @@ private fun Preview_BlockchainRow(@PreviewParameter(BlockchainRowParameterProvid
BlockchainRow(
model = state,
action = {
- TangemSwitch(onCheckedChange = { /* [REDACTED_TODO_COMMENT]*/ }, checked = true)
+ TangemSwitch(onCheckedChange = { }, checked = true)
},
)
},
diff --git a/core/ui/src/main/java/com/tangem/core/ui/res/TangemThemePreview.kt b/core/ui/src/main/java/com/tangem/core/ui/res/TangemThemePreview.kt
index 0347ca4893..dd15fd2212 100644
--- a/core/ui/src/main/java/com/tangem/core/ui/res/TangemThemePreview.kt
+++ b/core/ui/src/main/java/com/tangem/core/ui/res/TangemThemePreview.kt
@@ -6,6 +6,8 @@ import androidx.compose.runtime.Composable
import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.runtime.ProvidableCompositionLocal
import androidx.compose.runtime.compositionLocalOf
+import androidx.compose.ui.platform.LocalLayoutDirection
+import androidx.compose.ui.unit.LayoutDirection
import com.tangem.core.ui.windowsize.rememberWindowSizePreview
@Composable
@@ -14,12 +16,14 @@ fun TangemThemePreview(
typography: TangemTypography = TangemTheme.typography,
dimens: TangemDimens = TangemTheme.dimens,
alwaysShowBottomSheets: Boolean = true,
+ rtl: Boolean = false,
content: @Composable () -> Unit,
) {
val isDarkTheme = isDark ?: isSystemInDarkTheme()
CompositionLocalProvider(
LocalBottomSheetAlwaysVisible provides alwaysShowBottomSheets,
+ LocalLayoutDirection provides if (rtl) LayoutDirection.Rtl else LayoutDirection.Ltr,
) {
BoxWithConstraints {
TangemTheme(
diff --git a/core/ui/src/main/res/drawable/img_card_wallet_2_gray_22_36.xml b/core/ui/src/main/res/drawable/img_card_wallet_2_gray_22_36.xml
new file mode 100644
index 0000000000..977d693e60
--- /dev/null
+++ b/core/ui/src/main/res/drawable/img_card_wallet_2_gray_22_36.xml
@@ -0,0 +1,13 @@
+
+
+
+
+
diff --git a/features/details/impl/build.gradle.kts b/features/details/impl/build.gradle.kts
index 82ca657a96..ff405d0d36 100644
--- a/features/details/impl/build.gradle.kts
+++ b/features/details/impl/build.gradle.kts
@@ -25,6 +25,7 @@ dependencies {
implementation(projects.core.navigation)
implementation(projects.core.analytics.models)
implementation(projects.common.routing)
+ implementation(projects.common.ui)
/* Project - Domain */
implementation(projects.domain.models)
diff --git a/features/details/impl/src/main/kotlin/com/tangem/features/details/component/preview/PreviewUserWalletListComponent.kt b/features/details/impl/src/main/kotlin/com/tangem/features/details/component/preview/PreviewUserWalletListComponent.kt
index 92b6f46de3..e2a4774961 100644
--- a/features/details/impl/src/main/kotlin/com/tangem/features/details/component/preview/PreviewUserWalletListComponent.kt
+++ b/features/details/impl/src/main/kotlin/com/tangem/features/details/component/preview/PreviewUserWalletListComponent.kt
@@ -2,6 +2,7 @@ package com.tangem.features.details.component.preview
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
+import com.tangem.common.ui.userwallet.state.UserWalletItemUM
import com.tangem.core.ui.extensions.TextReference
import com.tangem.core.ui.extensions.resourceReference
import com.tangem.core.ui.extensions.stringReference
@@ -17,7 +18,7 @@ internal class PreviewUserWalletListComponent : UserWalletListComponent {
private val previewState = UserWalletListUM(
userWallets = persistentListOf(
- UserWalletListUM.UserWalletUM(
+ UserWalletItemUM(
id = UserWalletId("user_wallet_1".encodeToByteArray()),
name = stringReference("My Wallet"),
information = getInformation(3, "4 496,75 $"),
@@ -25,7 +26,7 @@ internal class PreviewUserWalletListComponent : UserWalletListComponent {
isEnabled = true,
onClick = {},
),
- UserWalletListUM.UserWalletUM(
+ UserWalletItemUM(
id = UserWalletId("user_wallet_2".encodeToByteArray()),
name = stringReference("Old wallet"),
information = getInformation(3, "4 496,75 $"),
@@ -33,7 +34,7 @@ internal class PreviewUserWalletListComponent : UserWalletListComponent {
isEnabled = true,
onClick = {},
),
- UserWalletListUM.UserWalletUM(
+ UserWalletItemUM(
id = UserWalletId("user_wallet_3".encodeToByteArray()),
name = stringReference("Multi Card"),
information = getInformation(3, "4 496,75 $"),
diff --git a/features/details/impl/src/main/kotlin/com/tangem/features/details/entity/UserWalletListUM.kt b/features/details/impl/src/main/kotlin/com/tangem/features/details/entity/UserWalletListUM.kt
index 1569f48efb..a8ef5eb141 100644
--- a/features/details/impl/src/main/kotlin/com/tangem/features/details/entity/UserWalletListUM.kt
+++ b/features/details/impl/src/main/kotlin/com/tangem/features/details/entity/UserWalletListUM.kt
@@ -1,25 +1,14 @@
package com.tangem.features.details.entity
import androidx.compose.runtime.Immutable
+import com.tangem.common.ui.userwallet.state.UserWalletItemUM
import com.tangem.core.ui.extensions.TextReference
-import com.tangem.domain.wallets.models.UserWalletId
import kotlinx.collections.immutable.ImmutableList
@Immutable
internal data class UserWalletListUM(
- val userWallets: ImmutableList,
+ val userWallets: ImmutableList,
val isWalletSavingInProgress: Boolean,
val addNewWalletText: TextReference,
val onAddNewWalletClick: () -> Unit,
-) {
-
- @Immutable
- data class UserWalletUM(
- val id: UserWalletId,
- val name: TextReference,
- val information: TextReference,
- val imageUrl: String,
- val isEnabled: Boolean,
- val onClick: () -> Unit,
- )
-}
\ No newline at end of file
+)
\ No newline at end of file
diff --git a/features/details/impl/src/main/kotlin/com/tangem/features/details/model/UserWalletListModel.kt b/features/details/impl/src/main/kotlin/com/tangem/features/details/model/UserWalletListModel.kt
index 12f18cc1f4..a36e126509 100644
--- a/features/details/impl/src/main/kotlin/com/tangem/features/details/model/UserWalletListModel.kt
+++ b/features/details/impl/src/main/kotlin/com/tangem/features/details/model/UserWalletListModel.kt
@@ -1,12 +1,12 @@
package com.tangem.features.details.model
+import com.tangem.common.ui.userwallet.state.UserWalletItemUM
import com.tangem.core.decompose.di.ComponentScoped
import com.tangem.core.decompose.model.Model
import com.tangem.core.ui.extensions.TextReference
import com.tangem.core.ui.extensions.resourceReference
import com.tangem.domain.wallets.usecase.ShouldSaveUserWalletsUseCase
import com.tangem.features.details.entity.UserWalletListUM
-import com.tangem.features.details.entity.UserWalletListUM.UserWalletUM
import com.tangem.features.details.impl.R
import com.tangem.features.details.utils.UserWalletSaver
import com.tangem.features.details.utils.UserWalletsFetcher
@@ -48,7 +48,7 @@ internal class UserWalletListModel @Inject constructor(
}
private fun updateState(
- userWallets: ImmutableList,
+ userWallets: ImmutableList,
shouldSaveUserWallets: Boolean,
isWalletSavingInProgress: Boolean,
) = state.update { value ->
diff --git a/features/details/impl/src/main/kotlin/com/tangem/features/details/ui/UserWalletListBlock.kt b/features/details/impl/src/main/kotlin/com/tangem/features/details/ui/UserWalletListBlock.kt
index 794ef632c3..b09124ca04 100644
--- a/features/details/impl/src/main/kotlin/com/tangem/features/details/ui/UserWalletListBlock.kt
+++ b/features/details/impl/src/main/kotlin/com/tangem/features/details/ui/UserWalletListBlock.kt
@@ -1,7 +1,6 @@
package com.tangem.features.details.ui
import androidx.compose.animation.AnimatedContent
-import androidx.compose.foundation.Image
import androidx.compose.foundation.layout.*
import androidx.compose.material3.CircularProgressIndicator
import androidx.compose.material3.Icon
@@ -10,32 +9,25 @@ import androidx.compose.runtime.Composable
import androidx.compose.runtime.key
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
-import androidx.compose.ui.draw.clip
-import androidx.compose.ui.platform.LocalContext
-import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.res.painterResource
-import androidx.compose.ui.text.style.TextOverflow
-import coil.compose.SubcomposeAsyncImage
-import coil.request.ImageRequest
-import com.tangem.core.ui.components.RectangleShimmer
+import com.tangem.common.ui.userwallet.UserWalletItem
import com.tangem.core.ui.components.block.BlockCard
import com.tangem.core.ui.extensions.TextReference
import com.tangem.core.ui.extensions.resolveReference
import com.tangem.core.ui.res.TangemTheme
import com.tangem.features.details.entity.UserWalletListUM
import com.tangem.features.details.impl.R
-import com.tangem.features.details.ui.coil.RotationTransformation
@Composable
internal fun UserWalletListBlock(state: UserWalletListUM, modifier: Modifier = Modifier) {
BlockCard(
modifier = modifier,
) {
- state.userWallets.forEach { model ->
- key(model.id) {
+ state.userWallets.forEach { state ->
+ key(state.id) {
UserWalletItem(
modifier = Modifier.fillMaxWidth(),
- model = model,
+ state = state,
)
}
}
@@ -47,96 +39,6 @@ internal fun UserWalletListBlock(state: UserWalletListUM, modifier: Modifier = M
}
}
-@Composable
-private fun UserWalletItem(model: UserWalletListUM.UserWalletUM, modifier: Modifier = Modifier) {
- BlockCard(
- modifier = modifier,
- onClick = model.onClick,
- enabled = model.isEnabled,
- ) {
- Row(
- modifier = Modifier
- .fillMaxWidth()
- .heightIn(min = TangemTheme.dimens.size68)
- .padding(all = TangemTheme.dimens.spacing12),
- verticalAlignment = Alignment.CenterVertically,
- horizontalArrangement = Arrangement.spacedBy(TangemTheme.dimens.spacing12),
- ) {
- Image(imageUrl = model.imageUrl)
- NameAndInfo(
- name = model.name,
- information = model.information,
- )
- }
- }
-}
-
-@Composable
-private fun NameAndInfo(name: TextReference, information: TextReference, modifier: Modifier = Modifier) {
- Column(
- modifier = modifier.heightIn(min = TangemTheme.dimens.size40),
- horizontalAlignment = Alignment.Start,
- verticalArrangement = Arrangement.SpaceEvenly,
- ) {
- Text(
- text = name.resolveReference(),
- style = TangemTheme.typography.subtitle1,
- color = TangemTheme.colors.text.primary1,
- maxLines = 1,
- overflow = TextOverflow.Ellipsis,
- )
-
- AnimatedContent(
- targetState = information.resolveReference(),
- label = "User wallet information",
- ) { information ->
- Text(
- text = information,
- style = TangemTheme.typography.caption2,
- color = TangemTheme.colors.text.tertiary,
- maxLines = 1,
- overflow = TextOverflow.Ellipsis,
- )
- }
- }
-}
-
-@Composable
-private fun Image(imageUrl: String, modifier: Modifier = Modifier) {
- val imageModifier = modifier
- .width(TangemTheme.dimens.size24)
- .height(TangemTheme.dimens.size36)
- .clip(TangemTheme.shapes.roundedCornersSmall)
-
- SubcomposeAsyncImage(
- modifier = imageModifier,
- model = ImageRequest.Builder(LocalContext.current)
- .transformations(RotationTransformation(angle = 90f))
- .size(
- width = with(LocalDensity.current) { TangemTheme.dimens.size36.roundToPx() },
- height = with(LocalDensity.current) { TangemTheme.dimens.size24.roundToPx() },
- )
- .data(imageUrl)
- .crossfade(enable = true)
- .allowHardware(enable = false)
- .build(),
- loading = {
- RectangleShimmer(
- modifier = imageModifier,
- radius = TangemTheme.dimens.size2,
- )
- },
- error = {
- Image(
- modifier = imageModifier,
- painter = painterResource(id = R.drawable.img_card_wallet_2_gray_22_36),
- contentDescription = null,
- )
- },
- contentDescription = null,
- )
-}
-
@Composable
private fun AddWalletButton(
text: TextReference,
diff --git a/features/details/impl/src/main/kotlin/com/tangem/features/details/utils/UserWalletMappers.kt b/features/details/impl/src/main/kotlin/com/tangem/features/details/utils/UserWalletMappers.kt
index a9782fc1d9..1a3efa0cf4 100644
--- a/features/details/impl/src/main/kotlin/com/tangem/features/details/utils/UserWalletMappers.kt
+++ b/features/details/impl/src/main/kotlin/com/tangem/features/details/utils/UserWalletMappers.kt
@@ -1,5 +1,6 @@
package com.tangem.features.details.utils
+import com.tangem.common.ui.userwallet.state.UserWalletItemUM
import com.tangem.core.ui.extensions.*
import com.tangem.core.ui.utils.BigDecimalFormatter
import com.tangem.domain.appcurrency.model.AppCurrency
@@ -7,7 +8,6 @@ import com.tangem.domain.models.scan.CardDTO
import com.tangem.domain.tokens.model.TotalFiatBalance
import com.tangem.domain.wallets.models.UserWallet
import com.tangem.domain.wallets.models.UserWalletId
-import com.tangem.features.details.entity.UserWalletListUM.UserWalletUM
import com.tangem.features.details.impl.R
import com.tangem.utils.StringsSigns.STARS
import kotlinx.collections.immutable.ImmutableList
@@ -19,7 +19,7 @@ internal fun List.toUiModels(
balances: Map = emptyMap(),
isLoading: Boolean = true,
isBalancesHidden: Boolean = false,
-): ImmutableList = this.map { model ->
+): ImmutableList = this.map { model ->
val balance = balances[model.walletId]
model.toUiModel(
@@ -37,7 +37,7 @@ private fun UserWallet.toUiModel(
isLoading: Boolean,
isBalanceHidden: Boolean,
onClick: () -> Unit,
-): UserWalletUM = UserWalletUM(
+): UserWalletItemUM = UserWalletItemUM(
id = walletId,
name = stringReference(name),
information = getInfo(
diff --git a/features/details/impl/src/main/kotlin/com/tangem/features/details/utils/UserWalletsFetcher.kt b/features/details/impl/src/main/kotlin/com/tangem/features/details/utils/UserWalletsFetcher.kt
index e141c4034e..cd5013c005 100644
--- a/features/details/impl/src/main/kotlin/com/tangem/features/details/utils/UserWalletsFetcher.kt
+++ b/features/details/impl/src/main/kotlin/com/tangem/features/details/utils/UserWalletsFetcher.kt
@@ -2,6 +2,7 @@ package com.tangem.features.details.utils
import arrow.core.Either
import com.tangem.common.routing.AppRoute
+import com.tangem.common.ui.userwallet.state.UserWalletItemUM
import com.tangem.core.decompose.di.ComponentScoped
import com.tangem.core.decompose.navigation.Router
import com.tangem.core.decompose.ui.UiMessageSender
@@ -22,7 +23,6 @@ import com.tangem.domain.tokens.model.TotalFiatBalance
import com.tangem.domain.wallets.models.UserWallet
import com.tangem.domain.wallets.models.UserWalletId
import com.tangem.domain.wallets.usecase.GetWalletsUseCase
-import com.tangem.features.details.entity.UserWalletListUM.UserWalletUM
import com.tangem.features.details.impl.R
import kotlinx.collections.immutable.ImmutableList
import kotlinx.coroutines.ExperimentalCoroutinesApi
@@ -40,7 +40,7 @@ internal class UserWalletsFetcher @Inject constructor(
) {
@OptIn(ExperimentalCoroutinesApi::class)
- val userWallets: Flow> = getWalletsUseCase().transformLatest { wallets ->
+ val userWallets: Flow> = getWalletsUseCase().transformLatest { wallets ->
emit(wallets.toUiModels(onClick = ::navigateToWalletSettings))
combine(
@@ -72,7 +72,7 @@ internal class UserWalletsFetcher @Inject constructor(
maybeAppCurrency: Either,
maybeBalances: Lce>,
balanceHidingSettings: BalanceHidingSettings,
- ): Lce> = lce {
+ ): Lce> = lce {
val balances = withError(
transform = { Error.UnableToGetBalances },
block = { maybeBalances.bindOrNull().orEmpty() },
diff --git a/features/manage-tokens/impl/src/main/kotlin/com/tangem/features/managetokens/ui/ManageTokensScreen.kt b/features/manage-tokens/impl/src/main/kotlin/com/tangem/features/managetokens/ui/ManageTokensScreen.kt
index 9f9584fac9..3008c84d30 100644
--- a/features/manage-tokens/impl/src/main/kotlin/com/tangem/features/managetokens/ui/ManageTokensScreen.kt
+++ b/features/manage-tokens/impl/src/main/kotlin/com/tangem/features/managetokens/ui/ManageTokensScreen.kt
@@ -286,6 +286,7 @@ private fun NetworksList(networks: NetworksUM, currencyId: String, modifier: Mod
isLastItem = index == currentItems.lastIndex,
content = {
BlockchainRow(
+ modifier = Modifier.padding(end = TangemTheme.dimens.spacing8),
model = with(network) {
BlockchainRowUM(
name = name,
diff --git a/features/markets/impl/build.gradle.kts b/features/markets/impl/build.gradle.kts
index 7d8b7d0667..2e1f37fd3e 100644
--- a/features/markets/impl/build.gradle.kts
+++ b/features/markets/impl/build.gradle.kts
@@ -20,6 +20,7 @@ dependencies {
implementation(projects.domain.markets)
implementation(projects.domain.appCurrency)
implementation(projects.domain.appCurrency.models)
+ implementation(projects.domain.wallets.models)
/* Compose */
implementation(deps.compose.coil)
@@ -46,6 +47,7 @@ dependencies {
implementation(projects.core.ui)
implementation(projects.core.featuretoggles)
+ /* Common */
implementation(projects.common.ui)
implementation(projects.common.uiCharts)
}
\ No newline at end of file
diff --git a/features/markets/impl/src/main/kotlin/com/tangem/features/markets/details/impl/model/MarketsTokenDetailsModel.kt b/features/markets/impl/src/main/kotlin/com/tangem/features/markets/details/impl/model/MarketsTokenDetailsModel.kt
index 2a40b0c83e..0f2d2d1465 100644
--- a/features/markets/impl/src/main/kotlin/com/tangem/features/markets/details/impl/model/MarketsTokenDetailsModel.kt
+++ b/features/markets/impl/src/main/kotlin/com/tangem/features/markets/details/impl/model/MarketsTokenDetailsModel.kt
@@ -3,6 +3,7 @@ package com.tangem.features.markets.details.impl.model
import androidx.compose.runtime.Stable
import arrow.core.getOrElse
import com.tangem.common.ui.charts.state.*
+import com.tangem.core.decompose.di.ComponentScoped
import com.tangem.core.decompose.model.Model
import com.tangem.core.decompose.model.ParamsContainer
import com.tangem.core.navigation.url.UrlOpener
@@ -43,6 +44,7 @@ import javax.inject.Inject
@Suppress("LargeClass", "LongParameterList")
@Stable
+@ComponentScoped
internal class MarketsTokenDetailsModel @Inject constructor(
paramsContainer: ParamsContainer,
override val dispatchers: CoroutineDispatcherProvider,
diff --git a/features/markets/impl/src/main/kotlin/com/tangem/features/markets/portfolio/api/MarketsPortfolioComponent.kt b/features/markets/impl/src/main/kotlin/com/tangem/features/markets/portfolio/api/MarketsPortfolioComponent.kt
new file mode 100644
index 0000000000..c4217765dd
--- /dev/null
+++ b/features/markets/impl/src/main/kotlin/com/tangem/features/markets/portfolio/api/MarketsPortfolioComponent.kt
@@ -0,0 +1,17 @@
+package com.tangem.features.markets.portfolio.api
+
+import androidx.compose.runtime.Stable
+import com.tangem.core.decompose.factory.ComponentFactory
+import com.tangem.core.ui.decompose.ComposableContentComponent
+import kotlinx.serialization.Serializable
+
+@Stable
+interface MarketsPortfolioComponent : ComposableContentComponent {
+
+ @Serializable
+ data class Params(
+ val tokenId: String,
+ )
+
+ interface Factory : ComponentFactory
+}
\ No newline at end of file
diff --git a/features/markets/impl/src/main/kotlin/com/tangem/features/markets/portfolio/impl/DefaultMarketsPortfolioComponent.kt b/features/markets/impl/src/main/kotlin/com/tangem/features/markets/portfolio/impl/DefaultMarketsPortfolioComponent.kt
new file mode 100644
index 0000000000..9b732e6e42
--- /dev/null
+++ b/features/markets/impl/src/main/kotlin/com/tangem/features/markets/portfolio/impl/DefaultMarketsPortfolioComponent.kt
@@ -0,0 +1,39 @@
+package com.tangem.features.markets.portfolio.impl
+
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.Stable
+import androidx.compose.ui.Modifier
+import com.tangem.core.decompose.context.AppComponentContext
+import com.tangem.core.decompose.model.getOrCreateModel
+import com.tangem.features.markets.portfolio.api.MarketsPortfolioComponent
+import com.tangem.features.markets.portfolio.impl.model.MarketsPortfolioModel
+import com.tangem.features.markets.portfolio.impl.ui.MyPortfolio
+import com.tangem.features.markets.portfolio.impl.ui.state.MyPortfolioUM
+import dagger.assisted.Assisted
+import dagger.assisted.AssistedFactory
+import dagger.assisted.AssistedInject
+
+@Stable
+internal class DefaultMarketsPortfolioComponent @AssistedInject constructor(
+ @Assisted context: AppComponentContext,
+ @Assisted private val params: MarketsPortfolioComponent.Params,
+) : AppComponentContext by context, MarketsPortfolioComponent {
+
+ private val model: MarketsPortfolioModel = getOrCreateModel(params)
+
+ @Composable
+ override fun Content(modifier: Modifier) {
+ MyPortfolio(
+ modifier = modifier,
+ state = MyPortfolioUM.Loading,
+ )
+ }
+
+ @AssistedFactory
+ interface Factory : MarketsPortfolioComponent.Factory {
+ override fun create(
+ context: AppComponentContext,
+ params: MarketsPortfolioComponent.Params,
+ ): DefaultMarketsPortfolioComponent
+ }
+}
\ No newline at end of file
diff --git a/features/markets/impl/src/main/kotlin/com/tangem/features/markets/portfolio/impl/di/ComponentModule.kt b/features/markets/impl/src/main/kotlin/com/tangem/features/markets/portfolio/impl/di/ComponentModule.kt
new file mode 100644
index 0000000000..d011fbf799
--- /dev/null
+++ b/features/markets/impl/src/main/kotlin/com/tangem/features/markets/portfolio/impl/di/ComponentModule.kt
@@ -0,0 +1,20 @@
+package com.tangem.features.markets.portfolio.impl.di
+
+import com.tangem.features.markets.portfolio.api.MarketsPortfolioComponent
+import com.tangem.features.markets.portfolio.impl.DefaultMarketsPortfolioComponent
+import dagger.Binds
+import dagger.Module
+import dagger.hilt.InstallIn
+import dagger.hilt.components.SingletonComponent
+import javax.inject.Singleton
+
+@Module
+@InstallIn(SingletonComponent::class)
+internal interface ComponentModule {
+
+ @Binds
+ @Singleton
+ fun bindMarketsPortfolioComponent(
+ factory: DefaultMarketsPortfolioComponent.Factory,
+ ): MarketsPortfolioComponent.Factory
+}
\ No newline at end of file
diff --git a/features/markets/impl/src/main/kotlin/com/tangem/features/markets/portfolio/impl/di/ModelModule.kt b/features/markets/impl/src/main/kotlin/com/tangem/features/markets/portfolio/impl/di/ModelModule.kt
new file mode 100644
index 0000000000..38ea8f8688
--- /dev/null
+++ b/features/markets/impl/src/main/kotlin/com/tangem/features/markets/portfolio/impl/di/ModelModule.kt
@@ -0,0 +1,20 @@
+package com.tangem.features.markets.portfolio.impl.di
+
+import com.tangem.core.decompose.di.DecomposeComponent
+import com.tangem.core.decompose.model.Model
+import com.tangem.features.markets.portfolio.impl.model.MarketsPortfolioModel
+import dagger.Binds
+import dagger.Module
+import dagger.hilt.InstallIn
+import dagger.multibindings.ClassKey
+import dagger.multibindings.IntoMap
+
+@Module
+@InstallIn(DecomposeComponent::class)
+internal interface ModelModule {
+
+ @Binds
+ @IntoMap
+ @ClassKey(MarketsPortfolioModel::class)
+ fun provideMarketsPortfolioModel(model: MarketsPortfolioModel): Model
+}
\ No newline at end of file
diff --git a/features/markets/impl/src/main/kotlin/com/tangem/features/markets/portfolio/impl/model/MarketsPortfolioModel.kt b/features/markets/impl/src/main/kotlin/com/tangem/features/markets/portfolio/impl/model/MarketsPortfolioModel.kt
new file mode 100644
index 0000000000..75baa3fd50
--- /dev/null
+++ b/features/markets/impl/src/main/kotlin/com/tangem/features/markets/portfolio/impl/model/MarketsPortfolioModel.kt
@@ -0,0 +1,20 @@
+package com.tangem.features.markets.portfolio.impl.model
+
+import androidx.compose.runtime.Stable
+import com.tangem.core.decompose.di.ComponentScoped
+import com.tangem.core.decompose.model.Model
+import com.tangem.core.decompose.model.ParamsContainer
+import com.tangem.features.markets.portfolio.api.MarketsPortfolioComponent
+import com.tangem.utils.coroutines.CoroutineDispatcherProvider
+import javax.inject.Inject
+
+@Stable
+@ComponentScoped
+internal class MarketsPortfolioModel @Inject constructor(
+ paramsContainer: ParamsContainer,
+ override val dispatchers: CoroutineDispatcherProvider,
+) : Model() {
+
+ @Suppress("UnusedPrivateMember")
+ private val params = paramsContainer.require()
+}
\ No newline at end of file
diff --git a/features/markets/impl/src/main/kotlin/com/tangem/features/markets/portfolio/impl/ui/AddToPortfolioBottomSheet.kt b/features/markets/impl/src/main/kotlin/com/tangem/features/markets/portfolio/impl/ui/AddToPortfolioBottomSheet.kt
new file mode 100644
index 0000000000..8a5a2ae45f
--- /dev/null
+++ b/features/markets/impl/src/main/kotlin/com/tangem/features/markets/portfolio/impl/ui/AddToPortfolioBottomSheet.kt
@@ -0,0 +1,240 @@
+package com.tangem.features.markets.portfolio.impl.ui
+
+import android.content.res.Configuration
+import androidx.compose.animation.AnimatedVisibility
+import androidx.compose.foundation.background
+import androidx.compose.foundation.layout.*
+import androidx.compose.foundation.rememberScrollState
+import androidx.compose.foundation.verticalScroll
+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.graphics.vector.ImageVector
+import androidx.compose.ui.res.stringResource
+import androidx.compose.ui.res.vectorResource
+import androidx.compose.ui.tooling.preview.Preview
+import androidx.compose.ui.tooling.preview.PreviewParameter
+import androidx.compose.ui.util.fastForEachIndexed
+import com.tangem.common.ui.userwallet.UserWalletItem
+import com.tangem.core.ui.components.PrimaryButtonIconEnd
+import com.tangem.core.ui.components.SpacerW12
+import com.tangem.core.ui.components.SpacerW6
+import com.tangem.core.ui.components.TangemSwitch
+import com.tangem.core.ui.components.block.information.InformationBlock
+import com.tangem.core.ui.components.bottomsheets.TangemBottomSheet
+import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfig
+import com.tangem.core.ui.components.currency.icon.CoinIcon
+import com.tangem.core.ui.components.rows.ArrowRow
+import com.tangem.core.ui.components.rows.BlockchainRow
+import com.tangem.core.ui.components.rows.model.BlockchainRowUM
+import com.tangem.core.ui.extensions.resourceReference
+import com.tangem.core.ui.res.TangemTheme
+import com.tangem.core.ui.res.TangemThemePreview
+import com.tangem.features.markets.impl.R
+import com.tangem.features.markets.portfolio.impl.ui.preview.PreviewAddToPortfolioBSContentProvider
+import com.tangem.features.markets.portfolio.impl.ui.state.AddToPortfolioBSContentUM
+import com.tangem.features.markets.portfolio.impl.ui.state.SelectNetworkUM
+
+@Composable
+internal fun AddToPortfolioBottomSheet(config: TangemBottomSheetConfig) {
+ TangemBottomSheet(
+ config = config,
+ containerColor = TangemTheme.colors.background.tertiary,
+ titleText = resourceReference(R.string.markets_add_to_portfolio_button),
+ ) {
+ Content(
+ modifier = Modifier
+ .fillMaxWidth()
+ .padding(bottom = TangemTheme.dimens.spacing16),
+ state = config.content as AddToPortfolioBSContentUM,
+ )
+ }
+}
+
+@Composable
+private fun Content(state: AddToPortfolioBSContentUM, modifier: Modifier = Modifier) {
+ Column(
+ modifier = modifier.padding(horizontal = TangemTheme.dimens.spacing16),
+ verticalArrangement = Arrangement.spacedBy(TangemTheme.dimens.spacing12),
+ ) {
+ UserWalletItem(state.selectedWallet)
+
+ NetworkSelection(
+ modifier = Modifier.fillMaxWidth(),
+ state = state.selectNetworkUM,
+ )
+
+ AnimatedVisibility(
+ visible = state.isScanCardNotificationVisible,
+ modifier = Modifier.fillMaxWidth(),
+ ) {
+ ScanWalletWarning(modifier = Modifier.fillMaxWidth())
+ }
+
+ PrimaryButtonIconEnd(
+ modifier = Modifier.fillMaxWidth(),
+ text = stringResource(R.string.common_continue),
+ iconResId = R.drawable.ic_tangem_24,
+ onClick = {},
+ )
+ }
+}
+
+@Suppress("LongMethod")
+@Composable
+private fun NetworkSelection(state: SelectNetworkUM, modifier: Modifier = Modifier) {
+ InformationBlock(
+ modifier = modifier,
+ title = {
+ Text(
+ text = stringResource(R.string.markets_select_network),
+ style = TangemTheme.typography.subtitle2,
+ color = TangemTheme.colors.text.tertiary,
+ )
+ },
+ ) {
+ Column(
+ modifier = Modifier
+ .verticalScroll(rememberScrollState()),
+ ) {
+ Row(
+ modifier = Modifier
+ .fillMaxWidth()
+ .padding(vertical = TangemTheme.dimens.spacing14),
+ verticalAlignment = Alignment.CenterVertically,
+ ) {
+ CoinIcon(
+ modifier = Modifier.size(TangemTheme.dimens.size36),
+ url = state.iconUrl,
+ alpha = 1f,
+ colorFilter = null,
+ fallbackResId = R.drawable.ic_custom_token_44,
+ )
+ SpacerW12()
+ Text(
+ modifier = Modifier
+ .align(Alignment.CenterVertically)
+ .alignByBaseline(),
+ text = state.tokenName,
+ style = TangemTheme.typography.body1,
+ color = TangemTheme.colors.text.primary1,
+ )
+ SpacerW6()
+ Text(
+ modifier = Modifier
+ .align(Alignment.CenterVertically)
+ .alignByBaseline(),
+ text = state.tokenCurrencySymbol,
+ style = TangemTheme.typography.body1,
+ color = TangemTheme.colors.text.tertiary,
+ )
+ }
+
+ state.networks.fastForEachIndexed { index, network ->
+ ArrowRow(
+ isLastItem = index == state.networks.lastIndex,
+ content = {
+ BlockchainRow(
+ modifier = Modifier.padding(
+ end = TangemTheme.dimens.spacing4,
+ ),
+ model = with(network) {
+ BlockchainRowUM(
+ name = name,
+ type = type,
+ iconResId = iconResId,
+ isMainNetwork = isMainNetwork,
+ isSelected = isSelected,
+ )
+ },
+ action = {
+ TangemSwitch(
+ checked = network.isSelected,
+ onCheckedChange = {
+ state.onNetworkSwitchClick(network, it)
+ },
+ )
+ },
+ )
+ },
+ )
+ }
+ }
+ }
+}
+
+@Composable
+private fun ScanWalletWarning(modifier: Modifier = Modifier) {
+ Row(
+ modifier = modifier
+ .background(
+ color = TangemTheme.colors.button.disabled,
+ shape = TangemTheme.shapes.roundedCornersXMedium,
+ )
+ .padding(TangemTheme.dimens.spacing12),
+ verticalAlignment = Alignment.CenterVertically,
+ horizontalArrangement = Arrangement.spacedBy(TangemTheme.dimens.spacing10),
+ ) {
+ Icon(
+ modifier = Modifier.requiredSize(TangemTheme.dimens.size20),
+ imageVector = ImageVector.vectorResource(R.drawable.ic_tangem_24),
+ contentDescription = null,
+ )
+ Text(
+ text = stringResource(R.string.markets_generate_addresses_notification),
+ style = TangemTheme.typography.caption2,
+ color = TangemTheme.colors.text.tertiary,
+ )
+ }
+}
+
+@Composable
+@Preview
+@Preview(uiMode = Configuration.UI_MODE_NIGHT_YES)
+private fun Preview(
+ @PreviewParameter(PreviewAddToPortfolioBSContentProvider::class) content: AddToPortfolioBSContentUM,
+) {
+ TangemThemePreview {
+ AddToPortfolioBottomSheet(
+ config = TangemBottomSheetConfig(
+ isShow = true,
+ content = content,
+ onDismissRequest = {},
+ ),
+ )
+ }
+}
+
+@Composable
+@Preview
+@Preview(uiMode = Configuration.UI_MODE_NIGHT_YES)
+private fun PreviewContent(
+ @PreviewParameter(PreviewAddToPortfolioBSContentProvider::class) content: AddToPortfolioBSContentUM,
+) {
+ TangemThemePreview {
+ Content(
+ modifier = Modifier
+ .background(TangemTheme.colors.background.tertiary)
+ .fillMaxWidth(),
+ state = content,
+ )
+ }
+}
+
+@Composable
+@Preview
+@Preview(uiMode = Configuration.UI_MODE_NIGHT_YES)
+private fun PreviewContentRtl(
+ @PreviewParameter(PreviewAddToPortfolioBSContentProvider::class) content: AddToPortfolioBSContentUM,
+) {
+ TangemThemePreview(rtl = true) {
+ Content(
+ modifier = Modifier
+ .background(TangemTheme.colors.background.tertiary)
+ .fillMaxWidth(),
+ state = content,
+ )
+ }
+}
\ No newline at end of file
diff --git a/features/markets/impl/src/main/kotlin/com/tangem/features/markets/portfolio/impl/ui/MyPortfolio.kt b/features/markets/impl/src/main/kotlin/com/tangem/features/markets/portfolio/impl/ui/MyPortfolio.kt
new file mode 100644
index 0000000000..30cbcb453f
--- /dev/null
+++ b/features/markets/impl/src/main/kotlin/com/tangem/features/markets/portfolio/impl/ui/MyPortfolio.kt
@@ -0,0 +1,174 @@
+package com.tangem.features.markets.portfolio.impl.ui
+
+import android.content.res.Configuration
+import androidx.compose.foundation.background
+import androidx.compose.foundation.layout.*
+import androidx.compose.foundation.shape.RoundedCornerShape
+import androidx.compose.material3.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.res.stringResource
+import androidx.compose.ui.tooling.preview.Preview
+import androidx.compose.ui.tooling.preview.PreviewParameter
+import androidx.compose.ui.unit.dp
+import androidx.compose.ui.util.fastForEachIndexed
+import com.tangem.core.ui.components.PrimaryButton
+import com.tangem.core.ui.components.SmallButtonShimmer
+import com.tangem.core.ui.components.TextShimmer
+import com.tangem.core.ui.components.block.information.InformationBlock
+import com.tangem.core.ui.components.buttons.SecondarySmallButton
+import com.tangem.core.ui.components.buttons.SmallButtonConfig
+import com.tangem.core.ui.components.buttons.common.TangemButtonIconPosition
+import com.tangem.core.ui.extensions.resourceReference
+import com.tangem.core.ui.res.TangemTheme
+import com.tangem.core.ui.res.TangemThemePreview
+import com.tangem.features.markets.impl.R
+import com.tangem.features.markets.portfolio.impl.ui.preview.PreviewMyPortfolioUMProvider
+import com.tangem.features.markets.portfolio.impl.ui.state.MyPortfolioUM
+
+@Composable
+internal fun MyPortfolio(state: MyPortfolioUM, modifier: Modifier = Modifier) {
+ InformationBlock(
+ modifier = modifier,
+ contentHorizontalPadding = 0.dp,
+ title = {
+ Text(
+ text = stringResource(R.string.markets_common_my_portfolio),
+ style = TangemTheme.typography.subtitle2,
+ color = TangemTheme.colors.text.tertiary,
+ )
+ },
+ action = {
+ if (state !is MyPortfolioUM.Tokens) return@InformationBlock
+
+ when (state.buttonState) {
+ MyPortfolioUM.Tokens.AddButtonState.Loading -> {
+ SmallButtonShimmer(
+ modifier = Modifier.size(width = 63.dp, height = TangemTheme.dimens.size18),
+ shape = RoundedCornerShape(TangemTheme.dimens.radius3),
+ )
+ }
+ else -> {
+ SecondarySmallButton(
+ config = SmallButtonConfig(
+ text = resourceReference(R.string.markets_add_token),
+ icon = TangemButtonIconPosition.Start(R.drawable.ic_plus_24),
+ onClick = state.onAddClick,
+ enabled = state.buttonState == MyPortfolioUM.Tokens.AddButtonState.Available,
+ ),
+ )
+ }
+ }
+ },
+ ) {
+ when (state) {
+ is MyPortfolioUM.Tokens -> TokenList(state = state)
+ is MyPortfolioUM.AddFirstToken -> AddFirstTokenContent(state = state)
+ MyPortfolioUM.Loading -> LoadingPlaceholder()
+ MyPortfolioUM.Unavailable -> UnavailableContent()
+ }
+ }
+}
+
+@Composable
+private fun TokenList(state: MyPortfolioUM.Tokens, modifier: Modifier = Modifier) {
+ Column(modifier) {
+ state.tokens.fastForEachIndexed { index, token ->
+ PortfolioItem(
+ state = token,
+ lastInList = index == state.tokens.size - 1,
+ )
+ }
+ }
+}
+
+@Composable
+private fun UnavailableContent(modifier: Modifier = Modifier) {
+ Text(
+ modifier = modifier
+ .padding(
+ start = TangemTheme.dimens.spacing12,
+ end = TangemTheme.dimens.spacing12,
+ bottom = TangemTheme.dimens.spacing12,
+ ),
+ text = stringResource(R.string.markets_add_to_my_portfolio_unavailable_description),
+ style = TangemTheme.typography.body2,
+ color = TangemTheme.colors.text.tertiary,
+ )
+}
+
+@Composable
+private fun AddFirstTokenContent(state: MyPortfolioUM.AddFirstToken, modifier: Modifier = Modifier) {
+ Column(
+ modifier = modifier
+ .padding(
+ start = TangemTheme.dimens.spacing12,
+ end = TangemTheme.dimens.spacing12,
+ bottom = TangemTheme.dimens.spacing12,
+ ),
+ verticalArrangement = Arrangement.spacedBy(TangemTheme.dimens.spacing12),
+ ) {
+ Text(
+ text = "To start buying, exchanging or receiving this asset, add this token to at least 1 network",
+ style = TangemTheme.typography.body2,
+ color = TangemTheme.colors.text.tertiary,
+ )
+ PrimaryButton(
+ modifier = Modifier.fillMaxWidth(),
+ text = stringResource(R.string.markets_add_to_portfolio_button),
+ onClick = state.onAddClick,
+ )
+ }
+}
+
+@Composable
+private fun LoadingPlaceholder(modifier: Modifier = Modifier) {
+ Column(
+ modifier = modifier
+ .padding(
+ start = TangemTheme.dimens.spacing12,
+ end = TangemTheme.dimens.spacing12,
+ bottom = TangemTheme.dimens.spacing12,
+ ),
+ ) {
+ TextShimmer(
+ modifier = Modifier.fillMaxWidth(),
+ style = TangemTheme.typography.body2,
+ textSizeHeight = true,
+ )
+ TextShimmer(
+ modifier = Modifier.fillMaxWidth(fraction = 0.7f),
+ style = TangemTheme.typography.body2,
+ textSizeHeight = true,
+ )
+ }
+}
+
+@Preview
+@Preview(uiMode = Configuration.UI_MODE_NIGHT_YES)
+@Composable
+private fun Preview(@PreviewParameter(PreviewMyPortfolioUMProvider::class) state: MyPortfolioUM) {
+ TangemThemePreview {
+ Box(
+ modifier = Modifier
+ .background(TangemTheme.colors.background.tertiary)
+ .padding(TangemTheme.dimens.spacing8),
+ ) {
+ MyPortfolio(state)
+ }
+ }
+}
+
+@Preview
+@Composable
+private fun PreviewRtl(@PreviewParameter(PreviewMyPortfolioUMProvider::class) state: MyPortfolioUM) {
+ TangemThemePreview(rtl = true) {
+ Box(
+ modifier = Modifier
+ .background(TangemTheme.colors.background.tertiary)
+ .padding(TangemTheme.dimens.spacing8),
+ ) {
+ MyPortfolio(state)
+ }
+ }
+}
\ No newline at end of file
diff --git a/features/markets/impl/src/main/kotlin/com/tangem/features/markets/portfolio/impl/ui/PortfolioItem.kt b/features/markets/impl/src/main/kotlin/com/tangem/features/markets/portfolio/impl/ui/PortfolioItem.kt
new file mode 100644
index 0000000000..3a98293707
--- /dev/null
+++ b/features/markets/impl/src/main/kotlin/com/tangem/features/markets/portfolio/impl/ui/PortfolioItem.kt
@@ -0,0 +1,292 @@
+package com.tangem.features.markets.portfolio.impl.ui
+
+import android.content.res.Configuration
+import androidx.compose.foundation.ExperimentalFoundationApi
+import androidx.compose.foundation.background
+import androidx.compose.foundation.combinedClickable
+import androidx.compose.foundation.layout.*
+import androidx.compose.material3.Text
+import androidx.compose.runtime.*
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.draw.clip
+import androidx.compose.ui.tooling.preview.Preview
+import com.tangem.core.ui.components.TextShimmer
+import com.tangem.core.ui.components.currency.icon.CoinIcon
+import com.tangem.core.ui.extensions.resolveReference
+import com.tangem.core.ui.extensions.stringReference
+import com.tangem.core.ui.haptic.TangemHapticEffect
+import com.tangem.core.ui.res.LocalHapticManager
+import com.tangem.core.ui.res.TangemTheme
+import com.tangem.core.ui.res.TangemThemePreview
+import com.tangem.features.markets.impl.R
+import com.tangem.features.markets.portfolio.impl.ui.preview.PreviewMyPortfolioUMProvider
+import com.tangem.features.markets.portfolio.impl.ui.state.PortfolioTokenUM
+import com.tangem.utils.StringsSigns
+
+// TODO add rest of the balance states ([REDACTED_TASK_KEY] [Markets] Portfolio token item UI Improvement)
+@OptIn(ExperimentalFoundationApi::class)
+@Composable
+internal fun PortfolioItem(state: PortfolioTokenUM, lastInList: Boolean, modifier: Modifier = Modifier) {
+ val hapticManager = LocalHapticManager.current
+
+ Column(modifier) {
+ Row(
+ modifier = Modifier
+ .combinedClickable(
+ onClick = {
+ hapticManager.perform(TangemHapticEffect.View.ContextClick)
+ state.onClick()
+ },
+ onLongClick = {
+ hapticManager.perform(TangemHapticEffect.View.LongPress)
+ state.onLongTap()
+ },
+ )
+ .clip(TangemTheme.shapes.roundedCornersXMedium)
+ .padding(
+ vertical = TangemTheme.dimens.spacing15,
+ horizontal = TangemTheme.dimens.spacing12,
+ ),
+ horizontalArrangement = Arrangement.spacedBy(TangemTheme.dimens.spacing12),
+ verticalAlignment = Alignment.CenterVertically,
+ ) {
+ Content(state)
+ }
+
+ PortfolioQuickActions(
+ modifier = Modifier.padding(
+ bottom = if (lastInList) {
+ TangemTheme.dimens.spacing12
+ } else {
+ TangemTheme.dimens.spacing24
+ },
+ ),
+ isVisible = state.isQuickActionsShown,
+ onActionClick = state.onQuickActionClick,
+ )
+ }
+}
+
+@Composable
+private fun RowScope.Content(state: PortfolioTokenUM) {
+ // TODO add custom token
+ CoinIcon(
+ modifier = Modifier.size(TangemTheme.dimens.size36),
+ url = state.iconUrl,
+ alpha = 1f, // TODO add disabled state
+ colorFilter = null,
+ fallbackResId = R.drawable.ic_custom_token_44,
+ )
+
+ Column(
+ modifier = Modifier.align(Alignment.CenterVertically),
+ verticalArrangement = Arrangement.spacedBy(TangemTheme.dimens.spacing2),
+ ) {
+ when (state.balanceContent) {
+ is PortfolioTokenUM.BalanceContent.Disabled -> {
+ Disabled(
+ state = state,
+ disabledText = state.balanceContent.text.resolveReference(),
+ )
+ }
+ PortfolioTokenUM.BalanceContent.Loading -> {
+ Loading(state = state)
+ }
+ is PortfolioTokenUM.BalanceContent.TokenBalance -> {
+ TokenBalance(
+ state = state,
+ content = state.balanceContent,
+ )
+ }
+ }
+ }
+}
+
+@Composable
+private fun ColumnScope.TokenBalance(state: PortfolioTokenUM, content: PortfolioTokenUM.BalanceContent.TokenBalance) {
+ val balance = if (content.hidden) {
+ StringsSigns.STARS
+ } else {
+ content.balance
+ }
+ val tokenAmount = if (content.hidden) {
+ StringsSigns.STARS
+ } else {
+ content.tokenAmount
+ }
+
+ Row(
+ modifier = Modifier.fillMaxWidth(),
+ horizontalArrangement = Arrangement.SpaceBetween,
+ ) {
+ Text(
+ modifier = Modifier.alignByBaseline(),
+ text = state.title,
+ style = TangemTheme.typography.body2,
+ color = TangemTheme.colors.text.primary1,
+ )
+ Text(
+ modifier = Modifier.alignByBaseline(),
+ text = balance,
+ style = TangemTheme.typography.body2,
+ color = TangemTheme.colors.text.primary1,
+ )
+ }
+ Row(
+ modifier = Modifier.fillMaxWidth(),
+ horizontalArrangement = Arrangement.SpaceBetween,
+ ) {
+ Text(
+ text = state.subtitle,
+ style = TangemTheme.typography.caption2,
+ color = TangemTheme.colors.text.tertiary,
+ )
+ Text(
+ text = tokenAmount,
+ style = TangemTheme.typography.caption2,
+ color = TangemTheme.colors.text.tertiary,
+ )
+ }
+}
+
+@Composable
+private fun ColumnScope.Loading(state: PortfolioTokenUM) {
+ Row(
+ modifier = Modifier.fillMaxWidth(),
+ horizontalArrangement = Arrangement.SpaceBetween,
+ ) {
+ Text(
+ modifier = Modifier.alignByBaseline(),
+ text = state.title,
+ style = TangemTheme.typography.body2,
+ color = TangemTheme.colors.text.primary1,
+ )
+ TextShimmer(
+ modifier = Modifier
+ .width(TangemTheme.dimens.size40)
+ .alignByBaseline(),
+ style = TangemTheme.typography.body2,
+ textSizeHeight = true,
+ )
+ }
+
+ Row(
+ modifier = Modifier.fillMaxWidth(),
+ horizontalArrangement = Arrangement.SpaceBetween,
+ ) {
+ Text(
+ text = state.subtitle,
+ style = TangemTheme.typography.caption2,
+ color = TangemTheme.colors.text.tertiary,
+ )
+ TextShimmer(
+ modifier = Modifier
+ .width(TangemTheme.dimens.size40)
+ .alignByBaseline(),
+ style = TangemTheme.typography.caption2,
+ textSizeHeight = true,
+ )
+ }
+}
+
+@Composable
+private fun Disabled(state: PortfolioTokenUM, disabledText: String, modifier: Modifier = Modifier) {
+ Row(
+ modifier = modifier,
+ horizontalArrangement = Arrangement.spacedBy(TangemTheme.dimens.spacing4),
+ verticalAlignment = Alignment.CenterVertically,
+ ) {
+ Column(
+ Modifier.weight(1f),
+ ) {
+ Text(
+ text = state.title,
+ style = TangemTheme.typography.body2,
+ color = TangemTheme.colors.text.primary1,
+ )
+ Text(
+ text = state.subtitle,
+ style = TangemTheme.typography.caption2,
+ color = TangemTheme.colors.text.tertiary,
+ )
+ }
+
+ Text(
+ text = disabledText,
+ style = TangemTheme.typography.caption2,
+ color = TangemTheme.colors.text.tertiary,
+ )
+ }
+}
+
+@Preview
+@Preview(uiMode = Configuration.UI_MODE_NIGHT_YES)
+@Composable
+private fun Preview() {
+ TangemThemePreview {
+ var quickActionsShown by remember { mutableStateOf(false) }
+ var quickActionsShown2 by remember { mutableStateOf(false) }
+ val sampleToken = PreviewMyPortfolioUMProvider().sampleToken
+
+ Box(
+ Modifier
+ .fillMaxSize()
+ .background(TangemTheme.colors.background.primary),
+ ) {
+ Column {
+ PortfolioItem(
+ state = sampleToken
+ .copy(
+ onClick = {
+ if (quickActionsShown2) {
+ quickActionsShown2 = false
+ }
+ quickActionsShown = quickActionsShown.not()
+ },
+ isQuickActionsShown = quickActionsShown,
+ ),
+ lastInList = true,
+ )
+ PortfolioItem(
+ state = sampleToken
+ .copy(
+ onClick = {
+ if (quickActionsShown) {
+ quickActionsShown = false
+ }
+ quickActionsShown2 = quickActionsShown2.not()
+ },
+ isQuickActionsShown = quickActionsShown2,
+ ),
+ lastInList = true,
+ )
+ PortfolioItem(
+ state = sampleToken
+ .copy(
+ balanceContent = (
+ sampleToken.balanceContent
+ as PortfolioTokenUM.BalanceContent.TokenBalance
+ )
+ .copy(hidden = true),
+ ),
+ lastInList = true,
+ )
+ PortfolioItem(
+ state = sampleToken
+ .copy(
+ balanceContent = PortfolioTokenUM.BalanceContent.Disabled(
+ stringReference("No Address"),
+ ),
+ ),
+ lastInList = true,
+ )
+ PortfolioItem(
+ state = sampleToken
+ .copy(balanceContent = PortfolioTokenUM.BalanceContent.Loading),
+ lastInList = true,
+ )
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/features/markets/impl/src/main/kotlin/com/tangem/features/markets/portfolio/impl/ui/PortfolioQuickActions.kt b/features/markets/impl/src/main/kotlin/com/tangem/features/markets/portfolio/impl/ui/PortfolioQuickActions.kt
new file mode 100644
index 0000000000..1af8cde8ce
--- /dev/null
+++ b/features/markets/impl/src/main/kotlin/com/tangem/features/markets/portfolio/impl/ui/PortfolioQuickActions.kt
@@ -0,0 +1,214 @@
+package com.tangem.features.markets.portfolio.impl.ui
+
+import android.content.res.Configuration
+import androidx.compose.animation.*
+import androidx.compose.animation.core.Spring
+import androidx.compose.animation.core.spring
+import androidx.compose.foundation.*
+import androidx.compose.foundation.layout.*
+import androidx.compose.foundation.shape.CircleShape
+import androidx.compose.material3.Button
+import androidx.compose.material3.Icon
+import androidx.compose.material3.Text
+import androidx.compose.runtime.*
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.draw.clip
+import androidx.compose.ui.geometry.Offset
+import androidx.compose.ui.graphics.vector.ImageVector
+import androidx.compose.ui.platform.LocalLayoutDirection
+import androidx.compose.ui.res.vectorResource
+import androidx.compose.ui.tooling.preview.Preview
+import androidx.compose.ui.unit.LayoutDirection
+import androidx.compose.ui.unit.dp
+import com.tangem.core.ui.components.SpacerH4
+import com.tangem.core.ui.extensions.resolveReference
+import com.tangem.core.ui.haptic.TangemHapticEffect
+import com.tangem.core.ui.res.LocalHapticManager
+import com.tangem.core.ui.res.TangemTheme
+import com.tangem.core.ui.res.TangemThemePreview
+import com.tangem.features.markets.portfolio.impl.ui.state.QuickActionUM
+
+@Composable
+internal fun PortfolioQuickActions(
+ isVisible: Boolean,
+ onActionClick: (QuickActionUM) -> Unit,
+ modifier: Modifier = Modifier,
+) {
+ AnimatedVisibility(
+ visible = isVisible,
+ enter = expandVertically(expandFrom = Alignment.Top),
+ exit = shrinkVertically(shrinkTowards = Alignment.Top),
+ ) {
+ Column(modifier = modifier) {
+ LineSeparator()
+ QuickActionItem(
+ state = QuickActionUM.Buy,
+ onClick = { onActionClick(QuickActionUM.Buy) },
+ )
+ LineSeparator()
+ QuickActionItem(
+ state = QuickActionUM.Exchange,
+ onClick = { onActionClick(QuickActionUM.Exchange) },
+ )
+ LineSeparator()
+ QuickActionItem(
+ state = QuickActionUM.Receive,
+ onClick = { onActionClick(QuickActionUM.Receive) },
+ )
+ }
+ }
+}
+
+@OptIn(ExperimentalAnimationApi::class)
+@Composable
+private fun AnimatedVisibilityScope.LineSeparator(modifier: Modifier = Modifier) {
+ val lineColor = TangemTheme.colors.stroke.primary
+ val strokeWidth = TangemTheme.dimens.size1
+ val isLtr = LocalLayoutDirection.current == LayoutDirection.Ltr
+ val verticalPadding = TangemTheme.dimens.spacing2
+ val startPadding = TangemTheme.dimens.spacing28
+
+ val height = TangemTheme.dimens.size16 + verticalPadding * 2
+
+ Canvas(
+ modifier = modifier
+ .animateEnterExit(
+ enter = expandVertically(
+ animationSpec = spring(
+ stiffness = Spring.StiffnessLow,
+ ),
+ expandFrom = Alignment.Top,
+ ) + fadeIn(),
+ exit = shrinkVertically(
+ spring(
+ stiffness = Spring.StiffnessLow,
+ ),
+ shrinkTowards = Alignment.Top,
+ ) + fadeOut(),
+ )
+ .fillMaxWidth()
+ .height(height),
+ ) {
+ val x = if (isLtr) startPadding.toPx() else size.width - startPadding.toPx()
+
+ drawLine(
+ color = lineColor,
+ start = Offset(x, verticalPadding.toPx()),
+ end = Offset(x, size.height - verticalPadding.toPx()),
+ strokeWidth = strokeWidth.toPx(),
+ )
+ }
+}
+
+@OptIn(ExperimentalAnimationApi::class)
+@Composable
+private fun AnimatedVisibilityScope.QuickActionItem(
+ state: QuickActionUM,
+ onClick: () -> Unit,
+ modifier: Modifier = Modifier,
+) {
+ val hapticManager = LocalHapticManager.current
+
+ Row(
+ modifier = modifier
+ .fillMaxWidth()
+ .clip(TangemTheme.shapes.roundedCornersMedium)
+ .clickable {
+ hapticManager.perform(TangemHapticEffect.View.SegmentTick)
+ onClick()
+ }
+ .padding(
+ vertical = TangemTheme.dimens.spacing2,
+ horizontal = TangemTheme.dimens.spacing12,
+ ),
+ verticalAlignment = Alignment.CenterVertically,
+ horizontalArrangement = Arrangement.spacedBy(TangemTheme.dimens.spacing18),
+ ) {
+ Box(
+ Modifier
+ .animateEnterExit(
+ enter = scaleIn(),
+ exit = scaleOut(),
+ )
+ .background(
+ color = TangemTheme.colors.button.secondary,
+ shape = CircleShape,
+ )
+ .size(TangemTheme.dimens.size32),
+ contentAlignment = Alignment.Center,
+ ) {
+ Icon(
+ modifier = Modifier
+ .requiredSize(TangemTheme.dimens.size16),
+ imageVector = ImageVector.vectorResource(id = state.icon),
+ contentDescription = null,
+ tint = TangemTheme.colors.button.primary,
+ )
+ }
+ Column(
+ modifier = Modifier
+ .animateEnterExit(
+ enter = fadeIn(),
+ exit = fadeOut(),
+ ),
+ verticalArrangement = Arrangement.spacedBy(TangemTheme.dimens.spacing2),
+ ) {
+ Text(
+ text = state.title.resolveReference(),
+ style = TangemTheme.typography.body2,
+ color = TangemTheme.colors.text.primary1,
+ )
+ Text(
+ text = state.description.resolveReference(),
+ style = TangemTheme.typography.caption2,
+ color = TangemTheme.colors.text.tertiary,
+ )
+ }
+ }
+}
+
+@Preview
+@Preview(uiMode = Configuration.UI_MODE_NIGHT_YES)
+@Composable
+private fun Preview() {
+ TangemThemePreview {
+ var isVisible by remember { mutableStateOf(true) }
+
+ Column(
+ modifier = Modifier
+ .fillMaxWidth()
+ .height(680.dp),
+ ) {
+ Button(
+ onClick = { isVisible = !isVisible },
+ modifier = Modifier.padding(TangemTheme.dimens.spacing12),
+ ) {
+ Text(text = "Toggle")
+ }
+ SpacerH4()
+ Box(
+ modifier = Modifier.background(color = TangemTheme.colors.background.action),
+ ) {
+ PortfolioQuickActions(
+ isVisible = isVisible,
+ onActionClick = {},
+ )
+ }
+ }
+ }
+}
+
+@Preview
+@Preview(uiMode = Configuration.UI_MODE_NIGHT_YES)
+@Composable
+private fun PreviewRtl() {
+ TangemThemePreview(rtl = true) {
+ Box(modifier = Modifier.background(color = TangemTheme.colors.background.action)) {
+ PortfolioQuickActions(
+ isVisible = true,
+ onActionClick = {},
+ )
+ }
+ }
+}
\ No newline at end of file
diff --git a/features/markets/impl/src/main/kotlin/com/tangem/features/markets/portfolio/impl/ui/TokenActionsBottomSheet.kt b/features/markets/impl/src/main/kotlin/com/tangem/features/markets/portfolio/impl/ui/TokenActionsBottomSheet.kt
new file mode 100644
index 0000000000..cfc6c814d1
--- /dev/null
+++ b/features/markets/impl/src/main/kotlin/com/tangem/features/markets/portfolio/impl/ui/TokenActionsBottomSheet.kt
@@ -0,0 +1,90 @@
+package com.tangem.features.markets.portfolio.impl.ui
+
+import android.content.res.Configuration
+import androidx.compose.foundation.background
+import androidx.compose.foundation.clickable
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.padding
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.draw.clip
+import androidx.compose.ui.tooling.preview.Preview
+import com.tangem.core.ui.components.bottomsheets.TangemBottomSheet
+import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfig
+import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetTitle
+import com.tangem.core.ui.components.inputrow.InputRowChecked
+import com.tangem.core.ui.components.inputrow.inner.DividerContainer
+import com.tangem.core.ui.components.rows.CornersToRound
+import com.tangem.core.ui.res.TangemTheme
+import com.tangem.core.ui.res.TangemThemePreview
+import com.tangem.features.markets.portfolio.impl.ui.state.TokenActionsBSContent
+import kotlinx.collections.immutable.toImmutableList
+
+@Composable
+fun TokenActionsBottomSheet(config: TangemBottomSheetConfig) {
+ TangemBottomSheet(
+ config = config,
+ title = { content ->
+ TangemBottomSheetTitle(content.title)
+ },
+ containerColor = TangemTheme.colors.background.tertiary,
+ content = { Content(it) },
+ )
+}
+
+@Composable
+private fun Content(content: TokenActionsBSContent) {
+ Column(
+ modifier = Modifier
+ .padding(
+ start = TangemTheme.dimens.spacing16,
+ end = TangemTheme.dimens.spacing16,
+ bottom = TangemTheme.dimens.spacing16,
+ ),
+ ) {
+ content.actions.forEachIndexed { index, action ->
+ val cornersToRound = when (index) {
+ 0 -> CornersToRound.TOP_2
+ content.actions.lastIndex -> CornersToRound.BOTTOM_2
+ else -> CornersToRound.ZERO
+ }
+
+ DividerContainer(
+ modifier = Modifier
+ .clip(cornersToRound.getShape())
+ .background(TangemTheme.colors.background.action)
+ .clickable { content.onActionClick(action) },
+ showDivider = index != content.actions.lastIndex,
+ ) {
+ InputRowChecked(
+ text = action.text,
+ checked = false,
+ )
+ }
+ }
+ }
+}
+
+@Preview(widthDp = 360, heightDp = 640)
+@Preview(widthDp = 360, heightDp = 640, uiMode = Configuration.UI_MODE_NIGHT_YES)
+@Composable
+private fun Preview() {
+ TangemThemePreview(
+ alwaysShowBottomSheets = true,
+ ) {
+ Box(Modifier.background(TangemTheme.colors.background.secondary)) {
+ TokenActionsBottomSheet(
+ TangemBottomSheetConfig(
+ isShow = true,
+ onDismissRequest = {},
+ content = TokenActionsBSContent(
+ title = "Wallet 1",
+ actions = TokenActionsBSContent.Action.entries.toImmutableList(),
+ onActionClick = {},
+ ),
+ ),
+ )
+ }
+ }
+}
\ No newline at end of file
diff --git a/features/markets/impl/src/main/kotlin/com/tangem/features/markets/portfolio/impl/ui/preview/PreviewAddToPortfolioBSContentProvider.kt b/features/markets/impl/src/main/kotlin/com/tangem/features/markets/portfolio/impl/ui/preview/PreviewAddToPortfolioBSContentProvider.kt
new file mode 100644
index 0000000000..2b33545fd3
--- /dev/null
+++ b/features/markets/impl/src/main/kotlin/com/tangem/features/markets/portfolio/impl/ui/preview/PreviewAddToPortfolioBSContentProvider.kt
@@ -0,0 +1,60 @@
+package com.tangem.features.markets.portfolio.impl.ui.preview
+
+import androidx.compose.ui.tooling.preview.PreviewParameterProvider
+import com.tangem.common.ui.userwallet.state.UserWalletItemUM
+import com.tangem.core.ui.components.rows.model.BlockchainRowUM
+import com.tangem.core.ui.extensions.stringReference
+import com.tangem.domain.wallets.models.UserWalletId
+import com.tangem.features.markets.impl.R
+import com.tangem.features.markets.portfolio.impl.ui.state.AddToPortfolioBSContentUM
+import com.tangem.features.markets.portfolio.impl.ui.state.SelectNetworkUM
+import kotlinx.collections.immutable.persistentListOf
+
+internal class PreviewAddToPortfolioBSContentProvider : PreviewParameterProvider {
+
+ override val values: Sequence
+ get() = sequenceOf(
+ AddToPortfolioBSContentUM(
+ selectedWallet = UserWalletItemUM(
+ id = UserWalletId("1"),
+ name = stringReference("Wallet 1"),
+ information = stringReference("3 cards, 10,123$"),
+ imageUrl = "",
+ isEnabled = true,
+ endIcon = UserWalletItemUM.EndIcon.Arrow,
+ onClick = {},
+ ),
+ selectNetworkUM = SelectNetworkUM(
+ tokenId = "etherium",
+ tokenName = "Etherium",
+ tokenCurrencySymbol = "ETH",
+ networks = persistentListOf(
+ BlockchainRowUM(
+ name = "Etherium",
+ type = "MAIN",
+ iconResId = R.drawable.ic_eth_16,
+ isMainNetwork = true,
+ isSelected = true,
+ ),
+ BlockchainRowUM(
+ name = "Etherium 2",
+ type = "TEST",
+ iconResId = R.drawable.ic_eth_16,
+ isMainNetwork = false,
+ isSelected = false,
+ ),
+ BlockchainRowUM(
+ name = "Etherium 3",
+ type = "TEST",
+ iconResId = R.drawable.ic_eth_16,
+ isMainNetwork = false,
+ isSelected = false,
+ ),
+ ),
+ onNetworkSwitchClick = { _, _ -> },
+ iconUrl = null,
+ ),
+ isScanCardNotificationVisible = true,
+ ),
+ )
+}
\ No newline at end of file
diff --git a/features/markets/impl/src/main/kotlin/com/tangem/features/markets/portfolio/impl/ui/preview/PreviewMyPortfolioUMProvider.kt b/features/markets/impl/src/main/kotlin/com/tangem/features/markets/portfolio/impl/ui/preview/PreviewMyPortfolioUMProvider.kt
new file mode 100644
index 0000000000..b607386001
--- /dev/null
+++ b/features/markets/impl/src/main/kotlin/com/tangem/features/markets/portfolio/impl/ui/preview/PreviewMyPortfolioUMProvider.kt
@@ -0,0 +1,50 @@
+package com.tangem.features.markets.portfolio.impl.ui.preview
+
+import androidx.compose.ui.tooling.preview.PreviewParameterProvider
+import com.tangem.features.markets.portfolio.impl.ui.state.MyPortfolioUM
+import com.tangem.features.markets.portfolio.impl.ui.state.PortfolioTokenUM
+import kotlinx.collections.immutable.persistentListOf
+
+internal class PreviewMyPortfolioUMProvider : PreviewParameterProvider {
+
+ override val values: Sequence
+ get() = sequenceOf(
+ MyPortfolioUM.Tokens(
+ tokens = persistentListOf(sampleToken, sampleToken),
+ buttonState = MyPortfolioUM.Tokens.AddButtonState.Available,
+ onAddClick = {},
+ ),
+ MyPortfolioUM.Tokens(
+ tokens = persistentListOf(sampleToken, sampleToken.copy(isQuickActionsShown = true)),
+ buttonState = MyPortfolioUM.Tokens.AddButtonState.Unavailable,
+ onAddClick = {},
+ ),
+ MyPortfolioUM.Tokens(
+ tokens = persistentListOf(sampleToken.copy(isQuickActionsShown = true), sampleToken),
+ buttonState = MyPortfolioUM.Tokens.AddButtonState.Loading,
+ onAddClick = {},
+ ),
+ MyPortfolioUM.AddFirstToken(
+ onAddClick = {},
+ ),
+ MyPortfolioUM.Loading,
+ MyPortfolioUM.Unavailable,
+ )
+
+ val sampleToken = PortfolioTokenUM(
+ id = "",
+ networkId = "",
+ iconUrl = "",
+ balanceContent = PortfolioTokenUM.BalanceContent.TokenBalance(
+ balance = "486,65 \$",
+ tokenAmount = "733,71097 MATIC",
+ hidden = false,
+ ),
+ title = "My wallet",
+ subtitle = "XRP Ledger token",
+ onClick = {},
+ onLongTap = {},
+ isQuickActionsShown = false,
+ onQuickActionClick = {},
+ )
+}
\ No newline at end of file
diff --git a/features/markets/impl/src/main/kotlin/com/tangem/features/markets/portfolio/impl/ui/state/AddToPortfolioBSContentUM.kt b/features/markets/impl/src/main/kotlin/com/tangem/features/markets/portfolio/impl/ui/state/AddToPortfolioBSContentUM.kt
new file mode 100644
index 0000000000..ad733c39cf
--- /dev/null
+++ b/features/markets/impl/src/main/kotlin/com/tangem/features/markets/portfolio/impl/ui/state/AddToPortfolioBSContentUM.kt
@@ -0,0 +1,10 @@
+package com.tangem.features.markets.portfolio.impl.ui.state
+
+import com.tangem.common.ui.userwallet.state.UserWalletItemUM
+import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfigContent
+
+internal data class AddToPortfolioBSContentUM(
+ val selectedWallet: UserWalletItemUM,
+ val selectNetworkUM: SelectNetworkUM,
+ val isScanCardNotificationVisible: Boolean,
+) : TangemBottomSheetConfigContent
\ No newline at end of file
diff --git a/features/markets/impl/src/main/kotlin/com/tangem/features/markets/portfolio/impl/ui/state/MyPortfolioUM.kt b/features/markets/impl/src/main/kotlin/com/tangem/features/markets/portfolio/impl/ui/state/MyPortfolioUM.kt
new file mode 100644
index 0000000000..7fa4c11a4a
--- /dev/null
+++ b/features/markets/impl/src/main/kotlin/com/tangem/features/markets/portfolio/impl/ui/state/MyPortfolioUM.kt
@@ -0,0 +1,29 @@
+package com.tangem.features.markets.portfolio.impl.ui.state
+
+import androidx.compose.runtime.Immutable
+import kotlinx.collections.immutable.ImmutableList
+
+@Immutable
+internal sealed class MyPortfolioUM {
+
+ data class Tokens(
+ val tokens: ImmutableList,
+ val buttonState: AddButtonState,
+ val onAddClick: () -> Unit,
+ ) : MyPortfolioUM() {
+
+ enum class AddButtonState {
+ Loading,
+ Available,
+ Unavailable,
+ }
+ }
+
+ data class AddFirstToken(
+ val onAddClick: () -> Unit,
+ ) : MyPortfolioUM()
+
+ data object Loading : MyPortfolioUM()
+
+ data object Unavailable : MyPortfolioUM()
+}
\ No newline at end of file
diff --git a/features/markets/impl/src/main/kotlin/com/tangem/features/markets/portfolio/impl/ui/state/PortfolioTokenUM.kt b/features/markets/impl/src/main/kotlin/com/tangem/features/markets/portfolio/impl/ui/state/PortfolioTokenUM.kt
new file mode 100644
index 0000000000..f7db3362b2
--- /dev/null
+++ b/features/markets/impl/src/main/kotlin/com/tangem/features/markets/portfolio/impl/ui/state/PortfolioTokenUM.kt
@@ -0,0 +1,34 @@
+package com.tangem.features.markets.portfolio.impl.ui.state
+
+import androidx.compose.runtime.Immutable
+import com.tangem.core.ui.extensions.TextReference
+
+internal data class PortfolioTokenUM(
+ val id: String,
+ val networkId: String,
+ val iconUrl: String,
+ val title: String,
+ val subtitle: String,
+ val balanceContent: BalanceContent,
+ val onClick: () -> Unit,
+ val onLongTap: () -> Unit,
+ val isQuickActionsShown: Boolean,
+ val onQuickActionClick: (QuickActionUM) -> Unit,
+) {
+
+ // TODO add rest of the balance states ([REDACTED_TASK_KEY] [Markets] Portfolio token item UI Improvement)
+ @Immutable
+ sealed class BalanceContent {
+ data class TokenBalance( // TODO Add stacking ([REDACTED_TASK_KEY] [Markets] Add staking info to portfolio token item)
+ val balance: String,
+ val tokenAmount: String,
+ val hidden: Boolean,
+ ) : BalanceContent()
+
+ data class Disabled(
+ val text: TextReference,
+ ) : BalanceContent()
+
+ data object Loading : BalanceContent()
+ }
+}
\ No newline at end of file
diff --git a/features/markets/impl/src/main/kotlin/com/tangem/features/markets/portfolio/impl/ui/state/QuickActionUM.kt b/features/markets/impl/src/main/kotlin/com/tangem/features/markets/portfolio/impl/ui/state/QuickActionUM.kt
new file mode 100644
index 0000000000..b079b5ce50
--- /dev/null
+++ b/features/markets/impl/src/main/kotlin/com/tangem/features/markets/portfolio/impl/ui/state/QuickActionUM.kt
@@ -0,0 +1,30 @@
+package com.tangem.features.markets.portfolio.impl.ui.state
+
+import androidx.annotation.DrawableRes
+import androidx.compose.runtime.Immutable
+import com.tangem.core.ui.extensions.TextReference
+import com.tangem.core.ui.extensions.resourceReference
+import com.tangem.features.markets.impl.R
+
+@Immutable
+internal enum class QuickActionUM(
+ val title: TextReference,
+ val description: TextReference,
+ @DrawableRes val icon: Int,
+) {
+ Buy(
+ title = resourceReference(R.string.common_buy),
+ description = resourceReference(R.string.buy_token_description),
+ icon = R.drawable.ic_plus_24,
+ ),
+ Exchange(
+ title = resourceReference(R.string.common_exchange),
+ description = resourceReference(R.string.exсhange_token_description),
+ icon = R.drawable.ic_exchange_vertical_24,
+ ),
+ Receive(
+ title = resourceReference(R.string.common_receive),
+ description = resourceReference(R.string.receive_token_description),
+ icon = R.drawable.ic_arrow_down_24,
+ ),
+}
\ No newline at end of file
diff --git a/features/markets/impl/src/main/kotlin/com/tangem/features/markets/portfolio/impl/ui/state/SelectNetworkUM.kt b/features/markets/impl/src/main/kotlin/com/tangem/features/markets/portfolio/impl/ui/state/SelectNetworkUM.kt
new file mode 100644
index 0000000000..90830679ca
--- /dev/null
+++ b/features/markets/impl/src/main/kotlin/com/tangem/features/markets/portfolio/impl/ui/state/SelectNetworkUM.kt
@@ -0,0 +1,13 @@
+package com.tangem.features.markets.portfolio.impl.ui.state
+
+import com.tangem.core.ui.components.rows.model.BlockchainRowUM
+import kotlinx.collections.immutable.ImmutableList
+
+internal data class SelectNetworkUM(
+ val tokenId: String,
+ val iconUrl: String?,
+ val tokenName: String,
+ val tokenCurrencySymbol: String,
+ val networks: ImmutableList,
+ val onNetworkSwitchClick: (BlockchainRowUM, Boolean) -> Unit,
+)
\ No newline at end of file
diff --git a/features/markets/impl/src/main/kotlin/com/tangem/features/markets/portfolio/impl/ui/state/TokenActionsBSContent.kt b/features/markets/impl/src/main/kotlin/com/tangem/features/markets/portfolio/impl/ui/state/TokenActionsBSContent.kt
new file mode 100644
index 0000000000..2252b38f70
--- /dev/null
+++ b/features/markets/impl/src/main/kotlin/com/tangem/features/markets/portfolio/impl/ui/state/TokenActionsBSContent.kt
@@ -0,0 +1,28 @@
+package com.tangem.features.markets.portfolio.impl.ui.state
+
+import androidx.compose.runtime.Immutable
+import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfigContent
+import com.tangem.core.ui.extensions.TextReference
+import com.tangem.core.ui.extensions.resourceReference
+import com.tangem.features.markets.impl.R
+import kotlinx.collections.immutable.ImmutableList
+
+internal data class TokenActionsBSContent(
+ val title: String,
+ val actions: ImmutableList,
+ val onActionClick: (Action) -> Unit,
+) : TangemBottomSheetConfigContent {
+
+ @Immutable
+ enum class Action(
+ val text: TextReference,
+ ) {
+ CopyAddress(text = resourceReference(R.string.common_copy_address)),
+ Receive(text = resourceReference(R.string.common_receive)),
+ Sell(text = resourceReference(R.string.common_sell)),
+ Buy(text = resourceReference(R.string.common_buy)),
+ Send(text = resourceReference(R.string.common_send)),
+ Exchange(text = resourceReference(R.string.common_exchange)),
+ Stake(text = resourceReference(R.string.common_stake)),
+ }
+}
\ No newline at end of file
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 517c795db6..3d80ba6721 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
@@ -363,8 +363,8 @@ internal class StateBuilder(
if (quoteModel.permissionState is PermissionDataState.PermissionLoading) {
warnings.add(
SwapWarning.TransactionInProgressWarning(
- title = resourceReference(R.string.warning_express_approval_in_progress_title),
- description = resourceReference(R.string.warning_express_approval_in_progress_message),
+ title = stringReference("//TODO"),
+ description = stringReference("//TODO"),
),
)
} else if (quoteModel.preparedSwapConfigState.hasOutgoingTransaction) {
@@ -1009,8 +1009,8 @@ internal class StateBuilder(
warnings.add(
0,
SwapWarning.TransactionInProgressWarning(
- title = resourceReference(R.string.warning_express_approval_in_progress_title),
- description = resourceReference(R.string.warning_express_approval_in_progress_message),
+ title = stringReference("//TODO"),
+ description = stringReference("//TODO"),
),
)
return uiState.copy(