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. 40
      Music/ContentView.swift
  2. 8
      Music/MusicApp.swift
  3. 5
      Music/Views/PlaylistBarView.swift

@ -9,9 +9,11 @@ struct ContentView: View {
var shazam: ShazamService var shazam: ShazamService
var db: DatabaseService var db: DatabaseService
@Binding var showNewPlaylistAlert: Bool @Binding var showNewPlaylistAlert: Bool
@Binding var showSmartPlaylistBuilder: Bool
var networkStatus: NetworkStatus? var networkStatus: NetworkStatus?
@State private var showRenameAlert = false @State private var showRenameAlert = false
@State private var showEditQueryAlert = false @State private var showEditQueryAlert = false
@State private var smartPlaylistBuilderEditing: SmartPlaylist?
@State private var playlistNameInput = "" @State private var playlistNameInput = ""
@State private var editQueryInput = "" @State private var editQueryInput = ""
@State private var itemToRename: (any PlaylistRepresentable)? @State private var itemToRename: (any PlaylistRepresentable)?
@ -24,8 +26,11 @@ struct ContentView: View {
@State private var totalDuration: Double = 0 @State private var totalDuration: Double = 0
@State private var monthlyAdditions: [MonthlyCount] = [] @State private var monthlyAdditions: [MonthlyCount] = []
var body: some View { /// The remote/streaming connection status banner. Extracted from `body` so the
VStack(spacing: 0) { /// 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 { if let status = networkStatus {
switch status.mode { switch status.mode {
case .remote(let hostName): case .remote(let hostName):
@ -70,6 +75,11 @@ struct ContentView: View {
.background(Color.purple.opacity(0.08)) .background(Color.purple.opacity(0.08))
} }
} }
}
var body: some View {
VStack(spacing: 0) {
networkBanner
SearchBarView( SearchBarView(
searchText: $searchText, searchText: $searchText,
@ -239,6 +249,9 @@ struct ContentView: View {
smartPlaylistToEdit = smart smartPlaylistToEdit = smart
editQueryInput = smart.searchQuery editQueryInput = smart.searchQuery
showEditQueryAlert = true showEditQueryAlert = true
},
onEditConditions: { smart in
smartPlaylistBuilderEditing = smart
} }
) )
@ -327,6 +340,29 @@ struct ContentView: View {
Text(error) 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 { private var playerControls: some View {

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

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

Loading…
Cancel
Save