diff --git a/Yattee/Views/Channel/ChannelView.swift b/Yattee/Views/Channel/ChannelView.swift index 4a148fd7..17941e8e 100644 --- a/Yattee/Views/Channel/ChannelView.swift +++ b/Yattee/Views/Channel/ChannelView.swift @@ -154,6 +154,15 @@ struct ChannelView: View { ) } + /// View options button lives on the leading edge on macOS, trailing elsewhere. + private var viewOptionsPlacement: ToolbarItemPlacement { + #if os(macOS) + .navigation + #else + .primaryAction + #endif + } + var body: some View { // The search modifier is hoisted above this state-switching Group so the // search bar is present in every load state (loading spinner / cached header @@ -387,7 +396,7 @@ struct ChannelView: View { } #endif - ToolbarItem(placement: .primaryAction) { + ToolbarItem(placement: viewOptionsPlacement) { Button { showViewOptions = true } label: { @@ -401,8 +410,11 @@ struct ChannelView: View { #endif } - #if !os(tvOS) - if #available(iOS 26, macOS 26, *) { + // iOS keeps a fixed gap between the trailing view options and channel + // menu buttons; on macOS view options moves to the leading edge, so no + // gap is needed here. + #if os(iOS) + if #available(iOS 26, *) { ToolbarSpacer(.fixed, placement: .primaryAction) } #endif @@ -509,7 +521,7 @@ struct ChannelView: View { } #endif - ToolbarItem(placement: .primaryAction) { + ToolbarItem(placement: viewOptionsPlacement) { Button { showViewOptions = true } label: { @@ -523,8 +535,11 @@ struct ChannelView: View { #endif } - #if !os(tvOS) - if #available(iOS 26, macOS 26, *) { + // iOS keeps a fixed gap between the trailing view options and channel + // menu buttons; on macOS view options moves to the leading edge, so no + // gap is needed here. + #if os(iOS) + if #available(iOS 26, *) { ToolbarSpacer(.fixed, placement: .primaryAction) } #endif diff --git a/Yattee/Views/Downloads/DownloadsView.swift b/Yattee/Views/Downloads/DownloadsView.swift index a6257f04..68dc4bee 100644 --- a/Yattee/Views/Downloads/DownloadsView.swift +++ b/Yattee/Views/Downloads/DownloadsView.swift @@ -27,6 +27,15 @@ struct DownloadsView: View { appEnvironment?.settingsManager.listStyle ?? .inset } + /// View options control lives on the leading edge on macOS, trailing elsewhere. + private var viewOptionsPlacement: ToolbarItemPlacement { + #if os(macOS) + .navigation + #else + .primaryAction + #endif + } + var body: some View { Group { if let manager = downloadManager, let settings = downloadSettings { @@ -45,7 +54,7 @@ struct DownloadsView: View { ToolbarSpacer(.flexible, placement: .primaryAction) } #endif - ToolbarItem(placement: .primaryAction) { + ToolbarItem(placement: viewOptionsPlacement) { if let settings = downloadSettings { sortAndGroupMenu(settings) } diff --git a/Yattee/Views/Home/BookmarksListView.swift b/Yattee/Views/Home/BookmarksListView.swift index db3a9d3d..f1b422a4 100644 --- a/Yattee/Views/Home/BookmarksListView.swift +++ b/Yattee/Views/Home/BookmarksListView.swift @@ -92,6 +92,15 @@ struct BookmarksListView: View { ) } + /// View options button lives on the leading edge on macOS, trailing elsewhere. + private var viewOptionsPlacement: ToolbarItemPlacement { + #if os(macOS) + .navigation + #else + .primaryAction + #endif + } + var body: some View { GeometryReader { geometry in #if os(tvOS) @@ -160,7 +169,7 @@ struct BookmarksListView: View { ToolbarSpacer(.flexible, placement: .primaryAction) } #endif - ToolbarItem(placement: .primaryAction) { + ToolbarItem(placement: viewOptionsPlacement) { Button { showViewOptions = true } label: { diff --git a/Yattee/Views/Home/ContinueWatchingView.swift b/Yattee/Views/Home/ContinueWatchingView.swift index 111ed4ee..cfca4b7b 100644 --- a/Yattee/Views/Home/ContinueWatchingView.swift +++ b/Yattee/Views/Home/ContinueWatchingView.swift @@ -50,6 +50,15 @@ struct ContinueWatchingView: View { ) } + /// View options button lives on the leading edge on macOS, trailing elsewhere. + private var viewOptionsPlacement: ToolbarItemPlacement { + #if os(macOS) + .navigation + #else + .primaryAction + #endif + } + var body: some View { GeometryReader { geometry in #if os(tvOS) @@ -124,7 +133,7 @@ struct ContinueWatchingView: View { .toolbarTitleDisplayMode(.inlineLarge) .toolbar { if !inProgressEntries.isEmpty { - ToolbarItem(placement: .primaryAction) { + ToolbarItem(placement: viewOptionsPlacement) { Button { showViewOptions = true } label: { diff --git a/Yattee/Views/Home/HistoryListView.swift b/Yattee/Views/Home/HistoryListView.swift index 46e05657..92ad9306 100644 --- a/Yattee/Views/Home/HistoryListView.swift +++ b/Yattee/Views/Home/HistoryListView.swift @@ -89,6 +89,15 @@ struct HistoryListView: View { ) } + /// View options button lives on the leading edge on macOS, trailing elsewhere. + private var viewOptionsPlacement: ToolbarItemPlacement { + #if os(macOS) + .navigation + #else + .primaryAction + #endif + } + var body: some View { GeometryReader { geometry in #if os(tvOS) @@ -175,7 +184,7 @@ struct HistoryListView: View { } #endif // View options button (always visible) - ToolbarItem(placement: .primaryAction) { + ToolbarItem(placement: viewOptionsPlacement) { Button { showViewOptions = true } label: { diff --git a/Yattee/Views/Instances/InstanceBrowseView.swift b/Yattee/Views/Instances/InstanceBrowseView.swift index 3c133e4c..26a57221 100644 --- a/Yattee/Views/Instances/InstanceBrowseView.swift +++ b/Yattee/Views/Instances/InstanceBrowseView.swift @@ -122,6 +122,15 @@ struct InstanceBrowseView: View { ) } + /// View options button lives on the leading edge on macOS, trailing elsewhere. + private var viewOptionsPlacement: ToolbarItemPlacement { + #if os(macOS) + .navigation + #else + .primaryAction + #endif + } + var body: some View { let backgroundStyle: ListBackgroundStyle = listStyle == .inset ? .grouped : .plain GeometryReader { geometry in @@ -332,6 +341,20 @@ struct InstanceBrowseView: View { .navigationTitle(instance.displayName) .toolbarTitleDisplayMode(.inlineLarge) .toolbar { + // View options first so it is the leftmost navigation item on macOS. + ToolbarItem(placement: viewOptionsPlacement) { + Button { + showViewOptions = true + } label: { + Label(String(localized: "viewOptions.title"), systemImage: "slider.horizontal.3") + } + .liquidGlassTransitionSource(id: "instanceBrowseViewOptions", in: sheetTransition) + #if os(macOS) + .popover(isPresented: $showViewOptions, arrowEdge: .bottom) { + viewOptionsSheetContent + } + #endif + } #if os(macOS) if #available(macOS 26, *) { // Search filters button appears only while showing search results. @@ -372,25 +395,12 @@ struct InstanceBrowseView: View { .fixedSize() } } - // Pin the trailing group (search field + toolbar buttons) to the right edge, + // Pin the trailing group (search field) to the right edge, // matching the global Search view. if #available(macOS 26, *) { ToolbarSpacer(.flexible, placement: .primaryAction) } #endif - ToolbarItem(placement: .primaryAction) { - Button { - showViewOptions = true - } label: { - Label(String(localized: "viewOptions.title"), systemImage: "slider.horizontal.3") - } - .liquidGlassTransitionSource(id: "instanceBrowseViewOptions", in: sheetTransition) - #if os(macOS) - .popover(isPresented: $showViewOptions, arrowEdge: .bottom) { - viewOptionsSheetContent - } - #endif - } } #endif #if !os(macOS) diff --git a/Yattee/Views/MediaBrowser/MediaBrowserView.swift b/Yattee/Views/MediaBrowser/MediaBrowserView.swift index f1e1ec7c..dd234c7f 100644 --- a/Yattee/Views/MediaBrowser/MediaBrowserView.swift +++ b/Yattee/Views/MediaBrowser/MediaBrowserView.swift @@ -68,6 +68,15 @@ struct MediaBrowserView: View { ) } + /// View options button lives on the leading edge on macOS, trailing elsewhere. + private var viewOptionsPlacement: ToolbarItemPlacement { + #if os(macOS) + .navigation + #else + .primaryAction + #endif + } + var body: some View { content #if !os(tvOS) @@ -88,7 +97,7 @@ struct MediaBrowserView: View { } } } - ToolbarItem(placement: .primaryAction) { + ToolbarItem(placement: viewOptionsPlacement) { Button { showViewOptions = true } label: { diff --git a/Yattee/Views/Search/SearchView.swift b/Yattee/Views/Search/SearchView.swift index 926df82d..75af6bbf 100644 --- a/Yattee/Views/Search/SearchView.swift +++ b/Yattee/Views/Search/SearchView.swift @@ -99,6 +99,15 @@ struct SearchView: View { self.initialQuery = initialQuery } + /// View options button lives on the leading edge on macOS, trailing elsewhere. + private var viewOptionsPlacement: ToolbarItemPlacement { + #if os(macOS) + .navigation + #else + .primaryAction + #endif + } + var body: some View { tvOSOrDefaultContent #if !os(macOS) @@ -266,6 +275,20 @@ struct SearchView: View { .navigationTitle(String(localized: "tabs.search")) .toolbarTitleDisplayMode(.inlineLarge) .toolbar { + // View options first so it is the leftmost navigation item on macOS. + ToolbarItem(placement: viewOptionsPlacement) { + Button { + showViewOptions = true + } label: { + Label(String(localized: "viewOptions.title"), systemImage: "slider.horizontal.3") + } + .liquidGlassTransitionSource(id: "searchViewOptions", in: sheetTransition) + #if os(macOS) + .popover(isPresented: $showViewOptions, arrowEdge: .bottom) { + viewOptionsSheetContent + } + #endif + } #if os(macOS) if searchInstance?.supportsSearchFilters == true { if #available(macOS 26, *) { @@ -298,25 +321,12 @@ struct SearchView: View { } #endif #if os(macOS) - // Pin the trailing group (view options + search field) to the right edge + // Pin the trailing group (search field) to the right edge // so it doesn't shift when the filter items appear/disappear. if #available(macOS 26, *) { ToolbarSpacer(.flexible, placement: .primaryAction) } #endif - ToolbarItem(placement: .primaryAction) { - Button { - showViewOptions = true - } label: { - Label(String(localized: "viewOptions.title"), systemImage: "slider.horizontal.3") - } - .liquidGlassTransitionSource(id: "searchViewOptions", in: sheetTransition) - #if os(macOS) - .popover(isPresented: $showViewOptions, arrowEdge: .bottom) { - viewOptionsSheetContent - } - #endif - } } .searchable(text: searchTextBinding, prompt: Text(String(localized: "search.placeholder"))) .onSubmit(of: .search) { diff --git a/Yattee/Views/Subscriptions/ManageChannelsView.swift b/Yattee/Views/Subscriptions/ManageChannelsView.swift index 35df1ae8..8b16cec6 100644 --- a/Yattee/Views/Subscriptions/ManageChannelsView.swift +++ b/Yattee/Views/Subscriptions/ManageChannelsView.swift @@ -169,6 +169,15 @@ struct ManageChannelsView: View { return result } + /// View options button lives on the leading edge on macOS, trailing elsewhere. + private var viewOptionsPlacement: ToolbarItemPlacement { + #if os(macOS) + .navigation + #else + .primaryAction + #endif + } + var body: some View { GeometryReader { geometry in #if os(tvOS) @@ -198,7 +207,7 @@ struct ManageChannelsView: View { ToolbarSpacer(.flexible, placement: .primaryAction) } #endif - ToolbarItem(placement: .primaryAction) { + ToolbarItem(placement: viewOptionsPlacement) { Button { showViewOptions = true } label: { diff --git a/Yattee/Views/Subscriptions/SubscriptionsView.swift b/Yattee/Views/Subscriptions/SubscriptionsView.swift index 9f0b65a8..2e05999b 100644 --- a/Yattee/Views/Subscriptions/SubscriptionsView.swift +++ b/Yattee/Views/Subscriptions/SubscriptionsView.swift @@ -318,6 +318,15 @@ struct SubscriptionsView: View { return progress > 0 && progress < 1 ? progress : nil } + /// View options button lives on the leading edge on macOS, trailing elsewhere. + private var viewOptionsPlacement: ToolbarItemPlacement { + #if os(macOS) + .navigation + #else + .primaryAction + #endif + } + var body: some View { GeometryReader { geometry in ScrollViewReader { proxy in @@ -418,7 +427,7 @@ struct SubscriptionsView: View { .toolbarTitleDisplayMode(.inlineLarge) #endif .toolbar { - ToolbarItem(placement: .primaryAction) { + ToolbarItem(placement: viewOptionsPlacement) { Button { showViewOptions = true } label: {