Files
yattee/Yattee/Models/VideoListLayout.swift
2026-02-08 18:33:56 +01: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)
32
#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)
}
}