// // PlaylistsView.swift // Mobile Music Assistant // // Created by Sven Hanold on 26.03.26. // import SwiftUI struct PlaylistsView: View { @Environment(MAService.self) private var service @State private var errorMessage: String? @State private var showError = false private var playlists: [MAPlaylist] { service.libraryManager.playlists } private var isLoading: Bool { service.libraryManager.isLoadingPlaylists } var body: some View { Group { if isLoading && playlists.isEmpty { ProgressView() } else if playlists.isEmpty { ContentUnavailableView( "No Playlists", systemImage: "music.note.list", description: Text("Your library doesn't contain any playlists yet") ) } else { List { ForEach(playlists) { playlist in NavigationLink(value: playlist) { PlaylistRow(playlist: playlist) } } } .listStyle(.plain) } } .navigationDestination(for: MAPlaylist.self) { playlist in PlaylistDetailView(playlist: playlist) } .refreshable { await loadPlaylists(refresh: true) } .task { if playlists.isEmpty { await loadPlaylists(refresh: false) } } .alert("Error", isPresented: $showError) { Button("OK", role: .cancel) { } } message: { if let errorMessage { Text(errorMessage) } } } private func loadPlaylists(refresh: Bool) async { do { try await service.libraryManager.loadPlaylists(refresh: refresh) } catch { errorMessage = error.localizedDescription showError = true } } } // MARK: - Playlist Row struct PlaylistRow: View { @Environment(MAService.self) private var service let playlist: MAPlaylist var body: some View { HStack(spacing: 12) { // Playlist Cover if let imageUrl = playlist.imageUrl { let coverURL = service.imageProxyURL(path: imageUrl, size: 128) CachedAsyncImage(url: coverURL) { image in image .resizable() .aspectRatio(contentMode: .fill) } placeholder: { Rectangle() .fill(Color.gray.opacity(0.2)) } .frame(width: 64, height: 64) .clipShape(RoundedRectangle(cornerRadius: 8)) } else { RoundedRectangle(cornerRadius: 8) .fill(Color.gray.opacity(0.2)) .frame(width: 64, height: 64) .overlay { Image(systemName: "music.note.list") .font(.title2) .foregroundStyle(.secondary) } } // Playlist Info VStack(alignment: .leading, spacing: 4) { Text(playlist.name) .font(.headline) .lineLimit(1) if let owner = playlist.owner { Text("By \(owner)") .font(.caption) .foregroundStyle(.secondary) .lineLimit(1) } if playlist.isEditable { Label("Editable", systemImage: "pencil") .font(.caption2) .foregroundStyle(.blue) } } Spacer() } .padding(.vertical, 4) } } #Preview { NavigationStack { PlaylistsView() .environment(MAService()) } }