Fix mini-player disappearing on detail screens; polish Now Playing UI
The user noticed the mini-player ribbon vanished whenever navigating into an artist/album/settings screen, and asked for the full-player transport controls to stay anchored to a fixed spot and for long titles to marquee like Spotify instead of wrapping. - Mini-player was owned by MainScreen's own Scaffold, so it unmounted entirely on any sibling nav destination (ArtistDetail/AlbumDetail/ Settings). Hoisted it to DeepwaveNavHost, wrapping the whole authenticated NavHost in an outer Scaffold whose bottomBar shows the mini-player on every route except Login and NowPlaying itself. MainScreen no longer needs PlayerViewModel at all as a result - reverted it to a single previewable composable. - That hoist exposed a real inset bug: MiniPlayerBar is a plain Row, not a Material3 NavigationBar/BottomAppBar, so it doesn't handle system-bar insets on its own - it only ever looked right because it sat above the inset-aware BottomNavBar in the same Column. Standing alone, it rendered underneath the gesture nav bar. Fixed with an explicit navigationBarsPadding(). - NowPlayingScreen: the artwork/title/artist block now gets weight(1f) in the Column, so the seek bar and transport controls always land at a fixed distance from the bottom regardless of title length - previously a long title wrapped to 2-3 lines and pushed the controls down inconsistently. - Long titles now use maxLines = 1 + Modifier.basicMarquee() instead of wrapping, on both NowPlayingScreen and MiniPlayerBar's title. Fixed navController.currentBackStackEntryAsState()-based route comparison to use route-string equality rather than the hasRoute<T>() generic (that API isn't available in navigation-compose 2.9.8, this project's pinned version - only the KClass-less String overload exists here). Verified on-device: mini-player now persists correctly through Home -> Album Detail -> Now Playing and back, sits above the system nav bar, and the transport controls stay in the same position across tracks with short and long titles. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Sonnet 5
parent
cf52bb854e
commit
f79da367dc
@@ -2,10 +2,12 @@ package com.InfernalAquatics.deepwave.ui.components
|
|||||||
|
|
||||||
import android.content.res.Configuration
|
import android.content.res.Configuration
|
||||||
import androidx.compose.foundation.background
|
import androidx.compose.foundation.background
|
||||||
|
import androidx.compose.foundation.basicMarquee
|
||||||
import androidx.compose.foundation.clickable
|
import androidx.compose.foundation.clickable
|
||||||
import androidx.compose.foundation.layout.Column
|
import androidx.compose.foundation.layout.Column
|
||||||
import androidx.compose.foundation.layout.Row
|
import androidx.compose.foundation.layout.Row
|
||||||
import androidx.compose.foundation.layout.fillMaxWidth
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
|
import androidx.compose.foundation.layout.navigationBarsPadding
|
||||||
import androidx.compose.foundation.layout.padding
|
import androidx.compose.foundation.layout.padding
|
||||||
import androidx.compose.material.icons.Icons
|
import androidx.compose.material.icons.Icons
|
||||||
import androidx.compose.material.icons.filled.Pause
|
import androidx.compose.material.icons.filled.Pause
|
||||||
@@ -38,6 +40,7 @@ fun MiniPlayerBar(
|
|||||||
modifier = modifier
|
modifier = modifier
|
||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
.background(MaterialTheme.colorScheme.surfaceVariant)
|
.background(MaterialTheme.colorScheme.surfaceVariant)
|
||||||
|
.navigationBarsPadding()
|
||||||
.clickable(enabled = title != null, onClick = onClick)
|
.clickable(enabled = title != null, onClick = onClick)
|
||||||
.padding(horizontal = 16.dp, vertical = 8.dp),
|
.padding(horizontal = 16.dp, vertical = 8.dp),
|
||||||
verticalAlignment = Alignment.CenterVertically,
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
@@ -53,6 +56,9 @@ fun MiniPlayerBar(
|
|||||||
style = MaterialTheme.typography.bodyMedium,
|
style = MaterialTheme.typography.bodyMedium,
|
||||||
maxLines = 1,
|
maxLines = 1,
|
||||||
overflow = TextOverflow.Ellipsis,
|
overflow = TextOverflow.Ellipsis,
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.basicMarquee(),
|
||||||
)
|
)
|
||||||
if (subtitle != null) {
|
if (subtitle != null) {
|
||||||
Text(
|
Text(
|
||||||
@@ -88,3 +94,17 @@ private fun MiniPlayerBarPlayingPreview() {
|
|||||||
MiniPlayerBar(title = "Sample Track", subtitle = "Sample Artist", artworkUrl = null, isPlaying = true, onTogglePlayPause = {})
|
MiniPlayerBar(title = "Sample Track", subtitle = "Sample Artist", artworkUrl = null, isPlaying = true, onTogglePlayPause = {})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Preview(name = "Long title", showBackground = true, uiMode = Configuration.UI_MODE_NIGHT_YES)
|
||||||
|
@Composable
|
||||||
|
private fun MiniPlayerBarLongTitlePreview() {
|
||||||
|
DeepwaveTheme {
|
||||||
|
MiniPlayerBar(
|
||||||
|
title = "Riddim March [Forthcoming Blacklight Audio Extended Remix Edit]",
|
||||||
|
subtitle = "TOOG",
|
||||||
|
artworkUrl = null,
|
||||||
|
isPlaying = true,
|
||||||
|
onTogglePlayPause = {},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -2,7 +2,9 @@ package com.InfernalAquatics.deepwave.ui.navigation
|
|||||||
|
|
||||||
import androidx.compose.foundation.layout.Box
|
import androidx.compose.foundation.layout.Box
|
||||||
import androidx.compose.foundation.layout.fillMaxSize
|
import androidx.compose.foundation.layout.fillMaxSize
|
||||||
|
import androidx.compose.foundation.layout.padding
|
||||||
import androidx.compose.material3.CircularProgressIndicator
|
import androidx.compose.material3.CircularProgressIndicator
|
||||||
|
import androidx.compose.material3.Scaffold
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.collectAsState
|
import androidx.compose.runtime.collectAsState
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
@@ -13,18 +15,28 @@ import androidx.media3.common.util.UnstableApi
|
|||||||
import androidx.navigation.NavHostController
|
import androidx.navigation.NavHostController
|
||||||
import androidx.navigation.compose.NavHost
|
import androidx.navigation.compose.NavHost
|
||||||
import androidx.navigation.compose.composable
|
import androidx.navigation.compose.composable
|
||||||
|
import androidx.navigation.compose.currentBackStackEntryAsState
|
||||||
import androidx.navigation.compose.rememberNavController
|
import androidx.navigation.compose.rememberNavController
|
||||||
|
import com.InfernalAquatics.deepwave.ui.components.MiniPlayerBar
|
||||||
import com.InfernalAquatics.deepwave.ui.library.AlbumDetailScreen
|
import com.InfernalAquatics.deepwave.ui.library.AlbumDetailScreen
|
||||||
import com.InfernalAquatics.deepwave.ui.library.ArtistDetailScreen
|
import com.InfernalAquatics.deepwave.ui.library.ArtistDetailScreen
|
||||||
import com.InfernalAquatics.deepwave.ui.login.LoginScreen
|
import com.InfernalAquatics.deepwave.ui.login.LoginScreen
|
||||||
import com.InfernalAquatics.deepwave.ui.player.NowPlayingScreen
|
import com.InfernalAquatics.deepwave.ui.player.NowPlayingScreen
|
||||||
|
import com.InfernalAquatics.deepwave.ui.player.PlayerViewModel
|
||||||
import com.InfernalAquatics.deepwave.ui.settings.SettingsScreen
|
import com.InfernalAquatics.deepwave.ui.settings.SettingsScreen
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Hosts the mini-player bar itself, above the NavHost content rather than inside any one
|
||||||
|
* screen - it needs to stay visible across Main/Settings/ArtistDetail/AlbumDetail (everywhere
|
||||||
|
* except the full-screen NowPlaying player and the pre-login state), which none of those
|
||||||
|
* screens' own Scaffolds could give it on their own since they're sibling nav destinations.
|
||||||
|
*/
|
||||||
@UnstableApi
|
@UnstableApi
|
||||||
@Composable
|
@Composable
|
||||||
fun DeepwaveNavHost(
|
fun DeepwaveNavHost(
|
||||||
navController: NavHostController = rememberNavController(),
|
navController: NavHostController = rememberNavController(),
|
||||||
sessionViewModel: SessionViewModel = hiltViewModel(),
|
sessionViewModel: SessionViewModel = hiltViewModel(),
|
||||||
|
playerViewModel: PlayerViewModel = hiltViewModel(),
|
||||||
) {
|
) {
|
||||||
val isLoggedIn by sessionViewModel.isLoggedIn.collectAsState()
|
val isLoggedIn by sessionViewModel.isLoggedIn.collectAsState()
|
||||||
|
|
||||||
@@ -32,9 +44,31 @@ fun DeepwaveNavHost(
|
|||||||
null -> Box(modifier = Modifier.fillMaxSize()) {
|
null -> Box(modifier = Modifier.fillMaxSize()) {
|
||||||
CircularProgressIndicator(modifier = Modifier.align(Alignment.Center))
|
CircularProgressIndicator(modifier = Modifier.align(Alignment.Center))
|
||||||
}
|
}
|
||||||
else -> NavHost(
|
else -> {
|
||||||
|
val playbackUiState by playerViewModel.uiState.collectAsState()
|
||||||
|
val currentDestination by navController.currentBackStackEntryAsState()
|
||||||
|
val showMiniPlayer = currentDestination?.destination?.route?.let { route ->
|
||||||
|
route != Route.Login::class.qualifiedName && route != Route.NowPlaying::class.qualifiedName
|
||||||
|
} ?: false
|
||||||
|
|
||||||
|
Scaffold(
|
||||||
|
bottomBar = {
|
||||||
|
if (showMiniPlayer) {
|
||||||
|
MiniPlayerBar(
|
||||||
|
title = playbackUiState.nowPlaying?.title,
|
||||||
|
subtitle = playbackUiState.nowPlaying?.artist,
|
||||||
|
artworkUrl = playbackUiState.nowPlaying?.artworkUri,
|
||||||
|
isPlaying = playbackUiState.isPlaying,
|
||||||
|
onTogglePlayPause = playerViewModel::togglePlayPause,
|
||||||
|
onClick = { navController.navigate(Route.NowPlaying) },
|
||||||
|
)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
) { innerPadding ->
|
||||||
|
NavHost(
|
||||||
navController = navController,
|
navController = navController,
|
||||||
startDestination = if (isLoggedIn == true) Route.Main else Route.Login,
|
startDestination = if (isLoggedIn == true) Route.Main else Route.Login,
|
||||||
|
modifier = Modifier.padding(innerPadding),
|
||||||
) {
|
) {
|
||||||
composable<Route.Login> {
|
composable<Route.Login> {
|
||||||
LoginScreen(
|
LoginScreen(
|
||||||
@@ -50,7 +84,6 @@ fun DeepwaveNavHost(
|
|||||||
onOpenSettings = { navController.navigate(Route.Settings) },
|
onOpenSettings = { navController.navigate(Route.Settings) },
|
||||||
onOpenArtist = { artistId -> navController.navigate(Route.ArtistDetail(artistId)) },
|
onOpenArtist = { artistId -> navController.navigate(Route.ArtistDetail(artistId)) },
|
||||||
onOpenAlbum = { albumId -> navController.navigate(Route.AlbumDetail(albumId)) },
|
onOpenAlbum = { albumId -> navController.navigate(Route.AlbumDetail(albumId)) },
|
||||||
onOpenNowPlaying = { navController.navigate(Route.NowPlaying) },
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
composable<Route.Settings> {
|
composable<Route.Settings> {
|
||||||
@@ -78,3 +111,5 @@ fun DeepwaveNavHost(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -2,7 +2,6 @@ package com.InfernalAquatics.deepwave.ui.navigation
|
|||||||
|
|
||||||
import android.content.res.Configuration
|
import android.content.res.Configuration
|
||||||
import androidx.compose.foundation.layout.Box
|
import androidx.compose.foundation.layout.Box
|
||||||
import androidx.compose.foundation.layout.Column
|
|
||||||
import androidx.compose.foundation.layout.fillMaxSize
|
import androidx.compose.foundation.layout.fillMaxSize
|
||||||
import androidx.compose.foundation.layout.padding
|
import androidx.compose.foundation.layout.padding
|
||||||
import androidx.compose.material.icons.Icons
|
import androidx.compose.material.icons.Icons
|
||||||
@@ -14,7 +13,6 @@ import androidx.compose.material3.Scaffold
|
|||||||
import androidx.compose.material3.Text
|
import androidx.compose.material3.Text
|
||||||
import androidx.compose.material3.TopAppBar
|
import androidx.compose.material3.TopAppBar
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.collectAsState
|
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
import androidx.compose.runtime.mutableStateOf
|
import androidx.compose.runtime.mutableStateOf
|
||||||
import androidx.compose.runtime.remember
|
import androidx.compose.runtime.remember
|
||||||
@@ -22,50 +20,26 @@ import androidx.compose.runtime.setValue
|
|||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.res.stringResource
|
import androidx.compose.ui.res.stringResource
|
||||||
import androidx.compose.ui.tooling.preview.Preview
|
import androidx.compose.ui.tooling.preview.Preview
|
||||||
import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel
|
|
||||||
import androidx.media3.common.util.UnstableApi
|
|
||||||
import com.InfernalAquatics.deepwave.R
|
import com.InfernalAquatics.deepwave.R
|
||||||
import com.InfernalAquatics.deepwave.media.PlaybackUiState
|
|
||||||
import com.InfernalAquatics.deepwave.ui.components.BottomNavBar
|
import com.InfernalAquatics.deepwave.ui.components.BottomNavBar
|
||||||
import com.InfernalAquatics.deepwave.ui.components.BottomTab
|
import com.InfernalAquatics.deepwave.ui.components.BottomTab
|
||||||
import com.InfernalAquatics.deepwave.ui.components.MiniPlayerBar
|
|
||||||
import com.InfernalAquatics.deepwave.ui.library.ArtistsScreen
|
import com.InfernalAquatics.deepwave.ui.library.ArtistsScreen
|
||||||
import com.InfernalAquatics.deepwave.ui.library.HomeScreen
|
import com.InfernalAquatics.deepwave.ui.library.HomeScreen
|
||||||
import com.InfernalAquatics.deepwave.ui.library.SearchScreen
|
import com.InfernalAquatics.deepwave.ui.library.SearchScreen
|
||||||
import com.InfernalAquatics.deepwave.ui.player.PlayerViewModel
|
|
||||||
import com.InfernalAquatics.deepwave.ui.theme.DeepwaveTheme
|
import com.InfernalAquatics.deepwave.ui.theme.DeepwaveTheme
|
||||||
|
|
||||||
/** Resolves the real player state, then hands off to the previewable [MainContent]. */
|
/**
|
||||||
@UnstableApi
|
* Hosts the bottom-tab shell (Home / Search / Library) and settings entry point. The
|
||||||
|
* mini-player bar is NOT owned here - it's hoisted to [DeepwaveNavHost] so it stays visible
|
||||||
|
* when navigating to sibling routes like ArtistDetail/AlbumDetail/Settings, which this
|
||||||
|
* screen's own Scaffold could never give it.
|
||||||
|
*/
|
||||||
|
@OptIn(ExperimentalMaterial3Api::class)
|
||||||
@Composable
|
@Composable
|
||||||
fun MainScreen(
|
fun MainScreen(
|
||||||
onOpenSettings: () -> Unit,
|
onOpenSettings: () -> Unit,
|
||||||
onOpenArtist: (String) -> Unit,
|
onOpenArtist: (String) -> Unit,
|
||||||
onOpenAlbum: (String) -> Unit,
|
onOpenAlbum: (String) -> Unit,
|
||||||
onOpenNowPlaying: () -> Unit,
|
|
||||||
playerViewModel: PlayerViewModel = hiltViewModel(),
|
|
||||||
) {
|
|
||||||
val playbackUiState by playerViewModel.uiState.collectAsState()
|
|
||||||
MainContent(
|
|
||||||
playbackUiState = playbackUiState,
|
|
||||||
onOpenSettings = onOpenSettings,
|
|
||||||
onOpenArtist = onOpenArtist,
|
|
||||||
onOpenAlbum = onOpenAlbum,
|
|
||||||
onOpenNowPlaying = onOpenNowPlaying,
|
|
||||||
onTogglePlayPause = playerViewModel::togglePlayPause,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Hosts the bottom-tab shell (Home / Search / Library) plus the mini-player bar and settings entry point. */
|
|
||||||
@OptIn(ExperimentalMaterial3Api::class)
|
|
||||||
@Composable
|
|
||||||
private fun MainContent(
|
|
||||||
playbackUiState: PlaybackUiState,
|
|
||||||
onOpenSettings: () -> Unit,
|
|
||||||
onOpenArtist: (String) -> Unit,
|
|
||||||
onOpenAlbum: (String) -> Unit,
|
|
||||||
onOpenNowPlaying: () -> Unit,
|
|
||||||
onTogglePlayPause: () -> Unit,
|
|
||||||
) {
|
) {
|
||||||
var selectedTab by remember { mutableStateOf(BottomTab.Home) }
|
var selectedTab by remember { mutableStateOf(BottomTab.Home) }
|
||||||
|
|
||||||
@@ -81,17 +55,7 @@ private fun MainContent(
|
|||||||
)
|
)
|
||||||
},
|
},
|
||||||
bottomBar = {
|
bottomBar = {
|
||||||
Column {
|
|
||||||
MiniPlayerBar(
|
|
||||||
title = playbackUiState.nowPlaying?.title,
|
|
||||||
subtitle = playbackUiState.nowPlaying?.artist,
|
|
||||||
artworkUrl = playbackUiState.nowPlaying?.artworkUri,
|
|
||||||
isPlaying = playbackUiState.isPlaying,
|
|
||||||
onTogglePlayPause = onTogglePlayPause,
|
|
||||||
onClick = onOpenNowPlaying,
|
|
||||||
)
|
|
||||||
BottomNavBar(selectedTab = selectedTab, onTabSelected = { selectedTab = it })
|
BottomNavBar(selectedTab = selectedTab, onTabSelected = { selectedTab = it })
|
||||||
}
|
|
||||||
},
|
},
|
||||||
) { innerPadding ->
|
) { innerPadding ->
|
||||||
Box(
|
Box(
|
||||||
@@ -111,15 +75,8 @@ private fun MainContent(
|
|||||||
/** Interactive in Android Studio's preview pane — try tapping the tabs and the settings icon. */
|
/** Interactive in Android Studio's preview pane — try tapping the tabs and the settings icon. */
|
||||||
@Preview(showBackground = true, uiMode = Configuration.UI_MODE_NIGHT_YES, widthDp = 411, heightDp = 891)
|
@Preview(showBackground = true, uiMode = Configuration.UI_MODE_NIGHT_YES, widthDp = 411, heightDp = 891)
|
||||||
@Composable
|
@Composable
|
||||||
private fun MainContentPreview() {
|
private fun MainScreenPreview() {
|
||||||
DeepwaveTheme {
|
DeepwaveTheme {
|
||||||
MainContent(
|
MainScreen(onOpenSettings = {}, onOpenArtist = {}, onOpenAlbum = {})
|
||||||
playbackUiState = PlaybackUiState(),
|
|
||||||
onOpenSettings = {},
|
|
||||||
onOpenArtist = {},
|
|
||||||
onOpenAlbum = {},
|
|
||||||
onOpenNowPlaying = {},
|
|
||||||
onTogglePlayPause = {},
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
package com.InfernalAquatics.deepwave.ui.player
|
package com.InfernalAquatics.deepwave.ui.player
|
||||||
|
|
||||||
import android.content.res.Configuration
|
import android.content.res.Configuration
|
||||||
|
import androidx.compose.foundation.basicMarquee
|
||||||
import androidx.compose.foundation.layout.Arrangement
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
import androidx.compose.foundation.layout.Column
|
import androidx.compose.foundation.layout.Column
|
||||||
import androidx.compose.foundation.layout.Row
|
import androidx.compose.foundation.layout.Row
|
||||||
@@ -86,15 +87,29 @@ private fun NowPlayingContent(
|
|||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxSize()
|
.fillMaxSize()
|
||||||
.padding(innerPadding)
|
.padding(innerPadding)
|
||||||
.padding(24.dp),
|
.padding(horizontal = 24.dp),
|
||||||
horizontalAlignment = Alignment.CenterHorizontally,
|
horizontalAlignment = Alignment.CenterHorizontally,
|
||||||
) {
|
) {
|
||||||
Artwork(imageUrl = uiState.nowPlaying?.artworkUri, size = 280.dp, modifier = Modifier.padding(top = 24.dp))
|
// Artwork/title/artist float in whatever space is left above the controls, so a
|
||||||
|
// wrapped or marqueed title never pushes the transport controls around - those
|
||||||
|
// stay anchored to a fixed distance from the bottom regardless of title length.
|
||||||
|
Column(
|
||||||
|
modifier = Modifier
|
||||||
|
.weight(1f)
|
||||||
|
.fillMaxWidth(),
|
||||||
|
horizontalAlignment = Alignment.CenterHorizontally,
|
||||||
|
verticalArrangement = Arrangement.Center,
|
||||||
|
) {
|
||||||
|
Artwork(imageUrl = uiState.nowPlaying?.artworkUri, size = 280.dp)
|
||||||
Spacer(modifier = Modifier.height(32.dp))
|
Spacer(modifier = Modifier.height(32.dp))
|
||||||
Text(
|
Text(
|
||||||
text = uiState.nowPlaying?.title ?: "Nothing playing",
|
text = uiState.nowPlaying?.title ?: "Nothing playing",
|
||||||
style = MaterialTheme.typography.headlineSmall,
|
style = MaterialTheme.typography.headlineSmall,
|
||||||
textAlign = TextAlign.Center,
|
textAlign = TextAlign.Center,
|
||||||
|
maxLines = 1,
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.basicMarquee(),
|
||||||
)
|
)
|
||||||
uiState.nowPlaying?.artist?.let {
|
uiState.nowPlaying?.artist?.let {
|
||||||
Text(
|
Text(
|
||||||
@@ -102,13 +117,19 @@ private fun NowPlayingContent(
|
|||||||
style = MaterialTheme.typography.bodyLarge,
|
style = MaterialTheme.typography.bodyLarge,
|
||||||
color = MaterialTheme.colorScheme.onSurfaceVariant,
|
color = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||||
textAlign = TextAlign.Center,
|
textAlign = TextAlign.Center,
|
||||||
|
maxLines = 1,
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.basicMarquee(),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
Spacer(modifier = Modifier.height(24.dp))
|
}
|
||||||
SeekBar(positionMs = uiState.positionMs, durationMs = uiState.durationMs, onSeek = onSeek)
|
SeekBar(positionMs = uiState.positionMs, durationMs = uiState.durationMs, onSeek = onSeek)
|
||||||
Spacer(modifier = Modifier.height(16.dp))
|
Spacer(modifier = Modifier.height(16.dp))
|
||||||
Row(
|
Row(
|
||||||
modifier = Modifier.fillMaxWidth(),
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.padding(bottom = 24.dp),
|
||||||
horizontalArrangement = Arrangement.SpaceEvenly,
|
horizontalArrangement = Arrangement.SpaceEvenly,
|
||||||
verticalAlignment = Alignment.CenterVertically,
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
) {
|
) {
|
||||||
@@ -179,3 +200,29 @@ private fun NowPlayingContentPreview() {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Demonstrates that a long title marquees on one line instead of wrapping, and that the controls stay put regardless. */
|
||||||
|
@Preview(name = "Long title", showBackground = true, uiMode = Configuration.UI_MODE_NIGHT_YES, widthDp = 411, heightDp = 891)
|
||||||
|
@Composable
|
||||||
|
private fun NowPlayingContentLongTitlePreview() {
|
||||||
|
DeepwaveTheme {
|
||||||
|
NowPlayingContent(
|
||||||
|
uiState = PlaybackUiState(
|
||||||
|
nowPlaying = NowPlaying(
|
||||||
|
mediaId = "subsonic:song:2",
|
||||||
|
title = "Riddim March [Forthcoming Blacklight Audio Extended Remix Edit]",
|
||||||
|
artist = "TOOG",
|
||||||
|
artworkUri = null,
|
||||||
|
),
|
||||||
|
isPlaying = true,
|
||||||
|
positionMs = 15_000,
|
||||||
|
durationMs = 109_000,
|
||||||
|
),
|
||||||
|
onBack = {},
|
||||||
|
onTogglePlayPause = {},
|
||||||
|
onSeek = {},
|
||||||
|
onSkipNext = {},
|
||||||
|
onSkipPrevious = {},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user