[tvOS] ItemTypeLibraryViewModel - Implement FilterViewModel (#1409)
* FilterViewModel only * comments --------- Co-authored-by: Ethan Pippin <ethanpippin2343@gmail.com>
This commit is contained in:
parent
c9ae01e792
commit
35c39a8d0a
|
@ -48,7 +48,10 @@ final class MainTabCoordinator: TabCoordinatable {
|
|||
}
|
||||
|
||||
func makeTVShows() -> NavigationViewCoordinator<LibraryCoordinator<BaseItemDto>> {
|
||||
let viewModel = ItemTypeLibraryViewModel(itemTypes: [.series])
|
||||
let viewModel = ItemTypeLibraryViewModel(
|
||||
itemTypes: [.series],
|
||||
filters: .default
|
||||
)
|
||||
return NavigationViewCoordinator(LibraryCoordinator(viewModel: viewModel))
|
||||
}
|
||||
|
||||
|
@ -62,7 +65,10 @@ final class MainTabCoordinator: TabCoordinatable {
|
|||
}
|
||||
|
||||
func makeMovies() -> NavigationViewCoordinator<LibraryCoordinator<BaseItemDto>> {
|
||||
let viewModel = ItemTypeLibraryViewModel(itemTypes: [.movie])
|
||||
let viewModel = ItemTypeLibraryViewModel(
|
||||
itemTypes: [.movie],
|
||||
filters: .default
|
||||
)
|
||||
return NavigationViewCoordinator(LibraryCoordinator(viewModel: viewModel))
|
||||
}
|
||||
|
||||
|
|
|
@ -19,12 +19,24 @@ final class FilterViewModel: ViewModel {
|
|||
var allFilters: ItemFilterCollection = .all
|
||||
|
||||
private let parent: (any LibraryParent)?
|
||||
private let itemTypes: [BaseItemKind]?
|
||||
|
||||
init(
|
||||
parent: (any LibraryParent)? = nil,
|
||||
currentFilters: ItemFilterCollection = .default
|
||||
) {
|
||||
self.parent = parent
|
||||
self.itemTypes = nil
|
||||
self.currentFilters = currentFilters
|
||||
super.init()
|
||||
}
|
||||
|
||||
init(
|
||||
itemTypes: [BaseItemKind],
|
||||
currentFilters: ItemFilterCollection = .default
|
||||
) {
|
||||
self.parent = nil
|
||||
self.itemTypes = itemTypes
|
||||
self.currentFilters = currentFilters
|
||||
super.init()
|
||||
}
|
||||
|
@ -43,7 +55,8 @@ final class FilterViewModel: ViewModel {
|
|||
private func getQueryFilters() async -> (genres: [ItemGenre], tags: [ItemTag], years: [ItemYear]) {
|
||||
let parameters = Paths.GetQueryFiltersLegacyParameters(
|
||||
userID: userSession.user.id,
|
||||
parentID: parent?.id as? String
|
||||
parentID: parent?.id as? String,
|
||||
includeItemTypes: itemTypes
|
||||
)
|
||||
|
||||
let request = Paths.getQueryFiltersLegacy(parameters: parameters)
|
||||
|
|
|
@ -56,9 +56,9 @@ final class ItemLibraryViewModel: PagingLibraryViewModel<BaseItemDto> {
|
|||
var includeItemTypes: [BaseItemKind] = [.movie, .series, .boxSet]
|
||||
var isRecursive: Bool? = true
|
||||
|
||||
// TODO: determine `includeItemTypes` better
|
||||
// - look at parent collection type if necessary
|
||||
// - condense supported values
|
||||
// TODO: this logic should be moved to a `LibraryParent` function
|
||||
// that transforms a `GetItemsByUserIDParameters` struct, instead
|
||||
// of having to do this case-by-case.
|
||||
|
||||
if let libraryType = parent?.libraryType, let id = parent?.id {
|
||||
switch libraryType {
|
||||
|
|
|
@ -11,17 +11,28 @@ import Foundation
|
|||
import Get
|
||||
import JellyfinAPI
|
||||
|
||||
// TODO: atow, this is only really used for tvOS tabs
|
||||
// TODO: filtering on `itemTypes` should be moved to `ItemFilterCollection`,
|
||||
// but there is additional logic based on the parent type, mainly `.folder`.
|
||||
final class ItemTypeLibraryViewModel: PagingLibraryViewModel<BaseItemDto> {
|
||||
|
||||
let itemTypes: [BaseItemKind]
|
||||
|
||||
init(itemTypes: [BaseItemKind]) {
|
||||
// MARK: Initializer
|
||||
|
||||
init(
|
||||
itemTypes: [BaseItemKind],
|
||||
filters: ItemFilterCollection? = nil
|
||||
) {
|
||||
self.itemTypes = itemTypes
|
||||
|
||||
super.init()
|
||||
super.init(
|
||||
itemTypes: itemTypes,
|
||||
filters: filters
|
||||
)
|
||||
}
|
||||
|
||||
// MARK: Get Page
|
||||
|
||||
override func get(page: Int) async throws -> [BaseItemDto] {
|
||||
|
||||
let parameters = itemParameters(for: page)
|
||||
|
@ -31,6 +42,8 @@ final class ItemTypeLibraryViewModel: PagingLibraryViewModel<BaseItemDto> {
|
|||
return response.value.items ?? []
|
||||
}
|
||||
|
||||
// MARK: Item Parameters
|
||||
|
||||
func itemParameters(for page: Int?) -> Paths.GetItemsByUserIDParameters {
|
||||
|
||||
var parameters = Paths.GetItemsByUserIDParameters()
|
||||
|
@ -38,8 +51,6 @@ final class ItemTypeLibraryViewModel: PagingLibraryViewModel<BaseItemDto> {
|
|||
parameters.fields = .MinimumFields
|
||||
parameters.includeItemTypes = itemTypes
|
||||
parameters.isRecursive = true
|
||||
parameters.sortBy = [ItemSortBy.name.rawValue]
|
||||
parameters.sortOrder = [.ascending]
|
||||
|
||||
// Page size
|
||||
if let page {
|
||||
|
@ -47,6 +58,48 @@ final class ItemTypeLibraryViewModel: PagingLibraryViewModel<BaseItemDto> {
|
|||
parameters.startIndex = page * pageSize
|
||||
}
|
||||
|
||||
// Filters
|
||||
if let filterViewModel {
|
||||
let filters = filterViewModel.currentFilters
|
||||
parameters.filters = filters.traits
|
||||
parameters.genres = filters.genres.map(\.value)
|
||||
parameters.sortBy = filters.sortBy.map(\.rawValue)
|
||||
parameters.sortOrder = filters.sortOrder
|
||||
parameters.tags = filters.tags.map(\.value)
|
||||
parameters.years = filters.years.compactMap { Int($0.value) }
|
||||
|
||||
if filters.letter.first?.value == "#" {
|
||||
parameters.nameLessThan = "A"
|
||||
} else {
|
||||
parameters.nameStartsWith = filters.letter
|
||||
.map(\.value)
|
||||
.filter { $0 != "#" }
|
||||
.first
|
||||
}
|
||||
|
||||
// Random sort won't take into account previous items, so
|
||||
// manual exclusion is necessary. This could possibly be
|
||||
// a performance issue for loading pages after already loading
|
||||
// many items, but there's nothing we can do about that.
|
||||
if filters.sortBy.first == ItemSortBy.random {
|
||||
parameters.excludeItemIDs = elements.compactMap(\.id)
|
||||
}
|
||||
}
|
||||
|
||||
return parameters
|
||||
}
|
||||
|
||||
// MARK: Get Random Item
|
||||
|
||||
override func getRandomItem() async -> BaseItemDto? {
|
||||
|
||||
var parameters = itemParameters(for: nil)
|
||||
parameters.limit = 1
|
||||
parameters.sortBy = [ItemSortBy.random.rawValue]
|
||||
|
||||
let request = Paths.getItemsByUserID(userID: userSession.user.id, parameters: parameters)
|
||||
let response = try? await userSession.client.send(request)
|
||||
|
||||
return response?.value.items?.first
|
||||
}
|
||||
}
|
||||
|
|
|
@ -49,6 +49,8 @@ protocol LibraryIdentifiable: Identifiable {
|
|||
// on refresh. Should make bidirectional/offset index start?
|
||||
// - use startIndex/index ranges instead of pages
|
||||
// - source of data doesn't guarantee that all items in 0 ..< startIndex exist
|
||||
// TODO: have `filterViewModel` be private to the parent and the `get_` overrides recieve the
|
||||
// current filters as a parameter
|
||||
|
||||
/*
|
||||
Note: if `rememberSort == true`, then will override given filters with stored sorts
|
||||
|
@ -218,6 +220,52 @@ class PagingLibraryViewModel<Element: Poster>: ViewModel, Eventful, Stateful {
|
|||
}
|
||||
}
|
||||
|
||||
// paging item type
|
||||
init(
|
||||
itemTypes: [BaseItemKind],
|
||||
filters: ItemFilterCollection? = nil,
|
||||
pageSize: Int = DefaultPageSize
|
||||
) {
|
||||
self.elements = IdentifiedArray([], id: \.unwrappedIDHashOrZero, uniquingIDsWith: { x, _ in x })
|
||||
self.isStatic = false
|
||||
self.pageSize = pageSize
|
||||
|
||||
self.parent = nil
|
||||
|
||||
if let filters {
|
||||
self.filterViewModel = .init(
|
||||
itemTypes: itemTypes,
|
||||
currentFilters: filters
|
||||
)
|
||||
} else {
|
||||
self.filterViewModel = nil
|
||||
}
|
||||
|
||||
super.init()
|
||||
|
||||
Notifications[.didDeleteItem]
|
||||
.publisher
|
||||
.sink { id in
|
||||
self.elements.remove(id: id.hashValue)
|
||||
}
|
||||
.store(in: &cancellables)
|
||||
|
||||
if let filterViewModel {
|
||||
filterViewModel.$currentFilters
|
||||
.dropFirst()
|
||||
.debounce(for: 1, scheduler: RunLoop.main)
|
||||
.removeDuplicates()
|
||||
.sink { [weak self] _ in
|
||||
guard let self else { return }
|
||||
|
||||
Task { @MainActor in
|
||||
self.send(.refresh)
|
||||
}
|
||||
}
|
||||
.store(in: &cancellables)
|
||||
}
|
||||
}
|
||||
|
||||
convenience init(
|
||||
title: String,
|
||||
id: String?,
|
||||
|
|
Loading…
Reference in New Issue