diff --git a/Yattee/Views/Settings/AdvancedSettingsView.swift b/Yattee/Views/Settings/AdvancedSettingsView.swift index 78fb9b62..ba62db5b 100644 --- a/Yattee/Views/Settings/AdvancedSettingsView.swift +++ b/Yattee/Views/Settings/AdvancedSettingsView.swift @@ -99,7 +99,7 @@ struct AdvancedSettingsView: View { @ViewBuilder private func feedSection(settingsManager: SettingsManager) -> some View { Section { - Picker(selection: Binding( + PlatformMenuPicker(selection: Binding( get: { settingsManager.feedCacheValidityMinutes }, set: { settingsManager.feedCacheValidityMinutes = $0 } )) { @@ -192,7 +192,7 @@ struct AdvancedSettingsView: View { private var mpvSection: some View { if let settingsManager = appEnvironment?.settingsManager { Section { - Picker(selection: Binding( + PlatformMenuPicker(selection: Binding( get: { settingsManager.mpvBufferSeconds }, set: { settingsManager.mpvBufferSeconds = $0 } )) { diff --git a/Yattee/Views/Settings/PlatformMenuPicker.swift b/Yattee/Views/Settings/PlatformMenuPicker.swift new file mode 100644 index 00000000..855ac0cf --- /dev/null +++ b/Yattee/Views/Settings/PlatformMenuPicker.swift @@ -0,0 +1,42 @@ +// +// PlatformMenuPicker.swift +// Yattee +// +// A Picker wrapper that renders as a compact menu inside LabeledContent on tvOS, +// and as a standard inline Picker on iOS/macOS. Use this for short option lists +// in settings forms so tvOS shows a dropdown menu rather than pushing a sub-view. +// + +import SwiftUI + +struct PlatformMenuPicker: View { + @Binding var selection: Selection + @ViewBuilder var content: () -> Content + @ViewBuilder var label: () -> Label + + var body: some View { + #if os(tvOS) + LabeledContent { + Picker(selection: $selection, content: content) { EmptyView() } + .pickerStyle(.menu) + .labelsHidden() + } label: { + label() + } + #else + Picker(selection: $selection, content: content, label: label) + #endif + } +} + +extension PlatformMenuPicker where Label == Text { + init( + _ titleKey: String, + selection: Binding, + @ViewBuilder content: @escaping () -> Content + ) { + self._selection = selection + self.content = content + self.label = { Text(titleKey) } + } +} diff --git a/Yattee/Views/Settings/PlaybackSettingsView.swift b/Yattee/Views/Settings/PlaybackSettingsView.swift index 26ba4877..0f56b8e5 100644 --- a/Yattee/Views/Settings/PlaybackSettingsView.swift +++ b/Yattee/Views/Settings/PlaybackSettingsView.swift @@ -42,7 +42,7 @@ private struct QualitySection: View { var body: some View { Section { - Picker( + PlatformMenuPicker( String(localized: "settings.playback.quality.preferred"), selection: $settings.preferredQuality ) { @@ -241,7 +241,7 @@ private struct BehaviorSection: View { var body: some View { Section(String(localized: "settings.playback.behavior.header")) { - Picker( + PlatformMenuPicker( String(localized: "settings.playback.resumeAction"), selection: $settings.resumeAction ) { @@ -273,7 +273,7 @@ private struct QueueSection: View { ) #if os(tvOS) - Picker( + PlatformMenuPicker( String(localized: "settings.playback.queue.autoPlayCountdown"), selection: $settings.queueAutoPlayCountdown ) { diff --git a/Yattee/Views/Settings/PrivacySettingsView.swift b/Yattee/Views/Settings/PrivacySettingsView.swift index 55cbc6f2..207f69e8 100644 --- a/Yattee/Views/Settings/PrivacySettingsView.swift +++ b/Yattee/Views/Settings/PrivacySettingsView.swift @@ -54,7 +54,7 @@ struct PrivacySettingsView: View { isOn: Bindable(settingsManager).saveWatchHistory ) - Picker( + PlatformMenuPicker( String(localized: "settings.behavior.historyRetention"), selection: Binding( get: { settingsManager.historyRetentionDays }, @@ -104,7 +104,7 @@ struct PrivacySettingsView: View { isOn: Bindable(settingsManager).saveRecentPlaylists ) - Picker( + PlatformMenuPicker( String(localized: "settings.behavior.searchHistoryLimit"), selection: Binding( get: { settingsManager.searchHistoryLimit }, diff --git a/Yattee/Views/Settings/SidebarSettingsView.swift b/Yattee/Views/Settings/SidebarSettingsView.swift index f7e9d6ab..7ccbaaa1 100644 --- a/Yattee/Views/Settings/SidebarSettingsView.swift +++ b/Yattee/Views/Settings/SidebarSettingsView.swift @@ -153,7 +153,7 @@ struct SidebarSettingsView: View { private var startupSection: some View { Section { - Picker(String(localized: "settings.sidebar.startup.tab"), selection: startupTabBinding) { + PlatformMenuPicker(String(localized: "settings.sidebar.startup.tab"), selection: startupTabBinding) { ForEach(validStartupTabs) { item in Text(item.localizedTitle).tag(item) } @@ -288,7 +288,7 @@ struct SidebarSettingsView: View { } // Source sort order - Picker(String(localized: "settings.sidebar.sourceSort"), selection: sourceSortBinding) { + PlatformMenuPicker(String(localized: "settings.sidebar.sourceSort"), selection: sourceSortBinding) { ForEach(SidebarSourceSort.allCases) { sort in Text(sort.localizedTitle).tag(sort) } @@ -309,7 +309,7 @@ struct SidebarSettingsView: View { if sourcesLimitEnabledBinding.wrappedValue { #if os(tvOS) // tvOS uses Picker instead of Slider (Slider/Stepper unavailable) - Picker(String(localized: "settings.sidebar.maxSources"), selection: maxSourcesBinding) { + PlatformMenuPicker(String(localized: "settings.sidebar.maxSources"), selection: maxSourcesBinding) { ForEach([5, 10, 15, 20, 25, 30, 35, 40, 45, 50, 60, 70, 80, 90, 100], id: \.self) { value in Text("\(value)").tag(value) } @@ -360,7 +360,7 @@ struct SidebarSettingsView: View { } // Channel sort order - Picker(String(localized: "settings.sidebar.channelSort"), selection: channelSortBinding) { + PlatformMenuPicker(String(localized: "settings.sidebar.channelSort"), selection: channelSortBinding) { ForEach(SidebarChannelSort.allCases.filter { $0 != .custom }) { sort in Text(sort.localizedTitle).tag(sort) } @@ -381,7 +381,7 @@ struct SidebarSettingsView: View { if channelsLimitEnabledBinding.wrappedValue { #if os(tvOS) // tvOS uses Picker instead of Slider (Slider/Stepper unavailable) - Picker(String(localized: "settings.sidebar.maxChannels"), selection: maxChannelsBinding) { + PlatformMenuPicker(String(localized: "settings.sidebar.maxChannels"), selection: maxChannelsBinding) { ForEach([5, 10, 15, 20, 25, 30, 35, 40, 45, 50, 60, 70, 80, 90, 100], id: \.self) { value in Text("\(value)").tag(value) } @@ -432,7 +432,7 @@ struct SidebarSettingsView: View { } // Playlist sort order - Picker(String(localized: "settings.sidebar.playlistSort"), selection: playlistSortBinding) { + PlatformMenuPicker(String(localized: "settings.sidebar.playlistSort"), selection: playlistSortBinding) { ForEach(SidebarPlaylistSort.allCases) { sort in Text(sort.localizedTitle).tag(sort) } @@ -453,7 +453,7 @@ struct SidebarSettingsView: View { if playlistsLimitEnabledBinding.wrappedValue { #if os(tvOS) // tvOS uses Picker instead of Slider (Slider/Stepper unavailable) - Picker(String(localized: "settings.sidebar.maxPlaylists"), selection: maxPlaylistsBinding) { + PlatformMenuPicker(String(localized: "settings.sidebar.maxPlaylists"), selection: maxPlaylistsBinding) { ForEach([5, 10, 15, 20, 25, 30], id: \.self) { value in Text("\(value)").tag(value) } diff --git a/Yattee/Views/Settings/SubtitlesSettingsView.swift b/Yattee/Views/Settings/SubtitlesSettingsView.swift index 7cdad2f8..19c60d2b 100644 --- a/Yattee/Views/Settings/SubtitlesSettingsView.swift +++ b/Yattee/Views/Settings/SubtitlesSettingsView.swift @@ -37,7 +37,7 @@ struct SubtitlesSettingsView: View { private var fontSection: some View { Section { - Picker( + PlatformMenuPicker( String(localized: "settings.subtitles.font"), selection: $settings.font ) { @@ -49,7 +49,7 @@ struct SubtitlesSettingsView: View { #if os(tvOS) // tvOS uses Picker instead of Slider (Slider unavailable) - Picker(String(localized: "settings.subtitles.fontSize"), selection: $settings.fontSize) { + PlatformMenuPicker(String(localized: "settings.subtitles.fontSize"), selection: $settings.fontSize) { ForEach([20, 25, 30, 35, 40, 45, 50, 55, 60, 65, 70, 75, 80, 85, 90, 95, 100], id: \.self) { size in Text("settings.subtitles.fontSize \(size)").tag(size) }