diff --git a/WordAX.xcodeproj/project.pbxproj b/WordAX.xcodeproj/project.pbxproj index 9fa4425..43c5a96 100644 --- a/WordAX.xcodeproj/project.pbxproj +++ b/WordAX.xcodeproj/project.pbxproj @@ -19,7 +19,7 @@ 6CEF7F7D2BC457E600E205F6 /* DataController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6CEF7F7C2BC457E600E205F6 /* DataController.swift */; }; 6CEF7F812BC4694900E205F6 /* WordAXCD.xcdatamodeld in Sources */ = {isa = PBXBuildFile; fileRef = 6CEF7F7F2BC4694900E205F6 /* WordAXCD.xcdatamodeld */; }; 6CEF7F842BC46B5900E205F6 /* Flashcard+CoreDataClass.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6CEF7F822BC46B5900E205F6 /* Flashcard+CoreDataClass.swift */; }; - 6CEF7F9E2BC6B4F100E205F6 /* Flashcard+CoreDataProperties.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6CEF7F9C2BC6B4F100E205F6 /* Flashcard+CoreDataProperties.swift */; }; + 6CEF7FA32BC88F6000E205F6 /* Flashcard+CoreDataProperties.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6CEF7FA12BC88F6000E205F6 /* Flashcard+CoreDataProperties.swift */; }; 6CF439522B83541D004C3543 /* WordAXApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6CF439512B83541D004C3543 /* WordAXApp.swift */; }; 6CF439542B83541D004C3543 /* MainView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6CF439532B83541D004C3543 /* MainView.swift */; }; 6CF439562B83541E004C3543 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 6CF439552B83541E004C3543 /* Assets.xcassets */; }; @@ -40,7 +40,8 @@ 6CEF7F802BC4694900E205F6 /* WordAX.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = WordAX.xcdatamodel; sourceTree = ""; }; 6CEF7F822BC46B5900E205F6 /* Flashcard+CoreDataClass.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Flashcard+CoreDataClass.swift"; sourceTree = ""; }; 6CEF7F962BC6B45F00E205F6 /* WordAX0.0.1.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = WordAX0.0.1.xcdatamodel; sourceTree = ""; }; - 6CEF7F9C2BC6B4F100E205F6 /* Flashcard+CoreDataProperties.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = "Flashcard+CoreDataProperties.swift"; path = "../../Flashcard+CoreDataProperties.swift"; sourceTree = ""; }; + 6CEF7F9F2BC88F3900E205F6 /* WordAX0.0.2.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = WordAX0.0.2.xcdatamodel; sourceTree = ""; }; + 6CEF7FA12BC88F6000E205F6 /* Flashcard+CoreDataProperties.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Flashcard+CoreDataProperties.swift"; sourceTree = ""; }; 6CF4394E2B83541D004C3543 /* WordAX.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = WordAX.app; sourceTree = BUILT_PRODUCTS_DIR; }; 6CF439512B83541D004C3543 /* WordAXApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WordAXApp.swift; sourceTree = ""; }; 6CF439532B83541D004C3543 /* MainView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MainView.swift; sourceTree = ""; }; @@ -80,7 +81,7 @@ 6CEF7F7C2BC457E600E205F6 /* DataController.swift */, 6CEF7F7F2BC4694900E205F6 /* WordAXCD.xcdatamodeld */, 6CEF7F822BC46B5900E205F6 /* Flashcard+CoreDataClass.swift */, - 6CEF7F9C2BC6B4F100E205F6 /* Flashcard+CoreDataProperties.swift */, + 6CEF7FA12BC88F6000E205F6 /* Flashcard+CoreDataProperties.swift */, ); path = Model; sourceTree = ""; @@ -197,7 +198,7 @@ 6CF439542B83541D004C3543 /* MainView.swift in Sources */, 6C8185082B8B523E0033CF46 /* NextRepetitionButtonView.swift in Sources */, 6C8185062B8A537F0033CF46 /* FlashCardView.swift in Sources */, - 6CEF7F9E2BC6B4F100E205F6 /* Flashcard+CoreDataProperties.swift in Sources */, + 6CEF7FA32BC88F6000E205F6 /* Flashcard+CoreDataProperties.swift in Sources */, 6C81850A2B8BA5740033CF46 /* FlashCardListView.swift in Sources */, 6C8185002B88C9660033CF46 /* WordAXModelView.swift in Sources */, 6C8185042B88CA210033CF46 /* AnkiView.swift in Sources */, @@ -418,6 +419,7 @@ 6CEF7F7F2BC4694900E205F6 /* WordAXCD.xcdatamodeld */ = { isa = XCVersionGroup; children = ( + 6CEF7F9F2BC88F3900E205F6 /* WordAX0.0.2.xcdatamodel */, 6CEF7F962BC6B45F00E205F6 /* WordAX0.0.1.xcdatamodel */, 6CEF7F802BC4694900E205F6 /* WordAX.xcdatamodel */, ); diff --git a/WordAX/Model/DataController.swift b/WordAX/Model/DataController.swift index 03b4dd8..032e15f 100644 --- a/WordAX/Model/DataController.swift +++ b/WordAX/Model/DataController.swift @@ -29,8 +29,8 @@ class DataController: ObservableObject { "This is a very short description", "This is a medium length description that should be long enough to cover all cases" ].randomElement()! - flashcard.nextSpacedRepetitionMilestone = SpacedRepetitionMilestoneEnum.allCases.randomElement()!.rawValue - flashcard.lastSeenOn = [nil, Date(), Date().addingTimeInterval([-86400, -24000, -100000].randomElement()!)].randomElement()! + flashcard.nextSpacedRepetitionMilestone = SpacedRepetitionMilestoneEnum.OneYear.rawValue + 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()! } @@ -70,43 +70,6 @@ class DataController: ObservableObject { return [] } } - - // public func getFlashCardsToDisplay() -> Flashcard? { - // let flashcards = self.getAllFlashcards() - // - // if flashcards.count > 0 { - // let notShownFlashCards = flashcards.filter({!$0.shown}) - // // if today is the date they're supposed to be shown - // - // let displayToday = flashcards.filter({ - // $0.lastSeenOn != nil && - // $0.lastSeenOn!.addSpacedRepetitionMilestone( - // milestone: SpacedRepetitionMilestoneEnum.getMilestoneFromInt( - // value: $0.nextSpacedRepetitionMilestone)) - // .isBeforeTodayOrToday() - // }) - // if displayToday.count > 0 { - // return displayToday.first! - // } - // - //// let shownWords = words.filter({ $0.shown }) - //// if shownWords.count == 0 { - // if notShownFlashCards.count == 0 { - // return nil - // } - // return notShownFlashCards.sorted(by: {$0.id < $1.id}).first - //// } - // // if today is the day to show a new word - //// let settings = model.settings - //// if shownWords.count == 0 || - //// settings.lastShownNew == nil || - //// settings.lastShownNew!.addFrequency(frequency: settings.frequency).isAfterToday() { - //// return words.first! - //// } - // } - // // otherwise show nothing - // return nil - // } } @@ -133,3 +96,32 @@ extension Int64 { return result } } + +extension Int { + func convertDurationSecondsToCountdown() -> String { + var result = "" + // Separate into days, hours, minutes and seconds and take the largest one + let days: Int = self / 86400 + let hours: Int = self / 60 / 60 % 60 + let minutes: Int = self / 60 % 60 + let seconds: Int = self % 60 + if days > 0 { + result += "\(days)d" + } + if hours > 0 { + result += " \(hours)h" + } + if minutes > 0 { + result += " \(minutes)min" + } + if seconds > 0 { + result += " \(seconds)s" + } +// else { +// result = "\(self)" +// } + + return result + + } +} diff --git a/WordAX/Model/Flashcard+CoreDataClass.swift b/WordAX/Model/Flashcard+CoreDataClass.swift index 9426afa..11534e9 100644 --- a/WordAX/Model/Flashcard+CoreDataClass.swift +++ b/WordAX/Model/Flashcard+CoreDataClass.swift @@ -39,14 +39,28 @@ public class Flashcard: NSManagedObject { } return SpacedRepetitionMilestoneEnum.OneYear } - + static func getMilestoneFromInt(value: Int64) -> SpacedRepetitionMilestoneEnum { return SpacedRepetitionMilestoneEnum.allCasesSorted.first(where: {$0.rawValue == value}) ?? SpacedRepetitionMilestoneEnum.Now } } - + 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 { + // calculatedNextRepetition = Calendar.current.date(byAdding: .day, value: Int(nextSpacedRepetitionMilestone), to: lastSeen) + // } + // } + + func getSpacedRepetitionMilestone() -> SpacedRepetitionMilestoneEnum { return SpacedRepetitionMilestoneEnum.getMilestoneFromInt(value: self.nextSpacedRepetitionMilestone) } diff --git a/Flashcard+CoreDataProperties.swift b/WordAX/Model/Flashcard+CoreDataProperties.swift similarity index 81% rename from Flashcard+CoreDataProperties.swift rename to WordAX/Model/Flashcard+CoreDataProperties.swift index 66a285e..ec6dbf7 100644 --- a/Flashcard+CoreDataProperties.swift +++ b/WordAX/Model/Flashcard+CoreDataProperties.swift @@ -2,7 +2,7 @@ // Flashcard+CoreDataProperties.swift // WordAX // -// Created by Oliver Hnát on 10.04.2024. +// Created by Oliver Hnát on 11.04.2024. // // @@ -22,7 +22,9 @@ extension Flashcard { @NSManaged public var lastSeenOn: Date? @NSManaged public var name: String? @NSManaged public var nextSpacedRepetitionMilestone: Int64 + @NSManaged public var shown: Bool @NSManaged public var shownCount: Int64 + @NSManaged public var calculatedNextRepetition: Date? } diff --git a/WordAX/Model/WordAXCD.xcdatamodeld/WordAX0.0.2.xcdatamodel/contents b/WordAX/Model/WordAXCD.xcdatamodeld/WordAX0.0.2.xcdatamodel/contents new file mode 100644 index 0000000..8cce522 --- /dev/null +++ b/WordAX/Model/WordAXCD.xcdatamodeld/WordAX0.0.2.xcdatamodel/contents @@ -0,0 +1,14 @@ + + + + + + + + + + + + + + \ No newline at end of file diff --git a/WordAX/Views/AnkiView.swift b/WordAX/Views/AnkiView.swift index e8e1cdd..36d92ef 100644 --- a/WordAX/Views/AnkiView.swift +++ b/WordAX/Views/AnkiView.swift @@ -21,6 +21,19 @@ struct AnkiView: View { ]), NSPredicate(format: "lastSeenOn == nil") ])) var flashcards: FetchedResults + @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 + + // get the most recent flashcard @State var showDescription = false @@ -73,12 +86,62 @@ struct AnkiView: View { } } } else { - Text("There are currently no words to display") - .padding() - .background(.yellow) - .clipShape(.buttonBorder) + // 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") + .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!)) +// } + } } } + @State private var timeRemaining = 10 + let timer = Timer.publish(every: 1, on: .main, in: .common).autoconnect() } #Preview { diff --git a/WordAX/WordAXModelView.swift b/WordAX/WordAXModelView.swift index f61a8c7..b92377e 100644 --- a/WordAX/WordAXModelView.swift +++ b/WordAX/WordAXModelView.swift @@ -68,7 +68,7 @@ extension Date { if milestone == nil { return self } - return self.addingTimeInterval(TimeInterval(milestone!.rawValue * 24 * 60 * 60)) + return self.addingTimeInterval(TimeInterval(milestone!.rawValue)) } func isAfterToday() -> Bool {