Skip to content

Commit

Permalink
Create Library Alpha Picker (#980)
Browse files Browse the repository at this point in the history
  • Loading branch information
JPKribs authored May 26, 2024
1 parent 142b881 commit 25b30b5
Show file tree
Hide file tree
Showing 10 changed files with 267 additions and 3 deletions.
25 changes: 25 additions & 0 deletions Shared/Components/LetterPickerOrientation.swift
Original file line number Diff line number Diff line change
@@ -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
}
}
}
4 changes: 4 additions & 0 deletions Shared/Services/SwiftfinDefaults.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
8 changes: 8 additions & 0 deletions Shared/Strings/Strings.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down
34 changes: 34 additions & 0 deletions Swiftfin.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -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 */; };
Expand Down Expand Up @@ -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>"; };
Expand Down Expand Up @@ -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 = (
Expand Down Expand Up @@ -2045,6 +2072,7 @@
E1921B7528E63306003A5238 /* GestureView.swift */,
E178B0752BE435D70023651B /* HourMinutePicker.swift */,
E1FE69A928C29CC20021BC93 /* LandscapePosterProgressBar.swift */,
4E16FD4E2C0183B500110147 /* LetterPickerBar */,
E1AEFA362BE317E200CFAFD8 /* ListRowButton.swift */,
E1FE69AF28C2DA4A0021BC93 /* NavigationBarFilterDrawer */,
E1DE84132B9531C1008CCE21 /* OrderedSectionSelectorView.swift */,
Expand Down Expand Up @@ -3169,6 +3197,7 @@
E145EB212BDCCA43003BF6F3 /* BulletedList.swift */,
E1153DCB2BBB633B00424D36 /* FastSVGView.swift */,
531AC8BE26750DE20091C7EB /* ImageView.swift */,
4E16FD562C01A32700110147 /* LetterPickerOrientation.swift */,
E1D37F472B9C648E00343D2B /* MaxHeightText.swift */,
E1DC983F296DEBA500982F06 /* PosterIndicators */,
E1FE69A628C29B720021BC93 /* ProgressBar.swift */,
Expand Down Expand Up @@ -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 */,
Expand Down Expand Up @@ -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 */,
Expand Down Expand Up @@ -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 */,
Expand Down Expand Up @@ -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 */,
Expand Down Expand Up @@ -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 */,
Expand Down
Original file line number Diff line number Diff line change
@@ -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)
}
}
}
}
}
Original file line number Diff line number Diff line change
@@ -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())
}
}
37 changes: 37 additions & 0 deletions Swiftfin/Components/LetterPickerBar/LetterPickerBar.swift
Original file line number Diff line number Diff line change
@@ -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)
}
}
44 changes: 41 additions & 3 deletions Swiftfin/Views/PagingLibraryView/PagingLibraryView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand All @@ -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
Expand Down
Loading

0 comments on commit 25b30b5

Please sign in to comment.