From a199d69a314aab1bf455528733f7af019ce7a647 Mon Sep 17 00:00:00 2001 From: Ethan Pippin Date: Fri, 30 Aug 2024 09:05:56 -0600 Subject: [PATCH] Some Cleanup (#1216) --- Shared/Components/SelectorView.swift | 26 ++++++++---- Shared/Objects/BindingBox.swift | 32 +++++++++++++++ Swiftfin.xcodeproj/project.pbxproj | 6 +++ .../OrderedSectionSelectorView.swift | 41 +++++++++---------- Swiftfin/Views/FontPickerView.swift | 16 +------- 5 files changed, 78 insertions(+), 43 deletions(-) create mode 100644 Shared/Objects/BindingBox.swift diff --git a/Shared/Components/SelectorView.swift b/Shared/Components/SelectorView.swift index fe4fe582..a8c7666d 100644 --- a/Shared/Components/SelectorView.swift +++ b/Shared/Components/SelectorView.swift @@ -22,8 +22,20 @@ struct SelectorView: View { @Default(.accentColor) private var accentColor - @Binding - private var selection: Set + @StateObject + private var selection: BindingBox> + + private init( + selection: Binding>, + sources: [Element], + label: @escaping (Element) -> Label, + type: SelectorType + ) { + self._selection = StateObject(wrappedValue: BindingBox(source: selection)) + self.sources = sources + self.label = label + self.type = type + } private let sources: [Element] private var label: (Element) -> Label @@ -44,7 +56,7 @@ struct SelectorView: View { Spacer() - if selection.contains(element) { + if selection.value.contains(element) { Image(systemName: "checkmark.circle.fill") .resizable() .backport @@ -60,14 +72,14 @@ struct SelectorView: View { } private func handleSingleSelect(with element: Element) { - selection = [element] + selection.value = [element] } private func handleMultiSelect(with element: Element) { - if selection.contains(element) { - selection.remove(element) + if selection.value.contains(element) { + selection.value.remove(element) } else { - selection.insert(element) + selection.value.insert(element) } } } diff --git a/Shared/Objects/BindingBox.swift b/Shared/Objects/BindingBox.swift new file mode 100644 index 00000000..175eac26 --- /dev/null +++ b/Shared/Objects/BindingBox.swift @@ -0,0 +1,32 @@ +// +// 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 Combine +import SwiftUI + +/// Utility for views that are passed a `Binding` that +/// may not be able to respond to view updates from +/// the source +class BindingBox: ObservableObject { + + @Published + var value: Wrapped + + private let source: Binding + private var valueObserver: AnyCancellable! + + init(source: Binding) { + self.source = source + self.value = source.wrappedValue + valueObserver = nil + + valueObserver = $value.sink { [weak self] in + self?.source.wrappedValue = $0 + } + } +} diff --git a/Swiftfin.xcodeproj/project.pbxproj b/Swiftfin.xcodeproj/project.pbxproj index 584379c3..fab11f0b 100644 --- a/Swiftfin.xcodeproj/project.pbxproj +++ b/Swiftfin.xcodeproj/project.pbxproj @@ -273,6 +273,8 @@ E1153DCD2BBB633B00424D36 /* FastSVGView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1153DCB2BBB633B00424D36 /* FastSVGView.swift */; }; E1153DD02BBB634F00424D36 /* SVGKit in Frameworks */ = {isa = PBXBuildFile; productRef = E1153DCF2BBB634F00424D36 /* SVGKit */; }; E1153DD22BBB649C00424D36 /* SVGKit in Frameworks */ = {isa = PBXBuildFile; productRef = E1153DD12BBB649C00424D36 /* SVGKit */; }; + E11562952C818CB2001D5DE4 /* BindingBox.swift in Sources */ = {isa = PBXBuildFile; fileRef = E11562942C818CB2001D5DE4 /* BindingBox.swift */; }; + E11562962C818CB2001D5DE4 /* BindingBox.swift in Sources */ = {isa = PBXBuildFile; fileRef = E11562942C818CB2001D5DE4 /* BindingBox.swift */; }; E118959D289312020042947B /* BaseItemPerson+Poster.swift in Sources */ = {isa = PBXBuildFile; fileRef = E118959C289312020042947B /* BaseItemPerson+Poster.swift */; }; E118959E289312020042947B /* BaseItemPerson+Poster.swift in Sources */ = {isa = PBXBuildFile; fileRef = E118959C289312020042947B /* BaseItemPerson+Poster.swift */; }; E11895A9289383BC0042947B /* ScrollViewOffsetModifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = E11895A8289383BC0042947B /* ScrollViewOffsetModifier.swift */; }; @@ -1143,6 +1145,7 @@ E1153D9B2BBA3E9D00424D36 /* LoadingCard.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoadingCard.swift; sourceTree = ""; }; E1153DB22BBA80B400424D36 /* EmptyCard.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EmptyCard.swift; sourceTree = ""; }; E1153DCB2BBB633B00424D36 /* FastSVGView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FastSVGView.swift; sourceTree = ""; }; + E11562942C818CB2001D5DE4 /* BindingBox.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BindingBox.swift; sourceTree = ""; }; E1171A1828A2212600FA1AF5 /* QuickConnectView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = QuickConnectView.swift; sourceTree = ""; }; E118959C289312020042947B /* BaseItemPerson+Poster.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "BaseItemPerson+Poster.swift"; sourceTree = ""; }; E11895A8289383BC0042947B /* ScrollViewOffsetModifier.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ScrollViewOffsetModifier.swift; sourceTree = ""; }; @@ -1817,6 +1820,7 @@ isa = PBXGroup; children = ( E1D4BF802719D22800A11E64 /* AppAppearance.swift */, + E11562942C818CB2001D5DE4 /* BindingBox.swift */, E129429728F4785200796AC6 /* CaseIterablePicker.swift */, E10231432BCF8A51009D71FC /* ChannelProgram.swift */, E17FB55128C119D400311DFE /* Displayable.swift */, @@ -4216,6 +4220,7 @@ E1575EA3293E7B1E001665B1 /* UIDevice.swift in Sources */, E193D547271941C500900D82 /* SelectUserView.swift in Sources */, E1BDF2E62951475300CC0294 /* VideoPlayerActionButton.swift in Sources */, + E11562962C818CB2001D5DE4 /* BindingBox.swift in Sources */, E10231592BCF8AF8009D71FC /* ChannelLibraryView.swift in Sources */, E1E6C44929AECEE70064123F /* AutoPlayActionButton.swift in Sources */, E1C926152887565C002A7A66 /* EpisodeCard.swift in Sources */, @@ -4432,6 +4437,7 @@ 6334175B287DDFB9000603CE /* QuickConnectAuthorizeView.swift in Sources */, E13F05F128BC9016003499D2 /* LibraryRow.swift in Sources */, E168BD10289A4162001A6922 /* HomeView.swift in Sources */, + E11562952C818CB2001D5DE4 /* BindingBox.swift in Sources */, 4E16FD532C01840C00110147 /* LetterPickerBar.swift in Sources */, E1BE1CEA2BDB5AFE008176A9 /* UserGridButton.swift in Sources */, E1401CB129386C9200E8B599 /* UIColor.swift in Sources */, diff --git a/Swiftfin/Components/OrderedSectionSelectorView.swift b/Swiftfin/Components/OrderedSectionSelectorView.swift index 1db42cec..b91ed34e 100644 --- a/Swiftfin/Components/OrderedSectionSelectorView.swift +++ b/Swiftfin/Components/OrderedSectionSelectorView.swift @@ -13,43 +13,44 @@ struct OrderedSectionSelectorView: View { @Environment(\.editMode) private var editMode - @Binding - private var selection: [Element] - - @State - private var updateSelection: [Element] + @StateObject + private var selection: BindingBox<[Element]> private var disabledSelection: [Element] { - sources.subtracting(updateSelection) + sources.subtracting(selection.value) } private var label: (Element) -> any View private let sources: [Element] private func move(from source: IndexSet, to destination: Int) { - updateSelection.move(fromOffsets: source, toOffset: destination) + selection.value.move(fromOffsets: source, toOffset: destination) } private func select(element: Element) { - if updateSelection.contains(element) { - updateSelection.removeAll(where: { $0 == element }) + if selection.value.contains(element) { + selection.value.removeAll(where: { $0 == element }) } else { - updateSelection.append(element) + selection.value.append(element) } } + private var isReordering: Bool { + editMode?.wrappedValue.isEditing ?? false + } + var body: some View { List { Section(L10n.enabled) { - if updateSelection.isEmpty { + if selection.value.isEmpty { L10n.none.text .foregroundStyle(.secondary) } - ForEach(updateSelection, id: \.self) { element in + ForEach(selection.value, id: \.self) { element in Button { - if !(editMode?.wrappedValue.isEditing ?? true) { + if !isReordering { select(element: element) } } label: { @@ -59,7 +60,7 @@ struct OrderedSectionSelectorView: View { Spacer() - if !(editMode?.wrappedValue.isEditing ?? false) { + if !isReordering { Image(systemName: "minus.circle.fill") .foregroundColor(.red) } @@ -79,7 +80,7 @@ struct OrderedSectionSelectorView: View { ForEach(disabledSelection, id: \.self) { element in Button { - if !(editMode?.wrappedValue.isEditing ?? true) { + if !isReordering { select(element: element) } } label: { @@ -89,7 +90,7 @@ struct OrderedSectionSelectorView: View { Spacer() - if !(editMode?.wrappedValue.isEditing ?? false) { + if !isReordering { Image(systemName: "plus.circle.fill") .foregroundColor(.green) } @@ -99,21 +100,17 @@ struct OrderedSectionSelectorView: View { } } } - .animation(.linear(duration: 0.2), value: updateSelection) + .animation(.linear(duration: 0.2), value: selection.value) .toolbar { EditButton() } - .onChange(of: updateSelection) { newValue in - selection = newValue - } } } extension OrderedSectionSelectorView { init(selection: Binding<[Element]>, sources: [Element]) { - self._selection = selection - self._updateSelection = State(initialValue: selection.wrappedValue) + self._selection = StateObject(wrappedValue: BindingBox(source: selection)) self.sources = sources self.label = { Text($0.displayTitle).foregroundColor(.primary) } } diff --git a/Swiftfin/Views/FontPickerView.swift b/Swiftfin/Views/FontPickerView.swift index d5eb9c64..73a51142 100644 --- a/Swiftfin/Views/FontPickerView.swift +++ b/Swiftfin/Views/FontPickerView.swift @@ -6,26 +6,17 @@ // Copyright (c) 2024 Jellyfin & Jellyfin Contributors // -import Defaults import SwiftUI import UIKit struct FontPickerView: View { @Binding - private var selection: String - - @State - private var updateSelection: String - - init(selection: Binding) { - self._selection = selection - self.updateSelection = selection.wrappedValue - } + var selection: String var body: some View { SelectorView( - selection: $updateSelection, + selection: $selection, sources: UIFont.familyNames ) .label { fontFamily in @@ -33,9 +24,6 @@ struct FontPickerView: View { .foregroundColor(.primary) .font(.custom(fontFamily, size: 18)) } - .onChange(of: updateSelection) { newValue in - selection = newValue - } .navigationTitle("Font") } }