App/Sources/Intents/LibraryPageEntity.swift
75 lines · 2802 bytes
1import Answers
2import AppIntents
3import CoreSpotlight
4import Library
5
6/// A Library page for Siri, Shortcuts and Spotlight.
7struct LibraryPageEntity: IndexedEntity {
8 static let typeDisplayRepresentation: TypeDisplayRepresentation = "Library Page"
9 static let defaultQuery = LibraryPageQuery()
10
11 let page: LibraryPage
12 var id: String { page.id }
13
14 var displayRepresentation: DisplayRepresentation {
15 DisplayRepresentation(title: "\(page.title)", subtitle: "\(page.subtitle)", image: .init(systemName: page.section.symbol))
16 }
17
18 var attributeSet: CSSearchableItemAttributeSet {
19 let a = defaultAttributeSet
20 a.contentDescription = page.paragraphs.first
21 a.keywords = [page.section.title] + page.facts.map(\.value)
22 return a
23 }
24}
25
26struct LibraryPageQuery: EntityStringQuery {
27 func entities(for identifiers: [String]) async throws -> [LibraryPageEntity] {
28 identifiers.compactMap { Library.page(id: $0).map(LibraryPageEntity.init) }
29 }
30
31 func entities(matching string: String) async throws -> [LibraryPageEntity] {
32 Library.search(string).prefix(20).map(LibraryPageEntity.init)
33 }
34
35 /// The pages the Sky tab can turn to: the Moon, planets, constellations and named stars.
36 /// These are also the names Siri listens for in "Look up …" and "Show … in the sky".
37 func suggestedEntities() async throws -> [LibraryPageEntity] {
38 Library.pages.filter { $0.skyTarget != nil }.map(LibraryPageEntity.init)
39 }
40}
41
42extension LibrarySection {
43 var symbol: String {
44 switch self {
45 case .sun: "sun.max"
46 case .planets: "circle.circle"
47 case .dwarfPlanets: "circle.dotted"
48 case .moons: "moon"
49 case .constellations: "star.circle"
50 case .stars: "star"
51 case .deepSky: "sparkles"
52 case .exoplanets: "globe"
53 case .missions: "antenna.radiowaves.left.and.right"
54 case .rockets: "airplane.departure"
55 }
56 }
57}
58
59/// Indexes every Library page in Spotlight once per build; pages only change with an app release.
60enum SpotlightIndexer {
61 static let key = "spotlightIndexedBuild"
62
63 static func indexIfNeeded() async {
64 let build = Bundle.main.object(forInfoDictionaryKey: "CFBundleVersion") as? String ?? ""
65 guard UserDefaults.standard.string(forKey: key) != build else { return }
66 let index = CSSearchableIndex(name: "library")
67 do {
68 try await index.deleteAppEntities(ofType: LibraryPageEntity.self)
69 try await index.indexAppEntities(Library.pages.map(LibraryPageEntity.init))
70 UserDefaults.standard.set(build, forKey: key)
71 } catch {
72 log.error("Spotlight indexing failed: \(error.localizedDescription, privacy: .public)")
73 }
74 }
75}