Skip to content

Commit

Permalink
Play all favorites button (#329)
Browse files Browse the repository at this point in the history
  • Loading branch information
siper authored Jan 26, 2025
1 parent 45832fb commit e3f7284
Show file tree
Hide file tree
Showing 10 changed files with 163 additions and 29 deletions.
38 changes: 38 additions & 0 deletions core/ui/src/main/java/ru/stersh/youamp/core/ui/PlayAllButton.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
package ru.stersh.youamp.core.ui

import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.rounded.PlayArrow
import androidx.compose.material3.ExtendedFloatingActionButton
import androidx.compose.material3.Icon
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.tooling.preview.Preview

@Composable
fun PlayAllFabButton(
onClick: () -> Unit,
modifier: Modifier = Modifier
) {
ExtendedFloatingActionButton(
onClick = onClick,
modifier = modifier
) {
Icon(
imageVector = Icons.Rounded.PlayArrow,
contentDescription = stringResource(R.string.play_all_title)
)
Text(text = stringResource(R.string.play_all_title))
}
}

@Composable
@Preview
private fun PlayAllFabButtonPreview() {
YouampPlayerTheme {
PlayAllFabButton(
onClick = {}
)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,5 +8,5 @@ import ru.stresh.youamp.feature.favorite.list.ui.FavoriteListViewModel

val favoriteListModule = module {
single<FavoritesRepository> { FavoritesRepositoryImpl(get()) }
viewModel { FavoriteListViewModel(get()) }
viewModel { FavoriteListViewModel(get(), get(), get()) }
}
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,8 @@ internal class FavoritesRepositoryImpl(private val apiProvider: ApiProvider) : F
id = id,
title = title,
artist = artist,
artworkUrl = api.getCoverArtUrl(coverArt)
artworkUrl = api.getCoverArtUrl(coverArt),
userRating = userRating
)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,5 +4,6 @@ internal data class FavoriteSong(
val id: String,
val title: String,
val artist: String?,
val artworkUrl: String?
val artworkUrl: String?,
val userRating: Int?
)
Original file line number Diff line number Diff line change
@@ -1,10 +1,15 @@
package ru.stresh.youamp.feature.favorite.list.ui

import android.content.res.Configuration
import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxSize
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.items
Expand All @@ -18,6 +23,7 @@ import androidx.compose.material3.Text
import androidx.compose.material3.pulltorefresh.PullToRefreshBox
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.tooling.preview.Preview
Expand All @@ -28,7 +34,8 @@ import org.koin.androidx.compose.koinViewModel
import ru.stersh.youamp.core.ui.Artwork
import ru.stersh.youamp.core.ui.EmptyLayout
import ru.stersh.youamp.core.ui.ErrorLayout
import ru.stersh.youamp.core.ui.SkeletonScope
import ru.stersh.youamp.core.ui.PlayAllFabButton
import ru.stersh.youamp.core.ui.SkeletonLayout

@Composable
fun FavoriteListScreen(
Expand All @@ -41,6 +48,7 @@ fun FavoriteListScreen(

FavoriteListScreen(
state = state,
onPlayAll = viewModel::playAll,
onRetry = viewModel::retry,
onRefresh = viewModel::refresh,
onSongClick = onSongClick
Expand All @@ -50,6 +58,7 @@ fun FavoriteListScreen(
@Composable
private fun FavoriteListScreen(
state: FavoriteListStateUi,
onPlayAll: () -> Unit,
onRetry: () -> Unit,
onRefresh: () -> Unit,
onSongClick: (id: String) -> Unit
Expand All @@ -76,19 +85,32 @@ private fun FavoriteListScreen(
}

state.favorites?.songs != null -> {
LazyColumn(
modifier = Modifier.fillMaxSize()
Box(
modifier = Modifier
.background(MaterialTheme.colorScheme.background)
.fillMaxSize()
) {
items(
items = state.favorites.songs,
contentType = { "song" },
key = { "song_${it.id}" }
) { song ->
FavoriteSongItem(
song = song,
onClick = { onSongClick(song.id) }
)
LazyColumn(modifier = Modifier.fillMaxSize()) {
items(
items = state.favorites.songs,
contentType = { "song" },
key = { "song_${it.id}" }
) { song ->
FavoriteSongItem(
song = song,
onClick = { onSongClick(song.id) }
)
}
item(key = "fab_spacer") {
Spacer(modifier = Modifier.height(88.dp))
}
}
PlayAllFabButton(
onClick = onPlayAll,
modifier = Modifier
.align(Alignment.BottomEnd)
.padding(16.dp)
)
}
}
}
Expand All @@ -97,17 +119,17 @@ private fun FavoriteListScreen(

@Composable
private fun Progress() {
Column {
SkeletonLayout {
repeat(10) {
ListItem(
headlineContent = {
Column(verticalArrangement = Arrangement.spacedBy(4.dp)) {
SkeletonScope.SkeletonItem(modifier = Modifier.size(width = 130.dp, height = 16.dp))
SkeletonScope.SkeletonItem(modifier = Modifier.size(width = 200.dp, height = 16.dp))
SkeletonItem(modifier = Modifier.size(width = 130.dp, height = 16.dp))
SkeletonItem(modifier = Modifier.size(width = 200.dp, height = 16.dp))
}
},
leadingContent = {
SkeletonScope.SkeletonItem(modifier = Modifier.size(48.dp))
SkeletonItem(modifier = Modifier.size(48.dp))
}
)
}
Expand Down Expand Up @@ -185,7 +207,7 @@ private fun FavoriteListScreenPreview() {
),
)
val state = FavoriteListStateUi(
progress = true,
progress = false,
isRefreshing = false,
error = false,
favorites = FavoritesUi(
Expand All @@ -194,6 +216,7 @@ private fun FavoriteListScreenPreview() {
)
FavoriteListScreen(
state = state,
onPlayAll = {},
onRetry = {},
onRefresh = {},
onSongClick = {}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,20 @@ import kotlinx.coroutines.Job
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.catch
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.update
import kotlinx.coroutines.launch
import ru.stersh.youamp.shared.player.queue.AudioSource
import ru.stersh.youamp.shared.player.queue.PlayerQueueAudioSourceManager
import ru.stersh.youamp.shared.player.queue.PlayerQueueManager
import ru.stresh.youamp.feature.favorite.list.domain.FavoritesRepository
import timber.log.Timber

internal class FavoriteListViewModel(
private val favoritesRepository: FavoritesRepository
private val favoritesRepository: FavoritesRepository,
private val playerQueueAudioSourceManager: PlayerQueueAudioSourceManager,
private val playerQueueManager: PlayerQueueManager
) : ViewModel() {
private val _state = MutableStateFlow(FavoriteListStateUi())
val state: StateFlow<FavoriteListStateUi>
Expand All @@ -25,6 +31,27 @@ internal class FavoriteListViewModel(
retry()
}

fun playAll() = viewModelScope.launch {
val favorites = runCatching { favoritesRepository.getFavorites().first() }
.onFailure { Timber.w(it) }
.getOrNull()
?: return@launch

favorites.songs.forEach {
playerQueueAudioSourceManager.addSource(
AudioSource.RawSong(
id = it.id,
title = it.title,
artist = it.artist,
artworkUrl = it.artworkUrl,
starred = true,
userRating = it.userRating
)
)
}
playerQueueManager.playPosition(0)
}

fun refresh() {
_state.update {
it.copy(isRefreshing = true)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,4 +15,13 @@ sealed class AudioSource(open val id: String) {
override val id: String,
val songId: String? = null
) : AudioSource(id)

data class RawSong(
override val id: String,
val title: String?,
val artist: String?,
val artworkUrl: String?,
val starred: Boolean?,
val userRating: Int?
) : AudioSource(id)
}
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,6 @@ import kotlinx.coroutines.coroutineScope
import ru.stersh.youamp.core.api.PlaylistEntry
import ru.stersh.youamp.core.api.provider.ApiProvider
import ru.stersh.youamp.shared.player.android.MusicService
import ru.stersh.youamp.shared.player.utils.MEDIA_ITEM_ALBUM_ID
import ru.stersh.youamp.shared.player.utils.MEDIA_ITEM_DURATION
import ru.stersh.youamp.shared.player.utils.MEDIA_SONG_ID
import ru.stersh.youamp.shared.player.utils.mediaControllerFuture
import ru.stersh.youamp.shared.player.utils.toMediaItem
Expand Down Expand Up @@ -89,6 +87,7 @@ internal class PlayerQueueAudioSourceManagerImpl(
is AudioSource.Album -> getSongs(source)
is AudioSource.Artist -> getSongs(source)
is AudioSource.Playlist -> getSongs(source)
is AudioSource.RawSong -> listOf(getSong(source))
}
}

Expand Down Expand Up @@ -137,6 +136,48 @@ internal class PlayerQueueAudioSourceManagerImpl(
?: emptyList()
}

private suspend fun getSong(source: AudioSource.RawSong): MediaItem {
val songUri = apiProvider
.getApi()
.downloadUrl(source.id)
.toUri()

val starredRating = HeartRating(source.starred != null)

val songRating = source.userRating
val rating = if (songRating != null && songRating > 0) {
StarRating(5, songRating.toFloat())
} else {
StarRating(5)
}

val metadata = MediaMetadata
.Builder()
.setTitle(source.title)
.setArtist(source.artist)
.setExtras(
bundleOf(
MEDIA_SONG_ID to source.id,
),
)
.setUserRating(starredRating)
.setOverallRating(rating)
.setArtworkUri(source.artworkUrl?.toUri())
.build()
val requestMetadata = MediaItem
.RequestMetadata
.Builder()
.setMediaUri(songUri)
.build()
return MediaItem
.Builder()
.setMediaId(source.id)
.setMediaMetadata(metadata)
.setRequestMetadata(requestMetadata)
.setUri(songUri)
.build()
}

private suspend fun PlaylistEntry.toMediaItem(): MediaItem {
val songUri = apiProvider
.getApi()
Expand All @@ -162,8 +203,6 @@ internal class PlayerQueueAudioSourceManagerImpl(
.setArtist(artist)
.setExtras(
bundleOf(
MEDIA_ITEM_ALBUM_ID to albumId,
MEDIA_ITEM_DURATION to (duration ?: 0) * 1000L,
MEDIA_SONG_ID to id,
),
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,8 +34,6 @@ internal suspend fun Song.toMediaItem(apiProvider: ApiProvider): MediaItem {
.setArtist(artist)
.setExtras(
bundleOf(
MEDIA_ITEM_ALBUM_ID to albumId,
MEDIA_ITEM_DURATION to (duration ?: 0) * 1000L,
MEDIA_SONG_ID to id,
),
)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
package ru.stersh.youamp.shared.player.utils

const val MEDIA_ITEM_ALBUM_ID = "album_id"
const val MEDIA_ITEM_DURATION = "duration"
const val MEDIA_SONG_ID = "song_id"

0 comments on commit e3f7284

Please sign in to comment.