mirror of
https://github.com/yattee/yattee.git
synced 2026-02-20 17:59:45 +00:00
Refactor views
This commit is contained in:
@@ -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
|
||||||
|
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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? {
|
||||||
|
|||||||
@@ -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
|
||||||
|
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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) {
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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?
|
||||||
|
|||||||
@@ -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 }
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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 {
|
||||||
|
|||||||
@@ -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
|
||||||
|
|
||||||
|
|||||||
@@ -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?
|
||||||
|
|||||||
@@ -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()
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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()
|
||||||
|
|
||||||
|
|||||||
@@ -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)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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 {
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
|
|
||||||
|
|||||||
@@ -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
|
||||||
|
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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?
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
|
|
||||||
|
|||||||
@@ -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 = ""
|
||||||
|
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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 {
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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?
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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?
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
Reference in New Issue
Block a user