mirror of
https://github.com/JustinZeus/Wholphin.git
synced 2026-07-08 15:50:16 +02:00
Add row for favorite items
This commit is contained in:
parent
d99c947068
commit
ccb79257fd
9 changed files with 334 additions and 24 deletions
|
|
@ -10,6 +10,7 @@ import com.github.damontecres.wholphin.ui.data.SortAndDirection
|
||||||
import kotlinx.serialization.SerialName
|
import kotlinx.serialization.SerialName
|
||||||
import kotlinx.serialization.Serializable
|
import kotlinx.serialization.Serializable
|
||||||
import kotlinx.serialization.UseSerializers
|
import kotlinx.serialization.UseSerializers
|
||||||
|
import org.jellyfin.sdk.model.api.BaseItemKind
|
||||||
import org.jellyfin.sdk.model.api.request.GetItemsRequest
|
import org.jellyfin.sdk.model.api.request.GetItemsRequest
|
||||||
import org.jellyfin.sdk.model.serializer.UUIDSerializer
|
import org.jellyfin.sdk.model.serializer.UUIDSerializer
|
||||||
import java.util.UUID
|
import java.util.UUID
|
||||||
|
|
@ -86,13 +87,33 @@ sealed interface HomeRowConfig {
|
||||||
val parentId: UUID,
|
val parentId: UUID,
|
||||||
override val viewOptions: HomeRowViewOptions =
|
override val viewOptions: HomeRowViewOptions =
|
||||||
HomeRowViewOptions(
|
HomeRowViewOptions(
|
||||||
heightDp = (Cards.HEIGHT_2X3_DP * .75f).toInt().let { it - it % 4 },
|
heightDp = Cards.HEIGHT_EPISODE,
|
||||||
aspectRatio = AspectRatio.WIDE,
|
aspectRatio = AspectRatio.WIDE,
|
||||||
),
|
),
|
||||||
) : HomeRowConfig {
|
) : HomeRowConfig {
|
||||||
override fun updateViewOptions(viewOptions: HomeRowViewOptions): Genres = this.copy(viewOptions = viewOptions)
|
override fun updateViewOptions(viewOptions: HomeRowViewOptions): Genres = this.copy(viewOptions = viewOptions)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Favorites for a specific type
|
||||||
|
*/
|
||||||
|
@Serializable
|
||||||
|
@SerialName("Favorite")
|
||||||
|
data class Favorite(
|
||||||
|
val kind: BaseItemKind,
|
||||||
|
override val viewOptions: HomeRowViewOptions =
|
||||||
|
if (kind == BaseItemKind.EPISODE) {
|
||||||
|
HomeRowViewOptions(
|
||||||
|
heightDp = Cards.HEIGHT_EPISODE,
|
||||||
|
aspectRatio = AspectRatio.WIDE,
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
HomeRowViewOptions()
|
||||||
|
},
|
||||||
|
) : HomeRowConfig {
|
||||||
|
override fun updateViewOptions(viewOptions: HomeRowViewOptions): Favorite = this.copy(viewOptions = viewOptions)
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Fetch by parent ID such as a library, collection, or playlist with optional simple sorting
|
* Fetch by parent ID such as a library, collection, or playlist with optional simple sorting
|
||||||
*/
|
*/
|
||||||
|
|
|
||||||
|
|
@ -15,7 +15,9 @@ import com.github.damontecres.wholphin.ui.nav.ServerNavDrawerItem
|
||||||
import com.github.damontecres.wholphin.ui.toServerString
|
import com.github.damontecres.wholphin.ui.toServerString
|
||||||
import com.github.damontecres.wholphin.util.GetGenresRequestHandler
|
import com.github.damontecres.wholphin.util.GetGenresRequestHandler
|
||||||
import com.github.damontecres.wholphin.util.GetItemsRequestHandler
|
import com.github.damontecres.wholphin.util.GetItemsRequestHandler
|
||||||
|
import com.github.damontecres.wholphin.util.GetPersonsHandler
|
||||||
import com.github.damontecres.wholphin.util.HomeRowLoadingState
|
import com.github.damontecres.wholphin.util.HomeRowLoadingState
|
||||||
|
import com.github.damontecres.wholphin.util.HomeRowLoadingState.Success
|
||||||
import dagger.hilt.android.qualifiers.ApplicationContext
|
import dagger.hilt.android.qualifiers.ApplicationContext
|
||||||
import kotlinx.coroutines.CoroutineScope
|
import kotlinx.coroutines.CoroutineScope
|
||||||
import kotlinx.coroutines.flow.MutableStateFlow
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
|
|
@ -31,12 +33,15 @@ 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.userLibraryApi
|
import org.jellyfin.sdk.api.client.extensions.userLibraryApi
|
||||||
import org.jellyfin.sdk.model.UUID
|
import org.jellyfin.sdk.model.UUID
|
||||||
|
import org.jellyfin.sdk.model.api.BaseItemKind
|
||||||
|
import org.jellyfin.sdk.model.api.ImageType
|
||||||
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
|
||||||
import org.jellyfin.sdk.model.api.UserDto
|
import org.jellyfin.sdk.model.api.UserDto
|
||||||
import org.jellyfin.sdk.model.api.request.GetGenresRequest
|
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 timber.log.Timber
|
import timber.log.Timber
|
||||||
import java.io.File
|
import java.io.File
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
@ -183,7 +188,19 @@ class HomeSettingsService
|
||||||
suspend fun loadCurrentSettings(userId: UUID) {
|
suspend fun loadCurrentSettings(userId: UUID) {
|
||||||
Timber.v("Getting setting for %s", userId)
|
Timber.v("Getting setting for %s", userId)
|
||||||
// User local then server/remote otherwise create a default
|
// User local then server/remote otherwise create a default
|
||||||
val settings = loadFromLocal(userId) ?: loadFromServer(userId)
|
val settings =
|
||||||
|
try {
|
||||||
|
loadFromLocal(userId)
|
||||||
|
} catch (ex: Exception) {
|
||||||
|
Timber.w(ex, "Error loading local settings")
|
||||||
|
// TODO show toast?
|
||||||
|
null
|
||||||
|
} ?: try {
|
||||||
|
loadFromServer(userId)
|
||||||
|
} catch (ex: Exception) {
|
||||||
|
Timber.w(ex, "Error loading remote settings")
|
||||||
|
null
|
||||||
|
}
|
||||||
val resolvedSettings =
|
val resolvedSettings =
|
||||||
if (settings != null) {
|
if (settings != null) {
|
||||||
Timber.v("Found settings")
|
Timber.v("Found settings")
|
||||||
|
|
@ -341,6 +358,11 @@ class HomeSettingsService
|
||||||
config,
|
config,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
is HomeRowConfig.Favorite -> {
|
||||||
|
val name = context.getString(R.string.favorites) // TODO "Favorite <type>"
|
||||||
|
HomeRowConfigDisplay(id, name, config)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -358,7 +380,7 @@ class HomeSettingsService
|
||||||
is HomeRowConfig.ContinueWatching -> {
|
is HomeRowConfig.ContinueWatching -> {
|
||||||
val resume = latestNextUpService.getResume(userDto.id, limit, true)
|
val resume = latestNextUpService.getResume(userDto.id, limit, true)
|
||||||
|
|
||||||
HomeRowLoadingState.Success(
|
Success(
|
||||||
title = context.getString(R.string.continue_watching),
|
title = context.getString(R.string.continue_watching),
|
||||||
items = resume,
|
items = resume,
|
||||||
viewOptions = row.viewOptions,
|
viewOptions = row.viewOptions,
|
||||||
|
|
@ -374,7 +396,7 @@ class HomeSettingsService
|
||||||
false,
|
false,
|
||||||
)
|
)
|
||||||
|
|
||||||
HomeRowLoadingState.Success(
|
Success(
|
||||||
title = context.getString(R.string.next_up),
|
title = context.getString(R.string.next_up),
|
||||||
items = nextUp,
|
items = nextUp,
|
||||||
viewOptions = row.viewOptions,
|
viewOptions = row.viewOptions,
|
||||||
|
|
@ -392,7 +414,7 @@ class HomeSettingsService
|
||||||
false,
|
false,
|
||||||
)
|
)
|
||||||
|
|
||||||
HomeRowLoadingState.Success(
|
Success(
|
||||||
title = context.getString(R.string.continue_watching),
|
title = context.getString(R.string.continue_watching),
|
||||||
items =
|
items =
|
||||||
latestNextUpService.buildCombined(
|
latestNextUpService.buildCombined(
|
||||||
|
|
@ -438,7 +460,7 @@ class HomeSettingsService
|
||||||
name?.let { context.getString(R.string.genres_in, it) }
|
name?.let { context.getString(R.string.genres_in, it) }
|
||||||
?: context.getString(R.string.genres)
|
?: context.getString(R.string.genres)
|
||||||
|
|
||||||
HomeRowLoadingState.Success(
|
Success(
|
||||||
title,
|
title,
|
||||||
genres,
|
genres,
|
||||||
viewOptions = row.viewOptions,
|
viewOptions = row.viewOptions,
|
||||||
|
|
@ -468,7 +490,7 @@ class HomeSettingsService
|
||||||
.content
|
.content
|
||||||
.map { BaseItem.Companion.from(it, api, true) }
|
.map { BaseItem.Companion.from(it, api, true) }
|
||||||
.let {
|
.let {
|
||||||
HomeRowLoadingState.Success(
|
Success(
|
||||||
title,
|
title,
|
||||||
it,
|
it,
|
||||||
row.viewOptions,
|
row.viewOptions,
|
||||||
|
|
@ -500,7 +522,7 @@ class HomeSettingsService
|
||||||
.content.items
|
.content.items
|
||||||
.map { BaseItem.Companion.from(it, api, true) }
|
.map { BaseItem.Companion.from(it, api, true) }
|
||||||
.let {
|
.let {
|
||||||
HomeRowLoadingState.Success(
|
Success(
|
||||||
title,
|
title,
|
||||||
it,
|
it,
|
||||||
row.viewOptions,
|
row.viewOptions,
|
||||||
|
|
@ -528,7 +550,7 @@ class HomeSettingsService
|
||||||
.content.items
|
.content.items
|
||||||
.map { BaseItem(it, true) }
|
.map { BaseItem(it, true) }
|
||||||
.let {
|
.let {
|
||||||
HomeRowLoadingState.Success(
|
Success(
|
||||||
name ?: context.getString(R.string.collection),
|
name ?: context.getString(R.string.collection),
|
||||||
it,
|
it,
|
||||||
row.viewOptions,
|
row.viewOptions,
|
||||||
|
|
@ -555,13 +577,59 @@ class HomeSettingsService
|
||||||
.content.items
|
.content.items
|
||||||
.map { BaseItem(it, true) }
|
.map { BaseItem(it, true) }
|
||||||
.let {
|
.let {
|
||||||
HomeRowLoadingState.Success(
|
Success(
|
||||||
row.name,
|
row.name,
|
||||||
it,
|
it,
|
||||||
row.viewOptions,
|
row.viewOptions,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
is HomeRowConfig.Favorite -> {
|
||||||
|
if (row.kind == BaseItemKind.PERSON) {
|
||||||
|
val request =
|
||||||
|
GetPersonsRequest(
|
||||||
|
userId = userDto.id,
|
||||||
|
limit = limit,
|
||||||
|
fields = DefaultItemFields,
|
||||||
|
isFavorite = true,
|
||||||
|
enableImages = true,
|
||||||
|
enableImageTypes = listOf(ImageType.PRIMARY),
|
||||||
|
)
|
||||||
|
GetPersonsHandler
|
||||||
|
.execute(api, request)
|
||||||
|
.content.items
|
||||||
|
.map { BaseItem(it, true) }
|
||||||
|
.let {
|
||||||
|
Success(
|
||||||
|
context.getString(R.string.favorites), // TODO
|
||||||
|
it,
|
||||||
|
row.viewOptions,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
val request =
|
||||||
|
GetItemsRequest(
|
||||||
|
userId = userDto.id,
|
||||||
|
recursive = true,
|
||||||
|
limit = limit,
|
||||||
|
fields = DefaultItemFields,
|
||||||
|
includeItemTypes = listOf(row.kind),
|
||||||
|
isFavorite = true,
|
||||||
|
)
|
||||||
|
GetItemsRequestHandler
|
||||||
|
.execute(api, request)
|
||||||
|
.content.items
|
||||||
|
.map { BaseItem(it, false) }
|
||||||
|
.let {
|
||||||
|
Success(
|
||||||
|
context.getString(R.string.favorites), // TODO
|
||||||
|
it,
|
||||||
|
row.viewOptions,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
|
|
|
||||||
|
|
@ -75,7 +75,8 @@ val SlimItemFields =
|
||||||
object Cards {
|
object Cards {
|
||||||
const val HEIGHT_2X3_DP = 172
|
const val HEIGHT_2X3_DP = 172
|
||||||
val height2x3 = HEIGHT_2X3_DP.dp
|
val height2x3 = HEIGHT_2X3_DP.dp
|
||||||
val heightEpisode = height2x3 * .75f
|
val HEIGHT_EPISODE = (HEIGHT_2X3_DP * .75f).toInt().let { it - it % 4 }
|
||||||
|
val heightEpisode = HEIGHT_EPISODE.dp
|
||||||
val playedPercentHeight = 6.dp
|
val playedPercentHeight = 6.dp
|
||||||
val serverUserCircle = height2x3 * .75f
|
val serverUserCircle = height2x3 * .75f
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -17,4 +17,8 @@ sealed interface HomeSettingsDestination : NavKey {
|
||||||
data class RowSettings(
|
data class RowSettings(
|
||||||
val rowId: Int,
|
val rowId: Int,
|
||||||
) : HomeSettingsDestination
|
) : HomeSettingsDestination
|
||||||
|
|
||||||
|
data object ChooseFavorite : HomeSettingsDestination
|
||||||
|
|
||||||
|
data object ChooseDiscover : HomeSettingsDestination
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,69 @@
|
||||||
|
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 androidx.tv.material3.ListItem
|
||||||
|
import androidx.tv.material3.Text
|
||||||
|
import com.github.damontecres.wholphin.R
|
||||||
|
import com.github.damontecres.wholphin.ui.ifElse
|
||||||
|
import com.github.damontecres.wholphin.ui.tryRequestFocus
|
||||||
|
import org.jellyfin.sdk.model.api.BaseItemKind
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun HomeSettingsFavoriteList(
|
||||||
|
onClick: (BaseItemKind) -> Unit,
|
||||||
|
modifier: Modifier = Modifier,
|
||||||
|
firstFocus: FocusRequester = remember { FocusRequester() },
|
||||||
|
) {
|
||||||
|
LaunchedEffect(Unit) { firstFocus.tryRequestFocus() }
|
||||||
|
Column(modifier = modifier) {
|
||||||
|
Text(
|
||||||
|
text = "Add favorites row...",
|
||||||
|
)
|
||||||
|
LazyColumn(
|
||||||
|
contentPadding = PaddingValues(8.dp),
|
||||||
|
verticalArrangement = Arrangement.spacedBy(8.dp),
|
||||||
|
modifier =
|
||||||
|
modifier
|
||||||
|
.fillMaxHeight()
|
||||||
|
.focusRestorer(firstFocus),
|
||||||
|
) {
|
||||||
|
itemsIndexed(favoriteOptionsList) { index, type ->
|
||||||
|
ListItem(
|
||||||
|
selected = false,
|
||||||
|
headlineContent = {
|
||||||
|
Text(
|
||||||
|
text = stringResource(favoriteOptions[type]!!),
|
||||||
|
)
|
||||||
|
},
|
||||||
|
onClick = { onClick.invoke(type) },
|
||||||
|
modifier = Modifier.ifElse(index == 0, Modifier.focusRequester(firstFocus)),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val favoriteOptions =
|
||||||
|
mapOf(
|
||||||
|
BaseItemKind.MOVIE to R.string.movies,
|
||||||
|
BaseItemKind.SERIES to R.string.tv_shows,
|
||||||
|
BaseItemKind.EPISODE to R.string.episodes,
|
||||||
|
BaseItemKind.VIDEO to R.string.videos,
|
||||||
|
BaseItemKind.PLAYLIST to R.string.playlists,
|
||||||
|
BaseItemKind.PERSON to R.string.people,
|
||||||
|
)
|
||||||
|
val favoriteOptionsList = favoriteOptions.keys.toList()
|
||||||
|
|
@ -5,6 +5,8 @@ import androidx.compose.foundation.layout.Arrangement
|
||||||
import androidx.compose.foundation.layout.Column
|
import androidx.compose.foundation.layout.Column
|
||||||
import androidx.compose.foundation.layout.PaddingValues
|
import androidx.compose.foundation.layout.PaddingValues
|
||||||
import androidx.compose.foundation.layout.fillMaxHeight
|
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.foundation.lazy.LazyColumn
|
||||||
import androidx.compose.foundation.lazy.itemsIndexed
|
import androidx.compose.foundation.lazy.itemsIndexed
|
||||||
import androidx.compose.material3.HorizontalDivider
|
import androidx.compose.material3.HorizontalDivider
|
||||||
|
|
@ -16,8 +18,10 @@ 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.res.stringResource
|
import androidx.compose.ui.res.stringResource
|
||||||
|
import androidx.compose.ui.text.style.TextAlign
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import androidx.tv.material3.ListItem
|
import androidx.tv.material3.ListItem
|
||||||
|
import androidx.tv.material3.MaterialTheme
|
||||||
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.ifElse
|
import com.github.damontecres.wholphin.ui.ifElse
|
||||||
|
|
@ -26,6 +30,7 @@ import com.github.damontecres.wholphin.ui.tryRequestFocus
|
||||||
@Composable
|
@Composable
|
||||||
fun HomeSettingsLibraryList(
|
fun HomeSettingsLibraryList(
|
||||||
libraries: List<Library>,
|
libraries: List<Library>,
|
||||||
|
showDiscover: Boolean,
|
||||||
onClick: (Library) -> Unit,
|
onClick: (Library) -> Unit,
|
||||||
onClickMeta: (MetaRowType) -> Unit,
|
onClickMeta: (MetaRowType) -> Unit,
|
||||||
modifier: Modifier,
|
modifier: Modifier,
|
||||||
|
|
@ -44,7 +49,25 @@ fun HomeSettingsLibraryList(
|
||||||
.fillMaxHeight()
|
.fillMaxHeight()
|
||||||
.focusRestorer(firstFocus),
|
.focusRestorer(firstFocus),
|
||||||
) {
|
) {
|
||||||
itemsIndexed(MetaRowType.entries) { index, type ->
|
item {
|
||||||
|
Text(
|
||||||
|
text = stringResource(R.string.continue_watching),
|
||||||
|
style = MaterialTheme.typography.titleMedium,
|
||||||
|
color = MaterialTheme.colorScheme.onSurface,
|
||||||
|
textAlign = TextAlign.Start,
|
||||||
|
modifier =
|
||||||
|
Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.padding(top = 8.dp, bottom = 4.dp),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
itemsIndexed(
|
||||||
|
listOf(
|
||||||
|
MetaRowType.CONTINUE_WATCHING,
|
||||||
|
MetaRowType.NEXT_UP,
|
||||||
|
MetaRowType.COMBINED_CONTINUE_WATCHING,
|
||||||
|
),
|
||||||
|
) { index, type ->
|
||||||
ListItem(
|
ListItem(
|
||||||
selected = false,
|
selected = false,
|
||||||
headlineContent = {
|
headlineContent = {
|
||||||
|
|
@ -56,6 +79,16 @@ fun HomeSettingsLibraryList(
|
||||||
}
|
}
|
||||||
item {
|
item {
|
||||||
HorizontalDivider()
|
HorizontalDivider()
|
||||||
|
Text(
|
||||||
|
text = stringResource(R.string.library),
|
||||||
|
style = MaterialTheme.typography.titleMedium,
|
||||||
|
color = MaterialTheme.colorScheme.onSurface,
|
||||||
|
textAlign = TextAlign.Start,
|
||||||
|
modifier =
|
||||||
|
Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.padding(top = 8.dp, bottom = 4.dp),
|
||||||
|
)
|
||||||
}
|
}
|
||||||
itemsIndexed(libraries) { index, library ->
|
itemsIndexed(libraries) { index, library ->
|
||||||
ListItem(
|
ListItem(
|
||||||
|
|
@ -67,6 +100,41 @@ fun HomeSettingsLibraryList(
|
||||||
modifier = Modifier, // .ifElse(index == 0, Modifier.focusRequester(firstFocus)),
|
modifier = Modifier, // .ifElse(index == 0, Modifier.focusRequester(firstFocus)),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
item {
|
||||||
|
HorizontalDivider()
|
||||||
|
Text(
|
||||||
|
text = stringResource(R.string.more),
|
||||||
|
style = MaterialTheme.typography.titleMedium,
|
||||||
|
color = MaterialTheme.colorScheme.onSurface,
|
||||||
|
textAlign = TextAlign.Start,
|
||||||
|
modifier =
|
||||||
|
Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.padding(top = 8.dp, bottom = 4.dp),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
item {
|
||||||
|
ListItem(
|
||||||
|
selected = false,
|
||||||
|
headlineContent = {
|
||||||
|
Text(stringResource(MetaRowType.FAVORITES.stringId))
|
||||||
|
},
|
||||||
|
onClick = { onClickMeta.invoke(MetaRowType.FAVORITES) },
|
||||||
|
modifier = Modifier,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
if (showDiscover) {
|
||||||
|
item {
|
||||||
|
ListItem(
|
||||||
|
selected = false,
|
||||||
|
headlineContent = {
|
||||||
|
Text(stringResource(MetaRowType.DISCOVER.stringId))
|
||||||
|
},
|
||||||
|
onClick = { onClickMeta.invoke(MetaRowType.DISCOVER) },
|
||||||
|
modifier = Modifier,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -77,4 +145,6 @@ enum class MetaRowType(
|
||||||
CONTINUE_WATCHING(R.string.continue_watching),
|
CONTINUE_WATCHING(R.string.continue_watching),
|
||||||
NEXT_UP(R.string.next_up),
|
NEXT_UP(R.string.next_up),
|
||||||
COMBINED_CONTINUE_WATCHING(R.string.combine_continue_next),
|
COMBINED_CONTINUE_WATCHING(R.string.combine_continue_next),
|
||||||
|
FAVORITES(R.string.favorites),
|
||||||
|
DISCOVER(R.string.discover),
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -22,6 +22,8 @@ import androidx.compose.ui.unit.dp
|
||||||
import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel
|
import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel
|
||||||
import androidx.tv.material3.MaterialTheme
|
import androidx.tv.material3.MaterialTheme
|
||||||
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.RowSettings
|
||||||
import com.github.damontecres.wholphin.util.ExceptionHandler
|
import com.github.damontecres.wholphin.util.ExceptionHandler
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import timber.log.Timber
|
import timber.log.Timber
|
||||||
|
|
@ -38,14 +40,15 @@ fun HomeSettingsPage(
|
||||||
var destination by remember { mutableStateOf<HomeSettingsDestination>(HomeSettingsDestination.RowList) }
|
var destination by remember { mutableStateOf<HomeSettingsDestination>(HomeSettingsDestination.RowList) }
|
||||||
|
|
||||||
val state by viewModel.state.collectAsState()
|
val state by viewModel.state.collectAsState()
|
||||||
|
val discoverEnabled by viewModel.discoverEnabled.collectAsState(false)
|
||||||
|
|
||||||
BackHandler(destination is HomeSettingsDestination.ChooseRowType) {
|
BackHandler(destination is ChooseRowType) {
|
||||||
destination = HomeSettingsDestination.ChooseLibrary
|
destination = HomeSettingsDestination.ChooseLibrary
|
||||||
}
|
}
|
||||||
BackHandler(destination is HomeSettingsDestination.ChooseLibrary) {
|
BackHandler(destination is HomeSettingsDestination.ChooseLibrary) {
|
||||||
destination = HomeSettingsDestination.RowList
|
destination = HomeSettingsDestination.RowList
|
||||||
}
|
}
|
||||||
BackHandler(destination is HomeSettingsDestination.RowSettings) {
|
BackHandler(destination is RowSettings) {
|
||||||
destination = HomeSettingsDestination.RowList
|
destination = HomeSettingsDestination.RowList
|
||||||
}
|
}
|
||||||
Row(
|
Row(
|
||||||
|
|
@ -72,7 +75,7 @@ fun HomeSettingsPage(
|
||||||
onClickMove = viewModel::moveRow,
|
onClickMove = viewModel::moveRow,
|
||||||
onClickDelete = viewModel::deleteRow,
|
onClickDelete = viewModel::deleteRow,
|
||||||
onClick = { index, row ->
|
onClick = { index, row ->
|
||||||
destination = HomeSettingsDestination.RowSettings(row.id)
|
destination = RowSettings(row.id)
|
||||||
scope.launch(ExceptionHandler()) {
|
scope.launch(ExceptionHandler()) {
|
||||||
Timber.v("Scroll to $index")
|
Timber.v("Scroll to $index")
|
||||||
listState.scrollToItem(index)
|
listState.scrollToItem(index)
|
||||||
|
|
@ -88,16 +91,32 @@ fun HomeSettingsPage(
|
||||||
is HomeSettingsDestination.ChooseLibrary -> {
|
is HomeSettingsDestination.ChooseLibrary -> {
|
||||||
HomeSettingsLibraryList(
|
HomeSettingsLibraryList(
|
||||||
libraries = state.libraries,
|
libraries = state.libraries,
|
||||||
onClick = { destination = HomeSettingsDestination.ChooseRowType(it) },
|
showDiscover = discoverEnabled,
|
||||||
|
onClick = { destination = ChooseRowType(it) },
|
||||||
onClickMeta = {
|
onClickMeta = {
|
||||||
viewModel.addRow(it)
|
when (it) {
|
||||||
destination = HomeSettingsDestination.RowList
|
MetaRowType.CONTINUE_WATCHING,
|
||||||
|
MetaRowType.NEXT_UP,
|
||||||
|
MetaRowType.COMBINED_CONTINUE_WATCHING,
|
||||||
|
-> {
|
||||||
|
viewModel.addRow(it)
|
||||||
|
destination = HomeSettingsDestination.RowList
|
||||||
|
}
|
||||||
|
|
||||||
|
MetaRowType.FAVORITES -> {
|
||||||
|
destination = HomeSettingsDestination.ChooseFavorite
|
||||||
|
}
|
||||||
|
|
||||||
|
MetaRowType.DISCOVER -> {
|
||||||
|
destination = HomeSettingsDestination.ChooseDiscover
|
||||||
|
}
|
||||||
|
}
|
||||||
},
|
},
|
||||||
modifier = destModifier,
|
modifier = destModifier,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
is HomeSettingsDestination.ChooseRowType -> {
|
is ChooseRowType -> {
|
||||||
HomeLibraryRowTypeList(
|
HomeLibraryRowTypeList(
|
||||||
library = dest.library,
|
library = dest.library,
|
||||||
onClick = {
|
onClick = {
|
||||||
|
|
@ -108,7 +127,7 @@ fun HomeSettingsPage(
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
is HomeSettingsDestination.RowSettings -> {
|
is RowSettings -> {
|
||||||
val row =
|
val row =
|
||||||
state.rows
|
state.rows
|
||||||
.first { it.id == dest.rowId }
|
.first { it.id == dest.rowId }
|
||||||
|
|
@ -124,6 +143,18 @@ fun HomeSettingsPage(
|
||||||
modifier = destModifier,
|
modifier = destModifier,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
HomeSettingsDestination.ChooseDiscover -> {
|
||||||
|
TODO()
|
||||||
|
}
|
||||||
|
|
||||||
|
HomeSettingsDestination.ChooseFavorite -> {
|
||||||
|
HomeSettingsFavoriteList(
|
||||||
|
onClick = { type ->
|
||||||
|
viewModel.addFavoriteRow(type)
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
HomePageContent(
|
HomePageContent(
|
||||||
|
|
|
||||||
|
|
@ -23,9 +23,11 @@ import androidx.compose.runtime.LaunchedEffect
|
||||||
import androidx.compose.runtime.remember
|
import androidx.compose.runtime.remember
|
||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.focus.FocusDirection
|
||||||
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.focus.focusRestorer
|
import androidx.compose.ui.focus.focusRestorer
|
||||||
|
import androidx.compose.ui.platform.LocalFocusManager
|
||||||
import androidx.compose.ui.res.stringResource
|
import androidx.compose.ui.res.stringResource
|
||||||
import androidx.compose.ui.text.style.TextOverflow
|
import androidx.compose.ui.text.style.TextOverflow
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
|
|
@ -57,6 +59,7 @@ fun HomeSettingsRowList(
|
||||||
modifier: Modifier,
|
modifier: Modifier,
|
||||||
firstFocus: FocusRequester = remember { FocusRequester() },
|
firstFocus: FocusRequester = remember { FocusRequester() },
|
||||||
) {
|
) {
|
||||||
|
val focusManager = LocalFocusManager.current
|
||||||
LaunchedEffect(Unit) { firstFocus.tryRequestFocus() }
|
LaunchedEffect(Unit) { firstFocus.tryRequestFocus() }
|
||||||
Column(modifier = modifier) {
|
Column(modifier = modifier) {
|
||||||
LazyColumn(
|
LazyColumn(
|
||||||
|
|
@ -148,7 +151,14 @@ fun HomeSettingsRowList(
|
||||||
moveDownAllowed = index != state.rows.lastIndex,
|
moveDownAllowed = index != state.rows.lastIndex,
|
||||||
deleteAllowed = state.rows.size > 1,
|
deleteAllowed = state.rows.size > 1,
|
||||||
onClickMove = { onClickMove.invoke(it, index) },
|
onClickMove = { onClickMove.invoke(it, index) },
|
||||||
onClickDelete = { onClickDelete.invoke(index) },
|
onClickDelete = {
|
||||||
|
if (index != state.rows.lastIndex) {
|
||||||
|
focusManager.moveFocus(FocusDirection.Down)
|
||||||
|
} else {
|
||||||
|
focusManager.moveFocus(FocusDirection.Up)
|
||||||
|
}
|
||||||
|
onClickDelete.invoke(index)
|
||||||
|
},
|
||||||
onClick = { onClick.invoke(index, row) },
|
onClick = { onClick.invoke(index, row) },
|
||||||
modifier =
|
modifier =
|
||||||
Modifier
|
Modifier
|
||||||
|
|
|
||||||
|
|
@ -11,11 +11,15 @@ import com.github.damontecres.wholphin.data.ServerRepository
|
||||||
import com.github.damontecres.wholphin.data.model.BaseItem
|
import com.github.damontecres.wholphin.data.model.BaseItem
|
||||||
import com.github.damontecres.wholphin.data.model.HomePageSettings
|
import com.github.damontecres.wholphin.data.model.HomePageSettings
|
||||||
import com.github.damontecres.wholphin.data.model.HomeRowConfig
|
import com.github.damontecres.wholphin.data.model.HomeRowConfig
|
||||||
|
import com.github.damontecres.wholphin.data.model.HomeRowConfig.ContinueWatching
|
||||||
|
import com.github.damontecres.wholphin.data.model.HomeRowConfig.ContinueWatchingCombined
|
||||||
|
import com.github.damontecres.wholphin.data.model.HomeRowConfig.NextUp
|
||||||
import com.github.damontecres.wholphin.data.model.HomeRowViewOptions
|
import com.github.damontecres.wholphin.data.model.HomeRowViewOptions
|
||||||
import com.github.damontecres.wholphin.services.BackdropService
|
import com.github.damontecres.wholphin.services.BackdropService
|
||||||
import com.github.damontecres.wholphin.services.HomePageResolvedSettings
|
import com.github.damontecres.wholphin.services.HomePageResolvedSettings
|
||||||
import com.github.damontecres.wholphin.services.HomeRowConfigDisplay
|
import com.github.damontecres.wholphin.services.HomeRowConfigDisplay
|
||||||
import com.github.damontecres.wholphin.services.HomeSettingsService
|
import com.github.damontecres.wholphin.services.HomeSettingsService
|
||||||
|
import com.github.damontecres.wholphin.services.SeerrServerRepository
|
||||||
import com.github.damontecres.wholphin.services.UserPreferencesService
|
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.ui.launchIO
|
import com.github.damontecres.wholphin.ui.launchIO
|
||||||
|
|
@ -30,6 +34,7 @@ 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 org.jellyfin.sdk.model.api.BaseItemKind
|
||||||
import org.jellyfin.sdk.model.api.CollectionType
|
import org.jellyfin.sdk.model.api.CollectionType
|
||||||
import timber.log.Timber
|
import timber.log.Timber
|
||||||
import java.util.UUID
|
import java.util.UUID
|
||||||
|
|
@ -45,11 +50,14 @@ class HomeSettingsViewModel
|
||||||
private val userPreferencesService: UserPreferencesService,
|
private val userPreferencesService: UserPreferencesService,
|
||||||
private val navDrawerItemRepository: NavDrawerItemRepository,
|
private val navDrawerItemRepository: NavDrawerItemRepository,
|
||||||
private val backdropService: BackdropService,
|
private val backdropService: BackdropService,
|
||||||
|
private val seerrServerRepository: SeerrServerRepository,
|
||||||
@param:IoCoroutineScope private val ioScope: CoroutineScope,
|
@param:IoCoroutineScope private val ioScope: CoroutineScope,
|
||||||
) : ViewModel() {
|
) : ViewModel() {
|
||||||
private val _state = MutableStateFlow(HomePageSettingsState.EMPTY)
|
private val _state = MutableStateFlow(HomePageSettingsState.EMPTY)
|
||||||
val state: StateFlow<HomePageSettingsState> = _state
|
val state: StateFlow<HomePageSettingsState> = _state
|
||||||
|
|
||||||
|
val discoverEnabled = seerrServerRepository.active
|
||||||
|
|
||||||
init {
|
init {
|
||||||
addCloseable { saveToLocal() }
|
addCloseable { saveToLocal() }
|
||||||
viewModelScope.launchIO {
|
viewModelScope.launchIO {
|
||||||
|
|
@ -164,7 +172,7 @@ class HomeSettingsViewModel
|
||||||
HomeRowConfigDisplay(
|
HomeRowConfigDisplay(
|
||||||
id = id,
|
id = id,
|
||||||
title = context.getString(R.string.continue_watching),
|
title = context.getString(R.string.continue_watching),
|
||||||
config = HomeRowConfig.ContinueWatching(),
|
config = ContinueWatching(),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -172,7 +180,7 @@ class HomeSettingsViewModel
|
||||||
HomeRowConfigDisplay(
|
HomeRowConfigDisplay(
|
||||||
id = id,
|
id = id,
|
||||||
title = context.getString(R.string.continue_watching),
|
title = context.getString(R.string.continue_watching),
|
||||||
config = HomeRowConfig.NextUp(),
|
config = NextUp(),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -180,9 +188,17 @@ class HomeSettingsViewModel
|
||||||
HomeRowConfigDisplay(
|
HomeRowConfigDisplay(
|
||||||
id = id,
|
id = id,
|
||||||
title = context.getString(R.string.combine_continue_next),
|
title = context.getString(R.string.combine_continue_next),
|
||||||
config = HomeRowConfig.ContinueWatchingCombined(),
|
config = ContinueWatchingCombined(),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
MetaRowType.FAVORITES -> {
|
||||||
|
throw IllegalArgumentException("Should use addRow(BaseItemKind) instead")
|
||||||
|
}
|
||||||
|
|
||||||
|
MetaRowType.DISCOVER -> {
|
||||||
|
TODO()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
updateState {
|
updateState {
|
||||||
it.copy(
|
it.copy(
|
||||||
|
|
@ -246,6 +262,26 @@ class HomeSettingsViewModel
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun addFavoriteRow(type: BaseItemKind) {
|
||||||
|
viewModelScope.launchIO {
|
||||||
|
Timber.v("Adding favorite row for $type")
|
||||||
|
val id = state.value.rows.size
|
||||||
|
val newRow =
|
||||||
|
HomeRowConfigDisplay(
|
||||||
|
id = id,
|
||||||
|
title = context.getString(favoriteOptions[type]!!),
|
||||||
|
config = HomeRowConfig.Favorite(type),
|
||||||
|
)
|
||||||
|
updateState {
|
||||||
|
it.copy(
|
||||||
|
loading = LoadingState.Loading,
|
||||||
|
rows = it.rows.toMutableList().apply { add(newRow) },
|
||||||
|
)
|
||||||
|
}
|
||||||
|
fetchRowData()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fun updateViewOptions(
|
fun updateViewOptions(
|
||||||
rowId: Int,
|
rowId: Int,
|
||||||
viewOptions: HomeRowViewOptions,
|
viewOptions: HomeRowViewOptions,
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue