Files
yattee/Yattee/Views/Components/ViewOptionsSheet.swift
Arkadiusz Fal 310869fad8 Add horizontal padding to View Options sheet on tvOS
Prevents form rows from being cut off at the left and right edges
of the narrow tvOS sheet.
2026-04-18 20:38:01 +02:00

127 lines
4.1 KiB
Swift

//
// ViewOptionsSheet.swift
// Yattee
//
// Reusable sheet for customizing video list display options.
//
import SwiftUI
/// A sheet for customizing video list display options.
///
/// Shows layout picker (list/grid), size options, and filter toggles.
/// Reusable across different views with their own storage bindings.
struct ViewOptionsSheet: View {
@Environment(\.appEnvironment) private var appEnvironment
@Binding var layout: VideoListLayout
@Binding var rowStyle: VideoRowStyle
@Binding var gridColumns: Int
/// Optional binding for hide watched toggle (only shown if provided).
var hideWatched: Binding<Bool>?
/// Optional binding for channel strip size (only shown if provided).
var channelStripSize: Binding<ChannelStripSize>?
/// Maximum columns allowed based on current view width.
var maxGridColumns: Int
/// Effective columns clamped to valid range.
private var effectiveColumns: Int {
min(max(1, gridColumns), maxGridColumns)
}
var body: some View {
#if os(tvOS)
NavigationStack {
formContent
}
.padding(.horizontal, 40)
#else
formContent
#endif
}
private var formContent: some View {
Form {
// Single section with all options
Section {
// Layout picker (segmented)
Picker(selection: $layout) {
ForEach(VideoListLayout.allCases, id: \.self) { option in
Label(option.displayName, systemImage: option.systemImage)
.tag(option)
}
} label: {
Text("viewOptions.layout")
}
.pickerStyle(.segmented)
.listRowBackground(Color.clear)
.listRowInsets(EdgeInsets(top: 8, leading: 0, bottom: 8, trailing: 0))
// List-specific options
if layout == .list {
Picker("viewOptions.rowSize", selection: $rowStyle) {
Text("viewOptions.rowSize.compact").tag(VideoRowStyle.compact)
Text("viewOptions.rowSize.regular").tag(VideoRowStyle.regular)
Text("viewOptions.rowSize.large").tag(VideoRowStyle.large)
}
}
// Grid-specific options
if layout == .grid {
#if os(tvOS)
Picker("viewOptions.columns.header", selection: $gridColumns) {
ForEach(1...maxGridColumns, id: \.self) { count in
Text("\(count)").tag(count)
}
}
#else
Stepper(
"viewOptions.columns \(effectiveColumns)",
value: $gridColumns,
in: 1...maxGridColumns
)
#endif
}
// Filters (optional)
if let hideWatched = hideWatched {
Toggle("viewOptions.hideWatched", isOn: hideWatched)
}
// Channel Strip (subscriptions only)
if let channelStripSize = channelStripSize {
Picker("viewOptions.channelStrip", selection: channelStripSize) {
ForEach(ChannelStripSize.allCases, id: \.self) { size in
Text(size.displayName).tag(size)
}
}
}
}
}
#if os(iOS)
.presentationDetents([.height(260)])
.presentationDragIndicator(.visible)
#endif
}
}
// MARK: - Preview
#Preview {
@Previewable @State var layout: VideoListLayout = .grid
@Previewable @State var rowStyle: VideoRowStyle = .regular
@Previewable @State var gridColumns = 2
@Previewable @State var hideWatched = false
ViewOptionsSheet(
layout: $layout,
rowStyle: $rowStyle,
gridColumns: $gridColumns,
hideWatched: $hideWatched,
channelStripSize: nil,
maxGridColumns: 4
)
}