Single-action recording rows, S.H.O.N.A.R. header branding, drop storage pill

This commit is contained in:
avi 2026-09-18 16:11:34 -05:00
commit 1f46d2a0e4
2 changed files with 50 additions and 66 deletions

View file

@ -26,7 +26,6 @@ import androidx.compose.material.icons.filled.Delete
import androidx.compose.material.icons.filled.Mic import androidx.compose.material.icons.filled.Mic
import androidx.compose.material.icons.filled.MoreVert import androidx.compose.material.icons.filled.MoreVert
import androidx.compose.material.icons.filled.Pause import androidx.compose.material.icons.filled.Pause
import androidx.compose.material.icons.filled.PhoneAndroid
import androidx.compose.material.icons.filled.PlayArrow import androidx.compose.material.icons.filled.PlayArrow
import androidx.compose.material.icons.filled.Settings import androidx.compose.material.icons.filled.Settings
import androidx.compose.material3.AlertDialog import androidx.compose.material3.AlertDialog
@ -190,7 +189,7 @@ fun HomeScreen(
ShonarSearchField( ShonarSearchField(
value = query, value = query,
onValueChange = { query = it }, onValueChange = { query = it },
placeholder = "Search saved recordings…", placeholder = "Search recordings…",
modifier = Modifier.fillMaxWidth(), modifier = Modifier.fillMaxWidth(),
) )
} }
@ -200,7 +199,6 @@ fun HomeScreen(
onSort = { sort = it }, onSort = { sort = it },
) )
} }
item { StorageChip() }
if (storageOk == false) { if (storageOk == false) {
item { item {
val ctx = context val ctx = context
@ -227,6 +225,9 @@ fun HomeScreen(
) )
} }
} }
// Section break between the controls above and the library:
// list spacing (10dp) + this spacer = a deliberate 20dp pause.
item { Spacer(Modifier.height(10.dp)) }
if (recordings.isNotEmpty()) { if (recordings.isNotEmpty()) {
item { item {
Row( Row(
@ -315,31 +316,32 @@ private fun HeaderBlock(recording: Boolean, onOpenSettings: () -> Unit) {
) { ) {
Box( Box(
modifier = Modifier modifier = Modifier
.size(44.dp) .size(76.dp)
.background( .background(
cs.primary.copy(alpha = 0.14f), androidx.compose.ui.graphics.Color(0xFF0B1220),
RoundedCornerShape(12.dp), RoundedCornerShape(16.dp),
) )
.border(1.dp, cs.primary.copy(alpha = 0.45f), RoundedCornerShape(12.dp)), .border(1.dp, cs.primary.copy(alpha = 0.45f), RoundedCornerShape(16.dp)),
contentAlignment = Alignment.Center, contentAlignment = Alignment.Center,
) { ) {
Icon( androidx.compose.foundation.Image(
Icons.Filled.Mic, painter = androidx.compose.ui.res.painterResource(
id = com.shonar.R.drawable.ic_launcher_foreground,
),
contentDescription = null, contentDescription = null,
tint = cs.primary, modifier = Modifier.size(58.dp),
modifier = Modifier.size(22.dp),
) )
} }
Spacer(Modifier.width(12.dp)) Spacer(Modifier.width(12.dp))
Column(Modifier.weight(1f)) { Column(Modifier.weight(1f)) {
Text( Text(
"Recordings", "S.H.O.N.A.R.",
style = MaterialTheme.typography.headlineSmall, style = MaterialTheme.typography.headlineSmall,
color = cs.onBackground, color = cs.onBackground,
) )
Text( Text(
if (recording) "Recording in progress — stays on this phone" if (recording) "Recording in progress — stays on this phone"
else "Your saved recordings stay on this phone", else "Self Hosted Oral Notes and Audio Recording",
style = MaterialTheme.typography.bodySmall, style = MaterialTheme.typography.bodySmall,
color = cs.onSurfaceVariant, color = cs.onSurfaceVariant,
) )
@ -522,59 +524,62 @@ private fun RecordingRow(
Modifier.fillMaxWidth().padding(12.dp), Modifier.fillMaxWidth().padding(12.dp),
verticalAlignment = Alignment.CenterVertically, verticalAlignment = Alignment.CenterVertically,
) { ) {
Box( // Single primary action: large play/pause. No mic tile here —
// the mic icon lives only on the Record button, where it
// unambiguously means "record".
IconButton(
onClick = onPlay,
modifier = Modifier modifier = Modifier
.size(44.dp) .size(52.dp)
.background( .background(
if (selected) cs.primary.copy(alpha = 0.16f) else cs.surfaceVariant, cs.primary.copy(alpha = if (playing) 0.22f else 0.14f),
RoundedCornerShape(10.dp), androidx.compose.foundation.shape.CircleShape,
) )
.border( .border(
1.dp, 1.dp,
if (selected) cs.primary.copy(alpha = 0.5f) else cs.outline.copy(alpha = 0.4f), cs.primary.copy(alpha = 0.55f),
RoundedCornerShape(10.dp), androidx.compose.foundation.shape.CircleShape,
), ),
contentAlignment = Alignment.Center,
) { ) {
Icon( Icon(
Icons.Filled.Mic, if (playing) Icons.Filled.Pause else Icons.Filled.PlayArrow,
contentDescription = null, contentDescription = if (playing) "Pause ${recording.title}" else "Play ${recording.title}",
tint = if (selected) cs.primary else cs.onSurfaceVariant, tint = cs.primary,
modifier = Modifier.size(22.dp), modifier = Modifier.size(24.dp),
) )
} }
Spacer(Modifier.width(12.dp)) Spacer(Modifier.width(12.dp))
Column(Modifier.weight(1f)) { Column(Modifier.weight(1f)) {
Text( Text(
recording.title, recording.title,
style = MaterialTheme.typography.titleSmall, style = MaterialTheme.typography.titleMedium,
color = cs.onSurface, color = cs.onSurface,
maxLines = 2, maxLines = 2,
) )
Spacer(Modifier.height(2.dp)) Spacer(Modifier.height(4.dp))
Text( Text(
"${formatBytes(recording.sizeBytes)} • ${formatDuration(recording.durationMs)} • ${formatDate(recording.createdAtEpochMs)}", "${formatBytes(recording.sizeBytes)} • ${formatDuration(recording.durationMs)} • ${formatDate(recording.createdAtEpochMs)}",
style = MaterialTheme.typography.bodySmall, style = MaterialTheme.typography.bodyMedium,
color = cs.onSurfaceVariant, color = cs.onSurfaceVariant,
maxLines = 1, maxLines = 2,
) )
} }
// Secondary action: visually quieter — muted tone inside a
// subtle outline so it reads as "more options", not primary.
IconButton( IconButton(
onClick = onPlay, onClick = { menu = true },
modifier = Modifier.size(48.dp), modifier = Modifier
.size(48.dp)
.border(
1.dp,
cs.outline.copy(alpha = 0.5f),
androidx.compose.foundation.shape.CircleShape,
),
) { ) {
Icon(
if (playing) Icons.Filled.Pause else Icons.Filled.PlayArrow,
contentDescription = if (playing) "Pause preview" else "Play preview",
tint = cs.primary,
modifier = Modifier.size(24.dp),
)
}
IconButton(onClick = { menu = true }, modifier = Modifier.size(48.dp)) {
Icon( Icon(
Icons.Filled.MoreVert, Icons.Filled.MoreVert,
contentDescription = "More actions for ${recording.title}", contentDescription = "More actions for ${recording.title}",
tint = cs.onSurfaceVariant, tint = LocalShonarExtras.current.muted,
) )
} }
androidx.compose.material3.DropdownMenu( androidx.compose.material3.DropdownMenu(
@ -683,32 +688,9 @@ private fun StorageAccessCard(onGrant: () -> Unit) {
} }
/** Fixed on-phone storage indicator — no selection, nothing leaves the phone. */ /** Fixed on-phone storage indicator — no selection, nothing leaves the phone. */
@Composable /* StorageChip removed per design review (2026-09-18): the "Stored: on this
private fun StorageChip() { phone" pill, text, icon, background, and reserved space are gone. The
val cs = MaterialTheme.colorScheme section spacer above keeps the layout rhythm intentional. */
Card(
shape = RoundedCornerShape(50),
colors = CardDefaults.cardColors(containerColor = cs.surfaceVariant),
border = androidx.compose.foundation.BorderStroke(1.dp, cs.outline.copy(alpha = 0.6f)),
) {
Row(
Modifier.padding(horizontal = 14.dp, vertical = 8.dp),
verticalAlignment = Alignment.CenterVertically,
) {
Icon(
Icons.Filled.PhoneAndroid, contentDescription = null,
modifier = Modifier.size(18.dp),
tint = cs.onSurfaceVariant,
)
Spacer(Modifier.size(8.dp))
Text(
"Stored: on this phone",
style = MaterialTheme.typography.labelLarge,
color = cs.onSurfaceVariant,
)
}
}
}
@Composable @Composable
private fun EmptyState(text: String) { private fun EmptyState(text: String) {

View file

@ -25,6 +25,8 @@ import androidx.compose.material3.TextButton
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.semantics.contentDescription
import androidx.compose.ui.semantics.semantics
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
/** /**
@ -63,7 +65,7 @@ fun ShonarSearchField(
focusedTextColor = MaterialTheme.colorScheme.onSurface, focusedTextColor = MaterialTheme.colorScheme.onSurface,
unfocusedTextColor = MaterialTheme.colorScheme.onSurface, unfocusedTextColor = MaterialTheme.colorScheme.onSurface,
), ),
modifier = modifier, modifier = modifier.semantics { contentDescription = placeholder },
) )
} }