Skip to content
Original file line number Diff line number Diff line change
Expand Up @@ -34,16 +34,17 @@ import kotlinx.coroutines.awaitAll
import kotlinx.coroutines.flow.collectLatest
import kotlinx.coroutines.launch

internal data class InteractionValuesIndication(val values: List<InteractionValue<*, *>>) : IndicationNodeFactory {
internal fun interactionValuesIndication(vararg interactionValues: InteractionValue<*, *>): IndicationNodeFactory =
InteractionValuesIndicationNodeFactory(values = interactionValues.toList())

constructor(vararg interactionValues: InteractionValue<*, *>) : this(interactionValues.toList())
private data class InteractionValuesIndicationNodeFactory(val values: List<InteractionValue<*, *>>) : IndicationNodeFactory {

override fun create(interactionSource: InteractionSource): DelegatableNode {
return InteractionValuesNode(interactionSource, values)
return InteractionValuesIndicationNode(interactionSource, values)
}
}

internal class InteractionValuesNode(
private class InteractionValuesIndicationNode(
private val interactionSource: InteractionSource,
interactionValues: List<InteractionValue<*, *>>
) : Modifier.Node(), DrawModifierNode {
Expand Down Expand Up @@ -132,7 +133,7 @@ internal class InteractionValuesNode(

/**
* A value holder that automatically updates its value when the user is interacting with the component it is attached to.
* [InteractionValuesNode] uses instances of this class and associated animatables to update the value.
* [InteractionValuesIndicationNode] uses instances of this class and associated animatables to update the value.
*
* @param T The type of the value.
* @param S The type of the associated animatable value. This must be a Compose [Color] or a [Float].
Expand Down
238 changes: 238 additions & 0 deletions core/src/main/java/com/orange/ouds/core/component/OudsAvatar.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,238 @@
/*
* 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.component

import androidx.compose.foundation.Image
import androidx.compose.foundation.background
import androidx.compose.foundation.interaction.MutableInteractionSource
import androidx.compose.foundation.isSystemInDarkTheme
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.alpha
import androidx.compose.ui.draw.clip
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.ImageBitmap
import androidx.compose.ui.graphics.painter.ColorPainter
import androidx.compose.ui.graphics.painter.Painter
import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.platform.LocalConfiguration
import androidx.compose.ui.semantics.Role
import androidx.compose.ui.semantics.clearAndSetSemantics
import androidx.compose.ui.semantics.onClick
import androidx.compose.ui.semantics.role
import androidx.compose.ui.semantics.semantics
import androidx.compose.ui.tooling.preview.PreviewLightDark
import androidx.compose.ui.tooling.preview.PreviewParameter
import androidx.compose.ui.unit.dp
import com.orange.ouds.core.theme.OudsTheme
import com.orange.ouds.core.utilities.OudsPreview
import com.orange.ouds.core.utilities.PreviewCheckerboardPainter
import com.orange.ouds.core.utilities.PreviewEnumEntries
import com.orange.ouds.core.utilities.getPreviewTheme
import com.orange.ouds.foundation.utilities.BasicPreviewParameterProvider
import com.orange.ouds.theme.OudsThemeContract

@Composable
internal fun OudsAvatar(
painter: Painter,
onClick: (() -> Unit)?,
modifier: Modifier = Modifier,
interactionSource: MutableInteractionSource? = null
) {
OudsAvatar(
graphicsObject = painter,
monogram = null,
monogramColor = null,
monogramBackgroundColor = null,
onClick = onClick,
modifier = modifier,
interactionSource = interactionSource
)
}

@Composable
internal fun OudsAvatar(
imageVector: ImageVector,
onClick: (() -> Unit)?,
modifier: Modifier = Modifier,
interactionSource: MutableInteractionSource? = null
) {
OudsAvatar(
graphicsObject = imageVector,
monogram = null,
monogramColor = null,
monogramBackgroundColor = null,
onClick = onClick,
modifier = modifier,
interactionSource = interactionSource
)
}

@Composable
internal fun OudsAvatar(
bitmap: ImageBitmap,
onClick: (() -> Unit)?,
modifier: Modifier = Modifier,
interactionSource: MutableInteractionSource? = null
) {
OudsAvatar(
graphicsObject = bitmap,
monogram = null,
monogramColor = null,
monogramBackgroundColor = null,
onClick = onClick,
modifier = modifier,
interactionSource = interactionSource
)
}

@Composable
internal fun OudsAvatar(
monogram: Char,
color: Color,
backgroundColor: Color,
onClick: (() -> Unit)?,
modifier: Modifier = Modifier,
interactionSource: MutableInteractionSource? = null
) {
OudsAvatar(
graphicsObject = null,
monogram = monogram,
monogramColor = color,
monogramBackgroundColor = backgroundColor,
onClick = onClick,
modifier = modifier,
interactionSource = interactionSource
)
}

@Composable
internal fun OudsAvatar(
graphicsObject: Any?,
monogram: Char?,
monogramColor: Color?,
monogramBackgroundColor: Color?,
onClick: (() -> Unit)?,
modifier: Modifier = Modifier,
interactionSource: MutableInteractionSource? = null
) {
@Suppress("NAME_SHADOWING") val interactionSource = interactionSource ?: remember { MutableInteractionSource() }

Box(
Comment thread
paulinea marked this conversation as resolved.
modifier = modifier.semantics(mergeDescendants = true) {
if (onClick != null) {
role = Role.Button
onClick(null) {
onClick()
true
}
}
},
contentAlignment = Alignment.Center
) {
OudsButton(
modifier = Modifier.alpha(if (onClick != null) 1f else 0f), // If onClick is null, draw the button with alpha 0 anyway to get a consistent size for the avatar
icon = OudsButtonIcon(ColorPainter(Color.Transparent), ""), // Use transparent painter to scale the button with the font
onClick = { onClick?.invoke() },
appearance = OudsButtonAppearance.Minimal,
interactionSource = interactionSource
)

val scale = LocalConfiguration.current.fontScale
val contentModifier = Modifier
.clip(CircleShape)
.size(AvatarSize * scale)
if (graphicsObject != null) {
val contentScale = ContentScale.Crop
when (graphicsObject) {
is Painter -> Image(
modifier = contentModifier,
painter = graphicsObject,
contentDescription = null,
contentScale = contentScale
)
is ImageVector -> Image(
modifier = contentModifier,
imageVector = graphicsObject,
contentDescription = null,
contentScale = contentScale
)
is ImageBitmap -> Image(
modifier = contentModifier,
bitmap = graphicsObject,
contentDescription = null,
contentScale = contentScale
)
}
} else if (monogram != null && monogramColor != null && monogramBackgroundColor != null) {
Box(
modifier = contentModifier.background(monogramBackgroundColor),
contentAlignment = Alignment.Center,
) {
Text(
modifier = Modifier.clearAndSetSemantics {},
text = monogram.uppercase(),
color = monogramColor,
style = MaterialTheme.typography.titleMedium, // This looks like the most accurate style according to Material specs at https://m3.material.io/components/app-bars/specs#606c6564-ce7d-489d-8852-af2b3b478bc6
fontFamily = OudsTheme.typography.fontFamily
)
}
}
}
}

private val AvatarSize = 32.dp

@PreviewLightDark
@Composable
@Suppress("PreviewShouldNotBeCalledRecursively")
private fun PreviewOudsAvatar(@PreviewParameter(OudsAvatarPreviewParameterProvider::class) isMonogram: Boolean) {
PreviewOudsAvatar(theme = getPreviewTheme(), darkThemeEnabled = isSystemInDarkTheme(), isMonogram = isMonogram)
}

@Composable
internal fun PreviewOudsAvatar(
theme: OudsThemeContract,
darkThemeEnabled: Boolean,
isMonogram: Boolean
) = OudsPreview(theme = theme, darkThemeEnabled = darkThemeEnabled) {
PreviewEnumEntries<OudsButtonState>(filter = { it !in listOf(OudsButtonState.Loading, OudsButtonState.Disabled) }) {
if (isMonogram) {
OudsAvatar(
monogram = 'A',
color = Color.White,
backgroundColor = Color(0xffd5204e),
onClick = {}
)
} else {
OudsAvatar(
painter = PreviewCheckerboardPainter(
squareSize = 6.dp,
primaryColor = Color(0xff247a85),
secondaryColor = Color(0xfffbcd00)
),
onClick = {}
)
}
}
}

internal class OudsAvatarPreviewParameterProvider : BasicPreviewParameterProvider<Boolean>(false, true)
Original file line number Diff line number Diff line change
Expand Up @@ -321,7 +321,7 @@ internal fun OudsButton(
.clickable(
enabled = state !in remember { listOf(OudsButtonState.Disabled, OudsButtonState.Loading) },
interactionSource = interactionSource,
indication = InteractionValuesIndication(contentColor, backgroundColor, borderColor, borderWidth),
indication = interactionValuesIndication(contentColor, backgroundColor, borderColor, borderWidth),
onClick = onClick
),
contentAlignment = Alignment.Center
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -179,7 +179,7 @@ fun OudsTriStateCheckbox(
if (onClick != null) {
Modifier.triStateToggleable(
interactionSource = interactionSource,
indication = InteractionValuesIndication(backgroundColor),
indication = interactionValuesIndication(backgroundColor),
state = state,
onClick = onClick,
enabled = enabled && !readOnly,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -189,7 +189,7 @@ fun OudsTriStateCheckboxItem(
val toggleableModifier = if (onClick != null) {
Modifier.triStateToggleable(
interactionSource = interactionSource,
indication = InteractionValuesIndication(backgroundColor),
indication = interactionValuesIndication(backgroundColor),
state = state,
onClick = onClick,
enabled = enabled && !readOnly,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -119,7 +119,7 @@ internal fun OudsChip(
}
.outerBorder(state = state, shape = shape)
.run {
val indication = InteractionValuesIndication(contentColor, tickColor, backgroundColor, borderColor, borderWidth)
val indication = interactionValuesIndication(contentColor, tickColor, backgroundColor, borderColor, borderWidth)
if (selectable) {
selectable(
selected = selected,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -342,7 +342,7 @@ private fun OudsFloatingActionButton(
val floatingActionButtonState = getFloatingActionButtonState(interactionState = floatingActionButtonInteractionState)
contentColor(appearance = appearance, state = floatingActionButtonState)
}
val indication = InteractionValuesIndication(containerColor, contentColor)
val indication = interactionValuesIndication(containerColor, contentColor)
val elevation = OudsTheme.elevations.sticky
val floatingActionButtonElevation = FloatingActionButtonDefaults.elevation(
defaultElevation = elevation,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -135,7 +135,7 @@ fun OudsInputTag(
.clickable(
enabled = enabled,
interactionSource = interactionSource,
indication = InteractionValuesIndication(contentColor, backgroundColor, borderColor, borderWidth),
indication = interactionValuesIndication(contentColor, backgroundColor, borderColor, borderWidth),
onClick = onClick,
onClickLabel = stringResource(R.string.core_inputTag_remove_a11y),
role = Role.Button
Expand Down Expand Up @@ -227,7 +227,7 @@ private fun contentColor(state: OudsInputTagState): Color {
}
}

internal enum class OudsInputTagState {
private enum class OudsInputTagState {
Enabled, Hovered, Pressed, Disabled, Focused
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -237,7 +237,7 @@ private fun OudsLink(
.padding(horizontal = linkTokens.spacePaddingInline.value, vertical = linkTokens.spacePaddingBlock.value)
.clickable(
interactionSource = interactionSource,
indication = InteractionValuesIndication(contentColor, chevronColor, isUnderlined),
indication = interactionValuesIndication(contentColor, chevronColor, isUnderlined),
enabled = state != OudsLinkState.Disabled,
onClick = onClick
),
Expand Down Expand Up @@ -434,7 +434,7 @@ open class OudsLinkIcon private constructor(
get() = extraParameters.tint
}

internal enum class OudsLinkState {
private enum class OudsLinkState {
Enabled, Hovered, Pressed, Disabled, Focused
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -434,7 +434,7 @@ class OudsNavigationBarItemIcon private constructor(
*/
data class OudsNavigationBarItemBadge(val contentDescription: String, val count: Int? = null)

internal enum class OudsNavigationBarItemState {
private enum class OudsNavigationBarItemState {
Enabled, Hovered, Pressed, Focused
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -124,7 +124,7 @@ fun OudsRadioButton(
onClick = onClick,
enabled = enabled && !readOnly,
interactionSource = interactionSource,
indication = InteractionValuesIndication(backgroundColor),
indication = interactionValuesIndication(backgroundColor),
role = Role.RadioButton,
)
} else Modifier
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -116,7 +116,7 @@ fun OudsRadioButtonItem(
onClick = onClick,
enabled = enabled && !readOnly,
interactionSource = interactionSource,
indication = InteractionValuesIndication(backgroundColor),
indication = interactionValuesIndication(backgroundColor),
role = Role.RadioButton,
)
} else {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -106,7 +106,7 @@ fun OudsSwitchItem(
Modifier.toggleable(
value = checked,
interactionSource = interactionSource,
indication = InteractionValuesIndication(backgroundColor),
indication = interactionValuesIndication(backgroundColor),
enabled = enabled && !readOnly,
role = Role.Switch,
onValueChange = onCheckedChange
Expand Down
Loading