Start UI redesign (#172)
This commit is contained in:
@@ -3,11 +3,13 @@ package it.vfsfitvnm.vimusic.ui.views
|
||||
import androidx.annotation.DrawableRes
|
||||
import androidx.compose.foundation.Image
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.BoxScope
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.requiredSize
|
||||
import androidx.compose.foundation.layout.requiredWidth
|
||||
import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.foundation.text.BasicText
|
||||
import androidx.compose.runtime.Composable
|
||||
@@ -17,7 +19,6 @@ import androidx.compose.runtime.remember
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.clip
|
||||
import androidx.compose.ui.graphics.Brush
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.graphics.ColorFilter
|
||||
import androidx.compose.ui.layout.ContentScale
|
||||
@@ -31,7 +32,7 @@ import it.vfsfitvnm.vimusic.Database
|
||||
import it.vfsfitvnm.vimusic.models.PlaylistPreview
|
||||
import it.vfsfitvnm.vimusic.ui.styling.Dimensions
|
||||
import it.vfsfitvnm.vimusic.ui.styling.LocalAppearance
|
||||
import it.vfsfitvnm.vimusic.utils.color
|
||||
import it.vfsfitvnm.vimusic.utils.center
|
||||
import it.vfsfitvnm.vimusic.utils.semiBold
|
||||
import it.vfsfitvnm.vimusic.utils.thumbnail
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
@@ -61,7 +62,6 @@ fun PlaylistPreviewItem(
|
||||
|
||||
PlaylistItem(
|
||||
name = playlistPreview.playlist.name,
|
||||
textColor = Color.White,
|
||||
thumbnailSize = thumbnailSize,
|
||||
imageContent = {
|
||||
if (thumbnails.toSet().size == 1) {
|
||||
@@ -112,7 +112,6 @@ fun BuiltInPlaylistItem(
|
||||
PlaylistItem(
|
||||
name = name,
|
||||
thumbnailSize = thumbnailSize,
|
||||
withGradient = false,
|
||||
imageContent = {
|
||||
Image(
|
||||
painter = painterResource(icon),
|
||||
@@ -131,48 +130,31 @@ fun BuiltInPlaylistItem(
|
||||
fun PlaylistItem(
|
||||
name: String,
|
||||
modifier: Modifier = Modifier,
|
||||
textColor: Color? = null,
|
||||
thumbnailSize: Dp = Dimensions.thumbnails.song,
|
||||
withGradient: Boolean = true,
|
||||
imageContent: @Composable BoxScope.() -> Unit
|
||||
) {
|
||||
val (colorPalette, typography, thumbnailShape) = LocalAppearance.current
|
||||
|
||||
Box(
|
||||
Column(
|
||||
horizontalAlignment = Alignment.CenterHorizontally,
|
||||
verticalArrangement = Arrangement.spacedBy(4.dp),
|
||||
modifier = modifier
|
||||
.clip(thumbnailShape)
|
||||
.background(colorPalette.background1)
|
||||
.size(thumbnailSize * 2)
|
||||
.requiredWidth(thumbnailSize * 2)
|
||||
) {
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.size(thumbnailSize * 2),
|
||||
.clip(thumbnailShape)
|
||||
.background(colorPalette.background1)
|
||||
.align(Alignment.CenterHorizontally)
|
||||
.requiredSize(thumbnailSize * 2),
|
||||
content = imageContent
|
||||
)
|
||||
|
||||
BasicText(
|
||||
text = name,
|
||||
style = typography.xxs.semiBold.color(textColor ?: colorPalette.text),
|
||||
style = typography.xxs.semiBold.center,
|
||||
maxLines = 2,
|
||||
overflow = TextOverflow.Ellipsis,
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.align(Alignment.BottomStart)
|
||||
.run {
|
||||
if (withGradient) {
|
||||
background(
|
||||
Brush.verticalGradient(
|
||||
colors = listOf(
|
||||
Color.Transparent,
|
||||
Color.Black.copy(alpha = 0.75f)
|
||||
)
|
||||
)
|
||||
)
|
||||
} else {
|
||||
this
|
||||
}
|
||||
}
|
||||
.padding(horizontal = 8.dp, vertical = 4.dp)
|
||||
overflow = TextOverflow.Ellipsis
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,297 @@
|
||||
package it.vfsfitvnm.vimusic.ui.views
|
||||
|
||||
import android.app.Application
|
||||
import android.content.SharedPreferences
|
||||
import androidx.annotation.DrawableRes
|
||||
import androidx.compose.animation.core.LinearEasing
|
||||
import androidx.compose.animation.core.animateFloatAsState
|
||||
import androidx.compose.animation.core.tween
|
||||
import androidx.compose.foundation.ExperimentalFoundationApi
|
||||
import androidx.compose.foundation.Image
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.interaction.MutableInteractionSource
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.Spacer
|
||||
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.layout.width
|
||||
import androidx.compose.foundation.lazy.grid.GridCells
|
||||
import androidx.compose.foundation.lazy.grid.GridItemSpan
|
||||
import androidx.compose.foundation.lazy.grid.LazyVerticalGrid
|
||||
import androidx.compose.foundation.lazy.grid.items
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.foundation.text.BasicText
|
||||
import androidx.compose.material.ripple.rememberRipple
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.saveable.rememberSaveable
|
||||
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.ColorFilter
|
||||
import androidx.compose.ui.graphics.graphicsLayer
|
||||
import androidx.compose.ui.res.painterResource
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.core.content.edit
|
||||
import androidx.lifecycle.AndroidViewModel
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import androidx.lifecycle.viewmodel.compose.viewModel
|
||||
import it.vfsfitvnm.vimusic.Database
|
||||
import it.vfsfitvnm.vimusic.LocalPlayerAwarePaddingValues
|
||||
import it.vfsfitvnm.vimusic.R
|
||||
import it.vfsfitvnm.vimusic.enums.BuiltInPlaylist
|
||||
import it.vfsfitvnm.vimusic.enums.PlaylistSortBy
|
||||
import it.vfsfitvnm.vimusic.enums.SortOrder
|
||||
import it.vfsfitvnm.vimusic.models.Playlist
|
||||
import it.vfsfitvnm.vimusic.models.PlaylistPreview
|
||||
import it.vfsfitvnm.vimusic.query
|
||||
import it.vfsfitvnm.vimusic.ui.components.themed.TextFieldDialog
|
||||
import it.vfsfitvnm.vimusic.ui.styling.Dimensions
|
||||
import it.vfsfitvnm.vimusic.ui.styling.LocalAppearance
|
||||
import it.vfsfitvnm.vimusic.utils.getEnum
|
||||
import it.vfsfitvnm.vimusic.utils.medium
|
||||
import it.vfsfitvnm.vimusic.utils.mutableStatePreferenceOf
|
||||
import it.vfsfitvnm.vimusic.utils.playlistSortByKey
|
||||
import it.vfsfitvnm.vimusic.utils.playlistSortOrderKey
|
||||
import it.vfsfitvnm.vimusic.utils.preferences
|
||||
import it.vfsfitvnm.vimusic.utils.putEnum
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.Job
|
||||
import kotlinx.coroutines.flow.flowOn
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
class PlaylistsTabViewModel(application: Application) : AndroidViewModel(application) {
|
||||
var items by mutableStateOf(emptyList<PlaylistPreview>())
|
||||
private set
|
||||
|
||||
var sortBy by mutableStatePreferenceOf(
|
||||
preferences.getEnum(
|
||||
playlistSortByKey,
|
||||
PlaylistSortBy.DateAdded
|
||||
)
|
||||
) {
|
||||
preferences.edit { putEnum(playlistSortByKey, it) }
|
||||
collectItems(sortBy = it)
|
||||
}
|
||||
|
||||
var sortOrder by mutableStatePreferenceOf(
|
||||
preferences.getEnum(
|
||||
playlistSortOrderKey,
|
||||
SortOrder.Ascending
|
||||
)
|
||||
) {
|
||||
preferences.edit { putEnum(playlistSortOrderKey, it) }
|
||||
collectItems(sortOrder = it)
|
||||
}
|
||||
|
||||
private var job: Job? = null
|
||||
|
||||
private val preferences: SharedPreferences
|
||||
get() = getApplication<Application>().preferences
|
||||
|
||||
init {
|
||||
collectItems()
|
||||
}
|
||||
|
||||
private fun collectItems(
|
||||
sortBy: PlaylistSortBy = this.sortBy,
|
||||
sortOrder: SortOrder = this.sortOrder
|
||||
) {
|
||||
job?.cancel()
|
||||
job = viewModelScope.launch {
|
||||
Database.playlistPreviews(sortBy, sortOrder).flowOn(Dispatchers.IO).collect {
|
||||
items = it
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ExperimentalFoundationApi
|
||||
@Composable
|
||||
fun PlaylistsTab(
|
||||
viewModel: PlaylistsTabViewModel = viewModel(),
|
||||
onBuiltInPlaylistClicked: (BuiltInPlaylist) -> Unit,
|
||||
onPlaylistClicked: (Playlist) -> Unit,
|
||||
) {
|
||||
val (colorPalette, typography) = LocalAppearance.current
|
||||
|
||||
var isCreatingANewPlaylist by rememberSaveable {
|
||||
mutableStateOf(false)
|
||||
}
|
||||
|
||||
if (isCreatingANewPlaylist) {
|
||||
TextFieldDialog(
|
||||
hintText = "Enter the playlist name",
|
||||
onDismiss = {
|
||||
isCreatingANewPlaylist = false
|
||||
},
|
||||
onDone = { text ->
|
||||
query {
|
||||
Database.insert(Playlist(name = text))
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
val sortOrderIconRotation by animateFloatAsState(
|
||||
targetValue = if (viewModel.sortOrder == SortOrder.Ascending) 0f else 180f,
|
||||
animationSpec = tween(durationMillis = 400, easing = LinearEasing)
|
||||
)
|
||||
|
||||
LazyVerticalGrid(
|
||||
columns = GridCells.Adaptive(Dimensions.thumbnails.song * 2 + Dimensions.itemsVerticalPadding * 2),
|
||||
contentPadding = LocalPlayerAwarePaddingValues.current,
|
||||
verticalArrangement = Arrangement.spacedBy(Dimensions.itemsVerticalPadding * 2),
|
||||
horizontalArrangement = Arrangement.spacedBy(
|
||||
space = Dimensions.itemsVerticalPadding * 2,
|
||||
alignment = Alignment.CenterHorizontally
|
||||
),
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
.background(colorPalette.background0)
|
||||
) {
|
||||
item(
|
||||
key = "header",
|
||||
contentType = 0,
|
||||
span = { GridItemSpan(maxLineSpan) }
|
||||
) {
|
||||
Column(
|
||||
horizontalAlignment = Alignment.End,
|
||||
verticalArrangement = Arrangement.Bottom,
|
||||
modifier = Modifier
|
||||
.padding(horizontal = 16.dp)
|
||||
.height(128.dp)
|
||||
.fillMaxWidth()
|
||||
) {
|
||||
BasicText(
|
||||
text = "Playlists",
|
||||
style = typography.xxl.medium
|
||||
)
|
||||
|
||||
Row(
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
horizontalArrangement = Arrangement.spacedBy(8.dp),
|
||||
modifier = Modifier
|
||||
.padding(vertical = 8.dp)
|
||||
) {
|
||||
@Composable
|
||||
fun Item(
|
||||
@DrawableRes iconId: Int,
|
||||
sortBy: PlaylistSortBy
|
||||
) {
|
||||
Image(
|
||||
painter = painterResource(iconId),
|
||||
contentDescription = null,
|
||||
colorFilter = ColorFilter.tint(if (viewModel.sortBy == sortBy) colorPalette.text else colorPalette.textDisabled),
|
||||
modifier = Modifier
|
||||
.clickable { viewModel.sortBy = sortBy }
|
||||
.padding(all = 4.dp)
|
||||
.size(18.dp)
|
||||
)
|
||||
}
|
||||
|
||||
BasicText(
|
||||
text = "New playlist",
|
||||
style = typography.xxs.medium,
|
||||
modifier = Modifier
|
||||
.clip(RoundedCornerShape(16.dp))
|
||||
.clickable { isCreatingANewPlaylist = true }
|
||||
.background(colorPalette.background2)
|
||||
.padding(all = 8.dp)
|
||||
.padding(horizontal = 8.dp)
|
||||
)
|
||||
|
||||
Spacer(
|
||||
modifier = Modifier
|
||||
.weight(1f)
|
||||
)
|
||||
|
||||
Item(
|
||||
iconId = R.drawable.medical,
|
||||
sortBy = PlaylistSortBy.SongCount
|
||||
)
|
||||
|
||||
Item(
|
||||
iconId = R.drawable.text,
|
||||
sortBy = PlaylistSortBy.Name
|
||||
)
|
||||
|
||||
Item(
|
||||
iconId = R.drawable.calendar,
|
||||
sortBy = PlaylistSortBy.DateAdded
|
||||
)
|
||||
|
||||
Spacer(
|
||||
modifier = Modifier
|
||||
.width(2.dp)
|
||||
)
|
||||
|
||||
Image(
|
||||
painter = painterResource(R.drawable.arrow_up),
|
||||
contentDescription = null,
|
||||
colorFilter = ColorFilter.tint(colorPalette.text),
|
||||
modifier = Modifier
|
||||
.clickable { viewModel.sortOrder = !viewModel.sortOrder }
|
||||
.padding(all = 4.dp)
|
||||
.size(18.dp)
|
||||
.graphicsLayer { rotationZ = sortOrderIconRotation }
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
item(key = "favorites") {
|
||||
BuiltInPlaylistItem(
|
||||
icon = R.drawable.heart,
|
||||
colorTint = colorPalette.red,
|
||||
name = "Favorites",
|
||||
modifier = Modifier
|
||||
.clickable(
|
||||
indication = rememberRipple(bounded = true),
|
||||
interactionSource = remember { MutableInteractionSource() },
|
||||
onClick = { onBuiltInPlaylistClicked(BuiltInPlaylist.Favorites) }
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
item(key = "offline") {
|
||||
BuiltInPlaylistItem(
|
||||
icon = R.drawable.airplane,
|
||||
colorTint = colorPalette.blue,
|
||||
name = "Offline",
|
||||
modifier = Modifier
|
||||
.clickable(
|
||||
indication = rememberRipple(bounded = true),
|
||||
interactionSource = remember { MutableInteractionSource() },
|
||||
onClick = { onBuiltInPlaylistClicked(BuiltInPlaylist.Offline) }
|
||||
)
|
||||
.animateItemPlacement()
|
||||
)
|
||||
}
|
||||
|
||||
items(
|
||||
items = viewModel.items,
|
||||
key = { it.playlist.id }
|
||||
) { playlistPreview ->
|
||||
PlaylistPreviewItem(
|
||||
playlistPreview = playlistPreview,
|
||||
modifier = Modifier
|
||||
.clickable(
|
||||
indication = rememberRipple(bounded = true),
|
||||
interactionSource = remember { MutableInteractionSource() },
|
||||
onClick = { onPlaylistClicked(playlistPreview.playlist) }
|
||||
)
|
||||
.animateItemPlacement()
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
261
app/src/main/kotlin/it/vfsfitvnm/vimusic/ui/views/SongsTab.kt
Normal file
261
app/src/main/kotlin/it/vfsfitvnm/vimusic/ui/views/SongsTab.kt
Normal file
@@ -0,0 +1,261 @@
|
||||
package it.vfsfitvnm.vimusic.ui.views
|
||||
|
||||
import android.app.Application
|
||||
import android.content.SharedPreferences
|
||||
import androidx.annotation.DrawableRes
|
||||
import androidx.compose.animation.AnimatedVisibility
|
||||
import androidx.compose.animation.ExperimentalAnimationApi
|
||||
import androidx.compose.animation.core.LinearEasing
|
||||
import androidx.compose.animation.core.animateFloatAsState
|
||||
import androidx.compose.animation.core.tween
|
||||
import androidx.compose.animation.fadeIn
|
||||
import androidx.compose.animation.fadeOut
|
||||
import androidx.compose.foundation.ExperimentalFoundationApi
|
||||
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.Spacer
|
||||
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.layout.width
|
||||
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.runtime.mutableStateOf
|
||||
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.Brush
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.graphics.ColorFilter
|
||||
import androidx.compose.ui.graphics.graphicsLayer
|
||||
import androidx.compose.ui.res.painterResource
|
||||
import androidx.compose.ui.text.style.TextOverflow
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.core.content.edit
|
||||
import androidx.lifecycle.AndroidViewModel
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import androidx.lifecycle.viewmodel.compose.viewModel
|
||||
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.SongSortBy
|
||||
import it.vfsfitvnm.vimusic.enums.SortOrder
|
||||
import it.vfsfitvnm.vimusic.enums.ThumbnailRoundness
|
||||
import it.vfsfitvnm.vimusic.models.DetailedSong
|
||||
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.utils.asMediaItem
|
||||
import it.vfsfitvnm.vimusic.utils.center
|
||||
import it.vfsfitvnm.vimusic.utils.color
|
||||
import it.vfsfitvnm.vimusic.utils.forcePlayAtIndex
|
||||
import it.vfsfitvnm.vimusic.utils.getEnum
|
||||
import it.vfsfitvnm.vimusic.utils.medium
|
||||
import it.vfsfitvnm.vimusic.utils.mutableStatePreferenceOf
|
||||
import it.vfsfitvnm.vimusic.utils.preferences
|
||||
import it.vfsfitvnm.vimusic.utils.putEnum
|
||||
import it.vfsfitvnm.vimusic.utils.semiBold
|
||||
import it.vfsfitvnm.vimusic.utils.songSortByKey
|
||||
import it.vfsfitvnm.vimusic.utils.songSortOrderKey
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.Job
|
||||
import kotlinx.coroutines.flow.flowOn
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
class SongsTabViewModel(application: Application) : AndroidViewModel(application) {
|
||||
var items by mutableStateOf(emptyList<DetailedSong>())
|
||||
private set
|
||||
|
||||
var sortBy by mutableStatePreferenceOf(preferences.getEnum(songSortByKey, SongSortBy.DateAdded)) {
|
||||
preferences.edit { putEnum(songSortByKey, it) }
|
||||
collectItems(sortBy = it)
|
||||
}
|
||||
|
||||
var sortOrder by mutableStatePreferenceOf(preferences.getEnum(songSortOrderKey, SortOrder.Ascending)) {
|
||||
preferences.edit { putEnum(songSortOrderKey, it) }
|
||||
collectItems(sortOrder = it)
|
||||
}
|
||||
|
||||
private var job: Job? = null
|
||||
|
||||
private val preferences: SharedPreferences
|
||||
get() = getApplication<Application>().preferences
|
||||
|
||||
init {
|
||||
collectItems()
|
||||
}
|
||||
|
||||
private fun collectItems(sortBy: SongSortBy = this.sortBy, sortOrder: SortOrder = this.sortOrder) {
|
||||
job?.cancel()
|
||||
job = viewModelScope.launch {
|
||||
Database.songs(sortBy, sortOrder).flowOn(Dispatchers.IO).collect {
|
||||
items = it
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ExperimentalFoundationApi
|
||||
@ExperimentalAnimationApi
|
||||
@Composable
|
||||
fun SongsTab(
|
||||
viewModel: SongsTabViewModel = viewModel()
|
||||
) {
|
||||
val (colorPalette, typography) = LocalAppearance.current
|
||||
val binder = LocalPlayerServiceBinder.current
|
||||
|
||||
val thumbnailSize = Dimensions.thumbnails.song.px
|
||||
|
||||
val sortOrderIconRotation by animateFloatAsState(
|
||||
targetValue = if (viewModel.sortOrder == SortOrder.Ascending) 0f else 180f,
|
||||
animationSpec = tween(durationMillis = 400, easing = LinearEasing)
|
||||
)
|
||||
|
||||
LazyColumn(
|
||||
contentPadding = LocalPlayerAwarePaddingValues.current,
|
||||
modifier = Modifier
|
||||
.background(colorPalette.background0)
|
||||
.fillMaxSize()
|
||||
) {
|
||||
item(
|
||||
key = "header",
|
||||
contentType = 0
|
||||
) {
|
||||
Column(
|
||||
horizontalAlignment = Alignment.End,
|
||||
verticalArrangement = Arrangement.Bottom,
|
||||
modifier = Modifier
|
||||
.padding(horizontal = 16.dp)
|
||||
.height(128.dp)
|
||||
.fillMaxWidth()
|
||||
) {
|
||||
BasicText(
|
||||
text = "Songs",
|
||||
style = typography.xxl.medium
|
||||
)
|
||||
|
||||
Row(
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
horizontalArrangement = Arrangement.spacedBy(8.dp),
|
||||
modifier = Modifier
|
||||
.padding(vertical = 8.dp)
|
||||
) {
|
||||
@Composable
|
||||
fun Item(
|
||||
@DrawableRes iconId: Int,
|
||||
sortBy: SongSortBy
|
||||
) {
|
||||
Image(
|
||||
painter = painterResource(iconId),
|
||||
contentDescription = null,
|
||||
colorFilter = ColorFilter.tint(if (viewModel.sortBy == sortBy) colorPalette.text else colorPalette.textDisabled),
|
||||
modifier = Modifier
|
||||
.clickable { viewModel.sortBy = sortBy }
|
||||
.padding(all = 4.dp)
|
||||
.size(18.dp)
|
||||
)
|
||||
}
|
||||
|
||||
Item(
|
||||
iconId = R.drawable.trending,
|
||||
sortBy = SongSortBy.PlayTime
|
||||
)
|
||||
|
||||
Item(
|
||||
iconId = R.drawable.text,
|
||||
sortBy = SongSortBy.Title
|
||||
)
|
||||
|
||||
Item(
|
||||
iconId = R.drawable.calendar,
|
||||
sortBy = SongSortBy.DateAdded
|
||||
)
|
||||
|
||||
Spacer(
|
||||
modifier = Modifier
|
||||
.width(2.dp)
|
||||
)
|
||||
|
||||
Image(
|
||||
painter = painterResource(R.drawable.arrow_up),
|
||||
contentDescription = null,
|
||||
colorFilter = ColorFilter.tint(colorPalette.text),
|
||||
modifier = Modifier
|
||||
.clickable { viewModel.sortOrder = !viewModel.sortOrder }
|
||||
.padding(all = 4.dp)
|
||||
.size(18.dp)
|
||||
.graphicsLayer { rotationZ = sortOrderIconRotation }
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
itemsIndexed(
|
||||
items = viewModel.items,
|
||||
key = { _, song -> song.id }
|
||||
) { index, song ->
|
||||
SongItem(
|
||||
song = song,
|
||||
thumbnailSize = thumbnailSize,
|
||||
onClick = {
|
||||
binder?.stopRadio()
|
||||
binder?.player?.forcePlayAtIndex(
|
||||
viewModel.items.map(DetailedSong::asMediaItem),
|
||||
index
|
||||
)
|
||||
},
|
||||
menuContent = {
|
||||
InHistoryMediaItemMenu(song = song)
|
||||
},
|
||||
onThumbnailContent = {
|
||||
AnimatedVisibility(
|
||||
visible = viewModel.sortBy == SongSortBy.PlayTime,
|
||||
enter = fadeIn(),
|
||||
exit = fadeOut(),
|
||||
modifier = Modifier
|
||||
.align(Alignment.BottomCenter)
|
||||
) {
|
||||
BasicText(
|
||||
text = song.formattedTotalPlayTime,
|
||||
style = typography.xxs.semiBold.center.color(
|
||||
Color.White
|
||||
),
|
||||
maxLines = 2,
|
||||
overflow = TextOverflow.Ellipsis,
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.background(
|
||||
brush = Brush.verticalGradient(
|
||||
colors = listOf(
|
||||
Color.Transparent,
|
||||
Color.Black.copy(alpha = 0.75f)
|
||||
)
|
||||
),
|
||||
shape = ThumbnailRoundness.shape
|
||||
)
|
||||
.padding(
|
||||
horizontal = 8.dp,
|
||||
vertical = 4.dp
|
||||
)
|
||||
)
|
||||
}
|
||||
},
|
||||
modifier = Modifier
|
||||
.animateItemPlacement()
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user