Files
yattee/Yattee/Views/Video/PlaylistSelectorSheet.swift
Arkadiusz Fal 612dce6b9f Refactor views
2026-02-09 01:13:02 +01:00

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)
}