Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

A11y: StopSearchScreen - Calculate background color to improve text contrast #305

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,6 @@ data class SearchStopState(
data class StopResult(
val stopName: String,
val stopId: String,
val transportModeType: List<TransportMode> = emptyList(),
val transportModeType: ImmutableList<TransportMode> = persistentListOf(),
)
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import androidx.compose.foundation.isSystemInDarkTheme
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.toArgb
import xyz.ksharma.krail.design.system.LocalThemeColor
import xyz.ksharma.krail.trip.planner.ui.state.TransportMode

Expand Down Expand Up @@ -92,3 +93,73 @@ val lightScrim = android.graphics.Color.argb(0xe6, 0xFF, 0xFF, 0xFF)
* https://cs.android.com/androidx/platform/frameworks/support/+/androidx-main:activity/activity/src/main/java/androidx/activity/EdgeToEdge.kt;l=40-44;drc=27e7d52e8604a080133e8b842db10c89b4482598
*/
val darkScrim = android.graphics.Color.argb(0x80, 0x1b, 0x1b, 0x1b)

/**
* Update the theme color to make text more readable on top of it.
*
* Steps
* 1. Convert to HSL, so we can modify the lightness without changing the core hue and saturation.
* For 0xFFEE343F (R: 238, G: 52, B: 63): Hue ≈ 355°, Saturation ≈ 85%, Lightness ≈ 57%
* 2. Increase lightness by 20%
* 3. Convert back to RGB
*
* @param color The color to brighten, in ARGB format.
* @param factor The factor by which to brighten the color (default is 20%)
*
* @return The brightened color in ARGB format
*/
fun brightenColor(color: Int, factor: Float = 0.2f): Int {
// Convert the color to RGB components
val red = android.graphics.Color.red(color) / 255f
val green = android.graphics.Color.green(color) / 255f
val blue = android.graphics.Color.blue(color) / 255f

// Convert RGB to HSL
val hsl = FloatArray(3)
android.graphics.Color.RGBToHSV((red * 255).toInt(), (green * 255).toInt(), (blue * 255).toInt(), hsl)

// Adjust lightness (value) within bounds
hsl[2] = (hsl[2] + factor).coerceIn(0f, 1f)

// Convert back to RGB
return android.graphics.Color.HSVToColor(hsl)
}

/**
* Extension function to brighten a Compose Color.
*
* This function increases the lightness of the given Compose Color by the specified factor.
* It converts the color to ARGB format, brightens it, and then converts it back to a Compose Color.
*
* @param factor The factor by which to brighten the color (default is 20%).
*
* @return The brightened Compose [Color].
*/
fun Color.brighten(factor: Float = 0.2f): Color {
val argb = this.toArgb()
val brightenedArgb = brightenColor(argb, factor)
return Color(brightenedArgb)
}

fun darkenColor(color: Int, factor: Float = 0.2f): Int {
// Convert the color to RGB components
val red = android.graphics.Color.red(color) / 255f
val green = android.graphics.Color.green(color) / 255f
val blue = android.graphics.Color.blue(color) / 255f

// Convert RGB to HSL
val hsl = FloatArray(3)
android.graphics.Color.RGBToHSV((red * 255).toInt(), (green * 255).toInt(), (blue * 255).toInt(), hsl)

// Adjust lightness (value) within bounds
hsl[2] = (hsl[2] - factor).coerceIn(0f, 1f)

// Convert back to RGB
return android.graphics.Color.HSVToColor(hsl)
}

fun Color.darken(factor: Float = 0.2f): Color {
val argb = this.toArgb()
val darkArgb = darkenColor(argb, factor)
return Color(darkArgb)
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.semantics.Role
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
Expand All @@ -25,6 +26,7 @@ fun StopSearchListItem(
stopName: String,
stopId: String,
transportModeSet: ImmutableSet<TransportMode>,
textColor: Color,
modifier: Modifier = Modifier,
onClick: (StopItem) -> Unit = {},
) {
Expand All @@ -44,6 +46,7 @@ fun StopSearchListItem(
) {
Text(
text = stopName,
color = textColor,
style = KrailTheme.typography.titleSmall,
)
Row(
Expand Down Expand Up @@ -72,6 +75,7 @@ private fun StopSearchListItemPreview() {
TransportMode.Bus(),
TransportMode.LightRail(),
),
textColor = KrailTheme.colors.onSurface,
modifier = Modifier.background(color = KrailTheme.colors.surface),
)
}
Expand All @@ -88,6 +92,7 @@ private fun StopSearchListItemLongNamePreview() {
TransportMode.Train(),
TransportMode.Ferry(),
),
textColor = KrailTheme.colors.onSurface,
modifier = Modifier.background(color = KrailTheme.colors.surface),
)
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package xyz.ksharma.krail.trip.planner.ui.searchstop

import androidx.compose.foundation.background
import androidx.compose.foundation.isSystemInDarkTheme
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.fillMaxSize
Expand All @@ -25,6 +26,7 @@ import androidx.compose.ui.graphics.Brush
import androidx.compose.ui.platform.LocalSoftwareKeyboardController
import androidx.compose.ui.tooling.preview.PreviewLightDark
import androidx.compose.ui.unit.dp
import kotlinx.collections.immutable.persistentListOf
import kotlinx.collections.immutable.toImmutableSet
import kotlinx.coroutines.flow.collectLatest
import kotlinx.coroutines.flow.debounce
Expand All @@ -38,6 +40,8 @@ import xyz.ksharma.krail.design.system.components.Text
import xyz.ksharma.krail.design.system.components.TextField
import xyz.ksharma.krail.design.system.theme.KrailTheme
import xyz.ksharma.krail.trip.planner.ui.components.StopSearchListItem
import xyz.ksharma.krail.trip.planner.ui.components.brighten
import xyz.ksharma.krail.trip.planner.ui.components.darken
import xyz.ksharma.krail.trip.planner.ui.components.hexToComposeColor
import xyz.ksharma.krail.trip.planner.ui.state.TransportMode
import xyz.ksharma.krail.trip.planner.ui.state.searchstop.SearchStopState
Expand All @@ -51,11 +55,12 @@ import xyz.ksharma.krail.trip.planner.ui.state.searchstop.model.StopItem
fun SearchStopScreen(
searchStopState: SearchStopState,
modifier: Modifier = Modifier,
searchQuery: String = "",
onStopSelect: (StopItem) -> Unit = {},
onEvent: (SearchStopUiEvent) -> Unit = {},
) {
val themeColor by LocalThemeColor.current
var textFieldText: String by remember { mutableStateOf("") }
var textFieldText: String by remember { mutableStateOf(searchQuery) }
val keyboard = LocalSoftwareKeyboardController.current
val focusRequester = remember { FocusRequester() }

Expand All @@ -67,15 +72,11 @@ fun SearchStopScreen(
val trimmedText by remember(textFieldText) { derivedStateOf { textFieldText.trim() } }

LaunchedEffect(trimmedText) {
snapshotFlow { trimmedText }
.distinctUntilChanged()
.debounce(250)
.filter { it.isNotBlank() }
snapshotFlow { trimmedText }.distinctUntilChanged().debounce(250).filter { it.isNotBlank() }
.mapLatest { text ->
Timber.d("Query - $text")
onEvent(SearchStopUiEvent.SearchTextChanged(text))
}
.collectLatest {}
}.collectLatest {}
}

Column(
Expand All @@ -84,7 +85,15 @@ fun SearchStopScreen(
.background(
brush = Brush.verticalGradient(
colors = listOf(
themeColor.hexToComposeColor().copy(alpha = 0.9f),
if (isSystemInDarkTheme()) {
themeColor
.hexToComposeColor()
.darken()
} else {
themeColor
.hexToComposeColor()
.brighten()
},
KrailTheme.colors.surface,
),
),
Expand Down Expand Up @@ -128,6 +137,7 @@ fun SearchStopScreen(
stopId = stop.stopId,
stopName = stop.stopName,
transportModeSet = stop.transportModeType.toImmutableSet(),
textColor = KrailTheme.colors.label,
onClick = { stopItem ->
keyboard?.hide()
onStopSelect(stopItem)
Expand All @@ -153,13 +163,110 @@ fun SearchStopScreen(

@PreviewLightDark
@Composable
private fun SearchStopScreenPreview() {
private fun PreviewSearchStopScreenTrain() {
KrailTheme {
val themeColor = remember { mutableStateOf(TransportMode.Train().colorCode) }
CompositionLocalProvider(LocalThemeColor provides themeColor) {
SearchStopScreen(
searchQuery = "Search Query",
searchStopState = searchStopState,
)
}
}
}

@PreviewLightDark
@Composable
private fun PreviewSearchStopScreenCoach() {
KrailTheme {
val themeColor = remember { mutableStateOf(TransportMode.Coach().colorCode) }
CompositionLocalProvider(LocalThemeColor provides themeColor) {
SearchStopScreen(
searchQuery = "Search Query",
searchStopState = searchStopState,
)
}
}
}

@PreviewLightDark
@Composable
private fun PreviewSearchStopScreenFerry() {
KrailTheme {
val themeColor = remember { mutableStateOf(TransportMode.Ferry().colorCode) }
CompositionLocalProvider(LocalThemeColor provides themeColor) {
SearchStopScreen(
searchQuery = "Search Query",
searchStopState = searchStopState,
)
}
}
}

@PreviewLightDark
@Composable
private fun PreviewSearchStopScreenMetro() {
KrailTheme {
val themeColor = remember { mutableStateOf(TransportMode.Metro().colorCode) }
CompositionLocalProvider(LocalThemeColor provides themeColor) {
SearchStopScreen(
searchQuery = "Search Query",
searchStopState = searchStopState,
)
}
}
}

@PreviewLightDark
@Composable
private fun PreviewSearchStopScreenLightRail() {
KrailTheme {
val themeColor = remember { mutableStateOf(TransportMode.LightRail().colorCode) }
CompositionLocalProvider(LocalThemeColor provides themeColor) {
SearchStopScreen(
searchQuery = "Search Query",
searchStopState = searchStopState,
)
}
}
}

@PreviewLightDark
@Composable
private fun PreviewSearchStopScreenBus() {
KrailTheme {
val themeColor = remember { mutableStateOf(TransportMode.Bus().colorCode) }
CompositionLocalProvider(LocalThemeColor provides themeColor) {
SearchStopScreen(searchStopState = SearchStopState())
SearchStopScreen(
searchQuery = "Search Query",
searchStopState = searchStopState,
)
}
}
}

private val searchStopState = SearchStopState(
isLoading = false,
stops = persistentListOf(
SearchStopState.StopResult(
stopId = "123",
stopName = "Stop Name",
transportModeType = persistentListOf(TransportMode.Bus()),
),
SearchStopState.StopResult(
stopId = "235",
stopName = "Stop Name",
transportModeType = persistentListOf(TransportMode.Ferry()),
),
SearchStopState.StopResult(
stopId = "235",
stopName = "Stop Name",
transportModeType = persistentListOf(
TransportMode.Train(),
TransportMode.Bus(),
),
),
),
)

// endregion
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package xyz.ksharma.krail.trip.planner.ui.searchstop

import kotlinx.collections.immutable.toPersistentList
import xyz.ksharma.krail.trip.planner.network.api.model.StopFinderResponse
import xyz.ksharma.krail.trip.planner.ui.state.TransportMode
import xyz.ksharma.krail.trip.planner.ui.state.searchstop.SearchStopState
Expand Down Expand Up @@ -32,7 +33,7 @@ object StopResultMapper {
SearchStopState.StopResult(
stopName = stopName,
stopId = stopId,
transportModeType = modes,
transportModeType = modes.toPersistentList(),
)
}.sortedBy { stopResult ->
stopResult.transportModeType.minOfOrNull { mode ->
Expand Down