mirror of
https://github.com/JustinZeus/Wholphin.git
synced 2026-11-07 03:04:50 +01:00
Fixes to artist detail page (#1762)
## Description Fixes an incorrect query for an artist's albums (thanks @StefanBS for reporting & fixing in #1761!) Additionally, another album row for "Appears on" is added. This is definitely necessary because Wholphin only has a tab for all artists which includes artists featured on albums attributed to a different artist. So those albums will now show in the "Appears on" similar to the web UI instead of an empty artist page. Finally, the long-click context menu is added for music videos on an artist's page. ### Related issues Fixes #1760 Replaced #1761 ### Testing Emulator, particularly with artists who are not album artists Also, verified the query fix against the web UI dev console ## Screenshots N/A ## AI or LLM usage None, but the fix included from #1761 did use some AI --------- Co-authored-by: Stefan Bullones <stefan.bullones@gmail.com>
This commit is contained in:
parent
bf43a41405
commit
79cb93db08
2 changed files with 178 additions and 60 deletions
|
|
@ -59,6 +59,7 @@ import com.github.damontecres.wholphin.ui.SlimItemFields
|
||||||
import com.github.damontecres.wholphin.ui.cards.BannerCardWithTitle
|
import com.github.damontecres.wholphin.ui.cards.BannerCardWithTitle
|
||||||
import com.github.damontecres.wholphin.ui.cards.ItemRow
|
import com.github.damontecres.wholphin.ui.cards.ItemRow
|
||||||
import com.github.damontecres.wholphin.ui.components.ContextMenu
|
import com.github.damontecres.wholphin.ui.components.ContextMenu
|
||||||
|
import com.github.damontecres.wholphin.ui.components.ContextMenuActions
|
||||||
import com.github.damontecres.wholphin.ui.components.ContextMenuDialog
|
import com.github.damontecres.wholphin.ui.components.ContextMenuDialog
|
||||||
import com.github.damontecres.wholphin.ui.components.ErrorMessage
|
import com.github.damontecres.wholphin.ui.components.ErrorMessage
|
||||||
import com.github.damontecres.wholphin.ui.components.GenreText
|
import com.github.damontecres.wholphin.ui.components.GenreText
|
||||||
|
|
@ -154,7 +155,8 @@ class ArtistViewModel
|
||||||
async {
|
async {
|
||||||
val request =
|
val request =
|
||||||
GetItemsRequest(
|
GetItemsRequest(
|
||||||
parentId = itemId,
|
albumArtistIds = listOf(itemId),
|
||||||
|
recursive = true,
|
||||||
fields = DefaultItemFields,
|
fields = DefaultItemFields,
|
||||||
includeItemTypes = listOf(BaseItemKind.MUSIC_ALBUM),
|
includeItemTypes = listOf(BaseItemKind.MUSIC_ALBUM),
|
||||||
sortBy =
|
sortBy =
|
||||||
|
|
@ -166,14 +168,38 @@ class ArtistViewModel
|
||||||
)
|
)
|
||||||
ApiRequestPager(api, request, GetItemsRequestHandler, viewModelScope).init()
|
ApiRequestPager(api, request, GetItemsRequestHandler, viewModelScope).init()
|
||||||
}
|
}
|
||||||
|
val appearsOnDeferred =
|
||||||
|
async {
|
||||||
|
val request =
|
||||||
|
GetItemsRequest(
|
||||||
|
contributingArtistIds = listOf(itemId),
|
||||||
|
recursive = true,
|
||||||
|
fields = DefaultItemFields,
|
||||||
|
includeItemTypes = listOf(BaseItemKind.MUSIC_ALBUM),
|
||||||
|
sortBy =
|
||||||
|
listOf(
|
||||||
|
ItemSortBy.PREMIERE_DATE,
|
||||||
|
ItemSortBy.SORT_NAME,
|
||||||
|
),
|
||||||
|
sortOrder = listOf(SortOrder.DESCENDING, SortOrder.ASCENDING),
|
||||||
|
)
|
||||||
|
ApiRequestPager(
|
||||||
|
api,
|
||||||
|
request,
|
||||||
|
GetItemsRequestHandler,
|
||||||
|
viewModelScope,
|
||||||
|
).init()
|
||||||
|
}
|
||||||
val artist = itemDeferred.await()
|
val artist = itemDeferred.await()
|
||||||
val albums = albumsDeferred.await()
|
val albums = albumsDeferred.await()
|
||||||
|
val appearsOn = appearsOnDeferred.await()
|
||||||
val imageUrl = imageUrlService.getItemImageUrl(artist, ImageType.PRIMARY)
|
val imageUrl = imageUrlService.getItemImageUrl(artist, ImageType.PRIMARY)
|
||||||
_state.update {
|
_state.update {
|
||||||
it.copy(
|
it.copy(
|
||||||
artist = artist,
|
artist = artist,
|
||||||
imageUrl = imageUrl,
|
imageUrl = imageUrl,
|
||||||
albums = albums,
|
albums = albums,
|
||||||
|
appearsOnAlbums = appearsOn,
|
||||||
loading = LoadingState.Success,
|
loading = LoadingState.Success,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
@ -218,24 +244,7 @@ class ArtistViewModel
|
||||||
_state.update { it.copy(similar = similar) }
|
_state.update { it.copy(similar = similar) }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
viewModelScope.launchIO {
|
updateMusicVideos()
|
||||||
val request =
|
|
||||||
GetItemsRequest(
|
|
||||||
userId = serverRepository.currentUser?.id,
|
|
||||||
artistIds = listOf(itemId),
|
|
||||||
parentId = null,
|
|
||||||
fields = DefaultItemFields,
|
|
||||||
recursive = true,
|
|
||||||
includeItemTypes = listOf(BaseItemKind.MUSIC_VIDEO),
|
|
||||||
)
|
|
||||||
val musicVideos =
|
|
||||||
GetItemsRequestHandler.execute(api, request).toBaseItems(api, false)
|
|
||||||
if (musicVideos.isNotEmpty()) {
|
|
||||||
_state.update {
|
|
||||||
it.copy(musicVideos = musicVideos)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch (ex: Exception) {
|
} catch (ex: Exception) {
|
||||||
_state.update { it.copy(loading = LoadingState.Error(ex)) }
|
_state.update { it.copy(loading = LoadingState.Error(ex)) }
|
||||||
}
|
}
|
||||||
|
|
@ -250,17 +259,52 @@ class ArtistViewModel
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun updateMusicVideos() {
|
||||||
|
viewModelScope.launchIO {
|
||||||
|
val request =
|
||||||
|
GetItemsRequest(
|
||||||
|
userId = serverRepository.currentUser?.id,
|
||||||
|
artistIds = listOf(itemId),
|
||||||
|
parentId = null,
|
||||||
|
fields = DefaultItemFields,
|
||||||
|
recursive = true,
|
||||||
|
includeItemTypes = listOf(BaseItemKind.MUSIC_VIDEO),
|
||||||
|
)
|
||||||
|
val musicVideos =
|
||||||
|
GetItemsRequestHandler.execute(api, request).toBaseItems(api, false)
|
||||||
|
if (musicVideos.isNotEmpty()) {
|
||||||
|
_state.update {
|
||||||
|
it.copy(musicVideos = musicVideos)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fun setFavorite(
|
fun setFavorite(
|
||||||
itemId: UUID,
|
itemId: UUID,
|
||||||
favorite: Boolean,
|
favorite: Boolean,
|
||||||
) = viewModelScope.launch(ExceptionHandler() + WholphinDispatchers.IO) {
|
) = viewModelScope.launch(ExceptionHandler() + WholphinDispatchers.IO) {
|
||||||
favoriteWatchManager.setFavorite(itemId, favorite)
|
favoriteWatchManager.setFavorite(itemId, favorite)
|
||||||
val artist =
|
// Refresh if artist, otherwise only could be a music video
|
||||||
api.userLibraryApi
|
if (itemId == this@ArtistViewModel.itemId) {
|
||||||
.getItem(itemId = itemId)
|
val artist =
|
||||||
.content
|
api.userLibraryApi
|
||||||
.let { BaseItem(it, false) }
|
.getItem(itemId = itemId)
|
||||||
_state.update { it.copy(artist = artist) }
|
.content
|
||||||
|
.let { BaseItem(it, false) }
|
||||||
|
_state.update { it.copy(artist = artist) }
|
||||||
|
} else {
|
||||||
|
updateMusicVideos()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun setWatched(
|
||||||
|
itemId: UUID,
|
||||||
|
played: Boolean,
|
||||||
|
) = viewModelScope.launch(ExceptionHandler() + WholphinDispatchers.IO) {
|
||||||
|
favoriteWatchManager.setWatched(itemId, played)
|
||||||
|
// Only applies to music videos
|
||||||
|
updateMusicVideos()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -269,6 +313,7 @@ data class ArtistState(
|
||||||
val imageUrl: String?,
|
val imageUrl: String?,
|
||||||
val topSongs: List<BaseItem?>,
|
val topSongs: List<BaseItem?>,
|
||||||
val albums: List<BaseItem?>,
|
val albums: List<BaseItem?>,
|
||||||
|
val appearsOnAlbums: List<BaseItem?>,
|
||||||
val similar: List<BaseItem>,
|
val similar: List<BaseItem>,
|
||||||
val loading: LoadingState,
|
val loading: LoadingState,
|
||||||
val musicVideos: List<BaseItem?>,
|
val musicVideos: List<BaseItem?>,
|
||||||
|
|
@ -282,6 +327,7 @@ data class ArtistState(
|
||||||
emptyList(),
|
emptyList(),
|
||||||
emptyList(),
|
emptyList(),
|
||||||
emptyList(),
|
emptyList(),
|
||||||
|
emptyList(),
|
||||||
LoadingState.Pending,
|
LoadingState.Pending,
|
||||||
emptyList(),
|
emptyList(),
|
||||||
false,
|
false,
|
||||||
|
|
@ -292,7 +338,8 @@ data class ArtistState(
|
||||||
private const val HEADER_ROW = 0
|
private const val HEADER_ROW = 0
|
||||||
private const val SONG_ROW = HEADER_ROW + 1
|
private const val SONG_ROW = HEADER_ROW + 1
|
||||||
private const val ALBUM_ROW = SONG_ROW + 1
|
private const val ALBUM_ROW = SONG_ROW + 1
|
||||||
private const val MUSIC_VIDEO_ROW = ALBUM_ROW + 1
|
private const val APPEARS_ON_ROW = SONG_ROW + 1
|
||||||
|
private const val MUSIC_VIDEO_ROW = APPEARS_ON_ROW + 1
|
||||||
private const val SIMILAR_ROW = MUSIC_VIDEO_ROW + 1
|
private const val SIMILAR_ROW = MUSIC_VIDEO_ROW + 1
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
|
|
@ -499,41 +546,81 @@ fun ArtistDetailsPage(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
item {
|
if (state.albums.isNotEmpty()) {
|
||||||
ItemRow(
|
item {
|
||||||
title = stringResource(R.string.albums),
|
ItemRow(
|
||||||
items = state.albums,
|
title = stringResource(R.string.albums),
|
||||||
onClickItem = { index, album ->
|
items = state.albums,
|
||||||
position = RowColumn(ALBUM_ROW, index)
|
onClickItem = { index, album ->
|
||||||
viewModel.navigationManager.navigateTo(album.destination())
|
position = RowColumn(ALBUM_ROW, index)
|
||||||
},
|
viewModel.navigationManager.navigateTo(album.destination())
|
||||||
onLongClickItem = { index, album ->
|
},
|
||||||
showContextMenu =
|
onLongClickItem = { index, album ->
|
||||||
ContextMenu.ForMusic(
|
showContextMenu =
|
||||||
fromLongClick = true,
|
ContextMenu.ForMusic(
|
||||||
|
fromLongClick = true,
|
||||||
|
item = album,
|
||||||
|
index = index,
|
||||||
|
canDelete =
|
||||||
|
viewModel.canDelete(
|
||||||
|
album,
|
||||||
|
preferences.appPreferences,
|
||||||
|
),
|
||||||
|
canRemoveFromQueue = false,
|
||||||
|
actions = moreDialogActions,
|
||||||
|
)
|
||||||
|
},
|
||||||
|
cardContent = { index: Int, album: BaseItem?, mod: Modifier, onClick: () -> Unit, onLongClick: () -> Unit ->
|
||||||
|
BannerCardWithTitle(
|
||||||
|
title = album?.name,
|
||||||
|
subtitle = album?.data?.productionYear?.toString(),
|
||||||
item = album,
|
item = album,
|
||||||
index = index,
|
onClick = onClick,
|
||||||
canDelete =
|
onLongClick = onLongClick,
|
||||||
viewModel.canDelete(
|
aspectRatio = AspectRatios.SQUARE,
|
||||||
album,
|
|
||||||
preferences.appPreferences,
|
|
||||||
),
|
|
||||||
canRemoveFromQueue = false,
|
|
||||||
actions = moreDialogActions,
|
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
cardContent = { index: Int, album: BaseItem?, mod: Modifier, onClick: () -> Unit, onLongClick: () -> Unit ->
|
modifier = Modifier.focusRequester(focusRequesters[ALBUM_ROW]),
|
||||||
BannerCardWithTitle(
|
)
|
||||||
title = album?.name,
|
}
|
||||||
subtitle = album?.data?.productionYear?.toString(),
|
}
|
||||||
item = album,
|
if (state.appearsOnAlbums.isNotEmpty()) {
|
||||||
onClick = onClick,
|
item {
|
||||||
onLongClick = onLongClick,
|
ItemRow(
|
||||||
aspectRatio = AspectRatios.SQUARE,
|
title = stringResource(R.string.appears_on),
|
||||||
)
|
items = state.appearsOnAlbums,
|
||||||
},
|
onClickItem = { index, album ->
|
||||||
modifier = Modifier.focusRequester(focusRequesters[ALBUM_ROW]),
|
position = RowColumn(APPEARS_ON_ROW, index)
|
||||||
)
|
viewModel.navigationManager.navigateTo(album.destination())
|
||||||
|
},
|
||||||
|
onLongClickItem = { index, album ->
|
||||||
|
showContextMenu =
|
||||||
|
ContextMenu.ForMusic(
|
||||||
|
fromLongClick = true,
|
||||||
|
item = album,
|
||||||
|
index = index,
|
||||||
|
canDelete =
|
||||||
|
viewModel.canDelete(
|
||||||
|
album,
|
||||||
|
preferences.appPreferences,
|
||||||
|
),
|
||||||
|
canRemoveFromQueue = false,
|
||||||
|
actions = moreDialogActions,
|
||||||
|
)
|
||||||
|
},
|
||||||
|
cardContent = { index: Int, album: BaseItem?, mod: Modifier, onClick: () -> Unit, onLongClick: () -> Unit ->
|
||||||
|
BannerCardWithTitle(
|
||||||
|
title = album?.name,
|
||||||
|
subtitle = album?.data?.productionYear?.toString(),
|
||||||
|
item = album,
|
||||||
|
onClick = onClick,
|
||||||
|
onLongClick = onLongClick,
|
||||||
|
aspectRatio = AspectRatios.SQUARE,
|
||||||
|
)
|
||||||
|
},
|
||||||
|
modifier = Modifier.focusRequester(focusRequesters[ALBUM_ROW]),
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if (state.musicVideos.isNotEmpty()) {
|
if (state.musicVideos.isNotEmpty()) {
|
||||||
item {
|
item {
|
||||||
|
|
@ -545,7 +632,37 @@ fun ArtistDetailsPage(
|
||||||
viewModel.navigationManager.navigateTo(item.destination())
|
viewModel.navigationManager.navigateTo(item.destination())
|
||||||
},
|
},
|
||||||
onLongClickItem = { index, item ->
|
onLongClickItem = { index, item ->
|
||||||
// TODO
|
showContextMenu =
|
||||||
|
ContextMenu.ForBaseItem(
|
||||||
|
fromLongClick = true,
|
||||||
|
item = item,
|
||||||
|
chosenStreams = null,
|
||||||
|
showGoTo = true,
|
||||||
|
showStreamChoices = false,
|
||||||
|
canDelete =
|
||||||
|
viewModel.canDelete(
|
||||||
|
item,
|
||||||
|
preferences.appPreferences,
|
||||||
|
),
|
||||||
|
canRemoveContinueWatching = false,
|
||||||
|
canRemoveNextUp = false,
|
||||||
|
actions =
|
||||||
|
ContextMenuActions(
|
||||||
|
navigateTo = viewModel.navigationManager::navigateTo,
|
||||||
|
onShowOverview = {},
|
||||||
|
onClickWatch = viewModel::setWatched,
|
||||||
|
onClickFavorite = viewModel::setFavorite,
|
||||||
|
onClickAddPlaylist = { itemId ->
|
||||||
|
playlistViewModel.loadPlaylists()
|
||||||
|
showPlaylistDialog.makePresent(itemId)
|
||||||
|
},
|
||||||
|
onSendMediaInfo = viewModel.mediaReportService::sendReportFor,
|
||||||
|
onDeleteItem = viewModel::deleteItem,
|
||||||
|
onChooseVersion = { _, _ -> },
|
||||||
|
onChooseTracks = { },
|
||||||
|
onClearChosenStreams = {},
|
||||||
|
),
|
||||||
|
)
|
||||||
},
|
},
|
||||||
cardContent = { index: Int, item: BaseItem?, mod: Modifier, onClick: () -> Unit, onLongClick: () -> Unit ->
|
cardContent = { index: Int, item: BaseItem?, mod: Modifier, onClick: () -> Unit, onLongClick: () -> Unit ->
|
||||||
BannerCardWithTitle(
|
BannerCardWithTitle(
|
||||||
|
|
|
||||||
|
|
@ -902,4 +902,5 @@
|
||||||
<string name="books">Books</string>
|
<string name="books">Books</string>
|
||||||
<string name="audio_books">Audio books</string>
|
<string name="audio_books">Audio books</string>
|
||||||
<string name="server_version_not_supported">Server version not supported</string>
|
<string name="server_version_not_supported">Server version not supported</string>
|
||||||
|
<string name="appears_on">Appears on</string>
|
||||||
</resources>
|
</resources>
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue