117 lines
5.3 KiB
Swift
117 lines
5.3 KiB
Swift
import SwiftUI
|
|
|
|
/// Saved internet radio stations. Tap to play, swipe to delete, long press to edit.
|
|
struct WebRadioScreen: View {
|
|
@Environment(\.palette) private var p
|
|
@State private var editing: WebStation?
|
|
private var g: AppGraph { .shared }
|
|
|
|
var body: some View {
|
|
let s = g.settings
|
|
let radio = g.radio
|
|
let list = s.stations
|
|
List {
|
|
if let on = radio.station {
|
|
Section {
|
|
HStack(spacing: 14) {
|
|
SettingsIcon(symbol: "antenna.radiowaves.left.and.right", bg: 0xFF2D55)
|
|
VStack(alignment: .leading, spacing: 1) {
|
|
Text(on.name).foregroundStyle(p.label).lineLimit(1)
|
|
Text(radio.playing ? "Live" : "Paused").font(.footnote).foregroundStyle(p.label2)
|
|
}
|
|
Spacer()
|
|
Button { radio.toggle() } label: {
|
|
Image(systemName: radio.playing ? "pause.fill" : "play.fill").font(.system(size: 20)).frame(width: 40, height: 40)
|
|
}
|
|
.buttonStyle(.borderless)
|
|
Button { radio.stop() } label: {
|
|
Image(systemName: "stop.fill").font(.system(size: 18)).frame(width: 40, height: 40)
|
|
}
|
|
.buttonStyle(.borderless)
|
|
}
|
|
.themedRow()
|
|
} header: { Text("Now Playing").foregroundStyle(p.label2) }
|
|
}
|
|
Section {
|
|
ForEach(list) { st in
|
|
Button { radio.play(st) } label: {
|
|
HStack(spacing: 14) {
|
|
SettingsIcon(symbol: "dot.radiowaves.left.and.right", bg: 0x5856D6)
|
|
VStack(alignment: .leading, spacing: 1) {
|
|
Text(st.name).foregroundStyle(p.label).lineLimit(1)
|
|
Text(st.url).font(.footnote).foregroundStyle(p.label2).lineLimit(1)
|
|
}
|
|
Spacer()
|
|
if radio.station?.id == st.id { PlayingBars(playing: radio.playing) }
|
|
}
|
|
}
|
|
.themedRow()
|
|
.swipeActions(edge: .leading) { Button("Edit") { editing = st }.tint(.orange) }
|
|
.contextMenu {
|
|
Button { editing = st } label: { Label("Edit", systemImage: "pencil") }
|
|
Button(role: .destructive) { remove(st) } label: { Label("Delete", systemImage: "trash") }
|
|
}
|
|
}
|
|
.onDelete { rows in rows.map { list[$0] }.forEach(remove) }
|
|
Button { editing = WebStation(name: "", url: "") } label: { Label("Add Station", systemImage: "plus.circle.fill") }
|
|
.themedRow()
|
|
} header: { Text("Stations").foregroundStyle(p.label2) } footer: {
|
|
Text("Any direct stream URL works (MP3, AAC or HLS). Playing a station pauses your queue, and playing a song stops the station.")
|
|
.foregroundStyle(p.label2)
|
|
}
|
|
}
|
|
.listStyle(.insetGrouped)
|
|
.themedList()
|
|
.navigationTitle("Web Radio")
|
|
.sheet(item: $editing) { st in StationSheet(station: st) }
|
|
}
|
|
|
|
private func remove(_ st: WebStation) {
|
|
if g.radio.station?.id == st.id { g.radio.stop() }
|
|
g.settings.stations.removeAll { $0.id == st.id }
|
|
}
|
|
}
|
|
|
|
/// Name and stream URL for a new or existing station.
|
|
private struct StationSheet: View {
|
|
let station: WebStation
|
|
@Environment(\.dismiss) private var dismiss
|
|
@State private var name = ""
|
|
@State private var url = ""
|
|
|
|
var body: some View {
|
|
let n = name.trimmingCharacters(in: .whitespaces)
|
|
let u = url.trimmingCharacters(in: .whitespaces)
|
|
let valid = !n.isEmpty && URL(string: u).map { ["http", "https"].contains($0.scheme?.lowercased() ?? "") } == true
|
|
NavigationStack {
|
|
List {
|
|
Section {
|
|
TextField("Name", text: $name).themedRow()
|
|
TextField("Stream URL", text: $url)
|
|
.keyboardType(.URL).textInputAutocapitalization(.never).autocorrectionDisabled()
|
|
.themedRow()
|
|
}
|
|
}
|
|
.listStyle(.insetGrouped)
|
|
.themedList()
|
|
.navigationTitle(station.name.isEmpty ? "New Station" : "Edit Station")
|
|
.navigationBarTitleDisplayMode(.inline)
|
|
.toolbar {
|
|
ToolbarItem(placement: .cancellationAction) { Button("Cancel") { dismiss() } }
|
|
ToolbarItem(placement: .confirmationAction) {
|
|
Button("Save") {
|
|
let s = AppGraph.shared.settings
|
|
var list = s.stations
|
|
let st = WebStation(id: station.id, name: n, url: u)
|
|
if let i = list.firstIndex(where: { $0.id == station.id }) { list[i] = st } else { list.append(st) }
|
|
s.stations = list
|
|
dismiss()
|
|
}
|
|
.disabled(!valid)
|
|
}
|
|
}
|
|
}
|
|
.onAppear { name = station.name; url = station.url }
|
|
}
|
|
}
|