Fix TV artwork, episode layout and focus visibility

This commit is contained in:
justinzeus 2026-11-06 20:25:50 +01:00
parent 34087d8eb5
commit 9d2471c5b1
28 changed files with 466 additions and 117 deletions

View file

@ -35,6 +35,7 @@ import org.justinvisser.wolfskin.design.TitlePageData
import org.justinvisser.wolfskin.design.TitlePerson
import org.justinvisser.wolfskin.design.TitleTrack
import org.justinvisser.wolfskin.design.TitleVersion
import org.justinvisser.wolfskin.design.WolfskinTokens
import java.io.File
import java.util.Locale
import java.util.UUID
@ -189,27 +190,44 @@ class TitleReadFacade @Inject constructor(
streams.filter { it.type == MediaStreamType.AUDIO }.map { TitleTrack(it.index, it.displayTitle ?: it.language ?: it.codec.orEmpty(), it.isDefault) },
streams.filter { it.type == MediaStreamType.SUBTITLE }.map { TitleTrack(it.index, it.displayTitle ?: it.language ?: it.codec.orEmpty(), it.isDefault) })
}
return TitlePageData(mapItem(item).copy(seasons = seasonNumbers.size), target?.takeIf { it.type == BaseItemKind.EPISODE || (it.userData?.playbackPositionTicks ?: 0) > 0 }?.let { mapItem(it) },
rows.first { it.id.toString() == watchId }.let { mapItem(it) }, versions, chosen?.source?.id, chosen?.audioStream?.stream?.index,
val pageItem = mapItem(item).copy(seasons = seasonNumbers.size)
return TitlePageData(pageItem, target?.takeIf { it.type == BaseItemKind.EPISODE || (it.userData?.playbackPositionTicks ?: 0) > 0 }?.let { mapItem(it, pageItem.backdrop) },
rows.first { it.id.toString() == watchId }.let { mapItem(it, pageItem.backdrop) }, versions, chosen?.source?.id, chosen?.audioStream?.stream?.index,
if (chosen?.subtitlesDisabled == true) -1 else chosen?.subtitleStream?.stream?.index ?: -1,
item.remoteTrailers.orEmpty().firstNotNullOfOrNull { it.url },
cast = item.people.orEmpty().filter { it.type.toString().equals("Actor", true) }.map { person ->
TitlePerson(person.id.toString(), person.name.orEmpty(), person.role.orEmpty(), person.primaryImageTag?.let { images.getItemImageUrl(person.id, ImageType.PRIMARY, maxWidth = 200, maxHeight = 200, tag = it) })
},
episodes = rows.filter { it.type == BaseItemKind.EPISODE && it.parentIndexNumber == selectedSeason }.map { mapItem(it) },
episodes = rows.filter { it.type == BaseItemKind.EPISODE && it.parentIndexNumber == selectedSeason }.map { mapItem(it, pageItem.backdrop) },
connection = TitleConnection.SignedOut, seasonNumbers = seasonNumbers, selectedSeason = selectedSeason)
}
private fun mapItem(item: BaseItemDto): TitleItem = TitleItem(
private fun mapItem(item: BaseItemDto, seriesBackdrop: String? = null): TitleItem = TitleItem(
item.id.toString(), item.name.orEmpty(), when (item.type) { BaseItemKind.MOVIE -> "movie"; BaseItemKind.EPISODE -> "episode"; else -> "tv" },
item.productionYear, (item.runTimeTicks ?: 0) / 10_000_000, overview = item.overview.orEmpty(), genres = item.genres.orEmpty(),
poster = item.imageTags?.get(ImageType.PRIMARY)?.let { images.getItemImageUrl(item.id, ImageType.PRIMARY, maxWidth = 256, maxHeight = 384, tag = it) },
backdrop = item.backdropImageTags?.firstOrNull()?.let { images.getItemImageUrl(item.id, ImageType.BACKDROP, maxWidth = 1280, maxHeight = 720, tag = it) },
backdrop = if (item.type == BaseItemKind.EPISODE) episodeArtwork(item, seriesBackdrop) else item.backdropImageTags?.firstOrNull()?.let { images.getItemImageUrl(item.id, ImageType.BACKDROP, maxWidth = WolfskinTokens.Image.heroRequestWidth, maxHeight = WolfskinTokens.Image.heroRequestHeight, tag = it) },
held = true, watched = item.userData?.played == true, resumeSeconds = (item.userData?.playbackPositionTicks ?: 0) / 10_000_000,
seriesId = item.seriesId?.toString(), seriesName = item.seriesName, season = item.parentIndexNumber ?: 0, episode = item.indexNumber ?: 0,
tmdbId = item.providerIds?.entries?.firstOrNull { it.key.equals("Tmdb", true) }?.value?.toLongOrNull(),
)
private fun episodeArtwork(item: BaseItemDto, seriesBackdrop: String?): String? {
val type = when {
item.imageTags?.get(ImageType.PRIMARY) != null -> ImageType.PRIMARY
item.imageTags?.get(ImageType.THUMB) != null -> ImageType.THUMB
else -> null
}
if (type != null) return images.getItemImageUrl(item.id, type,
maxWidth = WolfskinTokens.Image.episodeRequestWidth, maxHeight = WolfskinTokens.Image.episodeRequestHeight, tag = item.imageTags?.get(type))
return seriesBackdrop ?: item.parentBackdropItemId?.let { parent ->
item.parentBackdropImageTags?.firstOrNull()?.let { tag ->
images.getItemImageUrl(parent, ImageType.BACKDROP,
maxWidth = WolfskinTokens.Image.episodeRequestWidth, maxHeight = WolfskinTokens.Image.episodeRequestHeight, tag = tag)
}
}
}
private fun cacheFile(id: UUID): File = File(context.filesDir, "wolfskin-title/${repository.currentServer?.id}/${repository.currentUser?.id}/$id.json")
private fun personCacheFile(id: UUID): File = File(cacheFile(id).parentFile, "person-$id.json")
private fun savedTime(file: File): String = java.text.SimpleDateFormat("HH:mm", Locale.getDefault()).format(java.util.Date(file.lastModified()))