diff --git a/Shared/Defaults.swift b/Shared/Defaults.swift index 4d341b8a..0cf47ee6 100644 --- a/Shared/Defaults.swift +++ b/Shared/Defaults.swift @@ -128,7 +128,7 @@ extension Defaults.Keys { static let fullScreenPlayerControlsLayout = Key("fullScreenPlayerControlsLayout", default: fullScreenPlayerControlsLayoutDefault) static let horizontalPlayerGestureEnabled = Key("horizontalPlayerGestureEnabled", default: true) static let seekGestureSpeed = Key("seekGestureSpeed", default: 0.5) - static let seekGestureSensitivity = Key("seekGestureSensitivity", default: 20.0) + static let seekGestureSensitivity = Key("seekGestureSensitivity", default: 30.0) static let showKeywords = Key("showKeywords", default: false) #if !os(tvOS) static let commentsPlacement = Key("commentsPlacement", default: .separate) @@ -201,9 +201,24 @@ extension Defaults.Keys { static let actionButtonNextEnabled = Key("actionButtonNextEnabled", default: true) static let actionButtonHideEnabled = Key("actionButtonHideEnabled", default: false) static let actionButtonCloseEnabled = Key("actionButtonCloseEnabled", default: true) - static let actionButtonNextQueueCountEnabled = Key("actionButtonNextQueueCountEnabled", default: true) + #if os(iOS) + static let playerControlsLockOrientationEnabled = Key("playerControlsLockOrientationEnabled", default: true) + #endif + #if os(tvOS) + static let playerControlsSettingsEnabledDefault = true + #else + static let playerControlsSettingsEnabledDefault = false + #endif + static let playerControlsSettingsEnabled = Key("playerControlsSettingsEnabled", default: playerControlsSettingsEnabledDefault) + static let playerControlsCloseEnabled = Key("playerControlsCloseEnabled", default: true) + static let playerControlsRestartEnabled = Key("playerControlsRestartEnabled", default: false) + static let playerControlsAdvanceToNextEnabled = Key("playerControlsAdvanceToNextEnabled", default: false) + static let playerControlsPlaybackModeEnabled = Key("playerControlsPlaybackModeEnabled", default: false) + static let playerControlsNextEnabled = Key("playerControlsNextEnabled", default: true) + static let playerControlsMusicModeEnabled = Key("playerControlsMusicModeEnabled", default: true) + static let mpvCacheSecs = Key("mpvCacheSecs", default: "120") static let mpvCachePauseWait = Key("mpvCachePauseWait", default: "3") static let mpvEnableLogging = Key("mpvEnableLogging", default: false) diff --git a/Shared/Player/Controls/PlayerControls.swift b/Shared/Player/Controls/PlayerControls.swift index ad8543d2..5d8475f3 100644 --- a/Shared/Player/Controls/PlayerControls.swift +++ b/Shared/Player/Controls/PlayerControls.swift @@ -32,6 +32,17 @@ struct PlayerControls: View { @Default(.buttonBackwardSeekDuration) private var buttonBackwardSeekDuration @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 var playerControlsLayout: PlayerControlsLayout { @@ -135,17 +146,30 @@ struct PlayerControls: View { seekBackwardButton seekForwardButton #endif - restartVideoButton - advanceToNextItemButton + if playerControlsAdvanceToNextEnabled { + restartVideoButton + } + if playerControlsAdvanceToNextEnabled { + advanceToNextItemButton + } Spacer() #if os(tvOS) - settingsButton + if playerControlsSettingsEnabled { + settingsButton + } #endif - playbackModeButton + if playerControlsPlaybackModeEnabled { + playbackModeButton + } + if playerControlsNextEnabled { + watchNextButton + } #if os(tvOS) closeVideoButton #else - musicModeButton + if playerControlsMusicModeEnabled { + musicModeButton + } #endif } .zIndex(0) @@ -306,13 +330,20 @@ struct PlayerControls: View { pipButton #if os(iOS) - lockOrientationButton + if playerControlsLockOrientationEnabled { + lockOrientationButton + } #endif Spacer() - settingsButton - closeVideoButton + if playerControlsSettingsEnabled { + settingsButton + } + + if playerControlsCloseEnabled { + closeVideoButton + } } } @@ -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 foregroundColor: Color? var fontSize: Double? diff --git a/Shared/Player/PlayerGestures.swift b/Shared/Player/PlayerGestures.swift index f194bb48..e542a611 100644 --- a/Shared/Player/PlayerGestures.swift +++ b/Shared/Player/PlayerGestures.swift @@ -5,9 +5,6 @@ struct PlayerGestures: View { private var player = PlayerModel.shared @ObservedObject private var model = PlayerControlsModel.shared - @Default(.gestureBackwardSeekDuration) private var gestureBackwardSeekDuration - @Default(.gestureForwardSeekDuration) private var gestureForwardSeekDuration - var body: some View { HStack(spacing: 0) { gestureRectangle @@ -15,7 +12,7 @@ struct PlayerGestures: View { tapSensitivity: 0.2, singleTapAction: { singleTapAction() }, doubleTapAction: { - let interval = TimeInterval(gestureBackwardSeekDuration) ?? 10 + let interval = TimeInterval(Defaults[.gestureBackwardSeekDuration]) ?? 10 player.backend.seek(relative: .secondsInDefaultTimescale(-interval), seekType: .userInteracted) }, anyTapAction: { @@ -37,7 +34,7 @@ struct PlayerGestures: View { tapSensitivity: 0.2, singleTapAction: { singleTapAction() }, doubleTapAction: { - let interval = TimeInterval(gestureForwardSeekDuration) ?? 10 + let interval = TimeInterval(Defaults[.gestureForwardSeekDuration]) ?? 10 player.backend.seek(relative: .secondsInDefaultTimescale(interval), seekType: .userInteracted) } ) diff --git a/Shared/Player/Video Details/VideoActions.swift b/Shared/Player/Video Details/VideoActions.swift index a60ae826..09390233 100644 --- a/Shared/Player/Video Details/VideoActions.swift +++ b/Shared/Player/Video Details/VideoActions.swift @@ -84,9 +84,13 @@ struct VideoActions: View { Group { switch action { case .share: - ShareButton(contentItem: .init(video: video)) { - actionButton("Share", systemImage: "square.and.arrow.up") - } + #if os(tvOS) + EmptyView() + #else + ShareButton(contentItem: .init(video: video)) { + actionButton("Share", systemImage: "square.and.arrow.up") + } + #endif case .addToPlaylist: actionButton("Add", systemImage: "text.badge.plus") { guard let video else { return } diff --git a/Shared/Player/WatchNextView.swift b/Shared/Player/WatchNextView.swift index a3e42b09..b6ae1d85 100644 --- a/Shared/Player/WatchNextView.swift +++ b/Shared/Player/WatchNextView.swift @@ -35,8 +35,10 @@ struct WatchNextView: View { Spacer() HStack { - Text("Mode") - .foregroundColor(.secondary) + #if os(macOS) + Text("Mode") + .foregroundColor(.secondary) + #endif playbackModeControl @@ -267,6 +269,8 @@ struct WatchNextView: View { player.playbackMode = player.playbackMode.next() } label: { Label(player.playbackMode.description, systemImage: player.playbackMode.systemImage) + .transaction { t in t.animation = nil } + .frame(minWidth: 350) } #elseif os(macOS) playbackModePicker diff --git a/Shared/Settings/BrowsingSettings.swift b/Shared/Settings/BrowsingSettings.swift index 5b397c72..8ca3779d 100644 --- a/Shared/Settings/BrowsingSettings.swift +++ b/Shared/Settings/BrowsingSettings.swift @@ -193,6 +193,8 @@ struct BrowsingSettings: View { Text("Tap and hold channel thumbnail to open context menu with more actions") #elseif os(macOS) Text("Right click channel thumbnail to open context menu with more actions") + .foregroundColor(.secondary) + .padding(.bottom, 10) #endif } #endif diff --git a/Shared/Settings/PlayerControlsSettings.swift b/Shared/Settings/PlayerControlsSettings.swift new file mode 100644 index 00000000..96a720a5 --- /dev/null +++ b/Shared/Settings/PlayerControlsSettings.swift @@ -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) -> 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) + } +} diff --git a/Shared/Settings/PlayerSettings.swift b/Shared/Settings/PlayerSettings.swift index bdf273c0..bb4ccc48 100644 --- a/Shared/Settings/PlayerSettings.swift +++ b/Shared/Settings/PlayerSettings.swift @@ -6,11 +6,7 @@ struct PlayerSettings: View { @Default(.playerInstanceID) private var playerInstanceID @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(.pauseOnHidingPlayer) private var pauseOnHidingPlayer #if os(iOS) @@ -27,29 +23,12 @@ struct PlayerSettings: View { #endif @Default(.enableReturnYouTubeDislike) private var enableReturnYouTubeDislike - @Default(.systemControlsCommands) private var systemControlsCommands @Default(.openWatchNextOnClose) private var openWatchNextOnClose @Default(.openWatchNextOnFinishedWatching) private var openWatchNextOnFinishedWatching @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 - private var player = PlayerModel.shared #if os(iOS) private var idiom: UIUserInterfaceIdiom { @@ -87,40 +66,14 @@ struct PlayerSettings: View { #if !os(macOS) pauseOnEnteringBackgroundToogle #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")) { openWatchNextOnFinishedWatchingToggle openWatchNextOnFinishedWatchingDelayTextField 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())) { #if os(iOS) if idiom == .pad { @@ -190,25 +143,6 @@ struct PlayerSettings: View { .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 { Toggle("Open after manual close of video", isOn: $openWatchNextOnClose) } @@ -236,61 +170,6 @@ struct PlayerSettings: View { .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) -> 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) -> 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 { Picker("Sidebar", selection: $playerSidebar) { #if os(macOS) @@ -306,56 +185,6 @@ struct PlayerSettings: View { .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 { Toggle("Show keywords", isOn: $showKeywords) } diff --git a/Shared/Settings/SettingsView.swift b/Shared/Settings/SettingsView.swift index f17c3d07..bd3cf3e0 100644 --- a/Shared/Settings/SettingsView.swift +++ b/Shared/Settings/SettingsView.swift @@ -7,7 +7,7 @@ struct SettingsView: View { #if os(macOS) 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 @@ -48,6 +48,14 @@ struct SettingsView: View { } .tag(Tabs.player) + Form { + PlayerControlsSettings() + } + .tabItem { + Label("Controls", systemImage: "hand.tap") + } + .tag(Tabs.controls) + Form { QualitySettings() } @@ -98,7 +106,7 @@ struct SettingsView: View { .tag(Tabs.help) } .padding(20) - .frame(width: 600, height: windowHeight) + .frame(width: 650, height: windowHeight) #else NavigationView { settingsList @@ -138,6 +146,12 @@ struct SettingsView: View { Label("Player", systemImage: "play.rectangle") } + NavigationLink { + PlayerControlsSettings() + } label: { + Label("Controls", systemImage: "hand.tap") + } + NavigationLink { QualitySettings() } label: { @@ -225,9 +239,11 @@ struct SettingsView: View { private var windowHeight: Double { switch selection { case .browsing: - return 700 + return 820 case .player: - return 730 + return 450 + case .controls: + return 800 case .quality: return 420 case .history: diff --git a/Yattee.xcodeproj/project.pbxproj b/Yattee.xcodeproj/project.pbxproj index fd02d237..6102846b 100644 --- a/Yattee.xcodeproj/project.pbxproj +++ b/Yattee.xcodeproj/project.pbxproj @@ -873,6 +873,9 @@ 37D836BC294927E700005E5E /* 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 */; }; + 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 */; }; 37DD87C7271C9CFE0027CBF9 /* 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 = ""; }; 37D836BB294927E700005E5E /* ChannelsCacheModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChannelsCacheModel.swift; sourceTree = ""; }; 37D9169A27388A81002B1BAA /* README.md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = README.md; sourceTree = ""; }; + 37D9BA0529507F69002586BD /* PlayerControlsSettings.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PlayerControlsSettings.swift; sourceTree = ""; }; 37DD87C6271C9CFE0027CBF9 /* PlayerStreams.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PlayerStreams.swift; sourceTree = ""; }; 37DD9DA22785BBC900539416 /* NoCommentsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NoCommentsView.swift; sourceTree = ""; }; 37DD9DAF2785D58D00539416 /* RefreshControl.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RefreshControl.swift; sourceTree = ""; }; @@ -1981,6 +1985,7 @@ 37484C2426FC83E000287258 /* InstanceForm.swift */, 37484C2C26FC844700287258 /* InstanceSettings.swift */, 374924D92921050B0017D862 /* LocationsSettings.swift */, + 37D9BA0529507F69002586BD /* PlayerControlsSettings.swift */, 37484C1826FC837400287258 /* PlayerSettings.swift */, 374C053427242D9F009BDDBE /* SponsorBlockSettings.swift */, 376BE50627347B57009AD608 /* SettingsHeader.swift */, @@ -3041,6 +3046,7 @@ 37CEE4BD2677B670005A1EFE /* SingleAssetStream.swift in Sources */, 37C2211D27ADA33300305B41 /* MPVViewController.swift in Sources */, 371B7E612759706A00D21217 /* CommentsView.swift in Sources */, + 37D9BA0629507F69002586BD /* PlayerControlsSettings.swift in Sources */, 379DC3D128BA4EB400B09677 /* Seek.swift in Sources */, 371B7E6A2759791900D21217 /* CommentsModel.swift in Sources */, 37E8B0F027B326F30024006F /* Comparable+Clamped.swift in Sources */, @@ -3382,6 +3388,7 @@ 3776925329463C310055EC18 /* PlaylistsCacheModel.swift in Sources */, 37BD07C32698AD4F003EBB87 /* ContentView.swift in Sources */, 37484C3226FCB8F900287258 /* AccountValidator.swift in Sources */, + 37D9BA0729507F69002586BD /* PlayerControlsSettings.swift in Sources */, 378E9C4129455A5800B2D696 /* ChannelsView.swift in Sources */, 378AE944274EF00A006A4EE1 /* Color+Background.swift in Sources */, 37F49BA426CAA59B00304AC0 /* Playlist+Fixtures.swift in Sources */, @@ -3809,6 +3816,7 @@ 37FD43E52704847C0073EE42 /* View+Fixtures.swift in Sources */, 37FAE000272ED58000330459 /* EditFavorites.swift in Sources */, 37F961A127BD90BB00058149 /* PlayerBackendType.swift in Sources */, + 37D9BA0829507F69002586BD /* PlayerControlsSettings.swift in Sources */, 37599F32272B42810087F250 /* FavoriteItem.swift in Sources */, 37E8B0EE27B326C00024006F /* TimelineView.swift in Sources */, 3726386E2948A4B80043702D /* Badge+Backport.swift in Sources */,