Redesign BuiltInPlaylistScreen (#172)

This commit is contained in:
vfsfitvnm 2022-09-27 17:51:46 +02:00
parent 0d3ce95c80
commit acc2768eb4
4 changed files with 213 additions and 209 deletions

View file

@ -1,207 +0,0 @@
package it.vfsfitvnm.vimusic.ui.screens
import androidx.compose.animation.ExperimentalAnimationApi
import androidx.compose.foundation.Image
import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.itemsIndexed
import androidx.compose.foundation.lazy.rememberLazyListState
import androidx.compose.foundation.text.BasicText
import androidx.compose.runtime.Composable
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.ColorFilter
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.unit.dp
import androidx.compose.ui.zIndex
import it.vfsfitvnm.route.RouteHandler
import it.vfsfitvnm.vimusic.Database
import it.vfsfitvnm.vimusic.LocalPlayerAwarePaddingValues
import it.vfsfitvnm.vimusic.LocalPlayerServiceBinder
import it.vfsfitvnm.vimusic.R
import it.vfsfitvnm.vimusic.enums.BuiltInPlaylist
import it.vfsfitvnm.vimusic.models.DetailedSong
import it.vfsfitvnm.vimusic.ui.components.LocalMenuState
import it.vfsfitvnm.vimusic.ui.components.TopAppBar
import it.vfsfitvnm.vimusic.ui.components.themed.InFavoritesMediaItemMenu
import it.vfsfitvnm.vimusic.ui.components.themed.InHistoryMediaItemMenu
import it.vfsfitvnm.vimusic.ui.components.themed.Menu
import it.vfsfitvnm.vimusic.ui.components.themed.MenuEntry
import it.vfsfitvnm.vimusic.ui.styling.Dimensions
import it.vfsfitvnm.vimusic.ui.styling.LocalAppearance
import it.vfsfitvnm.vimusic.ui.styling.px
import it.vfsfitvnm.vimusic.ui.views.SongItem
import it.vfsfitvnm.vimusic.utils.asMediaItem
import it.vfsfitvnm.vimusic.utils.enqueue
import it.vfsfitvnm.vimusic.utils.forcePlayAtIndex
import it.vfsfitvnm.vimusic.utils.forcePlayFromBeginning
import it.vfsfitvnm.vimusic.utils.secondary
import it.vfsfitvnm.vimusic.utils.semiBold
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.map
@ExperimentalAnimationApi
@Composable
fun BuiltInPlaylistScreen(builtInPlaylist: BuiltInPlaylist) {
val lazyListState = rememberLazyListState()
RouteHandler(listenToGlobalEmitter = true) {
globalRoutes()
host {
val menuState = LocalMenuState.current
val binder = LocalPlayerServiceBinder.current
val (colorPalette, typography) = LocalAppearance.current
val thumbnailSize = Dimensions.thumbnails.song.px
val songs by remember(binder?.cache, builtInPlaylist) {
when (builtInPlaylist) {
BuiltInPlaylist.Favorites -> Database.favorites()
BuiltInPlaylist.Offline -> Database.songsWithContentLength().map { songs ->
songs.filter { song ->
song.contentLength?.let {
binder?.cache?.isCached(song.id, 0, song.contentLength)
} ?: false
}
}
}
}.collectAsState(initial = emptyList(), context = Dispatchers.IO)
LazyColumn(
state = lazyListState,
contentPadding = LocalPlayerAwarePaddingValues.current,
modifier = Modifier
.background(colorPalette.background0)
.fillMaxSize()
) {
item {
TopAppBar(
modifier = Modifier
.height(52.dp)
) {
Image(
painter = painterResource(R.drawable.chevron_back),
contentDescription = null,
colorFilter = ColorFilter.tint(colorPalette.text),
modifier = Modifier
.clickable(onClick = pop)
.padding(vertical = 8.dp, horizontal = 16.dp)
.size(24.dp)
)
}
}
item {
Column(
modifier = Modifier
.padding(top = 16.dp, bottom = 8.dp)
.padding(horizontal = 16.dp)
) {
BasicText(
text = when (builtInPlaylist) {
BuiltInPlaylist.Favorites -> "Favorites"
BuiltInPlaylist.Offline -> "Offline"
},
style = typography.m.semiBold
)
BasicText(
text = "${songs.size} songs",
style = typography.xxs.semiBold.secondary
)
}
}
item {
Row(
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.End,
modifier = Modifier
.fillMaxWidth()
.zIndex(1f)
.padding(horizontal = 8.dp)
) {
Image(
painter = painterResource(R.drawable.shuffle),
contentDescription = null,
colorFilter = ColorFilter.tint(colorPalette.text),
modifier = Modifier
.clickable(enabled = songs.isNotEmpty()) {
binder?.stopRadio()
binder?.player?.forcePlayFromBeginning(
songs
.shuffled()
.map(DetailedSong::asMediaItem)
)
}
.padding(horizontal = 8.dp, vertical = 8.dp)
.size(20.dp)
)
Image(
painter = painterResource(R.drawable.ellipsis_horizontal),
contentDescription = null,
colorFilter = ColorFilter.tint(colorPalette.text),
modifier = Modifier
.clickable {
menuState.display {
Menu {
MenuEntry(
icon = R.drawable.enqueue,
text = "Enqueue",
isEnabled = songs.isNotEmpty(),
onClick = {
menuState.hide()
binder?.player?.enqueue(songs.map(DetailedSong::asMediaItem))
}
)
}
}
}
.padding(horizontal = 8.dp, vertical = 8.dp)
.size(20.dp)
)
}
}
itemsIndexed(
items = songs,
key = { _, song -> song.id },
contentType = { _, song -> song },
) { index, song ->
SongItem(
song = song,
thumbnailSize = thumbnailSize,
onClick = {
binder?.stopRadio()
binder?.player?.forcePlayAtIndex(
songs.map(DetailedSong::asMediaItem),
index
)
},
menuContent = {
when (builtInPlaylist) {
BuiltInPlaylist.Favorites -> InFavoritesMediaItemMenu(song = song)
BuiltInPlaylist.Offline -> InHistoryMediaItemMenu(song = song)
}
}
)
}
}
}
}
}

View file

@ -0,0 +1,49 @@
package it.vfsfitvnm.vimusic.ui.screens.builtinplaylist
import androidx.compose.animation.ExperimentalAnimationApi
import androidx.compose.runtime.Composable
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.runtime.saveable.rememberSaveableStateHolder
import it.vfsfitvnm.route.RouteHandler
import it.vfsfitvnm.vimusic.R
import it.vfsfitvnm.vimusic.enums.BuiltInPlaylist
import it.vfsfitvnm.vimusic.ui.components.themed.Scaffold
import it.vfsfitvnm.vimusic.ui.screens.globalRoutes
@ExperimentalAnimationApi
@Composable
fun BuiltInPlaylistScreen(builtInPlaylist: BuiltInPlaylist) {
val saveableStateHolder = rememberSaveableStateHolder()
val (tabIndex, onTabIndexChanged) = rememberSaveable {
mutableStateOf(when (builtInPlaylist) {
BuiltInPlaylist.Favorites -> 0
BuiltInPlaylist.Offline -> 1
})
}
RouteHandler(listenToGlobalEmitter = true) {
globalRoutes()
host {
Scaffold(
topIconButtonId = R.drawable.chevron_back,
onTopIconButtonClick = pop,
tabIndex = tabIndex,
onTabChanged = onTabIndexChanged,
tabColumnContent = { Item ->
Item(0, "Favorites", R.drawable.heart)
Item(1, "Offline", R.drawable.airplane)
}
) { currentTabIndex ->
saveableStateHolder.SaveableStateProvider(key = currentTabIndex) {
when (currentTabIndex) {
0 -> BuiltInPlaylistSongList(builtInPlaylist = BuiltInPlaylist.Favorites)
1 -> BuiltInPlaylistSongList(builtInPlaylist = BuiltInPlaylist.Offline)
}
}
}
}
}
}

View file

@ -0,0 +1,162 @@
package it.vfsfitvnm.vimusic.ui.screens.builtinplaylist
import androidx.compose.animation.ExperimentalAnimationApi
import androidx.compose.foundation.Image
import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.itemsIndexed
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.foundation.text.BasicText
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.graphics.ColorFilter
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.unit.dp
import it.vfsfitvnm.vimusic.Database
import it.vfsfitvnm.vimusic.LocalPlayerAwarePaddingValues
import it.vfsfitvnm.vimusic.LocalPlayerServiceBinder
import it.vfsfitvnm.vimusic.R
import it.vfsfitvnm.vimusic.enums.BuiltInPlaylist
import it.vfsfitvnm.vimusic.models.DetailedSong
import it.vfsfitvnm.vimusic.savers.DetailedSongListSaver
import it.vfsfitvnm.vimusic.ui.components.themed.Header
import it.vfsfitvnm.vimusic.ui.components.themed.InFavoritesMediaItemMenu
import it.vfsfitvnm.vimusic.ui.components.themed.InHistoryMediaItemMenu
import it.vfsfitvnm.vimusic.ui.styling.Dimensions
import it.vfsfitvnm.vimusic.ui.styling.LocalAppearance
import it.vfsfitvnm.vimusic.ui.styling.px
import it.vfsfitvnm.vimusic.ui.views.SongItem
import it.vfsfitvnm.vimusic.utils.asMediaItem
import it.vfsfitvnm.vimusic.utils.enqueue
import it.vfsfitvnm.vimusic.utils.forcePlayAtIndex
import it.vfsfitvnm.vimusic.utils.forcePlayFromBeginning
import it.vfsfitvnm.vimusic.utils.medium
import it.vfsfitvnm.vimusic.utils.produceSaveableState
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.flowOn
import kotlinx.coroutines.flow.map
@ExperimentalAnimationApi
@Composable
fun BuiltInPlaylistSongList(builtInPlaylist: BuiltInPlaylist) {
val (colorPalette, typography) = LocalAppearance.current
val binder = LocalPlayerServiceBinder.current
val songs by produceSaveableState(
initialValue = emptyList(),
stateSaver = DetailedSongListSaver
) {
when (builtInPlaylist) {
BuiltInPlaylist.Favorites -> Database
.favorites()
.flowOn(Dispatchers.IO)
BuiltInPlaylist.Offline -> Database
.songsWithContentLength()
.flowOn(Dispatchers.IO)
.map { songs ->
songs.filter { song ->
song.contentLength?.let {
binder?.cache?.isCached(song.id, 0, song.contentLength)
} ?: false
}
}
}.collect { value = it }
}
val thumbnailSize = Dimensions.thumbnails.song.px
Box {
LazyColumn(
contentPadding = LocalPlayerAwarePaddingValues.current,
modifier = Modifier
.background(colorPalette.background0)
.fillMaxSize()
) {
item(
key = "header",
contentType = 0
) {
Header(
title = when (builtInPlaylist) {
BuiltInPlaylist.Favorites -> "Favorites"
BuiltInPlaylist.Offline -> "Offline"
}
) {
BasicText(
text = "Enqueue",
style = typography.xxs.medium,
modifier = Modifier
.clip(RoundedCornerShape(16.dp))
.clickable(enabled = songs.isNotEmpty()) {
binder?.player?.enqueue(songs.map(DetailedSong::asMediaItem))
}
.background(colorPalette.background2)
.padding(all = 8.dp)
.padding(horizontal = 8.dp)
)
Spacer(
modifier = Modifier
.weight(1f)
)
}
}
itemsIndexed(
items = songs,
key = { _, song -> song.id },
contentType = { _, song -> song },
) { index, song ->
SongItem(
song = song,
thumbnailSize = thumbnailSize,
onClick = {
binder?.stopRadio()
binder?.player?.forcePlayAtIndex(
songs.map(DetailedSong::asMediaItem),
index
)
},
menuContent = {
when (builtInPlaylist) {
BuiltInPlaylist.Favorites -> InFavoritesMediaItemMenu(song = song)
BuiltInPlaylist.Offline -> InHistoryMediaItemMenu(song = song)
}
}
)
}
}
Box(
modifier = Modifier
.align(Alignment.BottomEnd)
.padding(all = 16.dp)
.padding(LocalPlayerAwarePaddingValues.current)
.clip(RoundedCornerShape(16.dp))
.clickable(enabled = songs.isNotEmpty()) {
binder?.stopRadio()
binder?.player?.forcePlayFromBeginning(songs.shuffled().map(DetailedSong::asMediaItem))
}
.background(colorPalette.background2)
.size(62.dp)
) {
Image(
painter = painterResource(R.drawable.shuffle),
contentDescription = null,
colorFilter = ColorFilter.tint(colorPalette.text),
modifier = Modifier
.align(Alignment.Center)
.size(20.dp)
)
}
}
}

View file

@ -11,15 +11,15 @@ import it.vfsfitvnm.vimusic.R
import it.vfsfitvnm.vimusic.models.SearchQuery import it.vfsfitvnm.vimusic.models.SearchQuery
import it.vfsfitvnm.vimusic.query import it.vfsfitvnm.vimusic.query
import it.vfsfitvnm.vimusic.ui.components.themed.Scaffold import it.vfsfitvnm.vimusic.ui.components.themed.Scaffold
import it.vfsfitvnm.vimusic.ui.screens.BuiltInPlaylistScreen
import it.vfsfitvnm.vimusic.ui.screens.IntentUriScreen import it.vfsfitvnm.vimusic.ui.screens.IntentUriScreen
import it.vfsfitvnm.vimusic.ui.screens.localplaylist.LocalPlaylistScreen
import it.vfsfitvnm.vimusic.ui.screens.albumRoute import it.vfsfitvnm.vimusic.ui.screens.albumRoute
import it.vfsfitvnm.vimusic.ui.screens.artistRoute import it.vfsfitvnm.vimusic.ui.screens.artistRoute
import it.vfsfitvnm.vimusic.ui.screens.builtInPlaylistRoute import it.vfsfitvnm.vimusic.ui.screens.builtInPlaylistRoute
import it.vfsfitvnm.vimusic.ui.screens.builtinplaylist.BuiltInPlaylistScreen
import it.vfsfitvnm.vimusic.ui.screens.globalRoutes import it.vfsfitvnm.vimusic.ui.screens.globalRoutes
import it.vfsfitvnm.vimusic.ui.screens.intentUriRoute import it.vfsfitvnm.vimusic.ui.screens.intentUriRoute
import it.vfsfitvnm.vimusic.ui.screens.localPlaylistRoute import it.vfsfitvnm.vimusic.ui.screens.localPlaylistRoute
import it.vfsfitvnm.vimusic.ui.screens.localplaylist.LocalPlaylistScreen
import it.vfsfitvnm.vimusic.ui.screens.search.SearchScreen import it.vfsfitvnm.vimusic.ui.screens.search.SearchScreen
import it.vfsfitvnm.vimusic.ui.screens.searchResultRoute import it.vfsfitvnm.vimusic.ui.screens.searchResultRoute
import it.vfsfitvnm.vimusic.ui.screens.searchRoute import it.vfsfitvnm.vimusic.ui.screens.searchRoute