Create BearWave macOS radio-first app
This commit is contained in:
140
Sources/BearWaveCore/Views/StationListView.swift
Normal file
140
Sources/BearWaveCore/Views/StationListView.swift
Normal file
@@ -0,0 +1,140 @@
|
||||
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
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user