Improve UI in landscape mode
This commit is contained in:
@@ -0,0 +1,71 @@
|
|||||||
|
package it.vfsfitvnm.vimusic.ui.components.themed
|
||||||
|
|
||||||
|
import androidx.compose.foundation.background
|
||||||
|
import androidx.compose.foundation.layout.BoxWithConstraints
|
||||||
|
import androidx.compose.foundation.layout.Row
|
||||||
|
import androidx.compose.foundation.layout.Spacer
|
||||||
|
import androidx.compose.foundation.layout.padding
|
||||||
|
import androidx.compose.foundation.layout.size
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.ui.Alignment
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.draw.clip
|
||||||
|
import androidx.compose.ui.graphics.Shape
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
|
import coil.compose.AsyncImage
|
||||||
|
import com.valentinilk.shimmer.shimmer
|
||||||
|
import it.vfsfitvnm.vimusic.ui.styling.LocalAppearance
|
||||||
|
import it.vfsfitvnm.vimusic.ui.styling.px
|
||||||
|
import it.vfsfitvnm.vimusic.ui.styling.shimmer
|
||||||
|
import it.vfsfitvnm.vimusic.utils.isLandscape
|
||||||
|
import it.vfsfitvnm.vimusic.utils.thumbnail
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
inline fun LayoutWithAdaptiveThumbnail(
|
||||||
|
thumbnailContent: @Composable () -> Unit,
|
||||||
|
content: @Composable () -> Unit
|
||||||
|
) {
|
||||||
|
val isLandscape = isLandscape
|
||||||
|
|
||||||
|
if (isLandscape) {
|
||||||
|
Row(verticalAlignment = Alignment.CenterVertically) {
|
||||||
|
thumbnailContent()
|
||||||
|
content()
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
content()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun adaptiveThumbnailContent(
|
||||||
|
isLoading: Boolean,
|
||||||
|
url: String?,
|
||||||
|
shape: Shape? = null
|
||||||
|
): @Composable () -> Unit = {
|
||||||
|
val (colorPalette, _, thumbnailShape) = LocalAppearance.current
|
||||||
|
|
||||||
|
BoxWithConstraints(contentAlignment = Alignment.Center) {
|
||||||
|
val size = if (isLandscape) maxHeight else maxWidth
|
||||||
|
val thumbnailSizeDp = size - 64.dp
|
||||||
|
val thumbnailSizePx = thumbnailSizeDp.px
|
||||||
|
|
||||||
|
val modifier = Modifier
|
||||||
|
.padding(all = 16.dp)
|
||||||
|
.clip(shape ?: thumbnailShape)
|
||||||
|
.size(thumbnailSizeDp)
|
||||||
|
|
||||||
|
if (isLoading) {
|
||||||
|
Spacer(
|
||||||
|
modifier = modifier
|
||||||
|
.shimmer()
|
||||||
|
.background(colorPalette.shimmer)
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
AsyncImage(
|
||||||
|
model = url?.thumbnail(thumbnailSizePx),
|
||||||
|
contentDescription = null,
|
||||||
|
modifier = modifier
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,6 +1,5 @@
|
|||||||
package it.vfsfitvnm.vimusic.ui.components.themed
|
package it.vfsfitvnm.vimusic.ui.components.themed
|
||||||
|
|
||||||
import android.content.res.Configuration
|
|
||||||
import androidx.compose.animation.animateColor
|
import androidx.compose.animation.animateColor
|
||||||
import androidx.compose.animation.core.animateFloat
|
import androidx.compose.animation.core.animateFloat
|
||||||
import androidx.compose.animation.core.updateTransition
|
import androidx.compose.animation.core.updateTransition
|
||||||
@@ -27,12 +26,12 @@ import androidx.compose.ui.draw.rotate
|
|||||||
import androidx.compose.ui.graphics.ColorFilter
|
import androidx.compose.ui.graphics.ColorFilter
|
||||||
import androidx.compose.ui.graphics.graphicsLayer
|
import androidx.compose.ui.graphics.graphicsLayer
|
||||||
import androidx.compose.ui.layout.layout
|
import androidx.compose.ui.layout.layout
|
||||||
import androidx.compose.ui.platform.LocalConfiguration
|
|
||||||
import androidx.compose.ui.res.painterResource
|
import androidx.compose.ui.res.painterResource
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import it.vfsfitvnm.vimusic.LocalPlayerAwarePaddingValues
|
import it.vfsfitvnm.vimusic.LocalPlayerAwarePaddingValues
|
||||||
import it.vfsfitvnm.vimusic.ui.styling.Dimensions
|
import it.vfsfitvnm.vimusic.ui.styling.Dimensions
|
||||||
import it.vfsfitvnm.vimusic.ui.styling.LocalAppearance
|
import it.vfsfitvnm.vimusic.ui.styling.LocalAppearance
|
||||||
|
import it.vfsfitvnm.vimusic.utils.isLandscape
|
||||||
import it.vfsfitvnm.vimusic.utils.semiBold
|
import it.vfsfitvnm.vimusic.utils.semiBold
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
@@ -45,9 +44,8 @@ fun NavigationRail(
|
|||||||
modifier: Modifier = Modifier
|
modifier: Modifier = Modifier
|
||||||
) {
|
) {
|
||||||
val (colorPalette, typography) = LocalAppearance.current
|
val (colorPalette, typography) = LocalAppearance.current
|
||||||
val configuration = LocalConfiguration.current
|
|
||||||
|
|
||||||
val isLandscape = configuration.orientation == Configuration.ORIENTATION_LANDSCAPE
|
val isLandscape = isLandscape
|
||||||
|
|
||||||
Column(
|
Column(
|
||||||
horizontalAlignment = Alignment.CenterHorizontally,
|
horizontalAlignment = Alignment.CenterHorizontally,
|
||||||
|
|||||||
@@ -3,25 +3,16 @@ package it.vfsfitvnm.vimusic.ui.screens.album
|
|||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import androidx.compose.animation.ExperimentalAnimationApi
|
import androidx.compose.animation.ExperimentalAnimationApi
|
||||||
import androidx.compose.foundation.ExperimentalFoundationApi
|
import androidx.compose.foundation.ExperimentalFoundationApi
|
||||||
import androidx.compose.foundation.background
|
|
||||||
import androidx.compose.foundation.clickable
|
import androidx.compose.foundation.clickable
|
||||||
import androidx.compose.foundation.layout.BoxWithConstraints
|
|
||||||
import androidx.compose.foundation.layout.ColumnScope
|
|
||||||
import androidx.compose.foundation.layout.Spacer
|
import androidx.compose.foundation.layout.Spacer
|
||||||
import androidx.compose.foundation.layout.fillMaxWidth
|
|
||||||
import androidx.compose.foundation.layout.padding
|
|
||||||
import androidx.compose.foundation.layout.size
|
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
import androidx.compose.runtime.mutableStateOf
|
import androidx.compose.runtime.mutableStateOf
|
||||||
import androidx.compose.runtime.saveable.rememberSaveable
|
import androidx.compose.runtime.saveable.rememberSaveable
|
||||||
import androidx.compose.runtime.saveable.rememberSaveableStateHolder
|
import androidx.compose.runtime.saveable.rememberSaveableStateHolder
|
||||||
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.platform.LocalContext
|
import androidx.compose.ui.platform.LocalContext
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import coil.compose.AsyncImage
|
|
||||||
import com.valentinilk.shimmer.shimmer
|
import com.valentinilk.shimmer.shimmer
|
||||||
import it.vfsfitvnm.route.RouteHandler
|
import it.vfsfitvnm.route.RouteHandler
|
||||||
import it.vfsfitvnm.vimusic.Database
|
import it.vfsfitvnm.vimusic.Database
|
||||||
@@ -38,6 +29,7 @@ import it.vfsfitvnm.vimusic.ui.components.themed.Header
|
|||||||
import it.vfsfitvnm.vimusic.ui.components.themed.HeaderIconButton
|
import it.vfsfitvnm.vimusic.ui.components.themed.HeaderIconButton
|
||||||
import it.vfsfitvnm.vimusic.ui.components.themed.HeaderPlaceholder
|
import it.vfsfitvnm.vimusic.ui.components.themed.HeaderPlaceholder
|
||||||
import it.vfsfitvnm.vimusic.ui.components.themed.Scaffold
|
import it.vfsfitvnm.vimusic.ui.components.themed.Scaffold
|
||||||
|
import it.vfsfitvnm.vimusic.ui.components.themed.adaptiveThumbnailContent
|
||||||
import it.vfsfitvnm.vimusic.ui.items.AlbumItem
|
import it.vfsfitvnm.vimusic.ui.items.AlbumItem
|
||||||
import it.vfsfitvnm.vimusic.ui.items.AlbumItemPlaceholder
|
import it.vfsfitvnm.vimusic.ui.items.AlbumItemPlaceholder
|
||||||
import it.vfsfitvnm.vimusic.ui.screens.albumRoute
|
import it.vfsfitvnm.vimusic.ui.screens.albumRoute
|
||||||
@@ -45,10 +37,8 @@ import it.vfsfitvnm.vimusic.ui.screens.globalRoutes
|
|||||||
import it.vfsfitvnm.vimusic.ui.screens.searchresult.ItemsPage
|
import it.vfsfitvnm.vimusic.ui.screens.searchresult.ItemsPage
|
||||||
import it.vfsfitvnm.vimusic.ui.styling.LocalAppearance
|
import it.vfsfitvnm.vimusic.ui.styling.LocalAppearance
|
||||||
import it.vfsfitvnm.vimusic.ui.styling.px
|
import it.vfsfitvnm.vimusic.ui.styling.px
|
||||||
import it.vfsfitvnm.vimusic.ui.styling.shimmer
|
|
||||||
import it.vfsfitvnm.vimusic.utils.asMediaItem
|
import it.vfsfitvnm.vimusic.utils.asMediaItem
|
||||||
import it.vfsfitvnm.vimusic.utils.produceSaveableState
|
import it.vfsfitvnm.vimusic.utils.produceSaveableState
|
||||||
import it.vfsfitvnm.vimusic.utils.thumbnail
|
|
||||||
import it.vfsfitvnm.youtubemusic.Innertube
|
import it.vfsfitvnm.youtubemusic.Innertube
|
||||||
import it.vfsfitvnm.youtubemusic.models.bodies.BrowseBody
|
import it.vfsfitvnm.youtubemusic.models.bodies.BrowseBody
|
||||||
import it.vfsfitvnm.youtubemusic.requests.albumPage
|
import it.vfsfitvnm.youtubemusic.requests.albumPage
|
||||||
@@ -81,7 +71,11 @@ fun AlbumScreen(browseId: String) {
|
|||||||
stateSaver = nullableSaver(InnertubePlaylistOrAlbumPageSaver),
|
stateSaver = nullableSaver(InnertubePlaylistOrAlbumPageSaver),
|
||||||
tabIndex > 0
|
tabIndex > 0
|
||||||
) {
|
) {
|
||||||
if (value != null || (tabIndex == 0 && withContext(Dispatchers.IO) { Database.albumTimestamp(browseId) } != null)) return@produceSaveableState
|
if (value != null || (tabIndex == 0 && withContext(Dispatchers.IO) {
|
||||||
|
Database.albumTimestamp(
|
||||||
|
browseId
|
||||||
|
)
|
||||||
|
} != null)) return@produceSaveableState
|
||||||
|
|
||||||
withContext(Dispatchers.IO) {
|
withContext(Dispatchers.IO) {
|
||||||
Innertube.albumPage(BrowseBody(browseId = browseId))
|
Innertube.albumPage(BrowseBody(browseId = browseId))
|
||||||
@@ -121,94 +115,70 @@ fun AlbumScreen(browseId: String) {
|
|||||||
globalRoutes()
|
globalRoutes()
|
||||||
|
|
||||||
host {
|
host {
|
||||||
val headerContent: @Composable (textButton: (@Composable () -> Unit)?) -> Unit = { textButton ->
|
val headerContent: @Composable (textButton: (@Composable () -> Unit)?) -> Unit =
|
||||||
if (album?.timestamp == null) {
|
{ textButton ->
|
||||||
HeaderPlaceholder(
|
|
||||||
modifier = Modifier
|
|
||||||
.shimmer()
|
|
||||||
)
|
|
||||||
} else {
|
|
||||||
val (colorPalette) = LocalAppearance.current
|
|
||||||
val context = LocalContext.current
|
|
||||||
|
|
||||||
Header(title = album?.title ?: "Unknown") {
|
|
||||||
textButton?.invoke()
|
|
||||||
|
|
||||||
Spacer(
|
|
||||||
modifier = Modifier
|
|
||||||
.weight(1f)
|
|
||||||
)
|
|
||||||
|
|
||||||
HeaderIconButton(
|
|
||||||
icon = if (album?.bookmarkedAt == null) {
|
|
||||||
R.drawable.bookmark_outline
|
|
||||||
} else {
|
|
||||||
R.drawable.bookmark
|
|
||||||
},
|
|
||||||
color = colorPalette.accent,
|
|
||||||
onClick = {
|
|
||||||
val bookmarkedAt =
|
|
||||||
if (album?.bookmarkedAt == null) System.currentTimeMillis() else null
|
|
||||||
|
|
||||||
query {
|
|
||||||
album
|
|
||||||
?.copy(bookmarkedAt = bookmarkedAt)
|
|
||||||
?.let(Database::update)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
HeaderIconButton(
|
|
||||||
icon = R.drawable.share_social,
|
|
||||||
color = colorPalette.text,
|
|
||||||
onClick = {
|
|
||||||
album?.shareUrl?.let { url ->
|
|
||||||
val sendIntent = Intent().apply {
|
|
||||||
action = Intent.ACTION_SEND
|
|
||||||
type = "text/plain"
|
|
||||||
putExtra(Intent.EXTRA_TEXT, url)
|
|
||||||
}
|
|
||||||
|
|
||||||
context.startActivity(Intent.createChooser(sendIntent, null))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
val thumbnailContent: @Composable ColumnScope.() -> Unit = {
|
|
||||||
val (colorPalette, _, thumbnailShape) = LocalAppearance.current
|
|
||||||
|
|
||||||
BoxWithConstraints(
|
|
||||||
contentAlignment = Alignment.Center,
|
|
||||||
modifier = Modifier
|
|
||||||
.fillMaxWidth()
|
|
||||||
) {
|
|
||||||
val thumbnailSizeDp = maxWidth - 64.dp
|
|
||||||
val thumbnailSizePx = thumbnailSizeDp.px
|
|
||||||
|
|
||||||
if (album?.timestamp == null) {
|
if (album?.timestamp == null) {
|
||||||
Spacer(
|
HeaderPlaceholder(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.padding(all = 16.dp)
|
|
||||||
.shimmer()
|
.shimmer()
|
||||||
.clip(thumbnailShape)
|
|
||||||
.size(thumbnailSizeDp)
|
|
||||||
.background(colorPalette.shimmer)
|
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
AsyncImage(
|
val (colorPalette) = LocalAppearance.current
|
||||||
model = album?.thumbnailUrl?.thumbnail(thumbnailSizePx),
|
val context = LocalContext.current
|
||||||
contentDescription = null,
|
|
||||||
modifier = Modifier
|
Header(title = album?.title ?: "Unknown") {
|
||||||
.padding(all = 16.dp)
|
textButton?.invoke()
|
||||||
.clip(thumbnailShape)
|
|
||||||
.size(thumbnailSizeDp)
|
Spacer(
|
||||||
)
|
modifier = Modifier
|
||||||
|
.weight(1f)
|
||||||
|
)
|
||||||
|
|
||||||
|
HeaderIconButton(
|
||||||
|
icon = if (album?.bookmarkedAt == null) {
|
||||||
|
R.drawable.bookmark_outline
|
||||||
|
} else {
|
||||||
|
R.drawable.bookmark
|
||||||
|
},
|
||||||
|
color = colorPalette.accent,
|
||||||
|
onClick = {
|
||||||
|
val bookmarkedAt =
|
||||||
|
if (album?.bookmarkedAt == null) System.currentTimeMillis() else null
|
||||||
|
|
||||||
|
query {
|
||||||
|
album
|
||||||
|
?.copy(bookmarkedAt = bookmarkedAt)
|
||||||
|
?.let(Database::update)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
HeaderIconButton(
|
||||||
|
icon = R.drawable.share_social,
|
||||||
|
color = colorPalette.text,
|
||||||
|
onClick = {
|
||||||
|
album?.shareUrl?.let { url ->
|
||||||
|
val sendIntent = Intent().apply {
|
||||||
|
action = Intent.ACTION_SEND
|
||||||
|
type = "text/plain"
|
||||||
|
putExtra(Intent.EXTRA_TEXT, url)
|
||||||
|
}
|
||||||
|
|
||||||
|
context.startActivity(
|
||||||
|
Intent.createChooser(
|
||||||
|
sendIntent,
|
||||||
|
null
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
val thumbnailContent =
|
||||||
|
adaptiveThumbnailContent(album?.timestamp == null, album?.thumbnailUrl)
|
||||||
|
|
||||||
Scaffold(
|
Scaffold(
|
||||||
topIconButtonId = R.drawable.chevron_back,
|
topIconButtonId = R.drawable.chevron_back,
|
||||||
@@ -227,6 +197,7 @@ fun AlbumScreen(browseId: String) {
|
|||||||
headerContent = headerContent,
|
headerContent = headerContent,
|
||||||
thumbnailContent = thumbnailContent,
|
thumbnailContent = thumbnailContent,
|
||||||
)
|
)
|
||||||
|
|
||||||
1 -> {
|
1 -> {
|
||||||
val thumbnailSizeDp = 108.dp
|
val thumbnailSizeDp = 108.dp
|
||||||
val thumbnailSizePx = thumbnailSizeDp.px
|
val thumbnailSizePx = thumbnailSizeDp.px
|
||||||
|
|||||||
@@ -6,7 +6,6 @@ import androidx.compose.foundation.background
|
|||||||
import androidx.compose.foundation.combinedClickable
|
import androidx.compose.foundation.combinedClickable
|
||||||
import androidx.compose.foundation.layout.Box
|
import androidx.compose.foundation.layout.Box
|
||||||
import androidx.compose.foundation.layout.Column
|
import androidx.compose.foundation.layout.Column
|
||||||
import androidx.compose.foundation.layout.ColumnScope
|
|
||||||
import androidx.compose.foundation.layout.fillMaxSize
|
import androidx.compose.foundation.layout.fillMaxSize
|
||||||
import androidx.compose.foundation.layout.width
|
import androidx.compose.foundation.layout.width
|
||||||
import androidx.compose.foundation.lazy.LazyColumn
|
import androidx.compose.foundation.lazy.LazyColumn
|
||||||
@@ -24,10 +23,11 @@ import it.vfsfitvnm.vimusic.R
|
|||||||
import it.vfsfitvnm.vimusic.models.DetailedSong
|
import it.vfsfitvnm.vimusic.models.DetailedSong
|
||||||
import it.vfsfitvnm.vimusic.savers.DetailedSongListSaver
|
import it.vfsfitvnm.vimusic.savers.DetailedSongListSaver
|
||||||
import it.vfsfitvnm.vimusic.ui.components.LocalMenuState
|
import it.vfsfitvnm.vimusic.ui.components.LocalMenuState
|
||||||
|
import it.vfsfitvnm.vimusic.ui.components.ShimmerHost
|
||||||
|
import it.vfsfitvnm.vimusic.ui.components.themed.LayoutWithAdaptiveThumbnail
|
||||||
import it.vfsfitvnm.vimusic.ui.components.themed.NonQueuedMediaItemMenu
|
import it.vfsfitvnm.vimusic.ui.components.themed.NonQueuedMediaItemMenu
|
||||||
import it.vfsfitvnm.vimusic.ui.components.themed.PrimaryButton
|
import it.vfsfitvnm.vimusic.ui.components.themed.PrimaryButton
|
||||||
import it.vfsfitvnm.vimusic.ui.components.themed.SecondaryTextButton
|
import it.vfsfitvnm.vimusic.ui.components.themed.SecondaryTextButton
|
||||||
import it.vfsfitvnm.vimusic.ui.components.ShimmerHost
|
|
||||||
import it.vfsfitvnm.vimusic.ui.items.SongItem
|
import it.vfsfitvnm.vimusic.ui.items.SongItem
|
||||||
import it.vfsfitvnm.vimusic.ui.items.SongItemPlaceholder
|
import it.vfsfitvnm.vimusic.ui.items.SongItemPlaceholder
|
||||||
import it.vfsfitvnm.vimusic.ui.styling.Dimensions
|
import it.vfsfitvnm.vimusic.ui.styling.Dimensions
|
||||||
@@ -38,6 +38,7 @@ import it.vfsfitvnm.vimusic.utils.color
|
|||||||
import it.vfsfitvnm.vimusic.utils.enqueue
|
import it.vfsfitvnm.vimusic.utils.enqueue
|
||||||
import it.vfsfitvnm.vimusic.utils.forcePlayAtIndex
|
import it.vfsfitvnm.vimusic.utils.forcePlayAtIndex
|
||||||
import it.vfsfitvnm.vimusic.utils.forcePlayFromBeginning
|
import it.vfsfitvnm.vimusic.utils.forcePlayFromBeginning
|
||||||
|
import it.vfsfitvnm.vimusic.utils.isLandscape
|
||||||
import it.vfsfitvnm.vimusic.utils.produceSaveableState
|
import it.vfsfitvnm.vimusic.utils.produceSaveableState
|
||||||
import it.vfsfitvnm.vimusic.utils.semiBold
|
import it.vfsfitvnm.vimusic.utils.semiBold
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
@@ -49,7 +50,7 @@ import kotlinx.coroutines.flow.flowOn
|
|||||||
fun AlbumSongs(
|
fun AlbumSongs(
|
||||||
browseId: String,
|
browseId: String,
|
||||||
headerContent: @Composable (textButton: (@Composable () -> Unit)?) -> Unit,
|
headerContent: @Composable (textButton: (@Composable () -> Unit)?) -> Unit,
|
||||||
thumbnailContent: @Composable ColumnScope.() -> Unit,
|
thumbnailContent: @Composable () -> Unit,
|
||||||
) {
|
) {
|
||||||
val (colorPalette, typography) = LocalAppearance.current
|
val (colorPalette, typography) = LocalAppearance.current
|
||||||
val binder = LocalPlayerServiceBinder.current
|
val binder = LocalPlayerServiceBinder.current
|
||||||
@@ -67,93 +68,100 @@ fun AlbumSongs(
|
|||||||
|
|
||||||
val thumbnailSizeDp = Dimensions.thumbnails.song
|
val thumbnailSizeDp = Dimensions.thumbnails.song
|
||||||
|
|
||||||
Box {
|
LayoutWithAdaptiveThumbnail(thumbnailContent = thumbnailContent) {
|
||||||
LazyColumn(
|
Box {
|
||||||
contentPadding = LocalPlayerAwarePaddingValues.current,
|
LazyColumn(
|
||||||
modifier = Modifier
|
contentPadding = LocalPlayerAwarePaddingValues.current,
|
||||||
.background(colorPalette.background0)
|
modifier = Modifier
|
||||||
.fillMaxSize()
|
.background(colorPalette.background0)
|
||||||
) {
|
.fillMaxSize()
|
||||||
item(
|
|
||||||
key = "header",
|
|
||||||
contentType = 0
|
|
||||||
) {
|
) {
|
||||||
Column {
|
item(
|
||||||
headerContent {
|
key = "header",
|
||||||
SecondaryTextButton(
|
contentType = 0
|
||||||
text = "Enqueue",
|
) {
|
||||||
isEnabled = songs.isNotEmpty(),
|
Column(horizontalAlignment = Alignment.CenterHorizontally) {
|
||||||
onClick = {
|
headerContent {
|
||||||
binder?.player?.enqueue(songs.map(DetailedSong::asMediaItem))
|
SecondaryTextButton(
|
||||||
}
|
text = "Enqueue",
|
||||||
)
|
isEnabled = songs.isNotEmpty(),
|
||||||
|
onClick = {
|
||||||
|
binder?.player?.enqueue(songs.map(DetailedSong::asMediaItem))
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!isLandscape) {
|
||||||
|
thumbnailContent()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
thumbnailContent()
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
itemsIndexed(
|
itemsIndexed(
|
||||||
items = songs,
|
items = songs,
|
||||||
key = { _, song -> song.id }
|
key = { _, song -> song.id }
|
||||||
) { index, song ->
|
) { index, song ->
|
||||||
SongItem(
|
SongItem(
|
||||||
title = song.title,
|
title = song.title,
|
||||||
authors = song.artistsText,
|
authors = song.artistsText,
|
||||||
duration = song.durationText,
|
duration = song.durationText,
|
||||||
thumbnailSizeDp = thumbnailSizeDp,
|
thumbnailSizeDp = thumbnailSizeDp,
|
||||||
thumbnailContent = {
|
thumbnailContent = {
|
||||||
BasicText(
|
BasicText(
|
||||||
text = "${index + 1}",
|
text = "${index + 1}",
|
||||||
style = typography.s.semiBold.center.color(colorPalette.textDisabled),
|
style = typography.s.semiBold.center.color(colorPalette.textDisabled),
|
||||||
maxLines = 1,
|
maxLines = 1,
|
||||||
overflow = TextOverflow.Ellipsis,
|
overflow = TextOverflow.Ellipsis,
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.width(thumbnailSizeDp)
|
.width(thumbnailSizeDp)
|
||||||
.align(Alignment.Center)
|
.align(Alignment.Center)
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.combinedClickable(
|
.combinedClickable(
|
||||||
onLongClick = {
|
onLongClick = {
|
||||||
menuState.display {
|
menuState.display {
|
||||||
NonQueuedMediaItemMenu(
|
NonQueuedMediaItemMenu(
|
||||||
onDismiss = menuState::hide,
|
onDismiss = menuState::hide,
|
||||||
mediaItem = song.asMediaItem,
|
mediaItem = song.asMediaItem,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onClick = {
|
||||||
|
binder?.stopRadio()
|
||||||
|
binder?.player?.forcePlayAtIndex(
|
||||||
|
songs.map(DetailedSong::asMediaItem),
|
||||||
|
index
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
},
|
)
|
||||||
onClick = {
|
)
|
||||||
binder?.stopRadio()
|
}
|
||||||
binder?.player?.forcePlayAtIndex(songs.map(DetailedSong::asMediaItem), index)
|
|
||||||
}
|
|
||||||
)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (songs.isEmpty()) {
|
if (songs.isEmpty()) {
|
||||||
item(key = "loading") {
|
item(key = "loading") {
|
||||||
ShimmerHost(
|
ShimmerHost(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillParentMaxSize()
|
.fillParentMaxSize()
|
||||||
) {
|
) {
|
||||||
repeat(4) {
|
repeat(4) {
|
||||||
SongItemPlaceholder(thumbnailSizeDp = Dimensions.thumbnails.song)
|
SongItemPlaceholder(thumbnailSizeDp = Dimensions.thumbnails.song)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
PrimaryButton(
|
PrimaryButton(
|
||||||
iconId = R.drawable.shuffle,
|
iconId = R.drawable.shuffle,
|
||||||
isEnabled = songs.isNotEmpty(),
|
isEnabled = songs.isNotEmpty(),
|
||||||
onClick = {
|
onClick = {
|
||||||
binder?.stopRadio()
|
binder?.stopRadio()
|
||||||
binder?.player?.forcePlayFromBeginning(
|
binder?.player?.forcePlayFromBeginning(
|
||||||
songs.shuffled().map(DetailedSong::asMediaItem)
|
songs.shuffled().map(DetailedSong::asMediaItem)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,7 +6,6 @@ import androidx.compose.foundation.background
|
|||||||
import androidx.compose.foundation.combinedClickable
|
import androidx.compose.foundation.combinedClickable
|
||||||
import androidx.compose.foundation.layout.Box
|
import androidx.compose.foundation.layout.Box
|
||||||
import androidx.compose.foundation.layout.Column
|
import androidx.compose.foundation.layout.Column
|
||||||
import androidx.compose.foundation.layout.ColumnScope
|
|
||||||
import androidx.compose.foundation.layout.fillMaxSize
|
import androidx.compose.foundation.layout.fillMaxSize
|
||||||
import androidx.compose.foundation.lazy.LazyColumn
|
import androidx.compose.foundation.lazy.LazyColumn
|
||||||
import androidx.compose.foundation.lazy.itemsIndexed
|
import androidx.compose.foundation.lazy.itemsIndexed
|
||||||
@@ -21,10 +20,11 @@ import it.vfsfitvnm.vimusic.models.DetailedSong
|
|||||||
import it.vfsfitvnm.vimusic.savers.DetailedSongListSaver
|
import it.vfsfitvnm.vimusic.savers.DetailedSongListSaver
|
||||||
import it.vfsfitvnm.vimusic.savers.nullableSaver
|
import it.vfsfitvnm.vimusic.savers.nullableSaver
|
||||||
import it.vfsfitvnm.vimusic.ui.components.LocalMenuState
|
import it.vfsfitvnm.vimusic.ui.components.LocalMenuState
|
||||||
|
import it.vfsfitvnm.vimusic.ui.components.ShimmerHost
|
||||||
|
import it.vfsfitvnm.vimusic.ui.components.themed.LayoutWithAdaptiveThumbnail
|
||||||
import it.vfsfitvnm.vimusic.ui.components.themed.NonQueuedMediaItemMenu
|
import it.vfsfitvnm.vimusic.ui.components.themed.NonQueuedMediaItemMenu
|
||||||
import it.vfsfitvnm.vimusic.ui.components.themed.PrimaryButton
|
import it.vfsfitvnm.vimusic.ui.components.themed.PrimaryButton
|
||||||
import it.vfsfitvnm.vimusic.ui.components.themed.SecondaryTextButton
|
import it.vfsfitvnm.vimusic.ui.components.themed.SecondaryTextButton
|
||||||
import it.vfsfitvnm.vimusic.ui.components.ShimmerHost
|
|
||||||
import it.vfsfitvnm.vimusic.ui.items.SongItem
|
import it.vfsfitvnm.vimusic.ui.items.SongItem
|
||||||
import it.vfsfitvnm.vimusic.ui.items.SongItemPlaceholder
|
import it.vfsfitvnm.vimusic.ui.items.SongItemPlaceholder
|
||||||
import it.vfsfitvnm.vimusic.ui.styling.Dimensions
|
import it.vfsfitvnm.vimusic.ui.styling.Dimensions
|
||||||
@@ -44,7 +44,7 @@ import kotlinx.coroutines.flow.flowOn
|
|||||||
fun ArtistLocalSongs(
|
fun ArtistLocalSongs(
|
||||||
browseId: String,
|
browseId: String,
|
||||||
headerContent: @Composable (textButton: (@Composable () -> Unit)?) -> Unit,
|
headerContent: @Composable (textButton: (@Composable () -> Unit)?) -> Unit,
|
||||||
thumbnailContent: @Composable ColumnScope.() -> Unit,
|
thumbnailContent: @Composable () -> Unit,
|
||||||
) {
|
) {
|
||||||
val binder = LocalPlayerServiceBinder.current
|
val binder = LocalPlayerServiceBinder.current
|
||||||
val (colorPalette) = LocalAppearance.current
|
val (colorPalette) = LocalAppearance.current
|
||||||
@@ -63,79 +63,81 @@ fun ArtistLocalSongs(
|
|||||||
val songThumbnailSizeDp = Dimensions.thumbnails.song
|
val songThumbnailSizeDp = Dimensions.thumbnails.song
|
||||||
val songThumbnailSizePx = songThumbnailSizeDp.px
|
val songThumbnailSizePx = songThumbnailSizeDp.px
|
||||||
|
|
||||||
Box {
|
LayoutWithAdaptiveThumbnail(thumbnailContent = thumbnailContent) {
|
||||||
LazyColumn(
|
Box {
|
||||||
contentPadding = LocalPlayerAwarePaddingValues.current,
|
LazyColumn(
|
||||||
modifier = Modifier
|
contentPadding = LocalPlayerAwarePaddingValues.current,
|
||||||
.background(colorPalette.background0)
|
modifier = Modifier
|
||||||
.fillMaxSize()
|
.background(colorPalette.background0)
|
||||||
) {
|
.fillMaxSize()
|
||||||
item(
|
|
||||||
key = "header",
|
|
||||||
contentType = 0
|
|
||||||
) {
|
) {
|
||||||
Column {
|
item(
|
||||||
headerContent {
|
key = "header",
|
||||||
SecondaryTextButton(
|
contentType = 0
|
||||||
text = "Enqueue",
|
) {
|
||||||
isEnabled = !songs.isNullOrEmpty(),
|
Column {
|
||||||
onClick = {
|
headerContent {
|
||||||
binder?.player?.enqueue(songs!!.map(DetailedSong::asMediaItem))
|
SecondaryTextButton(
|
||||||
}
|
text = "Enqueue",
|
||||||
)
|
isEnabled = !songs.isNullOrEmpty(),
|
||||||
}
|
|
||||||
|
|
||||||
thumbnailContent()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
songs?.let { songs ->
|
|
||||||
itemsIndexed(
|
|
||||||
items = songs,
|
|
||||||
key = { _, song -> song.id }
|
|
||||||
) { index, song ->
|
|
||||||
SongItem(
|
|
||||||
song = song,
|
|
||||||
thumbnailSizeDp = songThumbnailSizeDp,
|
|
||||||
thumbnailSizePx = songThumbnailSizePx,
|
|
||||||
modifier = Modifier
|
|
||||||
.combinedClickable(
|
|
||||||
onLongClick = {
|
|
||||||
menuState.display {
|
|
||||||
NonQueuedMediaItemMenu(
|
|
||||||
onDismiss = menuState::hide,
|
|
||||||
mediaItem = song.asMediaItem,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
onClick = {
|
onClick = {
|
||||||
binder?.stopRadio()
|
binder?.player?.enqueue(songs!!.map(DetailedSong::asMediaItem))
|
||||||
binder?.player?.forcePlayAtIndex(
|
|
||||||
songs.map(DetailedSong::asMediaItem),
|
|
||||||
index
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
)
|
}
|
||||||
|
|
||||||
|
thumbnailContent()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} ?: item(key = "loading") {
|
|
||||||
ShimmerHost {
|
songs?.let { songs ->
|
||||||
repeat(4) {
|
itemsIndexed(
|
||||||
SongItemPlaceholder(thumbnailSizeDp = Dimensions.thumbnails.song)
|
items = songs,
|
||||||
|
key = { _, song -> song.id }
|
||||||
|
) { index, song ->
|
||||||
|
SongItem(
|
||||||
|
song = song,
|
||||||
|
thumbnailSizeDp = songThumbnailSizeDp,
|
||||||
|
thumbnailSizePx = songThumbnailSizePx,
|
||||||
|
modifier = Modifier
|
||||||
|
.combinedClickable(
|
||||||
|
onLongClick = {
|
||||||
|
menuState.display {
|
||||||
|
NonQueuedMediaItemMenu(
|
||||||
|
onDismiss = menuState::hide,
|
||||||
|
mediaItem = song.asMediaItem,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onClick = {
|
||||||
|
binder?.stopRadio()
|
||||||
|
binder?.player?.forcePlayAtIndex(
|
||||||
|
songs.map(DetailedSong::asMediaItem),
|
||||||
|
index
|
||||||
|
)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
} ?: item(key = "loading") {
|
||||||
|
ShimmerHost {
|
||||||
|
repeat(4) {
|
||||||
|
SongItemPlaceholder(thumbnailSizeDp = Dimensions.thumbnails.song)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
PrimaryButton(
|
PrimaryButton(
|
||||||
iconId = R.drawable.shuffle,
|
iconId = R.drawable.shuffle,
|
||||||
isEnabled = !songs.isNullOrEmpty(),
|
isEnabled = !songs.isNullOrEmpty(),
|
||||||
onClick = {
|
onClick = {
|
||||||
binder?.stopRadio()
|
binder?.stopRadio()
|
||||||
binder?.player?.forcePlayFromBeginning(
|
binder?.player?.forcePlayFromBeginning(
|
||||||
songs!!.shuffled().map(DetailedSong::asMediaItem)
|
songs!!.shuffled().map(DetailedSong::asMediaItem)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,7 +8,6 @@ import androidx.compose.foundation.combinedClickable
|
|||||||
import androidx.compose.foundation.layout.Arrangement
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
import androidx.compose.foundation.layout.Box
|
import androidx.compose.foundation.layout.Box
|
||||||
import androidx.compose.foundation.layout.Column
|
import androidx.compose.foundation.layout.Column
|
||||||
import androidx.compose.foundation.layout.ColumnScope
|
|
||||||
import androidx.compose.foundation.layout.Row
|
import androidx.compose.foundation.layout.Row
|
||||||
import androidx.compose.foundation.layout.fillMaxSize
|
import androidx.compose.foundation.layout.fillMaxSize
|
||||||
import androidx.compose.foundation.layout.fillMaxWidth
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
@@ -26,10 +25,11 @@ import it.vfsfitvnm.vimusic.LocalPlayerAwarePaddingValues
|
|||||||
import it.vfsfitvnm.vimusic.LocalPlayerServiceBinder
|
import it.vfsfitvnm.vimusic.LocalPlayerServiceBinder
|
||||||
import it.vfsfitvnm.vimusic.R
|
import it.vfsfitvnm.vimusic.R
|
||||||
import it.vfsfitvnm.vimusic.ui.components.LocalMenuState
|
import it.vfsfitvnm.vimusic.ui.components.LocalMenuState
|
||||||
|
import it.vfsfitvnm.vimusic.ui.components.ShimmerHost
|
||||||
|
import it.vfsfitvnm.vimusic.ui.components.themed.LayoutWithAdaptiveThumbnail
|
||||||
import it.vfsfitvnm.vimusic.ui.components.themed.NonQueuedMediaItemMenu
|
import it.vfsfitvnm.vimusic.ui.components.themed.NonQueuedMediaItemMenu
|
||||||
import it.vfsfitvnm.vimusic.ui.components.themed.PrimaryButton
|
import it.vfsfitvnm.vimusic.ui.components.themed.PrimaryButton
|
||||||
import it.vfsfitvnm.vimusic.ui.components.themed.SecondaryTextButton
|
import it.vfsfitvnm.vimusic.ui.components.themed.SecondaryTextButton
|
||||||
import it.vfsfitvnm.vimusic.ui.components.ShimmerHost
|
|
||||||
import it.vfsfitvnm.vimusic.ui.components.themed.TextPlaceholder
|
import it.vfsfitvnm.vimusic.ui.components.themed.TextPlaceholder
|
||||||
import it.vfsfitvnm.vimusic.ui.items.AlbumItem
|
import it.vfsfitvnm.vimusic.ui.items.AlbumItem
|
||||||
import it.vfsfitvnm.vimusic.ui.items.AlbumItemPlaceholder
|
import it.vfsfitvnm.vimusic.ui.items.AlbumItemPlaceholder
|
||||||
@@ -54,7 +54,7 @@ fun ArtistOverview(
|
|||||||
onViewAllAlbumsClick: () -> Unit,
|
onViewAllAlbumsClick: () -> Unit,
|
||||||
onViewAllSinglesClick: () -> Unit,
|
onViewAllSinglesClick: () -> Unit,
|
||||||
onAlbumClick: (String) -> Unit,
|
onAlbumClick: (String) -> Unit,
|
||||||
thumbnailContent: @Composable ColumnScope.() -> Unit,
|
thumbnailContent: @Composable () -> Unit,
|
||||||
headerContent: @Composable (textButton: (@Composable () -> Unit)?) -> Unit,
|
headerContent: @Composable (textButton: (@Composable () -> Unit)?) -> Unit,
|
||||||
) {
|
) {
|
||||||
val (colorPalette, typography) = LocalAppearance.current
|
val (colorPalette, typography) = LocalAppearance.current
|
||||||
@@ -70,199 +70,202 @@ fun ArtistOverview(
|
|||||||
.padding(horizontal = 16.dp)
|
.padding(horizontal = 16.dp)
|
||||||
.padding(top = 24.dp, bottom = 8.dp)
|
.padding(top = 24.dp, bottom = 8.dp)
|
||||||
|
|
||||||
Box {
|
LayoutWithAdaptiveThumbnail(thumbnailContent = thumbnailContent) {
|
||||||
Column(
|
Box {
|
||||||
modifier = Modifier
|
Column(
|
||||||
.background(colorPalette.background0)
|
horizontalAlignment = Alignment.CenterHorizontally,
|
||||||
.fillMaxSize()
|
modifier = Modifier
|
||||||
.verticalScroll(rememberScrollState())
|
.background(colorPalette.background0)
|
||||||
.padding(LocalPlayerAwarePaddingValues.current)
|
.fillMaxSize()
|
||||||
) {
|
.verticalScroll(rememberScrollState())
|
||||||
headerContent {
|
.padding(LocalPlayerAwarePaddingValues.current)
|
||||||
youtubeArtistPage?.radioEndpoint?.let { radioEndpoint ->
|
) {
|
||||||
SecondaryTextButton(
|
headerContent {
|
||||||
text = "Start radio",
|
youtubeArtistPage?.radioEndpoint?.let { radioEndpoint ->
|
||||||
onClick = {
|
SecondaryTextButton(
|
||||||
binder?.stopRadio()
|
text = "Start radio",
|
||||||
binder?.playRadio(radioEndpoint)
|
onClick = {
|
||||||
}
|
binder?.stopRadio()
|
||||||
)
|
binder?.playRadio(radioEndpoint)
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
thumbnailContent()
|
|
||||||
|
|
||||||
if (youtubeArtistPage != null) {
|
|
||||||
youtubeArtistPage.songs?.let { songs ->
|
|
||||||
Row(
|
|
||||||
verticalAlignment = Alignment.Bottom,
|
|
||||||
horizontalArrangement = Arrangement.SpaceBetween,
|
|
||||||
modifier = Modifier
|
|
||||||
.fillMaxSize()
|
|
||||||
) {
|
|
||||||
BasicText(
|
|
||||||
text = "Songs",
|
|
||||||
style = typography.m.semiBold,
|
|
||||||
modifier = sectionTextModifier
|
|
||||||
)
|
)
|
||||||
|
|
||||||
youtubeArtistPage.songsEndpoint?.let {
|
|
||||||
BasicText(
|
|
||||||
text = "View all",
|
|
||||||
style = typography.xs.secondary,
|
|
||||||
modifier = sectionTextModifier
|
|
||||||
.clickable(onClick = onViewAllSongsClick),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
songs.forEach { song ->
|
thumbnailContent()
|
||||||
SongItem(
|
|
||||||
song = song,
|
if (youtubeArtistPage != null) {
|
||||||
thumbnailSizeDp = songThumbnailSizeDp,
|
youtubeArtistPage.songs?.let { songs ->
|
||||||
thumbnailSizePx = songThumbnailSizePx,
|
Row(
|
||||||
|
verticalAlignment = Alignment.Bottom,
|
||||||
|
horizontalArrangement = Arrangement.SpaceBetween,
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.combinedClickable(
|
.fillMaxSize()
|
||||||
onLongClick = {
|
) {
|
||||||
menuState.display {
|
BasicText(
|
||||||
NonQueuedMediaItemMenu(
|
text = "Songs",
|
||||||
onDismiss = menuState::hide,
|
style = typography.m.semiBold,
|
||||||
mediaItem = song.asMediaItem,
|
modifier = sectionTextModifier
|
||||||
|
)
|
||||||
|
|
||||||
|
youtubeArtistPage.songsEndpoint?.let {
|
||||||
|
BasicText(
|
||||||
|
text = "View all",
|
||||||
|
style = typography.xs.secondary,
|
||||||
|
modifier = sectionTextModifier
|
||||||
|
.clickable(onClick = onViewAllSongsClick),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
songs.forEach { song ->
|
||||||
|
SongItem(
|
||||||
|
song = song,
|
||||||
|
thumbnailSizeDp = songThumbnailSizeDp,
|
||||||
|
thumbnailSizePx = songThumbnailSizePx,
|
||||||
|
modifier = Modifier
|
||||||
|
.combinedClickable(
|
||||||
|
onLongClick = {
|
||||||
|
menuState.display {
|
||||||
|
NonQueuedMediaItemMenu(
|
||||||
|
onDismiss = menuState::hide,
|
||||||
|
mediaItem = song.asMediaItem,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onClick = {
|
||||||
|
val mediaItem = song.asMediaItem
|
||||||
|
binder?.stopRadio()
|
||||||
|
binder?.player?.forcePlay(mediaItem)
|
||||||
|
binder?.setupRadio(
|
||||||
|
NavigationEndpoint.Endpoint.Watch(videoId = mediaItem.mediaId)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
},
|
)
|
||||||
onClick = {
|
)
|
||||||
val mediaItem = song.asMediaItem
|
}
|
||||||
binder?.stopRadio()
|
}
|
||||||
binder?.player?.forcePlay(mediaItem)
|
|
||||||
binder?.setupRadio(
|
youtubeArtistPage.albums?.let { albums ->
|
||||||
NavigationEndpoint.Endpoint.Watch(videoId = mediaItem.mediaId)
|
Row(
|
||||||
)
|
verticalAlignment = Alignment.Bottom,
|
||||||
}
|
horizontalArrangement = Arrangement.SpaceBetween,
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxSize()
|
||||||
|
) {
|
||||||
|
BasicText(
|
||||||
|
text = "Albums",
|
||||||
|
style = typography.m.semiBold,
|
||||||
|
modifier = sectionTextModifier
|
||||||
|
)
|
||||||
|
|
||||||
|
youtubeArtistPage.albumsEndpoint?.let {
|
||||||
|
BasicText(
|
||||||
|
text = "View all",
|
||||||
|
style = typography.xs.secondary,
|
||||||
|
modifier = sectionTextModifier
|
||||||
|
.clickable(onClick = onViewAllAlbumsClick),
|
||||||
)
|
)
|
||||||
)
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
LazyRow(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
) {
|
||||||
|
items(
|
||||||
|
items = albums,
|
||||||
|
key = Innertube.AlbumItem::key
|
||||||
|
) { album ->
|
||||||
|
AlbumItem(
|
||||||
|
album = album,
|
||||||
|
thumbnailSizePx = albumThumbnailSizePx,
|
||||||
|
thumbnailSizeDp = albumThumbnailSizeDp,
|
||||||
|
alternative = true,
|
||||||
|
modifier = Modifier
|
||||||
|
.clickable(onClick = { onAlbumClick(album.key) })
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
youtubeArtistPage.albums?.let { albums ->
|
youtubeArtistPage.singles?.let { singles ->
|
||||||
Row(
|
Row(
|
||||||
verticalAlignment = Alignment.Bottom,
|
verticalAlignment = Alignment.Bottom,
|
||||||
horizontalArrangement = Arrangement.SpaceBetween,
|
horizontalArrangement = Arrangement.SpaceBetween,
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxSize()
|
.fillMaxSize()
|
||||||
) {
|
) {
|
||||||
BasicText(
|
|
||||||
text = "Albums",
|
|
||||||
style = typography.m.semiBold,
|
|
||||||
modifier = sectionTextModifier
|
|
||||||
)
|
|
||||||
|
|
||||||
youtubeArtistPage.albumsEndpoint?.let {
|
|
||||||
BasicText(
|
BasicText(
|
||||||
text = "View all",
|
text = "Singles",
|
||||||
style = typography.xs.secondary,
|
style = typography.m.semiBold,
|
||||||
modifier = sectionTextModifier
|
modifier = sectionTextModifier
|
||||||
.clickable(onClick = onViewAllAlbumsClick),
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
youtubeArtistPage.singlesEndpoint?.let {
|
||||||
|
BasicText(
|
||||||
|
text = "View all",
|
||||||
|
style = typography.xs.secondary,
|
||||||
|
modifier = sectionTextModifier
|
||||||
|
.clickable(onClick = onViewAllSinglesClick),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
LazyRow(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
) {
|
||||||
|
items(
|
||||||
|
items = singles,
|
||||||
|
key = Innertube.AlbumItem::key
|
||||||
|
) { album ->
|
||||||
|
AlbumItem(
|
||||||
|
album = album,
|
||||||
|
thumbnailSizePx = albumThumbnailSizePx,
|
||||||
|
thumbnailSizeDp = albumThumbnailSizeDp,
|
||||||
|
alternative = true,
|
||||||
|
modifier = Modifier
|
||||||
|
.clickable(onClick = { onAlbumClick(album.key) })
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
LazyRow(
|
ShimmerHost {
|
||||||
modifier = Modifier
|
|
||||||
.fillMaxWidth()
|
|
||||||
) {
|
|
||||||
items(
|
|
||||||
items = albums,
|
|
||||||
key = Innertube.AlbumItem::key
|
|
||||||
) { album ->
|
|
||||||
AlbumItem(
|
|
||||||
album = album,
|
|
||||||
thumbnailSizePx = albumThumbnailSizePx,
|
|
||||||
thumbnailSizeDp = albumThumbnailSizeDp,
|
|
||||||
alternative = true,
|
|
||||||
modifier = Modifier
|
|
||||||
.clickable(onClick = { onAlbumClick(album.key) })
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
youtubeArtistPage.singles?.let { singles ->
|
|
||||||
Row(
|
|
||||||
verticalAlignment = Alignment.Bottom,
|
|
||||||
horizontalArrangement = Arrangement.SpaceBetween,
|
|
||||||
modifier = Modifier
|
|
||||||
.fillMaxSize()
|
|
||||||
) {
|
|
||||||
BasicText(
|
|
||||||
text = "Singles",
|
|
||||||
style = typography.m.semiBold,
|
|
||||||
modifier = sectionTextModifier
|
|
||||||
)
|
|
||||||
|
|
||||||
youtubeArtistPage.singlesEndpoint?.let {
|
|
||||||
BasicText(
|
|
||||||
text = "View all",
|
|
||||||
style = typography.xs.secondary,
|
|
||||||
modifier = sectionTextModifier
|
|
||||||
.clickable(onClick = onViewAllSinglesClick),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
LazyRow(
|
|
||||||
modifier = Modifier
|
|
||||||
.fillMaxWidth()
|
|
||||||
) {
|
|
||||||
items(
|
|
||||||
items = singles,
|
|
||||||
key = Innertube.AlbumItem::key
|
|
||||||
) { album ->
|
|
||||||
AlbumItem(
|
|
||||||
album = album,
|
|
||||||
thumbnailSizePx = albumThumbnailSizePx,
|
|
||||||
thumbnailSizeDp = albumThumbnailSizeDp,
|
|
||||||
alternative = true,
|
|
||||||
modifier = Modifier
|
|
||||||
.clickable(onClick = { onAlbumClick(album.key) })
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
ShimmerHost {
|
|
||||||
TextPlaceholder(modifier = sectionTextModifier)
|
|
||||||
|
|
||||||
repeat(5) {
|
|
||||||
SongItemPlaceholder(
|
|
||||||
thumbnailSizeDp = songThumbnailSizeDp,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
repeat(2) {
|
|
||||||
TextPlaceholder(modifier = sectionTextModifier)
|
TextPlaceholder(modifier = sectionTextModifier)
|
||||||
|
|
||||||
Row {
|
repeat(5) {
|
||||||
repeat(2) {
|
SongItemPlaceholder(
|
||||||
AlbumItemPlaceholder(
|
thumbnailSizeDp = songThumbnailSizeDp,
|
||||||
thumbnailSizeDp = albumThumbnailSizeDp,
|
)
|
||||||
alternative = true
|
}
|
||||||
)
|
|
||||||
|
repeat(2) {
|
||||||
|
TextPlaceholder(modifier = sectionTextModifier)
|
||||||
|
|
||||||
|
Row {
|
||||||
|
repeat(2) {
|
||||||
|
AlbumItemPlaceholder(
|
||||||
|
thumbnailSizeDp = albumThumbnailSizeDp,
|
||||||
|
alternative = true
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
youtubeArtistPage?.shuffleEndpoint?.let { shuffleEndpoint ->
|
youtubeArtistPage?.shuffleEndpoint?.let { shuffleEndpoint ->
|
||||||
PrimaryButton(
|
PrimaryButton(
|
||||||
iconId = R.drawable.shuffle,
|
iconId = R.drawable.shuffle,
|
||||||
onClick = {
|
onClick = {
|
||||||
binder?.stopRadio()
|
binder?.stopRadio()
|
||||||
binder?.playRadio(shuffleEndpoint)
|
binder?.playRadio(shuffleEndpoint)
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,25 +3,16 @@ package it.vfsfitvnm.vimusic.ui.screens.artist
|
|||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import androidx.compose.animation.ExperimentalAnimationApi
|
import androidx.compose.animation.ExperimentalAnimationApi
|
||||||
import androidx.compose.foundation.ExperimentalFoundationApi
|
import androidx.compose.foundation.ExperimentalFoundationApi
|
||||||
import androidx.compose.foundation.background
|
|
||||||
import androidx.compose.foundation.clickable
|
import androidx.compose.foundation.clickable
|
||||||
import androidx.compose.foundation.combinedClickable
|
import androidx.compose.foundation.combinedClickable
|
||||||
import androidx.compose.foundation.layout.BoxWithConstraints
|
|
||||||
import androidx.compose.foundation.layout.ColumnScope
|
|
||||||
import androidx.compose.foundation.layout.Spacer
|
import androidx.compose.foundation.layout.Spacer
|
||||||
import androidx.compose.foundation.layout.fillMaxWidth
|
|
||||||
import androidx.compose.foundation.layout.padding
|
|
||||||
import androidx.compose.foundation.layout.size
|
|
||||||
import androidx.compose.foundation.shape.CircleShape
|
import androidx.compose.foundation.shape.CircleShape
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
import androidx.compose.runtime.saveable.rememberSaveableStateHolder
|
import androidx.compose.runtime.saveable.rememberSaveableStateHolder
|
||||||
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.platform.LocalContext
|
import androidx.compose.ui.platform.LocalContext
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import coil.compose.AsyncImage
|
|
||||||
import com.valentinilk.shimmer.shimmer
|
import com.valentinilk.shimmer.shimmer
|
||||||
import it.vfsfitvnm.route.RouteHandler
|
import it.vfsfitvnm.route.RouteHandler
|
||||||
import it.vfsfitvnm.vimusic.Database
|
import it.vfsfitvnm.vimusic.Database
|
||||||
@@ -40,6 +31,7 @@ import it.vfsfitvnm.vimusic.ui.components.themed.HeaderIconButton
|
|||||||
import it.vfsfitvnm.vimusic.ui.components.themed.HeaderPlaceholder
|
import it.vfsfitvnm.vimusic.ui.components.themed.HeaderPlaceholder
|
||||||
import it.vfsfitvnm.vimusic.ui.components.themed.NonQueuedMediaItemMenu
|
import it.vfsfitvnm.vimusic.ui.components.themed.NonQueuedMediaItemMenu
|
||||||
import it.vfsfitvnm.vimusic.ui.components.themed.Scaffold
|
import it.vfsfitvnm.vimusic.ui.components.themed.Scaffold
|
||||||
|
import it.vfsfitvnm.vimusic.ui.components.themed.adaptiveThumbnailContent
|
||||||
import it.vfsfitvnm.vimusic.ui.items.AlbumItem
|
import it.vfsfitvnm.vimusic.ui.items.AlbumItem
|
||||||
import it.vfsfitvnm.vimusic.ui.items.AlbumItemPlaceholder
|
import it.vfsfitvnm.vimusic.ui.items.AlbumItemPlaceholder
|
||||||
import it.vfsfitvnm.vimusic.ui.items.SongItem
|
import it.vfsfitvnm.vimusic.ui.items.SongItem
|
||||||
@@ -50,13 +42,11 @@ import it.vfsfitvnm.vimusic.ui.screens.searchresult.ItemsPage
|
|||||||
import it.vfsfitvnm.vimusic.ui.styling.Dimensions
|
import it.vfsfitvnm.vimusic.ui.styling.Dimensions
|
||||||
import it.vfsfitvnm.vimusic.ui.styling.LocalAppearance
|
import it.vfsfitvnm.vimusic.ui.styling.LocalAppearance
|
||||||
import it.vfsfitvnm.vimusic.ui.styling.px
|
import it.vfsfitvnm.vimusic.ui.styling.px
|
||||||
import it.vfsfitvnm.vimusic.ui.styling.shimmer
|
|
||||||
import it.vfsfitvnm.vimusic.utils.artistScreenTabIndexKey
|
import it.vfsfitvnm.vimusic.utils.artistScreenTabIndexKey
|
||||||
import it.vfsfitvnm.vimusic.utils.asMediaItem
|
import it.vfsfitvnm.vimusic.utils.asMediaItem
|
||||||
import it.vfsfitvnm.vimusic.utils.forcePlay
|
import it.vfsfitvnm.vimusic.utils.forcePlay
|
||||||
import it.vfsfitvnm.vimusic.utils.produceSaveableState
|
import it.vfsfitvnm.vimusic.utils.produceSaveableState
|
||||||
import it.vfsfitvnm.vimusic.utils.rememberPreference
|
import it.vfsfitvnm.vimusic.utils.rememberPreference
|
||||||
import it.vfsfitvnm.vimusic.utils.thumbnail
|
|
||||||
import it.vfsfitvnm.youtubemusic.Innertube
|
import it.vfsfitvnm.youtubemusic.Innertube
|
||||||
import it.vfsfitvnm.youtubemusic.models.bodies.BrowseBody
|
import it.vfsfitvnm.youtubemusic.models.bodies.BrowseBody
|
||||||
import it.vfsfitvnm.youtubemusic.models.bodies.ContinuationBody
|
import it.vfsfitvnm.youtubemusic.models.bodies.ContinuationBody
|
||||||
@@ -123,38 +113,12 @@ fun ArtistScreen(browseId: String) {
|
|||||||
globalRoutes()
|
globalRoutes()
|
||||||
|
|
||||||
host {
|
host {
|
||||||
val thumbnailContent: @Composable ColumnScope.() -> Unit = {
|
val thumbnailContent =
|
||||||
BoxWithConstraints(
|
adaptiveThumbnailContent(
|
||||||
contentAlignment = Alignment.Center,
|
artist?.timestamp == null,
|
||||||
modifier = Modifier
|
artist?.thumbnailUrl,
|
||||||
.fillMaxWidth()
|
CircleShape
|
||||||
) {
|
)
|
||||||
val thumbnailSizeDp = maxWidth - 64.dp
|
|
||||||
val thumbnailSizePx = thumbnailSizeDp.px
|
|
||||||
|
|
||||||
if (artist?.timestamp == null) {
|
|
||||||
val (colorPalette) = LocalAppearance.current
|
|
||||||
|
|
||||||
Spacer(
|
|
||||||
modifier = Modifier
|
|
||||||
.padding(all = 16.dp)
|
|
||||||
.shimmer()
|
|
||||||
.clip(CircleShape)
|
|
||||||
.size(thumbnailSizeDp)
|
|
||||||
.background(colorPalette.shimmer)
|
|
||||||
)
|
|
||||||
} else {
|
|
||||||
AsyncImage(
|
|
||||||
model = artist?.thumbnailUrl?.thumbnail(thumbnailSizePx),
|
|
||||||
contentDescription = null,
|
|
||||||
modifier = Modifier
|
|
||||||
.padding(all = 16.dp)
|
|
||||||
.clip(CircleShape)
|
|
||||||
.size(thumbnailSizeDp)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
val headerContent: @Composable (textButton: (@Composable () -> Unit)?) -> Unit =
|
val headerContent: @Composable (textButton: (@Composable () -> Unit)?) -> Unit =
|
||||||
{ textButton ->
|
{ textButton ->
|
||||||
|
|||||||
@@ -5,30 +5,18 @@ import androidx.compose.animation.ExperimentalAnimationApi
|
|||||||
import androidx.compose.foundation.ExperimentalFoundationApi
|
import androidx.compose.foundation.ExperimentalFoundationApi
|
||||||
import androidx.compose.foundation.background
|
import androidx.compose.foundation.background
|
||||||
import androidx.compose.foundation.combinedClickable
|
import androidx.compose.foundation.combinedClickable
|
||||||
import androidx.compose.foundation.layout.Arrangement
|
|
||||||
import androidx.compose.foundation.layout.Box
|
import androidx.compose.foundation.layout.Box
|
||||||
import androidx.compose.foundation.layout.BoxWithConstraints
|
|
||||||
import androidx.compose.foundation.layout.Column
|
import androidx.compose.foundation.layout.Column
|
||||||
import androidx.compose.foundation.layout.Row
|
|
||||||
import androidx.compose.foundation.layout.Spacer
|
import androidx.compose.foundation.layout.Spacer
|
||||||
import androidx.compose.foundation.layout.fillMaxSize
|
import androidx.compose.foundation.layout.fillMaxSize
|
||||||
import androidx.compose.foundation.layout.fillMaxWidth
|
|
||||||
import androidx.compose.foundation.layout.height
|
|
||||||
import androidx.compose.foundation.layout.padding
|
|
||||||
import androidx.compose.foundation.layout.size
|
|
||||||
import androidx.compose.foundation.lazy.LazyColumn
|
import androidx.compose.foundation.lazy.LazyColumn
|
||||||
import androidx.compose.foundation.lazy.itemsIndexed
|
import androidx.compose.foundation.lazy.itemsIndexed
|
||||||
import androidx.compose.foundation.text.BasicText
|
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
import androidx.compose.runtime.saveable.autoSaver
|
import androidx.compose.runtime.saveable.autoSaver
|
||||||
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.alpha
|
|
||||||
import androidx.compose.ui.draw.clip
|
|
||||||
import androidx.compose.ui.platform.LocalContext
|
import androidx.compose.ui.platform.LocalContext
|
||||||
import androidx.compose.ui.unit.dp
|
|
||||||
import coil.compose.AsyncImage
|
|
||||||
import com.valentinilk.shimmer.shimmer
|
import com.valentinilk.shimmer.shimmer
|
||||||
import it.vfsfitvnm.vimusic.Database
|
import it.vfsfitvnm.vimusic.Database
|
||||||
import it.vfsfitvnm.vimusic.LocalPlayerAwarePaddingValues
|
import it.vfsfitvnm.vimusic.LocalPlayerAwarePaddingValues
|
||||||
@@ -37,29 +25,30 @@ import it.vfsfitvnm.vimusic.R
|
|||||||
import it.vfsfitvnm.vimusic.models.Playlist
|
import it.vfsfitvnm.vimusic.models.Playlist
|
||||||
import it.vfsfitvnm.vimusic.models.SongPlaylistMap
|
import it.vfsfitvnm.vimusic.models.SongPlaylistMap
|
||||||
import it.vfsfitvnm.vimusic.savers.InnertubePlaylistOrAlbumPageSaver
|
import it.vfsfitvnm.vimusic.savers.InnertubePlaylistOrAlbumPageSaver
|
||||||
import it.vfsfitvnm.vimusic.savers.resultSaver
|
import it.vfsfitvnm.vimusic.savers.nullableSaver
|
||||||
import it.vfsfitvnm.vimusic.transaction
|
import it.vfsfitvnm.vimusic.transaction
|
||||||
import it.vfsfitvnm.vimusic.ui.components.LocalMenuState
|
import it.vfsfitvnm.vimusic.ui.components.LocalMenuState
|
||||||
|
import it.vfsfitvnm.vimusic.ui.components.ShimmerHost
|
||||||
import it.vfsfitvnm.vimusic.ui.components.themed.Header
|
import it.vfsfitvnm.vimusic.ui.components.themed.Header
|
||||||
import it.vfsfitvnm.vimusic.ui.components.themed.HeaderIconButton
|
import it.vfsfitvnm.vimusic.ui.components.themed.HeaderIconButton
|
||||||
import it.vfsfitvnm.vimusic.ui.components.themed.HeaderPlaceholder
|
import it.vfsfitvnm.vimusic.ui.components.themed.HeaderPlaceholder
|
||||||
|
import it.vfsfitvnm.vimusic.ui.components.themed.LayoutWithAdaptiveThumbnail
|
||||||
import it.vfsfitvnm.vimusic.ui.components.themed.NonQueuedMediaItemMenu
|
import it.vfsfitvnm.vimusic.ui.components.themed.NonQueuedMediaItemMenu
|
||||||
import it.vfsfitvnm.vimusic.ui.components.themed.PrimaryButton
|
import it.vfsfitvnm.vimusic.ui.components.themed.PrimaryButton
|
||||||
import it.vfsfitvnm.vimusic.ui.components.themed.SecondaryTextButton
|
import it.vfsfitvnm.vimusic.ui.components.themed.SecondaryTextButton
|
||||||
import it.vfsfitvnm.vimusic.ui.components.themed.TextPlaceholder
|
import it.vfsfitvnm.vimusic.ui.components.themed.adaptiveThumbnailContent
|
||||||
import it.vfsfitvnm.vimusic.ui.items.SongItem
|
import it.vfsfitvnm.vimusic.ui.items.SongItem
|
||||||
|
import it.vfsfitvnm.vimusic.ui.items.SongItemPlaceholder
|
||||||
import it.vfsfitvnm.vimusic.ui.styling.Dimensions
|
import it.vfsfitvnm.vimusic.ui.styling.Dimensions
|
||||||
import it.vfsfitvnm.vimusic.ui.styling.LocalAppearance
|
import it.vfsfitvnm.vimusic.ui.styling.LocalAppearance
|
||||||
import it.vfsfitvnm.vimusic.ui.styling.px
|
import it.vfsfitvnm.vimusic.ui.styling.px
|
||||||
import it.vfsfitvnm.vimusic.ui.styling.shimmer
|
|
||||||
import it.vfsfitvnm.vimusic.utils.asMediaItem
|
import it.vfsfitvnm.vimusic.utils.asMediaItem
|
||||||
import it.vfsfitvnm.vimusic.utils.center
|
|
||||||
import it.vfsfitvnm.vimusic.utils.completed
|
import it.vfsfitvnm.vimusic.utils.completed
|
||||||
import it.vfsfitvnm.vimusic.utils.enqueue
|
import it.vfsfitvnm.vimusic.utils.enqueue
|
||||||
import it.vfsfitvnm.vimusic.utils.forcePlayAtIndex
|
import it.vfsfitvnm.vimusic.utils.forcePlayAtIndex
|
||||||
import it.vfsfitvnm.vimusic.utils.forcePlayFromBeginning
|
import it.vfsfitvnm.vimusic.utils.forcePlayFromBeginning
|
||||||
|
import it.vfsfitvnm.vimusic.utils.isLandscape
|
||||||
import it.vfsfitvnm.vimusic.utils.produceSaveableState
|
import it.vfsfitvnm.vimusic.utils.produceSaveableState
|
||||||
import it.vfsfitvnm.vimusic.utils.secondary
|
|
||||||
import it.vfsfitvnm.youtubemusic.Innertube
|
import it.vfsfitvnm.youtubemusic.Innertube
|
||||||
import it.vfsfitvnm.youtubemusic.models.bodies.BrowseBody
|
import it.vfsfitvnm.youtubemusic.models.bodies.BrowseBody
|
||||||
import it.vfsfitvnm.youtubemusic.requests.playlistPage
|
import it.vfsfitvnm.youtubemusic.requests.playlistPage
|
||||||
@@ -78,14 +67,14 @@ fun PlaylistSongList(
|
|||||||
val context = LocalContext.current
|
val context = LocalContext.current
|
||||||
val menuState = LocalMenuState.current
|
val menuState = LocalMenuState.current
|
||||||
|
|
||||||
val playlistPageResult by produceSaveableState(
|
val playlistPage by produceSaveableState(
|
||||||
initialValue = null,
|
initialValue = null,
|
||||||
stateSaver = resultSaver(InnertubePlaylistOrAlbumPageSaver),
|
stateSaver = nullableSaver(InnertubePlaylistOrAlbumPageSaver),
|
||||||
) {
|
) {
|
||||||
if (value != null && value?.getOrNull()?.songsPage?.continuation == null) return@produceSaveableState
|
if (value != null && value?.songsPage?.continuation == null) return@produceSaveableState
|
||||||
|
|
||||||
value = withContext(Dispatchers.IO) {
|
value = withContext(Dispatchers.IO) {
|
||||||
Innertube.playlistPage(BrowseBody(browseId = browseId))?.completed()
|
Innertube.playlistPage(BrowseBody(browseId = browseId))?.completed()?.getOrNull()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -99,17 +88,82 @@ fun PlaylistSongList(
|
|||||||
.collect { value = it }
|
.collect { value = it }
|
||||||
}
|
}
|
||||||
|
|
||||||
BoxWithConstraints(
|
val songThumbnailSizeDp = Dimensions.thumbnails.song
|
||||||
modifier = Modifier
|
val songThumbnailSizePx = songThumbnailSizeDp.px
|
||||||
.fillMaxWidth()
|
|
||||||
) {
|
|
||||||
val thumbnailSizeDp = maxWidth - 64.dp
|
|
||||||
val thumbnailSizePx = thumbnailSizeDp.px
|
|
||||||
|
|
||||||
val songThumbnailSizeDp = Dimensions.thumbnails.song
|
val headerContent: @Composable () -> Unit = {
|
||||||
val songThumbnailSizePx = songThumbnailSizeDp.px
|
if (playlistPage == null) {
|
||||||
|
HeaderPlaceholder(
|
||||||
|
modifier = Modifier
|
||||||
|
.shimmer()
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
Header(title = playlistPage?.title ?: "Unknown") {
|
||||||
|
SecondaryTextButton(
|
||||||
|
text = "Enqueue",
|
||||||
|
isEnabled = playlistPage?.songsPage?.items?.isNotEmpty() == true,
|
||||||
|
onClick = {
|
||||||
|
playlistPage?.songsPage?.items?.map(Innertube.SongItem::asMediaItem)?.let { mediaItems ->
|
||||||
|
binder?.player?.enqueue(mediaItems)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
playlistPageResult?.getOrNull()?.let { playlist ->
|
Spacer(
|
||||||
|
modifier = Modifier
|
||||||
|
.weight(1f)
|
||||||
|
)
|
||||||
|
|
||||||
|
HeaderIconButton(
|
||||||
|
icon = if (isImported == true) R.drawable.bookmark else R.drawable.bookmark_outline,
|
||||||
|
color = colorPalette.accent,
|
||||||
|
onClick = {
|
||||||
|
transaction {
|
||||||
|
val playlistId =
|
||||||
|
Database.insert(
|
||||||
|
Playlist(
|
||||||
|
name = playlistPage?.title ?: "Unknown",
|
||||||
|
browseId = browseId
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
playlistPage?.songsPage?.items
|
||||||
|
?.map(Innertube.SongItem::asMediaItem)
|
||||||
|
?.onEach(Database::insert)
|
||||||
|
?.mapIndexed { index, mediaItem ->
|
||||||
|
SongPlaylistMap(
|
||||||
|
songId = mediaItem.mediaId,
|
||||||
|
playlistId = playlistId,
|
||||||
|
position = index
|
||||||
|
)
|
||||||
|
}?.let(Database::insertSongPlaylistMaps)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
HeaderIconButton(
|
||||||
|
icon = R.drawable.share_social,
|
||||||
|
color = colorPalette.text,
|
||||||
|
onClick = {
|
||||||
|
(playlistPage?.url ?: "https://music.youtube.com/playlist?list=${browseId.removePrefix("VL")}").let { url ->
|
||||||
|
val sendIntent = Intent().apply {
|
||||||
|
action = Intent.ACTION_SEND
|
||||||
|
type = "text/plain"
|
||||||
|
putExtra(Intent.EXTRA_TEXT, url)
|
||||||
|
}
|
||||||
|
|
||||||
|
context.startActivity(Intent.createChooser(sendIntent, null))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val thumbnailContent = adaptiveThumbnailContent(playlistPage == null, playlistPage?.thumbnail?.url)
|
||||||
|
|
||||||
|
LayoutWithAdaptiveThumbnail(thumbnailContent = thumbnailContent) {
|
||||||
|
Box {
|
||||||
LazyColumn(
|
LazyColumn(
|
||||||
contentPadding = LocalPlayerAwarePaddingValues.current,
|
contentPadding = LocalPlayerAwarePaddingValues.current,
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
@@ -120,79 +174,13 @@ fun PlaylistSongList(
|
|||||||
key = "header",
|
key = "header",
|
||||||
contentType = 0
|
contentType = 0
|
||||||
) {
|
) {
|
||||||
Column {
|
Column(horizontalAlignment = Alignment.CenterHorizontally) {
|
||||||
Header(title = playlist.title ?: "Unknown") {
|
headerContent()
|
||||||
SecondaryTextButton(
|
if (!isLandscape) thumbnailContent()
|
||||||
text = "Enqueue",
|
|
||||||
isEnabled = playlist.songsPage?.items?.isNotEmpty() == true,
|
|
||||||
onClick = {
|
|
||||||
playlist.songsPage?.items?.map(Innertube.SongItem::asMediaItem)?.let { mediaItems ->
|
|
||||||
binder?.player?.enqueue(mediaItems)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
Spacer(
|
|
||||||
modifier = Modifier
|
|
||||||
.weight(1f)
|
|
||||||
)
|
|
||||||
|
|
||||||
HeaderIconButton(
|
|
||||||
icon = if (isImported == true) R.drawable.bookmark else R.drawable.bookmark_outline,
|
|
||||||
color = colorPalette.accent,
|
|
||||||
onClick = {
|
|
||||||
transaction {
|
|
||||||
val playlistId =
|
|
||||||
Database.insert(
|
|
||||||
Playlist(
|
|
||||||
name = playlist.title ?: "Unknown",
|
|
||||||
browseId = browseId
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
playlist.songsPage?.items
|
|
||||||
?.map(Innertube.SongItem::asMediaItem)
|
|
||||||
?.onEach(Database::insert)
|
|
||||||
?.mapIndexed { index, mediaItem ->
|
|
||||||
SongPlaylistMap(
|
|
||||||
songId = mediaItem.mediaId,
|
|
||||||
playlistId = playlistId,
|
|
||||||
position = index
|
|
||||||
)
|
|
||||||
}?.let(Database::insertSongPlaylistMaps)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
HeaderIconButton(
|
|
||||||
icon = R.drawable.share_social,
|
|
||||||
color = colorPalette.text,
|
|
||||||
onClick = {
|
|
||||||
(playlist.url ?: "https://music.youtube.com/playlist?list=${browseId.removePrefix("VL")}").let { url ->
|
|
||||||
val sendIntent = Intent().apply {
|
|
||||||
action = Intent.ACTION_SEND
|
|
||||||
type = "text/plain"
|
|
||||||
putExtra(Intent.EXTRA_TEXT, url)
|
|
||||||
}
|
|
||||||
|
|
||||||
context.startActivity(Intent.createChooser(sendIntent, null))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
AsyncImage(
|
|
||||||
model = playlist.thumbnail?.size(thumbnailSizePx),
|
|
||||||
contentDescription = null,
|
|
||||||
modifier = Modifier
|
|
||||||
.padding(all = 16.dp)
|
|
||||||
.clip(thumbnailShape)
|
|
||||||
.size(thumbnailSizeDp)
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
itemsIndexed(items = playlist.songsPage?.items ?: emptyList()) { index, song ->
|
itemsIndexed(items = playlistPage?.songsPage?.items ?: emptyList()) { index, song ->
|
||||||
SongItem(
|
SongItem(
|
||||||
song = song,
|
song = song,
|
||||||
thumbnailSizePx = songThumbnailSizePx,
|
thumbnailSizePx = songThumbnailSizePx,
|
||||||
@@ -202,13 +190,13 @@ fun PlaylistSongList(
|
|||||||
onLongClick = {
|
onLongClick = {
|
||||||
menuState.display {
|
menuState.display {
|
||||||
NonQueuedMediaItemMenu(
|
NonQueuedMediaItemMenu(
|
||||||
onDismiss = menuState::hide,
|
onDismiss = menuState::hide,
|
||||||
mediaItem = song.asMediaItem,
|
mediaItem = song.asMediaItem,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
onClick = {
|
onClick = {
|
||||||
playlist.songsPage?.items?.map(Innertube.SongItem::asMediaItem)?.let { mediaItems ->
|
playlistPage?.songsPage?.items?.map(Innertube.SongItem::asMediaItem)?.let { mediaItems ->
|
||||||
binder?.stopRadio()
|
binder?.stopRadio()
|
||||||
binder?.player?.forcePlayAtIndex(mediaItems, index)
|
binder?.player?.forcePlayAtIndex(mediaItems, index)
|
||||||
}
|
}
|
||||||
@@ -216,69 +204,31 @@ fun PlaylistSongList(
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (playlistPage == null) {
|
||||||
|
item(key = "loading") {
|
||||||
|
ShimmerHost(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillParentMaxSize()
|
||||||
|
) {
|
||||||
|
repeat(4) {
|
||||||
|
SongItemPlaceholder(thumbnailSizeDp = songThumbnailSizeDp)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
PrimaryButton(
|
PrimaryButton(
|
||||||
iconId = R.drawable.shuffle,
|
iconId = R.drawable.shuffle,
|
||||||
isEnabled = playlist.songsPage?.items?.isNotEmpty() == true,
|
isEnabled = playlistPage?.songsPage?.items?.isNotEmpty() == true,
|
||||||
onClick = {
|
onClick = {
|
||||||
playlist.songsPage?.items?.map(Innertube.SongItem::asMediaItem)?.let { mediaItems ->
|
playlistPage?.songsPage?.items?.map(Innertube.SongItem::asMediaItem)?.let { mediaItems ->
|
||||||
binder?.stopRadio()
|
binder?.stopRadio()
|
||||||
binder?.player?.forcePlayFromBeginning(mediaItems.shuffled())
|
binder?.player?.forcePlayFromBeginning(mediaItems.shuffled())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
} ?: playlistPageResult?.exceptionOrNull()?.let {
|
|
||||||
Box(
|
|
||||||
modifier = Modifier
|
|
||||||
.align(Alignment.Center)
|
|
||||||
.fillMaxSize()
|
|
||||||
) {
|
|
||||||
BasicText(
|
|
||||||
text = "An error has occurred.\nTap to retry",
|
|
||||||
style = typography.s.secondary.center,
|
|
||||||
modifier = Modifier
|
|
||||||
.align(Alignment.Center)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
} ?: Column(
|
|
||||||
modifier = Modifier
|
|
||||||
.padding(LocalPlayerAwarePaddingValues.current)
|
|
||||||
.shimmer()
|
|
||||||
.fillMaxSize()
|
|
||||||
) {
|
|
||||||
HeaderPlaceholder()
|
|
||||||
|
|
||||||
Spacer(
|
|
||||||
modifier = Modifier
|
|
||||||
.padding(all = 16.dp)
|
|
||||||
.clip(thumbnailShape)
|
|
||||||
.size(thumbnailSizeDp)
|
|
||||||
.background(colorPalette.shimmer)
|
|
||||||
)
|
|
||||||
|
|
||||||
repeat(3) { index ->
|
|
||||||
Row(
|
|
||||||
verticalAlignment = Alignment.CenterVertically,
|
|
||||||
horizontalArrangement = Arrangement.spacedBy(12.dp),
|
|
||||||
modifier = Modifier
|
|
||||||
.alpha(1f - index * 0.25f)
|
|
||||||
.fillMaxWidth()
|
|
||||||
.padding(horizontal = 16.dp, vertical = Dimensions.itemsVerticalPadding)
|
|
||||||
.height(Dimensions.thumbnails.song)
|
|
||||||
) {
|
|
||||||
Spacer(
|
|
||||||
modifier = Modifier
|
|
||||||
.background(color = colorPalette.shimmer, shape = thumbnailShape)
|
|
||||||
.size(Dimensions.thumbnails.song)
|
|
||||||
)
|
|
||||||
|
|
||||||
Column {
|
|
||||||
TextPlaceholder()
|
|
||||||
TextPlaceholder()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,11 @@
|
|||||||
|
package it.vfsfitvnm.vimusic.utils
|
||||||
|
|
||||||
|
import android.content.res.Configuration
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.ReadOnlyComposable
|
||||||
|
import androidx.compose.ui.platform.LocalConfiguration
|
||||||
|
|
||||||
|
val isLandscape
|
||||||
|
@Composable
|
||||||
|
@ReadOnlyComposable
|
||||||
|
get() = LocalConfiguration.current.orientation == Configuration.ORIENTATION_LANDSCAPE
|
||||||
Reference in New Issue
Block a user