Apple Music fix

This commit is contained in:
2026-04-07 20:15:56 +02:00
parent fe3ed1e204
commit f55b7e478b
7 changed files with 145 additions and 51 deletions
@@ -18,7 +18,7 @@ struct ArtistDetailView: View {
@State private var showError = false
@State private var kenBurnsScale: CGFloat = 1.0
@State private var isBiographyExpanded = false
@State private var scrollPositionAlbumID: String? = nil
@State private var kenBurnsStarted = false
var body: some View {
ZStack {
@@ -53,7 +53,6 @@ struct ArtistDetailView: View {
}
}
}
.scrollPosition(id: $scrollPositionAlbumID)
}
.navigationTitle(artist.name)
.navigationBarTitleDisplayMode(.inline)
@@ -70,6 +69,8 @@ struct ArtistDetailView: View {
await loadArtistDetail()
}
.onAppear {
guard !kenBurnsStarted else { return }
kenBurnsStarted = true
withAnimation(.linear(duration: 20).repeatForever(autoreverses: true)) {
kenBurnsScale = 1.15
}
@@ -126,10 +127,47 @@ struct ArtistDetailView: View {
}
// MARK: - Artist Header
/// All distinct music providers for this artist.
/// Uses the authoritative provider_mappings field from MA when available,
/// and falls back to scanning loaded album metadata.
private var artistProviders: [MusicProvider] {
var seen = Set<MusicProvider>()
var result = [MusicProvider]()
// Primary: provider_mappings from the artist object (available immediately)
for mapping in artist.providerMappings {
if let p = MusicProvider.from(providerKey: mapping.providerDomain), !seen.contains(p) {
seen.insert(p)
result.append(p)
}
}
// Fallback: scan loaded albums when providerMappings is empty
if result.isEmpty {
for album in albums {
if let scheme = album.uri.components(separatedBy: "://").first,
let p = MusicProvider.from(scheme: scheme),
p != .library,
!seen.contains(p) {
seen.insert(p)
result.append(p)
}
for key in album.metadata?.images?.compactMap({ $0.provider }) ?? [] {
if let p = MusicProvider.from(providerKey: key), !seen.contains(p) {
seen.insert(p)
result.append(p)
}
}
}
}
return result
}
@ViewBuilder
private var artistHeader: some View {
VStack(spacing: 16) {
VStack(spacing: 12) {
CachedAsyncImage(url: service.imageProxyURL(path: artist.imageUrl, provider: artist.imageProvider, size: 512)) { image in
image
.resizable()
@@ -146,18 +184,28 @@ struct ArtistDetailView: View {
.frame(width: 200, height: 200)
.clipShape(Circle())
.shadow(color: .black.opacity(0.5), radius: 20, y: 10)
HStack(spacing: 6) {
ProviderBadge(uri: artist.uri, imageProvider: artist.imageProvider)
if !albums.isEmpty {
Text("\(albums.count) albums")
.font(.subheadline)
.fontWeight(.semibold)
.foregroundStyle(.white.opacity(0.8))
.shadow(color: .black.opacity(0.3), radius: 2, x: 0, y: 1)
// One badge per distinct source
if !artistProviders.isEmpty {
HStack(spacing: 4) {
ForEach(artistProviders, id: \.self) { provider in
Image(systemName: provider.icon)
.font(.system(size: 9, weight: .bold))
.foregroundStyle(.white)
.frame(width: 20, height: 20)
.background(.black.opacity(0.55))
.clipShape(Circle())
}
}
}
if !albums.isEmpty {
Text("\(albums.count) albums")
.font(.subheadline)
.fontWeight(.semibold)
.foregroundStyle(.white.opacity(0.8))
.shadow(color: .black.opacity(0.3), radius: 2, x: 0, y: 1)
}
}
.padding(.top)
}
@@ -184,7 +232,6 @@ struct ArtistDetailView: View {
.buttonStyle(.plain)
}
}
.scrollTargetLayout()
.padding(.horizontal)
.padding(.bottom, 24)
}