diff --git a/app/src/main/java/com/tangem/tap/features/tokens/impl/presentation/ui/TokenItem.kt b/app/src/main/java/com/tangem/tap/features/tokens/impl/presentation/ui/TokenItem.kt index 9107775b0a..bae044a2a6 100644 --- a/app/src/main/java/com/tangem/tap/features/tokens/impl/presentation/ui/TokenItem.kt +++ b/app/src/main/java/com/tangem/tap/features/tokens/impl/presentation/ui/TokenItem.kt @@ -14,6 +14,7 @@ import androidx.compose.ui.draw.clip import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.toArgb import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.platform.LocalDensity import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.style.TextAlign @@ -25,7 +26,6 @@ import coil.request.ImageRequest import com.tangem.core.ui.components.CurrencyPlaceholderIcon import com.tangem.core.ui.res.TangemTheme import com.tangem.core.ui.utils.ImageBackgroundContrastChecker -import com.tangem.tap.common.compose.extensions.toPx import com.tangem.tap.features.tokens.impl.presentation.states.TokenItemState import com.tangem.wallet.R import kotlinx.coroutines.launch @@ -126,29 +126,37 @@ internal fun TokenItem(model: TokenItemState) { @Composable private fun Icon(name: String, iconUrl: String, onContrastCalculate: (Color) -> Unit, modifier: Modifier = Modifier) { + val pixelsSize = with(LocalDensity.current) { TangemTheme.dimens.size46.roundToPx() } val iconModifier = modifier .size(size = TangemTheme.dimens.size46) .clip(TangemTheme.shapes.roundedCorners8) - val screenBackgroundColor = TangemTheme.colors.background.primary.toArgb() + + var iconBackgroundColor by remember { mutableStateOf(Color.Transparent) } + var isBackgroundColorDefined by remember { mutableStateOf(false) } + val itemBackgroundColor = TangemTheme.colors.background.primary.toArgb() val isDarkTheme = isSystemInDarkTheme() val coroutineScope = rememberCoroutineScope() SubcomposeAsyncImage( modifier = iconModifier, model = ImageRequest.Builder(context = LocalContext.current) - .size(size = TangemTheme.dimens.size46.toPx().toInt()) .data(data = iconUrl) + .size(size = pixelsSize) + .memoryCacheKey(key = iconUrl + pixelsSize) .crossfade(enable = true) .allowHardware(false) .listener( onSuccess = { _, result -> - if (isDarkTheme) { + if (!isBackgroundColorDefined && isDarkTheme) { coroutineScope.launch { val color = ImageBackgroundContrastChecker( drawable = result.drawable, - backgroundColor = screenBackgroundColor, - ).getContrastColorIfNeeded(isDarkTheme) + backgroundColor = itemBackgroundColor, + size = pixelsSize, + ).getContrastColor(isDarkTheme = true) onContrastCalculate(color) + iconBackgroundColor = color + isBackgroundColorDefined = true } } }, @@ -189,13 +197,12 @@ private fun Subtitle(isExpanded: Boolean, modifier: Modifier = Modifier) { } } -@OptIn(ExperimentalAnimationApi::class) @Composable private fun ChangeNetworksViewButton(isExpanded: Boolean, onClick: () -> Unit, modifier: Modifier = Modifier) { IconButton(onClick = onClick, modifier = modifier.size(size = TangemTheme.dimens.size46)) { AnimatedContent( targetState = isExpanded, - transitionSpec = { fadeIn() + scaleIn() with scaleOut() + fadeOut() }, + transitionSpec = { (fadeIn() + scaleIn()).togetherWith(scaleOut() + fadeOut()) }, ) { isExpanded -> Icon( painter = painterResource( diff --git a/core/ui/src/main/java/com/tangem/core/ui/components/currency/DefaultCurrencyIcon.kt b/core/ui/src/main/java/com/tangem/core/ui/components/currency/DefaultCurrencyIcon.kt index b169176106..005053e2c8 100644 --- a/core/ui/src/main/java/com/tangem/core/ui/components/currency/DefaultCurrencyIcon.kt +++ b/core/ui/src/main/java/com/tangem/core/ui/components/currency/DefaultCurrencyIcon.kt @@ -9,6 +9,8 @@ import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.ColorFilter import androidx.compose.ui.graphics.toArgb import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.platform.LocalDensity +import androidx.compose.ui.unit.Dp import coil.compose.SubcomposeAsyncImage import coil.request.ImageRequest import com.tangem.core.ui.components.currency.tokenicon.LoadingIcon @@ -19,16 +21,19 @@ import kotlinx.coroutines.launch @Composable internal inline fun DefaultCurrencyIcon( iconData: Any, + size: Dp, alpha: Float, colorFilter: ColorFilter?, crossinline errorIcon: @Composable () -> Unit, modifier: Modifier = Modifier, ) { var iconBackgroundColor by remember { mutableStateOf(Color.Transparent) } + var isBackgroundColorDefined by remember { mutableStateOf(false) } val itemBackgroundColor = TangemTheme.colors.background.primary.toArgb() val isDarkTheme = isSystemInDarkTheme() val coroutineScope = rememberCoroutineScope() + val pixelsSize = with(LocalDensity.current) { size.roundToPx() } SubcomposeAsyncImage( modifier = modifier .background( @@ -38,17 +43,21 @@ internal inline fun DefaultCurrencyIcon( .clip(TangemTheme.shapes.roundedCorners8), model = ImageRequest.Builder(context = LocalContext.current) .data(iconData) + .size(size = pixelsSize) + .memoryCacheKey(key = iconData.toString() + pixelsSize) .crossfade(enable = true) - .allowHardware(false) + .allowHardware(enable = false) .listener( onSuccess = { _, result -> - if (isDarkTheme) { + if (!isBackgroundColorDefined && isDarkTheme) { coroutineScope.launch { val color = ImageBackgroundContrastChecker( drawable = result.drawable, backgroundColor = itemBackgroundColor, - ).getContrastColorIfNeeded(isDarkTheme) + size = pixelsSize, + ).getContrastColor(isDarkTheme = true) iconBackgroundColor = color + isBackgroundColorDefined = true } } }, diff --git a/core/ui/src/main/java/com/tangem/core/ui/components/currency/fiaticon/FiatIcon.kt b/core/ui/src/main/java/com/tangem/core/ui/components/currency/fiaticon/FiatIcon.kt index 879ea4575a..3255b50af7 100644 --- a/core/ui/src/main/java/com/tangem/core/ui/components/currency/fiaticon/FiatIcon.kt +++ b/core/ui/src/main/java/com/tangem/core/ui/components/currency/fiaticon/FiatIcon.kt @@ -5,6 +5,7 @@ import androidx.compose.foundation.Image import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import androidx.compose.ui.res.painterResource +import androidx.compose.ui.unit.Dp import com.tangem.core.ui.R import com.tangem.core.ui.components.currency.DefaultCurrencyIcon @@ -18,21 +19,23 @@ import com.tangem.core.ui.components.currency.DefaultCurrencyIcon @Composable fun FiatIcon( url: String?, + size: Dp, modifier: Modifier = Modifier, @DrawableRes fallbackResId: Int = R.drawable.ic_shape_circle, ) { val iconData: Any = if (url.isNullOrBlank()) fallbackResId else url DefaultCurrencyIcon( - modifier = modifier, iconData = iconData, + size = size, + alpha = 1f, + colorFilter = null, errorIcon = { Image( painter = painterResource(id = fallbackResId), contentDescription = null, ) }, - alpha = 1f, - colorFilter = null, + modifier = modifier, ) } \ No newline at end of file diff --git a/core/ui/src/main/java/com/tangem/core/ui/components/currency/tokenicon/ContentIcon.kt b/core/ui/src/main/java/com/tangem/core/ui/components/currency/tokenicon/ContentIcon.kt index dd3e892301..cdf96d7b53 100644 --- a/core/ui/src/main/java/com/tangem/core/ui/components/currency/tokenicon/ContentIcon.kt +++ b/core/ui/src/main/java/com/tangem/core/ui/components/currency/tokenicon/ContentIcon.kt @@ -14,6 +14,7 @@ import androidx.compose.ui.graphics.ColorFilter import androidx.compose.ui.res.painterResource import com.tangem.core.ui.R import com.tangem.core.ui.components.currency.DefaultCurrencyIcon +import com.tangem.core.ui.res.TangemTheme @Composable internal fun ContentIcon( @@ -67,8 +68,10 @@ private fun CoinIcon( val iconData: Any = if (url.isNullOrBlank()) fallbackResId else url DefaultCurrencyIcon( - modifier = modifier, iconData = iconData, + size = TangemTheme.dimens.size36, + alpha = alpha, + colorFilter = colorFilter, errorIcon = { Image( painter = painterResource(id = fallbackResId), @@ -77,8 +80,7 @@ private fun CoinIcon( contentDescription = null, ) }, - alpha = alpha, - colorFilter = colorFilter, + modifier = modifier, ) } @@ -94,11 +96,12 @@ private fun TokenIcon( errorIcon() } else { DefaultCurrencyIcon( - modifier = modifier, iconData = url, - errorIcon = errorIcon, + size = TangemTheme.dimens.size36, alpha = alpha, colorFilter = colorFilter, + errorIcon = errorIcon, + modifier = modifier, ) } } diff --git a/core/ui/src/main/java/com/tangem/core/ui/utils/ImageBackgroundContrastChecker.kt b/core/ui/src/main/java/com/tangem/core/ui/utils/ImageBackgroundContrastChecker.kt index 5fab6c4856..ed6cab9758 100644 --- a/core/ui/src/main/java/com/tangem/core/ui/utils/ImageBackgroundContrastChecker.kt +++ b/core/ui/src/main/java/com/tangem/core/ui/utils/ImageBackgroundContrastChecker.kt @@ -13,12 +13,12 @@ class ImageBackgroundContrastChecker( private val image: Bitmap, private val backgroundColor: Int, ) { - constructor(drawable: Drawable, backgroundColor: Int) : this( - image = drawable.toBitmap(), + constructor(drawable: Drawable, backgroundColor: Int, size: Int) : this( + image = drawable.toBitmap(width = size, height = size), backgroundColor = backgroundColor, ) - suspend fun getContrastColorIfNeeded(isDarkTheme: Boolean): Color { + suspend fun getContrastColor(isDarkTheme: Boolean): Color { return if (isLowContrast()) { if (isDarkTheme) Color.White else Color.Black } else { @@ -26,7 +26,7 @@ class ImageBackgroundContrastChecker( } } - suspend fun isLowContrast(): Boolean { + private suspend fun isLowContrast(): Boolean { val palette = generatePaletteAsync(bitmap = image) val color = palette.getDominantColor(backgroundColor) val contrast = ColorUtils.calculateContrast(color, backgroundColor) diff --git a/features/manage-tokens/impl/src/main/java/com/tangem/managetokens/presentation/managetokens/ui/components/TokenIcon.kt b/features/manage-tokens/impl/src/main/java/com/tangem/managetokens/presentation/managetokens/ui/components/TokenIcon.kt index bc7701328b..262ebcf808 100644 --- a/features/manage-tokens/impl/src/main/java/com/tangem/managetokens/presentation/managetokens/ui/components/TokenIcon.kt +++ b/features/manage-tokens/impl/src/main/java/com/tangem/managetokens/presentation/managetokens/ui/components/TokenIcon.kt @@ -12,6 +12,7 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.toArgb import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.platform.LocalDensity import androidx.compose.ui.res.painterResource import coil.compose.SubcomposeAsyncImage import coil.request.ImageRequest @@ -25,8 +26,7 @@ import kotlinx.coroutines.launch @Composable internal fun TokenIcon(state: TokenIconState, modifier: Modifier = Modifier) { - val iconModifier = modifier - .size(TangemTheme.dimens.size36) + val iconModifier = modifier.size(TangemTheme.dimens.size36) if (state.iconReference != null) { DefaultCurrencyIcon( modifier = iconModifier, @@ -73,11 +73,14 @@ private inline fun DefaultCurrencyIcon( crossinline errorIcon: @Composable () -> Unit, modifier: Modifier = Modifier, ) { - val itemBackgroundColor = TangemTheme.colors.background.primary.toArgb() var iconBackgroundColor by remember { mutableStateOf(Color.Transparent) } + var isBackgroundColorDefined by remember { mutableStateOf(false) } + val itemBackgroundColor = TangemTheme.colors.background.primary.toArgb() val isDarkTheme = isSystemInDarkTheme() val coroutineScope = rememberCoroutineScope() + val pixelsSize = with(LocalDensity.current) { TangemTheme.dimens.size36.roundToPx() } + SubcomposeAsyncImage( modifier = modifier .background( @@ -86,17 +89,21 @@ private inline fun DefaultCurrencyIcon( ), model = ImageRequest.Builder(context = LocalContext.current) .data(iconReference.getReference()) + .size(size = pixelsSize) + .memoryCacheKey(key = iconReference.getReference().toString() + pixelsSize) .crossfade(enable = true) .allowHardware(false) .listener( onSuccess = { _, result -> - if (isDarkTheme) { + if (!isBackgroundColorDefined && isDarkTheme) { coroutineScope.launch { val color = ImageBackgroundContrastChecker( drawable = result.drawable, backgroundColor = itemBackgroundColor, - ).getContrastColorIfNeeded(isDarkTheme) + size = pixelsSize, + ).getContrastColor(isDarkTheme = true) iconBackgroundColor = color + isBackgroundColorDefined = true } } }, diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/amount/SendAmountContent.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/amount/SendAmountContent.kt index d400239936..ba14154d01 100644 --- a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/amount/SendAmountContent.kt +++ b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/amount/SendAmountContent.kt @@ -15,6 +15,7 @@ import com.tangem.core.ui.components.currency.fiaticon.FiatIcon import com.tangem.core.ui.components.currency.tokenicon.TokenIcon import com.tangem.core.ui.extensions.resolveReference import com.tangem.core.ui.res.TangemTheme + import com.tangem.features.send.impl.R import com.tangem.features.send.impl.presentation.state.SendStates import com.tangem.features.send.impl.presentation.state.amount.SendAmountSegmentedButtonsConfig @@ -72,8 +73,8 @@ private fun SendAmountCurrencyButton(button: SendAmountSegmentedButtonsConfig) { if (button.isFiat) { FiatIcon( url = button.iconUrl, - modifier = Modifier - .size(TangemTheme.dimens.size18), + size = TangemTheme.dimens.size18, + modifier = Modifier.size(TangemTheme.dimens.size18), ) } else { button.iconState?.let { diff --git a/features/swap/presentation/src/main/java/com/tangem/feature/swap/ui/SwapSelectTokenScreen.kt b/features/swap/presentation/src/main/java/com/tangem/feature/swap/ui/SwapSelectTokenScreen.kt index 566447924f..80d9af4c8a 100644 --- a/features/swap/presentation/src/main/java/com/tangem/feature/swap/ui/SwapSelectTokenScreen.kt +++ b/features/swap/presentation/src/main/java/com/tangem/feature/swap/ui/SwapSelectTokenScreen.kt @@ -20,6 +20,7 @@ import androidx.compose.ui.graphics.ColorFilter import androidx.compose.ui.graphics.ColorMatrix import androidx.compose.ui.graphics.toArgb import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.platform.LocalDensity import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.style.TextAlign @@ -217,6 +218,7 @@ private fun TokenItem(token: TokenToSelect, network: Network, screenBackgroundCo @Composable private fun TokenIcon(token: TokenToSelect, screenBackgroundColor: Color, @DrawableRes iconPlaceholder: Int?) { var iconBackgroundColor by remember { mutableStateOf(Color.Transparent) } + var isBackgroundColorDefined by remember { mutableStateOf(false) } val isDarkTheme = isSystemInDarkTheme() val coroutineScope = rememberCoroutineScope() @@ -231,29 +233,35 @@ private fun TokenIcon(token: TokenToSelect, screenBackgroundColor: Color, @Drawa shape = TangemTheme.shapes.roundedCorners8, ), ) { - val iconModifier = Modifier - .size(TangemTheme.dimens.size40) + val pixelsSize = with(LocalDensity.current) { TangemTheme.dimens.size40.roundToPx() } + val iconModifier = Modifier.size(TangemTheme.dimens.size40) + val colorFilter = if (!token.available) { val matrix = ColorMatrix().apply { setToSaturation(0f) } ColorFilter.colorMatrix(matrix) } else { null } + SubcomposeAsyncImage( modifier = iconModifier, model = ImageRequest.Builder(LocalContext.current) .data(data) + .size(size = pixelsSize) + .memoryCacheKey(key = data.toString() + pixelsSize) .crossfade(true) .allowHardware(false) .listener( onSuccess = { _, result -> - if (isDarkTheme) { + if (!isBackgroundColorDefined && isDarkTheme) { coroutineScope.launch { val color = ImageBackgroundContrastChecker( drawable = result.drawable, backgroundColor = screenBackgroundColor.toArgb(), - ).getContrastColorIfNeeded(isDarkTheme) + size = pixelsSize, + ).getContrastColor(isDarkTheme = true) iconBackgroundColor = color + isBackgroundColorDefined = true } } }, diff --git a/features/swap/presentation/src/main/java/com/tangem/feature/swap/ui/TransactionCard.kt b/features/swap/presentation/src/main/java/com/tangem/feature/swap/ui/TransactionCard.kt index bd57a551b1..3cbd35a23f 100644 --- a/features/swap/presentation/src/main/java/com/tangem/feature/swap/ui/TransactionCard.kt +++ b/features/swap/presentation/src/main/java/com/tangem/feature/swap/ui/TransactionCard.kt @@ -20,6 +20,7 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.toArgb import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.platform.LocalDensity import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.AnnotatedString @@ -292,8 +293,9 @@ private fun TokenIcon( @DrawableRes iconPlaceholder: Int? = null, @DrawableRes networkIconRes: Int? = null, ) { - val itemBackgroundColor = TangemTheme.colors.background.primary.toArgb() var iconBackgroundColor by remember { mutableStateOf(Color.Transparent) } + var isBackgroundColorDefined by remember { mutableStateOf(false) } + val itemBackgroundColor = TangemTheme.colors.background.primary.toArgb() val isDarkTheme = isSystemInDarkTheme() val coroutineScope = rememberCoroutineScope() @@ -310,13 +312,16 @@ private fun TokenIcon( shape = TangemTheme.shapes.roundedCorners8, ) - val data = tokenIconUrl.ifEmpty { - iconPlaceholder - } + val data = tokenIconUrl.ifEmpty { iconPlaceholder } + + val pixelsSize = with(LocalDensity.current) { TangemTheme.dimens.size36.roundToPx() } + SubcomposeAsyncImage( modifier = tokenImageModifier, model = ImageRequest.Builder(LocalContext.current) .data(data) + .size(size = pixelsSize) + .memoryCacheKey(key = data.toString() + pixelsSize) .crossfade(true) .allowHardware(false) .listener( @@ -326,8 +331,10 @@ private fun TokenIcon( val color = ImageBackgroundContrastChecker( drawable = result.drawable, backgroundColor = itemBackgroundColor, - ).getContrastColorIfNeeded(isDarkTheme) + size = pixelsSize, + ).getContrastColor(true) iconBackgroundColor = color + isBackgroundColorDefined = true } } }, diff --git a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/ui/components/TokenIcon.kt b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/ui/components/TokenIcon.kt index 2a11731a45..8bf7ed9b78 100644 --- a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/ui/components/TokenIcon.kt +++ b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/ui/components/TokenIcon.kt @@ -14,6 +14,7 @@ import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.ColorFilter import androidx.compose.ui.graphics.toArgb import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.platform.LocalDensity import androidx.compose.ui.res.painterResource import coil.compose.SubcomposeAsyncImage import coil.request.ImageRequest @@ -136,11 +137,13 @@ private inline fun DefaultCurrencyIcon( crossinline errorIcon: @Composable () -> Unit, modifier: Modifier = Modifier, ) { - val itemBackgroundColor = TangemTheme.colors.background.primary.toArgb() var iconBackgroundColor by remember { mutableStateOf(Color.Transparent) } + var isBackgroundColorDefined by remember { mutableStateOf(false) } + val itemBackgroundColor = TangemTheme.colors.background.primary.toArgb() val isDarkTheme = isSystemInDarkTheme() val coroutineScope = rememberCoroutineScope() + val pixelsSize = with(LocalDensity.current) { TangemTheme.dimens.size48.roundToPx() } SubcomposeAsyncImage( modifier = modifier .background( @@ -149,17 +152,21 @@ private inline fun DefaultCurrencyIcon( ), model = ImageRequest.Builder(context = LocalContext.current) .data(iconData) + .size(pixelsSize) + .memoryCacheKey(iconData.toString() + pixelsSize) .crossfade(enable = true) .allowHardware(false) .listener( onSuccess = { _, result -> - if (isDarkTheme) { + if (!isBackgroundColorDefined && isDarkTheme) { coroutineScope.launch { val color = ImageBackgroundContrastChecker( drawable = result.drawable, backgroundColor = itemBackgroundColor, - ).getContrastColorIfNeeded(isDarkTheme) + size = pixelsSize, + ).getContrastColor(isDarkTheme = true) iconBackgroundColor = color + isBackgroundColorDefined = true } } },