Show every video info action in the macOS toolbar

The toolbar layout on macOS now lists Play, Download, Share, Add to Playlist
and Bookmark as separate toolbar items, with a spacer setting Play apart from
the secondary actions. The primary action picker stays iOS-only, where the
pinned action plus the ⋯ menu layout is kept, and macOS gets its own settings
footer text.

The macOS items are written out explicitly rather than with ForEach: a ForEach
inside the toolbar builder is evaluated once and never re-renders on state
changes, which left the bookmark and download icons stale.
This commit is contained in:
Arkadiusz Fal
2026-09-28 23:48:17 +02:00
parent e0a07e93d2
commit 423c310bd4
3 changed files with 52 additions and 3 deletions

View File

@@ -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" : {

View File

@@ -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
}
}
}

View File

@@ -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