// // ProviderBadge.swift // Mobile Music Assistant // // Created by Sven Hanold on 06.04.26. // import SwiftUI /// Monochrome badges indicating which music provider(s) an item comes from. /// For provider-specific URIs (e.g. spotify://) a single badge is shown. /// For library:// items all distinct source providers found in the metadata /// images are shown side by side. struct ProviderBadge: View { let uri: String var metadata: MediaItemMetadata? = nil private var providers: [MusicProvider] { // Use string-based parsing — URL(string:) returns nil for schemes with // underscores like "apple_music" which violate RFC 2396. let scheme = uri.components(separatedBy: "://").first?.lowercased() // Non-library URI → show only that provider if let fromScheme = MusicProvider.from(scheme: scheme), fromScheme != .library { return [fromScheme] } // library:// URI → collect all distinct providers from metadata images var seen = Set() var result = [MusicProvider]() let keys = metadata?.images?.compactMap { $0.provider } ?? [] for key in keys { if let p = MusicProvider.from(providerKey: key), !seen.contains(p) { seen.insert(p) result.append(p) } } // Nothing found for a library item → fall back to the library badge if result.isEmpty && scheme == "library" { return [.library] } return result } var body: some View { HStack(spacing: 4) { ForEach(providers, 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()) } } } } // MARK: - Provider Mapping enum MusicProvider: Hashable { case library case subsonic case spotify case tidal case qobuz case plex case ytmusic case appleMusic case deezer case soundcloud case tunein case filesystem case jellyfin case dlna /// SF Symbol name for this provider. var icon: String { switch self { case .library: return "building.columns.fill" case .subsonic: return "ferry.fill" case .spotify: return "antenna.radiowaves.left.and.right.circle.fill" case .tidal: return "water.waves" case .qobuz: return "hifispeaker.fill" case .plex: return "play.square.stack.fill" case .ytmusic: return "play.rectangle.fill" case .appleMusic: return "applelogo" case .deezer: return "waveform" case .soundcloud: return "cloud.fill" case .tunein: return "radio.fill" case .filesystem: return "folder.fill" case .jellyfin: return "server.rack" case .dlna: return "wifi" } } /// Match a URI scheme to a known provider. static func from(scheme: String?) -> MusicProvider? { guard let scheme = scheme?.lowercased() else { return nil } if scheme == "library" { return .library } if scheme.hasPrefix("subsonic") { return .subsonic } if scheme.hasPrefix("spotify") { return .spotify } if scheme.hasPrefix("tidal") { return .tidal } if scheme.hasPrefix("qobuz") { return .qobuz } if scheme.hasPrefix("plex") { return .plex } if scheme.hasPrefix("ytmusic") { return .ytmusic } if scheme.hasPrefix("apple") { return .appleMusic } if scheme.hasPrefix("deezer") { return .deezer } if scheme.hasPrefix("soundcloud") { return .soundcloud } if scheme.hasPrefix("tunein") { return .tunein } if scheme.hasPrefix("filesystem") { return .filesystem } if scheme.hasPrefix("jellyfin") { return .jellyfin } if scheme.hasPrefix("dlna") { return .dlna } return nil } /// Match a provider key from image metadata (e.g. "subsonic", "spotify", "filesystem_local"). static func from(providerKey key: String) -> MusicProvider? { let k = key.lowercased() if k.hasPrefix("subsonic") { return .subsonic } if k.hasPrefix("spotify") { return .spotify } if k.hasPrefix("tidal") { return .tidal } if k.hasPrefix("qobuz") { return .qobuz } if k.hasPrefix("plex") { return .plex } if k.hasPrefix("ytmusic") { return .ytmusic } if k.hasPrefix("apple") { return .appleMusic } if k.hasPrefix("deezer") { return .deezer } if k.hasPrefix("soundcloud") { return .soundcloud } if k.hasPrefix("tunein") { return .tunein } if k.hasPrefix("filesystem") { return .filesystem } if k.hasPrefix("jellyfin") { return .jellyfin } if k.hasPrefix("dlna") { return .dlna } // Image-only metadata providers — not a music source if k == "lastfm" || k == "musicbrainz" || k == "fanarttv" { return nil } return nil } }