From fb2e0ad7a2575e2a035c1b8ea1ff42a1fbe37903 Mon Sep 17 00:00:00 2001 From: Joe Date: Sun, 2 Mar 2025 14:29:25 -0700 Subject: [PATCH] Comment cleanup. --- .../Components/FilterButton.swift | 151 ++++++++++++++++++ .../FilterPickerBar.swift} | 9 +- .../FilterViews/Components/FilterButton.swift | 149 ----------------- Swiftfin tvOS/Extensions/View/View-tvOS.swift | 2 +- Swiftfin.xcodeproj/project.pbxproj | 14 +- 5 files changed, 164 insertions(+), 161 deletions(-) create mode 100644 Swiftfin tvOS/Components/LibraryFilters/FilterPickerBar/Components/FilterButton.swift rename Swiftfin tvOS/Components/LibraryFilters/{FilterViews/FilterBar.swift => FilterPickerBar/FilterPickerBar.swift} (91%) delete mode 100644 Swiftfin tvOS/Components/LibraryFilters/FilterViews/Components/FilterButton.swift diff --git a/Swiftfin tvOS/Components/LibraryFilters/FilterPickerBar/Components/FilterButton.swift b/Swiftfin tvOS/Components/LibraryFilters/FilterPickerBar/Components/FilterButton.swift new file mode 100644 index 000000000..5e1948252 --- /dev/null +++ b/Swiftfin tvOS/Components/LibraryFilters/FilterPickerBar/Components/FilterButton.swift @@ -0,0 +1,151 @@ +// +// 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) 2025 Jellyfin & Jellyfin Contributors +// + +import Defaults +import SwiftUI + +extension FilterPickerBar { + + struct FilterPickerButton: View { + + // MARK: - Defaults + + @Default(.accentColor) + private var accentColor + + // MARK: - Environment Variables + + @Environment(\.isSelected) + private var isSelected + + // MARK: - Focus State + + @FocusState + private var isFocused: Bool + + // MARK: - Button Variables + + private let systemName: String? + private let title: String + private let role: ButtonRole? + private var onSelect: () -> Void + + // MARK: - Button Dimensions + + private let collapsedWidth: CGFloat = 75 + + private var expandedWidth: CGFloat { + textWidth(for: title) + 20 + collapsedWidth + } + + // MARK: - Button Styles + + private var buttonColor: Color { + isSelected ? ((role == .destructive && isFocused) ? Color.red.opacity(0.2) : accentColor) : Color.secondarySystemFill + } + + private var textColor: Color { + isFocused ? ((role == .destructive) ? .red : .black) : .primary + } + + // MARK: - Initializer + + init( + systemName: String?, + title: String, + role: ButtonRole?, + onSelect: @escaping () -> Void + ) { + self.systemName = systemName + self.title = title + self.role = role + self.onSelect = onSelect + } + + // MARK: - Text Width + + private func textWidth(for text: String) -> CGFloat { + let textSize = String().height( + withConstrainedWidth: CGFloat.greatestFiniteMagnitude, + font: UIFont.preferredFont( + forTextStyle: .footnote + ) + ) + let font = UIFont.systemFont(ofSize: textSize, weight: .semibold) + let attributes = [NSAttributedString.Key.font: font] + let size = (text as NSString).size(withAttributes: attributes) + return size.width + } + + // MARK: - Body + + var body: some View { + ZStack(alignment: .leading) { + Capsule() + .foregroundColor(buttonColor) + .brightness(isFocused ? 0.25 : 0) + .opacity(isFocused ? 1 : 0.5) + .frame(width: isFocused ? expandedWidth : collapsedWidth, height: collapsedWidth) + .overlay { + Capsule() + .stroke(buttonColor, lineWidth: 1) + .brightness(isFocused ? 0.25 : 0) + } + .allowsHitTesting(false) + + HStack(spacing: 10) { + if let systemName = systemName { + Image(systemName: systemName) + .hoverEffectDisabled() + .focusEffectDisabled() + .foregroundColor(textColor) + .frame(width: collapsedWidth, alignment: .center) + } + + if isFocused { + Text(title) + .foregroundColor(textColor) + .transition(.move(edge: .leading).combined(with: .opacity)) + + Spacer(minLength: 0) + } + } + .font(.footnote.weight(.semibold)) + .frame(height: collapsedWidth) + .allowsHitTesting(false) + + Button { + onSelect() + } label: { + Color.clear + .frame(width: collapsedWidth, height: collapsedWidth) + } + .padding(0) + .buttonStyle(.borderless) + .focused($isFocused) + } + .frame(width: collapsedWidth, height: collapsedWidth, alignment: .leading) + .animation(.easeIn(duration: 0.2), value: isFocused) + } + } +} + +extension FilterPickerBar.FilterPickerButton { + init(systemName: String, title: String, role: ButtonRole? = nil) { + self.init( + systemName: systemName, + title: title, + role: role, + onSelect: {} + ) + } + + func onSelect(_ action: @escaping () -> Void) -> Self { + copy(modifying: \.onSelect, with: action) + } +} diff --git a/Swiftfin tvOS/Components/LibraryFilters/FilterViews/FilterBar.swift b/Swiftfin tvOS/Components/LibraryFilters/FilterPickerBar/FilterPickerBar.swift similarity index 91% rename from Swiftfin tvOS/Components/LibraryFilters/FilterViews/FilterBar.swift rename to Swiftfin tvOS/Components/LibraryFilters/FilterPickerBar/FilterPickerBar.swift index 2d18a7ea1..c5ec7fac5 100644 --- a/Swiftfin tvOS/Components/LibraryFilters/FilterViews/FilterBar.swift +++ b/Swiftfin tvOS/Components/LibraryFilters/FilterPickerBar/FilterPickerBar.swift @@ -10,7 +10,7 @@ import Defaults import JellyfinAPI import SwiftUI -struct FilterBar: View { +struct FilterPickerBar: View { // MARK: - Observed Object @@ -27,7 +27,7 @@ struct FilterBar: View { var body: some View { VStack(spacing: 20) { if viewModel.currentFilters.hasFilters { - FilterButton( + FilterPickerButton( systemName: "line.3.horizontal.decrease.circle.fill", title: L10n.reset, role: .destructive @@ -37,12 +37,13 @@ struct FilterBar: View { } .environment(\.isSelected, true) } else { + // Leave space for the Reset Button Spacer() .frame(width: 75, height: 75) } ForEach(filterTypes, id: \.self) { type in - FilterButton( + FilterPickerButton( systemName: type.systemImage, title: type.displayTitle ) @@ -60,7 +61,7 @@ struct FilterBar: View { } } -extension FilterBar { +extension FilterPickerBar { init(viewModel: FilterViewModel, types: [ItemFilterType]) { self.init( viewModel: viewModel, diff --git a/Swiftfin tvOS/Components/LibraryFilters/FilterViews/Components/FilterButton.swift b/Swiftfin tvOS/Components/LibraryFilters/FilterViews/Components/FilterButton.swift deleted file mode 100644 index 532c86616..000000000 --- a/Swiftfin tvOS/Components/LibraryFilters/FilterViews/Components/FilterButton.swift +++ /dev/null @@ -1,149 +0,0 @@ -// -// 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) 2025 Jellyfin & Jellyfin Contributors -// - -import Defaults -import SwiftUI - -struct FilterButton: View { - - // MARK: - Defaults - - @Default(.accentColor) - private var accentColor - - // MARK: - Environment Variables - - @Environment(\.isSelected) - private var isSelected - - // MARK: - Focus State - - @FocusState - private var isFocused: Bool - - // MARK: - Button Variables - - private let systemName: String? - private let title: String - private let role: ButtonRole? - private var onSelect: () -> Void - - // MARK: - Button Dimensions - - private let collapsedWidth: CGFloat = 75 - - private var expandedWidth: CGFloat { - textWidth(for: title) + 20 + collapsedWidth - } - - // MARK: - Button Styles - - private var buttonColor: Color { - isSelected ? ((role == .destructive && isFocused) ? Color.red.opacity(0.2) : accentColor) : Color.secondarySystemFill - } - - private var textColor: Color { - isFocused ? ((role == .destructive) ? .red : .black) : .primary - } - - // MARK: - Initializer - - init( - systemName: String?, - title: String, - role: ButtonRole?, - onSelect: @escaping () -> Void - ) { - self.systemName = systemName - self.title = title - self.role = role - self.onSelect = onSelect - } - - // MARK: - Text Width - - private func textWidth(for text: String) -> CGFloat { - let textSize = String().height( - withConstrainedWidth: CGFloat.greatestFiniteMagnitude, - font: UIFont.preferredFont( - forTextStyle: .footnote - ) - ) - let font = UIFont.systemFont(ofSize: textSize, weight: .semibold) - let attributes = [NSAttributedString.Key.font: font] - let size = (text as NSString).size(withAttributes: attributes) - return size.width - } - - // MARK: - Body - - var body: some View { - ZStack(alignment: .leading) { - // Visual background that expands/contracts - Capsule() - .foregroundColor(buttonColor) - .brightness(isFocused ? 0.25 : 0) - .opacity(isFocused ? 1 : 0.5) - .frame(width: isFocused ? expandedWidth : collapsedWidth, height: collapsedWidth) - .overlay { - Capsule() - .stroke(buttonColor, lineWidth: 1) - .brightness(isFocused ? 0.25 : 0) - } - .allowsHitTesting(false) - - HStack(spacing: 10) { - if let systemName = systemName { - Image(systemName: systemName) - .hoverEffectDisabled() - .focusEffectDisabled() - .foregroundColor(textColor) - .frame(width: collapsedWidth, alignment: .center) - } - - if isFocused { - Text(title) - .foregroundColor(textColor) - .transition(.move(edge: .leading).combined(with: .opacity)) - - Spacer(minLength: 0) - } - } - .font(.footnote.weight(.semibold)) - .frame(height: collapsedWidth) - .allowsHitTesting(false) - - Button { - onSelect() - } label: { - Color.clear - .frame(width: collapsedWidth, height: collapsedWidth) - } - .padding(0) - .buttonStyle(.borderless) - .focused($isFocused) - } - .frame(width: collapsedWidth, height: collapsedWidth, alignment: .leading) - .animation(.easeIn(duration: 0.2), value: isFocused) - } -} - -extension FilterButton { - init(systemName: String, title: String, role: ButtonRole? = nil) { - self.init( - systemName: systemName, - title: title, - role: role, - onSelect: {} - ) - } - - func onSelect(_ action: @escaping () -> Void) -> Self { - copy(modifying: \.onSelect, with: action) - } -} diff --git a/Swiftfin tvOS/Extensions/View/View-tvOS.swift b/Swiftfin tvOS/Extensions/View/View-tvOS.swift index 8ae16feb2..3a95fb561 100644 --- a/Swiftfin tvOS/Extensions/View/View-tvOS.swift +++ b/Swiftfin tvOS/Extensions/View/View-tvOS.swift @@ -43,7 +43,7 @@ extension View { } else { libraryFilterBars( filters: { - FilterBar( + FilterPickerBar( viewModel: viewModel, types: types ) diff --git a/Swiftfin.xcodeproj/project.pbxproj b/Swiftfin.xcodeproj/project.pbxproj index 9d92fc227..f167ff220 100644 --- a/Swiftfin.xcodeproj/project.pbxproj +++ b/Swiftfin.xcodeproj/project.pbxproj @@ -80,7 +80,7 @@ 4E45939E2D04E20000E277E1 /* ItemImagesViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4E45939D2D04E1E600E277E1 /* ItemImagesViewModel.swift */; }; 4E4593A32D04E2B500E277E1 /* ItemImagesView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4E4593A22D04E2AF00E277E1 /* ItemImagesView.swift */; }; 4E4593A62D04E4E300E277E1 /* AddItemImageView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4E4593A52D04E4DE00E277E1 /* AddItemImageView.swift */; }; - 4E4803F02D74334200B5F1ED /* FilterBar.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4E4803EF2D74333F00B5F1ED /* FilterBar.swift */; }; + 4E4803F02D74334200B5F1ED /* FilterPickerBar.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4E4803EF2D74333F00B5F1ED /* FilterPickerBar.swift */; }; 4E49DECB2CE54AA200352DCD /* SessionsSection.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4E49DECA2CE54A9200352DCD /* SessionsSection.swift */; }; 4E49DECD2CE54C7A00352DCD /* PermissionSection.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4E49DECC2CE54C7200352DCD /* PermissionSection.swift */; }; 4E49DECF2CE54D3000352DCD /* MaxBitratePolicy.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4E49DECE2CE54D2700352DCD /* MaxBitratePolicy.swift */; }; @@ -1329,7 +1329,7 @@ 4E45939D2D04E1E600E277E1 /* ItemImagesViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ItemImagesViewModel.swift; sourceTree = ""; }; 4E4593A22D04E2AF00E277E1 /* ItemImagesView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ItemImagesView.swift; sourceTree = ""; }; 4E4593A52D04E4DE00E277E1 /* AddItemImageView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AddItemImageView.swift; sourceTree = ""; }; - 4E4803EF2D74333F00B5F1ED /* FilterBar.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FilterBar.swift; sourceTree = ""; }; + 4E4803EF2D74333F00B5F1ED /* FilterPickerBar.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FilterPickerBar.swift; sourceTree = ""; }; 4E49DECA2CE54A9200352DCD /* SessionsSection.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SessionsSection.swift; sourceTree = ""; }; 4E49DECC2CE54C7200352DCD /* PermissionSection.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PermissionSection.swift; sourceTree = ""; }; 4E49DECE2CE54D2700352DCD /* MaxBitratePolicy.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MaxBitratePolicy.swift; sourceTree = ""; }; @@ -2665,13 +2665,13 @@ path = Translations; sourceTree = ""; }; - 4E81D2B42D72B17100CA71CC /* FilterViews */ = { + 4E81D2B42D72B17100CA71CC /* FilterPickerBar */ = { isa = PBXGroup; children = ( 4E81D2B52D72B1D600CA71CC /* Components */, - 4E4803EF2D74333F00B5F1ED /* FilterBar.swift */, + 4E4803EF2D74333F00B5F1ED /* FilterPickerBar.swift */, ); - path = FilterViews; + path = FilterPickerBar; sourceTree = ""; }; 4E81D2B52D72B1D600CA71CC /* Components */ = { @@ -2913,7 +2913,7 @@ isa = PBXGroup; children = ( 4E81D2B62D72BE3900CA71CC /* FilterView.swift */, - 4E81D2B42D72B17100CA71CC /* FilterViews */, + 4E81D2B42D72B17100CA71CC /* FilterPickerBar */, 4EEACAA32D420FEF00F1D54D /* LetterPickerBar */, ); path = LibraryFilters; @@ -6185,7 +6185,7 @@ E1DC981A296DD1CD00982F06 /* CinematicBackgroundView.swift in Sources */, E1A1528B28FD22F600600579 /* TextPairView.swift in Sources */, E11042762B8013DF00821020 /* Stateful.swift in Sources */, - 4E4803F02D74334200B5F1ED /* FilterBar.swift in Sources */, + 4E4803F02D74334200B5F1ED /* FilterPickerBar.swift in Sources */, 091B5A8D268315D400D78B61 /* ServerDiscovery.swift in Sources */, E1575E66293E77B5001665B1 /* Poster.swift in Sources */, E18E021F2887492B0022598C /* SystemImageContentView.swift in Sources */,