mirror of
https://github.com/yattee/yattee.git
synced 2026-02-19 17:29:45 +00:00
214 lines
7.3 KiB
Swift
214 lines
7.3 KiB
Swift
//
|
|
// PlaylistSelectorSheet.swift
|
|
// Yattee
|
|
//
|
|
// Sheet for selecting a playlist to add a video to.
|
|
//
|
|
|
|
import SwiftUI
|
|
import NukeUI
|
|
|
|
struct PlaylistSelectorSheet: View {
|
|
@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?
|
|
@State private var pendingPlaylistDescription: String?
|
|
@State private var addedToPlaylist: LocalPlaylist?
|
|
|
|
private var dataManager: DataManager? { appEnvironment?.dataManager }
|
|
|
|
var body: some View {
|
|
NavigationStack {
|
|
List {
|
|
// Warning for local folder videos
|
|
if video.isFromLocalFolder {
|
|
Section {
|
|
Label {
|
|
Text(String(localized: "playlist.localFileWarning"))
|
|
} icon: {
|
|
Image(systemName: "exclamationmark.triangle.fill")
|
|
.foregroundStyle(.orange)
|
|
}
|
|
}
|
|
}
|
|
|
|
// Only show playlist options if not from local folder
|
|
if !video.isFromLocalFolder {
|
|
// Create new playlist section
|
|
Section {
|
|
Button {
|
|
showingNewPlaylist = true
|
|
} label: {
|
|
Label(
|
|
String(localized: "playlist.new"),
|
|
systemImage: "plus.circle"
|
|
)
|
|
}
|
|
}
|
|
|
|
// Existing playlists
|
|
if !playlists.isEmpty {
|
|
Section {
|
|
ForEach(playlists, id: \.id) { playlist in
|
|
PlaylistSelectionRow(
|
|
playlist: playlist,
|
|
video: video,
|
|
wasAdded: addedToPlaylist?.id == playlist.id
|
|
) {
|
|
addVideoToPlaylist(playlist)
|
|
}
|
|
}
|
|
}
|
|
} else {
|
|
Section {
|
|
ContentUnavailableView {
|
|
Label(
|
|
String(localized: "playlist.empty.title"),
|
|
systemImage: "music.note.list"
|
|
)
|
|
} description: {
|
|
Text(String(localized: "playlist.empty.description"))
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
.navigationTitle(String(localized: "playlist.addTo"))
|
|
#if os(iOS)
|
|
.navigationBarTitleDisplayMode(.inline)
|
|
#endif
|
|
.toolbar {
|
|
ToolbarItem(placement: .confirmationAction) {
|
|
Button(role: .cancel) {
|
|
dismiss()
|
|
} label: {
|
|
Label(String(localized: "common.close"), systemImage: "xmark")
|
|
.labelStyle(.iconOnly)
|
|
}
|
|
}
|
|
}
|
|
.sheet(isPresented: $showingNewPlaylist) {
|
|
PlaylistFormSheet(mode: .create) { title, description in
|
|
pendingPlaylistTitle = title
|
|
pendingPlaylistDescription = description
|
|
}
|
|
}
|
|
.onChange(of: pendingPlaylistTitle) { _, newValue in
|
|
if newValue != nil {
|
|
createAndAddToPlaylist()
|
|
}
|
|
}
|
|
.onAppear {
|
|
loadPlaylists()
|
|
}
|
|
}
|
|
.presentationDetents([.medium, .large])
|
|
}
|
|
|
|
private func loadPlaylists() {
|
|
playlists = dataManager?.playlists() ?? []
|
|
}
|
|
|
|
private func addVideoToPlaylist(_ playlist: LocalPlaylist) {
|
|
guard let dataManager else { return }
|
|
|
|
dataManager.addToPlaylist(video, playlist: playlist)
|
|
addedToPlaylist = playlist
|
|
|
|
// Auto-dismiss after a short delay to show the checkmark
|
|
DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) {
|
|
dismiss()
|
|
}
|
|
}
|
|
|
|
private func createAndAddToPlaylist() {
|
|
guard let dataManager, let title = pendingPlaylistTitle else { return }
|
|
|
|
let newPlaylist = dataManager.createPlaylist(title: title, description: pendingPlaylistDescription)
|
|
dataManager.addToPlaylist(video, playlist: newPlaylist)
|
|
pendingPlaylistTitle = nil
|
|
pendingPlaylistDescription = nil
|
|
addedToPlaylist = newPlaylist
|
|
|
|
// Refresh list and auto-dismiss
|
|
loadPlaylists()
|
|
DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) {
|
|
dismiss()
|
|
}
|
|
}
|
|
}
|
|
|
|
// MARK: - Playlist Selection Row
|
|
|
|
private struct PlaylistSelectionRow: View {
|
|
let playlist: LocalPlaylist
|
|
let video: Video
|
|
let wasAdded: Bool
|
|
let onAdd: () -> Void
|
|
|
|
var body: some View {
|
|
Button(action: onAdd) {
|
|
HStack(spacing: 12) {
|
|
// Thumbnail
|
|
LazyImage(url: playlist.thumbnailURL) { state in
|
|
if let image = state.image {
|
|
image
|
|
.resizable()
|
|
.aspectRatio(contentMode: .fill)
|
|
} else {
|
|
Rectangle()
|
|
.fill(.quaternary)
|
|
.overlay {
|
|
Image(systemName: "music.note.list")
|
|
.foregroundStyle(.secondary)
|
|
}
|
|
}
|
|
}
|
|
.frame(width: 60, height: 34)
|
|
.clipShape(RoundedRectangle(cornerRadius: 6))
|
|
|
|
// Info
|
|
VStack(alignment: .leading, spacing: 2) {
|
|
Text(playlist.title)
|
|
.font(.body)
|
|
.foregroundStyle(.primary)
|
|
|
|
Text(String(localized: "playlist.videoCount \(playlist.videoCount)"))
|
|
.font(.caption)
|
|
.foregroundStyle(.secondary)
|
|
}
|
|
|
|
Spacer()
|
|
|
|
// Status
|
|
if wasAdded {
|
|
Image(systemName: "checkmark.circle.fill")
|
|
.foregroundStyle(.green)
|
|
} else if playlist.contains(videoID: video.id.videoID) {
|
|
Text(String(localized: "playlist.alreadyAdded"))
|
|
.font(.caption)
|
|
.foregroundStyle(.secondary)
|
|
} else {
|
|
Image(systemName: "plus.circle")
|
|
.foregroundStyle(.secondary)
|
|
}
|
|
}
|
|
.contentShape(Rectangle())
|
|
}
|
|
.buttonStyle(.plain)
|
|
.disabled(playlist.contains(videoID: video.id.videoID) && !wasAdded)
|
|
}
|
|
}
|
|
|
|
// MARK: - Preview
|
|
|
|
#Preview {
|
|
PlaylistSelectorSheet(video: .preview)
|
|
.appEnvironment(.preview)
|
|
}
|