custom seek bar, don't play on seek, trim CJK line height, fix icon aspect

This commit is contained in:
austinried
2026-10-04 00:29:54 +00:00
parent 5e114f6edc
commit c145fe51f0
3 changed files with 78 additions and 46 deletions
@@ -376,10 +376,7 @@ class PlaybackController(
fun previous() = skip(-1) fun previous() = skip(-1)
fun seekTo(positionMs: Long) { fun seekTo(positionMs: Long) {
val player = player ?: return player?.seekTo(positionMs)
player.ensurePrepared()
player.seekTo(positionMs)
player.play()
} }
fun coverArt( fun coverArt(
@@ -3,6 +3,8 @@ package com.subtracks.ui.playback
import android.content.Context import android.content.Context
import androidx.compose.foundation.background import androidx.compose.foundation.background
import androidx.compose.foundation.basicMarquee import androidx.compose.foundation.basicMarquee
import androidx.compose.foundation.gestures.detectHorizontalDragGestures
import androidx.compose.foundation.gestures.detectTapGestures
import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Column
@@ -30,8 +32,6 @@ import androidx.compose.material3.IconButton
import androidx.compose.material3.LocalContentColor import androidx.compose.material3.LocalContentColor
import androidx.compose.material3.MaterialTheme import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Scaffold import androidx.compose.material3.Scaffold
import androidx.compose.material3.Slider
import androidx.compose.material3.SliderDefaults
import androidx.compose.material3.Text import androidx.compose.material3.Text
import androidx.compose.material3.TopAppBar import androidx.compose.material3.TopAppBar
import androidx.compose.material3.TopAppBarDefaults import androidx.compose.material3.TopAppBarDefaults
@@ -40,6 +40,7 @@ import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableFloatStateOf import androidx.compose.runtime.mutableFloatStateOf
import androidx.compose.runtime.mutableIntStateOf
import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue import androidx.compose.runtime.setValue
@@ -47,8 +48,11 @@ import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip import androidx.compose.ui.draw.clip
import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.Color
import androidx.compose.ui.input.pointer.pointerInput
import androidx.compose.ui.layout.onSizeChanged
import androidx.compose.ui.platform.LocalDensity import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.text.PlatformTextStyle import androidx.compose.ui.text.PlatformTextStyle
import androidx.compose.ui.text.style.LineHeightStyle
import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.lifecycle.compose.collectAsStateWithLifecycle import androidx.lifecycle.compose.collectAsStateWithLifecycle
@@ -131,10 +135,12 @@ fun NowPlayingScreen(
val titleStyle = val titleStyle =
MaterialTheme.typography.headlineSmall.copy( MaterialTheme.typography.headlineSmall.copy(
platformStyle = PlatformTextStyle(includeFontPadding = false), platformStyle = PlatformTextStyle(includeFontPadding = false),
lineHeightStyle = LineHeightStyle(LineHeightStyle.Alignment.Center, LineHeightStyle.Trim.Both),
) )
val subtitleStyle = val subtitleStyle =
MaterialTheme.typography.bodyMedium.copy( MaterialTheme.typography.bodyMedium.copy(
platformStyle = PlatformTextStyle(includeFontPadding = false), platformStyle = PlatformTextStyle(includeFontPadding = false),
lineHeightStyle = LineHeightStyle(LineHeightStyle.Alignment.Center, LineHeightStyle.Trim.Both),
) )
CompositionLocalProvider(LocalContentColor provides MaterialTheme.colorScheme.onBackground) { CompositionLocalProvider(LocalContentColor provides MaterialTheme.colorScheme.onBackground) {
@@ -218,26 +224,9 @@ fun NowPlayingScreen(
modifier = Modifier.padding(top = 6.dp).basicMarquee(), modifier = Modifier.padding(top = 6.dp).basicMarquee(),
) )
} }
var dragging by remember { mutableStateOf(false) } SeekBar(
var dragPosition by remember { mutableFloatStateOf(0f) } progress = if (state.durationMs > 0) (state.positionMs.toFloat() / state.durationMs).coerceIn(0f, 1f) else 0f,
Slider( onSeek = { fraction -> onSeek((fraction * state.durationMs).toLong()) },
value = if (dragging) dragPosition else state.positionMs.toFloat(),
onValueChange = {
dragging = true
dragPosition = it
},
onValueChangeFinished = {
onSeek(dragPosition.toLong())
dragging = false
},
valueRange = 0f..state.durationMs.toFloat().coerceAtLeast(1f),
track = { sliderState ->
SliderDefaults.Track(
sliderState = sliderState,
thumbTrackGapSize = 0.dp,
drawStopIndicator = {},
)
},
modifier = Modifier.padding(top = 28.dp), modifier = Modifier.padding(top = 28.dp),
) )
Row( Row(
@@ -317,6 +306,50 @@ fun NowPlayingScreen(
} }
} }
@Composable
private fun SeekBar(
progress: Float,
onSeek: (Float) -> Unit,
modifier: Modifier = Modifier,
) {
var widthPx by remember { mutableIntStateOf(0) }
var dragging by remember { mutableStateOf(false) }
var dragFraction by remember { mutableFloatStateOf(0f) }
val active = MaterialTheme.colorScheme.primary
val track = active.copy(alpha = 0.24f)
val shown = if (dragging) dragFraction else progress
Box(
modifier =
modifier
.fillMaxWidth()
.height(32.dp)
.onSizeChanged { widthPx = it.width }
.pointerInput(widthPx) {
detectHorizontalDragGestures(
onDragStart = { offset ->
dragging = true
dragFraction = if (widthPx > 0) (offset.x / widthPx).coerceIn(0f, 1f) else 0f
},
onDragEnd = {
dragging = false
onSeek(dragFraction)
},
onDragCancel = { dragging = false },
) { change, _ ->
dragFraction = if (widthPx > 0) (change.position.x / widthPx).coerceIn(0f, 1f) else 0f
}
}.pointerInput(widthPx) {
detectTapGestures { offset ->
if (widthPx > 0) onSeek((offset.x / widthPx).coerceIn(0f, 1f))
}
},
contentAlignment = Alignment.CenterStart,
) {
Box(Modifier.fillMaxWidth().height(4.dp).background(track))
Box(Modifier.fillMaxWidth(shown).height(4.dp).background(active))
}
}
private fun formatTime(milliseconds: Long): String { private fun formatTime(milliseconds: Long): String {
val totalSeconds = (milliseconds / 1000).coerceAtLeast(0) val totalSeconds = (milliseconds / 1000).coerceAtLeast(0)
val minutes = totalSeconds / 60 val minutes = totalSeconds / 60
@@ -1,24 +1,26 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android" <vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp" android:width="24dp"
android:height="24dp" android:height="24dp"
android:viewportWidth="299.84" android:viewportWidth="300"
android:viewportHeight="219.51"> android:viewportHeight="300">
<path <group android:translateY="40.25">
android:pathData="m23.84,-0c-12.76,0 -23.44,13 -23.65,25.12 -0.42,79.03 0,160.73 0,170.4 0,12.59 9.8,23.99 23.41,23.99l253.58,0c11.4,0 22.66,-12.38 22.66,-22.79L299.84,24.12c0,-10.8 -10.24,-23.81 -21.84,-23.81 -11.6,0 -254.16,-0.31 -254.16,-0.31zM39.05,25.28c0,0 217.71,0.71 223.67,0.71 6.07,0 12.17,6.83 12.17,12.06l0,142.25c0,8.97 -6.97,14.63 -12.48,14.64l-33.42,0.07c-5.32,0.01 -9.93,-7.19 -11.35,-10.61 -1.89,-4.55 -11.08,-25.75 -12.1,-28.1 -0.6,-1.38 -2.9,-2.83 -5.89,-2.83L98.33,153.45c-2.68,0 -4.89,2.87 -5.67,4.71 -1.12,2.66 -9.02,21.39 -11.63,27.57 -2.73,6.46 -4.37,9.06 -11.47,9.06l-30.97,0c-7.44,0 -13.69,-7.17 -13.67,-15.18l0.28,-139.51c0,-6.88 5.54,-14.83 13.85,-14.83z" <path
android:strokeWidth="0.9" android:pathData="m23.84,-0c-12.76,0 -23.44,13 -23.65,25.12 -0.42,79.03 0,160.73 0,170.4 0,12.59 9.8,23.99 23.41,23.99l253.58,0c11.4,0 22.66,-12.38 22.66,-22.79L299.84,24.12c0,-10.8 -10.24,-23.81 -21.84,-23.81 -11.6,0 -254.16,-0.31 -254.16,-0.31zM39.05,25.28c0,0 217.71,0.71 223.67,0.71 6.07,0 12.17,6.83 12.17,12.06l0,142.25c0,8.97 -6.97,14.63 -12.48,14.64l-33.42,0.07c-5.32,0.01 -9.93,-7.19 -11.35,-10.61 -1.89,-4.55 -11.08,-25.75 -12.1,-28.1 -0.6,-1.38 -2.9,-2.83 -5.89,-2.83L98.33,153.45c-2.68,0 -4.89,2.87 -5.67,4.71 -1.12,2.66 -9.02,21.39 -11.63,27.57 -2.73,6.46 -4.37,9.06 -11.47,9.06l-30.97,0c-7.44,0 -13.69,-7.17 -13.67,-15.18l0.28,-139.51c0,-6.88 5.54,-14.83 13.85,-14.83z"
android:fillColor="#FFFFFFFF" android:strokeWidth="0.9"
android:fillType="nonZero" android:fillColor="#FFFFFFFF"
android:strokeColor="#00000000"/> android:fillType="nonZero"
<path android:strokeColor="#00000000"/>
android:pathData="M90.09,95.03m-32.1,0a32.42,32.1 90,1 1,64.21 0a32.42,32.1 90,1 1,-64.21 0" <path
android:strokeWidth="0.86" android:pathData="M90.09,95.03m-32.1,0a32.42,32.1 90,1 1,64.21 0a32.42,32.1 90,1 1,-64.21 0"
android:fillColor="#FFFFFFFF" android:strokeWidth="0.86"
android:fillType="nonZero" android:fillColor="#FFFFFFFF"
android:strokeColor="#00000000"/> android:fillType="nonZero"
<path android:strokeColor="#00000000"/>
android:pathData="M209.94,95.03m-32.1,0a32.42,32.1 90,1 1,64.21 0a32.42,32.1 90,1 1,-64.21 0" <path
android:strokeWidth="0.86" android:pathData="M209.94,95.03m-32.1,0a32.42,32.1 90,1 1,64.21 0a32.42,32.1 90,1 1,-64.21 0"
android:fillColor="#FFFFFFFF" android:strokeWidth="0.86"
android:fillType="nonZero" android:fillColor="#FFFFFFFF"
android:strokeColor="#00000000"/> android:fillType="nonZero"
android:strokeColor="#00000000"/>
</group>
</vector> </vector>