diff --git a/Yattee/Localizable.xcstrings b/Yattee/Localizable.xcstrings index 2dd87fcb..2716b198 100644 --- a/Yattee/Localizable.xcstrings +++ b/Yattee/Localizable.xcstrings @@ -16385,6 +16385,16 @@ } } }, + "settings.videoInfo.actions.footer.macOS" : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Toolbar shows all actions in the window toolbar instead of buttons below the video." + } + } + } + }, "settings.videoInfo.actions.header" : { "localizations" : { "en" : { diff --git a/Yattee/Views/Settings/LayoutNavigationSettingsView.swift b/Yattee/Views/Settings/LayoutNavigationSettingsView.swift index 02bb12bc..8d48a4f1 100644 --- a/Yattee/Views/Settings/LayoutNavigationSettingsView.swift +++ b/Yattee/Views/Settings/LayoutNavigationSettingsView.swift @@ -230,8 +230,17 @@ private struct VideoActionsSection: View { private struct VideoInfoActionsSection: View { @Bindable var settings: SettingsManager + /// macOS shows every action in the toolbar; iOS pins one and tucks the rest into the ⋯ menu. + private var actionsFooter: LocalizedStringKey { + #if os(macOS) + "settings.videoInfo.actions.footer.macOS" + #else + "settings.videoInfo.actions.footer" + #endif + } + var body: some View { - SettingsFormSection("settings.videoInfo.actions.header", footer: "settings.videoInfo.actions.footer") { + SettingsFormSection("settings.videoInfo.actions.header", footer: actionsFooter) { Picker( String(localized: "settings.videoInfo.actionsLayout"), selection: $settings.videoInfoActionsLayout @@ -241,6 +250,7 @@ private struct VideoInfoActionsSection: View { } } + #if os(iOS) if settings.videoInfoActionsLayout == .toolbar { Picker( String(localized: "settings.videoInfo.primaryAction"), @@ -251,6 +261,7 @@ private struct VideoInfoActionsSection: View { } } } + #endif } } } diff --git a/Yattee/Views/Video/VideoInfoView.swift b/Yattee/Views/Video/VideoInfoView.swift index 1c17f7c5..cbb44d50 100644 --- a/Yattee/Views/Video/VideoInfoView.swift +++ b/Yattee/Views/Video/VideoInfoView.swift @@ -303,14 +303,36 @@ struct VideoInfoView: View { #if !os(tvOS) .toolbar { if usesToolbarActions, displayedVideo != nil { + #if os(macOS) + // macOS has room for every action, so show them all as toolbar items. + // Explicit items rather than ForEach: a ForEach inside the toolbar builder + // is evaluated once and never picks up state changes (bookmark/download icons). + ToolbarItem(placement: .primaryAction) { + toolbarActionView(.play, inMenu: false) + } + // Separate Play from the secondary actions so it gets its own glass pill. + if #available(macOS 26, *) { + ToolbarSpacer(.fixed, placement: .primaryAction) + } + ToolbarItem(placement: .primaryAction) { + toolbarActionView(.download, inMenu: false) + } + ToolbarItem(placement: .primaryAction) { + toolbarActionView(.share, inMenu: false) + } + ToolbarItem(placement: .primaryAction) { + toolbarActionView(.addToPlaylist, inMenu: false) + } + ToolbarItem(placement: .primaryAction) { + toolbarActionView(.bookmark, inMenu: false) + } + #else ToolbarItem(placement: .primaryAction) { toolbarActionView(primaryAction, inMenu: false) } - #if os(iOS) if #available(iOS 26, *) { ToolbarSpacer(.fixed, placement: .primaryAction) } - #endif ToolbarItem(placement: .primaryAction) { Menu { ForEach(VideoInfoPrimaryAction.allCases.filter { $0 != primaryAction }, id: \.self) { action in @@ -320,6 +342,7 @@ struct VideoInfoView: View { Label(String(localized: "videoInfo.toolbar.moreActions"), systemImage: "ellipsis") } } + #endif } } #endif @@ -1334,8 +1357,13 @@ struct VideoInfoView: View { } /// The action pinned to the toolbar; every other action lives in the ellipsis menu. + /// macOS shows every action in the toolbar, so the pinned action only matters on iOS. private var primaryAction: VideoInfoPrimaryAction { + #if os(macOS) + .play + #else appEnvironment?.settingsManager.videoInfoPrimaryAction ?? .play + #endif } /// The hero thumbnail is the only play affordance when the toolbar layout is active