import MusicAssistantCore import SwiftUI struct FinalReviewView: View { @Binding var project: SongProject @State private var newFieldKey = "" @State private var newFieldValue = "" var body: some View { VStack(alignment: .leading, spacing: 12) { TextField("Approved lyrics", text: $project.lyrics.text, axis: .vertical) .lineLimit(4...10) if !project.orderedSections.isEmpty { VStack(alignment: .leading, spacing: 8) { Text("Section lyrics") .font(.headline) ForEach(project.orderedSections) { section in TextField(section.title, text: sectionLyricsBinding(for: section.id), axis: .vertical) .lineLimit(2...5) } } } Divider() HStack { Text("Suno fields") .font(.headline) Spacer() Button { ensureSunoOutput() } label: { Label("Prepare fields", systemImage: "wand.and.stars") } .disabled(project.sunoOutput != nil) } TextField("Suno lyrics", text: sunoLyricsBinding, axis: .vertical) .lineLimit(4...10) TextField("Suno style prompt", text: sunoStylePromptBinding, axis: .vertical) .lineLimit(3...8) if !additionalFieldKeys.isEmpty { VStack(alignment: .leading, spacing: 8) { Text("Additional fields") .font(.headline) ForEach(additionalFieldKeys, id: \.self) { key in HStack(alignment: .top, spacing: 8) { TextField("Field", text: additionalFieldKeyBinding(for: key)) TextField("Value", text: additionalFieldValueBinding(for: key), axis: .vertical) .lineLimit(1...4) Button { project.sunoOutput?.additionalFields.removeValue(forKey: key) } label: { Label("Remove field", systemImage: "minus.circle") .labelStyle(.iconOnly) } .buttonStyle(.borderless) .help("Remove field") } } } } HStack { TextField("Field name", text: $newFieldKey) TextField("Value", text: $newFieldValue) Button(action: addAdditionalField) { Label("Add field", systemImage: "plus.circle.fill") .labelStyle(.iconOnly) } .buttonStyle(.borderless) .disabled(newFieldKey.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty) .help("Add field") } } } private var additionalFieldKeys: [String] { guard let additionalFields = project.sunoOutput?.additionalFields else { return [] } return additionalFields.keys.sorted { $0.localizedStandardCompare($1) == .orderedAscending } } private var sunoLyricsBinding: Binding { Binding { project.sunoOutput?.lyricsText ?? "" } set: { newValue in ensureSunoOutput() project.sunoOutput?.lyricsText = newValue } } private var sunoStylePromptBinding: Binding { Binding { project.sunoOutput?.stylePrompt ?? "" } set: { newValue in ensureSunoOutput() project.sunoOutput?.stylePrompt = newValue } } private func sectionLyricsBinding(for sectionID: String) -> Binding { Binding { project.lyrics.sectionTexts[sectionID] ?? "" } set: { newValue in if newValue.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty { project.lyrics.sectionTexts.removeValue(forKey: sectionID) } else { project.lyrics.sectionTexts[sectionID] = newValue } } } private func additionalFieldKeyBinding(for key: String) -> Binding { Binding { key } set: { newKey in let trimmedKey = newKey.trimmingCharacters(in: .whitespacesAndNewlines) guard !trimmedKey.isEmpty, trimmedKey != key else { return } ensureSunoOutput() let value = project.sunoOutput?.additionalFields.removeValue(forKey: key) ?? "" project.sunoOutput?.additionalFields[trimmedKey] = value } } private func additionalFieldValueBinding(for key: String) -> Binding { Binding { project.sunoOutput?.additionalFields[key] ?? "" } set: { newValue in ensureSunoOutput() project.sunoOutput?.additionalFields[key] = newValue } } private func addAdditionalField() { let key = newFieldKey.trimmingCharacters(in: .whitespacesAndNewlines) guard !key.isEmpty else { return } ensureSunoOutput() project.sunoOutput?.additionalFields[key] = newFieldValue newFieldKey = "" newFieldValue = "" } private func ensureSunoOutput() { guard project.sunoOutput == nil else { return } project.sunoOutput = SunoOutput( lyricsText: project.lyrics.text, stylePrompt: "", generatedAt: Date() ) } } #Preview { FinalReviewPreview() } private struct FinalReviewPreview: View { @State private var project = SongProject( title: "Review", idea: "Review generated fields", sections: [ SongSection(id: "verse", type: .verse, title: "Verse"), SongSection(id: "chorus", type: .chorus, title: "Chorus", order: 1) ], lyrics: Lyrics(text: "Draft lyrics", sectionTexts: ["verse": "Verse text"]), sunoOutput: SunoOutput( lyricsText: "Draft lyrics", stylePrompt: "Cinematic pop with oud", additionalFields: ["mood": "Hopeful"] ) ) var body: some View { Form { FinalReviewView(project: $project) } .formStyle(.grouped) .frame(width: 420) } }