@@ -8,15 +8,18 @@ import androidx.compose.animation.core.tween
88import androidx.compose.foundation.Canvas
99import androidx.compose.foundation.layout.fillMaxWidth
1010import androidx.compose.foundation.layout.height
11+ import androidx.compose.foundation.layout.size
1112import androidx.compose.runtime.Composable
1213import androidx.compose.runtime.Immutable
1314import androidx.compose.runtime.LaunchedEffect
1415import androidx.compose.runtime.Stable
1516import androidx.compose.runtime.remember
1617import androidx.compose.ui.Modifier
1718import androidx.compose.ui.geometry.Offset
19+ import androidx.compose.ui.geometry.Size
1820import androidx.compose.ui.graphics.Color
1921import androidx.compose.ui.graphics.StrokeCap
22+ import androidx.compose.ui.graphics.drawscope.Stroke
2023import androidx.compose.ui.unit.Dp
2124import androidx.compose.ui.unit.dp
2225import 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+
138263object 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