Files
yattee/Yattee/Models/VideoListLayout.swift
Arkadiusz Fal debfdef26f Increase grid spacing on tvOS for focus effect clearance
The tvOS focus effect scales up the focused card, causing it to overlap
adjacent cards' text. Increase grid spacing from 32 to 48 points.
2026-04-18 20:38:01 +02:00

134 lines
4.0 KiB
Swift

//
// VideoListLayout.swift
// Yattee
//
// Layout options for video listing views.
//
import SwiftUI
/// Layout type for video lists.
enum VideoListLayout: String, CaseIterable {
case list
case grid
var displayName: LocalizedStringKey {
switch self {
case .list: "viewOptions.layout.list"
case .grid: "viewOptions.layout.grid"
}
}
var systemImage: String {
switch self {
case .list: "list.bullet"
case .grid: "square.grid.2x2"
}
}
}
// MARK: - Grid Layout Helpers
/// Constants for grid layout calculations.
enum GridConstants {
/// Minimum width for a video card thumbnail to remain usable.
static let minCardWidth: CGFloat = {
#if os(tvOS)
200
#else
100
#endif
}()
/// Spacing between grid items.
static let spacing: CGFloat = {
#if os(tvOS)
48
#else
12
#endif
}()
/// Horizontal padding for the grid container.
static let horizontalPadding: CGFloat = 32
/// Maximum allowed columns (to prevent excessive density).
static let maxAllowedColumns = 6
/// Threshold for compact card styling (columns >= this use compact mode).
static let compactThreshold = 3
}
/// Calculates the maximum number of grid columns that fit within a given width.
/// - Parameters:
/// - width: Available container width
/// - minCardWidth: Minimum width per card (defaults to GridConstants.minCardWidth)
/// - spacing: Spacing between cards (defaults to GridConstants.spacing)
/// - Returns: Maximum columns that fit, clamped between 1 and maxAllowedColumns
func maxGridColumns(
forWidth width: CGFloat,
minCardWidth: CGFloat = GridConstants.minCardWidth,
spacing: CGFloat = GridConstants.spacing
) -> Int {
let availableWidth = width - GridConstants.horizontalPadding
// Formula: availableWidth = (columns * minCardWidth) + ((columns - 1) * spacing)
// Solving for columns: columns = (availableWidth + spacing) / (minCardWidth + spacing)
let maxColumns = Int((availableWidth + spacing) / (minCardWidth + spacing))
return max(1, min(maxColumns, GridConstants.maxAllowedColumns))
}
/// Creates grid columns for a LazyVGrid with the specified count.
/// - Parameter count: Number of columns
/// - Returns: Array of flexible GridItems with top alignment
func makeGridColumns(count: Int) -> [GridItem] {
Array(repeating: GridItem(.flexible(), spacing: GridConstants.spacing, alignment: .top), count: max(1, count))
}
// MARK: - Grid Layout Configuration
/// Encapsulates grid layout calculations for views with grid/list layouts.
///
/// Use this to eliminate repeated computed properties across grid-enabled views.
/// Create an instance with the current view width and user-selected column count,
/// then use the computed properties for layout decisions.
///
/// Usage:
/// ```swift
/// @State private var viewWidth: CGFloat = 0
/// @AppStorage("myView.gridColumns") private var gridColumns = 2
///
/// private var gridConfig: GridLayoutConfiguration {
/// GridLayoutConfiguration(viewWidth: viewWidth, gridColumns: gridColumns)
/// }
///
/// // Then use:
/// // gridConfig.effectiveColumns - actual column count
/// // gridConfig.isCompactCards - whether to use compact card styling
/// // gridConfig.columns - GridItem array for LazyVGrid
/// // gridConfig.maxColumns - for ViewOptionsSheet
/// ```
struct GridLayoutConfiguration {
let viewWidth: CGFloat
let gridColumns: Int
/// Maximum columns that fit in the current width.
var maxColumns: Int {
maxGridColumns(forWidth: viewWidth)
}
/// Effective column count, clamped to valid range.
var effectiveColumns: Int {
min(max(1, gridColumns), max(1, maxColumns))
}
/// Whether cards should use compact styling (3+ columns).
var isCompactCards: Bool {
effectiveColumns >= GridConstants.compactThreshold
}
/// GridItem array for LazyVGrid columns parameter.
var columns: [GridItem] {
makeGridColumns(count: effectiveColumns)
}
}