Add Display size customization page

Consolidates UI sizing into three percentage sliders surfaced through a
new Settings -> Interface -> Display size page: UI scale (50-150%),
Card size (50-150%), Spacing (25-175%). Each persists immediately via
DataStore; the rest of the app reflects them through
LocalInterfaceCustomization and a root-level LocalDensity override in
MainContent.

The page mirrors HomeSettingsPage's two-column shell: a fixed-width
settings panel on the left, HomePageContent rendering live preview rows
on the right. The page snapshots its own density on entry so the slider
rail stays fixed while UI scale changes the rest of the app.

Includes a one-shot AppUpgradeHandler migration block for installs at or
before 0.6.4-31-g0 that resets the three new percent fields to their
defaults.
This commit is contained in:
Justin Visser 2026-05-26 10:48:16 +02:00
parent 81e5f31401
commit 8fb301c2bb
12 changed files with 551 additions and 11 deletions

View file

@ -18,6 +18,8 @@ import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.RectangleShape import androidx.compose.ui.graphics.RectangleShape
import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.unit.Density
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.lifecycle.Lifecycle import androidx.lifecycle.Lifecycle
import androidx.lifecycle.compose.LifecycleEventEffect import androidx.lifecycle.compose.LifecycleEventEffect
@ -60,7 +62,19 @@ fun MainContent(
// setupNavigationManager.backStack = backStack // setupNavigationManager.backStack = backStack
val interfaceCustomization = val interfaceCustomization =
remember(appPreferences) { InterfaceCustomization(appPreferences) } remember(appPreferences) { InterfaceCustomization(appPreferences) }
CompositionLocalProvider(LocalInterfaceCustomization provides interfaceCustomization) { val baseDensity = LocalDensity.current
val scaledDensity =
remember(baseDensity, interfaceCustomization.uiScalePercent) {
val factor = interfaceCustomization.uiScalePercent / 100f
Density(
density = baseDensity.density * factor,
fontScale = baseDensity.fontScale * factor,
)
}
CompositionLocalProvider(
LocalInterfaceCustomization provides interfaceCustomization,
LocalDensity provides scaledDensity,
) {
NavDisplay( NavDisplay(
backStack = backStack, backStack = backStack,
onBack = { backStack.removeLastOrNull() }, onBack = { backStack.removeLastOrNull() },

View file

@ -504,6 +504,55 @@ sealed interface AppPreference<Pref, T> {
summaryOff = R.string.disabled, summaryOff = R.string.disabled,
) )
val UiScale =
AppSliderPreference<AppPreferences>(
title = R.string.ui_scale,
defaultValue = 100,
min = 50,
max = 150,
interval = 5,
getter = { it.interfacePreferences.uiScalePercent.toLong() },
setter = { prefs, value ->
prefs.updateInterfacePreferences { uiScalePercent = value.toInt() }
},
summarizer = { it?.let { "$it%" } },
)
val CardSize =
AppSliderPreference<AppPreferences>(
title = R.string.card_size,
defaultValue = 100,
min = 50,
max = 150,
interval = 5,
getter = { it.interfacePreferences.cardSizePercent.toLong() },
setter = { prefs, value ->
prefs.updateInterfacePreferences { cardSizePercent = value.toInt() }
},
summarizer = { it?.let { "$it%" } },
)
val Spacing =
AppSliderPreference<AppPreferences>(
title = R.string.spacing,
defaultValue = 100,
min = 25,
max = 175,
interval = 10,
getter = { it.interfacePreferences.spacingPercent.toLong() },
setter = { prefs, value ->
prefs.updateInterfacePreferences { spacingPercent = value.toInt() }
},
summarizer = { it?.let { "$it%" } },
)
val DisplaySizePref =
AppDestinationPreference<AppPreferences>(
title = R.string.display_size,
summary = R.string.display_size_summary,
destination = Destination.DisplaySize,
)
val DisplayTogglesPref = val DisplayTogglesPref =
AppMultiChoicePreference<AppPreferences, DisplayToggle>( AppMultiChoicePreference<AppPreferences, DisplayToggle>(
title = R.string.display_toggles_title, title = R.string.display_toggles_title,
@ -1074,6 +1123,7 @@ val basicPreferences =
AppPreference.SignInAuto, AppPreference.SignInAuto,
AppPreference.RememberSelectedTab, AppPreference.RememberSelectedTab,
AppPreference.PlayThemeMusic, AppPreference.PlayThemeMusic,
AppPreference.DisplaySizePref,
AppPreference.SubtitleStyle, AppPreference.SubtitleStyle,
AppPreference.ThemeColors, AppPreference.ThemeColors,
AppPreference.ScreensaverSettings, AppPreference.ScreensaverSettings,

View file

@ -100,6 +100,9 @@ class AppPreferencesSerializer
showLogos = AppPreference.ShowLogos.defaultValue showLogos = AppPreference.ShowLogos.defaultValue
clearDisplayToggles() clearDisplayToggles()
addAllDisplayToggles(AppPreference.DisplayTogglesPref.defaultValue) addAllDisplayToggles(AppPreference.DisplayTogglesPref.defaultValue)
uiScalePercent = AppPreference.UiScale.defaultValue.toInt()
cardSizePercent = AppPreference.CardSize.defaultValue.toInt()
spacingPercent = AppPreference.Spacing.defaultValue.toInt()
searchPreferences = searchPreferences =
SearchPreferences SearchPreferences

View file

@ -350,5 +350,15 @@ class AppUpgradeHandler
} }
} }
} }
if (previous.isEqualOrBefore(Version.fromString("0.6.4-31-g0"))) {
appPreferences.updateData {
it.updateInterfacePreferences {
uiScalePercent = AppPreference.UiScale.defaultValue.toInt()
cardSizePercent = AppPreference.CardSize.defaultValue.toInt()
spacingPercent = AppPreference.Spacing.defaultValue.toInt()
}
}
}
} }
} }

View file

@ -43,6 +43,8 @@ object AppColors {
const val DEFAULT_PAGE_SIZE = 100 const val DEFAULT_PAGE_SIZE = 100
const val BASE_SPACING_DP = 16
/** /**
* The default [ItemFields] to fetch for most queries * The default [ItemFields] to fetch for most queries
*/ */

View file

@ -52,6 +52,9 @@ sealed class Destination(
val hdr: Boolean, val hdr: Boolean,
) : Destination(true) ) : Destination(true)
@Serializable
data object DisplaySize : Destination(true)
@Serializable @Serializable
data object Search : Destination() data object Search : Destination()

View file

@ -45,6 +45,7 @@ import com.github.damontecres.wholphin.ui.main.settings.HomeSettingsPage
import com.github.damontecres.wholphin.ui.playback.PlayExternalPage import com.github.damontecres.wholphin.ui.playback.PlayExternalPage
import com.github.damontecres.wholphin.ui.playback.PlaybackPage import com.github.damontecres.wholphin.ui.playback.PlaybackPage
import com.github.damontecres.wholphin.ui.preferences.PreferencesPage import com.github.damontecres.wholphin.ui.preferences.PreferencesPage
import com.github.damontecres.wholphin.ui.preferences.displaysize.DisplaySizePage
import com.github.damontecres.wholphin.ui.preferences.subtitle.SubtitleStylePage import com.github.damontecres.wholphin.ui.preferences.subtitle.SubtitleStylePage
import com.github.damontecres.wholphin.ui.setup.InstallUpdatePage import com.github.damontecres.wholphin.ui.setup.InstallUpdatePage
import com.github.damontecres.wholphin.ui.slideshow.SlideshowPage import com.github.damontecres.wholphin.ui.slideshow.SlideshowPage
@ -127,6 +128,13 @@ fun DestinationContent(
) )
} }
is Destination.DisplaySize -> {
DisplaySizePage(
preferences.appPreferences,
modifier,
)
}
is Destination.SeriesOverview -> { is Destination.SeriesOverview -> {
SeriesOverview( SeriesOverview(
preferences = preferences, preferences = preferences,

View file

@ -0,0 +1,417 @@
package com.github.damontecres.wholphin.ui.preferences.displaysize
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxHeight
import androidx.compose.foundation.layout.fillMaxSize
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.material.icons.Icons
import androidx.compose.material.icons.filled.Add
import androidx.compose.material.icons.filled.ArrowDropDown
import androidx.compose.material.icons.filled.Favorite
import androidx.compose.material.icons.filled.Home
import androidx.compose.material.icons.filled.Person
import androidx.compose.material.icons.filled.PlayArrow
import androidx.compose.material.icons.filled.Search
import androidx.compose.material.icons.filled.Settings
import androidx.compose.material.icons.filled.Star
import androidx.compose.material3.HorizontalDivider
import androidx.compose.runtime.Composable
import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.focus.FocusRequester
import androidx.compose.ui.focus.focusRequester
import androidx.compose.ui.focus.focusRestorer
import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.Density
import androidx.compose.ui.unit.dp
import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import androidx.tv.material3.Icon
import androidx.tv.material3.MaterialTheme
import androidx.tv.material3.Text
import com.github.damontecres.wholphin.R
import com.github.damontecres.wholphin.data.model.BaseItem
import com.github.damontecres.wholphin.preferences.AppPreference
import com.github.damontecres.wholphin.preferences.AppPreferences
import com.github.damontecres.wholphin.preferences.AppSliderPreference
import com.github.damontecres.wholphin.preferences.updateInterfacePreferences
import com.github.damontecres.wholphin.services.BackdropService
import com.github.damontecres.wholphin.ui.FontAwesome
import com.github.damontecres.wholphin.ui.data.RowColumn
import com.github.damontecres.wholphin.ui.launchIO
import com.github.damontecres.wholphin.ui.main.HomePageContent
import com.github.damontecres.wholphin.ui.main.settings.HomeSettingsListItem
import com.github.damontecres.wholphin.ui.main.settings.TitleText
import com.github.damontecres.wholphin.ui.main.settings.settingsWidth
import com.github.damontecres.wholphin.ui.preferences.PreferencesViewModel
import com.github.damontecres.wholphin.ui.preferences.SliderPreference
import com.github.damontecres.wholphin.ui.tryRequestFocus
import com.github.damontecres.wholphin.ui.util.ResStringProvider
import com.github.damontecres.wholphin.ui.util.withinBoundsOrDefault
import com.github.damontecres.wholphin.util.HomeRowLoadingState
import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.launch
import org.jellyfin.sdk.api.client.ApiClient
import org.jellyfin.sdk.api.client.extensions.itemsApi
import org.jellyfin.sdk.model.api.BaseItemKind
import org.jellyfin.sdk.model.api.ItemSortBy
import org.jellyfin.sdk.model.api.SortOrder
import org.jellyfin.sdk.model.api.request.GetItemsRequest
import timber.log.Timber
import javax.inject.Inject
private const val PREVIEW_POSTER_COUNT = 5
private const val PREVIEW_EPISODE_COUNT = 4
private val PREVIEW_NAV_RAIL_WIDTH_DP = 64.dp
private val PREVIEW_NAV_ITEM_HEIGHT_DP = 56.dp
private val PREVIEW_NAV_ICON_SIZE_DP = 24.dp
private val PreviewNavRailIcons: List<ImageVector> =
listOf(
Icons.Default.Person,
Icons.Default.Home,
Icons.Default.Favorite,
Icons.Default.Search,
Icons.Default.Add,
Icons.Default.Star,
Icons.Default.PlayArrow,
Icons.Default.ArrowDropDown,
Icons.Default.Settings,
)
@HiltViewModel
class DisplaySizeViewModel
@Inject
constructor(
private val api: ApiClient,
private val backdropService: BackdropService,
) : ViewModel() {
val posters = MutableStateFlow<List<BaseItem>>(emptyList())
val episodes = MutableStateFlow<List<BaseItem>>(emptyList())
fun updateBackdrop(item: BaseItem) {
viewModelScope.launchIO { backdropService.submit(item) }
}
init {
viewModelScope.launchIO {
fetch(BaseItemKind.MOVIE, PREVIEW_POSTER_COUNT, posters)
}
viewModelScope.launchIO {
fetch(BaseItemKind.EPISODE, PREVIEW_EPISODE_COUNT, episodes)
}
}
private suspend fun fetch(
kind: BaseItemKind,
limit: Int,
target: MutableStateFlow<List<BaseItem>>,
) {
try {
val request =
GetItemsRequest(
recursive = true,
includeItemTypes = listOf(kind),
sortBy = listOf(ItemSortBy.DATE_CREATED),
sortOrder = listOf(SortOrder.DESCENDING),
limit = limit,
)
val result = api.itemsApi.getItems(request).content
val items = (result.items ?: emptyList()).map { BaseItem.from(it, api, false) }
target.value = items
} catch (ex: Exception) {
Timber.w(ex, "DisplaySize preview: could not fetch %s sample", kind)
}
}
}
@Composable
fun DisplaySizePage(
initialPreferences: AppPreferences,
modifier: Modifier = Modifier,
viewModel: PreferencesViewModel = hiltViewModel(),
previewViewModel: DisplaySizeViewModel = hiltViewModel(),
) {
var preferences by remember { mutableStateOf(initialPreferences) }
LaunchedEffect(Unit) {
viewModel.preferenceDataStore.data.collect { preferences = it }
}
val scope = rememberCoroutineScope()
val iface = preferences.interfacePreferences
val uiScale = iface.uiScalePercent.withinBoundsOrDefault(AppPreference.UiScale)
val cardSize = iface.cardSizePercent.withinBoundsOrDefault(AppPreference.CardSize)
val spacing = iface.spacingPercent.withinBoundsOrDefault(AppPreference.Spacing)
val posters by previewViewModel.posters.collectAsState()
val episodes by previewViewModel.episodes.collectAsState()
// Snapshot the page's own density on first composition so the slider rail
// and labels stay fixed while the user drags UI scale.
val ambientDensity = LocalDensity.current
val pageDensity = remember { ambientDensity }
// Resolve back to system base so the preview density is independent of the
// snapshotted page density and reflects the draft uiScale faithfully.
val context = LocalContext.current
val systemDensity =
remember {
val dm = context.resources.displayMetrics
Density(
density = dm.density,
fontScale = context.resources.configuration.fontScale,
)
}
val previewDensity =
remember(systemDensity, uiScale) {
val factor = uiScale / 100f
Density(
density = systemDensity.density * factor,
fontScale = systemDensity.fontScale * factor,
)
}
CompositionLocalProvider(LocalDensity provides pageDensity) {
Row(
horizontalArrangement = Arrangement.spacedBy(8.dp),
modifier = modifier.fillMaxSize(),
) {
Box(
modifier =
Modifier
.width(settingsWidth)
.fillMaxHeight()
.background(color = MaterialTheme.colorScheme.surface)
.padding(8.dp),
) {
DisplaySizeSettingsColumn(
uiScale = uiScale,
cardSize = cardSize,
spacing = spacing,
onUiScaleChange = { value ->
scope.launch {
viewModel.preferenceDataStore.updateData {
it.updateInterfacePreferences { uiScalePercent = value }
}
}
},
onCardSizeChange = { value ->
scope.launch {
viewModel.preferenceDataStore.updateData {
it.updateInterfacePreferences { cardSizePercent = value }
}
}
},
onSpacingChange = { value ->
scope.launch {
viewModel.preferenceDataStore.updateData {
it.updateInterfacePreferences { spacingPercent = value }
}
}
},
onClickReset = {
scope.launch {
viewModel.preferenceDataStore.updateData {
it.updateInterfacePreferences {
uiScalePercent = AppPreference.UiScale.defaultValue.toInt()
cardSizePercent = AppPreference.CardSize.defaultValue.toInt()
spacingPercent = AppPreference.Spacing.defaultValue.toInt()
}
}
}
},
)
}
Box(
modifier =
Modifier
.fillMaxHeight()
.weight(1f),
) {
CompositionLocalProvider(LocalDensity provides previewDensity) {
DisplaySizePreview(
posters = posters,
episodes = episodes,
showLogos = iface.showLogos,
onUpdateBackdrop = previewViewModel::updateBackdrop,
)
}
}
}
}
}
@Composable
private fun DisplaySizeSettingsColumn(
uiScale: Int,
cardSize: Int,
spacing: Int,
onUiScaleChange: (Int) -> Unit,
onCardSizeChange: (Int) -> Unit,
onSpacingChange: (Int) -> Unit,
onClickReset: () -> Unit,
) {
val firstFocus = remember { FocusRequester() }
LaunchedEffect(Unit) { firstFocus.tryRequestFocus() }
Column(modifier = Modifier.fillMaxSize()) {
TitleText(stringResource(R.string.display_size))
LazyColumn(
verticalArrangement = Arrangement.spacedBy(8.dp),
contentPadding = PaddingValues(horizontal = 8.dp),
modifier =
Modifier
.fillMaxHeight()
.focusRestorer(firstFocus),
) {
item {
DisplaySizeSlider(
preference = AppPreference.UiScale,
value = uiScale,
onChange = onUiScaleChange,
modifier = Modifier.focusRequester(firstFocus),
)
}
item {
DisplaySizeSlider(
preference = AppPreference.CardSize,
value = cardSize,
onChange = onCardSizeChange,
)
}
item {
DisplaySizeSlider(
preference = AppPreference.Spacing,
value = spacing,
onChange = onSpacingChange,
)
}
item { HorizontalDivider() }
item {
HomeSettingsListItem(
selected = false,
headlineText = stringResource(R.string.reset_to_defaults),
leadingContent = {
Text(
text = stringResource(R.string.fa_arrows_rotate),
fontFamily = FontAwesome,
)
},
onClick = onClickReset,
)
}
}
}
}
@Composable
private fun DisplaySizeSlider(
preference: AppSliderPreference<AppPreferences>,
value: Int,
onChange: (Int) -> Unit,
modifier: Modifier = Modifier,
) {
val context = LocalContext.current
SliderPreference(
preference = preference,
title = stringResource(preference.title),
summary = preference.summary(context, value.toLong()),
value = value.toLong(),
onChange = { onChange(it.toInt()) },
modifier = modifier,
)
}
@Composable
private fun DisplaySizePreview(
posters: List<BaseItem>,
episodes: List<BaseItem>,
showLogos: Boolean,
onUpdateBackdrop: (BaseItem) -> Unit,
) {
val recentlyAddedTitle = ResStringProvider(R.string.recently_added)
val nextUpTitle = ResStringProvider(R.string.next_up)
val previewRows =
remember(posters, episodes) {
listOf(
HomeRowLoadingState.Success(title = recentlyAddedTitle, items = posters),
HomeRowLoadingState.Success(title = nextUpTitle, items = episodes),
)
}
var position by remember { mutableStateOf(RowColumn(0, 0)) }
Row(modifier = Modifier.fillMaxSize()) {
PreviewNavRail()
HomePageContent(
homeRows = previewRows,
position = position,
onFocusPosition = { position = it },
onClickItem = { _, _ -> },
onLongClickItem = { _, _ -> },
onClickPlay = { _, _ -> },
showClock = false,
onUpdateBackdrop = onUpdateBackdrop,
showLogo = showLogos,
showViewMore = false,
takeFocus = false,
showEmptyRows = true,
modifier =
Modifier
.fillMaxHeight()
.weight(1f),
)
}
}
@Composable
private fun PreviewNavRail() {
Column(
modifier =
Modifier
.width(PREVIEW_NAV_RAIL_WIDTH_DP)
.fillMaxHeight(),
verticalArrangement = Arrangement.spacedBy(4.dp, Alignment.CenterVertically),
horizontalAlignment = Alignment.CenterHorizontally,
) {
PreviewNavRailIcons.forEach { icon ->
Box(
modifier =
Modifier
.fillMaxWidth()
.height(PREVIEW_NAV_ITEM_HEIGHT_DP),
contentAlignment = Alignment.Center,
) {
Icon(
imageVector = icon,
contentDescription = null,
tint = MaterialTheme.colorScheme.onSurfaceVariant,
modifier = Modifier.size(PREVIEW_NAV_ICON_SIZE_DP),
)
}
}
}
}

View file

@ -1,8 +1,11 @@
package com.github.damontecres.wholphin.ui.util package com.github.damontecres.wholphin.ui.util
import androidx.compose.runtime.staticCompositionLocalOf import androidx.compose.runtime.staticCompositionLocalOf
import com.github.damontecres.wholphin.preferences.AppPreference
import com.github.damontecres.wholphin.preferences.AppPreferences import com.github.damontecres.wholphin.preferences.AppPreferences
import com.github.damontecres.wholphin.preferences.DisplayToggle import com.github.damontecres.wholphin.preferences.DisplayToggle
import com.github.damontecres.wholphin.ui.BASE_SPACING_DP
import com.github.damontecres.wholphin.ui.Cards
import java.util.EnumSet import java.util.EnumSet
/** /**
@ -10,8 +13,12 @@ import java.util.EnumSet
*/ */
data class InterfaceCustomization( data class InterfaceCustomization(
val enabledDisplayToggles: EnumSet<DisplayToggle>, val enabledDisplayToggles: EnumSet<DisplayToggle>,
val uiScalePercent: Int = AppPreference.UiScale.defaultValue.toInt(),
val cardSizePercent: Int = AppPreference.CardSize.defaultValue.toInt(),
val spacingPercent: Int = AppPreference.Spacing.defaultValue.toInt(),
) { ) {
constructor(prefs: AppPreferences) : this( constructor(prefs: AppPreferences) : this(
enabledDisplayToggles =
prefs.interfacePreferences.displayTogglesList.let { prefs.interfacePreferences.displayTogglesList.let {
if (it.isEmpty()) { if (it.isEmpty()) {
EnumSet.noneOf(DisplayToggle::class.java) EnumSet.noneOf(DisplayToggle::class.java)
@ -19,7 +26,19 @@ data class InterfaceCustomization(
EnumSet.copyOf(it) EnumSet.copyOf(it)
} }
}, },
uiScalePercent =
prefs.interfacePreferences.uiScalePercent.withinBoundsOrDefault(AppPreference.UiScale),
cardSizePercent =
prefs.interfacePreferences.cardSizePercent.withinBoundsOrDefault(AppPreference.CardSize),
spacingPercent =
prefs.interfacePreferences.spacingPercent.withinBoundsOrDefault(AppPreference.Spacing),
) )
val cardHeightDp: Int
get() = Cards.HEIGHT_2X3_DP * cardSizePercent / 100
val spacingDp: Int
get() = BASE_SPACING_DP * spacingPercent / 100
} }
val LocalInterfaceCustomization = val LocalInterfaceCustomization =

View file

@ -0,0 +1,9 @@
package com.github.damontecres.wholphin.ui.util
import com.github.damontecres.wholphin.preferences.AppSliderPreference
internal fun Int.withinBoundsOrDefault(preference: AppSliderPreference<*>): Int {
val min = preference.min.toInt()
val max = preference.max.toInt()
return if (this in min..max) this else preference.defaultValue.toInt()
}

View file

@ -193,6 +193,9 @@ message InterfacePreferences {
bool show_logos = 13; bool show_logos = 13;
SearchPreferences search_preferences = 14; SearchPreferences search_preferences = 14;
repeated DisplayToggle display_toggles = 15; repeated DisplayToggle display_toggles = 15;
int32 ui_scale_percent = 16;
int32 card_size_percent = 17;
int32 spacing_percent = 18;
} }
message AdvancedPreferences { message AdvancedPreferences {

View file

@ -356,6 +356,7 @@
<string name="subtitle_style">Subtitle style</string> <string name="subtitle_style">Subtitle style</string>
<string name="background_color">Background color</string> <string name="background_color">Background color</string>
<string name="reset">Reset</string> <string name="reset">Reset</string>
<string name="reset_to_defaults">Reset to defaults</string>
<string name="bold_font">Bold font</string> <string name="bold_font">Bold font</string>
<string name="player_backend">Playback Backend</string> <string name="player_backend">Playback Backend</string>
@ -396,6 +397,10 @@
<string name="view_options">View options</string> <string name="view_options">View options</string>
<string name="columns">Columns</string> <string name="columns">Columns</string>
<string name="spacing">Spacing</string> <string name="spacing">Spacing</string>
<string name="card_size">Card size</string>
<string name="display_size">Display size</string>
<string name="display_size_summary">Scale the app UI, card size, and spacing</string>
<string name="ui_scale">UI scale</string>
<string name="aspect_ratio">Aspect Ratio</string> <string name="aspect_ratio">Aspect Ratio</string>
<string name="show_details">Show details</string> <string name="show_details">Show details</string>
<string name="image_type">Image type</string> <string name="image_type">Image type</string>
@ -525,7 +530,6 @@
<string name="add_row">Add row</string> <string name="add_row">Add row</string>
<string name="genres_in">Genres in %1$s</string> <string name="genres_in">Genres in %1$s</string>
<string name="recently_released_in">Recently released in %1$s</string> <string name="recently_released_in">Recently released in %1$s</string>
<string name="height">Height</string>
<string name="apply_all_rows">Apply to all rows</string> <string name="apply_all_rows">Apply to all rows</string>
<string name="customize_home">Customize home page</string> <string name="customize_home">Customize home page</string>
<string name="home_rows">Home rows</string> <string name="home_rows">Home rows</string>
@ -538,8 +542,6 @@
<string name="overwrite_local_settings">Overwrite local settings?</string> <string name="overwrite_local_settings">Overwrite local settings?</string>
<string name="for_episodes">For episodes</string> <string name="for_episodes">For episodes</string>
<string name="suggestions_for">Suggestions for %1$s</string> <string name="suggestions_for">Suggestions for %1$s</string>
<string name="increase_all_cards_size">Increase size for all cards</string>
<string name="decrease_all_cards_size">Decrease size for all cards</string>
<string name="use_thumb_images">Use thumb images</string> <string name="use_thumb_images">Use thumb images</string>
<string name="settings_saved">Settings saved</string> <string name="settings_saved">Settings saved</string>
<string name="display_presets">Display presets</string> <string name="display_presets">Display presets</string>