mirror of
https://github.com/yattee/yattee.git
synced 2026-02-20 17:59:45 +00:00
Yattee v2 rewrite
This commit is contained in:
213
Yattee/Views/Video/PlaylistSelectorSheet.swift
Normal file
213
Yattee/Views/Video/PlaylistSelectorSheet.swift
Normal file
@@ -0,0 +1,213 @@
|
||||
//
|
||||
// PlaylistSelectorSheet.swift
|
||||
// Yattee
|
||||
//
|
||||
// Sheet for selecting a playlist to add a video to.
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
import NukeUI
|
||||
|
||||
struct PlaylistSelectorSheet: View {
|
||||
let video: Video
|
||||
|
||||
@Environment(\.dismiss) private var dismiss
|
||||
@Environment(\.appEnvironment) private var appEnvironment
|
||||
|
||||
@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("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)
|
||||
}
|
||||
Reference in New Issue
Block a user