From 6c6aeae5bb26b35fb2228f7c956ddc272f08e72a Mon Sep 17 00:00:00 2001 From: diyaa Date: Mon, 14 Sep 2026 01:08:30 +0200 Subject: [PATCH] Add instrument region filtering --- .../Presentation/InstrumentBrowserView.swift | 27 ++++++++++++++----- .../LocalInstrumentCatalog.swift | 12 +++++++-- .../LocalInstrumentCatalogTests.swift | 15 +++++++++++ docs/TASKS.md | 2 +- 4 files changed, 47 insertions(+), 9 deletions(-) diff --git a/Sources/MusicAssistantApp/Presentation/InstrumentBrowserView.swift b/Sources/MusicAssistantApp/Presentation/InstrumentBrowserView.swift index da0a485..186908d 100644 --- a/Sources/MusicAssistantApp/Presentation/InstrumentBrowserView.swift +++ b/Sources/MusicAssistantApp/Presentation/InstrumentBrowserView.swift @@ -6,6 +6,7 @@ struct InstrumentBrowserView: View { @State private var searchText = "" @State private var selectedFamilyCategory: String? + @State private var selectedRegionOrigin: String? @Environment(\.dismiss) private var dismiss init(catalog: LocalInstrumentCatalog = LocalInstrumentCatalog()) { @@ -15,14 +16,27 @@ struct InstrumentBrowserView: View { var body: some View { NavigationStack { VStack(spacing: 0) { - Picker("Family", selection: $selectedFamilyCategory) { - Text("All families").tag(String?.none) + HStack { + Picker("Family", selection: $selectedFamilyCategory) { + Text("All families").tag(String?.none) - ForEach(catalog.familyCategories, id: \.self) { familyCategory in - Text(familyCategory).tag(Optional(familyCategory)) + ForEach(catalog.familyCategories, id: \.self) { familyCategory in + Text(familyCategory).tag(Optional(familyCategory)) + } } + .pickerStyle(.menu) + + Spacer() + + Picker("Region", selection: $selectedRegionOrigin) { + Text("All regions").tag(String?.none) + + ForEach(catalog.regionsOfOrigin, id: \.self) { regionOrigin in + Text(regionOrigin).tag(Optional(regionOrigin)) + } + } + .pickerStyle(.menu) } - .pickerStyle(.menu) .padding(.horizontal) .padding(.vertical, 8) @@ -31,7 +45,8 @@ struct InstrumentBrowserView: View { List( catalog.instruments( matching: searchText, - familyCategory: selectedFamilyCategory + familyCategory: selectedFamilyCategory, + regionOrigin: selectedRegionOrigin ), id: \.id ) { instrument in diff --git a/Sources/MusicAssistantCore/Services/InstrumentCatalog/LocalInstrumentCatalog.swift b/Sources/MusicAssistantCore/Services/InstrumentCatalog/LocalInstrumentCatalog.swift index d23a750..bce98d3 100644 --- a/Sources/MusicAssistantCore/Services/InstrumentCatalog/LocalInstrumentCatalog.swift +++ b/Sources/MusicAssistantCore/Services/InstrumentCatalog/LocalInstrumentCatalog.swift @@ -12,12 +12,19 @@ public struct LocalInstrumentCatalog: Sendable { .sorted { $0.localizedCaseInsensitiveCompare($1) == .orderedAscending } } + public var regionsOfOrigin: [String] { + Array(Set(items.compactMap(\.regionOrigin))) + .sorted { $0.localizedCaseInsensitiveCompare($1) == .orderedAscending } + } + public func instruments( matching query: String, - familyCategory: String? = nil + familyCategory: String? = nil, + regionOrigin: String? = nil ) -> [InstrumentCatalogItem] { let searchTokens = Self.searchTokens(for: query) let selectedFamilyCategory = familyCategory?.trimmingCharacters(in: .whitespacesAndNewlines) + let selectedRegionOrigin = regionOrigin?.trimmingCharacters(in: .whitespacesAndNewlines) return items .filter { item in @@ -25,7 +32,8 @@ public struct LocalInstrumentCatalog: Sendable { Self.searchableTerms(for: item).contains { $0.contains(token) } } let matchesFamily = selectedFamilyCategory?.isEmpty != false || item.familyCategory == selectedFamilyCategory - return matchesSearch && matchesFamily + let matchesRegion = selectedRegionOrigin?.isEmpty != false || item.regionOrigin == selectedRegionOrigin + return matchesSearch && matchesFamily && matchesRegion } .sorted { $0.name.localizedCaseInsensitiveCompare($1.name) == .orderedAscending } } diff --git a/Tests/MusicAssistantCoreTests/LocalInstrumentCatalogTests.swift b/Tests/MusicAssistantCoreTests/LocalInstrumentCatalogTests.swift index 48b6a44..0cc70e7 100644 --- a/Tests/MusicAssistantCoreTests/LocalInstrumentCatalogTests.swift +++ b/Tests/MusicAssistantCoreTests/LocalInstrumentCatalogTests.swift @@ -47,4 +47,19 @@ final class LocalInstrumentCatalogTests: XCTestCase { .allSatisfy { $0.familyCategory == "Plucked strings" } ) } + + func testRegionsAndFilteringComeFromCatalogMetadata() { + let catalog = LocalInstrumentCatalog() + let middleEasternPluckedStrings = catalog.instruments( + matching: "", + familyCategory: "Plucked strings", + regionOrigin: "Middle East" + ) + + XCTAssertTrue(catalog.regionsOfOrigin.contains("Middle East")) + XCTAssertFalse(middleEasternPluckedStrings.isEmpty) + XCTAssertTrue(middleEasternPluckedStrings.contains { $0.id == "oud" }) + XCTAssertTrue(middleEasternPluckedStrings.allSatisfy { $0.regionOrigin == "Middle East" }) + XCTAssertTrue(middleEasternPluckedStrings.allSatisfy { $0.familyCategory == "Plucked strings" }) + } } diff --git a/docs/TASKS.md b/docs/TASKS.md index 2c3445f..58e3619 100644 --- a/docs/TASKS.md +++ b/docs/TASKS.md @@ -84,7 +84,7 @@ requirement is missing and blocks implementation, record it in - [x] Keep catalog data out of SwiftUI views. - [x] Build searchable instrument browser. - [x] Add browsing/filtering by family/category. -- [ ] Add browsing/filtering by region/origin where useful. +- [x] Add browsing/filtering by region/origin where useful. - [ ] Add checkbox-based multi-select and deselect behavior. - [ ] Persist selected instruments on the current Song Project. - [ ] Make selected instruments available to OpenAI/song-generation