diff --git a/Yattee/Views/Settings/AddSource/AddLocalFolderView.swift b/Yattee/Views/Settings/AddSource/AddLocalFolderView.swift index 5f8d6102..eb78d93a 100644 --- a/Yattee/Views/Settings/AddSource/AddLocalFolderView.swift +++ b/Yattee/Views/Settings/AddSource/AddLocalFolderView.swift @@ -31,6 +31,21 @@ struct AddLocalFolderView: View { !name.isEmpty && selectedFolderURL != nil } + /// macOS surfaces toolbar confirmation items from a pushed-in-sheet view + /// reliably only from macOS 26 onward. On older macOS we render the + /// "Add Source" action button inline in the form instead. + private var usesToolbarActionButton: Bool { + #if os(macOS) + if #available(macOS 26, *) { + return true + } else { + return false + } + #else + return false + #endif + } + // MARK: - Body var body: some View { @@ -99,16 +114,22 @@ struct AddLocalFolderView: View { if let result = testResult { SourceTestResultSection(result: result) } + + if !usesToolbarActionButton { + actionSection + } } .formStyle(.grouped) .navigationTitle(String(localized: "sources.addLocalFolder")) .toolbar { - ToolbarItem(placement: .confirmationAction) { - Button(String(localized: "sources.addSource")) { - addSource() + if usesToolbarActionButton { + ToolbarItem(placement: .confirmationAction) { + Button(String(localized: "sources.addSource")) { + addSource() + } + .disabled(!canAdd) + .keyboardShortcut(.defaultAction) } - .disabled(!canAdd) - .keyboardShortcut(.defaultAction) } } } diff --git a/Yattee/Views/Settings/AddSource/AddRemoteServerView.swift b/Yattee/Views/Settings/AddSource/AddRemoteServerView.swift index ae59523b..6e1199e2 100644 --- a/Yattee/Views/Settings/AddSource/AddRemoteServerView.swift +++ b/Yattee/Views/Settings/AddSource/AddRemoteServerView.swift @@ -87,6 +87,22 @@ struct AddRemoteServerView: View { return false } + /// macOS surfaces toolbar confirmation items from a pushed-in-sheet view + /// reliably only from macOS 26 onward. On older macOS (and on iOS/tvOS) we + /// render the "Add Source" action button inline in the form instead, so it + /// is always visible. + private var usesToolbarActionButton: Bool { + #if os(macOS) + if #available(macOS 26, *) { + return true + } else { + return false + } + #else + return false + #endif + } + private var canAdd: Bool { guard !urlString.isEmpty else { return false } @@ -172,9 +188,9 @@ struct AddRemoteServerView: View { if isFieldsRevealed { serverConfigurationFields - #if !os(macOS) - actionSection - #endif + if !usesToolbarActionButton { + actionSection + } } } #if os(iOS) @@ -183,8 +199,8 @@ struct AddRemoteServerView: View { #if os(macOS) .formStyle(.grouped) .toolbar { - ToolbarItem(placement: .confirmationAction) { - if isFieldsRevealed { + if isFieldsRevealed, usesToolbarActionButton { + ToolbarItem(placement: .confirmationAction) { Button { addSource() } label: { diff --git a/Yattee/Views/Settings/AddSource/AddSMBView.swift b/Yattee/Views/Settings/AddSource/AddSMBView.swift index 3cd5ffb7..4895231e 100644 --- a/Yattee/Views/Settings/AddSource/AddSMBView.swift +++ b/Yattee/Views/Settings/AddSource/AddSMBView.swift @@ -36,6 +36,21 @@ struct AddSMBView: View { !name.isEmpty && !server.isEmpty } + /// macOS surfaces toolbar confirmation items from a pushed-in-sheet view + /// reliably only from macOS 26 onward. On older macOS we render the + /// "Add Source" action button inline in the form instead. + private var usesToolbarActionButton: Bool { + #if os(macOS) + if #available(macOS 26, *) { + return true + } else { + return false + } + #else + return false + #endif + } + // MARK: - Body var body: some View { @@ -133,25 +148,31 @@ struct AddSMBView: View { if let result = testResult { SourceTestResultSection(result: result) } + + if !usesToolbarActionButton { + actionSection + } } .formStyle(.grouped) .navigationTitle(String(localized: "sources.addSMB")) .toolbar { - ToolbarItem(placement: .confirmationAction) { - Button { - addSource() - } label: { - if isTesting { - HStack(spacing: 6) { - ProgressView().controlSize(.small) - Text(testProgress ?? String(localized: "sources.testing")) + if usesToolbarActionButton { + ToolbarItem(placement: .confirmationAction) { + Button { + addSource() + } label: { + if isTesting { + HStack(spacing: 6) { + ProgressView().controlSize(.small) + Text(testProgress ?? String(localized: "sources.testing")) + } + } else { + Text(String(localized: "sources.addSource")) } - } else { - Text(String(localized: "sources.addSource")) } + .disabled(!canAdd || isTesting) + .keyboardShortcut(.defaultAction) } - .disabled(!canAdd || isTesting) - .keyboardShortcut(.defaultAction) } } .onAppear { diff --git a/Yattee/Views/Settings/AddSource/AddWebDAVView.swift b/Yattee/Views/Settings/AddSource/AddWebDAVView.swift index 3211a568..a57d7dc4 100644 --- a/Yattee/Views/Settings/AddSource/AddWebDAVView.swift +++ b/Yattee/Views/Settings/AddSource/AddWebDAVView.swift @@ -37,6 +37,21 @@ struct AddWebDAVView: View { !name.isEmpty && !urlString.isEmpty && URL(string: urlString) != nil } + /// macOS surfaces toolbar confirmation items from a pushed-in-sheet view + /// reliably only from macOS 26 onward. On older macOS we render the + /// "Add Source" action button inline in the form instead. + private var usesToolbarActionButton: Bool { + #if os(macOS) + if #available(macOS 26, *) { + return true + } else { + return false + } + #else + return false + #endif + } + // MARK: - Body var body: some View { @@ -134,25 +149,31 @@ struct AddWebDAVView: View { if let result = testResult { SourceTestResultSection(result: result) } + + if !usesToolbarActionButton { + actionSection + } } .formStyle(.grouped) .navigationTitle(String(localized: "sources.addWebDAV")) .toolbar { - ToolbarItem(placement: .confirmationAction) { - Button { - addSource() - } label: { - if isTesting { - HStack(spacing: 6) { - ProgressView().controlSize(.small) - Text(testProgress ?? String(localized: "sources.testing")) + if usesToolbarActionButton { + ToolbarItem(placement: .confirmationAction) { + Button { + addSource() + } label: { + if isTesting { + HStack(spacing: 6) { + ProgressView().controlSize(.small) + Text(testProgress ?? String(localized: "sources.testing")) + } + } else { + Text(String(localized: "sources.addSource")) } - } else { - Text(String(localized: "sources.addSource")) } + .disabled(!canAdd || isTesting) + .keyboardShortcut(.defaultAction) } - .disabled(!canAdd || isTesting) - .keyboardShortcut(.defaultAction) } } .onAppear { diff --git a/Yattee/Views/Settings/SourcesListView.swift b/Yattee/Views/Settings/SourcesListView.swift index 355ae0fe..358af3c0 100644 --- a/Yattee/Views/Settings/SourcesListView.swift +++ b/Yattee/Views/Settings/SourcesListView.swift @@ -64,6 +64,16 @@ struct SourcesListView: View { .focusSection() } } + #elseif os(macOS) + VStack(spacing: 0) { + // macOS 15 does not surface a detail-pane NavigationStack's + // toolbar items to the window title bar, so the toolbar + // "Add Source" button is invisible there. Show an inline + // header button as a fallback on older macOS. On macOS 26+ + // the toolbar button works, so this header is omitted. + macOSLegacyAddHeader + sourcesInner + } #else sourcesInner #endif @@ -76,13 +86,15 @@ struct SourcesListView: View { #endif #if os(iOS) || os(macOS) .toolbar { - ToolbarItem(placement: .primaryAction) { - Button { - showingAddSheet = true - } label: { - Label(String(localized: "sources.addSource"), systemImage: "plus") + if showAddButtonInToolbar { + ToolbarItem(placement: .primaryAction) { + Button { + showingAddSheet = true + } label: { + Label(String(localized: "sources.addSource"), systemImage: "plus") + } + .accessibilityIdentifier("sources.addButton") } - .accessibilityIdentifier("sources.addButton") } } #endif @@ -122,6 +134,47 @@ struct SourcesListView: View { #endif } + /// Whether the "Add Source" button should be placed in the window/navigation + /// toolbar. On macOS this only works reliably from macOS 26 onward (see + /// `macOSLegacyAddHeader`); iOS always uses the toolbar. + private var showAddButtonInToolbar: Bool { + #if os(iOS) + return true + #elseif os(macOS) + if #available(macOS 26, *) { + return true + } else { + return false + } + #else + return false + #endif + } + + #if os(macOS) + // Inline fallback "Add Source" button for macOS versions where the + // detail-pane toolbar item is not shown (pre-macOS 26). Only needed when + // there are existing sources — the empty state already offers its own + // add button. + @ViewBuilder + private var macOSLegacyAddHeader: some View { + if !showAddButtonInToolbar, !isEmpty { + HStack { + Spacer() + Button { + showingAddSheet = true + } label: { + Label(String(localized: "sources.addSource"), systemImage: "plus") + } + .accessibilityIdentifier("sources.addButton") + } + .padding(.horizontal, 16) + .padding(.top, 12) + .padding(.bottom, 4) + } + } + #endif + @ViewBuilder private var sourcesInner: some View { if isEmpty {