Header for discover page

This commit is contained in:
Damontecres 2025-12-23 18:29:22 -05:00
parent 22348a5adc
commit d814d533ab
No known key found for this signature in database
3 changed files with 174 additions and 118 deletions

View file

@ -62,6 +62,8 @@ data class DiscoverItem(
val id: Int, val id: Int,
val type: SeerrItemType, val type: SeerrItemType,
val title: String?, val title: String?,
val subtitle: String?,
val overview: String?,
val availability: SeerrAvailability, val availability: SeerrAvailability,
val releaseDate: String?, val releaseDate: String?,
val posterPath: String?, val posterPath: String?,
@ -75,6 +77,8 @@ data class DiscoverItem(
id = movie.id, id = movie.id,
type = SeerrItemType.MOVIE, type = SeerrItemType.MOVIE,
title = movie.title, title = movie.title,
subtitle = null,
overview = movie.overview,
availability = SeerrAvailability.from(movie.mediaInfo?.status) ?: SeerrAvailability.UNKNOWN, availability = SeerrAvailability.from(movie.mediaInfo?.status) ?: SeerrAvailability.UNKNOWN,
releaseDate = movie.releaseDate, releaseDate = movie.releaseDate,
posterPath = movie.posterPath, posterPath = movie.posterPath,
@ -86,6 +90,8 @@ data class DiscoverItem(
id = movie.id ?: -1, id = movie.id ?: -1,
type = SeerrItemType.MOVIE, type = SeerrItemType.MOVIE,
title = movie.title, title = movie.title,
subtitle = null,
overview = movie.overview,
availability = SeerrAvailability.from(movie.mediaInfo?.status) ?: SeerrAvailability.UNKNOWN, availability = SeerrAvailability.from(movie.mediaInfo?.status) ?: SeerrAvailability.UNKNOWN,
releaseDate = movie.releaseDate, releaseDate = movie.releaseDate,
posterPath = movie.posterPath, posterPath = movie.posterPath,
@ -97,6 +103,8 @@ data class DiscoverItem(
id = tv.id!!, id = tv.id!!,
type = SeerrItemType.MOVIE, type = SeerrItemType.MOVIE,
title = tv.name, title = tv.name,
subtitle = null,
overview = tv.overview,
availability = SeerrAvailability.from(tv.mediaInfo?.status) ?: SeerrAvailability.UNKNOWN, availability = SeerrAvailability.from(tv.mediaInfo?.status) ?: SeerrAvailability.UNKNOWN,
releaseDate = tv.firstAirDate, releaseDate = tv.firstAirDate,
posterPath = tv.posterPath, posterPath = tv.posterPath,
@ -108,6 +116,8 @@ data class DiscoverItem(
id = search.id, id = search.id,
type = SeerrItemType.fromString(search.mediaType), type = SeerrItemType.fromString(search.mediaType),
title = search.title ?: search.name, title = search.title ?: search.name,
subtitle = null,
overview = search.overview,
availability = availability =
SeerrAvailability.from(search.mediaInfo?.status) SeerrAvailability.from(search.mediaInfo?.status)
?: SeerrAvailability.UNKNOWN, ?: SeerrAvailability.UNKNOWN,

View file

@ -437,62 +437,85 @@ fun HomePageHeader(
modifier: Modifier = Modifier, modifier: Modifier = Modifier,
) { ) {
item?.let { item?.let {
val isEpisode = item.type == BaseItemKind.EPISODE
val dto = item.data val dto = item.data
Column(
verticalArrangement = Arrangement.spacedBy(4.dp), HomePageHeader(
modifier = modifier, title = item.title,
) { subtitle = if (isEpisode) dto.name else null,
item.title?.let { overview = dto.overview,
Text( overviewTwoLines = isEpisode,
text = it, quickDetails = {
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,
)
}
when (item.type) { when (item.type) {
BaseItemKind.EPISODE -> EpisodeQuickDetails(dto, Modifier) BaseItemKind.EPISODE -> EpisodeQuickDetails(dto, Modifier)
BaseItemKind.SERIES -> SeriesQuickDetails(dto, Modifier) BaseItemKind.SERIES -> SeriesQuickDetails(dto, Modifier)
else -> MovieQuickDetails(dto, Modifier) else -> MovieQuickDetails(dto, Modifier)
} }
val overviewModifier = },
Modifier modifier = modifier,
.padding(0.dp) )
.height(48.dp + if (!isEpisode) 12.dp else 0.dp) }
.width(400.dp) }
if (overview.isNotNullOrBlank()) {
Text( @Composable
text = overview, fun HomePageHeader(
style = MaterialTheme.typography.bodyMedium, title: String?,
color = MaterialTheme.colorScheme.onSurface, subtitle: String?,
maxLines = if (isEpisode) 2 else 3, overview: String?,
overflow = TextOverflow.Ellipsis, overviewTwoLines: Boolean,
modifier = overviewModifier, quickDetails: (@Composable () -> Unit)?,
) modifier: Modifier = Modifier,
} else { ) {
Spacer(overviewModifier) 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)
} }
} }
} }

View file

@ -2,6 +2,7 @@ package com.github.damontecres.wholphin.ui.seerr
import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxHeight
import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.verticalScroll 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.cards.ItemRow
import com.github.damontecres.wholphin.ui.data.RowColumn import com.github.damontecres.wholphin.ui.data.RowColumn
import com.github.damontecres.wholphin.ui.launchIO 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.nav.Destination
import com.github.damontecres.wholphin.ui.rememberPosition import com.github.damontecres.wholphin.ui.rememberPosition
import com.github.damontecres.wholphin.ui.setValueOnMain 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 dagger.hilt.android.lifecycle.HiltViewModel
import org.jellyfin.sdk.api.client.ApiClient import org.jellyfin.sdk.api.client.ApiClient
import org.jellyfin.sdk.model.api.BaseItemKind import org.jellyfin.sdk.model.api.BaseItemKind
import timber.log.Timber
import javax.inject.Inject import javax.inject.Inject
@HiltViewModel @HiltViewModel
@ -99,84 +100,106 @@ fun SeerrDiscoverPage(
LaunchedEffect(movies) { if (movies.isNotEmpty()) focusRequester.tryRequestFocus() } LaunchedEffect(movies) { if (movies.isNotEmpty()) focusRequester.tryRequestFocus() }
val scrollState = rememberScrollState() val scrollState = rememberScrollState()
var position by rememberPosition(0, 0) var position by rememberPosition()
LaunchedEffect(position) { LaunchedEffect(position) {
position.let { position.let {
val item = if (it.row == 0) movies.getOrNull(it.column) else tv.getOrNull(it.column) val item = if (it.row == 0) movies.getOrNull(it.column) else tv.getOrNull(it.column)
Timber.v("Backdrop for $item")
viewModel.updateBackdrop(item) viewModel.updateBackdrop(item)
} }
} }
val focusedItem =
remember(position) {
position.let {
if (it.row == 0) movies.getOrNull(it.column) else tv.getOrNull(it.column)
}
}
Column( Column(
verticalArrangement = Arrangement.spacedBy(16.dp), modifier = modifier,
modifier =
modifier
.verticalScroll(scrollState)
.padding(16.dp),
) { ) {
ItemRow( HomePageHeader(
title = stringResource(R.string.movies), title = focusedItem?.title,
items = movies, subtitle = focusedItem?.subtitle,
onClickItem = { index, item -> overview = focusedItem?.overview,
if (item.jellyfinItemId != null) { overviewTwoLines = true,
viewModel.navigationManager.navigateTo( quickDetails = {
Destination.MediaItem( // TODO
itemId = item.jellyfinItemId,
type = BaseItemKind.MOVIE,
),
)
} else {
viewModel.navigationManager.navigateTo(Destination.DiscoveredItem(item))
}
}, },
onLongClickItem = { index, item -> }, modifier =
cardContent = { index: Int, item: DiscoverItem?, mod: Modifier, onClick: () -> Unit, onLongClick: () -> Unit -> Modifier
DiscoverItemCard( .padding(top = 48.dp, bottom = 32.dp, start = 32.dp)
item = item, .fillMaxHeight(.33f),
onClick = onClick,
onLongClick = onLongClick,
showOverlay = false,
modifier = mod,
)
},
cardOnFocus = { isFocused, index ->
if (isFocused) {
position = RowColumn(0, index)
}
},
modifier = Modifier.focusRequester(focusRequester),
) )
ItemRow( Column(
title = stringResource(R.string.tv_shows), verticalArrangement = Arrangement.spacedBy(8.dp),
items = tv, modifier =
onClickItem = { index, item -> Modifier
if (item.jellyfinItemId != null) { .verticalScroll(scrollState)
viewModel.navigationManager.navigateTo( .padding(horizontal = 16.dp),
Destination.MediaItem( ) {
itemId = item.jellyfinItemId, ItemRow(
type = BaseItemKind.SERIES, 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)) cardOnFocus = { isFocused, index ->
} if (isFocused) {
}, position = RowColumn(0, index)
onLongClickItem = { index, item -> }, }
cardContent = { index: Int, item: DiscoverItem?, mod: Modifier, onClick: () -> Unit, onLongClick: () -> Unit -> },
DiscoverItemCard( modifier = Modifier.focusRequester(focusRequester),
item = item, )
onClick = onClick, ItemRow(
onLongClick = onLongClick, title = stringResource(R.string.tv_shows),
showOverlay = false, items = tv,
modifier = mod, onClickItem = { index, item ->
) if (item.jellyfinItemId != null) {
}, viewModel.navigationManager.navigateTo(
cardOnFocus = { isFocused, index -> Destination.MediaItem(
if (isFocused) { itemId = item.jellyfinItemId,
position = RowColumn(1, index) 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)
}
},
)
}
} }
} }