Skip to content

Commit

Permalink
lumen/LMN-420 update Range slider to support custom thumb with label (#…
Browse files Browse the repository at this point in the history
…1745)

* lumen/LMN-420_update_slider_to_provide_custom_thumb

* Updated snapshots for 'rtl'

* Updated snapshots for 'default'

* LMN-420 Remove custom semantics

There was an issue where this was announcing various different slider options with percentage/value based content. This provided a confusing user behaviour.
The current behaviour isn't perfect, but we'd need to build a completely custom slider to solve this. We can create an issue for the slider to improve. the API/accessibility support

* added missing liecnes

---------

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
Co-authored-by: Maria Neumayer <[email protected]>
  • Loading branch information
3 people authored Oct 2, 2023
1 parent b3fcffa commit 5c2c71a
Show file tree
Hide file tree
Showing 10 changed files with 227 additions and 15 deletions.
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.
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ import net.skyscanner.backpack.Variants
import net.skyscanner.backpack.compose.BpkSnapshotTest
import net.skyscanner.backpack.demo.compose.DefaultSliderSample
import net.skyscanner.backpack.demo.compose.RangeSliderSample
import net.skyscanner.backpack.demo.compose.RangeSliderWithLabelsSample
import org.junit.Test
import org.junit.runner.RunWith

Expand All @@ -42,4 +43,10 @@ class BpkSliderTest : BpkSnapshotTest() {
fun range() = snap(width = 200.dp) {
RangeSliderSample()
}

@Test
@Variants(BpkTestVariant.Default, BpkTestVariant.Rtl)
fun rangeWithLabel() = snap(width = 200.dp) {
RangeSliderWithLabelsSample()
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,11 @@ fun SliderStory(modifier: Modifier = Modifier) {
style = BpkTheme.typography.label2,
)
RangeSliderSample()
BpkText(
text = stringResource(R.string.slider_range_with_label),
style = BpkTheme.typography.label2,
)
RangeSliderWithLabelsSample()
}
}

Expand All @@ -75,6 +80,19 @@ internal fun RangeSliderSample(modifier: Modifier = Modifier) {
)
}

@Composable
internal fun RangeSliderWithLabelsSample(modifier: Modifier = Modifier) {
var rangeSliderValue by remember { mutableStateOf(12f..80f) }
BpkRangeSlider(
modifier = modifier,
maxValue = 100f,
value = rangeSliderValue,
lowerThumbLabel = stringResource(id = R.string.gbp_formatter, rangeSliderValue.start),
upperThumbLabel = stringResource(id = R.string.gbp_formatter, rangeSliderValue.endInclusive),
onValueChange = { newValue -> rangeSliderValue = newValue },
)
}

@Composable
internal fun DefaultSliderSample(modifier: Modifier = Modifier) {
var sliderValue by remember { mutableStateOf(0.5f) }
Expand Down
2 changes: 2 additions & 0 deletions app/src/main/res/values/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -235,6 +235,8 @@
<string name="rating_subtitle">1532 reviews</string>

<string name="slider_range">Range</string>
<string name="slider_range_with_label">Range With Label</string>
<string name="gbp_formatter">£%.1f</string>
<string name="slider_standard">Standard</string>
<string name="slider_stepped">Stepped</string>

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,14 +20,12 @@
package net.skyscanner.backpack.compose.slider

import androidx.compose.foundation.interaction.MutableInteractionSource
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.RangeSlider
import androidx.compose.material3.Slider
import androidx.compose.material3.SliderDefaults
import androidx.compose.runtime.Composable
import androidx.compose.runtime.remember
import androidx.compose.ui.Modifier
import net.skyscanner.backpack.compose.theme.BpkTheme
import net.skyscanner.backpack.compose.slider.internal.BpkRangeSliderImpl
import net.skyscanner.backpack.compose.slider.internal.sliderColors

@Composable
fun BpkSlider(
Expand All @@ -54,7 +52,6 @@ fun BpkSlider(
)
}

@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun BpkRangeSlider(
value: ClosedFloatingPointRange<Float>,
Expand All @@ -66,23 +63,41 @@ fun BpkRangeSlider(
steps: Int = 0,
onValueChangeFinished: (() -> Unit)? = null,
) {
RangeSlider(
BpkRangeSliderImpl(
value = value,
onValueChange = onValueChange,
modifier = modifier,
enabled = enabled,
valueRange = minValue..maxValue,
minValue = minValue,
maxValue = maxValue,
steps = steps,
onValueChangeFinished = onValueChangeFinished,
colors = sliderColors(),
)
}

@Composable
private fun sliderColors() = SliderDefaults.colors(
thumbColor = BpkTheme.colors.coreAccent,
activeTrackColor = BpkTheme.colors.coreAccent,
inactiveTrackColor = BpkTheme.colors.line,
activeTickColor = BpkTheme.colors.coreAccent,
inactiveTickColor = BpkTheme.colors.line,
)
fun BpkRangeSlider(
value: ClosedFloatingPointRange<Float>,
onValueChange: (ClosedFloatingPointRange<Float>) -> Unit,
lowerThumbLabel: String,
upperThumbLabel: String,
modifier: Modifier = Modifier,
maxValue: Float = 1f,
enabled: Boolean = true,
minValue: Float = 0f,
steps: Int = 0,
onValueChangeFinished: (() -> Unit)? = null,
) {
BpkRangeSliderImpl(
modifier = modifier,
value = value,
onValueChange = onValueChange,
enabled = enabled,
minValue = minValue,
maxValue = maxValue,
steps = steps,
lowerThumbLabel = lowerThumbLabel,
upperThumbLabel = upperThumbLabel,
onValueChangeFinished = onValueChangeFinished,
)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,156 @@
/*
* Backpack for Android - Skyscanner's Design System
*
* Copyright 2018 Skyscanner Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
package net.skyscanner.backpack.compose.slider.internal

import androidx.compose.foundation.background
import androidx.compose.foundation.interaction.MutableInteractionSource
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.RangeSlider
import androidx.compose.material3.SliderDefaults
import androidx.compose.runtime.Composable
import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment
import androidx.compose.ui.ExperimentalComposeUiApi
import androidx.compose.ui.Modifier
import androidx.compose.ui.semantics.invisibleToUser
import androidx.compose.ui.semantics.semantics
import androidx.compose.ui.semantics.stateDescription
import androidx.compose.ui.text.style.TextAlign
import net.skyscanner.backpack.compose.flare.BpkFlarePointerDirection
import net.skyscanner.backpack.compose.text.BpkText
import net.skyscanner.backpack.compose.theme.BpkTheme
import net.skyscanner.backpack.compose.tokens.BpkBorderRadius
import net.skyscanner.backpack.compose.tokens.BpkSpacing
import net.skyscanner.backpack.compose.utils.FlareShape

@OptIn(ExperimentalMaterial3Api::class)
@Composable
internal fun BpkRangeSliderImpl(
value: ClosedFloatingPointRange<Float>,
onValueChange: (ClosedFloatingPointRange<Float>) -> Unit,
enabled: Boolean,
minValue: Float,
maxValue: Float,
steps: Int,
onValueChangeFinished: (() -> Unit)?,
modifier: Modifier = Modifier,
lowerThumbLabel: String? = null,
upperThumbLabel: String? = null,
) {
val startInteractionSource = remember { MutableInteractionSource() }
val endInteractionSource = remember { MutableInteractionSource() }
RangeSlider(
value = value,
onValueChange = onValueChange,
modifier = modifier,
enabled = enabled,
startInteractionSource = startInteractionSource,
endInteractionSource = endInteractionSource,
valueRange = minValue..maxValue,
steps = steps,
onValueChangeFinished = onValueChangeFinished,
startThumb = {
lowerThumbLabel?.let {
SlideRangeLabel(
label = it,
enabled = enabled,
interactionSource = startInteractionSource,
)
} ?: SliderDefaults.Thumb(
interactionSource = startInteractionSource,
colors = sliderColors(),
enabled = enabled,
)
},
endThumb = {
upperThumbLabel?.let {
SlideRangeLabel(
label = it,
enabled = enabled,
interactionSource = endInteractionSource,
)
} ?: SliderDefaults.Thumb(
interactionSource = endInteractionSource,
colors = sliderColors(),
enabled = enabled,
)
},
colors = sliderColors(),
)
}

@OptIn(ExperimentalComposeUiApi::class)
@Composable
private fun SlideRangeLabel(
enabled: Boolean,
label: String,
interactionSource: MutableInteractionSource,
modifier: Modifier = Modifier,
) {
Column(
modifier = modifier
.semantics { stateDescription = label }
.padding(bottom = BpkSpacing.Xl),
verticalArrangement = Arrangement.Center,
horizontalAlignment = Alignment.CenterHorizontally,
) {
BpkText(
text = label,
color = BpkTheme.colors.textPrimaryInverse,
style = BpkTheme.typography.label2,
textAlign = TextAlign.Center,
modifier = Modifier
.background(
color = BpkTheme.colors.coreAccent,
shape = FlareShape(
borderRadius = BpkBorderRadius.Sm,
flareHeight = BpkSpacing.Sm,
pointerDirection = BpkFlarePointerDirection.Down,
),
)
.padding(top = BpkSpacing.Sm)
.padding(bottom = BpkSpacing.Md)
.padding(horizontal = BpkSpacing.Md)
.semantics { invisibleToUser() },

)
Spacer(
modifier = Modifier.height(BpkSpacing.Sm),
)
SliderDefaults.Thumb(
interactionSource = interactionSource,
colors = sliderColors(),
enabled = enabled,
)
}
}

@Composable
internal fun sliderColors() = SliderDefaults.colors(
thumbColor = BpkTheme.colors.coreAccent,
activeTrackColor = BpkTheme.colors.coreAccent,
inactiveTrackColor = BpkTheme.colors.line,
activeTickColor = BpkTheme.colors.coreAccent,
inactiveTickColor = BpkTheme.colors.line,
)
14 changes: 14 additions & 0 deletions docs/compose/Slider/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,3 +37,17 @@ BpkRangeSlider(
onValueChange = { newValue -> } // Handle update
)
```

Example of a `BpkRangeSlider` with Label:

```Kotlin
import net.skyscanner.backpack.compose.slider.BpkSlider

var rangeSliderValue by remember { mutableStateOf(0.2f..0.8f) }
BpkRangeSlider(
value = rangeSliderValue,
lowerThumbLabel = stringResource(id = R.string.gbp_formatter, rangeSliderValue.start),
upperThumbLabel = stringResource(id = R.string.gbp_formatter, rangeSliderValue.endInclusive),
onValueChange = { newValue -> } // Handle update
)
```
Binary file modified docs/compose/Slider/screenshots/default.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified docs/compose/Slider/screenshots/default_dm.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.

0 comments on commit 5c2c71a

Please sign in to comment.