mirror of
https://github.com/yattee/yattee.git
synced 2026-07-19 22:02:10 +00:00
Move instance browse pickers into macOS toolbar
The Popular/Trending tab picker now lives in the toolbar (principal placement) instead of inline above the content. Once a search is submitted it swaps to the search content type picker, with the search filters button alongside it as a popover, matching the global Search view. Items stay mounted invisible on macOS 26 so the toolbar layout stays stable while typing.
This commit is contained in:
@@ -170,14 +170,8 @@ struct InstanceBrowseView: View {
|
||||
|
||||
// Tab picker (hidden during search)
|
||||
if !isInSearchMode {
|
||||
Picker("", selection: $selectedTab) {
|
||||
ForEach(availableTabs) { tab in
|
||||
Label(tab.title, systemImage: tab.systemImage)
|
||||
.tag(tab)
|
||||
}
|
||||
}
|
||||
.pickerStyle(.segmented)
|
||||
.padding()
|
||||
tabPicker
|
||||
.padding()
|
||||
}
|
||||
|
||||
// Content
|
||||
@@ -248,27 +242,25 @@ struct InstanceBrowseView: View {
|
||||
.overlay(
|
||||
ScrollView {
|
||||
VStack(spacing: 0) {
|
||||
// Tab picker (hidden during search)
|
||||
// Tab picker (hidden during search; lives in the toolbar on macOS)
|
||||
#if !os(macOS)
|
||||
if !isInSearchMode {
|
||||
Picker("", selection: $selectedTab) {
|
||||
ForEach(availableTabs) { tab in
|
||||
Label(tab.title, systemImage: tab.systemImage)
|
||||
.tag(tab)
|
||||
}
|
||||
}
|
||||
.pickerStyle(.segmented)
|
||||
.padding()
|
||||
tabPicker
|
||||
.padding()
|
||||
}
|
||||
#endif
|
||||
|
||||
// Feed channel filter strip (hidden during search)
|
||||
if selectedTab == .feed && !feedSubscriptions.isEmpty && !isInSearchMode {
|
||||
feedChannelFilterStrip
|
||||
}
|
||||
|
||||
// Search filter strip (shown persistently after search submitted)
|
||||
// Search filter strip (shown persistently after search submitted; lives in the toolbar on macOS)
|
||||
#if !os(macOS)
|
||||
if isInSearchMode && (searchViewModel?.hasSearched ?? false) && instance.supportsSearchFilters {
|
||||
searchFiltersStrip
|
||||
}
|
||||
#endif
|
||||
|
||||
// Content
|
||||
Group {
|
||||
@@ -341,6 +333,45 @@ struct InstanceBrowseView: View {
|
||||
.toolbarTitleDisplayMode(.inlineLarge)
|
||||
.toolbar {
|
||||
#if os(macOS)
|
||||
if #available(macOS 26, *) {
|
||||
// Search filters button appears only while showing search results.
|
||||
// Items stay mounted (invisible/inert) otherwise so the toolbar layout is stable.
|
||||
ToolbarItem(placement: .navigation) {
|
||||
searchFiltersToolbarButton
|
||||
.opacity(showsSearchFiltersInToolbar ? 1 : 0)
|
||||
.disabled(!showsSearchFiltersInToolbar)
|
||||
.accessibilityHidden(!showsSearchFiltersInToolbar)
|
||||
}
|
||||
.sharedBackgroundVisibility(showsSearchFiltersInToolbar ? .automatic : .hidden)
|
||||
// Search content type picker while showing results, browse tab picker otherwise
|
||||
ToolbarItem(placement: .principal) {
|
||||
Group {
|
||||
if showsSearchFiltersInToolbar {
|
||||
searchContentTypePicker
|
||||
} else {
|
||||
tabPicker
|
||||
}
|
||||
}
|
||||
.fixedSize()
|
||||
.opacity(isToolbarPickerVisible ? 1 : 0)
|
||||
.disabled(!isToolbarPickerVisible)
|
||||
.accessibilityHidden(!isToolbarPickerVisible)
|
||||
}
|
||||
.sharedBackgroundVisibility(isToolbarPickerVisible ? .automatic : .hidden)
|
||||
} else if showsSearchFiltersInToolbar {
|
||||
ToolbarItem(placement: .navigation) {
|
||||
searchFiltersToolbarButton
|
||||
}
|
||||
ToolbarItem(placement: .principal) {
|
||||
searchContentTypePicker
|
||||
.fixedSize()
|
||||
}
|
||||
} else if !isInSearchMode {
|
||||
ToolbarItem(placement: .principal) {
|
||||
tabPicker
|
||||
.fixedSize()
|
||||
}
|
||||
}
|
||||
// Pin the trailing group (search field + toolbar buttons) to the right edge,
|
||||
// matching the global Search view.
|
||||
if #available(macOS 26, *) {
|
||||
@@ -440,23 +471,91 @@ struct InstanceBrowseView: View {
|
||||
searchViewModel?.fetchSuggestions(for: newValue)
|
||||
}
|
||||
}
|
||||
#if !os(macOS)
|
||||
.sheet(isPresented: $showFilterSheet) {
|
||||
SearchFiltersSheet(onApply: {
|
||||
Task {
|
||||
await searchViewModel?.search(query: searchText)
|
||||
}
|
||||
}, filters: Binding(
|
||||
get: { searchViewModel?.filters ?? .defaults },
|
||||
set: { searchViewModel?.filters = $0 }
|
||||
))
|
||||
searchFiltersSheetContent
|
||||
#if !os(tvOS)
|
||||
.presentationDetents([.medium, .large])
|
||||
.presentationDetents([.medium, .large])
|
||||
#endif
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
// MARK: - Computed Properties
|
||||
|
||||
private var searchFiltersSheetContent: some View {
|
||||
SearchFiltersSheet(onApply: {
|
||||
Task {
|
||||
await searchViewModel?.search(query: searchText)
|
||||
}
|
||||
}, filters: Binding(
|
||||
get: { searchViewModel?.filters ?? .defaults },
|
||||
set: { searchViewModel?.filters = $0 }
|
||||
))
|
||||
}
|
||||
|
||||
#if os(macOS)
|
||||
private var isShowingSearchResults: Bool {
|
||||
isInSearchMode && (searchViewModel?.hasSearched ?? false)
|
||||
}
|
||||
|
||||
private var showsSearchFiltersInToolbar: Bool {
|
||||
isShowingSearchResults && instance.supportsSearchFilters
|
||||
}
|
||||
|
||||
private var isToolbarPickerVisible: Bool {
|
||||
// Tab picker stays visible while typing; it swaps to the search content
|
||||
// type picker only once a search has been submitted
|
||||
!isShowingSearchResults || showsSearchFiltersInToolbar
|
||||
}
|
||||
|
||||
private var searchContentTypePicker: some View {
|
||||
Picker("", selection: Binding(
|
||||
get: { searchViewModel?.filters.type ?? .video },
|
||||
set: { searchViewModel?.filters.type = $0 }
|
||||
)) {
|
||||
ForEach(SearchContentType.allCases) { type in
|
||||
Text(type.title).tag(type)
|
||||
}
|
||||
}
|
||||
.pickerStyle(.segmented)
|
||||
.onChange(of: searchViewModel?.filters.type) { _, _ in
|
||||
Task {
|
||||
await searchViewModel?.search(query: searchText)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private var searchFiltersToolbarButton: some View {
|
||||
Button {
|
||||
showFilterSheet = true
|
||||
} label: {
|
||||
Label(String(localized: "search.filters"), systemImage: (searchViewModel?.filters.isDefault ?? true)
|
||||
? "line.3.horizontal.decrease.circle"
|
||||
: "line.3.horizontal.decrease.circle.fill")
|
||||
}
|
||||
.popover(isPresented: $showFilterSheet, arrowEdge: .bottom) {
|
||||
searchFiltersSheetContent
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
private var tabPicker: some View {
|
||||
Picker("", selection: $selectedTab) {
|
||||
ForEach(availableTabs) { tab in
|
||||
#if os(macOS)
|
||||
// Labels render icon-only in a macOS toolbar segmented control
|
||||
Text(tab.title)
|
||||
.tag(tab)
|
||||
#else
|
||||
Label(tab.title, systemImage: tab.systemImage)
|
||||
.tag(tab)
|
||||
#endif
|
||||
}
|
||||
}
|
||||
.pickerStyle(.segmented)
|
||||
}
|
||||
|
||||
private var availableTabs: [BrowseTab] {
|
||||
if instance.supportsFeed && isLoggedIn {
|
||||
// Playlists tab only available for Invidious (Piped playlists to be added in future)
|
||||
|
||||
Reference in New Issue
Block a user