fix(anki): make quality of life improvements

This commit is contained in:
2025-12-06 17:07:07 +01:00
parent 6ef789bf31
commit 5994f8021f
2 changed files with 89 additions and 5 deletions

View File

@@ -22,6 +22,8 @@ struct FlashcardHomeView: View {
@State private var showingSession = false
@State private var showingStats = false
@State private var session: FlashcardSession?
@State private var currentTime = Date()
@State private var timer: Timer?
private var statistics: FlashcardStatistics {
let tags = showAllTags ? nil : Array(selectedTagObjects)
@@ -40,6 +42,11 @@ struct FlashcardHomeView: View {
return SpacedRepetitionEngine.getDueCards(in: viewContext, tags: tags)
}
private var nextCardDate: Date? {
let tags = showAllTags ? nil : Array(selectedTagObjects)
return SpacedRepetitionEngine.getNextCardDate(in: viewContext, tags: tags)
}
var body: some View {
NavigationView {
ScrollView {
@@ -183,16 +190,30 @@ struct FlashcardHomeView: View {
.font(.title3)
.fontWeight(.semibold)
Text("No cards due for review right now")
.font(.caption)
.foregroundColor(.secondary)
if statistics.totalCards == 0 {
if let nextDate = nextCardDate, nextDate > currentTime {
Text("Next card available in")
.font(.caption)
.foregroundColor(.secondary)
Text(timeUntilNextCard(nextDate: nextDate))
.font(.title2)
.fontWeight(.bold)
.foregroundColor(.blue)
.monospacedDigit()
} else if statistics.totalCards == 0 {
Text("No cards due for review right now")
.font(.caption)
.foregroundColor(.secondary)
Text("💡 Tip: Cards need both German word AND English translation to appear in flashcards")
.font(.caption2)
.foregroundColor(.orange)
.multilineTextAlignment(.center)
.padding(.top, 4)
} else {
Text("No cards due for review right now")
.font(.caption)
.foregroundColor(.secondary)
}
}
.frame(maxWidth: .infinity)
@@ -218,6 +239,12 @@ struct FlashcardHomeView: View {
}
}
.navigationTitle("Flashcards")
.onAppear {
startTimer()
}
.onDisappear {
stopTimer()
}
.sheet(isPresented: $showingSession) {
if let session = session {
FlashcardSessionView(session: session)
@@ -244,6 +271,35 @@ struct FlashcardHomeView: View {
session = FlashcardSession(cards: cards, context: viewContext)
showingSession = true
}
private func startTimer() {
timer = Timer.scheduledTimer(withTimeInterval: 1.0, repeats: true) { _ in
currentTime = Date()
}
}
private func stopTimer() {
timer?.invalidate()
timer = nil
}
private func timeUntilNextCard(nextDate: Date) -> String {
let timeInterval = nextDate.timeIntervalSince(currentTime)
guard timeInterval > 0 else {
return "Available now!"
}
let hours = Int(timeInterval) / 3600
let minutes = (Int(timeInterval) % 3600) / 60
let seconds = Int(timeInterval) % 60
if hours > 0 {
return String(format: "%02d:%02d:%02d", hours, minutes, seconds)
} else {
return String(format: "%02d:%02d", minutes, seconds)
}
}
}
// MARK: - Stat Card

View File

@@ -163,6 +163,34 @@ class SpacedRepetitionEngine {
}
}
/// Get the date when the next card will become available
static func getNextCardDate(
in context: NSManagedObjectContext,
tags: [Tag]? = nil
) -> Date? {
let fetchRequest: NSFetchRequest<VocabularyEntry> = VocabularyEntry.fetchRequest()
// Filter by tags if provided
if let tags = tags, !tags.isEmpty {
fetchRequest.predicate = NSPredicate(format: "ANY tags IN %@", tags)
}
do {
let allEntries = try context.fetch(fetchRequest)
// Get all flashcard-ready entries that are NOT yet due
let futureCards = allEntries.filter { entry in
entry.isFlashcardReady && !entry.isDueForReview && entry.nextReviewDate != nil
}
// Find the earliest next review date
return futureCards.compactMap { $0.nextReviewDate }.min()
} catch {
print("Error fetching next card date: \(error)")
return nil
}
}
/// Get overall statistics for flashcards
static func getStatistics(
in context: NSManagedObjectContext,