Add preset for home page episode thumbnails (#954)

## Description
Adds a preset for "Episode thumbnails" which shows episodes using their
Primary image (ie a thumbnail or screenshot of the episode).

The previous "Thumbnails" preset is renamed "Series Thumbs" because it
uses the episode's Series Thumb image.

Also fixes a bug where toggling "Use series" might not update the home
page preview automatically.

### Related issues
Fixes #940

### Testing
Emulator

## Screenshots
N/A

## AI or LLM usage
None
This commit is contained in:
Ray 2026-02-23 09:56:22 -05:00 committed by GitHub
parent a51d9e9c65
commit a44fad890f
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 66 additions and 15 deletions

View file

@ -68,6 +68,7 @@ fun BannerCard(
interactionSource: MutableInteractionSource? = null, interactionSource: MutableInteractionSource? = null,
imageType: ImageType = ImageType.PRIMARY, imageType: ImageType = ImageType.PRIMARY,
imageContentScale: ContentScale = ContentScale.FillBounds, imageContentScale: ContentScale = ContentScale.FillBounds,
useSeriesForPrimary: Boolean = true,
) { ) {
val imageUrlService = LocalImageUrlService.current val imageUrlService = LocalImageUrlService.current
val density = LocalDensity.current val density = LocalDensity.current
@ -82,7 +83,7 @@ fun BannerCard(
} }
} }
val imageUrl = val imageUrl =
remember(item, fillHeight, imageType) { remember(item, fillHeight, imageType, useSeriesForPrimary) {
if (item != null) { if (item != null) {
item.imageUrlOverride item.imageUrlOverride
?: imageUrlService.getItemImageUrl( ?: imageUrlService.getItemImageUrl(
@ -90,6 +91,7 @@ fun BannerCard(
imageType, imageType,
fillWidth = null, fillWidth = null,
fillHeight = fillHeight, fillHeight = fillHeight,
useSeriesForPrimary = useSeriesForPrimary,
) )
} else { } else {
null null
@ -208,6 +210,7 @@ fun BannerCardWithTitle(
interactionSource: MutableInteractionSource = remember { MutableInteractionSource() }, interactionSource: MutableInteractionSource = remember { MutableInteractionSource() },
imageType: ImageType = ImageType.PRIMARY, imageType: ImageType = ImageType.PRIMARY,
imageContentScale: ContentScale = ContentScale.FillBounds, imageContentScale: ContentScale = ContentScale.FillBounds,
useSeriesForPrimary: Boolean = item?.useSeriesForPrimary ?: true,
) { ) {
val focused by interactionSource.collectIsFocusedAsState() val focused by interactionSource.collectIsFocusedAsState()
val spaceBetween by animateDpAsState(if (focused) 12.dp else 4.dp) val spaceBetween by animateDpAsState(if (focused) 12.dp else 4.dp)
@ -234,6 +237,7 @@ fun BannerCardWithTitle(
interactionSource = interactionSource, interactionSource = interactionSource,
imageType = imageType, imageType = imageType,
imageContentScale = imageContentScale, imageContentScale = imageContentScale,
useSeriesForPrimary = useSeriesForPrimary,
) )
Column( Column(
verticalArrangement = Arrangement.spacedBy(0.dp), verticalArrangement = Arrangement.spacedBy(0.dp),

View file

@ -524,6 +524,7 @@ fun HomePageCardContent(
onLongClick = onLongClick, onLongClick = onLongClick,
modifier = modifier, modifier = modifier,
cardHeight = viewOptions.heightDp.dp, cardHeight = viewOptions.heightDp.dp,
useSeriesForPrimary = viewOptions.useSeries,
) )
} else { } else {
BannerCard( BannerCard(
@ -543,6 +544,7 @@ fun HomePageCardContent(
modifier = modifier, modifier = modifier,
interactionSource = null, interactionSource = null,
cardHeight = viewOptions.heightDp.dp, cardHeight = viewOptions.heightDp.dp,
useSeriesForPrimary = viewOptions.useSeries,
) )
} }
} }

View file

@ -123,7 +123,7 @@ data class HomeRowPresets(
) )
} }
val Thumbnails by lazy { val SeriesThumbs by lazy {
val height = 148 val height = 148
val epHeight = 100 val epHeight = 100
HomeRowPresets( HomeRowPresets(
@ -132,6 +132,7 @@ data class HomeRowPresets(
heightDp = epHeight, heightDp = epHeight,
imageType = ViewOptionImageType.THUMB, imageType = ViewOptionImageType.THUMB,
aspectRatio = AspectRatio.WIDE, aspectRatio = AspectRatio.WIDE,
useSeries = true,
episodeImageType = ViewOptionImageType.THUMB, episodeImageType = ViewOptionImageType.THUMB,
episodeAspectRatio = AspectRatio.WIDE, episodeAspectRatio = AspectRatio.WIDE,
), ),
@ -164,6 +165,50 @@ data class HomeRowPresets(
genreSize = epHeight, genreSize = epHeight,
) )
} }
val EpisodeThumbnails by lazy {
val height = 148
val epHeight = 100
HomeRowPresets(
continueWatching =
HomeRowViewOptions(
heightDp = epHeight,
imageType = ViewOptionImageType.THUMB,
aspectRatio = AspectRatio.WIDE,
showTitles = true,
useSeries = false,
episodeImageType = ViewOptionImageType.PRIMARY,
episodeAspectRatio = AspectRatio.WIDE,
),
movieLibrary =
HomeRowViewOptions(
heightDp = height,
),
tvLibrary =
HomeRowViewOptions(
heightDp = height,
),
videoLibrary =
HomeRowViewOptions(
heightDp = epHeight,
aspectRatio = AspectRatio.WIDE,
),
photoLibrary =
HomeRowViewOptions(
heightDp = epHeight,
aspectRatio = AspectRatio.WIDE,
contentScale = PrefContentScale.CROP,
),
playlist =
HomeRowViewOptions(
heightDp = epHeight,
aspectRatio = AspectRatio.SQUARE,
contentScale = PrefContentScale.FIT,
),
liveTv = HomeRowViewOptions.liveTvDefault,
genreSize = epHeight,
)
}
} }
} }
@ -173,13 +218,13 @@ fun HomeRowPresetsContent(
modifier: Modifier = Modifier, modifier: Modifier = Modifier,
) { ) {
val presets = val presets =
remember {
listOf( listOf(
"Wholphin Default", stringResource(R.string.display_preset_default) to HomeRowPresets.WholphinDefault,
"Wholphin Compact", stringResource(R.string.display_preset_compact) to HomeRowPresets.WholphinCompact,
"Thumbnails", stringResource(R.string.display_preset_series_thumb) to HomeRowPresets.SeriesThumbs,
stringResource(R.string.display_preset_episode_thumbnails) to HomeRowPresets.EpisodeThumbnails,
) )
}
val focusRequesters = remember { List(presets.size) { FocusRequester() } } val focusRequesters = remember { List(presets.size) { FocusRequester() } }
LaunchedEffect(Unit) { focusRequesters[0].tryRequestFocus() } LaunchedEffect(Unit) { focusRequesters[0].tryRequestFocus() }
Column(modifier = modifier) { Column(modifier = modifier) {
@ -192,16 +237,12 @@ fun HomeRowPresetsContent(
.fillMaxHeight() .fillMaxHeight()
.focusRestorer(focusRequesters[0]), .focusRestorer(focusRequesters[0]),
) { ) {
itemsIndexed(presets) { index, title -> itemsIndexed(presets) { index, (title, preset) ->
HomeSettingsListItem( HomeSettingsListItem(
selected = false, selected = false,
headlineText = title, headlineText = title,
onClick = { onClick = {
when (index) { onApply.invoke(preset)
0 -> onApply.invoke(HomeRowPresets.WholphinDefault)
1 -> onApply.invoke(HomeRowPresets.WholphinCompact)
2 -> onApply.invoke(HomeRowPresets.Thumbnails)
}
}, },
modifier = Modifier.focusRequester(focusRequesters[index]), modifier = Modifier.focusRequester(focusRequesters[index]),
) )

View file

@ -522,6 +522,10 @@
<string name="display_presets">Display presets</string> <string name="display_presets">Display presets</string>
<string name="display_presets_description">Built-in presets to quickly style all rows</string> <string name="display_presets_description">Built-in presets to quickly style all rows</string>
<string name="customize_home_summary">Choose rows and images on the home page</string> <string name="customize_home_summary">Choose rows and images on the home page</string>
<string name="display_preset_default">Wholphin Default</string>
<string name="display_preset_compact">Wholphin Compact</string>
<string name="display_preset_series_thumb">Series Thumb images</string>
<string name="display_preset_episode_thumbnails">Episode Thumbnail images</string>
<string-array name="theme_song_volume"> <string-array name="theme_song_volume">
<item>Disabled</item> <item>Disabled</item>