Updated on 2026-08-14
This commit is contained in:
parent
e7bbd950dd
commit
2c29708284
45 changed files with 1776 additions and 175 deletions
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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))
|
||||
}
|
||||
|
|
@ -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,
|
||||
}
|
||||
}
|
||||
|
|
@ -835,6 +835,8 @@
|
|||
<string name="wallet_settings_title">Wallet-Einstellungen</string>
|
||||
<string name="wallet_title">Tangem</string>
|
||||
<string name="warning_access_denied_message">Verwende %s oder scanne eine Karte, um den Zugriff auf deine Wallet freizuschalten.</string>
|
||||
<string name="warning_approval_in_progress_message">Die Genehmigung des Swaps ist im Gange und wird in Kürze abgeschlossen sein.</string>
|
||||
<string name="warning_approval_in_progress_title">Genehmigung in Arbeit</string>
|
||||
<string name="warning_backup_errors_message">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.</string>
|
||||
<string name="warning_backup_errors_title">Aktivierungsfehler</string>
|
||||
<string name="warning_beacon_chain_retirement_content">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.</string>
|
||||
|
|
@ -852,8 +854,6 @@
|
|||
<string name="warning_existential_deposit_title">Netzwerk erfordert eine Mindesteinzahlung</string>
|
||||
<string name="warning_express_active_transaction_message">Der Swap wird nach Abschluss der Transaktion %s verfügbar sein.</string>
|
||||
<string name="warning_express_active_transaction_title">Du hast aktive Transaktion</string>
|
||||
<string name="warning_express_approval_in_progress_message">Die Genehmigung des Swaps ist im Gange und wird in Kürze abgeschlossen sein.</string>
|
||||
<string name="warning_express_approval_in_progress_title">Genehmigung in Arbeit</string>
|
||||
<string name="warning_express_dust_message">Der Mindestbetrag für den Tausch beträgt %1$s. Bitte stelle sicher, dass der Restsaldo nach dem Swap nicht unter %2$s liegt.</string>
|
||||
<string name="warning_express_no_exchangeable_coins_description">Du hast keine %s Coins in deiner Liste</string>
|
||||
<string name="warning_express_no_exchangeable_coins_title">Keine Token zum Tauschen verfügbar</string>
|
||||
|
|
|
|||
|
|
@ -823,6 +823,8 @@
|
|||
<string name="wallet_settings_title">ウォレット設定</string>
|
||||
<string name="wallet_title">Tangem</string>
|
||||
<string name="warning_access_denied_message">%sを使用するか、カードをスキャンしてウォレットにアクセスしてください</string>
|
||||
<string name="warning_approval_in_progress_message">スワップの承認は現在進行中で、まもなく完了する予定です。</string>
|
||||
<string name="warning_approval_in_progress_title">承認が進行中</string>
|
||||
<string name="warning_backup_errors_message">カードのアクティベーションが正しく完了しませんでした。デバイスの NFCモジュールに問題があるか、カードをデバイスに正しくタップしていないことが原因かもしれません。サポートチームにお問い合わせください。</string>
|
||||
<string name="warning_backup_errors_title">アクティベーションに失敗しました</string>
|
||||
<string name="warning_beacon_chain_retirement_content">BNBネットワーク開発者によると、BEP-2規格のサポートは2024年6月に終了します。この規格の資産を失わないために、BEP-20規格に変換してください。BNBスマートチェーンネットワークへ移行するには、Tangemのスワップサービスをご利用ください。</string>
|
||||
|
|
@ -840,8 +842,6 @@
|
|||
<string name="warning_existential_deposit_title">ネットワークには最低残高が必要です</string>
|
||||
<string name="warning_express_active_transaction_message">スワップは、%s の取引完了後に利用可能となります。</string>
|
||||
<string name="warning_express_active_transaction_title">アクティブな取引があります</string>
|
||||
<string name="warning_express_approval_in_progress_message">スワップの承認は現在進行中で、まもなく完了する予定です。</string>
|
||||
<string name="warning_express_approval_in_progress_title">承認が進行中</string>
|
||||
<string name="warning_express_dust_message">最低のスワップ金額は%1$s です。スワップ後の残金が%2$s を下回らないようにしてください。</string>
|
||||
<string name="warning_express_no_exchangeable_coins_description">あなたのリストには、交換可能な %s トークンがありません。</string>
|
||||
<string name="warning_express_no_exchangeable_coins_title">スワップ可能なトークンがありません</string>
|
||||
|
|
|
|||
|
|
@ -859,8 +859,6 @@
|
|||
<string name="warning_existential_deposit_title">Для работы с сетью необходим депозит</string>
|
||||
<string name="warning_express_active_transaction_message">Обмен будет доступен после завершения %s транзакции</string>
|
||||
<string name="warning_express_active_transaction_title">У вас есть активная транзакция</string>
|
||||
<string name="warning_express_approval_in_progress_message">Разрешение обмена в процессе и будет скоро завершено</string>
|
||||
<string name="warning_express_approval_in_progress_title">Разрешение в процессе</string>
|
||||
<string name="warning_express_dust_message">Минимальная сумма обмена - %1$s. Пожалуйста, убедитесь, что остаток после обмена также не будет меньше %2$s.</string>
|
||||
<string name="warning_express_no_exchangeable_coins_description">У вас в списке нет монет доступных для обмена с %s</string>
|
||||
<string name="warning_express_no_exchangeable_coins_title">Нет доступных для обмена токенов</string>
|
||||
|
|
|
|||
|
|
@ -844,6 +844,8 @@
|
|||
<string name="wallet_settings_title">Налаштування гаманця</string>
|
||||
<string name="wallet_title">Tangem</string>
|
||||
<string name="warning_access_denied_message">Використовуйте %s або відскануйте картку, щоб розблокувати доступ до гаманця</string>
|
||||
<string name="warning_approval_in_progress_message">Затвердження обміну триває і незабаром буде завершено</string>
|
||||
<string name="warning_approval_in_progress_title">Затвердження в процесі</string>
|
||||
<string name="warning_backup_errors_message">Схоже, що активація картки була виконана неправильно. Це може бути пов\'язано з проблемою з модулем NFC вашого пристрою або неправильним прикладанням картки до пристрою. Зверніться за допомогою до нашої служби підтримки.</string>
|
||||
<string name="warning_backup_errors_title">Помилка активації</string>
|
||||
<string name="warning_beacon_chain_retirement_content">За рішенням розробників мережі BNB стандарт BEP-2 перестане підтримуватись у червні 2024 року. Щоб не втратити свої активи, їх необхідно конвертувати у стандарт BEP-20. Використовуйте функцію обміну, щоб перевести їх у мережу BNB Smart Chain.</string>
|
||||
|
|
@ -861,8 +863,6 @@
|
|||
<string name="warning_existential_deposit_title">Для роботи з мережею вимагається депозит</string>
|
||||
<string name="warning_express_active_transaction_message">Обмін буде доступний після завершення %s транзакції</string>
|
||||
<string name="warning_express_active_transaction_title">У вас є активна транзакція</string>
|
||||
<string name="warning_express_approval_in_progress_message">Затвердження обміну триває і незабаром буде завершено</string>
|
||||
<string name="warning_express_approval_in_progress_title">Затвердження в процесі</string>
|
||||
<string name="warning_express_dust_message">Мінімальна сума обміну становить - %1$s. Будь ласка, переконайтеся, що залишок на рахунку після обміну буде не менше за %2$s.</string>
|
||||
<string name="warning_express_no_exchangeable_coins_description">У вашому списку немає доступних монет для обміну %s</string>
|
||||
<string name="warning_express_no_exchangeable_coins_title">Немає доступних токенів для обміну</string>
|
||||
|
|
|
|||
|
|
@ -1,3 +1,5 @@
|
|||
import com.android.ide.common.resources.generateLocaleList
|
||||
|
||||
plugins {
|
||||
alias(deps.plugins.android.library)
|
||||
alias(deps.plugins.kotlin.android)
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
@ -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),
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -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,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -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)
|
||||
},
|
||||
)
|
||||
},
|
||||
|
|
|
|||
|
|
@ -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(
|
||||
|
|
|
|||
|
|
@ -0,0 +1,13 @@
|
|||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="22dp"
|
||||
android:height="36dp"
|
||||
android:viewportWidth="22"
|
||||
android:viewportHeight="36">
|
||||
<path
|
||||
android:fillColor="#C9C9CA"
|
||||
android:pathData="M22,2L22,34A2,2 0,0 1,20 36L2,36A2,2 0,0 1,0 34L0,2A2,2 0,0 1,2 0L20,0A2,2 0,0 1,22 2z" />
|
||||
|
||||
<path
|
||||
android:fillColor="#A1A1A1"
|
||||
android:pathData="M10.92,22.14H10.96C11.52,22.14 11.85,21.83 11.85,21.35C11.85,20.84 11.48,20.55 10.95,20.55H10.91C10.36,20.55 10.03,20.89 10.03,21.32C10.03,21.79 10.36,22.14 10.92,22.14ZM9.29,19.89V20.58C9,20.63 8.83,20.85 8.83,21.3C8.83,21.82 9.09,22.11 9.62,22.11H10.05C9.77,21.95 9.51,21.59 9.51,21.16C9.51,20.42 10.06,19.86 10.9,19.86H10.94C11.76,19.86 12.38,20.42 12.38,21.17C12.38,21.65 12.16,21.95 11.87,22.11H12.32V22.79H9.61C8.75,22.78 8.32,22.21 8.32,21.3C8.32,20.39 8.73,19.97 9.29,19.89ZM11.17,7.82V9.23H9.41C9.06,9.23 8.88,9.23 8.75,9.16C8.63,9.1 8.54,9 8.48,8.89C8.41,8.75 8.41,8.58 8.41,8.23V7.82H11.17ZM13.69,8.23V6C13.69,5.65 13.69,5.48 13.62,5.34C13.56,5.22 13.47,5.13 13.35,5.07C13.21,5 13.04,5 12.69,5H12.31V9.23H12.69H12.69C13.04,9.23 13.21,9.23 13.35,9.16C13.47,9.1 13.56,9 13.62,8.89C13.69,8.75 13.69,8.58 13.69,8.23ZM11.17,5V6.41H8.41V6C8.41,5.65 8.41,5.48 8.48,5.34C8.54,5.22 8.63,5.13 8.75,5.07C8.88,5 9.06,5 9.41,5L11.17,5ZM10.21,11.96H11.81V11.57H12.32V11.96H12.96L12.96,12.64H12.32V13.28H11.81V12.64H10.27C10.01,12.64 9.89,12.76 9.89,12.98C9.89,13.11 9.91,13.21 9.95,13.31H9.41C9.37,13.2 9.34,13.05 9.34,12.85C9.34,12.27 9.65,11.96 10.21,11.96ZM12.32,17.44V16.76H9.39V17.44H11.11C11.58,17.44 11.81,17.74 11.81,18.12C11.81,18.53 11.61,18.71 11.17,18.71H9.39L9.39,19.38H11.23C12.04,19.38 12.38,18.97 12.38,18.38C12.38,17.9 12.14,17.58 11.85,17.44H12.32ZM11.88,24.74C11.88,25.16 11.66,25.42 11.16,25.45V23.99C11.61,24.06 11.88,24.33 11.88,24.74ZM10.87,23.29H10.82C9.9,23.29 9.33,23.91 9.33,24.77C9.33,25.52 9.67,26.02 10.29,26.11V25.46C10,25.41 9.84,25.19 9.84,24.79C9.84,24.28 10.15,24 10.7,23.98V26.12H10.9C11.95,26.12 12.38,25.47 12.38,24.74C12.38,23.91 11.77,23.29 10.87,23.29ZM12.32,26.64V27.32H11.87C12.14,27.46 12.38,27.78 12.38,28.21C12.38,28.59 12.22,28.89 11.85,29.04C12.22,29.26 12.38,29.66 12.38,30.03C12.38,30.56 12.05,31 11.24,31H9.39V30.32H11.2C11.63,30.32 11.81,30.14 11.81,29.8C11.81,29.47 11.59,29.16 11.14,29.16H9.39V28.48H11.2C11.63,28.48 11.81,28.29 11.81,27.96C11.81,27.63 11.59,27.32 11.14,27.32H9.39V26.64H12.32ZM10.7,15.52H10.41C10.04,15.52 9.82,15.22 9.82,14.8C9.82,14.47 9.98,14.33 10.23,14.33C10.59,14.33 10.7,14.66 10.7,15.18V15.52ZM11.13,15.16C11.13,14.32 10.88,13.66 10.2,13.66C9.59,13.66 9.33,14.1 9.33,14.64C9.33,15.09 9.5,15.34 9.75,15.53H9.39V16.2H11.31C12.11,16.2 12.38,15.68 12.38,15.03C12.38,14.38 12.09,13.83 11.41,13.78V14.43C11.7,14.47 11.87,14.64 11.87,14.99C11.87,15.39 11.67,15.52 11.28,15.52H11.13V15.16Z" />
|
||||
</vector>
|
||||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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 $"),
|
||||
|
|
|
|||
|
|
@ -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<UserWalletUM>,
|
||||
val userWallets: ImmutableList<UserWalletItemUM>,
|
||||
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,
|
||||
)
|
||||
}
|
||||
)
|
||||
|
|
@ -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<UserWalletUM>,
|
||||
userWallets: ImmutableList<UserWalletItemUM>,
|
||||
shouldSaveUserWallets: Boolean,
|
||||
isWalletSavingInProgress: Boolean,
|
||||
) = state.update { value ->
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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<UserWallet>.toUiModels(
|
|||
balances: Map<UserWalletId, TotalFiatBalance> = emptyMap(),
|
||||
isLoading: Boolean = true,
|
||||
isBalancesHidden: Boolean = false,
|
||||
): ImmutableList<UserWalletUM> = this.map { model ->
|
||||
): ImmutableList<UserWalletItemUM> = 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(
|
||||
|
|
|
|||
|
|
@ -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<ImmutableList<UserWalletUM>> = getWalletsUseCase().transformLatest { wallets ->
|
||||
val userWallets: Flow<ImmutableList<UserWalletItemUM>> = getWalletsUseCase().transformLatest { wallets ->
|
||||
emit(wallets.toUiModels(onClick = ::navigateToWalletSettings))
|
||||
|
||||
combine(
|
||||
|
|
@ -72,7 +72,7 @@ internal class UserWalletsFetcher @Inject constructor(
|
|||
maybeAppCurrency: Either<SelectedAppCurrencyError, AppCurrency>,
|
||||
maybeBalances: Lce<TokenListError, Map<UserWalletId, TotalFiatBalance>>,
|
||||
balanceHidingSettings: BalanceHidingSettings,
|
||||
): Lce<Error, ImmutableList<UserWalletUM>> = lce {
|
||||
): Lce<Error, ImmutableList<UserWalletItemUM>> = lce {
|
||||
val balances = withError(
|
||||
transform = { Error.UnableToGetBalances },
|
||||
block = { maybeBalances.bindOrNull().orEmpty() },
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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<Params, MarketsPortfolioComponent>
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
|
|
@ -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<MarketsPortfolioComponent.Params>()
|
||||
}
|
||||
|
|
@ -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<AddToPortfolioBSContentUM>(
|
||||
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,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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 = {},
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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<TokenActionsBSContent>(
|
||||
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 = {},
|
||||
),
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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<AddToPortfolioBSContentUM> {
|
||||
|
||||
override val values: Sequence<AddToPortfolioBSContentUM>
|
||||
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,
|
||||
),
|
||||
)
|
||||
}
|
||||
|
|
@ -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<MyPortfolioUM> {
|
||||
|
||||
override val values: Sequence<MyPortfolioUM>
|
||||
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 = {},
|
||||
)
|
||||
}
|
||||
|
|
@ -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
|
||||
|
|
@ -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<PortfolioTokenUM>,
|
||||
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()
|
||||
}
|
||||
|
|
@ -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()
|
||||
}
|
||||
}
|
||||
|
|
@ -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,
|
||||
),
|
||||
}
|
||||
|
|
@ -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<BlockchainRowUM>,
|
||||
val onNetworkSwitchClick: (BlockchainRowUM, Boolean) -> Unit,
|
||||
)
|
||||
|
|
@ -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<Action>,
|
||||
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)),
|
||||
}
|
||||
}
|
||||
|
|
@ -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(
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue