Create Library Alpha Picker (#980)

This commit is contained in:
Joe 2024-05-26 15:07:13 -06:00 committed by GitHub
parent 142b881ea9
commit 25b30b5436
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
10 changed files with 267 additions and 3 deletions

View File

@ -0,0 +1,25 @@
//
// 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) 2024 Jellyfin & Jellyfin Contributors
//
import Defaults
import SwiftUI
enum LetterPickerOrientation: String, CaseIterable, Defaults.Serializable, Displayable {
case leading
case trailing
var displayTitle: String {
switch self {
case .leading:
return L10n.left
case .trailing:
return L10n.right
}
}
}

View File

@ -145,6 +145,10 @@ extension Defaults.Keys {
"libraryEnabledDrawerFilters",
default: ItemFilterType.allCases
)
static let letterPickerEnabled: Key<Bool> = UserKey("letterPickerEnabled", default: false)
static let letterPickerOrientation: Key<LetterPickerOrientation> = .init(
"letterPickerOrientation", default: .trailing
)
static let displayType: Key<LibraryDisplayType> = UserKey("libraryViewType", default: .grid)
static let posterType: Key<PosterDisplayType> = UserKey("libraryPosterType", default: .portrait)
static let listColumnCount: Key<Int> = UserKey("listColumnCount", default: 1)

View File

@ -220,6 +220,10 @@ internal enum L10n {
internal static func latestWithString(_ p1: Any) -> String {
return L10n.tr("Localizable", "latestWithString", String(describing: p1), fallback: "Latest %@")
}
/// Left
internal static let `left` = L10n.tr("Localizable", "left", fallback: "Left")
/// Letter Picker
internal static let letterPicker = L10n.tr("Localizable", "letterPicker", fallback: "Letter Picker")
/// Library
internal static let library = L10n.tr("Localizable", "library", fallback: "Library")
/// Light
@ -310,6 +314,8 @@ internal enum L10n {
internal static let orange = L10n.tr("Localizable", "orange", fallback: "Orange")
/// Order
internal static let order = L10n.tr("Localizable", "order", fallback: "Order")
/// Orientation
internal static let orientation = L10n.tr("Localizable", "orientation", fallback: "Orientation")
/// Other
internal static let other = L10n.tr("Localizable", "other", fallback: "Other")
/// Other User
@ -442,6 +448,8 @@ internal enum L10n {
internal static let retrievingMediaInformation = L10n.tr("Localizable", "retrievingMediaInformation", fallback: "Retrieving media information")
/// Retry
internal static let retry = L10n.tr("Localizable", "retry", fallback: "Retry")
/// Right
internal static let `right` = L10n.tr("Localizable", "right", fallback: "Right")
/// Runtime
internal static let runtime = L10n.tr("Localizable", "runtime", fallback: "Runtime")
/// Scrub Current Time

View File

@ -9,9 +9,14 @@
/* Begin PBXBuildFile section */
091B5A8A2683142E00D78B61 /* ServerDiscovery.swift in Sources */ = {isa = PBXBuildFile; fileRef = 091B5A872683142E00D78B61 /* ServerDiscovery.swift */; };
091B5A8D268315D400D78B61 /* ServerDiscovery.swift in Sources */ = {isa = PBXBuildFile; fileRef = 091B5A872683142E00D78B61 /* ServerDiscovery.swift */; };
4E16FD512C0183DB00110147 /* LetterPickerButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4E16FD502C0183DB00110147 /* LetterPickerButton.swift */; };
4E16FD532C01840C00110147 /* LetterPickerBar.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4E16FD522C01840C00110147 /* LetterPickerBar.swift */; };
4E16FD572C01A32700110147 /* LetterPickerOrientation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4E16FD562C01A32700110147 /* LetterPickerOrientation.swift */; };
4E16FD582C01A32700110147 /* LetterPickerOrientation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4E16FD562C01A32700110147 /* LetterPickerOrientation.swift */; };
4E5E48E52AB59806003F1B48 /* CustomizeViewsSettings.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4E5E48E42AB59806003F1B48 /* CustomizeViewsSettings.swift */; };
4E8B34EA2AB91B6E0018F305 /* ItemFilter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4E8B34E92AB91B6E0018F305 /* ItemFilter.swift */; };
4E8B34EB2AB91B6E0018F305 /* ItemFilter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4E8B34E92AB91B6E0018F305 /* ItemFilter.swift */; };
4EF7A3E22C031FEB00CC58A2 /* LetterPickerOverflow.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4EF7A3E12C031FEB00CC58A2 /* LetterPickerOverflow.swift */; };
531690E7267ABD79005D8AB9 /* HomeView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 531690E6267ABD79005D8AB9 /* HomeView.swift */; };
531AC8BF26750DE20091C7EB /* ImageView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 531AC8BE26750DE20091C7EB /* ImageView.swift */; };
5321753B2671BCFC005491E6 /* SettingsViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5321753A2671BCFC005491E6 /* SettingsViewModel.swift */; };
@ -917,8 +922,12 @@
/* Begin PBXFileReference section */
091B5A872683142E00D78B61 /* ServerDiscovery.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ServerDiscovery.swift; sourceTree = "<group>"; };
4E16FD502C0183DB00110147 /* LetterPickerButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LetterPickerButton.swift; sourceTree = "<group>"; };
4E16FD522C01840C00110147 /* LetterPickerBar.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LetterPickerBar.swift; sourceTree = "<group>"; };
4E16FD562C01A32700110147 /* LetterPickerOrientation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LetterPickerOrientation.swift; sourceTree = "<group>"; };
4E5E48E42AB59806003F1B48 /* CustomizeViewsSettings.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CustomizeViewsSettings.swift; sourceTree = "<group>"; };
4E8B34E92AB91B6E0018F305 /* ItemFilter.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ItemFilter.swift; sourceTree = "<group>"; };
4EF7A3E12C031FEB00CC58A2 /* LetterPickerOverflow.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LetterPickerOverflow.swift; sourceTree = "<group>"; };
531690E6267ABD79005D8AB9 /* HomeView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HomeView.swift; sourceTree = "<group>"; };
531AC8BE26750DE20091C7EB /* ImageView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImageView.swift; sourceTree = "<group>"; };
5321753A2671BCFC005491E6 /* SettingsViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsViewModel.swift; sourceTree = "<group>"; };
@ -1634,6 +1643,24 @@
path = ServerDiscovery;
sourceTree = "<group>";
};
4E16FD4E2C0183B500110147 /* LetterPickerBar */ = {
isa = PBXGroup;
children = (
4E16FD4F2C0183C500110147 /* Components */,
4E16FD522C01840C00110147 /* LetterPickerBar.swift */,
);
path = LetterPickerBar;
sourceTree = "<group>";
};
4E16FD4F2C0183C500110147 /* Components */ = {
isa = PBXGroup;
children = (
4E16FD502C0183DB00110147 /* LetterPickerButton.swift */,
4EF7A3E12C031FEB00CC58A2 /* LetterPickerOverflow.swift */,
);
path = Components;
sourceTree = "<group>";
};
5310694F2684E7EE00CFFDBA /* VideoPlayer */ = {
isa = PBXGroup;
children = (
@ -2045,6 +2072,7 @@
E1921B7528E63306003A5238 /* GestureView.swift */,
E178B0752BE435D70023651B /* HourMinutePicker.swift */,
E1FE69A928C29CC20021BC93 /* LandscapePosterProgressBar.swift */,
4E16FD4E2C0183B500110147 /* LetterPickerBar */,
E1AEFA362BE317E200CFAFD8 /* ListRowButton.swift */,
E1FE69AF28C2DA4A0021BC93 /* NavigationBarFilterDrawer */,
E1DE84132B9531C1008CCE21 /* OrderedSectionSelectorView.swift */,
@ -3169,6 +3197,7 @@
E145EB212BDCCA43003BF6F3 /* BulletedList.swift */,
E1153DCB2BBB633B00424D36 /* FastSVGView.swift */,
531AC8BE26750DE20091C7EB /* ImageView.swift */,
4E16FD562C01A32700110147 /* LetterPickerOrientation.swift */,
E1D37F472B9C648E00343D2B /* MaxHeightText.swift */,
E1DC983F296DEBA500982F06 /* PosterIndicators */,
E1FE69A628C29B720021BC93 /* ProgressBar.swift */,
@ -4114,6 +4143,7 @@
C4E5081B2703F82A0045C9AB /* MediaView.swift in Sources */,
E193D53B27193F9200900D82 /* SettingsCoordinator.swift in Sources */,
E113133B28BEB71D00930F75 /* FilterViewModel.swift in Sources */,
4E16FD582C01A32700110147 /* LetterPickerOrientation.swift in Sources */,
E1575E70293E77B5001665B1 /* TextPair.swift in Sources */,
E18E021C2887492B0022598C /* BlurView.swift in Sources */,
E187F7682B8E6A1C005400FE /* EnvironmentValue+Values.swift in Sources */,
@ -4223,6 +4253,7 @@
E18CE0AF28A222240092E7F1 /* PublicUserRow.swift in Sources */,
E129429828F4785200796AC6 /* CaseIterablePicker.swift in Sources */,
E18E01E5288747230022598C /* CinematicScrollView.swift in Sources */,
4EF7A3E22C031FEB00CC58A2 /* LetterPickerOverflow.swift in Sources */,
E154965E296CA2EF00C4EF88 /* DownloadTask.swift in Sources */,
535BAE9F2649E569005FA86D /* ItemView.swift in Sources */,
E1E2F8422B757E0900B75998 /* OnFirstAppearModifier.swift in Sources */,
@ -4345,6 +4376,7 @@
6334175B287DDFB9000603CE /* QuickConnectAuthorizeView.swift in Sources */,
E13F05F128BC9016003499D2 /* LibraryRow.swift in Sources */,
E168BD10289A4162001A6922 /* HomeView.swift in Sources */,
4E16FD532C01840C00110147 /* LetterPickerBar.swift in Sources */,
E1BE1CEA2BDB5AFE008176A9 /* UserGridButton.swift in Sources */,
E1401CB129386C9200E8B599 /* UIColor.swift in Sources */,
E1E2F8452B757E3400B75998 /* SinceLastDisappearModifier.swift in Sources */,
@ -4479,6 +4511,7 @@
E18E01E9288747230022598C /* SeriesItemView.swift in Sources */,
E15756342936851D00976E1F /* NativeVideoPlayerSettingsView.swift in Sources */,
E1D4BF7C2719D05000A11E64 /* AppSettingsView.swift in Sources */,
4E16FD512C0183DB00110147 /* LetterPickerButton.swift in Sources */,
E19D41AE2BF288320082B8B2 /* ServerCheckViewModel.swift in Sources */,
E1BDF2F329524C3B00CC0294 /* ChaptersActionButton.swift in Sources */,
E173DA5026D048D600CC4EB7 /* ServerDetailView.swift in Sources */,
@ -4571,6 +4604,7 @@
E1D3043528D1763100587289 /* SeeAllButton.swift in Sources */,
E172D3B22BACA569007B4647 /* EpisodeContent.swift in Sources */,
E13F05EC28BC9000003499D2 /* LibraryDisplayType.swift in Sources */,
4E16FD572C01A32700110147 /* LetterPickerOrientation.swift in Sources */,
E1356E0329A730B200382563 /* SeparatorHStack.swift in Sources */,
5377CBF5263B596A003A4E83 /* SwiftfinApp.swift in Sources */,
E13DD4022717EE79009D4DAF /* SelectUserCoordinator.swift in Sources */,

View File

@ -0,0 +1,55 @@
//
// 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) 2024 Jellyfin & Jellyfin Contributors
//
import Defaults
import SwiftUI
extension LetterPickerBar {
struct LetterPickerButton: View {
@Default(.accentColor)
private var accentColor
@Environment(\.isSelected)
private var isSelected
private let filterLetter: ItemLetter
private let viewModel: FilterViewModel
init(filterLetter: ItemLetter, viewModel: FilterViewModel) {
self.filterLetter = filterLetter
self.viewModel = viewModel
}
var body: some View {
Button {
if !viewModel.currentFilters.letter.contains(filterLetter) {
viewModel.currentFilters.letter = [ItemLetter(stringLiteral: filterLetter.value)]
} else {
viewModel.currentFilters.letter = []
}
} label: {
Text(
filterLetter.value
)
.environment(\.isSelected, viewModel.currentFilters.letter.contains(filterLetter))
.font(.headline)
.frame(width: 15, height: 15)
.foregroundStyle(isSelected ? accentColor.overlayColor : accentColor)
.padding(.vertical, 2)
.fixedSize(horizontal: false, vertical: true)
.background {
RoundedRectangle(cornerRadius: 5)
.frame(width: 20, height: 20)
.foregroundStyle(isSelected ? accentColor.opacity(0.5) : Color.clear)
}
}
}
}
}

View File

@ -0,0 +1,50 @@
//
// 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) 2024 Jellyfin & Jellyfin Contributors
//
import Foundation
import SwiftUI
struct LetterPickerOverflow: ViewModifier {
@State
private var contentOverflow: Bool = false
func body(content: Content) -> some View {
GeometryReader { geometry in
content
.background(
GeometryReader { contentGeometry in
Color.clear.onAppear {
contentOverflow = contentGeometry.size.height > geometry.size.height
}
}
)
.wrappedInScrollView(when: contentOverflow)
}
}
}
extension View {
@ViewBuilder
func wrappedInScrollView(when condition: Bool) -> some View {
if condition {
ScrollView(showsIndicators: false) {
self
}
.frame(maxWidth: .infinity, alignment: .center)
} else {
self
.frame(width: 30, alignment: .center)
}
}
}
extension View {
func scrollOnOverflow() -> some View {
modifier(LetterPickerOverflow())
}
}

View File

@ -0,0 +1,37 @@
//
// 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) 2024 Jellyfin & Jellyfin Contributors
//
import Defaults
import SwiftUI
struct LetterPickerBar: View {
@ObservedObject
private var viewModel: FilterViewModel
init(viewModel: FilterViewModel) {
self.viewModel = viewModel
}
@ViewBuilder
var body: some View {
VStack(spacing: 0) {
Spacer()
ForEach(ItemLetter.allCases, id: \.hashValue) { filterLetter in
LetterPickerButton(
filterLetter: filterLetter,
viewModel: viewModel
)
.environment(\.isSelected, viewModel.currentFilters.letter.contains(filterLetter))
.frame(maxWidth: .infinity)
}
Spacer()
}
.scrollOnOverflow()
.frame(width: 30, alignment: .center)
}
}

View File

@ -50,11 +50,18 @@ struct PagingLibraryView<Element: Poster>: View {
@Default
private var defaultPosterType: PosterDisplayType
@Default(.Customization.Library.letterPickerEnabled)
private var letterPickerEnabled
@Default(.Customization.Library.letterPickerOrientation)
private var letterPickerOrientation
@EnvironmentObject
private var router: LibraryCoordinator<Element>.Router
@State
private var layout: CollectionVGridLayout
@State
private var safeArea: EdgeInsets = .zero
@StoredValue
private var displayType: LibraryDisplayType
@ -251,6 +258,34 @@ struct PagingLibraryView<Element: Poster>: View {
.proxy(collectionVGridProxy)
}
@ViewBuilder
private func contentLetterBarView(content: some View) -> some View {
if letterPickerEnabled, let filterViewModel = viewModel.filterViewModel {
switch letterPickerOrientation {
case .trailing:
HStack(spacing: 0) {
content
.frame(maxWidth: .infinity)
LetterPickerBar(viewModel: filterViewModel)
.padding(.top, safeArea.top)
.padding(.bottom, safeArea.bottom)
}
case .leading:
HStack(spacing: 0) {
LetterPickerBar(viewModel: filterViewModel)
.padding(.top, safeArea.top)
.padding(.bottom, safeArea.bottom)
content
.frame(maxWidth: .infinity)
}
}
} else {
content
}
}
// MARK: body
// TODO: becoming too large for typechecker during development, should break up somehow
@ -260,18 +295,21 @@ struct PagingLibraryView<Element: Poster>: View {
switch viewModel.state {
case .content:
if viewModel.elements.isEmpty {
L10n.noResults.text
contentLetterBarView(content: L10n.noResults.text)
} else {
contentView
contentLetterBarView(content: contentView)
}
case let .error(error):
errorView(with: error)
case .initial, .refreshing:
DelayedProgressView()
contentLetterBarView(content: DelayedProgressView())
}
}
.animation(.linear(duration: 0.1), value: viewModel.state)
.ignoresSafeArea()
.onSizeChanged { _, safeArea in
self.safeArea = safeArea
}
.navigationTitle(viewModel.parent?.displayTitle ?? "")
.navigationBarTitleDisplayMode(.inline)
.ifLet(viewModel.filterViewModel) { view, filterViewModel in

View File

@ -23,6 +23,10 @@ struct CustomizeViewsSettings: View {
@Default(.Customization.shouldShowMissingEpisodes)
private var shouldShowMissingEpisodes
@Default(.Customization.Library.letterPickerEnabled)
var letterPickerEnabled
@Default(.Customization.Library.letterPickerOrientation)
var letterPickerOrientation
@Default(.Customization.Library.enabledDrawerFilters)
private var libraryEnabledDrawerFilters
@Default(.Customization.Search.enabledDrawerFilters)
@ -91,6 +95,15 @@ struct CustomizeViewsSettings: View {
Section {
Toggle(L10n.letterPicker, isOn: $letterPickerEnabled)
if letterPickerEnabled {
CaseIterablePicker(
L10n.orientation,
selection: $letterPickerOrientation
)
}
ChevronButton(L10n.library)
.onSelect {
router.route(to: \.itemFilterDrawerSelector, $libraryEnabledDrawerFilters)