// // ArtistsView.swift // Mobile Music Assistant // // Created by Sven Hanold on 26.03.26. // import SwiftUI struct ArtistsView: View { @Environment(MAService.self) private var service @State private var errorMessage: String? @State private var showError = false private var artists: [MAArtist] { service.libraryManager.artists } private var isLoading: Bool { service.libraryManager.isLoadingArtists } private let columns = [ GridItem(.adaptive(minimum: 160), spacing: 16) ] var body: some View { ScrollView { LazyVGrid(columns: columns, spacing: 16) { ForEach(artists) { artist in NavigationLink(value: artist) { ArtistGridItem(artist: artist) } .buttonStyle(.plain) .task { await loadMoreIfNeeded(currentItem: artist) } } if isLoading { ProgressView() .gridCellColumns(columns.count) .padding() } } .padding() } .navigationDestination(for: MAArtist.self) { artist in ArtistDetailView(artist: artist) } .refreshable { await loadArtists(refresh: true) } .task { if artists.isEmpty { await loadArtists(refresh: false) } } .alert("Error", isPresented: $showError) { Button("OK", role: .cancel) { } } message: { if let errorMessage { Text(errorMessage) } } .overlay { if artists.isEmpty && !isLoading { ContentUnavailableView( "No Artists", systemImage: "music.mic", description: Text("Your library doesn't contain any artists yet") ) } } } private func loadArtists(refresh: Bool) async { do { try await service.libraryManager.loadArtists(refresh: refresh) } catch { errorMessage = error.localizedDescription showError = true } } private func loadMoreIfNeeded(currentItem: MAArtist) async { do { try await service.libraryManager.loadMoreArtistsIfNeeded(currentItem: currentItem) } catch { errorMessage = error.localizedDescription showError = true } } } // MARK: - Artist Grid Item struct ArtistGridItem: View { @Environment(MAService.self) private var service let artist: MAArtist var body: some View { VStack(spacing: 8) { // Artist Image if let imageUrl = artist.imageUrl { let coverURL = service.imageProxyURL(path: imageUrl, size: 256) CachedAsyncImage(url: coverURL) { image in image .resizable() .aspectRatio(contentMode: .fill) } placeholder: { Circle() .fill(Color.gray.opacity(0.2)) } .frame(width: 160, height: 160) .clipShape(Circle()) } else { Circle() .fill(Color.gray.opacity(0.2)) .frame(width: 160, height: 160) .overlay { Image(systemName: "music.mic") .font(.system(size: 40)) .foregroundStyle(.secondary) } } // Artist Name Text(artist.name) .font(.subheadline) .fontWeight(.medium) .lineLimit(2) .multilineTextAlignment(.center) .foregroundStyle(.primary) } } } #Preview { NavigationStack { ArtistsView() .environment(MAService()) } }