Improvements to home page customization (#1458)

## Description
Adds a few enhancements to home page customization:
- Add option to combined continue watching & next up to split it
- Add option to continue watching and next up rows to combine them
- Add option for sorting playlist or collections
- Show suggestions (recently updated) when searching for a playlist or
collection to add
- Better handling for loading vs unsupported types for suggestions

Also, the old toggle for combining continue watching & next up in
advanced settings is removed. Now by default the rows will be combined
unless changed in the home page customization settings.

### Related issues
Closes #340
Closes #1383
Closes #1350

### Testing
Just on the emulator so far

## Screenshots
N/A

## AI or LLM usage
None
This commit is contained in:
Damontecres 2026-05-26 13:30:00 -04:00 committed by GitHub
parent 23856ffcf8
commit 8ea6bbbfca
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
13 changed files with 497 additions and 134 deletions

View file

@ -202,18 +202,6 @@ sealed interface AppPreference<Pref, T> {
},
)
val CombineContinueNext =
AppSwitchPreference<AppPreferences>(
title = R.string.combine_continue_next,
defaultValue = false,
getter = { it.homePagePreferences.combineContinueNext },
setter = { prefs, value ->
prefs.updateHomePagePreferences { combineContinueNext = value }
},
summaryOn = R.string.enabled,
summaryOff = R.string.disabled,
)
val RewatchNextUp =
AppSwitchPreference<AppPreferences>(
title = R.string.rewatch_next_up,
@ -1202,7 +1190,6 @@ val advancedPreferences =
AppPreference.BackdropStylePref,
AppPreference.ShowLogos,
AppPreference.ManageMedia,
AppPreference.CombineContinueNext,
AppPreference.DisplayTogglesPref,
// Temporarily disabled, see https://github.com/damontecres/Wholphin/pull/127#issuecomment-3478058418
// AppPreference.NavDrawerSwitchOnFocus,

View file

@ -85,7 +85,6 @@ class AppPreferencesSerializer
.apply {
maxItemsPerRow = AppPreference.HomePageItems.defaultValue.toInt()
enableRewatchingNextUp = AppPreference.RewatchNextUp.defaultValue
combineContinueNext = AppPreference.CombineContinueNext.defaultValue
maxDaysNextUp = AppPreference.MaxDaysNextUp.defaultValue.toInt()
}.build()
interfacePreferences =

View file

@ -33,7 +33,6 @@ import com.github.damontecres.wholphin.util.GetPersonsHandler
import com.github.damontecres.wholphin.util.GetRecordingsRequestHandler
import com.github.damontecres.wholphin.util.GetStudiosRequestHandler
import com.github.damontecres.wholphin.util.HomeRowLoadingState
import com.github.damontecres.wholphin.util.HomeRowLoadingState.Error
import com.github.damontecres.wholphin.util.HomeRowLoadingState.Success
import com.github.damontecres.wholphin.util.supportedHomeCollectionTypes
import dagger.hilt.android.qualifiers.ApplicationContext
@ -274,9 +273,6 @@ class HomeSettingsService
navDrawerService.getAllUserLibraries(userId, userDto?.tvAccess ?: false)
}
val prefs =
userPreferencesService.getCurrent().appPreferences.homePagePreferences
val includedIds =
libraries
.mapIndexed { index, it ->
@ -296,30 +292,15 @@ class HomeSettingsService
)
}
}
val continueWatchingRows =
if (prefs.combineContinueNext) {
listOf(
HomeRowConfigDisplay(
id = includedIds.size + 1,
title = ResStringProvider(R.string.combine_continue_next),
config = HomeRowConfig.ContinueWatchingCombined(),
),
)
} else {
listOf(
HomeRowConfigDisplay(
id = includedIds.size + 1,
title = ResStringProvider(R.string.continue_watching),
config = HomeRowConfig.ContinueWatching(),
),
HomeRowConfigDisplay(
id = includedIds.size + 2,
title = ResStringProvider(R.string.next_up),
config = HomeRowConfig.NextUp(),
),
)
}
val rowConfig = continueWatchingRows + includedIds
val continueWatchingRow =
listOf(
HomeRowConfigDisplay(
id = includedIds.size + 1,
title = ResStringProvider(R.string.combine_continue_next),
config = HomeRowConfig.ContinueWatchingCombined(),
),
)
val rowConfig = continueWatchingRow + includedIds
return HomePageResolvedSettings(rowConfig)
}
@ -864,8 +845,24 @@ class HomeSettingsService
userId = userDto.id,
parentId = row.parentId,
recursive = row.recursive,
sortBy = row.sort?.let { listOf(it.sort) },
sortOrder = row.sort?.let { listOf(it.direction) },
sortBy =
row.sort?.let {
buildList {
add(it.sort)
if (it.sort != ItemSortBy.SORT_NAME) {
add(ItemSortBy.SORT_NAME)
}
}
},
sortOrder =
row.sort?.let {
buildList {
add(it.direction)
if (it.sort != ItemSortBy.SORT_NAME) {
add(SortOrder.ASCENDING)
}
}
},
limit = limit,
fields = DefaultItemFields,
)
@ -1090,29 +1087,39 @@ class HomeSettingsService
.content
val title = ResArgStringProvider(R.string.suggestions_for, library.name ?: "")
val itemKind = SuggestionsWorker.getTypeForCollection(library.collectionType)
val suggestions =
itemKind?.let {
if (itemKind != null) {
val suggestions =
suggestionService
.getSuggestionsFlow(row.parentId, itemKind)
.firstOrNull()
when (suggestions) {
SuggestionsResource.Empty -> {
Success(
title,
listOf(),
row.viewOptions,
rowType = row,
)
}
is SuggestionsResource.Success -> {
Success(
title,
suggestions.items,
row.viewOptions,
rowType = row,
)
}
SuggestionsResource.Loading,
null,
-> {
HomeRowLoadingState.Loading(title)
}
}
if (suggestions != null && suggestions is SuggestionsResource.Success) {
Success(
title,
suggestions.items,
row.viewOptions,
rowType = row,
)
} else if (suggestions is SuggestionsResource.Empty) {
Success(
title,
listOf(),
row.viewOptions,
rowType = row,
)
} else {
Error(
title,
HomeRowLoadingState.Error(
title = title,
message = "Unsupported type ${library.collectionType}",
)
}

View file

@ -8,7 +8,6 @@ import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.SpanStyle
import androidx.compose.ui.text.buildAnnotatedString
@ -16,11 +15,9 @@ import androidx.compose.ui.text.withStyle
import androidx.tv.material3.Text
import com.github.damontecres.wholphin.R
import com.github.damontecres.wholphin.ui.FontAwesome
import com.github.damontecres.wholphin.ui.PreviewTvSpec
import com.github.damontecres.wholphin.ui.data.SortAndDirection
import com.github.damontecres.wholphin.ui.data.flip
import com.github.damontecres.wholphin.ui.data.getStringRes
import com.github.damontecres.wholphin.ui.theme.WholphinTheme
import org.jellyfin.sdk.model.api.ItemSortBy
import org.jellyfin.sdk.model.api.SortOrder
@ -37,10 +34,8 @@ fun SortByButton(
modifier: Modifier = Modifier,
) {
val currentSort = current.sort
val name = stringResource(getStringRes(currentSort))
val currentDirection = current.direction
var sortByDropDown by remember { mutableStateOf(false) }
val context = LocalContext.current
Box(modifier = modifier) {
TextButton(
@ -50,22 +45,7 @@ fun SortByButton(
},
) {
Text(
text =
buildAnnotatedString {
withStyle(SpanStyle(fontFamily = FontAwesome)) {
append(
stringResource(
if (currentDirection == SortOrder.ASCENDING) {
R.string.fa_caret_up
} else {
R.string.fa_caret_down
},
),
)
}
append(" ")
append(name)
},
text = current.toAnnotatedString(),
)
}
@ -117,3 +97,21 @@ fun SortByButton(
}
}
}
@Composable
fun SortAndDirection.toAnnotatedString() =
buildAnnotatedString {
withStyle(SpanStyle(fontFamily = FontAwesome)) {
append(
stringResource(
if (direction == SortOrder.ASCENDING) {
R.string.fa_caret_up
} else {
R.string.fa_caret_down
},
),
)
}
append(" ")
append(stringResource(getStringRes(sort)))
}

View file

@ -73,6 +73,7 @@ val VideoSortOptions =
val PlaylistSortOptions =
listOf(
ItemSortBy.DEFAULT,
ItemSortBy.SORT_NAME,
ItemSortBy.PREMIERE_DATE,
ItemSortBy.DATE_CREATED,

View file

@ -9,6 +9,8 @@ import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxHeight
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.material3.HorizontalDivider
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.collectAsState
@ -43,6 +45,7 @@ import com.github.damontecres.wholphin.R
import com.github.damontecres.wholphin.data.model.BaseItem
import com.github.damontecres.wholphin.ui.Cards
import com.github.damontecres.wholphin.ui.cards.ItemRow
import com.github.damontecres.wholphin.ui.cards.ItemRowTitle
import com.github.damontecres.wholphin.ui.cards.SeasonCard
import com.github.damontecres.wholphin.ui.components.BasicDialog
import com.github.damontecres.wholphin.ui.components.ErrorMessage
@ -57,7 +60,13 @@ fun SearchForContent(
searchType: BaseItemKind,
onClick: (BaseItem) -> Unit,
modifier: Modifier = Modifier,
viewModel: SearchForViewModel = hiltViewModel(key = searchType.serialName),
viewModel: SearchForViewModel =
hiltViewModel<SearchForViewModel, SearchForViewModel.Factory>(
key = searchType.serialName,
creationCallback = {
it.create(searchType)
},
),
) {
val focusManager = LocalFocusManager.current
val keyboardController = LocalSoftwareKeyboardController.current
@ -92,6 +101,7 @@ fun SearchForContent(
}
}
}
val titleRes =
remember {
when (searchType) {
@ -178,53 +188,91 @@ fun SearchForContent(
}
}
when (val st = state.results) {
is SearchResult.Error -> {
ErrorMessage("Error", st.ex)
}
SearchResult.NoQuery -> {
// no-op
}
SearchResult.Searching -> {
Text(
text = stringResource(R.string.searching),
LazyColumn(
verticalArrangement = Arrangement.spacedBy(8.dp),
) {
item {
SearchForResultsRow(
title = stringResource(R.string.results),
results = state.results,
onClick = onClick,
modifier = Modifier.focusRequester(focusRequester),
)
}
is SearchResult.SuccessSeerr -> {
Text(
text = "Not supported",
color = MaterialTheme.colorScheme.error,
item {
HorizontalDivider()
}
item {
SearchForResultsRow(
title = stringResource(R.string.suggestions),
results = state.recent,
onClick = onClick,
modifier = Modifier,
)
}
}
}
}
is SearchResult.Success -> {
if (st.items.isEmpty()) {
Text(
text = stringResource(R.string.no_results),
)
} else {
ItemRow(
title = "",
items = st.items,
onClickItem = { _, item -> onClick.invoke(item) },
onLongClickItem = { _, _ -> },
modifier = Modifier.focusRequester(focusRequester),
cardContent = { index, item, mod, onClick, onLongClick ->
SeasonCard(
item = item,
onClick = {
onClick.invoke()
},
onLongClick = onLongClick,
imageHeight = Cards.height2x3,
modifier = mod,
)
},
)
}
@Composable
fun SearchForResultsRow(
title: String,
results: SearchResult,
onClick: (BaseItem) -> Unit,
modifier: Modifier = Modifier,
) {
when (val st = results) {
is SearchResult.Error -> {
ErrorMessage("Error", st.ex, modifier)
}
SearchResult.NoQuery -> {
ItemRowTitle(
title = stringResource(R.string.no_results),
modifier = modifier,
)
}
SearchResult.Searching -> {
ItemRowTitle(
title = stringResource(R.string.searching),
modifier = modifier,
)
}
is SearchResult.SuccessSeerr -> {
Text(
text = "Not supported",
color = MaterialTheme.colorScheme.error,
modifier = modifier,
)
}
is SearchResult.Success -> {
if (st.items.isEmpty()) {
ItemRowTitle(
title = stringResource(R.string.no_results),
modifier = modifier,
)
} else {
ItemRow(
title = title,
items = st.items,
onClickItem = { _, item -> onClick.invoke(item) },
onLongClickItem = { _, _ -> },
modifier = modifier,
cardContent = { index, item, mod, onClick, onLongClick ->
SeasonCard(
item = item,
onClick = {
onClick.invoke()
},
onLongClick = onLongClick,
imageHeight = Cards.heightEpisode,
modifier = mod,
)
},
)
}
}
}
@ -242,6 +290,7 @@ fun SearchForDialog(
DialogProperties(
usePlatformDefaultWidth = false,
),
elevation = 3.dp,
) {
SearchForContent(
searchType = searchType,

View file

@ -8,30 +8,68 @@ import com.github.damontecres.wholphin.ui.SlimItemFields
import com.github.damontecres.wholphin.ui.components.VoiceInputManager
import com.github.damontecres.wholphin.ui.launchIO
import com.github.damontecres.wholphin.ui.main.SearchResult
import com.github.damontecres.wholphin.ui.toBaseItems
import com.github.damontecres.wholphin.util.ApiRequestPager
import com.github.damontecres.wholphin.util.GetItemsRequestHandler
import dagger.assisted.Assisted
import dagger.assisted.AssistedFactory
import dagger.assisted.AssistedInject
import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.CancellationException
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.update
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
@HiltViewModel
@HiltViewModel(assistedFactory = SearchForViewModel.Factory::class)
class SearchForViewModel
@Inject
@AssistedInject
constructor(
private val api: ApiClient,
private val serverRepository: ServerRepository,
val navigationManager: NavigationManager,
val voiceInputManager: VoiceInputManager,
@Assisted val searchType: BaseItemKind,
) : ViewModel() {
@AssistedFactory
interface Factory {
fun create(searchType: BaseItemKind): SearchForViewModel
}
val state = MutableStateFlow(SearchForState())
init {
state.value = SearchForState()
viewModelScope.launchIO {
try {
val request =
GetItemsRequest(
userId = serverRepository.currentUser?.id,
includeItemTypes = listOf(searchType),
recursive = true,
fields = SlimItemFields,
sortBy = listOf(ItemSortBy.DATE_LAST_CONTENT_ADDED),
sortOrder = listOf(SortOrder.DESCENDING),
limit = 25,
)
val recent = api.itemsApi.getItems(request).toBaseItems(api, false)
state.update {
it.copy(recent = SearchResult.Success(recent))
}
} catch (ex: CancellationException) {
throw ex
} catch (ex: Exception) {
Timber.e(ex, "Error fetching recent %s", searchType)
state.update {
it.copy(recent = SearchResult.Error(ex))
}
}
}
}
fun search(
@ -41,10 +79,10 @@ class SearchForViewModel
viewModelScope.launchIO {
if (state.value.query != query) {
if (query.isBlank()) {
state.update { SearchForState(query, SearchResult.NoQuery) }
state.update { it.copy(query = query, results = SearchResult.NoQuery) }
return@launchIO
}
state.update { SearchForState(query, SearchResult.Searching) }
state.update { it.copy(query = query, results = SearchResult.NoQuery) }
try {
val request =
GetItemsRequest(
@ -55,10 +93,15 @@ class SearchForViewModel
fields = SlimItemFields,
)
val pager = ApiRequestPager(api, request, GetItemsRequestHandler, viewModelScope).init()
state.update { SearchForState(query, SearchResult.Success(pager)) }
state.update {
it.copy(
query = query,
results = SearchResult.Success(pager),
)
}
} catch (ex: Exception) {
Timber.e(ex)
state.update { SearchForState(query, SearchResult.Error(ex)) }
state.update { it.copy(query = query, results = SearchResult.Error(ex)) }
}
}
}
@ -67,5 +110,6 @@ class SearchForViewModel
data class SearchForState(
val query: String = "",
val recent: SearchResult = SearchResult.Searching,
val results: SearchResult = SearchResult.NoQuery,
)

View file

@ -0,0 +1,7 @@
package com.github.damontecres.wholphin.ui.main.settings
sealed interface HomeRowConfigAction {
data object Combine : HomeRowConfigAction
data object Split : HomeRowConfigAction
}

View file

@ -1,17 +1,32 @@
package com.github.damontecres.wholphin.ui.main.settings
import android.view.Gravity
import androidx.compose.foundation.interaction.MutableInteractionSource
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.LazyListScope
import androidx.compose.foundation.lazy.items
import androidx.compose.foundation.lazy.itemsIndexed
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
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.focus.FocusRequester
import androidx.compose.ui.focus.focusRequester
import androidx.compose.ui.platform.LocalView
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp
import androidx.compose.ui.window.DialogProperties
import androidx.compose.ui.window.DialogWindowProvider
import androidx.tv.material3.ListItem
import androidx.tv.material3.Text
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.AppChoicePreference
import com.github.damontecres.wholphin.preferences.AppClickablePreference
@ -21,18 +36,31 @@ import com.github.damontecres.wholphin.preferences.AppSwitchPreference
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.FontAwesome
import com.github.damontecres.wholphin.ui.components.BasicDialog
import com.github.damontecres.wholphin.ui.components.ViewOptionImageType
import com.github.damontecres.wholphin.ui.components.toAnnotatedString
import com.github.damontecres.wholphin.ui.data.BoxSetSortOptions
import com.github.damontecres.wholphin.ui.data.SortAndDirection
import com.github.damontecres.wholphin.ui.data.getStringRes
import com.github.damontecres.wholphin.ui.ifElse
import com.github.damontecres.wholphin.ui.preferences.ClickPreference
import com.github.damontecres.wholphin.ui.preferences.ComposablePreference
import com.github.damontecres.wholphin.ui.preferences.PreferenceGroup
import com.github.damontecres.wholphin.ui.preferences.PreferenceTitle
import com.github.damontecres.wholphin.ui.tryRequestFocus
import org.jellyfin.sdk.model.api.ItemSortBy
import org.jellyfin.sdk.model.api.SortOrder
@Composable
fun HomeRowSettings(
title: String,
config: HomeRowConfig,
preferenceOptions: List<PreferenceGroup<HomeRowViewOptions>>,
viewOptions: HomeRowViewOptions,
onViewOptionsChange: (HomeRowViewOptions) -> Unit,
onConfigChange: (HomeRowConfig) -> Unit,
onConfigAction: (HomeRowConfigAction) -> Unit,
onApplyApplyAll: () -> Unit,
modifier: Modifier = Modifier,
defaultViewOptions: HomeRowViewOptions = HomeRowViewOptions(),
@ -95,6 +123,135 @@ fun HomeRowSettings(
)
}
}
addAdditionalConfig(
config = config,
onConfigChange = onConfigChange,
onConfigAction = onConfigAction,
)
}
}
}
private fun LazyListScope.addAdditionalConfig(
config: HomeRowConfig,
onConfigChange: (HomeRowConfig) -> Unit,
onConfigAction: (HomeRowConfigAction) -> Unit,
) {
when (config) {
is HomeRowConfig.ContinueWatchingCombined -> {
item {
ClickPreference(
title = stringResource(R.string.split_into_separate_rows),
onClick = { onConfigAction.invoke(HomeRowConfigAction.Split) },
)
}
}
is HomeRowConfig.ContinueWatching,
is HomeRowConfig.NextUp,
-> {
item {
ClickPreference(
title = stringResource(R.string.combine_continue_next),
onClick = { onConfigAction.invoke(HomeRowConfigAction.Combine) },
)
}
}
is HomeRowConfig.ByParent -> {
item {
val sortAndDirection =
remember(config) {
config.sort ?: SortAndDirection(
ItemSortBy.DEFAULT,
SortOrder.ASCENDING,
)
}
var showDialog by remember { mutableStateOf(false) }
ListItem(
selected = false,
onClick = { showDialog = true },
headlineContent = {
PreferenceTitle(stringResource(R.string.sort))
},
supportingContent = {
Text(
text = sortAndDirection.toAnnotatedString(),
)
},
)
if (showDialog) {
BasicDialog(
onDismissRequest = { showDialog = false },
properties = DialogProperties(usePlatformDefaultWidth = false),
elevation = 5.dp,
) {
val dialogWindowProvider = LocalView.current.parent as? DialogWindowProvider
dialogWindowProvider?.window?.let { window ->
window.setGravity(Gravity.START)
}
ByParentSortDialogContent(
current = sortAndDirection,
onClick = {
val newSort =
if (config.sort?.sort == it) {
config.sort.flip()
} else if (it == ItemSortBy.DEFAULT) {
null
} else {
SortAndDirection(it, SortOrder.ASCENDING)
}
onConfigChange.invoke(config.copy(sort = newSort))
showDialog = false
},
modifier = Modifier.width(320.dp),
)
}
}
}
}
else -> {}
}
}
@Composable
fun ByParentSortDialogContent(
current: SortAndDirection,
onClick: (ItemSortBy) -> Unit,
modifier: Modifier = Modifier,
) {
LazyColumn(
contentPadding = PaddingValues(8.dp),
modifier = modifier,
) {
items(BoxSetSortOptions) { sort ->
ListItem(
selected = sort == current.sort,
onClick = { onClick.invoke(sort) },
leadingContent = {
Text(
fontFamily = FontAwesome,
text =
if (sort == current.sort) {
stringResource(
if (current.direction == SortOrder.ASCENDING) {
R.string.fa_caret_up
} else {
R.string.fa_caret_down
},
)
} else {
""
},
)
},
headlineContent = {
Text(
text = stringResource(getStringRes(sort)),
)
},
)
}
}
}

View file

@ -232,6 +232,22 @@ fun HomeSettingsPage(
viewModel.updateViewOptionsForAll(row.config.viewOptions)
},
modifier = destModifier,
config = row.config,
onConfigChange = {
viewModel.onConfigChange(row, it)
},
onConfigAction = {
viewModel.onConfigAction(row, it)
when (it) {
HomeRowConfigAction.Combine,
HomeRowConfigAction.Split,
-> {
backStack.removeAt(
backStack.lastIndex,
)
}
}
},
)
}

View file

@ -34,6 +34,7 @@ import com.github.damontecres.wholphin.services.UserPreferencesService
import com.github.damontecres.wholphin.services.hilt.IoCoroutineScope
import com.github.damontecres.wholphin.services.tvAccess
import com.github.damontecres.wholphin.ui.AspectRatio
import com.github.damontecres.wholphin.ui.launchDefault
import com.github.damontecres.wholphin.ui.launchIO
import com.github.damontecres.wholphin.ui.showToast
import com.github.damontecres.wholphin.ui.util.ResArgStringProvider
@ -782,6 +783,101 @@ class HomeSettingsViewModel
}
}
}
fun onConfigAction(
row: HomeRowConfigDisplay,
action: HomeRowConfigAction,
) {
viewModelScope.launchDefault {
when (action) {
HomeRowConfigAction.Combine -> {
val index =
state.value.rows
.indexOf(row)
.coerceAtLeast(0)
val rowsToRemove =
state.value.rows
.filter { it.config is ContinueWatching || it.config is NextUp }
updateState {
it.copy(
loading = LoadingState.Loading,
rows =
it.rows.toMutableList().apply {
set(
index,
HomeRowConfigDisplay(
id = it.rows[index].id,
title = ResStringProvider(R.string.combine_continue_next),
config = ContinueWatchingCombined(row.config.viewOptions),
),
)
removeAll(rowsToRemove)
},
)
}
fetchRowData()
}
HomeRowConfigAction.Split -> {
val index =
state.value.rows
.indexOf(row)
.coerceAtLeast(0)
updateState {
it.copy(
loading = LoadingState.Loading,
rows =
it.rows.toMutableList().apply {
set(
index,
HomeRowConfigDisplay(
id = it.rows[index].id,
title = ResStringProvider(R.string.continue_watching),
config = ContinueWatching(row.config.viewOptions),
),
)
add(
index + 1,
HomeRowConfigDisplay(
id = idCounter++,
title = ResStringProvider(R.string.next_up),
config = NextUp(row.config.viewOptions),
),
)
},
)
}
fetchRowData()
}
}
}
}
fun onConfigChange(
row: HomeRowConfigDisplay,
config: HomeRowConfig,
) {
viewModelScope.launchDefault {
val index =
state.value.rows
.indexOf(row)
.coerceAtLeast(0)
updateState {
it.copy(
loading = LoadingState.Loading,
rows =
it.rows.toMutableList().apply {
set(
index,
row.copy(config = config),
)
},
)
}
fetchRowData()
}
}
}
data class HomePageSettingsState(

View file

@ -92,7 +92,7 @@ message PlaybackPreferences {
message HomePagePreferences{
int32 max_items_per_row = 1;
bool enable_rewatching_next_up = 2;
bool combine_continue_next = 3;
bool combine_continue_next = 3 [deprecated = true];
int32 max_days_next_up = 4;
bool click_to_play = 5;
}

View file

@ -820,4 +820,6 @@
</array>
<string name="stars">Stars</string>
<string name="tomatoes">Tomatoes</string>
<string name="sort">Sort</string>
<string name="split_into_separate_rows">Split into separate rows</string>
</resources>