Skip to content
Draft
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 @@ -13,19 +13,37 @@
package com.orange.ouds.core.test

import com.orange.ouds.core.utilities.OudsPreviewableComponent
import org.junit.experimental.runners.Enclosed
import org.junit.runner.RunWith
import org.junit.runners.Parameterized

@RunWith(Parameterized::class)
internal class OudsFilterChipTest(parameter: Any) : OudsComponentSnapshotTest(
OudsPreviewableComponent.FilterChip,
parameter,
OudsComponentTestSuite.theme
) {
@RunWith(Enclosed::class)
internal class OudsFilterChipTest() {

companion object {
@JvmStatic
@Parameterized.Parameters
internal fun data() = OudsPreviewableComponent.FilterChip.parameters
@RunWith(Parameterized::class)
class Default(parameter: Any) : OudsComponentSnapshotTest(
OudsPreviewableComponent.FilterChip.Default,
parameter,
OudsComponentTestSuite.theme
) {
companion object {
@JvmStatic
@Parameterized.Parameters
internal fun data() = OudsPreviewableComponent.FilterChip.Default.parameters
}
}
}

@RunWith(Parameterized::class)
class HighContrastModeEnabled(parameter: Any) : OudsComponentSnapshotTest(
OudsPreviewableComponent.FilterChip.HighContrastModeEnabled,
parameter,
OudsComponentTestSuite.theme
) {

companion object {
@JvmStatic
@Parameterized.Parameters
internal fun data() = OudsPreviewableComponent.FilterChip.HighContrastModeEnabled.parameters
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -13,19 +13,29 @@
package com.orange.ouds.core.test

import com.orange.ouds.core.utilities.OudsPreviewableComponent
import org.junit.experimental.runners.Enclosed
import org.junit.runner.RunWith
import org.junit.runners.Parameterized

@RunWith(Parameterized::class)
internal class OudsSuggestionChipTest(parameter: Any) : OudsComponentSnapshotTest(
OudsPreviewableComponent.SuggestionChip,
parameter,
OudsComponentTestSuite.theme
) {
@RunWith(Enclosed::class)
internal class OudsSuggestionChipTest {

companion object {
@JvmStatic
@Parameterized.Parameters
internal fun data() = OudsPreviewableComponent.SuggestionChip.parameters
@RunWith(Parameterized::class)
class Default(parameter: Any) : OudsComponentSnapshotTest(
OudsPreviewableComponent.SuggestionChip.Default,
parameter,
OudsComponentTestSuite.theme
) {
companion object {
@JvmStatic
@Parameterized.Parameters
internal fun data() = OudsPreviewableComponent.SuggestionChip.Default.parameters
}
}
}

class HighContrastModeEnabled : OudsComponentSnapshotTest(
OudsPreviewableComponent.SuggestionChip.HighContrastModeEnabled,
null,
OudsComponentTestSuite.theme
)
}
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ import androidx.compose.ui.unit.Dp
import com.orange.ouds.core.component.common.OudsError
import com.orange.ouds.core.component.common.outerBorder
import com.orange.ouds.core.extensions.collectInteractionStateAsState
import com.orange.ouds.core.theme.LocalHighContrastModeEnabled
import com.orange.ouds.core.extensions.highContrasted
import com.orange.ouds.core.theme.OudsTheme
import com.orange.ouds.core.theme.takeUnlessHairline
import com.orange.ouds.core.theme.value
Expand Down Expand Up @@ -278,8 +278,7 @@ private fun indicatorColor(state: OudsControlState, selected: Boolean, error: Bo
} else {
when (state) {
OudsControlState.Enabled -> if (selected) {
// In order to reach the a11y AAA level, when high contrast mode is enabled, the selected checkbox must use `color.content.default` token
if (LocalHighContrastModeEnabled.current) OudsTheme.colorScheme.content.default else this.selected
this.selected.highContrasted()
} else {
enabled
}
Expand Down Expand Up @@ -315,15 +314,25 @@ private fun backgroundColor(state: OudsControlState): Color {
}

@Preview(name = "Light", widthDp = OudsPreviewableComponent.Checkbox.PreviewWidthDp, device = OudsPreviewDevice)
@Preview(name = "Dark", uiMode = UI_MODE_NIGHT_YES or UI_MODE_TYPE_NORMAL, widthDp = OudsPreviewableComponent.Checkbox.PreviewWidthDp, device = OudsPreviewDevice)
@Preview(
name = "Dark",
uiMode = UI_MODE_NIGHT_YES or UI_MODE_TYPE_NORMAL,
widthDp = OudsPreviewableComponent.Checkbox.PreviewWidthDp,
device = OudsPreviewDevice
)
@Composable
@Suppress("PreviewShouldNotBeCalledRecursively")
private fun PreviewOudsCheckbox(@PreviewParameter(OudsCheckboxPreviewParameterProvider::class) parameter: OudsCheckboxPreviewParameter) {
PreviewOudsCheckbox(theme = getPreviewTheme(), darkThemeEnabled = isSystemInDarkTheme(), parameter = parameter)
}

@Preview(name = "Light", widthDp = OudsPreviewableComponent.Checkbox.PreviewWidthDp, device = OudsPreviewDevice)
@Preview(name = "Dark", uiMode = UI_MODE_NIGHT_YES or UI_MODE_TYPE_NORMAL, widthDp = OudsPreviewableComponent.Checkbox.PreviewWidthDp, device = OudsPreviewDevice)
@Preview(
name = "Dark",
uiMode = UI_MODE_NIGHT_YES or UI_MODE_TYPE_NORMAL,
widthDp = OudsPreviewableComponent.Checkbox.PreviewWidthDp,
device = OudsPreviewDevice
)
@Composable
internal fun PreviewOudsCheckboxHighContrastModeEnabled(@PreviewParameter(OudsCheckboxHighContrastModePreviewParameterProvider::class) parameter: ToggleableState) {
PreviewOudsCheckbox(
Expand Down
61 changes: 37 additions & 24 deletions core/src/main/java/com/orange/ouds/core/component/OudsChip.kt
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ import com.orange.ouds.core.component.content.OudsComponentContent
import com.orange.ouds.core.component.content.OudsComponentIcon
import com.orange.ouds.core.extensions.InteractionState
import com.orange.ouds.core.extensions.collectInteractionStateAsState
import com.orange.ouds.core.extensions.highContrasted
import com.orange.ouds.core.theme.OudsTheme
import com.orange.ouds.core.theme.takeUnlessHairline
import com.orange.ouds.core.theme.value
Expand Down Expand Up @@ -216,38 +217,50 @@ private fun borderWidth(state: OudsChipState, selected: Boolean): Dp? {
private fun borderColor(state: OudsChipState, selected: Boolean): Color {
return with(OudsTheme.componentsTokens.chip) {
when (state) {
OudsChipState.Enabled -> if (selected) colorBorderSelectedEnabled else colorBorderUnselectedEnabled
OudsChipState.Focused -> if (selected) colorBorderSelectedFocus else colorBorderUnselectedFocus
OudsChipState.Hovered -> if (selected) colorBorderSelectedHover else colorBorderUnselectedHover
OudsChipState.Pressed -> if (selected) colorBorderSelectedPressed else colorBorderUnselectedPressed
OudsChipState.Disabled -> if (selected) colorBorderSelectedDisabled else colorBorderUnselectedDisabled
}.value
OudsChipState.Enabled -> if (selected) {
colorBorderSelectedEnabled.value.highContrasted(background = colorBgSelectedEnabled.value.highContrasted())
} else {
colorBorderUnselectedEnabled.value
}
OudsChipState.Focused -> if (selected) colorBorderSelectedFocus.value else colorBorderUnselectedFocus.value
OudsChipState.Hovered -> if (selected) colorBorderSelectedHover.value else colorBorderUnselectedHover.value
OudsChipState.Pressed -> if (selected) colorBorderSelectedPressed.value else colorBorderUnselectedPressed.value
OudsChipState.Disabled -> if (selected) colorBorderSelectedDisabled.value else colorBorderUnselectedDisabled.value
}
}
}

@Composable
private fun backgroundColor(state: OudsChipState, selected: Boolean): Color {
return with(OudsTheme.componentsTokens.chip) {
when (state) {
OudsChipState.Enabled -> if (selected) colorBgSelectedEnabled else colorBgUnselectedEnabled
OudsChipState.Focused -> if (selected) colorBgSelectedFocus else colorBgUnselectedFocus
OudsChipState.Hovered -> if (selected) colorBgSelectedHover else colorBgUnselectedHover
OudsChipState.Pressed -> if (selected) colorBgSelectedPressed else colorBgUnselectedPressed
OudsChipState.Disabled -> if (selected) colorBgSelectedDisabled else colorBgUnselectedDisabled
}.value
OudsChipState.Enabled -> if (selected) {
colorBgSelectedEnabled.value.highContrasted()
} else {
colorBgUnselectedEnabled.value
}
OudsChipState.Focused -> if (selected) colorBgSelectedFocus.value else colorBgUnselectedFocus.value
OudsChipState.Hovered -> if (selected) colorBgSelectedHover.value else colorBgUnselectedHover.value
OudsChipState.Pressed -> if (selected) colorBgSelectedPressed.value else colorBgUnselectedPressed.value
OudsChipState.Disabled -> if (selected) colorBgSelectedDisabled.value else colorBgUnselectedDisabled.value
}
}
}

@Composable
private fun contentColor(state: OudsChipState, selected: Boolean): Color {
return with(OudsTheme.componentsTokens.chip) {
when (state) {
OudsChipState.Enabled -> if (selected) colorContentSelectedEnabled else colorContentUnselectedEnabled
OudsChipState.Focused -> if (selected) colorContentSelectedFocus else colorContentUnselectedFocus
OudsChipState.Hovered -> if (selected) colorContentSelectedHover else colorContentUnselectedHover
OudsChipState.Pressed -> if (selected) colorContentSelectedPressed else colorContentUnselectedPressed
OudsChipState.Disabled -> if (selected) colorContentSelectedDisabled else colorContentUnselectedDisabled
}.value
OudsChipState.Enabled -> if (selected) {
colorContentSelectedEnabled.value.highContrasted(background = colorBgSelectedEnabled.value.highContrasted())
} else {
colorContentUnselectedEnabled.value
}
OudsChipState.Focused -> if (selected) colorContentSelectedFocus.value else colorContentUnselectedFocus.value
OudsChipState.Hovered -> if (selected) colorContentSelectedHover.value else colorContentUnselectedHover.value
OudsChipState.Pressed -> if (selected) colorContentSelectedPressed.value else colorContentUnselectedPressed.value
OudsChipState.Disabled -> if (selected) colorContentSelectedDisabled.value else colorContentUnselectedDisabled.value
}
}
}

Expand All @@ -256,12 +269,12 @@ private fun tickColor(state: OudsChipState, selected: Boolean): Color? {
return with(OudsTheme.componentsTokens.chip) {
if (selected) {
when (state) {
OudsChipState.Enabled -> colorContentSelectedTickEnabled
OudsChipState.Focused -> colorContentSelectedFocus
OudsChipState.Hovered -> colorContentSelectedHover
OudsChipState.Pressed -> colorContentSelectedPressed
OudsChipState.Disabled -> colorContentSelectedDisabled
}.value
OudsChipState.Enabled -> colorContentSelectedTickEnabled.value.highContrasted(background = colorBgSelectedEnabled.value.highContrasted())
OudsChipState.Focused -> colorContentSelectedFocus.value
OudsChipState.Hovered -> colorContentSelectedHover.value
OudsChipState.Pressed -> colorContentSelectedPressed.value
OudsChipState.Disabled -> colorContentSelectedDisabled.value
}
} else {
null
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -213,6 +213,26 @@ internal fun PreviewOudsFilterChip(theme: OudsThemeContract, darkThemeEnabled: B
}
}

@OudsPreviewLightDark
@Composable
@Suppress("PreviewShouldNotBeCalledRecursively")
private fun PreviewOudsFilterChipHighContrastModeEnabled(@PreviewParameter(OudsFilterChipHighContrastModePreviewParameterProvider::class) selected: Boolean) {
PreviewOudsFilterChipHighContrastModeEnabled(
theme = getPreviewTheme(),
darkThemeEnabled = isSystemInDarkTheme(),
selected = selected
)
}

@Composable
internal fun PreviewOudsFilterChipHighContrastModeEnabled(
theme: OudsThemeContract,
darkThemeEnabled: Boolean,
selected: Boolean
) = OudsPreview(theme = theme, darkThemeEnabled = darkThemeEnabled, highContrastModeEnabled = true) {
OudsFilterChip(selected = selected, nullableIcon = null, nullableLabel = "Label", onClick = {})
}

internal data class OudsFilterChipPreviewParameter(
val selected: Boolean,
val hasLabel: Boolean,
Expand All @@ -230,3 +250,5 @@ private val previewParameterValues: List<OudsFilterChipPreviewParameter>
OudsFilterChipPreviewParameter(selected = true, hasLabel = false, hasIcon = true),
OudsFilterChipPreviewParameter(selected = false, hasLabel = false, hasIcon = true)
)

internal class OudsFilterChipHighContrastModePreviewParameterProvider : BasicPreviewParameterProvider<Boolean>(true, false)
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ import androidx.compose.ui.unit.Dp
import com.orange.ouds.core.component.common.OudsError
import com.orange.ouds.core.component.common.outerBorder
import com.orange.ouds.core.extensions.collectInteractionStateAsState
import com.orange.ouds.core.theme.LocalHighContrastModeEnabled
import com.orange.ouds.core.extensions.highContrasted
import com.orange.ouds.core.theme.OudsTheme
import com.orange.ouds.core.theme.takeUnlessHairline
import com.orange.ouds.core.theme.value
Expand Down Expand Up @@ -207,8 +207,7 @@ private fun indicatorColor(state: OudsControlState, selected: Boolean, error: Bo
} else {
when (state) {
OudsControlState.Enabled -> if (selected) {
// In order to reach the a11y AAA level, when high contrast mode is enabled, the selected radio button must use `color.content.default` token
if (LocalHighContrastModeEnabled.current) OudsTheme.colorScheme.content.default else this.selected
this.selected.highContrasted()
} else {
OudsTheme.colorScheme.border.emphasized
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -206,6 +206,24 @@ internal fun PreviewOudsSuggestionChip(
}
}

@OudsPreviewLightDark
@Composable
@Suppress("PreviewShouldNotBeCalledRecursively")
private fun PreviewOudsSuggestionChipHighContrastModeEnabled() {
PreviewOudsSuggestionChipHighContrastModeEnabled(
theme = getPreviewTheme(),
darkThemeEnabled = isSystemInDarkTheme()
)
}

@Composable
internal fun PreviewOudsSuggestionChipHighContrastModeEnabled(
theme: OudsThemeContract,
darkThemeEnabled: Boolean
) = OudsPreview(theme = theme, darkThemeEnabled = darkThemeEnabled, highContrastModeEnabled = true) {
OudsSuggestionChip(nullableIcon = null, nullableLabel = "Label", onClick = {})
}

internal data class OudsSuggestionChipPreviewParameter(
val hasLabel: Boolean,
val hasIcon: Boolean
Expand Down
47 changes: 47 additions & 0 deletions core/src/main/java/com/orange/ouds/core/extensions/ColorExt.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
/*
* Software Name: OUDS Android
* SPDX-FileCopyrightText: Copyright (c) Orange SA
* SPDX-License-Identifier: MIT
*
* This software is distributed under the MIT license,
* the text of which is available at https://opensource.org/license/MIT/
* or see the "LICENSE" file for more details.
*
* Software description: Android library of reusable graphical components
*/

package com.orange.ouds.core.extensions

import androidx.compose.runtime.Composable
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.compositeOver
import androidx.compose.ui.graphics.luminance
import com.orange.ouds.core.theme.LocalHighContrastModeEnabled
import com.orange.ouds.core.theme.OudsTheme

/**
* Returns a high contrasted color for better contrast when high contrast mode is enabled.
*
* When high contrast mode is disabled, the original color is returned unchanged.
*
* @param background The background color on which this color will be displayed.
* If transparent, the theme's primary background color is used as reference.
* @return `OudsTheme.colorScheme.always.black` or `OudsTheme.colorScheme.always.white` color when high contrast mode is enabled, or the original color otherwise.
*/
@Composable
fun Color.highContrasted(background: Color = Color.Transparent): Color {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should this method be internal?

if (!LocalHighContrastModeEnabled.current) return this

// Composite the background over primary background color to handle transparency
val effectiveBackground = background.compositeOver(OudsTheme.colorScheme.background.primary)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If I am correct, this line makes the assumption that the background color of the app is always background primary. Are we sure about that?


// Composite the current color over background to handle transparency
val effectiveColor = compositeOver(effectiveBackground)

// Return the best contrast color for the current color
return if (effectiveColor.luminance() < 0.3f) {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Where does 0.3f comes from? I would have naively thought that we should use 0.5f.

OudsTheme.colorScheme.always.black
} else {
OudsTheme.colorScheme.always.white
}
}
Loading
Loading