Skip to content

Commit b99dda5

Browse files
committed
library: Add LinearProgressIndicator Component
1 parent 165372b commit b99dda5

File tree

3 files changed

+187
-1
lines changed

3 files changed

+187
-1
lines changed

composeApp/src/commonMain/kotlin/component/OtherComponent.kt

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ import androidx.compose.ui.unit.sp
2626
import top.yukonga.miuix.kmp.basic.ButtonDefaults
2727
import top.yukonga.miuix.kmp.basic.Card
2828
import top.yukonga.miuix.kmp.basic.Icon
29+
import top.yukonga.miuix.kmp.basic.LinearProgressIndicator
2930
import top.yukonga.miuix.kmp.basic.Slider
3031
import top.yukonga.miuix.kmp.basic.SliderDefaults.SliderHapticEffect
3132
import top.yukonga.miuix.kmp.basic.SmallTitle
@@ -184,6 +185,20 @@ fun OtherComponent(padding: PaddingValues) {
184185
)
185186
}
186187

188+
SmallTitle(text = "ProgressIndicator")
189+
LinearProgressIndicator(
190+
progress = progress,
191+
modifier = Modifier
192+
.padding(horizontal = 15.dp) // Increased from 12.dp because of StrokeCap.Round.
193+
.padding(bottom = 12.dp)
194+
)
195+
196+
LinearProgressIndicator(
197+
modifier = Modifier
198+
.padding(horizontal = 15.dp) // Increased from 12.dp because of StrokeCap.Round.
199+
.padding(bottom = 12.dp)
200+
)
201+
187202
SmallTitle(text = "TextField")
188203
TextField(
189204
value = text1,
Lines changed: 171 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,171 @@
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+
}

miuix/src/commonMain/kotlin/top/yukonga/miuix/kmp/basic/TextField.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -215,7 +215,7 @@ fun TextField(
215215
value: String,
216216
onValueChange: (String) -> Unit,
217217
modifier: Modifier = Modifier,
218-
insideMargin: DpSize = DpSize(16.dp, 15.dp),
218+
insideMargin: DpSize = DpSize(16.dp, 16.dp),
219219
backgroundColor: Color = MiuixTheme.colorScheme.secondaryContainer,
220220
cornerRadius: Dp = 16.dp,
221221
label: String = "",

0 commit comments

Comments
 (0)