Library rows: circular teal play button at row left (Android-home affordance) — plays/stops without opening the detail page; DONE files only; Stop square while playing

This commit is contained in:
avi 2026-09-21 19:45:20 -05:00
commit 13aae7818d
2 changed files with 61 additions and 0 deletions

View file

@ -40,6 +40,11 @@ import androidx.compose.ui.semantics.contentDescription
import androidx.compose.ui.semantics.semantics
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.shadow
import androidx.compose.ui.draw.clip
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.PlayArrow
import androidx.compose.material.icons.filled.Stop
import androidx.compose.material3.Icon
import androidx.compose.ui.focus.onFocusChanged
import androidx.compose.ui.focus.onFocusEvent
import androidx.compose.ui.graphics.Color
@ -165,6 +170,43 @@ fun ShonarGhostButton(
)
}
/** Circular transport button, matching the Android home rows: teal
* hairline ring + teal play triangle; swaps to a filled-look stop
* square while its row is playing. Inner clickable consumes the tap,
* so the enclosing card's open-on-click doesn't fire. */
@Composable
fun ShonarPlayCircle(
playing: Boolean,
onClick: () -> Unit,
modifier: Modifier = Modifier,
size: androidx.compose.ui.unit.Dp = 40.dp,
) {
Surface(
modifier = modifier.size(size)
.clip(CircleShape)
.clickable(
interactionSource = remember { MutableInteractionSource() },
indication = null,
onClick = onClick,
)
.border(Shonar.BorderW, Shonar.Accent.copy(alpha = 0.7f), CircleShape),
color = androidx.compose.ui.graphics.Color.Transparent,
shape = CircleShape,
) {
Box(contentAlignment = Alignment.Center) {
if (playing) {
Icon(Icons.Filled.Stop, contentDescription = "Stop",
tint = Shonar.Accent,
modifier = Modifier.size(Shonar.IconSm))
} else {
Icon(Icons.Filled.PlayArrow, contentDescription = "Play",
tint = Shonar.Accent,
modifier = Modifier.size(Shonar.IconSm))
}
}
}
}
/** Destructive action: quiet red outline — visible without shouting.
* ink overrides the label/icon color (detail-page Play/Delete use
* TextPrimary = white-on-dark per user directive, Sep 21). */

View file

@ -423,6 +423,25 @@ fun LibraryScreen(state: DesktopState) {
checked = isSelected,
onCheckedChange = { state.toggleSelected(e.file.absolutePath) },
)
// Android-home affordance: circular play at the
// left of the row — listen without opening.
// Transcribed files only (nothing to hear yet
// otherwise); shows Stop while playing.
val playingFile by state.playingFile.collectAsState()
val isThisPlaying =
playingFile?.absolutePath == e.file.absolutePath
if (e.status == FileStatus.DONE) {
Spacer(Modifier.width(Shonar.Space1))
ShonarPlayCircle(
playing = isThisPlaying,
onClick = {
if (isThisPlaying) state.stopPlayback()
else state.playFrom(e.file)
})
Spacer(Modifier.width(Shonar.Space2))
} else {
Spacer(Modifier.width(Shonar.Space2))
}
Column(Modifier.weight(1f)) {
Text(e.file.nameWithoutExtension,
style = MaterialTheme.typography.titleSmall,