mirror of
https://github.com/JustinZeus/Wholphin.git
synced 2026-07-08 15:50:16 +02:00
Swap percent fields for DisplaySizeLevel enum end-to-end
The three v1 free sliders (UI scale, Card size, Spacing) become two discrete enum-based controls (Card size, Spacing) and a new Text size control, each selecting one of four DisplaySizeLevel values (Extra small / Small / Default / Large). UI scale is dropped entirely; the density override at the MainContent root goes with it. Proto: replaces ui_scale_percent / card_size_percent / spacing_percent with text_size_level / card_size_level / spacing_level on InterfacePreferences. DisplaySizeLevel enum is ordered DEFAULT = 0 so proto-zero-default lands new installs and any unset upgrader on DEFAULT for free; no upgrade-reset block needed, no serializer default-setting needed. AppPreference UiScale / CardSize / Spacing slider objects become three AppChoicePreference<AppPreferences, DisplaySizeLevel> entries (TextSize, CardSize, Spacing). DisplaySizeLevels.kt holds the user-facing display order and the three per-control percent-mapping extensions (textPercent / cardPercent / spacingPercent). InterfaceCustomization stores the three level fields and exposes cardSizePercent / spacingPercent as derived getters off the levels, so every consumer of cardHeightDp / episodeCardHeightDp / spacingDp stays unchanged. DisplaySizePage keeps the preview infrastructure (sample-item viewmodel, HomePageContent-based canvas, fake nav rail, two-column layout) and swaps the slider column for three ChoicePreference selectors bound to the new AppPreferences. The density-override wrap on the preview pane is removed. InterfaceHelpersTest withinBoundsOrDefault now exercises SkipForward since CardSize / Spacing are no longer AppSliderPreference. The two InterfaceCustomization scaling tests assert against cardSizeLevel = LARGE (116%) and spacingLevel = LARGE (125%).
This commit is contained in:
parent
b6c4f0dd4f
commit
c9433fbf57
10 changed files with 220 additions and 228 deletions
|
|
@ -18,7 +18,6 @@ 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.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
|
||||||
|
|
@ -40,7 +39,6 @@ import com.github.damontecres.wholphin.ui.setup.SwitchServerContent
|
||||||
import com.github.damontecres.wholphin.ui.setup.SwitchUserContent
|
import com.github.damontecres.wholphin.ui.setup.SwitchUserContent
|
||||||
import com.github.damontecres.wholphin.ui.util.InterfaceCustomization
|
import com.github.damontecres.wholphin.ui.util.InterfaceCustomization
|
||||||
import com.github.damontecres.wholphin.ui.util.LocalInterfaceCustomization
|
import com.github.damontecres.wholphin.ui.util.LocalInterfaceCustomization
|
||||||
import com.github.damontecres.wholphin.ui.util.scaledDensity
|
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun MainContent(
|
fun MainContent(
|
||||||
|
|
@ -62,14 +60,8 @@ fun MainContent(
|
||||||
// setupNavigationManager.backStack = backStack
|
// setupNavigationManager.backStack = backStack
|
||||||
val interfaceCustomization =
|
val interfaceCustomization =
|
||||||
remember(appPreferences) { InterfaceCustomization(appPreferences) }
|
remember(appPreferences) { InterfaceCustomization(appPreferences) }
|
||||||
val baseDensity = LocalDensity.current
|
|
||||||
val uiScaleDensity =
|
|
||||||
remember(baseDensity, interfaceCustomization.uiScalePercent) {
|
|
||||||
scaledDensity(baseDensity, interfaceCustomization.uiScalePercent)
|
|
||||||
}
|
|
||||||
CompositionLocalProvider(
|
CompositionLocalProvider(
|
||||||
LocalInterfaceCustomization provides interfaceCustomization,
|
LocalInterfaceCustomization provides interfaceCustomization,
|
||||||
LocalDensity provides uiScaleDensity,
|
|
||||||
) {
|
) {
|
||||||
NavDisplay(
|
NavDisplay(
|
||||||
backStack = backStack,
|
backStack = backStack,
|
||||||
|
|
|
||||||
|
|
@ -504,46 +504,67 @@ sealed interface AppPreference<Pref, T> {
|
||||||
summaryOff = R.string.disabled,
|
summaryOff = R.string.disabled,
|
||||||
)
|
)
|
||||||
|
|
||||||
val UiScale =
|
val TextSize =
|
||||||
AppSliderPreference<AppPreferences>(
|
AppChoicePreference<AppPreferences, DisplaySizeLevel>(
|
||||||
title = R.string.ui_scale,
|
title = R.string.text_size,
|
||||||
defaultValue = 100,
|
defaultValue = DisplaySizeLevel.DEFAULT,
|
||||||
min = 50,
|
displayValues = R.array.display_size_levels,
|
||||||
max = 150,
|
indexToValue = {
|
||||||
interval = 5,
|
displaySizeLevelDisplayOrder.getOrElse(it) { DisplaySizeLevel.DEFAULT }
|
||||||
getter = { it.interfacePreferences.uiScalePercent.toLong() },
|
},
|
||||||
setter = { prefs, value ->
|
valueToIndex = {
|
||||||
prefs.updateInterfacePreferences { uiScalePercent = value.toInt() }
|
displaySizeLevelDisplayOrder
|
||||||
|
.indexOf(it)
|
||||||
|
.let { idx ->
|
||||||
|
if (idx >= 0) idx else displaySizeLevelDisplayOrder.indexOf(DisplaySizeLevel.DEFAULT)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
getter = { it.interfacePreferences.textSizeLevel },
|
||||||
|
setter = { prefs, value ->
|
||||||
|
prefs.updateInterfacePreferences { textSizeLevel = value }
|
||||||
},
|
},
|
||||||
summarizer = { it?.let { "$it%" } },
|
|
||||||
)
|
)
|
||||||
|
|
||||||
val CardSize =
|
val CardSize =
|
||||||
AppSliderPreference<AppPreferences>(
|
AppChoicePreference<AppPreferences, DisplaySizeLevel>(
|
||||||
title = R.string.card_size,
|
title = R.string.card_size,
|
||||||
defaultValue = 100,
|
defaultValue = DisplaySizeLevel.DEFAULT,
|
||||||
min = 50,
|
displayValues = R.array.display_size_levels,
|
||||||
max = 150,
|
indexToValue = {
|
||||||
interval = 5,
|
displaySizeLevelDisplayOrder.getOrElse(it) { DisplaySizeLevel.DEFAULT }
|
||||||
getter = { it.interfacePreferences.cardSizePercent.toLong() },
|
},
|
||||||
setter = { prefs, value ->
|
valueToIndex = {
|
||||||
prefs.updateInterfacePreferences { cardSizePercent = value.toInt() }
|
displaySizeLevelDisplayOrder
|
||||||
|
.indexOf(it)
|
||||||
|
.let { idx ->
|
||||||
|
if (idx >= 0) idx else displaySizeLevelDisplayOrder.indexOf(DisplaySizeLevel.DEFAULT)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
getter = { it.interfacePreferences.cardSizeLevel },
|
||||||
|
setter = { prefs, value ->
|
||||||
|
prefs.updateInterfacePreferences { cardSizeLevel = value }
|
||||||
},
|
},
|
||||||
summarizer = { it?.let { "$it%" } },
|
|
||||||
)
|
)
|
||||||
|
|
||||||
val Spacing =
|
val Spacing =
|
||||||
AppSliderPreference<AppPreferences>(
|
AppChoicePreference<AppPreferences, DisplaySizeLevel>(
|
||||||
title = R.string.spacing,
|
title = R.string.spacing,
|
||||||
defaultValue = 100,
|
defaultValue = DisplaySizeLevel.DEFAULT,
|
||||||
min = 25,
|
displayValues = R.array.display_size_levels,
|
||||||
max = 175,
|
indexToValue = {
|
||||||
interval = 10,
|
displaySizeLevelDisplayOrder.getOrElse(it) { DisplaySizeLevel.DEFAULT }
|
||||||
getter = { it.interfacePreferences.spacingPercent.toLong() },
|
},
|
||||||
setter = { prefs, value ->
|
valueToIndex = {
|
||||||
prefs.updateInterfacePreferences { spacingPercent = value.toInt() }
|
displaySizeLevelDisplayOrder
|
||||||
|
.indexOf(it)
|
||||||
|
.let { idx ->
|
||||||
|
if (idx >= 0) idx else displaySizeLevelDisplayOrder.indexOf(DisplaySizeLevel.DEFAULT)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
getter = { it.interfacePreferences.spacingLevel },
|
||||||
|
setter = { prefs, value ->
|
||||||
|
prefs.updateInterfacePreferences { spacingLevel = value }
|
||||||
},
|
},
|
||||||
summarizer = { it?.let { "$it%" } },
|
|
||||||
)
|
)
|
||||||
|
|
||||||
val DisplaySizePref =
|
val DisplaySizePref =
|
||||||
|
|
|
||||||
|
|
@ -100,9 +100,6 @@ 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
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,33 @@
|
||||||
|
package com.github.damontecres.wholphin.preferences
|
||||||
|
|
||||||
|
val displaySizeLevelDisplayOrder: List<DisplaySizeLevel> =
|
||||||
|
listOf(
|
||||||
|
DisplaySizeLevel.EXTRA_SMALL,
|
||||||
|
DisplaySizeLevel.SMALL,
|
||||||
|
DisplaySizeLevel.DEFAULT,
|
||||||
|
DisplaySizeLevel.LARGE,
|
||||||
|
)
|
||||||
|
|
||||||
|
fun DisplaySizeLevel.textPercent(): Int =
|
||||||
|
when (this) {
|
||||||
|
DisplaySizeLevel.EXTRA_SMALL -> 88
|
||||||
|
DisplaySizeLevel.SMALL -> 94
|
||||||
|
DisplaySizeLevel.LARGE -> 108
|
||||||
|
DisplaySizeLevel.DEFAULT, DisplaySizeLevel.UNRECOGNIZED -> 100
|
||||||
|
}
|
||||||
|
|
||||||
|
fun DisplaySizeLevel.cardPercent(): Int =
|
||||||
|
when (this) {
|
||||||
|
DisplaySizeLevel.EXTRA_SMALL -> 78
|
||||||
|
DisplaySizeLevel.SMALL -> 90
|
||||||
|
DisplaySizeLevel.LARGE -> 116
|
||||||
|
DisplaySizeLevel.DEFAULT, DisplaySizeLevel.UNRECOGNIZED -> 100
|
||||||
|
}
|
||||||
|
|
||||||
|
fun DisplaySizeLevel.spacingPercent(): Int =
|
||||||
|
when (this) {
|
||||||
|
DisplaySizeLevel.EXTRA_SMALL -> 75
|
||||||
|
DisplaySizeLevel.SMALL -> 88
|
||||||
|
DisplaySizeLevel.LARGE -> 125
|
||||||
|
DisplaySizeLevel.DEFAULT, DisplaySizeLevel.UNRECOGNIZED -> 100
|
||||||
|
}
|
||||||
|
|
@ -350,15 +350,5 @@ 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()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -16,17 +16,13 @@ import androidx.compose.foundation.layout.width
|
||||||
import androidx.compose.foundation.lazy.LazyColumn
|
import androidx.compose.foundation.lazy.LazyColumn
|
||||||
import androidx.compose.material.icons.Icons
|
import androidx.compose.material.icons.Icons
|
||||||
import androidx.compose.material.icons.filled.Add
|
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.Favorite
|
||||||
import androidx.compose.material.icons.filled.Home
|
import androidx.compose.material.icons.filled.Home
|
||||||
import androidx.compose.material.icons.filled.Person
|
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.Search
|
||||||
import androidx.compose.material.icons.filled.Settings
|
import androidx.compose.material.icons.filled.Settings
|
||||||
import androidx.compose.material.icons.filled.Star
|
import androidx.compose.material.icons.filled.Star
|
||||||
import androidx.compose.material3.HorizontalDivider
|
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.CompositionLocalProvider
|
|
||||||
import androidx.compose.runtime.LaunchedEffect
|
import androidx.compose.runtime.LaunchedEffect
|
||||||
import androidx.compose.runtime.collectAsState
|
import androidx.compose.runtime.collectAsState
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
|
|
@ -40,37 +36,31 @@ import androidx.compose.ui.focus.FocusRequester
|
||||||
import androidx.compose.ui.focus.focusRequester
|
import androidx.compose.ui.focus.focusRequester
|
||||||
import androidx.compose.ui.focus.focusRestorer
|
import androidx.compose.ui.focus.focusRestorer
|
||||||
import androidx.compose.ui.graphics.vector.ImageVector
|
import androidx.compose.ui.graphics.vector.ImageVector
|
||||||
import androidx.compose.ui.platform.LocalContext
|
import androidx.compose.ui.res.stringArrayResource
|
||||||
import androidx.compose.ui.platform.LocalDensity
|
|
||||||
import androidx.compose.ui.res.stringResource
|
import androidx.compose.ui.res.stringResource
|
||||||
import androidx.compose.ui.unit.Density
|
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel
|
import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel
|
||||||
import androidx.lifecycle.ViewModel
|
import androidx.lifecycle.ViewModel
|
||||||
import androidx.lifecycle.viewModelScope
|
import androidx.lifecycle.viewModelScope
|
||||||
import androidx.tv.material3.Icon
|
import androidx.tv.material3.Icon
|
||||||
import androidx.tv.material3.MaterialTheme
|
import androidx.tv.material3.MaterialTheme
|
||||||
import androidx.tv.material3.Text
|
|
||||||
import com.github.damontecres.wholphin.R
|
import com.github.damontecres.wholphin.R
|
||||||
import com.github.damontecres.wholphin.data.model.BaseItem
|
import com.github.damontecres.wholphin.data.model.BaseItem
|
||||||
|
import com.github.damontecres.wholphin.preferences.AppChoicePreference
|
||||||
import com.github.damontecres.wholphin.preferences.AppPreference
|
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.AppSliderPreference
|
import com.github.damontecres.wholphin.preferences.DisplaySizeLevel
|
||||||
import com.github.damontecres.wholphin.preferences.updateInterfacePreferences
|
import com.github.damontecres.wholphin.preferences.updateInterfacePreferences
|
||||||
import com.github.damontecres.wholphin.services.BackdropService
|
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.data.RowColumn
|
||||||
import com.github.damontecres.wholphin.ui.launchIO
|
import com.github.damontecres.wholphin.ui.launchIO
|
||||||
import com.github.damontecres.wholphin.ui.main.HomePageContent
|
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.TitleText
|
||||||
import com.github.damontecres.wholphin.ui.main.settings.settingsWidth
|
import com.github.damontecres.wholphin.ui.main.settings.settingsWidth
|
||||||
|
import com.github.damontecres.wholphin.ui.preferences.ChoicePreference
|
||||||
import com.github.damontecres.wholphin.ui.preferences.PreferencesViewModel
|
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.tryRequestFocus
|
||||||
import com.github.damontecres.wholphin.ui.util.ResStringProvider
|
import com.github.damontecres.wholphin.ui.util.ResStringProvider
|
||||||
import com.github.damontecres.wholphin.ui.util.scaledDensity
|
|
||||||
import com.github.damontecres.wholphin.ui.util.withinBoundsOrDefault
|
|
||||||
import com.github.damontecres.wholphin.util.HomeRowLoadingState
|
import com.github.damontecres.wholphin.util.HomeRowLoadingState
|
||||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||||
import kotlinx.coroutines.flow.MutableStateFlow
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
|
|
@ -98,8 +88,6 @@ private val PreviewNavRailIcons: List<ImageVector> =
|
||||||
Icons.Default.Search,
|
Icons.Default.Search,
|
||||||
Icons.Default.Add,
|
Icons.Default.Add,
|
||||||
Icons.Default.Star,
|
Icons.Default.Star,
|
||||||
Icons.Default.PlayArrow,
|
|
||||||
Icons.Default.ArrowDropDown,
|
|
||||||
Icons.Default.Settings,
|
Icons.Default.Settings,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -163,110 +151,73 @@ fun DisplaySizePage(
|
||||||
val scope = rememberCoroutineScope()
|
val scope = rememberCoroutineScope()
|
||||||
val iface = preferences.interfacePreferences
|
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 posters by previewViewModel.posters.collectAsState()
|
||||||
val episodes by previewViewModel.episodes.collectAsState()
|
val episodes by previewViewModel.episodes.collectAsState()
|
||||||
|
|
||||||
// Freezes the rail width and slider labels so they don't reflow under the user's finger
|
Row(
|
||||||
// while dragging UI scale.
|
horizontalArrangement = Arrangement.spacedBy(8.dp),
|
||||||
val ambientDensity = LocalDensity.current
|
modifier = modifier.fillMaxSize(),
|
||||||
val pageDensity = remember { ambientDensity }
|
) {
|
||||||
|
Box(
|
||||||
val context = LocalContext.current
|
modifier =
|
||||||
val systemDensity =
|
Modifier
|
||||||
remember {
|
.width(settingsWidth)
|
||||||
val dm = context.resources.displayMetrics
|
.fillMaxHeight()
|
||||||
Density(
|
.background(color = MaterialTheme.colorScheme.surface)
|
||||||
density = dm.density,
|
.padding(8.dp),
|
||||||
fontScale = context.resources.configuration.fontScale,
|
) {
|
||||||
|
DisplaySizeSettingsColumn(
|
||||||
|
textLevel = iface.textSizeLevel,
|
||||||
|
cardLevel = iface.cardSizeLevel,
|
||||||
|
spacingLevel = iface.spacingLevel,
|
||||||
|
onChangeTextLevel = { value ->
|
||||||
|
scope.launch {
|
||||||
|
viewModel.preferenceDataStore.updateData {
|
||||||
|
it.updateInterfacePreferences { textSizeLevel = value }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onChangeCardLevel = { value ->
|
||||||
|
scope.launch {
|
||||||
|
viewModel.preferenceDataStore.updateData {
|
||||||
|
it.updateInterfacePreferences { cardSizeLevel = value }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onChangeSpacingLevel = { value ->
|
||||||
|
scope.launch {
|
||||||
|
viewModel.preferenceDataStore.updateData {
|
||||||
|
it.updateInterfacePreferences { spacingLevel = value }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
val previewDensity =
|
|
||||||
remember(systemDensity, uiScale) { scaledDensity(systemDensity, uiScale) }
|
|
||||||
|
|
||||||
CompositionLocalProvider(LocalDensity provides pageDensity) {
|
Box(
|
||||||
Row(
|
modifier =
|
||||||
horizontalArrangement = Arrangement.spacedBy(8.dp),
|
Modifier
|
||||||
modifier = modifier.fillMaxSize(),
|
.fillMaxHeight()
|
||||||
|
.weight(1f),
|
||||||
) {
|
) {
|
||||||
Box(
|
DisplaySizePreview(
|
||||||
modifier =
|
posters = posters,
|
||||||
Modifier
|
episodes = episodes,
|
||||||
.width(settingsWidth)
|
showLogos = iface.showLogos,
|
||||||
.fillMaxHeight()
|
onUpdateBackdrop = previewViewModel::updateBackdrop,
|
||||||
.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
|
@Composable
|
||||||
private fun DisplaySizeSettingsColumn(
|
private fun DisplaySizeSettingsColumn(
|
||||||
uiScale: Int,
|
textLevel: DisplaySizeLevel,
|
||||||
cardSize: Int,
|
cardLevel: DisplaySizeLevel,
|
||||||
spacing: Int,
|
spacingLevel: DisplaySizeLevel,
|
||||||
onUiScaleChange: (Int) -> Unit,
|
onChangeTextLevel: (DisplaySizeLevel) -> Unit,
|
||||||
onCardSizeChange: (Int) -> Unit,
|
onChangeCardLevel: (DisplaySizeLevel) -> Unit,
|
||||||
onSpacingChange: (Int) -> Unit,
|
onChangeSpacingLevel: (DisplaySizeLevel) -> Unit,
|
||||||
onClickReset: () -> Unit,
|
|
||||||
) {
|
) {
|
||||||
val firstFocus = remember { FocusRequester() }
|
val firstFocus = remember { FocusRequester() }
|
||||||
LaunchedEffect(Unit) { firstFocus.tryRequestFocus() }
|
LaunchedEffect(Unit) { firstFocus.tryRequestFocus() }
|
||||||
|
|
@ -282,39 +233,25 @@ private fun DisplaySizeSettingsColumn(
|
||||||
.focusRestorer(firstFocus),
|
.focusRestorer(firstFocus),
|
||||||
) {
|
) {
|
||||||
item {
|
item {
|
||||||
DisplaySizeSlider(
|
LevelChoicePreference(
|
||||||
preference = AppPreference.UiScale,
|
preference = AppPreference.TextSize,
|
||||||
value = uiScale,
|
currentValue = textLevel,
|
||||||
onChange = onUiScaleChange,
|
onChange = onChangeTextLevel,
|
||||||
modifier = Modifier.focusRequester(firstFocus),
|
modifier = Modifier.focusRequester(firstFocus),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
item {
|
item {
|
||||||
DisplaySizeSlider(
|
LevelChoicePreference(
|
||||||
preference = AppPreference.CardSize,
|
preference = AppPreference.CardSize,
|
||||||
value = cardSize,
|
currentValue = cardLevel,
|
||||||
onChange = onCardSizeChange,
|
onChange = onChangeCardLevel,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
item {
|
item {
|
||||||
DisplaySizeSlider(
|
LevelChoicePreference(
|
||||||
preference = AppPreference.Spacing,
|
preference = AppPreference.Spacing,
|
||||||
value = spacing,
|
currentValue = spacingLevel,
|
||||||
onChange = onSpacingChange,
|
onChange = onChangeSpacingLevel,
|
||||||
)
|
|
||||||
}
|
|
||||||
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,
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -322,19 +259,20 @@ private fun DisplaySizeSettingsColumn(
|
||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
private fun DisplaySizeSlider(
|
private fun LevelChoicePreference(
|
||||||
preference: AppSliderPreference<AppPreferences>,
|
preference: AppChoicePreference<AppPreferences, DisplaySizeLevel>,
|
||||||
value: Int,
|
currentValue: DisplaySizeLevel,
|
||||||
onChange: (Int) -> Unit,
|
onChange: (DisplaySizeLevel) -> Unit,
|
||||||
modifier: Modifier = Modifier,
|
modifier: Modifier = Modifier,
|
||||||
) {
|
) {
|
||||||
val context = LocalContext.current
|
val levelNames = stringArrayResource(preference.displayValues).toList()
|
||||||
SliderPreference(
|
val selectedIndex = preference.valueToIndex(currentValue)
|
||||||
preference = preference,
|
ChoicePreference(
|
||||||
title = stringResource(preference.title),
|
title = stringResource(preference.title),
|
||||||
summary = preference.summary(context, value.toLong()),
|
summary = levelNames.getOrNull(selectedIndex),
|
||||||
value = value.toLong(),
|
possibleValues = levelNames,
|
||||||
onChange = { onChange(it.toInt()) },
|
selectedIndex = selectedIndex,
|
||||||
|
onValueChange = { onChange(preference.indexToValue(it)) },
|
||||||
modifier = modifier,
|
modifier = modifier,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,9 +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.DisplaySizeLevel
|
||||||
import com.github.damontecres.wholphin.preferences.DisplayToggle
|
import com.github.damontecres.wholphin.preferences.DisplayToggle
|
||||||
|
import com.github.damontecres.wholphin.preferences.cardPercent
|
||||||
|
import com.github.damontecres.wholphin.preferences.spacingPercent
|
||||||
import com.github.damontecres.wholphin.ui.BASE_SPACING_DP
|
import com.github.damontecres.wholphin.ui.BASE_SPACING_DP
|
||||||
import com.github.damontecres.wholphin.ui.Cards
|
import com.github.damontecres.wholphin.ui.Cards
|
||||||
import java.util.EnumSet
|
import java.util.EnumSet
|
||||||
|
|
@ -13,9 +15,9 @@ 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 textSizeLevel: DisplaySizeLevel = DisplaySizeLevel.DEFAULT,
|
||||||
val cardSizePercent: Int = AppPreference.CardSize.defaultValue.toInt(),
|
val cardSizeLevel: DisplaySizeLevel = DisplaySizeLevel.DEFAULT,
|
||||||
val spacingPercent: Int = AppPreference.Spacing.defaultValue.toInt(),
|
val spacingLevel: DisplaySizeLevel = DisplaySizeLevel.DEFAULT,
|
||||||
) {
|
) {
|
||||||
constructor(prefs: AppPreferences) : this(
|
constructor(prefs: AppPreferences) : this(
|
||||||
enabledDisplayToggles =
|
enabledDisplayToggles =
|
||||||
|
|
@ -26,14 +28,17 @@ data class InterfaceCustomization(
|
||||||
EnumSet.copyOf(it)
|
EnumSet.copyOf(it)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
uiScalePercent =
|
textSizeLevel = prefs.interfacePreferences.textSizeLevel,
|
||||||
prefs.interfacePreferences.uiScalePercent.withinBoundsOrDefault(AppPreference.UiScale),
|
cardSizeLevel = prefs.interfacePreferences.cardSizeLevel,
|
||||||
cardSizePercent =
|
spacingLevel = prefs.interfacePreferences.spacingLevel,
|
||||||
prefs.interfacePreferences.cardSizePercent.withinBoundsOrDefault(AppPreference.CardSize),
|
|
||||||
spacingPercent =
|
|
||||||
prefs.interfacePreferences.spacingPercent.withinBoundsOrDefault(AppPreference.Spacing),
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
val cardSizePercent: Int
|
||||||
|
get() = cardSizeLevel.cardPercent()
|
||||||
|
|
||||||
|
val spacingPercent: Int
|
||||||
|
get() = spacingLevel.spacingPercent()
|
||||||
|
|
||||||
val cardHeightDp: Int
|
val cardHeightDp: Int
|
||||||
get() = Cards.HEIGHT_2X3_DP * cardSizePercent / 100
|
get() = Cards.HEIGHT_2X3_DP * cardSizePercent / 100
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -177,6 +177,13 @@ enum DisplayToggle{
|
||||||
COMMUNITY_RATING = 2;
|
COMMUNITY_RATING = 2;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
enum DisplaySizeLevel{
|
||||||
|
DEFAULT = 0;
|
||||||
|
EXTRA_SMALL = 1;
|
||||||
|
SMALL = 2;
|
||||||
|
LARGE = 3;
|
||||||
|
}
|
||||||
|
|
||||||
message InterfacePreferences {
|
message InterfacePreferences {
|
||||||
ThemeSongVolume play_theme_songs = 1;
|
ThemeSongVolume play_theme_songs = 1;
|
||||||
bool remember_selected_tab = 2;
|
bool remember_selected_tab = 2;
|
||||||
|
|
@ -193,9 +200,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;
|
DisplaySizeLevel text_size_level = 19;
|
||||||
int32 card_size_percent = 17;
|
DisplaySizeLevel card_size_level = 20;
|
||||||
int32 spacing_percent = 18;
|
DisplaySizeLevel spacing_level = 21;
|
||||||
}
|
}
|
||||||
|
|
||||||
message AdvancedPreferences {
|
message AdvancedPreferences {
|
||||||
|
|
|
||||||
|
|
@ -399,8 +399,12 @@
|
||||||
<string name="spacing">Spacing</string>
|
<string name="spacing">Spacing</string>
|
||||||
<string name="card_size">Card size</string>
|
<string name="card_size">Card size</string>
|
||||||
<string name="display_size">Display 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="display_size_summary">Adjust text size, card size, and spacing</string>
|
||||||
<string name="ui_scale">UI scale</string>
|
<string name="text_size">Text size</string>
|
||||||
|
<string name="display_size_level_extra_small">Extra small</string>
|
||||||
|
<string name="display_size_level_small">Small</string>
|
||||||
|
<string name="display_size_level_default">Default</string>
|
||||||
|
<string name="display_size_level_large">Large</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>
|
||||||
|
|
@ -597,6 +601,13 @@
|
||||||
<item>@string/black</item>
|
<item>@string/black</item>
|
||||||
</string-array>
|
</string-array>
|
||||||
|
|
||||||
|
<string-array name="display_size_levels">
|
||||||
|
<item>@string/display_size_level_extra_small</item>
|
||||||
|
<item>@string/display_size_level_small</item>
|
||||||
|
<item>@string/display_size_level_default</item>
|
||||||
|
<item>@string/display_size_level_large</item>
|
||||||
|
</string-array>
|
||||||
|
|
||||||
<string name="skip_ignore">Ignore</string>
|
<string name="skip_ignore">Ignore</string>
|
||||||
<string name="skip_automatically">Skip automatically</string>
|
<string name="skip_automatically">Skip automatically</string>
|
||||||
<string name="skip_ask">Ask to skip</string>
|
<string name="skip_ask">Ask to skip</string>
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,7 @@ import com.github.damontecres.wholphin.data.model.HomeRowConfig
|
||||||
import com.github.damontecres.wholphin.data.model.HomeRowViewOptions
|
import com.github.damontecres.wholphin.data.model.HomeRowViewOptions
|
||||||
import com.github.damontecres.wholphin.preferences.AppPreference
|
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.DisplaySizeLevel
|
||||||
import com.github.damontecres.wholphin.services.migrateLegacyRowSpacing
|
import com.github.damontecres.wholphin.services.migrateLegacyRowSpacing
|
||||||
import com.github.damontecres.wholphin.ui.AspectRatio
|
import com.github.damontecres.wholphin.ui.AspectRatio
|
||||||
import com.github.damontecres.wholphin.ui.Cards
|
import com.github.damontecres.wholphin.ui.Cards
|
||||||
|
|
@ -16,31 +17,28 @@ import java.util.EnumSet
|
||||||
class InterfaceHelpersTest {
|
class InterfaceHelpersTest {
|
||||||
@Test
|
@Test
|
||||||
fun `withinBoundsOrDefault returns value when in range`() {
|
fun `withinBoundsOrDefault returns value when in range`() {
|
||||||
assertEquals(100, 100.withinBoundsOrDefault(AppPreference.CardSize))
|
assertEquals(30, 30.withinBoundsOrDefault(AppPreference.SkipForward))
|
||||||
assertEquals(75, 75.withinBoundsOrDefault(AppPreference.CardSize))
|
assertEquals(60, 60.withinBoundsOrDefault(AppPreference.SkipForward))
|
||||||
assertEquals(150, 150.withinBoundsOrDefault(AppPreference.CardSize))
|
assertEquals(120, 120.withinBoundsOrDefault(AppPreference.SkipForward))
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `withinBoundsOrDefault returns default when below min`() {
|
fun `withinBoundsOrDefault returns default when below min`() {
|
||||||
// CardSize is 50..150 default 100; Spacing is 25..175 default 100.
|
assertEquals(30, 4.withinBoundsOrDefault(AppPreference.SkipForward))
|
||||||
assertEquals(100, 49.withinBoundsOrDefault(AppPreference.CardSize))
|
assertEquals(30, 0.withinBoundsOrDefault(AppPreference.SkipForward))
|
||||||
assertEquals(100, 0.withinBoundsOrDefault(AppPreference.Spacing))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `withinBoundsOrDefault returns default when above max`() {
|
fun `withinBoundsOrDefault returns default when above max`() {
|
||||||
assertEquals(100, 151.withinBoundsOrDefault(AppPreference.CardSize))
|
assertEquals(30, 301.withinBoundsOrDefault(AppPreference.SkipForward))
|
||||||
assertEquals(100, 200.withinBoundsOrDefault(AppPreference.Spacing))
|
assertEquals(30, 1000.withinBoundsOrDefault(AppPreference.SkipForward))
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `withinBoundsOrDefault includes both bounds inclusively`() {
|
fun `withinBoundsOrDefault includes both bounds inclusively`() {
|
||||||
// Sanity: min and max themselves are accepted, NOT replaced with default.
|
// Sanity: min and max themselves are accepted, NOT replaced with default.
|
||||||
assertEquals(50, 50.withinBoundsOrDefault(AppPreference.CardSize))
|
assertEquals(5, 5.withinBoundsOrDefault(AppPreference.SkipForward))
|
||||||
assertEquals(150, 150.withinBoundsOrDefault(AppPreference.CardSize))
|
assertEquals(300, 300.withinBoundsOrDefault(AppPreference.SkipForward))
|
||||||
assertEquals(25, 25.withinBoundsOrDefault(AppPreference.Spacing))
|
|
||||||
assertEquals(175, 175.withinBoundsOrDefault(AppPreference.Spacing))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
|
@ -134,38 +132,38 @@ class InterfaceHelpersTest {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `InterfaceCustomization cardHeightDp computes global from slider percent`() {
|
fun `InterfaceCustomization cardHeightDp at DEFAULT level matches natural base`() {
|
||||||
val customization = InterfaceCustomization(prefs = AppPreferences.getDefaultInstance())
|
val customization = InterfaceCustomization(prefs = AppPreferences.getDefaultInstance())
|
||||||
// Default cardSizePercent is 100, so cardHeightDp == HEIGHT_2X3_DP.
|
// proto-zero-default lands cardSizeLevel on DEFAULT (100%), so cardHeightDp == HEIGHT_2X3_DP.
|
||||||
assertEquals(Cards.HEIGHT_2X3_DP, customization.cardHeightDp)
|
assertEquals(Cards.HEIGHT_2X3_DP, customization.cardHeightDp)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `InterfaceCustomization cardHeightDp scales linearly`() {
|
fun `InterfaceCustomization cardHeightDp scales by Card level percent`() {
|
||||||
val customization =
|
val customization =
|
||||||
InterfaceCustomization(
|
InterfaceCustomization(
|
||||||
enabledDisplayToggles =
|
enabledDisplayToggles =
|
||||||
EnumSet.noneOf(
|
EnumSet.noneOf(
|
||||||
com.github.damontecres.wholphin.preferences.DisplayToggle::class.java,
|
com.github.damontecres.wholphin.preferences.DisplayToggle::class.java,
|
||||||
),
|
),
|
||||||
cardSizePercent = 150,
|
cardSizeLevel = DisplaySizeLevel.LARGE,
|
||||||
)
|
)
|
||||||
// 172 * 150 / 100 = 258.
|
// LARGE → cardPercent() = 116. 172 * 116 / 100 = 199 (integer truncation).
|
||||||
assertEquals(258, customization.cardHeightDp)
|
assertEquals(199, customization.cardHeightDp)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `InterfaceCustomization spacingDp scales linearly`() {
|
fun `InterfaceCustomization spacingDp scales by Spacing level percent`() {
|
||||||
val customization =
|
val customization =
|
||||||
InterfaceCustomization(
|
InterfaceCustomization(
|
||||||
enabledDisplayToggles =
|
enabledDisplayToggles =
|
||||||
EnumSet.noneOf(
|
EnumSet.noneOf(
|
||||||
com.github.damontecres.wholphin.preferences.DisplayToggle::class.java,
|
com.github.damontecres.wholphin.preferences.DisplayToggle::class.java,
|
||||||
),
|
),
|
||||||
spacingPercent = 175,
|
spacingLevel = DisplaySizeLevel.LARGE,
|
||||||
)
|
)
|
||||||
// BASE_SPACING_DP = 16. 16 * 175 / 100 = 28.
|
// LARGE → spacingPercent() = 125. BASE_SPACING_DP = 16. 16 * 125 / 100 = 20.
|
||||||
assertEquals(28, customization.spacingDp)
|
assertEquals(20, customization.spacingDp)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue