feat: wire SmartPlaylistBuilderSheet into menu and context menu

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
feat/music-streaming
Laurent 1 month ago
parent 9eb47b61e1
commit 7b88b180fd
  1. 118
      Music/ContentView.swift
  2. 8
      Music/MusicApp.swift
  3. 7
      Music/Views/PlaylistBarView.swift

@ -9,9 +9,11 @@ struct ContentView: View {
var shazam: ShazamService
var db: DatabaseService
@Binding var showNewPlaylistAlert: Bool
@Binding var showSmartPlaylistBuilder: Bool
var networkStatus: NetworkStatus?
@State private var showRenameAlert = false
@State private var showEditQueryAlert = false
@State private var smartPlaylistBuilderEditing: SmartPlaylist?
@State private var playlistNameInput = ""
@State private var editQueryInput = ""
@State private var itemToRename: (any PlaylistRepresentable)?
@ -24,52 +26,60 @@ struct ContentView: View {
@State private var totalDuration: Double = 0
@State private var monthlyAdditions: [MonthlyCount] = []
var body: some View {
VStack(spacing: 0) {
if let status = networkStatus {
switch status.mode {
case .remote(let hostName):
HStack(spacing: 8) {
Image(systemName: "antenna.radiowaves.left.and.right")
.font(.system(size: 10)).foregroundStyle(.blue)
Text("Connected to \(hostName)")
.font(.system(size: 11, weight: .medium)).foregroundStyle(.blue)
Spacer()
/// The remote/streaming connection status banner. Extracted from `body` so the
/// type-checker doesn't have to solve the whole (very large) view in one expression
/// without this, a clean build fails with "unable to type-check in reasonable time".
@ViewBuilder
private var networkBanner: some View {
if let status = networkStatus {
switch status.mode {
case .remote(let hostName):
HStack(spacing: 8) {
Image(systemName: "antenna.radiowaves.left.and.right")
.font(.system(size: 10)).foregroundStyle(.blue)
Text("Connected to \(hostName)")
.font(.system(size: 11, weight: .medium)).foregroundStyle(.blue)
Spacer()
Button("Refresh") { status.onRefreshLibrary?() }
.font(.system(size: 11)).buttonStyle(.plain).foregroundStyle(.secondary)
Button("Disconnect") { status.onDisconnect?() }
.font(.system(size: 11)).buttonStyle(.plain).foregroundStyle(.red)
}
.padding(.horizontal, 12).padding(.vertical, 4)
.background(Color.blue.opacity(0.08))
case .hosting(let remoteName):
HStack(spacing: 8) {
Image(systemName: "antenna.radiowaves.left.and.right")
.font(.system(size: 10)).foregroundStyle(.green)
Text(remoteName != nil ? "Hosting · \(remoteName!) connected" : "Hosting")
.font(.system(size: 11, weight: .medium)).foregroundStyle(.green)
Spacer()
}
.padding(.horizontal, 12).padding(.vertical, 4)
.background(Color.green.opacity(0.08))
case .streamHosting, .streamClient:
HStack(spacing: 8) {
Image(systemName: "cloud")
.font(.system(size: 10)).foregroundStyle(.purple)
Text(status.statusMessage)
.font(.system(size: 11, weight: .medium)).foregroundStyle(.purple)
Spacer()
if case .streamClient = status.mode {
Button("Refresh") { status.onRefreshLibrary?() }
.font(.system(size: 11)).buttonStyle(.plain).foregroundStyle(.secondary)
Button("Disconnect") { status.onDisconnect?() }
.font(.system(size: 11)).buttonStyle(.plain).foregroundStyle(.red)
}
.padding(.horizontal, 12).padding(.vertical, 4)
.background(Color.blue.opacity(0.08))
case .hosting(let remoteName):
HStack(spacing: 8) {
Image(systemName: "antenna.radiowaves.left.and.right")
.font(.system(size: 10)).foregroundStyle(.green)
Text(remoteName != nil ? "Hosting · \(remoteName!) connected" : "Hosting")
.font(.system(size: 11, weight: .medium)).foregroundStyle(.green)
Spacer()
}
.padding(.horizontal, 12).padding(.vertical, 4)
.background(Color.green.opacity(0.08))
case .streamHosting, .streamClient:
HStack(spacing: 8) {
Image(systemName: "cloud")
.font(.system(size: 10)).foregroundStyle(.purple)
Text(status.statusMessage)
.font(.system(size: 11, weight: .medium)).foregroundStyle(.purple)
Spacer()
if case .streamClient = status.mode {
Button("Refresh") { status.onRefreshLibrary?() }
.font(.system(size: 11)).buttonStyle(.plain).foregroundStyle(.secondary)
}
Button("Disconnect") { status.onDisconnect?() }
.font(.system(size: 11)).buttonStyle(.plain).foregroundStyle(.red)
}
.padding(.horizontal, 12).padding(.vertical, 4)
.background(Color.purple.opacity(0.08))
Button("Disconnect") { status.onDisconnect?() }
.font(.system(size: 11)).buttonStyle(.plain).foregroundStyle(.red)
}
.padding(.horizontal, 12).padding(.vertical, 4)
.background(Color.purple.opacity(0.08))
}
}
}
var body: some View {
VStack(spacing: 0) {
networkBanner
SearchBarView(
searchText: $searchText,
@ -239,6 +249,9 @@ struct ContentView: View {
smartPlaylistToEdit = smart
editQueryInput = smart.searchQuery
showEditQueryAlert = true
},
onEditConditions: { smart in
smartPlaylistBuilderEditing = smart
}
)
@ -327,6 +340,29 @@ struct ContentView: View {
Text(error)
}
}
.sheet(isPresented: $showSmartPlaylistBuilder) {
SmartPlaylistBuilderSheet(
editingPlaylist: nil,
onSave: { name, conditions in
try? playlist.createSmartPlaylist(name: name, conditions: conditions)
showSmartPlaylistBuilder = false
},
onCancel: { showSmartPlaylistBuilder = false }
)
}
.sheet(item: $smartPlaylistBuilderEditing) { smart in
SmartPlaylistBuilderSheet(
editingPlaylist: smart,
onSave: { name, conditions in
if name != smart.name {
try? playlist.renameSmartPlaylist(smart, to: name)
}
try? playlist.updateSmartPlaylistConditions(smart, to: conditions)
smartPlaylistBuilderEditing = nil
},
onCancel: { smartPlaylistBuilderEditing = nil }
)
}
}
private var playerControls: some View {

@ -11,6 +11,7 @@ struct MusicApp: App {
@State private var shazamService = ShazamService()
@State private var playlistVM: PlaylistViewModel?
@State private var showNewPlaylistAlert = false
@State private var showSmartPlaylistBuilder = false
@State private var initError: String?
@State private var hostServer: HostServer?
@State private var remoteClient = RemoteClient()
@ -38,6 +39,7 @@ struct MusicApp: App {
shazam: shazamService,
db: db,
showNewPlaylistAlert: $showNewPlaylistAlert,
showSmartPlaylistBuilder: $showSmartPlaylistBuilder,
networkStatus: computeNetworkStatus()
)
} else if let error = initError {
@ -83,6 +85,12 @@ struct MusicApp: App {
.keyboardShortcut("n")
.disabled(remoteClient.connectionState.isConnected)
Button("New Smart Playlist...") {
showSmartPlaylistBuilder = true
}
.keyboardShortcut("n", modifiers: [.command, .shift])
.disabled(remoteClient.connectionState.isConnected)
Divider()
Toggle("Enable Host Mode", isOn: Binding(

@ -11,6 +11,7 @@ struct PlaylistBarView: View {
var onRename: (any PlaylistRepresentable) -> Void
var onDelete: (any PlaylistRepresentable) -> Void
var onEditQuery: (SmartPlaylist) -> Void
var onEditConditions: (SmartPlaylist) -> Void
var body: some View {
FlowLayout(spacing: 6) {
@ -39,7 +40,11 @@ struct PlaylistBarView: View {
if !isRemoteMode {
Button("Rename...") { onRename(item) }
if let smart = item as? SmartPlaylist {
Button("Edit Search Query...") { onEditQuery(smart) }
if smart.conditions != nil {
Button("Edit...") { onEditConditions(smart) }
} else {
Button("Edit Search Query...") { onEditQuery(smart) }
}
}
Button("Delete") { onDelete(item) }
}

Loading…
Cancel
Save