feat(ui): integrate LocalTextStyle for dynamic tinting in player controls and UI components

This commit is contained in:
Kingkor Roy Tirtho 2026-10-15 13:12:39 +06:00
parent 58dfa2de92
commit fb96984d6e
9 changed files with 512 additions and 334 deletions

17
.mcp.json Normal file
View File

@ -0,0 +1,17 @@
{
"mcpServers": {
"compose-hot-reload": {
"command": "./gradlew",
"args": [
"--no-daemon",
"--quiet",
"--console=plain",
"hotMcpServer"
]
},
"studio": {
"url": "http://127.0.0.1:64342/stream",
"type": "http"
}
}
}

View File

@ -13,19 +13,9 @@
"hotMcpServer"
]
},
"android-studio-mcp": {
"type": "local",
"environment": {
"IJ_MCP_SERVER_PORT": "64342"
},
"enabled": true,
"disabled": false,
"command": [
"/opt/android-studio/jbr/bin/java",
"-classpath",
"/home/krtirtho/.local/share/Google/AndroidStudio2026.1.4/mcpserver/lib/mcpserver.jar:/home/krtirtho/.local/share/Google/AndroidStudio2026.1.4/mcpserver/lib/io.modelcontextprotocol.kotlin.sdk.jar:/home/krtirtho/.local/share/Google/AndroidStudio2026.1.4/mcpserver/lib/io.github.oshai.kotlin.logging.jvm.jar:/opt/android-studio/lib/util-8.jar:/opt/android-studio/lib/intellij.libraries.kotlinx.coroutines.core.jar:/opt/android-studio/lib/intellij.libraries.ktor.client.cio.jar:/opt/android-studio/lib/intellij.libraries.ktor.client.jar:/opt/android-studio/lib/intellij.libraries.ktor.network.tls.jar:/opt/android-studio/lib/intellij.libraries.ktor.io.jar:/opt/android-studio/lib/intellij.libraries.ktor.utils.jar:/opt/android-studio/lib/intellij.libraries.kotlinx.io.jar:/opt/android-studio/lib/intellij.libraries.kotlinx.serialization.core.jar:/opt/android-studio/lib/intellij.libraries.kotlinx.serialization.json.jar",
"com.intellij.mcpserver.stdio.McpStdioRunnerKt"
]
"studio": {
"url": "http://127.0.0.1:64342/stream",
"type": "remote"
}
}
}

View File

@ -35,6 +35,7 @@ import androidx.compose.foundation.shape.RoundedCornerShape
import dev.krtirtho.spotube.core.ui.theming.AppTheme
import com.composeunstyled.Text
import com.composeunstyled.LocalTextStyle
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.rememberCoroutineScope
@ -158,7 +159,8 @@ fun LikedTracksCard(
Iconsax.IconsaxPauseCircle
} else Iconsax.Play,
contentDescription = stringResource(Res.string.player_play_content_description),
modifier = Modifier.size(16.dp)
modifier = Modifier.size(16.dp),
tint = LocalTextStyle.current.color
)
}
}

View File

@ -21,6 +21,7 @@ package dev.krtirtho.spotube.core.ui.component.cards
import org.spotube.millennium.composables.MillenniumIcon
import org.spotube.millennium.composables.MillenniumIconButton
import org.spotube.millennium.composables.MillenniumIconButtonStyle
import com.composeunstyled.LocalTextStyle
import androidx.compose.animation.AnimatedVisibility
import androidx.compose.animation.ExperimentalSharedTransitionApi
import androidx.compose.animation.fadeIn
@ -143,7 +144,7 @@ fun PlayableCard(
imageVector = Iconsax.IconsaxAddSquare,
contentDescription = stringResource(
Res.string.player_add_to_queue_content_description
)
),
)
}
Spacer(modifier = Modifier.height(8.dp))
@ -165,7 +166,8 @@ fun PlayableCard(
} else {
Res.string.player_play_content_description
}
)
),
tint = LocalTextStyle.current.color
)
}
}

View File

@ -20,6 +20,7 @@ import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.dp
import com.composeunstyled.LocalTextStyle
import coil3.compose.AsyncImage
import dev.krtirtho.spotube.core.ui.component.LocalAnimatedVisibilityScope
import dev.krtirtho.spotube.core.ui.component.LocalSharedTransitionScope
@ -130,7 +131,8 @@ fun PlayableListTile(
) {
MillenniumIcon(
if (isPlaying) Iconsax.IconsaxPause else Iconsax.IconsaxPlay,
stringResource(if (isPlaying) Res.string.player_pause_content_description else Res.string.player_play_content_description)
stringResource(if (isPlaying) Res.string.player_pause_content_description else Res.string.player_play_content_description),
tint = LocalTextStyle.current.color
)
}
}

View File

@ -48,6 +48,7 @@ import androidx.compose.foundation.shape.CircleShape
import dev.krtirtho.spotube.core.ui.theming.AppTheme
import com.composeunstyled.Text
import com.composeunstyled.LocalTextStyle
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
@ -117,7 +118,8 @@ fun ArtistScreen(
Column(
modifier = Modifier
.fillMaxSize()
.background(MillenniumTheme.colors.background)) {
.background(MillenniumTheme.colors.background)
) {
ApplicationMainBar()
Box(modifier = Modifier.fillMaxWidth().weight(1f)) {
when (val currentState = state) {
@ -140,7 +142,8 @@ fun ArtistScreen(
modifier = Modifier
.fillMaxSize()
.padding(bottom = LocalAppShellBottomInset.current + 24.dp),
verticalArrangement = Arrangement.spacedBy(24.dp)) {
verticalArrangement = Arrangement.spacedBy(24.dp)
) {
item {
ArtistHeaderCard(
artist = currentState.artist,
@ -153,7 +156,8 @@ fun ArtistScreen(
item {
TopTracksHeader(
onPlay = viewModel::playTopTracks,
onAddToQueue = viewModel::addTopTracksToQueue)
onAddToQueue = viewModel::addTopTracksToQueue
)
}
item {
@ -191,7 +195,8 @@ fun ArtistScreen(
onBulkAddToPlaylist = viewModel::showAddToPlaylistPicker,
onBulkAddToJam = viewModel::addTracksToJam,
isInJam = jamActive,
isJamGuest = isJamGuest)
isJamGuest = isJamGuest
)
}
if (currentState.albums.isNotEmpty() || currentState.albumsNextPagination != null) {
@ -247,7 +252,8 @@ fun ArtistScreen(
visible = showAddToPlaylistPicker,
currentUserId = currentUserId,
onDismiss = viewModel::dismissAddToPlaylistPicker,
onPlaylistSelected = viewModel::addTracksToPlaylist)
onPlaylistSelected = viewModel::addTracksToPlaylist
)
}
}
@ -256,41 +262,50 @@ private fun ArtistLoadingContent() {
Box(
modifier = Modifier
.fillMaxSize(),
contentAlignment = Alignment.Center) {
contentAlignment = Alignment.Center
) {
SkeletonTree(true) {
Column(
modifier = Modifier.fillMaxWidth(),
verticalArrangement = Arrangement.spacedBy(24.dp)) {
verticalArrangement = Arrangement.spacedBy(24.dp)
) {
Box(
modifier = Modifier
.fillMaxWidth()
.padding(horizontal = 16.dp)) {
.padding(horizontal = 16.dp)
) {
Box(
modifier = Modifier
.size(200.dp)
.clip(CircleShape)
.background(AppTheme.colors.surfaceContainerHighest))
.background(AppTheme.colors.surfaceContainerHighest)
)
}
Column(
modifier = Modifier.fillMaxWidth(),
verticalArrangement = Arrangement.spacedBy(8.dp)) {
verticalArrangement = Arrangement.spacedBy(8.dp)
) {
Box(
modifier = Modifier
.fillMaxWidth()
.padding(horizontal = 16.dp)) {
.padding(horizontal = 16.dp)
) {
Box(
modifier = Modifier
.fillMaxWidth()
.padding(vertical = 8.dp))
.padding(vertical = 8.dp)
)
}
LazyRow(
horizontalArrangement = Arrangement.spacedBy(12.dp),
contentPadding = PaddingValues(horizontal = 16.dp)) {
contentPadding = PaddingValues(horizontal = 16.dp)
) {
items(4) {
PlayableCard(
title = "Item Title",
subtitle = "Subtitle",
imageURL = "https://placehold.co/600x400")
imageURL = "https://placehold.co/600x400"
)
}
}
}
@ -302,18 +317,22 @@ private fun ArtistLoadingContent() {
@Composable
private fun ArtistErrorContent(
message: String,
onRetry: () -> Unit) {
onRetry: () -> Unit
) {
Box(
modifier = Modifier
.fillMaxSize(),
contentAlignment = Alignment.Center) {
contentAlignment = Alignment.Center
) {
Column(
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.spacedBy(16.dp)) {
verticalArrangement = Arrangement.spacedBy(16.dp)
) {
Text(
text = message,
color = AppTheme.colors.error,
style = AppTheme.typography.bodyLarge)
style = AppTheme.typography.bodyLarge
)
MillenniumButton(onClick = onRetry) {
Text("Retry")
}
@ -327,34 +346,40 @@ private fun ArtistHeaderCard(
isSaved: Boolean,
onFollowClick: () -> Unit,
isBlacklisted: Boolean,
onBlacklistClick: () -> Unit) {
onBlacklistClick: () -> Unit
) {
Box(modifier = Modifier.fillMaxWidth()) {
val isCompact = currentWindowWidthBreakpoint() isBelow WindowBreakpoints.Medium
MillenniumCard(
modifier = Modifier
.fillMaxWidth()
.padding(horizontal = 12.dp, vertical = 8.dp)) {
.padding(horizontal = 12.dp, vertical = 8.dp)
) {
if (isCompact) {
Column(
modifier = Modifier
.fillMaxWidth()
.padding(16.dp),
verticalArrangement = Arrangement.spacedBy(14.dp),
horizontalAlignment = Alignment.CenterHorizontally) {
horizontalAlignment = Alignment.CenterHorizontally
) {
ArtistAvatar(
artist = artist,
size = 180.dp)
size = 180.dp
)
ArtistMeta(
artist = artist,
isCompact = true)
isCompact = true
)
ArtistHeaderActions(
isSaved = isSaved,
onFollowClick = onFollowClick,
isBlacklisted = isBlacklisted,
onBlacklistClick = onBlacklistClick)
onBlacklistClick = onBlacklistClick
)
}
} else {
Row(
@ -362,23 +387,28 @@ private fun ArtistHeaderCard(
.fillMaxWidth()
.padding(20.dp),
horizontalArrangement = Arrangement.spacedBy(20.dp),
verticalAlignment = Alignment.Top) {
verticalAlignment = Alignment.Top
) {
ArtistAvatar(
artist = artist,
size = 220.dp)
size = 220.dp
)
Column(
modifier = Modifier.weight(1f),
verticalArrangement = Arrangement.spacedBy(14.dp)) {
verticalArrangement = Arrangement.spacedBy(14.dp)
) {
ArtistMeta(
artist = artist,
isCompact = false)
isCompact = false
)
ArtistHeaderActions(
isSaved = isSaved,
onFollowClick = onFollowClick,
isBlacklisted = isBlacklisted,
onBlacklistClick = onBlacklistClick)
onBlacklistClick = onBlacklistClick
)
}
}
}
@ -389,26 +419,30 @@ private fun ArtistHeaderCard(
@Composable
private fun ArtistAvatar(
artist: MetadataArtist.Detailed,
size: androidx.compose.ui.unit.Dp) {
size: androidx.compose.ui.unit.Dp
) {
Box(
modifier = Modifier
.size(size)
.clip(CircleShape)
.background(AppTheme.colors.surfaceContainerHighest),
contentAlignment = Alignment.Center) {
contentAlignment = Alignment.Center
) {
val imageUrl = artist.thumbnails.firstOrNull()?.url.orEmpty()
if (imageUrl.isNotBlank()) {
AsyncImage(
model = imageUrl,
contentDescription = artist.name,
modifier = Modifier.fillMaxSize(),
contentScale = ContentScale.Crop)
contentScale = ContentScale.Crop
)
} else {
MillenniumIcon(
imageVector = Iconsax.User,
contentDescription = artist.name,
tint = AppTheme.colors.onSurfaceVariant,
modifier = Modifier.size(size * 0.4f))
modifier = Modifier.size(size * 0.4f)
)
}
}
}
@ -416,18 +450,21 @@ private fun ArtistAvatar(
@Composable
private fun ArtistMeta(
artist: MetadataArtist.Detailed,
isCompact: Boolean) {
isCompact: Boolean
) {
Column(
modifier = Modifier.fillMaxWidth(),
horizontalAlignment = if (isCompact) Alignment.CenterHorizontally else Alignment.Start,
verticalArrangement = Arrangement.spacedBy(10.dp)) {
verticalArrangement = Arrangement.spacedBy(10.dp)
) {
Text(
text = artist.name,
style = if (isCompact) AppTheme.typography.headlineSmall else AppTheme.typography.headlineLarge,
fontWeight = FontWeight.SemiBold,
textAlign = if (isCompact) TextAlign.Center else TextAlign.Start,
maxLines = 2,
overflow = TextOverflow.Ellipsis)
overflow = TextOverflow.Ellipsis
)
Text(
text = buildString {
@ -439,7 +476,8 @@ private fun ArtistMeta(
},
style = AppTheme.typography.bodyMedium,
color = AppTheme.colors.onSurfaceVariant,
textAlign = if (isCompact) TextAlign.Center else TextAlign.Start)
textAlign = if (isCompact) TextAlign.Center else TextAlign.Start
)
artist.biography?.takeIf { it.isNotBlank() }?.let {
Text(
@ -448,7 +486,8 @@ private fun ArtistMeta(
color = AppTheme.colors.onSurfaceVariant,
maxLines = if (isCompact) 4 else 6,
overflow = TextOverflow.Ellipsis,
textAlign = if (isCompact) TextAlign.Center else TextAlign.Start)
textAlign = if (isCompact) TextAlign.Center else TextAlign.Start
)
}
}
}
@ -458,19 +497,23 @@ private fun ArtistHeaderActions(
isSaved: Boolean,
onFollowClick: () -> Unit,
isBlacklisted: Boolean,
onBlacklistClick: () -> Unit) {
onBlacklistClick: () -> Unit
) {
Row(
horizontalArrangement = Arrangement.spacedBy(10.dp),
verticalAlignment = Alignment.CenterVertically) {
verticalAlignment = Alignment.CenterVertically
) {
if (isSaved) {
MillenniumButton(
onClick = onFollowClick,
style = MillenniumButtonStyle.Secondary) {
style = MillenniumButtonStyle.Secondary
) {
Text(
"Following",
textAlign = TextAlign.Center,
maxLines = 1,
overflow = TextOverflow.Ellipsis)
overflow = TextOverflow.Ellipsis
)
}
} else {
MillenniumButton(onClick = onFollowClick) {
@ -484,19 +527,23 @@ private fun ArtistHeaderActions(
if (isBlacklisted) {
MillenniumIconButton(
onClick = onBlacklistClick,
style = MillenniumIconButtonStyle.Secondary) {
style = MillenniumIconButtonStyle.Secondary
) {
MillenniumIcon(
imageVector = Iconsax.IconsaxUserRemove,
contentDescription = "Remove from blacklist",
tint = AppTheme.colors.error)
tint = AppTheme.colors.error
)
}
} else {
MillenniumIconButton(
onClick = onBlacklistClick,
style = MillenniumIconButtonStyle.Secondary) {
style = MillenniumIconButtonStyle.Secondary
) {
MillenniumIcon(
imageVector = Iconsax.IconsaxUserRemove,
contentDescription = "Add to blacklist")
contentDescription = "Add to blacklist"
)
}
}
}
@ -506,32 +553,40 @@ private fun ArtistHeaderActions(
@Composable
private fun TopTracksHeader(
onPlay: () -> Unit,
onAddToQueue: () -> Unit) {
onAddToQueue: () -> Unit
) {
Row(
modifier = Modifier
.fillMaxWidth()
.padding(horizontal = 16.dp),
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.SpaceBetween) {
horizontalArrangement = Arrangement.SpaceBetween
) {
Text(
text = "Top Tracks",
style = AppTheme.typography.titleLarge,
fontWeight = FontWeight.SemiBold)
fontWeight = FontWeight.SemiBold
)
Row(horizontalArrangement = Arrangement.spacedBy(10.dp)) {
MillenniumIconButton(
onClick = onPlay,
style = MillenniumIconButtonStyle.Primary) {
style = MillenniumIconButtonStyle.Primary
) {
MillenniumIcon(
imageVector = Iconsax.IconsaxPlay,
contentDescription = "Play top tracks")
contentDescription = "Play top tracks",
tint = LocalTextStyle.current.color
)
}
MillenniumIconButton(
onClick = onAddToQueue,
style = MillenniumIconButtonStyle.Secondary) {
style = MillenniumIconButtonStyle.Secondary
) {
MillenniumIcon(
imageVector = Iconsax.IconsaxAddSquare,
contentDescription = "Add top tracks to queue")
contentDescription = "Add top tracks to queue"
)
}
}
}
@ -541,7 +596,8 @@ private fun TopTracksHeader(
private fun AlbumsSection(
albums: List<MetadataAlbum.Detailed>,
onAlbumClick: (MetadataAlbum.Detailed) -> Unit,
onLoadMore: () -> Unit) {
onLoadMore: () -> Unit
) {
val rowState = rememberLazyListState()
LaunchedEffect(rowState) {
@ -566,7 +622,8 @@ private fun AlbumsSection(
modifier = Modifier.dragScrollable(rowState),
contentPadding = PaddingValues(horizontal = 16.dp),
horizontalArrangement = Arrangement.spacedBy(12.dp),
verticalAlignment = Alignment.Top) {
verticalAlignment = Alignment.Top
) {
items(albums, key = { it.id }) { album ->
AlbumCard(album = album)
}
@ -577,7 +634,8 @@ private fun AlbumsSection(
private fun RelatedArtistsSection(
artists: List<MetadataArtist.Basic>,
onArtistClick: (MetadataArtist.Basic) -> Unit,
onLoadMore: () -> Unit) {
onLoadMore: () -> Unit
) {
val rowState = rememberLazyListState()
LaunchedEffect(rowState) {
@ -602,7 +660,8 @@ private fun RelatedArtistsSection(
modifier = Modifier.dragScrollable(rowState),
contentPadding = PaddingValues(horizontal = 16.dp),
horizontalArrangement = Arrangement.spacedBy(12.dp),
verticalAlignment = Alignment.Top) {
verticalAlignment = Alignment.Top
) {
items(artists, key = { it.id }) { artist ->
ArtistCard(artist = artist)
}
@ -613,7 +672,8 @@ private fun RelatedArtistsSection(
private fun FeaturedPlaylistsSection(
playlists: List<MetadataPlaylist>,
onPlaylistClick: (MetadataPlaylist) -> Unit,
onLoadMore: () -> Unit) {
onLoadMore: () -> Unit
) {
val rowState = rememberLazyListState()
LaunchedEffect(rowState) {
@ -638,7 +698,8 @@ private fun FeaturedPlaylistsSection(
modifier = Modifier.dragScrollable(rowState),
contentPadding = PaddingValues(horizontal = 16.dp),
horizontalArrangement = Arrangement.spacedBy(12.dp),
verticalAlignment = Alignment.Top) {
verticalAlignment = Alignment.Top
) {
items(playlists, key = { it.id }) { playlist ->
PlaylistCard(playlist = playlist)
}
@ -651,7 +712,8 @@ private fun SectionHeader(title: String) {
text = title,
style = AppTheme.typography.titleLarge,
fontWeight = FontWeight.SemiBold,
modifier = Modifier.padding(horizontal = 16.dp))
modifier = Modifier.padding(horizontal = 16.dp)
)
}
private fun formatFollowers(count: Int?): String {

View File

@ -50,6 +50,7 @@ import androidx.compose.foundation.verticalScroll
import dev.krtirtho.spotube.core.ui.theming.AppTheme
import com.composeunstyled.Text
import com.composeunstyled.LocalTextStyle
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
@ -86,7 +87,8 @@ import kotlin.time.Duration.Companion.milliseconds
@Composable
fun RemoteControlScreen(
onDisconnect: () -> Unit) {
onDisconnect: () -> Unit
) {
val viewModel = koinViewModel<RemoteControlViewModel>()
val playerState by viewModel.playerState.collectAsStateWithLifecycle()
val connectionState by viewModel.connectionState.collectAsStateWithLifecycle()
@ -98,18 +100,21 @@ fun RemoteControlScreen(
// stretch awkwardly on large screens.
Box(
modifier = Modifier.fillMaxSize(),
contentAlignment = Alignment.TopCenter) {
contentAlignment = Alignment.TopCenter
) {
Column(
modifier = Modifier
.fillMaxHeight()
.background(MillenniumTheme.colors.background)) {
.background(MillenniumTheme.colors.background)
) {
ApplicationMainBar(
title = { Text("Remote Control") },
backButton = true,
actions = {
MillenniumIconButton(
onClick = viewModel::toggleQueueVisibility,
style = MillenniumIconButtonStyle.Quiet) {
style = MillenniumIconButtonStyle.Quiet
) {
MillenniumIcon(
imageVector = Iconsax.IconsaxMusicFilter,
contentDescription = "Queue",
@ -117,17 +122,20 @@ fun RemoteControlScreen(
AppTheme.colors.primary
} else {
AppTheme.colors.onSurfaceVariant
})
}
)
}
MillenniumIconButton(
onClick = {
viewModel.disconnect()
onDisconnect()
},
style = MillenniumIconButtonStyle.Quiet) {
style = MillenniumIconButtonStyle.Quiet
) {
MillenniumIcon(
imageVector = Iconsax.IconsaxCloseSquare,
contentDescription = "Disconnect")
contentDescription = "Disconnect"
)
}
}
)
@ -146,6 +154,7 @@ fun RemoteControlScreen(
modifier = Modifier.padding(bottom = shellBottomInset)
)
}
is ConnectionState.Connecting -> {
Box(
modifier = Modifier
@ -156,6 +165,7 @@ fun RemoteControlScreen(
Text("Connecting...")
}
}
is ConnectionState.Disconnected -> {
Box(
modifier = Modifier
@ -166,6 +176,7 @@ fun RemoteControlScreen(
Text("Disconnected")
}
}
is ConnectionState.Error -> {
Box(
modifier = Modifier
@ -198,15 +209,18 @@ fun RemoteControlScreen(
Box(
modifier = Modifier
.fillMaxSize()
.padding(bottom = shellBottomInset)) {
.padding(bottom = shellBottomInset)
) {
QueueSheet(
isVisible = isQueueVisible,
onDismiss = { viewModel.toggleQueueVisibility() },
modifier = Modifier.fillMaxSize()) {
modifier = Modifier.fillMaxSize()
) {
RemoteQueueSection(
queueState = queueState,
onPlayQueueItem = viewModel::playQueueItem,
onRemoveQueueItem = viewModel::removeQueueItem)
onRemoveQueueItem = viewModel::removeQueueItem
)
}
}
}
@ -221,15 +235,17 @@ private fun RemoteControlContent(
onSetVolume: (Float) -> Unit,
onToggleShuffle: () -> Unit,
onCycleLoopMode: () -> Unit,
modifier: Modifier = Modifier) {
Box (modifier = Modifier.fillMaxSize()) {
modifier: Modifier = Modifier
) {
Box(modifier = Modifier.fillMaxSize()) {
Column(
modifier = modifier
.verticalScroll(rememberScrollState())
.padding(horizontal = 24.dp)
.widthIn(max = 480.dp)
.align(Alignment.TopCenter),
horizontalAlignment = Alignment.CenterHorizontally) {
horizontalAlignment = Alignment.CenterHorizontally
) {
Spacer(modifier = Modifier.height(32.dp))
// Album art
@ -238,22 +254,26 @@ private fun RemoteControlContent(
.fillMaxWidth()
.aspectRatio(1f)
.clip(RoundedCornerShape(16.dp))
.background(AppTheme.colors.surfaceVariant)) {
.background(AppTheme.colors.surfaceVariant)
) {
if (playerState.currentTrackCoverUrl != null) {
AsyncImage(
model = playerState.currentTrackCoverUrl,
contentDescription = "Album cover",
modifier = Modifier.fillMaxSize(),
contentScale = ContentScale.Crop)
contentScale = ContentScale.Crop
)
} else {
Box(
modifier = Modifier.fillMaxSize(),
contentAlignment = Alignment.Center) {
contentAlignment = Alignment.Center
) {
MillenniumIcon(
imageVector = Iconsax.IconsaxMusicFilter,
contentDescription = null,
tint = AppTheme.colors.onSurfaceVariant,
modifier = Modifier.size(48.dp))
modifier = Modifier.size(48.dp)
)
}
}
}
@ -263,14 +283,16 @@ private fun RemoteControlContent(
// Track info
Column(
modifier = Modifier.fillMaxWidth(),
horizontalAlignment = Alignment.CenterHorizontally) {
horizontalAlignment = Alignment.CenterHorizontally
) {
Text(
text = playerState.currentTrackTitle ?: "Unknown Track",
style = AppTheme.typography.headlineSmall,
fontWeight = FontWeight.Bold,
textAlign = TextAlign.Center,
maxLines = 2,
overflow = TextOverflow.Ellipsis)
overflow = TextOverflow.Ellipsis
)
Spacer(modifier = Modifier.height(8.dp))
@ -280,7 +302,8 @@ private fun RemoteControlContent(
color = AppTheme.colors.onSurfaceVariant,
textAlign = TextAlign.Center,
maxLines = 1,
overflow = TextOverflow.Ellipsis)
overflow = TextOverflow.Ellipsis
)
if (playerState.currentTrackAlbum != null) {
Spacer(modifier = Modifier.height(4.dp))
@ -290,7 +313,8 @@ private fun RemoteControlContent(
color = AppTheme.colors.onSurfaceVariant.copy(alpha = 0.7f),
textAlign = TextAlign.Center,
maxLines = 1,
overflow = TextOverflow.Ellipsis)
overflow = TextOverflow.Ellipsis
)
}
}
@ -298,25 +322,30 @@ private fun RemoteControlContent(
// Seek bar
Column(
modifier = Modifier.fillMaxWidth()) {
modifier = Modifier.fillMaxWidth()
) {
MillenniumSlider(
value = playerState.positionMs.toFloat(),
onValueChange = { onSeek(it.toLong()) },
label = "Seek",
valueRange = 0f..playerState.durationMs.toFloat().coerceAtLeast(1f),
modifier = Modifier.fillMaxWidth())
modifier = Modifier.fillMaxWidth()
)
Row(
modifier = Modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.SpaceBetween) {
horizontalArrangement = Arrangement.SpaceBetween
) {
Text(
text = formatDuration(playerState.positionMs),
style = AppTheme.typography.bodySmall,
color = AppTheme.colors.onSurfaceVariant)
color = AppTheme.colors.onSurfaceVariant
)
Text(
text = formatDuration(playerState.durationMs),
style = AppTheme.typography.bodySmall,
color = AppTheme.colors.onSurfaceVariant)
color = AppTheme.colors.onSurfaceVariant
)
}
}
@ -326,11 +355,13 @@ private fun RemoteControlContent(
Row(
modifier = Modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.SpaceEvenly,
verticalAlignment = Alignment.CenterVertically) {
verticalAlignment = Alignment.CenterVertically
) {
// Shuffle
MillenniumIconButton(
onClick = onToggleShuffle,
style = MillenniumIconButtonStyle.Quiet) {
style = MillenniumIconButtonStyle.Quiet
) {
MillenniumIcon(
imageVector = Iconsax.IconsaxShuffle,
contentDescription = "Shuffle",
@ -338,23 +369,27 @@ private fun RemoteControlContent(
AppTheme.colors.primary
} else {
AppTheme.colors.onSurfaceVariant
})
}
)
}
// Skip previous
MillenniumIconButton(
onClick = onSkipPrevious,
style = MillenniumIconButtonStyle.Quiet) {
style = MillenniumIconButtonStyle.Quiet
) {
MillenniumIcon(
imageVector = Iconsax.IconsaxPrevious,
contentDescription = "Previous",
modifier = Modifier.size(32.dp))
modifier = Modifier.size(32.dp)
)
}
// Play/Pause
MillenniumIconButton(
onClick = onTogglePlayPause,
style = MillenniumIconButtonStyle.Primary) {
style = MillenniumIconButtonStyle.Primary
) {
MillenniumIcon(
imageVector = if (playerState.isPlaying) {
Iconsax.IconsaxPause
@ -362,23 +397,28 @@ private fun RemoteControlContent(
Iconsax.IconsaxPlay
},
contentDescription = if (playerState.isPlaying) "Pause" else "Play",
modifier = Modifier.size(40.dp))
modifier = Modifier.size(40.dp),
tint = LocalTextStyle.current.color
)
}
// Skip next
MillenniumIconButton(
onClick = onSkipNext,
style = MillenniumIconButtonStyle.Quiet) {
style = MillenniumIconButtonStyle.Quiet
) {
MillenniumIcon(
imageVector = Iconsax.IconsaxNext,
contentDescription = "Next",
modifier = Modifier.size(32.dp))
modifier = Modifier.size(32.dp)
)
}
// Loop mode
MillenniumIconButton(
onClick = onCycleLoopMode,
style = MillenniumIconButtonStyle.Quiet) {
style = MillenniumIconButtonStyle.Quiet
) {
MillenniumIcon(
imageVector = Iconsax.IconsaxRepeateMusic,
contentDescription = "Loop mode",
@ -386,7 +426,8 @@ private fun RemoteControlContent(
AppTheme.colors.primary
} else {
AppTheme.colors.onSurfaceVariant
})
}
)
}
}
@ -396,19 +437,22 @@ private fun RemoteControlContent(
Row(
modifier = Modifier.fillMaxWidth(),
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.spacedBy(12.dp)) {
horizontalArrangement = Arrangement.spacedBy(12.dp)
) {
MillenniumIcon(
imageVector = Iconsax.IconsaxVolumeHigh,
contentDescription = "Volume",
tint = AppTheme.colors.onSurfaceVariant,
modifier = Modifier.size(24.dp))
modifier = Modifier.size(24.dp)
)
MillenniumSlider(
value = playerState.volume,
onValueChange = onSetVolume,
label = "Volume",
valueRange = 0f..1f,
modifier = Modifier.weight(1f))
modifier = Modifier.weight(1f)
)
}
Spacer(modifier = Modifier.height(32.dp))
@ -420,26 +464,31 @@ private fun RemoteControlContent(
private fun RemoteQueueSection(
queueState: RemoteQueueState,
onPlayQueueItem: (Int) -> Unit,
onRemoveQueueItem: (String) -> Unit) {
onRemoveQueueItem: (String) -> Unit
) {
Column(
modifier = Modifier
.fillMaxSize()
.padding(16.dp),
verticalArrangement = Arrangement.spacedBy(10.dp)) {
verticalArrangement = Arrangement.spacedBy(10.dp)
) {
Text(
text = "Queue",
style = AppTheme.typography.titleLarge)
style = AppTheme.typography.titleLarge
)
if (queueState.entries.isEmpty()) {
Text(
text = "No queue entries",
style = AppTheme.typography.bodyMedium,
color = AppTheme.colors.onSurfaceVariant)
color = AppTheme.colors.onSurfaceVariant
)
} else {
LazyColumn(
modifier = Modifier.fillMaxSize(),
contentPadding = PaddingValues(bottom = 8.dp),
verticalArrangement = Arrangement.spacedBy(4.dp)) {
verticalArrangement = Arrangement.spacedBy(4.dp)
) {
itemsIndexed(
items = queueState.entries,
key = { index, entry -> "${entry.mediaUrl}@$index" }) { index, entry ->
@ -461,7 +510,8 @@ private fun RemoteQueueItemRow(
index: Int,
isCurrent: Boolean,
onPlayClick: () -> Unit,
onRemoveClick: () -> Unit) {
onRemoveClick: () -> Unit
) {
var showMenu by remember { mutableStateOf(false) }
MillenniumListTile(
@ -474,18 +524,21 @@ private fun RemoteQueueItemRow(
.size(48.dp)
.clip(AppTheme.shapes.small)
.background(AppTheme.colors.surfaceVariant),
contentAlignment = Alignment.Center) {
contentAlignment = Alignment.Center
) {
if (entry.coverUrl != null) {
AsyncImage(
model = entry.coverUrl,
contentDescription = null,
modifier = Modifier.fillMaxSize(),
contentScale = ContentScale.Crop)
contentScale = ContentScale.Crop
)
} else {
Text(
text = "${index + 1}",
style = AppTheme.typography.labelMedium,
color = AppTheme.colors.onSurfaceVariant)
color = AppTheme.colors.onSurfaceVariant
)
}
}
},
@ -499,7 +552,8 @@ private fun RemoteQueueItemRow(
AppTheme.colors.onSecondaryContainer
} else {
AppTheme.colors.onSurface
})
}
)
},
subtitle = {
Text(
@ -507,13 +561,15 @@ private fun RemoteQueueItemRow(
style = AppTheme.typography.bodySmall,
color = AppTheme.colors.onSurfaceVariant,
maxLines = 1,
overflow = TextOverflow.Ellipsis)
overflow = TextOverflow.Ellipsis
)
},
trailing = {
Text(
text = formatDuration(entry.durationMs),
style = AppTheme.typography.bodySmall,
color = AppTheme.colors.onSurfaceVariant)
color = AppTheme.colors.onSurfaceVariant
)
MillenniumDropdownMenu(
expanded = showMenu,
@ -526,11 +582,13 @@ private fun RemoteQueueItemRow(
anchor = {
MillenniumIconButton(
onClick = { showMenu = true },
style = MillenniumIconButtonStyle.Quiet) {
style = MillenniumIconButtonStyle.Quiet
) {
MillenniumIcon(
Iconsax.Iconsax3DotsMore,
contentDescription = "More options",
modifier = Modifier.size(18.dp))
modifier = Modifier.size(18.dp)
)
}
})
}

View File

@ -65,6 +65,7 @@ import com.composeunstyled.rememberModalBottomSheetState
import androidx.compose.ui.graphics.SolidColor
import com.composeunstyled.Text
import com.composeunstyled.LocalTextStyle
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.collectAsState
@ -158,7 +159,8 @@ fun AppExpandedPlayer(
savedTracksViewModel: SavedTracksViewModel = koinViewModel<SavedTracksViewModel>(
key = SAVED_TRACKS_COLLECTION_ID,
parameters = { parametersOf() }
)) {
)
) {
val playerUiState = rememberPlayerUiState(audioPlayer, audioPlayerQueue)
val jamRoomService: JamRoomService = koinInject()
val isJamGuest by jamRoomService.role
@ -185,7 +187,8 @@ fun AppExpandedPlayer(
var isSeeking by remember { mutableStateOf(false) }
var seekProgress by remember { mutableFloatStateOf(playerUiState.progress) }
val moreOptionsSheetState = rememberModalBottomSheetState(
initialDetent = SheetDetent.FullyExpanded)
initialDetent = SheetDetent.FullyExpanded
)
PlayerOptionDialogs(viewModel = playerOptionsViewModel)
@ -262,7 +265,8 @@ fun AppExpandedPlayer(
contentPadding = PaddingValues(16.dp),
horizontalArrangement = Arrangement.spacedBy(12.dp),
verticalArrangement = Arrangement.spacedBy(12.dp),
modifier = Modifier.fillMaxWidth()) {
modifier = Modifier.fillMaxWidth()
) {
item {
OptionTile(
icon = Iconsax.SwapHorizontal2,
@ -297,7 +301,8 @@ fun AppExpandedPlayer(
shape = RoundedCornerShape(14.dp),
borderColor = MillenniumTheme.colors.border,
borderWidth = 1.dp,
shadowElevation = 2.dp)
shadowElevation = 2.dp
)
.clickable {
playerOptionsViewModel.dismissMoreOptionsSheet()
when (status) {
@ -337,14 +342,16 @@ fun AppExpandedPlayer(
.fillMaxSize()
.padding(12.dp),
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.Center) {
verticalArrangement = Arrangement.Center
) {
if (status is DownloadStatus.Downloading) {
DownloadProgressIcon(
track = currentTrack,
icon = Iconsax.IconsaxDirectboxReceive,
contentDescription = "Download",
modifier = Modifier.size(28.dp),
tint = AppTheme.colors.onSurface)
tint = AppTheme.colors.onSurface
)
} else {
val icon = when (status) {
is DownloadStatus.Completed -> Iconsax.IconsaxCheckCircle
@ -361,7 +368,8 @@ fun AppExpandedPlayer(
is DownloadStatus.Completed -> AppTheme.colors.primary
is DownloadStatus.Failed -> AppTheme.colors.error
else -> AppTheme.colors.onSurface
})
}
)
}
Spacer(modifier = Modifier.height(8.dp))
Text(
@ -370,7 +378,8 @@ fun AppExpandedPlayer(
textAlign = TextAlign.Center,
maxLines = 2,
overflow = TextOverflow.Ellipsis,
color = statusColor)
color = statusColor
)
}
}
}
@ -416,7 +425,8 @@ fun AppExpandedPlayer(
.windowInsetsPadding(WindowInsets.safeDrawing)
.verticalScroll(rememberScrollState())
.padding(horizontal = 16.dp),
horizontalAlignment = Alignment.CenterHorizontally) {
horizontalAlignment = Alignment.CenterHorizontally
) {
Spacer(modifier = Modifier.height(4.dp))
Row(
modifier = Modifier
@ -425,18 +435,24 @@ fun AppExpandedPlayer(
) {
MillenniumIconButton(
onClick = onCollapse,
style = MillenniumIconButtonStyle.Quiet) {
MillenniumIcon(Iconsax.IconsaxArrowDown4, contentDescription = "Collapse player")
style = MillenniumIconButtonStyle.Quiet
) {
MillenniumIcon(
Iconsax.IconsaxArrowDown4,
contentDescription = "Collapse player"
)
}
Text(
text = "Now Playing",
style = AppTheme.typography.titleMedium,
modifier = Modifier.weight(1f),
textAlign = TextAlign.Center,
maxLines = 1)
maxLines = 1
)
MillenniumIconButton(
onClick = playerOptionsViewModel::showMoreOptionsSheet,
style = MillenniumIconButtonStyle.Quiet) {
style = MillenniumIconButtonStyle.Quiet
) {
MillenniumIcon(Iconsax.Iconsax3DotsMore, contentDescription = "Player options")
}
}
@ -445,7 +461,8 @@ fun AppExpandedPlayer(
modifier = Modifier
.fillMaxWidth()
.padding(top = 20.dp, bottom = 24.dp),
contentAlignment = Alignment.Center) {
contentAlignment = Alignment.Center
) {
Box(
modifier = Modifier
.fillMaxWidth(0.84f)
@ -458,7 +475,8 @@ fun AppExpandedPlayer(
model = coverModel,
contentDescription = playerUiState.title,
modifier = Modifier.fillMaxSize(),
contentScale = ContentScale.Crop)
contentScale = ContentScale.Crop
)
}
}
@ -471,16 +489,19 @@ fun AppExpandedPlayer(
) {
PlayerHeartButton(
audioPlayerQueue = audioPlayerQueue,
savedTracksViewModel = savedTracksViewModel)
savedTracksViewModel = savedTracksViewModel
)
Column(
modifier = Modifier.weight(1f),
horizontalAlignment = Alignment.CenterHorizontally) {
horizontalAlignment = Alignment.CenterHorizontally
) {
Text(
text = playerUiState.title,
style = AppTheme.typography.headlineSmall,
maxLines = 1,
overflow = TextOverflow.Ellipsis,
textAlign = TextAlign.Center)
textAlign = TextAlign.Center
)
PlayerArtistsText(
artists = playerUiState.artistList,
onArtistClick = { artistId ->
@ -488,7 +509,8 @@ fun AppExpandedPlayer(
},
style = AppTheme.typography.bodyLarge,
color = AppTheme.colors.onSurfaceVariant,
textAlign = TextAlign.Center)
textAlign = TextAlign.Center
)
}
MillenniumIconButton(
onClick = onQueue,
@ -518,26 +540,31 @@ fun AppExpandedPlayer(
.fillMaxWidth()
.padding(top = 2.dp),
horizontalArrangement = Arrangement.SpaceBetween,
verticalAlignment = Alignment.CenterVertically) {
verticalAlignment = Alignment.CenterVertically
) {
Text(
text = formatPlayerTime(playerUiState.position),
style = AppTheme.typography.labelSmall,
color = AppTheme.colors.onSurfaceVariant)
color = AppTheme.colors.onSurfaceVariant
)
Text(
text = formatPlayerTime(playerUiState.displayDuration),
style = AppTheme.typography.labelSmall,
color = AppTheme.colors.onSurfaceVariant)
color = AppTheme.colors.onSurfaceVariant
)
}
Row(
modifier = Modifier
.fillMaxWidth()
.padding(top = 16.dp, bottom = 12.dp),
horizontalArrangement = Arrangement.SpaceBetween,
verticalAlignment = Alignment.CenterVertically) {
verticalAlignment = Alignment.CenterVertically
) {
MillenniumIconButton(
onClick = ::onShuffleToggle,
enabled = !isJamGuest,
style = MillenniumIconButtonStyle.Quiet) {
style = MillenniumIconButtonStyle.Quiet
) {
MillenniumIcon(
Iconsax.IconsaxShuffle,
contentDescription = if (playerUiState.isShuffling) "Disable shuffle" else "Enable shuffle",
@ -551,27 +578,33 @@ fun AppExpandedPlayer(
MillenniumIconButton(
onClick = ::onSkipPrevious,
enabled = !isJamGuest,
style = MillenniumIconButtonStyle.Quiet) {
style = MillenniumIconButtonStyle.Quiet
) {
MillenniumIcon(Iconsax.IconsaxPrevious, contentDescription = "Previous")
}
MillenniumIconButton(
onClick = ::onPlayPause,
style = MillenniumIconButtonStyle.Primary) {
style = MillenniumIconButtonStyle.Primary
) {
MillenniumIcon(
if (playerUiState.isPlaying) Iconsax.IconsaxPause else Iconsax.IconsaxPlay,
contentDescription = if (playerUiState.isPlaying) "Pause" else "Play",
modifier = Modifier.size(30.dp))
modifier = Modifier.size(30.dp),
tint = LocalTextStyle.current.color
)
}
MillenniumIconButton(
onClick = ::onSkipNext,
enabled = !isJamGuest,
style = MillenniumIconButtonStyle.Quiet) {
style = MillenniumIconButtonStyle.Quiet
) {
MillenniumIcon(Iconsax.IconsaxNext, contentDescription = "Next")
}
MillenniumIconButton(
onClick = ::onLoopToggle,
enabled = !isJamGuest,
style = MillenniumIconButtonStyle.Quiet) {
style = MillenniumIconButtonStyle.Quiet
) {
MillenniumIcon(
imageVector = when (playerUiState.loopState) {
LoopState.NONE -> Iconsax.IconsaxRepeateMusic
@ -594,12 +627,14 @@ fun AppExpandedPlayer(
.fillMaxWidth()
.padding(bottom = 16.dp),
audioPlayer = audioPlayer,
onExpand = onExpandLyrics)
onExpand = onExpandLyrics
)
}
MillenniumSnackbarHost(
hostState = snackbarHostState,
modifier = Modifier.align(Alignment.BottomCenter))
modifier = Modifier.align(Alignment.BottomCenter)
)
}
}
@ -607,13 +642,15 @@ fun AppExpandedPlayer(
@Composable
private fun rememberSharedAlbumArtModifier(
sharedTransitionScope: SharedTransitionScope?,
animatedVisibilityScope: AnimatedVisibilityScope?): Modifier {
animatedVisibilityScope: AnimatedVisibilityScope?
): Modifier {
if (sharedTransitionScope == null || animatedVisibilityScope == null) return Modifier
with(sharedTransitionScope) {
return Modifier.sharedElement(
sharedContentState = rememberSharedContentState(key = "player_album_art"),
animatedVisibilityScope = animatedVisibilityScope)
animatedVisibilityScope = animatedVisibilityScope
)
}
}
@ -660,24 +697,30 @@ private fun LyricsPreviewCard(
.millenniumSurface(
background = SolidColor(AppTheme.colors.surfaceContainerHighest),
shape = RoundedCornerShape(16.dp),
shadowElevation = 2.dp)
.clickable(onClick = onExpand)) {
shadowElevation = 2.dp
)
.clickable(onClick = onExpand)
) {
Column(modifier = Modifier.padding(12.dp)) {
Row(
modifier = Modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.SpaceBetween,
verticalAlignment = Alignment.CenterVertically) {
verticalAlignment = Alignment.CenterVertically
) {
Text(
text = "Lyrics",
style = AppTheme.typography.labelMedium,
color = AppTheme.colors.onSurfaceVariant)
color = AppTheme.colors.onSurfaceVariant
)
MillenniumIconButton(
onClick = onExpand,
style = MillenniumIconButtonStyle.Quiet) {
style = MillenniumIconButtonStyle.Quiet
) {
MillenniumIcon(
Iconsax.IconsaxArrowSquareUp,
contentDescription = "Expand lyrics",
tint = AppTheme.colors.onSurfaceVariant)
tint = AppTheme.colors.onSurfaceVariant
)
}
}
@ -687,7 +730,8 @@ private fun LyricsPreviewCard(
.fillMaxWidth()
.height(120.dp),
contentPadding = PaddingValues(vertical = 4.dp),
verticalArrangement = Arrangement.spacedBy(2.dp)) {
verticalArrangement = Arrangement.spacedBy(2.dp)
) {
val displayLines = syncedLyrics.take(6)
itemsIndexed(displayLines) { index, line ->
val isCurrent =
@ -707,7 +751,8 @@ private fun LyricsPreviewCard(
),
maxLines = 1,
overflow = TextOverflow.Ellipsis,
textAlign = TextAlign.Center)
textAlign = TextAlign.Center
)
}
}
}
@ -719,21 +764,25 @@ private fun OptionTile(
icon: ImageVector,
label: String,
onClick: () -> Unit,
modifier: Modifier = Modifier) {
modifier: Modifier = Modifier
) {
MillenniumCard(
modifier = modifier
.aspectRatio(1f)
.clickable(onClick = onClick),
contentPadding = PaddingValues(12.dp)) {
contentPadding = PaddingValues(12.dp)
) {
Box(Modifier.fillMaxSize(), contentAlignment = Alignment.Center) {
Column(
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.Center) {
verticalArrangement = Arrangement.Center
) {
MillenniumIcon(
imageVector = icon,
contentDescription = label,
modifier = Modifier.size(28.dp),
tint = AppTheme.colors.onSurface)
tint = AppTheme.colors.onSurface
)
Spacer(modifier = Modifier.height(8.dp))
Text(
text = label,
@ -741,7 +790,8 @@ private fun OptionTile(
textAlign = TextAlign.Center,
maxLines = 2,
overflow = TextOverflow.Ellipsis,
color = AppTheme.colors.onSurfaceVariant)
color = AppTheme.colors.onSurfaceVariant
)
}
}
}

View File

@ -18,12 +18,6 @@
package dev.krtirtho.spotube.modules.shell
import org.spotube.millennium.composables.MillenniumIcon
import org.spotube.millennium.composables.MillenniumDropdownMenu
import org.spotube.millennium.composables.MillenniumIconButton
import org.spotube.millennium.composables.MillenniumIconButtonStyle
import org.spotube.millennium.composables.MillenniumSeparator
import org.spotube.millennium.composables.MillenniumSlider
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
@ -38,12 +32,7 @@ import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.layout.windowInsetsPadding
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.foundation.shape.RoundedCornerShape
import dev.krtirtho.spotube.core.ui.theming.AppTheme
import com.composeunstyled.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
@ -55,13 +44,14 @@ import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.dp
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import coil3.compose.AsyncImage
import com.composeunstyled.AnchorSide
import com.composeunstyled.Text
import com.composeunstyled.LocalTextStyle
import dev.chrisbanes.haze.HazeState
import dev.chrisbanes.haze.blur.HazeColorEffect
import dev.chrisbanes.haze.blur.blurEffect
@ -74,15 +64,14 @@ import dev.krtirtho.spotube.core.jam.JamRole
import dev.krtirtho.spotube.core.jam.JamRoomService
import dev.krtirtho.spotube.core.navigation.NavigationCommands
import dev.krtirtho.spotube.core.navigation.Routes
import dev.krtirtho.spotube.core.ui.theming.AppTheme
import dev.krtirtho.spotube.modules.downloads.DownloadProgressIcon
import dev.krtirtho.spotube.modules.downloads.DownloadsViewModel
import dev.krtirtho.spotube.modules.saved_tracks.SAVED_TRACKS_COLLECTION_ID
import dev.krtirtho.spotube.modules.saved_tracks.SavedTracksViewModel
import dev.krtirtho.spotube.resources.iconsax.Iconsax
import dev.krtirtho.spotube.resources.iconsax.Iconsax3DotsMore
import dev.krtirtho.spotube.resources.iconsax.IconsaxCd
import dev.krtirtho.spotube.resources.iconsax.IconsaxDirectboxReceive
import dev.krtirtho.spotube.resources.iconsax.IconsaxInformation
import dev.krtirtho.spotube.resources.iconsax.IconsaxMusic
import dev.krtirtho.spotube.resources.iconsax.IconsaxMusicFilter
import dev.krtirtho.spotube.resources.iconsax.IconsaxNext
@ -96,7 +85,6 @@ import dev.krtirtho.spotube.resources.iconsax.IconsaxShuffle
import dev.krtirtho.spotube.resources.iconsax.IconsaxVolumeCross
import dev.krtirtho.spotube.resources.iconsax.IconsaxVolumeHigh
import dev.krtirtho.spotube.resources.iconsax.IconsaxVolumeLow
import dev.krtirtho.spotube.resources.iconsax.InconsaxClock
import dev.krtirtho.spotube.resources.iconsax.SwapHorizontal2
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.launch
@ -104,6 +92,12 @@ import org.jetbrains.compose.resources.stringResource
import org.koin.compose.koinInject
import org.koin.compose.viewmodel.koinViewModel
import org.koin.core.parameter.parametersOf
import org.spotube.millennium.composables.MillenniumDropdownMenu
import org.spotube.millennium.composables.MillenniumIcon
import org.spotube.millennium.composables.MillenniumIconButton
import org.spotube.millennium.composables.MillenniumIconButtonStyle
import org.spotube.millennium.composables.MillenniumSeparator
import org.spotube.millennium.composables.MillenniumSlider
import spotube.composeapp.generated.resources.Res
import spotube.composeapp.generated.resources.player_go_to_album
import spotube.composeapp.generated.resources.player_sleep_timer
@ -346,7 +340,8 @@ fun AppLargePlayer(
) {
MillenniumIcon(
if (playerUiState.isPlaying) Iconsax.IconsaxPause else Iconsax.IconsaxPlay,
contentDescription = if (playerUiState.isPlaying) "Pause" else "Play or pause"
contentDescription = if (playerUiState.isPlaying) "Pause" else "Play or pause",
tint = LocalTextStyle.current.color
)
}
MillenniumIconButton(