Refactor views

This commit is contained in:
Arkadiusz Fal
2026-02-09 01:13:02 +01:00
parent 8464464199
commit 612dce6b9f
43 changed files with 143 additions and 125 deletions

View File

@@ -142,10 +142,10 @@ struct ContentView: View {
/// Compact width (iPhone, iPad Stage Manager small): CompactTabView with settings-based customization /// Compact width (iPhone, iPad Stage Manager small): CompactTabView with settings-based customization
/// Regular width (iPad full, iPad larger windows): UnifiedTabView with sidebar adaptable /// Regular width (iPad full, iPad larger windows): UnifiedTabView with sidebar adaptable
struct iOS18AdaptiveTabView: View { struct iOS18AdaptiveTabView: View {
let appEnvironment: AppEnvironment
@Environment(\.horizontalSizeClass) private var horizontalSizeClass @Environment(\.horizontalSizeClass) private var horizontalSizeClass
// Mini player sheet state (synced with NavigationCoordinator) let appEnvironment: AppEnvironment
@State private var showingMiniPlayerQueueSheet = false @State private var showingMiniPlayerQueueSheet = false
@State private var showingMiniPlayerPlaylistSheet = false @State private var showingMiniPlayerPlaylistSheet = false
@@ -267,10 +267,10 @@ struct iOS18AdaptiveTabView: View {
/// Uses UnifiedTabView with sidebarAdaptable for regular width, CompactTabView for compact. /// Uses UnifiedTabView with sidebarAdaptable for regular width, CompactTabView for compact.
@available(iOS 26.1, *) @available(iOS 26.1, *)
struct iOS26AdaptiveTabView: View { struct iOS26AdaptiveTabView: View {
let appEnvironment: AppEnvironment
@Environment(\.horizontalSizeClass) private var horizontalSizeClass @Environment(\.horizontalSizeClass) private var horizontalSizeClass
// Mini player sheet state (synced with NavigationCoordinator) let appEnvironment: AppEnvironment
@State private var showingMiniPlayerQueueSheet = false @State private var showingMiniPlayerQueueSheet = false
@State private var showingMiniPlayerPlaylistSheet = false @State private var showingMiniPlayerPlaylistSheet = false

View File

@@ -28,6 +28,9 @@ private struct CachedChannelHeader {
} }
struct ChannelView: View { struct ChannelView: View {
@Environment(\.appEnvironment) private var appEnvironment
@Environment(\.horizontalSizeClass) private var horizontalSizeClass
let channelID: String let channelID: String
let source: ContentSource let source: ContentSource
/// URL for external channel extraction (nil for YouTube/Invidious channels) /// URL for external channel extraction (nil for YouTube/Invidious channels)
@@ -38,9 +41,7 @@ struct ChannelView: View {
channelURL != nil channelURL != nil
} }
@Environment(\.appEnvironment) private var appEnvironment
@Namespace private var sheetTransition @Namespace private var sheetTransition
@Environment(\.horizontalSizeClass) private var horizontalSizeClass
@State private var channel: Channel? @State private var channel: Channel?
@State private var selectedTab: ChannelTab = .videos @State private var selectedTab: ChannelTab = .videos
@State private var isLoading = true @State private var isLoading = true

View File

@@ -10,13 +10,13 @@ import SwiftUI
/// A bookmark card for grid/horizontal scroll layouts. /// A bookmark card for grid/horizontal scroll layouts.
/// Displays video content via VideoCardView with additional tags/notes. /// Displays video content via VideoCardView with additional tags/notes.
struct BookmarkCardView: View { struct BookmarkCardView: View {
@Environment(\.appEnvironment) private var appEnvironment
let bookmark: Bookmark let bookmark: Bookmark
var watchProgress: Double? = nil var watchProgress: Double? = nil
/// Use compact styling for dense grids (3+ columns). /// Use compact styling for dense grids (3+ columns).
var isCompact: Bool = false var isCompact: Bool = false
@Environment(\.appEnvironment) private var appEnvironment
private var accentColor: Color { private var accentColor: Color {
appEnvironment?.settingsManager.accentColor.color ?? .accentColor appEnvironment?.settingsManager.accentColor.color ?? .accentColor
} }

View File

@@ -12,6 +12,8 @@ import SwiftUI
/// Automatically handles DeArrow integration. /// Automatically handles DeArrow integration.
/// Supports optional queue context for auto-play functionality. /// Supports optional queue context for auto-play functionality.
struct BookmarkRowView: View { struct BookmarkRowView: View {
@Environment(\.appEnvironment) private var appEnvironment
let bookmark: Bookmark let bookmark: Bookmark
var style: VideoRowStyle = .regular var style: VideoRowStyle = .regular
var watchProgress: Double? = nil var watchProgress: Double? = nil
@@ -24,8 +26,6 @@ struct BookmarkRowView: View {
var videoIndex: Int? = nil var videoIndex: Int? = nil
var loadMoreVideos: LoadMoreVideosCallback? = nil var loadMoreVideos: LoadMoreVideosCallback? = nil
@Environment(\.appEnvironment) private var appEnvironment
private var accentColor: Color { private var accentColor: Color {
appEnvironment?.settingsManager.accentColor.color ?? .accentColor appEnvironment?.settingsManager.accentColor.color ?? .accentColor
} }

View File

@@ -9,11 +9,11 @@ import SwiftUI
/// Read-only display of bookmark tags with optional overflow indicator. /// Read-only display of bookmark tags with optional overflow indicator.
struct BookmarkTagsView: View { struct BookmarkTagsView: View {
@Environment(\.appEnvironment) private var appEnvironment
let tags: [String] let tags: [String]
var maxVisible: Int = 3 var maxVisible: Int = 3
@Environment(\.appEnvironment) private var appEnvironment
private var accentColor: Color { private var accentColor: Color {
appEnvironment?.settingsManager.accentColor.color ?? .accentColor appEnvironment?.settingsManager.accentColor.color ?? .accentColor
} }

View File

@@ -9,12 +9,13 @@ import SwiftUI
import NukeUI import NukeUI
struct CommentView: View { struct CommentView: View {
@Environment(\.appEnvironment) private var appEnvironment
let comment: Comment let comment: Comment
let videoID: String? let videoID: String?
let source: ContentSource? let source: ContentSource?
let isReply: Bool let isReply: Bool
@Environment(\.appEnvironment) private var appEnvironment
@State private var replies: [Comment] = [] @State private var replies: [Comment] = []
@State private var isLoadingReplies = false @State private var isLoadingReplies = false
@State private var showReplies = false @State private var showReplies = false

View File

@@ -19,6 +19,8 @@ import SwiftUI
/// tracks dictionary access per-key, so this view only re-renders when THIS video's /// tracks dictionary access per-key, so this view only re-renders when THIS video's
/// progress changes - not when any other download progresses. /// progress changes - not when any other download progresses.
struct DeArrowVideoThumbnail: View { struct DeArrowVideoThumbnail: View {
@Environment(\.appEnvironment) private var appEnvironment
let video: Video let video: Video
var cornerRadius: CGFloat = 8 var cornerRadius: CGFloat = 8
@@ -26,7 +28,6 @@ struct DeArrowVideoThumbnail: View {
var duration: String? = nil var duration: String? = nil
var durationAlignment: Alignment = .bottomLeading var durationAlignment: Alignment = .bottomLeading
@Environment(\.appEnvironment) private var appEnvironment
@State private var isWatched = false @State private var isWatched = false
private var deArrowProvider: DeArrowBrandingProvider? { private var deArrowProvider: DeArrowBrandingProvider? {

View File

@@ -8,12 +8,12 @@
import SwiftUI import SwiftUI
struct ExpandableText: View { struct ExpandableText: View {
let text: String
var lineLimit: Int = 2
@Binding var isExpanded: Bool
@Environment(\.font) private var font @Environment(\.font) private var font
let text: String
var lineLimit: Int = 2
@Binding var isExpanded: Bool
@State private var fullHeight: CGFloat = 0 @State private var fullHeight: CGFloat = 0
@State private var truncatedHeight: CGFloat = 0 @State private var truncatedHeight: CGFloat = 0

View File

@@ -8,6 +8,8 @@
import SwiftUI import SwiftUI
struct ExpandedCommentsView: View { struct ExpandedCommentsView: View {
@Environment(\.appEnvironment) private var appEnvironment
let videoID: String let videoID: String
let onClose: () -> Void let onClose: () -> Void
var onDismissOffsetChanged: ((CGFloat) -> Void)? = nil var onDismissOffsetChanged: ((CGFloat) -> Void)? = nil
@@ -18,8 +20,6 @@ struct ExpandedCommentsView: View {
var onDragChanged: ((CGFloat) -> Void)? = nil var onDragChanged: ((CGFloat) -> Void)? = nil
var onDragEnded: ((CGFloat, CGFloat) -> Void)? = nil var onDragEnded: ((CGFloat, CGFloat) -> Void)? = nil
@Environment(\.appEnvironment) private var appEnvironment
@State private var showScrollButton = false @State private var showScrollButton = false
@State private var scrollToTopTrigger: Int = 0 @State private var scrollToTopTrigger: Int = 0
@State private var scrollBounceOffset: CGFloat = 0 @State private var scrollBounceOffset: CGFloat = 0

View File

@@ -9,12 +9,12 @@ import SwiftUI
/// A sheet that appears when tapping a video, offering options to play or add to queue. /// A sheet that appears when tapping a video, offering options to play or add to queue.
struct QueueActionSheet: View { struct QueueActionSheet: View {
let video: Video
var queueSource: QueueSource?
@Environment(\.dismiss) private var dismiss @Environment(\.dismiss) private var dismiss
@Environment(\.appEnvironment) private var appEnvironment @Environment(\.appEnvironment) private var appEnvironment
let video: Video
var queueSource: QueueSource?
var body: some View { var body: some View {
VStack(spacing: 0) { VStack(spacing: 0) {
// Drag indicator // Drag indicator

View File

@@ -9,13 +9,13 @@ import SwiftUI
/// A sheet that appears when playing a partially watched video, offering options to resume or start over. /// A sheet that appears when playing a partially watched video, offering options to resume or start over.
struct ResumeActionSheet: View { struct ResumeActionSheet: View {
@Environment(\.dismiss) private var dismiss
let video: Video let video: Video
let resumeTime: TimeInterval let resumeTime: TimeInterval
let onContinue: () -> Void let onContinue: () -> Void
let onStartOver: () -> Void let onStartOver: () -> Void
@Environment(\.dismiss) private var dismiss
var body: some View { var body: some View {
VStack(spacing: 0) { VStack(spacing: 0) {
// Drag indicator // Drag indicator

View File

@@ -9,9 +9,10 @@ import SwiftUI
/// Shows source badge only when user has multiple sources configured. /// Shows source badge only when user has multiple sources configured.
struct SourceBadge: View { struct SourceBadge: View {
let source: ContentSource
@Environment(\.appEnvironment) private var appEnvironment @Environment(\.appEnvironment) private var appEnvironment
let source: ContentSource
var body: some View { var body: some View {
if shouldShowBadge { if shouldShowBadge {
HStack(spacing: 2) { HStack(spacing: 2) {

View File

@@ -8,11 +8,11 @@
import SwiftUI import SwiftUI
struct TagInputView: View { struct TagInputView: View {
@Environment(\.appEnvironment) private var appEnvironment
@Binding var tags: [String] @Binding var tags: [String]
var isFocused: Bool = false var isFocused: Bool = false
@Environment(\.appEnvironment) private var appEnvironment
@State private var inputText: String = "" @State private var inputText: String = ""
@State private var showMaxTagsWarning = false @State private var showMaxTagsWarning = false
@State private var showMaxLengthWarning = false @State private var showMaxLengthWarning = false

View File

@@ -18,6 +18,8 @@ struct ResumeSheetData: Identifiable {
/// When queue is enabled and not empty, shows a sheet with queue options. /// When queue is enabled and not empty, shows a sheet with queue options.
/// When queue is empty or disabled, plays the video directly and queues subsequent videos. /// When queue is empty or disabled, plays the video directly and queues subsequent videos.
struct TappableVideoModifier: ViewModifier { struct TappableVideoModifier: ViewModifier {
@Environment(\.appEnvironment) private var appEnvironment
let video: Video let video: Video
var startTime: Double? = nil var startTime: Double? = nil
var customActions: [VideoContextAction] = [] var customActions: [VideoContextAction] = []
@@ -31,16 +33,15 @@ struct TappableVideoModifier: ViewModifier {
var videoList: [Video]? = nil var videoList: [Video]? = nil
/// Index of this video in the list /// Index of this video in the list
var videoIndex: Int? = nil var videoIndex: Int? = nil
/// Callback to load more videos via continuation /// Callback to load more videos via continuation
nonisolated(unsafe) var loadMoreVideos: LoadMoreVideosCallback? = nil nonisolated(unsafe) var loadMoreVideos: LoadMoreVideosCallback? = nil
@Environment(\.appEnvironment) private var appEnvironment
@State private var showingQueueSheet = false @State private var showingQueueSheet = false
// Resume action sheet state - using item-based sheet to ensure data is available when presented // Resume action sheet state - using item-based sheet to ensure data is available when presented
@State private var resumeSheetData: ResumeSheetData? = nil @State private var resumeSheetData: ResumeSheetData? = nil
// Password alert state (for WebDAV sources) // Password alert state (for WebDAV sources)
@State private var showingPasswordAlert = false @State private var showingPasswordAlert = false
@State private var sourceNeedingPassword: MediaSource? @State private var sourceNeedingPassword: MediaSource?

View File

@@ -13,6 +13,9 @@ import SwiftUI
/// Download status is automatically shown from the download manager. /// Download status is automatically shown from the download manager.
/// On iOS/macOS, supports configurable tap zones for thumbnail and text area. /// On iOS/macOS, supports configurable tap zones for thumbnail and text area.
struct VideoCardView: View { struct VideoCardView: View {
@Environment(\.appEnvironment) private var appEnvironment
@Environment(\.videoQueueContext) private var videoQueueContext
let video: Video let video: Video
var watchProgress: Double? = nil var watchProgress: Double? = nil
/// Use compact styling for dense grids (3+ columns). /// Use compact styling for dense grids (3+ columns).
@@ -22,9 +25,6 @@ struct VideoCardView: View {
/// Custom duration text to show on thumbnail (e.g., remaining time). If nil, uses video.formattedDuration. /// Custom duration text to show on thumbnail (e.g., remaining time). If nil, uses video.formattedDuration.
var customDuration: String? = nil var customDuration: String? = nil
@Environment(\.appEnvironment) private var appEnvironment
@Environment(\.videoQueueContext) private var videoQueueContext
// Platform-specific fonts // Platform-specific fonts
#if os(tvOS) #if os(tvOS)
private var titleFont: Font { isCompact ? .subheadline : .body } private var titleFont: Font { isCompact ? .subheadline : .body }

View File

@@ -41,13 +41,14 @@ enum VideoContextMenuContext {
/// View modifier that attaches VideoContextMenu and its required sheets. /// View modifier that attaches VideoContextMenu and its required sheets.
struct VideoContextMenuModifier: ViewModifier { struct VideoContextMenuModifier: ViewModifier {
@Environment(\.appEnvironment) private var appEnvironment
let video: Video let video: Video
var customActions: [VideoContextAction] = [] var customActions: [VideoContextAction] = []
var context: VideoContextMenuContext = .default var context: VideoContextMenuContext = .default
var startTime: Double? = nil var startTime: Double? = nil
var watchProgress: Double? = nil var watchProgress: Double? = nil
@Environment(\.appEnvironment) private var appEnvironment
@State private var showingPlaylistSheet = false @State private var showingPlaylistSheet = false
@State private var showingDownloadSheet = false @State private var showingDownloadSheet = false
@State private var showingDeleteDownloadConfirmation = false @State private var showingDeleteDownloadConfirmation = false
@@ -128,6 +129,8 @@ struct VideoContextMenuModifier: ViewModifier {
/// The actual menu content (uses bindings from parent for sheet presentation). /// The actual menu content (uses bindings from parent for sheet presentation).
/// All observable values are snapshotted at init time to prevent redraws during playback. /// All observable values are snapshotted at init time to prevent redraws during playback.
struct VideoContextMenuContent: View { struct VideoContextMenuContent: View {
@Environment(\.appEnvironment) private var appEnvironment
let video: Video let video: Video
var customActions: [VideoContextAction] = [] var customActions: [VideoContextAction] = []
var context: VideoContextMenuContext = .default var context: VideoContextMenuContext = .default
@@ -137,8 +140,6 @@ struct VideoContextMenuContent: View {
@Binding var showingDeleteDownloadConfirmation: Bool @Binding var showingDeleteDownloadConfirmation: Bool
@Binding var downloadToDelete: Download? @Binding var downloadToDelete: Download?
@Environment(\.appEnvironment) private var appEnvironment
// MARK: - Snapshotted Values (captured at init to prevent observation) // MARK: - Snapshotted Values (captured at init to prevent observation)
/// Snapshotted remote control enabled state. /// Snapshotted remote control enabled state.
@@ -592,13 +593,14 @@ extension View {
/// A dropdown menu view for videos, showing context menu actions. /// A dropdown menu view for videos, showing context menu actions.
/// Used in player views where the video is already playing. /// Used in player views where the video is already playing.
struct VideoContextMenuView: View { struct VideoContextMenuView: View {
@Environment(\.appEnvironment) private var appEnvironment
let video: Video let video: Video
let accentColor: Color let accentColor: Color
var buttonSize: CGFloat = 32 var buttonSize: CGFloat = 32
var buttonBackgroundStyle: ButtonBackgroundStyle = .none var buttonBackgroundStyle: ButtonBackgroundStyle = .none
var theme: ControlsTheme = .dark var theme: ControlsTheme = .dark
@Environment(\.appEnvironment) private var appEnvironment
@State private var showingPlaylistSheet = false @State private var showingPlaylistSheet = false
@State private var showingDownloadSheet = false @State private var showingDownloadSheet = false
@State private var showingDeleteDownloadConfirmation = false @State private var showingDeleteDownloadConfirmation = false

View File

@@ -13,6 +13,9 @@ import SwiftUI
/// Download status is automatically shown from the download manager. /// Download status is automatically shown from the download manager.
/// On iOS/macOS, supports configurable tap zones for thumbnail and text area. /// On iOS/macOS, supports configurable tap zones for thumbnail and text area.
struct VideoRowView: View { struct VideoRowView: View {
@Environment(\.appEnvironment) private var appEnvironment
@Environment(\.videoQueueContext) private var videoQueueContext
let video: Video let video: Video
var style: VideoRowStyle = .regular var style: VideoRowStyle = .regular
var watchProgress: Double? = nil var watchProgress: Double? = nil
@@ -24,9 +27,6 @@ struct VideoRowView: View {
/// When true, disables internal tap handling so parent view can handle all taps. /// When true, disables internal tap handling so parent view can handle all taps.
var disableInternalTapHandling: Bool = false var disableInternalTapHandling: Bool = false
@Environment(\.appEnvironment) private var appEnvironment
@Environment(\.videoQueueContext) private var videoQueueContext
// Platform-specific fonts // Platform-specific fonts
#if os(tvOS) #if os(tvOS)
private var titleFont: Font { private var titleFont: Font {

View File

@@ -10,11 +10,12 @@ import SwiftUI
#if !os(tvOS) #if !os(tvOS)
/// View modifier that applies user-configurable swipe actions plus fixed context-specific actions. /// View modifier that applies user-configurable swipe actions plus fixed context-specific actions.
struct VideoSwipeActionsModifier: ViewModifier { struct VideoSwipeActionsModifier: ViewModifier {
@Environment(\.appEnvironment) private var appEnvironment
@Environment(\.videoQueueContext) private var queueContext
let video: Video let video: Video
var fixedActions: [SwipeAction] = [] var fixedActions: [SwipeAction] = []
@Environment(\.appEnvironment) private var appEnvironment
@Environment(\.videoQueueContext) private var queueContext
@State private var showingPlaylistSheet = false @State private var showingPlaylistSheet = false
@State private var showingDownloadSheet = false @State private var showingDownloadSheet = false

View File

@@ -13,10 +13,12 @@ import SwiftUI
/// For completed downloads, uses VideoRowView with tap zone support (thumbnail plays, text opens info). /// For completed downloads, uses VideoRowView with tap zone support (thumbnail plays, text opens info).
/// For active downloads, shows custom progress UI with no tap actions. /// For active downloads, shows custom progress UI with no tap actions.
struct DownloadRowView: View { struct DownloadRowView: View {
@Environment(\.appEnvironment) private var appEnvironment
let download: Download let download: Download
let isActive: Bool let isActive: Bool
var onDelete: (() -> Void)? = nil var onDelete: (() -> Void)? = nil
// Queue context (optional, enables auto-play when provided) // Queue context (optional, enables auto-play when provided)
var queueSource: QueueSource? = nil var queueSource: QueueSource? = nil
var sourceLabel: String? = nil var sourceLabel: String? = nil
@@ -24,8 +26,6 @@ struct DownloadRowView: View {
var videoIndex: Int? = nil var videoIndex: Int? = nil
var loadMoreVideos: LoadMoreVideosCallback? = nil var loadMoreVideos: LoadMoreVideosCallback? = nil
@Environment(\.appEnvironment) private var appEnvironment
// Cache watch progress to avoid CoreData fetches on every re-render // Cache watch progress to avoid CoreData fetches on every re-render
@State private var cachedWatchProgress: Double? @State private var cachedWatchProgress: Double?
@State private var cachedWatchedSeconds: TimeInterval? @State private var cachedWatchedSeconds: TimeInterval?

View File

@@ -339,14 +339,14 @@ private struct ActiveDownloadsSectionContentView: View {
/// Only accesses manager.completedDownloads, so won't re-render when activeDownloads progress updates. /// Only accesses manager.completedDownloads, so won't re-render when activeDownloads progress updates.
/// Generates rows directly for VideoListContainer (no Section wrapper). /// Generates rows directly for VideoListContainer (no Section wrapper).
private struct CompletedDownloadsSectionContentView: View { private struct CompletedDownloadsSectionContentView: View {
@Environment(\.appEnvironment) private var appEnvironment
let manager: DownloadManager let manager: DownloadManager
let settings: DownloadSettings let settings: DownloadSettings
let searchText: String let searchText: String
let listStyle: VideoListStyle let listStyle: VideoListStyle
let isGroupedMode: Bool let isGroupedMode: Bool
@Environment(\.appEnvironment) private var appEnvironment
private var completedFiltered: [Download] { private var completedFiltered: [Download] {
guard !searchText.isEmpty else { return manager.completedDownloads } guard !searchText.isEmpty else { return manager.completedDownloads }
let query = searchText.lowercased() let query = searchText.lowercased()

View File

@@ -8,6 +8,11 @@
import SwiftUI import SwiftUI
struct HomeShortcutCardView<StatusIndicator: View>: View { struct HomeShortcutCardView<StatusIndicator: View>: View {
@Environment(\.dynamicTypeSize) private var dynamicTypeSize
#if os(tvOS)
@Environment(\.isFocused) private var isFocused
#endif
let icon: String let icon: String
let title: String let title: String
let count: Int let count: Int
@@ -28,11 +33,6 @@ struct HomeShortcutCardView<StatusIndicator: View>: View {
self.statusIndicator = statusIndicator self.statusIndicator = statusIndicator
} }
@Environment(\.dynamicTypeSize) private var dynamicTypeSize
#if os(tvOS)
@Environment(\.isFocused) private var isFocused
#endif
// Platform-specific styling // Platform-specific styling
#if os(tvOS) #if os(tvOS)
private let iconSize: CGFloat = 36 private let iconSize: CGFloat = 36

View File

@@ -8,11 +8,12 @@
import SwiftUI import SwiftUI
struct InstanceBrowseView: View { struct InstanceBrowseView: View {
@Environment(\.appEnvironment) private var appEnvironment
let instance: Instance let instance: Instance
let initialTab: BrowseTab? let initialTab: BrowseTab?
@Environment(\.appEnvironment) private var appEnvironment
@Namespace private var sheetTransition
@Namespace private var sheetTransition
@State private var selectedTab: BrowseTab = .popular @State private var selectedTab: BrowseTab = .popular
@State private var popularVideos: [Video] = [] @State private var popularVideos: [Video] = []
@State private var trendingVideos: [Video] = [] @State private var trendingVideos: [Video] = []
@@ -282,14 +283,14 @@ struct InstanceBrowseView: View {
} }
} }
.sheet(isPresented: $showFilterSheet) { .sheet(isPresented: $showFilterSheet) {
SearchFiltersSheet(filters: Binding( SearchFiltersSheet(onApply: {
get: { searchViewModel?.filters ?? .defaults },
set: { searchViewModel?.filters = $0 }
)) {
Task { Task {
await searchViewModel?.search(query: searchText) await searchViewModel?.search(query: searchText)
} }
} }, filters: Binding(
get: { searchViewModel?.filters ?? .defaults },
set: { searchViewModel?.filters = $0 }
))
#if !os(tvOS) #if !os(tvOS)
.presentationDetents([.medium, .large]) .presentationDetents([.medium, .large])
#endif #endif

View File

@@ -8,10 +8,11 @@
import SwiftUI import SwiftUI
struct MediaBrowserView: View { struct MediaBrowserView: View {
@Environment(\.appEnvironment) private var appEnvironment
let source: MediaSource let source: MediaSource
let initialPath: String let initialPath: String
@Environment(\.appEnvironment) private var appEnvironment
@Namespace private var sheetTransition @Namespace private var sheetTransition
@State private var currentPath: String @State private var currentPath: String
@State private var files: [MediaFile] = [] @State private var files: [MediaFile] = []
@@ -103,10 +104,10 @@ struct MediaBrowserView: View {
} }
.sheet(isPresented: $showViewOptions) { .sheet(isPresented: $showViewOptions) {
MediaBrowserViewOptionsSheet( MediaBrowserViewOptionsSheet(
sourceType: source.type,
sortOrder: $sortOrder, sortOrder: $sortOrder,
sortAscending: $sortAscending, sortAscending: $sortAscending,
showOnlyPlayable: $showOnlyPlayable, showOnlyPlayable: $showOnlyPlayable
sourceType: source.type
) )
.liquidGlassSheetContent(sourceID: "mediaBrowserViewOptions", in: sheetTransition) .liquidGlassSheetContent(sourceID: "mediaBrowserViewOptions", in: sheetTransition)
} }

View File

@@ -8,12 +8,13 @@
import SwiftUI import SwiftUI
struct MediaBrowserViewOptionsSheet: View { struct MediaBrowserViewOptionsSheet: View {
@Environment(\.dismiss) private var dismiss
let sourceType: MediaSourceType
@Binding var sortOrder: MediaBrowserSortOrder @Binding var sortOrder: MediaBrowserSortOrder
@Binding var sortAscending: Bool @Binding var sortAscending: Bool
@Binding var showOnlyPlayable: Bool @Binding var showOnlyPlayable: Bool
let sourceType: MediaSourceType
@Environment(\.dismiss) private var dismiss
private var availableSortOptions: [MediaBrowserSortOrder] { private var availableSortOptions: [MediaBrowserSortOrder] {
MediaBrowserSortOrder.availableOptions(for: sourceType) MediaBrowserSortOrder.availableOptions(for: sourceType)
@@ -89,10 +90,10 @@ struct MediaBrowserViewOptionsSheet: View {
@Previewable @State var showOnlyPlayable = false @Previewable @State var showOnlyPlayable = false
MediaBrowserViewOptionsSheet( MediaBrowserViewOptionsSheet(
sourceType: .localFolder,
sortOrder: $sortOrder, sortOrder: $sortOrder,
sortAscending: $sortAscending, sortAscending: $sortAscending,
showOnlyPlayable: $showOnlyPlayable, showOnlyPlayable: $showOnlyPlayable
sourceType: .localFolder
) )
} }
@@ -102,9 +103,9 @@ struct MediaBrowserViewOptionsSheet: View {
@Previewable @State var showOnlyPlayable = false @Previewable @State var showOnlyPlayable = false
MediaBrowserViewOptionsSheet( MediaBrowserViewOptionsSheet(
sourceType: .webdav,
sortOrder: $sortOrder, sortOrder: $sortOrder,
sortAscending: $sortAscending, sortAscending: $sortAscending,
showOnlyPlayable: $showOnlyPlayable, showOnlyPlayable: $showOnlyPlayable
sourceType: .webdav
) )
} }

View File

@@ -13,10 +13,11 @@ import SwiftUI
#if os(iOS) #if os(iOS)
struct UnifiedTabView: View { struct UnifiedTabView: View {
@Binding var selectedTab: AppTab
@Environment(\.appEnvironment) private var appEnvironment @Environment(\.appEnvironment) private var appEnvironment
@Environment(\.horizontalSizeClass) private var horizontalSizeClass @Environment(\.horizontalSizeClass) private var horizontalSizeClass
@Binding var selectedTab: AppTab
// Sidebar manager for dynamic content // Sidebar manager for dynamic content
@State private var sidebarManager = SidebarManager() @State private var sidebarManager = SidebarManager()
@@ -297,9 +298,10 @@ extension MiniPlayerMinimizeBehavior {
#if os(macOS) #if os(macOS)
struct UnifiedTabView: View { struct UnifiedTabView: View {
@Binding var selectedTab: AppTab
@Environment(\.appEnvironment) private var appEnvironment @Environment(\.appEnvironment) private var appEnvironment
@Binding var selectedTab: AppTab
// Sidebar manager for dynamic content // Sidebar manager for dynamic content
@State private var sidebarManager = SidebarManager() @State private var sidebarManager = SidebarManager()
@@ -517,9 +519,10 @@ struct UnifiedTabView: View {
#if os(tvOS) #if os(tvOS)
struct UnifiedTabView: View { struct UnifiedTabView: View {
@Binding var selectedTab: AppTab
@Environment(\.appEnvironment) private var appEnvironment @Environment(\.appEnvironment) private var appEnvironment
@Binding var selectedTab: AppTab
// Sidebar manager for dynamic content // Sidebar manager for dynamic content
@State private var sidebarManager = SidebarManager() @State private var sidebarManager = SidebarManager()

View File

@@ -10,10 +10,10 @@ import SwiftUI
struct OnboardingMigrationScreen: View { struct OnboardingMigrationScreen: View {
@Environment(\.appEnvironment) private var appEnvironment @Environment(\.appEnvironment) private var appEnvironment
@Binding var items: [LegacyImportItem]
let onContinue: () -> Void let onContinue: () -> Void
let onSkip: () -> Void let onSkip: () -> Void
@Binding var items: [LegacyImportItem]
@State private var isImporting = false @State private var isImporting = false
@State private var importProgress: Double = 0.0 @State private var importProgress: Double = 0.0
@State private var showingResultSheet = false @State private var showingResultSheet = false
@@ -352,9 +352,9 @@ struct OnboardingMigrationScreen: View {
] ]
OnboardingMigrationScreen( OnboardingMigrationScreen(
items: $items,
onContinue: {}, onContinue: {},
onSkip: {} onSkip: {},
items: $items
) )
.appEnvironment(.preview) .appEnvironment(.preview)
} }

View File

@@ -45,9 +45,9 @@ struct OnboardingSheetView: View {
// Migration screen is now page 2 (when present) // Migration screen is now page 2 (when present)
if hasLegacyData, let binding = Binding($legacyItems) { if hasLegacyData, let binding = Binding($legacyItems) {
OnboardingMigrationScreen( OnboardingMigrationScreen(
items: binding,
onContinue: advanceToNextPage, onContinue: advanceToNextPage,
onSkip: advanceToNextPage onSkip: advanceToNextPage,
items: binding
) )
.tag(2) .tag(2)
} }

View File

@@ -8,12 +8,12 @@
import SwiftUI import SwiftUI
struct ChaptersView: View { struct ChaptersView: View {
@Environment(\.dismiss) private var dismiss
let chapters: [VideoChapter] let chapters: [VideoChapter]
let currentTime: TimeInterval let currentTime: TimeInterval
let storyboard: Storyboard? let storyboard: Storyboard?
let onChapterTap: (VideoChapter) async -> Void let onChapterTap: (VideoChapter) async -> Void
@Environment(\.dismiss) private var dismiss
@State private var sheetsLoaded = false @State private var sheetsLoaded = false
var body: some View { var body: some View {

View File

@@ -19,6 +19,9 @@ private struct PanelHeightKey: PreferenceKey {
} }
struct FloatingDetailsPanel: View { struct FloatingDetailsPanel: View {
@Environment(\.appEnvironment) private var appEnvironment
@Environment(\.colorScheme) private var systemColorScheme
let onPinToggle: () -> Void let onPinToggle: () -> Void
let onAlignmentToggle: () -> Void let onAlignmentToggle: () -> Void
let isPinned: Bool let isPinned: Bool
@@ -33,9 +36,6 @@ struct FloatingDetailsPanel: View {
// Player controls layout for pill settings // Player controls layout for pill settings
let playerControlsLayout: PlayerControlsLayout let playerControlsLayout: PlayerControlsLayout
@Environment(\.appEnvironment) private var appEnvironment
@Environment(\.colorScheme) private var systemColorScheme
@State private var isCommentsExpanded: Bool = false @State private var isCommentsExpanded: Bool = false
@State private var showFormattedDate = false @State private var showFormattedDate = false
@State private var showOriginalTitle = false @State private var showOriginalTitle = false

View File

@@ -12,14 +12,14 @@ import SwiftUI
/// A floating button that toggles the panel between left and right sides. /// A floating button that toggles the panel between left and right sides.
/// Mirrors the style of PanelPinButton: 36pt glass circle, same shadow and haptic. /// Mirrors the style of PanelPinButton: 36pt glass circle, same shadow and haptic.
struct PanelAlignmentButton: View { struct PanelAlignmentButton: View {
@Environment(\.appEnvironment) private var appEnvironment
let panelSide: FloatingPanelSide let panelSide: FloatingPanelSide
let onAlignmentToggle: () -> Void let onAlignmentToggle: () -> Void
/// Whether the drag handle is currently active (being dragged or hovered). /// Whether the drag handle is currently active (being dragged or hovered).
@Binding var isDragHandleActive: Bool @Binding var isDragHandleActive: Bool
@Environment(\.appEnvironment) private var appEnvironment
/// Button diameter /// Button diameter
private static let buttonSize: CGFloat = 36 private static let buttonSize: CGFloat = 36

View File

@@ -12,6 +12,8 @@ import SwiftUI
/// A floating pin button that appears on the divider edge between player and panel. /// A floating pin button that appears on the divider edge between player and panel.
/// Features auto-hide behavior with 3s timer, reappearing on drag handle interaction. /// Features auto-hide behavior with 3s timer, reappearing on drag handle interaction.
struct PanelPinButton: View { struct PanelPinButton: View {
@Environment(\.appEnvironment) private var appEnvironment
let isPinned: Bool let isPinned: Bool
let panelSide: FloatingPanelSide let panelSide: FloatingPanelSide
let onPinToggle: () -> Void let onPinToggle: () -> Void
@@ -19,8 +21,6 @@ struct PanelPinButton: View {
/// Whether the drag handle is currently active (being dragged or hovered). /// Whether the drag handle is currently active (being dragged or hovered).
@Binding var isDragHandleActive: Bool @Binding var isDragHandleActive: Bool
@Environment(\.appEnvironment) private var appEnvironment
/// Button diameter /// Button diameter
private static let buttonSize: CGFloat = 36 private static let buttonSize: CGFloat = 36

View File

@@ -11,6 +11,8 @@ import SwiftUI
#if os(iOS) #if os(iOS)
struct PortraitDetailsPanel: View { struct PortraitDetailsPanel: View {
@Environment(\.appEnvironment) private var appEnvironment
let onChannelTap: (() -> Void)? let onChannelTap: (() -> Void)?
let playerControlsLayout: PlayerControlsLayout let playerControlsLayout: PlayerControlsLayout
let onFullscreen: (() -> Void)? let onFullscreen: (() -> Void)?
@@ -19,8 +21,6 @@ struct PortraitDetailsPanel: View {
var onDragChanged: ((CGFloat) -> Void)? var onDragChanged: ((CGFloat) -> Void)?
var onDragEnded: ((CGFloat, CGFloat) -> Void)? var onDragEnded: ((CGFloat, CGFloat) -> Void)?
var onDragCancelled: (() -> Void)? var onDragCancelled: (() -> Void)?
@Environment(\.appEnvironment) private var appEnvironment
@GestureState private var isDraggingHandle: Bool = false @GestureState private var isDraggingHandle: Bool = false
@State private var isCommentsExpanded: Bool = false @State private var isCommentsExpanded: Bool = false
@State private var showFormattedDate = false @State private var showFormattedDate = false

View File

@@ -8,6 +8,11 @@
import SwiftUI import SwiftUI
struct QualitySelectorView: View { struct QualitySelectorView: View {
// MARK: - Environment
@Environment(\.dismiss) var dismiss
@Environment(\.appEnvironment) private var appEnvironment
// MARK: - Properties // MARK: - Properties
let streams: [Stream] let streams: [Stream]
@@ -39,10 +44,7 @@ struct QualitySelectorView: View {
/// Whether to show the segmented tab picker (false for focused single-tab mode) /// Whether to show the segmented tab picker (false for focused single-tab mode)
var showTabPicker: Bool = true var showTabPicker: Bool = true
// MARK: - Environment & State // MARK: - State
@Environment(\.dismiss) var dismiss
@Environment(\.appEnvironment) private var appEnvironment
@State var selectedTab: QualitySelectorTab = .video @State var selectedTab: QualitySelectorTab = .video
@State var selectedVideoStream: Stream? @State var selectedVideoStream: Stream?
@State var selectedAudioStream: Stream? @State var selectedAudioStream: Stream?

View File

@@ -13,6 +13,8 @@ import SwiftUI
#if os(iOS) || os(macOS) #if os(iOS) || os(macOS)
struct WideScreenPlayerLayout<PlayerContent: View>: View { struct WideScreenPlayerLayout<PlayerContent: View>: View {
@Environment(\.appEnvironment) private var appEnvironment
let playerControlsLayout: PlayerControlsLayout let playerControlsLayout: PlayerControlsLayout
@ViewBuilder let playerContent: ( @ViewBuilder let playerContent: (
@@ -30,8 +32,6 @@ struct WideScreenPlayerLayout<PlayerContent: View>: View {
// Callbacks for panel actions // Callbacks for panel actions
let onChannelTap: (() -> Void)? let onChannelTap: (() -> Void)?
let onFullscreen: (() -> Void)? let onFullscreen: (() -> Void)?
@Environment(\.appEnvironment) private var appEnvironment
@State private var controlsVisible = false @State private var controlsVisible = false
@State private var isPanelVisible = false // Local state synced with settingsManager @State private var isPanelVisible = false // Local state synced with settingsManager
@State private var lastVideoId: String? // Track video ID to detect actual video changes @State private var lastVideoId: String? // Track video ID to detect actual video changes

View File

@@ -9,12 +9,12 @@ import SwiftUI
#if !os(tvOS) #if !os(tvOS)
struct BatchDownloadQualitySheet: View { struct BatchDownloadQualitySheet: View {
let videoCount: Int
let onConfirm: (DownloadQuality, Bool) -> Void
@Environment(\.dismiss) private var dismiss @Environment(\.dismiss) private var dismiss
@Environment(\.appEnvironment) private var appEnvironment @Environment(\.appEnvironment) private var appEnvironment
let videoCount: Int
let onConfirm: (DownloadQuality, Bool) -> Void
@State private var selectedQuality: DownloadQuality = .best @State private var selectedQuality: DownloadQuality = .best
@State private var includeSubtitles = false @State private var includeSubtitles = false

View File

@@ -24,11 +24,11 @@ struct PlaylistFormSheet: View {
} }
} }
@Environment(\.dismiss) private var dismiss
let mode: Mode let mode: Mode
let onSave: (String, String?) -> Void let onSave: (String, String?) -> Void
@Environment(\.dismiss) private var dismiss
@State private var title: String = "" @State private var title: String = ""
@State private var descriptionText: String = "" @State private var descriptionText: String = ""

View File

@@ -46,11 +46,11 @@ enum PlaylistSource: Hashable {
} }
struct UnifiedPlaylistDetailView: View { struct UnifiedPlaylistDetailView: View {
let source: PlaylistSource
@Environment(\.appEnvironment) private var appEnvironment @Environment(\.appEnvironment) private var appEnvironment
@Environment(\.dismiss) private var dismiss @Environment(\.dismiss) private var dismiss
let source: PlaylistSource
// MARK: - Shared State // MARK: - Shared State
@State private var title: String @State private var title: String

View File

@@ -144,17 +144,17 @@ struct SearchView: View {
} }
.searchable(text: searchTextBinding, prompt: Text(String(localized: "search.placeholder"))) .searchable(text: searchTextBinding, prompt: Text(String(localized: "search.placeholder")))
.sheet(isPresented: $showFilterSheet) { .sheet(isPresented: $showFilterSheet) {
SearchFiltersSheet(filters: Binding( SearchFiltersSheet(onApply: {
if hasResults {
Task { await searchViewModel?.search(query: searchTextBinding.wrappedValue) }
}
}, filters: Binding(
get: { searchViewModel?.filters ?? .defaults }, get: { searchViewModel?.filters ?? .defaults },
set: { newFilters in set: { newFilters in
searchViewModel?.filters = newFilters searchViewModel?.filters = newFilters
saveFilters(newFilters) saveFilters(newFilters)
} }
), onApply: { ))
if hasResults {
Task { await searchViewModel?.search(query: searchTextBinding.wrappedValue) }
}
})
.presentationDetents([.medium, .large]) .presentationDetents([.medium, .large])
} }
.sheet(isPresented: $showViewOptions) { .sheet(isPresented: $showViewOptions) {
@@ -1169,10 +1169,12 @@ struct SearchView: View {
// MARK: - Search Filters Sheet // MARK: - Search Filters Sheet
struct SearchFiltersSheet: View { struct SearchFiltersSheet: View {
@Binding var filters: SearchFilters
let onApply: () -> Void
@Environment(\.dismiss) private var dismiss @Environment(\.dismiss) private var dismiss
let onApply: () -> Void
@Binding var filters: SearchFilters
var body: some View { var body: some View {
NavigationStack { NavigationStack {
Form { Form {

View File

@@ -8,11 +8,11 @@
import SwiftUI import SwiftUI
struct SettingsView: View { struct SettingsView: View {
var showCloseButton: Bool = true
@Environment(\.dismiss) private var dismiss @Environment(\.dismiss) private var dismiss
@Environment(\.appEnvironment) private var appEnvironment @Environment(\.appEnvironment) private var appEnvironment
var showCloseButton: Bool = true
#if os(macOS) #if os(macOS)
@State private var selectedSection: SettingsSection? = .sources @State private var selectedSection: SettingsSection? = .sources
#endif #endif

View File

@@ -9,11 +9,6 @@ import SwiftUI
#if !os(tvOS) #if !os(tvOS)
struct DownloadQualitySheet: View { struct DownloadQualitySheet: View {
let video: Video
var streams: [Stream] = []
var captions: [Caption] = []
var dislikeCount: Int?
enum DownloadTab: String, CaseIterable { enum DownloadTab: String, CaseIterable {
case video case video
case audio case audio
@@ -31,6 +26,11 @@ struct DownloadQualitySheet: View {
@Environment(\.dismiss) private var dismiss @Environment(\.dismiss) private var dismiss
@Environment(\.appEnvironment) private var appEnvironment @Environment(\.appEnvironment) private var appEnvironment
let video: Video
var streams: [Stream] = []
var captions: [Caption] = []
var dislikeCount: Int?
@State private var selectedTab: DownloadTab = .video @State private var selectedTab: DownloadTab = .video
@State private var selectedVideoStream: Stream? @State private var selectedVideoStream: Stream?
@State private var selectedAudioStream: Stream? @State private var selectedAudioStream: Stream?

View File

@@ -10,11 +10,11 @@ import SwiftUI
/// View for extracting and playing videos from external sites (non-YouTube/PeerTube). /// View for extracting and playing videos from external sites (non-YouTube/PeerTube).
/// Uses Yattee Server's yt-dlp integration to extract video information. /// Uses Yattee Server's yt-dlp integration to extract video information.
struct ExternalVideoView: View { struct ExternalVideoView: View {
let url: URL
@Environment(\.appEnvironment) private var appEnvironment @Environment(\.appEnvironment) private var appEnvironment
@Environment(\.dismiss) private var dismiss @Environment(\.dismiss) private var dismiss
let url: URL
@State private var isLoading = true @State private var isLoading = true
@State private var errorMessage: String? @State private var errorMessage: String?
@State private var shouldDismissWhenPlayerExpands = false @State private var shouldDismissWhenPlayerExpands = false

View File

@@ -9,11 +9,11 @@ import SwiftUI
import NukeUI import NukeUI
struct PlaylistSelectorSheet: View { struct PlaylistSelectorSheet: View {
let video: Video
@Environment(\.dismiss) private var dismiss @Environment(\.dismiss) private var dismiss
@Environment(\.appEnvironment) private var appEnvironment @Environment(\.appEnvironment) private var appEnvironment
let video: Video
@State private var playlists: [LocalPlaylist] = [] @State private var playlists: [LocalPlaylist] = []
@State private var showingNewPlaylist = false @State private var showingNewPlaylist = false
@State private var pendingPlaylistTitle: String? @State private var pendingPlaylistTitle: String?

View File

@@ -15,11 +15,11 @@ enum VideoInfoInitMode: Sendable {
} }
struct VideoInfoView: View { struct VideoInfoView: View {
private let initMode: VideoInfoInitMode
@Environment(\.appEnvironment) private var appEnvironment @Environment(\.appEnvironment) private var appEnvironment
@Environment(\.videoQueueContext) private var videoQueueContext @Environment(\.videoQueueContext) private var videoQueueContext
private let initMode: VideoInfoInitMode
// Video loading state (for videoID mode) // Video loading state (for videoID mode)
@State private var loadedVideo: Video? @State private var loadedVideo: Video?
@State private var isLoadingInitialVideo = false @State private var isLoadingInitialVideo = false