Updated on 2026-08-14

This commit is contained in:
Tangem 2026-03-04 15:19:16 +03:00
parent 1858d5453a
commit 90abe449d6
23 changed files with 1741 additions and 11 deletions

View file

@ -66,6 +66,12 @@ internal object WalletsDomainModule {
return GetUserWalletUseCase(userWalletsListRepository = userWalletsListRepository)
}
@Provides
@Singleton
fun provideGetWalletIconUseCase(walletsRepository: WalletsRepository): GetWalletIconUseCase {
return GetWalletIconUseCase(walletsRepository = walletsRepository)
}
@Provides
@Singleton
fun providesGetSelectedWalletSyncUseCase(

View file

@ -0,0 +1,55 @@
package com.tangem.common.ui.userwallet.converter
import androidx.compose.ui.graphics.Color
import androidx.core.graphics.toColorInt
import com.tangem.core.ui.ds.image.DeviceIconUM
import com.tangem.domain.models.wallet.UserWalletIcon
import com.tangem.utils.converter.Converter
import javax.inject.Inject
/**
* Converter for mapping [UserWalletIcon] to [DeviceIconUM],
* which is used for displaying the wallet icon in the UI.
*/
class WalletIconUMConverter @Inject constructor() : Converter<UserWalletIcon, DeviceIconUM> {
override fun convert(value: UserWalletIcon): DeviceIconUM = with(value) {
fun String.parseHexColor(): Color = try {
Color(this.toColorInt())
} catch (_: IllegalArgumentException) {
Color.Unspecified
}
return when (this) {
UserWalletIcon.Hot -> DeviceIconUM.Mobile
is UserWalletIcon.Stub ->
DeviceIconUM.Stub(cardsCount = this.cardsCount)
is UserWalletIcon.Default -> if (isRing) {
DeviceIconUM.Ring(
mainColor = Color.Unspecified,
cardColor = Color.Unspecified,
secondCardColor = if (cardsCount > 2) Color.Unspecified else null,
)
} else {
DeviceIconUM.Card(
mainColor = Color.Unspecified,
secondColor = if (cardsCount > 1) Color.Unspecified else null,
thirdColor = if (cardsCount > 2) Color.Unspecified else null,
)
}
is UserWalletIcon.Colored -> if (isRing) {
DeviceIconUM.Ring(
mainColor = mainColor.parseHexColor(),
cardColor = secondColor?.parseHexColor(),
secondCardColor = thirdColor?.parseHexColor(),
)
} else {
DeviceIconUM.Card(
mainColor = mainColor.parseHexColor(),
secondColor = secondColor?.parseHexColor(),
thirdColor = thirdColor?.parseHexColor(),
)
}
}
}
}

View file

@ -0,0 +1,202 @@
package com.tangem.core.ui.ds.image
import android.content.res.Configuration
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.*
import androidx.compose.material3.Icon
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.takeOrElse
import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.res.vectorResource
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import com.tangem.core.ui.R
import com.tangem.core.ui.res.TangemTheme
import com.tangem.core.ui.res.TangemThemePreviewRedesign
/**
* Composable function for displaying a wallet icon based on the provided [DeviceIconUM] state.
*
* The icon can represent different types of devices, such as cards, rings, stubs, or mobile wallet,
* with customizable colors and styles.
*
* @param state The state of the device icon, which determines its appearance.
* @param modifier Optional [Modifier] for styling the composable.
*/
@Composable
fun TangemDeviceIcon(state: DeviceIconUM, modifier: Modifier = Modifier) {
when (state) {
is DeviceIconUM.Card -> DeviceIcon(
modifier = modifier,
isRing = false,
mainColor = state.mainColor,
secondColor = state.secondColor,
thirdColor = state.thirdColor,
tColor = null,
)
is DeviceIconUM.Ring -> DeviceIcon(
modifier = modifier,
isRing = true,
mainColor = state.mainColor,
secondColor = state.cardColor,
thirdColor = state.secondCardColor,
tColor = null,
)
is DeviceIconUM.Stub -> DeviceIcon(
modifier = modifier,
isRing = false,
mainColor = Color.Unspecified,
secondColor = Color.Unspecified.takeIf { state.cardsCount > 1 },
thirdColor = Color.Unspecified.takeIf { state.cardsCount > 2 },
tColor = TangemTheme.colors2.graphic.neutral.secondary,
)
DeviceIconUM.Mobile -> Icon(
modifier = modifier,
imageVector = ImageVector.vectorResource(R.drawable.ic_shield_24),
contentDescription = null,
tint = TangemTheme.colors2.graphic.status.attention,
)
}
}
@Composable
private fun DeviceIcon(
isRing: Boolean,
mainColor: Color,
secondColor: Color?,
thirdColor: Color?,
tColor: Color?,
modifier: Modifier = Modifier,
) {
val main = mainColor.takeOrElse { TangemTheme.colors2.graphic.neutral.tertiaryConstant }
val second = secondColor?.takeOrElse { TangemTheme.colors2.graphic.neutral.tertiaryConstant }
val third = thirdColor?.takeOrElse { TangemTheme.colors2.graphic.neutral.tertiaryConstant }
val borderColor = TangemTheme.colors2.border.walletIcon
val imageVector = remember(isRing, main, second, third, borderColor, tColor) {
when {
isRing && second != null && third != null -> WalletIconVectorBuilders.buildRingWithCard2(
mainColor = main,
cardColor = second,
secondCardColor = third,
borderColor = borderColor,
)
!isRing && second != null && third != null -> WalletIconVectorBuilders.buildCard3(
mainColor = main,
secondColor = second,
thirdColor = third,
tColor = tColor,
borderColor = borderColor,
)
isRing && second != null -> WalletIconVectorBuilders.buildRingWithCard(
mainColor = main,
cardColor = second,
borderColor = borderColor,
)
!isRing && second != null -> WalletIconVectorBuilders.buildCard2(
mainColor = main,
secondColor = second,
tColor = tColor,
borderColor = borderColor,
)
isRing -> WalletIconVectorBuilders.buildRing(
mainColor = main,
borderColor = borderColor,
)
else -> WalletIconVectorBuilders.buildCard(
mainColor = main,
borderColor = borderColor,
tColor = tColor,
)
}
}
Icon(
imageVector = imageVector,
contentDescription = null,
modifier = modifier,
tint = Color.Unspecified,
)
}
// region Preview
private val previewCardBlue
get() = Color(0xFF1C5FBF)
private val previewCardGold
get() = Color(0xFFD4A017)
private val previewCardPurple
get() = Color(0xFF7B2FBE)
private val previewRingGreen
get() = Color(0xFF2ECC71)
private val previewStates: List<Pair<String, DeviceIconUM>>
get() = listOf(
"Card 1" to DeviceIconUM.Card(
mainColor = previewCardBlue,
secondColor = null,
),
"Card 2" to DeviceIconUM.Card(
mainColor = previewCardBlue,
secondColor = previewCardGold,
),
"Card 3" to DeviceIconUM.Card(
mainColor = previewCardBlue,
secondColor = previewCardGold,
thirdColor = previewCardPurple,
),
"Ring" to DeviceIconUM.Ring(
mainColor = previewRingGreen,
),
"Ring + Card" to DeviceIconUM.Ring(
mainColor = previewRingGreen,
cardColor = previewCardBlue,
),
"Ring + 2 Cards" to DeviceIconUM.Ring(
mainColor = previewRingGreen,
cardColor = previewCardBlue,
secondCardColor = previewCardGold,
),
"Stub 1" to DeviceIconUM.Stub(cardsCount = 1),
"Stub 2" to DeviceIconUM.Stub(cardsCount = 2),
"Stub 3" to DeviceIconUM.Stub(cardsCount = 3),
"Mobile" to DeviceIconUM.Mobile,
)
@Composable
@Preview(showBackground = true, widthDp = 360)
@Preview(showBackground = true, widthDp = 360, uiMode = Configuration.UI_MODE_NIGHT_YES)
private fun TangemDeviceIcon_Preview() {
TangemThemePreviewRedesign {
Column(
modifier = Modifier
.background(TangemTheme.colors.background.primary)
.padding(16.dp),
verticalArrangement = Arrangement.spacedBy(12.dp),
) {
previewStates.forEach { (label, state) ->
Row(
horizontalArrangement = Arrangement.spacedBy(12.dp),
verticalAlignment = Alignment.CenterVertically,
) {
TangemDeviceIcon(
modifier = Modifier.size(40.dp),
state = state,
)
Text(
text = label,
style = TangemTheme.typography.body1,
color = TangemTheme.colors.text.primary1,
)
}
}
}
}
}
// endregion

View file

@ -0,0 +1,24 @@
package com.tangem.core.ui.ds.image
import androidx.compose.runtime.Immutable
import androidx.compose.ui.graphics.Color
@Immutable
sealed interface DeviceIconUM {
data class Card(
val mainColor: Color,
val secondColor: Color?,
val thirdColor: Color? = null,
) : DeviceIconUM
data class Ring(
val mainColor: Color = Color.Unspecified,
val cardColor: Color? = null,
val secondCardColor: Color? = null,
) : DeviceIconUM
data class Stub(val cardsCount: Int) : DeviceIconUM
data object Mobile : DeviceIconUM
}

File diff suppressed because it is too large Load diff

View file

@ -326,7 +326,10 @@ class TangemColors2 internal constructor(
class Border internal constructor(
val neutral: Neutral,
val status: Status,
walletIcon: Color,
) {
var walletIcon by mutableStateOf(walletIcon)
private set
@Stable
class Neutral internal constructor(
@ -367,6 +370,7 @@ class TangemColors2 internal constructor(
fun update(other: Border) {
neutral.update(other.neutral)
status.update(other.status)
walletIcon = other.walletIcon
}
}

View file

@ -85,6 +85,7 @@ private fun lightThemeColors2(): TangemColors2 {
warning = TangemColorPalette.Amaranth,
attention = TangemColorPalette.Tangerine,
),
walletIcon = TangemColorPalette.Dark_10,
)
val overlay = TangemColors2.Overlay(
overlayPrimary = TangemColorPalette.Overlay1,
@ -247,6 +248,7 @@ private fun darkThemeColors2(): TangemColors2 {
warning = TangemColorPalette.Flamingo,
attention = TangemColorPalette.Mustard,
),
walletIcon = TangemColorPalette.Light_10,
)
val overlay = TangemColors2.Overlay(
overlayPrimary = TangemColorPalette.Overlay1,

View file

@ -0,0 +1,5 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:autoMirrored="true" android:height="24dp" android:viewportHeight="24" android:viewportWidth="24" android:width="24dp">
<path android:fillColor="#000000" android:pathData="M12,21C11.922,21 11.824,20.984 11.707,20.952C11.596,20.927 11.478,20.882 11.354,20.818C9.992,20.06 8.838,19.391 7.892,18.811C6.954,18.232 6.197,17.671 5.623,17.129C5.056,16.581 4.642,15.992 4.381,15.361C4.127,14.724 4,13.975 4,13.115V5.909C4,5.418 4.108,5.065 4.323,4.848C4.538,4.625 4.848,4.434 5.252,4.275C5.48,4.185 5.793,4.068 6.191,3.921C6.588,3.768 7.025,3.606 7.501,3.434C7.984,3.255 8.463,3.083 8.939,2.918C9.421,2.745 9.861,2.593 10.259,2.459C10.657,2.319 10.97,2.21 11.198,2.134C11.328,2.096 11.459,2.064 11.589,2.038C11.726,2.013 11.863,2 12,2C12.137,2 12.274,2.013 12.411,2.038C12.548,2.064 12.681,2.096 12.812,2.134C13.04,2.21 13.35,2.319 13.741,2.459C14.139,2.593 14.575,2.745 15.051,2.918C15.534,3.09 16.013,3.262 16.489,3.434C16.972,3.606 17.412,3.765 17.809,3.911C18.207,4.058 18.52,4.179 18.748,4.275C19.159,4.44 19.469,4.631 19.677,4.848C19.892,5.065 20,5.418 20,5.909V13.115C20,13.975 19.876,14.73 19.628,15.38C19.381,16.024 18.973,16.626 18.406,17.187C17.845,17.747 17.092,18.314 16.147,18.888C15.208,19.461 14.041,20.105 12.646,20.818C12.522,20.882 12.401,20.927 12.284,20.952C12.173,20.984 12.078,21 12,21ZM7.599,11.815C7.599,11.943 7.641,12.048 7.726,12.131C7.817,12.207 7.928,12.245 8.059,12.245H11.482L9.653,17.034C9.568,17.244 9.571,17.416 9.663,17.55C9.76,17.684 9.894,17.754 10.064,17.76C10.233,17.76 10.386,17.674 10.523,17.502L16.049,10.726C16.16,10.598 16.215,10.471 16.215,10.344C16.215,10.216 16.17,10.111 16.078,10.028C15.993,9.945 15.886,9.904 15.755,9.904H12.333L14.161,5.116C14.246,4.905 14.24,4.737 14.142,4.609C14.05,4.475 13.92,4.408 13.751,4.408C13.588,4.402 13.434,4.485 13.291,4.657L7.765,11.433C7.654,11.554 7.599,11.682 7.599,11.815Z"/>
</vector>

View file

@ -0,0 +1,21 @@
package com.tangem.domain.models.wallet
/**
* Represents the icon of a user wallet, which can be of different types such as hot, stub, default, or colored.
*/
sealed class UserWalletIcon {
data object Hot : UserWalletIcon()
data class Stub(val cardsCount: Int) : UserWalletIcon()
data class Default(
val isRing: Boolean,
val cardsCount: Int,
) : UserWalletIcon()
data class Colored(
val isRing: Boolean,
val mainColor: String,
val secondColor: String? = null,
val thirdColor: String? = null,
) : UserWalletIcon()
}

View file

@ -0,0 +1,242 @@
package com.tangem.domain.wallets.usecase
import com.tangem.blockchain.common.Blockchain
import com.tangem.domain.card.common.util.cardTypesResolver
import com.tangem.domain.card.common.util.getCardsCount
import com.tangem.domain.demo.models.DemoConfig
import com.tangem.domain.models.wallet.UserWallet
import com.tangem.domain.models.wallet.UserWalletIcon
import com.tangem.domain.models.wallet.isHotWallet
import com.tangem.domain.models.wallet.requireColdWallet
import com.tangem.domain.wallets.repository.WalletsRepository
import kotlinx.coroutines.runBlocking
class GetWalletIconUseCase(
private val walletsRepository: WalletsRepository,
) {
@Suppress("CyclomaticComplexMethod", "UnsafeCallOnNullableType")
operator fun invoke(userWallet: UserWallet): UserWalletIcon {
if (userWallet.isHotWallet) {
return UserWalletIcon.Hot
}
userWallet.requireColdWallet()
val cardTypesResolver = userWallet.scanResponse.cardTypesResolver
val cardsCount = userWallet.getCardsCount() ?: 1
val cobrandColor by lazy {
colorByBatchId(userWallet.scanResponse.card.batchId)
}
return when {
cardTypesResolver.isDevKit() -> otherColor(OtherCardType.Devkit)
userWallet.isRing() -> UserWalletIcon.Default(isRing = true, cardsCount = cardsCount)
cobrandColor != null -> cobrandColor!!.withCount(cardsCount)
cardTypesResolver.isWallet2() -> UserWalletIcon.Default(isRing = false, cardsCount = cardsCount)
cardTypesResolver.isShibaWallet() -> otherColor(OtherCardType.Shiba, cardsCount)
cardTypesResolver.isTangemWallet() -> otherColor(OtherCardType.Wallet1, cardsCount)
cardTypesResolver.isWhiteWallet() -> otherColor(OtherCardType.WhiteWallet, cardsCount)
cardTypesResolver.isTangemTwins() -> otherColor(OtherCardType.Twins, cardsCount)
cardTypesResolver.isStart2Coin() -> otherColor(OtherCardType.Starts2com, cardsCount)
cardTypesResolver.isTangemNote() ->
resolveNoteColor(userWallet) ?: UserWalletIcon.Stub(cardsCount = cardsCount)
DemoConfig.isDemoCardId(cardId = userWallet.cardId) ->
UserWalletIcon.Default(isRing = false, cardsCount = cardsCount)
else -> UserWalletIcon.Stub(cardsCount = cardsCount)
}
}
private fun resolveNoteColor(userWallet: UserWallet.Cold): UserWalletIcon? {
val noteBlockchain = userWallet.scanResponse.cardTypesResolver.getBlockchain()
val otherCardType = when (noteBlockchain) {
Blockchain.Bitcoin -> OtherCardType.NoteBitcoin
Blockchain.Ethereum -> OtherCardType.NoteEthereum
Blockchain.XRP -> OtherCardType.NoteXRP
Blockchain.Binance -> OtherCardType.NoteBinance
Blockchain.Cardano -> OtherCardType.NoteCardano
Blockchain.Dogecoin -> OtherCardType.NoteDoge
else -> return null
}
return otherColor(otherCardType)
}
private fun otherColor(otherCardType: OtherCardType, cardsCount: Int = 1): UserWalletIcon.Colored {
return UserWalletIcon.Colored(
isRing = false,
mainColor = otherCardType.mainColor,
secondColor = if (cardsCount > 1) otherCardType.mainColor else null,
thirdColor = if (cardsCount > 2) otherCardType.mainColor else null,
)
}
private fun UserWalletIcon.Colored.withCount(count: Int): UserWalletIcon.Colored {
return this.copy(
mainColor = mainColor,
secondColor = if (count > 1) secondColor else null,
thirdColor = if (count > 2) thirdColor else null,
)
}
private fun UserWallet.Cold.isRing(): Boolean {
return scanResponse.cardTypesResolver.isRing() ||
runBlocking { walletsRepository.isWalletWithRing(userWalletId = this@isRing.walletId) }
}
@Suppress("CyclomaticComplexMethod")
private fun colorByBatchId(batchId: String): UserWalletIcon.Colored? {
fun color(main: String, second: String = main, third: String = main) =
UserWalletIcon.Colored(isRing = false, mainColor = main, secondColor = second, thirdColor = third)
val cobrandType = CobrandType.entries.firstOrNull { it.batchIds.contains(batchId) }
return when (cobrandType) {
CobrandType.Avrora -> color("#1E1E1C")
CobrandType.BabyDoge -> color("#E7D34C")
CobrandType.Bad -> color("#395467")
CobrandType.BitcoinGold -> color("#F08A1F")
CobrandType.BitcoinPizzaDay -> color("#AF4D37")
CobrandType.BitcoinPizza2 -> color("#F3C63A")
CobrandType.BTC365 -> color("#191E3E")
CobrandType.CashClubGold -> color("#D1BF78")
CobrandType.Changenow -> color("#191B2C")
CobrandType.Chilliz -> color("#49324A")
CobrandType.CoinMetrica -> color("#A140C7")
CobrandType.COQ -> color("#ED1F3A")
CobrandType.CryptoCasey -> color("#301E45")
CobrandType.CryptoOrg -> color("#27B39A")
CobrandType.CryptoSeth -> color("#414954")
CobrandType.GetsMine -> color("#AFC3CE")
CobrandType.Grim -> color("#131313")
CobrandType.Hodl -> color("#111111")
CobrandType.Jr -> color("#1C1C1C")
CobrandType.Kaspa -> color("#545C5C")
CobrandType.Kaspa2 -> color("#353535")
CobrandType.KaspaReseller -> color("#3B3D3A")
CobrandType.Kaspa3 -> color("#85CBC1")
CobrandType.Kasper -> color("#34302E")
CobrandType.Kaspy -> color("#DEC764")
CobrandType.Keiro -> color("#4D645C")
CobrandType.KishuInu -> color("#2DA4CE")
CobrandType.Kango -> color("#5BA495")
CobrandType.Konan -> color("#64C9C9")
CobrandType.Kroak -> color("#8EB8AF")
CobrandType.Neiro -> color("#E7A524")
CobrandType.NewWorldElite -> color("#292722")
CobrandType.PassimPay -> color("#6A4361")
CobrandType.Pastel -> color("#FFC7A4", "#84A479", "#6FB5BB")
CobrandType.Pepecoin -> color("#303439")
CobrandType.RamenCat -> color("#DEBE88")
CobrandType.RedPanda -> color("#C5C5C5")
CobrandType.Rizo -> color("#1765A7")
CobrandType.Sakura -> color("#F0E9C4")
CobrandType.SatoshiFriends -> color("#242424")
CobrandType.SinCity -> color("#D3C487")
CobrandType.SpringBloom -> color("#FAC73A")
CobrandType.StealthCard -> color("#555557")
CobrandType.SunDrop -> color("#FEC035")
CobrandType.Trillant -> color("#955091")
CobrandType.Tron -> color("#D4221D")
CobrandType.Upbit -> color("#2B2B2B")
CobrandType.USA -> color("#0D185F")
CobrandType.VeChain -> color("#5186A2")
CobrandType.Vivid -> color("#D3CF09", "#F76952", "#2BCAD1")
CobrandType.Vnish -> color("#292522")
CobrandType.VoltInu -> color("#34312B")
CobrandType.WhiteTangem -> color("#D3D3D3")
CobrandType.WildGoat -> color("#1B1B1B")
CobrandType.Winter -> color("#7FB9C8", "#80BDE8", "#B2C6E4")
CobrandType.WinterSakura -> color("#88B9E9")
CobrandType.LockedMoney -> color("#272625")
CobrandType.Ghoad -> color("#6EC5C5")
CobrandType.BlushSky -> color("#C1E9E8", "#FACAD3", "#DCCEE0")
CobrandType.ElectraSea -> color("#0D5A67", "#29939E", "#30C6B1")
CobrandType.HyperBlue -> color("#0F397C", "#1474D3", "#0BC9EC")
CobrandType.Lunar -> color("#B0313A")
null -> null
}
}
}
private enum class CobrandType(val batchIds: List<String>) {
Avrora(listOf("AF18")),
BabyDoge(listOf("AF51")),
Bad(listOf("AF09")),
BitcoinGold(listOf("AF71", "AF990016", "AF990009")),
BitcoinPizzaDay(listOf("AF33")),
BitcoinPizza2(listOf("AF990019")),
BTC365(listOf("AF97")),
CashClubGold(listOf("BB000004")),
Changenow(listOf("BB000013")),
Chilliz(listOf("BB000016")),
CoinMetrica(listOf("AF27")),
COQ(listOf("AF28")),
CryptoCasey(listOf("AF21", "AF22", "AF23")),
CryptoOrg(listOf("AF57")),
CryptoSeth(listOf("AF32")),
GetsMine(listOf("BB000008")),
Grim(listOf("AF13")),
Hodl(listOf("BB000009")),
Jr(listOf("AF14")),
Kaspa(listOf("AF08")),
Kaspa2(listOf("AF25", "AF61", "AF72")),
KaspaReseller(listOf("AF31")),
Kaspa3(listOf("AF73")),
Kasper(listOf("AF96")),
Kaspy(listOf("AF95")),
Keiro(listOf("BB000017")),
KishuInu(listOf("AF52")),
Kango(listOf("BB000006")),
Konan(listOf("AF93")),
Kroak(listOf("BB000011")),
Neiro(listOf("AF98")),
NewWorldElite(listOf("AF26")),
PassimPay(listOf("BB000007")),
Pastel(listOf("AF43", "AF44", "AF45", "AF78", "AF79", "AF80")),
Pepecoin(listOf("BB000015")),
RamenCat(listOf("AF990006", "AF990007", "AF990008")),
RedPanda(listOf("AF34")),
Rizo(listOf("BB000012")),
Sakura(listOf("AF990029", "AF990030", "AF990031", "AF990071", "AF990072", "AF990073")),
SatoshiFriends(listOf("AF19")),
SinCity(listOf("BB000010")),
SpringBloom(listOf("AF990001", "AF990002", "AF990004")),
StealthCard(listOf("AF60", "AF74", "AF88")),
SunDrop(listOf("AF990005", "AF990003")),
Trillant(listOf("AF16")),
Tron(listOf("AF07")),
Upbit(listOf("BB000019")),
USA(listOf("AF91", "AF990017", "AF990056")),
VeChain(listOf("AF29")),
Vivid(listOf("AF40", "AF41", "AF42", "AF75", "AF76", "AF77")),
Vnish(listOf("BB000005")),
VoltInu(listOf("AF35")),
WhiteTangem(listOf("AF15")),
WildGoat(listOf("BB000001")),
Winter(listOf("AF85", "AF86", "AF87", "AF990013", "AF990012", "AF990011")),
WinterSakura(listOf("AF990053", "AF990054", "AF990055")),
LockedMoney(listOf("AF63")),
Ghoad(listOf("AF89")),
BlushSky(listOf("AF990020", "AF990021", "AF990022")),
ElectraSea(listOf("AF990023", "AF990024", "AF990025")),
HyperBlue(listOf("AF990026", "AF990027", "AF990028", "AF990050", "AF990051", "AF990052")),
Lunar(listOf("AF990057", "AF990058", "AF990059")),
}
private enum class OtherCardType(val mainColor: String) {
NoteXRP("#726799"),
NoteDoge("#BFB565"),
NoteEthereum("#989C9F"),
NoteBinance("#C9B87C"),
NoteCardano("#5979AD"),
NoteBitcoin("#EABE8B"),
Starts2com("#356F99"),
Wallet1("#2E3944"),
Twins("#B8B7B6"),
Devkit("#938C92"),
WhiteWallet("#DDDDDD"),
Shiba("#D5963A"),
}

View file

@ -27,6 +27,7 @@ import com.tangem.domain.pay.usecase.TangemPayMainScreenCustomerInfoUseCase
import com.tangem.domain.settings.*
import com.tangem.domain.tokens.RefreshMultiCurrencyWalletQuotesUseCase
import com.tangem.domain.wallets.usecase.*
import com.tangem.domain.wallets.usecase.GetWalletIconUseCase
import com.tangem.domain.yield.supply.usecase.YieldSupplyApyUpdateUseCase
import com.tangem.feature.wallet.child.wallet.model.intents.WalletClickIntents
import com.tangem.feature.wallet.presentation.router.InnerWalletRouter
@ -100,6 +101,7 @@ internal class WalletModel @Inject constructor(
private val bindRefcodeWithWalletUseCase: BindRefcodeWithWalletUseCase,
private val appsFlyerStore: AppsFlyerStore,
private val getSelectedAppCurrencyUseCase: GetSelectedAppCurrencyUseCase,
private val getWalletIconUseCase: GetWalletIconUseCase,
val screenLifecycleProvider: ScreenLifecycleProvider,
val innerWalletRouter: InnerWalletRouter,
) : Model() {
@ -520,6 +522,7 @@ internal class WalletModel @Inject constructor(
wallets = action.wallets,
clickIntents = clickIntents,
walletImageResolver = walletImageResolver,
getWalletIconUseCase = getWalletIconUseCase,
),
)
@ -565,6 +568,7 @@ internal class WalletModel @Inject constructor(
newUserWallet = action.selectedWallet,
clickIntents = clickIntents,
walletImageResolver = walletImageResolver,
getWalletIconUseCase = getWalletIconUseCase,
),
)
}
@ -585,6 +589,7 @@ internal class WalletModel @Inject constructor(
userWallet = userWallet,
clickIntents = clickIntents,
walletImageResolver = walletImageResolver,
getWalletIconUseCase = getWalletIconUseCase,
),
)
}
@ -598,6 +603,7 @@ internal class WalletModel @Inject constructor(
userWallet = action.selectedWallet,
clickIntents = clickIntents,
walletImageResolver = walletImageResolver,
getWalletIconUseCase = getWalletIconUseCase,
),
)
@ -658,6 +664,7 @@ internal class WalletModel @Inject constructor(
unlockedWallets = action.unlockedWallets,
clickIntents = clickIntents,
walletImageResolver = walletImageResolver,
getWalletIconUseCase = getWalletIconUseCase,
),
)

View file

@ -1,6 +1,7 @@
package com.tangem.feature.wallet.presentation.preview
import androidx.compose.ui.text.SpanStyle
import com.tangem.core.ui.ds.image.DeviceIconUM
import com.tangem.core.ui.extensions.combinedReference
import com.tangem.core.ui.extensions.stringReference
import com.tangem.core.ui.extensions.styledStringReference
@ -31,6 +32,7 @@ internal object WalletBalancePreview {
),
stringReference(" $"),
),
deviceIcon = DeviceIconUM.Stub(cardsCount = 3),
isBalanceFlickering = false,
isZeroBalance = false,
)
@ -38,10 +40,12 @@ internal object WalletBalancePreview {
val loading: WalletBalanceUM.Loading = WalletBalanceUM.Loading(
id = UserWalletId("1"),
name = "My Wallet",
deviceIcon = DeviceIconUM.Mobile,
)
val error: WalletBalanceUM.Error = WalletBalanceUM.Error(
id = UserWalletId("2"),
name = "My Wallet",
deviceIcon = DeviceIconUM.Stub(cardsCount = 3),
)
}

View file

@ -17,6 +17,7 @@ import javax.inject.Inject
*
[REDACTED_AUTHOR]
*/
@Deprecated("Will be removed in favor of getWalletIconUseCase")
internal class WalletImageResolver @Inject constructor(
private val walletsRepository: WalletsRepository,
) {

View file

@ -1,6 +1,7 @@
package com.tangem.feature.wallet.presentation.wallet.state.model
import androidx.compose.runtime.Immutable
import com.tangem.core.ui.ds.image.DeviceIconUM
import com.tangem.core.ui.extensions.TextReference
import com.tangem.domain.models.wallet.UserWalletId
@ -24,6 +25,9 @@ internal sealed interface WalletBalanceUM {
/** Wallet Name */
val name: String
/** Wallet Icon */
val deviceIcon: DeviceIconUM
/**
* Wallet card content state
*
@ -34,6 +38,7 @@ internal sealed interface WalletBalanceUM {
data class Content(
override val id: UserWalletId,
override val name: String,
override val deviceIcon: DeviceIconUM,
val balance: TextReference,
val balanceInAppBar: TextReference,
val isBalanceFlickering: Boolean,
@ -49,6 +54,7 @@ internal sealed interface WalletBalanceUM {
data class Error(
override val id: UserWalletId,
override val name: String,
override val deviceIcon: DeviceIconUM,
) : WalletBalanceUM
/**
@ -60,6 +66,7 @@ internal sealed interface WalletBalanceUM {
data class Loading(
override val id: UserWalletId,
override val name: String,
override val deviceIcon: DeviceIconUM,
) : WalletBalanceUM
fun copySealed(name: String): WalletBalanceUM {

View file

@ -1,6 +1,7 @@
package com.tangem.feature.wallet.presentation.wallet.state.transformers
import com.tangem.domain.models.wallet.UserWallet
import com.tangem.domain.wallets.usecase.GetWalletIconUseCase
import com.tangem.feature.wallet.child.wallet.model.intents.WalletClickIntents
import com.tangem.feature.wallet.presentation.wallet.domain.WalletImageResolver
import com.tangem.feature.wallet.presentation.wallet.state.model.WalletScreenState
@ -11,12 +12,14 @@ internal class AddWalletTransformer(
private val userWallet: UserWallet,
private val clickIntents: WalletClickIntents,
private val walletImageResolver: WalletImageResolver,
private val getWalletIconUseCase: GetWalletIconUseCase,
) : WalletScreenStateTransformer {
private val walletLoadingStateFactory by lazy {
WalletLoadingStateFactory(
clickIntents = clickIntents,
walletImageResolver = walletImageResolver,
getWalletIconUseCase = getWalletIconUseCase,
)
}

View file

@ -1,9 +1,11 @@
package com.tangem.feature.wallet.presentation.wallet.state.transformers
import com.tangem.common.ui.userwallet.converter.WalletIconUMConverter
import com.tangem.core.ui.ds.button.TangemButtonUM
import com.tangem.domain.card.common.util.cardTypesResolver
import com.tangem.domain.models.wallet.UserWallet
import com.tangem.domain.models.wallet.isLocked
import com.tangem.domain.wallets.usecase.GetWalletIconUseCase
import com.tangem.feature.wallet.child.wallet.model.intents.WalletClickIntents
import com.tangem.feature.wallet.presentation.wallet.domain.WalletAdditionalInfoFactory
import com.tangem.feature.wallet.presentation.wallet.domain.WalletImageResolver
@ -22,12 +24,14 @@ internal class InitializeWalletsTransformer(
private val wallets: List<UserWallet>,
private val clickIntents: WalletClickIntents,
private val walletImageResolver: WalletImageResolver,
private val getWalletIconUseCase: GetWalletIconUseCase,
) : WalletScreenStateTransformer {
private val walletLoadingStateFactory by lazy {
WalletLoadingStateFactory(
clickIntents = clickIntents,
walletImageResolver = walletImageResolver,
getWalletIconUseCase = getWalletIconUseCase,
)
}
@ -111,6 +115,8 @@ internal class InitializeWalletsTransformer(
walletsBalanceUM = WalletBalanceUM.Loading(
id = walletId,
name = name,
deviceIcon = getWalletIconUseCase.invoke(userWallet = this)
.let { WalletIconUMConverter().convert(it) },
),
buttons = createWalletActions(userWallet = this),
type = when (this) {

View file

@ -2,6 +2,7 @@ package com.tangem.feature.wallet.presentation.wallet.state.transformers
import com.tangem.domain.models.wallet.UserWallet
import com.tangem.domain.models.wallet.UserWalletId
import com.tangem.domain.wallets.usecase.GetWalletIconUseCase
import com.tangem.feature.wallet.child.wallet.model.intents.WalletClickIntents
import com.tangem.feature.wallet.presentation.wallet.domain.WalletImageResolver
import com.tangem.feature.wallet.presentation.wallet.state.model.WalletScreenState
@ -22,12 +23,14 @@ internal class ReinitializeNewWalletTransformer(
private val newUserWallet: UserWallet,
private val clickIntents: WalletClickIntents,
private val walletImageResolver: WalletImageResolver,
private val getWalletIconUseCase: GetWalletIconUseCase,
) : WalletScreenStateTransformer {
private val walletLoadingStateFactory by lazy {
WalletLoadingStateFactory(
clickIntents = clickIntents,
walletImageResolver = walletImageResolver,
getWalletIconUseCase = getWalletIconUseCase,
)
}

View file

@ -1,6 +1,7 @@
package com.tangem.feature.wallet.presentation.wallet.state.transformers
import com.tangem.domain.models.wallet.UserWallet
import com.tangem.domain.wallets.usecase.GetWalletIconUseCase
import com.tangem.feature.wallet.child.wallet.model.intents.WalletClickIntents
import com.tangem.feature.wallet.presentation.wallet.domain.WalletImageResolver
import com.tangem.feature.wallet.presentation.wallet.state.model.WalletState
@ -18,12 +19,14 @@ internal class ReinitializeWalletTransformer(
private val userWallet: UserWallet,
private val clickIntents: WalletClickIntents,
private val walletImageResolver: WalletImageResolver,
private val getWalletIconUseCase: GetWalletIconUseCase,
) : WalletStateTransformer(userWalletId = userWallet.walletId) {
private val walletLoadingStateFactory by lazy {
WalletLoadingStateFactory(
clickIntents = clickIntents,
walletImageResolver = walletImageResolver,
getWalletIconUseCase = getWalletIconUseCase,
)
}

View file

@ -91,6 +91,7 @@ internal class SetTokenListErrorTransformer(
return WalletBalanceUM.Content(
id = id,
name = name,
deviceIcon = deviceIcon,
balanceInAppBar = BigDecimal.ZERO.formatStyled {
fiat(
fiatCurrencyCode = appCurrency.code,

View file

@ -2,6 +2,7 @@ package com.tangem.feature.wallet.presentation.wallet.state.transformers
import com.tangem.domain.models.wallet.UserWallet
import com.tangem.domain.models.wallet.UserWalletId
import com.tangem.domain.wallets.usecase.GetWalletIconUseCase
import com.tangem.feature.wallet.child.wallet.model.intents.WalletClickIntents
import com.tangem.feature.wallet.presentation.wallet.domain.WalletImageResolver
import com.tangem.feature.wallet.presentation.wallet.state.model.WalletScreenState
@ -16,12 +17,14 @@ internal class UnlockWalletTransformer(
private val unlockedWallets: List<UserWallet>,
private val clickIntents: WalletClickIntents,
private val walletImageResolver: WalletImageResolver,
private val getWalletIconUseCase: GetWalletIconUseCase,
) : WalletScreenStateTransformer {
private val walletLoadingStateFactory by lazy {
WalletLoadingStateFactory(
clickIntents = clickIntents,
walletImageResolver = walletImageResolver,
getWalletIconUseCase = getWalletIconUseCase,
)
}

View file

@ -28,6 +28,7 @@ internal class MultiWalletBalanceUMTransformer(
return WalletBalanceUM.Loading(
id = id,
name = name,
deviceIcon = deviceIcon,
)
}
@ -35,6 +36,7 @@ internal class MultiWalletBalanceUMTransformer(
return WalletBalanceUM.Error(
id = id,
name = name,
deviceIcon = deviceIcon,
)
}
@ -42,6 +44,7 @@ internal class MultiWalletBalanceUMTransformer(
return WalletBalanceUM.Content(
id = id,
name = name,
deviceIcon = deviceIcon,
balanceInAppBar = fiatBalance.amount.formatStyled {
fiat(
fiatCurrencyCode = appCurrency.code,

View file

@ -1,5 +1,6 @@
package com.tangem.feature.wallet.presentation.wallet.state.utils
import com.tangem.common.ui.userwallet.converter.WalletIconUMConverter
import com.tangem.core.analytics.models.event.MainScreenAnalyticsEvent.Companion.WALLET_TYPE
import com.tangem.core.ui.components.containers.pullToRefresh.PullToRefreshConfig
import com.tangem.core.ui.components.marketprice.MarketPriceBlockState
@ -10,6 +11,7 @@ import com.tangem.domain.card.common.util.cardTypesResolver
import com.tangem.domain.models.wallet.UserWallet
import com.tangem.domain.models.wallet.UserWalletId
import com.tangem.domain.models.wallet.isMultiCurrency
import com.tangem.domain.wallets.usecase.GetWalletIconUseCase
import com.tangem.feature.wallet.child.wallet.model.intents.WalletClickIntents
import com.tangem.feature.wallet.impl.R
import com.tangem.feature.wallet.presentation.wallet.domain.WalletAdditionalInfoFactory
@ -30,6 +32,7 @@ import kotlinx.coroutines.flow.MutableStateFlow
internal class WalletLoadingStateFactory(
private val clickIntents: WalletClickIntents,
private val walletImageResolver: WalletImageResolver,
private val getWalletIconUseCase: GetWalletIconUseCase,
) {
fun create(userWallet: UserWallet): WalletState {
@ -52,6 +55,8 @@ internal class WalletLoadingStateFactory(
walletsBalanceUM = WalletBalanceUM.Loading(
id = userWallet.walletId,
name = userWallet.name,
deviceIcon = getWalletIconUseCase.invoke(userWallet = userWallet)
.let { WalletIconUMConverter().convert(it) },
),
buttons = createWalletActions(userWallet),
notifications = persistentListOf(),

View file

@ -8,7 +8,6 @@ import androidx.compose.animation.fadeOut
import androidx.compose.animation.togetherWith
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.text.TextAutoSize
import androidx.compose.material3.Icon
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.key
@ -16,21 +15,19 @@ import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.alpha
import androidx.compose.ui.draw.scale
import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.platform.testTag
import androidx.compose.ui.res.vectorResource
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.tooling.preview.PreviewParameter
import androidx.compose.ui.tooling.preview.PreviewParameterProvider
import androidx.compose.ui.unit.dp
import com.tangem.core.ui.R
import com.tangem.core.ui.components.SpacerH
import com.tangem.core.ui.components.TextShimmer
import com.tangem.core.ui.components.text.applyBladeBrush
import com.tangem.core.ui.ds.button.SecondaryTangemButton
import com.tangem.core.ui.ds.button.TangemButtonShape
import com.tangem.core.ui.ds.button.TangemButtonUM
import com.tangem.core.ui.ds.image.TangemDeviceIcon
import com.tangem.core.ui.ds.topbar.collapsing.TangemCollapsingAppBarBehavior
import com.tangem.core.ui.ds.topbar.collapsing.rememberTangemExitUntilCollapsedScrollBehavior
import com.tangem.core.ui.ds.topbar.collapsing.snapToExitUntilCollapsed
@ -87,19 +84,14 @@ internal fun WalletBalance(
SpacerH(TangemTheme.dimens2.x3)
Row(
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.spacedBy(TangemTheme.dimens2.x2),
horizontalArrangement = Arrangement.spacedBy(TangemTheme.dimens2.x1),
) {
Text(
text = walletBalanceUM.name,
style = TangemTheme.typography2.bodyRegular14,
color = TangemTheme.colors2.text.neutral.tertiary,
)
Icon(
imageVector = ImageVector.vectorResource(R.drawable.ic_tangem_24),
contentDescription = null,
tint = TangemTheme.colors2.graphic.neutral.tertiaryConstant,
modifier = Modifier.size(TangemTheme.dimens2.x6),
)
TangemDeviceIcon(state = walletBalanceUM.deviceIcon)
}
}
SpacerH(TangemTheme.dimens2.x2)