Widget word-of-day picks from master verb list filtered by user level
Previously the widget was picking from course VocabCards, which could land on any course week and was showing unrelated phrases instead of the verbs the user is actually studying. Now the widget uses a new VerbStore.fetchVerbOfDay helper that: - Expands the user's selectedLevel via VerbLevelGroup.dataLevels - Runs a FetchDescriptor<Verb> filtered by those levels, sorted by rank - Uses fetchCount + fetchOffset for a deterministic daily pick The main app mirrors UserProgress.selectedLevel into the shared app group UserDefaults (key "selectedVerbLevel") on every WidgetDataService update, so the widget process can read it without touching the cloud store. WordOfDay.weekNumber was replaced with a more flexible subtitle: String so widgets can display "Level: Basic" instead of course week numbers. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
32
Conjuga/SharedModels/Sources/SharedModels/VerbStore.swift
Normal file
32
Conjuga/SharedModels/Sources/SharedModels/VerbStore.swift
Normal file
@@ -0,0 +1,32 @@
|
||||
import Foundation
|
||||
import SwiftData
|
||||
|
||||
public enum VerbStore {
|
||||
/// Deterministically pick a `Verb` for the given date, filtered by the user's
|
||||
/// selected level (which expands to the set of underlying data levels).
|
||||
/// Returns nil if there are no matching verbs.
|
||||
public static func fetchVerbOfDay(
|
||||
for date: Date,
|
||||
dayOffset: Int,
|
||||
selectedLevel: String,
|
||||
context: ModelContext
|
||||
) -> Verb? {
|
||||
let allowedLevels = Array(VerbLevelGroup.dataLevels(for: selectedLevel))
|
||||
var descriptor = FetchDescriptor<Verb>(
|
||||
predicate: #Predicate<Verb> { verb in
|
||||
allowedLevels.contains(verb.level)
|
||||
},
|
||||
sortBy: [SortDescriptor(\Verb.rank), SortDescriptor(\Verb.infinitive)]
|
||||
)
|
||||
|
||||
let count = (try? context.fetchCount(descriptor)) ?? 0
|
||||
guard count > 0 else { return nil }
|
||||
|
||||
let dayOfYear = Calendar.current.ordinality(of: .day, in: .year, for: date) ?? 1
|
||||
let index = (dayOfYear + dayOffset) % count
|
||||
|
||||
descriptor.fetchOffset = index
|
||||
descriptor.fetchLimit = 1
|
||||
return (try? context.fetch(descriptor))?.first
|
||||
}
|
||||
}
|
||||
@@ -3,12 +3,12 @@ import Foundation
|
||||
public struct WordOfDay: Codable, Equatable, Sendable {
|
||||
public var spanish: String
|
||||
public var english: String
|
||||
public var weekNumber: Int
|
||||
public var subtitle: String
|
||||
|
||||
public init(spanish: String, english: String, weekNumber: Int) {
|
||||
public init(spanish: String, english: String, subtitle: String) {
|
||||
self.spanish = spanish
|
||||
self.english = english
|
||||
self.weekNumber = weekNumber
|
||||
self.subtitle = subtitle
|
||||
}
|
||||
}
|
||||
|
||||
@@ -50,7 +50,7 @@ public struct WidgetData: Codable, Equatable, Sendable {
|
||||
dailyGoal: 50,
|
||||
currentStreak: 3,
|
||||
dueCardCount: 8,
|
||||
wordOfTheDay: WordOfDay(spanish: "hablar", english: "to speak", weekNumber: 1),
|
||||
wordOfTheDay: WordOfDay(spanish: "hablar", english: "to speak", subtitle: "Level: Basic"),
|
||||
latestTestScore: 85,
|
||||
latestTestWeek: 2,
|
||||
currentWeek: 2,
|
||||
|
||||
Reference in New Issue
Block a user