Skip to content

Commit 4e53526

Browse files
committed
library: Add CircularProgressIndicator Component
1 parent f0d135c commit 4e53526

File tree

2 files changed

+156
-13
lines changed

2 files changed

+156
-13
lines changed

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

Lines changed: 23 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ import androidx.compose.ui.unit.dp
2525
import androidx.compose.ui.unit.sp
2626
import top.yukonga.miuix.kmp.basic.ButtonDefaults
2727
import top.yukonga.miuix.kmp.basic.Card
28+
import top.yukonga.miuix.kmp.basic.CircularProgressIndicator
2829
import top.yukonga.miuix.kmp.basic.Icon
2930
import top.yukonga.miuix.kmp.basic.LinearProgressIndicator
3031
import top.yukonga.miuix.kmp.basic.Slider
@@ -89,6 +90,7 @@ fun OtherComponent(padding: PaddingValues) {
8990
var text3 by remember { mutableStateOf("") }
9091
var progress by remember { mutableStateOf(0.5f) }
9192
var progressHaptic by remember { mutableStateOf(0.5f) }
93+
val progressValues = remember { listOf(0.1f, 0.3f, progress, 0.7f, 0.9f, null) }
9294
val progressDisable by remember { mutableStateOf(0.5f) }
9395
val tabTexts = listOf("tab1", "tab2", "tab3", "tab4", "tab5", "tab6")
9496
var selectedTabIndex1 by remember { mutableStateOf(0) }
@@ -184,19 +186,29 @@ fun OtherComponent(padding: PaddingValues) {
184186
)
185187
}
186188

187-
SmallTitle(text = "ProgressIndicator")
188-
LinearProgressIndicator(
189-
progress = progress,
190-
modifier = Modifier
191-
.padding(horizontal = 15.dp) // Increased from 12.dp because of StrokeCap.Round.
192-
.padding(bottom = 12.dp)
193-
)
189+
SmallTitle(text = "LinearProgressIndicator")
190+
progressValues.forEach { progressValue ->
191+
LinearProgressIndicator(
192+
progress = progressValue,
193+
modifier = Modifier
194+
.padding(horizontal = 15.dp) // Increased from 12.dp because of StrokeCap.Round.
195+
.padding(bottom = 12.dp)
196+
)
197+
}
194198

195-
LinearProgressIndicator(
199+
FlowRow(
196200
modifier = Modifier
197-
.padding(horizontal = 15.dp) // Increased from 12.dp because of StrokeCap.Round.
198-
.padding(bottom = 6.dp)
199-
)
201+
.fillMaxWidth()
202+
.padding(horizontal = 12.dp)
203+
.padding(bottom = 6.dp),
204+
horizontalArrangement = Arrangement.SpaceEvenly,
205+
) {
206+
progressValues.forEach { progressValue ->
207+
CircularProgressIndicator(
208+
progress = progressValue
209+
)
210+
}
211+
}
200212

201213
SmallTitle(text = "TextField")
202214
TextField(

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

Lines changed: 133 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,15 +8,18 @@ import androidx.compose.animation.core.tween
88
import androidx.compose.foundation.Canvas
99
import androidx.compose.foundation.layout.fillMaxWidth
1010
import androidx.compose.foundation.layout.height
11+
import androidx.compose.foundation.layout.size
1112
import androidx.compose.runtime.Composable
1213
import androidx.compose.runtime.Immutable
1314
import androidx.compose.runtime.LaunchedEffect
1415
import androidx.compose.runtime.Stable
1516
import androidx.compose.runtime.remember
1617
import androidx.compose.ui.Modifier
1718
import androidx.compose.ui.geometry.Offset
19+
import androidx.compose.ui.geometry.Size
1820
import androidx.compose.ui.graphics.Color
1921
import androidx.compose.ui.graphics.StrokeCap
22+
import androidx.compose.ui.graphics.drawscope.Stroke
2023
import androidx.compose.ui.unit.Dp
2124
import androidx.compose.ui.unit.dp
2225
import top.yukonga.miuix.kmp.basic.ProgressIndicatorDefaults.ProgressIndicatorColors
@@ -36,7 +39,7 @@ fun LinearProgressIndicator(
3639
progress: Float? = null,
3740
modifier: Modifier = Modifier,
3841
colors: ProgressIndicatorColors = ProgressIndicatorDefaults.progressIndicatorColors(),
39-
height: Dp = ProgressIndicatorDefaults.DefaultProgressIndicatorHeight,
42+
height: Dp = ProgressIndicatorDefaults.DefaultLinearProgressIndicatorHeight,
4043
strokeCap: StrokeCap = StrokeCap.Round
4144
) {
4245
val isIndeterminate = progress == null
@@ -135,9 +138,137 @@ fun LinearProgressIndicator(
135138
}
136139
}
137140

141+
/**
142+
* A [CircularProgressIndicator] with Miuix style.
143+
*
144+
* @param progress The current progress value between 0.0 and 1.0, or null for indeterminate state.
145+
* @param modifier The modifier to be applied to the indicator.
146+
* @param colors The colors used for the indicator.
147+
* @param strokeWidth The width of the circular stroke.
148+
* @param size The size (diameter) of the circular indicator.
149+
* @param strokeCap The shape of the indicator ends.
150+
*/
151+
@Composable
152+
fun CircularProgressIndicator(
153+
progress: Float? = null,
154+
modifier: Modifier = Modifier,
155+
colors: ProgressIndicatorColors = ProgressIndicatorDefaults.progressIndicatorColors(),
156+
strokeWidth: Dp = ProgressIndicatorDefaults.DefaultCircularProgressIndicatorStrokeWidth,
157+
size: Dp = ProgressIndicatorDefaults.DefaultCircularProgressIndicatorSize,
158+
strokeCap: StrokeCap = StrokeCap.Round
159+
) {
160+
val isIndeterminate = progress == null
161+
162+
if (isIndeterminate) {
163+
val rotationAnim = remember { Animatable(0f) }
164+
val sweepAnim = remember { Animatable(30f) }
165+
166+
LaunchedEffect(Unit) {
167+
rotationAnim.animateTo(
168+
targetValue = 360f,
169+
animationSpec = infiniteRepeatable(
170+
animation = tween(1000, easing = LinearEasing),
171+
repeatMode = RepeatMode.Restart
172+
)
173+
)
174+
}
175+
176+
LaunchedEffect(Unit) {
177+
while (true) {
178+
sweepAnim.animateTo(
179+
targetValue = 120f,
180+
animationSpec = tween(600, easing = LinearEasing)
181+
)
182+
sweepAnim.animateTo(
183+
targetValue = 30f,
184+
animationSpec = tween(600, easing = LinearEasing)
185+
)
186+
}
187+
}
188+
189+
Canvas(
190+
modifier = modifier.size(size)
191+
) {
192+
val strokeWidthPx = strokeWidth.toPx()
193+
val diameter = size.toPx() - strokeWidthPx
194+
val topLeft = Offset(strokeWidthPx / 2, strokeWidthPx / 2)
195+
val arcSize = Size(diameter, diameter)
196+
197+
drawArc(
198+
color = colors.backgroundColor(),
199+
startAngle = 0f,
200+
sweepAngle = 360f,
201+
useCenter = false,
202+
topLeft = topLeft,
203+
size = arcSize,
204+
style = Stroke(
205+
width = strokeWidthPx,
206+
cap = strokeCap
207+
)
208+
)
209+
210+
drawArc(
211+
color = colors.foregroundColor(true),
212+
startAngle = rotationAnim.value,
213+
sweepAngle = sweepAnim.value,
214+
useCenter = false,
215+
topLeft = topLeft,
216+
size = arcSize,
217+
style = Stroke(
218+
width = strokeWidthPx,
219+
cap = strokeCap
220+
)
221+
)
222+
}
223+
} else {
224+
val progressValue = progress.coerceIn(0f, 1f)
225+
226+
Canvas(
227+
modifier = modifier.size(size)
228+
) {
229+
val strokeWidthPx = strokeWidth.toPx()
230+
val diameter = size.toPx() - strokeWidthPx
231+
val topLeft = Offset(strokeWidthPx / 2, strokeWidthPx / 2)
232+
val arcSize = Size(diameter, diameter)
233+
234+
drawArc(
235+
color = colors.backgroundColor(),
236+
startAngle = 0f,
237+
sweepAngle = 360f,
238+
useCenter = false,
239+
topLeft = topLeft,
240+
size = arcSize,
241+
style = Stroke(
242+
width = strokeWidthPx,
243+
cap = strokeCap
244+
)
245+
)
246+
247+
drawArc(
248+
color = colors.foregroundColor(true),
249+
startAngle = -90f, // Start from the top
250+
sweepAngle = 360f * progressValue,
251+
useCenter = false,
252+
topLeft = topLeft,
253+
size = arcSize,
254+
style = Stroke(
255+
width = strokeWidthPx,
256+
cap = strokeCap
257+
)
258+
)
259+
}
260+
}
261+
}
262+
138263
object ProgressIndicatorDefaults {
139264
/** The default height of [LinearProgressIndicator]. */
140-
val DefaultProgressIndicatorHeight = 6.dp
265+
val DefaultLinearProgressIndicatorHeight = 6.dp
266+
267+
/** The default stroke width of [CircularProgressIndicator]. */
268+
val DefaultCircularProgressIndicatorStrokeWidth = 4.dp
269+
270+
/** The default size of [CircularProgressIndicator]. */
271+
val DefaultCircularProgressIndicatorSize = 36.dp
141272

142273
/**
143274
* The default [ProgressIndicatorColors] used in [LinearProgressIndicator].

0 commit comments

Comments
 (0)