1package soy.iko.opencode.ui.components
3import androidx.compose.animation.core.RepeatMode
4import androidx.compose.animation.core.animateFloat
5import androidx.compose.animation.core.infiniteRepeatable
6import androidx.compose.animation.core.rememberInfiniteTransition
7import androidx.compose.animation.core.tween
8import androidx.compose.foundation.background
9import androidx.compose.foundation.layout.Arrangement
10import androidx.compose.foundation.layout.Box
11import androidx.compose.foundation.layout.Column
12import androidx.compose.foundation.layout.Row
13import androidx.compose.foundation.layout.fillMaxWidth
14import androidx.compose.foundation.layout.height
15import androidx.compose.foundation.layout.size
16import androidx.compose.foundation.shape.RoundedCornerShape
17import androidx.compose.material3.MaterialTheme
18import androidx.compose.runtime.Composable
19import androidx.compose.runtime.getValue
20import androidx.compose.ui.Modifier
21import androidx.compose.ui.draw.alpha
22import androidx.compose.ui.draw.clip
23import androidx.compose.ui.res.stringResource
24import androidx.compose.ui.semantics.contentDescription
25import androidx.compose.ui.semantics.semantics
26import androidx.compose.ui.unit.Dp
27import androidx.compose.ui.unit.dp
28import soy.iko.opencode.R
31 * A single pulse-animated placeholder row mirroring the session list's skeleton pattern: a
32 * leading avatar/box and two text bars (title + subtitle). Used by the server/MCP/usage/file
33 * browser screens' initial-load states so a structured preview of the eventual list reads more
34 * polished than a bare centered [androidx.compose.material3.CircularProgressIndicator], and
35 * matches the chat/session/file-viewer skeletons.
37 * Pulses 0.35↔0.7 alpha over 900ms; under [LocalReducedMotion] the alpha is a static 0.5 so the
38 * placeholder is still visible but doesn't animate. The whole row is one merged semantics node
39 * (TalkBack announces "Loading…" once per row, not three times for each bar).
41 * @param circleSize diameter of the leading placeholder (40dp matches the session avatar).
42 * @param barWidths fractional widths of the title and subtitle bars (0.7 to 0.45 by default).
46 modifier: Modifier = Modifier,
47 circleSize: Dp = 40.dp,
48 barWidths: Pair<Float, Float> = 0.7f to 0.45f,
50 val loadingLabel = stringResource(R.string.loading)
51 val reducedMotion = LocalReducedMotion.current
52 val transition = rememberInfiniteTransition(label = "skeleton_row")
53 val pulse by transition.animateFloat(
56 animationSpec = infiniteRepeatable(tween(900), RepeatMode.Reverse),
57 label = "skeleton_row_alpha",
59 val skeletonAlpha = if (reducedMotion) 0.5f else pulse
60 val skeletonColor = MaterialTheme.colorScheme.surfaceVariant
64 .semantics(mergeDescendants = true) { contentDescription = loadingLabel },
65 horizontalArrangement = Arrangement.spacedBy(12.dp),
66 verticalAlignment = androidx.compose.ui.Alignment.CenterVertically,
71 .clip(RoundedCornerShape(circleSize / 2))
72 .background(skeletonColor)
73 .alpha(skeletonAlpha),
76 modifier = Modifier.weight(1f),
77 verticalArrangement = Arrangement.spacedBy(6.dp),
81 .fillMaxWidth(barWidths.first)
83 .clip(RoundedCornerShape(4.dp))
84 .background(skeletonColor)
85 .alpha(skeletonAlpha),
89 .fillMaxWidth(barWidths.second)
91 .clip(RoundedCornerShape(4.dp))
92 .background(skeletonColor)
93 .alpha(skeletonAlpha),
keysesc to close
- j k
- move down, up
- enter
- open
- esc
- back, or up a level
- 1 2 3 4
- readme, files, commits, refs
- /
- filter
- y
- copy the clone command
- c
- compare the chosen commit with another (or shift-click both)
- ?
- show or hide this list