@@ -11,24 +11,23 @@ import androidx.compose.foundation.Canvas
1111import androidx.compose.foundation.Image
1212import androidx.compose.foundation.layout.Box
1313import androidx.compose.foundation.layout.fillMaxSize
14+ import androidx.compose.material3.MaterialTheme
1415import androidx.compose.runtime.Composable
1516import androidx.compose.runtime.getValue
1617import androidx.compose.ui.Modifier
1718import androidx.compose.ui.geometry.Offset
1819import androidx.compose.ui.graphics.Color
1920import androidx.compose.ui.layout.ContentScale
20- import androidx.compose.ui.platform.LocalDensity
2121import androidx.compose.ui.res.painterResource
22- import androidx.compose.ui.unit.dp
2322import androidx.core.graphics.PathParser
2423import com.anysoftkeyboard.janus.app.R
2524
2625// The path data from docs/loader_path.svg
2726private const val LOADER_PATH_DATA =
2827 "M 218.00,126.00 C 218.00,126.00 231.33,130.00 231.33,130.00 231.33,130.00 243.33,138.67 243.33,138.67 243.33,138.67 251.33,148.00 251.33,148.00 251.33,148.00 254.67,161.33 254.67,161.33 254.67,161.33 253.33,178.67 253.33,178.67 253.33,178.67 252.00,192.00 252.00,192.00 252.00,192.00 247.33,206.00 247.33,206.00 247.33,206.00 243.33,217.33 243.33,217.33 243.33,217.33 239.33,226.67 239.33,226.67 239.33,226.67 234.67,235.33 234.67,235.33 234.67,235.33 225.33,246.67 225.33,246.67 225.33,246.67 204.00,274.00 204.00,274.00 204.00,274.00 197.33,280.00 197.33,280.00 197.33,280.00 187.33,286.00 187.33,286.00 187.33,286.00 174.67,290.67 174.67,290.67 174.67,290.67 160.67,291.33 160.67,291.33 160.67,291.33 148.00,288.67 148.00,288.67 148.00,288.67 137.33,280.00 137.33,280.00 137.33,280.00 128.67,269.33 128.67,269.33 128.67,269.33 124.00,255.33 124.00,255.33 124.00,255.33 124.00,240.67 124.00,240.67 124.00,240.67 129.33,230.67 129.33,230.67 129.33,230.67 140.00,223.33 140.00,223.33 140.00,223.33 151.33,216.67 151.33,216.67 151.33,216.67 164.00,211.33 164.00,211.33 164.00,211.33 176.67,206.00 176.67,206.00 176.67,206.00 190.00,203.33 190.00,203.33 190.00,203.33 203.33,200.67 203.33,200.67 203.33,200.67 216.00,199.33 216.00,199.33 216.00,199.33 231.33,200.00 231.33,200.00 231.33,200.00 265.33,207.33 265.33,207.33 265.33,207.33 276.67,212.00 276.67,212.00 276.67,212.00 286.67,218.00 286.67,218.00 286.67,218.00 296.00,226.67 296.00,226.67 296.00,226.67 302.67,235.33 302.67,235.33 302.67,235.33 306.67,246.67 306.67,246.67 306.67,246.67 305.33,262.00 305.33,262.00 305.33,262.00 300.00,272.00 300.00,272.00 300.00,272.00 291.33,284.67 291.33,284.67 291.33,284.67 280.67,288.00 280.67,288.00 280.67,288.00 266.00,290.00 266.00,290.00 266.00,290.00 252.67,290.00 252.67,290.00 252.67,290.00 238.67,284.00 238.67,284.00 238.67,284.00 230.00,274.67 230.00,274.67 230.00,274.67 223.33,267.33 223.33,267.33 223.33,267.33 211.33,255.33 211.33,255.33 211.33,255.33 204.67,246.67 204.67,246.67 204.67,246.67 198.00,235.33 198.00,235.33 198.00,235.33 190.00,220.00 190.00,220.00 190.00,220.00 178.00,186.00 178.00,186.00 178.00,186.00 177.33,172.67 177.33,172.67 177.33,172.67 178.00,158.67 178.00,158.67 178.00,158.67 180.67,146.00 180.67,146.00 180.67,146.00 188.67,138.00 188.67,138.00 188.67,138.00 202.00,130.00 202.00,130.00"
2928
30- // The Gold Color requested
31- private val GoldColor = Color ( 0xFFFFD700 )
29+ private const val viewPortSize = 432f
30+ private const val dotRadiusFactor = 0.03f
3231
3332private val NormalizedPathPoints : List <Offset > by lazy {
3433 val path = PathParser .createPathFromPathData(LOADER_PATH_DATA )
@@ -41,15 +40,21 @@ private val NormalizedPathPoints: List<Offset> by lazy {
4140 for (i in 0 until pointsCount) {
4241 val distance = (i.toFloat() / pointsCount) * length
4342 pathMeasure.getPosTan(distance, pos, tan)
44- // Normalize by viewport size 432x432
45- points.add(Offset (pos[0 ] / 432f , pos[1 ] / 432f ))
43+ // Normalize by viewport size
44+ points.add(Offset (pos[0 ] / viewPortSize , pos[1 ] / viewPortSize ))
4645 }
4746 points
4847}
4948
5049@Composable
51- fun JanusLoader (modifier : Modifier = Modifier , durationMillis : Int = 1000) {
52- // 1. Define the Animation
50+ fun JanusLoader (
51+ modifier : Modifier = Modifier ,
52+ durationMillis : Int = 2000,
53+ tint : Color = MaterialTheme .colorScheme.primary,
54+ dotColor : Color = MaterialTheme .colorScheme.onPrimary,
55+ dotColorFill : Color = MaterialTheme .colorScheme.secondary
56+ ) {
57+ // Define the Animation
5358 val infiniteTransition = rememberInfiniteTransition(label = " JanusLoaderAnimation" )
5459 val progress by
5560 infiniteTransition.animateFloat(
@@ -61,29 +66,56 @@ fun JanusLoader(modifier: Modifier = Modifier, durationMillis: Int = 1000) {
6166 repeatMode = RepeatMode .Restart ),
6267 label = " Progress" )
6368
64- // 2. Density for converting dp to px
65- val density = LocalDensity .current
66- val dotRadiusPx = with (density) { 4 .dp.toPx() }
67-
6869 Box (modifier = modifier) {
6970 // Background Image
7071 Image (
7172 painter = painterResource(id = R .mipmap.ic_launcher_foreground),
7273 contentDescription = null ,
7374 modifier = Modifier .fillMaxSize(),
74- contentScale = ContentScale .Fit )
75-
76- // 3. Draw Content
75+ contentScale = ContentScale .Fit ,
76+ colorFilter = androidx.compose.ui.graphics. ColorFilter .tint(tint))
77+ // Traveling dots
7778 Canvas (modifier = Modifier .fillMaxSize()) {
7879 val points = NormalizedPathPoints
79- val index = (progress * (points.size - 1 )).toInt().coerceIn(0 , points.size - 1 )
80- val normalizedPoint = points[index]
80+ fun drawTravelingDot (index : Int , dotRadiusPx : Float ) {
81+ val normalizedPoint = points[index]
82+ val dotPosition =
83+ Offset (x = normalizedPoint.x * size.width, y = normalizedPoint.y * size.height)
84+ drawCircle(color = dotColor, radius = dotRadiusPx, center = dotPosition)
85+ drawCircle(color = dotColorFill, radius = dotRadiusPx - 2 , center = dotPosition)
86+ }
8187
82- val dotPosition =
83- Offset (x = normalizedPoint.x * size.width, y = normalizedPoint.y * size.height)
88+ val baseDotRadiusPx = size.width * dotRadiusFactor
89+ val pointsCount = points. size
8490
85- // Draw the Traveling Dot
86- drawCircle(color = GoldColor , radius = dotRadiusPx, center = dotPosition)
91+ val dots =
92+ List (4 ) { level ->
93+ TravelingDot (
94+ pointsCount = pointsCount,
95+ durationMillis = durationMillis,
96+ baseDotRadiusPx = baseDotRadiusPx,
97+ delayLevel = level,
98+ drawTravelingDot = ::drawTravelingDot)
99+ }
100+
101+ dots.forEach { it.draw(progress) }
87102 }
88103 }
89104}
105+
106+ private class TravelingDot (
107+ private val pointsCount : Int ,
108+ private val durationMillis : Int ,
109+ private val baseDotRadiusPx : Float ,
110+ private val delayLevel : Int ,
111+ private val drawTravelingDot : (Int , Float ) -> Unit
112+ ) {
113+ private val delay = delayLevel * 70f
114+ private val radius = baseDotRadiusPx / (1 + delayLevel)
115+
116+ fun draw (progress : Float ) {
117+ val delayedProgress = (progress - (delay / durationMillis)).let { if (it < 0 ) it + 1f else it }
118+ val index = (delayedProgress * (pointsCount - 1 )).toInt().coerceIn(0 , pointsCount - 1 )
119+ drawTravelingDot(index, radius)
120+ }
121+ }
0 commit comments