Add language catalog selector

This commit is contained in:
diyaa
2026-09-15 23:20:48 +02:00
parent b43c5416f2
commit c8242a844a
10 changed files with 425 additions and 14 deletions
@@ -0,0 +1,128 @@
import MusicAssistantCore
import SwiftUI
struct LanguageBrowserView: View {
@Binding private var project: SongProject
private let saveProject: (SongProject) async -> Bool
private let catalog: LocalLanguageCatalog
@State private var searchText = ""
@State private var selectedFamilyCategory: String?
@State private var hasUnsavedSelectionChanges = false
@State private var isSaving = false
@Environment(\.dismiss) private var dismiss
init(
project: Binding<SongProject>,
saveProject: @escaping (SongProject) async -> Bool = { _ in true },
catalog: LocalLanguageCatalog = LocalLanguageCatalog()
) {
_project = project
self.saveProject = saveProject
self.catalog = catalog
}
var body: some View {
NavigationStack {
List(
catalog.languages(
matching: searchText,
familyCategory: selectedFamilyCategory
),
id: \.id
) { language in
Toggle(isOn: selectionBinding(for: language)) {
VStack(alignment: .leading, spacing: 4) {
Text(language.name)
.font(.body.weight(.medium))
Text(language.familyCategory)
.font(.caption)
.foregroundStyle(.secondary)
}
}
.toggleStyle(.checkbox)
.padding(.vertical, 2)
}
.navigationTitle("Language Catalog")
.searchable(text: $searchText, prompt: "Search languages")
.toolbar {
ToolbarItem(placement: .automatic) {
Picker("Region", selection: $selectedFamilyCategory) {
Text("All regions").tag(String?.none)
ForEach(catalog.familyCategories, id: \.self) { familyCategory in
Text(familyCategory).tag(Optional(familyCategory))
}
}
.pickerStyle(.menu)
}
ToolbarItem(placement: .cancellationAction) {
Button("Done") {
Task {
await saveSelectionChangesIfNeeded()
dismiss()
}
}
.disabled(isSaving)
}
if isSaving {
ToolbarItem(placement: .status) {
ProgressView()
.controlSize(.small)
}
}
}
}
.frame(minWidth: 480, minHeight: 560)
.onDisappear {
guard hasUnsavedSelectionChanges, !isSaving else { return }
let projectToSave = project
hasUnsavedSelectionChanges = false
Task {
_ = await saveProject(projectToSave)
}
}
}
private func selectionBinding(for item: LanguageCatalogItem) -> Binding<Bool> {
Binding {
project.language(for: item.id) != nil
} set: { isSelected in
if isSelected {
project.upsertLanguage(item.language)
} else {
project.removeLanguage(id: item.id)
}
hasUnsavedSelectionChanges = true
}
}
private func saveSelectionChangesIfNeeded() async {
guard hasUnsavedSelectionChanges else { return }
isSaving = true
let didSave = await saveProject(project)
hasUnsavedSelectionChanges = !didSave
isSaving = false
}
}
#Preview {
LanguageBrowserPreview()
}
private struct LanguageBrowserPreview: View {
@State private var project = SongProject(
title: "Language Catalog Preview",
idea: "Choose song languages."
)
var body: some View {
LanguageBrowserView(project: $project)
}
}
@@ -6,10 +6,10 @@ struct ProjectInspectorView: View {
let saveProject: (SongProject) async -> Bool
@State private var newInstrumentName = ""
@State private var newLanguageName = ""
@State private var isInstrumentBrowserPresented = false
@State private var isGenreStyleBrowserPresented = false
@State private var isVocalistBrowserPresented = false
@State private var isLanguageBrowserPresented = false
@State private var isSaving = false
@State private var saveStatus: SaveStatus?
@@ -100,17 +100,18 @@ struct ProjectInspectorView: View {
}
Section("Languages") {
Button {
isLanguageBrowserPresented = true
} label: {
Label("Browse languages", systemImage: "character.book.closed")
}
EditableTokenList(
values: project.languages.map(\.name),
removeAction: { index in
project.removeLanguage(id: project.languages[index].id)
}
)
AddTextRow(
placeholder: "Add language",
text: $newLanguageName,
addAction: addLanguage
)
}
Section("Arabic Pronunciation") {
@@ -157,6 +158,9 @@ struct ProjectInspectorView: View {
.sheet(isPresented: $isVocalistBrowserPresented) {
VocalistBrowserView(project: $project, saveProject: saveProject)
}
.sheet(isPresented: $isLanguageBrowserPresented) {
LanguageBrowserView(project: $project, saveProject: saveProject)
}
}
private var durationSecondsBinding: Binding<String> {
@@ -187,13 +191,6 @@ struct ProjectInspectorView: View {
newInstrumentName = ""
}
private func addLanguage() {
let name = trimmed(newLanguageName)
guard !name.isEmpty else { return }
project.upsertLanguage(LanguageSelection(id: stableID(from: name), name: name))
newLanguageName = ""
}
private func save() async {
isSaving = true
let didSave = await saveProject(project)