mirror of
https://github.com/JustinZeus/Wholphin.git
synced 2026-07-08 15:50:16 +02:00
Fixes to user interface (#374)
- Fixes a crash if the same person has multiple roles in an item - Better tab focus behavior, especially on series pages - Use consistent series production years on all pages - Slightly decrease size and increase space on home page
This commit is contained in:
parent
887b7b597e
commit
a197f6911b
9 changed files with 73 additions and 63 deletions
|
|
@ -5,6 +5,7 @@ import com.github.damontecres.wholphin.ui.formatDateTime
|
|||
import com.github.damontecres.wholphin.ui.nav.Destination
|
||||
import com.github.damontecres.wholphin.ui.seasonEpisode
|
||||
import com.github.damontecres.wholphin.ui.seasonEpisodePadded
|
||||
import com.github.damontecres.wholphin.ui.seriesProductionYears
|
||||
import kotlinx.serialization.Serializable
|
||||
import kotlinx.serialization.Transient
|
||||
import org.jellyfin.sdk.api.client.ApiClient
|
||||
|
|
@ -28,7 +29,11 @@ data class BaseItem(
|
|||
|
||||
val subtitle
|
||||
get() =
|
||||
if (type == BaseItemKind.EPISODE) data.seasonEpisode + " - " + name else data.productionYear?.toString()
|
||||
when (type) {
|
||||
BaseItemKind.EPISODE -> data.seasonEpisode + " - " + name
|
||||
BaseItemKind.SERIES -> data.seriesProductionYears
|
||||
else -> data.productionYear?.toString()
|
||||
}
|
||||
|
||||
val subtitleLong: String? by lazy {
|
||||
if (type == BaseItemKind.EPISODE) {
|
||||
|
|
|
|||
|
|
@ -54,6 +54,27 @@ val BaseItemDto.seasonEpisodePadded: String?
|
|||
null
|
||||
}
|
||||
|
||||
val BaseItemDto.seriesProductionYears: String?
|
||||
get() =
|
||||
if (productionYear != null) {
|
||||
buildString {
|
||||
append(productionYear.toString())
|
||||
if (status == "Continuing") {
|
||||
append(" - ")
|
||||
append("Present")
|
||||
} else if (status == "Ended") {
|
||||
endDate?.let {
|
||||
if (it.year != productionYear) {
|
||||
append(" - ")
|
||||
append(it.year)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
null
|
||||
}
|
||||
|
||||
private val abbrevSuffixes = listOf("", "K", "M", "B")
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -75,7 +75,7 @@ val DefaultButtonPadding =
|
|||
)
|
||||
|
||||
object Cards {
|
||||
val height2x3 = 180.dp
|
||||
val height2x3 = 172.dp
|
||||
val playedPercentHeight = 6.dp
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -52,7 +52,7 @@ fun PersonRow(
|
|||
.fillMaxWidth()
|
||||
.focusRestorer(firstFocus),
|
||||
) {
|
||||
itemsIndexed(people, key = { _, item -> item.id }) { index, item ->
|
||||
itemsIndexed(people) { index, item ->
|
||||
PersonCard(
|
||||
item = item,
|
||||
onClick = { onClick.invoke(item) },
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ import androidx.tv.material3.Text
|
|||
import com.github.damontecres.wholphin.ui.formatDateTime
|
||||
import com.github.damontecres.wholphin.ui.roundMinutes
|
||||
import com.github.damontecres.wholphin.ui.seasonEpisode
|
||||
import com.github.damontecres.wholphin.ui.seriesProductionYears
|
||||
import com.github.damontecres.wholphin.ui.timeRemaining
|
||||
import org.jellyfin.sdk.model.api.BaseItemDto
|
||||
import org.jellyfin.sdk.model.extensions.ticks
|
||||
|
|
@ -85,22 +86,7 @@ fun SeriesQuickDetails(
|
|||
val details =
|
||||
remember(dto) {
|
||||
buildList {
|
||||
if (dto?.productionYear != null) {
|
||||
val date =
|
||||
buildString {
|
||||
append(dto.productionYear.toString())
|
||||
if (dto.status == "Continuing") {
|
||||
append(" - ")
|
||||
append("Present")
|
||||
} else if (dto.status == "Ended") {
|
||||
dto.endDate?.let {
|
||||
append(" - ")
|
||||
append(it.year)
|
||||
}
|
||||
}
|
||||
}
|
||||
add(date)
|
||||
}
|
||||
dto?.seriesProductionYears?.let(::add)
|
||||
val duration = dto?.runTimeTicks?.ticks
|
||||
duration
|
||||
?.roundMinutes
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ package com.github.damontecres.wholphin.ui.components
|
|||
import androidx.compose.animation.core.animateDpAsState
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.focusGroup
|
||||
import androidx.compose.foundation.interaction.MutableInteractionSource
|
||||
import androidx.compose.foundation.interaction.collectIsFocusedAsState
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
|
|
@ -23,6 +24,7 @@ import androidx.compose.runtime.remember
|
|||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.focus.FocusDirection
|
||||
import androidx.compose.ui.focus.FocusRequester
|
||||
import androidx.compose.ui.focus.focusProperties
|
||||
import androidx.compose.ui.focus.focusRequester
|
||||
|
|
@ -58,9 +60,20 @@ fun TabRow(
|
|||
modifier
|
||||
.onFocusChanged {
|
||||
rowHasFocus = it.hasFocus
|
||||
}.focusProperties {
|
||||
}.focusGroup()
|
||||
.focusProperties {
|
||||
onEnter = {
|
||||
focusRequesters.getOrNull(selectedTabIndex)?.tryRequestFocus()
|
||||
// If entering from left or right, use last or first tab
|
||||
// Otherwise use the selected tab
|
||||
val focusRequester =
|
||||
if (requestedFocusDirection == FocusDirection.Left) {
|
||||
focusRequesters.lastOrNull()
|
||||
} else if (requestedFocusDirection == FocusDirection.Right) {
|
||||
focusRequesters.firstOrNull()
|
||||
} else {
|
||||
focusRequesters.getOrNull(selectedTabIndex)
|
||||
}
|
||||
(focusRequester ?: FocusRequester.Default).tryRequestFocus()
|
||||
}
|
||||
},
|
||||
) {
|
||||
|
|
|
|||
|
|
@ -55,7 +55,6 @@ import com.github.damontecres.wholphin.ui.components.DetailsBackdropImage
|
|||
import com.github.damontecres.wholphin.ui.components.DialogItem
|
||||
import com.github.damontecres.wholphin.ui.components.DialogParams
|
||||
import com.github.damontecres.wholphin.ui.components.DialogPopup
|
||||
import com.github.damontecres.wholphin.ui.components.DotSeparatedRow
|
||||
import com.github.damontecres.wholphin.ui.components.ErrorMessage
|
||||
import com.github.damontecres.wholphin.ui.components.ExpandableFaButton
|
||||
import com.github.damontecres.wholphin.ui.components.ExpandablePlayButton
|
||||
|
|
@ -63,6 +62,7 @@ import com.github.damontecres.wholphin.ui.components.GenreText
|
|||
import com.github.damontecres.wholphin.ui.components.LoadingPage
|
||||
import com.github.damontecres.wholphin.ui.components.Optional
|
||||
import com.github.damontecres.wholphin.ui.components.OverviewText
|
||||
import com.github.damontecres.wholphin.ui.components.SeriesQuickDetails
|
||||
import com.github.damontecres.wholphin.ui.data.AddPlaylistViewModel
|
||||
import com.github.damontecres.wholphin.ui.data.ItemDetailsDialog
|
||||
import com.github.damontecres.wholphin.ui.data.ItemDetailsDialogInfo
|
||||
|
|
@ -75,14 +75,12 @@ import com.github.damontecres.wholphin.ui.detail.movie.TrailerRow
|
|||
import com.github.damontecres.wholphin.ui.letNotEmpty
|
||||
import com.github.damontecres.wholphin.ui.nav.Destination
|
||||
import com.github.damontecres.wholphin.ui.rememberInt
|
||||
import com.github.damontecres.wholphin.ui.roundMinutes
|
||||
import com.github.damontecres.wholphin.ui.tryRequestFocus
|
||||
import com.github.damontecres.wholphin.util.ExceptionHandler
|
||||
import com.github.damontecres.wholphin.util.LoadingState
|
||||
import kotlinx.coroutines.launch
|
||||
import org.jellyfin.sdk.model.api.BaseItemKind
|
||||
import org.jellyfin.sdk.model.api.MediaType
|
||||
import org.jellyfin.sdk.model.extensions.ticks
|
||||
import java.util.UUID
|
||||
import kotlin.time.Duration
|
||||
|
||||
|
|
@ -572,17 +570,6 @@ fun SeriesDetailsHeader(
|
|||
) {
|
||||
val scope = rememberCoroutineScope()
|
||||
val dto = series.data
|
||||
val details =
|
||||
remember(series) {
|
||||
buildList {
|
||||
dto.productionYear?.let { add(it.toString()) }
|
||||
dto.runTimeTicks
|
||||
?.ticks
|
||||
?.roundMinutes
|
||||
?.let { add(it.toString()) }
|
||||
dto.officialRating?.let(::add)
|
||||
}
|
||||
}
|
||||
Column(
|
||||
verticalArrangement = Arrangement.spacedBy(8.dp),
|
||||
modifier = modifier,
|
||||
|
|
@ -598,13 +585,7 @@ fun SeriesDetailsHeader(
|
|||
verticalArrangement = Arrangement.spacedBy(4.dp),
|
||||
modifier = Modifier.fillMaxWidth(.60f),
|
||||
) {
|
||||
DotSeparatedRow(
|
||||
texts = details,
|
||||
communityRating = dto.communityRating,
|
||||
criticRating = dto.criticRating,
|
||||
textStyle = MaterialTheme.typography.titleSmall,
|
||||
modifier = Modifier,
|
||||
)
|
||||
SeriesQuickDetails(dto)
|
||||
dto.genres?.letNotEmpty {
|
||||
GenreText(it)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -151,7 +151,6 @@ fun SeriesOverviewContent(
|
|||
modifier =
|
||||
Modifier
|
||||
.focusRequester(tabRowFocusRequester)
|
||||
.focusRestorer()
|
||||
.padding(paddingValues)
|
||||
.fillMaxWidth(),
|
||||
)
|
||||
|
|
|
|||
|
|
@ -14,6 +14,7 @@ import androidx.compose.foundation.layout.fillMaxWidth
|
|||
import androidx.compose.foundation.layout.height
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.foundation.layout.width
|
||||
import androidx.compose.foundation.lazy.LazyColumn
|
||||
import androidx.compose.foundation.lazy.itemsIndexed
|
||||
import androidx.compose.foundation.lazy.rememberLazyListState
|
||||
|
|
@ -246,9 +247,8 @@ fun HomePageContent(
|
|||
item = focusedItem,
|
||||
modifier =
|
||||
Modifier
|
||||
.fillMaxWidth(.6f)
|
||||
.fillMaxHeight(.33f)
|
||||
.padding(16.dp),
|
||||
.padding(top = 48.dp, bottom = 32.dp, start = 32.dp)
|
||||
.fillMaxHeight(.33f),
|
||||
)
|
||||
LazyColumn(
|
||||
state = listState,
|
||||
|
|
@ -411,29 +411,33 @@ fun HomePageContent(
|
|||
fun HomePageHeader(
|
||||
item: BaseItem?,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
Box(
|
||||
modifier = modifier,
|
||||
) {
|
||||
Column(
|
||||
verticalArrangement = Arrangement.spacedBy(8.dp),
|
||||
modifier = Modifier.fillMaxSize(),
|
||||
) {
|
||||
item?.let {
|
||||
val dto = item.data
|
||||
val isEpisode = item.type == BaseItemKind.EPISODE
|
||||
val title = if (isEpisode) dto.seriesName ?: item.name else item.name
|
||||
val subtitle = if (isEpisode) dto.name else null
|
||||
val overview = dto.overview
|
||||
title?.let {
|
||||
Column(
|
||||
verticalArrangement = Arrangement.spacedBy(4.dp),
|
||||
modifier = modifier,
|
||||
) {
|
||||
item.title?.let {
|
||||
Text(
|
||||
text = title,
|
||||
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,
|
||||
|
|
@ -452,6 +456,7 @@ fun HomePageHeader(
|
|||
Modifier
|
||||
.padding(0.dp)
|
||||
.height(48.dp + if (!isEpisode) 12.dp else 0.dp)
|
||||
.width(400.dp)
|
||||
if (overview.isNotNullOrBlank()) {
|
||||
Text(
|
||||
text = overview,
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue