[iOS & tvOS] FilterViewModel - Cleanup (#1412)
* Filter Changes * Use `viewModel.modifiedFilters` for tracking if the filter has been modified. Update the init and update. Hold only the modified filters in `modifiedFilters` instead of `(modifiedFilters, bool)` since that's just clunky and unnecessary. * Reset button should be disabled when only THAT filter is non-default. * ... * PagingLIbraryViewModel.filterQueryTask is no longer in use since that should now be handled on the FilterViewModel * fix merge * cleanup --------- Co-authored-by: Ethan Pippin <ethanpippin2343@gmail.com>
This commit is contained in:
parent
c934ac4219
commit
0235793bc6
|
@ -22,8 +22,13 @@ struct SelectorView<Element: Displayable & Hashable, Label: View>: View {
|
||||||
@Default(.accentColor)
|
@Default(.accentColor)
|
||||||
private var accentColor
|
private var accentColor
|
||||||
|
|
||||||
@StateObject
|
@State
|
||||||
private var selection: BindingBox<Set<Element>>
|
private var selectedItems: Set<Element>
|
||||||
|
|
||||||
|
private let selectionBinding: Binding<Set<Element>>
|
||||||
|
private let sources: [Element]
|
||||||
|
private var label: (Element) -> Label
|
||||||
|
private let type: SelectorType
|
||||||
|
|
||||||
private init(
|
private init(
|
||||||
selection: Binding<Set<Element>>,
|
selection: Binding<Set<Element>>,
|
||||||
|
@ -31,16 +36,13 @@ struct SelectorView<Element: Displayable & Hashable, Label: View>: View {
|
||||||
label: @escaping (Element) -> Label,
|
label: @escaping (Element) -> Label,
|
||||||
type: SelectorType
|
type: SelectorType
|
||||||
) {
|
) {
|
||||||
self._selection = StateObject(wrappedValue: BindingBox(source: selection))
|
self.selectionBinding = selection
|
||||||
|
self._selectedItems = State(initialValue: selection.wrappedValue)
|
||||||
self.sources = sources
|
self.sources = sources
|
||||||
self.label = label
|
self.label = label
|
||||||
self.type = type
|
self.type = type
|
||||||
}
|
}
|
||||||
|
|
||||||
private let sources: [Element]
|
|
||||||
private var label: (Element) -> Label
|
|
||||||
private let type: SelectorType
|
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
List(sources, id: \.hashValue) { element in
|
List(sources, id: \.hashValue) { element in
|
||||||
Button {
|
Button {
|
||||||
|
@ -56,7 +58,7 @@ struct SelectorView<Element: Displayable & Hashable, Label: View>: View {
|
||||||
|
|
||||||
Spacer()
|
Spacer()
|
||||||
|
|
||||||
if selection.value.contains(element) {
|
if selectedItems.contains(element) {
|
||||||
Image(systemName: "checkmark.circle.fill")
|
Image(systemName: "checkmark.circle.fill")
|
||||||
.resizable()
|
.resizable()
|
||||||
.backport
|
.backport
|
||||||
|
@ -69,33 +71,37 @@ struct SelectorView<Element: Displayable & Hashable, Label: View>: View {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
.onChange(of: selectionBinding.wrappedValue) { newValue in
|
||||||
|
selectedItems = newValue
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private func handleSingleSelect(with element: Element) {
|
private func handleSingleSelect(with element: Element) {
|
||||||
selection.value = [element]
|
selectedItems = [element]
|
||||||
|
selectionBinding.wrappedValue = selectedItems
|
||||||
}
|
}
|
||||||
|
|
||||||
private func handleMultiSelect(with element: Element) {
|
private func handleMultiSelect(with element: Element) {
|
||||||
if selection.value.contains(element) {
|
if selectedItems.contains(element) {
|
||||||
selection.value.remove(element)
|
selectedItems.remove(element)
|
||||||
} else {
|
} else {
|
||||||
selection.value.insert(element)
|
selectedItems.insert(element)
|
||||||
}
|
}
|
||||||
|
selectionBinding.wrappedValue = selectedItems
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
extension SelectorView where Label == Text {
|
extension SelectorView where Label == Text {
|
||||||
|
|
||||||
init(selection: Binding<[Element]>, sources: [Element], type: SelectorType) {
|
init(selection: Binding<[Element]>, sources: [Element], type: SelectorType) {
|
||||||
|
let setBinding = Binding<Set<Element>>(
|
||||||
let selectionBinding = Binding {
|
get: { Set(selection.wrappedValue) },
|
||||||
Set(selection.wrappedValue)
|
set: { newValue in
|
||||||
} set: { newValue in
|
selection.wrappedValue = Array(newValue)
|
||||||
selection.wrappedValue = sources.intersection(newValue)
|
}
|
||||||
}
|
)
|
||||||
|
|
||||||
self.init(
|
self.init(
|
||||||
selection: selectionBinding,
|
selection: setBinding,
|
||||||
sources: sources,
|
sources: sources,
|
||||||
label: { Text($0.displayTitle).foregroundColor(.primary) },
|
label: { Text($0.displayTitle).foregroundColor(.primary) },
|
||||||
type: type
|
type: type
|
||||||
|
@ -103,15 +109,17 @@ extension SelectorView where Label == Text {
|
||||||
}
|
}
|
||||||
|
|
||||||
init(selection: Binding<Element>, sources: [Element]) {
|
init(selection: Binding<Element>, sources: [Element]) {
|
||||||
|
let setBinding = Binding<Set<Element>>(
|
||||||
let selectionBinding = Binding {
|
get: { Set([selection.wrappedValue]) },
|
||||||
Set([selection.wrappedValue])
|
set: { newValue in
|
||||||
} set: { newValue in
|
if let first = newValue.first {
|
||||||
selection.wrappedValue = newValue.first!
|
selection.wrappedValue = first
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
self.init(
|
self.init(
|
||||||
selection: selectionBinding,
|
selection: setBinding,
|
||||||
sources: sources,
|
sources: sources,
|
||||||
label: { Text($0.displayTitle).foregroundColor(.primary) },
|
label: { Text($0.displayTitle).foregroundColor(.primary) },
|
||||||
type: .single
|
type: .single
|
||||||
|
|
|
@ -16,7 +16,7 @@ import OrderedCollections
|
||||||
// parent class actions
|
// parent class actions
|
||||||
// TODO: official way for a cleaner `respond` method so it doesn't have all Task
|
// TODO: official way for a cleaner `respond` method so it doesn't have all Task
|
||||||
// construction and get bloated
|
// construction and get bloated
|
||||||
// TODO: make Action: Hashable just for consistency
|
// TODO: move backgroundStates to just a `Set`
|
||||||
|
|
||||||
protocol Stateful: AnyObject {
|
protocol Stateful: AnyObject {
|
||||||
|
|
||||||
|
|
|
@ -6,26 +6,65 @@
|
||||||
// Copyright (c) 2025 Jellyfin & Jellyfin Contributors
|
// Copyright (c) 2025 Jellyfin & Jellyfin Contributors
|
||||||
//
|
//
|
||||||
|
|
||||||
|
import Combine
|
||||||
import Foundation
|
import Foundation
|
||||||
import JellyfinAPI
|
import JellyfinAPI
|
||||||
|
import OrderedCollections
|
||||||
import SwiftUI
|
import SwiftUI
|
||||||
|
|
||||||
final class FilterViewModel: ViewModel {
|
final class FilterViewModel: ViewModel, Stateful {
|
||||||
|
|
||||||
@Published
|
// MARK: - Action
|
||||||
var currentFilters: ItemFilterCollection
|
|
||||||
|
|
||||||
|
enum Action: Equatable {
|
||||||
|
case cancel
|
||||||
|
case getQueryFilters
|
||||||
|
case reset(ItemFilterType? = nil)
|
||||||
|
case update(ItemFilterType, [AnyItemFilter])
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: - Background State
|
||||||
|
|
||||||
|
enum BackgroundState: Hashable {
|
||||||
|
case gettingQueryFilters
|
||||||
|
case failedToGetQueryFilters
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: - State
|
||||||
|
|
||||||
|
enum State: Hashable {
|
||||||
|
case content
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Tracks the current filters
|
||||||
@Published
|
@Published
|
||||||
var allFilters: ItemFilterCollection = .all
|
private(set) var currentFilters: ItemFilterCollection
|
||||||
|
|
||||||
|
/// All filters available
|
||||||
|
@Published
|
||||||
|
private(set) var allFilters: ItemFilterCollection = .all
|
||||||
|
|
||||||
|
/// ViewModel Background State(s)
|
||||||
|
@Published
|
||||||
|
var backgroundStates: OrderedSet<BackgroundState> = []
|
||||||
|
|
||||||
|
/// ViewModel State
|
||||||
|
@Published
|
||||||
|
var state: State = .content
|
||||||
|
|
||||||
private let parent: (any LibraryParent)?
|
private let parent: (any LibraryParent)?
|
||||||
|
|
||||||
|
private var queryFiltersTask: AnyCancellable?
|
||||||
|
|
||||||
|
// MARK: - Initialize from Library Parent
|
||||||
|
|
||||||
init(
|
init(
|
||||||
parent: (any LibraryParent)? = nil,
|
parent: (any LibraryParent)? = nil,
|
||||||
currentFilters: ItemFilterCollection = .default
|
currentFilters: ItemFilterCollection = .default
|
||||||
) {
|
) {
|
||||||
self.parent = parent
|
self.parent = parent
|
||||||
self.currentFilters = currentFilters
|
self.currentFilters = currentFilters
|
||||||
|
|
||||||
super.init()
|
super.init()
|
||||||
|
|
||||||
if let parent {
|
if let parent {
|
||||||
|
@ -33,9 +72,102 @@ final class FilterViewModel: ViewModel {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func isFilterSelected(type: ItemFilterType) -> Bool {
|
||||||
|
currentFilters[keyPath: type.collectionAnyKeyPath] != ItemFilterCollection.default[keyPath: type.collectionAnyKeyPath]
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: - Respond to Action
|
||||||
|
|
||||||
|
func respond(to action: Action) -> State {
|
||||||
|
switch action {
|
||||||
|
case .cancel:
|
||||||
|
queryFiltersTask?.cancel()
|
||||||
|
backgroundStates.removeAll()
|
||||||
|
|
||||||
|
case .getQueryFilters:
|
||||||
|
queryFiltersTask?.cancel()
|
||||||
|
queryFiltersTask = Task {
|
||||||
|
do {
|
||||||
|
await MainActor.run {
|
||||||
|
_ = self.backgroundStates.append(.gettingQueryFilters)
|
||||||
|
}
|
||||||
|
|
||||||
|
try await setQueryFilters()
|
||||||
|
} catch {
|
||||||
|
await MainActor.run {
|
||||||
|
_ = self.backgroundStates.append(.failedToGetQueryFilters)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
await MainActor.run {
|
||||||
|
_ = self.backgroundStates.remove(.gettingQueryFilters)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.asAnyCancellable()
|
||||||
|
|
||||||
|
case let .reset(type):
|
||||||
|
if let type {
|
||||||
|
resetCurrentFilters(for: type)
|
||||||
|
} else {
|
||||||
|
currentFilters = .default
|
||||||
|
}
|
||||||
|
|
||||||
|
case let .update(type, filters):
|
||||||
|
updateCurrentFilters(for: type, with: filters)
|
||||||
|
}
|
||||||
|
|
||||||
|
return state
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: - Reset Current Filters
|
||||||
|
|
||||||
|
/// Reset the filter for a specific type to its default value
|
||||||
|
private func resetCurrentFilters(for type: ItemFilterType) {
|
||||||
|
switch type {
|
||||||
|
case .genres:
|
||||||
|
currentFilters.genres = ItemFilterCollection.default.genres
|
||||||
|
case .letter:
|
||||||
|
currentFilters.letter = ItemFilterCollection.default.letter
|
||||||
|
case .sortBy:
|
||||||
|
currentFilters.sortBy = ItemFilterCollection.default.sortBy
|
||||||
|
case .sortOrder:
|
||||||
|
currentFilters.sortOrder = ItemFilterCollection.default.sortOrder
|
||||||
|
case .tags:
|
||||||
|
currentFilters.tags = ItemFilterCollection.default.tags
|
||||||
|
case .traits:
|
||||||
|
currentFilters.traits = ItemFilterCollection.default.traits
|
||||||
|
case .years:
|
||||||
|
currentFilters.years = ItemFilterCollection.default.years
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: - Update Current Filters
|
||||||
|
|
||||||
|
/// Update the filter for a specific type with new values
|
||||||
|
private func updateCurrentFilters(for type: ItemFilterType, with newValue: [AnyItemFilter]) {
|
||||||
|
switch type {
|
||||||
|
case .genres:
|
||||||
|
currentFilters.genres = newValue.map(ItemGenre.init)
|
||||||
|
case .letter:
|
||||||
|
currentFilters.letter = newValue.map(ItemLetter.init)
|
||||||
|
case .sortBy:
|
||||||
|
currentFilters.sortBy = newValue.map(ItemSortBy.init)
|
||||||
|
case .sortOrder:
|
||||||
|
currentFilters.sortOrder = newValue.map(ItemSortOrder.init)
|
||||||
|
case .tags:
|
||||||
|
currentFilters.tags = newValue.map(ItemTag.init)
|
||||||
|
case .traits:
|
||||||
|
currentFilters.traits = newValue.map(ItemTrait.init)
|
||||||
|
case .years:
|
||||||
|
currentFilters.years = newValue.map(ItemYear.init)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: - Set Query Filters
|
||||||
|
|
||||||
/// Sets the query filters from the parent
|
/// Sets the query filters from the parent
|
||||||
func setQueryFilters() async {
|
private func setQueryFilters() async throws {
|
||||||
let queryFilters = await getQueryFilters()
|
let queryFilters = try await getQueryFilters()
|
||||||
|
|
||||||
await MainActor.run {
|
await MainActor.run {
|
||||||
allFilters.genres = queryFilters.genres
|
allFilters.genres = queryFilters.genres
|
||||||
|
@ -44,7 +176,10 @@ final class FilterViewModel: ViewModel {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private func getQueryFilters() async -> (genres: [ItemGenre], tags: [ItemTag], years: [ItemYear]) {
|
// MARK: - Get Query Filters
|
||||||
|
|
||||||
|
/// Gets the query filters from the parent
|
||||||
|
private func getQueryFilters() async throws -> (genres: [ItemGenre], tags: [ItemTag], years: [ItemYear]) {
|
||||||
|
|
||||||
let parameters = Paths.GetQueryFiltersLegacyParameters(
|
let parameters = Paths.GetQueryFiltersLegacyParameters(
|
||||||
userID: userSession.user.id,
|
userID: userSession.user.id,
|
||||||
|
@ -52,7 +187,7 @@ final class FilterViewModel: ViewModel {
|
||||||
)
|
)
|
||||||
|
|
||||||
let request = Paths.getQueryFiltersLegacy(parameters: parameters)
|
let request = Paths.getQueryFiltersLegacy(parameters: parameters)
|
||||||
guard let response = try? await userSession.client.send(request) else { return ([], [], []) }
|
let response = try await userSession.client.send(request)
|
||||||
|
|
||||||
let genres: [ItemGenre] = (response.value.genres ?? [])
|
let genres: [ItemGenre] = (response.value.genres ?? [])
|
||||||
.map(ItemGenre.init)
|
.map(ItemGenre.init)
|
||||||
|
|
|
@ -119,7 +119,6 @@ class PagingLibraryViewModel<Element: Poster>: ViewModel, Eventful, Stateful {
|
||||||
|
|
||||||
// tasks
|
// tasks
|
||||||
|
|
||||||
private var filterQueryTask: AnyCancellable?
|
|
||||||
private var pagingTask: AnyCancellable?
|
private var pagingTask: AnyCancellable?
|
||||||
private var randomItemTask: AnyCancellable?
|
private var randomItemTask: AnyCancellable?
|
||||||
|
|
||||||
|
@ -252,14 +251,10 @@ class PagingLibraryViewModel<Element: Poster>: ViewModel, Eventful, Stateful {
|
||||||
return .error(error)
|
return .error(error)
|
||||||
case .refresh:
|
case .refresh:
|
||||||
|
|
||||||
filterQueryTask?.cancel()
|
|
||||||
pagingTask?.cancel()
|
pagingTask?.cancel()
|
||||||
randomItemTask?.cancel()
|
randomItemTask?.cancel()
|
||||||
|
|
||||||
filterQueryTask = Task {
|
filterViewModel?.send(.getQueryFilters)
|
||||||
await filterViewModel?.setQueryFilters()
|
|
||||||
}
|
|
||||||
.asAnyCancellable()
|
|
||||||
|
|
||||||
pagingTask = Task { [weak self] in
|
pagingTask = Task { [weak self] in
|
||||||
guard let self else { return }
|
guard let self else { return }
|
||||||
|
|
|
@ -117,10 +117,7 @@ final class SearchViewModel: ViewModel, Stateful {
|
||||||
return .searching
|
return .searching
|
||||||
}
|
}
|
||||||
case .getSuggestions:
|
case .getSuggestions:
|
||||||
Task {
|
filterViewModel.send(.getQueryFilters)
|
||||||
await filterViewModel.setQueryFilters()
|
|
||||||
}
|
|
||||||
.store(in: &cancellables)
|
|
||||||
|
|
||||||
Task {
|
Task {
|
||||||
let suggestions = try await getSuggestions()
|
let suggestions = try await getSuggestions()
|
||||||
|
@ -223,6 +220,15 @@ final class SearchViewModel: ViewModel, Stateful {
|
||||||
parameters.tags = filters.tags.map(\.value)
|
parameters.tags = filters.tags.map(\.value)
|
||||||
parameters.years = filters.years.map(\.intValue)
|
parameters.years = filters.years.map(\.intValue)
|
||||||
|
|
||||||
|
if filters.letter.first?.value == "#" {
|
||||||
|
parameters.nameLessThan = "A"
|
||||||
|
} else {
|
||||||
|
parameters.nameStartsWith = filters.letter
|
||||||
|
.map(\.value)
|
||||||
|
.filter { $0 != "#" }
|
||||||
|
.first
|
||||||
|
}
|
||||||
|
|
||||||
let request = Paths.getItemsByUserID(userID: userSession.user.id, parameters: parameters)
|
let request = Paths.getItemsByUserID(userID: userSession.user.id, parameters: parameters)
|
||||||
let response = try await userSession.client.send(request)
|
let response = try await userSession.client.send(request)
|
||||||
|
|
||||||
|
|
|
@ -31,10 +31,10 @@ extension LetterPickerBar {
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
Button {
|
Button {
|
||||||
if !viewModel.currentFilters.letter.contains(letter) {
|
if viewModel.currentFilters.letter.contains(letter) {
|
||||||
viewModel.currentFilters.letter = [ItemLetter(stringLiteral: letter.value)]
|
viewModel.send(.update(.letter, []))
|
||||||
} else {
|
} else {
|
||||||
viewModel.currentFilters.letter = []
|
viewModel.send(.update(.letter, [ItemLetter(stringLiteral: letter.value).asAnyItemFilter]))
|
||||||
}
|
}
|
||||||
} label: {
|
} label: {
|
||||||
ZStack {
|
ZStack {
|
||||||
|
|
|
@ -16,9 +16,11 @@ extension NavigationBarFilterDrawer {
|
||||||
@Default(.accentColor)
|
@Default(.accentColor)
|
||||||
private var accentColor
|
private var accentColor
|
||||||
|
|
||||||
|
@Environment(\.isSelected)
|
||||||
|
private var isSelected
|
||||||
|
|
||||||
private let systemName: String?
|
private let systemName: String?
|
||||||
private let title: String
|
private let title: String
|
||||||
private let activated: Bool
|
|
||||||
private var onSelect: () -> Void
|
private var onSelect: () -> Void
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
|
@ -43,12 +45,12 @@ extension NavigationBarFilterDrawer {
|
||||||
.padding(.vertical, 5)
|
.padding(.vertical, 5)
|
||||||
.background {
|
.background {
|
||||||
Capsule()
|
Capsule()
|
||||||
.foregroundColor(activated ? accentColor : Color(UIColor.secondarySystemFill))
|
.foregroundColor(isSelected ? accentColor : Color(UIColor.secondarySystemFill))
|
||||||
.opacity(0.5)
|
.opacity(0.5)
|
||||||
}
|
}
|
||||||
.overlay {
|
.overlay {
|
||||||
Capsule()
|
Capsule()
|
||||||
.stroke(activated ? accentColor : Color(UIColor.secondarySystemFill), lineWidth: 1)
|
.stroke(isSelected ? accentColor : Color(UIColor.secondarySystemFill), lineWidth: 1)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -57,20 +59,18 @@ extension NavigationBarFilterDrawer {
|
||||||
|
|
||||||
extension NavigationBarFilterDrawer.FilterDrawerButton {
|
extension NavigationBarFilterDrawer.FilterDrawerButton {
|
||||||
|
|
||||||
init(title: String, activated: Bool) {
|
init(title: String) {
|
||||||
self.init(
|
self.init(
|
||||||
systemName: nil,
|
systemName: nil,
|
||||||
title: title,
|
title: title,
|
||||||
activated: activated,
|
|
||||||
onSelect: {}
|
onSelect: {}
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
init(systemName: String, activated: Bool) {
|
init(systemName: String) {
|
||||||
self.init(
|
self.init(
|
||||||
systemName: systemName,
|
systemName: systemName,
|
||||||
title: "",
|
title: "",
|
||||||
activated: activated,
|
|
||||||
onSelect: {}
|
onSelect: {}
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -24,22 +24,25 @@ struct NavigationBarFilterDrawer: View {
|
||||||
if viewModel.currentFilters.hasFilters {
|
if viewModel.currentFilters.hasFilters {
|
||||||
Menu {
|
Menu {
|
||||||
Button(L10n.reset, role: .destructive) {
|
Button(L10n.reset, role: .destructive) {
|
||||||
viewModel.currentFilters = .default
|
viewModel.send(.reset())
|
||||||
}
|
}
|
||||||
} label: {
|
} label: {
|
||||||
FilterDrawerButton(systemName: "line.3.horizontal.decrease.circle.fill", activated: true)
|
FilterDrawerButton(systemName: "line.3.horizontal.decrease.circle.fill")
|
||||||
|
.environment(\.isSelected, true)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
ForEach(filterTypes, id: \.self) { type in
|
ForEach(filterTypes, id: \.self) { type in
|
||||||
FilterDrawerButton(
|
FilterDrawerButton(
|
||||||
title: type.displayTitle,
|
title: type.displayTitle
|
||||||
activated: viewModel.currentFilters[keyPath: type.collectionAnyKeyPath] != ItemFilterCollection
|
|
||||||
.default[keyPath: type.collectionAnyKeyPath]
|
|
||||||
)
|
)
|
||||||
.onSelect {
|
.onSelect {
|
||||||
onSelect(.init(type: type, viewModel: viewModel))
|
onSelect(.init(type: type, viewModel: viewModel))
|
||||||
}
|
}
|
||||||
|
.environment(
|
||||||
|
\.isSelected,
|
||||||
|
viewModel.isFilterSelected(type: type)
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.padding(.horizontal)
|
.padding(.horizontal)
|
||||||
|
|
|
@ -9,8 +9,6 @@
|
||||||
import JellyfinAPI
|
import JellyfinAPI
|
||||||
import SwiftUI
|
import SwiftUI
|
||||||
|
|
||||||
// Note: Keep all of the ItemFilterCollection/ItemFilter/AnyItemFilter KeyPath wackiness in this file
|
|
||||||
|
|
||||||
// TODO: multiple filter types?
|
// TODO: multiple filter types?
|
||||||
// - for sort order and sort by combined
|
// - for sort order and sort by combined
|
||||||
struct FilterView: View {
|
struct FilterView: View {
|
||||||
|
@ -39,27 +37,11 @@ struct FilterView: View {
|
||||||
}
|
}
|
||||||
.topBarTrailing {
|
.topBarTrailing {
|
||||||
Button(L10n.reset) {
|
Button(L10n.reset) {
|
||||||
switch type {
|
viewModel.send(.reset(type))
|
||||||
case .genres:
|
|
||||||
viewModel.currentFilters.genres = ItemFilterCollection.default.genres
|
|
||||||
case .letter:
|
|
||||||
viewModel.currentFilters.letter = ItemFilterCollection.default.letter
|
|
||||||
case .sortBy:
|
|
||||||
viewModel.currentFilters.sortBy = ItemFilterCollection.default.sortBy
|
|
||||||
case .sortOrder:
|
|
||||||
viewModel.currentFilters.sortOrder = ItemFilterCollection.default.sortOrder
|
|
||||||
case .tags:
|
|
||||||
viewModel.currentFilters.tags = ItemFilterCollection.default.tags
|
|
||||||
case .traits:
|
|
||||||
viewModel.currentFilters.traits = ItemFilterCollection.default.traits
|
|
||||||
case .years:
|
|
||||||
viewModel.currentFilters.years = ItemFilterCollection.default.years
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
.environment(
|
.environment(
|
||||||
\.isEnabled,
|
\.isEnabled,
|
||||||
viewModel.currentFilters[keyPath: type.collectionAnyKeyPath] != ItemFilterCollection
|
viewModel.isFilterSelected(type: type)
|
||||||
.default[keyPath: type.collectionAnyKeyPath]
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -75,22 +57,7 @@ extension FilterView {
|
||||||
let selectionBinding: Binding<[AnyItemFilter]> = Binding {
|
let selectionBinding: Binding<[AnyItemFilter]> = Binding {
|
||||||
viewModel.currentFilters[keyPath: type.collectionAnyKeyPath]
|
viewModel.currentFilters[keyPath: type.collectionAnyKeyPath]
|
||||||
} set: { newValue in
|
} set: { newValue in
|
||||||
switch type {
|
viewModel.send(.update(type, newValue))
|
||||||
case .genres:
|
|
||||||
viewModel.currentFilters.genres = newValue.map(ItemGenre.init)
|
|
||||||
case .letter:
|
|
||||||
viewModel.currentFilters.letter = newValue.map(ItemLetter.init)
|
|
||||||
case .sortBy:
|
|
||||||
viewModel.currentFilters.sortBy = newValue.map(ItemSortBy.init)
|
|
||||||
case .sortOrder:
|
|
||||||
viewModel.currentFilters.sortOrder = newValue.map(ItemSortOrder.init)
|
|
||||||
case .tags:
|
|
||||||
viewModel.currentFilters.tags = newValue.map(ItemTag.init)
|
|
||||||
case .traits:
|
|
||||||
viewModel.currentFilters.traits = newValue.map(ItemTrait.init)
|
|
||||||
case .years:
|
|
||||||
viewModel.currentFilters.years = newValue.map(ItemYear.init)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
self.init(
|
self.init(
|
||||||
|
|
|
@ -24,6 +24,6 @@ struct FontPickerView: View {
|
||||||
.foregroundColor(.primary)
|
.foregroundColor(.primary)
|
||||||
.font(.custom(fontFamily, size: 18))
|
.font(.custom(fontFamily, size: 18))
|
||||||
}
|
}
|
||||||
.navigationTitle("Font")
|
.navigationTitle(L10n.subtitleFont)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue