mirror of
				https://github.com/yattee/yattee.git
				synced 2025-10-31 12:41:57 +00:00 
			
		
		
		
	Player controls settings
This commit is contained in:
		| @@ -128,7 +128,7 @@ extension Defaults.Keys { | |||||||
|     static let fullScreenPlayerControlsLayout = Key<PlayerControlsLayout>("fullScreenPlayerControlsLayout", default: fullScreenPlayerControlsLayoutDefault) |     static let fullScreenPlayerControlsLayout = Key<PlayerControlsLayout>("fullScreenPlayerControlsLayout", default: fullScreenPlayerControlsLayoutDefault) | ||||||
|     static let horizontalPlayerGestureEnabled = Key<Bool>("horizontalPlayerGestureEnabled", default: true) |     static let horizontalPlayerGestureEnabled = Key<Bool>("horizontalPlayerGestureEnabled", default: true) | ||||||
|     static let seekGestureSpeed = Key<Double>("seekGestureSpeed", default: 0.5) |     static let seekGestureSpeed = Key<Double>("seekGestureSpeed", default: 0.5) | ||||||
|     static let seekGestureSensitivity = Key<Double>("seekGestureSensitivity", default: 20.0) |     static let seekGestureSensitivity = Key<Double>("seekGestureSensitivity", default: 30.0) | ||||||
|     static let showKeywords = Key<Bool>("showKeywords", default: false) |     static let showKeywords = Key<Bool>("showKeywords", default: false) | ||||||
|     #if !os(tvOS) |     #if !os(tvOS) | ||||||
|         static let commentsPlacement = Key<CommentsPlacement>("commentsPlacement", default: .separate) |         static let commentsPlacement = Key<CommentsPlacement>("commentsPlacement", default: .separate) | ||||||
| @@ -201,9 +201,24 @@ extension Defaults.Keys { | |||||||
|     static let actionButtonNextEnabled = Key<Bool>("actionButtonNextEnabled", default: true) |     static let actionButtonNextEnabled = Key<Bool>("actionButtonNextEnabled", default: true) | ||||||
|     static let actionButtonHideEnabled = Key<Bool>("actionButtonHideEnabled", default: false) |     static let actionButtonHideEnabled = Key<Bool>("actionButtonHideEnabled", default: false) | ||||||
|     static let actionButtonCloseEnabled = Key<Bool>("actionButtonCloseEnabled", default: true) |     static let actionButtonCloseEnabled = Key<Bool>("actionButtonCloseEnabled", default: true) | ||||||
|  |  | ||||||
|     static let actionButtonNextQueueCountEnabled = Key<Bool>("actionButtonNextQueueCountEnabled", default: true) |     static let actionButtonNextQueueCountEnabled = Key<Bool>("actionButtonNextQueueCountEnabled", default: true) | ||||||
|  |  | ||||||
|  |     #if os(iOS) | ||||||
|  |         static let playerControlsLockOrientationEnabled = Key<Bool>("playerControlsLockOrientationEnabled", default: true) | ||||||
|  |     #endif | ||||||
|  |     #if os(tvOS) | ||||||
|  |         static let playerControlsSettingsEnabledDefault = true | ||||||
|  |     #else | ||||||
|  |         static let playerControlsSettingsEnabledDefault = false | ||||||
|  |     #endif | ||||||
|  |     static let playerControlsSettingsEnabled = Key<Bool>("playerControlsSettingsEnabled", default: playerControlsSettingsEnabledDefault) | ||||||
|  |     static let playerControlsCloseEnabled = Key<Bool>("playerControlsCloseEnabled", default: true) | ||||||
|  |     static let playerControlsRestartEnabled = Key<Bool>("playerControlsRestartEnabled", default: false) | ||||||
|  |     static let playerControlsAdvanceToNextEnabled = Key<Bool>("playerControlsAdvanceToNextEnabled", default: false) | ||||||
|  |     static let playerControlsPlaybackModeEnabled = Key<Bool>("playerControlsPlaybackModeEnabled", default: false) | ||||||
|  |     static let playerControlsNextEnabled = Key<Bool>("playerControlsNextEnabled", default: true) | ||||||
|  |     static let playerControlsMusicModeEnabled = Key<Bool>("playerControlsMusicModeEnabled", default: true) | ||||||
|  |  | ||||||
|     static let mpvCacheSecs = Key<String>("mpvCacheSecs", default: "120") |     static let mpvCacheSecs = Key<String>("mpvCacheSecs", default: "120") | ||||||
|     static let mpvCachePauseWait = Key<String>("mpvCachePauseWait", default: "3") |     static let mpvCachePauseWait = Key<String>("mpvCachePauseWait", default: "3") | ||||||
|     static let mpvEnableLogging = Key<Bool>("mpvEnableLogging", default: false) |     static let mpvEnableLogging = Key<Bool>("mpvEnableLogging", default: false) | ||||||
|   | |||||||
| @@ -32,6 +32,17 @@ struct PlayerControls: View { | |||||||
|     @Default(.buttonBackwardSeekDuration) private var buttonBackwardSeekDuration |     @Default(.buttonBackwardSeekDuration) private var buttonBackwardSeekDuration | ||||||
|     @Default(.buttonForwardSeekDuration) private var buttonForwardSeekDuration |     @Default(.buttonForwardSeekDuration) private var buttonForwardSeekDuration | ||||||
|  |  | ||||||
|  |     #if os(iOS) | ||||||
|  |         @Default(.playerControlsLockOrientationEnabled) private var playerControlsLockOrientationEnabled | ||||||
|  |     #endif | ||||||
|  |     @Default(.playerControlsSettingsEnabled) private var playerControlsSettingsEnabled | ||||||
|  |     @Default(.playerControlsCloseEnabled) private var playerControlsCloseEnabled | ||||||
|  |     @Default(.playerControlsRestartEnabled) private var playerControlsRestartEnabled | ||||||
|  |     @Default(.playerControlsAdvanceToNextEnabled) private var playerControlsAdvanceToNextEnabled | ||||||
|  |     @Default(.playerControlsPlaybackModeEnabled) private var playerControlsPlaybackModeEnabled | ||||||
|  |     @Default(.playerControlsNextEnabled) private var playerControlsNextEnabled | ||||||
|  |     @Default(.playerControlsMusicModeEnabled) private var playerControlsMusicModeEnabled | ||||||
|  |  | ||||||
|     private let controlsOverlayModel = ControlOverlaysModel.shared |     private let controlsOverlayModel = ControlOverlaysModel.shared | ||||||
|  |  | ||||||
|     var playerControlsLayout: PlayerControlsLayout { |     var playerControlsLayout: PlayerControlsLayout { | ||||||
| @@ -135,17 +146,30 @@ struct PlayerControls: View { | |||||||
|                                     seekBackwardButton |                                     seekBackwardButton | ||||||
|                                     seekForwardButton |                                     seekForwardButton | ||||||
|                                 #endif |                                 #endif | ||||||
|  |                                 if playerControlsAdvanceToNextEnabled { | ||||||
|                                     restartVideoButton |                                     restartVideoButton | ||||||
|  |                                 } | ||||||
|  |                                 if playerControlsAdvanceToNextEnabled { | ||||||
|                                     advanceToNextItemButton |                                     advanceToNextItemButton | ||||||
|  |                                 } | ||||||
|                                 Spacer() |                                 Spacer() | ||||||
|                                 #if os(tvOS) |                                 #if os(tvOS) | ||||||
|  |                                     if playerControlsSettingsEnabled { | ||||||
|                                         settingsButton |                                         settingsButton | ||||||
|  |                                     } | ||||||
|                                 #endif |                                 #endif | ||||||
|  |                                 if playerControlsPlaybackModeEnabled { | ||||||
|                                     playbackModeButton |                                     playbackModeButton | ||||||
|  |                                 } | ||||||
|  |                                 if playerControlsNextEnabled { | ||||||
|  |                                     watchNextButton | ||||||
|  |                                 } | ||||||
|                                 #if os(tvOS) |                                 #if os(tvOS) | ||||||
|                                     closeVideoButton |                                     closeVideoButton | ||||||
|                                 #else |                                 #else | ||||||
|  |                                     if playerControlsMusicModeEnabled { | ||||||
|                                         musicModeButton |                                         musicModeButton | ||||||
|  |                                     } | ||||||
|                                 #endif |                                 #endif | ||||||
|                             } |                             } | ||||||
|                             .zIndex(0) |                             .zIndex(0) | ||||||
| @@ -306,15 +330,22 @@ struct PlayerControls: View { | |||||||
|  |  | ||||||
|             pipButton |             pipButton | ||||||
|             #if os(iOS) |             #if os(iOS) | ||||||
|  |                 if playerControlsLockOrientationEnabled { | ||||||
|                     lockOrientationButton |                     lockOrientationButton | ||||||
|  |                 } | ||||||
|             #endif |             #endif | ||||||
|  |  | ||||||
|             Spacer() |             Spacer() | ||||||
|  |  | ||||||
|  |             if playerControlsSettingsEnabled { | ||||||
|                 settingsButton |                 settingsButton | ||||||
|  |             } | ||||||
|  |  | ||||||
|  |             if playerControlsCloseEnabled { | ||||||
|                 closeVideoButton |                 closeVideoButton | ||||||
|             } |             } | ||||||
|         } |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|     var fullscreenButton: some View { |     var fullscreenButton: some View { | ||||||
|         button( |         button( | ||||||
| @@ -390,6 +421,12 @@ struct PlayerControls: View { | |||||||
|         } |         } | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  |     var watchNextButton: some View { | ||||||
|  |         button("Watch Next", systemImage: Constants.nextSystemImage) { | ||||||
|  |             WatchNextViewModel.shared.userInteractedOpen(player.currentItem) | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|     var seekBackwardButton: some View { |     var seekBackwardButton: some View { | ||||||
|         var foregroundColor: Color? |         var foregroundColor: Color? | ||||||
|         var fontSize: Double? |         var fontSize: Double? | ||||||
|   | |||||||
| @@ -5,9 +5,6 @@ struct PlayerGestures: View { | |||||||
|     private var player = PlayerModel.shared |     private var player = PlayerModel.shared | ||||||
|     @ObservedObject private var model = PlayerControlsModel.shared |     @ObservedObject private var model = PlayerControlsModel.shared | ||||||
|  |  | ||||||
|     @Default(.gestureBackwardSeekDuration) private var gestureBackwardSeekDuration |  | ||||||
|     @Default(.gestureForwardSeekDuration) private var gestureForwardSeekDuration |  | ||||||
|  |  | ||||||
|     var body: some View { |     var body: some View { | ||||||
|         HStack(spacing: 0) { |         HStack(spacing: 0) { | ||||||
|             gestureRectangle |             gestureRectangle | ||||||
| @@ -15,7 +12,7 @@ struct PlayerGestures: View { | |||||||
|                     tapSensitivity: 0.2, |                     tapSensitivity: 0.2, | ||||||
|                     singleTapAction: { singleTapAction() }, |                     singleTapAction: { singleTapAction() }, | ||||||
|                     doubleTapAction: { |                     doubleTapAction: { | ||||||
|                         let interval = TimeInterval(gestureBackwardSeekDuration) ?? 10 |                         let interval = TimeInterval(Defaults[.gestureBackwardSeekDuration]) ?? 10 | ||||||
|                         player.backend.seek(relative: .secondsInDefaultTimescale(-interval), seekType: .userInteracted) |                         player.backend.seek(relative: .secondsInDefaultTimescale(-interval), seekType: .userInteracted) | ||||||
|                     }, |                     }, | ||||||
|                     anyTapAction: { |                     anyTapAction: { | ||||||
| @@ -37,7 +34,7 @@ struct PlayerGestures: View { | |||||||
|                     tapSensitivity: 0.2, |                     tapSensitivity: 0.2, | ||||||
|                     singleTapAction: { singleTapAction() }, |                     singleTapAction: { singleTapAction() }, | ||||||
|                     doubleTapAction: { |                     doubleTapAction: { | ||||||
|                         let interval = TimeInterval(gestureForwardSeekDuration) ?? 10 |                         let interval = TimeInterval(Defaults[.gestureForwardSeekDuration]) ?? 10 | ||||||
|                         player.backend.seek(relative: .secondsInDefaultTimescale(interval), seekType: .userInteracted) |                         player.backend.seek(relative: .secondsInDefaultTimescale(interval), seekType: .userInteracted) | ||||||
|                     } |                     } | ||||||
|                 ) |                 ) | ||||||
|   | |||||||
| @@ -84,9 +84,13 @@ struct VideoActions: View { | |||||||
|             Group { |             Group { | ||||||
|                 switch action { |                 switch action { | ||||||
|                 case .share: |                 case .share: | ||||||
|  |                     #if os(tvOS) | ||||||
|  |                         EmptyView() | ||||||
|  |                     #else | ||||||
|                         ShareButton(contentItem: .init(video: video)) { |                         ShareButton(contentItem: .init(video: video)) { | ||||||
|                             actionButton("Share", systemImage: "square.and.arrow.up") |                             actionButton("Share", systemImage: "square.and.arrow.up") | ||||||
|                         } |                         } | ||||||
|  |                     #endif | ||||||
|                 case .addToPlaylist: |                 case .addToPlaylist: | ||||||
|                     actionButton("Add", systemImage: "text.badge.plus") { |                     actionButton("Add", systemImage: "text.badge.plus") { | ||||||
|                         guard let video else { return } |                         guard let video else { return } | ||||||
|   | |||||||
| @@ -35,8 +35,10 @@ struct WatchNextView: View { | |||||||
|                         Spacer() |                         Spacer() | ||||||
|  |  | ||||||
|                         HStack { |                         HStack { | ||||||
|  |                             #if os(macOS) | ||||||
|                                 Text("Mode") |                                 Text("Mode") | ||||||
|                                     .foregroundColor(.secondary) |                                     .foregroundColor(.secondary) | ||||||
|  |                             #endif | ||||||
|  |  | ||||||
|                             playbackModeControl |                             playbackModeControl | ||||||
|  |  | ||||||
| @@ -267,6 +269,8 @@ struct WatchNextView: View { | |||||||
|                 player.playbackMode = player.playbackMode.next() |                 player.playbackMode = player.playbackMode.next() | ||||||
|             } label: { |             } label: { | ||||||
|                 Label(player.playbackMode.description, systemImage: player.playbackMode.systemImage) |                 Label(player.playbackMode.description, systemImage: player.playbackMode.systemImage) | ||||||
|  |                     .transaction { t in t.animation = nil } | ||||||
|  |                     .frame(minWidth: 350) | ||||||
|             } |             } | ||||||
|         #elseif os(macOS) |         #elseif os(macOS) | ||||||
|             playbackModePicker |             playbackModePicker | ||||||
|   | |||||||
| @@ -193,6 +193,8 @@ struct BrowsingSettings: View { | |||||||
|                 Text("Tap and hold channel thumbnail to open context menu with more actions") |                 Text("Tap and hold channel thumbnail to open context menu with more actions") | ||||||
|             #elseif os(macOS) |             #elseif os(macOS) | ||||||
|                 Text("Right click channel thumbnail to open context menu with more actions") |                 Text("Right click channel thumbnail to open context menu with more actions") | ||||||
|  |                     .foregroundColor(.secondary) | ||||||
|  |                     .padding(.bottom, 10) | ||||||
|             #endif |             #endif | ||||||
|         } |         } | ||||||
|     #endif |     #endif | ||||||
|   | |||||||
							
								
								
									
										262
									
								
								Shared/Settings/PlayerControlsSettings.swift
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										262
									
								
								Shared/Settings/PlayerControlsSettings.swift
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,262 @@ | |||||||
|  | import Defaults | ||||||
|  | import SwiftUI | ||||||
|  |  | ||||||
|  | struct PlayerControlsSettings: View { | ||||||
|  |     @Default(.systemControlsCommands) private var systemControlsCommands | ||||||
|  |     @Default(.playerControlsLayout) private var playerControlsLayout | ||||||
|  |     @Default(.fullScreenPlayerControlsLayout) private var fullScreenPlayerControlsLayout | ||||||
|  |     @Default(.horizontalPlayerGestureEnabled) private var horizontalPlayerGestureEnabled | ||||||
|  |     @Default(.seekGestureSpeed) private var seekGestureSpeed | ||||||
|  |     @Default(.seekGestureSensitivity) private var seekGestureSensitivity | ||||||
|  |     @Default(.buttonBackwardSeekDuration) private var buttonBackwardSeekDuration | ||||||
|  |     @Default(.buttonForwardSeekDuration) private var buttonForwardSeekDuration | ||||||
|  |     @Default(.gestureBackwardSeekDuration) private var gestureBackwardSeekDuration | ||||||
|  |     @Default(.gestureForwardSeekDuration) private var gestureForwardSeekDuration | ||||||
|  |     @Default(.systemControlsSeekDuration) private var systemControlsSeekDuration | ||||||
|  |     @Default(.actionButtonShareEnabled) private var actionButtonShareEnabled | ||||||
|  |     @Default(.actionButtonSubscribeEnabled) private var actionButtonSubscribeEnabled | ||||||
|  |     @Default(.actionButtonNextEnabled) private var actionButtonNextEnabled | ||||||
|  |     @Default(.actionButtonCloseEnabled) private var actionButtonCloseEnabled | ||||||
|  |     @Default(.actionButtonAddToPlaylistEnabled) private var actionButtonAddToPlaylistEnabled | ||||||
|  |     @Default(.actionButtonSettingsEnabled) private var actionButtonSettingsEnabled | ||||||
|  |     @Default(.actionButtonHideEnabled) private var actionButtonHideEnabled | ||||||
|  |     @Default(.actionButtonNextQueueCountEnabled) private var actionButtonNextQueueCountEnabled | ||||||
|  |  | ||||||
|  |     #if os(iOS) | ||||||
|  |         @Default(.playerControlsLockOrientationEnabled) private var playerControlsLockOrientationEnabled | ||||||
|  |     #endif | ||||||
|  |     @Default(.playerControlsSettingsEnabled) private var playerControlsSettingsEnabled | ||||||
|  |     @Default(.playerControlsCloseEnabled) private var playerControlsCloseEnabled | ||||||
|  |     @Default(.playerControlsRestartEnabled) private var playerControlsRestartEnabled | ||||||
|  |     @Default(.playerControlsAdvanceToNextEnabled) private var playerControlsAdvanceToNextEnabled | ||||||
|  |     @Default(.playerControlsPlaybackModeEnabled) private var playerControlsPlaybackModeEnabled | ||||||
|  |     @Default(.playerControlsNextEnabled) private var playerControlsNextEnabled | ||||||
|  |     @Default(.playerControlsMusicModeEnabled) private var playerControlsMusicModeEnabled | ||||||
|  |  | ||||||
|  |     private var player = PlayerModel.shared | ||||||
|  |  | ||||||
|  |     var body: some View { | ||||||
|  |         Group { | ||||||
|  |             #if os(macOS) | ||||||
|  |                 sections | ||||||
|  |  | ||||||
|  |                 Spacer() | ||||||
|  |             #else | ||||||
|  |                 List { | ||||||
|  |                     sections | ||||||
|  |                 } | ||||||
|  |             #endif | ||||||
|  |         } | ||||||
|  |         #if os(tvOS) | ||||||
|  |         .frame(maxWidth: 1000) | ||||||
|  |         #elseif os(iOS) | ||||||
|  |         .listStyle(.insetGrouped) | ||||||
|  |         #endif | ||||||
|  |         .navigationTitle("Player Controls") | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     @ViewBuilder var sections: some View { | ||||||
|  |         #if !os(tvOS) | ||||||
|  |             Section(header: SettingsHeader(text: "Controls".localized()), footer: controlsLayoutFooter) { | ||||||
|  |                 horizontalPlayerGestureEnabledToggle | ||||||
|  |                 SettingsHeader(text: "Seek gesture sensitivity".localized(), secondary: true) | ||||||
|  |                 seekGestureSensitivityPicker | ||||||
|  |                 SettingsHeader(text: "Seek gesture speed".localized(), secondary: true) | ||||||
|  |                 seekGestureSpeedPicker | ||||||
|  |                 SettingsHeader(text: "Regular size".localized(), secondary: true) | ||||||
|  |                 playerControlsLayoutPicker | ||||||
|  |                 SettingsHeader(text: "Fullscreen size".localized(), secondary: true) | ||||||
|  |                 fullScreenPlayerControlsLayoutPicker | ||||||
|  |             } | ||||||
|  |         #endif | ||||||
|  |  | ||||||
|  |         Section(header: SettingsHeader(text: "Seeking"), footer: seekingGestureSection) { | ||||||
|  |             systemControlsCommandsPicker | ||||||
|  |  | ||||||
|  |             seekingSection | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         #if os(macOS) | ||||||
|  |             HStack(alignment: .top) { | ||||||
|  |                 VStack(alignment: .leading) { | ||||||
|  |                     controlsButtonsSection | ||||||
|  |                 } | ||||||
|  |                 .frame(maxWidth: .infinity, alignment: .leading) | ||||||
|  |  | ||||||
|  |                 VStack(alignment: .leading) { | ||||||
|  |                     actionsButtonsSection | ||||||
|  |                 } | ||||||
|  |                 .frame(maxWidth: .infinity, alignment: .leading) | ||||||
|  |             } | ||||||
|  |             .padding(.top, 10) | ||||||
|  |         #else | ||||||
|  |  | ||||||
|  |             controlsButtonsSection | ||||||
|  |  | ||||||
|  |             #if !os(tvOS) | ||||||
|  |                 actionsButtonsSection | ||||||
|  |             #endif | ||||||
|  |         #endif | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     var controlsButtonsSection: some View { | ||||||
|  |         Section(header: SettingsHeader(text: "Controls Buttons")) { | ||||||
|  |             controlButtonToggles | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     @ViewBuilder var actionsButtonsSection: some View { | ||||||
|  |         Section(header: SettingsHeader(text: "Actions Buttons")) { | ||||||
|  |             actionButtonToggles | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         actionButtonNextQueueCountEnabledToggle | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     private var systemControlsCommandsPicker: some View { | ||||||
|  |         func labelText(_ label: String) -> String { | ||||||
|  |             #if os(macOS) | ||||||
|  |                 String(format: "System controls show buttons for %@".localized(), label) | ||||||
|  |             #else | ||||||
|  |                 label | ||||||
|  |             #endif | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         return Picker("System controls buttons", selection: $systemControlsCommands) { | ||||||
|  |             Text(labelText("Seek".localized())).tag(SystemControlsCommands.seek) | ||||||
|  |             Text(labelText("Restart/Play next".localized())).tag(SystemControlsCommands.restartAndAdvanceToNext) | ||||||
|  |         } | ||||||
|  |         .onChange(of: systemControlsCommands) { _ in | ||||||
|  |             player.updateRemoteCommandCenter() | ||||||
|  |         } | ||||||
|  |         .modifier(SettingsPickerModifier()) | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     @ViewBuilder private var controlsLayoutFooter: some View { | ||||||
|  |         #if os(iOS) | ||||||
|  |             Text("Large layout is not suitable for all devices and using it may cause controls not to fit on the screen.") | ||||||
|  |         #endif | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     private var horizontalPlayerGestureEnabledToggle: some View { | ||||||
|  |         Toggle("Seek with horizontal swipe on video", isOn: $horizontalPlayerGestureEnabled) | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     private var seekGestureSensitivityPicker: some View { | ||||||
|  |         Picker("Seek gesture sensitivity", selection: $seekGestureSensitivity) { | ||||||
|  |             Text("Highest").tag(1.0) | ||||||
|  |             Text("High").tag(10.0) | ||||||
|  |             Text("Normal").tag(30.0) | ||||||
|  |             Text("Low").tag(50.0) | ||||||
|  |             Text("Lowest").tag(100.0) | ||||||
|  |         } | ||||||
|  |         .disabled(!horizontalPlayerGestureEnabled) | ||||||
|  |         .modifier(SettingsPickerModifier()) | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     private var seekGestureSpeedPicker: some View { | ||||||
|  |         Picker("Seek gesture speed", selection: $seekGestureSpeed) { | ||||||
|  |             ForEach([1, 0.75, 0.66, 0.5, 0.33, 0.25, 0.1], id: \.self) { value in | ||||||
|  |                 Text(String(format: "%.0f%%", value * 100)).tag(value) | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |         .disabled(!horizontalPlayerGestureEnabled) | ||||||
|  |         .modifier(SettingsPickerModifier()) | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     private var playerControlsLayoutPicker: some View { | ||||||
|  |         Picker("Regular Size", selection: $playerControlsLayout) { | ||||||
|  |             ForEach(PlayerControlsLayout.allCases.filter(\.available), id: \.self) { layout in | ||||||
|  |                 Text(layout.description).tag(layout.rawValue) | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |         .modifier(SettingsPickerModifier()) | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     private var fullScreenPlayerControlsLayoutPicker: some View { | ||||||
|  |         Picker("Fullscreen size", selection: $fullScreenPlayerControlsLayout) { | ||||||
|  |             ForEach(PlayerControlsLayout.allCases.filter(\.available), id: \.self) { layout in | ||||||
|  |                 Text(layout.description).tag(layout.rawValue) | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |         .modifier(SettingsPickerModifier()) | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     @ViewBuilder private var seekingSection: some View { | ||||||
|  |         seekingDurationSetting("System controls", $systemControlsSeekDuration) | ||||||
|  |             .foregroundColor(systemControlsCommands == .restartAndAdvanceToNext ? .secondary : .primary) | ||||||
|  |             .disabled(systemControlsCommands == .restartAndAdvanceToNext) | ||||||
|  |         seekingDurationSetting("Controls button: backwards", $buttonBackwardSeekDuration) | ||||||
|  |         seekingDurationSetting("Controls button: forwards", $buttonForwardSeekDuration) | ||||||
|  |         seekingDurationSetting("Gesture: backwards", $gestureBackwardSeekDuration) | ||||||
|  |         seekingDurationSetting("Gesture: fowards", $gestureForwardSeekDuration) | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     private var seekingGestureSection: some View { | ||||||
|  |         #if os(iOS) | ||||||
|  |             Text("Gesture settings control skipping interval for double tap gesture on left/right side of the player. Changing system controls settings requires restart.") | ||||||
|  |         #elseif os(macOS) | ||||||
|  |             Text("Gesture settings control skipping interval for double click on left/right side of the player. Changing system controls settings requires restart.") | ||||||
|  |                 .foregroundColor(.secondary) | ||||||
|  |         #else | ||||||
|  |             Text("Gesture settings control skipping interval for remote arrow buttons (for 2nd generation Siri Remote or newer). Changing system controls settings requires restart.") | ||||||
|  |         #endif | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     private func seekingDurationSetting(_ name: String, _ value: Binding<String>) -> some View { | ||||||
|  |         HStack { | ||||||
|  |             Text(name) | ||||||
|  |                 .frame(minWidth: 140, alignment: .leading) | ||||||
|  |             Spacer() | ||||||
|  |             TextField("Duration", text: value) | ||||||
|  |  | ||||||
|  |                 .frame(maxWidth: 100, alignment: .trailing) | ||||||
|  |                 .multilineTextAlignment(.trailing) | ||||||
|  |  | ||||||
|  |                 .labelsHidden() | ||||||
|  |             #if !os(macOS) | ||||||
|  |                 .keyboardType(.numberPad) | ||||||
|  |             #endif | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     @ViewBuilder private var actionButtonToggles: some View { | ||||||
|  |         Toggle("Share", isOn: $actionButtonShareEnabled) | ||||||
|  |         Toggle("Add to Playlist", isOn: $actionButtonAddToPlaylistEnabled) | ||||||
|  |         Toggle("Subscribe/Unsubscribe", isOn: $actionButtonSubscribeEnabled) | ||||||
|  |         Toggle("Settings", isOn: $actionButtonSettingsEnabled) | ||||||
|  |         Toggle("Watch Next", isOn: $actionButtonNextEnabled) | ||||||
|  |         Toggle("Hide player", isOn: $actionButtonHideEnabled) | ||||||
|  |         Toggle("Close video", isOn: $actionButtonCloseEnabled) | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     @ViewBuilder private var controlButtonToggles: some View { | ||||||
|  |         #if os(iOS) | ||||||
|  |             Toggle("Lock orientation", isOn: $playerControlsLockOrientationEnabled) | ||||||
|  |         #endif | ||||||
|  |         Toggle("Settings", isOn: $playerControlsSettingsEnabled) | ||||||
|  |         #if !os(tvOS) | ||||||
|  |             Toggle("Close", isOn: $playerControlsCloseEnabled) | ||||||
|  |         #endif | ||||||
|  |         Toggle("Restart", isOn: $playerControlsRestartEnabled) | ||||||
|  |         Toggle("Play next item", isOn: $playerControlsAdvanceToNextEnabled) | ||||||
|  |         Toggle("Watch Next", isOn: $playerControlsNextEnabled) | ||||||
|  |         Toggle("Playback mode", isOn: $playerControlsPlaybackModeEnabled) | ||||||
|  |         #if !os(tvOS) | ||||||
|  |             Toggle("Music mode", isOn: $playerControlsMusicModeEnabled) | ||||||
|  |         #endif | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     var actionButtonNextQueueCountEnabledToggle: some View { | ||||||
|  |         Toggle("Count of items in queue in Watch Next button", isOn: $actionButtonNextQueueCountEnabled) | ||||||
|  |     } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | struct PlayerControlsSettings_Previews: PreviewProvider { | ||||||
|  |     static var previews: some View { | ||||||
|  |         VStack(alignment: .leading) { | ||||||
|  |             PlayerControlsSettings() | ||||||
|  |         } | ||||||
|  |         .frame(minHeight: 800) | ||||||
|  |     } | ||||||
|  | } | ||||||
| @@ -6,11 +6,7 @@ struct PlayerSettings: View { | |||||||
|     @Default(.playerInstanceID) private var playerInstanceID |     @Default(.playerInstanceID) private var playerInstanceID | ||||||
|  |  | ||||||
|     @Default(.playerSidebar) private var playerSidebar |     @Default(.playerSidebar) private var playerSidebar | ||||||
|     @Default(.playerControlsLayout) private var playerControlsLayout |  | ||||||
|     @Default(.fullScreenPlayerControlsLayout) private var fullScreenPlayerControlsLayout |  | ||||||
|     @Default(.horizontalPlayerGestureEnabled) private var horizontalPlayerGestureEnabled |  | ||||||
|     @Default(.seekGestureSpeed) private var seekGestureSpeed |  | ||||||
|     @Default(.seekGestureSensitivity) private var seekGestureSensitivity |  | ||||||
|     @Default(.showKeywords) private var showKeywords |     @Default(.showKeywords) private var showKeywords | ||||||
|     @Default(.pauseOnHidingPlayer) private var pauseOnHidingPlayer |     @Default(.pauseOnHidingPlayer) private var pauseOnHidingPlayer | ||||||
|     #if os(iOS) |     #if os(iOS) | ||||||
| @@ -27,29 +23,12 @@ struct PlayerSettings: View { | |||||||
|     #endif |     #endif | ||||||
|  |  | ||||||
|     @Default(.enableReturnYouTubeDislike) private var enableReturnYouTubeDislike |     @Default(.enableReturnYouTubeDislike) private var enableReturnYouTubeDislike | ||||||
|     @Default(.systemControlsCommands) private var systemControlsCommands |  | ||||||
|  |  | ||||||
|     @Default(.openWatchNextOnClose) private var openWatchNextOnClose |     @Default(.openWatchNextOnClose) private var openWatchNextOnClose | ||||||
|     @Default(.openWatchNextOnFinishedWatching) private var openWatchNextOnFinishedWatching |     @Default(.openWatchNextOnFinishedWatching) private var openWatchNextOnFinishedWatching | ||||||
|     @Default(.openWatchNextOnFinishedWatchingDelay) private var openWatchNextOnFinishedWatchingDelay |     @Default(.openWatchNextOnFinishedWatchingDelay) private var openWatchNextOnFinishedWatchingDelay | ||||||
|  |  | ||||||
|     @Default(.buttonBackwardSeekDuration) private var buttonBackwardSeekDuration |  | ||||||
|     @Default(.buttonForwardSeekDuration) private var buttonForwardSeekDuration |  | ||||||
|     @Default(.gestureBackwardSeekDuration) private var gestureBackwardSeekDuration |  | ||||||
|     @Default(.gestureForwardSeekDuration) private var gestureForwardSeekDuration |  | ||||||
|     @Default(.systemControlsSeekDuration) private var systemControlsSeekDuration |  | ||||||
|  |  | ||||||
|     @Default(.actionButtonShareEnabled) private var actionButtonShareEnabled |  | ||||||
|     @Default(.actionButtonSubscribeEnabled) private var actionButtonSubscribeEnabled |  | ||||||
|     @Default(.actionButtonNextEnabled) private var actionButtonNextEnabled |  | ||||||
|     @Default(.actionButtonCloseEnabled) private var actionButtonCloseEnabled |  | ||||||
|     @Default(.actionButtonAddToPlaylistEnabled) private var actionButtonAddToPlaylistEnabled |  | ||||||
|     @Default(.actionButtonSettingsEnabled) private var actionButtonSettingsEnabled |  | ||||||
|     @Default(.actionButtonHideEnabled) private var actionButtonHideEnabled |  | ||||||
|     @Default(.actionButtonNextQueueCountEnabled) private var actionButtonNextQueueCountEnabled |  | ||||||
|  |  | ||||||
|     @ObservedObject private var accounts = AccountsModel.shared |     @ObservedObject private var accounts = AccountsModel.shared | ||||||
|     private var player = PlayerModel.shared |  | ||||||
|  |  | ||||||
|     #if os(iOS) |     #if os(iOS) | ||||||
|         private var idiom: UIUserInterfaceIdiom { |         private var idiom: UIUserInterfaceIdiom { | ||||||
| @@ -87,40 +66,14 @@ struct PlayerSettings: View { | |||||||
|                 #if !os(macOS) |                 #if !os(macOS) | ||||||
|                     pauseOnEnteringBackgroundToogle |                     pauseOnEnteringBackgroundToogle | ||||||
|                 #endif |                 #endif | ||||||
|                 systemControlsCommandsPicker |  | ||||||
|             } |             } | ||||||
|  |  | ||||||
|             Section(header: SettingsHeader(text: "Seeking"), footer: seekingGestureSection) { |  | ||||||
|                 seekingSection |  | ||||||
|             } |  | ||||||
|  |  | ||||||
|             #if !os(tvOS) |  | ||||||
|                 Section(header: SettingsHeader(text: "Actions Buttons")) { |  | ||||||
|                     actionButtonToggles |  | ||||||
|                 } |  | ||||||
|                 actionButtonNextQueueCountEnabledToggle |  | ||||||
|             #endif |  | ||||||
|  |  | ||||||
|             Section(header: SettingsHeader(text: "Watch Next")) { |             Section(header: SettingsHeader(text: "Watch Next")) { | ||||||
|                 openWatchNextOnFinishedWatchingToggle |                 openWatchNextOnFinishedWatchingToggle | ||||||
|                 openWatchNextOnFinishedWatchingDelayTextField |                 openWatchNextOnFinishedWatchingDelayTextField | ||||||
|                 openWatchNextOnCloseToggle |                 openWatchNextOnCloseToggle | ||||||
|             } |             } | ||||||
|  |  | ||||||
|             #if !os(tvOS) |  | ||||||
|                 Section(header: SettingsHeader(text: "Controls".localized()), footer: controlsLayoutFooter) { |  | ||||||
|                     horizontalPlayerGestureEnabledToggle |  | ||||||
|                     SettingsHeader(text: "Seek gesture sensitivity".localized(), secondary: true) |  | ||||||
|                     seekGestureSensitivityPicker |  | ||||||
|                     SettingsHeader(text: "Seek gesture speed".localized(), secondary: true) |  | ||||||
|                     seekGestureSpeedPicker |  | ||||||
|                     SettingsHeader(text: "Regular size".localized(), secondary: true) |  | ||||||
|                     playerControlsLayoutPicker |  | ||||||
|                     SettingsHeader(text: "Fullscreen size".localized(), secondary: true) |  | ||||||
|                     fullScreenPlayerControlsLayoutPicker |  | ||||||
|                 } |  | ||||||
|             #endif |  | ||||||
|  |  | ||||||
|             let interface = Section(header: SettingsHeader(text: "Interface".localized())) { |             let interface = Section(header: SettingsHeader(text: "Interface".localized())) { | ||||||
|                 #if os(iOS) |                 #if os(iOS) | ||||||
|                     if idiom == .pad { |                     if idiom == .pad { | ||||||
| @@ -190,25 +143,6 @@ struct PlayerSettings: View { | |||||||
|         .modifier(SettingsPickerModifier()) |         .modifier(SettingsPickerModifier()) | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     private var systemControlsCommandsPicker: some View { |  | ||||||
|         func labelText(_ label: String) -> String { |  | ||||||
|             #if os(macOS) |  | ||||||
|                 String(format: "System controls show buttons for %@".localized(), label) |  | ||||||
|             #else |  | ||||||
|                 label |  | ||||||
|             #endif |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         return Picker("System controls buttons", selection: $systemControlsCommands) { |  | ||||||
|             Text(labelText("10 seconds forwards/backwards".localized())).tag(SystemControlsCommands.seek) |  | ||||||
|             Text(labelText("Restart/Play next".localized())).tag(SystemControlsCommands.restartAndAdvanceToNext) |  | ||||||
|         } |  | ||||||
|         .onChange(of: systemControlsCommands) { _ in |  | ||||||
|             player.updateRemoteCommandCenter() |  | ||||||
|         } |  | ||||||
|         .modifier(SettingsPickerModifier()) |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     private var openWatchNextOnCloseToggle: some View { |     private var openWatchNextOnCloseToggle: some View { | ||||||
|         Toggle("Open after manual close of video", isOn: $openWatchNextOnClose) |         Toggle("Open after manual close of video", isOn: $openWatchNextOnClose) | ||||||
|     } |     } | ||||||
| @@ -236,61 +170,6 @@ struct PlayerSettings: View { | |||||||
|         .multilineTextAlignment(.trailing) |         .multilineTextAlignment(.trailing) | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     @ViewBuilder private var seekingSection: some View { |  | ||||||
|         seekingDurationSetting("System controls", $systemControlsSeekDuration) |  | ||||||
|             .foregroundColor(systemControlsCommands == .restartAndAdvanceToNext ? .secondary : .primary) |  | ||||||
|             .disabled(systemControlsCommands == .restartAndAdvanceToNext) |  | ||||||
|         seekingDurationSetting("Controls button: backwards", $buttonBackwardSeekDuration) |  | ||||||
|         seekingDurationSetting("Controls button: forwards", $buttonForwardSeekDuration) |  | ||||||
|         seekingDurationSetting("Gesture: backwards", $gestureBackwardSeekDuration) |  | ||||||
|         seekingDurationSetting("Gesture: fowards", $gestureForwardSeekDuration) |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     private var seekingGestureSection: some View { |  | ||||||
|         #if os(iOS) |  | ||||||
|             Text("Gesture settings control skipping interval for double tap gesture on left/right side of the player. Changing system controls settings requires restart.") |  | ||||||
|         #elseif os(macOS) |  | ||||||
|             Text("Gesture settings control skipping interval for double click on left/right side of the player. Changing system controls settings requires restart.") |  | ||||||
|         #else |  | ||||||
|             Text("Gesture settings control skipping interval for remote arrow buttons (for 2nd generation Siri Remote or newer). Changing system controls settings requires restart.") |  | ||||||
|         #endif |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     private func seekingDurationSetting(_ name: String, _ value: Binding<String>) -> some View { |  | ||||||
|         HStack { |  | ||||||
|             Text(name) |  | ||||||
|                 .frame(minWidth: 140, alignment: .leading) |  | ||||||
|             Spacer() |  | ||||||
|             TextField("Duration", text: value) |  | ||||||
|  |  | ||||||
|                 .frame(maxWidth: 50, alignment: .trailing) |  | ||||||
|                 .multilineTextAlignment(.trailing) |  | ||||||
|  |  | ||||||
|                 .labelsHidden() |  | ||||||
|             #if !os(macOS) |  | ||||||
|                 .keyboardType(.numberPad) |  | ||||||
|             #endif |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     @ViewBuilder private var actionButtonToggles: some View { |  | ||||||
|         actionButtonToggle("Share", $actionButtonShareEnabled) |  | ||||||
|         actionButtonToggle("Add to Playlist", $actionButtonAddToPlaylistEnabled) |  | ||||||
|         actionButtonToggle("Subscribe/Unsubscribe", $actionButtonSubscribeEnabled) |  | ||||||
|         actionButtonToggle("Settings", $actionButtonSettingsEnabled) |  | ||||||
|         actionButtonToggle("Watch Next", $actionButtonNextEnabled) |  | ||||||
|         actionButtonToggle("Hide player", $actionButtonHideEnabled) |  | ||||||
|         actionButtonToggle("Close video", $actionButtonCloseEnabled) |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     private func actionButtonToggle(_ name: String, _ value: Binding<Bool>) -> some View { |  | ||||||
|         Toggle(name, isOn: value) |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     var actionButtonNextQueueCountEnabledToggle: some View { |  | ||||||
|         Toggle("Show queue items count in Watch Next button label", isOn: $actionButtonNextQueueCountEnabled) |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     private var sidebarPicker: some View { |     private var sidebarPicker: some View { | ||||||
|         Picker("Sidebar", selection: $playerSidebar) { |         Picker("Sidebar", selection: $playerSidebar) { | ||||||
|             #if os(macOS) |             #if os(macOS) | ||||||
| @@ -306,56 +185,6 @@ struct PlayerSettings: View { | |||||||
|         .modifier(SettingsPickerModifier()) |         .modifier(SettingsPickerModifier()) | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     private var horizontalPlayerGestureEnabledToggle: some View { |  | ||||||
|         Toggle("Seek with horizontal swipe on video", isOn: $horizontalPlayerGestureEnabled) |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     private var seekGestureSpeedPicker: some View { |  | ||||||
|         Picker("Seek gesture speed", selection: $seekGestureSpeed) { |  | ||||||
|             ForEach([1, 0.75, 0.66, 0.5, 0.33, 0.25, 0.1], id: \.self) { value in |  | ||||||
|                 Text(String(format: "%.0f%%", value * 100)).tag(value) |  | ||||||
|             } |  | ||||||
|         } |  | ||||||
|         .disabled(!horizontalPlayerGestureEnabled) |  | ||||||
|         .modifier(SettingsPickerModifier()) |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     private var seekGestureSensitivityPicker: some View { |  | ||||||
|         Picker("Seek gesture sensitivity", selection: $seekGestureSensitivity) { |  | ||||||
|             Text("Highest").tag(1.0) |  | ||||||
|             Text("High").tag(10.0) |  | ||||||
|             Text("Normal").tag(30.0) |  | ||||||
|             Text("Low").tag(50.0) |  | ||||||
|             Text("Lowest").tag(100.0) |  | ||||||
|         } |  | ||||||
|         .disabled(!horizontalPlayerGestureEnabled) |  | ||||||
|         .modifier(SettingsPickerModifier()) |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     @ViewBuilder private var controlsLayoutFooter: some View { |  | ||||||
|         #if os(iOS) |  | ||||||
|             Text("Large layout is not suitable for all devices and using it may cause controls not to fit on the screen.") |  | ||||||
|         #endif |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     private var playerControlsLayoutPicker: some View { |  | ||||||
|         Picker("Regular Size", selection: $playerControlsLayout) { |  | ||||||
|             ForEach(PlayerControlsLayout.allCases.filter(\.available), id: \.self) { layout in |  | ||||||
|                 Text(layout.description).tag(layout.rawValue) |  | ||||||
|             } |  | ||||||
|         } |  | ||||||
|         .modifier(SettingsPickerModifier()) |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     private var fullScreenPlayerControlsLayoutPicker: some View { |  | ||||||
|         Picker("Fullscreen size", selection: $fullScreenPlayerControlsLayout) { |  | ||||||
|             ForEach(PlayerControlsLayout.allCases.filter(\.available), id: \.self) { layout in |  | ||||||
|                 Text(layout.description).tag(layout.rawValue) |  | ||||||
|             } |  | ||||||
|         } |  | ||||||
|         .modifier(SettingsPickerModifier()) |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     private var keywordsToggle: some View { |     private var keywordsToggle: some View { | ||||||
|         Toggle("Show keywords", isOn: $showKeywords) |         Toggle("Show keywords", isOn: $showKeywords) | ||||||
|     } |     } | ||||||
|   | |||||||
| @@ -7,7 +7,7 @@ struct SettingsView: View { | |||||||
|  |  | ||||||
|     #if os(macOS) |     #if os(macOS) | ||||||
|         private enum Tabs: Hashable { |         private enum Tabs: Hashable { | ||||||
|             case browsing, player, quality, history, sponsorBlock, locations, advanced, help |             case browsing, player, controls, quality, history, sponsorBlock, locations, advanced, help | ||||||
|         } |         } | ||||||
|  |  | ||||||
|         @State private var selection: Tabs = .browsing |         @State private var selection: Tabs = .browsing | ||||||
| @@ -48,6 +48,14 @@ struct SettingsView: View { | |||||||
|                 } |                 } | ||||||
|                 .tag(Tabs.player) |                 .tag(Tabs.player) | ||||||
|  |  | ||||||
|  |                 Form { | ||||||
|  |                     PlayerControlsSettings() | ||||||
|  |                 } | ||||||
|  |                 .tabItem { | ||||||
|  |                     Label("Controls", systemImage: "hand.tap") | ||||||
|  |                 } | ||||||
|  |                 .tag(Tabs.controls) | ||||||
|  |  | ||||||
|                 Form { |                 Form { | ||||||
|                     QualitySettings() |                     QualitySettings() | ||||||
|                 } |                 } | ||||||
| @@ -98,7 +106,7 @@ struct SettingsView: View { | |||||||
|                 .tag(Tabs.help) |                 .tag(Tabs.help) | ||||||
|             } |             } | ||||||
|             .padding(20) |             .padding(20) | ||||||
|             .frame(width: 600, height: windowHeight) |             .frame(width: 650, height: windowHeight) | ||||||
|         #else |         #else | ||||||
|             NavigationView { |             NavigationView { | ||||||
|                 settingsList |                 settingsList | ||||||
| @@ -138,6 +146,12 @@ struct SettingsView: View { | |||||||
|                         Label("Player", systemImage: "play.rectangle") |                         Label("Player", systemImage: "play.rectangle") | ||||||
|                     } |                     } | ||||||
|  |  | ||||||
|  |                     NavigationLink { | ||||||
|  |                         PlayerControlsSettings() | ||||||
|  |                     } label: { | ||||||
|  |                         Label("Controls", systemImage: "hand.tap") | ||||||
|  |                     } | ||||||
|  |  | ||||||
|                     NavigationLink { |                     NavigationLink { | ||||||
|                         QualitySettings() |                         QualitySettings() | ||||||
|                     } label: { |                     } label: { | ||||||
| @@ -225,9 +239,11 @@ struct SettingsView: View { | |||||||
|         private var windowHeight: Double { |         private var windowHeight: Double { | ||||||
|             switch selection { |             switch selection { | ||||||
|             case .browsing: |             case .browsing: | ||||||
|                 return 700 |                 return 820 | ||||||
|             case .player: |             case .player: | ||||||
|                 return 730 |                 return 450 | ||||||
|  |             case .controls: | ||||||
|  |                 return 800 | ||||||
|             case .quality: |             case .quality: | ||||||
|                 return 420 |                 return 420 | ||||||
|             case .history: |             case .history: | ||||||
|   | |||||||
| @@ -873,6 +873,9 @@ | |||||||
| 		37D836BC294927E700005E5E /* ChannelsCacheModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37D836BB294927E700005E5E /* ChannelsCacheModel.swift */; }; | 		37D836BC294927E700005E5E /* ChannelsCacheModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37D836BB294927E700005E5E /* ChannelsCacheModel.swift */; }; | ||||||
| 		37D836BD294927E700005E5E /* ChannelsCacheModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37D836BB294927E700005E5E /* ChannelsCacheModel.swift */; }; | 		37D836BD294927E700005E5E /* ChannelsCacheModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37D836BB294927E700005E5E /* ChannelsCacheModel.swift */; }; | ||||||
| 		37D836BE294927E700005E5E /* ChannelsCacheModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37D836BB294927E700005E5E /* ChannelsCacheModel.swift */; }; | 		37D836BE294927E700005E5E /* ChannelsCacheModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37D836BB294927E700005E5E /* ChannelsCacheModel.swift */; }; | ||||||
|  | 		37D9BA0629507F69002586BD /* PlayerControlsSettings.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37D9BA0529507F69002586BD /* PlayerControlsSettings.swift */; }; | ||||||
|  | 		37D9BA0729507F69002586BD /* PlayerControlsSettings.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37D9BA0529507F69002586BD /* PlayerControlsSettings.swift */; }; | ||||||
|  | 		37D9BA0829507F69002586BD /* PlayerControlsSettings.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37D9BA0529507F69002586BD /* PlayerControlsSettings.swift */; }; | ||||||
| 		37DA0F20291DD6B8009B38CF /* Logging in Frameworks */ = {isa = PBXBuildFile; productRef = 37DA0F1F291DD6B8009B38CF /* Logging */; }; | 		37DA0F20291DD6B8009B38CF /* Logging in Frameworks */ = {isa = PBXBuildFile; productRef = 37DA0F1F291DD6B8009B38CF /* Logging */; }; | ||||||
| 		37DD87C7271C9CFE0027CBF9 /* PlayerStreams.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37DD87C6271C9CFE0027CBF9 /* PlayerStreams.swift */; }; | 		37DD87C7271C9CFE0027CBF9 /* PlayerStreams.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37DD87C6271C9CFE0027CBF9 /* PlayerStreams.swift */; }; | ||||||
| 		37DD87C8271C9CFE0027CBF9 /* PlayerStreams.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37DD87C6271C9CFE0027CBF9 /* PlayerStreams.swift */; }; | 		37DD87C8271C9CFE0027CBF9 /* PlayerStreams.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37DD87C6271C9CFE0027CBF9 /* PlayerStreams.swift */; }; | ||||||
| @@ -1442,6 +1445,7 @@ | |||||||
| 		37D6025C28C17719009E8D98 /* ControlsOverlayButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ControlsOverlayButton.swift; sourceTree = "<group>"; }; | 		37D6025C28C17719009E8D98 /* ControlsOverlayButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ControlsOverlayButton.swift; sourceTree = "<group>"; }; | ||||||
| 		37D836BB294927E700005E5E /* ChannelsCacheModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChannelsCacheModel.swift; sourceTree = "<group>"; }; | 		37D836BB294927E700005E5E /* ChannelsCacheModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChannelsCacheModel.swift; sourceTree = "<group>"; }; | ||||||
| 		37D9169A27388A81002B1BAA /* README.md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = README.md; sourceTree = "<group>"; }; | 		37D9169A27388A81002B1BAA /* README.md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = README.md; sourceTree = "<group>"; }; | ||||||
|  | 		37D9BA0529507F69002586BD /* PlayerControlsSettings.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PlayerControlsSettings.swift; sourceTree = "<group>"; }; | ||||||
| 		37DD87C6271C9CFE0027CBF9 /* PlayerStreams.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PlayerStreams.swift; sourceTree = "<group>"; }; | 		37DD87C6271C9CFE0027CBF9 /* PlayerStreams.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PlayerStreams.swift; sourceTree = "<group>"; }; | ||||||
| 		37DD9DA22785BBC900539416 /* NoCommentsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NoCommentsView.swift; sourceTree = "<group>"; }; | 		37DD9DA22785BBC900539416 /* NoCommentsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NoCommentsView.swift; sourceTree = "<group>"; }; | ||||||
| 		37DD9DAF2785D58D00539416 /* RefreshControl.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RefreshControl.swift; sourceTree = "<group>"; }; | 		37DD9DAF2785D58D00539416 /* RefreshControl.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RefreshControl.swift; sourceTree = "<group>"; }; | ||||||
| @@ -1981,6 +1985,7 @@ | |||||||
| 				37484C2426FC83E000287258 /* InstanceForm.swift */, | 				37484C2426FC83E000287258 /* InstanceForm.swift */, | ||||||
| 				37484C2C26FC844700287258 /* InstanceSettings.swift */, | 				37484C2C26FC844700287258 /* InstanceSettings.swift */, | ||||||
| 				374924D92921050B0017D862 /* LocationsSettings.swift */, | 				374924D92921050B0017D862 /* LocationsSettings.swift */, | ||||||
|  | 				37D9BA0529507F69002586BD /* PlayerControlsSettings.swift */, | ||||||
| 				37484C1826FC837400287258 /* PlayerSettings.swift */, | 				37484C1826FC837400287258 /* PlayerSettings.swift */, | ||||||
| 				374C053427242D9F009BDDBE /* SponsorBlockSettings.swift */, | 				374C053427242D9F009BDDBE /* SponsorBlockSettings.swift */, | ||||||
| 				376BE50627347B57009AD608 /* SettingsHeader.swift */, | 				376BE50627347B57009AD608 /* SettingsHeader.swift */, | ||||||
| @@ -3041,6 +3046,7 @@ | |||||||
| 				37CEE4BD2677B670005A1EFE /* SingleAssetStream.swift in Sources */, | 				37CEE4BD2677B670005A1EFE /* SingleAssetStream.swift in Sources */, | ||||||
| 				37C2211D27ADA33300305B41 /* MPVViewController.swift in Sources */, | 				37C2211D27ADA33300305B41 /* MPVViewController.swift in Sources */, | ||||||
| 				371B7E612759706A00D21217 /* CommentsView.swift in Sources */, | 				371B7E612759706A00D21217 /* CommentsView.swift in Sources */, | ||||||
|  | 				37D9BA0629507F69002586BD /* PlayerControlsSettings.swift in Sources */, | ||||||
| 				379DC3D128BA4EB400B09677 /* Seek.swift in Sources */, | 				379DC3D128BA4EB400B09677 /* Seek.swift in Sources */, | ||||||
| 				371B7E6A2759791900D21217 /* CommentsModel.swift in Sources */, | 				371B7E6A2759791900D21217 /* CommentsModel.swift in Sources */, | ||||||
| 				37E8B0F027B326F30024006F /* Comparable+Clamped.swift in Sources */, | 				37E8B0F027B326F30024006F /* Comparable+Clamped.swift in Sources */, | ||||||
| @@ -3382,6 +3388,7 @@ | |||||||
| 				3776925329463C310055EC18 /* PlaylistsCacheModel.swift in Sources */, | 				3776925329463C310055EC18 /* PlaylistsCacheModel.swift in Sources */, | ||||||
| 				37BD07C32698AD4F003EBB87 /* ContentView.swift in Sources */, | 				37BD07C32698AD4F003EBB87 /* ContentView.swift in Sources */, | ||||||
| 				37484C3226FCB8F900287258 /* AccountValidator.swift in Sources */, | 				37484C3226FCB8F900287258 /* AccountValidator.swift in Sources */, | ||||||
|  | 				37D9BA0729507F69002586BD /* PlayerControlsSettings.swift in Sources */, | ||||||
| 				378E9C4129455A5800B2D696 /* ChannelsView.swift in Sources */, | 				378E9C4129455A5800B2D696 /* ChannelsView.swift in Sources */, | ||||||
| 				378AE944274EF00A006A4EE1 /* Color+Background.swift in Sources */, | 				378AE944274EF00A006A4EE1 /* Color+Background.swift in Sources */, | ||||||
| 				37F49BA426CAA59B00304AC0 /* Playlist+Fixtures.swift in Sources */, | 				37F49BA426CAA59B00304AC0 /* Playlist+Fixtures.swift in Sources */, | ||||||
| @@ -3809,6 +3816,7 @@ | |||||||
| 				37FD43E52704847C0073EE42 /* View+Fixtures.swift in Sources */, | 				37FD43E52704847C0073EE42 /* View+Fixtures.swift in Sources */, | ||||||
| 				37FAE000272ED58000330459 /* EditFavorites.swift in Sources */, | 				37FAE000272ED58000330459 /* EditFavorites.swift in Sources */, | ||||||
| 				37F961A127BD90BB00058149 /* PlayerBackendType.swift in Sources */, | 				37F961A127BD90BB00058149 /* PlayerBackendType.swift in Sources */, | ||||||
|  | 				37D9BA0829507F69002586BD /* PlayerControlsSettings.swift in Sources */, | ||||||
| 				37599F32272B42810087F250 /* FavoriteItem.swift in Sources */, | 				37599F32272B42810087F250 /* FavoriteItem.swift in Sources */, | ||||||
| 				37E8B0EE27B326C00024006F /* TimelineView.swift in Sources */, | 				37E8B0EE27B326C00024006F /* TimelineView.swift in Sources */, | ||||||
| 				3726386E2948A4B80043702D /* Badge+Backport.swift in Sources */, | 				3726386E2948A4B80043702D /* Badge+Backport.swift in Sources */, | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user
	 Arkadiusz Fal
					Arkadiusz Fal