Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 6 additions & 6 deletions Shared/Components/ListRowCheckbox.swift
Original file line number Diff line number Diff line change
Expand Up @@ -14,22 +14,22 @@ struct ListRowCheckbox: View {
@Default(.accentColor)
private var accentColor

// MARK: - Environment Variables

@Environment(\.isEditing)
private var isEditing
@Environment(\.isSelected)
private var isSelected

// MARK: - Sizing Variable
private let showDeselected: Bool

#if os(tvOS)
private let size: CGFloat = 36
#else
private let size: CGFloat = 24
#endif

// MARK: - Body
init(showDeselected: Bool = true) {
self.showDeselected = showDeselected
}

@ViewBuilder
var body: some View {
Expand All @@ -42,13 +42,13 @@ struct ListRowCheckbox: View {
.symbolRenderingMode(.palette)
.foregroundStyle(accentColor.overlayColor, accentColor)

} else if isEditing {
} else if isEditing, showDeselected {
Image(systemName: "circle")
.resizable()
.fontWeight(.bold)
.aspectRatio(1, contentMode: .fit)
.frame(width: size, height: size)
.foregroundStyle(.secondary)
.foregroundStyle(Color.secondary)
}
}
}
16 changes: 3 additions & 13 deletions Shared/Components/SelectorView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@
// Copyright (c) 2026 Jellyfin & Jellyfin Contributors
//

import Defaults
import SwiftUI

enum SelectorType {
Expand All @@ -16,9 +15,6 @@ enum SelectorType {

struct SelectorView<Element: Displayable & Hashable, Label: View>: View {

@Default(.accentColor)
private var accentColor

@State
private var selectedItems: Set<Element>

Expand Down Expand Up @@ -68,15 +64,9 @@ struct SelectorView<Element: Displayable & Hashable, Label: View>: View {
label(element)
.frame(maxWidth: .infinity, alignment: .leading)

if selectedItems.contains(element) {
Image(systemName: "checkmark.circle.fill")
.resizable()
.fontWeight(.bold)
.aspectRatio(1, contentMode: .fit)
.frame(width: 24, height: 24)
.symbolRenderingMode(.palette)
.foregroundStyle(accentColor.overlayColor, accentColor)
}
ListRowCheckbox(showDeselected: false)
.isEditing(true)
.isSelected(selectedItems.contains(element))
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,16 +11,14 @@ import SwiftUI

extension NavigationRoute {

#if os(iOS)
static func filter(type: ItemFilterType, viewModel: FilterViewModel) -> NavigationRoute {
static func filter(types: [ItemFilterType], viewModel: FilterViewModel) -> NavigationRoute {
NavigationRoute(
id: "filter",
style: .sheet
) {
FilterView(viewModel: viewModel, type: type)
FilterView(viewModel: viewModel, types: types)
}
}
#endif

static func library(
viewModel: PagingLibraryViewModel<some Poster>
Expand Down
28 changes: 27 additions & 1 deletion Shared/Objects/ItemFilter/ItemFilterType.swift
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
// Copyright (c) 2026 Jellyfin & Jellyfin Contributors
//

enum ItemFilterType: String, CaseIterable, Storable {
enum ItemFilterType: String, CaseIterable, Storable, Identifiable {

case genres
case letter
Expand All @@ -16,6 +16,10 @@ enum ItemFilterType: String, CaseIterable, Storable {
case traits
case years

var id: String {
rawValue
}

var selectorType: SelectorType {
switch self {
case .genres, .tags, .traits, .years:
Expand Down Expand Up @@ -66,3 +70,25 @@ extension ItemFilterType: Displayable {
}
}
}

extension ItemFilterType: SystemImageable {

var systemImage: String {
switch self {
case .genres:
"theatermasks"
case .letter:
"character.textbox"
case .sortBy:
"line.3.horizontal.decrease"
case .sortOrder:
"arrow.up.arrow.down"
case .tags:
"tag"
case .traits:
"heart"
case .years:
"calendar"
}
}
}
93 changes: 93 additions & 0 deletions Shared/Views/FilterView.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
//
// Swiftfin is subject to the terms of the Mozilla Public
// License, v2.0. If a copy of the MPL was not distributed with this
// file, you can obtain one at https://mozilla.org/MPL/2.0/.
//
// Copyright (c) 2026 Jellyfin & Jellyfin Contributors
//

import JellyfinAPI
import SwiftUI

struct FilterView: View {

@Router
private var router

@ObservedObject
private var viewModel: FilterViewModel

private let types: [ItemFilterType]

private var title: String {
if types.count > 1 {
types.map(\.displayTitle).joined(separator: " & ")
} else {
types.first?.displayTitle ?? L10n.unknown
}
}

init(
viewModel: FilterViewModel,
types: [ItemFilterType]
) {
self.viewModel = viewModel
self.types = types
}

var body: some View {
Form(systemImage: types.first?.systemImage ?? "line.3.horizontal.decrease") {
ForEach(types) { type in
selectorView(for: type)
}
}
.navigationTitle(title)
.topBarTrailing {
Button(L10n.reset) {
for type in types {
viewModel.send(.reset(type))
}
}
.environment(
\.isEnabled,
types.contains {
viewModel.isFilterSelected(type: $0)
}
)
}
.navigationBarCloseButton {
router.dismiss()
}
}

@ViewBuilder
private func selectorView(for type: ItemFilterType) -> some View {

let source = viewModel.allFilters[keyPath: type.collectionAnyKeyPath]

if source.isNotEmpty {
Section {
SelectorView(
selection: Binding<[AnyItemFilter]>(
get: {
viewModel.currentFilters[keyPath: type.collectionAnyKeyPath]
},
set: { newValue in
viewModel.send(.update(type, newValue))
}
),
sources: source,
type: type.selectorType
)
} header: {
if types.count > 1 {
Text(type.displayTitle)
}
}
} else {
Text(L10n.none)
.foregroundStyle(.secondary)
.frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .center)
}
}
}
9 changes: 9 additions & 0 deletions Swiftfin tvOS/Extensions/View/View-tvOS.swift
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,15 @@ extension View {
func prefersStatusBarHidden(_ hidden: Bool) -> some View {
self
}

/// - Important: This does nothing on tvOS.
@ViewBuilder
func navigationBarCloseButton(
disabled: Bool = false,
_ action: @escaping () -> Void
) -> some View {
self
}
}

extension EnvironmentValues {
Expand Down
95 changes: 0 additions & 95 deletions Swiftfin/Views/FilterView.swift

This file was deleted.

2 changes: 1 addition & 1 deletion Swiftfin/Views/PagingLibraryView/PagingLibraryView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -297,7 +297,7 @@ struct PagingLibraryView<Element: Poster>: View {
viewModel: filterViewModel,
types: enabledDrawerFilters
) {
router.route(to: .filter(type: $0.type, viewModel: $0.viewModel))
router.route(to: .filter(types: [$0.type], viewModel: $0.viewModel))
}
}
.onChange(of: defaultDisplayType) { newValue in
Expand Down
2 changes: 1 addition & 1 deletion Swiftfin/Views/SearchView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -215,7 +215,7 @@ struct SearchView: View {
viewModel: viewModel.filterViewModel,
types: enabledDrawerFilters
) {
router.route(to: .filter(type: $0.type, viewModel: $0.viewModel))
router.route(to: .filter(types: [$0.type], viewModel: $0.viewModel))
}
.onFirstAppear {
viewModel.getSuggestions()
Expand Down