feat(mac,ios,playback,store): initial macos implementation

This commit is contained in:
hh committed 2026-09-23 04:56:48 +02:00
1 parent e5bb559d7f
commit d2d7166cf0
72 files changed
+4452 -318

No files matched your search

@@ -115,7 +115,7 @@ public actor MemoryLibrary: Library {
public func tracks(byArtist id: EntityID) -> [Track] {
tracksByID.values.filter { $0.artistID == id }
.sorted { (annotations[$0.id]?.playCount ?? 0, $1.title) > (annotations[$1.id]?.playCount ?? 0, $0.title) }
.sorted { (plays($0), $1.title) > (plays($1), $0.title) }
}
public func tracks(inGenre genre: String) -> [Track] {
@@ -181,14 +181,59 @@ public actor MemoryLibrary: Library {
public func search(_ query: String) -> SearchResults {
let needle = query.trimmingCharacters(in: .whitespaces)
guard !needle.isEmpty else { return SearchResults() }
func matches(_ text: String) -> Bool {
text.range(of: needle, options: [.caseInsensitive, .diacriticInsensitive]) != nil
func named(_ name: String) -> Int? {
relevance(of: needle, in: name)
}
func credited(_ title: String, _ artist: String) -> Int? {
[named(title), named(artist).map { $0 + 1 }].compactMap(\.self).min()
}
let popular = tracks().sorted { (plays($0), $1.title) > (plays($1), $0.title) }
let artists = ranked(artists()) { named($0.name) }
let tracks = Array(ranked(popular) { credited($0.title, $0.artistName) }.prefix(50))
let albums = ranked(albums(sortedBy: .title)) { credited($0.title, $0.artistName) }
let playlists = ranked(playlists()) { named($0.name) }
let candidates = artists.prefix(3).map { $0.map(SearchHit.artist) }
+ tracks.prefix(12).map { $0.map(SearchHit.track) }
+ albums.prefix(4).map { $0.map(SearchHit.album) }
+ playlists.prefix(3).map { $0.map(SearchHit.playlist) }
var results = SearchResults()
results.artists = artists().filter { matches($0.name) }
results.albums = albums(sortedBy: .title).filter { matches($0.title) }
results.tracks = Array(tracks().filter { matches($0.title) }.prefix(50))
results.playlists = playlists().filter { matches($0.name) }
results.top = ranked(candidates) { $0.score }.map(\.item.item)
results.artists = artists.map(\.item)
results.albums = albums.map(\.item)
results.tracks = tracks.map(\.item)
results.playlists = playlists.map(\.item)
return results
}
private func plays(_ track: Track) -> Int {
annotations[track.id]?.playCount ?? 0
}
}
private struct Ranked<Item> {
let item: Item
let score: Int
func map<Other>(_ transform: (Item) -> Other) -> Ranked<Other> {
Ranked<Other>(item: transform(item), score: score)
}
}
private func ranked<Item>(_ items: [Item], by score: (Item) -> Int?) -> [Ranked<Item>] {
items.enumerated()
.compactMap { offset, item in score(item).map { (Ranked(item: item, score: $0), offset) } }
.sorted { ($0.0.score, $0.1) < ($1.0.score, $1.1) }
.map(\.0)
}
private func relevance(of needle: String, in text: String) -> Int? {
let options: String.CompareOptions = [.caseInsensitive, .diacriticInsensitive]
guard let range = text.range(of: needle, options: options) else { return nil }
if text.compare(needle, options: options) == .orderedSame {
return 0
}
if range.lowerBound == text.startIndex {
return 1
}
return text[text.index(before: range.lowerBound)].isLetter ? 3 : 2
}