Skip to content
Open
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
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ All notable changes to this project will be documented in this file. See [standa
* **SBBPrimaryButton:** finished component and out of beta state
* **SBBSecondaryButton:** finished component and out of beta state
* **SBBTertiaryButton:** finished component and out of beta state
* **SBBSwitch:** finished component and out of beta state

# 0.1.8

Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package ch.sbb.compose_mds.beta.switch
package ch.sbb.compose_mds.composables.switch

import SBBTheme
import android.content.res.Configuration
Expand All @@ -24,7 +24,6 @@ import androidx.compose.foundation.layout.size
import androidx.compose.foundation.selection.toggleable
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.material3.Icon
import androidx.compose.material3.MaterialTheme
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
Expand All @@ -39,14 +38,26 @@ import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.DpSize
import androidx.compose.ui.unit.IntOffset
import androidx.compose.ui.unit.dp
import ch.sbb.compose_mds.beta.ExperimentalSBBComponent
import ch.sbb.compose_mds.sbbicons.SBBIcons
import ch.sbb.compose_mds.sbbicons.Small
import ch.sbb.compose_mds.sbbicons.small.TickSmall
import ch.sbb.compose_mds.theme.PrimitiveColors
import ch.sbb.compose_mds.theme.SBBSpacing

@ExperimentalSBBComponent

private val knobSize = 28.dp
private val trackWidth = 52.dp

/***
* Implementation of the SBB Switch.
*
* @param checked whether or not this switch is checked
* @param enabled controls the enabled state of this switch
* @param onCheckedChange called when this switch is clicked. If null, then this switch will not be interactable, unless something else handles its input events and updates its state.
* @param interactionSource an optional hoisted MutableInteractionSource for observing and emitting Interactions for this switch.
*
* For a complete definition of the component, please visit [digital.sbb.ch](https://digital.sbb.ch/de/design-system/mobile/components/switch/)
*/
@Composable
fun SBBSwitch(
modifier: Modifier = Modifier,
Expand All @@ -55,13 +66,37 @@ fun SBBSwitch(
enabled: Boolean = true,
interactionSource: MutableInteractionSource = remember { MutableInteractionSource() },
) {
val color by animateColorAsState(
targetValue = when (checked) {
true -> MaterialTheme.colorScheme.primary
false -> PrimitiveColors.graphite
}
val colors = if (SBBTheme.isDarkMode) lightSwitchColors() else darkSwitchColors()
Box(
modifier = modifier
.alpha(if (enabled) 1f else 0.5f)
.size(DpSize(trackWidth + 1.dp, knobSize + 2.dp))
.padding(end = 1.dp)
.toggleable(
enabled = enabled,
value = checked,
interactionSource = interactionSource,
role = Role.Switch,
onValueChange = { onCheckedChange?.invoke(it) },
indication = null
),
contentAlignment = Alignment.CenterStart,
) {
SwitchTrack(checked, colors)
SwitchKnob(checked, enabled, colors)
}
}

@Composable
private fun SwitchKnob(
checked: Boolean,
enabled: Boolean = true,
colors: SBBSwitchColors,
) {
val shadow = if (enabled) 4.dp else 0.dp
val borderColor by animateColorAsState(
targetValue = if (checked) colors.checkedKnobBorderColor else colors.knobBorderColor
)
val knobSize = 26.dp
val knobOffset by animateIntOffsetAsState(
targetValue = IntOffset(
x = with(LocalDensity.current) {
Expand All @@ -73,60 +108,46 @@ fun SBBSwitch(
y = 0,
),
)
val alpha = when (enabled) {
true -> 1f
false -> 0.5f
}
val shadow = when (enabled) {
true -> 4.dp
false -> 0.dp
}
Box(
modifier = modifier
.alpha(alpha)
.size(DpSize(53.dp, knobSize + 2.dp))
.padding(end = 1.dp)
.toggleable(
enabled = enabled,
value = checked,
interactionSource = interactionSource,
role = Role.Switch,
onValueChange = { onCheckedChange?.invoke(it) },
indication = null
),
contentAlignment = Alignment.CenterStart,
modifier = Modifier
.offset { knobOffset }
.size(knobSize)
.shadow(elevation = shadow, shape = CircleShape)
.background(colors.knobBackgroundColor, shape = CircleShape)
.border(border = BorderStroke(width = 1.dp, color = borderColor), shape = CircleShape),
contentAlignment = Alignment.Center,
) {
Box(
modifier = Modifier
.fillMaxWidth()
.height(20.dp)
.background(color = color, shape = CircleShape),
)
Box(
modifier = Modifier
.offset { knobOffset }
.size(knobSize)
.shadow(elevation = shadow, shape = CircleShape)
.background(PrimitiveColors.white, shape = CircleShape)
.border(border = BorderStroke(width = 1.dp, color = color), shape = CircleShape),
contentAlignment = Alignment.Center,
AnimatedVisibility(
visible = checked,
enter = fadeIn(),
exit = fadeOut(),
) {
AnimatedVisibility(
visible = checked,
enter = fadeIn(),
exit = fadeOut(),
) {
Icon(
imageVector = SBBIcons.Small.TickSmall,
tint = color,
contentDescription = null
)
}
Icon(
imageVector = SBBIcons.Small.TickSmall,
tint = colors.iconColor,
contentDescription = null
)
}
}
}

@OptIn(ExperimentalSBBComponent::class)
@Composable
private fun SwitchTrack(
checked: Boolean,
colors: SBBSwitchColors,
) {
val backgroundColor by animateColorAsState(
targetValue = if (checked) colors.checkedBackgroundColor else colors.backgroundColor
)

Box(
modifier = Modifier
.fillMaxWidth()
.height(20.dp)
.background(color = backgroundColor, shape = CircleShape),
)
}

@Preview(showBackground = true)
@Preview(showBackground = true, uiMode = Configuration.UI_MODE_NIGHT_YES)
@Composable
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
package ch.sbb.compose_mds.composables.switch

import SBBTheme
import androidx.compose.runtime.Composable
import androidx.compose.ui.graphics.Color

@Composable
internal fun lightSwitchColors(): SBBSwitchColors {
val colors = SBBTheme.colors
return SBBSwitchColors(
knobBackgroundColor = colors.white,
knobBorderColor = colors.granite,
checkedKnobBorderColor = colors.primary,
iconColor = colors.primary,
backgroundColor = colors.graphite,
checkedBackgroundColor = colors.primary,
)
}

@Composable
internal fun darkSwitchColors(): SBBSwitchColors {
val colors = SBBTheme.colors
return SBBSwitchColors(
knobBackgroundColor = colors.white,
knobBorderColor = colors.granite,
checkedKnobBorderColor = colors.primary,
iconColor = colors.primary,
backgroundColor = colors.graphite,
checkedBackgroundColor = colors.primary,
)
}

internal data class SBBSwitchColors(
val knobBackgroundColor: Color,
val knobBorderColor: Color,
val checkedKnobBorderColor: Color,
val iconColor: Color,
val backgroundColor: Color,
val checkedBackgroundColor: Color,
)
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.tooling.preview.Preview
import ch.sbb.compose_mds.beta.ExperimentalSBBComponent
import ch.sbb.compose_mds.beta.list.SBBListHeader
import ch.sbb.compose_mds.beta.switch.SBBSwitch
import ch.sbb.compose_mds.composables.switch.SBBSwitch
import ch.sbb.compose_mds.theme.SBBSpacing
import ch.sbb.compose_mds.theme.defaultPadding

Expand Down
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading