Skip to content

Commit

Permalink
OudsButton now supports both determinate and indeterminate circular p…
Browse files Browse the repository at this point in the history
…rogress indicators
  • Loading branch information
florentmaitre committed Nov 29, 2024
1 parent bddd1cb commit 49c3c06
Show file tree
Hide file tree
Showing 4 changed files with 73 additions and 55 deletions.
2 changes: 0 additions & 2 deletions NOTICE.txt
Original file line number Diff line number Diff line change
Expand Up @@ -67,8 +67,6 @@ app/src/main/res/drawable-night-xxxhdpi/il_tokens_grid_min_width.png
app/src/prod/res/drawable/ic_launcher_background.xml
app/src/prod/res/drawable/ic_launcher_foreground.xml

core/src/main/res/drawable/loading_indicator_circular.xml

docs/images/favicon-16x16.png
docs/images/orange-logo.svg

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import androidx.compose.foundation.layout.padding
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.rememberBottomSheetScaffoldState
import androidx.compose.runtime.Composable
import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.painterResource
Expand Down Expand Up @@ -43,12 +44,19 @@ fun ButtonDemoScreen() = DemoScreen(rememberButtonDemoState()) {
selectedChipIndex = OudsButton.Hierarchy.entries.indexOf(hierarchy),
onSelectionChange = { id -> hierarchy = OudsButton.Hierarchy.entries[id] }
)
val styles = remember {
listOf(
OudsButton.Style.Default,
OudsButton.Style.Loading(progress = null),
OudsButton.Style.Skeleton
)
}
CustomizationChoiceChipsColumn(
modifier = Modifier.padding(top = OudsSpaceKeyToken.Fixed.Medium.value),
label = stringResource(R.string.app_components_button_style_label),
chipsLabels = OudsButton.Style.entries.map { it.name },
selectedChipIndex = OudsButton.Style.entries.indexOf(style),
onSelectionChange = { id -> style = OudsButton.Style.entries[id] }
chipsLabels = styles.map { it::class.simpleName.orEmpty() },
selectedChipIndex = styles.indexOf(style),
onSelectionChange = { id -> style = styles[id] }
)
CustomizationChoiceChipsColumn(
modifier = Modifier.padding(top = OudsSpaceKeyToken.Fixed.Medium.value),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,7 @@

package com.orange.ouds.core.component.button

import androidx.compose.animation.core.LinearEasing
import androidx.compose.animation.core.animateFloat
import androidx.compose.animation.core.infiniteRepeatable
import androidx.compose.animation.core.rememberInfiniteTransition
import androidx.compose.animation.core.tween
import android.os.Parcelable
import androidx.compose.foundation.border
import androidx.compose.foundation.interaction.MutableInteractionSource
import androidx.compose.foundation.isSystemInDarkTheme
Expand All @@ -33,8 +29,8 @@ import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material3.Button
import androidx.compose.material3.ButtonColors
import androidx.compose.material3.ButtonDefaults
import androidx.compose.material3.CircularProgressIndicator
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.Icon
import androidx.compose.material3.LocalRippleConfiguration
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
Expand All @@ -47,14 +43,14 @@ import androidx.compose.ui.draw.alpha
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.ImageBitmap
import androidx.compose.ui.graphics.Shape
import androidx.compose.ui.graphics.graphicsLayer
import androidx.compose.ui.graphics.StrokeCap
import androidx.compose.ui.graphics.painter.Painter
import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.platform.LocalInspectionMode
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.tooling.preview.PreviewParameter
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp
import com.orange.ouds.core.R
import com.orange.ouds.core.component.content.OudsComponentContent
import com.orange.ouds.core.component.content.OudsComponentIcon
import com.orange.ouds.core.extensions.InteractionState
Expand All @@ -69,6 +65,7 @@ import com.orange.ouds.theme.outerBorder
import com.orange.ouds.theme.tokens.OudsBorderKeyToken
import com.orange.ouds.theme.tokens.OudsColorKeyToken
import com.orange.ouds.theme.tokens.OudsTypographyKeyToken
import kotlinx.parcelize.Parcelize

/**
* An OUDS button which displays only text.
Expand Down Expand Up @@ -193,7 +190,7 @@ private fun OudsButton(
modifier = modifier
.widthIn(min = buttonTokens.sizeMinWidth.dp)
.heightIn(min = buttonTokens.sizeMinHeight.dp, max = maxHeight)
.buttonBorder(hierarchy = hierarchy, state = state, shape = shape),
.border(hierarchy = hierarchy, state = state, shape = shape),
enabled = state != OudsButton.State.Disabled && state != OudsButton.State.Loading && state != OudsButton.State.Skeleton,
shape = shape,
colors = buttonColors(hierarchy = hierarchy, buttonState = state),
Expand All @@ -204,21 +201,9 @@ private fun OudsButton(
Box(contentAlignment = Alignment.Center) {
val isLoadingIndicatorVisible = state == OudsButton.State.Loading
if (isLoadingIndicatorVisible) {
val infiniteTransition = rememberInfiniteTransition("LoadingIndicatorInfiniteTransition")
val angle by infiniteTransition.animateFloat(
initialValue = 0f,
targetValue = 360f,
animationSpec = infiniteRepeatable(tween(1000, easing = LinearEasing)),
label = "LoadingIndicatorAngleAnimation"
)
Icon(
modifier = Modifier
.size(buttonTokens.sizeLoader.value)
.graphicsLayer { rotationZ = angle },
contentDescription = null,
painter = painterResource(id = R.drawable.loading_indicator_circular),
tint = contentColor(hierarchy = hierarchy, state = state)
)
val loadingStyle = style as? OudsButton.Style.Loading
val progress = if (LocalInspectionMode.current) 0.75f else loadingStyle?.progress
LoadingIndicator(hierarchy = hierarchy, progress)
}

Row(
Expand Down Expand Up @@ -261,13 +246,13 @@ private fun rememberOudsButtonState(
interactionState == InteractionState.Focused -> OudsButton.State.Focused
else -> OudsButton.State.Enabled
}
OudsButton.Style.Loading -> OudsButton.State.Loading
is OudsButton.Style.Loading -> OudsButton.State.Loading
OudsButton.Style.Skeleton -> OudsButton.State.Skeleton
}
}

@Composable
private fun Modifier.buttonBorder(hierarchy: OudsButton.Hierarchy, state: OudsButton.State, shape: Shape): Modifier {
private fun Modifier.border(hierarchy: OudsButton.Hierarchy, state: OudsButton.State, shape: Shape): Modifier {
val borderWidth = borderWidth(hierarchy = hierarchy, state = state)
val borderColor = borderColor(hierarchy = hierarchy, state = state)

Expand Down Expand Up @@ -464,6 +449,33 @@ private fun contentPadding(icon: OudsButton.Icon?, text: String?): PaddingValues
}
}

@Composable
private fun LoadingIndicator(hierarchy: OudsButton.Hierarchy, progress: Float?) {
val modifier = Modifier.size(OudsTheme.componentsTokens.button.sizeLoader.value)
val color = contentColor(hierarchy = hierarchy, state = OudsButton.State.Loading)
val strokeWidth = 3.dp
val trackColor = Color.Transparent
val strokeCap = StrokeCap.Square
if (progress != null) {
CircularProgressIndicator(
progress = { progress },
modifier = modifier,
color = color,
strokeWidth = strokeWidth,
trackColor = trackColor,
strokeCap = strokeCap
)
} else {
CircularProgressIndicator(
modifier = modifier,
color = color,
strokeWidth = strokeWidth,
trackColor = trackColor,
strokeCap = strokeCap
)
}
}

/**
* Contains the default values used by OUDS buttons.
*/
Expand Down Expand Up @@ -532,10 +544,31 @@ object OudsButton {
}

/**
* Represents the style of an OUDS button.
* Represents the different styles of an OUDS button.
*/
enum class Style {
Default, Loading, Skeleton
sealed class Style : Parcelable {

/**
* The button displays an icon and/or a text and supports user interactions if it is enabled.
*/
@Parcelize
data object Default : Style()

/**
* The button displays a circular loading indicator.
*
* @param progress The loading progress, where 0.0 represents no progress and 1.0 represents full progress.
* Values outside of this range are coerced into the range.
* Set this value to `null` to display a circular indeterminate progress indicator.
*/
@Parcelize
data class Loading(val progress: Float?) : Style()

/**
* This button displays a skeleton.
*/
@Parcelize
data object Skeleton : Style()
}

internal enum class State {
Expand Down
21 changes: 0 additions & 21 deletions core/src/main/res/drawable/loading_indicator_circular.xml

This file was deleted.

0 comments on commit 49c3c06

Please sign in to comment.