Skip to content

Commit 6a8af50

Browse files
feat: Add Slider component (#5)
* chore(Slider): Setup slider module * feat(Slider): Add basic slider track with disabled range and colors * feat(Slider): add basic SliderLayout to position slider elements * fix(Slider): Add requiredSize for the SliderLayout * style: apply spotless * feat(Slider): Add SliderState and basic computation * style: apply spotless * feat(Slider): Add Custom Value distribution and parabolic example * style: apply spotless * docs(Slider): Add kdoc for the public apis * feat(Slider): Add Tap functionality * feat(Slider): Add initial version of the disabled range feature * refactor(Slider): Add extension function to interpolate the range * refactor(Slider): Add another Slider fucntion that accepts SliderState * docs(Slider): Add kdoc for the new slider function * feat(Slider): Add slider semantics modifier for accesability * feat: Add disabled state to the Slider, also refactor the state management * docs(Slider): Add some more kdoc comments * feat: Update demo for the Slider * refactor: apply PR comments * style: apply spotless * build: put basic compose deps into bundle * style(Slider): add FloatRange annotation to progress part * fix: swap inverse interplote methods * add LinearEquation and RangedLinearEquation classes * implement SensitivityDistribution & its builder * write tests for SensitivityDistribution * add default sensitivity in the builder if no values were added * make values normalized ( between 0 and 1 ) * rename variable * add valueToFraction and fractionToValue functions * update tests * make FragmentedLinearDistribution implement the SliderValueDistribution interface * update SliderState * update Demo * update coerceInDisabledRange function implementation * create linear equation from 2 points * implement ValueCheckPointDistribution * implement tests ValueCheckPointDistribution * remove unnecessary lateinit and add TODO * refactor: move distribution related stuff to the separate package * refactor: move distribution related stuff to the separate package * chore: Delete FragmentedLinearDistribution.kt * refactor: rename coerce function in state * style: apply spotless * refactor: move math things to the separate package * style: apply spotless * docs: add kdoc for CheckPointsValueDistribution * feat: add SliderValue Distribution builder for checkpoint inside cmpanion object * docs: add missing kdoc --------- Co-authored-by: kamel.mohamad <[email protected]>
1 parent 50267b7 commit 6a8af50

33 files changed

+1320
-19
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
*.iml
22
.gradle
33
/local.properties
4+
/.idea/
45
/.idea/caches
56
/.idea/libraries
67
/.idea/modules.xml

.idea/deploymentTargetDropDown.xml

Lines changed: 30 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

.idea/deploymentTargetSelector.xml

Lines changed: 0 additions & 3 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

.idea/gradle.xml

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

demo/build.gradle.kts

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ android {
2929
compose = true
3030
}
3131
composeOptions {
32-
kotlinCompilerExtensionVersion = "1.5.1"
32+
kotlinCompilerExtensionVersion = libs.versions.composeCompiler.get()
3333
}
3434
packaging {
3535
resources {
@@ -43,14 +43,11 @@ kotlin {
4343
}
4444

4545
dependencies {
46-
46+
implementation(project(":slider"))
4747
implementation(libs.androidx.core.ktx)
4848
implementation(libs.androidx.lifecycle.runtime.ktx)
4949
implementation(libs.androidx.activity.compose)
5050
implementation(platform(libs.compose.bom))
51-
implementation(libs.compose.ui)
52-
implementation(libs.compose.ui.graphics)
53-
implementation(libs.compose.ui.tooling.preview)
5451
implementation(libs.compose.material3)
5552
testImplementation(libs.junit)
5653
androidTestImplementation(libs.androidx.junit)

demo/src/main/java/io/monstarlab/mosaic/MainActivity.kt

Lines changed: 2 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -4,13 +4,11 @@ import android.os.Bundle
44
import androidx.activity.ComponentActivity
55
import androidx.activity.compose.setContent
66
import androidx.activity.enableEdgeToEdge
7-
import androidx.compose.foundation.layout.fillMaxSize
8-
import androidx.compose.foundation.layout.padding
9-
import androidx.compose.material3.Scaffold
107
import androidx.compose.material3.Text
118
import androidx.compose.runtime.Composable
129
import androidx.compose.ui.Modifier
1310
import androidx.compose.ui.tooling.preview.Preview
11+
import io.monstarlab.mosaic.features.SliderDemo
1412
import io.monstarlab.mosaic.ui.theme.MosaicTheme
1513

1614
class MainActivity : ComponentActivity() {
@@ -19,12 +17,7 @@ class MainActivity : ComponentActivity() {
1917
enableEdgeToEdge()
2018
setContent {
2119
MosaicTheme {
22-
Scaffold(modifier = Modifier.fillMaxSize()) { innerPadding ->
23-
Greeting(
24-
name = "Android",
25-
modifier = Modifier.padding(innerPadding),
26-
)
27-
}
20+
SliderDemo()
2821
}
2922
}
3023
}
Lines changed: 210 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,210 @@
1+
package io.monstarlab.mosaic.features
2+
3+
import androidx.compose.animation.animateColor
4+
import androidx.compose.animation.core.LinearEasing
5+
import androidx.compose.animation.core.RepeatMode
6+
import androidx.compose.animation.core.animateFloat
7+
import androidx.compose.animation.core.infiniteRepeatable
8+
import androidx.compose.animation.core.rememberInfiniteTransition
9+
import androidx.compose.animation.core.tween
10+
import androidx.compose.foundation.background
11+
import androidx.compose.foundation.layout.Arrangement
12+
import androidx.compose.foundation.layout.Box
13+
import androidx.compose.foundation.layout.Column
14+
import androidx.compose.foundation.layout.Row
15+
import androidx.compose.foundation.layout.Spacer
16+
import androidx.compose.foundation.layout.fillMaxWidth
17+
import androidx.compose.foundation.layout.height
18+
import androidx.compose.foundation.layout.padding
19+
import androidx.compose.foundation.layout.size
20+
import androidx.compose.foundation.shape.CircleShape
21+
import androidx.compose.foundation.shape.RoundedCornerShape
22+
import androidx.compose.material3.MaterialTheme
23+
import androidx.compose.material3.Scaffold
24+
import androidx.compose.material3.Switch
25+
import androidx.compose.material3.Text
26+
import androidx.compose.runtime.Composable
27+
import androidx.compose.runtime.getValue
28+
import androidx.compose.runtime.mutableFloatStateOf
29+
import androidx.compose.runtime.mutableStateOf
30+
import androidx.compose.runtime.remember
31+
import androidx.compose.runtime.setValue
32+
import androidx.compose.ui.Alignment
33+
import androidx.compose.ui.Modifier
34+
import androidx.compose.ui.draw.clip
35+
import androidx.compose.ui.draw.rotate
36+
import androidx.compose.ui.graphics.Color
37+
import androidx.compose.ui.text.style.TextAlign
38+
import androidx.compose.ui.tooling.preview.Preview
39+
import androidx.compose.ui.unit.dp
40+
import io.monstarlab.mosaic.slider.Slider
41+
import io.monstarlab.mosaic.slider.SliderColors
42+
import io.monstarlab.mosaic.slider.distribution.CheckPointsValueDistribution
43+
import io.monstarlab.mosaic.slider.distribution.SliderValueDistribution
44+
import kotlin.math.roundToInt
45+
import androidx.compose.material3.Slider as MaterialSlider
46+
47+
@Composable
48+
fun SliderDemo() = Scaffold(modifier = Modifier) {
49+
Column(
50+
modifier = Modifier
51+
.padding(it)
52+
.padding(16.dp),
53+
verticalArrangement = Arrangement.spacedBy(32.dp),
54+
) {
55+
MosaicSliderDemo()
56+
}
57+
}
58+
59+
@Composable
60+
fun MosaicSliderDemo() {
61+
Column {
62+
val colors = SliderColors(
63+
activeTrackColor = Color.Black,
64+
disabledRangeTrackColor = Color.Red,
65+
thumbColor = Color.Yellow,
66+
)
67+
var enabled by remember { mutableStateOf(true) }
68+
var isCustom by remember { mutableStateOf(false) }
69+
var linearDistribution by remember { mutableStateOf(false) }
70+
var sliderValue by remember { mutableFloatStateOf(500f) }
71+
72+
MaterialSlider(
73+
value = sliderValue,
74+
onValueChange = { sliderValue = it },
75+
valueRange = 0f..1000f,
76+
)
77+
78+
val modifier = if (isCustom) {
79+
Modifier
80+
.clip(RoundedCornerShape(16.dp))
81+
.height(15.dp)
82+
} else {
83+
Modifier
84+
}
85+
86+
val fragmentedDistribution: SliderValueDistribution = remember {
87+
CheckPointsValueDistribution(
88+
listOf(
89+
0f to 0f,
90+
0.2f to 500f,
91+
0.4f to 800f,
92+
1f to 1000f,
93+
),
94+
)
95+
}
96+
97+
Slider(
98+
value = sliderValue,
99+
onValueChange = { sliderValue = it },
100+
modifier = modifier,
101+
enabled = enabled,
102+
colors = colors,
103+
range = 0f..1000f,
104+
disabledRange = 50f..300f,
105+
valueDistribution = if (linearDistribution) {
106+
SliderValueDistribution.Linear
107+
} else {
108+
fragmentedDistribution
109+
},
110+
thumb = {
111+
if (isCustom) {
112+
val transition = rememberInfiniteTransition(label = "")
113+
val color = transition.animateColor(
114+
initialValue = Color.Red,
115+
targetValue = Color.Green,
116+
animationSpec = infiniteRepeatable(
117+
animation = tween(1000, easing = LinearEasing),
118+
repeatMode = RepeatMode.Reverse,
119+
),
120+
label = "",
121+
)
122+
123+
val rotation = transition.animateFloat(
124+
initialValue = 0f,
125+
targetValue = 360f,
126+
animationSpec = infiniteRepeatable(
127+
tween(5000, easing = LinearEasing),
128+
repeatMode = RepeatMode.Restart,
129+
),
130+
label = "",
131+
)
132+
133+
Box(
134+
modifier = Modifier
135+
.rotate(rotation.value)
136+
.size(48.dp)
137+
.background(color.value),
138+
)
139+
} else {
140+
Box(
141+
modifier = Modifier
142+
.size(16.dp)
143+
.background(colors.thumbColor(enabled), CircleShape),
144+
)
145+
}
146+
},
147+
148+
)
149+
150+
Text(
151+
text = "Current value: ${sliderValue.roundToInt()}",
152+
modifier = Modifier.align(Alignment.CenterHorizontally),
153+
textAlign = TextAlign.Center,
154+
style = MaterialTheme.typography.bodyLarge,
155+
)
156+
157+
Row(
158+
horizontalArrangement = Arrangement.SpaceBetween,
159+
modifier = Modifier
160+
.fillMaxWidth()
161+
.padding(top = 32.dp),
162+
) {
163+
LabeledSwitch(
164+
label = "Slider enabled",
165+
checked = enabled,
166+
onValueChange = { enabled = it },
167+
)
168+
169+
LabeledSwitch(
170+
label = "Customise",
171+
checked = isCustom,
172+
onValueChange = { isCustom = it },
173+
)
174+
}
175+
176+
LabeledSwitch(
177+
label = "Use linear distribution or parabolic",
178+
checked = linearDistribution,
179+
onValueChange = { linearDistribution = it },
180+
modifier = Modifier.align(Alignment.CenterHorizontally),
181+
)
182+
}
183+
}
184+
185+
@Composable
186+
private fun LabeledSwitch(
187+
label: String,
188+
checked: Boolean,
189+
onValueChange: (Boolean) -> Unit,
190+
modifier: Modifier = Modifier,
191+
) {
192+
Column(
193+
modifier = modifier,
194+
horizontalAlignment = Alignment.CenterHorizontally,
195+
196+
) {
197+
Text(text = label, textAlign = TextAlign.Center)
198+
Spacer(modifier = Modifier.height(3.dp))
199+
Switch(
200+
checked = checked,
201+
onCheckedChange = onValueChange,
202+
)
203+
}
204+
}
205+
206+
@Preview
207+
@Composable
208+
private fun PreviewSliderDemo() {
209+
SliderDemo()
210+
}

gradle/libs.versions.toml

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,12 +7,14 @@ junitVersion = "1.1.5"
77
espresso = "3.5.1"
88
lifecyle = "2.7.0"
99
activityCompose = "1.8.2"
10+
composeCompiler = "1.5.1"
1011
composeBom = "2024.02.00"
1112
spotless="6.25.0"
1213
detekt="1.23.5"
1314
gradle-maven-publsih ="0.28.0"
1415

1516
[libraries]
17+
kotlin_gradle_plugin = { module = "org.jetbrains.kotlin:kotlin-gradle-plugin", version.ref = "kotlin"}
1618
android_gradle_plugin = { module = "com.android.tools.build:gradle", version.ref = "agp" }
1719
androidx-core-ktx = { module = "androidx.core:core-ktx", version.ref = "coreKtx" }
1820
junit = { module = "junit:junit", version.ref = "junit" }
@@ -24,6 +26,7 @@ androidx-activity-compose = { module = "androidx.activity:activity-compose", ver
2426
compose-bom = { module = "androidx.compose:compose-bom", version.ref = "composeBom" }
2527
compose-ui = { module = "androidx.compose.ui:ui" }
2628
compose-ui-graphics = { module = "androidx.compose.ui:ui-graphics" }
29+
compose-foundation = { module = "androidx.compose.foundation:foundation" }
2730
compose-ui-tooling = { module = "androidx.compose.ui:ui-tooling" }
2831
compose-ui-tooling-preview = { module = "androidx.compose.ui:ui-tooling-preview" }
2932
compose-ui-test-manifest = { module = "androidx.compose.ui:ui-test-manifest" }
@@ -41,5 +44,13 @@ mosaic-application = { id = "io.monstarlab.mosaic.application", version = "undef
4144
mosaic-library = { id = "io.monstarlab.mosaic.library", version = "undefined" }
4245
gradle-maven-publish = {id = "com.vanniktech.maven.publish", version.ref = "gradle-maven-publsih"}
4346

47+
[bundles]
48+
compose_core = [
49+
"compose-ui",
50+
"compose-ui-graphics",
51+
"compose-foundation",
52+
"compose-ui-tooling",
53+
"compose-ui-tooling-preview"
54+
]
4455

4556

lib/build.gradle.kts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ android {
2525
}
2626
}
2727
}
28+
2829
mavenPublishing {
2930
configure(AndroidSingleVariantLibrary("release"))
3031
publishToMavenCentral(SonatypeHost.S01)
@@ -34,10 +35,11 @@ mavenPublishing {
3435

3536
kotlin {
3637
jvmToolchain(17)
38+
explicitApi()
3739
}
3840

3941
dependencies {
4042
implementation(libs.androidx.core.ktx)
4143
androidTestImplementation(libs.androidx.junit)
4244
androidTestImplementation(libs.androidx.espresso.core)
43-
}
45+
}

settings.gradle.kts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,3 +23,4 @@ dependencyResolutionManagement {
2323
rootProject.name = "Mosaic"
2424
include(":demo")
2525
include(":lib")
26+
include(":slider")

0 commit comments

Comments
 (0)