Add some display option presets

This commit is contained in:
Damontecres 2026-02-11 22:14:47 -05:00
parent 561b12fe87
commit f592f3081b
No known key found for this signature in database
6 changed files with 348 additions and 1 deletions

View file

@ -0,0 +1,206 @@
package com.github.damontecres.wholphin.ui.main.settings
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.fillMaxHeight
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.itemsIndexed
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.remember
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.res.stringResource
import androidx.compose.ui.unit.dp
import com.github.damontecres.wholphin.R
import com.github.damontecres.wholphin.data.model.HomeRowViewOptions
import com.github.damontecres.wholphin.preferences.PrefContentScale
import com.github.damontecres.wholphin.ui.AspectRatio
import com.github.damontecres.wholphin.ui.Cards
import com.github.damontecres.wholphin.ui.components.ViewOptionImageType
import com.github.damontecres.wholphin.ui.tryRequestFocus
import org.jellyfin.sdk.model.api.CollectionType
data class HomeRowPresets(
val continueWatching: HomeRowViewOptions,
val movieLibrary: HomeRowViewOptions,
val tvLibrary: HomeRowViewOptions,
val videoLibrary: HomeRowViewOptions,
val photoLibrary: HomeRowViewOptions,
val playlist: HomeRowViewOptions,
val genreSize: Int,
) {
fun getByCollectionType(collectionType: CollectionType): HomeRowViewOptions =
when (collectionType) {
CollectionType.MOVIES -> movieLibrary
CollectionType.TVSHOWS -> tvLibrary
CollectionType.MUSICVIDEOS -> videoLibrary
CollectionType.TRAILERS -> videoLibrary
CollectionType.HOMEVIDEOS -> videoLibrary
CollectionType.BOXSETS -> movieLibrary
CollectionType.PHOTOS -> photoLibrary
CollectionType.UNKNOWN,
CollectionType.MUSIC,
CollectionType.BOOKS,
CollectionType.LIVETV,
CollectionType.PLAYLISTS,
CollectionType.FOLDERS,
-> HomeRowViewOptions()
}
companion object {
val WholphinDefault by lazy {
HomeRowPresets(
continueWatching = HomeRowViewOptions(),
movieLibrary = HomeRowViewOptions(),
tvLibrary = HomeRowViewOptions(),
videoLibrary =
HomeRowViewOptions(
aspectRatio = AspectRatio.WIDE,
),
photoLibrary =
HomeRowViewOptions(
aspectRatio = AspectRatio.WIDE,
contentScale = PrefContentScale.CROP,
),
playlist =
HomeRowViewOptions(
aspectRatio = AspectRatio.SQUARE,
contentScale = PrefContentScale.FIT,
),
genreSize = Cards.HEIGHT_2X3_DP,
)
}
val WholphinCompact by lazy {
val height = 148
val epHeight = 100
HomeRowPresets(
continueWatching =
HomeRowViewOptions(
heightDp = height,
),
movieLibrary =
HomeRowViewOptions(
heightDp = height,
),
tvLibrary =
HomeRowViewOptions(
heightDp = height,
),
videoLibrary =
HomeRowViewOptions(
heightDp = epHeight,
aspectRatio = AspectRatio.WIDE,
),
photoLibrary =
HomeRowViewOptions(
heightDp = epHeight,
aspectRatio = AspectRatio.WIDE,
contentScale = PrefContentScale.CROP,
),
playlist =
HomeRowViewOptions(
heightDp = epHeight,
aspectRatio = AspectRatio.SQUARE,
contentScale = PrefContentScale.FIT,
),
genreSize = epHeight,
)
}
val Thumbnails by lazy {
val height = 148
val epHeight = 100
HomeRowPresets(
continueWatching =
HomeRowViewOptions(
heightDp = epHeight,
imageType = ViewOptionImageType.THUMB,
aspectRatio = AspectRatio.WIDE,
episodeImageType = ViewOptionImageType.THUMB,
episodeAspectRatio = AspectRatio.WIDE,
),
movieLibrary =
HomeRowViewOptions(
heightDp = height,
),
tvLibrary =
HomeRowViewOptions(
heightDp = height,
),
videoLibrary =
HomeRowViewOptions(
heightDp = epHeight,
aspectRatio = AspectRatio.WIDE,
),
photoLibrary =
HomeRowViewOptions(
heightDp = epHeight,
aspectRatio = AspectRatio.WIDE,
contentScale = PrefContentScale.CROP,
),
playlist =
HomeRowViewOptions(
heightDp = epHeight,
aspectRatio = AspectRatio.SQUARE,
contentScale = PrefContentScale.FIT,
),
genreSize = epHeight,
)
}
}
}
@Composable
fun HomeRowPresetsContent(
onApply: (HomeRowPresets) -> Unit,
modifier: Modifier = Modifier,
) {
val presets =
remember {
listOf(
"Wholphin Default",
"Wholphin Compact",
"Thumbnails",
)
}
val focusRequesters = remember { List(presets.size) { FocusRequester() } }
LaunchedEffect(Unit) { focusRequesters[0].tryRequestFocus() }
Column(modifier = modifier) {
TitleText(stringResource(R.string.display_presets))
LazyColumn(
contentPadding = PaddingValues(8.dp),
verticalArrangement = Arrangement.spacedBy(8.dp),
modifier =
modifier
.fillMaxHeight()
.focusRestorer(focusRequesters[0]),
) {
itemsIndexed(presets) { index, title ->
HomeSettingsListItem(
selected = false,
headlineText = title,
onClick = {
when (index) {
0 -> onApply.invoke(HomeRowPresets.WholphinDefault)
1 -> onApply.invoke(HomeRowPresets.WholphinCompact)
2 -> onApply.invoke(HomeRowPresets.Thumbnails)
}
},
modifier = Modifier.focusRequester(focusRequesters[index]),
)
}
}
}
}

View file

@ -32,4 +32,7 @@ sealed interface HomeSettingsDestination : NavKey {
@Serializable
data object GlobalSettings : HomeSettingsDestination
@Serializable
data object Presets : HomeSettingsDestination
}

View file

@ -100,6 +100,7 @@ fun HomeSettingsPage(
state = state,
onClickAdd = { backStack.add(HomeSettingsDestination.AddRow) },
onClickSettings = { backStack.add(HomeSettingsDestination.GlobalSettings) },
onClickPresets = { backStack.add(HomeSettingsDestination.Presets) },
onClickMove = viewModel::moveRow,
onClickDelete = viewModel::deleteRow,
onClick = { index, row ->
@ -241,6 +242,13 @@ fun HomeSettingsPage(
modifier = destModifier,
)
}
HomeSettingsDestination.Presets -> {
HomeRowPresetsContent(
onApply = viewModel::applyPreset,
modifier = destModifier,
)
}
}
}
},

View file

@ -56,6 +56,7 @@ fun HomeSettingsRowList(
onClick: (Int, HomeRowConfigDisplay) -> Unit,
onClickAdd: () -> Unit,
onClickSettings: () -> Unit,
onClickPresets: () -> Unit,
onClickMove: (MoveDirection, Int) -> Unit,
onClickDelete: (Int) -> Unit,
modifier: Modifier,
@ -64,7 +65,7 @@ fun HomeSettingsRowList(
val scope = rememberCoroutineScope()
val listState = rememberLazyListState()
val itemsBeforeRows = 3
val itemsBeforeRows = 4
val focusRequesters =
remember(state.rows.size) { List(itemsBeforeRows + state.rows.size) { FocusRequester() } }
@ -117,6 +118,28 @@ fun HomeSettingsRowList(
modifier = Modifier.focusRequester(focusRequesters[1]),
)
}
item {
HomeSettingsListItem(
selected = false,
headlineText = stringResource(R.string.display_presets),
supportingContent = {
Text(
text = stringResource(R.string.display_presets_description),
)
},
leadingContent = {
Text(
text = stringResource(R.string.fa_sliders),
fontFamily = FontAwesome,
)
},
onClick = {
position = 2
onClickPresets.invoke()
},
modifier = Modifier.focusRequester(focusRequesters[1]),
)
}
item {
TitleText(stringResource(R.string.home_rows))
HorizontalDivider()

View file

@ -49,6 +49,8 @@ import kotlinx.coroutines.flow.update
import kotlinx.coroutines.sync.Semaphore
import kotlinx.coroutines.sync.withPermit
import kotlinx.serialization.Serializable
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.CollectionType
import org.jellyfin.sdk.model.serializer.UUIDSerializer
@ -62,6 +64,7 @@ class HomeSettingsViewModel
@Inject
constructor(
@param:ApplicationContext private val context: Context,
private val api: ApiClient,
private val homeSettingsService: HomeSettingsService,
private val serverRepository: ServerRepository,
private val userPreferencesService: UserPreferencesService,
@ -534,6 +537,108 @@ class HomeSettingsViewModel
context.getString(R.string.settings_saved),
Toast.LENGTH_SHORT,
)
fun applyPreset(preset: HomeRowPresets) {
_state.update { it.copy(loading = LoadingState.Loading) }
viewModelScope.launchIO {
val state = state.value
val typeCache = mutableMapOf<UUID, CollectionType?>()
suspend fun getCollectionType(itemId: UUID): CollectionType =
typeCache.getOrPut(itemId) {
state.libraries
.firstOrNull { it.itemId == itemId }
?.collectionType
?: api.userLibraryApi
.getItem(itemId)
.content.collectionType ?: CollectionType.UNKNOWN
} ?: CollectionType.UNKNOWN
val newRows =
state.rows.map {
val newConfig =
when (it.config) {
is ContinueWatching,
is NextUp,
is ContinueWatchingCombined,
-> {
it.config.updateViewOptions(preset.continueWatching)
}
is HomeRowConfig.ByParent -> {
val collectionType = getCollectionType(it.config.parentId)
val viewOptions = preset.getByCollectionType(collectionType)
it.config.updateViewOptions(viewOptions)
}
is HomeRowConfig.Favorite -> {
val viewOptions =
when (it.config.kind) {
BaseItemKind.MOVIE -> preset.movieLibrary
BaseItemKind.SERIES -> preset.tvLibrary
BaseItemKind.EPISODE -> preset.continueWatching
BaseItemKind.VIDEO -> preset.videoLibrary
BaseItemKind.PLAYLIST -> preset.playlist
BaseItemKind.PERSON -> preset.movieLibrary
else -> preset.movieLibrary
}
it.config.updateViewOptions(viewOptions)
}
is Genres -> {
it.config.updateViewOptions(it.config.viewOptions.copy(heightDp = preset.genreSize))
}
is HomeRowConfig.GetItems -> {
it.config
}
is RecentlyAdded -> {
val collectionType = getCollectionType(it.config.parentId)
val viewOptions = preset.getByCollectionType(collectionType)
it.config.updateViewOptions(viewOptions)
}
is RecentlyReleased -> {
val collectionType = getCollectionType(it.config.parentId)
val viewOptions = preset.getByCollectionType(collectionType)
it.config.updateViewOptions(viewOptions)
}
is HomeRowConfig.Recordings -> {
it.config.updateViewOptions(preset.tvLibrary)
}
is Suggestions -> {
val collectionType = getCollectionType(it.config.parentId)
val viewOptions = preset.getByCollectionType(collectionType)
it.config.updateViewOptions(viewOptions)
}
is HomeRowConfig.TvPrograms -> {
it.config.updateViewOptions(preset.tvLibrary)
}
}
it.copy(config = newConfig)
}
_state.update {
it.copy(
loading = LoadingState.Success,
rows = newRows,
rowData =
it.rowData.toMutableList().mapIndexed { index, row ->
if (row is HomeRowLoadingState.Success) {
row.copy(viewOptions = newRows[index].config.viewOptions)
} else {
row
}
},
)
}
}
}
}
data class HomePageSettingsState(

View file

@ -513,6 +513,8 @@
<string name="decrease_all_cards_size">Decrease size for all cards</string>
<string name="use_thumb_images">Use thumb images</string>
<string name="settings_saved">Settings saved</string>
<string name="display_presets">Display presets</string>
<string name="display_presets_description">Built-in presets to quickly style all rows</string>
<string-array name="theme_song_volume">
<item>Disabled</item>