mirror of
https://github.com/JustinZeus/Wholphin.git
synced 2026-07-08 23:51:21 +02:00
Header for discover page
This commit is contained in:
parent
22348a5adc
commit
d814d533ab
3 changed files with 174 additions and 118 deletions
|
|
@ -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,
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue