import SwiftUI struct StationListView: View { @Environment(AppModel.self) private var model var body: some View { VStack(spacing: 0) { if let error = model.library.lastError { Text(error) .foregroundStyle(.red) .frame(maxWidth: .infinity, alignment: .leading) .padding(.horizontal) .padding(.vertical, 8) .background(.red.opacity(0.08)) } if model.library.visibleStations.isEmpty && !model.library.isLoading { ContentUnavailableView( "No stations loaded yet", systemImage: "radio", description: Text("Load a station page or search worldwide.") ) } else { List(selection: Binding( get: { model.library.selectedStationID }, set: { model.library.selectedStationID = $0 } )) { ForEach(model.library.visibleStations) { station in StationRowView(station: station) .tag(station.id) .contentShape(Rectangle()) .onTapGesture { model.library.select(station) } .contextMenu { Button(model.library.isFavorite(station) ? "Remove Favorite" : "Add Favorite") { model.library.toggleFavorite(station) } if !model.library.groups.isEmpty { Menu("Add to Folder") { ForEach(model.library.groups) { group in Button(group.name) { model.library.addStationToGroup(station, group: group) } } } } Button("Play") { model.play(station) } } } } } } } } struct StationRowView: View { @Environment(AppModel.self) private var model let station: RadioStation var body: some View { HStack(spacing: 12) { Image(nsImage: model.artwork.image(for: station) ?? NSImage()) .resizable() .scaledToFill() .frame(width: 34, height: 34) .background(.quaternary) .clipShape(RoundedRectangle(cornerRadius: 6)) .overlay(alignment: .bottomTrailing) { Image(systemName: station.isOnline ? "dot.radiowaves.left.and.right" : "exclamationmark.triangle") .font(.system(size: 9, weight: .semibold)) .foregroundStyle(station.isOnline ? Color.secondary : Color.orange) .padding(2) .background(.regularMaterial, in: Circle()) .offset(x: 4, y: 4) } VStack(alignment: .leading, spacing: 4) { Text(station.name) .font(.headline) .lineLimit(1) HStack(spacing: 8) { if isCurrentStation { Text(model.playback.state.title) .foregroundStyle(statusColor) } if !station.country.isEmpty { Text(station.country) } if !station.codec.isEmpty { Text(station.codec.uppercased()) } if station.bitrate > 0 { Text("\(station.bitrate) kbps") } if station.votes > 0 { Text("\(station.votes) votes") } } .font(.caption) .foregroundStyle(.secondary) .lineLimit(1) } Spacer() Button { model.library.toggleFavorite(station) } label: { Image(systemName: model.library.isFavorite(station) ? "star.fill" : "star") } .buttonStyle(.plain) .foregroundStyle(model.library.isFavorite(station) ? .yellow : .secondary) Button { model.play(station) } label: { Image(systemName: "play.fill") } .buttonStyle(.bordered) } .padding(.vertical, 5) } private var isCurrentStation: Bool { model.playback.currentStation?.id == station.id } private var statusColor: Color { if case .failed = model.playback.state { return .red } if case .buffering = model.playback.state { return .orange } return .secondary } }