Includes SwiftData dual-store architecture (local reference + CloudKit user data), JSON-based data seeding, 20 tense guides, 20 grammar notes, SRS review system, course vocabulary, and widget support. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
76 lines
2.4 KiB
Swift
76 lines
2.4 KiB
Swift
import CryptoKit
|
|
import Foundation
|
|
import SwiftData
|
|
|
|
public enum CourseCardStore {
|
|
private static var sortDescriptors: [SortDescriptor<VocabCard>] {
|
|
[
|
|
SortDescriptor(\VocabCard.deckId),
|
|
SortDescriptor(\VocabCard.front),
|
|
SortDescriptor(\VocabCard.back),
|
|
]
|
|
}
|
|
|
|
public static func reviewKey(for card: VocabCard) -> String {
|
|
reviewKey(
|
|
deckId: card.deckId,
|
|
front: card.front,
|
|
back: card.back,
|
|
examplesES: card.examplesES,
|
|
examplesEN: card.examplesEN
|
|
)
|
|
}
|
|
|
|
public static func reviewKey(
|
|
deckId: String,
|
|
front: String,
|
|
back: String,
|
|
examplesES: [String],
|
|
examplesEN: [String]
|
|
) -> String {
|
|
let source = [
|
|
deckId,
|
|
front,
|
|
back,
|
|
examplesES.joined(separator: "\u{1F}"),
|
|
examplesEN.joined(separator: "\u{1F}"),
|
|
].joined(separator: "\u{1E}")
|
|
|
|
let digest = SHA256.hash(data: Data(source.utf8))
|
|
return digest.map { String(format: "%02x", $0) }.joined()
|
|
}
|
|
|
|
public static func fetchCardCount(context: ModelContext) -> Int {
|
|
(try? context.fetchCount(FetchDescriptor<VocabCard>())) ?? 0
|
|
}
|
|
|
|
public static func fetchCard(at index: Int, context: ModelContext) -> VocabCard? {
|
|
guard index >= 0 else { return nil }
|
|
var descriptor = FetchDescriptor<VocabCard>(sortBy: sortDescriptors)
|
|
descriptor.fetchOffset = index
|
|
descriptor.fetchLimit = 1
|
|
return (try? context.fetch(descriptor))?.first
|
|
}
|
|
|
|
public static func fetchCards(offset: Int, limit: Int, context: ModelContext) -> [VocabCard] {
|
|
guard limit > 0 else { return [] }
|
|
var descriptor = FetchDescriptor<VocabCard>(sortBy: sortDescriptors)
|
|
descriptor.fetchOffset = max(offset, 0)
|
|
descriptor.fetchLimit = limit
|
|
return (try? context.fetch(descriptor)) ?? []
|
|
}
|
|
|
|
public static func fetchWordOfDayCard(
|
|
for date: Date,
|
|
wordOffset: Int,
|
|
context: ModelContext
|
|
) -> VocabCard? {
|
|
let count = fetchCardCount(context: context)
|
|
guard count > 0 else { return nil }
|
|
|
|
let dayOfYear = Calendar.current.ordinality(of: .day, in: .year, for: date) ?? 1
|
|
let index = (dayOfYear + wordOffset) % count
|
|
return fetchCard(at: index, context: context)
|
|
}
|
|
}
|