From e583aa3fd73cec43e8a16de8fc7db66c617bacad Mon Sep 17 00:00:00 2001 From: Arkadiusz Fal Date: Fri, 17 Apr 2026 01:51:25 +0200 Subject: [PATCH] Rework tvOS PeerTube browse and discovery sheets for sidebar layout Drop nested NavigationStack, .searchable, and close button from the PeerTube explore view on tvOS; use an inline search field plus Filters button so focus separates cleanly and the sidebar title is no longer overlapped. Keep the header visible across empty/error states so the query can be cleared. Hide the filters and scan-network sheet toolbars on tvOS, apply filter changes immediately, and add padding plus scrollClipDisabled so focused rows aren't clipped. --- .../Settings/NetworkShareDiscoverySheet.swift | 6 ++ .../Views/Settings/PeerTubeFiltersSheet.swift | 8 +++ .../PeerTubeInstancesExploreView.swift | 68 ++++++++++++++++--- 3 files changed, 74 insertions(+), 8 deletions(-) diff --git a/Yattee/Views/Settings/NetworkShareDiscoverySheet.swift b/Yattee/Views/Settings/NetworkShareDiscoverySheet.swift index 6318b2c7..8658638c 100644 --- a/Yattee/Views/Settings/NetworkShareDiscoverySheet.swift +++ b/Yattee/Views/Settings/NetworkShareDiscoverySheet.swift @@ -29,6 +29,11 @@ struct NetworkShareDiscoverySheet: View { var body: some View { NavigationStack { content + #if os(tvOS) + .scrollClipDisabled() + .padding(.horizontal, 40) + .padding(.vertical, 40) + #else .navigationTitle(String(localized: "discovery.title")) #if os(iOS) .navigationBarTitleDisplayMode(.inline) @@ -41,6 +46,7 @@ struct NetworkShareDiscoverySheet: View { } } } + #endif .onAppear { discoveryService?.startDiscovery() } diff --git a/Yattee/Views/Settings/PeerTubeFiltersSheet.swift b/Yattee/Views/Settings/PeerTubeFiltersSheet.swift index d52f771d..c3545c21 100644 --- a/Yattee/Views/Settings/PeerTubeFiltersSheet.swift +++ b/Yattee/Views/Settings/PeerTubeFiltersSheet.swift @@ -55,6 +55,13 @@ struct PeerTubeFiltersSheet: View { .disabled(filters.isDefault) } } + #if os(tvOS) + .scrollClipDisabled() + .padding(.horizontal, 40) + .padding(.vertical, 40) + .onChange(of: filters.language) { _, _ in onApply() } + .onChange(of: filters.country) { _, _ in onApply() } + #else .navigationTitle(String(localized: "peertube.explore.filters")) #if os(iOS) .navigationBarTitleDisplayMode(.inline) @@ -72,6 +79,7 @@ struct PeerTubeFiltersSheet: View { } } } + #endif } #if os(iOS) .presentationDetents([.medium]) diff --git a/Yattee/Views/Settings/PeerTubeInstancesExploreView.swift b/Yattee/Views/Settings/PeerTubeInstancesExploreView.swift index 3f7c9be3..7f5c9c6f 100644 --- a/Yattee/Views/Settings/PeerTubeInstancesExploreView.swift +++ b/Yattee/Views/Settings/PeerTubeInstancesExploreView.swift @@ -65,18 +65,27 @@ struct PeerTubeInstancesExploreView: View { } var body: some View { - NavigationStack { + Group { + #if os(tvOS) content - .navigationTitle(String(localized: "peertube.explore.title")) - #if os(iOS) - .navigationBarTitleDisplayMode(.inline) - #endif - .toolbar { toolbarContent } - .searchable(text: $searchText, prompt: Text(String(localized: "peertube.explore.search"))) .onChange(of: searchText) { _, _ in - // Reset display limit when search changes displayLimit = pageSize } + #else + NavigationStack { + content + .navigationTitle(String(localized: "peertube.explore.title")) + #if os(iOS) + .navigationBarTitleDisplayMode(.inline) + #endif + .toolbar { toolbarContent } + .searchable(text: $searchText, prompt: Text(String(localized: "peertube.explore.search"))) + .onChange(of: searchText) { _, _ in + // Reset display limit when search changes + displayLimit = pageSize + } + } + #endif } #if os(macOS) .frame(minWidth: 500, minHeight: 400) @@ -101,6 +110,20 @@ struct PeerTubeInstancesExploreView: View { @ViewBuilder private var content: some View { + #if os(tvOS) + VStack(spacing: 0) { + if !allInstances.isEmpty { + tvOSSearchHeader + } + contentBody + } + #else + contentBody + #endif + } + + @ViewBuilder + private var contentBody: some View { if isLoading && allInstances.isEmpty { ProgressView() .frame(maxWidth: .infinity, maxHeight: .infinity) @@ -130,6 +153,33 @@ struct PeerTubeInstancesExploreView: View { } } + #if os(tvOS) + @ViewBuilder + private var tvOSSearchHeader: some View { + HStack(spacing: 16) { + TextField( + "", + text: $searchText, + prompt: Text(String(localized: "peertube.explore.search")) + ) + .textFieldStyle(.plain) + .focusSection() + + Button { + showFiltersSheet = true + } label: { + Label( + String(localized: "peertube.explore.filters"), + systemImage: filters.isDefault ? "line.3.horizontal.decrease.circle" : "line.3.horizontal.decrease.circle.fill" + ) + } + .focusSection() + } + .padding(.horizontal, 16) + .padding(.vertical, 8) + } + #endif + private var instancesList: some View { List { // Instances @@ -164,6 +214,7 @@ struct PeerTubeInstancesExploreView: View { @ToolbarContentBuilder private var toolbarContent: some ToolbarContent { + #if !os(tvOS) ToolbarItem(placement: .confirmationAction) { Button(role: .cancel) { dismiss() @@ -172,6 +223,7 @@ struct PeerTubeInstancesExploreView: View { .labelStyle(.iconOnly) } } + #endif ToolbarItem(placement: .primaryAction) { Button {