Use navigation

This commit is contained in:
Damontecres 2026-01-29 14:57:50 -05:00
parent 01bdf2cefa
commit 75183802e7
No known key found for this signature in database
5 changed files with 182 additions and 111 deletions

View file

@ -33,6 +33,7 @@ import kotlinx.serialization.json.jsonArray
import kotlinx.serialization.json.jsonObject import kotlinx.serialization.json.jsonObject
import org.jellyfin.sdk.api.client.ApiClient import org.jellyfin.sdk.api.client.ApiClient
import org.jellyfin.sdk.api.client.extensions.displayPreferencesApi import org.jellyfin.sdk.api.client.extensions.displayPreferencesApi
import org.jellyfin.sdk.api.client.extensions.liveTvApi
import org.jellyfin.sdk.api.client.extensions.userApi import org.jellyfin.sdk.api.client.extensions.userApi
import org.jellyfin.sdk.api.client.extensions.userLibraryApi import org.jellyfin.sdk.api.client.extensions.userLibraryApi
import org.jellyfin.sdk.api.client.extensions.userViewsApi import org.jellyfin.sdk.api.client.extensions.userViewsApi
@ -46,6 +47,7 @@ import org.jellyfin.sdk.model.api.request.GetGenresRequest
import org.jellyfin.sdk.model.api.request.GetItemsRequest import org.jellyfin.sdk.model.api.request.GetItemsRequest
import org.jellyfin.sdk.model.api.request.GetLatestMediaRequest import org.jellyfin.sdk.model.api.request.GetLatestMediaRequest
import org.jellyfin.sdk.model.api.request.GetPersonsRequest import org.jellyfin.sdk.model.api.request.GetPersonsRequest
import org.jellyfin.sdk.model.api.request.GetRecommendedProgramsRequest
import org.jellyfin.sdk.model.api.request.GetRecordingsRequest import org.jellyfin.sdk.model.api.request.GetRecordingsRequest
import timber.log.Timber import timber.log.Timber
import java.io.File import java.io.File
@ -315,8 +317,11 @@ class HomeSettingsService
val config = val config =
when (sectionType) { when (sectionType) {
HomeSectionType.ACTIVE_RECORDINGS -> { HomeSectionType.ACTIVE_RECORDINGS -> {
// GetRecordingsRequest HomeRowConfigDisplay(
TODO() id = id++,
title = context.getString(R.string.active_recordings),
config = HomeRowConfig.Recordings(),
)
} }
HomeSectionType.RESUME -> { HomeSectionType.RESUME -> {
@ -337,8 +342,11 @@ class HomeSettingsService
HomeSectionType.LIVE_TV -> { HomeSectionType.LIVE_TV -> {
if (userDto.policy?.enableLiveTvAccess == true) { if (userDto.policy?.enableLiveTvAccess == true) {
// GetRecommendedProgramsRequest HomeRowConfigDisplay(
TODO() id = id++,
title = context.getString(R.string.live_tv),
config = HomeRowConfig.TvPrograms(),
)
} else { } else {
null null
} }
@ -478,11 +486,19 @@ class HomeSettingsService
} }
is HomeRowConfig.Recordings -> { is HomeRowConfig.Recordings -> {
TODO() HomeRowConfigDisplay(
id = id,
title = context.getString(R.string.active_recordings),
config,
)
} }
is HomeRowConfig.TvPrograms -> { is HomeRowConfig.TvPrograms -> {
TODO() HomeRowConfigDisplay(
id = id,
title = context.getString(R.string.live_tv),
config,
)
} }
} }
@ -753,12 +769,48 @@ class HomeSettingsService
} }
is HomeRowConfig.Recordings -> { is HomeRowConfig.Recordings -> {
GetRecordingsRequest() val request =
TODO() GetRecordingsRequest(
userId = userDto.id,
isInProgress = true,
fields = DefaultItemFields,
limit = limit,
enableImages = true,
enableUserData = true,
)
api.liveTvApi
.getRecordings(request)
.content.items
.map { BaseItem(it, true) }
.let {
Success(
context.getString(R.string.active_recordings),
it,
row.viewOptions,
)
}
} }
is HomeRowConfig.TvPrograms -> { is HomeRowConfig.TvPrograms -> {
TODO() val request =
GetRecommendedProgramsRequest(
userId = userDto.id,
fields = DefaultItemFields,
limit = limit,
enableImages = true,
enableUserData = true,
)
api.liveTvApi
.getRecommendedPrograms(request)
.content.items
.map { BaseItem(it, true) }
.let {
Success(
context.getString(R.string.live_tv),
it,
row.viewOptions,
)
}
} }
} }

View file

@ -28,7 +28,7 @@ import com.github.damontecres.wholphin.ui.ifElse
import com.github.damontecres.wholphin.ui.tryRequestFocus import com.github.damontecres.wholphin.ui.tryRequestFocus
@Composable @Composable
fun HomeSettingsLibraryList( fun HomeSettingsAddRow(
libraries: List<Library>, libraries: List<Library>,
showDiscover: Boolean, showDiscover: Boolean,
onClick: (Library) -> Unit, onClick: (Library) -> Unit,

View file

@ -1,24 +1,32 @@
package com.github.damontecres.wholphin.ui.main.settings package com.github.damontecres.wholphin.ui.main.settings
import androidx.navigation3.runtime.NavKey import androidx.navigation3.runtime.NavKey
import kotlinx.serialization.Serializable
/** /**
* Tracking the pages for selecting and configuring rows * Tracking the pages for selecting and configuring rows
*/ */
@Serializable
sealed interface HomeSettingsDestination : NavKey { sealed interface HomeSettingsDestination : NavKey {
@Serializable
data object RowList : HomeSettingsDestination data object RowList : HomeSettingsDestination
data object ChooseLibrary : HomeSettingsDestination @Serializable
data object AddRow : HomeSettingsDestination
@Serializable
data class ChooseRowType( data class ChooseRowType(
val library: Library, val library: Library,
) : HomeSettingsDestination ) : HomeSettingsDestination
@Serializable
data class RowSettings( data class RowSettings(
val rowId: Int, val rowId: Int,
) : HomeSettingsDestination ) : HomeSettingsDestination
@Serializable
data object ChooseFavorite : HomeSettingsDestination data object ChooseFavorite : HomeSettingsDestination
@Serializable
data object ChooseDiscover : HomeSettingsDestination data object ChooseDiscover : HomeSettingsDestination
} }

View file

@ -1,6 +1,5 @@
package com.github.damontecres.wholphin.ui.main.settings package com.github.damontecres.wholphin.ui.main.settings
import androidx.activity.compose.BackHandler
import androidx.compose.foundation.background import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Box
@ -13,14 +12,17 @@ import androidx.compose.foundation.lazy.rememberLazyListState
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.collectAsState import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
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.navigation3.rememberViewModelStoreNavEntryDecorator
import androidx.navigation3.runtime.NavEntry
import androidx.navigation3.runtime.rememberNavBackStack
import androidx.navigation3.runtime.rememberSaveableStateHolderNavEntryDecorator
import androidx.navigation3.ui.NavDisplay
import androidx.tv.material3.MaterialTheme import androidx.tv.material3.MaterialTheme
import androidx.tv.material3.surfaceColorAtElevation
import com.github.damontecres.wholphin.ui.main.HomePageContent import com.github.damontecres.wholphin.ui.main.HomePageContent
import com.github.damontecres.wholphin.ui.main.settings.HomeSettingsDestination.ChooseRowType import com.github.damontecres.wholphin.ui.main.settings.HomeSettingsDestination.ChooseRowType
import com.github.damontecres.wholphin.ui.main.settings.HomeSettingsDestination.RowSettings import com.github.damontecres.wholphin.ui.main.settings.HomeSettingsDestination.RowSettings
@ -37,20 +39,11 @@ fun HomeSettingsPage(
) { ) {
val scope = rememberCoroutineScope() val scope = rememberCoroutineScope()
val listState = rememberLazyListState() val listState = rememberLazyListState()
var destination by remember { mutableStateOf<HomeSettingsDestination>(HomeSettingsDestination.RowList) } val backStack = rememberNavBackStack(HomeSettingsDestination.RowList)
val state by viewModel.state.collectAsState() val state by viewModel.state.collectAsState()
val discoverEnabled by viewModel.discoverEnabled.collectAsState(false) val discoverEnabled by viewModel.discoverEnabled.collectAsState(false)
BackHandler(destination is ChooseRowType) {
destination = HomeSettingsDestination.ChooseLibrary
}
BackHandler(destination is HomeSettingsDestination.ChooseLibrary) {
destination = HomeSettingsDestination.RowList
}
BackHandler(destination is RowSettings) {
destination = HomeSettingsDestination.RowList
}
Row( Row(
horizontalArrangement = Arrangement.spacedBy(8.dp), horizontalArrangement = Arrangement.spacedBy(8.dp),
modifier = modifier, modifier = modifier,
@ -62,100 +55,115 @@ fun HomeSettingsPage(
.fillMaxHeight() .fillMaxHeight()
.background(color = MaterialTheme.colorScheme.surface), .background(color = MaterialTheme.colorScheme.surface),
) { ) {
val destModifier = NavDisplay(
Modifier backStack = backStack,
.fillMaxSize() // onBack = { navigationManager.goBack() },
.padding(4.dp) entryDecorators =
when (val dest = destination) { listOf(
HomeSettingsDestination.RowList -> { rememberSaveableStateHolderNavEntryDecorator(),
HomeSettingsRowList( rememberViewModelStoreNavEntryDecorator(),
state = state, ),
onClickAdd = { destination = HomeSettingsDestination.ChooseLibrary }, modifier = Modifier.background(MaterialTheme.colorScheme.surfaceColorAtElevation(1.dp)),
onClickSaveLocal = { viewModel.saveToLocal() }, entryProvider = { key ->
onClickMove = viewModel::moveRow, key as HomeSettingsDestination
onClickDelete = viewModel::deleteRow, NavEntry(key, contentKey = key.toString()) {
onClick = { index, row -> val destModifier =
destination = RowSettings(row.id) Modifier
scope.launch(ExceptionHandler()) { .fillMaxSize()
Timber.v("Scroll to $index") .padding(4.dp)
listState.scrollToItem(index) when (val dest = key) {
HomeSettingsDestination.RowList -> {
HomeSettingsRowList(
state = state,
onClickAdd = { backStack.add(HomeSettingsDestination.AddRow) },
onClickSaveLocal = { viewModel.saveToLocal() },
onClickMove = viewModel::moveRow,
onClickDelete = viewModel::deleteRow,
onClick = { index, row ->
backStack.add(RowSettings(row.id))
scope.launch(ExceptionHandler()) {
Timber.v("Scroll to $index")
listState.scrollToItem(index)
}
},
onClickResize = {
viewModel.resizeCards(it)
},
modifier = destModifier,
)
} }
},
onClickResize = {
viewModel.resizeCards(it)
},
modifier = destModifier,
)
}
is HomeSettingsDestination.ChooseLibrary -> { is HomeSettingsDestination.AddRow -> {
HomeSettingsLibraryList( HomeSettingsAddRow(
libraries = state.libraries, libraries = state.libraries,
showDiscover = discoverEnabled, showDiscover = discoverEnabled,
onClick = { destination = ChooseRowType(it) }, onClick = { backStack.add(ChooseRowType(it)) },
onClickMeta = { onClickMeta = {
when (it) { when (it) {
MetaRowType.CONTINUE_WATCHING, MetaRowType.CONTINUE_WATCHING,
MetaRowType.NEXT_UP, MetaRowType.NEXT_UP,
MetaRowType.COMBINED_CONTINUE_WATCHING, MetaRowType.COMBINED_CONTINUE_WATCHING,
-> { -> {
viewModel.addRow(it) viewModel.addRow(it)
destination = HomeSettingsDestination.RowList backStack.add(HomeSettingsDestination.RowList)
} }
MetaRowType.FAVORITES -> { MetaRowType.FAVORITES -> {
destination = HomeSettingsDestination.ChooseFavorite backStack.add(HomeSettingsDestination.ChooseFavorite)
} }
MetaRowType.DISCOVER -> { MetaRowType.DISCOVER -> {
destination = HomeSettingsDestination.ChooseDiscover backStack.add(HomeSettingsDestination.ChooseDiscover)
} }
}
},
modifier = destModifier,
)
} }
},
modifier = destModifier,
)
}
is ChooseRowType -> { is ChooseRowType -> {
HomeLibraryRowTypeList( HomeLibraryRowTypeList(
library = dest.library, library = dest.library,
onClick = { onClick = {
viewModel.addRow(dest.library, it) viewModel.addRow(dest.library, it)
destination = HomeSettingsDestination.RowList backStack.add(HomeSettingsDestination.RowList)
}, },
modifier = destModifier, modifier = destModifier,
) )
} }
is RowSettings -> { is RowSettings -> {
val row = val row =
state.rows state.rows
.first { it.id == dest.rowId } .first { it.id == dest.rowId }
HomeRowSettings( HomeRowSettings(
title = row.title, title = row.title,
viewOptions = row.config.viewOptions, viewOptions = row.config.viewOptions,
onViewOptionsChange = { onViewOptionsChange = {
viewModel.updateViewOptions(dest.rowId, it) viewModel.updateViewOptions(dest.rowId, it)
}, },
onApplyApplyAll = { onApplyApplyAll = {
viewModel.updateViewOptionsForAll(row.config.viewOptions) viewModel.updateViewOptionsForAll(row.config.viewOptions)
}, },
modifier = destModifier, modifier = destModifier,
) )
} }
HomeSettingsDestination.ChooseDiscover -> { HomeSettingsDestination.ChooseDiscover -> {
TODO() TODO()
} }
HomeSettingsDestination.ChooseFavorite -> { HomeSettingsDestination.ChooseFavorite -> {
HomeSettingsFavoriteList( HomeSettingsFavoriteList(
onClick = { type -> onClick = { type ->
viewModel.addFavoriteRow(type) viewModel.addFavoriteRow(type)
}, },
) )
} }
} }
}
},
)
} }
HomePageContent( HomePageContent(
loadingState = state.loading, loadingState = state.loading,

View file

@ -34,8 +34,10 @@ import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.first import kotlinx.coroutines.flow.first
import kotlinx.coroutines.flow.update import kotlinx.coroutines.flow.update
import kotlinx.serialization.Serializable
import org.jellyfin.sdk.model.api.BaseItemKind import org.jellyfin.sdk.model.api.BaseItemKind
import org.jellyfin.sdk.model.api.CollectionType import org.jellyfin.sdk.model.api.CollectionType
import org.jellyfin.sdk.model.serializer.UUIDSerializer
import timber.log.Timber import timber.log.Timber
import java.util.UUID import java.util.UUID
import javax.inject.Inject import javax.inject.Inject
@ -400,8 +402,9 @@ data class HomePageSettingsState(
} }
@Immutable @Immutable
@Serializable
data class Library( data class Library(
val itemId: UUID, @Serializable(UUIDSerializer::class) val itemId: UUID,
val name: String, val name: String,
val collectionType: CollectionType, val collectionType: CollectionType,
) )