Add more to discover tab

This commit is contained in:
Damontecres 2026-01-06 20:58:51 -05:00
parent f43ee6b36c
commit 27dc031012
No known key found for this signature in database
5 changed files with 140 additions and 8 deletions

View file

@ -50,6 +50,27 @@ class SeerrService
?.map(::DiscoverItem) ?.map(::DiscoverItem)
.orEmpty() .orEmpty()
suspend fun trending(page: Int = 1): List<DiscoverItem> =
api.searchApi
.discoverTrendingGet(page = page)
.results
?.map(::DiscoverItem)
.orEmpty()
suspend fun upcomingMovies(page: Int = 1): List<DiscoverItem> =
api.searchApi
.discoverMoviesUpcomingGet(page = page)
.results
?.map(::DiscoverItem)
.orEmpty()
suspend fun upcomingTv(page: Int = 1): List<DiscoverItem> =
api.searchApi
.discoverTvUpcomingGet(page = page)
.results
?.map(::DiscoverItem)
.orEmpty()
/** /**
* Get [DiscoverItem]s similar to the JF items such as movies, series, or people * Get [DiscoverItem]s similar to the JF items such as movies, series, or people
* *

View file

@ -0,0 +1,38 @@
package com.github.damontecres.wholphin.ui.detail.discover
import androidx.compose.runtime.Composable
import androidx.compose.runtime.remember
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext
import androidx.tv.material3.MaterialTheme
import com.github.damontecres.wholphin.data.model.DiscoverItem
import com.github.damontecres.wholphin.data.model.DiscoverRating
import com.github.damontecres.wholphin.ui.components.DotSeparatedRow
import timber.log.Timber
@Composable
fun DiscoverQuickDetails(
item: DiscoverItem?,
rating: DiscoverRating?,
modifier: Modifier = Modifier,
) {
Timber.v("id=${item?.id}, rating=$rating")
val context = LocalContext.current
val details =
remember(item) {
buildList {
item
?.releaseDate
?.year
?.toString()
?.let(::add)
}
}
DotSeparatedRow(
texts = details,
communityRating = rating?.audienceRating,
criticRating = rating?.criticRating?.toFloat(),
textStyle = MaterialTheme.typography.titleSmall,
modifier = modifier,
)
}

View file

@ -32,6 +32,8 @@ 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.data.model.DiscoverItem import com.github.damontecres.wholphin.data.model.DiscoverItem
import com.github.damontecres.wholphin.data.model.DiscoverRating
import com.github.damontecres.wholphin.data.model.SeerrItemType
import com.github.damontecres.wholphin.preferences.UserPreferences import com.github.damontecres.wholphin.preferences.UserPreferences
import com.github.damontecres.wholphin.services.BackdropService import com.github.damontecres.wholphin.services.BackdropService
import com.github.damontecres.wholphin.services.NavigationManager import com.github.damontecres.wholphin.services.NavigationManager
@ -40,11 +42,13 @@ import com.github.damontecres.wholphin.ui.cards.DiscoverItemCard
import com.github.damontecres.wholphin.ui.cards.ItemRow import com.github.damontecres.wholphin.ui.cards.ItemRow
import com.github.damontecres.wholphin.ui.components.ErrorMessage import com.github.damontecres.wholphin.ui.components.ErrorMessage
import com.github.damontecres.wholphin.ui.data.RowColumn import com.github.damontecres.wholphin.ui.data.RowColumn
import com.github.damontecres.wholphin.ui.detail.discover.DiscoverQuickDetails
import com.github.damontecres.wholphin.ui.launchIO import com.github.damontecres.wholphin.ui.launchIO
import com.github.damontecres.wholphin.ui.main.HomePageHeader import com.github.damontecres.wholphin.ui.main.HomePageHeader
import com.github.damontecres.wholphin.ui.rememberPosition import com.github.damontecres.wholphin.ui.rememberPosition
import com.github.damontecres.wholphin.ui.tryRequestFocus import com.github.damontecres.wholphin.ui.tryRequestFocus
import com.github.damontecres.wholphin.util.DataLoadingState import com.github.damontecres.wholphin.util.DataLoadingState
import com.google.common.cache.CacheBuilder
import dagger.hilt.android.lifecycle.HiltViewModel import dagger.hilt.android.lifecycle.HiltViewModel
import dagger.hilt.android.qualifiers.ApplicationContext import dagger.hilt.android.qualifiers.ApplicationContext
import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.MutableStateFlow
@ -63,6 +67,7 @@ class SeerrDiscoverViewModel
private val backdropService: BackdropService, private val backdropService: BackdropService,
) : ViewModel() { ) : ViewModel() {
val state = MutableStateFlow<DiscoverState>(DiscoverState()) val state = MutableStateFlow<DiscoverState>(DiscoverState())
val rating = MutableStateFlow<Map<Int, DiscoverRating>>(mapOf())
init { init {
viewModelScope.launchIO { viewModelScope.launchIO {
@ -74,6 +79,21 @@ class SeerrDiscoverViewModel
fetchAndUpdateState(seerrService::discoverTv) { fetchAndUpdateState(seerrService::discoverTv) {
this.copy(tv = DiscoverRowData(context.getString(R.string.tv_shows), it)) this.copy(tv = DiscoverRowData(context.getString(R.string.tv_shows), it))
} }
fetchAndUpdateState(seerrService::trending) {
this.copy(trending = DiscoverRowData(context.getString(R.string.trending), it))
}
fetchAndUpdateState(seerrService::upcomingMovies) {
this.copy(
upcomingMovies =
DiscoverRowData(context.getString(R.string.upcoming_movies), it),
)
}
fetchAndUpdateState(seerrService::upcomingTv) {
this.copy(
upcomingTv =
DiscoverRowData(context.getString(R.string.upcoming_tv), it),
)
}
} }
private fun fetchAndUpdateState( private fun fetchAndUpdateState(
@ -101,6 +121,49 @@ class SeerrDiscoverViewModel
viewModelScope.launchIO { viewModelScope.launchIO {
if (item != null) { if (item != null) {
backdropService.submit("discover_${item.id}", item.backDropUrl) backdropService.submit("discover_${item.id}", item.backDropUrl)
fetchRating(item)
}
}
}
private val ratingCache =
CacheBuilder
.newBuilder()
.maximumSize(100)
.build<Int, DiscoverRating>()
// TODO this is not very efficient
fun fetchRating(item: DiscoverItem) {
viewModelScope.launchIO {
val cachedResult = ratingCache.getIfPresent(item.id)
if (cachedResult != null) {
return@launchIO
}
val result =
when (item.type) {
SeerrItemType.MOVIE -> {
DiscoverRating(
seerrService.api.moviesApi.movieMovieIdRatingsGet(
movieId = item.id,
),
)
}
SeerrItemType.TV -> {
DiscoverRating(seerrService.api.tvApi.tvTvIdRatingsGet(tvId = item.id))
}
SeerrItemType.PERSON -> {
DiscoverRating(null, null)
}
SeerrItemType.UNKNOWN -> {
DiscoverRating(null, null)
}
}
ratingCache.put(item.id, result)
rating.update {
ratingCache.asMap().toMap()
} }
} }
} }
@ -118,6 +181,9 @@ data class DiscoverRowData(
data class DiscoverState( data class DiscoverState(
val movies: DiscoverRowData = DiscoverRowData.EMPTY, val movies: DiscoverRowData = DiscoverRowData.EMPTY,
val tv: DiscoverRowData = DiscoverRowData.EMPTY, val tv: DiscoverRowData = DiscoverRowData.EMPTY,
val trending: DiscoverRowData = DiscoverRowData.EMPTY,
val upcomingMovies: DiscoverRowData = DiscoverRowData.EMPTY,
val upcomingTv: DiscoverRowData = DiscoverRowData.EMPTY,
) )
@Composable @Composable
@ -127,7 +193,9 @@ fun SeerrDiscoverPage(
viewModel: SeerrDiscoverViewModel = hiltViewModel(), viewModel: SeerrDiscoverViewModel = hiltViewModel(),
) { ) {
val state by viewModel.state.collectAsState() val state by viewModel.state.collectAsState()
val rows = listOf(state.movies, state.tv) val rows =
listOf(state.trending, state.movies, state.tv, state.upcomingMovies, state.upcomingTv)
val ratingMap by viewModel.rating.collectAsState()
val focusRequesters = remember(2) { List(rows.size) { FocusRequester() } } val focusRequesters = remember(2) { List(rows.size) { FocusRequester() } }
var position by rememberPosition(0, -1) var position by rememberPosition(0, -1)
@ -141,8 +209,8 @@ fun SeerrDiscoverPage(
viewModel.updateBackdrop(focusedItem) viewModel.updateBackdrop(focusedItem)
} }
var firstFocused by rememberSaveable { mutableStateOf(false) } var firstFocused by rememberSaveable { mutableStateOf(false) }
LaunchedEffect(state.movies) { LaunchedEffect(state.trending) {
if (!firstFocused && state.movies.items is DataLoadingState.Success<*>) { if (!firstFocused && state.trending.items is DataLoadingState.Success<*>) {
firstFocused = focusRequesters.getOrNull(0)?.tryRequestFocus("discover") == true firstFocused = focusRequesters.getOrNull(0)?.tryRequestFocus("discover") == true
} }
} }
@ -156,12 +224,15 @@ fun SeerrDiscoverPage(
overview = focusedItem?.overview, overview = focusedItem?.overview,
overviewTwoLines = true, overviewTwoLines = true,
quickDetails = { quickDetails = {
// TODO DiscoverQuickDetails(
item = focusedItem,
rating = focusedItem?.id?.let { ratingMap[it] },
)
}, },
modifier = modifier =
Modifier Modifier
.fillMaxHeight(.33f) .padding(top = 24.dp, bottom = 16.dp, start = 32.dp)
.padding(top = 48.dp, bottom = 32.dp, start = 32.dp), .fillMaxHeight(.25f),
) )
LazyColumn( LazyColumn(
verticalArrangement = Arrangement.spacedBy(8.dp), verticalArrangement = Arrangement.spacedBy(8.dp),

View file

@ -640,8 +640,7 @@ fun NavigationDrawerScope.NavItem(
} }
NavDrawerItem.Discover -> { NavDrawerItem.Discover -> {
// TODO seerr R.string.fa_magnifying_glass_plus
R.string.fa_list_ul
} }
is ServerNavDrawerItem -> { is ServerNavDrawerItem -> {

View file

@ -428,6 +428,9 @@
<string name="password">Password</string> <string name="password">Password</string>
<string name="username">Username</string> <string name="username">Username</string>
<string name="url">URL</string> <string name="url">URL</string>
<string name="trending">Trending</string>
<string name="upcoming_movies">Upcoming Movies</string>
<string name="upcoming_tv">Upcoming TV Shows</string>
<string-array name="theme_song_volume"> <string-array name="theme_song_volume">
<item>Disabled</item> <item>Disabled</item>