mirror of
https://github.com/JustinZeus/Wholphin.git
synced 2026-07-09 08:01:20 +02:00
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:
parent
23856ffcf8
commit
8ea6bbbfca
13 changed files with 497 additions and 134 deletions
|
|
@ -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 =
|
val RewatchNextUp =
|
||||||
AppSwitchPreference<AppPreferences>(
|
AppSwitchPreference<AppPreferences>(
|
||||||
title = R.string.rewatch_next_up,
|
title = R.string.rewatch_next_up,
|
||||||
|
|
@ -1202,7 +1190,6 @@ val advancedPreferences =
|
||||||
AppPreference.BackdropStylePref,
|
AppPreference.BackdropStylePref,
|
||||||
AppPreference.ShowLogos,
|
AppPreference.ShowLogos,
|
||||||
AppPreference.ManageMedia,
|
AppPreference.ManageMedia,
|
||||||
AppPreference.CombineContinueNext,
|
|
||||||
AppPreference.DisplayTogglesPref,
|
AppPreference.DisplayTogglesPref,
|
||||||
// Temporarily disabled, see https://github.com/damontecres/Wholphin/pull/127#issuecomment-3478058418
|
// Temporarily disabled, see https://github.com/damontecres/Wholphin/pull/127#issuecomment-3478058418
|
||||||
// AppPreference.NavDrawerSwitchOnFocus,
|
// AppPreference.NavDrawerSwitchOnFocus,
|
||||||
|
|
|
||||||
|
|
@ -85,7 +85,6 @@ class AppPreferencesSerializer
|
||||||
.apply {
|
.apply {
|
||||||
maxItemsPerRow = AppPreference.HomePageItems.defaultValue.toInt()
|
maxItemsPerRow = AppPreference.HomePageItems.defaultValue.toInt()
|
||||||
enableRewatchingNextUp = AppPreference.RewatchNextUp.defaultValue
|
enableRewatchingNextUp = AppPreference.RewatchNextUp.defaultValue
|
||||||
combineContinueNext = AppPreference.CombineContinueNext.defaultValue
|
|
||||||
maxDaysNextUp = AppPreference.MaxDaysNextUp.defaultValue.toInt()
|
maxDaysNextUp = AppPreference.MaxDaysNextUp.defaultValue.toInt()
|
||||||
}.build()
|
}.build()
|
||||||
interfacePreferences =
|
interfacePreferences =
|
||||||
|
|
|
||||||
|
|
@ -33,7 +33,6 @@ import com.github.damontecres.wholphin.util.GetPersonsHandler
|
||||||
import com.github.damontecres.wholphin.util.GetRecordingsRequestHandler
|
import com.github.damontecres.wholphin.util.GetRecordingsRequestHandler
|
||||||
import com.github.damontecres.wholphin.util.GetStudiosRequestHandler
|
import com.github.damontecres.wholphin.util.GetStudiosRequestHandler
|
||||||
import com.github.damontecres.wholphin.util.HomeRowLoadingState
|
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.HomeRowLoadingState.Success
|
||||||
import com.github.damontecres.wholphin.util.supportedHomeCollectionTypes
|
import com.github.damontecres.wholphin.util.supportedHomeCollectionTypes
|
||||||
import dagger.hilt.android.qualifiers.ApplicationContext
|
import dagger.hilt.android.qualifiers.ApplicationContext
|
||||||
|
|
@ -274,9 +273,6 @@ class HomeSettingsService
|
||||||
navDrawerService.getAllUserLibraries(userId, userDto?.tvAccess ?: false)
|
navDrawerService.getAllUserLibraries(userId, userDto?.tvAccess ?: false)
|
||||||
}
|
}
|
||||||
|
|
||||||
val prefs =
|
|
||||||
userPreferencesService.getCurrent().appPreferences.homePagePreferences
|
|
||||||
|
|
||||||
val includedIds =
|
val includedIds =
|
||||||
libraries
|
libraries
|
||||||
.mapIndexed { index, it ->
|
.mapIndexed { index, it ->
|
||||||
|
|
@ -296,30 +292,15 @@ class HomeSettingsService
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
val continueWatchingRows =
|
val continueWatchingRow =
|
||||||
if (prefs.combineContinueNext) {
|
listOf(
|
||||||
listOf(
|
HomeRowConfigDisplay(
|
||||||
HomeRowConfigDisplay(
|
id = includedIds.size + 1,
|
||||||
id = includedIds.size + 1,
|
title = ResStringProvider(R.string.combine_continue_next),
|
||||||
title = ResStringProvider(R.string.combine_continue_next),
|
config = HomeRowConfig.ContinueWatchingCombined(),
|
||||||
config = HomeRowConfig.ContinueWatchingCombined(),
|
),
|
||||||
),
|
)
|
||||||
)
|
val rowConfig = continueWatchingRow + includedIds
|
||||||
} 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
|
|
||||||
return HomePageResolvedSettings(rowConfig)
|
return HomePageResolvedSettings(rowConfig)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -864,8 +845,24 @@ class HomeSettingsService
|
||||||
userId = userDto.id,
|
userId = userDto.id,
|
||||||
parentId = row.parentId,
|
parentId = row.parentId,
|
||||||
recursive = row.recursive,
|
recursive = row.recursive,
|
||||||
sortBy = row.sort?.let { listOf(it.sort) },
|
sortBy =
|
||||||
sortOrder = row.sort?.let { listOf(it.direction) },
|
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,
|
limit = limit,
|
||||||
fields = DefaultItemFields,
|
fields = DefaultItemFields,
|
||||||
)
|
)
|
||||||
|
|
@ -1090,29 +1087,39 @@ class HomeSettingsService
|
||||||
.content
|
.content
|
||||||
val title = ResArgStringProvider(R.string.suggestions_for, library.name ?: "")
|
val title = ResArgStringProvider(R.string.suggestions_for, library.name ?: "")
|
||||||
val itemKind = SuggestionsWorker.getTypeForCollection(library.collectionType)
|
val itemKind = SuggestionsWorker.getTypeForCollection(library.collectionType)
|
||||||
val suggestions =
|
if (itemKind != null) {
|
||||||
itemKind?.let {
|
val suggestions =
|
||||||
suggestionService
|
suggestionService
|
||||||
.getSuggestionsFlow(row.parentId, itemKind)
|
.getSuggestionsFlow(row.parentId, itemKind)
|
||||||
.firstOrNull()
|
.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 {
|
} else {
|
||||||
Error(
|
HomeRowLoadingState.Error(
|
||||||
title,
|
title = title,
|
||||||
message = "Unsupported type ${library.collectionType}",
|
message = "Unsupported type ${library.collectionType}",
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -8,7 +8,6 @@ import androidx.compose.runtime.mutableStateOf
|
||||||
import androidx.compose.runtime.remember
|
import androidx.compose.runtime.remember
|
||||||
import androidx.compose.runtime.setValue
|
import androidx.compose.runtime.setValue
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.platform.LocalContext
|
|
||||||
import androidx.compose.ui.res.stringResource
|
import androidx.compose.ui.res.stringResource
|
||||||
import androidx.compose.ui.text.SpanStyle
|
import androidx.compose.ui.text.SpanStyle
|
||||||
import androidx.compose.ui.text.buildAnnotatedString
|
import androidx.compose.ui.text.buildAnnotatedString
|
||||||
|
|
@ -16,11 +15,9 @@ import androidx.compose.ui.text.withStyle
|
||||||
import androidx.tv.material3.Text
|
import androidx.tv.material3.Text
|
||||||
import com.github.damontecres.wholphin.R
|
import com.github.damontecres.wholphin.R
|
||||||
import com.github.damontecres.wholphin.ui.FontAwesome
|
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.SortAndDirection
|
||||||
import com.github.damontecres.wholphin.ui.data.flip
|
import com.github.damontecres.wholphin.ui.data.flip
|
||||||
import com.github.damontecres.wholphin.ui.data.getStringRes
|
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.ItemSortBy
|
||||||
import org.jellyfin.sdk.model.api.SortOrder
|
import org.jellyfin.sdk.model.api.SortOrder
|
||||||
|
|
||||||
|
|
@ -37,10 +34,8 @@ fun SortByButton(
|
||||||
modifier: Modifier = Modifier,
|
modifier: Modifier = Modifier,
|
||||||
) {
|
) {
|
||||||
val currentSort = current.sort
|
val currentSort = current.sort
|
||||||
val name = stringResource(getStringRes(currentSort))
|
|
||||||
val currentDirection = current.direction
|
val currentDirection = current.direction
|
||||||
var sortByDropDown by remember { mutableStateOf(false) }
|
var sortByDropDown by remember { mutableStateOf(false) }
|
||||||
val context = LocalContext.current
|
|
||||||
|
|
||||||
Box(modifier = modifier) {
|
Box(modifier = modifier) {
|
||||||
TextButton(
|
TextButton(
|
||||||
|
|
@ -50,22 +45,7 @@ fun SortByButton(
|
||||||
},
|
},
|
||||||
) {
|
) {
|
||||||
Text(
|
Text(
|
||||||
text =
|
text = current.toAnnotatedString(),
|
||||||
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)
|
|
||||||
},
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -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)))
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -73,6 +73,7 @@ val VideoSortOptions =
|
||||||
|
|
||||||
val PlaylistSortOptions =
|
val PlaylistSortOptions =
|
||||||
listOf(
|
listOf(
|
||||||
|
ItemSortBy.DEFAULT,
|
||||||
ItemSortBy.SORT_NAME,
|
ItemSortBy.SORT_NAME,
|
||||||
ItemSortBy.PREMIERE_DATE,
|
ItemSortBy.PREMIERE_DATE,
|
||||||
ItemSortBy.DATE_CREATED,
|
ItemSortBy.DATE_CREATED,
|
||||||
|
|
|
||||||
|
|
@ -9,6 +9,8 @@ import androidx.compose.foundation.layout.Row
|
||||||
import androidx.compose.foundation.layout.fillMaxHeight
|
import androidx.compose.foundation.layout.fillMaxHeight
|
||||||
import androidx.compose.foundation.layout.fillMaxWidth
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
import androidx.compose.foundation.layout.padding
|
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.Composable
|
||||||
import androidx.compose.runtime.LaunchedEffect
|
import androidx.compose.runtime.LaunchedEffect
|
||||||
import androidx.compose.runtime.collectAsState
|
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.data.model.BaseItem
|
||||||
import com.github.damontecres.wholphin.ui.Cards
|
import com.github.damontecres.wholphin.ui.Cards
|
||||||
import com.github.damontecres.wholphin.ui.cards.ItemRow
|
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.cards.SeasonCard
|
||||||
import com.github.damontecres.wholphin.ui.components.BasicDialog
|
import com.github.damontecres.wholphin.ui.components.BasicDialog
|
||||||
import com.github.damontecres.wholphin.ui.components.ErrorMessage
|
import com.github.damontecres.wholphin.ui.components.ErrorMessage
|
||||||
|
|
@ -57,7 +60,13 @@ fun SearchForContent(
|
||||||
searchType: BaseItemKind,
|
searchType: BaseItemKind,
|
||||||
onClick: (BaseItem) -> Unit,
|
onClick: (BaseItem) -> Unit,
|
||||||
modifier: Modifier = Modifier,
|
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 focusManager = LocalFocusManager.current
|
||||||
val keyboardController = LocalSoftwareKeyboardController.current
|
val keyboardController = LocalSoftwareKeyboardController.current
|
||||||
|
|
@ -92,6 +101,7 @@ fun SearchForContent(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
val titleRes =
|
val titleRes =
|
||||||
remember {
|
remember {
|
||||||
when (searchType) {
|
when (searchType) {
|
||||||
|
|
@ -178,53 +188,91 @@ fun SearchForContent(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
when (val st = state.results) {
|
LazyColumn(
|
||||||
is SearchResult.Error -> {
|
verticalArrangement = Arrangement.spacedBy(8.dp),
|
||||||
ErrorMessage("Error", st.ex)
|
) {
|
||||||
}
|
item {
|
||||||
|
SearchForResultsRow(
|
||||||
SearchResult.NoQuery -> {
|
title = stringResource(R.string.results),
|
||||||
// no-op
|
results = state.results,
|
||||||
}
|
onClick = onClick,
|
||||||
|
modifier = Modifier.focusRequester(focusRequester),
|
||||||
SearchResult.Searching -> {
|
|
||||||
Text(
|
|
||||||
text = stringResource(R.string.searching),
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
item {
|
||||||
is SearchResult.SuccessSeerr -> {
|
HorizontalDivider()
|
||||||
Text(
|
}
|
||||||
text = "Not supported",
|
item {
|
||||||
color = MaterialTheme.colorScheme.error,
|
SearchForResultsRow(
|
||||||
|
title = stringResource(R.string.suggestions),
|
||||||
|
results = state.recent,
|
||||||
|
onClick = onClick,
|
||||||
|
modifier = Modifier,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
is SearchResult.Success -> {
|
@Composable
|
||||||
if (st.items.isEmpty()) {
|
fun SearchForResultsRow(
|
||||||
Text(
|
title: String,
|
||||||
text = stringResource(R.string.no_results),
|
results: SearchResult,
|
||||||
)
|
onClick: (BaseItem) -> Unit,
|
||||||
} else {
|
modifier: Modifier = Modifier,
|
||||||
ItemRow(
|
) {
|
||||||
title = "",
|
when (val st = results) {
|
||||||
items = st.items,
|
is SearchResult.Error -> {
|
||||||
onClickItem = { _, item -> onClick.invoke(item) },
|
ErrorMessage("Error", st.ex, modifier)
|
||||||
onLongClickItem = { _, _ -> },
|
}
|
||||||
modifier = Modifier.focusRequester(focusRequester),
|
|
||||||
cardContent = { index, item, mod, onClick, onLongClick ->
|
SearchResult.NoQuery -> {
|
||||||
SeasonCard(
|
ItemRowTitle(
|
||||||
item = item,
|
title = stringResource(R.string.no_results),
|
||||||
onClick = {
|
modifier = modifier,
|
||||||
onClick.invoke()
|
)
|
||||||
},
|
}
|
||||||
onLongClick = onLongClick,
|
|
||||||
imageHeight = Cards.height2x3,
|
SearchResult.Searching -> {
|
||||||
modifier = mod,
|
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(
|
DialogProperties(
|
||||||
usePlatformDefaultWidth = false,
|
usePlatformDefaultWidth = false,
|
||||||
),
|
),
|
||||||
|
elevation = 3.dp,
|
||||||
) {
|
) {
|
||||||
SearchForContent(
|
SearchForContent(
|
||||||
searchType = searchType,
|
searchType = searchType,
|
||||||
|
|
|
||||||
|
|
@ -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.components.VoiceInputManager
|
||||||
import com.github.damontecres.wholphin.ui.launchIO
|
import com.github.damontecres.wholphin.ui.launchIO
|
||||||
import com.github.damontecres.wholphin.ui.main.SearchResult
|
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.ApiRequestPager
|
||||||
import com.github.damontecres.wholphin.util.GetItemsRequestHandler
|
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 dagger.hilt.android.lifecycle.HiltViewModel
|
||||||
|
import kotlinx.coroutines.CancellationException
|
||||||
import kotlinx.coroutines.flow.MutableStateFlow
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
import kotlinx.coroutines.flow.update
|
import kotlinx.coroutines.flow.update
|
||||||
import org.jellyfin.sdk.api.client.ApiClient
|
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.BaseItemKind
|
||||||
|
import org.jellyfin.sdk.model.api.ItemSortBy
|
||||||
|
import org.jellyfin.sdk.model.api.SortOrder
|
||||||
import org.jellyfin.sdk.model.api.request.GetItemsRequest
|
import org.jellyfin.sdk.model.api.request.GetItemsRequest
|
||||||
import timber.log.Timber
|
import timber.log.Timber
|
||||||
import javax.inject.Inject
|
|
||||||
|
|
||||||
@HiltViewModel
|
@HiltViewModel(assistedFactory = SearchForViewModel.Factory::class)
|
||||||
class SearchForViewModel
|
class SearchForViewModel
|
||||||
@Inject
|
@AssistedInject
|
||||||
constructor(
|
constructor(
|
||||||
private val api: ApiClient,
|
private val api: ApiClient,
|
||||||
private val serverRepository: ServerRepository,
|
private val serverRepository: ServerRepository,
|
||||||
val navigationManager: NavigationManager,
|
val navigationManager: NavigationManager,
|
||||||
val voiceInputManager: VoiceInputManager,
|
val voiceInputManager: VoiceInputManager,
|
||||||
|
@Assisted val searchType: BaseItemKind,
|
||||||
) : ViewModel() {
|
) : ViewModel() {
|
||||||
|
@AssistedFactory
|
||||||
|
interface Factory {
|
||||||
|
fun create(searchType: BaseItemKind): SearchForViewModel
|
||||||
|
}
|
||||||
|
|
||||||
val state = MutableStateFlow(SearchForState())
|
val state = MutableStateFlow(SearchForState())
|
||||||
|
|
||||||
init {
|
init {
|
||||||
state.value = SearchForState()
|
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(
|
fun search(
|
||||||
|
|
@ -41,10 +79,10 @@ class SearchForViewModel
|
||||||
viewModelScope.launchIO {
|
viewModelScope.launchIO {
|
||||||
if (state.value.query != query) {
|
if (state.value.query != query) {
|
||||||
if (query.isBlank()) {
|
if (query.isBlank()) {
|
||||||
state.update { SearchForState(query, SearchResult.NoQuery) }
|
state.update { it.copy(query = query, results = SearchResult.NoQuery) }
|
||||||
return@launchIO
|
return@launchIO
|
||||||
}
|
}
|
||||||
state.update { SearchForState(query, SearchResult.Searching) }
|
state.update { it.copy(query = query, results = SearchResult.NoQuery) }
|
||||||
try {
|
try {
|
||||||
val request =
|
val request =
|
||||||
GetItemsRequest(
|
GetItemsRequest(
|
||||||
|
|
@ -55,10 +93,15 @@ class SearchForViewModel
|
||||||
fields = SlimItemFields,
|
fields = SlimItemFields,
|
||||||
)
|
)
|
||||||
val pager = ApiRequestPager(api, request, GetItemsRequestHandler, viewModelScope).init()
|
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) {
|
} catch (ex: Exception) {
|
||||||
Timber.e(ex)
|
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(
|
data class SearchForState(
|
||||||
val query: String = "",
|
val query: String = "",
|
||||||
|
val recent: SearchResult = SearchResult.Searching,
|
||||||
val results: SearchResult = SearchResult.NoQuery,
|
val results: SearchResult = SearchResult.NoQuery,
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,7 @@
|
||||||
|
package com.github.damontecres.wholphin.ui.main.settings
|
||||||
|
|
||||||
|
sealed interface HomeRowConfigAction {
|
||||||
|
data object Combine : HomeRowConfigAction
|
||||||
|
|
||||||
|
data object Split : HomeRowConfigAction
|
||||||
|
}
|
||||||
|
|
@ -1,17 +1,32 @@
|
||||||
package com.github.damontecres.wholphin.ui.main.settings
|
package com.github.damontecres.wholphin.ui.main.settings
|
||||||
|
|
||||||
|
import android.view.Gravity
|
||||||
import androidx.compose.foundation.interaction.MutableInteractionSource
|
import androidx.compose.foundation.interaction.MutableInteractionSource
|
||||||
import androidx.compose.foundation.layout.Column
|
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.LazyColumn
|
||||||
|
import androidx.compose.foundation.lazy.LazyListScope
|
||||||
|
import androidx.compose.foundation.lazy.items
|
||||||
import androidx.compose.foundation.lazy.itemsIndexed
|
import androidx.compose.foundation.lazy.itemsIndexed
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.LaunchedEffect
|
import androidx.compose.runtime.LaunchedEffect
|
||||||
|
import androidx.compose.runtime.getValue
|
||||||
|
import androidx.compose.runtime.mutableStateOf
|
||||||
import androidx.compose.runtime.remember
|
import androidx.compose.runtime.remember
|
||||||
|
import androidx.compose.runtime.setValue
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.focus.FocusRequester
|
import androidx.compose.ui.focus.FocusRequester
|
||||||
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.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.R
|
||||||
|
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.AppChoicePreference
|
import com.github.damontecres.wholphin.preferences.AppChoicePreference
|
||||||
import com.github.damontecres.wholphin.preferences.AppClickablePreference
|
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.preferences.PrefContentScale
|
||||||
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
|
||||||
|
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.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.ifElse
|
||||||
|
import com.github.damontecres.wholphin.ui.preferences.ClickPreference
|
||||||
import com.github.damontecres.wholphin.ui.preferences.ComposablePreference
|
import com.github.damontecres.wholphin.ui.preferences.ComposablePreference
|
||||||
import com.github.damontecres.wholphin.ui.preferences.PreferenceGroup
|
import com.github.damontecres.wholphin.ui.preferences.PreferenceGroup
|
||||||
|
import com.github.damontecres.wholphin.ui.preferences.PreferenceTitle
|
||||||
import com.github.damontecres.wholphin.ui.tryRequestFocus
|
import com.github.damontecres.wholphin.ui.tryRequestFocus
|
||||||
|
import org.jellyfin.sdk.model.api.ItemSortBy
|
||||||
|
import org.jellyfin.sdk.model.api.SortOrder
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun HomeRowSettings(
|
fun HomeRowSettings(
|
||||||
title: String,
|
title: String,
|
||||||
|
config: HomeRowConfig,
|
||||||
preferenceOptions: List<PreferenceGroup<HomeRowViewOptions>>,
|
preferenceOptions: List<PreferenceGroup<HomeRowViewOptions>>,
|
||||||
viewOptions: HomeRowViewOptions,
|
viewOptions: HomeRowViewOptions,
|
||||||
onViewOptionsChange: (HomeRowViewOptions) -> Unit,
|
onViewOptionsChange: (HomeRowViewOptions) -> Unit,
|
||||||
|
onConfigChange: (HomeRowConfig) -> Unit,
|
||||||
|
onConfigAction: (HomeRowConfigAction) -> Unit,
|
||||||
onApplyApplyAll: () -> Unit,
|
onApplyApplyAll: () -> Unit,
|
||||||
modifier: Modifier = Modifier,
|
modifier: Modifier = Modifier,
|
||||||
defaultViewOptions: HomeRowViewOptions = HomeRowViewOptions(),
|
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)),
|
||||||
|
)
|
||||||
|
},
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -232,6 +232,22 @@ fun HomeSettingsPage(
|
||||||
viewModel.updateViewOptionsForAll(row.config.viewOptions)
|
viewModel.updateViewOptionsForAll(row.config.viewOptions)
|
||||||
},
|
},
|
||||||
modifier = destModifier,
|
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,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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.hilt.IoCoroutineScope
|
||||||
import com.github.damontecres.wholphin.services.tvAccess
|
import com.github.damontecres.wholphin.services.tvAccess
|
||||||
import com.github.damontecres.wholphin.ui.AspectRatio
|
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.launchIO
|
||||||
import com.github.damontecres.wholphin.ui.showToast
|
import com.github.damontecres.wholphin.ui.showToast
|
||||||
import com.github.damontecres.wholphin.ui.util.ResArgStringProvider
|
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(
|
data class HomePageSettingsState(
|
||||||
|
|
|
||||||
|
|
@ -92,7 +92,7 @@ message PlaybackPreferences {
|
||||||
message HomePagePreferences{
|
message HomePagePreferences{
|
||||||
int32 max_items_per_row = 1;
|
int32 max_items_per_row = 1;
|
||||||
bool enable_rewatching_next_up = 2;
|
bool enable_rewatching_next_up = 2;
|
||||||
bool combine_continue_next = 3;
|
bool combine_continue_next = 3 [deprecated = true];
|
||||||
int32 max_days_next_up = 4;
|
int32 max_days_next_up = 4;
|
||||||
bool click_to_play = 5;
|
bool click_to_play = 5;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -820,4 +820,6 @@
|
||||||
</array>
|
</array>
|
||||||
<string name="stars">Stars</string>
|
<string name="stars">Stars</string>
|
||||||
<string name="tomatoes">Tomatoes</string>
|
<string name="tomatoes">Tomatoes</string>
|
||||||
|
<string name="sort">Sort</string>
|
||||||
|
<string name="split_into_separate_rows">Split into separate rows</string>
|
||||||
</resources>
|
</resources>
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue