// // ArtistDetailView.swift // Mobile Music Assistant // // Created by Sven Hanold on 26.03.26. // import SwiftUI struct ArtistDetailView: View { @Environment(MAService.self) private var service let artist: MAArtist @State private var albums: [MAAlbum] = [] @State private var isLoading = true @State private var errorMessage: String? @State private var showError = false var body: some View { ScrollView { VStack(spacing: 24) { // Artist Header artistHeader Divider() // Albums Section if isLoading { ProgressView() .padding() } else if albums.isEmpty { Text("No albums found") .foregroundStyle(.secondary) .padding() } else { albumGrid } } } .navigationTitle(artist.name) .navigationBarTitleDisplayMode(.inline) .task { await loadAlbums() } .alert("Error", isPresented: $showError) { Button("OK", role: .cancel) { } } message: { if let errorMessage { Text(errorMessage) } } } // MARK: - Artist Header @ViewBuilder private var artistHeader: some View { VStack(spacing: 16) { if let imageUrl = artist.imageUrl { let coverURL = service.imageProxyURL(path: imageUrl, size: 512) CachedAsyncImage(url: coverURL) { image in image .resizable() .aspectRatio(contentMode: .fill) } placeholder: { Circle() .fill(Color.gray.opacity(0.2)) } .frame(width: 200, height: 200) .clipShape(Circle()) .shadow(radius: 10) } else { Circle() .fill(Color.gray.opacity(0.2)) .frame(width: 200, height: 200) .overlay { Image(systemName: "music.mic") .font(.system(size: 60)) .foregroundStyle(.secondary) } } if !albums.isEmpty { Text("\(albums.count) albums") .font(.subheadline) .foregroundStyle(.secondary) } } .padding(.top) } // MARK: - Album Grid @ViewBuilder private var albumGrid: some View { VStack(alignment: .leading, spacing: 12) { Text("Albums") .font(.title2.bold()) .padding(.horizontal) LazyVGrid( columns: [GridItem(.adaptive(minimum: 160), spacing: 16)], spacing: 16 ) { ForEach(albums) { album in NavigationLink(destination: AlbumDetailView(album: album)) { ArtistAlbumCard(album: album, service: service) } .buttonStyle(.plain) } } .padding(.horizontal) } } // MARK: - Actions private func loadAlbums() async { isLoading = true errorMessage = nil do { albums = try await service.libraryManager.getArtistAlbums(artistUri: artist.uri) isLoading = false } catch { errorMessage = error.localizedDescription showError = true isLoading = false } } } // MARK: - Artist Album Card private struct ArtistAlbumCard: View { let album: MAAlbum let service: MAService var body: some View { VStack(alignment: .leading, spacing: 8) { if let imageUrl = album.imageUrl { let coverURL = service.imageProxyURL(path: imageUrl, size: 256) CachedAsyncImage(url: coverURL) { image in image .resizable() .aspectRatio(contentMode: .fill) } placeholder: { Rectangle() .fill(Color.gray.opacity(0.2)) } .aspectRatio(1, contentMode: .fit) .clipShape(RoundedRectangle(cornerRadius: 10)) } else { RoundedRectangle(cornerRadius: 10) .fill(Color.gray.opacity(0.2)) .aspectRatio(1, contentMode: .fit) .overlay { Image(systemName: "opticaldisc") .font(.system(size: 36)) .foregroundStyle(.secondary) } } Text(album.name) .font(.caption.bold()) .lineLimit(2) .foregroundStyle(.primary) if let year = album.year { Text(String(year)) .font(.caption2) .foregroundStyle(.secondary) } } } } #Preview { NavigationStack { ArtistDetailView( artist: MAArtist( uri: "library://artist/1", name: "Test Artist", imageUrl: nil, sortName: nil, musicbrainzId: nil ) ) .environment(MAService()) } }