diff --git a/Shared/Defaults.swift b/Shared/Defaults.swift index f48c0a54..e340ce68 100644 --- a/Shared/Defaults.swift +++ b/Shared/Defaults.swift @@ -188,6 +188,16 @@ extension Defaults.Keys { static let playerActionsButtonLabelStyle = Key("playerActionsButtonLabelStyle", default: .iconAndText) static let systemControlsCommands = Key("systemControlsCommands", default: .restartAndAdvanceToNext) + static let actionButtonShareEnabled = Key("actionButtonShareEnabled", default: true) + static let actionButtonAddToPlaylistEnabled = Key("actionButtonAddToPlaylistEnabled", default: true) + static let actionButtonSubscribeEnabled = Key("actionButtonSubscribeEnabled", default: false) + static let actionButtonSettingsEnabled = Key("actionButtonSettingsEnabled", default: true) + 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) + 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/Video Details/VideoActions.swift b/Shared/Player/Video Details/VideoActions.swift index 3a7cb912..a60ae826 100644 --- a/Shared/Player/Video Details/VideoActions.swift +++ b/Shared/Player/Video Details/VideoActions.swift @@ -2,6 +2,16 @@ import Defaults import SwiftUI struct VideoActions: View { + enum Action: String, CaseIterable { + case share + case addToPlaylist + case subscribe + case settings + case next + case hide + case close + } + @ObservedObject private var accounts = AccountsModel.shared var navigation = NavigationModel.shared @ObservedObject private var subscriptions = SubscribedChannelsModel.shared @@ -12,70 +22,22 @@ struct VideoActions: View { @Default(.openWatchNextOnClose) private var openWatchNextOnClose @Default(.playerActionsButtonLabelStyle) private var playerActionsButtonLabelStyle + @Default(.actionButtonShareEnabled) private var actionButtonShareEnabled + @Default(.actionButtonAddToPlaylistEnabled) private var actionButtonAddToPlaylistEnabled + @Default(.actionButtonSubscribeEnabled) private var actionButtonSubscribeEnabled + @Default(.actionButtonSettingsEnabled) private var actionButtonSettingsEnabled + @Default(.actionButtonNextEnabled) private var actionButtonNextEnabled + @Default(.actionButtonHideEnabled) private var actionButtonHideEnabled + @Default(.actionButtonCloseEnabled) private var actionButtonCloseEnabled + @Default(.actionButtonNextQueueCountEnabled) private var actionButtonNextQueueCountEnabled + var body: some View { HStack(spacing: 6) { - if let video { - #if !os(tvOS) - if video.isShareable { - ShareButton(contentItem: .init(video: video)) { - actionButton("Share", systemImage: "square.and.arrow.up") - } - - Spacer() - } - #endif - - if !video.isLocal { - if accounts.signedIn, accounts.app.supportsUserPlaylists { - actionButton("Add", systemImage: "text.badge.plus") { - navigation.presentAddToPlaylist(video) - } - Spacer() - } - if accounts.signedIn, accounts.app.supportsSubscriptions { - if subscriptions.isSubscribing(video.channel.id) { - actionButton("Unsubscribe", systemImage: "xmark.circle") { - #if os(tvOS) - subscriptions.unsubscribe(video.channel.id) - #else - navigation.presentUnsubscribeAlert(video.channel, subscriptions: subscriptions) - #endif - } - } else { - actionButton("Subscribe", systemImage: "star.circle") { - subscriptions.subscribe(video.channel.id) { - navigation.sidebarSectionChanged.toggle() - } - } - } - Spacer() - } - } - } else { - Spacer() - } - actionButton("Next", systemImage: Constants.nextSystemImage) { - WatchNextViewModel.shared.userInteractedOpen(player.currentItem) - } - - Spacer() - - actionButton("Hide", systemImage: "chevron.down") { - player.hide(animate: true) - } - - Spacer() - - actionButton("Close", systemImage: "xmark") { - if openWatchNextOnClose { - player.pause() - WatchNextViewModel.shared.closed(player.currentItem) - } else { - player.closeCurrentItem() - } + ForEach(Action.allCases, id: \.self) { action in + actionBody(action) + .frame(maxWidth: .infinity) } } - .padding(.horizontal) .multilineTextAlignment(.center) .frame(maxWidth: .infinity) .frame(height: 50) @@ -83,6 +45,111 @@ struct VideoActions: View { .foregroundColor(.accentColor) } + func isVisible(_ action: Action) -> Bool { + switch action { + case .share: + return actionButtonShareEnabled + case .addToPlaylist: + return actionButtonAddToPlaylistEnabled + case .subscribe: + return actionButtonSubscribeEnabled + case .settings: + return actionButtonSettingsEnabled + case .next: + return actionButtonNextEnabled + case .hide: + return actionButtonHideEnabled + case .close: + return actionButtonCloseEnabled + } + } + + func isActionable(_ action: Action) -> Bool { + switch action { + case .share: + return video?.isShareable ?? false + case .addToPlaylist: + return !(video?.isLocal ?? true) + case .subscribe: + return !(video?.isLocal ?? true) && accounts.signedIn && accounts.app.supportsSubscriptions + case .settings: + return video != nil + default: + return true + } + } + + @ViewBuilder func actionBody(_ action: Action) -> some View { + if isVisible(action) { + Group { + switch action { + case .share: + ShareButton(contentItem: .init(video: video)) { + actionButton("Share", systemImage: "square.and.arrow.up") + } + case .addToPlaylist: + actionButton("Add", systemImage: "text.badge.plus") { + guard let video else { return } + navigation.presentAddToPlaylist(video) + } + case .subscribe: + if let channel = video?.channel, + subscriptions.isSubscribing(channel.id) + { + actionButton("Unsubscribe", systemImage: "xmark.circle") { + #if os(tvOS) + subscriptions.unsubscribe(channel.id) + #else + navigation.presentUnsubscribeAlert(channel, subscriptions: subscriptions) + #endif + } + } else { + actionButton("Subscribe", systemImage: "star.circle") { + guard let video else { return } + + subscriptions.subscribe(video.channel.id) { + navigation.sidebarSectionChanged.toggle() + } + } + } + case .settings: + actionButton("Settings", systemImage: "gear") { + withAnimation(ControlOverlaysModel.animation) { + ControlOverlaysModel.shared.show() + } + } + case .next: + actionButton(nextLabel, systemImage: Constants.nextSystemImage) { + WatchNextViewModel.shared.userInteractedOpen(player.currentItem) + } + case .hide: + actionButton("Hide", systemImage: "chevron.down") { + player.hide(animate: true) + } + + case .close: + actionButton("Close", systemImage: "xmark") { + if openWatchNextOnClose { + player.pause() + WatchNextViewModel.shared.closed(player.currentItem) + } else { + player.closeCurrentItem() + } + } + } + } + .disabled(!isActionable(action)) + } + } + + var nextLabel: String { + if actionButtonNextQueueCountEnabled, !player.queue.isEmpty { + return "\("Next".localized()) • \(player.queue.count)" + } + + return "Next".localized() + } + func actionButton( _ name: String, systemImage: String, diff --git a/Shared/Settings/PlayerSettings.swift b/Shared/Settings/PlayerSettings.swift index 8145478e..55e965dc 100644 --- a/Shared/Settings/PlayerSettings.swift +++ b/Shared/Settings/PlayerSettings.swift @@ -33,6 +33,14 @@ struct PlayerSettings: View { @Default(.openWatchNextOnFinishedWatching) private var openWatchNextOnFinishedWatching @Default(.openWatchNextOnFinishedWatchingDelay) private var openWatchNextOnFinishedWatchingDelay + @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 @@ -75,6 +83,13 @@ struct PlayerSettings: View { systemControlsCommandsPicker } + #if !os(tvOS) + Section(header: SettingsHeader(text: "Actions Buttons")) { + actionButtonToggles + } + actionButtonNextQueueCountEnabledToggle + #endif + Section(header: SettingsHeader(text: "Watch Next")) { openWatchNextOnFinishedWatchingToggle openWatchNextOnFinishedWatchingDelayTextField @@ -210,6 +225,24 @@ struct PlayerSettings: View { .multilineTextAlignment(.trailing) } + @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) diff --git a/Shared/Views/ShareButton.swift b/Shared/Views/ShareButton.swift index 953ff608..76306212 100644 --- a/Shared/Views/ShareButton.swift +++ b/Shared/Views/ShareButton.swift @@ -21,8 +21,10 @@ struct ShareButton: View { @ViewBuilder var body: some View { // TODO: this should work with other content item types - if let video = contentItem.video, !video.localStreamIsFile { - Menu { + Menu { + if let video = contentItem.video, + !video.localStreamIsFile + { if video.localStreamIsRemoteURL { remoteURLAction } else { @@ -32,14 +34,14 @@ struct ShareButton: View { youtubeActions } } - } label: { - label } - .menuStyle(.borderlessButton) - #if os(macOS) - .frame(maxWidth: 60) - #endif + } label: { + label } + .menuStyle(.borderlessButton) + #if os(macOS) + .frame(maxWidth: 60) + #endif } private var instanceActions: some View {