From bf21406ec368abaaef474c833b974a908163cf1e Mon Sep 17 00:00:00 2001 From: Kirill Zhukov Date: Sun, 2 Feb 2025 17:40:09 +0300 Subject: [PATCH] Like artists and albums --- .idea/deploymentTargetSelector.xml | 8 + .../ru/stersh/youamp/core/api/SubsonicApi.kt | 8 + .../ru/stersh/youamp/core/ui/HeaderButtons.kt | 145 ++++++++++++++++++ .../ru/stersh/youamp/core/ui/PlayButtons.kt | 46 ------ core/ui/src/main/res/values/strings.xml | 2 + .../java/ru/stersh/youamp/feature/album/Di.kt | 6 +- .../album/data/AlbumFavoriteRepositoryImpl.kt | 18 +++ .../album/data/AlbumInfoRepositoryImpl.kt | 20 ++- .../album/domain/AlbumFavoriteRepository.kt | 6 + .../youamp/feature/album/domain/AlbumInfo.kt | 1 + .../feature/album/ui/AlbumInfoScreen.kt | 26 +++- .../feature/album/ui/AlbumInfoStateUi.kt | 1 + .../feature/album/ui/AlbumInfoViewModel.kt | 15 ++ .../stersh/youamp/feature/album/ui/Mapper.kt | 1 + .../ru/stersh/youamp/feature/artist/Di.kt | 5 +- .../data/ArtistFavoriteRepositoryImpl.kt | 18 +++ .../artist/data/ArtistInfoRepositoryImpl.kt | 17 +- .../artist/domain/ArtistFavoriteRepository.kt | 6 + .../feature/artist/domain/ArtistInfo.kt | 1 + .../feature/artist/ui/ArtistInfoScreen.kt | 30 ++-- .../artist/ui/ArtistInfoScreenState.kt | 1 + .../feature/artist/ui/ArtistInfoViewModel.kt | 15 ++ .../stersh/youamp/feature/artist/ui/Mapper.kt | 1 + .../feature/playlist/ui/PlaylistInfoScreen.kt | 11 +- 24 files changed, 329 insertions(+), 79 deletions(-) create mode 100644 core/ui/src/main/java/ru/stersh/youamp/core/ui/HeaderButtons.kt delete mode 100644 core/ui/src/main/java/ru/stersh/youamp/core/ui/PlayButtons.kt create mode 100644 feature/album/info/src/main/java/ru/stersh/youamp/feature/album/data/AlbumFavoriteRepositoryImpl.kt create mode 100644 feature/album/info/src/main/java/ru/stersh/youamp/feature/album/domain/AlbumFavoriteRepository.kt create mode 100644 feature/artist/info/src/main/java/ru/stersh/youamp/feature/artist/data/ArtistFavoriteRepositoryImpl.kt create mode 100644 feature/artist/info/src/main/java/ru/stersh/youamp/feature/artist/domain/ArtistFavoriteRepository.kt diff --git a/.idea/deploymentTargetSelector.xml b/.idea/deploymentTargetSelector.xml index b268ef36..517e0ce4 100644 --- a/.idea/deploymentTargetSelector.xml +++ b/.idea/deploymentTargetSelector.xml @@ -4,6 +4,14 @@ diff --git a/core/api/src/main/java/ru/stersh/youamp/core/api/SubsonicApi.kt b/core/api/src/main/java/ru/stersh/youamp/core/api/SubsonicApi.kt index b6b372b5..3c346f3c 100644 --- a/core/api/src/main/java/ru/stersh/youamp/core/api/SubsonicApi.kt +++ b/core/api/src/main/java/ru/stersh/youamp/core/api/SubsonicApi.kt @@ -115,6 +115,14 @@ class SubsonicApi( suspend fun unstarSong(vararg id: String) = api.unstar(id = id.asList()) + suspend fun starAlbum(vararg id: String) = api.star(albumIds = id.asList()) + + suspend fun unstarAlbum(vararg id: String) = api.unstar(albumIds = id.asList()) + + suspend fun starArtist(vararg id: String) = api.star(artistIds = id.asList()) + + suspend fun unstarArtist(vararg id: String) = api.unstar(artistIds = id.asList()) + suspend fun scrobble( id: String, time: Long? = null, diff --git a/core/ui/src/main/java/ru/stersh/youamp/core/ui/HeaderButtons.kt b/core/ui/src/main/java/ru/stersh/youamp/core/ui/HeaderButtons.kt new file mode 100644 index 00000000..dcbfae2f --- /dev/null +++ b/core/ui/src/main/java/ru/stersh/youamp/core/ui/HeaderButtons.kt @@ -0,0 +1,145 @@ +package ru.stersh.youamp.core.ui + +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.layout.width +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.rounded.Favorite +import androidx.compose.material.icons.rounded.FavoriteBorder +import androidx.compose.material.icons.rounded.PlayArrow +import androidx.compose.material.icons.rounded.Shuffle +import androidx.compose.material3.FilledTonalIconButton +import androidx.compose.material3.FilledTonalIconToggleButton +import androidx.compose.material3.Icon +import androidx.compose.material3.LocalTextStyle +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.CompositionLocalProvider +import androidx.compose.runtime.Stable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.text.style.TextAlign +import androidx.compose.ui.text.style.TextOverflow +import androidx.compose.ui.unit.dp + +@Composable +fun FavoriteButton( + isFavorite: Boolean, + onChange: (isFavorite: Boolean) -> Unit, + modifier: Modifier = Modifier +) { + val text = if (isFavorite) { + stringResource(R.string.dislike_title) + } else { + stringResource(R.string.like_title) + } + ButtonLayout( + button = { + FilledTonalIconToggleButton( + checked = isFavorite, + onCheckedChange = onChange, + modifier = modifier.size(ButtonSize) + ) { + Icon( + imageVector = if (isFavorite) { + Icons.Rounded.Favorite + } else { + Icons.Rounded.FavoriteBorder + }, + contentDescription = text + ) + } + }, + title = { + Text( + text = text, + maxLines = 1, + overflow = TextOverflow.Ellipsis, + textAlign = TextAlign.Center + ) + }, + modifier = modifier + ) +} + +@Composable +fun PlayAllButton( + onClick: () -> Unit, + modifier: Modifier = Modifier +) { + ButtonLayout( + button = { + FilledTonalIconButton( + onClick = onClick, + modifier = modifier.size(ButtonSize) + ) { + Icon( + imageVector = Icons.Rounded.PlayArrow, + contentDescription = stringResource(R.string.play_all_title) + ) + } + }, + title = { + Text( + text = stringResource(R.string.play_all_title), + maxLines = 1, + overflow = TextOverflow.Ellipsis, + textAlign = TextAlign.Center + ) + }, + modifier = modifier + ) +} + +@Composable +fun PlayShuffledButton( + onClick: () -> Unit, + modifier: Modifier = Modifier +) { + ButtonLayout( + button = { + FilledTonalIconButton( + onClick = onClick, + modifier = modifier.size(ButtonSize) + ) { + Icon( + imageVector = Icons.Rounded.Shuffle, + contentDescription = stringResource(R.string.shuffle_title) + ) + } + }, + title = { + Text( + text = stringResource(R.string.shuffle_title), + maxLines = 1, + overflow = TextOverflow.Ellipsis, + textAlign = TextAlign.Center + ) + }, + modifier = modifier + ) +} + +@Stable +private val ButtonSize = 64.dp + +@Composable +private fun ButtonLayout( + button: @Composable () -> Unit, + title: @Composable () -> Unit, + modifier: Modifier = Modifier +) { + Column( + horizontalAlignment = Alignment.CenterHorizontally, + verticalArrangement = Arrangement.spacedBy(8.dp), + modifier = modifier.width(72.dp) + ) { + button() + CompositionLocalProvider(LocalTextStyle provides MaterialTheme.typography.labelSmall) { + title() + } + } +} \ No newline at end of file diff --git a/core/ui/src/main/java/ru/stersh/youamp/core/ui/PlayButtons.kt b/core/ui/src/main/java/ru/stersh/youamp/core/ui/PlayButtons.kt deleted file mode 100644 index d2794c09..00000000 --- a/core/ui/src/main/java/ru/stersh/youamp/core/ui/PlayButtons.kt +++ /dev/null @@ -1,46 +0,0 @@ -package ru.stersh.youamp.core.ui - -import androidx.compose.material.icons.Icons -import androidx.compose.material.icons.rounded.PlayArrow -import androidx.compose.material.icons.rounded.Shuffle -import androidx.compose.material3.Button -import androidx.compose.material3.Icon -import androidx.compose.material3.OutlinedButton -import androidx.compose.material3.Text -import androidx.compose.runtime.Composable -import androidx.compose.ui.Modifier -import androidx.compose.ui.res.stringResource - -@Composable -fun PlayAllButton( - onClick: () -> Unit, - modifier: Modifier = Modifier -) { - Button( - 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 -fun PlayShuffledButton( - onClick: () -> Unit, - modifier: Modifier = Modifier -) { - OutlinedButton( - onClick = onClick, - modifier = modifier - ) { - Icon( - imageVector = Icons.Rounded.Shuffle, - contentDescription = stringResource(R.string.shuffle_title) - ) - Text(text = stringResource(R.string.shuffle_title)) - } -} \ No newline at end of file diff --git a/core/ui/src/main/res/values/strings.xml b/core/ui/src/main/res/values/strings.xml index 1aa614f7..02b36689 100644 --- a/core/ui/src/main/res/values/strings.xml +++ b/core/ui/src/main/res/values/strings.xml @@ -9,4 +9,6 @@ Oops, something went wrong There\'s nothing here yet… User avatar + Dislike + Like \ No newline at end of file diff --git a/feature/album/info/src/main/java/ru/stersh/youamp/feature/album/Di.kt b/feature/album/info/src/main/java/ru/stersh/youamp/feature/album/Di.kt index 4c061cc4..693f7cf0 100644 --- a/feature/album/info/src/main/java/ru/stersh/youamp/feature/album/Di.kt +++ b/feature/album/info/src/main/java/ru/stersh/youamp/feature/album/Di.kt @@ -2,13 +2,17 @@ package ru.stersh.youamp.feature.album import org.koin.core.module.dsl.viewModel import org.koin.dsl.module +import ru.stersh.youamp.feature.album.data.AlbumFavoriteRepositoryImpl import ru.stersh.youamp.feature.album.data.AlbumInfoRepositoryImpl +import ru.stersh.youamp.feature.album.domain.AlbumFavoriteRepository import ru.stersh.youamp.feature.album.domain.AlbumInfoRepository import ru.stersh.youamp.feature.album.ui.AlbumInfoViewModel val albumInfoModule = module { single { AlbumInfoRepositoryImpl(get()) } + single { AlbumFavoriteRepositoryImpl(get()) } + viewModel { (id: String) -> - AlbumInfoViewModel(id, get(), get()) + AlbumInfoViewModel(id, get(), get(), get()) } } \ No newline at end of file diff --git a/feature/album/info/src/main/java/ru/stersh/youamp/feature/album/data/AlbumFavoriteRepositoryImpl.kt b/feature/album/info/src/main/java/ru/stersh/youamp/feature/album/data/AlbumFavoriteRepositoryImpl.kt new file mode 100644 index 00000000..02e5d262 --- /dev/null +++ b/feature/album/info/src/main/java/ru/stersh/youamp/feature/album/data/AlbumFavoriteRepositoryImpl.kt @@ -0,0 +1,18 @@ +package ru.stersh.youamp.feature.album.data + +import ru.stersh.youamp.core.api.provider.ApiProvider +import ru.stersh.youamp.feature.album.domain.AlbumFavoriteRepository + +internal class AlbumFavoriteRepositoryImpl( + private val apiProvider: ApiProvider +) : AlbumFavoriteRepository { + + override suspend fun setFavorite(id: String, isFavorite: Boolean) { + val api = apiProvider.getApi() + if (isFavorite) { + api.starAlbum(id) + } else { + api.unstarAlbum(id) + } + } +} \ No newline at end of file diff --git a/feature/album/info/src/main/java/ru/stersh/youamp/feature/album/data/AlbumInfoRepositoryImpl.kt b/feature/album/info/src/main/java/ru/stersh/youamp/feature/album/data/AlbumInfoRepositoryImpl.kt index 7e7ffb30..d0a70d0a 100644 --- a/feature/album/info/src/main/java/ru/stersh/youamp/feature/album/data/AlbumInfoRepositoryImpl.kt +++ b/feature/album/info/src/main/java/ru/stersh/youamp/feature/album/data/AlbumInfoRepositoryImpl.kt @@ -1,5 +1,7 @@ package ru.stersh.youamp.feature.album.data +import kotlinx.coroutines.async +import kotlinx.coroutines.coroutineScope import ru.stersh.youamp.core.api.Album import ru.stersh.youamp.core.api.Song import ru.stersh.youamp.core.api.SubsonicApi @@ -13,19 +15,27 @@ internal class AlbumInfoRepositoryImpl( private val apiProvider: ApiProvider ) : AlbumInfoRepository { - override suspend fun getAlbumInfo(id: String): AlbumInfo { + override suspend fun getAlbumInfo(id: String): AlbumInfo = coroutineScope { val api = apiProvider.getApi() - return api - .getAlbum(id) - .toDomain(api) + val starred = async { api.getStarred2() } + val albumInfo = async { api.getAlbum(id) } + val isFavorite = starred + .await() + .starred2Result + .album + ?.any { it.id == id } == true + return@coroutineScope albumInfo + .await() + .toDomain(api, isFavorite) } - private fun Album.toDomain(api: SubsonicApi): AlbumInfo { + private fun Album.toDomain(api: SubsonicApi, isFavorite: Boolean): AlbumInfo { return AlbumInfo( title = requireNotNull(name ?: album), artist = artist, year = year?.toString(), artworkUrl = api.getCoverArtUrl(coverArt), + isFavorite = isFavorite, songs = song .orEmpty() .map { it.toDomain() } diff --git a/feature/album/info/src/main/java/ru/stersh/youamp/feature/album/domain/AlbumFavoriteRepository.kt b/feature/album/info/src/main/java/ru/stersh/youamp/feature/album/domain/AlbumFavoriteRepository.kt new file mode 100644 index 00000000..9e91dc3a --- /dev/null +++ b/feature/album/info/src/main/java/ru/stersh/youamp/feature/album/domain/AlbumFavoriteRepository.kt @@ -0,0 +1,6 @@ +package ru.stersh.youamp.feature.album.domain + +internal interface AlbumFavoriteRepository { + + suspend fun setFavorite(id: String, isFavorite: Boolean) +} \ No newline at end of file diff --git a/feature/album/info/src/main/java/ru/stersh/youamp/feature/album/domain/AlbumInfo.kt b/feature/album/info/src/main/java/ru/stersh/youamp/feature/album/domain/AlbumInfo.kt index 17ec3ad7..8be3a816 100644 --- a/feature/album/info/src/main/java/ru/stersh/youamp/feature/album/domain/AlbumInfo.kt +++ b/feature/album/info/src/main/java/ru/stersh/youamp/feature/album/domain/AlbumInfo.kt @@ -5,5 +5,6 @@ internal data class AlbumInfo( val title: String, val artist: String, val year: String?, + val isFavorite: Boolean, val songs: List ) \ No newline at end of file diff --git a/feature/album/info/src/main/java/ru/stersh/youamp/feature/album/ui/AlbumInfoScreen.kt b/feature/album/info/src/main/java/ru/stersh/youamp/feature/album/ui/AlbumInfoScreen.kt index 11becf8e..049ae588 100644 --- a/feature/album/info/src/main/java/ru/stersh/youamp/feature/album/ui/AlbumInfoScreen.kt +++ b/feature/album/info/src/main/java/ru/stersh/youamp/feature/album/ui/AlbumInfoScreen.kt @@ -33,6 +33,7 @@ import org.koin.core.parameter.parametersOf import ru.stersh.youamp.core.ui.Artwork import ru.stersh.youamp.core.ui.BackNavigationButton import ru.stersh.youamp.core.ui.ErrorLayout +import ru.stersh.youamp.core.ui.FavoriteButton import ru.stersh.youamp.core.ui.PlayAllButton import ru.stersh.youamp.core.ui.PlayShuffledButton import ru.stersh.youamp.core.ui.SkeletonLayout @@ -52,6 +53,7 @@ fun AlbumInfoScreen( state = state, onPlayAll = viewModel::playAll, onPlayShuffled = viewModel::playShuffled, + onFavoriteChange = viewModel::onFavoriteChange, onPlaySong = onOpenSongInfo, onRetry = viewModel::retry, onBackClick = onBackClick @@ -63,6 +65,7 @@ private fun AlbumInfoScreen( state: AlbumInfoStateUi, onPlayAll: () -> Unit, onPlayShuffled: () -> Unit, + onFavoriteChange: (isFavorite: Boolean) -> Unit, onPlaySong: (id: String) -> Unit, onRetry: () -> Unit, onBackClick: () -> Unit, @@ -94,6 +97,7 @@ private fun AlbumInfoScreen( state = state.content, onPlayAll = onPlayAll, onPlayShuffled = onPlayShuffled, + onFavoriteChange = onFavoriteChange, onPlaySong = onPlaySong, modifier = Modifier.padding(it) ) @@ -168,6 +172,7 @@ private fun ContentState( state: AlbumInfoUi, onPlayAll: () -> Unit, onPlayShuffled: () -> Unit, + onFavoriteChange: (isFavorite: Boolean) -> Unit, onPlaySong: (id: String) -> Unit, modifier: Modifier ) { @@ -180,6 +185,7 @@ private fun ContentState( state = state, onPlayAll = onPlayAll, onPlayShuffled = onPlayShuffled, + onFavoriteChange = onFavoriteChange ) } items( @@ -200,7 +206,8 @@ private fun ContentState( private fun Header( state: AlbumInfoUi, onPlayAll: () -> Unit, - onPlayShuffled: () -> Unit + onPlayShuffled: () -> Unit, + onFavoriteChange: (isFavorite: Boolean) -> Unit ) { Column( horizontalAlignment = Alignment.CenterHorizontally, @@ -246,16 +253,19 @@ private fun Header( Row( modifier = Modifier .fillMaxWidth() - .padding(horizontal = 24.dp), - horizontalArrangement = Arrangement.spacedBy(8.dp) + .padding(horizontal = 24.dp) + .padding(bottom = 12.dp), + horizontalArrangement = Arrangement.SpaceEvenly ) { PlayAllButton( - onClick = onPlayAll, - modifier = Modifier.weight(0.5f) + onClick = onPlayAll ) PlayShuffledButton( - onClick = onPlayShuffled, - modifier = Modifier.weight(0.5f) + onClick = onPlayShuffled + ) + FavoriteButton( + isFavorite = state.isFavorite, + onChange = onFavoriteChange ) } } @@ -342,6 +352,7 @@ private fun AlbumInfoScreenPreview() { error = false, content = AlbumInfoUi( artworkUrl = null, + isFavorite = true, title = "Test", artist = "Test", year = "2024", @@ -350,6 +361,7 @@ private fun AlbumInfoScreenPreview() { ), onPlayAll = {}, onPlayShuffled = {}, + onFavoriteChange = {}, onPlaySong = {}, onRetry = {}, onBackClick = {} diff --git a/feature/album/info/src/main/java/ru/stersh/youamp/feature/album/ui/AlbumInfoStateUi.kt b/feature/album/info/src/main/java/ru/stersh/youamp/feature/album/ui/AlbumInfoStateUi.kt index 427ee376..a8b53fcc 100644 --- a/feature/album/info/src/main/java/ru/stersh/youamp/feature/album/ui/AlbumInfoStateUi.kt +++ b/feature/album/info/src/main/java/ru/stersh/youamp/feature/album/ui/AlbumInfoStateUi.kt @@ -15,6 +15,7 @@ internal data class AlbumInfoUi( val title: String, val artist: String, val year: String?, + val isFavorite: Boolean, val songs: List ) diff --git a/feature/album/info/src/main/java/ru/stersh/youamp/feature/album/ui/AlbumInfoViewModel.kt b/feature/album/info/src/main/java/ru/stersh/youamp/feature/album/ui/AlbumInfoViewModel.kt index f9c51d32..b520a88e 100644 --- a/feature/album/info/src/main/java/ru/stersh/youamp/feature/album/ui/AlbumInfoViewModel.kt +++ b/feature/album/info/src/main/java/ru/stersh/youamp/feature/album/ui/AlbumInfoViewModel.kt @@ -6,6 +6,7 @@ import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.update import kotlinx.coroutines.launch +import ru.stersh.youamp.feature.album.domain.AlbumFavoriteRepository import ru.stersh.youamp.feature.album.domain.AlbumInfoRepository import ru.stersh.youamp.shared.player.queue.AudioSource import ru.stersh.youamp.shared.player.queue.PlayerQueueAudioSourceManager @@ -14,6 +15,7 @@ import timber.log.Timber internal class AlbumInfoViewModel( private val id: String, private val albumInfoRepository: AlbumInfoRepository, + private val albumFavoriteRepository: AlbumFavoriteRepository, private val playerQueueAudioSourceManager: PlayerQueueAudioSourceManager ) : ViewModel() { @@ -37,6 +39,19 @@ internal class AlbumInfoViewModel( loadAlbum() } + fun onFavoriteChange(isFavorite: Boolean) = viewModelScope.launch { + runCatching { albumFavoriteRepository.setFavorite(id, isFavorite) }.fold( + onSuccess = { + _state.update { + it.copy(content = it.content?.copy(isFavorite = isFavorite)) + } + }, + onFailure = { + Timber.w(it) + } + ) + } + private fun loadAlbum() = viewModelScope.launch { _state.update { it.copy( diff --git a/feature/album/info/src/main/java/ru/stersh/youamp/feature/album/ui/Mapper.kt b/feature/album/info/src/main/java/ru/stersh/youamp/feature/album/ui/Mapper.kt index e40dbed8..df4b6f02 100644 --- a/feature/album/info/src/main/java/ru/stersh/youamp/feature/album/ui/Mapper.kt +++ b/feature/album/info/src/main/java/ru/stersh/youamp/feature/album/ui/Mapper.kt @@ -9,6 +9,7 @@ internal fun AlbumInfo.toUi(): AlbumInfoUi { artist = artist, year = year, artworkUrl = artworkUrl, + isFavorite = isFavorite, songs = songs.map { it.toUi() } ) } diff --git a/feature/artist/info/src/main/java/ru/stersh/youamp/feature/artist/Di.kt b/feature/artist/info/src/main/java/ru/stersh/youamp/feature/artist/Di.kt index 25a252cc..fb67f3b9 100644 --- a/feature/artist/info/src/main/java/ru/stersh/youamp/feature/artist/Di.kt +++ b/feature/artist/info/src/main/java/ru/stersh/youamp/feature/artist/Di.kt @@ -2,13 +2,16 @@ package ru.stersh.youamp.feature.artist import org.koin.core.module.dsl.viewModel import org.koin.dsl.module +import ru.stersh.youamp.feature.artist.data.ArtistFavoriteRepositoryImpl import ru.stersh.youamp.feature.artist.data.ArtistInfoRepositoryImpl +import ru.stersh.youamp.feature.artist.domain.ArtistFavoriteRepository import ru.stersh.youamp.feature.artist.domain.ArtistInfoRepository import ru.stersh.youamp.feature.artist.ui.ArtistInfoViewModel val artistInfoModule = module { single { ArtistInfoRepositoryImpl(get()) } + single { ArtistFavoriteRepositoryImpl(get()) } viewModel { (id: String) -> - ArtistInfoViewModel(id, get(), get()) + ArtistInfoViewModel(id, get(), get(), get()) } } \ No newline at end of file diff --git a/feature/artist/info/src/main/java/ru/stersh/youamp/feature/artist/data/ArtistFavoriteRepositoryImpl.kt b/feature/artist/info/src/main/java/ru/stersh/youamp/feature/artist/data/ArtistFavoriteRepositoryImpl.kt new file mode 100644 index 00000000..573beb9e --- /dev/null +++ b/feature/artist/info/src/main/java/ru/stersh/youamp/feature/artist/data/ArtistFavoriteRepositoryImpl.kt @@ -0,0 +1,18 @@ +package ru.stersh.youamp.feature.artist.data + +import ru.stersh.youamp.core.api.provider.ApiProvider +import ru.stersh.youamp.feature.artist.domain.ArtistFavoriteRepository + +internal class ArtistFavoriteRepositoryImpl( + private val apiProvider: ApiProvider +) : ArtistFavoriteRepository { + + override suspend fun setFavorite(id: String, isFavorite: Boolean) { + val api = apiProvider.getApi() + if (isFavorite) { + api.starArtist(id) + } else { + api.unstarArtist(id) + } + } +} \ No newline at end of file diff --git a/feature/artist/info/src/main/java/ru/stersh/youamp/feature/artist/data/ArtistInfoRepositoryImpl.kt b/feature/artist/info/src/main/java/ru/stersh/youamp/feature/artist/data/ArtistInfoRepositoryImpl.kt index 2846426c..c4db6ab2 100644 --- a/feature/artist/info/src/main/java/ru/stersh/youamp/feature/artist/data/ArtistInfoRepositoryImpl.kt +++ b/feature/artist/info/src/main/java/ru/stersh/youamp/feature/artist/data/ArtistInfoRepositoryImpl.kt @@ -1,5 +1,7 @@ package ru.stersh.youamp.feature.artist.data +import kotlinx.coroutines.async +import kotlinx.coroutines.coroutineScope import ru.stersh.youamp.core.api.Album import ru.stersh.youamp.core.api.Artist import ru.stersh.youamp.core.api.SubsonicApi @@ -12,17 +14,24 @@ internal class ArtistInfoRepositoryImpl( private val apiProvider: ApiProvider ) : ArtistInfoRepository { - override suspend fun getArtistInfo(id: String): ArtistInfo { + override suspend fun getArtistInfo(id: String): ArtistInfo = coroutineScope { val api = apiProvider.getApi() - val artist = api.getArtist(id) + val starred = async { api.getStarred2() } + val artistInfo = async { api.getArtist(id) } + val isFavorite = starred + .await() + .starred2Result + .artist + ?.any { it.id == id } == true - return artist.toDomain(api) + return@coroutineScope artistInfo.await().toDomain(api, isFavorite) } - private fun Artist.toDomain(api: SubsonicApi): ArtistInfo { + private fun Artist.toDomain(api: SubsonicApi, isFavorite: Boolean): ArtistInfo { return ArtistInfo( name = name, artworkUrl = api.getCoverArtUrl(coverArt), + isFavorite = isFavorite, albums = albums .orEmpty() .map { it.toDomain(api) } diff --git a/feature/artist/info/src/main/java/ru/stersh/youamp/feature/artist/domain/ArtistFavoriteRepository.kt b/feature/artist/info/src/main/java/ru/stersh/youamp/feature/artist/domain/ArtistFavoriteRepository.kt new file mode 100644 index 00000000..f60da73b --- /dev/null +++ b/feature/artist/info/src/main/java/ru/stersh/youamp/feature/artist/domain/ArtistFavoriteRepository.kt @@ -0,0 +1,6 @@ +package ru.stersh.youamp.feature.artist.domain + +internal interface ArtistFavoriteRepository { + + suspend fun setFavorite(id: String, isFavorite: Boolean) +} \ No newline at end of file diff --git a/feature/artist/info/src/main/java/ru/stersh/youamp/feature/artist/domain/ArtistInfo.kt b/feature/artist/info/src/main/java/ru/stersh/youamp/feature/artist/domain/ArtistInfo.kt index 16a26898..72ba99b0 100644 --- a/feature/artist/info/src/main/java/ru/stersh/youamp/feature/artist/domain/ArtistInfo.kt +++ b/feature/artist/info/src/main/java/ru/stersh/youamp/feature/artist/domain/ArtistInfo.kt @@ -2,6 +2,7 @@ package ru.stersh.youamp.feature.artist.domain internal data class ArtistInfo( val artworkUrl: String? = null, + val isFavorite: Boolean, val name: String, val albums: List ) \ No newline at end of file diff --git a/feature/artist/info/src/main/java/ru/stersh/youamp/feature/artist/ui/ArtistInfoScreen.kt b/feature/artist/info/src/main/java/ru/stersh/youamp/feature/artist/ui/ArtistInfoScreen.kt index 1118fb35..b71db96b 100644 --- a/feature/artist/info/src/main/java/ru/stersh/youamp/feature/artist/ui/ArtistInfoScreen.kt +++ b/feature/artist/info/src/main/java/ru/stersh/youamp/feature/artist/ui/ArtistInfoScreen.kt @@ -34,6 +34,7 @@ import ru.stersh.youamp.core.ui.AlbumItem import ru.stersh.youamp.core.ui.BackNavigationButton import ru.stersh.youamp.core.ui.CircleArtwork import ru.stersh.youamp.core.ui.ErrorLayout +import ru.stersh.youamp.core.ui.FavoriteButton import ru.stersh.youamp.core.ui.PlayAllButton import ru.stersh.youamp.core.ui.PlayShuffledButton import ru.stersh.youamp.core.ui.SkeletonLayout @@ -53,6 +54,7 @@ fun ArtistInfoScreen( state = state, onPlayAll = viewModel::playAll, onPlayShuffled = viewModel::playShuffled, + onFavoriteChange = viewModel::onFavoriteChange, onAlbumClick = onAlbumClick, onRetry = viewModel::retry, onBackClick = onBackClick @@ -64,6 +66,7 @@ private fun ArtistInfoScreen( state: ArtistInfoStateUi, onPlayAll: () -> Unit, onPlayShuffled: () -> Unit, + onFavoriteChange: (isFavorite: Boolean) -> Unit, onAlbumClick: (albumId: String) -> Unit, onRetry: () -> Unit, onBackClick: () -> Unit, @@ -97,6 +100,7 @@ private fun ArtistInfoScreen( state = state.content, onPlayAll = onPlayAll, onPlayShuffled = onPlayShuffled, + onFavoriteChange = onFavoriteChange, onAlbumClick = onAlbumClick ) } @@ -110,6 +114,7 @@ private fun Content( state: ArtistInfoUi, onPlayAll: () -> Unit, onPlayShuffled: () -> Unit, + onFavoriteChange: (isFavorite: Boolean) -> Unit, onAlbumClick: (albumId: String) -> Unit ) { LazyVerticalGrid( @@ -124,7 +129,8 @@ private fun Content( Header( state = state, onPlayAll = onPlayAll, - onPlayShuffled = onPlayShuffled + onPlayShuffled = onPlayShuffled, + onFavoriteChange = onFavoriteChange ) } @@ -153,7 +159,8 @@ private fun Content( private fun Header( state: ArtistInfoUi, onPlayAll: () -> Unit, - onPlayShuffled: () -> Unit + onPlayShuffled: () -> Unit, + onFavoriteChange: (isFavorite: Boolean) -> Unit ) { Column( horizontalAlignment = Alignment.CenterHorizontally, @@ -176,16 +183,19 @@ private fun Header( Row( modifier = Modifier .fillMaxWidth() - .padding(horizontal = 24.dp), - horizontalArrangement = Arrangement.spacedBy(8.dp) + .padding(horizontal = 24.dp) + .padding(bottom = 12.dp), + horizontalArrangement = Arrangement.SpaceEvenly ) { PlayAllButton( - onClick = onPlayAll, - modifier = Modifier.weight(0.5f) + onClick = onPlayAll ) PlayShuffledButton( - onClick = onPlayShuffled, - modifier = Modifier.weight(0.5f) + onClick = onPlayShuffled + ) + FavoriteButton( + isFavorite = state.isFavorite, + onChange = onFavoriteChange ) } } @@ -285,10 +295,11 @@ private fun ArtistInfoScreenPreview() { MaterialTheme { ArtistInfoScreen( state = ArtistInfoStateUi( - progress = true, + progress = false, error = false, content = ArtistInfoUi( artworkUrl = null, + isFavorite = false, name = "Artist", albums = albums ) @@ -296,6 +307,7 @@ private fun ArtistInfoScreenPreview() { onPlayAll = {}, onAlbumClick = {}, onPlayShuffled = {}, + onFavoriteChange = {}, onRetry = {}, onBackClick = {} ) diff --git a/feature/artist/info/src/main/java/ru/stersh/youamp/feature/artist/ui/ArtistInfoScreenState.kt b/feature/artist/info/src/main/java/ru/stersh/youamp/feature/artist/ui/ArtistInfoScreenState.kt index c7cb2daa..a0fae6a5 100644 --- a/feature/artist/info/src/main/java/ru/stersh/youamp/feature/artist/ui/ArtistInfoScreenState.kt +++ b/feature/artist/info/src/main/java/ru/stersh/youamp/feature/artist/ui/ArtistInfoScreenState.kt @@ -13,5 +13,6 @@ internal data class ArtistInfoStateUi( internal data class ArtistInfoUi( val artworkUrl: String? = null, val name: String, + val isFavorite: Boolean, val albums: List ) \ No newline at end of file diff --git a/feature/artist/info/src/main/java/ru/stersh/youamp/feature/artist/ui/ArtistInfoViewModel.kt b/feature/artist/info/src/main/java/ru/stersh/youamp/feature/artist/ui/ArtistInfoViewModel.kt index 7be2abf5..882e38b0 100644 --- a/feature/artist/info/src/main/java/ru/stersh/youamp/feature/artist/ui/ArtistInfoViewModel.kt +++ b/feature/artist/info/src/main/java/ru/stersh/youamp/feature/artist/ui/ArtistInfoViewModel.kt @@ -6,6 +6,7 @@ import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.update import kotlinx.coroutines.launch +import ru.stersh.youamp.feature.artist.domain.ArtistFavoriteRepository import ru.stersh.youamp.feature.artist.domain.ArtistInfoRepository import ru.stersh.youamp.shared.player.queue.AudioSource import ru.stersh.youamp.shared.player.queue.PlayerQueueAudioSourceManager @@ -14,6 +15,7 @@ import timber.log.Timber internal class ArtistInfoViewModel( private val id: String, private val artistInfoRepository: ArtistInfoRepository, + private val artistFavoriteRepository: ArtistFavoriteRepository, private val playerQueueAudioSourceManager: PlayerQueueAudioSourceManager ) : ViewModel() { @@ -37,6 +39,19 @@ internal class ArtistInfoViewModel( loadArtist() } + fun onFavoriteChange(isFavorite: Boolean) = viewModelScope.launch { + runCatching { artistFavoriteRepository.setFavorite(id, isFavorite) }.fold( + onSuccess = { + _state.update { + it.copy(content = it.content?.copy(isFavorite = isFavorite)) + } + }, + onFailure = { + Timber.w(it) + } + ) + } + private fun loadArtist() = viewModelScope.launch { _state.update { it.copy( diff --git a/feature/artist/info/src/main/java/ru/stersh/youamp/feature/artist/ui/Mapper.kt b/feature/artist/info/src/main/java/ru/stersh/youamp/feature/artist/ui/Mapper.kt index b9acd300..4eb18143 100644 --- a/feature/artist/info/src/main/java/ru/stersh/youamp/feature/artist/ui/Mapper.kt +++ b/feature/artist/info/src/main/java/ru/stersh/youamp/feature/artist/ui/Mapper.kt @@ -7,6 +7,7 @@ internal fun ArtistInfo.toUi(): ArtistInfoUi { return ArtistInfoUi( artworkUrl = artworkUrl, name = name, + isFavorite = isFavorite, albums = albums.map { it.toUi() } ) } diff --git a/feature/playlist/info/src/main/java/ru/stersh/youamp/feature/playlist/ui/PlaylistInfoScreen.kt b/feature/playlist/info/src/main/java/ru/stersh/youamp/feature/playlist/ui/PlaylistInfoScreen.kt index 8750640b..6a519a8e 100644 --- a/feature/playlist/info/src/main/java/ru/stersh/youamp/feature/playlist/ui/PlaylistInfoScreen.kt +++ b/feature/playlist/info/src/main/java/ru/stersh/youamp/feature/playlist/ui/PlaylistInfoScreen.kt @@ -175,16 +175,15 @@ private fun Header( Row( modifier = Modifier .fillMaxWidth() - .padding(horizontal = 24.dp), - horizontalArrangement = Arrangement.spacedBy(8.dp) + .padding(horizontal = 24.dp) + .padding(bottom = 12.dp), + horizontalArrangement = Arrangement.SpaceEvenly ) { PlayAllButton( - onClick = onPlayAll, - modifier = Modifier.weight(0.5f) + onClick = onPlayAll ) PlayShuffledButton( - onClick = onPlayShuffled, - modifier = Modifier.weight(0.5f) + onClick = onPlayShuffled ) } }