feat: Implement dynamic icon and text colors based on background luminance for improved accessibility

This commit is contained in:
2026-01-25 19:00:35 +05:00
parent ff900846d1
commit 89746c5bbd
4 changed files with 33 additions and 15 deletions

View File

@@ -898,7 +898,7 @@ fun ChatDetailScreen(
contentDescription = contentDescription =
"Call", "Call",
tint = tint =
Color.White if (isDarkTheme) Color.White else Color(0xFF007AFF)
) )
} }
} }
@@ -931,7 +931,7 @@ fun ChatDetailScreen(
contentDescription = contentDescription =
"More", "More",
tint = tint =
Color.White, if (isDarkTheme) Color.White else Color(0xFF007AFF),
modifier = modifier =
Modifier.size( Modifier.size(
26.dp 26.dp

View File

@@ -94,6 +94,15 @@ data class AvatarColors(val textColor: Color, val backgroundColor: Color)
private val avatarColorCache = mutableMapOf<String, AvatarColors>() private val avatarColorCache = mutableMapOf<String, AvatarColors>()
/**
* Определяет, является ли цвет светлым (true) или темным (false)
* Использует формулу relative luminance из WCAG
*/
fun isColorLight(color: Color): Boolean {
val luminance = 0.299f * color.red + 0.587f * color.green + 0.114f * color.blue
return luminance > 0.5f
}
fun getAvatarColor(name: String, isDarkTheme: Boolean): AvatarColors { fun getAvatarColor(name: String, isDarkTheme: Boolean): AvatarColors {
val cacheKey = "${name}_${if (isDarkTheme) "dark" else "light"}" val cacheKey = "${name}_${if (isDarkTheme) "dark" else "light"}"
return avatarColorCache.getOrPut(cacheKey) { return avatarColorCache.getOrPut(cacheKey) {
@@ -453,7 +462,7 @@ fun ChatsListScreen(
text = accountName, text = accountName,
fontSize = 16.sp, fontSize = 16.sp,
fontWeight = FontWeight.SemiBold, fontWeight = FontWeight.SemiBold,
color = Color.White color = if (isColorLight(headerColor)) Color.Black else Color.White
) )
} }
@@ -465,11 +474,7 @@ fun ChatsListScreen(
"@$accountUsername", "@$accountUsername",
fontSize = 13.sp, fontSize = 13.sp,
color = color =
Color.White if (isColorLight(headerColor)) Color.Black.copy(alpha = 0.7f) else Color.White.copy(alpha = 0.7f)
.copy(
alpha =
0.7f
)
) )
} }
} }

View File

@@ -248,7 +248,7 @@ private fun CollapsingOtherProfileHeader(
Icon( Icon(
imageVector = Icons.Filled.ArrowBack, imageVector = Icons.Filled.ArrowBack,
contentDescription = "Back", contentDescription = "Back",
tint = Color.White, tint = if (isColorLight(avatarColors.backgroundColor)) Color.Black else Color.White,
modifier = Modifier.size(24.dp) modifier = Modifier.size(24.dp)
) )
} }
@@ -272,7 +272,7 @@ private fun CollapsingOtherProfileHeader(
Icon( Icon(
imageVector = Icons.Default.MoreVert, imageVector = Icons.Default.MoreVert,
contentDescription = "Profile menu", contentDescription = "Profile menu",
tint = Color.White, tint = if (isColorLight(avatarColors.backgroundColor)) Color.Black else Color.White,
modifier = Modifier.size(24.dp) modifier = Modifier.size(24.dp)
) )
} }
@@ -347,7 +347,7 @@ private fun CollapsingOtherProfileHeader(
text = name, text = name,
fontSize = nameFontSize, fontSize = nameFontSize,
fontWeight = FontWeight.SemiBold, fontWeight = FontWeight.SemiBold,
color = Color.White, color = if (isColorLight(avatarColors.backgroundColor)) Color.Black else Color.White,
maxLines = 1, maxLines = 1,
overflow = TextOverflow.Ellipsis, overflow = TextOverflow.Ellipsis,
textAlign = TextAlign.Center textAlign = TextAlign.Center
@@ -368,7 +368,11 @@ private fun CollapsingOtherProfileHeader(
Text( Text(
text = if (isOnline) "online" else "offline", text = if (isOnline) "online" else "offline",
fontSize = onlineFontSize, fontSize = onlineFontSize,
color = if (isOnline) Color(0xFF4CAF50) else Color.White.copy(alpha = 0.6f) color = if (isOnline) {
Color(0xFF4CAF50)
} else {
if (isColorLight(avatarColors.backgroundColor)) Color.Black.copy(alpha = 0.6f) else Color.White.copy(alpha = 0.6f)
}
) )
} }
} }

View File

@@ -102,6 +102,15 @@ data class AvatarColors(val textColor: Color, val backgroundColor: Color)
private val avatarColorCache = mutableMapOf<String, AvatarColors>() private val avatarColorCache = mutableMapOf<String, AvatarColors>()
/**
* Определяет, является ли цвет светлым (true) или темным (false)
* Использует формулу relative luminance из WCAG
*/
fun isColorLight(color: Color): Boolean {
val luminance = 0.299f * color.red + 0.587f * color.green + 0.114f * color.blue
return luminance > 0.5f
}
fun getAvatarColor(name: String, isDarkTheme: Boolean): AvatarColors { fun getAvatarColor(name: String, isDarkTheme: Boolean): AvatarColors {
val cacheKey = "${name}_${if (isDarkTheme) "dark" else "light"}" val cacheKey = "${name}_${if (isDarkTheme) "dark" else "light"}"
return avatarColorCache.getOrPut(cacheKey) { return avatarColorCache.getOrPut(cacheKey) {
@@ -697,7 +706,7 @@ private fun CollapsingProfileHeader(
Icon( Icon(
imageVector = TablerIcons.ArrowLeft, imageVector = TablerIcons.ArrowLeft,
contentDescription = "Back", contentDescription = "Back",
tint = Color.White, tint = if (isColorLight(avatarColors.backgroundColor)) Color.Black else Color.White,
modifier = Modifier.size(24.dp) modifier = Modifier.size(24.dp)
) )
} }
@@ -721,7 +730,7 @@ private fun CollapsingProfileHeader(
Icon( Icon(
imageVector = TablerIcons.DotsVertical, imageVector = TablerIcons.DotsVertical,
contentDescription = "Profile menu", contentDescription = "Profile menu",
tint = Color.White, tint = if (isColorLight(avatarColors.backgroundColor)) Color.Black else Color.White,
modifier = Modifier.size(24.dp) modifier = Modifier.size(24.dp)
) )
} }
@@ -805,7 +814,7 @@ private fun CollapsingProfileHeader(
text = name, text = name,
fontSize = nameFontSize, fontSize = nameFontSize,
fontWeight = FontWeight.SemiBold, fontWeight = FontWeight.SemiBold,
color = Color.White, color = if (isColorLight(avatarColors.backgroundColor)) Color.Black else Color.White,
maxLines = 1, maxLines = 1,
overflow = TextOverflow.Ellipsis, overflow = TextOverflow.Ellipsis,
modifier = Modifier.widthIn(max = 220.dp), modifier = Modifier.widthIn(max = 220.dp),