mirror of
https://github.com/yattee/yattee.git
synced 2026-02-20 09:49:46 +00:00
Yattee v2 rewrite
This commit is contained in:
290
Yattee/Views/Components/VideoSwipeActionsModifier.swift
Normal file
290
Yattee/Views/Components/VideoSwipeActionsModifier.swift
Normal file
@@ -0,0 +1,290 @@
|
||||
//
|
||||
// VideoSwipeActionsModifier.swift
|
||||
// Yattee
|
||||
//
|
||||
// View modifier that applies configurable swipe actions to video rows.
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
|
||||
#if !os(tvOS)
|
||||
/// View modifier that applies user-configurable swipe actions plus fixed context-specific actions.
|
||||
struct VideoSwipeActionsModifier: ViewModifier {
|
||||
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
|
||||
|
||||
func body(content: Content) -> some View {
|
||||
content
|
||||
.swipeActions(actionsArray: allActions())
|
||||
.sheet(isPresented: $showingPlaylistSheet) {
|
||||
PlaylistSelectorSheet(video: video)
|
||||
}
|
||||
.sheet(isPresented: $showingDownloadSheet) {
|
||||
DownloadQualitySheet(video: video)
|
||||
}
|
||||
}
|
||||
|
||||
private var visibleActions: [VideoSwipeAction] {
|
||||
appEnvironment?.settingsManager.visibleVideoSwipeActions() ?? []
|
||||
}
|
||||
|
||||
private func allActions() -> [SwipeAction] {
|
||||
var actions = visibleActions.map { swipeAction(for: $0) }
|
||||
actions.append(contentsOf: fixedActions)
|
||||
return actions
|
||||
}
|
||||
|
||||
private func swipeAction(for action: VideoSwipeAction) -> SwipeAction {
|
||||
switch action {
|
||||
case .playNext:
|
||||
return SwipeAction(
|
||||
symbolImage: action.symbolImage,
|
||||
tint: action.tint,
|
||||
background: action.backgroundColor
|
||||
) { reset in
|
||||
appEnvironment?.queueManager.playNext(video)
|
||||
reset()
|
||||
}
|
||||
|
||||
case .addToQueue:
|
||||
return SwipeAction(
|
||||
symbolImage: action.symbolImage,
|
||||
tint: action.tint,
|
||||
background: action.backgroundColor
|
||||
) { reset in
|
||||
appEnvironment?.queueManager.addToQueue(video)
|
||||
reset()
|
||||
}
|
||||
|
||||
case .download:
|
||||
return downloadSwipeAction(action: action)
|
||||
|
||||
case .share:
|
||||
return SwipeAction(
|
||||
symbolImage: action.symbolImage,
|
||||
tint: action.tint,
|
||||
background: action.backgroundColor
|
||||
) { reset in
|
||||
shareVideo()
|
||||
reset()
|
||||
}
|
||||
|
||||
case .videoInfo:
|
||||
return SwipeAction(
|
||||
symbolImage: action.symbolImage,
|
||||
tint: action.tint,
|
||||
background: action.backgroundColor
|
||||
) { reset in
|
||||
appEnvironment?.navigationCoordinator.navigate(to: .video(.loaded(video)))
|
||||
reset()
|
||||
}
|
||||
|
||||
case .goToChannel:
|
||||
return SwipeAction(
|
||||
symbolImage: action.symbolImage,
|
||||
tint: action.tint,
|
||||
background: action.backgroundColor
|
||||
) { reset in
|
||||
appEnvironment?.navigationCoordinator.navigateToChannel(for: video)
|
||||
reset()
|
||||
}
|
||||
|
||||
case .addToBookmarks:
|
||||
return bookmarkSwipeAction(action: action)
|
||||
|
||||
case .addToPlaylist:
|
||||
return SwipeAction(
|
||||
symbolImage: action.symbolImage,
|
||||
tint: action.tint,
|
||||
background: action.backgroundColor
|
||||
) { reset in
|
||||
showingPlaylistSheet = true
|
||||
reset()
|
||||
}
|
||||
|
||||
case .markWatched:
|
||||
return watchedSwipeAction(action: action)
|
||||
}
|
||||
}
|
||||
|
||||
private func downloadSwipeAction(action: VideoSwipeAction) -> SwipeAction {
|
||||
let isDownloading = appEnvironment?.downloadManager.isDownloading(video.id) ?? false
|
||||
let isDownloaded = appEnvironment?.downloadManager.isDownloaded(video.id) ?? false
|
||||
|
||||
if isDownloading {
|
||||
// Cancel download
|
||||
return SwipeAction(
|
||||
symbolImage: "xmark.circle",
|
||||
tint: action.tint,
|
||||
background: .red
|
||||
) { reset in
|
||||
if let download = appEnvironment?.downloadManager.download(for: video.id) {
|
||||
Task {
|
||||
await appEnvironment?.downloadManager.cancel(download)
|
||||
}
|
||||
}
|
||||
reset()
|
||||
}
|
||||
} else if isDownloaded {
|
||||
// Already downloaded - show checkmark
|
||||
return SwipeAction(
|
||||
symbolImage: "checkmark.circle.fill",
|
||||
tint: action.tint,
|
||||
background: action.backgroundColor
|
||||
) { reset in
|
||||
// No action - already downloaded
|
||||
reset()
|
||||
}
|
||||
} else {
|
||||
// Start download
|
||||
return SwipeAction(
|
||||
symbolImage: action.symbolImage,
|
||||
tint: action.tint,
|
||||
background: action.backgroundColor
|
||||
) { reset in
|
||||
startDownload()
|
||||
reset()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private func bookmarkSwipeAction(action: VideoSwipeAction) -> SwipeAction {
|
||||
let isBookmarked = appEnvironment?.dataManager.isBookmarked(videoID: video.id.videoID) ?? false
|
||||
|
||||
return SwipeAction(
|
||||
symbolImage: isBookmarked ? "bookmark.slash" : action.symbolImage,
|
||||
tint: action.tint,
|
||||
background: action.backgroundColor
|
||||
) { reset in
|
||||
if isBookmarked {
|
||||
appEnvironment?.dataManager.removeBookmark(for: video.id.videoID)
|
||||
} else {
|
||||
appEnvironment?.dataManager.addBookmark(for: video)
|
||||
}
|
||||
reset()
|
||||
}
|
||||
}
|
||||
|
||||
private func watchedSwipeAction(action: VideoSwipeAction) -> SwipeAction {
|
||||
let isWatched = appEnvironment?.dataManager.watchEntry(for: video.id.videoID)?.isFinished ?? false
|
||||
|
||||
return SwipeAction(
|
||||
symbolImage: isWatched ? "eye.slash" : action.symbolImage,
|
||||
tint: action.tint,
|
||||
background: action.backgroundColor
|
||||
) { reset in
|
||||
if isWatched {
|
||||
appEnvironment?.dataManager.markAsUnwatched(videoID: video.id.videoID)
|
||||
} else {
|
||||
appEnvironment?.dataManager.markAsWatched(video: video)
|
||||
}
|
||||
reset()
|
||||
}
|
||||
}
|
||||
|
||||
private func shareVideo() {
|
||||
#if os(iOS)
|
||||
let activityVC = UIActivityViewController(
|
||||
activityItems: [video.shareURL],
|
||||
applicationActivities: nil
|
||||
)
|
||||
if let windowScene = UIApplication.shared.connectedScenes.first as? UIWindowScene,
|
||||
let window = windowScene.windows.first,
|
||||
let rootVC = window.rootViewController {
|
||||
// Find the top-most view controller
|
||||
var topVC = rootVC
|
||||
while let presented = topVC.presentedViewController {
|
||||
topVC = presented
|
||||
}
|
||||
topVC.present(activityVC, animated: true)
|
||||
}
|
||||
#elseif os(macOS)
|
||||
let pasteboard = NSPasteboard.general
|
||||
pasteboard.clearContents()
|
||||
pasteboard.setString(video.shareURL.absoluteString, forType: .string)
|
||||
#endif
|
||||
}
|
||||
|
||||
private func startDownload() {
|
||||
guard let appEnvironment else {
|
||||
showingDownloadSheet = true
|
||||
return
|
||||
}
|
||||
|
||||
// Media source videos use direct file URLs
|
||||
if video.isFromMediaSource {
|
||||
Task {
|
||||
do {
|
||||
try await appEnvironment.downloadManager.autoEnqueueMediaSource(
|
||||
video,
|
||||
mediaSourcesManager: appEnvironment.mediaSourcesManager,
|
||||
webDAVClient: appEnvironment.webDAVClient,
|
||||
smbClient: appEnvironment.smbClient
|
||||
)
|
||||
} catch {
|
||||
appEnvironment.toastManager.show(
|
||||
category: .error,
|
||||
title: String(localized: "download.error.title"),
|
||||
subtitle: error.localizedDescription,
|
||||
icon: "exclamationmark.triangle",
|
||||
iconColor: .red
|
||||
)
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
let downloadSettings = appEnvironment.downloadSettings
|
||||
|
||||
// Check if auto-download mode
|
||||
if downloadSettings.preferredDownloadQuality != .ask,
|
||||
let instance = appEnvironment.instancesManager.instance(for: video) {
|
||||
Task {
|
||||
do {
|
||||
try await appEnvironment.downloadManager.autoEnqueue(
|
||||
video,
|
||||
preferredQuality: downloadSettings.preferredDownloadQuality,
|
||||
preferredAudioLanguage: appEnvironment.settingsManager.preferredAudioLanguage,
|
||||
preferredSubtitlesLanguage: appEnvironment.settingsManager.preferredSubtitlesLanguage,
|
||||
includeSubtitles: downloadSettings.includeSubtitlesInAutoDownload,
|
||||
contentService: appEnvironment.contentService,
|
||||
instance: instance
|
||||
)
|
||||
} catch {
|
||||
appEnvironment.toastManager.show(
|
||||
category: .error,
|
||||
title: String(localized: "download.error.title"),
|
||||
subtitle: error.localizedDescription,
|
||||
icon: "exclamationmark.triangle",
|
||||
iconColor: .red
|
||||
)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
showingDownloadSheet = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - View Extension
|
||||
|
||||
extension View {
|
||||
/// Applies user-configurable video swipe actions with optional fixed context-specific actions.
|
||||
///
|
||||
/// - Parameters:
|
||||
/// - video: The video to apply swipe actions for.
|
||||
/// - fixedActions: Context-specific fixed actions (e.g., delete for history).
|
||||
/// These appear to the right of configurable actions.
|
||||
func videoSwipeActions(
|
||||
video: Video,
|
||||
fixedActions: [SwipeAction] = []
|
||||
) -> some View {
|
||||
modifier(VideoSwipeActionsModifier(video: video, fixedActions: fixedActions))
|
||||
}
|
||||
}
|
||||
#endif
|
||||
Reference in New Issue
Block a user