Updated on 2026-08-14
This commit is contained in:
parent
2223f38cd9
commit
fe8d2719a3
10 changed files with 94 additions and 42 deletions
|
|
@ -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(
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
},
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
)
|
||||
}
|
||||
|
|
@ -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,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
},
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
},
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
},
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
},
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue