package at.lockstep.player.ui import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.fillMaxSize 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 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). */ val durationSeconds: Int, val isPlaying: Boolean, ) @Composable fun NowPlayingScreen( state: NowPlayingUiState, onProgressChange: (Float) -> Unit, onPrevious: () -> Unit, onTogglePlayPause: () -> Unit, onNext: () -> Unit, modifier: Modifier = Modifier, ) { val elapsed = (state.progress * state.durationSeconds).toInt().coerceIn(0, state.durationSeconds) Column( 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, style = MaterialTheme.typography.headlineSmall, maxLines = 2, overflow = TextOverflow.Ellipsis, ) Text( text = state.artist, style = MaterialTheme.typography.bodyLarge, color = MaterialTheme.colorScheme.onSurfaceVariant, maxLines = 1, overflow = TextOverflow.Ellipsis, ) } Column( modifier = Modifier.fillMaxWidth(), verticalArrangement = Arrangement.spacedBy(12.dp), ) { Column( modifier = Modifier.fillMaxWidth(), verticalArrangement = Arrangement.spacedBy(4.dp), ) { Slider( value = state.progress, onValueChange = onProgressChange, modifier = Modifier.fillMaxWidth(), ) Row( modifier = Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.SpaceBetween, ) { Text( text = formatMmSs(elapsed), style = MaterialTheme.typography.labelMedium, color = MaterialTheme.colorScheme.onSurfaceVariant, ) Text( text = formatMmSs(state.durationSeconds), style = MaterialTheme.typography.labelMedium, color = MaterialTheme.colorScheme.onSurfaceVariant, ) } } Row( modifier = Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.SpaceEvenly, verticalAlignment = Alignment.CenterVertically, ) { IconButton(onClick = onPrevious, modifier = Modifier.size(80.dp)) { Icon(Icons.Default.SkipPrevious, contentDescription = "Previous track") } IconButton(onClick = onTogglePlayPause, modifier = Modifier.size(104.dp)) { Icon( imageVector = if (state.isPlaying) Icons.Default.Pause else Icons.Default.PlayArrow, contentDescription = if (state.isPlaying) "Pause" else "Play", modifier = Modifier.size(56.dp), ) } IconButton(onClick = onNext, modifier = Modifier.size(80.dp)) { Icon(Icons.Default.SkipNext, contentDescription = "Next track") } } } } } @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 val r = s % 60 return "%d:%02d".format(m, r) } @Preview(showBackground = true) @Composable private fun NowPlayingScreenPreview() { MaterialTheme { var progress by remember { mutableFloatStateOf(0.35f) } var playing by remember { mutableStateOf(true) } NowPlayingScreen( state = NowPlayingUiState( title = "Example Track Title", artist = "Example Artist", stepFrequencyDisplay = "--", progress = progress, durationSeconds = 215, isPlaying = playing, ), onProgressChange = { progress = it }, onPrevious = {}, onTogglePlayPause = { playing = !playing }, onNext = {}, ) } }