Skip to content

Commit

Permalink
[tvOS] ItemTypeLibraryViewModel - Implement FilterViewModel (#1409)
Browse files Browse the repository at this point in the history
* FilterViewModel only

* comments

---------

Co-authored-by: Ethan Pippin <[email protected]>
  • Loading branch information
JPKribs and LePips authored Jan 26, 2025
1 parent c9ae01e commit 35c39a8
Show file tree
Hide file tree
Showing 5 changed files with 131 additions and 11 deletions.
10 changes: 8 additions & 2 deletions Shared/Coordinators/MainCoordinator/tvOSMainTabCoordinator.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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))
}

Expand All @@ -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))
}

Expand Down
15 changes: 14 additions & 1 deletion Shared/ViewModels/FilterViewModel.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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()
}
Expand All @@ -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)
Expand Down
6 changes: 3 additions & 3 deletions Shared/ViewModels/LibraryViewModel/ItemLibraryViewModel.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
63 changes: 58 additions & 5 deletions Shared/ViewModels/LibraryViewModel/ItemTypeLibraryViewModel.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand All @@ -31,22 +42,64 @@ final class ItemTypeLibraryViewModel: PagingLibraryViewModel<BaseItemDto> {
return response.value.items ?? []
}

// MARK: Item Parameters

func itemParameters(for page: Int?) -> Paths.GetItemsByUserIDParameters {

var parameters = Paths.GetItemsByUserIDParameters()
parameters.enableUserData = true
parameters.fields = .MinimumFields
parameters.includeItemTypes = itemTypes
parameters.isRecursive = true
parameters.sortBy = [ItemSortBy.name.rawValue]
parameters.sortOrder = [.ascending]

// Page size
if let page {
parameters.limit = pageSize
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
}
}
48 changes: 48 additions & 0 deletions Shared/ViewModels/LibraryViewModel/PagingLibraryViewModel.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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?,
Expand Down

0 comments on commit 35c39a8

Please sign in to comment.