From 612dce6b9fefc99c4aab53639f90218c48a7c584 Mon Sep 17 00:00:00 2001 From: Arkadiusz Fal Date: Mon, 9 Feb 2026 01:13:02 +0100 Subject: [PATCH] Refactor views --- Yattee/ContentView.swift | 8 ++++---- Yattee/Views/Channel/ChannelView.swift | 5 +++-- Yattee/Views/Components/BookmarkCardView.swift | 4 ++-- Yattee/Views/Components/BookmarkRowView.swift | 4 ++-- Yattee/Views/Components/BookmarkTagsView.swift | 4 ++-- Yattee/Views/Components/CommentView.swift | 3 ++- .../Components/DeArrowVideoThumbnail.swift | 3 ++- Yattee/Views/Components/ExpandableText.swift | 8 ++++---- .../Components/ExpandedCommentsView.swift | 4 ++-- Yattee/Views/Components/QueueActionSheet.swift | 6 +++--- .../Views/Components/ResumeActionSheet.swift | 4 ++-- Yattee/Views/Components/SourceBadge.swift | 3 ++- Yattee/Views/Components/TagInputView.swift | 4 ++-- .../Components/TappableVideoModifier.swift | 9 +++++---- Yattee/Views/Components/VideoCardView.swift | 6 +++--- Yattee/Views/Components/VideoContextMenu.swift | 10 ++++++---- Yattee/Views/Components/VideoRowView.swift | 6 +++--- .../Components/VideoSwipeActionsModifier.swift | 5 +++-- Yattee/Views/Downloads/DownloadRowView.swift | 6 +++--- Yattee/Views/Downloads/DownloadsView.swift | 4 ++-- Yattee/Views/Home/HomeShortcutCardView.swift | 10 +++++----- .../Views/Instances/InstanceBrowseView.swift | 15 ++++++++------- .../Views/MediaBrowser/MediaBrowserView.swift | 7 ++++--- .../MediaBrowserViewOptionsSheet.swift | 15 ++++++++------- Yattee/Views/Navigation/UnifiedTabView.swift | 9 ++++++--- .../Onboarding/OnboardingMigrationScreen.swift | 6 +++--- .../Views/Onboarding/OnboardingSheetView.swift | 4 ++-- Yattee/Views/Player/ChaptersView.swift | 4 ++-- Yattee/Views/Player/FloatingDetailsPanel.swift | 6 +++--- Yattee/Views/Player/PanelAlignmentButton.swift | 4 ++-- Yattee/Views/Player/PanelPinButton.swift | 4 ++-- Yattee/Views/Player/PortraitDetailsPanel.swift | 4 ++-- Yattee/Views/Player/QualitySelectorView.swift | 10 ++++++---- .../Views/Player/WideScreenPlayerLayout.swift | 4 ++-- .../Playlist/BatchDownloadQualitySheet.swift | 6 +++--- Yattee/Views/Playlist/PlaylistFormSheet.swift | 4 ++-- .../Playlist/UnifiedPlaylistDetailView.swift | 4 ++-- Yattee/Views/Search/SearchView.swift | 18 ++++++++++-------- Yattee/Views/Settings/SettingsView.swift | 4 ++-- Yattee/Views/Video/DownloadQualitySheet.swift | 10 +++++----- Yattee/Views/Video/ExternalVideoView.swift | 4 ++-- Yattee/Views/Video/PlaylistSelectorSheet.swift | 4 ++-- Yattee/Views/Video/VideoInfoView.swift | 6 +++--- 43 files changed, 143 insertions(+), 125 deletions(-) diff --git a/Yattee/ContentView.swift b/Yattee/ContentView.swift index f7aea63d..5c861dd5 100644 --- a/Yattee/ContentView.swift +++ b/Yattee/ContentView.swift @@ -142,10 +142,10 @@ struct ContentView: View { /// Compact width (iPhone, iPad Stage Manager small): CompactTabView with settings-based customization /// Regular width (iPad full, iPad larger windows): UnifiedTabView with sidebar adaptable struct iOS18AdaptiveTabView: View { - let appEnvironment: AppEnvironment @Environment(\.horizontalSizeClass) private var horizontalSizeClass - // Mini player sheet state (synced with NavigationCoordinator) + let appEnvironment: AppEnvironment + @State private var showingMiniPlayerQueueSheet = false @State private var showingMiniPlayerPlaylistSheet = false @@ -267,10 +267,10 @@ struct iOS18AdaptiveTabView: View { /// Uses UnifiedTabView with sidebarAdaptable for regular width, CompactTabView for compact. @available(iOS 26.1, *) struct iOS26AdaptiveTabView: View { - let appEnvironment: AppEnvironment @Environment(\.horizontalSizeClass) private var horizontalSizeClass - // Mini player sheet state (synced with NavigationCoordinator) + let appEnvironment: AppEnvironment + @State private var showingMiniPlayerQueueSheet = false @State private var showingMiniPlayerPlaylistSheet = false diff --git a/Yattee/Views/Channel/ChannelView.swift b/Yattee/Views/Channel/ChannelView.swift index 09d4bfa1..ecb3ad46 100644 --- a/Yattee/Views/Channel/ChannelView.swift +++ b/Yattee/Views/Channel/ChannelView.swift @@ -28,6 +28,9 @@ private struct CachedChannelHeader { } struct ChannelView: View { + @Environment(\.appEnvironment) private var appEnvironment + @Environment(\.horizontalSizeClass) private var horizontalSizeClass + let channelID: String let source: ContentSource /// URL for external channel extraction (nil for YouTube/Invidious channels) @@ -38,9 +41,7 @@ struct ChannelView: View { channelURL != nil } - @Environment(\.appEnvironment) private var appEnvironment @Namespace private var sheetTransition - @Environment(\.horizontalSizeClass) private var horizontalSizeClass @State private var channel: Channel? @State private var selectedTab: ChannelTab = .videos @State private var isLoading = true diff --git a/Yattee/Views/Components/BookmarkCardView.swift b/Yattee/Views/Components/BookmarkCardView.swift index 16b3c405..e42ae3ec 100644 --- a/Yattee/Views/Components/BookmarkCardView.swift +++ b/Yattee/Views/Components/BookmarkCardView.swift @@ -10,13 +10,13 @@ import SwiftUI /// A bookmark card for grid/horizontal scroll layouts. /// Displays video content via VideoCardView with additional tags/notes. struct BookmarkCardView: View { + @Environment(\.appEnvironment) private var appEnvironment + let bookmark: Bookmark var watchProgress: Double? = nil /// Use compact styling for dense grids (3+ columns). var isCompact: Bool = false - @Environment(\.appEnvironment) private var appEnvironment - private var accentColor: Color { appEnvironment?.settingsManager.accentColor.color ?? .accentColor } diff --git a/Yattee/Views/Components/BookmarkRowView.swift b/Yattee/Views/Components/BookmarkRowView.swift index b0b4e55a..24e299a6 100644 --- a/Yattee/Views/Components/BookmarkRowView.swift +++ b/Yattee/Views/Components/BookmarkRowView.swift @@ -12,6 +12,8 @@ import SwiftUI /// Automatically handles DeArrow integration. /// Supports optional queue context for auto-play functionality. struct BookmarkRowView: View { + @Environment(\.appEnvironment) private var appEnvironment + let bookmark: Bookmark var style: VideoRowStyle = .regular var watchProgress: Double? = nil @@ -24,8 +26,6 @@ struct BookmarkRowView: View { var videoIndex: Int? = nil var loadMoreVideos: LoadMoreVideosCallback? = nil - @Environment(\.appEnvironment) private var appEnvironment - private var accentColor: Color { appEnvironment?.settingsManager.accentColor.color ?? .accentColor } diff --git a/Yattee/Views/Components/BookmarkTagsView.swift b/Yattee/Views/Components/BookmarkTagsView.swift index 26622da0..129bb3e2 100644 --- a/Yattee/Views/Components/BookmarkTagsView.swift +++ b/Yattee/Views/Components/BookmarkTagsView.swift @@ -9,11 +9,11 @@ import SwiftUI /// Read-only display of bookmark tags with optional overflow indicator. struct BookmarkTagsView: View { + @Environment(\.appEnvironment) private var appEnvironment + let tags: [String] var maxVisible: Int = 3 - @Environment(\.appEnvironment) private var appEnvironment - private var accentColor: Color { appEnvironment?.settingsManager.accentColor.color ?? .accentColor } diff --git a/Yattee/Views/Components/CommentView.swift b/Yattee/Views/Components/CommentView.swift index 8df0279e..f384c3da 100644 --- a/Yattee/Views/Components/CommentView.swift +++ b/Yattee/Views/Components/CommentView.swift @@ -9,12 +9,13 @@ import SwiftUI import NukeUI struct CommentView: View { + @Environment(\.appEnvironment) private var appEnvironment + let comment: Comment let videoID: String? let source: ContentSource? let isReply: Bool - @Environment(\.appEnvironment) private var appEnvironment @State private var replies: [Comment] = [] @State private var isLoadingReplies = false @State private var showReplies = false diff --git a/Yattee/Views/Components/DeArrowVideoThumbnail.swift b/Yattee/Views/Components/DeArrowVideoThumbnail.swift index 05440f4f..893c6fdf 100644 --- a/Yattee/Views/Components/DeArrowVideoThumbnail.swift +++ b/Yattee/Views/Components/DeArrowVideoThumbnail.swift @@ -19,6 +19,8 @@ import SwiftUI /// tracks dictionary access per-key, so this view only re-renders when THIS video's /// progress changes - not when any other download progresses. struct DeArrowVideoThumbnail: View { + @Environment(\.appEnvironment) private var appEnvironment + let video: Video var cornerRadius: CGFloat = 8 @@ -26,7 +28,6 @@ struct DeArrowVideoThumbnail: View { var duration: String? = nil var durationAlignment: Alignment = .bottomLeading - @Environment(\.appEnvironment) private var appEnvironment @State private var isWatched = false private var deArrowProvider: DeArrowBrandingProvider? { diff --git a/Yattee/Views/Components/ExpandableText.swift b/Yattee/Views/Components/ExpandableText.swift index d63a985b..3ef46028 100644 --- a/Yattee/Views/Components/ExpandableText.swift +++ b/Yattee/Views/Components/ExpandableText.swift @@ -8,12 +8,12 @@ import SwiftUI struct ExpandableText: View { - let text: String - var lineLimit: Int = 2 - @Binding var isExpanded: Bool - @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 truncatedHeight: CGFloat = 0 diff --git a/Yattee/Views/Components/ExpandedCommentsView.swift b/Yattee/Views/Components/ExpandedCommentsView.swift index 6d64e4d5..b21ef6fb 100644 --- a/Yattee/Views/Components/ExpandedCommentsView.swift +++ b/Yattee/Views/Components/ExpandedCommentsView.swift @@ -8,6 +8,8 @@ import SwiftUI struct ExpandedCommentsView: View { + @Environment(\.appEnvironment) private var appEnvironment + let videoID: String let onClose: () -> Void var onDismissOffsetChanged: ((CGFloat) -> Void)? = nil @@ -18,8 +20,6 @@ struct ExpandedCommentsView: View { var onDragChanged: ((CGFloat) -> Void)? = nil var onDragEnded: ((CGFloat, CGFloat) -> Void)? = nil - @Environment(\.appEnvironment) private var appEnvironment - @State private var showScrollButton = false @State private var scrollToTopTrigger: Int = 0 @State private var scrollBounceOffset: CGFloat = 0 diff --git a/Yattee/Views/Components/QueueActionSheet.swift b/Yattee/Views/Components/QueueActionSheet.swift index 107014ec..61891c72 100644 --- a/Yattee/Views/Components/QueueActionSheet.swift +++ b/Yattee/Views/Components/QueueActionSheet.swift @@ -9,12 +9,12 @@ import SwiftUI /// A sheet that appears when tapping a video, offering options to play or add to queue. struct QueueActionSheet: View { - let video: Video - var queueSource: QueueSource? - @Environment(\.dismiss) private var dismiss @Environment(\.appEnvironment) private var appEnvironment + let video: Video + var queueSource: QueueSource? + var body: some View { VStack(spacing: 0) { // Drag indicator diff --git a/Yattee/Views/Components/ResumeActionSheet.swift b/Yattee/Views/Components/ResumeActionSheet.swift index 82e99e8d..ecb5867d 100644 --- a/Yattee/Views/Components/ResumeActionSheet.swift +++ b/Yattee/Views/Components/ResumeActionSheet.swift @@ -9,13 +9,13 @@ import SwiftUI /// A sheet that appears when playing a partially watched video, offering options to resume or start over. struct ResumeActionSheet: View { + @Environment(\.dismiss) private var dismiss + let video: Video let resumeTime: TimeInterval let onContinue: () -> Void let onStartOver: () -> Void - @Environment(\.dismiss) private var dismiss - var body: some View { VStack(spacing: 0) { // Drag indicator diff --git a/Yattee/Views/Components/SourceBadge.swift b/Yattee/Views/Components/SourceBadge.swift index a71fa52d..bb048c0e 100644 --- a/Yattee/Views/Components/SourceBadge.swift +++ b/Yattee/Views/Components/SourceBadge.swift @@ -9,9 +9,10 @@ import SwiftUI /// Shows source badge only when user has multiple sources configured. struct SourceBadge: View { - let source: ContentSource @Environment(\.appEnvironment) private var appEnvironment + let source: ContentSource + var body: some View { if shouldShowBadge { HStack(spacing: 2) { diff --git a/Yattee/Views/Components/TagInputView.swift b/Yattee/Views/Components/TagInputView.swift index b8aaf3b8..6756decd 100644 --- a/Yattee/Views/Components/TagInputView.swift +++ b/Yattee/Views/Components/TagInputView.swift @@ -8,11 +8,11 @@ import SwiftUI struct TagInputView: View { + @Environment(\.appEnvironment) private var appEnvironment + @Binding var tags: [String] var isFocused: Bool = false - @Environment(\.appEnvironment) private var appEnvironment - @State private var inputText: String = "" @State private var showMaxTagsWarning = false @State private var showMaxLengthWarning = false diff --git a/Yattee/Views/Components/TappableVideoModifier.swift b/Yattee/Views/Components/TappableVideoModifier.swift index d936d727..45510701 100644 --- a/Yattee/Views/Components/TappableVideoModifier.swift +++ b/Yattee/Views/Components/TappableVideoModifier.swift @@ -18,6 +18,8 @@ struct ResumeSheetData: Identifiable { /// 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. struct TappableVideoModifier: ViewModifier { + @Environment(\.appEnvironment) private var appEnvironment + let video: Video var startTime: Double? = nil var customActions: [VideoContextAction] = [] @@ -31,16 +33,15 @@ struct TappableVideoModifier: ViewModifier { var videoList: [Video]? = nil /// Index of this video in the list var videoIndex: Int? = nil - + /// Callback to load more videos via continuation nonisolated(unsafe) var loadMoreVideos: LoadMoreVideosCallback? = nil - @Environment(\.appEnvironment) private var appEnvironment @State private var showingQueueSheet = false - + // Resume action sheet state - using item-based sheet to ensure data is available when presented @State private var resumeSheetData: ResumeSheetData? = nil - + // Password alert state (for WebDAV sources) @State private var showingPasswordAlert = false @State private var sourceNeedingPassword: MediaSource? diff --git a/Yattee/Views/Components/VideoCardView.swift b/Yattee/Views/Components/VideoCardView.swift index 7edf9a77..52c1cad3 100644 --- a/Yattee/Views/Components/VideoCardView.swift +++ b/Yattee/Views/Components/VideoCardView.swift @@ -13,6 +13,9 @@ import SwiftUI /// Download status is automatically shown from the download manager. /// On iOS/macOS, supports configurable tap zones for thumbnail and text area. struct VideoCardView: View { + @Environment(\.appEnvironment) private var appEnvironment + @Environment(\.videoQueueContext) private var videoQueueContext + let video: Video var watchProgress: Double? = nil /// 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. var customDuration: String? = nil - @Environment(\.appEnvironment) private var appEnvironment - @Environment(\.videoQueueContext) private var videoQueueContext - // Platform-specific fonts #if os(tvOS) private var titleFont: Font { isCompact ? .subheadline : .body } diff --git a/Yattee/Views/Components/VideoContextMenu.swift b/Yattee/Views/Components/VideoContextMenu.swift index f1ff24c6..b83f5fbc 100644 --- a/Yattee/Views/Components/VideoContextMenu.swift +++ b/Yattee/Views/Components/VideoContextMenu.swift @@ -41,13 +41,14 @@ enum VideoContextMenuContext { /// View modifier that attaches VideoContextMenu and its required sheets. struct VideoContextMenuModifier: ViewModifier { + @Environment(\.appEnvironment) private var appEnvironment + let video: Video var customActions: [VideoContextAction] = [] var context: VideoContextMenuContext = .default var startTime: Double? = nil var watchProgress: Double? = nil - @Environment(\.appEnvironment) private var appEnvironment @State private var showingPlaylistSheet = false @State private var showingDownloadSheet = false @State private var showingDeleteDownloadConfirmation = false @@ -128,6 +129,8 @@ struct VideoContextMenuModifier: ViewModifier { /// The actual menu content (uses bindings from parent for sheet presentation). /// All observable values are snapshotted at init time to prevent redraws during playback. struct VideoContextMenuContent: View { + @Environment(\.appEnvironment) private var appEnvironment + let video: Video var customActions: [VideoContextAction] = [] var context: VideoContextMenuContext = .default @@ -137,8 +140,6 @@ struct VideoContextMenuContent: View { @Binding var showingDeleteDownloadConfirmation: Bool @Binding var downloadToDelete: Download? - @Environment(\.appEnvironment) private var appEnvironment - // MARK: - Snapshotted Values (captured at init to prevent observation) /// Snapshotted remote control enabled state. @@ -592,13 +593,14 @@ extension View { /// A dropdown menu view for videos, showing context menu actions. /// Used in player views where the video is already playing. struct VideoContextMenuView: View { + @Environment(\.appEnvironment) private var appEnvironment + let video: Video let accentColor: Color var buttonSize: CGFloat = 32 var buttonBackgroundStyle: ButtonBackgroundStyle = .none var theme: ControlsTheme = .dark - @Environment(\.appEnvironment) private var appEnvironment @State private var showingPlaylistSheet = false @State private var showingDownloadSheet = false @State private var showingDeleteDownloadConfirmation = false diff --git a/Yattee/Views/Components/VideoRowView.swift b/Yattee/Views/Components/VideoRowView.swift index c29dd881..1a4108a6 100644 --- a/Yattee/Views/Components/VideoRowView.swift +++ b/Yattee/Views/Components/VideoRowView.swift @@ -13,6 +13,9 @@ import SwiftUI /// Download status is automatically shown from the download manager. /// On iOS/macOS, supports configurable tap zones for thumbnail and text area. struct VideoRowView: View { + @Environment(\.appEnvironment) private var appEnvironment + @Environment(\.videoQueueContext) private var videoQueueContext + let video: Video var style: VideoRowStyle = .regular var watchProgress: Double? = nil @@ -24,9 +27,6 @@ struct VideoRowView: View { /// When true, disables internal tap handling so parent view can handle all taps. var disableInternalTapHandling: Bool = false - @Environment(\.appEnvironment) private var appEnvironment - @Environment(\.videoQueueContext) private var videoQueueContext - // Platform-specific fonts #if os(tvOS) private var titleFont: Font { diff --git a/Yattee/Views/Components/VideoSwipeActionsModifier.swift b/Yattee/Views/Components/VideoSwipeActionsModifier.swift index 03488023..cf4b7fef 100644 --- a/Yattee/Views/Components/VideoSwipeActionsModifier.swift +++ b/Yattee/Views/Components/VideoSwipeActionsModifier.swift @@ -10,11 +10,12 @@ import SwiftUI #if !os(tvOS) /// View modifier that applies user-configurable swipe actions plus fixed context-specific actions. struct VideoSwipeActionsModifier: ViewModifier { + @Environment(\.appEnvironment) private var appEnvironment + @Environment(\.videoQueueContext) private var queueContext + let video: Video var fixedActions: [SwipeAction] = [] - @Environment(\.appEnvironment) private var appEnvironment - @Environment(\.videoQueueContext) private var queueContext @State private var showingPlaylistSheet = false @State private var showingDownloadSheet = false diff --git a/Yattee/Views/Downloads/DownloadRowView.swift b/Yattee/Views/Downloads/DownloadRowView.swift index dc4ed001..a67a067f 100644 --- a/Yattee/Views/Downloads/DownloadRowView.swift +++ b/Yattee/Views/Downloads/DownloadRowView.swift @@ -13,10 +13,12 @@ import SwiftUI /// 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. struct DownloadRowView: View { + @Environment(\.appEnvironment) private var appEnvironment + let download: Download let isActive: Bool var onDelete: (() -> Void)? = nil - + // Queue context (optional, enables auto-play when provided) var queueSource: QueueSource? = nil var sourceLabel: String? = nil @@ -24,8 +26,6 @@ struct DownloadRowView: View { var videoIndex: Int? = nil var loadMoreVideos: LoadMoreVideosCallback? = nil - @Environment(\.appEnvironment) private var appEnvironment - // Cache watch progress to avoid CoreData fetches on every re-render @State private var cachedWatchProgress: Double? @State private var cachedWatchedSeconds: TimeInterval? diff --git a/Yattee/Views/Downloads/DownloadsView.swift b/Yattee/Views/Downloads/DownloadsView.swift index 767532e0..b17ca693 100644 --- a/Yattee/Views/Downloads/DownloadsView.swift +++ b/Yattee/Views/Downloads/DownloadsView.swift @@ -339,14 +339,14 @@ private struct ActiveDownloadsSectionContentView: View { /// Only accesses manager.completedDownloads, so won't re-render when activeDownloads progress updates. /// Generates rows directly for VideoListContainer (no Section wrapper). private struct CompletedDownloadsSectionContentView: View { + @Environment(\.appEnvironment) private var appEnvironment + let manager: DownloadManager let settings: DownloadSettings let searchText: String let listStyle: VideoListStyle let isGroupedMode: Bool - @Environment(\.appEnvironment) private var appEnvironment - private var completedFiltered: [Download] { guard !searchText.isEmpty else { return manager.completedDownloads } let query = searchText.lowercased() diff --git a/Yattee/Views/Home/HomeShortcutCardView.swift b/Yattee/Views/Home/HomeShortcutCardView.swift index 7ad9d8f1..002be4e3 100644 --- a/Yattee/Views/Home/HomeShortcutCardView.swift +++ b/Yattee/Views/Home/HomeShortcutCardView.swift @@ -8,6 +8,11 @@ import SwiftUI struct HomeShortcutCardView: View { + @Environment(\.dynamicTypeSize) private var dynamicTypeSize + #if os(tvOS) + @Environment(\.isFocused) private var isFocused + #endif + let icon: String let title: String let count: Int @@ -28,11 +33,6 @@ struct HomeShortcutCardView: View { self.statusIndicator = statusIndicator } - @Environment(\.dynamicTypeSize) private var dynamicTypeSize - #if os(tvOS) - @Environment(\.isFocused) private var isFocused - #endif - // Platform-specific styling #if os(tvOS) private let iconSize: CGFloat = 36 diff --git a/Yattee/Views/Instances/InstanceBrowseView.swift b/Yattee/Views/Instances/InstanceBrowseView.swift index 5ab59e70..d09bad79 100644 --- a/Yattee/Views/Instances/InstanceBrowseView.swift +++ b/Yattee/Views/Instances/InstanceBrowseView.swift @@ -8,11 +8,12 @@ import SwiftUI struct InstanceBrowseView: View { + @Environment(\.appEnvironment) private var appEnvironment + let instance: Instance 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 popularVideos: [Video] = [] @State private var trendingVideos: [Video] = [] @@ -282,14 +283,14 @@ struct InstanceBrowseView: View { } } .sheet(isPresented: $showFilterSheet) { - SearchFiltersSheet(filters: Binding( - get: { searchViewModel?.filters ?? .defaults }, - set: { searchViewModel?.filters = $0 } - )) { + SearchFiltersSheet(onApply: { Task { await searchViewModel?.search(query: searchText) } - } + }, filters: Binding( + get: { searchViewModel?.filters ?? .defaults }, + set: { searchViewModel?.filters = $0 } + )) #if !os(tvOS) .presentationDetents([.medium, .large]) #endif diff --git a/Yattee/Views/MediaBrowser/MediaBrowserView.swift b/Yattee/Views/MediaBrowser/MediaBrowserView.swift index dd1c35ad..00a09f50 100644 --- a/Yattee/Views/MediaBrowser/MediaBrowserView.swift +++ b/Yattee/Views/MediaBrowser/MediaBrowserView.swift @@ -8,10 +8,11 @@ import SwiftUI struct MediaBrowserView: View { + @Environment(\.appEnvironment) private var appEnvironment + let source: MediaSource let initialPath: String - @Environment(\.appEnvironment) private var appEnvironment @Namespace private var sheetTransition @State private var currentPath: String @State private var files: [MediaFile] = [] @@ -103,10 +104,10 @@ struct MediaBrowserView: View { } .sheet(isPresented: $showViewOptions) { MediaBrowserViewOptionsSheet( + sourceType: source.type, sortOrder: $sortOrder, sortAscending: $sortAscending, - showOnlyPlayable: $showOnlyPlayable, - sourceType: source.type + showOnlyPlayable: $showOnlyPlayable ) .liquidGlassSheetContent(sourceID: "mediaBrowserViewOptions", in: sheetTransition) } diff --git a/Yattee/Views/MediaBrowser/MediaBrowserViewOptionsSheet.swift b/Yattee/Views/MediaBrowser/MediaBrowserViewOptionsSheet.swift index cd0a6f92..7d4d6457 100644 --- a/Yattee/Views/MediaBrowser/MediaBrowserViewOptionsSheet.swift +++ b/Yattee/Views/MediaBrowser/MediaBrowserViewOptionsSheet.swift @@ -8,12 +8,13 @@ import SwiftUI struct MediaBrowserViewOptionsSheet: View { + @Environment(\.dismiss) private var dismiss + + let sourceType: MediaSourceType + @Binding var sortOrder: MediaBrowserSortOrder @Binding var sortAscending: Bool @Binding var showOnlyPlayable: Bool - let sourceType: MediaSourceType - - @Environment(\.dismiss) private var dismiss private var availableSortOptions: [MediaBrowserSortOrder] { MediaBrowserSortOrder.availableOptions(for: sourceType) @@ -89,10 +90,10 @@ struct MediaBrowserViewOptionsSheet: View { @Previewable @State var showOnlyPlayable = false MediaBrowserViewOptionsSheet( + sourceType: .localFolder, sortOrder: $sortOrder, sortAscending: $sortAscending, - showOnlyPlayable: $showOnlyPlayable, - sourceType: .localFolder + showOnlyPlayable: $showOnlyPlayable ) } @@ -102,9 +103,9 @@ struct MediaBrowserViewOptionsSheet: View { @Previewable @State var showOnlyPlayable = false MediaBrowserViewOptionsSheet( + sourceType: .webdav, sortOrder: $sortOrder, sortAscending: $sortAscending, - showOnlyPlayable: $showOnlyPlayable, - sourceType: .webdav + showOnlyPlayable: $showOnlyPlayable ) } diff --git a/Yattee/Views/Navigation/UnifiedTabView.swift b/Yattee/Views/Navigation/UnifiedTabView.swift index 27df391a..df3cb409 100644 --- a/Yattee/Views/Navigation/UnifiedTabView.swift +++ b/Yattee/Views/Navigation/UnifiedTabView.swift @@ -13,10 +13,11 @@ import SwiftUI #if os(iOS) struct UnifiedTabView: View { - @Binding var selectedTab: AppTab @Environment(\.appEnvironment) private var appEnvironment @Environment(\.horizontalSizeClass) private var horizontalSizeClass + @Binding var selectedTab: AppTab + // Sidebar manager for dynamic content @State private var sidebarManager = SidebarManager() @@ -297,9 +298,10 @@ extension MiniPlayerMinimizeBehavior { #if os(macOS) struct UnifiedTabView: View { - @Binding var selectedTab: AppTab @Environment(\.appEnvironment) private var appEnvironment + @Binding var selectedTab: AppTab + // Sidebar manager for dynamic content @State private var sidebarManager = SidebarManager() @@ -517,9 +519,10 @@ struct UnifiedTabView: View { #if os(tvOS) struct UnifiedTabView: View { - @Binding var selectedTab: AppTab @Environment(\.appEnvironment) private var appEnvironment + @Binding var selectedTab: AppTab + // Sidebar manager for dynamic content @State private var sidebarManager = SidebarManager() diff --git a/Yattee/Views/Onboarding/OnboardingMigrationScreen.swift b/Yattee/Views/Onboarding/OnboardingMigrationScreen.swift index 4005d37c..1e830b8f 100644 --- a/Yattee/Views/Onboarding/OnboardingMigrationScreen.swift +++ b/Yattee/Views/Onboarding/OnboardingMigrationScreen.swift @@ -10,10 +10,10 @@ import SwiftUI struct OnboardingMigrationScreen: View { @Environment(\.appEnvironment) private var appEnvironment - @Binding var items: [LegacyImportItem] let onContinue: () -> Void let onSkip: () -> Void + @Binding var items: [LegacyImportItem] @State private var isImporting = false @State private var importProgress: Double = 0.0 @State private var showingResultSheet = false @@ -352,9 +352,9 @@ struct OnboardingMigrationScreen: View { ] OnboardingMigrationScreen( - items: $items, onContinue: {}, - onSkip: {} + onSkip: {}, + items: $items ) .appEnvironment(.preview) } diff --git a/Yattee/Views/Onboarding/OnboardingSheetView.swift b/Yattee/Views/Onboarding/OnboardingSheetView.swift index c9e79d64..87463425 100644 --- a/Yattee/Views/Onboarding/OnboardingSheetView.swift +++ b/Yattee/Views/Onboarding/OnboardingSheetView.swift @@ -45,9 +45,9 @@ struct OnboardingSheetView: View { // Migration screen is now page 2 (when present) if hasLegacyData, let binding = Binding($legacyItems) { OnboardingMigrationScreen( - items: binding, onContinue: advanceToNextPage, - onSkip: advanceToNextPage + onSkip: advanceToNextPage, + items: binding ) .tag(2) } diff --git a/Yattee/Views/Player/ChaptersView.swift b/Yattee/Views/Player/ChaptersView.swift index 5927649d..f49c8335 100644 --- a/Yattee/Views/Player/ChaptersView.swift +++ b/Yattee/Views/Player/ChaptersView.swift @@ -8,12 +8,12 @@ import SwiftUI struct ChaptersView: View { + @Environment(\.dismiss) private var dismiss + let chapters: [VideoChapter] let currentTime: TimeInterval let storyboard: Storyboard? let onChapterTap: (VideoChapter) async -> Void - - @Environment(\.dismiss) private var dismiss @State private var sheetsLoaded = false var body: some View { diff --git a/Yattee/Views/Player/FloatingDetailsPanel.swift b/Yattee/Views/Player/FloatingDetailsPanel.swift index 7eea8993..ddcc48e4 100644 --- a/Yattee/Views/Player/FloatingDetailsPanel.swift +++ b/Yattee/Views/Player/FloatingDetailsPanel.swift @@ -19,6 +19,9 @@ private struct PanelHeightKey: PreferenceKey { } struct FloatingDetailsPanel: View { + @Environment(\.appEnvironment) private var appEnvironment + @Environment(\.colorScheme) private var systemColorScheme + let onPinToggle: () -> Void let onAlignmentToggle: () -> Void let isPinned: Bool @@ -33,9 +36,6 @@ struct FloatingDetailsPanel: View { // Player controls layout for pill settings let playerControlsLayout: PlayerControlsLayout - - @Environment(\.appEnvironment) private var appEnvironment - @Environment(\.colorScheme) private var systemColorScheme @State private var isCommentsExpanded: Bool = false @State private var showFormattedDate = false @State private var showOriginalTitle = false diff --git a/Yattee/Views/Player/PanelAlignmentButton.swift b/Yattee/Views/Player/PanelAlignmentButton.swift index 4daaa490..53fe2240 100644 --- a/Yattee/Views/Player/PanelAlignmentButton.swift +++ b/Yattee/Views/Player/PanelAlignmentButton.swift @@ -12,14 +12,14 @@ import SwiftUI /// A floating button that toggles the panel between left and right sides. /// Mirrors the style of PanelPinButton: 36pt glass circle, same shadow and haptic. struct PanelAlignmentButton: View { + @Environment(\.appEnvironment) private var appEnvironment + let panelSide: FloatingPanelSide let onAlignmentToggle: () -> Void /// Whether the drag handle is currently active (being dragged or hovered). @Binding var isDragHandleActive: Bool - @Environment(\.appEnvironment) private var appEnvironment - /// Button diameter private static let buttonSize: CGFloat = 36 diff --git a/Yattee/Views/Player/PanelPinButton.swift b/Yattee/Views/Player/PanelPinButton.swift index 95771046..2531bdc3 100644 --- a/Yattee/Views/Player/PanelPinButton.swift +++ b/Yattee/Views/Player/PanelPinButton.swift @@ -12,6 +12,8 @@ import SwiftUI /// 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. struct PanelPinButton: View { + @Environment(\.appEnvironment) private var appEnvironment + let isPinned: Bool let panelSide: FloatingPanelSide let onPinToggle: () -> Void @@ -19,8 +21,6 @@ struct PanelPinButton: View { /// Whether the drag handle is currently active (being dragged or hovered). @Binding var isDragHandleActive: Bool - @Environment(\.appEnvironment) private var appEnvironment - /// Button diameter private static let buttonSize: CGFloat = 36 diff --git a/Yattee/Views/Player/PortraitDetailsPanel.swift b/Yattee/Views/Player/PortraitDetailsPanel.swift index d06c79a4..2b33be1a 100644 --- a/Yattee/Views/Player/PortraitDetailsPanel.swift +++ b/Yattee/Views/Player/PortraitDetailsPanel.swift @@ -11,6 +11,8 @@ import SwiftUI #if os(iOS) struct PortraitDetailsPanel: View { + @Environment(\.appEnvironment) private var appEnvironment + let onChannelTap: (() -> Void)? let playerControlsLayout: PlayerControlsLayout let onFullscreen: (() -> Void)? @@ -19,8 +21,6 @@ struct PortraitDetailsPanel: View { var onDragChanged: ((CGFloat) -> Void)? var onDragEnded: ((CGFloat, CGFloat) -> Void)? var onDragCancelled: (() -> Void)? - - @Environment(\.appEnvironment) private var appEnvironment @GestureState private var isDraggingHandle: Bool = false @State private var isCommentsExpanded: Bool = false @State private var showFormattedDate = false diff --git a/Yattee/Views/Player/QualitySelectorView.swift b/Yattee/Views/Player/QualitySelectorView.swift index 632b987c..cee821c2 100644 --- a/Yattee/Views/Player/QualitySelectorView.swift +++ b/Yattee/Views/Player/QualitySelectorView.swift @@ -8,6 +8,11 @@ import SwiftUI struct QualitySelectorView: View { + // MARK: - Environment + + @Environment(\.dismiss) var dismiss + @Environment(\.appEnvironment) private var appEnvironment + // MARK: - Properties let streams: [Stream] @@ -39,10 +44,7 @@ struct QualitySelectorView: View { /// Whether to show the segmented tab picker (false for focused single-tab mode) var showTabPicker: Bool = true - // MARK: - Environment & State - - @Environment(\.dismiss) var dismiss - @Environment(\.appEnvironment) private var appEnvironment + // MARK: - State @State var selectedTab: QualitySelectorTab = .video @State var selectedVideoStream: Stream? @State var selectedAudioStream: Stream? diff --git a/Yattee/Views/Player/WideScreenPlayerLayout.swift b/Yattee/Views/Player/WideScreenPlayerLayout.swift index a587b516..0e9fe834 100644 --- a/Yattee/Views/Player/WideScreenPlayerLayout.swift +++ b/Yattee/Views/Player/WideScreenPlayerLayout.swift @@ -13,6 +13,8 @@ import SwiftUI #if os(iOS) || os(macOS) struct WideScreenPlayerLayout: View { + @Environment(\.appEnvironment) private var appEnvironment + let playerControlsLayout: PlayerControlsLayout @ViewBuilder let playerContent: ( @@ -30,8 +32,6 @@ struct WideScreenPlayerLayout: View { // Callbacks for panel actions let onChannelTap: (() -> Void)? let onFullscreen: (() -> Void)? - - @Environment(\.appEnvironment) private var appEnvironment @State private var controlsVisible = false @State private var isPanelVisible = false // Local state synced with settingsManager @State private var lastVideoId: String? // Track video ID to detect actual video changes diff --git a/Yattee/Views/Playlist/BatchDownloadQualitySheet.swift b/Yattee/Views/Playlist/BatchDownloadQualitySheet.swift index 1c06bcb1..85184b54 100644 --- a/Yattee/Views/Playlist/BatchDownloadQualitySheet.swift +++ b/Yattee/Views/Playlist/BatchDownloadQualitySheet.swift @@ -9,12 +9,12 @@ import SwiftUI #if !os(tvOS) struct BatchDownloadQualitySheet: View { - let videoCount: Int - let onConfirm: (DownloadQuality, Bool) -> Void - @Environment(\.dismiss) private var dismiss @Environment(\.appEnvironment) private var appEnvironment + let videoCount: Int + let onConfirm: (DownloadQuality, Bool) -> Void + @State private var selectedQuality: DownloadQuality = .best @State private var includeSubtitles = false diff --git a/Yattee/Views/Playlist/PlaylistFormSheet.swift b/Yattee/Views/Playlist/PlaylistFormSheet.swift index a23b1e7b..9742752d 100644 --- a/Yattee/Views/Playlist/PlaylistFormSheet.swift +++ b/Yattee/Views/Playlist/PlaylistFormSheet.swift @@ -24,11 +24,11 @@ struct PlaylistFormSheet: View { } } + @Environment(\.dismiss) private var dismiss + let mode: Mode let onSave: (String, String?) -> Void - @Environment(\.dismiss) private var dismiss - @State private var title: String = "" @State private var descriptionText: String = "" diff --git a/Yattee/Views/Playlist/UnifiedPlaylistDetailView.swift b/Yattee/Views/Playlist/UnifiedPlaylistDetailView.swift index 0de5bba2..2a52057e 100644 --- a/Yattee/Views/Playlist/UnifiedPlaylistDetailView.swift +++ b/Yattee/Views/Playlist/UnifiedPlaylistDetailView.swift @@ -46,11 +46,11 @@ enum PlaylistSource: Hashable { } struct UnifiedPlaylistDetailView: View { - let source: PlaylistSource - @Environment(\.appEnvironment) private var appEnvironment @Environment(\.dismiss) private var dismiss + let source: PlaylistSource + // MARK: - Shared State @State private var title: String diff --git a/Yattee/Views/Search/SearchView.swift b/Yattee/Views/Search/SearchView.swift index 2f29315a..6e5b3243 100644 --- a/Yattee/Views/Search/SearchView.swift +++ b/Yattee/Views/Search/SearchView.swift @@ -144,17 +144,17 @@ struct SearchView: View { } .searchable(text: searchTextBinding, prompt: Text(String(localized: "search.placeholder"))) .sheet(isPresented: $showFilterSheet) { - SearchFiltersSheet(filters: Binding( + SearchFiltersSheet(onApply: { + if hasResults { + Task { await searchViewModel?.search(query: searchTextBinding.wrappedValue) } + } + }, filters: Binding( get: { searchViewModel?.filters ?? .defaults }, set: { newFilters in searchViewModel?.filters = newFilters saveFilters(newFilters) } - ), onApply: { - if hasResults { - Task { await searchViewModel?.search(query: searchTextBinding.wrappedValue) } - } - }) + )) .presentationDetents([.medium, .large]) } .sheet(isPresented: $showViewOptions) { @@ -1169,10 +1169,12 @@ struct SearchView: View { // MARK: - Search Filters Sheet struct SearchFiltersSheet: View { - @Binding var filters: SearchFilters - let onApply: () -> Void @Environment(\.dismiss) private var dismiss + let onApply: () -> Void + + @Binding var filters: SearchFilters + var body: some View { NavigationStack { Form { diff --git a/Yattee/Views/Settings/SettingsView.swift b/Yattee/Views/Settings/SettingsView.swift index 98488c35..a8d66d46 100644 --- a/Yattee/Views/Settings/SettingsView.swift +++ b/Yattee/Views/Settings/SettingsView.swift @@ -8,11 +8,11 @@ import SwiftUI struct SettingsView: View { - var showCloseButton: Bool = true - @Environment(\.dismiss) private var dismiss @Environment(\.appEnvironment) private var appEnvironment + var showCloseButton: Bool = true + #if os(macOS) @State private var selectedSection: SettingsSection? = .sources #endif diff --git a/Yattee/Views/Video/DownloadQualitySheet.swift b/Yattee/Views/Video/DownloadQualitySheet.swift index 811c3639..a4efa8e1 100644 --- a/Yattee/Views/Video/DownloadQualitySheet.swift +++ b/Yattee/Views/Video/DownloadQualitySheet.swift @@ -9,11 +9,6 @@ import SwiftUI #if !os(tvOS) struct DownloadQualitySheet: View { - let video: Video - var streams: [Stream] = [] - var captions: [Caption] = [] - var dislikeCount: Int? - enum DownloadTab: String, CaseIterable { case video case audio @@ -31,6 +26,11 @@ struct DownloadQualitySheet: View { @Environment(\.dismiss) private var dismiss @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 selectedVideoStream: Stream? @State private var selectedAudioStream: Stream? diff --git a/Yattee/Views/Video/ExternalVideoView.swift b/Yattee/Views/Video/ExternalVideoView.swift index 4eaad11a..6cc15d57 100644 --- a/Yattee/Views/Video/ExternalVideoView.swift +++ b/Yattee/Views/Video/ExternalVideoView.swift @@ -10,11 +10,11 @@ import SwiftUI /// View for extracting and playing videos from external sites (non-YouTube/PeerTube). /// Uses Yattee Server's yt-dlp integration to extract video information. struct ExternalVideoView: View { - let url: URL - @Environment(\.appEnvironment) private var appEnvironment @Environment(\.dismiss) private var dismiss + let url: URL + @State private var isLoading = true @State private var errorMessage: String? @State private var shouldDismissWhenPlayerExpands = false diff --git a/Yattee/Views/Video/PlaylistSelectorSheet.swift b/Yattee/Views/Video/PlaylistSelectorSheet.swift index 0c0a1d3f..fb6c47ab 100644 --- a/Yattee/Views/Video/PlaylistSelectorSheet.swift +++ b/Yattee/Views/Video/PlaylistSelectorSheet.swift @@ -9,11 +9,11 @@ import SwiftUI import NukeUI struct PlaylistSelectorSheet: View { - let video: Video - @Environment(\.dismiss) private var dismiss @Environment(\.appEnvironment) private var appEnvironment + let video: Video + @State private var playlists: [LocalPlaylist] = [] @State private var showingNewPlaylist = false @State private var pendingPlaylistTitle: String? diff --git a/Yattee/Views/Video/VideoInfoView.swift b/Yattee/Views/Video/VideoInfoView.swift index 9f2f5af4..1e65efb9 100644 --- a/Yattee/Views/Video/VideoInfoView.swift +++ b/Yattee/Views/Video/VideoInfoView.swift @@ -15,11 +15,11 @@ enum VideoInfoInitMode: Sendable { } struct VideoInfoView: View { - private let initMode: VideoInfoInitMode - @Environment(\.appEnvironment) private var appEnvironment @Environment(\.videoQueueContext) private var videoQueueContext - + + private let initMode: VideoInfoInitMode + // Video loading state (for videoID mode) @State private var loadedVideo: Video? @State private var isLoadingInitialVideo = false