feat: sync Playlists, wip: pair songs
This commit is contained in:
@@ -8,16 +8,21 @@ import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.automirrored.filled.ArrowBack
|
||||
import androidx.compose.material.icons.filled.Pause
|
||||
import androidx.compose.material.icons.filled.PlayArrow
|
||||
import androidx.compose.material.icons.filled.SkipNext
|
||||
import androidx.compose.material.icons.filled.SkipPrevious
|
||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.IconButton
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Scaffold
|
||||
import androidx.compose.material3.Slider
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.material3.TopAppBar
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableFloatStateOf
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
@@ -25,13 +30,18 @@ import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.text.style.TextOverflow
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.unit.dp
|
||||
import at.lockstep.player.playback.PlaybackService
|
||||
import at.lockstep.player.R
|
||||
|
||||
data class NowPlayingUiState(
|
||||
val title: String,
|
||||
val artist: String,
|
||||
/** Placeholder until live cadence / JNI wiring; MVP shows "--". */
|
||||
val stepFrequencyDisplay: String,
|
||||
/** 0f..1f until real timing is wired to Oboe / JNI. */
|
||||
val progress: Float,
|
||||
/** Total track length in seconds (placeholder for UI formatting). */
|
||||
@@ -51,11 +61,25 @@ fun NowPlayingScreen(
|
||||
val elapsed = (state.progress * state.durationSeconds).toInt().coerceIn(0, state.durationSeconds)
|
||||
|
||||
Column(
|
||||
modifier = modifier
|
||||
.fillMaxSize()
|
||||
.padding(horizontal = 24.dp, vertical = 16.dp),
|
||||
modifier =
|
||||
modifier
|
||||
.fillMaxSize()
|
||||
.padding(horizontal = 24.dp, vertical = 16.dp),
|
||||
verticalArrangement = Arrangement.spacedBy(24.dp),
|
||||
) {
|
||||
Column(verticalArrangement = Arrangement.spacedBy(4.dp)) {
|
||||
Text(
|
||||
text = LocalContext.current.getString(R.string.now_playing_step_frequency_label),
|
||||
style = MaterialTheme.typography.labelLarge,
|
||||
color = MaterialTheme.colorScheme.primary,
|
||||
)
|
||||
Text(
|
||||
text = state.stepFrequencyDisplay,
|
||||
style = MaterialTheme.typography.displaySmall,
|
||||
color = MaterialTheme.colorScheme.onSurface,
|
||||
)
|
||||
}
|
||||
|
||||
Column(verticalArrangement = Arrangement.spacedBy(4.dp)) {
|
||||
Text(
|
||||
text = state.title,
|
||||
@@ -124,6 +148,70 @@ fun NowPlayingScreen(
|
||||
}
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
fun NowPlayingRoute(
|
||||
playlistId: String,
|
||||
playback: PlaybackService?,
|
||||
onBack: () -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
val context = LocalContext.current
|
||||
var ui by remember {
|
||||
mutableStateOf(
|
||||
NowPlayingUiState(
|
||||
title = context.getString(R.string.now_playing_idle_title),
|
||||
artist = context.getString(R.string.now_playing_idle_artist),
|
||||
stepFrequencyDisplay = context.getString(R.string.step_frequency_placeholder),
|
||||
progress = 0f,
|
||||
durationSeconds = 180,
|
||||
isPlaying = false,
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
LaunchedEffect(playback) {
|
||||
val service = playback ?: return@LaunchedEffect
|
||||
service.uiState.collect { p ->
|
||||
ui =
|
||||
NowPlayingUiState(
|
||||
title = p.title,
|
||||
artist = p.artist,
|
||||
stepFrequencyDisplay = context.getString(R.string.step_frequency_placeholder),
|
||||
progress = p.progress,
|
||||
durationSeconds = p.durationSeconds,
|
||||
isPlaying = p.isPlaying,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
Scaffold(
|
||||
modifier = modifier.fillMaxSize(),
|
||||
topBar = {
|
||||
TopAppBar(
|
||||
title = { Text(text = context.getString(R.string.now_playing_title)) },
|
||||
navigationIcon = {
|
||||
IconButton(onClick = onBack) {
|
||||
Icon(Icons.AutoMirrored.Filled.ArrowBack, contentDescription = null)
|
||||
}
|
||||
},
|
||||
)
|
||||
},
|
||||
) { padding ->
|
||||
NowPlayingScreen(
|
||||
state = ui,
|
||||
onProgressChange = { fraction ->
|
||||
playback?.requestSeek(fraction)
|
||||
ui = ui.copy(progress = fraction)
|
||||
},
|
||||
onPrevious = { playback?.requestSkipPrevious() },
|
||||
onTogglePlayPause = { playback?.requestTogglePause() },
|
||||
onNext = { playback?.requestSkipNext() },
|
||||
modifier = Modifier.padding(padding),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun formatMmSs(totalSeconds: Int): String {
|
||||
val s = totalSeconds.coerceAtLeast(0)
|
||||
val m = s / 60
|
||||
@@ -138,13 +226,15 @@ private fun NowPlayingScreenPreview() {
|
||||
var progress by remember { mutableFloatStateOf(0.35f) }
|
||||
var playing by remember { mutableStateOf(true) }
|
||||
NowPlayingScreen(
|
||||
state = NowPlayingUiState(
|
||||
title = "Example Track Title",
|
||||
artist = "Example Artist",
|
||||
progress = progress,
|
||||
durationSeconds = 215,
|
||||
isPlaying = playing,
|
||||
),
|
||||
state =
|
||||
NowPlayingUiState(
|
||||
title = "Example Track Title",
|
||||
artist = "Example Artist",
|
||||
stepFrequencyDisplay = "--",
|
||||
progress = progress,
|
||||
durationSeconds = 215,
|
||||
isPlaying = playing,
|
||||
),
|
||||
onProgressChange = { progress = it },
|
||||
onPrevious = {},
|
||||
onTogglePlayPause = { playing = !playing },
|
||||
|
||||
Reference in New Issue
Block a user