fix(anki): make quality of life improvements
This commit is contained in:
@@ -22,6 +22,8 @@ struct FlashcardHomeView: View {
|
|||||||
@State private var showingSession = false
|
@State private var showingSession = false
|
||||||
@State private var showingStats = false
|
@State private var showingStats = false
|
||||||
@State private var session: FlashcardSession?
|
@State private var session: FlashcardSession?
|
||||||
|
@State private var currentTime = Date()
|
||||||
|
@State private var timer: Timer?
|
||||||
|
|
||||||
private var statistics: FlashcardStatistics {
|
private var statistics: FlashcardStatistics {
|
||||||
let tags = showAllTags ? nil : Array(selectedTagObjects)
|
let tags = showAllTags ? nil : Array(selectedTagObjects)
|
||||||
@@ -40,6 +42,11 @@ struct FlashcardHomeView: View {
|
|||||||
return SpacedRepetitionEngine.getDueCards(in: viewContext, tags: tags)
|
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 {
|
var body: some View {
|
||||||
NavigationView {
|
NavigationView {
|
||||||
ScrollView {
|
ScrollView {
|
||||||
@@ -183,16 +190,30 @@ struct FlashcardHomeView: View {
|
|||||||
.font(.title3)
|
.font(.title3)
|
||||||
.fontWeight(.semibold)
|
.fontWeight(.semibold)
|
||||||
|
|
||||||
Text("No cards due for review right now")
|
if let nextDate = nextCardDate, nextDate > currentTime {
|
||||||
.font(.caption)
|
Text("Next card available in")
|
||||||
.foregroundColor(.secondary)
|
.font(.caption)
|
||||||
|
.foregroundColor(.secondary)
|
||||||
if statistics.totalCards == 0 {
|
|
||||||
|
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")
|
Text("💡 Tip: Cards need both German word AND English translation to appear in flashcards")
|
||||||
.font(.caption2)
|
.font(.caption2)
|
||||||
.foregroundColor(.orange)
|
.foregroundColor(.orange)
|
||||||
.multilineTextAlignment(.center)
|
.multilineTextAlignment(.center)
|
||||||
.padding(.top, 4)
|
.padding(.top, 4)
|
||||||
|
} else {
|
||||||
|
Text("No cards due for review right now")
|
||||||
|
.font(.caption)
|
||||||
|
.foregroundColor(.secondary)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.frame(maxWidth: .infinity)
|
.frame(maxWidth: .infinity)
|
||||||
@@ -218,6 +239,12 @@ struct FlashcardHomeView: View {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
.navigationTitle("Flashcards")
|
.navigationTitle("Flashcards")
|
||||||
|
.onAppear {
|
||||||
|
startTimer()
|
||||||
|
}
|
||||||
|
.onDisappear {
|
||||||
|
stopTimer()
|
||||||
|
}
|
||||||
.sheet(isPresented: $showingSession) {
|
.sheet(isPresented: $showingSession) {
|
||||||
if let session = session {
|
if let session = session {
|
||||||
FlashcardSessionView(session: session)
|
FlashcardSessionView(session: session)
|
||||||
@@ -244,6 +271,35 @@ struct FlashcardHomeView: View {
|
|||||||
session = FlashcardSession(cards: cards, context: viewContext)
|
session = FlashcardSession(cards: cards, context: viewContext)
|
||||||
showingSession = true
|
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
|
// MARK: - Stat Card
|
||||||
|
|||||||
@@ -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
|
/// Get overall statistics for flashcards
|
||||||
static func getStatistics(
|
static func getStatistics(
|
||||||
in context: NSManagedObjectContext,
|
in context: NSManagedObjectContext,
|
||||||
|
|||||||
Reference in New Issue
Block a user