From d814d533abbc41e1cc78f4968f4360362b8aff4c Mon Sep 17 00:00:00 2001 From: Damontecres Date: Tue, 23 Dec 2025 18:29:22 -0500 Subject: [PATCH] Header for discover page --- .../wholphin/data/model/DiscoverItem.kt | 10 ++ .../damontecres/wholphin/ui/main/HomePage.kt | 123 ++++++++------ .../wholphin/ui/seerr/SeerrDiscoverPage.kt | 159 ++++++++++-------- 3 files changed, 174 insertions(+), 118 deletions(-) diff --git a/app/src/main/java/com/github/damontecres/wholphin/data/model/DiscoverItem.kt b/app/src/main/java/com/github/damontecres/wholphin/data/model/DiscoverItem.kt index c5d02b37..251f45b3 100644 --- a/app/src/main/java/com/github/damontecres/wholphin/data/model/DiscoverItem.kt +++ b/app/src/main/java/com/github/damontecres/wholphin/data/model/DiscoverItem.kt @@ -62,6 +62,8 @@ data class DiscoverItem( val id: Int, val type: SeerrItemType, val title: String?, + val subtitle: String?, + val overview: String?, val availability: SeerrAvailability, val releaseDate: String?, val posterPath: String?, @@ -75,6 +77,8 @@ data class DiscoverItem( id = movie.id, type = SeerrItemType.MOVIE, title = movie.title, + subtitle = null, + overview = movie.overview, availability = SeerrAvailability.from(movie.mediaInfo?.status) ?: SeerrAvailability.UNKNOWN, releaseDate = movie.releaseDate, posterPath = movie.posterPath, @@ -86,6 +90,8 @@ data class DiscoverItem( id = movie.id ?: -1, type = SeerrItemType.MOVIE, title = movie.title, + subtitle = null, + overview = movie.overview, availability = SeerrAvailability.from(movie.mediaInfo?.status) ?: SeerrAvailability.UNKNOWN, releaseDate = movie.releaseDate, posterPath = movie.posterPath, @@ -97,6 +103,8 @@ data class DiscoverItem( id = tv.id!!, type = SeerrItemType.MOVIE, title = tv.name, + subtitle = null, + overview = tv.overview, availability = SeerrAvailability.from(tv.mediaInfo?.status) ?: SeerrAvailability.UNKNOWN, releaseDate = tv.firstAirDate, posterPath = tv.posterPath, @@ -108,6 +116,8 @@ data class DiscoverItem( id = search.id, type = SeerrItemType.fromString(search.mediaType), title = search.title ?: search.name, + subtitle = null, + overview = search.overview, availability = SeerrAvailability.from(search.mediaInfo?.status) ?: SeerrAvailability.UNKNOWN, diff --git a/app/src/main/java/com/github/damontecres/wholphin/ui/main/HomePage.kt b/app/src/main/java/com/github/damontecres/wholphin/ui/main/HomePage.kt index 67b4200b..e19f44aa 100644 --- a/app/src/main/java/com/github/damontecres/wholphin/ui/main/HomePage.kt +++ b/app/src/main/java/com/github/damontecres/wholphin/ui/main/HomePage.kt @@ -437,62 +437,85 @@ fun HomePageHeader( modifier: Modifier = Modifier, ) { item?.let { + val isEpisode = item.type == BaseItemKind.EPISODE val dto = item.data - Column( - verticalArrangement = Arrangement.spacedBy(4.dp), - modifier = modifier, - ) { - item.title?.let { - Text( - text = it, - style = MaterialTheme.typography.headlineMedium.copy(fontWeight = FontWeight.SemiBold), - color = MaterialTheme.colorScheme.onBackground, - maxLines = 1, - overflow = TextOverflow.Ellipsis, - modifier = Modifier.fillMaxWidth(.75f), - ) - } - Column( - verticalArrangement = Arrangement.spacedBy(8.dp), - modifier = - Modifier - .fillMaxWidth(.6f) - .fillMaxHeight(), - ) { - val isEpisode = item.type == BaseItemKind.EPISODE - val subtitle = if (isEpisode) dto.name else null - val overview = dto.overview - subtitle?.let { - Text( - text = subtitle, - style = MaterialTheme.typography.headlineSmall, - color = MaterialTheme.colorScheme.onBackground, - maxLines = 1, - overflow = TextOverflow.Ellipsis, - ) - } + + HomePageHeader( + title = item.title, + subtitle = if (isEpisode) dto.name else null, + overview = dto.overview, + overviewTwoLines = isEpisode, + quickDetails = { when (item.type) { BaseItemKind.EPISODE -> EpisodeQuickDetails(dto, Modifier) BaseItemKind.SERIES -> SeriesQuickDetails(dto, Modifier) else -> MovieQuickDetails(dto, Modifier) } - val overviewModifier = - Modifier - .padding(0.dp) - .height(48.dp + if (!isEpisode) 12.dp else 0.dp) - .width(400.dp) - if (overview.isNotNullOrBlank()) { - Text( - text = overview, - style = MaterialTheme.typography.bodyMedium, - color = MaterialTheme.colorScheme.onSurface, - maxLines = if (isEpisode) 2 else 3, - overflow = TextOverflow.Ellipsis, - modifier = overviewModifier, - ) - } else { - Spacer(overviewModifier) - } + }, + modifier = modifier, + ) + } +} + +@Composable +fun HomePageHeader( + title: String?, + subtitle: String?, + overview: String?, + overviewTwoLines: Boolean, + quickDetails: (@Composable () -> Unit)?, + modifier: Modifier = Modifier, +) { + Column( + verticalArrangement = Arrangement.spacedBy(4.dp), + modifier = modifier, + ) { + title?.let { + Text( + text = it, + style = MaterialTheme.typography.headlineMedium.copy(fontWeight = FontWeight.SemiBold), + color = MaterialTheme.colorScheme.onBackground, + maxLines = 1, + overflow = TextOverflow.Ellipsis, + modifier = Modifier.fillMaxWidth(.75f), + ) + } + Column( + verticalArrangement = Arrangement.spacedBy(8.dp), + modifier = + Modifier + .fillMaxWidth(.6f) + .fillMaxHeight(), + ) { +// val isEpisode = item.type == BaseItemKind.EPISODE +// val subtitle = if (isEpisode) dto.name else null +// val overview = dto.overview + subtitle?.let { + Text( + text = subtitle, + style = MaterialTheme.typography.headlineSmall, + color = MaterialTheme.colorScheme.onBackground, + maxLines = 1, + overflow = TextOverflow.Ellipsis, + ) + } + quickDetails?.invoke() + val overviewModifier = + Modifier + .padding(0.dp) + .height(48.dp + if (!overviewTwoLines) 12.dp else 0.dp) + .width(400.dp) + if (overview.isNotNullOrBlank()) { + Text( + text = overview, + style = MaterialTheme.typography.bodyMedium, + color = MaterialTheme.colorScheme.onSurface, + maxLines = if (overviewTwoLines) 2 else 3, + overflow = TextOverflow.Ellipsis, + modifier = overviewModifier, + ) + } else { + Spacer(overviewModifier) } } } diff --git a/app/src/main/java/com/github/damontecres/wholphin/ui/seerr/SeerrDiscoverPage.kt b/app/src/main/java/com/github/damontecres/wholphin/ui/seerr/SeerrDiscoverPage.kt index eaa51fee..b59b4417 100644 --- a/app/src/main/java/com/github/damontecres/wholphin/ui/seerr/SeerrDiscoverPage.kt +++ b/app/src/main/java/com/github/damontecres/wholphin/ui/seerr/SeerrDiscoverPage.kt @@ -2,6 +2,7 @@ package com.github.damontecres.wholphin.ui.seerr import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.fillMaxHeight import androidx.compose.foundation.layout.padding import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.verticalScroll @@ -31,6 +32,7 @@ import com.github.damontecres.wholphin.ui.cards.DiscoverItemCard import com.github.damontecres.wholphin.ui.cards.ItemRow import com.github.damontecres.wholphin.ui.data.RowColumn import com.github.damontecres.wholphin.ui.launchIO +import com.github.damontecres.wholphin.ui.main.HomePageHeader import com.github.damontecres.wholphin.ui.nav.Destination import com.github.damontecres.wholphin.ui.rememberPosition import com.github.damontecres.wholphin.ui.setValueOnMain @@ -38,7 +40,6 @@ import com.github.damontecres.wholphin.ui.tryRequestFocus import dagger.hilt.android.lifecycle.HiltViewModel import org.jellyfin.sdk.api.client.ApiClient import org.jellyfin.sdk.model.api.BaseItemKind -import timber.log.Timber import javax.inject.Inject @HiltViewModel @@ -99,84 +100,106 @@ fun SeerrDiscoverPage( LaunchedEffect(movies) { if (movies.isNotEmpty()) focusRequester.tryRequestFocus() } val scrollState = rememberScrollState() - var position by rememberPosition(0, 0) + var position by rememberPosition() LaunchedEffect(position) { position.let { val item = if (it.row == 0) movies.getOrNull(it.column) else tv.getOrNull(it.column) - Timber.v("Backdrop for $item") viewModel.updateBackdrop(item) } } + val focusedItem = + remember(position) { + position.let { + if (it.row == 0) movies.getOrNull(it.column) else tv.getOrNull(it.column) + } + } Column( - verticalArrangement = Arrangement.spacedBy(16.dp), - modifier = - modifier - .verticalScroll(scrollState) - .padding(16.dp), + modifier = modifier, ) { - ItemRow( - title = stringResource(R.string.movies), - items = movies, - onClickItem = { index, item -> - if (item.jellyfinItemId != null) { - viewModel.navigationManager.navigateTo( - Destination.MediaItem( - itemId = item.jellyfinItemId, - type = BaseItemKind.MOVIE, - ), - ) - } else { - viewModel.navigationManager.navigateTo(Destination.DiscoveredItem(item)) - } + HomePageHeader( + title = focusedItem?.title, + subtitle = focusedItem?.subtitle, + overview = focusedItem?.overview, + overviewTwoLines = true, + quickDetails = { + // TODO }, - onLongClickItem = { index, item -> }, - cardContent = { index: Int, item: DiscoverItem?, mod: Modifier, onClick: () -> Unit, onLongClick: () -> Unit -> - DiscoverItemCard( - item = item, - onClick = onClick, - onLongClick = onLongClick, - showOverlay = false, - modifier = mod, - ) - }, - cardOnFocus = { isFocused, index -> - if (isFocused) { - position = RowColumn(0, index) - } - }, - modifier = Modifier.focusRequester(focusRequester), + modifier = + Modifier + .padding(top = 48.dp, bottom = 32.dp, start = 32.dp) + .fillMaxHeight(.33f), ) - ItemRow( - title = stringResource(R.string.tv_shows), - items = tv, - onClickItem = { index, item -> - if (item.jellyfinItemId != null) { - viewModel.navigationManager.navigateTo( - Destination.MediaItem( - itemId = item.jellyfinItemId, - type = BaseItemKind.SERIES, - ), + Column( + verticalArrangement = Arrangement.spacedBy(8.dp), + modifier = + Modifier + .verticalScroll(scrollState) + .padding(horizontal = 16.dp), + ) { + ItemRow( + title = stringResource(R.string.movies), + items = movies, + onClickItem = { index, item -> + if (item.jellyfinItemId != null) { + viewModel.navigationManager.navigateTo( + Destination.MediaItem( + itemId = item.jellyfinItemId, + type = BaseItemKind.MOVIE, + ), + ) + } else { + viewModel.navigationManager.navigateTo(Destination.DiscoveredItem(item)) + } + }, + onLongClickItem = { index, item -> }, + cardContent = { index: Int, item: DiscoverItem?, mod: Modifier, onClick: () -> Unit, onLongClick: () -> Unit -> + DiscoverItemCard( + item = item, + onClick = onClick, + onLongClick = onLongClick, + showOverlay = false, + modifier = mod, ) - } else { - viewModel.navigationManager.navigateTo(Destination.DiscoveredItem(item)) - } - }, - onLongClickItem = { index, item -> }, - cardContent = { index: Int, item: DiscoverItem?, mod: Modifier, onClick: () -> Unit, onLongClick: () -> Unit -> - DiscoverItemCard( - item = item, - onClick = onClick, - onLongClick = onLongClick, - showOverlay = false, - modifier = mod, - ) - }, - cardOnFocus = { isFocused, index -> - if (isFocused) { - position = RowColumn(1, index) - } - }, - ) + }, + cardOnFocus = { isFocused, index -> + if (isFocused) { + position = RowColumn(0, index) + } + }, + modifier = Modifier.focusRequester(focusRequester), + ) + ItemRow( + title = stringResource(R.string.tv_shows), + items = tv, + onClickItem = { index, item -> + if (item.jellyfinItemId != null) { + viewModel.navigationManager.navigateTo( + Destination.MediaItem( + itemId = item.jellyfinItemId, + type = BaseItemKind.SERIES, + ), + ) + } else { + viewModel.navigationManager.navigateTo(Destination.DiscoveredItem(item)) + } + }, + onLongClickItem = { index, item -> }, + cardContent = { index: Int, item: DiscoverItem?, mod: Modifier, onClick: () -> Unit, onLongClick: () -> Unit -> + DiscoverItemCard( + item = item, + onClick = onClick, + onLongClick = onLongClick, + showOverlay = false, + modifier = mod, + ) + }, + cardOnFocus = { isFocused, index -> + if (isFocused) { + position = RowColumn(1, index) + } + }, + ) + } } }