feat(android): TrackActions kebab on NowPlaying with hideQueueActions
NowPlaying gains the 7-item TrackActions menu in its bottom action row alongside the View Queue button. hideQueueActions=true suppresses Play next / Add to queue since the menu's track IS the playing one. Go to album / artist pops NowPlaying first (it's a full-screen overlay) before navigating, mirroring Flutter's shell-route hook. Adds a thin Scaffold around the screen body so the TrackActions transient messages have a SnackbarHost to surface in. MiniPlayer kebab still deferred — needs shell-level snackbar plumbing that lives outside this slice's scope. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -17,11 +17,16 @@ import androidx.compose.foundation.shape.RoundedCornerShape
|
|||||||
import androidx.compose.material3.Icon
|
import androidx.compose.material3.Icon
|
||||||
import androidx.compose.material3.IconButton
|
import androidx.compose.material3.IconButton
|
||||||
import androidx.compose.material3.MaterialTheme
|
import androidx.compose.material3.MaterialTheme
|
||||||
|
import androidx.compose.material3.Scaffold
|
||||||
import androidx.compose.material3.Slider
|
import androidx.compose.material3.Slider
|
||||||
import androidx.compose.material3.SliderDefaults
|
import androidx.compose.material3.SliderDefaults
|
||||||
|
import androidx.compose.material3.SnackbarHost
|
||||||
|
import androidx.compose.material3.SnackbarHostState
|
||||||
import androidx.compose.material3.Text
|
import androidx.compose.material3.Text
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.LaunchedEffect
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
|
import androidx.compose.runtime.remember
|
||||||
import androidx.compose.ui.Alignment
|
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
|
||||||
@@ -40,8 +45,12 @@ import com.composables.icons.lucide.Pause
|
|||||||
import com.composables.icons.lucide.Play
|
import com.composables.icons.lucide.Play
|
||||||
import com.composables.icons.lucide.SkipBack
|
import com.composables.icons.lucide.SkipBack
|
||||||
import com.composables.icons.lucide.SkipForward
|
import com.composables.icons.lucide.SkipForward
|
||||||
|
import com.fabledsword.minstrel.nav.AlbumDetail
|
||||||
|
import com.fabledsword.minstrel.nav.ArtistDetail
|
||||||
import com.fabledsword.minstrel.nav.Queue
|
import com.fabledsword.minstrel.nav.Queue
|
||||||
import com.fabledsword.minstrel.shared.widgets.EmptyState
|
import com.fabledsword.minstrel.shared.widgets.EmptyState
|
||||||
|
import com.fabledsword.minstrel.shared.widgets.track_actions.TrackActionsButton
|
||||||
|
import com.fabledsword.minstrel.shared.widgets.track_actions.TrackActionsViewModel
|
||||||
import com.fabledsword.minstrel.theme.FabledSwordFlatTokens
|
import com.fabledsword.minstrel.theme.FabledSwordFlatTokens
|
||||||
import com.fabledsword.minstrel.theme.LocalActionColors
|
import com.fabledsword.minstrel.theme.LocalActionColors
|
||||||
|
|
||||||
@@ -61,8 +70,15 @@ private const val SECONDS_PER_MINUTE = 60L
|
|||||||
fun NowPlayingScreen(
|
fun NowPlayingScreen(
|
||||||
navController: NavHostController,
|
navController: NavHostController,
|
||||||
viewModel: PlayerViewModel = hiltViewModel(),
|
viewModel: PlayerViewModel = hiltViewModel(),
|
||||||
|
trackActionsViewModel: TrackActionsViewModel = hiltViewModel(),
|
||||||
) {
|
) {
|
||||||
val state by viewModel.uiState.collectAsStateWithLifecycle()
|
val state by viewModel.uiState.collectAsStateWithLifecycle()
|
||||||
|
val snackbarHostState = remember { SnackbarHostState() }
|
||||||
|
LaunchedEffect(Unit) {
|
||||||
|
trackActionsViewModel.transientMessages.collect { msg ->
|
||||||
|
snackbarHostState.showSnackbar(msg)
|
||||||
|
}
|
||||||
|
}
|
||||||
val track = state.currentTrack
|
val track = state.currentTrack
|
||||||
if (track == null) {
|
if (track == null) {
|
||||||
EmptyState(
|
EmptyState(
|
||||||
@@ -71,35 +87,59 @@ fun NowPlayingScreen(
|
|||||||
)
|
)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
Column(
|
Scaffold(
|
||||||
modifier = Modifier
|
modifier = Modifier.fillMaxSize(),
|
||||||
.fillMaxSize()
|
snackbarHost = { SnackbarHost(snackbarHostState) },
|
||||||
.padding(horizontal = 24.dp),
|
containerColor = MaterialTheme.colorScheme.background,
|
||||||
verticalArrangement = Arrangement.Center,
|
) { inner ->
|
||||||
horizontalAlignment = Alignment.CenterHorizontally,
|
Column(
|
||||||
) {
|
modifier = Modifier
|
||||||
NowPlayingCover(coverUrl = track.coverUrl, contentDescription = track.title)
|
.fillMaxSize()
|
||||||
Spacer(Modifier.height(24.dp))
|
.padding(inner)
|
||||||
TrackHeader(
|
.padding(horizontal = 24.dp),
|
||||||
title = track.title,
|
verticalArrangement = Arrangement.Center,
|
||||||
artist = track.artistName,
|
horizontalAlignment = Alignment.CenterHorizontally,
|
||||||
album = track.albumTitle,
|
) {
|
||||||
)
|
NowPlayingCover(coverUrl = track.coverUrl, contentDescription = track.title)
|
||||||
Spacer(Modifier.height(24.dp))
|
Spacer(Modifier.height(24.dp))
|
||||||
ScrubberRow(
|
TrackHeader(
|
||||||
positionMs = state.positionMs,
|
title = track.title,
|
||||||
durationMs = state.durationMs,
|
artist = track.artistName,
|
||||||
onSeek = viewModel::seekTo,
|
album = track.albumTitle,
|
||||||
)
|
)
|
||||||
Spacer(Modifier.height(8.dp))
|
Spacer(Modifier.height(24.dp))
|
||||||
TransportRow(
|
ScrubberRow(
|
||||||
isPlaying = state.isPlaying,
|
positionMs = state.positionMs,
|
||||||
onPrev = viewModel::skipToPrevious,
|
durationMs = state.durationMs,
|
||||||
onPlayPause = { if (state.isPlaying) viewModel.pause() else viewModel.play() },
|
onSeek = viewModel::seekTo,
|
||||||
onNext = viewModel::skipToNext,
|
)
|
||||||
)
|
Spacer(Modifier.height(8.dp))
|
||||||
Spacer(Modifier.height(16.dp))
|
TransportRow(
|
||||||
ViewQueueButton(onClick = { navController.navigate(Queue) })
|
isPlaying = state.isPlaying,
|
||||||
|
onPrev = viewModel::skipToPrevious,
|
||||||
|
onPlayPause = { if (state.isPlaying) viewModel.pause() else viewModel.play() },
|
||||||
|
onNext = viewModel::skipToNext,
|
||||||
|
)
|
||||||
|
Spacer(Modifier.height(16.dp))
|
||||||
|
Row(
|
||||||
|
horizontalArrangement = Arrangement.spacedBy(8.dp),
|
||||||
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
|
) {
|
||||||
|
ViewQueueButton(onClick = { navController.navigate(Queue) })
|
||||||
|
TrackActionsButton(
|
||||||
|
track = track,
|
||||||
|
hideQueueActions = true,
|
||||||
|
onNavigateToAlbum = { id ->
|
||||||
|
navController.popBackStack()
|
||||||
|
navController.navigate(AlbumDetail(id))
|
||||||
|
},
|
||||||
|
onNavigateToArtist = { id ->
|
||||||
|
navController.popBackStack()
|
||||||
|
navController.navigate(ArtistDetail(id))
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user