Add countdown till when the next flashcard is going to show up

This commit is contained in:
2024-04-12 15:32:32 +02:00
parent 7625f62c85
commit dae08d79f6
6 changed files with 79 additions and 73 deletions

View File

@@ -33,6 +33,7 @@ class DataController: ObservableObject {
flashcard.lastSeenOn = [Date().addingTimeInterval([86400, 24000, 100000].randomElement()!)].randomElement()!
flashcard.shownCount = [0, 1, 2, 3, 4, 5].randomElement()!
flashcard.dateAdded = [Date(), Date().addingTimeInterval(-86400), Date().addingTimeInterval(-172800)].randomElement()!
// flashcard.calculatedNextRepetition = flashcard.lastSeenOn ?? Date() + TimeInterval(flashcard.nextSpacedRepetitionMilestone)
}
do {
try viewContext.save()

View File

@@ -11,6 +11,25 @@ import CoreData
@objc(Flashcard)
public class Flashcard: NSManagedObject {
@objc dynamic var calculatedNextRepetition: Date {
if lastSeenOn != nil {
return lastSeenOn!.addSpacedRepetitionMilestone(milestone: self.getSpacedRepetitionMilestone())
} else {
return Date()
}
}
override public class func keyPathsForValuesAffectingValue(forKey key: String) -> Set<String> {
// public override func value(forKey key: String) -> Any? {
let keyPaths = super.keyPathsForValuesAffectingValue(forKey: key)
if key == "calculatedNextRepetition" {
return keyPaths.union(Set(["lastSeenOn", "nextSpacedRepetitionMilestone"]))
} else {
return keyPaths
}
}
enum SpacedRepetitionMilestoneEnum: Int64, CaseIterable {
case Now = 0 // starting value
case OneMinute = 60 // 60 * 1
@@ -31,28 +50,28 @@ public class Flashcard: NSManagedObject {
static func getNext(milestone: SpacedRepetitionMilestoneEnum?) -> SpacedRepetitionMilestoneEnum {
let sorted = SpacedRepetitionMilestoneEnum.allCasesSorted
if milestone == nil {
return SpacedRepetitionMilestoneEnum.TenMinutes
return .TenMinutes
}
let milestoneIndex = sorted.firstIndex(where: {$0.rawValue == milestone!.rawValue})!
if milestoneIndex < SpacedRepetitionMilestoneEnum.allCasesSorted.count {
return sorted[milestoneIndex + 1]
}
return SpacedRepetitionMilestoneEnum.OneYear
return .OneYear
}
static func getMilestoneFromInt(value: Int64) -> SpacedRepetitionMilestoneEnum {
return SpacedRepetitionMilestoneEnum.allCasesSorted.first(where: {$0.rawValue == value}) ?? SpacedRepetitionMilestoneEnum.Now
return .allCasesSorted.first(where: {$0.rawValue == value}) ?? .Now
}
}
public override func didChangeValue(forKey key: String) {
super.didChangeValue(forKey: key)
if key == "lastSeenOn" || key == "nextSpacedRepetitionMilestone" {
// updateCalculatedNextRepetition()
calculatedNextRepetition = lastSeenOn ?? Date() + TimeInterval(nextSpacedRepetitionMilestone)
}
}
// public override func didChangeValue(forKey key: String) {
// super.didChangeValue(forKey: key)
// if key == "lastSeenOn" || key == "nextSpacedRepetitionMilestone" {
// // updateCalculatedNextRepetition()
// calculatedNextRepetition = lastSeenOn ?? Date() + TimeInterval(nextSpacedRepetitionMilestone)
// }
// }
// func updateCalculatedNextRepetition() {
// if let lastSeen = lastSeenOn {

View File

@@ -24,8 +24,6 @@ extension Flashcard {
@NSManaged public var nextSpacedRepetitionMilestone: Int64
@NSManaged public var shown: Bool
@NSManaged public var shownCount: Int64
@NSManaged public var calculatedNextRepetition: Date?
}
extension Flashcard : Identifiable {

View File

@@ -0,0 +1,13 @@
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<model type="com.apple.IDECoreDataModeler.DataModel" documentVersion="1.0" lastSavedToolsVersion="22757" systemVersion="23D60" minimumToolsVersion="Automatic" sourceLanguage="Swift" usedWithSwiftData="YES" userDefinedModelVersionIdentifier="">
<entity name="Flashcard" representedClassName="Flashcard" syncable="YES">
<attribute name="dateAdded" optional="YES" attributeType="Date" usesScalarValueType="NO"/>
<attribute name="desc" optional="YES" attributeType="String"/>
<attribute name="id" optional="YES" attributeType="UUID" usesScalarValueType="NO"/>
<attribute name="lastSeenOn" optional="YES" attributeType="Date" usesScalarValueType="NO"/>
<attribute name="name" optional="YES" attributeType="String"/>
<attribute name="nextSpacedRepetitionMilestone" optional="YES" attributeType="Integer 64" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="shown" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
<attribute name="shownCount" optional="YES" attributeType="Integer 64" defaultValueString="0" usesScalarValueType="YES"/>
</entity>
</model>

View File

@@ -21,19 +21,18 @@ struct AnkiView: View {
]),
NSPredicate(format: "lastSeenOn == nil")
])) var flashcards: FetchedResults<Flashcard>
@FetchRequest(
sortDescriptors: [
NSSortDescriptor(key: "calculatedNextRepetition", ascending: false)
],
predicate:
NSCompoundPredicate(type: .and, subpredicates: [
NSPredicate(format: "%K != nil", "lastSeenOn"),
NSPredicate(
format: "lastSeenOn + nextSpacedRepetitionMilestone > %@", Date() as CVarArg)
]
)) var soonestFlashcard: FetchedResults<Flashcard>
@State private var timeRemaining = 10
let timer = Timer.publish(every: 1, on: .main, in: .common).autoconnect()
@State var sortedFlashcards: [Flashcard] = []
@Environment(\.colorScheme) var colorScheme
// get the most recent flashcard
@FetchRequest(sortDescriptors: [],
predicate: NSPredicate(format: "%K != nil", "lastSeenOn")) var soonestFlashcard: FetchedResults<Flashcard>
@State var showDescription = false
@@ -86,62 +85,32 @@ struct AnkiView: View {
}
}
} else {
// TODO: Add countdown to the next available word
VStack{
if !soonestFlashcard.isEmpty {
Text("Time: \(timeRemaining.convertDurationSecondsToCountdown())")
.foregroundStyle(.white)
.padding(.horizontal, 20)
.padding(.vertical, 5)
.background(.black.opacity(0.75))
.clipShape(.capsule)
}
// if !soonestFlashcard.isEmpty {
// Text("HERE")
// Text("\(soonestFlashcard.first!.lastSeenOn!.addingTimeInterval(TimeInterval(soonestFlashcard.first!.nextSpacedRepetitionMilestone)).timeIntervalSince(Date()))")
// Text("\(Int64(timeRemaining))")
// Text("\(soonestFlashcard.first!.lastSeenOn!)")
// Text("\(soonestFlashcard.first!.getSpacedRepetitionMilestone().rawValue)")
// Text("\(soonestFlashcard.first!.lastSeenOn!.addSpacedRepetitionMilestone(milestone: soonestFlashcard.first!.getSpacedRepetitionMilestone()))")
// }
// if soonestFlashcard.first! {
// Text("HERE")
// }
Text("There are currently no words to display")
if !soonestFlashcard.isEmpty {
Text("Next flashcard in: \(timeRemaining.convertDurationSecondsToCountdown())")
.foregroundStyle(.black)
.padding()
.background(.yellow)
.clipShape(.buttonBorder)
}
.onAppear {
if !soonestFlashcard.isEmpty {
var lastSeen = soonestFlashcard.first!.lastSeenOn!
var showIn = soonestFlashcard.first!.getSpacedRepetitionMilestone()
var show = lastSeen.addSpacedRepetitionMilestone(milestone: showIn)
timeRemaining = Int(show.timeIntervalSinceNow)
}
}
.onReceive(timer) { time in
if !soonestFlashcard.isEmpty {
var lastSeen = soonestFlashcard.first!.lastSeenOn!
var showIn = soonestFlashcard.first!.getSpacedRepetitionMilestone()
var show = lastSeen.addSpacedRepetitionMilestone(milestone: showIn)
timeRemaining = Int(show.timeIntervalSinceNow)
}
// timeRemaining = Int(soonestFlashcard.first!.lastSeenOn!.addSpacedRepetitionMilestone(milestone: soonestFlashcard.first!.getSpacedRepetitionMilestone()).timeIntervalSince(Date()))
// timeRemaining = Int(soonestFlashcard.first!.lastSeenOn!.addingTimeInterval(TimeInterval(soonestFlashcard.first!.nextSpacedRepetitionMilestone)).timeIntervalSince(Date()))
// if timeRemaining > 0 {
// timeRemaining -= 1
// }
// if !soonestFlashcard.isEmpty {
// timeRemaining = Int(Date().timeIntervalSince(soonestFlashcard.first!.lastSeenOn!))
// }
.padding(.vertical, 50)
.padding(.horizontal)
.background(.gray.opacity(0.3))
.clipShape(.buttonBorder)
.onAppear {
if !soonestFlashcard.isEmpty {
sortedFlashcards = soonestFlashcard.sorted(by: {
($0.lastSeenOn!.addSpacedRepetitionMilestone(milestone:$0.getSpacedRepetitionMilestone()).timeIntervalSinceNow) < ($1.lastSeenOn!.addSpacedRepetitionMilestone(milestone: $1.getSpacedRepetitionMilestone()).timeIntervalSinceNow)
})
timeRemaining = Int(sortedFlashcards.first!.lastSeenOn!.addSpacedRepetitionMilestone(milestone:sortedFlashcards.first!.getSpacedRepetitionMilestone()).timeIntervalSinceNow)
}
}
.onReceive(timer) { time in
if timeRemaining > 0 {
timeRemaining -= 1
}
}
}
}
}
@State private var timeRemaining = 10
let timer = Timer.publish(every: 1, on: .main, in: .common).autoconnect()
}
#Preview {