mirror of
https://github.com/JustinZeus/Wholphin.git
synced 2026-07-09 16:11:20 +02:00
Add ability to filter libraries, collection, etc (#308)
Adds a button to filter contents next to the sort button on grid pages The filters are saved and restored you return to the page. It works with #278, playing the filtered items in the sorted order. The drop down menus for both sorting & filtering are updated to closer match the overall look. Filters for: - Played or unplayed - Favorite or not - Genres - Community (star) rating - Parental rating - Video type (HD vs SD, etc) - Release year and/or decade
This commit is contained in:
parent
50dc4b9a35
commit
78e7eb421f
16 changed files with 1325 additions and 75 deletions
|
|
@ -7,25 +7,29 @@ import androidx.room.TypeConverter
|
|||
import androidx.room.TypeConverters
|
||||
import androidx.room.migration.Migration
|
||||
import androidx.sqlite.db.SupportSQLiteDatabase
|
||||
import com.github.damontecres.wholphin.data.model.GetItemsFilter
|
||||
import com.github.damontecres.wholphin.data.model.ItemPlayback
|
||||
import com.github.damontecres.wholphin.data.model.JellyfinServer
|
||||
import com.github.damontecres.wholphin.data.model.JellyfinUser
|
||||
import com.github.damontecres.wholphin.data.model.LibraryDisplayInfo
|
||||
import com.github.damontecres.wholphin.data.model.NavDrawerPinnedItem
|
||||
import kotlinx.serialization.json.Json
|
||||
import org.jellyfin.sdk.model.api.ItemSortBy
|
||||
import org.jellyfin.sdk.model.api.SortOrder
|
||||
import org.jellyfin.sdk.model.serializer.toUUID
|
||||
import timber.log.Timber
|
||||
import java.util.UUID
|
||||
|
||||
@Database(
|
||||
entities = [JellyfinServer::class, JellyfinUser::class, ItemPlayback::class, NavDrawerPinnedItem::class, LibraryDisplayInfo::class],
|
||||
version = 7,
|
||||
version = 8,
|
||||
exportSchema = true,
|
||||
autoMigrations = [
|
||||
AutoMigration(3, 4),
|
||||
AutoMigration(4, 5),
|
||||
AutoMigration(5, 6),
|
||||
AutoMigration(6, 7),
|
||||
AutoMigration(7, 8),
|
||||
],
|
||||
)
|
||||
@TypeConverters(Converters::class)
|
||||
|
|
@ -57,6 +61,18 @@ class Converters {
|
|||
|
||||
@TypeConverter
|
||||
fun convertSortOrder(sort: String): SortOrder? = SortOrder.fromNameOrNull(sort)
|
||||
|
||||
@TypeConverter
|
||||
fun convertGetItemsFilter(filter: GetItemsFilter): String = Json.encodeToString(filter)
|
||||
|
||||
@TypeConverter
|
||||
fun convertGetItemsFilter(filter: String): GetItemsFilter =
|
||||
try {
|
||||
Json.decodeFromString(filter)
|
||||
} catch (ex: Exception) {
|
||||
Timber.e(ex, "Error parsing filter")
|
||||
GetItemsFilter()
|
||||
}
|
||||
}
|
||||
|
||||
object Migrations {
|
||||
|
|
|
|||
|
|
@ -0,0 +1,6 @@
|
|||
package com.github.damontecres.wholphin.data.filter
|
||||
|
||||
data class FilterValueOption(
|
||||
val name: String,
|
||||
val value: Any?,
|
||||
)
|
||||
|
|
@ -0,0 +1,169 @@
|
|||
package com.github.damontecres.wholphin.data.filter
|
||||
|
||||
import androidx.annotation.StringRes
|
||||
import com.github.damontecres.wholphin.R
|
||||
import com.github.damontecres.wholphin.data.model.GetItemsFilter
|
||||
import java.util.UUID
|
||||
|
||||
val DefaultFilterOptions =
|
||||
listOf(
|
||||
PlayedFilter,
|
||||
FavoriteFilter,
|
||||
GenreFilter,
|
||||
CommunityRatingFilter,
|
||||
OfficialRatingFilter,
|
||||
VideoTypeFilter,
|
||||
YearFilter,
|
||||
DecadeFilter,
|
||||
)
|
||||
|
||||
val DefaultForFavoritesFilterOptions =
|
||||
listOf(
|
||||
PlayedFilter,
|
||||
GenreFilter,
|
||||
CommunityRatingFilter,
|
||||
OfficialRatingFilter,
|
||||
VideoTypeFilter,
|
||||
YearFilter,
|
||||
DecadeFilter,
|
||||
)
|
||||
|
||||
val DefaultForGenresFilterOptions =
|
||||
listOf(
|
||||
PlayedFilter,
|
||||
FavoriteFilter,
|
||||
CommunityRatingFilter,
|
||||
OfficialRatingFilter,
|
||||
VideoTypeFilter,
|
||||
YearFilter,
|
||||
DecadeFilter,
|
||||
)
|
||||
|
||||
sealed interface ItemFilterBy<T> {
|
||||
@get:StringRes
|
||||
val stringRes: Int
|
||||
|
||||
val supportMultiple: Boolean
|
||||
|
||||
fun get(filter: GetItemsFilter): T?
|
||||
|
||||
fun set(
|
||||
value: T?,
|
||||
filter: GetItemsFilter,
|
||||
): GetItemsFilter
|
||||
}
|
||||
|
||||
data object GenreFilter : ItemFilterBy<List<UUID>> {
|
||||
override val stringRes: Int = R.string.genres
|
||||
|
||||
override val supportMultiple: Boolean = true
|
||||
|
||||
override fun get(filter: GetItemsFilter): List<UUID>? = filter.genres
|
||||
|
||||
override fun set(
|
||||
value: List<UUID>?,
|
||||
filter: GetItemsFilter,
|
||||
): GetItemsFilter = filter.copy(genres = value)
|
||||
}
|
||||
|
||||
data object PlayedFilter : ItemFilterBy<Boolean> {
|
||||
override val stringRes: Int = R.string.played
|
||||
|
||||
override val supportMultiple: Boolean = false
|
||||
|
||||
override fun get(filter: GetItemsFilter): Boolean? = filter.played
|
||||
|
||||
override fun set(
|
||||
value: Boolean?,
|
||||
filter: GetItemsFilter,
|
||||
): GetItemsFilter = filter.copy(played = value)
|
||||
}
|
||||
|
||||
data object FavoriteFilter : ItemFilterBy<Boolean> {
|
||||
override val stringRes: Int = R.string.favorites
|
||||
|
||||
override val supportMultiple: Boolean = false
|
||||
|
||||
override fun get(filter: GetItemsFilter): Boolean? = filter.favorite
|
||||
|
||||
override fun set(
|
||||
value: Boolean?,
|
||||
filter: GetItemsFilter,
|
||||
): GetItemsFilter = filter.copy(favorite = value)
|
||||
}
|
||||
|
||||
data object OfficialRatingFilter : ItemFilterBy<List<String>> {
|
||||
override val stringRes: Int = R.string.official_rating
|
||||
|
||||
override val supportMultiple: Boolean = true
|
||||
|
||||
override fun get(filter: GetItemsFilter): List<String>? = filter.officialRatings
|
||||
|
||||
override fun set(
|
||||
value: List<String>?,
|
||||
filter: GetItemsFilter,
|
||||
): GetItemsFilter = filter.copy(officialRatings = value)
|
||||
}
|
||||
|
||||
enum class FilterVideoType(
|
||||
val readable: String,
|
||||
) {
|
||||
FOUR_K("4K"),
|
||||
HD("HD"),
|
||||
SD("SD"),
|
||||
THREE_D("3D"),
|
||||
BLU_RAY("Blu-Ray"),
|
||||
DVD("DVD"),
|
||||
}
|
||||
|
||||
data object VideoTypeFilter : ItemFilterBy<List<FilterVideoType>> {
|
||||
override val stringRes: Int = R.string.video
|
||||
|
||||
override val supportMultiple: Boolean = true
|
||||
|
||||
override fun get(filter: GetItemsFilter): List<FilterVideoType>? = filter.videoTypes
|
||||
|
||||
override fun set(
|
||||
value: List<FilterVideoType>?,
|
||||
filter: GetItemsFilter,
|
||||
): GetItemsFilter = filter.copy(videoTypes = value)
|
||||
}
|
||||
|
||||
data object YearFilter : ItemFilterBy<List<Int>> {
|
||||
override val stringRes: Int = R.string.year
|
||||
|
||||
override val supportMultiple: Boolean = true
|
||||
|
||||
override fun get(filter: GetItemsFilter): List<Int>? = filter.years
|
||||
|
||||
override fun set(
|
||||
value: List<Int>?,
|
||||
filter: GetItemsFilter,
|
||||
): GetItemsFilter = filter.copy(years = value)
|
||||
}
|
||||
|
||||
data object DecadeFilter : ItemFilterBy<List<Int>> {
|
||||
override val stringRes: Int = R.string.decade
|
||||
|
||||
override val supportMultiple: Boolean = true
|
||||
|
||||
override fun get(filter: GetItemsFilter): List<Int>? = filter.decades
|
||||
|
||||
override fun set(
|
||||
value: List<Int>?,
|
||||
filter: GetItemsFilter,
|
||||
): GetItemsFilter = filter.copy(decades = value)
|
||||
}
|
||||
|
||||
data object CommunityRatingFilter : ItemFilterBy<Int> {
|
||||
override val stringRes: Int = R.string.community_rating
|
||||
|
||||
override val supportMultiple: Boolean = true
|
||||
|
||||
override fun get(filter: GetItemsFilter): Int? = filter.minCommunityRating?.toInt()
|
||||
|
||||
override fun set(
|
||||
value: Int?,
|
||||
filter: GetItemsFilter,
|
||||
): GetItemsFilter = filter.copy(minCommunityRating = value?.toDouble())
|
||||
}
|
||||
|
|
@ -2,9 +2,13 @@
|
|||
|
||||
package com.github.damontecres.wholphin.data.model
|
||||
|
||||
import com.github.damontecres.wholphin.data.filter.FilterVideoType
|
||||
import com.github.damontecres.wholphin.data.filter.ItemFilterBy
|
||||
import com.github.damontecres.wholphin.ui.letNotEmpty
|
||||
import kotlinx.serialization.Serializable
|
||||
import kotlinx.serialization.UseSerializers
|
||||
import org.jellyfin.sdk.model.api.BaseItemKind
|
||||
import org.jellyfin.sdk.model.api.VideoType
|
||||
import org.jellyfin.sdk.model.api.request.GetItemsRequest
|
||||
import org.jellyfin.sdk.model.api.request.GetPersonsRequest
|
||||
import org.jellyfin.sdk.model.serializer.UUIDSerializer
|
||||
|
|
@ -14,30 +18,112 @@ import java.util.UUID
|
|||
data class GetItemsFilter(
|
||||
val favorite: Boolean? = null,
|
||||
val genres: List<UUID>? = null,
|
||||
val minCriticRating: Double? = null,
|
||||
val minCommunityRating: Double? = null,
|
||||
val officialRatings: List<String>? = null,
|
||||
val persons: List<UUID>? = null,
|
||||
val played: Boolean? = null,
|
||||
val studios: List<UUID>? = null,
|
||||
val tags: List<String>? = null,
|
||||
val includeItemTypes: List<BaseItemKind>? = null,
|
||||
val videoTypes: List<FilterVideoType>? = null,
|
||||
val years: List<Int>? = null,
|
||||
val decades: List<Int>? = null,
|
||||
val override: GetItemsFilterOverride = GetItemsFilterOverride.NONE,
|
||||
) {
|
||||
/**
|
||||
* Returns how many of filters are actually being used in this [GetItemsFilter]
|
||||
*/
|
||||
fun countFilters(filterOptions: List<ItemFilterBy<*>>): Int {
|
||||
var count = 0
|
||||
filterOptions.forEach {
|
||||
if (it.get(this) != null) count++
|
||||
}
|
||||
return count
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear all of the values for the given filters
|
||||
*/
|
||||
fun delete(filterOptions: List<ItemFilterBy<*>>): GetItemsFilter {
|
||||
var newFilter = this
|
||||
filterOptions.forEach {
|
||||
newFilter = it.set(null, newFilter)
|
||||
}
|
||||
return newFilter
|
||||
}
|
||||
|
||||
fun applyTo(req: GetItemsRequest) =
|
||||
req.copy(
|
||||
includeItemTypes = includeItemTypes,
|
||||
isFavorite = favorite,
|
||||
genreIds = genres,
|
||||
minCriticRating = minCriticRating,
|
||||
minCommunityRating = minCommunityRating,
|
||||
personIds = persons,
|
||||
isPlayed = played,
|
||||
studioIds = studios,
|
||||
tags = tags,
|
||||
officialRatings = officialRatings,
|
||||
years =
|
||||
buildSet {
|
||||
years?.letNotEmpty(::addAll)
|
||||
decades?.forEach { addAll(it..<(it + 10)) }
|
||||
},
|
||||
is4k =
|
||||
videoTypes?.letNotEmpty {
|
||||
videoTypes.contains(FilterVideoType.FOUR_K).takeIf { it }
|
||||
},
|
||||
isHd =
|
||||
videoTypes?.letNotEmpty {
|
||||
if (videoTypes.contains(FilterVideoType.HD)) {
|
||||
true
|
||||
} else if (videoTypes.contains(FilterVideoType.SD)) {
|
||||
false
|
||||
} else {
|
||||
null
|
||||
}
|
||||
},
|
||||
is3d =
|
||||
videoTypes?.letNotEmpty {
|
||||
videoTypes.contains(FilterVideoType.THREE_D).takeIf { it }
|
||||
},
|
||||
videoTypes =
|
||||
videoTypes?.letNotEmpty {
|
||||
it.mapNotNull {
|
||||
when (it) {
|
||||
FilterVideoType.FOUR_K,
|
||||
FilterVideoType.HD,
|
||||
FilterVideoType.SD,
|
||||
FilterVideoType.THREE_D,
|
||||
-> null
|
||||
|
||||
FilterVideoType.BLU_RAY -> VideoType.BLU_RAY
|
||||
FilterVideoType.DVD -> VideoType.DVD
|
||||
}
|
||||
}
|
||||
},
|
||||
)
|
||||
|
||||
fun applyTo(req: GetPersonsRequest) =
|
||||
req.copy(
|
||||
isFavorite = favorite,
|
||||
)
|
||||
|
||||
fun merge(filter: GetItemsFilter): GetItemsFilter =
|
||||
this.copy(
|
||||
favorite = favorite ?: filter.favorite,
|
||||
genres = genres ?: filter.genres,
|
||||
minCommunityRating = minCommunityRating ?: filter.minCommunityRating,
|
||||
officialRatings = officialRatings ?: filter.officialRatings,
|
||||
persons = persons ?: filter.persons,
|
||||
played = played ?: filter.played,
|
||||
studios = studios ?: filter.studios,
|
||||
tags = tags ?: filter.tags,
|
||||
includeItemTypes = includeItemTypes ?: filter.includeItemTypes,
|
||||
videoTypes = videoTypes ?: filter.videoTypes,
|
||||
years = years ?: filter.years,
|
||||
decades = decades ?: filter.decades,
|
||||
override = override,
|
||||
)
|
||||
}
|
||||
|
||||
enum class GetItemsFilterOverride {
|
||||
|
|
|
|||
|
|
@ -2,19 +2,18 @@
|
|||
|
||||
package com.github.damontecres.wholphin.data.model
|
||||
|
||||
import androidx.room.ColumnInfo
|
||||
import androidx.room.Entity
|
||||
import androidx.room.ForeignKey
|
||||
import androidx.room.Ignore
|
||||
import androidx.room.Index
|
||||
import com.github.damontecres.wholphin.ui.data.SortAndDirection
|
||||
import com.github.damontecres.wholphin.ui.toServerString
|
||||
import kotlinx.serialization.Serializable
|
||||
import kotlinx.serialization.Transient
|
||||
import kotlinx.serialization.UseSerializers
|
||||
import org.jellyfin.sdk.model.api.ItemSortBy
|
||||
import org.jellyfin.sdk.model.api.SortOrder
|
||||
import org.jellyfin.sdk.model.serializer.UUIDSerializer
|
||||
import java.util.UUID
|
||||
|
||||
@Entity(
|
||||
foreignKeys = [
|
||||
|
|
@ -35,14 +34,9 @@ data class LibraryDisplayInfo(
|
|||
val itemId: String,
|
||||
val sort: ItemSortBy,
|
||||
val direction: SortOrder,
|
||||
@ColumnInfo(defaultValue = "{}")
|
||||
val filter: GetItemsFilter,
|
||||
) {
|
||||
constructor(
|
||||
userId: Int,
|
||||
itemId: UUID,
|
||||
sort: ItemSortBy,
|
||||
direction: SortOrder,
|
||||
) : this(userId, itemId.toServerString(), sort, direction)
|
||||
|
||||
@Ignore @Transient
|
||||
val sortAndDirection = SortAndDirection(sort, direction)
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue