Discover movies & tv shows by genre (#1610)

## Description
Adds genre rows for movies & TV shows to the discover page. These work
the same as on the Seerr web UI, clicking one will show media in the
genre.

### Related issues
Related to https://github.com/damontecres/Wholphin/issues/702

### Testing
Just on the emulator so far

## Screenshots
<img width="1280" height="720" alt="discover_genres Large"
src="https://github.com/user-attachments/assets/93e5ef25-e253-49bc-9009-89f814889c98"
/>


## AI or LLM usage
None
This commit is contained in:
Damontecres 2026-06-22 19:35:44 -04:00 • committed by GitHub
parent c945e1a472
commit f21b29fe1b
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
9 changed files with 564 additions and 130 deletions

View file

@ -5,9 +5,14 @@ import com.github.damontecres.wholphin.R
import com.github.damontecres.wholphin.api.seerr.model.MovieResult
import com.github.damontecres.wholphin.api.seerr.model.TvResult
import com.github.damontecres.wholphin.data.model.DiscoverItem
import com.github.damontecres.wholphin.data.model.SeerrItemType
import com.github.damontecres.wholphin.services.SeerrApi
import com.github.damontecres.wholphin.services.SeerrSearchResult
import com.github.damontecres.wholphin.ui.discover.discoverGenreTitle
import com.github.damontecres.wholphin.ui.util.ResStringProvider
import com.github.damontecres.wholphin.ui.util.StringProvider
import kotlinx.coroutines.CoroutineScope
import kotlinx.serialization.Serializable
/**
* A [RequestPager] for Seerr server queries
@ -45,6 +50,27 @@ enum class DiscoverRequestType(
UNKNOWN(R.string.unknown),
}
@Serializable
sealed interface DiscoverPagerType {
val title: StringProvider
@Serializable
data class RequestType(
val type: DiscoverRequestType,
) : DiscoverPagerType {
override val title: StringProvider = ResStringProvider(type.stringRes)
}
@Serializable
data class Genre(
val genreId: Int,
val name: String,
val type: SeerrItemType,
) : DiscoverPagerType {
override val title: StringProvider = discoverGenreTitle(name, type)
}
}
/**
* Specifies how a [RequestPager] should prepare and execute API calls
*/
@ -55,27 +81,37 @@ interface DiscoverRequestHandler<T> {
): QueryResult<T>
}
val DiscoverTvRequestHandler =
object : DiscoverRequestHandler<TvResult> {
override suspend fun execute(
api: SeerrApi,
pageNumber: Int,
): QueryResult<TvResult> =
api.api.searchApi.discoverTvGet(page = pageNumber).let {
class DiscoverTvRequestHandler(
private val genre: Int? = null,
) : DiscoverRequestHandler<TvResult> {
override suspend fun execute(
api: SeerrApi,
pageNumber: Int,
): QueryResult<TvResult> =
api.api.searchApi
.discoverTvGet(
page = pageNumber,
genre = genre?.toString(),
).let {
QueryResult(it.results.orEmpty(), it.totalResults ?: 0)
}
}
}
val DiscoverMovieRequestHandler =
object : DiscoverRequestHandler<MovieResult> {
override suspend fun execute(
api: SeerrApi,
pageNumber: Int,
): QueryResult<MovieResult> =
api.api.searchApi.discoverMoviesGet(page = pageNumber).let {
class DiscoverMovieRequestHandler(
private val genre: Int? = null,
) : DiscoverRequestHandler<MovieResult> {
override suspend fun execute(
api: SeerrApi,
pageNumber: Int,
): QueryResult<MovieResult> =
api.api.searchApi
.discoverMoviesGet(
page = pageNumber,
genre = genre?.toString(),
).let {
QueryResult(it.results.orEmpty(), it.totalResults ?: 0)
}
}
}
val TrendingRequestHandler =
object : DiscoverRequestHandler<SeerrSearchResult> {