Rewrote the app to use Core Data, some features not working fully

This commit is contained in:
2024-04-08 20:18:49 +02:00
parent 1512053e34
commit 24fb236f6b
14 changed files with 218 additions and 40 deletions

View File

@@ -16,6 +16,10 @@
6C81850A2B8BA5740033CF46 /* FlashCardListView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6C8185092B8BA5740033CF46 /* FlashCardListView.swift */; };
6C81850C2B8BA6BC0033CF46 /* FlashCardListRowView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6C81850B2B8BA6BC0033CF46 /* FlashCardListRowView.swift */; };
6CEF7F522BC2DBF800E205F6 /* AddFlashCard.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6CEF7F512BC2DBF800E205F6 /* AddFlashCard.swift */; };
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 */; };
6CEF7F852BC46B5900E205F6 /* Flashcard+CoreDataProperties.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6CEF7F832BC46B5900E205F6 /* 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 */; };
@@ -32,6 +36,10 @@
6C8185092B8BA5740033CF46 /* FlashCardListView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FlashCardListView.swift; sourceTree = "<group>"; };
6C81850B2B8BA6BC0033CF46 /* FlashCardListRowView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FlashCardListRowView.swift; sourceTree = "<group>"; };
6CEF7F512BC2DBF800E205F6 /* AddFlashCard.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AddFlashCard.swift; sourceTree = "<group>"; };
6CEF7F7C2BC457E600E205F6 /* DataController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = DataController.swift; path = WordAX/Model/DataController.swift; sourceTree = "<group>"; };
6CEF7F802BC4694900E205F6 /* WordAX.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = WordAX.xcdatamodel; sourceTree = "<group>"; };
6CEF7F822BC46B5900E205F6 /* Flashcard+CoreDataClass.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Flashcard+CoreDataClass.swift"; sourceTree = "<group>"; };
6CEF7F832BC46B5900E205F6 /* Flashcard+CoreDataProperties.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Flashcard+CoreDataProperties.swift"; sourceTree = "<group>"; };
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 = "<group>"; };
6CF439532B83541D004C3543 /* MainView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MainView.swift; sourceTree = "<group>"; };
@@ -53,6 +61,7 @@
6C4632342BC2D99E003ED0E9 /* Views */ = {
isa = PBXGroup;
children = (
6C8185072B8B523D0033CF46 /* NextRepetitionButtonView.swift */,
6CEF7F512BC2DBF800E205F6 /* AddFlashCard.swift */,
6CF439532B83541D004C3543 /* MainView.swift */,
6C8185092B8BA5740033CF46 /* FlashCardListView.swift */,
@@ -64,9 +73,20 @@
path = Views;
sourceTree = "<group>";
};
6CEF7F7B2BC456A100E205F6 /* Model */ = {
isa = PBXGroup;
children = (
6CEF7F822BC46B5900E205F6 /* Flashcard+CoreDataClass.swift */,
6CEF7F832BC46B5900E205F6 /* Flashcard+CoreDataProperties.swift */,
);
path = Model;
sourceTree = "<group>";
};
6CF439452B83541D004C3543 = {
isa = PBXGroup;
children = (
6CEF7F7F2BC4694900E205F6 /* WordAXCD.xcdatamodeld */,
6CEF7F7C2BC457E600E205F6 /* DataController.swift */,
6CF439502B83541D004C3543 /* WordAX */,
6CF4394F2B83541D004C3543 /* Products */,
);
@@ -83,9 +103,9 @@
6CF439502B83541D004C3543 /* WordAX */ = {
isa = PBXGroup;
children = (
6CEF7F7B2BC456A100E205F6 /* Model */,
6CF439512B83541D004C3543 /* WordAXApp.swift */,
6C4632342BC2D99E003ED0E9 /* Views */,
6C8185072B8B523D0033CF46 /* NextRepetitionButtonView.swift */,
6C8184FD2B88C9580033CF46 /* WordAX.swift */,
6C8184FF2B88C9660033CF46 /* WordAXModelView.swift */,
6CF439552B83541E004C3543 /* Assets.xcassets */,
@@ -179,10 +199,14 @@
6C81850A2B8BA5740033CF46 /* FlashCardListView.swift in Sources */,
6C8185002B88C9660033CF46 /* WordAXModelView.swift in Sources */,
6C8185042B88CA210033CF46 /* AnkiView.swift in Sources */,
6CEF7F7D2BC457E600E205F6 /* DataController.swift in Sources */,
6CF439522B83541D004C3543 /* WordAXApp.swift in Sources */,
6CEF7F522BC2DBF800E205F6 /* AddFlashCard.swift in Sources */,
6C8184FE2B88C9580033CF46 /* WordAX.swift in Sources */,
6CEF7F812BC4694900E205F6 /* WordAXCD.xcdatamodeld in Sources */,
6C81850C2B8BA6BC0033CF46 /* FlashCardListRowView.swift in Sources */,
6CEF7F842BC46B5900E205F6 /* Flashcard+CoreDataClass.swift in Sources */,
6CEF7F852BC46B5900E205F6 /* Flashcard+CoreDataProperties.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@@ -388,6 +412,20 @@
defaultConfigurationName = Release;
};
/* End XCConfigurationList section */
/* Begin XCVersionGroup section */
6CEF7F7F2BC4694900E205F6 /* WordAXCD.xcdatamodeld */ = {
isa = XCVersionGroup;
children = (
6CEF7F802BC4694900E205F6 /* WordAX.xcdatamodel */,
);
currentVersion = 6CEF7F802BC4694900E205F6 /* WordAX.xcdatamodel */;
name = WordAXCD.xcdatamodeld;
path = WordAX/Model/WordAXCD.xcdatamodeld;
sourceTree = "<group>";
versionGroupType = wrapper.xcdatamodel;
};
/* End XCVersionGroup section */
};
rootObject = 6CF439462B83541D004C3543 /* Project object */;
}

View File

@@ -0,0 +1,50 @@
//
// DataController.swift
// WordAX
//
// Created by Oliver Hnát on 08.04.2024.
//
import Foundation
import CoreData
class DataController: ObservableObject {
let container = NSPersistentContainer(name: "WordAXCD")
static let shared = DataController()
var viewContext: NSManagedObjectContext {
container.viewContext
}
init() {
container.loadPersistentStores { description, error in
if let error = error {
print("Core data failed to load: \(error.localizedDescription)")
}
}
}
public func addFlashcard(name: String, description: String) {
let flashcard = Flashcard(context: viewContext)
flashcard.id = UUID()
flashcard.name = name
flashcard.desc = description
flashcard.shown = false
flashcard.nextSpacedRepetitionMilestone = 0
flashcard.lastSeenOn = nil
flashcard.shownCount = 0
flashcard.dateAdded = Date()
try? viewContext.save()
}
public func getAllFlashcards() -> [Flashcard]{
let request = NSFetchRequest<Flashcard>(entityName: "Flashcard")
do {
return try viewContext.fetch(request)
} catch {
return []
}
}
}

View File

@@ -0,0 +1,19 @@
//
// Flashcard+CoreDataClass.swift
// WordAX
//
// Created by Oliver Hnát on 08.04.2024.
//
//
import Foundation
import CoreData
@objc(Flashcard)
public class Flashcard: NSManagedObject {
typealias SpacedRepetitionMilestoneEnum = WordAX.SpacedRepetitionMilestoneEnum
func getSpacedRepetitionMilestone() -> SpacedRepetitionMilestoneEnum {
return SpacedRepetitionMilestoneEnum.getMilestoneFromInt(value: self.nextSpacedRepetitionMilestone)
}
}

View File

@@ -0,0 +1,32 @@
//
// Flashcard+CoreDataProperties.swift
// WordAX
//
// Created by Oliver Hnát on 08.04.2024.
//
//
import Foundation
import CoreData
extension Flashcard {
@nonobjc public class func fetchRequest() -> NSFetchRequest<Flashcard> {
return NSFetchRequest<Flashcard>(entityName: "Flashcard")
}
@NSManaged public var name: String?
@NSManaged public var desc: String?
@NSManaged public var id: UUID?
@NSManaged public var shown: Bool
@NSManaged public var nextSpacedRepetitionMilestone: Int64
@NSManaged public var lastSeenOn: Date?
@NSManaged public var dateAdded: Date?
@NSManaged public var shownCount: Int64
}
extension Flashcard : Identifiable {
}

View File

@@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>_XCCurrentVersionName</key>
<string>WordAX.xcdatamodel</string>
</dict>
</plist>

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

@@ -10,7 +10,7 @@ import SwiftUI
struct AnkiView: View {
@EnvironmentObject var model: WordAXModelView
@State var showDescription = false
var flashcard: WordAX.FlashCard? {
var flashcard: Flashcard? {
model.getFlashCardsToDisplay()
}
var body: some View {
@@ -25,8 +25,8 @@ struct AnkiView: View {
HStack(alignment: .center) {
NextRepetitionButtonView(
buttonText: "Wrong",
nextMilestone: flashcard!.nextSpacedRepetitionMilestone,
flashcardId: flashcard!.id,
nextMilestone: flashcard!.getSpacedRepetitionMilestone(),
flashcardId: flashcard!.id!,
width: geometry.size.width,
color: .red,
geometry: geometry,
@@ -35,8 +35,8 @@ struct AnkiView: View {
)
NextRepetitionButtonView(
buttonText: "Correct",
nextMilestone: WordAX.SpacedRepetitionMilestoneEnum.getNext(milestone: flashcard!.nextSpacedRepetitionMilestone),
flashcardId: flashcard!.id,
nextMilestone: WordAX.SpacedRepetitionMilestoneEnum.getNext(milestone: flashcard!.getSpacedRepetitionMilestone()),
flashcardId: flashcard!.id!,
width:geometry.size.width,
color: .orange,
geometry: geometry,
@@ -45,8 +45,8 @@ struct AnkiView: View {
)
NextRepetitionButtonView(
buttonText: "Easy",
nextMilestone: WordAX.SpacedRepetitionMilestoneEnum.getNext(milestone: WordAX.SpacedRepetitionMilestoneEnum.getNext(milestone: flashcard!.nextSpacedRepetitionMilestone)),
flashcardId: flashcard!.id,
nextMilestone: WordAX.SpacedRepetitionMilestoneEnum.getNext(milestone: WordAX.SpacedRepetitionMilestoneEnum.getNext(milestone: flashcard!.getSpacedRepetitionMilestone())),
flashcardId: flashcard!.id!,
width: geometry.size.width,
color: .green,
geometry: geometry,

View File

@@ -9,7 +9,7 @@ import SwiftUI
struct FlashCardListRowView: View {
@EnvironmentObject var model: WordAXModelView
var flashcard: WordAX.FlashCard
var flashcard: Flashcard
@State var favorite = true
var body: some View {
HStack {
@@ -30,12 +30,12 @@ struct FlashCardListRowView: View {
}
.padding(.trailing)
VStack {
Text(flashcard.name)
Text(flashcard.name ?? "Unknown")
.bold()
.font(.system(size: 19))
.multilineTextAlignment(.leading)
.frame(maxWidth: .infinity, alignment: .leading)
Text(flashcard.description)
Text(flashcard.desc ?? "Unknown")
.multilineTextAlignment(.leading)
.frame(maxWidth: .infinity, alignment: .leading)
.lineLimit(1)
@@ -46,12 +46,16 @@ struct FlashCardListRowView: View {
}
#Preview {
Group {
FlashCardListRowView(flashcard: WordAX.FlashCard(id: 0, name: "Mesmerizing", description: "Some very long description like Lorem Ipsum which I'm to lazy to copy", shown: false, nextSpacedRepetitionMilestone: WordAX.SpacedRepetitionMilestoneEnum.OneDay, lastSeenOn: Date(), shownCount: 1))
let fc = Flashcard()
fc.id = UUID()
fc.name = "Mesmerizing"
fc.desc = "Some very long description like Lorem Ipsum which I'm to lazy to copy"
return Group {
FlashCardListRowView(flashcard: fc)
.environmentObject(WordAXModelView())
FlashCardListRowView(flashcard: WordAX.FlashCard(id: 0, name: "Mesmerizing", description: "Some very long description like Lorem Ipsum which I'm to lazy to copy", shown: false, nextSpacedRepetitionMilestone: WordAX.SpacedRepetitionMilestoneEnum.OneDay, lastSeenOn: Date(), shownCount: 1))
FlashCardListRowView(flashcard: fc)
.environmentObject(WordAXModelView())
FlashCardListRowView(flashcard: WordAX.FlashCard(id: 0, name: "Mesmerizing", description: "Some very long description like Lorem Ipsum which I'm to lazy to copy", shown: false, nextSpacedRepetitionMilestone: WordAX.SpacedRepetitionMilestoneEnum.OneDay, lastSeenOn: Date(), shownCount: 1))
FlashCardListRowView(flashcard: fc)
.environmentObject(WordAXModelView())
}
}

View File

@@ -15,12 +15,12 @@ struct FlashCardListView: View {
GeometryReader { geometry in
NavigationSplitView {
Group {
if !model.flashcards.isEmpty {
List(model.flashcards) { word in
if !DataController.shared.getAllFlashcards().isEmpty {
List(DataController.shared.getAllFlashcards()) { flashcard in
NavigationLink {
FlashCardView(flashcard: word, showDescription: $showDescription)
FlashCardView(flashcard: flashcard, showDescription: $showDescription)
} label: {
FlashCardListRowView(flashcard: word)
FlashCardListRowView(flashcard: flashcard)
}
}
}
@@ -32,7 +32,7 @@ struct FlashCardListView: View {
.frame(maxWidth: geometry.size.width - 30)
}
}
.navigationTitle("Word List")
.navigationTitle("All Flashcards")
.toolbar {
Button(action: {
self.addFlashcard = true

View File

@@ -9,14 +9,14 @@ import SwiftUI
import UIKit
struct FlashCardView: View {
var flashcard: WordAX.FlashCard
var flashcard: Flashcard
@Binding var showDescription: Bool
@EnvironmentObject var model: WordAXModelView
@Environment(\.colorScheme) var colorScheme
var body: some View {
let flashcardText = Text(flashcard.name)
let flashcardText = Text(flashcard.name ?? "Unknown")
.font(.title)
.bold()
VStack {
@@ -30,7 +30,7 @@ struct FlashCardView: View {
Divider()
.background(colorScheme == .light ? Color.black : Color.white)
.padding(.horizontal)
Text(flashcard.description)
Text(flashcard.desc ?? "No description added")
.multilineTextAlignment(.center)
} else {
flashcardText
@@ -47,6 +47,6 @@ struct FlashCardView: View {
#Preview {
@State var showDescription = false
return FlashCardView(flashcard: WordAXModelView().getFlashCardsToDisplay()!, showDescription: $showDescription)
return FlashCardView(flashcard: DataController.shared.getAllFlashcards()[0], showDescription: $showDescription)
.environmentObject(WordAXModelView())
}

View File

@@ -10,7 +10,7 @@ import SwiftUI
struct NextRepetitionButtonView: View {
let buttonText: String
let nextMilestone: WordAX.SpacedRepetitionMilestoneEnum?
let flashcardId: Int
let flashcardId: UUID
let width: CGFloat
let color: Color
let geometry: GeometryProxy

View File

@@ -6,6 +6,7 @@
//
import Foundation
import SwiftUI
struct WordAX {
struct FlashCard: Identifiable, Hashable {
@@ -25,7 +26,14 @@ struct WordAX {
case Monthly = 30
}
enum SpacedRepetitionMilestoneEnum: Int, CaseIterable {
public mutating func add(flashcard: FlashCard) {
self.flashcards.append(flashcard)
}
@objc enum SpacedRepetitionMilestoneEnum: Int64, CaseIterable {
case Now = 0 // starting value
case OneMinute = 60 // 60 * 1
case TenMinutes = 600 // 60 * 10
case OneHour = 3600 // 60 * 60
@@ -52,6 +60,11 @@ struct WordAX {
}
return nil
}
static func getMilestoneFromInt(value: Int64) -> SpacedRepetitionMilestoneEnum {
return SpacedRepetitionMilestoneEnum.allCasesSorted.first(where: {$0.rawValue == value}) ?? SpacedRepetitionMilestoneEnum.Now
}
}
struct Settings {
@@ -94,10 +107,6 @@ struct WordAX {
var flashcards: [FlashCard] = []
var settings: Settings
public mutating func add(flashcard: FlashCard) {
self.flashcards.append(flashcard)
}
init() {
self.flashcards = []
let dateFormatter = DateFormatter()

View File

@@ -9,11 +9,13 @@ import SwiftUI
@main
struct WordAXApp: App {
@StateObject var model = WordAXModelView()
@StateObject private var model = WordAXModelView()
@StateObject private var dataControler = DataController()
var body: some Scene {
WindowGroup {
MainView()
.environmentObject(model)
.environment(\.managedObjectContext, dataControler.container.viewContext)
}
}
}

View File

@@ -10,20 +10,21 @@ import Foundation
class WordAXModelView: ObservableObject {
@Published private var model: WordAX
typealias FlashCard = WordAX.FlashCard
typealias SpacedRepetitionMilestoneEnum = WordAX.SpacedRepetitionMilestoneEnum
init() {
model = WordAX()
}
public var flashcards: [FlashCard] {
model.flashcards
public var flashcards: [Flashcard] {
DataController.shared.getAllFlashcards()
}
public func getDateFormatter() -> DateFormatter {
model.settings.dateFormatter
}
public func getFlashCardsToDisplay() -> FlashCard? {
let flashcards = model.flashcards
public func getFlashCardsToDisplay() -> Flashcard? {
let flashcards = self.flashcards
if flashcards.count > 0 {
let notShownFlashCards = flashcards.filter({!$0.shown})
@@ -32,7 +33,7 @@ class WordAXModelView: ObservableObject {
}
// if today is the date they're supposed to be shown
let displayToday = flashcards.filter({ $0.lastSeenOn != nil && $0.lastSeenOn!.addSpacedRepetitionMilestone(milestone: $0.nextSpacedRepetitionMilestone).isBeforeTodayOrToday()})
let displayToday = flashcards.filter({ $0.lastSeenOn != nil && $0.lastSeenOn!.addSpacedRepetitionMilestone(milestone: SpacedRepetitionMilestoneEnum.getMilestoneFromInt(value: $0.nextSpacedRepetitionMilestone)).isBeforeTodayOrToday()})
if displayToday.count > 0 {
return displayToday.first!
}
@@ -53,13 +54,15 @@ class WordAXModelView: ObservableObject {
return nil
}
public func ankiButtonClicked(flashcardId: Int, milestone: WordAX.SpacedRepetitionMilestoneEnum?) {
model.setSpacedRepetitionMilestone(flashcardId: flashcardId, milestone: milestone)
model.flashcardShown(flashcardId: flashcardId)
public func ankiButtonClicked(flashcardId: UUID, milestone: WordAX.SpacedRepetitionMilestoneEnum?) {
// TODO: Fix this with Core Data
// model.setSpacedRepetitionMilestone(flashcardId: flashcardId, milestone: milestone)
// model.flashcardShown(flashcardId: flashcardId)
}
public func addFlashCard(name: String, description: String) {
self.model.add(flashcard: FlashCard(id: (self.flashcards.map{$0.id}.max() ?? -1) + 1, name: name, description: description))
// self.model.add(flashcard: FlashCard(id: (self.flashcards.map{$0.id}.max() ?? -1) + 1, name: name, description: description))
DataController.shared.addFlashcard(name: name, description: description)
}
}