Files
bearwave-mac/Sources/BearWaveCore/Views/StationListView.swift
2026-06-25 09:21:42 +02:00

141 lines
5.2 KiB
Swift

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
}
}