mirror of
https://github.com/yattee/yattee.git
synced 2026-07-20 14:22:02 +00:00
Pin view options button to leading toolbar edge on macOS
Place the view options control on the leading (`.navigation`) edge on macOS so it is consistently the first/leftmost toolbar button across all views, instead of sitting on the right next to the search field in search views and on the left in non-search views. Each affected view gains a computed `viewOptionsPlacement` (`.navigation` on macOS, `.primaryAction` elsewhere) so iOS/iPadOS/tvOS keep the button trailing. Search-style views (Search, InstanceBrowse) declare the view options item first so it renders left of the search-filters button; their flexible spacer is kept to right-pin the search field. Channel's fixed spacer between view options and the channel menu is now iOS-only.
This commit is contained in:
@@ -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 {
|
var body: some View {
|
||||||
// The search modifier is hoisted above this state-switching Group so the
|
// The search modifier is hoisted above this state-switching Group so the
|
||||||
// search bar is present in every load state (loading spinner / cached header
|
// search bar is present in every load state (loading spinner / cached header
|
||||||
@@ -387,7 +396,7 @@ struct ChannelView: View {
|
|||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
ToolbarItem(placement: .primaryAction) {
|
ToolbarItem(placement: viewOptionsPlacement) {
|
||||||
Button {
|
Button {
|
||||||
showViewOptions = true
|
showViewOptions = true
|
||||||
} label: {
|
} label: {
|
||||||
@@ -401,8 +410,11 @@ struct ChannelView: View {
|
|||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
#if !os(tvOS)
|
// iOS keeps a fixed gap between the trailing view options and channel
|
||||||
if #available(iOS 26, macOS 26, *) {
|
// 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)
|
ToolbarSpacer(.fixed, placement: .primaryAction)
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
@@ -509,7 +521,7 @@ struct ChannelView: View {
|
|||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
ToolbarItem(placement: .primaryAction) {
|
ToolbarItem(placement: viewOptionsPlacement) {
|
||||||
Button {
|
Button {
|
||||||
showViewOptions = true
|
showViewOptions = true
|
||||||
} label: {
|
} label: {
|
||||||
@@ -523,8 +535,11 @@ struct ChannelView: View {
|
|||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
#if !os(tvOS)
|
// iOS keeps a fixed gap between the trailing view options and channel
|
||||||
if #available(iOS 26, macOS 26, *) {
|
// 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)
|
ToolbarSpacer(.fixed, placement: .primaryAction)
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
@@ -27,6 +27,15 @@ struct DownloadsView: View {
|
|||||||
appEnvironment?.settingsManager.listStyle ?? .inset
|
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 {
|
var body: some View {
|
||||||
Group {
|
Group {
|
||||||
if let manager = downloadManager, let settings = downloadSettings {
|
if let manager = downloadManager, let settings = downloadSettings {
|
||||||
@@ -45,7 +54,7 @@ struct DownloadsView: View {
|
|||||||
ToolbarSpacer(.flexible, placement: .primaryAction)
|
ToolbarSpacer(.flexible, placement: .primaryAction)
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
ToolbarItem(placement: .primaryAction) {
|
ToolbarItem(placement: viewOptionsPlacement) {
|
||||||
if let settings = downloadSettings {
|
if let settings = downloadSettings {
|
||||||
sortAndGroupMenu(settings)
|
sortAndGroupMenu(settings)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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 {
|
var body: some View {
|
||||||
GeometryReader { geometry in
|
GeometryReader { geometry in
|
||||||
#if os(tvOS)
|
#if os(tvOS)
|
||||||
@@ -160,7 +169,7 @@ struct BookmarksListView: View {
|
|||||||
ToolbarSpacer(.flexible, placement: .primaryAction)
|
ToolbarSpacer(.flexible, placement: .primaryAction)
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
ToolbarItem(placement: .primaryAction) {
|
ToolbarItem(placement: viewOptionsPlacement) {
|
||||||
Button {
|
Button {
|
||||||
showViewOptions = true
|
showViewOptions = true
|
||||||
} label: {
|
} label: {
|
||||||
|
|||||||
@@ -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 {
|
var body: some View {
|
||||||
GeometryReader { geometry in
|
GeometryReader { geometry in
|
||||||
#if os(tvOS)
|
#if os(tvOS)
|
||||||
@@ -124,7 +133,7 @@ struct ContinueWatchingView: View {
|
|||||||
.toolbarTitleDisplayMode(.inlineLarge)
|
.toolbarTitleDisplayMode(.inlineLarge)
|
||||||
.toolbar {
|
.toolbar {
|
||||||
if !inProgressEntries.isEmpty {
|
if !inProgressEntries.isEmpty {
|
||||||
ToolbarItem(placement: .primaryAction) {
|
ToolbarItem(placement: viewOptionsPlacement) {
|
||||||
Button {
|
Button {
|
||||||
showViewOptions = true
|
showViewOptions = true
|
||||||
} label: {
|
} label: {
|
||||||
|
|||||||
@@ -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 {
|
var body: some View {
|
||||||
GeometryReader { geometry in
|
GeometryReader { geometry in
|
||||||
#if os(tvOS)
|
#if os(tvOS)
|
||||||
@@ -175,7 +184,7 @@ struct HistoryListView: View {
|
|||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
// View options button (always visible)
|
// View options button (always visible)
|
||||||
ToolbarItem(placement: .primaryAction) {
|
ToolbarItem(placement: viewOptionsPlacement) {
|
||||||
Button {
|
Button {
|
||||||
showViewOptions = true
|
showViewOptions = true
|
||||||
} label: {
|
} label: {
|
||||||
|
|||||||
@@ -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 {
|
var body: some View {
|
||||||
let backgroundStyle: ListBackgroundStyle = listStyle == .inset ? .grouped : .plain
|
let backgroundStyle: ListBackgroundStyle = listStyle == .inset ? .grouped : .plain
|
||||||
GeometryReader { geometry in
|
GeometryReader { geometry in
|
||||||
@@ -332,6 +341,20 @@ struct InstanceBrowseView: View {
|
|||||||
.navigationTitle(instance.displayName)
|
.navigationTitle(instance.displayName)
|
||||||
.toolbarTitleDisplayMode(.inlineLarge)
|
.toolbarTitleDisplayMode(.inlineLarge)
|
||||||
.toolbar {
|
.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 os(macOS)
|
||||||
if #available(macOS 26, *) {
|
if #available(macOS 26, *) {
|
||||||
// Search filters button appears only while showing search results.
|
// Search filters button appears only while showing search results.
|
||||||
@@ -372,25 +395,12 @@ struct InstanceBrowseView: View {
|
|||||||
.fixedSize()
|
.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.
|
// matching the global Search view.
|
||||||
if #available(macOS 26, *) {
|
if #available(macOS 26, *) {
|
||||||
ToolbarSpacer(.flexible, placement: .primaryAction)
|
ToolbarSpacer(.flexible, placement: .primaryAction)
|
||||||
}
|
}
|
||||||
#endif
|
#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
|
#endif
|
||||||
#if !os(macOS)
|
#if !os(macOS)
|
||||||
|
|||||||
@@ -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 {
|
var body: some View {
|
||||||
content
|
content
|
||||||
#if !os(tvOS)
|
#if !os(tvOS)
|
||||||
@@ -88,7 +97,7 @@ struct MediaBrowserView: View {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
ToolbarItem(placement: .primaryAction) {
|
ToolbarItem(placement: viewOptionsPlacement) {
|
||||||
Button {
|
Button {
|
||||||
showViewOptions = true
|
showViewOptions = true
|
||||||
} label: {
|
} label: {
|
||||||
|
|||||||
@@ -99,6 +99,15 @@ struct SearchView: View {
|
|||||||
self.initialQuery = initialQuery
|
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 {
|
var body: some View {
|
||||||
tvOSOrDefaultContent
|
tvOSOrDefaultContent
|
||||||
#if !os(macOS)
|
#if !os(macOS)
|
||||||
@@ -266,6 +275,20 @@ struct SearchView: View {
|
|||||||
.navigationTitle(String(localized: "tabs.search"))
|
.navigationTitle(String(localized: "tabs.search"))
|
||||||
.toolbarTitleDisplayMode(.inlineLarge)
|
.toolbarTitleDisplayMode(.inlineLarge)
|
||||||
.toolbar {
|
.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 os(macOS)
|
||||||
if searchInstance?.supportsSearchFilters == true {
|
if searchInstance?.supportsSearchFilters == true {
|
||||||
if #available(macOS 26, *) {
|
if #available(macOS 26, *) {
|
||||||
@@ -298,25 +321,12 @@ struct SearchView: View {
|
|||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
#if os(macOS)
|
#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.
|
// so it doesn't shift when the filter items appear/disappear.
|
||||||
if #available(macOS 26, *) {
|
if #available(macOS 26, *) {
|
||||||
ToolbarSpacer(.flexible, placement: .primaryAction)
|
ToolbarSpacer(.flexible, placement: .primaryAction)
|
||||||
}
|
}
|
||||||
#endif
|
#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")))
|
.searchable(text: searchTextBinding, prompt: Text(String(localized: "search.placeholder")))
|
||||||
.onSubmit(of: .search) {
|
.onSubmit(of: .search) {
|
||||||
|
|||||||
@@ -169,6 +169,15 @@ struct ManageChannelsView: View {
|
|||||||
return result
|
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 {
|
var body: some View {
|
||||||
GeometryReader { geometry in
|
GeometryReader { geometry in
|
||||||
#if os(tvOS)
|
#if os(tvOS)
|
||||||
@@ -198,7 +207,7 @@ struct ManageChannelsView: View {
|
|||||||
ToolbarSpacer(.flexible, placement: .primaryAction)
|
ToolbarSpacer(.flexible, placement: .primaryAction)
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
ToolbarItem(placement: .primaryAction) {
|
ToolbarItem(placement: viewOptionsPlacement) {
|
||||||
Button {
|
Button {
|
||||||
showViewOptions = true
|
showViewOptions = true
|
||||||
} label: {
|
} label: {
|
||||||
|
|||||||
@@ -318,6 +318,15 @@ struct SubscriptionsView: View {
|
|||||||
return progress > 0 && progress < 1 ? progress : nil
|
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 {
|
var body: some View {
|
||||||
GeometryReader { geometry in
|
GeometryReader { geometry in
|
||||||
ScrollViewReader { proxy in
|
ScrollViewReader { proxy in
|
||||||
@@ -418,7 +427,7 @@ struct SubscriptionsView: View {
|
|||||||
.toolbarTitleDisplayMode(.inlineLarge)
|
.toolbarTitleDisplayMode(.inlineLarge)
|
||||||
#endif
|
#endif
|
||||||
.toolbar {
|
.toolbar {
|
||||||
ToolbarItem(placement: .primaryAction) {
|
ToolbarItem(placement: viewOptionsPlacement) {
|
||||||
Button {
|
Button {
|
||||||
showViewOptions = true
|
showViewOptions = true
|
||||||
} label: {
|
} label: {
|
||||||
|
|||||||
Reference in New Issue
Block a user