Add prefer logos instead of text titles setting (#1151)

## Description
Adds an advanced settings to prefer logos for titles instead of text. It
is enabled by default.

### Related issues
Closes #383

### Testing
Emulator & onn

## Screenshots
<details><summary>Click to show</summary>
<p>

### Home page
![logo_1
Large](https://github.com/user-attachments/assets/629a65ab-cf43-4050-af4c-5925763a5f4f)

### Series overview
![logo_2
Large](https://github.com/user-attachments/assets/13ca2d28-cc61-4f4e-abaa-61a310b5c707)

### Movie Page
![logo_movie
Large](https://github.com/user-attachments/assets/a590e6cf-452d-4313-b6a3-35c6f59dc1ff)

### Square-ish logo
![logo_square
Large](https://github.com/user-attachments/assets/7d1340c2-4a3e-42c3-b7bd-ae1114cb6645)

</p>
</details> 


## AI or LLM usage
None
This commit is contained in:
Ray 2026-03-29 10:38:23 -04:00 committed by GitHub
parent 253b203c02
commit 2485d2c644
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
27 changed files with 290 additions and 122 deletions

View file

@ -501,6 +501,18 @@ sealed interface AppPreference<Pref, T> {
valueToIndex = { if (it != AppThemeColors.UNRECOGNIZED) it.number else 0 },
)
val ShowLogos =
AppSwitchPreference<AppPreferences>(
title = R.string.prefer_logos,
defaultValue = true,
getter = { it.interfacePreferences.showLogos },
setter = { prefs, value ->
prefs.updateInterfacePreferences { showLogos = value }
},
summaryOn = R.string.enabled,
summaryOff = R.string.disabled,
)
val InstalledVersion =
AppClickablePreference<AppPreferences>(
title = R.string.installed_version,
@ -1122,6 +1134,7 @@ val advancedPreferences =
preferences =
listOf(
AppPreference.ShowClock,
AppPreference.ShowLogos,
AppPreference.ManageMedia,
AppPreference.CombineContinueNext,
// Temporarily disabled, see https://github.com/damontecres/Wholphin/pull/127#issuecomment-3478058418

View file

@ -95,6 +95,7 @@ class AppPreferencesSerializer
AppPreference.NavDrawerSwitchOnFocus.defaultValue
showClock = AppPreference.ShowClock.defaultValue
backdropStyle = AppPreference.BackdropStylePref.defaultValue
showLogos = AppPreference.ShowLogos.defaultValue
subtitlesPreferences =
SubtitlePreferences

View file

@ -282,7 +282,7 @@ class AppUpgradeHandler
}
}
if (previous.isEqualOrBefore(Version.fromString("0.6.0-0-g0"))) {
if (previous.isEqualOrBefore(Version.fromString("0.5.4-6-g0"))) {
appPreferences.updateData {
it.updateMusicPreferences {
showBackdrop = true
@ -291,5 +291,13 @@ class AppUpgradeHandler
}
}
}
if (previous.isEqualOrBefore(Version.fromString("0.5.4-15-g0"))) {
appPreferences.updateData {
it.updateInterfacePreferences {
showLogos = AppPreference.ShowLogos.defaultValue
}
}
}
}
}

View file

@ -886,7 +886,7 @@ class HomeSettingsService
limit = limit,
enableUserData = true,
enableImages = true,
enableImageTypes = listOf(ImageType.PRIMARY),
enableImageTypes = listOf(ImageType.PRIMARY, ImageType.LOGO),
imageTypeLimit = 1,
)
api.liveTvApi

View file

@ -20,7 +20,6 @@ import org.jellyfin.sdk.api.client.extensions.userLibraryApi
import org.jellyfin.sdk.api.client.extensions.userViewsApi
import org.jellyfin.sdk.model.api.BaseItemKind
import org.jellyfin.sdk.model.api.CollectionType
import org.jellyfin.sdk.model.api.ImageType
import org.jellyfin.sdk.model.api.UserDto
import org.jellyfin.sdk.model.api.request.GetLatestMediaRequest
import org.jellyfin.sdk.model.api.request.GetNextUpRequest
@ -61,12 +60,13 @@ class LatestNextUpService
remove(BaseItemKind.EPISODE)
}
},
enableImageTypes =
listOf(
ImageType.PRIMARY,
ImageType.THUMB,
ImageType.BACKDROP,
),
// enableImageTypes =
// listOf(
// ImageType.PRIMARY,
// ImageType.THUMB,
// ImageType.BACKDROP,
// ImageType.LOGO,
// ),
)
val items =
api.itemsApi
@ -98,6 +98,13 @@ class LatestNextUpService
enableUserData = true,
enableRewatching = enableRewatching,
nextUpDateCutoff = nextUpDateCutoff,
// enableImageTypes =
// listOf(
// ImageType.PRIMARY,
// ImageType.THUMB,
// ImageType.BACKDROP,
// ImageType.LOGO,
// ),
)
val nextUp =
api.tvShowsApi

View file

@ -114,7 +114,12 @@ class PlaylistCreator
req =
GetItemsRequest(
parentId = item.id,
enableImageTypes = listOf(ImageType.PRIMARY, ImageType.THUMB),
enableImageTypes =
listOf(
ImageType.PRIMARY,
ImageType.THUMB,
ImageType.LOGO,
),
includeItemTypes = includeItemTypes,
recursive = true,
excludeItemIds = listOf(item.id),

View file

@ -214,7 +214,7 @@ fun BannerCardWithTitle(
) {
val focused by interactionSource.collectIsFocusedAsState()
val spaceBetween by animateDpAsState(if (focused) 12.dp else 4.dp)
val spaceBelow by animateDpAsState(if (focused) 4.dp else 12.dp)
val spaceBelow by animateDpAsState(if (focused) 0.dp else 8.dp)
val focusedAfterDelay by rememberFocusedAfterDelay(interactionSource)
val aspectRationToUse = aspectRatio.coerceAtLeast(AspectRatios.MIN)
val width = cardHeight * aspectRationToUse

View file

@ -398,6 +398,7 @@ class CollectionFolderViewModel
ImageType.PRIMARY,
ImageType.THUMB,
ImageType.BACKDROP,
ImageType.LOGO,
),
includeItemTypes = includeItemTypes,
recursive = recursive,
@ -960,11 +961,12 @@ fun CollectionFolderGridContent(
AnimatedVisibility(viewOptions.showDetails) {
HomePageHeader(
item = focusedItem,
showLogo = preferences.appPreferences.interfacePreferences.showLogos,
modifier =
Modifier
.fillMaxWidth()
.height(200.dp)
.padding(top = 48.dp, bottom = 32.dp, start = 8.dp),
.padding(HeaderUtils.padding),
)
}
when (val state = loadingState) {

View file

@ -0,0 +1,24 @@
package com.github.damontecres.wholphin.ui.components
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
object HeaderUtils {
val topPadding = 48.dp
val bottomPadding = 32.dp
val startPadding = 8.dp
val padding = PaddingValues(top = topPadding, bottom = bottomPadding, start = startPadding)
val height = 180.dp
val logoHeight = 60.dp
val modifier =
Modifier
.padding(padding)
.height(height)
}

View file

@ -207,6 +207,7 @@ fun RecommendedContent(
},
showClock = preferences.appPreferences.interfacePreferences.showClock,
onUpdateBackdrop = viewModel::updateBackdrop,
showLogo = preferences.appPreferences.interfacePreferences.showLogos,
modifier = modifier,
)
}

View file

@ -0,0 +1,95 @@
package com.github.damontecres.wholphin.ui.components
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.heightIn
import androidx.compose.foundation.layout.widthIn
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.dp
import androidx.tv.material3.MaterialTheme
import androidx.tv.material3.Text
import coil3.compose.AsyncImage
import com.github.damontecres.wholphin.data.model.BaseItem
import com.github.damontecres.wholphin.ui.LocalImageUrlService
import org.jellyfin.sdk.model.api.BaseItemKind
import org.jellyfin.sdk.model.api.ImageType
@Composable
fun TitleOrLogo(
title: String?,
logoImageUrl: String?,
showLogo: Boolean,
modifier: Modifier = Modifier,
) {
var imageError by remember { mutableStateOf(false) }
Box(
modifier = modifier.heightIn(max = HeaderUtils.logoHeight),
) {
if (showLogo && logoImageUrl != null && !imageError) {
AsyncImage(
model = logoImageUrl,
contentDescription = title,
contentScale = ContentScale.Fit,
modifier =
Modifier
.height(HeaderUtils.logoHeight)
.widthIn(max = 320.dp),
)
} else {
Title(title, Modifier)
}
}
}
@Composable
private fun Title(
title: String?,
modifier: Modifier = Modifier,
) {
Text(
text = title ?: "",
color = MaterialTheme.colorScheme.onSurface,
style = MaterialTheme.typography.headlineMedium,
fontWeight = FontWeight.SemiBold,
maxLines = 2,
overflow = TextOverflow.Ellipsis,
modifier = modifier,
)
}
@Composable
fun TitleOrLogo(
item: BaseItem?,
showLogo: Boolean,
modifier: Modifier = Modifier,
) {
val logoImageUrl = rememberLogoUrl(item)
TitleOrLogo(
title = item?.title,
logoImageUrl = logoImageUrl,
showLogo = showLogo,
modifier = modifier,
)
}
@Composable
fun rememberLogoUrl(item: BaseItem?): String? {
val imageUrlService = LocalImageUrlService.current
return remember(item?.id) {
if (item?.type == BaseItemKind.EPISODE && item.data.seriesId != null && item.data.parentLogoImageTag != null) {
imageUrlService.getItemImageUrl(item.data.seriesId!!, ImageType.LOGO)
} else if (ImageType.LOGO in item?.data?.imageTags.orEmpty()) {
imageUrlService.getItemImageUrl(item, ImageType.LOGO)
} else {
null
}
}
}

View file

@ -11,7 +11,6 @@ import androidx.compose.foundation.focusable
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxHeight
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
@ -50,6 +49,7 @@ 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.ErrorMessage
import com.github.damontecres.wholphin.ui.components.HeaderUtils
import com.github.damontecres.wholphin.ui.components.LoadingPage
import com.github.damontecres.wholphin.ui.components.Optional
import com.github.damontecres.wholphin.ui.data.AddPlaylistViewModel
@ -379,11 +379,13 @@ fun CollectionDetailsContent(
if (state.viewOptions.cardViewOptions.showDetails) {
HomePageHeader(
item = focusedItem,
showLogo = preferences.appPreferences.interfacePreferences.showLogos,
modifier =
Modifier
.padding(top = 48.dp, start = 8.dp)
.fillMaxHeight(.33f)
.fillMaxWidth(),
.padding(
top = HeaderUtils.topPadding,
bottom = 8.dp,
).height(HeaderUtils.height),
)
}
}
@ -412,15 +414,16 @@ fun CollectionDetailsContent(
) {
CollectionDetailsHeader(
collection = state.collection!!,
showLogo = preferences.appPreferences.interfacePreferences.showLogos,
logoImageUrl = state.logoImageUrl,
overviewOnClick = overviewOnClick,
bringIntoViewRequester = bringIntoViewRequester,
modifier =
Modifier
.padding(top = 48.dp, start = 8.dp)
// TODO
.fillMaxHeight(.36f)
.fillMaxWidth(),
.padding(
top = HeaderUtils.topPadding,
bottom = HeaderUtils.bottomPadding,
).height(HeaderUtils.height),
)
CollectionButtons(
state = state,

View file

@ -3,28 +3,24 @@ package com.github.damontecres.wholphin.ui.detail.collection
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.relocation.BringIntoViewRequester
import androidx.compose.runtime.Composable
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.focus.onFocusChanged
import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.text.font.FontStyle
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp
import androidx.tv.material3.MaterialTheme
import androidx.tv.material3.Text
import coil3.compose.AsyncImage
import com.github.damontecres.wholphin.data.model.BaseItem
import com.github.damontecres.wholphin.ui.components.GenreText
import com.github.damontecres.wholphin.ui.components.HeaderUtils
import com.github.damontecres.wholphin.ui.components.OverviewText
import com.github.damontecres.wholphin.ui.components.QuickDetails
import com.github.damontecres.wholphin.ui.components.TitleOrLogo
import com.github.damontecres.wholphin.ui.letNotEmpty
import com.github.damontecres.wholphin.util.ExceptionHandler
import kotlinx.coroutines.launch
@ -32,6 +28,7 @@ import kotlinx.coroutines.launch
@Composable
fun CollectionDetailsHeader(
collection: BaseItem,
showLogo: Boolean,
logoImageUrl: String?,
overviewOnClick: () -> Unit,
bringIntoViewRequester: BringIntoViewRequester,
@ -44,28 +41,15 @@ fun CollectionDetailsHeader(
verticalArrangement = Arrangement.spacedBy(4.dp),
modifier = modifier,
) {
if (logoImageUrl != null) {
AsyncImage(
model = logoImageUrl,
contentDescription = collection.name,
modifier = Modifier.height(80.dp),
alignment = Alignment.TopStart,
contentScale = ContentScale.Fit,
)
} else {
// Title
Text(
text = collection.name ?: "",
color = MaterialTheme.colorScheme.onBackground,
style = MaterialTheme.typography.headlineMedium.copy(fontWeight = FontWeight.SemiBold),
maxLines = 1,
overflow = TextOverflow.Ellipsis,
TitleOrLogo(
title = collection.name,
showLogo = showLogo,
logoImageUrl = logoImageUrl,
modifier =
Modifier
.fillMaxWidth(.75f)
.padding(start = 8.dp),
.padding(start = HeaderUtils.startPadding),
)
}
Column(
verticalArrangement = Arrangement.spacedBy(4.dp),
@ -74,18 +58,18 @@ fun CollectionDetailsHeader(
QuickDetails(
collection.ui.quickDetails,
collection.timeRemainingOrRuntime,
Modifier.padding(start = 8.dp),
Modifier.padding(start = HeaderUtils.startPadding),
)
dto.genres?.letNotEmpty {
GenreText(it, Modifier.padding(start = 8.dp))
GenreText(it, Modifier.padding(start = HeaderUtils.startPadding))
}
dto.taglines?.firstOrNull()?.let { tagline ->
Text(
text = tagline,
style = MaterialTheme.typography.bodyLarge,
fontStyle = FontStyle.Italic,
modifier = Modifier.padding(start = 8.dp),
modifier = Modifier.padding(start = HeaderUtils.startPadding),
)
}

View file

@ -83,6 +83,7 @@ fun CollectionRows(
onUpdateBackdrop = {},
headerComposable = {},
takeFocus = false,
showLogo = preferences.appPreferences.interfacePreferences.showLogos,
modifier = Modifier,
)
}

View file

@ -62,6 +62,7 @@ import kotlinx.coroutines.withContext
import org.jellyfin.sdk.api.client.ApiClient
import org.jellyfin.sdk.api.client.extensions.userLibraryApi
import org.jellyfin.sdk.model.api.BaseItemKind
import org.jellyfin.sdk.model.api.ImageType
import org.jellyfin.sdk.model.api.ItemSortBy
import org.jellyfin.sdk.model.api.SortOrder
import org.jellyfin.sdk.model.api.request.GetItemsRequest
@ -139,13 +140,12 @@ class CollectionViewModel
.content
.let { BaseItem(it, false) }
backdropService.submit(collection)
val logoImageUrl = null
// TODO add logo back
// if (ImageType.LOGO in collection.data.imageTags.orEmpty()) {
// imageUrlService.getItemImageUrl(collection, ImageType.LOGO)
// } else {
// null
// }
val logoImageUrl =
if (ImageType.LOGO in collection.data.imageTags.orEmpty()) {
imageUrlService.getItemImageUrl(collection, ImageType.LOGO)
} else {
null
}
_state.update {
it.copy(
collection = collection,

View file

@ -36,6 +36,7 @@ import com.github.damontecres.wholphin.ui.components.DialogParams
import com.github.damontecres.wholphin.ui.components.DialogPopup
import com.github.damontecres.wholphin.ui.components.ErrorMessage
import com.github.damontecres.wholphin.ui.components.ExpandablePlayButtons
import com.github.damontecres.wholphin.ui.components.HeaderUtils
import com.github.damontecres.wholphin.ui.components.LoadingPage
import com.github.damontecres.wholphin.ui.components.Optional
import com.github.damontecres.wholphin.ui.components.chooseStream
@ -332,7 +333,7 @@ fun EpisodeDetailsContent(
Box(modifier = modifier) {
LazyColumn(
verticalArrangement = Arrangement.spacedBy(16.dp),
contentPadding = PaddingValues(vertical = 8.dp),
contentPadding = PaddingValues(bottom = 8.dp),
modifier = Modifier.fillMaxSize(),
) {
item {
@ -352,7 +353,7 @@ fun EpisodeDetailsContent(
modifier =
Modifier
.fillMaxWidth()
.padding(top = 32.dp, bottom = 16.dp),
.padding(top = HeaderUtils.topPadding, bottom = 16.dp),
)
ExpandablePlayButtons(
resumePosition = resumePosition,

View file

@ -51,6 +51,7 @@ import com.github.damontecres.wholphin.ui.components.DialogParams
import com.github.damontecres.wholphin.ui.components.DialogPopup
import com.github.damontecres.wholphin.ui.components.ErrorMessage
import com.github.damontecres.wholphin.ui.components.ExpandablePlayButtons
import com.github.damontecres.wholphin.ui.components.HeaderUtils
import com.github.damontecres.wholphin.ui.components.LoadingPage
import com.github.damontecres.wholphin.ui.components.Optional
import com.github.damontecres.wholphin.ui.components.chooseStream
@ -439,7 +440,7 @@ fun MovieDetailsContent(
Box(modifier = modifier) {
LazyColumn(
verticalArrangement = Arrangement.spacedBy(16.dp),
contentPadding = PaddingValues(vertical = 8.dp),
contentPadding = PaddingValues(bottom = 8.dp),
modifier = Modifier.fillMaxSize(),
) {
item {
@ -459,7 +460,7 @@ fun MovieDetailsContent(
modifier =
Modifier
.fillMaxWidth()
.padding(top = 40.dp, bottom = 16.dp),
.padding(top = HeaderUtils.topPadding, bottom = 16.dp),
)
ExpandablePlayButtons(
resumePosition = resumePosition,

View file

@ -13,8 +13,6 @@ import androidx.compose.ui.focus.onFocusChanged
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.font.FontStyle
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp
import androidx.tv.material3.MaterialTheme
@ -24,8 +22,10 @@ import com.github.damontecres.wholphin.data.ChosenStreams
import com.github.damontecres.wholphin.data.model.BaseItem
import com.github.damontecres.wholphin.preferences.UserPreferences
import com.github.damontecres.wholphin.ui.components.GenreText
import com.github.damontecres.wholphin.ui.components.HeaderUtils
import com.github.damontecres.wholphin.ui.components.OverviewText
import com.github.damontecres.wholphin.ui.components.QuickDetails
import com.github.damontecres.wholphin.ui.components.TitleOrLogo
import com.github.damontecres.wholphin.ui.components.VideoStreamDetails
import com.github.damontecres.wholphin.ui.isNotNullOrBlank
import com.github.damontecres.wholphin.ui.letNotEmpty
@ -50,16 +50,13 @@ fun MovieDetailsHeader(
modifier = modifier,
) {
// Title
Text(
text = movie.name ?: "",
color = MaterialTheme.colorScheme.onBackground,
style = MaterialTheme.typography.headlineMedium.copy(fontWeight = FontWeight.SemiBold),
maxLines = 1,
overflow = TextOverflow.Ellipsis,
TitleOrLogo(
item = movie,
showLogo = preferences.appPreferences.interfacePreferences.showLogos,
modifier =
Modifier
.fillMaxWidth(.75f)
.padding(start = 8.dp),
.padding(start = HeaderUtils.startPadding),
)
Column(
@ -69,24 +66,29 @@ fun MovieDetailsHeader(
QuickDetails(
movie.ui.quickDetails,
movie.timeRemainingOrRuntime,
Modifier.padding(start = 8.dp),
Modifier.padding(start = HeaderUtils.startPadding),
)
dto.genres?.letNotEmpty {
GenreText(it, Modifier.padding(start = 8.dp))
GenreText(it, Modifier.padding(start = HeaderUtils.startPadding))
}
VideoStreamDetails(
chosenStreams = chosenStreams,
numberOfVersions = movie.data.mediaSourceCount ?: 0,
modifier = Modifier.padding(start = 8.dp, top = 4.dp, bottom = 16.dp),
modifier =
Modifier.padding(
start = HeaderUtils.startPadding,
top = 4.dp,
bottom = 16.dp,
),
)
dto.taglines?.firstOrNull()?.let { tagline ->
Text(
text = tagline,
style = MaterialTheme.typography.bodyLarge,
fontStyle = FontStyle.Italic,
modifier = Modifier.padding(start = 8.dp),
modifier = Modifier.padding(start = HeaderUtils.startPadding),
)
}
@ -122,7 +124,7 @@ fun MovieDetailsHeader(
text = stringResource(R.string.directed_by, it),
style = MaterialTheme.typography.bodyMedium,
color = MaterialTheme.colorScheme.onSurface,
modifier = Modifier.padding(start = 8.dp),
modifier = Modifier.padding(start = HeaderUtils.startPadding),
)
}
}

View file

@ -13,6 +13,7 @@ import com.github.damontecres.wholphin.data.ChosenStreams
import com.github.damontecres.wholphin.data.model.BaseItem
import com.github.damontecres.wholphin.preferences.UserPreferences
import com.github.damontecres.wholphin.ui.components.EpisodeName
import com.github.damontecres.wholphin.ui.components.HeaderUtils
import com.github.damontecres.wholphin.ui.components.OverviewText
import com.github.damontecres.wholphin.ui.components.QuickDetails
import com.github.damontecres.wholphin.ui.components.VideoStreamDetails
@ -32,17 +33,21 @@ fun FocusedEpisodeHeader(
verticalArrangement = Arrangement.spacedBy(8.dp),
modifier = modifier,
) {
EpisodeName(dto, modifier = Modifier.padding(start = 8.dp))
EpisodeName(dto, modifier = Modifier.padding(start = HeaderUtils.startPadding))
ep?.ui?.quickDetails?.let {
QuickDetails(it, ep.timeRemainingOrRuntime, Modifier.padding(start = 8.dp))
QuickDetails(
it,
ep.timeRemainingOrRuntime,
Modifier.padding(start = HeaderUtils.startPadding),
)
}
if (dto != null) {
VideoStreamDetails(
chosenStreams = chosenStreams,
numberOfVersions = dto.mediaSourceCount ?: 0,
modifier = Modifier.padding(start = 8.dp),
modifier = Modifier.padding(start = HeaderUtils.startPadding),
)
}
OverviewText(

View file

@ -37,14 +37,10 @@ import androidx.compose.ui.graphics.Color
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.platform.LocalFocusManager
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp
import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel
import androidx.lifecycle.compose.LifecycleResumeEffect
import androidx.tv.material3.MaterialTheme
import androidx.tv.material3.Text
import com.github.damontecres.wholphin.R
import com.github.damontecres.wholphin.data.ExtrasItem
import com.github.damontecres.wholphin.data.model.BaseItem
@ -69,10 +65,12 @@ import com.github.damontecres.wholphin.ui.components.ErrorMessage
import com.github.damontecres.wholphin.ui.components.ExpandableFaButton
import com.github.damontecres.wholphin.ui.components.ExpandablePlayButton
import com.github.damontecres.wholphin.ui.components.GenreText
import com.github.damontecres.wholphin.ui.components.HeaderUtils
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.QuickDetails
import com.github.damontecres.wholphin.ui.components.TitleOrLogo
import com.github.damontecres.wholphin.ui.components.TrailerButton
import com.github.damontecres.wholphin.ui.data.AddPlaylistViewModel
import com.github.damontecres.wholphin.ui.data.ItemDetailsDialog
@ -386,7 +384,6 @@ fun SeriesDetailsContent(
Column(
modifier =
Modifier
.padding(vertical = 16.dp)
.fillMaxSize(),
) {
LazyColumn(
@ -397,18 +394,19 @@ fun SeriesDetailsContent(
item {
SeriesDetailsHeader(
series = series,
showLogo = preferences.appPreferences.interfacePreferences.showLogos,
overviewOnClick = overviewOnClick,
modifier =
Modifier
.fillMaxWidth()
.bringIntoViewRequester(bringIntoViewRequester)
.padding(top = 32.dp, bottom = 16.dp),
.padding(top = HeaderUtils.topPadding, bottom = 16.dp),
)
Row(
horizontalArrangement = Arrangement.spacedBy(16.dp),
modifier =
Modifier
.padding(start = 8.dp)
.padding(start = HeaderUtils.startPadding)
.focusRequester(focusRequesters[HEADER_ROW])
.focusRestorer(playFocusRequester)
.focusGroup()
@ -684,6 +682,7 @@ fun SeriesDetailsContent(
@Composable
fun SeriesDetailsHeader(
series: BaseItem,
showLogo: Boolean,
overviewOnClick: () -> Unit,
modifier: Modifier = Modifier,
) {
@ -693,24 +692,25 @@ fun SeriesDetailsHeader(
verticalArrangement = Arrangement.spacedBy(4.dp),
modifier = modifier,
) {
Text(
text = series.name ?: stringResource(R.string.unknown),
color = MaterialTheme.colorScheme.onBackground,
style = MaterialTheme.typography.headlineMedium.copy(fontWeight = FontWeight.SemiBold),
maxLines = 1,
overflow = TextOverflow.Ellipsis,
TitleOrLogo(
item = series,
showLogo = showLogo,
modifier =
Modifier
.fillMaxWidth(.75f)
.padding(start = 8.dp),
.padding(start = HeaderUtils.startPadding),
)
Column(
verticalArrangement = Arrangement.spacedBy(4.dp),
modifier = Modifier.fillMaxWidth(.60f),
) {
QuickDetails(series.ui.quickDetails, null, Modifier.padding(start = 8.dp))
QuickDetails(
series.ui.quickDetails,
null,
Modifier.padding(start = HeaderUtils.startPadding),
)
dto.genres?.letNotEmpty {
GenreText(it, Modifier.padding(start = 8.dp, bottom = 8.dp))
GenreText(it, Modifier.padding(start = HeaderUtils.startPadding, bottom = 8.dp))
}
dto.overview?.let { overview ->
OverviewText(

View file

@ -52,9 +52,10 @@ import com.github.damontecres.wholphin.ui.AspectRatios
import com.github.damontecres.wholphin.ui.cards.BannerCard
import com.github.damontecres.wholphin.ui.cards.PersonRow
import com.github.damontecres.wholphin.ui.components.ErrorMessage
import com.github.damontecres.wholphin.ui.components.HeaderUtils
import com.github.damontecres.wholphin.ui.components.LoadingPage
import com.github.damontecres.wholphin.ui.components.SeriesName
import com.github.damontecres.wholphin.ui.components.TabRow
import com.github.damontecres.wholphin.ui.components.TitleOrLogo
import com.github.damontecres.wholphin.ui.ifElse
import com.github.damontecres.wholphin.ui.logTab
import com.github.damontecres.wholphin.ui.playback.isPlayKeyUp
@ -143,11 +144,13 @@ fun SeriesOverviewContent(
.bringIntoViewRequester(bringIntoViewRequester),
) {
val paddingValues =
remember(preferences.appPreferences.interfacePreferences.showClock) {
if (preferences.appPreferences.interfacePreferences.showClock) {
PaddingValues(start = 0.dp, end = 184.dp)
} else {
PaddingValues(start = 0.dp, end = 16.dp)
}
}
TabRow(
selectedTabIndex = selectedTabIndex,
tabs = tabs,
@ -164,7 +167,11 @@ fun SeriesOverviewContent(
.padding(bottom = 4.dp)
.fillMaxWidth(),
)
SeriesName(series.name, Modifier.padding(start = 8.dp))
TitleOrLogo(
item = series,
showLogo = preferences.appPreferences.interfacePreferences.showLogos,
modifier = Modifier.padding(start = HeaderUtils.startPadding),
)
FocusedEpisodeHeader(
preferences = preferences,
ep = focusedEpisode,

View file

@ -250,6 +250,8 @@ fun SeerrDiscoverPage(
overviewTwoLines = true,
quickDetails = details,
timeRemaining = null,
showLogo = preferences.appPreferences.interfacePreferences.showLogos,
logoImageUrl = null, // TODO
modifier =
Modifier
.padding(top = 24.dp, bottom = 16.dp, start = 32.dp)

View file

@ -8,7 +8,6 @@ import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxHeight
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
@ -39,7 +38,6 @@ import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.AnnotatedString
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.dp
import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel
@ -61,9 +59,12 @@ import com.github.damontecres.wholphin.ui.components.DialogPopup
import com.github.damontecres.wholphin.ui.components.EpisodeName
import com.github.damontecres.wholphin.ui.components.ErrorMessage
import com.github.damontecres.wholphin.ui.components.FocusableItemRow
import com.github.damontecres.wholphin.ui.components.HeaderUtils
import com.github.damontecres.wholphin.ui.components.LoadingPage
import com.github.damontecres.wholphin.ui.components.QuickDetails
import com.github.damontecres.wholphin.ui.components.RowColumnItem
import com.github.damontecres.wholphin.ui.components.TitleOrLogo
import com.github.damontecres.wholphin.ui.components.rememberLogoUrl
import com.github.damontecres.wholphin.ui.data.AddPlaylistViewModel
import com.github.damontecres.wholphin.ui.data.RowColumn
import com.github.damontecres.wholphin.ui.detail.MoreDialogActions
@ -172,6 +173,7 @@ fun HomePage(
loadingState = refreshing,
showClock = preferences.appPreferences.interfacePreferences.showClock,
onUpdateBackdrop = viewModel::updateBackdrop,
showLogo = preferences.appPreferences.interfacePreferences.showLogos,
modifier = modifier,
)
dialog?.let { params ->
@ -222,6 +224,7 @@ fun HomePageContent(
onClickPlay: (RowColumn, BaseItem) -> Unit,
showClock: Boolean,
onUpdateBackdrop: (BaseItem) -> Unit,
showLogo: Boolean,
modifier: Modifier = Modifier,
loadingState: LoadingState? = null,
listState: LazyListState = rememberLazyListState(),
@ -230,10 +233,8 @@ fun HomePageContent(
headerComposable: @Composable (focusedItem: BaseItem?) -> Unit = { focusedItem ->
HomePageHeader(
item = focusedItem,
modifier =
Modifier
.padding(top = 48.dp, bottom = 32.dp, start = 8.dp)
.fillMaxHeight(.33f),
showLogo = showLogo,
modifier = HeaderUtils.modifier,
)
},
) {
@ -410,6 +411,7 @@ fun HomePageContent(
@Composable
fun HomePageHeader(
item: BaseItem?,
showLogo: Boolean,
modifier: Modifier = Modifier,
) {
val isEpisode = item?.type == BaseItemKind.EPISODE
@ -421,6 +423,8 @@ fun HomePageHeader(
overviewTwoLines = isEpisode,
quickDetails = item?.ui?.quickDetails ?: AnnotatedString(""),
timeRemaining = item?.timeRemainingOrRuntime,
showLogo = showLogo,
logoImageUrl = rememberLogoUrl(item),
modifier = modifier,
)
}
@ -433,31 +437,28 @@ fun HomePageHeader(
overviewTwoLines: Boolean,
quickDetails: AnnotatedString?,
timeRemaining: Duration?,
showLogo: Boolean,
logoImageUrl: String?,
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,
TitleOrLogo(
title = title,
logoImageUrl = logoImageUrl,
showLogo = showLogo,
modifier = Modifier.fillMaxWidth(.75f),
)
}
Column(
verticalArrangement = Arrangement.spacedBy(8.dp),
modifier =
Modifier
.fillMaxWidth(.6f)
.fillMaxHeight(),
.fillMaxWidth(.6f),
) {
subtitle?.let {
EpisodeName(it)
if (subtitle != null) {
EpisodeName(subtitle)
}
QuickDetails(quickDetails ?: AnnotatedString(""), timeRemaining)
val overviewModifier =

View file

@ -32,6 +32,7 @@ import com.github.damontecres.wholphin.R
import com.github.damontecres.wholphin.data.model.HomeRowConfig
import com.github.damontecres.wholphin.data.model.HomeRowViewOptions
import com.github.damontecres.wholphin.preferences.AppPreferences
import com.github.damontecres.wholphin.preferences.UserPreferences
import com.github.damontecres.wholphin.ui.components.ConfirmDialog
import com.github.damontecres.wholphin.ui.data.RowColumn
import com.github.damontecres.wholphin.ui.detail.search.SearchForDialog
@ -51,6 +52,7 @@ val settingsWidth = 360.dp
@Composable
fun HomeSettingsPage(
preferences: UserPreferences,
modifier: Modifier,
viewModel: HomeSettingsViewModel = hiltViewModel(),
) {
@ -310,6 +312,7 @@ fun HomeSettingsPage(
listState = listState,
takeFocus = false,
showEmptyRows = true,
showLogo = preferences.appPreferences.interfacePreferences.showLogos,
modifier =
Modifier
.fillMaxHeight()

View file

@ -69,7 +69,7 @@ fun DestinationContent(
}
is Destination.HomeSettings -> {
HomeSettingsPage(modifier)
HomeSettingsPage(preferences, modifier)
}
is Destination.PlaybackList,

View file

@ -167,6 +167,7 @@ message InterfacePreferences {
SubtitlePreferences hdr_subtitles_preferences = 10;
ScreensaverPreferences screensaver_preference = 11;
bool enable_media_management = 12;
bool show_logos = 13;
}
message AdvancedPreferences {

View file

@ -747,5 +747,6 @@
<string name="continue_string">Continue</string>
<string name="select_all">Select all</string>
<string name="separate_types">Separate types</string>
<string name="prefer_logos">Prefer showing logos for titles</string>
</resources>