diff --git a/core/ui/src/main/java/com/tangem/core/ui/components/block/BlockCard.kt b/core/ui/src/main/java/com/tangem/core/ui/components/block/BlockCard.kt index 24f64babdc..49752f2700 100644 --- a/core/ui/src/main/java/com/tangem/core/ui/components/block/BlockCard.kt +++ b/core/ui/src/main/java/com/tangem/core/ui/components/block/BlockCard.kt @@ -32,6 +32,6 @@ val TangemBlockCardColors: CardColors get() = CardColors( containerColor = TangemTheme.colors.background.primary, contentColor = TangemTheme.colors.text.primary1, - disabledContainerColor = TangemTheme.colors.button.disabled, - disabledContentColor = TangemTheme.colors.text.disabled, + disabledContainerColor = TangemTheme.colors.background.primary, + disabledContentColor = TangemTheme.colors.text.primary1, ) \ No newline at end of file diff --git a/features/details/impl/build.gradle.kts b/features/details/impl/build.gradle.kts index c1f2899eff..c6694323ba 100644 --- a/features/details/impl/build.gradle.kts +++ b/features/details/impl/build.gradle.kts @@ -37,11 +37,15 @@ dependencies { implementation(projects.domain.appCurrency) implementation(projects.domain.appCurrency.models) implementation(projects.domain.walletConnect) + implementation(projects.domain.balanceHiding) + implementation(projects.domain.balanceHiding.models) implementation(projects.domain.legacy) /* SDK */ // TODO: For TangemError model, should be removed after card domain scanning refactoring implementation(deps.tangem.card.core) + // For image resolving + implementation(deps.tangem.blockchain) /* AndroidX */ implementation(deps.androidx.fragment.ktx) @@ -55,6 +59,7 @@ dependencies { implementation(deps.compose.foundation) implementation(deps.compose.material3) implementation(deps.compose.shimmer) + implementation(deps.compose.coil) /* DI */ implementation(deps.hilt.android) diff --git a/features/details/impl/src/main/kotlin/com/tangem/features/details/component/preview/PreviewUserWalletListComponent.kt b/features/details/impl/src/main/kotlin/com/tangem/features/details/component/preview/PreviewUserWalletListComponent.kt index a8906b5061..92b6f46de3 100644 --- a/features/details/impl/src/main/kotlin/com/tangem/features/details/component/preview/PreviewUserWalletListComponent.kt +++ b/features/details/impl/src/main/kotlin/com/tangem/features/details/component/preview/PreviewUserWalletListComponent.kt @@ -19,23 +19,26 @@ internal class PreviewUserWalletListComponent : UserWalletListComponent { userWallets = persistentListOf( UserWalletListUM.UserWalletUM( id = UserWalletId("user_wallet_1".encodeToByteArray()), - name = "My Wallet", + name = stringReference("My Wallet"), information = getInformation(3, "4 496,75 $"), - imageResId = R.drawable.ill_card_wallet_2_211_343, + imageUrl = "", + isEnabled = true, onClick = {}, ), UserWalletListUM.UserWalletUM( id = UserWalletId("user_wallet_2".encodeToByteArray()), - name = "Old wallet", + name = stringReference("Old wallet"), information = getInformation(3, "4 496,75 $"), - imageResId = R.drawable.ill_card_note_eth_211_343, + imageUrl = "", + isEnabled = true, onClick = {}, ), UserWalletListUM.UserWalletUM( id = UserWalletId("user_wallet_3".encodeToByteArray()), - name = "Multi Card", + name = stringReference("Multi Card"), information = getInformation(3, "4 496,75 $"), - imageResId = R.drawable.ill_card_note_bnb_211_343, + imageUrl = "", + isEnabled = false, onClick = {}, ), ), diff --git a/features/details/impl/src/main/kotlin/com/tangem/features/details/entity/UserWalletListUM.kt b/features/details/impl/src/main/kotlin/com/tangem/features/details/entity/UserWalletListUM.kt index d6f947b063..1569f48efb 100644 --- a/features/details/impl/src/main/kotlin/com/tangem/features/details/entity/UserWalletListUM.kt +++ b/features/details/impl/src/main/kotlin/com/tangem/features/details/entity/UserWalletListUM.kt @@ -1,10 +1,11 @@ package com.tangem.features.details.entity -import androidx.annotation.DrawableRes +import androidx.compose.runtime.Immutable import com.tangem.core.ui.extensions.TextReference import com.tangem.domain.wallets.models.UserWalletId import kotlinx.collections.immutable.ImmutableList +@Immutable internal data class UserWalletListUM( val userWallets: ImmutableList, val isWalletSavingInProgress: Boolean, @@ -12,12 +13,13 @@ internal data class UserWalletListUM( val onAddNewWalletClick: () -> Unit, ) { + @Immutable data class UserWalletUM( val id: UserWalletId, - val name: String, + val name: TextReference, val information: TextReference, - @DrawableRes - val imageResId: Int, + val imageUrl: String, + val isEnabled: Boolean, val onClick: () -> Unit, ) } \ No newline at end of file diff --git a/features/details/impl/src/main/kotlin/com/tangem/features/details/ui/UserWalletListBlock.kt b/features/details/impl/src/main/kotlin/com/tangem/features/details/ui/UserWalletListBlock.kt index cb235d20fa..794ef632c3 100644 --- a/features/details/impl/src/main/kotlin/com/tangem/features/details/ui/UserWalletListBlock.kt +++ b/features/details/impl/src/main/kotlin/com/tangem/features/details/ui/UserWalletListBlock.kt @@ -10,15 +10,21 @@ import androidx.compose.runtime.Composable import androidx.compose.runtime.key import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier -import androidx.compose.ui.layout.ContentScale +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.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) { @@ -46,6 +52,7 @@ private fun UserWalletItem(model: UserWalletListUM.UserWalletUM, modifier: Modif BlockCard( modifier = modifier, onClick = model.onClick, + enabled = model.isEnabled, ) { Row( modifier = Modifier @@ -55,39 +62,81 @@ private fun UserWalletItem(model: UserWalletListUM.UserWalletUM, modifier: Modif verticalAlignment = Alignment.CenterVertically, horizontalArrangement = Arrangement.spacedBy(TangemTheme.dimens.spacing12), ) { - Image( - modifier = Modifier - .width(TangemTheme.dimens.size24) - .height(TangemTheme.dimens.size36), - painter = painterResource(id = model.imageResId), - contentScale = ContentScale.FillBounds, - contentDescription = null, + Image(imageUrl = model.imageUrl) + NameAndInfo( + name = model.name, + information = model.information, ) - - Column( - modifier = Modifier.heightIn(min = TangemTheme.dimens.size40), - horizontalAlignment = Alignment.Start, - verticalArrangement = Arrangement.SpaceEvenly, - ) { - Text( - text = model.name, - style = TangemTheme.typography.subtitle1, - color = TangemTheme.colors.text.primary1, - maxLines = 1, - overflow = TextOverflow.Ellipsis, - ) - Text( - text = model.information.resolveReference(), - style = TangemTheme.typography.caption2, - color = TangemTheme.colors.text.tertiary, - maxLines = 1, - overflow = TextOverflow.Ellipsis, - ) - } } } } +@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, @@ -110,6 +159,7 @@ private fun AddWalletButton( AnimatedContent( modifier = Modifier.size(TangemTheme.dimens.size24), targetState = isInProgress, + label = "Add wallet progress", ) { isInProgress -> if (isInProgress) { CircularProgressIndicator( diff --git a/features/details/impl/src/main/kotlin/com/tangem/features/details/ui/coil/RotationTransformation.kt b/features/details/impl/src/main/kotlin/com/tangem/features/details/ui/coil/RotationTransformation.kt new file mode 100644 index 0000000000..ca7f0929d0 --- /dev/null +++ b/features/details/impl/src/main/kotlin/com/tangem/features/details/ui/coil/RotationTransformation.kt @@ -0,0 +1,22 @@ +package com.tangem.features.details.ui.coil + +import android.graphics.Bitmap +import android.graphics.Matrix +import coil.size.Size +import coil.transform.Transformation + +internal class RotationTransformation(private val angle: Float) : Transformation { + + override val cacheKey: String = "rotate:$angle" + + override suspend fun transform(input: Bitmap, size: Size): Bitmap { + val matrix = Matrix().apply { + val centerX = input.width / 2f + val centerY = input.height / 2f + + postRotate(angle, centerX, centerY) + } + + return Bitmap.createBitmap(input, 0, 0, input.width, input.height, matrix, true) + } +} \ No newline at end of file diff --git a/features/details/impl/src/main/kotlin/com/tangem/features/details/utils/UserWalletMappers.kt b/features/details/impl/src/main/kotlin/com/tangem/features/details/utils/UserWalletMappers.kt index 992fee20eb..94fc6e00b1 100644 --- a/features/details/impl/src/main/kotlin/com/tangem/features/details/utils/UserWalletMappers.kt +++ b/features/details/impl/src/main/kotlin/com/tangem/features/details/utils/UserWalletMappers.kt @@ -1,9 +1,6 @@ package com.tangem.features.details.utils -import androidx.annotation.DrawableRes -import com.tangem.core.ui.extensions.TextReference -import com.tangem.core.ui.extensions.stringReference -import com.tangem.core.ui.extensions.wrappedList +import com.tangem.core.ui.extensions.* import com.tangem.core.ui.utils.BigDecimalFormatter import com.tangem.domain.appcurrency.model.AppCurrency import com.tangem.domain.models.scan.CardDTO @@ -12,6 +9,7 @@ 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.Strings.STARS import kotlinx.collections.immutable.ImmutableList import kotlinx.collections.immutable.toImmutableList @@ -19,11 +17,15 @@ internal fun List.toUiModels( onClick: (UserWalletId) -> Unit, appCurrency: AppCurrency? = null, balances: Map = emptyMap(), + isLoading: Boolean = true, + isBalancesHidden: Boolean = false, ): ImmutableList = this.map { model -> val balance = balances[model.walletId] model.mapToUiModel( balance = balance, appCurrency = appCurrency, + isLoading = isLoading, + isBalanceHidden = isBalancesHidden, onClick = { onClick(model.walletId) }, ) }.toImmutableList() @@ -31,22 +33,52 @@ internal fun List.toUiModels( private fun UserWallet.mapToUiModel( balance: TotalFiatBalance?, appCurrency: AppCurrency?, + isLoading: Boolean, + isBalanceHidden: Boolean, onClick: () -> Unit, ): UserWalletUM = UserWalletUM( id = walletId, - name = name, - information = getInfo(appCurrency, balance), - imageResId = resolveImage(), + name = stringReference(name), + information = getInfo( + appCurrency = appCurrency, + balance = balance, + isBalanceHidden = isBalanceHidden, + isLoading = isLoading, + ), + imageUrl = artworkUrl, + isEnabled = !isLocked, onClick = onClick, ) -private fun UserWallet.getInfo(appCurrency: AppCurrency?, balance: TotalFiatBalance?): TextReference { +private fun UserWallet.getInfo( + appCurrency: AppCurrency?, + balance: TotalFiatBalance?, + isBalanceHidden: Boolean, + isLoading: Boolean, +): TextReference { + val dividerRef = stringReference(value = " • ") + val cardCount = getCardCount() val cardCountRef = TextReference.PluralRes( id = R.plurals.card_label_card_count, count = cardCount, formatArgs = wrappedList(cardCount), ) + + return when { + isLocked -> combinedReference(cardCountRef, dividerRef, resourceReference(R.string.common_locked)) + isLoading -> cardCountRef + isBalanceHidden -> combinedReference(cardCountRef, dividerRef, stringReference(STARS)) + else -> getBalanceInfo(balance, appCurrency, cardCountRef, dividerRef) + } +} + +private fun getBalanceInfo( + balance: TotalFiatBalance?, + appCurrency: AppCurrency?, + cardCountRef: TextReference, + dividerRef: TextReference, +): TextReference { val amount = when (balance) { is TotalFiatBalance.Loaded -> balance.amount.takeIf { balance.isAllAmountsSummarized } is TotalFiatBalance.Failed, @@ -56,16 +88,15 @@ private fun UserWallet.getInfo(appCurrency: AppCurrency?, balance: TotalFiatBala } return if (amount != null && appCurrency != null) { - val divider = stringReference(value = " • ") val formattedAmount = BigDecimalFormatter.formatFiatAmount( fiatAmount = amount, fiatCurrencyCode = appCurrency.code, fiatCurrencySymbol = appCurrency.symbol, ) val amountRef = stringReference(formattedAmount) - TextReference.Combined(wrappedList(cardCountRef, divider, amountRef)) + combinedReference(cardCountRef, dividerRef, amountRef) } else { - cardCountRef + combinedReference(cardCountRef, dividerRef, stringReference(BigDecimalFormatter.EMPTY_BALANCE_SIGN)) } } @@ -75,10 +106,4 @@ private fun UserWallet.getCardCount() = when (val status = scanResponse.card.bac is CardDTO.BackupStatus.NoBackup, null, -> 1 -} - -@DrawableRes -private fun UserWallet.resolveImage(): Int { - // TODO: Implement image resolving [REDACTED_JIRA] - return R.drawable.ill_card_wallet_2_211_343 } \ No newline at end of file diff --git a/features/details/impl/src/main/kotlin/com/tangem/features/details/utils/UserWalletsFetcher.kt b/features/details/impl/src/main/kotlin/com/tangem/features/details/utils/UserWalletsFetcher.kt index 5377be02ea..e141c4034e 100644 --- a/features/details/impl/src/main/kotlin/com/tangem/features/details/utils/UserWalletsFetcher.kt +++ b/features/details/impl/src/main/kotlin/com/tangem/features/details/utils/UserWalletsFetcher.kt @@ -10,6 +10,8 @@ import com.tangem.core.ui.message.SnackbarMessage import com.tangem.domain.appcurrency.GetSelectedAppCurrencyUseCase import com.tangem.domain.appcurrency.error.SelectedAppCurrencyError import com.tangem.domain.appcurrency.model.AppCurrency +import com.tangem.domain.balancehiding.BalanceHidingSettings +import com.tangem.domain.balancehiding.GetBalanceHidingSettingsUseCase import com.tangem.domain.core.lce.Lce import com.tangem.domain.core.lce.lce import com.tangem.domain.core.utils.getOrElse @@ -24,10 +26,7 @@ import com.tangem.features.details.entity.UserWalletListUM.UserWalletUM import com.tangem.features.details.impl.R import kotlinx.collections.immutable.ImmutableList import kotlinx.coroutines.ExperimentalCoroutinesApi -import kotlinx.coroutines.flow.Flow -import kotlinx.coroutines.flow.collect -import kotlinx.coroutines.flow.combine -import kotlinx.coroutines.flow.transformLatest +import kotlinx.coroutines.flow.* import javax.inject.Inject @ComponentScoped @@ -35,6 +34,7 @@ internal class UserWalletsFetcher @Inject constructor( getWalletsUseCase: GetWalletsUseCase, private val getWalletTotalBalanceUseCase: GetWalletTotalBalanceUseCase, private val getSelectedAppCurrencyUseCase: GetSelectedAppCurrencyUseCase, + private val getBalanceHidingSettingsUseCase: GetBalanceHidingSettingsUseCase, private val router: Router, private val messageSender: UiMessageSender, ) { @@ -44,10 +44,16 @@ internal class UserWalletsFetcher @Inject constructor( emit(wallets.toUiModels(onClick = ::navigateToWalletSettings)) combine( - getSelectedAppCurrencyUseCase(), - getWalletTotalBalanceUseCase(wallets.map(UserWallet::walletId)), - ) { maybeAppCurrency, maybeBalances -> - val models = createUiModels(wallets, maybeAppCurrency, maybeBalances).getOrElse( + getSelectedAppCurrencyUseCase().distinctUntilChanged(), + getBalanceHidingSettingsUseCase().distinctUntilChanged(), + getWalletTotalBalanceUseCase(wallets.map(UserWallet::walletId)).distinctUntilChanged(), + ) { maybeAppCurrency, balanceHidingSettings, maybeBalances -> + val models = createUiModels( + wallets = wallets, + maybeAppCurrency = maybeAppCurrency, + maybeBalances = maybeBalances, + balanceHidingSettings = balanceHidingSettings, + ).getOrElse( ifLoading = { return@combine }, ifError = { val message = resourceReference(R.string.common_unknown_error) @@ -65,10 +71,11 @@ internal class UserWalletsFetcher @Inject constructor( wallets: List, maybeAppCurrency: Either, maybeBalances: Lce>, + balanceHidingSettings: BalanceHidingSettings, ): Lce> = lce { val balances = withError( transform = { Error.UnableToGetBalances }, - block = { maybeBalances.bind() }, + block = { maybeBalances.bindOrNull().orEmpty() }, ) val appCurrency = withError( transform = { Error.UnableToGetAppCurrency }, @@ -79,6 +86,8 @@ internal class UserWalletsFetcher @Inject constructor( appCurrency = appCurrency, balances = balances, onClick = ::navigateToWalletSettings, + isBalancesHidden = balanceHidingSettings.isBalanceHidden, + isLoading = maybeBalances.isLoading(), ) } diff --git a/features/details/impl/src/main/res/drawable/ill_card_note_bnb_211_343.png b/features/details/impl/src/main/res/drawable/ill_card_note_bnb_211_343.png deleted file mode 100644 index 033efd2699..0000000000 Binary files a/features/details/impl/src/main/res/drawable/ill_card_note_bnb_211_343.png and /dev/null differ diff --git a/features/details/impl/src/main/res/drawable/ill_card_note_eth_211_343.png b/features/details/impl/src/main/res/drawable/ill_card_note_eth_211_343.png deleted file mode 100644 index b338ee1eb5..0000000000 Binary files a/features/details/impl/src/main/res/drawable/ill_card_note_eth_211_343.png and /dev/null differ diff --git a/features/details/impl/src/main/res/drawable/ill_card_wallet_2_211_343.png b/features/details/impl/src/main/res/drawable/ill_card_wallet_2_211_343.png deleted file mode 100644 index 9625b941a1..0000000000 Binary files a/features/details/impl/src/main/res/drawable/ill_card_wallet_2_211_343.png and /dev/null differ diff --git a/features/details/impl/src/main/res/drawable/img_card_wallet_2_gray_22_36.xml b/features/details/impl/src/main/res/drawable/img_card_wallet_2_gray_22_36.xml new file mode 100644 index 0000000000..977d693e60 --- /dev/null +++ b/features/details/impl/src/main/res/drawable/img_card_wallet_2_gray_22_36.xml @@ -0,0 +1,13 @@ + + + + +