Highlight main tenses with Essential badges and focus mode #7
@@ -29,6 +29,18 @@ enum TenseID: String, CaseIterable, Codable, Sendable, Hashable {
|
||||
.ind_futuro,
|
||||
]
|
||||
|
||||
/// The 6 most essential tenses every learner should master.
|
||||
static let coreTenses: Set<TenseID> = [
|
||||
.ind_presente,
|
||||
.ind_preterito,
|
||||
.ind_imperfecto,
|
||||
.ind_futuro,
|
||||
.subj_presente,
|
||||
.imp_afirmativo,
|
||||
]
|
||||
|
||||
static let coreTenseIDs = coreTenses.map(\.rawValue)
|
||||
|
||||
static let defaultPracticeIDs = defaultPractice.map(\.rawValue)
|
||||
}
|
||||
|
||||
@@ -39,6 +51,10 @@ struct TenseInfo: Identifiable, Hashable, Sendable {
|
||||
let mood: String
|
||||
let order: Int
|
||||
|
||||
var isCore: Bool {
|
||||
TenseID(rawValue: id).map { TenseID.coreTenses.contains($0) } ?? false
|
||||
}
|
||||
|
||||
static let all: [TenseInfo] = [
|
||||
TenseInfo(id: TenseID.ind_presente.rawValue, spanish: "Indicativo Presente", english: "Present", mood: "Indicative", order: 0),
|
||||
TenseInfo(id: TenseID.ind_preterito.rawValue, spanish: "Indicativo Pretérito", english: "Preterite", mood: "Indicative", order: 1),
|
||||
|
||||
@@ -58,6 +58,10 @@ struct PracticeSessionService {
|
||||
if let form = pickIrregularForm(filter: filter) {
|
||||
return loadCard(from: form)
|
||||
}
|
||||
case .commonTenses:
|
||||
if let form = pickCommonTenseForm() {
|
||||
return loadCard(from: form)
|
||||
}
|
||||
case .none:
|
||||
break
|
||||
}
|
||||
@@ -231,6 +235,20 @@ struct PracticeSessionService {
|
||||
)
|
||||
}
|
||||
|
||||
private func pickCommonTenseForm() -> VerbForm? {
|
||||
let settings = settings()
|
||||
let coreTenseIDs = TenseID.coreTenseIDs
|
||||
let verbs = referenceStore.fetchVerbs(selectedLevel: settings.selectedLevel)
|
||||
guard let verb = verbs.randomElement() else { return nil }
|
||||
|
||||
let forms = referenceStore.fetchVerbForms(verbId: verb.id).filter { form in
|
||||
coreTenseIDs.contains(form.tenseId) &&
|
||||
(settings.showVosotros || form.personIndex != 4)
|
||||
}
|
||||
|
||||
return forms.randomElement()
|
||||
}
|
||||
|
||||
private func pickRandomForm() -> VerbForm? {
|
||||
let settings = settings()
|
||||
let verbs = referenceStore.fetchVerbs(selectedLevel: settings.selectedLevel)
|
||||
|
||||
@@ -45,6 +45,7 @@ enum FocusMode: Sendable {
|
||||
case none
|
||||
case weakVerbs
|
||||
case irregularity(IrregularityFilter)
|
||||
case commonTenses
|
||||
}
|
||||
|
||||
@MainActor
|
||||
|
||||
@@ -100,12 +100,25 @@ private struct TenseRowView: View {
|
||||
let tense: TenseInfo
|
||||
|
||||
var body: some View {
|
||||
VStack(alignment: .leading, spacing: 2) {
|
||||
Text(tense.english)
|
||||
.font(.headline)
|
||||
Text(tense.spanish)
|
||||
.font(.subheadline)
|
||||
.foregroundStyle(.secondary)
|
||||
HStack {
|
||||
VStack(alignment: .leading, spacing: 2) {
|
||||
Text(tense.english)
|
||||
.font(.headline)
|
||||
Text(tense.spanish)
|
||||
.font(.subheadline)
|
||||
.foregroundStyle(.secondary)
|
||||
}
|
||||
|
||||
Spacer()
|
||||
|
||||
if tense.isCore {
|
||||
Text("Essential")
|
||||
.font(.caption2.weight(.semibold))
|
||||
.foregroundStyle(.orange)
|
||||
.padding(.horizontal, 8)
|
||||
.padding(.vertical, 3)
|
||||
.background(.orange.opacity(0.12), in: Capsule())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -135,6 +135,44 @@ struct PracticeView: View {
|
||||
.font(.headline)
|
||||
.frame(maxWidth: .infinity, alignment: .leading)
|
||||
|
||||
// Common tenses focus
|
||||
Button {
|
||||
viewModel.practiceMode = .flashcard
|
||||
viewModel.focusMode = .commonTenses
|
||||
viewModel.sessionCorrect = 0
|
||||
viewModel.sessionTotal = 0
|
||||
viewModel.loadNextCard(
|
||||
localContext: modelContext,
|
||||
cloudContext: cloudModelContext
|
||||
)
|
||||
withAnimation { isPracticing = true }
|
||||
} label: {
|
||||
HStack(spacing: 14) {
|
||||
Image(systemName: "star.fill")
|
||||
.font(.title3)
|
||||
.foregroundStyle(.orange)
|
||||
.frame(width: 32)
|
||||
|
||||
VStack(alignment: .leading, spacing: 2) {
|
||||
Text("Common Tenses")
|
||||
.font(.subheadline.weight(.semibold))
|
||||
Text("Practice the 6 most essential tenses")
|
||||
.font(.caption)
|
||||
.foregroundStyle(.secondary)
|
||||
}
|
||||
|
||||
Spacer()
|
||||
|
||||
Image(systemName: "chevron.right")
|
||||
.font(.caption)
|
||||
.foregroundStyle(.tertiary)
|
||||
}
|
||||
.padding(.horizontal, 16)
|
||||
.padding(.vertical, 12)
|
||||
}
|
||||
.tint(.primary)
|
||||
.glassEffect(in: RoundedRectangle(cornerRadius: 14))
|
||||
|
||||
// Weak verbs focus
|
||||
Button {
|
||||
viewModel.practiceMode = .flashcard
|
||||
|
||||
Reference in New Issue
Block a user