98badc98ad
Bundles the 100 most common reflexive verbs from spanishwithdaniel.com as a canonical list and wires it through the UI. Compound list entries (recibirse / graduarse, equivocarse / confundirse) are split. Trailing prepositions and set-phrase completions are captured as usageHint (e.g. acordarse "de", ponerse "de acuerdo"). ReflexiveVerbStore loads the JSON at launch and exposes lookups by base infinitive, both via @Environment for SwiftUI and a static shared instance for services. Verbs whose bare infinitive isn't in the list skip the UI treatment silently. VerbDetailView shows a new Reflexive section with the reflexive infinitive, usage hint, and English meaning when there is a match. VerbListView gains a "Reflexive verbs only" filter alongside the existing Level and Irregularity filters. Settings adds the same flag so it also constrains the practice pool; PracticeSessionService applies the reflexive filter in all six pick paths. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
60 lines
2.3 KiB
Swift
60 lines
2.3 KiB
Swift
import Foundation
|
|
import SharedModels
|
|
|
|
/// Loads and queries the curated reflexive-verb list bundled with the app
|
|
/// (Gitea issue #28). One JSON load at init; in-memory lookup thereafter.
|
|
///
|
|
/// `entries(for:)` returns a list because a single base infinitive may map to
|
|
/// multiple reflexive entries — e.g., `ponerse` covers both "to put on
|
|
/// (clothing) / to become" and "to come to an agreement (with)".
|
|
@MainActor
|
|
@Observable
|
|
final class ReflexiveVerbStore {
|
|
|
|
/// Process-wide accessor for services that can't use @Environment injection
|
|
/// (e.g. PracticeSessionService called from ViewModels). Views should still
|
|
/// prefer @Environment(ReflexiveVerbStore.self) for consistency.
|
|
static let shared = ReflexiveVerbStore()
|
|
|
|
private(set) var entries: [ReflexiveVerb] = []
|
|
private var indexByBase: [String: [ReflexiveVerb]] = [:]
|
|
|
|
/// Set of base infinitives present in the list. Cheap lookup for filters.
|
|
private(set) var baseInfinitives: Set<String> = []
|
|
|
|
init(bundle: Bundle = .main) {
|
|
load(from: bundle)
|
|
}
|
|
|
|
/// All reflexive entries whose base infinitive matches (case-insensitive).
|
|
func entries(for baseInfinitive: String) -> [ReflexiveVerb] {
|
|
indexByBase[baseInfinitive.lowercased()] ?? []
|
|
}
|
|
|
|
/// Convenience — true when the verb's bare infinitive appears in the list.
|
|
func isReflexive(baseInfinitive: String) -> Bool {
|
|
baseInfinitives.contains(baseInfinitive.lowercased())
|
|
}
|
|
|
|
private func load(from bundle: Bundle) {
|
|
guard let url = bundle.url(forResource: "reflexive_verbs", withExtension: "json"),
|
|
let data = try? Data(contentsOf: url) else {
|
|
print("[ReflexiveVerbStore] bundled reflexive_verbs.json not found")
|
|
return
|
|
}
|
|
do {
|
|
let decoded = try JSONDecoder().decode([ReflexiveVerb].self, from: data)
|
|
entries = decoded
|
|
var index: [String: [ReflexiveVerb]] = [:]
|
|
for entry in decoded {
|
|
index[entry.baseInfinitive.lowercased(), default: []].append(entry)
|
|
}
|
|
indexByBase = index
|
|
baseInfinitives = Set(index.keys)
|
|
print("[ReflexiveVerbStore] loaded \(decoded.count) entries (\(baseInfinitives.count) distinct base infinitives)")
|
|
} catch {
|
|
print("[ReflexiveVerbStore] decode failed: \(error)")
|
|
}
|
|
}
|
|
}
|