vc=26: look + feel pass — sulkta.com palette + Material Icons

Color palette pulled directly from sulkta.com's stylesheet — the same
greens used on the website now drive the app theme:
  #166534  deep green   (light-theme primary, top app bar background)
  #4ade80  bright lime  (dark-theme primary, accents in dark mode)
  #86efac  light green  (primaryContainer in light theme)
  #e8f5e8  pale green   (secondary container tint)
  #d97706  amber accent (tertiary)
  #374137  olive gray   (secondary on light, container on dark)

Replaces the made-up forest palette from vc=23 with the real Sulkta
brand. Same M3 tonal-role mapping so derived surfaces stay consistent.

TopAppBar redone NewPipe-style: solid deep-green bar with white
"straw" title, white hamburger + search icons. Clear bold header
instead of the previous white-with-a-pill-underneath layout.

Material Icons swapped in everywhere we had emoji:
  drawer        Person / History / PlaylistPlay / Download / Settings
  minibar       PlayArrow / Pause / Close
  fullscreen    Speed / Headphones / Videocam / Share /
                PictureInPictureAlt / KeyboardArrowDown
Pulled in material-icons-extended (4 MB APK growth, all icons).
Consistent renders across vendors; no more emoji font fallback drift.

FeedRow gets a NewPipe-style duration pill burned into the bottom-right
of every thumbnail (mm:ss / h:mm:ss). Live streams / mixes with no
duration leave it off.

Audit deferred-MED items addressed:
  MED-6: dropped the PlayerService STATE_ENDED auto-stop. Service
  shutdown is now driven only by onTaskRemoved + the minibar's ×.
  Removes the implicit "we'll never queue" assumption and is correct
  for a future autoplay/queue feature.
  LOW-7: DownloadsScreen adaptive poll — 1s while a download is
  active, 5s when idle. No more wasted DB queries when nothing
  is running.
This commit is contained in:
Kayos 2026-05-25 17:46:23 +00:00
parent 21fc81ee77
commit 885398e3bd
8 changed files with 207 additions and 124 deletions

View file

@ -55,6 +55,6 @@ const val NEWPIPE_APPLICATION_ID_NEW = "net.newpipe.app"
// vc=19 / 0.1.0-AE — rust pipeline cutover. Extraction via
// strawcore-core (Sulkta-Coop/strawcore) via the UniFFI wrapper; no
// NewPipeExtractor in the runtime path.
const val STRAW_VERSION_CODE = 25
const val STRAW_VERSION_NAME = "0.1.0-AK"
const val STRAW_VERSION_CODE = 26
const val STRAW_VERSION_NAME = "0.1.0-AL"
const val STRAW_APPLICATION_ID = "com.sulkta.straw"

View file

@ -81,7 +81,7 @@ dependencies {
implementation(libs.jetbrains.compose.foundation)
implementation(libs.jetbrains.compose.material3)
implementation(libs.jetbrains.compose.ui)
implementation("androidx.compose.material:material-icons-core:1.7.5")
implementation("androidx.compose.material:material-icons-extended:1.7.5")
// Lifecycle + ViewModel for Compose
implementation("androidx.lifecycle:lifecycle-viewmodel-compose:2.10.0")

View file

@ -8,8 +8,10 @@
package com.sulkta.straw
import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
@ -25,7 +27,13 @@ import androidx.compose.foundation.lazy.items
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Download
import androidx.compose.material.icons.filled.History
import androidx.compose.material.icons.filled.Menu
import androidx.compose.material.icons.filled.Person
import androidx.compose.material.icons.filled.PlaylistPlay
import androidx.compose.material.icons.filled.Search
import androidx.compose.material.icons.filled.Settings
import androidx.compose.material3.CircularProgressIndicator
import androidx.compose.material3.DrawerValue
import androidx.compose.material3.ExperimentalMaterial3Api
@ -36,14 +44,12 @@ import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.ModalDrawerSheet
import androidx.compose.material3.ModalNavigationDrawer
import androidx.compose.material3.NavigationDrawerItem
import androidx.compose.foundation.background
import androidx.compose.foundation.border
import androidx.compose.foundation.layout.Box
import androidx.compose.material3.Scaffold
import androidx.compose.material3.Surface
import androidx.compose.material3.Text
import androidx.compose.material3.TextButton
import androidx.compose.material3.TopAppBar
import androidx.compose.material3.TopAppBarDefaults
import androidx.compose.material3.rememberDrawerState
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
@ -68,6 +74,7 @@ import com.sulkta.straw.data.Subscriptions
import com.sulkta.straw.data.WatchHistoryItem
import com.sulkta.straw.feature.feed.SubscriptionFeedViewModel
import com.sulkta.straw.feature.search.StreamItem
import com.sulkta.straw.OverlayDimColor
import com.sulkta.straw.util.formatViews
import kotlinx.coroutines.launch
@ -109,7 +116,7 @@ fun StrawHome(
NavigationDrawerItem(
label = { Text("Subscriptions") },
icon = { Text("👤") },
icon = { Icon(Icons.Filled.Person, contentDescription = null) },
selected = view == HomeView.Subs,
onClick = {
view = HomeView.Subs
@ -119,7 +126,7 @@ fun StrawHome(
)
NavigationDrawerItem(
label = { Text("History") },
icon = { Text("📺") },
icon = { Icon(Icons.Filled.History, contentDescription = null) },
selected = view == HomeView.History,
onClick = {
view = HomeView.History
@ -129,7 +136,7 @@ fun StrawHome(
)
NavigationDrawerItem(
label = { Text("Playlists") },
icon = { Text("📃") },
icon = { Icon(Icons.Filled.PlaylistPlay, contentDescription = null) },
selected = false,
onClick = {
scope.launch { drawerState.close() }
@ -139,7 +146,7 @@ fun StrawHome(
)
NavigationDrawerItem(
label = { Text("Downloads") },
icon = { Text("") },
icon = { Icon(Icons.Filled.Download, contentDescription = null) },
selected = false,
onClick = {
scope.launch { drawerState.close() }
@ -150,7 +157,7 @@ fun StrawHome(
HorizontalDivider(modifier = Modifier.padding(vertical = 12.dp))
NavigationDrawerItem(
label = { Text("Settings") },
icon = { Text("") },
icon = { Icon(Icons.Filled.Settings, contentDescription = null) },
selected = false,
onClick = {
scope.launch { drawerState.close() }
@ -163,42 +170,33 @@ fun StrawHome(
) {
Scaffold(
topBar = {
// Green-tinted bar inspired by NewPipe/Tubular's colored
// header, but using our forest-green primary container so
// it sits cleanly with the rest of the Material 3 surfaces.
TopAppBar(
title = {
// Search-pill in the title slot — tap takes you to the
// full search screen with the field auto-focused. Same
// idea as YT's mobile top bar.
Surface(
modifier = Modifier
.fillMaxWidth()
.padding(end = 8.dp)
.height(40.dp)
.clip(RoundedCornerShape(20.dp))
.clickable(onClick = onOpenSearch),
color = MaterialTheme.colorScheme.surfaceVariant,
) {
Row(
verticalAlignment = Alignment.CenterVertically,
modifier = Modifier.padding(horizontal = 14.dp),
) {
Text(
"🔍",
style = MaterialTheme.typography.bodyMedium,
)
Spacer(modifier = Modifier.width(10.dp))
Text(
"Search YouTube",
style = MaterialTheme.typography.bodyMedium,
color = MaterialTheme.colorScheme.onSurfaceVariant,
)
}
}
Text(
"straw",
style = MaterialTheme.typography.titleLarge,
fontWeight = FontWeight.SemiBold,
)
},
navigationIcon = {
IconButton(onClick = { scope.launch { drawerState.open() } }) {
Icon(Icons.Filled.Menu, contentDescription = "Menu")
}
},
actions = {
IconButton(onClick = onOpenSearch) {
Icon(Icons.Filled.Search, contentDescription = "Search")
}
},
colors = TopAppBarDefaults.topAppBarColors(
containerColor = MaterialTheme.colorScheme.primary,
titleContentColor = MaterialTheme.colorScheme.onPrimary,
navigationIconContentColor = MaterialTheme.colorScheme.onPrimary,
actionIconContentColor = MaterialTheme.colorScheme.onPrimary,
),
)
},
) { padding ->
@ -352,13 +350,12 @@ private fun FeedRow(item: StreamItem, onClick: () -> Unit) {
.padding(vertical = 8.dp),
verticalAlignment = Alignment.Top,
) {
AsyncImage(
model = item.thumbnail,
contentDescription = null,
ThumbnailWithDuration(
thumbnail = item.thumbnail,
durationSeconds = item.durationSeconds,
modifier = Modifier
.width(140.dp)
.height(80.dp)
.clip(RoundedCornerShape(6.dp)),
.height(80.dp),
)
Spacer(modifier = Modifier.width(10.dp))
Column(modifier = Modifier.weight(1f)) {
@ -387,6 +384,48 @@ private fun FeedRow(item: StreamItem, onClick: () -> Unit) {
}
}
/**
* 16:9 thumbnail with a NewPipe-style duration pill burned into the
* bottom-right corner. `durationSeconds == 0` skips the badge (live
* streams, mixes that come back without a duration, etc.).
*/
@Composable
private fun ThumbnailWithDuration(
thumbnail: String?,
durationSeconds: Long,
modifier: Modifier = Modifier,
) {
Box(modifier = modifier) {
AsyncImage(
model = thumbnail,
contentDescription = null,
modifier = Modifier
.fillMaxSize()
.clip(RoundedCornerShape(6.dp)),
)
if (durationSeconds > 0) {
Text(
text = formatDurationShort(durationSeconds),
style = MaterialTheme.typography.labelSmall,
color = androidx.compose.ui.graphics.Color.White,
modifier = Modifier
.align(Alignment.BottomEnd)
.padding(4.dp)
.clip(RoundedCornerShape(3.dp))
.background(OverlayDimColor)
.padding(horizontal = 4.dp, vertical = 1.dp),
)
}
}
}
private fun formatDurationShort(totalSec: Long): String {
val h = totalSec / 3600
val m = (totalSec % 3600) / 60
val s = totalSec % 60
return if (h > 0) "%d:%02d:%02d".format(h, m, s) else "%d:%02d".format(m, s)
}
@Composable
private fun SubChip(
ch: ChannelRef,

View file

@ -2,10 +2,19 @@
* SPDX-FileCopyrightText: 2026 Sulkta-Coop
* SPDX-License-Identifier: GPL-3.0-or-later
*
* Sulkta green palette for Straw. Replaces the M3 default lavender/red
* tints with a clean forest green primary modern, clean, distinct from
* NewPipe / Tubular's red. Same Tonal Palette structure Material 3 uses
* internally so all the derived surfaces stay in harmony.
* Straw palette pulled directly from sulkta.com's stylesheet:
* #4ade80 primary green (Tailwind green-400, most-used on the site)
* #166534 deep green (green-800, headings + emphasis)
* #22c55e mid green (green-500, links + buttons)
* #86efac light green container (green-300)
* #e8f5e8 pale green tint
* #d97706 amber accent (sulkta.com calls this out for chips)
* #374137 olive-gray secondary
* #0a0a0a near-black text on light
* #111411 near-black with green tint for dark surface
*
* Mapped into Material 3's primary / secondary / tertiary tonal roles
* so all the derived M3 surfaces (containers, outlines, etc.) follow.
*/
package com.sulkta.straw
@ -15,56 +24,61 @@ import androidx.compose.material3.darkColorScheme
import androidx.compose.material3.lightColorScheme
import androidx.compose.ui.graphics.Color
private val GreenPrimary = Color(0xFF386A1F)
private val GreenOnPrimary = Color(0xFFFFFFFF)
private val GreenPrimaryContainer = Color(0xFFB6F397)
private val GreenOnPrimaryContainer = Color(0xFF0B2000)
private val GreenSecondary = Color(0xFF55624C)
private val GreenOnSecondary = Color(0xFFFFFFFF)
private val GreenSecondaryContainer = Color(0xFFD8E7CB)
private val GreenOnSecondaryContainer = Color(0xFF131F0D)
private val GreenTertiary = Color(0xFF386666)
private val GreenOnTertiary = Color(0xFFFFFFFF)
// Light theme — primary is sulkta.com's deep green (#166534), strong
// enough for white text and matches the site's heading emphasis.
private val LPrimary = Color(0xFF166534)
private val LOnPrimary = Color(0xFFFFFFFF)
private val LPrimaryContainer = Color(0xFF86EFAC)
private val LOnPrimaryContainer = Color(0xFF0A0A0A)
private val LSecondary = Color(0xFF374137)
private val LOnSecondary = Color(0xFFFFFFFF)
private val LSecondaryContainer = Color(0xFFE8F5E8)
private val LOnSecondaryContainer = Color(0xFF0A0A0A)
private val LTertiary = Color(0xFFD97706)
private val LOnTertiary = Color(0xFFFFFFFF)
private val DarkGreenPrimary = Color(0xFF9BD67D)
private val DarkGreenOnPrimary = Color(0xFF153800)
private val DarkGreenPrimaryContainer = Color(0xFF205107)
private val DarkGreenOnPrimaryContainer = Color(0xFFB6F397)
private val DarkGreenSecondary = Color(0xFFBDCBB0)
private val DarkGreenOnSecondary = Color(0xFF283420)
private val DarkGreenSecondaryContainer = Color(0xFF3E4A35)
private val DarkGreenOnSecondaryContainer = Color(0xFFD8E7CB)
private val DarkGreenTertiary = Color(0xFFA0CFD0)
private val DarkGreenOnTertiary = Color(0xFF003738)
// Dark theme — primary is sulkta.com's bright lime (#4ade80) since dark
// backgrounds need a brighter accent for readability. PrimaryContainer
// is the deep green so emphasis stays consistent across themes.
private val DPrimary = Color(0xFF4ADE80)
private val DOnPrimary = Color(0xFF0A0A0A)
private val DPrimaryContainer = Color(0xFF166534)
private val DOnPrimaryContainer = Color(0xFF86EFAC)
private val DSecondary = Color(0xFF9AB89A)
private val DOnSecondary = Color(0xFF111411)
private val DSecondaryContainer = Color(0xFF374137)
private val DOnSecondaryContainer = Color(0xFFE8F5E8)
private val DTertiary = Color(0xFFD97706)
private val DOnTertiary = Color(0xFF0A0A0A)
fun strawLightColors(): ColorScheme = lightColorScheme(
primary = GreenPrimary,
onPrimary = GreenOnPrimary,
primaryContainer = GreenPrimaryContainer,
onPrimaryContainer = GreenOnPrimaryContainer,
secondary = GreenSecondary,
onSecondary = GreenOnSecondary,
secondaryContainer = GreenSecondaryContainer,
onSecondaryContainer = GreenOnSecondaryContainer,
tertiary = GreenTertiary,
onTertiary = GreenOnTertiary,
primary = LPrimary,
onPrimary = LOnPrimary,
primaryContainer = LPrimaryContainer,
onPrimaryContainer = LOnPrimaryContainer,
secondary = LSecondary,
onSecondary = LOnSecondary,
secondaryContainer = LSecondaryContainer,
onSecondaryContainer = LOnSecondaryContainer,
tertiary = LTertiary,
onTertiary = LOnTertiary,
)
fun strawDarkColors(): ColorScheme = darkColorScheme(
primary = DarkGreenPrimary,
onPrimary = DarkGreenOnPrimary,
primaryContainer = DarkGreenPrimaryContainer,
onPrimaryContainer = DarkGreenOnPrimaryContainer,
secondary = DarkGreenSecondary,
onSecondary = DarkGreenOnSecondary,
secondaryContainer = DarkGreenSecondaryContainer,
onSecondaryContainer = DarkGreenOnSecondaryContainer,
tertiary = DarkGreenTertiary,
onTertiary = DarkGreenOnTertiary,
primary = DPrimary,
onPrimary = DOnPrimary,
primaryContainer = DPrimaryContainer,
onPrimaryContainer = DOnPrimaryContainer,
secondary = DSecondary,
onSecondary = DOnSecondary,
secondaryContainer = DSecondaryContainer,
onSecondaryContainer = DOnSecondaryContainer,
tertiary = DTertiary,
onTertiary = DOnTertiary,
)
// Semi-transparent overlays for chrome (overlay buttons, the SB badge,
// the inline-player fullscreen pill) and for the dimmed area behind the
// minibar thumbnail. Kept here so a theme tweak touches one place.
val OverlayChromeColor = androidx.compose.ui.graphics.Color(0xCC222222)
val OverlayDimColor = androidx.compose.ui.graphics.Color(0xCC000000)
val OverlayChromeColor = Color(0xCC222222)
val OverlayDimColor = Color(0xCC000000)

View file

@ -86,14 +86,19 @@ fun DownloadsScreen() {
val context = LocalContext.current
var rows by remember { mutableStateOf<List<DownloadRow>>(emptyList()) }
// Poll DownloadManager every second while the screen is visible.
// DownloadManager doesn't broadcast progress, so polling is the
// standard pattern. Cheap query — single cursor across the app's own
// download queue.
// DownloadManager doesn't broadcast progress, so we poll while the
// screen is visible. Fast cadence (1s) when something is actively
// running, slow cadence (5s) when everything is settled — no
// animations to update.
LaunchedEffect(Unit) {
while (true) {
rows = queryDownloads(context)
delay(1000)
val fresh = queryDownloads(context)
rows = fresh
val active = fresh.any {
it.status == DownloadManager.STATUS_RUNNING ||
it.status == DownloadManager.STATUS_PENDING
}
delay(if (active) 1000 else 5000)
}
}

View file

@ -27,7 +27,12 @@ import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Close
import androidx.compose.material.icons.filled.Pause
import androidx.compose.material.icons.filled.PlayArrow
import androidx.compose.material3.HorizontalDivider
import androidx.compose.material3.Icon
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Surface
import androidx.compose.material3.Text
@ -113,10 +118,13 @@ fun MinibarOverlay(
)
}
Row(horizontalArrangement = Arrangement.spacedBy(4.dp)) {
MinibarIconButton(label = if (isPlaying) "" else "") {
MinibarIconButton(
icon = if (isPlaying) Icons.Filled.Pause else Icons.Filled.PlayArrow,
desc = if (isPlaying) "Pause" else "Play",
) {
if (controller.isPlaying) controller.pause() else controller.play()
}
MinibarIconButton(label = "×") {
MinibarIconButton(icon = Icons.Filled.Close, desc = "Stop") {
controller.stop()
controller.clearMediaItems()
NowPlaying.clear()
@ -128,7 +136,11 @@ fun MinibarOverlay(
}
@Composable
private fun MinibarIconButton(label: String, onClick: () -> Unit) {
private fun MinibarIconButton(
icon: androidx.compose.ui.graphics.vector.ImageVector,
desc: String,
onClick: () -> Unit,
) {
Box(
modifier = Modifier
.size(44.dp)
@ -136,6 +148,6 @@ private fun MinibarIconButton(label: String, onClick: () -> Unit) {
.clickable(onClick = onClick),
contentAlignment = Alignment.Center,
) {
Text(label, style = MaterialTheme.typography.titleMedium)
Icon(imageVector = icon, contentDescription = desc, modifier = Modifier.size(22.dp))
}
}

View file

@ -79,14 +79,11 @@ class PlaybackService : MediaSessionService() {
)
.build()
// Stop ourselves once playback genuinely ends so the foreground slot
// is released. STATE_IDLE + STATE_ENDED both qualify; STATE_BUFFERING
// / STATE_READY mean we're still doing work even if paused.
player.addListener(object : Player.Listener {
override fun onPlaybackStateChanged(state: Int) {
if (state == Player.STATE_ENDED) stopSelfWhenIdle()
}
})
// Service shutdown is driven by onTaskRemoved (user swiped app away)
// + the user pressing × on the minibar (which clears the queue).
// Don't auto-stop on STATE_ENDED — a future autoplay/queue feature
// expects the service to stay alive between items in the queue.
// Foreground notification fades on its own when nothing is playing.
val sessionActivityIntent = PendingIntent.getActivity(
this,
@ -132,13 +129,6 @@ class PlaybackService : MediaSessionService() {
super.onDestroy()
}
private fun stopSelfWhenIdle() {
val p = mediaSession?.player ?: return
if (p.mediaItemCount == 0 || p.playbackState == Player.STATE_IDLE) {
stopSelf()
}
}
companion object {
const val MEDIA_SESSION_ID = "straw"

View file

@ -33,11 +33,20 @@ import androidx.compose.foundation.layout.offset
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.KeyboardArrowDown
import androidx.compose.material.icons.filled.Headphones
import androidx.compose.material.icons.filled.PictureInPictureAlt
import androidx.compose.material.icons.filled.Share
import androidx.compose.material.icons.filled.Speed
import androidx.compose.material.icons.filled.Videocam
import androidx.compose.material3.AlertDialog
import androidx.compose.material3.CircularProgressIndicator
import androidx.compose.material3.Icon
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.material3.TextButton
import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.runtime.Composable
import androidx.compose.runtime.DisposableEffect
import androidx.compose.runtime.LaunchedEffect
@ -209,10 +218,13 @@ fun PlayerScreen(
modifier = Modifier.align(Alignment.TopEnd).padding(12.dp),
horizontalArrangement = Arrangement.spacedBy(8.dp),
) {
OverlayButton(label = if (playbackSpeed == 1f) "1×" else "${playbackSpeed}×") {
OverlayIconButton(icon = Icons.Filled.Speed, desc = "Playback speed") {
showSpeedDialog = true
}
OverlayButton(label = if (audioOnly) "📻" else "📺") {
OverlayIconButton(
icon = if (audioOnly) Icons.Filled.Headphones else Icons.Filled.Videocam,
desc = if (audioOnly) "Audio-only on" else "Video on",
) {
audioOnly = !audioOnly
controller.trackSelectionParameters = TrackSelectionParameters.Builder(context)
.setTrackTypeDisabled(C.TRACK_TYPE_VIDEO, audioOnly)
@ -223,7 +235,7 @@ fun PlayerScreen(
Toast.LENGTH_SHORT,
).show()
}
OverlayButton(label = "") {
OverlayIconButton(icon = Icons.Filled.Share, desc = "Share") {
val send = Intent(Intent.ACTION_SEND).apply {
type = "text/plain"
putExtra(Intent.EXTRA_TEXT, streamUrl)
@ -231,14 +243,14 @@ fun PlayerScreen(
}
context.startActivity(Intent.createChooser(send, "Share video"))
}
OverlayButton(label = "") {
OverlayIconButton(icon = Icons.Filled.PictureInPictureAlt, desc = "Picture in picture") {
if (activity == null) {
Toast.makeText(context, "PiP: no activity", Toast.LENGTH_SHORT).show()
return@OverlayButton
return@OverlayIconButton
}
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) {
Toast.makeText(context, "PiP needs Android 8+", Toast.LENGTH_SHORT).show()
return@OverlayButton
return@OverlayIconButton
}
val params = PictureInPictureParams.Builder()
.setAspectRatio(Rational(16, 9))
@ -251,7 +263,9 @@ fun PlayerScreen(
Toast.makeText(context, "PiP failed: ${t.message}", Toast.LENGTH_LONG).show()
}
}
OverlayButton(label = "") { onMinimize() }
OverlayIconButton(icon = Icons.Filled.KeyboardArrowDown, desc = "Minimize") {
onMinimize()
}
}
if (showSpeedDialog) {
@ -271,7 +285,11 @@ fun PlayerScreen(
}
@Composable
private fun OverlayButton(label: String, onClick: () -> Unit) {
private fun OverlayIconButton(
icon: ImageVector,
desc: String,
onClick: () -> Unit,
) {
Box(
modifier = Modifier
.size(36.dp)
@ -280,7 +298,12 @@ private fun OverlayButton(label: String, onClick: () -> Unit) {
.clickable(onClick = onClick),
contentAlignment = Alignment.Center,
) {
Text(label, color = Color.White, style = MaterialTheme.typography.titleSmall)
Icon(
imageVector = icon,
contentDescription = desc,
tint = Color.White,
modifier = Modifier.size(20.dp),
)
}
}