1+ package top.yukonga.miuix.kmp.basic
2+
3+ import androidx.compose.animation.core.Animatable
4+ import androidx.compose.animation.core.LinearEasing
5+ import androidx.compose.animation.core.RepeatMode
6+ import androidx.compose.animation.core.infiniteRepeatable
7+ import androidx.compose.animation.core.tween
8+ import androidx.compose.foundation.Canvas
9+ import androidx.compose.foundation.layout.fillMaxWidth
10+ import androidx.compose.foundation.layout.height
11+ import androidx.compose.runtime.Composable
12+ import androidx.compose.runtime.Immutable
13+ import androidx.compose.runtime.LaunchedEffect
14+ import androidx.compose.runtime.Stable
15+ import androidx.compose.runtime.remember
16+ import androidx.compose.ui.Modifier
17+ import androidx.compose.ui.geometry.Offset
18+ import androidx.compose.ui.graphics.Color
19+ import androidx.compose.ui.graphics.StrokeCap
20+ import androidx.compose.ui.unit.Dp
21+ import androidx.compose.ui.unit.dp
22+ import top.yukonga.miuix.kmp.basic.ProgressIndicatorDefaults.ProgressIndicatorColors
23+ import top.yukonga.miuix.kmp.theme.MiuixTheme
24+
25+ /* *
26+ * A [LinearProgressIndicator] with Miuix style.
27+ *
28+ * @param progress The current progress value between 0.0 and 1.0, or null for indeterminate state.
29+ * @param modifier The modifier to be applied to the indicator.
30+ * @param colors The colors used for the indicator.
31+ * @param height The height of the indicator.
32+ * @param strokeCap The shape of the indicator ends.
33+ */
34+ @Composable
35+ fun LinearProgressIndicator (
36+ progress : Float? = null,
37+ modifier : Modifier = Modifier ,
38+ colors : ProgressIndicatorColors = ProgressIndicatorDefaults .progressIndicatorColors(),
39+ height : Dp = ProgressIndicatorDefaults .DefaultProgressIndicatorHeight ,
40+ strokeCap : StrokeCap = StrokeCap .Round
41+ ) {
42+ val isIndeterminate = progress == null
43+
44+ if (isIndeterminate) {
45+ val animatedValue = remember { Animatable (initialValue = 0f ) }
46+
47+ LaunchedEffect (Unit ) {
48+ animatedValue.animateTo(
49+ targetValue = 1f ,
50+ animationSpec = infiniteRepeatable(
51+ animation = tween(1200 , easing = LinearEasing ),
52+ repeatMode = RepeatMode .Restart
53+ )
54+ )
55+ }
56+
57+ Canvas (
58+ modifier = modifier
59+ .fillMaxWidth()
60+ .height(height)
61+ ) {
62+ drawLine(
63+ color = colors.backgroundColor(),
64+ start = Offset (0f , size.height / 2 ),
65+ end = Offset (size.width, size.height / 2 ),
66+ strokeWidth = size.height,
67+ cap = strokeCap
68+ )
69+
70+ val value = animatedValue.value
71+ val segmentWidth = 0.45f
72+ val gap = 0.55f
73+
74+ val positions = listOf (
75+ value,
76+ value - (segmentWidth + gap),
77+ value - 2 * (segmentWidth + gap)
78+ )
79+
80+ positions.forEach { position ->
81+ val adjustedPos = (position % 1f + 1f ) % 1f
82+
83+ if (adjustedPos < 1f - segmentWidth) {
84+ drawLine(
85+ color = colors.foregroundColor(true ),
86+ start = Offset (adjustedPos * size.width, size.height / 2 ),
87+ end = Offset ((adjustedPos + segmentWidth) * size.width, size.height / 2 ),
88+ strokeWidth = size.height,
89+ cap = strokeCap
90+ )
91+ } else {
92+ drawLine(
93+ color = colors.foregroundColor(true ),
94+ start = Offset (adjustedPos * size.width, size.height / 2 ),
95+ end = Offset (size.width, size.height / 2 ),
96+ strokeWidth = size.height,
97+ cap = strokeCap
98+ )
99+
100+ val remainingWidth = adjustedPos + segmentWidth - 1f
101+ drawLine(
102+ color = colors.foregroundColor(true ),
103+ start = Offset (0f , size.height / 2 ),
104+ end = Offset (remainingWidth * size.width, size.height / 2 ),
105+ strokeWidth = size.height,
106+ cap = strokeCap
107+ )
108+ }
109+ }
110+ }
111+ } else {
112+ val progressValue = progress.coerceIn(0f , 1f )
113+
114+ Canvas (
115+ modifier = modifier
116+ .fillMaxWidth()
117+ .height(height)
118+ ) {
119+ drawLine(
120+ color = colors.backgroundColor(),
121+ start = Offset (0f , size.height / 2 ),
122+ end = Offset (size.width, size.height / 2 ),
123+ strokeWidth = size.height,
124+ cap = strokeCap
125+ )
126+
127+ drawLine(
128+ color = colors.foregroundColor(true ),
129+ start = Offset (0f , size.height / 2 ),
130+ end = Offset (size.width * progressValue, size.height / 2 ),
131+ strokeWidth = size.height,
132+ cap = strokeCap
133+ )
134+ }
135+ }
136+ }
137+
138+ object ProgressIndicatorDefaults {
139+ /* * The default height of [LinearProgressIndicator]. */
140+ val DefaultProgressIndicatorHeight = 6 .dp
141+
142+ /* *
143+ * The default [ProgressIndicatorColors] used in [LinearProgressIndicator].
144+ */
145+ @Composable
146+ fun progressIndicatorColors (
147+ foregroundColor : Color = MiuixTheme .colorScheme.primary,
148+ disabledForegroundColor : Color = MiuixTheme .colorScheme.disabledPrimarySlider,
149+ backgroundColor : Color = MiuixTheme .colorScheme.tertiaryContainerVariant
150+ ): ProgressIndicatorColors {
151+ return ProgressIndicatorColors (
152+ foregroundColor = foregroundColor,
153+ disabledForegroundColor = disabledForegroundColor,
154+ backgroundColor = backgroundColor
155+ )
156+ }
157+
158+ @Immutable
159+ class ProgressIndicatorColors (
160+ private val foregroundColor : Color ,
161+ private val disabledForegroundColor : Color ,
162+ private val backgroundColor : Color
163+ ) {
164+ @Stable
165+ internal fun foregroundColor (enabled : Boolean ): Color =
166+ if (enabled) foregroundColor else disabledForegroundColor
167+
168+ @Stable
169+ internal fun backgroundColor (): Color = backgroundColor
170+ }
171+ }
0 commit comments