mirror of
https://github.com/JustinZeus/Wholphin.git
synced 2026-07-08 23:51:21 +02:00
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  ### Series overview  ### Movie Page  ### Square-ish logo  </p> </details> ## AI or LLM usage None
This commit is contained in:
parent
253b203c02
commit
2485d2c644
27 changed files with 290 additions and 122 deletions
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -95,6 +95,7 @@ class AppPreferencesSerializer
|
|||
AppPreference.NavDrawerSwitchOnFocus.defaultValue
|
||||
showClock = AppPreference.ShowClock.defaultValue
|
||||
backdropStyle = AppPreference.BackdropStylePref.defaultValue
|
||||
showLogos = AppPreference.ShowLogos.defaultValue
|
||||
|
||||
subtitlesPreferences =
|
||||
SubtitlePreferences
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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),
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
|
|
@ -207,6 +207,7 @@ fun RecommendedContent(
|
|||
},
|
||||
showClock = preferences.appPreferences.interfacePreferences.showClock,
|
||||
onUpdateBackdrop = viewModel::updateBackdrop,
|
||||
showLogo = preferences.appPreferences.interfacePreferences.showLogos,
|
||||
modifier = modifier,
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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),
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -83,6 +83,7 @@ fun CollectionRows(
|
|||
onUpdateBackdrop = {},
|
||||
headerComposable = {},
|
||||
takeFocus = false,
|
||||
showLogo = preferences.appPreferences.interfacePreferences.showLogos,
|
||||
modifier = Modifier,
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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(
|
||||
|
|
|
|||
|
|
@ -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(
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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 =
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
|
|
|||
|
|
@ -69,7 +69,7 @@ fun DestinationContent(
|
|||
}
|
||||
|
||||
is Destination.HomeSettings -> {
|
||||
HomeSettingsPage(modifier)
|
||||
HomeSettingsPage(preferences, modifier)
|
||||
}
|
||||
|
||||
is Destination.PlaybackList,
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue