mirror of
https://github.com/JustinZeus/Wholphin.git
synced 2026-07-08 15:50:16 +02:00
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:
parent
6004687deb
commit
7be4fb5fdf
7 changed files with 156 additions and 131 deletions
|
|
@ -52,56 +52,65 @@ data class GetItemsFilter(
|
|||
return newFilter
|
||||
}
|
||||
|
||||
fun applyTo(req: GetItemsRequest) =
|
||||
req.copy(
|
||||
includeItemTypes = includeItemTypes,
|
||||
isFavorite = favorite,
|
||||
genreIds = genres,
|
||||
minCommunityRating = minCommunityRating,
|
||||
personIds = persons,
|
||||
isPlayed = played,
|
||||
studioIds = studios,
|
||||
tags = tags,
|
||||
officialRatings = officialRatings,
|
||||
years =
|
||||
buildSet {
|
||||
years?.letNotEmpty(::addAll)
|
||||
decades?.forEach { addAll(it..<(it + 10)) }
|
||||
},
|
||||
is4k =
|
||||
videoTypes?.letNotEmpty {
|
||||
videoTypes.contains(FilterVideoType.FOUR_K).takeIf { it }
|
||||
},
|
||||
isHd =
|
||||
videoTypes?.letNotEmpty {
|
||||
if (videoTypes.contains(FilterVideoType.HD)) {
|
||||
true
|
||||
} else if (videoTypes.contains(FilterVideoType.SD)) {
|
||||
false
|
||||
} else {
|
||||
null
|
||||
}
|
||||
},
|
||||
is3d =
|
||||
videoTypes?.letNotEmpty {
|
||||
videoTypes.contains(FilterVideoType.THREE_D).takeIf { it }
|
||||
},
|
||||
videoTypes =
|
||||
videoTypes?.letNotEmpty {
|
||||
it.mapNotNull {
|
||||
when (it) {
|
||||
FilterVideoType.FOUR_K,
|
||||
FilterVideoType.HD,
|
||||
FilterVideoType.SD,
|
||||
FilterVideoType.THREE_D,
|
||||
-> null
|
||||
/**
|
||||
* 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,
|
||||
personIds = persons,
|
||||
isPlayed = played,
|
||||
studioIds = studios,
|
||||
tags = tags,
|
||||
officialRatings = officialRatings,
|
||||
years =
|
||||
buildSet {
|
||||
years?.letNotEmpty(::addAll)
|
||||
decades?.forEach { addAll(it..<(it + 10)) }
|
||||
},
|
||||
is4k =
|
||||
videoTypes?.letNotEmpty {
|
||||
videoTypes.contains(FilterVideoType.FOUR_K).takeIf { it }
|
||||
},
|
||||
isHd =
|
||||
videoTypes?.letNotEmpty {
|
||||
if (videoTypes.contains(FilterVideoType.HD)) {
|
||||
true
|
||||
} else if (videoTypes.contains(FilterVideoType.SD)) {
|
||||
false
|
||||
} else {
|
||||
null
|
||||
}
|
||||
},
|
||||
is3d =
|
||||
videoTypes?.letNotEmpty {
|
||||
videoTypes.contains(FilterVideoType.THREE_D).takeIf { it }
|
||||
},
|
||||
videoTypes =
|
||||
videoTypes?.letNotEmpty {
|
||||
it.mapNotNull {
|
||||
when (it) {
|
||||
FilterVideoType.FOUR_K,
|
||||
FilterVideoType.HD,
|
||||
FilterVideoType.SD,
|
||||
FilterVideoType.THREE_D,
|
||||
-> null
|
||||
|
||||
FilterVideoType.BLU_RAY -> VideoType.BLU_RAY
|
||||
FilterVideoType.DVD -> VideoType.DVD
|
||||
}
|
||||
FilterVideoType.BLU_RAY -> VideoType.BLU_RAY
|
||||
FilterVideoType.DVD -> VideoType.DVD
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
},
|
||||
)
|
||||
|
||||
fun applyTo(req: GetPersonsRequest) =
|
||||
req.copy(
|
||||
|
|
|
|||
|
|
@ -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,26 +94,30 @@ 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(
|
||||
GetItemsRequest(
|
||||
parentId = item.id,
|
||||
enableImageTypes = listOf(ImageType.PRIMARY, ImageType.THUMB),
|
||||
includeItemTypes = includeItemTypes,
|
||||
recursive = true,
|
||||
excludeItemIds = listOf(item.id),
|
||||
sortBy = sortAndDirection?.let { listOf(sortAndDirection.sort) },
|
||||
sortOrder = sortAndDirection?.let { listOf(sortAndDirection.direction) },
|
||||
fields = DefaultItemFields,
|
||||
startIndex = startIndex,
|
||||
limit = Playlist.MAX_SIZE,
|
||||
),
|
||||
overwriteIncludeTypes = false,
|
||||
req =
|
||||
GetItemsRequest(
|
||||
parentId = item.id,
|
||||
enableImageTypes = listOf(ImageType.PRIMARY, ImageType.THUMB),
|
||||
includeItemTypes = includeItemTypes,
|
||||
recursive = true,
|
||||
excludeItemIds = listOf(item.id),
|
||||
sortBy = sortAndDirection?.let { listOf(sortAndDirection.sort) },
|
||||
sortOrder = sortAndDirection?.let { listOf(sortAndDirection.direction) },
|
||||
fields = DefaultItemFields,
|
||||
startIndex = startIndex,
|
||||
limit = Playlist.MAX_SIZE,
|
||||
),
|
||||
)
|
||||
val items =
|
||||
GetItemsRequestHandler.execute(api, request).content.items.map {
|
||||
|
|
@ -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,
|
||||
) {
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -95,50 +95,57 @@ fun TvGuideGrid(
|
|||
}
|
||||
}
|
||||
Column(modifier = modifier) {
|
||||
TvGuideGridContent(
|
||||
loading = state is LoadingState.Loading,
|
||||
channels = channels,
|
||||
programs = programs,
|
||||
channelProgramCount = viewModel.channelProgramCount,
|
||||
start = viewModel.guideStart,
|
||||
onClickChannel = { index, channel ->
|
||||
viewModel.navigationManager.navigateTo(
|
||||
Destination.Playback(
|
||||
itemId = channel.id,
|
||||
positionMs = 0L,
|
||||
),
|
||||
)
|
||||
},
|
||||
onFocus = {
|
||||
onRowPosition.invoke(it.row)
|
||||
viewModel.onFocusChannel(it)
|
||||
},
|
||||
onClickProgram = { index, program ->
|
||||
if (program.isFake) {
|
||||
val now = LocalDateTime.now()
|
||||
if (now.isAfter(program.start) && now.isBefore(program.end)) {
|
||||
viewModel.navigationManager.navigateTo(
|
||||
Destination.Playback(
|
||||
itemId = program.channelId,
|
||||
positionMs = 0L,
|
||||
),
|
||||
)
|
||||
if (channels.isEmpty()) {
|
||||
ErrorMessage("Live TV is enabled, but no channels were found.", null)
|
||||
} else {
|
||||
TvGuideGridContent(
|
||||
loading = state is LoadingState.Loading,
|
||||
channels = channels,
|
||||
programs = programs,
|
||||
channelProgramCount = viewModel.channelProgramCount,
|
||||
start = viewModel.guideStart,
|
||||
onClickChannel = { index, channel ->
|
||||
viewModel.navigationManager.navigateTo(
|
||||
Destination.Playback(
|
||||
itemId = channel.id,
|
||||
positionMs = 0L,
|
||||
),
|
||||
)
|
||||
},
|
||||
onFocus = {
|
||||
onRowPosition.invoke(it.row)
|
||||
viewModel.onFocusChannel(it)
|
||||
},
|
||||
onClickProgram = { index, program ->
|
||||
if (program.isFake) {
|
||||
val now = LocalDateTime.now()
|
||||
if (now.isAfter(program.start) && now.isBefore(program.end)) {
|
||||
viewModel.navigationManager.navigateTo(
|
||||
Destination.Playback(
|
||||
itemId = program.channelId,
|
||||
positionMs = 0L,
|
||||
),
|
||||
)
|
||||
} else {
|
||||
Toast
|
||||
.makeText(
|
||||
context,
|
||||
"No guide data found!",
|
||||
Toast.LENGTH_SHORT,
|
||||
).show()
|
||||
}
|
||||
} else {
|
||||
Toast
|
||||
.makeText(context, "No guide data found!", Toast.LENGTH_SHORT)
|
||||
.show()
|
||||
viewModel.getItem(program.id)
|
||||
showItemDialog = index
|
||||
}
|
||||
} else {
|
||||
viewModel.getItem(program.id)
|
||||
showItemDialog = index
|
||||
}
|
||||
},
|
||||
modifier =
|
||||
Modifier
|
||||
.fillMaxSize()
|
||||
.background(MaterialTheme.colorScheme.surface)
|
||||
.focusRequester(focusRequester),
|
||||
)
|
||||
},
|
||||
modifier =
|
||||
Modifier
|
||||
.fillMaxSize()
|
||||
.background(MaterialTheme.colorScheme.surface)
|
||||
.focusRequester(focusRequester),
|
||||
)
|
||||
}
|
||||
}
|
||||
if (showItemDialog != null) {
|
||||
val onDismissRequest = { showItemDialog = null }
|
||||
|
|
|
|||
|
|
@ -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 ->
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue