Highlight main tenses with Essential badges and focus mode #7

Merged
admin merged 1 commits from feature/main-tenses-highlight into main 2026-04-13 10:08:02 -05:00
5 changed files with 92 additions and 6 deletions

View File

@@ -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),

View File

@@ -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)

View File

@@ -45,6 +45,7 @@ enum FocusMode: Sendable {
case none
case weakVerbs
case irregularity(IrregularityFilter)
case commonTenses
}
@MainActor

View File

@@ -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())
}
}
}
}

View File

@@ -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