Desktop: adopt the Android app's companion design (navy+teal sonar skin)
Theme.kt now copies the Android ui/theme dark scheme verbatim: deep navy #0B1220 + navy surfaces + teal ping #4FD1C5 — replaces warm-graphite gold. Headers carry the identity: SonarLogo (same geometry as the Android launcher's ic_launcher_foreground: center dot, teal upper-right arcs, teal-dark lower-left) + S.H.O.N.A.R. wordmark + 'Self-hosted notes and audio recording' tagline. Action buttons use the phone's icon vocabulary (Filled PlayArrow/Edit/Settings/Stop/Delete). Row 'Open' button removed — clicking the card already opens the recording. Verified on-screen: navy+teal render, wordmark + tagline, icons, Open gone. Ctrl+=/- zoom was already ported (4944fcd) and confirmed present.
This commit is contained in:
parent
0a713879b9
commit
3e0e3b50cb
2 changed files with 131 additions and 58 deletions
|
|
@ -10,15 +10,23 @@ import androidx.compose.foundation.layout.fillMaxWidth
|
|||
import androidx.compose.foundation.layout.height
|
||||
import androidx.compose.foundation.layout.heightIn
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.foundation.layout.width
|
||||
import androidx.compose.foundation.lazy.LazyColumn
|
||||
import androidx.compose.foundation.lazy.items
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.filled.Delete
|
||||
import androidx.compose.material.icons.filled.Edit
|
||||
import androidx.compose.material.icons.filled.PlayArrow
|
||||
import androidx.compose.material.icons.filled.Settings
|
||||
import androidx.compose.material.icons.filled.Stop
|
||||
import androidx.compose.material3.AlertDialog
|
||||
import androidx.compose.material3.Button
|
||||
import androidx.compose.material3.ButtonDefaults
|
||||
import androidx.compose.material3.Card
|
||||
import androidx.compose.material3.Checkbox
|
||||
import androidx.compose.material3.CircularProgressIndicator
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.LinearProgressIndicator
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.OutlinedButton
|
||||
|
|
@ -45,8 +53,9 @@ fun EngineScreen(state: DesktopState) {
|
|||
val error by state.engineError.collectAsState()
|
||||
|
||||
Column(Modifier.fillMaxSize(), verticalArrangement = Arrangement.spacedBy(12.dp)) {
|
||||
ShonarWordmark()
|
||||
Text("Starting the transcription engine…",
|
||||
style = MaterialTheme.typography.headlineSmall)
|
||||
style = MaterialTheme.typography.titleMedium)
|
||||
Text(
|
||||
"SHONAR transcribes on this computer with local models — nothing " +
|
||||
"is uploaded anywhere. The engine runs in the background while " +
|
||||
|
|
@ -102,9 +111,13 @@ fun LibraryScreen(state: DesktopState) {
|
|||
|
||||
Column(Modifier.fillMaxSize(), verticalArrangement = Arrangement.spacedBy(12.dp)) {
|
||||
Row(verticalAlignment = Alignment.CenterVertically) {
|
||||
Text("Recordings", style = MaterialTheme.typography.headlineSmall,
|
||||
modifier = Modifier.weight(1f))
|
||||
TextButton({ state.go(DesktopState.Screen.SETTINGS) }) { Text("Settings") }
|
||||
ShonarWordmark(modifier = Modifier.weight(1f))
|
||||
TextButton({ state.go(DesktopState.Screen.SETTINGS) }) {
|
||||
Icon(Icons.Filled.Settings, contentDescription = null,
|
||||
modifier = Modifier.size(18.dp))
|
||||
Spacer(Modifier.width(6.dp))
|
||||
Text("Settings")
|
||||
}
|
||||
}
|
||||
if (!connected) {
|
||||
Card(Modifier.fillMaxWidth()) {
|
||||
|
|
@ -249,18 +262,33 @@ fun LibraryScreen(state: DesktopState) {
|
|||
}
|
||||
}
|
||||
if (e.status == FileStatus.NEW || e.status == FileStatus.FAILED) {
|
||||
TextButton({ state.pumpFile(e.file) }) { Text("Transcribe") }
|
||||
TextButton({ state.pumpFile(e.file) }) {
|
||||
Icon(Icons.Filled.PlayArrow, contentDescription = null,
|
||||
modifier = Modifier.size(18.dp))
|
||||
Spacer(Modifier.width(4.dp))
|
||||
Text("Transcribe")
|
||||
}
|
||||
}
|
||||
if (e.status != FileStatus.RUNNING &&
|
||||
e.status != FileStatus.QUEUED) {
|
||||
TextButton({ renaming = e.file }) { Text("Rename") }
|
||||
TextButton({ renaming = e.file }) {
|
||||
Icon(Icons.Filled.Edit, contentDescription = null,
|
||||
modifier = Modifier.size(18.dp))
|
||||
Spacer(Modifier.width(4.dp))
|
||||
Text("Rename")
|
||||
}
|
||||
}
|
||||
TextButton({ state.openDetail(e.file) }) { Text("Open") }
|
||||
// No "Open" button: clicking the card opens the
|
||||
// recording (the button was redundant).
|
||||
if (e.status != FileStatus.RUNNING &&
|
||||
e.status != FileStatus.QUEUED) {
|
||||
TextButton(
|
||||
{ deleting = e.file },
|
||||
content = {
|
||||
Icon(Icons.Filled.Delete, contentDescription = null,
|
||||
modifier = Modifier.size(18.dp),
|
||||
tint = MaterialTheme.colorScheme.error)
|
||||
Spacer(Modifier.width(4.dp))
|
||||
Text("Delete",
|
||||
color = MaterialTheme.colorScheme.error)
|
||||
},
|
||||
|
|
@ -411,11 +439,21 @@ fun DetailScreen(state: DesktopState) {
|
|||
Row(verticalAlignment = Alignment.CenterVertically,
|
||||
horizontalArrangement = Arrangement.spacedBy(8.dp)) {
|
||||
if (isPlaying) {
|
||||
Button({ state.stopPlayback() }) { Text("Stop") }
|
||||
Button({ state.stopPlayback() }) {
|
||||
Icon(Icons.Filled.Stop, contentDescription = null,
|
||||
modifier = Modifier.size(18.dp))
|
||||
Spacer(Modifier.width(6.dp))
|
||||
Text("Stop")
|
||||
}
|
||||
Text("playing…", style = MaterialTheme.typography.bodySmall,
|
||||
color = MaterialTheme.colorScheme.primary)
|
||||
} else {
|
||||
OutlinedButton({ state.playFrom(detail.file) }) { Text("Play") }
|
||||
OutlinedButton({ state.playFrom(detail.file) }) {
|
||||
Icon(Icons.Filled.PlayArrow, contentDescription = null,
|
||||
modifier = Modifier.size(18.dp))
|
||||
Spacer(Modifier.width(6.dp))
|
||||
Text("Play")
|
||||
}
|
||||
}
|
||||
playError?.let {
|
||||
Text(it, color = MaterialTheme.colorScheme.error,
|
||||
|
|
|
|||
|
|
@ -1,69 +1,104 @@
|
|||
package com.shonar.desktop
|
||||
|
||||
import androidx.compose.foundation.Canvas
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.Spacer
|
||||
import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.foundation.layout.width
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.material3.darkColorScheme
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.geometry.Offset
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.graphics.StrokeCap
|
||||
import androidx.compose.ui.graphics.drawscope.Stroke
|
||||
import androidx.compose.ui.unit.Dp
|
||||
import androidx.compose.ui.unit.dp
|
||||
|
||||
/**
|
||||
* SHONAR Desktop theme — "warm graphite": near-black charcoal surfaces
|
||||
* with an olive-gold accent, inspired by the Skills Hub hero design.
|
||||
* Desktop-only for now (Android keeps its own ui/theme/Theme.kt).
|
||||
* SHONAR Desktop theme — the Android companion's "deep-sea sonar" palette,
|
||||
* copied exactly from android ui/theme/Theme.kt dark scheme: dark navy +
|
||||
* teal ping. Same seven role overrides the phone app sets; everything else
|
||||
* falls through to Material3 defaults, same as Android.
|
||||
*
|
||||
* Status semantics in the library list map to scheme roles:
|
||||
* done -> primary (gold), failed -> error (red), queued/running ->
|
||||
* done -> primary (teal), failed -> error (red), queued/running ->
|
||||
* onSurfaceVariant with a primary progress bar.
|
||||
*/
|
||||
private val ShonarGold = Color(0xFFE0B23A) // text-safe gold (brighter for AA on cards)
|
||||
private val ShonarGoldBright = Color(0xFFF0C419)
|
||||
private val GoldInk = Color(0xFF1F1800) // text on gold fills (AA)
|
||||
private val GraphiteBg = Color(0xFF1A1A1A)
|
||||
private val GraphiteSurface = Color(0xFF222222)
|
||||
private val GraphiteHigh = Color(0xFF2B2B2B)
|
||||
private val InkPrimary = Color(0xFFF4F4F4)
|
||||
private val InkSecondary = Color(0xFFB3B3B3)
|
||||
private val InkMuted = Color(0xFF8A8A8A)
|
||||
private val OutlineWarm = Color(0xFF3A3A3A)
|
||||
private val DangerWarm = Color(0xFFE5544F)
|
||||
private val Teal = Color(0xFF4FD1C5)
|
||||
private val TealDark = Color(0xFF2A9D94)
|
||||
private val DeepNavy = Color(0xFF0B1220)
|
||||
private val NavySurface = Color(0xFF121C2E)
|
||||
private val Ink = Color(0xFF10141A)
|
||||
|
||||
private val ShonarDarkScheme = darkColorScheme(
|
||||
primary = ShonarGold,
|
||||
onPrimary = GoldInk,
|
||||
primaryContainer = Color(0xFF3A3216),
|
||||
onPrimaryContainer = ShonarGoldBright,
|
||||
|
||||
secondary = Color(0xFFB99B5E),
|
||||
onSecondary = GoldInk,
|
||||
secondaryContainer = Color(0xFF332C1A),
|
||||
onSecondaryContainer = Color(0xFFE8D9AE),
|
||||
|
||||
tertiary = Color(0xFF8FA6B2), // cool counterpoint (info-ish)
|
||||
onTertiary = Color(0xFF0E1A20),
|
||||
tertiaryContainer = Color(0xFF2A3B45),
|
||||
onTertiaryContainer = Color(0xFFCDE2EC),
|
||||
|
||||
background = GraphiteBg,
|
||||
onBackground = InkPrimary,
|
||||
|
||||
surface = GraphiteSurface,
|
||||
onSurface = InkPrimary,
|
||||
surfaceVariant = Color(0xFF2E2A21),
|
||||
onSurfaceVariant = InkSecondary,
|
||||
surfaceContainer = GraphiteHigh, // cards lift off the bg
|
||||
surfaceContainerHigh = Color(0xFF333333),
|
||||
surfaceContainerHighest = Color(0xFF3A3A3A),
|
||||
|
||||
outline = OutlineWarm,
|
||||
outlineVariant = Color(0xFF2E2E2E),
|
||||
|
||||
error = DangerWarm,
|
||||
onError = Color(0xFF2B0A08),
|
||||
errorContainer = Color(0xFF47201E),
|
||||
onErrorContainer = Color(0xFFFFB4AF),
|
||||
primary = Teal,
|
||||
onPrimary = Ink,
|
||||
secondary = TealDark,
|
||||
background = DeepNavy,
|
||||
surface = NavySurface,
|
||||
onBackground = Color(0xFFE6EDF3),
|
||||
onSurface = Color(0xFFE6EDF3),
|
||||
)
|
||||
|
||||
/** Wrap the app content in the SHONAR dark-gold theme. */
|
||||
/** Wrap the app content in the SHONAR dark sonar theme. */
|
||||
@Composable
|
||||
fun ShonarTheme(content: @Composable () -> Unit) {
|
||||
MaterialTheme(colorScheme = ShonarDarkScheme, content = content)
|
||||
}
|
||||
|
||||
/**
|
||||
* The SHONAR sonar-ping logo — same geometry as the Android launcher's
|
||||
* ic_launcher_foreground.xml: a center dot with quarter arcs ringing out
|
||||
* (teal upper-right, teal-dark lower-left), 108-unit viewport.
|
||||
*/
|
||||
@Composable
|
||||
fun SonarLogo(size: Dp = 32.dp, modifier: Modifier = Modifier) {
|
||||
Canvas(modifier = modifier.size(size)) {
|
||||
val s = this.size.minDimension / 108f
|
||||
fun c(x: Float, y: Float) = Offset(x * s, y * s)
|
||||
// Center dot: r=6 at (54,54)
|
||||
drawCircle(Teal, radius = 6f * s, center = c(54f, 54f))
|
||||
// Upper-right quarter arcs (teal), centered on the dot.
|
||||
val arcStyle = Stroke(width = 4f * s, cap = StrokeCap.Round)
|
||||
fun arc(color: Color, startDeg: Float, boxTopLeft: Float) {
|
||||
val box = when (boxTopLeft) { 38f -> 32f; else -> 56f }
|
||||
drawArc(
|
||||
color, startAngle = startDeg, sweepAngle = 90f, useCenter = false,
|
||||
topLeft = c(boxTopLeft, boxTopLeft),
|
||||
size = androidx.compose.ui.geometry.Size(box * s, box * s),
|
||||
style = arcStyle,
|
||||
)
|
||||
}
|
||||
arc(Teal, -90f, 38f)
|
||||
arc(Teal, -90f, 26f)
|
||||
// Lower-left quarter arcs (teal-dark).
|
||||
arc(TealDark, 90f, 38f)
|
||||
arc(TealDark, 90f, 26f)
|
||||
}
|
||||
}
|
||||
|
||||
/** Wordmark row shared by screen headers: ping logo + S.H.O.N.A.R. + tagline. */
|
||||
@Composable
|
||||
fun ShonarWordmark(
|
||||
modifier: Modifier = Modifier,
|
||||
logoSize: Dp = 28.dp,
|
||||
style: androidx.compose.ui.text.TextStyle = MaterialTheme.typography.headlineSmall,
|
||||
) {
|
||||
Row(modifier, verticalAlignment = Alignment.CenterVertically) {
|
||||
SonarLogo(logoSize)
|
||||
Spacer(Modifier.width(10.dp))
|
||||
Column {
|
||||
Text("S.H.O.N.A.R.", style = style)
|
||||
Text(
|
||||
"Self-hosted notes and audio recording",
|
||||
style = MaterialTheme.typography.bodySmall,
|
||||
color = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue