mirror of
https://github.com/JustinZeus/Wholphin.git
synced 2026-11-06 02:40:58 +01:00
Only show relevant parental ratings when filtering (#1529)
## Description Updates the parental rating filter so it only shows the relevant options instead of all of them. This is now the same behavior as the web UI. Additionally, the filter options are cached for a short time so the UI doesn't need to load them every time the menu is opened. Dev note: Refactors `FilterUtils` into a service which allows for the caching ### Related issues Closes #410 ### Testing Emulator with movie & tv show libraries ## Screenshots N/A ## AI or LLM usage None
This commit is contained in:
parent
d1b8c09615
commit
a4ee1e5175
5 changed files with 192 additions and 138 deletions
|
|
@ -0,0 +1,183 @@
|
|||
package com.github.damontecres.wholphin.services
|
||||
|
||||
import android.content.Context
|
||||
import com.github.damontecres.wholphin.data.ServerRepository
|
||||
import com.github.damontecres.wholphin.data.filter.CommunityRatingFilter
|
||||
import com.github.damontecres.wholphin.data.filter.DecadeFilter
|
||||
import com.github.damontecres.wholphin.data.filter.FavoriteFilter
|
||||
import com.github.damontecres.wholphin.data.filter.FilterValueOption
|
||||
import com.github.damontecres.wholphin.data.filter.FilterVideoType
|
||||
import com.github.damontecres.wholphin.data.filter.GenreFilter
|
||||
import com.github.damontecres.wholphin.data.filter.ItemFilterBy
|
||||
import com.github.damontecres.wholphin.data.filter.OfficialRatingFilter
|
||||
import com.github.damontecres.wholphin.data.filter.PlayedFilter
|
||||
import com.github.damontecres.wholphin.data.filter.StudioFilter
|
||||
import com.github.damontecres.wholphin.data.filter.VideoTypeFilter
|
||||
import com.github.damontecres.wholphin.data.filter.YearFilter
|
||||
import com.github.damontecres.wholphin.services.hilt.IoCoroutineScope
|
||||
import com.github.damontecres.wholphin.ui.showToast
|
||||
import com.mayakapps.kache.InMemoryKache
|
||||
import dagger.hilt.android.qualifiers.ApplicationContext
|
||||
import kotlinx.coroutines.CancellationException
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import org.jellyfin.sdk.api.client.ApiClient
|
||||
import org.jellyfin.sdk.api.client.extensions.filterApi
|
||||
import org.jellyfin.sdk.api.client.extensions.genresApi
|
||||
import org.jellyfin.sdk.api.client.extensions.localizationApi
|
||||
import org.jellyfin.sdk.api.client.extensions.studiosApi
|
||||
import org.jellyfin.sdk.api.client.extensions.yearsApi
|
||||
import org.jellyfin.sdk.model.UUID
|
||||
import org.jellyfin.sdk.model.api.BaseItemKind
|
||||
import org.jellyfin.sdk.model.api.ItemSortBy
|
||||
import org.jellyfin.sdk.model.api.SortOrder
|
||||
import timber.log.Timber
|
||||
import java.util.TreeSet
|
||||
import javax.inject.Inject
|
||||
import javax.inject.Singleton
|
||||
import kotlin.time.Duration.Companion.hours
|
||||
|
||||
/**
|
||||
* Get the possible values for filters in a library
|
||||
*/
|
||||
@Singleton
|
||||
class FilterOptionCache
|
||||
@Inject
|
||||
constructor(
|
||||
@param:ApplicationContext private val context: Context,
|
||||
@param:IoCoroutineScope private val ioCoroutineScope: CoroutineScope,
|
||||
private val api: ApiClient,
|
||||
private val serverRepository: ServerRepository,
|
||||
) {
|
||||
private val cache =
|
||||
InMemoryKache<FilterOptionCacheKey, List<FilterValueOption>>(16) {
|
||||
creationScope = ioCoroutineScope
|
||||
expireAfterWriteDuration = 1.hours
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the possible values for a filter
|
||||
*
|
||||
* For example, the possible genres in the parent ID
|
||||
*/
|
||||
suspend fun getFilterOptionValues(
|
||||
parentId: UUID?,
|
||||
filterOption: ItemFilterBy<*>,
|
||||
): List<FilterValueOption> {
|
||||
val cacheKey = FilterOptionCacheKey(serverRepository.currentUser?.id, parentId, filterOption)
|
||||
return try {
|
||||
cache
|
||||
.getOrPut(cacheKey) { (userId, parentId, filterOption) ->
|
||||
getFilterOptionValues(userId, parentId, filterOption)
|
||||
}.orEmpty()
|
||||
} catch (ex: CancellationException) {
|
||||
throw ex
|
||||
} catch (ex: Exception) {
|
||||
Timber.e(ex, "Error fetching options for %s", filterOption)
|
||||
showToast(context, "Error occurred: ${ex.localizedMessage}")
|
||||
emptyList()
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun getFilterOptionValues(
|
||||
userId: UUID?,
|
||||
parentId: UUID?,
|
||||
filterOption: ItemFilterBy<*>,
|
||||
) = when (filterOption) {
|
||||
GenreFilter -> {
|
||||
api.genresApi
|
||||
.getGenres(
|
||||
parentId = parentId,
|
||||
userId = userId,
|
||||
).content.items
|
||||
.map { FilterValueOption(it.name ?: "", it.id) }
|
||||
}
|
||||
|
||||
StudioFilter -> {
|
||||
api.studiosApi
|
||||
.getStudios(
|
||||
parentId = parentId,
|
||||
userId = userId,
|
||||
includeItemTypes = listOf(BaseItemKind.SERIES),
|
||||
).content.items
|
||||
.map { FilterValueOption(it.name ?: "", it.id) }
|
||||
}
|
||||
|
||||
FavoriteFilter,
|
||||
PlayedFilter,
|
||||
-> {
|
||||
listOf(
|
||||
FilterValueOption("True", null),
|
||||
FilterValueOption("False", null),
|
||||
)
|
||||
}
|
||||
|
||||
OfficialRatingFilter -> {
|
||||
val ratings =
|
||||
api.localizationApi
|
||||
.getParentalRatings()
|
||||
.content
|
||||
.associate {
|
||||
it.name to it.value
|
||||
}
|
||||
api.filterApi
|
||||
.getQueryFiltersLegacy(
|
||||
parentId = parentId,
|
||||
userId = userId,
|
||||
).content.officialRatings
|
||||
?.mapNotNull { r ->
|
||||
val value = ratings[r]
|
||||
value?.let { FilterValueOption(r, value) }
|
||||
}?.sortedBy { it.value as Int }
|
||||
.orEmpty()
|
||||
}
|
||||
|
||||
VideoTypeFilter -> {
|
||||
FilterVideoType.entries.map {
|
||||
FilterValueOption(it.readable, it)
|
||||
}
|
||||
}
|
||||
|
||||
YearFilter -> {
|
||||
api.yearsApi
|
||||
.getYears(
|
||||
parentId = parentId,
|
||||
userId = userId,
|
||||
sortBy = listOf(ItemSortBy.SORT_NAME),
|
||||
sortOrder = listOf(SortOrder.ASCENDING),
|
||||
).content.items
|
||||
.mapNotNull {
|
||||
it.name?.toIntOrNull()?.let { FilterValueOption(it.toString(), it) }
|
||||
}
|
||||
}
|
||||
|
||||
DecadeFilter -> {
|
||||
val items = TreeSet<Int>()
|
||||
api.yearsApi
|
||||
.getYears(
|
||||
parentId = parentId,
|
||||
userId = userId,
|
||||
sortBy = listOf(ItemSortBy.SORT_NAME),
|
||||
sortOrder = listOf(SortOrder.ASCENDING),
|
||||
).content.items
|
||||
.mapNotNullTo(items) {
|
||||
it.name
|
||||
?.toIntOrNull()
|
||||
?.div(10)
|
||||
?.times(10)
|
||||
}
|
||||
items.toList().sorted().map { FilterValueOption("$it's", it) }
|
||||
}
|
||||
|
||||
CommunityRatingFilter -> {
|
||||
(1..10).map {
|
||||
FilterValueOption("$it", it)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private data class FilterOptionCacheKey(
|
||||
val userId: UUID?,
|
||||
val parentId: UUID?,
|
||||
val filterOption: ItemFilterBy<*>,
|
||||
)
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue