diff --git a/Swiftfin tvOS/Components/InlineEnumToggle.swift b/Swiftfin tvOS/Components/InlineEnumToggle.swift deleted file mode 100644 index c38fbd47..00000000 --- a/Swiftfin tvOS/Components/InlineEnumToggle.swift +++ /dev/null @@ -1,48 +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 SwiftUI - -struct InlineEnumToggle: View { - - @Binding - private var selection: ItemType - - private let title: String - - var body: some View { - Button { - guard let currentSelectionIndex = ItemType.allCases.firstIndex(of: selection) else { return } - - if ItemType.allCases.index(currentSelectionIndex, offsetBy: 1) == ItemType.allCases.endIndex { - selection = ItemType.allCases[ItemType.allCases.startIndex] - } else { - selection = ItemType.allCases[ItemType.allCases.index(currentSelectionIndex, offsetBy: 1)] - } - } label: { - HStack { - Text(title) - - Spacer() - - Text(selection.displayTitle) - .foregroundColor(.secondary) - } - } - } -} - -extension InlineEnumToggle { - - init(title: String, selection: Binding) { - self.init( - selection: selection, - title: title - ) - } -} diff --git a/Swiftfin tvOS/Components/ListRowMenu.swift b/Swiftfin tvOS/Components/ListRowMenu.swift new file mode 100644 index 00000000..660c6744 --- /dev/null +++ b/Swiftfin tvOS/Components/ListRowMenu.swift @@ -0,0 +1,139 @@ +// +// 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 SwiftUI + +struct ListRowMenu: View { + + // MARK: - Focus State + + @FocusState + private var isFocused: Bool + + // MARK: - Properties + + private let title: Text + private let subtitle: Subtitle? + private let content: () -> Content + + // MARK: - Body + + var body: some View { + Menu(content: content) { + ZStack { + RoundedRectangle(cornerRadius: 10) + .fill(isFocused ? Color.white : Color.clear) + + HStack { + title + .foregroundStyle(isFocused ? Color.black : Color.white) + .padding(.leading, 4) + + Spacer() + + if let subtitle { + subtitle + .foregroundStyle(isFocused ? Color.black : Color.secondary) + .brightness(isFocused ? 0.4 : 0) + } + + Image(systemName: "chevron.up.chevron.down") + .font(.body.weight(.regular)) + .foregroundStyle(isFocused ? Color.black : Color.secondary) + .brightness(isFocused ? 0.4 : 0) + } + .padding(.horizontal) + } + .scaleEffect(isFocused ? 1.05 : 1.0) + .animation(.spring(response: 0.15, dampingFraction: 0.75), value: isFocused) + } + .menuStyle(.borderlessButton) + .listRowInsets(.zero) + .focused($isFocused) + } +} + +// MARK: - Initializers + +// Base initializer +extension ListRowMenu where Subtitle == Text? { + + init(_ title: Text, @ViewBuilder content: @escaping () -> Content) { + self.title = title + self.subtitle = nil + self.content = content + } + + init(_ title: Text, subtitle: Text?, @ViewBuilder content: @escaping () -> Content) { + self.title = title + self.subtitle = subtitle + self.content = content + } + + init(_ title: Text, subtitle: String?, @ViewBuilder content: @escaping () -> Content) { + self.title = title + self.subtitle = subtitle.map { Text($0) } + self.content = content + } + + init(_ title: String, @ViewBuilder content: @escaping () -> Content) { + self.title = Text(title) + self.subtitle = nil + self.content = content + } + + init(_ title: String, subtitle: String?, @ViewBuilder content: @escaping () -> Content) { + self.title = Text(title) + self.subtitle = subtitle.map { Text($0) } + self.content = content + } + + init(_ title: String, subtitle: Text?, @ViewBuilder content: @escaping () -> Content) { + self.title = Text(title) + self.subtitle = subtitle + self.content = content + } +} + +// Custom view subtitles +extension ListRowMenu { + + init(_ title: String, @ViewBuilder subtitle: @escaping () -> Subtitle, @ViewBuilder content: @escaping () -> Content) { + self.title = Text(title) + self.subtitle = subtitle() + self.content = content + } + + init(_ title: Text, @ViewBuilder subtitle: @escaping () -> Subtitle, @ViewBuilder content: @escaping () -> Content) { + self.title = title + self.subtitle = subtitle() + self.content = content + } +} + +// Initialize from a CaseIterable Enum +extension ListRowMenu where Subtitle == Text, Content == AnyView { + + init( + _ title: String, + selection: Binding + ) where ItemType: CaseIterable & Displayable & Hashable, + ItemType.AllCases: RandomAccessCollection + { + self.title = Text(title) + self.subtitle = Text(selection.wrappedValue.displayTitle) + self.content = { + Picker(title, selection: selection) { + ForEach(Array(ItemType.allCases), id: \.self) { option in + Text(option.displayTitle).tag(option) + } + } + .eraseToAnyView() + } + } +} diff --git a/Swiftfin tvOS/Views/AppSettingsView/AppSettingsView.swift b/Swiftfin tvOS/Views/AppSettingsView/AppSettingsView.swift index 582442ad..4352db85 100644 --- a/Swiftfin tvOS/Views/AppSettingsView/AppSettingsView.swift +++ b/Swiftfin tvOS/Views/AppSettingsView/AppSettingsView.swift @@ -31,6 +31,12 @@ struct AppSettingsView: View { @State private var removeAllServersSelected: Bool = false + private var selectedServer: ServerState? { + viewModel.servers.first { server in + selectUserAllServersSplashscreen == .server(id: server.id) + } + } + var body: some View { SplitFormWindowView() .descriptionView { @@ -51,9 +57,16 @@ struct AppSettingsView: View { Toggle(L10n.useSplashscreen, isOn: $selectUserUseSplashscreen) if selectUserUseSplashscreen { - Menu { + ListRowMenu(L10n.servers) { + if selectUserAllServersSplashscreen == .all { + Label(L10n.random, systemImage: "dice.fill") + } else if let selectedServer { + Text(selectedServer.name) + } else { + Text(L10n.none) + } + } content: { Picker(L10n.servers, selection: $selectUserAllServersSplashscreen) { - Label(L10n.random, systemImage: "dice.fill") .tag(SelectUserServerSelection.all) @@ -62,24 +75,7 @@ struct AppSettingsView: View { .tag(SelectUserServerSelection.server(id: server.id)) } } - } label: { - HStack { - Text(L10n.servers) - .frame(maxWidth: .infinity, alignment: .leading) - - if selectUserAllServersSplashscreen == .all { - Label(L10n.random, systemImage: "dice.fill") - } else if let server = viewModel.servers.first( - where: { server in - selectUserAllServersSplashscreen == .server(id: server.id) - } - ) { - Text(server.name) - } - } } - .listRowBackground(Color.clear) - .listRowInsets(.zero) } } header: { Text(L10n.splashscreen) @@ -96,5 +92,6 @@ struct AppSettingsView: View { router.route(to: \.log) } } + .navigationTitle(L10n.advanced) } } diff --git a/Swiftfin tvOS/Views/AppSettingsView/Components/SignOutIntervalSection.swift b/Swiftfin tvOS/Views/AppSettingsView/Components/SignOutIntervalSection.swift index a0fc975f..794725e0 100644 --- a/Swiftfin tvOS/Views/AppSettingsView/Components/SignOutIntervalSection.swift +++ b/Swiftfin tvOS/Views/AppSettingsView/Components/SignOutIntervalSection.swift @@ -12,6 +12,7 @@ import SwiftUI extension AppSettingsView { struct SignOutIntervalSection: View { + @EnvironmentObject private var router: AppSettingsCoordinator.Router @@ -36,24 +37,12 @@ extension AppSettingsView { Toggle(L10n.signoutBackground, isOn: $signOutOnBackground) if signOutOnBackground { - HStack { - Text(L10n.duration) - - Spacer() - - Button { - router.route(to: \.hourPicker) - } label: { - HStack { - Text(backgroundSignOutInterval, format: .hourMinute) - .foregroundStyle(.secondary) - - Image(systemName: "chevron.right") - .font(.body.weight(.semibold)) - .foregroundStyle(.secondary) - } - } - .foregroundStyle(.primary, .secondary) + ChevronButton( + L10n.duration, + subtitle: Text(backgroundSignOutInterval, format: .hourMinute) + ) + .onSelect { + router.route(to: \.hourPicker) } } } footer: { diff --git a/Swiftfin tvOS/Views/ServerDetailView.swift b/Swiftfin tvOS/Views/ServerDetailView.swift index d6af5224..f7d90d6e 100644 --- a/Swiftfin tvOS/Views/ServerDetailView.swift +++ b/Swiftfin tvOS/Views/ServerDetailView.swift @@ -45,12 +45,12 @@ struct EditServerView: View { } Section(L10n.url) { - Menu { + ListRowMenu(L10n.serverURL, subtitle: viewModel.server.currentURL.absoluteString) { ForEach(viewModel.server.urls.sorted(using: \.absoluteString), id: \.self) { url in - Button(action: { + Button { guard viewModel.server.currentURL != url else { return } viewModel.setCurrentURL(to: url) - }) { + } label: { HStack { Text(url.absoluteString) .foregroundColor(.primary) @@ -65,18 +65,7 @@ struct EditServerView: View { } } } - } label: { - HStack { - Text(viewModel.server.currentURL.absoluteString) - .foregroundColor(.primary) - .frame(maxWidth: .infinity, alignment: .leading) - Spacer() - Image(systemName: "chevron.down") - .foregroundColor(.secondary) - } } - .listRowBackground(Color.clear) - .listRowInsets(.zero) } if isEditing { diff --git a/Swiftfin tvOS/Views/SettingsView/CustomDeviceProfileSettingsView/CustomDeviceProfileSettingsView.swift b/Swiftfin tvOS/Views/SettingsView/CustomDeviceProfileSettingsView/CustomDeviceProfileSettingsView.swift index 9af28401..d6b7b534 100644 --- a/Swiftfin tvOS/Views/SettingsView/CustomDeviceProfileSettingsView/CustomDeviceProfileSettingsView.swift +++ b/Swiftfin tvOS/Views/SettingsView/CustomDeviceProfileSettingsView/CustomDeviceProfileSettingsView.swift @@ -45,10 +45,7 @@ struct CustomDeviceProfileSettingsView: View { } .contentView { Section { - InlineEnumToggle( - title: L10n.behavior, - selection: $customDeviceProfileAction - ) + ListRowMenu(L10n.behavior, selection: $customDeviceProfileAction) } header: { L10n.behavior.text } footer: { diff --git a/Swiftfin tvOS/Views/SettingsView/CustomizeViewsSettings/Components/Sections/LibrarySection.swift b/Swiftfin tvOS/Views/SettingsView/CustomizeViewsSettings/Components/Sections/LibrarySection.swift index 6b37aaeb..5ddfe8ad 100644 --- a/Swiftfin tvOS/Views/SettingsView/CustomizeViewsSettings/Components/Sections/LibrarySection.swift +++ b/Swiftfin tvOS/Views/SettingsView/CustomizeViewsSettings/Components/Sections/LibrarySection.swift @@ -49,9 +49,9 @@ extension CustomizeViewsSettings { Section(L10n.library) { Toggle(L10n.cinematicBackground, isOn: $cinematicBackground) - InlineEnumToggle(title: L10n.posters, selection: $libraryPosterType) + ListRowMenu(L10n.posters, selection: $libraryPosterType) - InlineEnumToggle(title: L10n.library, selection: $libraryDisplayType) + ListRowMenu(L10n.library, selection: $libraryDisplayType) if libraryDisplayType == .list { ChevronButton( diff --git a/Swiftfin tvOS/Views/SettingsView/CustomizeViewsSettings/CustomizeViewsSettings.swift b/Swiftfin tvOS/Views/SettingsView/CustomizeViewsSettings/CustomizeViewsSettings.swift index 049ad591..2b26db73 100644 --- a/Swiftfin tvOS/Views/SettingsView/CustomizeViewsSettings/CustomizeViewsSettings.swift +++ b/Swiftfin tvOS/Views/SettingsView/CustomizeViewsSettings/CustomizeViewsSettings.swift @@ -60,15 +60,15 @@ struct CustomizeViewsSettings: View { Toggle(L10n.showPosterLabels, isOn: $showPosterLabels) - InlineEnumToggle(title: L10n.next, selection: $nextUpPosterType) + ListRowMenu(L10n.next, selection: $nextUpPosterType) - InlineEnumToggle(title: L10n.recentlyAdded, selection: $recentlyAddedPosterType) + ListRowMenu(L10n.recentlyAdded, selection: $recentlyAddedPosterType) - InlineEnumToggle(title: L10n.latestWithString(L10n.library), selection: $latestInLibraryPosterType) + ListRowMenu(L10n.latestWithString(L10n.library), selection: $latestInLibraryPosterType) - InlineEnumToggle(title: L10n.recommended, selection: $similarPosterType) + ListRowMenu(L10n.recommended, selection: $similarPosterType) - InlineEnumToggle(title: L10n.search, selection: $searchPosterType) + ListRowMenu(L10n.search, selection: $searchPosterType) } LibrarySection() diff --git a/Swiftfin tvOS/Views/SettingsView/PlaybackQualitySettingsView.swift b/Swiftfin tvOS/Views/SettingsView/PlaybackQualitySettingsView.swift index fec28486..69f4bb0c 100644 --- a/Swiftfin tvOS/Views/SettingsView/PlaybackQualitySettingsView.swift +++ b/Swiftfin tvOS/Views/SettingsView/PlaybackQualitySettingsView.swift @@ -39,11 +39,8 @@ struct PlaybackQualitySettingsView: View { } .contentView { Section { - InlineEnumToggle( - title: L10n.maximumBitrate, - selection: $appMaximumBitrate - ) - .focused($focusedItem, equals: .maximumBitrate) + ListRowMenu(L10n.maximumBitrate, selection: $appMaximumBitrate) + .focused($focusedItem, equals: .maximumBitrate) } header: { L10n.bitrateDefault.text } footer: { @@ -53,21 +50,15 @@ struct PlaybackQualitySettingsView: View { if appMaximumBitrate == .auto { Section { - InlineEnumToggle( - title: L10n.testSize, - selection: $appMaximumBitrateTest - ) + ListRowMenu(L10n.testSize, selection: $appMaximumBitrateTest) } footer: { L10n.bitrateTestDisclaimer.text } } Section { - InlineEnumToggle( - title: L10n.compatibility, - selection: $compatibilityMode - ) - .focused($focusedItem, equals: .compatibility) + ListRowMenu(L10n.compatibility, selection: $compatibilityMode) + .focused($focusedItem, equals: .compatibility) if compatibilityMode == .custom { ChevronButton(L10n.profiles) diff --git a/Swiftfin tvOS/Views/SettingsView/SettingsView.swift b/Swiftfin tvOS/Views/SettingsView/SettingsView.swift index 89e553c3..caa8751e 100644 --- a/Swiftfin tvOS/Views/SettingsView/SettingsView.swift +++ b/Swiftfin tvOS/Views/SettingsView/SettingsView.swift @@ -56,7 +56,7 @@ struct SettingsView: View { Section(L10n.videoPlayer) { - InlineEnumToggle(title: L10n.videoPlayerType, selection: $videoPlayerType) + ListRowMenu(L10n.videoPlayerType, selection: $videoPlayerType) ChevronButton(L10n.videoPlayer) .onSelect { diff --git a/Swiftfin.xcodeproj/project.pbxproj b/Swiftfin.xcodeproj/project.pbxproj index c222353c..fa30cca7 100644 --- a/Swiftfin.xcodeproj/project.pbxproj +++ b/Swiftfin.xcodeproj/project.pbxproj @@ -207,7 +207,6 @@ 4EBE06472C7E9509004A6C03 /* PlaybackCompatibility.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4EBE06452C7E9509004A6C03 /* PlaybackCompatibility.swift */; }; 4EBE064D2C7EB6D3004A6C03 /* VideoPlayerType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4EBE064C2C7EB6D3004A6C03 /* VideoPlayerType.swift */; }; 4EBE064E2C7EB6D3004A6C03 /* VideoPlayerType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4EBE064C2C7EB6D3004A6C03 /* VideoPlayerType.swift */; }; - 4EBE064F2C7ECE8D004A6C03 /* InlineEnumToggle.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1549677296CB22B00C4EF88 /* InlineEnumToggle.swift */; }; 4EBE06532C7ED0E1004A6C03 /* DeviceProfile.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4EBE06502C7ED0E1004A6C03 /* DeviceProfile.swift */; }; 4EBE06542C7ED0E1004A6C03 /* DeviceProfile.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4EBE06502C7ED0E1004A6C03 /* DeviceProfile.swift */; }; 4EC1C8522C7FDFA300E2879E /* PlaybackDeviceProfile.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4EC1C8512C7FDFA300E2879E /* PlaybackDeviceProfile.swift */; }; @@ -221,6 +220,8 @@ 4EC2B1A92CC97C0700D866BE /* ServerUserDetailsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4EC2B1A82CC97C0400D866BE /* ServerUserDetailsView.swift */; }; 4EC50D612C934B3A00FC3D0E /* ServerTasksViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4EC50D602C934B3A00FC3D0E /* ServerTasksViewModel.swift */; }; 4EC6C16B2C92999800FC904B /* TranscodeSection.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4EC6C16A2C92999800FC904B /* TranscodeSection.swift */; }; + 4ECCF9F92D8504420048B331 /* WrappingHStack in Frameworks */ = {isa = PBXBuildFile; productRef = 321BE8311E445482ED5C95C3 /* WrappingHStack */; }; + 4ECCF9FB2D8505890048B331 /* ListRowMenu.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4ECCF9FA2D8505860048B331 /* ListRowMenu.swift */; }; 4ECDAA9E2C920A8E0030F2F5 /* TranscodeReason.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4ECDAA9D2C920A8E0030F2F5 /* TranscodeReason.swift */; }; 4ECDAA9F2C920A8E0030F2F5 /* TranscodeReason.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4ECDAA9D2C920A8E0030F2F5 /* TranscodeReason.swift */; }; 4ECF5D882D0A3D0200F066B1 /* AddAccessScheduleView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4ECF5D812D0A3D0200F066B1 /* AddAccessScheduleView.swift */; }; @@ -761,7 +762,6 @@ E154966B296CA2EF00C4EF88 /* DownloadManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = E154965B296CA2EF00C4EF88 /* DownloadManager.swift */; }; E154966E296CA2EF00C4EF88 /* LogManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = E154965D296CA2EF00C4EF88 /* LogManager.swift */; }; E154966F296CA2EF00C4EF88 /* LogManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = E154965D296CA2EF00C4EF88 /* LogManager.swift */; }; - E1549678296CB22B00C4EF88 /* InlineEnumToggle.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1549677296CB22B00C4EF88 /* InlineEnumToggle.swift */; }; E154967A296CB4B000C4EF88 /* VideoPlayerSettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1549679296CB4B000C4EF88 /* VideoPlayerSettingsView.swift */; }; E154967C296CBB1A00C4EF88 /* FontPickerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E154967B296CBB1A00C4EF88 /* FontPickerView.swift */; }; E154967E296CCB6C00C4EF88 /* BasicNavigationCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = E154967D296CCB6C00C4EF88 /* BasicNavigationCoordinator.swift */; }; @@ -922,10 +922,6 @@ E18A8E8328D60BC400333B9A /* VideoPlayer.swift in Sources */ = {isa = PBXBuildFile; fileRef = E18A8E8228D60BC400333B9A /* VideoPlayer.swift */; }; E18A8E8528D60D0000333B9A /* VideoPlayerCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = E18A8E8428D60D0000333B9A /* VideoPlayerCoordinator.swift */; }; E18ACA8B2A14301800BB4F35 /* ScalingButtonStyle.swift in Sources */ = {isa = PBXBuildFile; fileRef = E18ACA8A2A14301800BB4F35 /* ScalingButtonStyle.swift */; }; - E18ACA8D2A14773500BB4F35 /* BuildFile in Sources */ = {isa = PBXBuildFile; }; - E18ACA8F2A15A2CF00BB4F35 /* BuildFile in Sources */ = {isa = PBXBuildFile; }; - E18ACA922A15A32F00BB4F35 /* BuildFile in Sources */ = {isa = PBXBuildFile; }; - E18ACA952A15A3E100BB4F35 /* BuildFile in Sources */ = {isa = PBXBuildFile; }; E18CE0AF28A222240092E7F1 /* PublicUserRow.swift in Sources */ = {isa = PBXBuildFile; fileRef = E18CE0AE28A222240092E7F1 /* PublicUserRow.swift */; }; E18CE0B228A229E70092E7F1 /* UserDto.swift in Sources */ = {isa = PBXBuildFile; fileRef = E18CE0B128A229E70092E7F1 /* UserDto.swift */; }; E18CE0B428A22EDA0092E7F1 /* RepeatingTimer.swift in Sources */ = {isa = PBXBuildFile; fileRef = E18CE0B328A22EDA0092E7F1 /* RepeatingTimer.swift */; }; @@ -1448,6 +1444,7 @@ 4EC50D602C934B3A00FC3D0E /* ServerTasksViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ServerTasksViewModel.swift; sourceTree = ""; }; 4EC6C16A2C92999800FC904B /* TranscodeSection.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TranscodeSection.swift; sourceTree = ""; }; 4EC71FBB2D161FE300D0B3A8 /* AlphabetizeStrings.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AlphabetizeStrings.swift; sourceTree = ""; }; + 4ECCF9FA2D8505860048B331 /* ListRowMenu.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ListRowMenu.swift; sourceTree = ""; }; 4ECDAA9D2C920A8E0030F2F5 /* TranscodeReason.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TranscodeReason.swift; sourceTree = ""; }; 4ECF5D812D0A3D0200F066B1 /* AddAccessScheduleView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AddAccessScheduleView.swift; sourceTree = ""; }; 4ECF5D892D0A57EF00F066B1 /* DynamicDayOfWeek.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DynamicDayOfWeek.swift; sourceTree = ""; }; @@ -1829,7 +1826,6 @@ E1549659296CA2EF00C4EF88 /* Notifications.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Notifications.swift; sourceTree = ""; }; E154965B296CA2EF00C4EF88 /* DownloadManager.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DownloadManager.swift; sourceTree = ""; }; E154965D296CA2EF00C4EF88 /* LogManager.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LogManager.swift; sourceTree = ""; }; - E1549677296CB22B00C4EF88 /* InlineEnumToggle.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InlineEnumToggle.swift; sourceTree = ""; }; E1549679296CB4B000C4EF88 /* VideoPlayerSettingsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VideoPlayerSettingsView.swift; sourceTree = ""; }; E154967B296CBB1A00C4EF88 /* FontPickerView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FontPickerView.swift; sourceTree = ""; }; E154967D296CCB6C00C4EF88 /* BasicNavigationCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BasicNavigationCoordinator.swift; sourceTree = ""; }; @@ -2191,6 +2187,7 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( + 4ECCF9F92D8504420048B331 /* WrappingHStack in Frameworks */, E12B93072947CD0F00CE0BD9 /* Pulse in Frameworks */, 62666E3E27E503FA00EC0ECD /* MediaAccessibility.framework in Frameworks */, 62666DFF27E5016400EC0ECD /* CFNetwork.framework in Frameworks */, @@ -3384,9 +3381,9 @@ E1C92618288756BD002A7A66 /* DotHStack.swift */, E12E30F4296392EC0022FAC9 /* EnumPickerView.swift */, 4EF0DCA82D49751B005A5194 /* ErrorView.swift */, - E1549677296CB22B00C4EF88 /* InlineEnumToggle.swift */, E1A42E5028CBE44500A14DCB /* LandscapePosterProgressBar.swift */, E1763A632BF3C9AA004DF6AB /* ListRowButton.swift */, + 4ECCF9FA2D8505860048B331 /* ListRowMenu.swift */, E10E842B29A589860064EA49 /* NonePosterButton.swift */, 4E2AC4D32C6C4C1200DD600D /* OrderedSectionSelectorView.swift */, E1C92617288756BD002A7A66 /* PosterButton.swift */, @@ -5915,6 +5912,7 @@ E1575E92293E7B1E001665B1 /* CGSize.swift in Sources */, E1575E7E293E77B5001665B1 /* ItemFilterCollection.swift in Sources */, 4E98F7D22D123AD4001E7518 /* NavigationBarMenuButton.swift in Sources */, + 4ECCF9FB2D8505890048B331 /* ListRowMenu.swift in Sources */, 4E98F7D32D123AD4001E7518 /* View-tvOS.swift in Sources */, C46DD8EF2A8FB56E0046A504 /* LiveBottomBarView.swift in Sources */, 4EE766FB2D132954009658F0 /* RemoteSearchResult.swift in Sources */, @@ -6173,7 +6171,6 @@ E1BCDB502BE1F491009F6744 /* ResetUserPasswordViewModel.swift in Sources */, C46DD8D72A8DC2990046A504 /* LiveVideoPlayer.swift in Sources */, E1575E88293E7A00001665B1 /* LightAppIcon.swift in Sources */, - E1549678296CB22B00C4EF88 /* InlineEnumToggle.swift in Sources */, E193D5432719407E00900D82 /* tvOSMainCoordinator.swift in Sources */, E1DABAFA2A270E62008AC34A /* OverviewCard.swift in Sources */, E11CEB8928998549003E74C7 /* BottomEdgeGradientModifier.swift in Sources */, @@ -6503,7 +6500,6 @@ E104C870296E087200C1C3F9 /* IndicatorSettingsView.swift in Sources */, E12A9EF829499E0100731C3A /* JellyfinClient.swift in Sources */, E1722DB129491C3900CC0239 /* ImageBlurHashes.swift in Sources */, - 4EBE064F2C7ECE8D004A6C03 /* InlineEnumToggle.swift in Sources */, E14EDEC82B8FB65F000F00A4 /* ItemFilterType.swift in Sources */, E1EBCB42278BD174009FE6E9 /* TruncatedText.swift in Sources */, 4ED25CA12D07E3590010333C /* EditAccessScheduleView.swift in Sources */,