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.
This commit is contained in:
Arkadiusz Fal
2026-04-17 01:51:25 +02:00
parent 6d3bea7678
commit e583aa3fd7
3 changed files with 74 additions and 8 deletions

View File

@@ -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()
}

View File

@@ -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])

View File

@@ -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 {