Fix possible TV guide crash & update Play All libraries (#315)

- Fixes a possible crash if the server has Live TV enabled, but no
channels
- Disable Play All/Shuffle for Collections "library"
This commit is contained in:
damontecres 2025-11-24 11:40:15 -05:00 committed by GitHub
parent 6004687deb
commit 7be4fb5fdf
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
7 changed files with 156 additions and 131 deletions

View file

@ -52,9 +52,18 @@ data class GetItemsFilter(
return newFilter
}
fun applyTo(req: GetItemsRequest) =
req.copy(
includeItemTypes = includeItemTypes,
/**
* Add the filtering from this into the [GetItemsRequest], overwriting the fields
*
* @param req the [GetItemsRequest]
* @param overwriteIncludeTypes whether the includeItemTypes field should be overwritten (used from this) or used as-is from the [GetItemsRequest]
*
*/
fun applyTo(
req: GetItemsRequest,
overwriteIncludeTypes: Boolean = true,
) = req.copy(
includeItemTypes = if (overwriteIncludeTypes) includeItemTypes else req.includeItemTypes,
isFavorite = favorite,
genreIds = genres,
minCommunityRating = minCommunityRating,

View file

@ -11,6 +11,7 @@ import com.github.damontecres.wholphin.ui.DefaultItemFields
import com.github.damontecres.wholphin.ui.components.baseItemKinds
import com.github.damontecres.wholphin.ui.data.SortAndDirection
import com.github.damontecres.wholphin.ui.indexOfFirstOrNull
import com.github.damontecres.wholphin.ui.playback.playable
import com.github.damontecres.wholphin.ui.toServerString
import com.github.damontecres.wholphin.util.ApiRequestPager
import com.github.damontecres.wholphin.util.GetEpisodesRequestHandler
@ -93,14 +94,18 @@ class PlaylistCreator
item: BaseItemDto,
startIndex: Int = 0,
sortAndDirection: SortAndDirection?,
recursive: Boolean,
filter: GetItemsFilter,
): Playlist {
val includeItemTypes =
item.collectionType?.baseItemKinds?.takeIf { it.isNotEmpty() }
?: listOf(BaseItemKind.MOVIE, BaseItemKind.EPISODE, BaseItemKind.VIDEO)
item.collectionType
?.baseItemKinds
?.filter { it.playable }
?.takeIf { it.isNotEmpty() }
?: BaseItemKind.entries.filter { it.playable }
val request =
filter.applyTo(
overwriteIncludeTypes = false,
req =
GetItemsRequest(
parentId = item.id,
enableImageTypes = listOf(ImageType.PRIMARY, ImageType.THUMB),
@ -144,7 +149,6 @@ class PlaylistCreator
} else {
sortAndDirection
},
recursive = recursive,
filter = filter,
),
)
@ -211,7 +215,7 @@ class PlaylistCreator
else -> PlaylistCreationResult.Error(null, "Unsupported type: ${item.type}")
}
suspend fun getPlaylists(
suspend fun getServerPlaylists(
mediaType: MediaType?,
scope: CoroutineScope,
): List<PlaylistInfo?> {
@ -234,7 +238,7 @@ class PlaylistCreator
}
}
suspend fun createPlaylist(
suspend fun createServerPlaylist(
name: String,
initialItems: List<UUID>,
): UUID? =
@ -251,14 +255,14 @@ class PlaylistCreator
.toUUIDOrNull()
}
suspend fun addToPlaylist(
suspend fun addToServerPlaylist(
playlistId: UUID,
itemId: UUID,
) {
api.playlistsApi.addItemToPlaylist(playlistId, listOf(itemId))
}
suspend fun removeFromPlaylist(
suspend fun removeFromServerPlaylist(
playlistId: UUID,
itemId: UUID,
) {

View file

@ -30,7 +30,7 @@ class AddPlaylistViewModel
viewModelScope.launchIO {
this@AddPlaylistViewModel.playlistState.setValueOnMain(PlaylistLoadingState.Loading)
try {
val playlists = playlistCreator.getPlaylists(mediaType, viewModelScope)
val playlists = playlistCreator.getServerPlaylists(mediaType, viewModelScope)
this@AddPlaylistViewModel.playlistState.setValueOnMain(PlaylistLoadingState.Success(playlists))
} catch (ex: Exception) {
playlistState.setValueOnMain(PlaylistLoadingState.Error(ex))
@ -43,7 +43,7 @@ class AddPlaylistViewModel
itemId: UUID,
) {
viewModelScope.launchIO(ExceptionHandler(autoToast = true)) {
playlistCreator.addToPlaylist(playlistId, itemId)
playlistCreator.addToServerPlaylist(playlistId, itemId)
}
}
@ -52,7 +52,7 @@ class AddPlaylistViewModel
itemId: UUID,
) {
viewModelScope.launchIO(ExceptionHandler(autoToast = true)) {
val playlistId = playlistCreator.createPlaylist(playlistName, listOf(itemId))
val playlistId = playlistCreator.createServerPlaylist(playlistName, listOf(itemId))
if (playlistId == null) {
showToast(context, "Error creating playlist", Toast.LENGTH_LONG)
}

View file

@ -27,6 +27,7 @@ fun CollectionFolderBoxSet(
modifier: Modifier = Modifier,
filter: GetItemsFilter = GetItemsFilter(),
preferencesViewModel: PreferencesViewModel = hiltViewModel(),
playEnabled: Boolean = false,
) {
var showHeader by remember { mutableStateOf(true) }
CollectionFolderGrid(
@ -44,6 +45,6 @@ fun CollectionFolderBoxSet(
showHeader = position < columns
},
params = CollectionFolderGridParameters.POSTER,
playEnabled = true,
playEnabled = playEnabled,
)
}

View file

@ -95,6 +95,9 @@ fun TvGuideGrid(
}
}
Column(modifier = modifier) {
if (channels.isEmpty()) {
ErrorMessage("Live TV is enabled, but no channels were found.", null)
} else {
TvGuideGridContent(
loading = state is LoadingState.Loading,
channels = channels,
@ -125,8 +128,11 @@ fun TvGuideGrid(
)
} else {
Toast
.makeText(context, "No guide data found!", Toast.LENGTH_SHORT)
.show()
.makeText(
context,
"No guide data found!",
Toast.LENGTH_SHORT,
).show()
}
} else {
viewModel.getItem(program.id)
@ -140,6 +146,7 @@ fun TvGuideGrid(
.focusRequester(focusRequester),
)
}
}
if (showItemDialog != null) {
val onDismissRequest = { showItemDialog = null }
ProgramDialog(

View file

@ -103,11 +103,12 @@ fun DestinationContent(
BaseItemKind.BOX_SET ->
CollectionFolderBoxSet(
preferences,
destination.itemId,
destination.item,
false,
modifier,
preferences = preferences,
itemId = destination.itemId,
item = destination.item,
recursive = false,
playEnabled = true,
modifier = modifier,
)
BaseItemKind.PLAYLIST ->

View file

@ -37,21 +37,24 @@ val PrefContentScale.scale: ContentScale
val BaseItemKind.playable: Boolean
get() =
when (this) {
BaseItemKind.EPISODE,
BaseItemKind.MOVIE,
BaseItemKind.MUSIC_VIDEO,
BaseItemKind.TRAILER,
BaseItemKind.VIDEO,
-> true
// TODO add support for these eventually
BaseItemKind.LIVE_TV_CHANNEL,
BaseItemKind.LIVE_TV_PROGRAM,
BaseItemKind.PROGRAM,
BaseItemKind.RECORDING,
BaseItemKind.TV_CHANNEL,
BaseItemKind.TV_PROGRAM,
BaseItemKind.AUDIO_BOOK,
BaseItemKind.AUDIO,
BaseItemKind.CHANNEL,
BaseItemKind.EPISODE,
BaseItemKind.MOVIE,
BaseItemKind.LIVE_TV_CHANNEL,
BaseItemKind.LIVE_TV_PROGRAM,
BaseItemKind.MUSIC_VIDEO,
BaseItemKind.PROGRAM,
BaseItemKind.RECORDING,
BaseItemKind.TRAILER,
BaseItemKind.TV_CHANNEL,
BaseItemKind.TV_PROGRAM,
BaseItemKind.VIDEO,
-> true
-> false
BaseItemKind.AGGREGATE_FOLDER,
BaseItemKind.BASE_PLUGIN_FOLDER,