diff --git a/README.md b/README.md
index a28cb47..250424f 100644
--- a/README.md
+++ b/README.md
@@ -61,7 +61,7 @@ Why settle for pricey apps when you can have WordAX? We're here to prove that ef
- [x] Create flashcards
- [ ] Add tags to cards
- [ ] Specify how often you want each card to be seen (per folder/tag?)
-- [ ] Add tags/folders
+- [x] Add tags/folders - added decks to group cards
- [x] Store cards persistently
- [x] For start store them on the phone - stored using Core Data
- [ ] Maybe later add a storage in cloud, so that you can sync with other devices if the app is multi-platform?
@@ -70,6 +70,7 @@ Why settle for pricey apps when you can have WordAX? We're here to prove that ef
- [ ] Make an apple watch version of the app
- [ ] Option to add a hint
- [ ] Add haptic touch
+- [ ] Add emoji to decks? design:)
(back to top)
diff --git a/WordAX.xcodeproj/project.pbxproj b/WordAX.xcodeproj/project.pbxproj
index 9138880..e030b64 100644
--- a/WordAX.xcodeproj/project.pbxproj
+++ b/WordAX.xcodeproj/project.pbxproj
@@ -9,6 +9,8 @@
/* Begin PBXBuildFile section */
6C4A87D22BE25D260074E0A9 /* Deck+CoreDataClass.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6C4A87D02BE25D260074E0A9 /* Deck+CoreDataClass.swift */; };
6C4A87D32BE25D260074E0A9 /* Deck+CoreDataProperties.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6C4A87D12BE25D260074E0A9 /* Deck+CoreDataProperties.swift */; };
+ 6C7B6D5D2BE69FA900FB6ECC /* DeckSelectView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6C7B6D5C2BE69FA900FB6ECC /* DeckSelectView.swift */; };
+ 6C7B6D5F2BE6A1C100FB6ECC /* DeckRowView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6C7B6D5E2BE6A1C100FB6ECC /* DeckRowView.swift */; };
6C8184FE2B88C9580033CF46 /* WordAX.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6C8184FD2B88C9580033CF46 /* WordAX.swift */; };
6C8185002B88C9660033CF46 /* Miscellaneous.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6C8184FF2B88C9660033CF46 /* Miscellaneous.swift */; };
6C8185022B88C9FB0033CF46 /* SettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6C8185012B88C9FB0033CF46 /* SettingsView.swift */; };
@@ -34,6 +36,8 @@
/* Begin PBXFileReference section */
6C4A87D02BE25D260074E0A9 /* Deck+CoreDataClass.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Deck+CoreDataClass.swift"; sourceTree = ""; };
6C4A87D12BE25D260074E0A9 /* Deck+CoreDataProperties.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Deck+CoreDataProperties.swift"; sourceTree = ""; };
+ 6C7B6D5C2BE69FA900FB6ECC /* DeckSelectView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = DeckSelectView.swift; path = WordAX/Views/Deck/DeckSelectView.swift; sourceTree = SOURCE_ROOT; };
+ 6C7B6D5E2BE6A1C100FB6ECC /* DeckRowView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DeckRowView.swift; sourceTree = ""; };
6C8184FD2B88C9580033CF46 /* WordAX.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WordAX.swift; sourceTree = ""; };
6C8184FF2B88C9660033CF46 /* Miscellaneous.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Miscellaneous.swift; sourceTree = ""; };
6C8185012B88C9FB0033CF46 /* SettingsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsView.swift; sourceTree = ""; };
@@ -109,7 +113,9 @@
6CD2D7C92BE26FFB0079C4FA /* Deck */ = {
isa = PBXGroup;
children = (
+ 6C7B6D5E2BE6A1C100FB6ECC /* DeckRowView.swift */,
6CB09C2C2BE28C3E0020FE0C /* AddDeckView.swift */,
+ 6C7B6D5C2BE69FA900FB6ECC /* DeckSelectView.swift */,
6CD2D7CA2BE270240079C4FA /* DeckListView.swift */,
);
path = Deck;
@@ -257,6 +263,7 @@
6CF439542B83541D004C3543 /* MainView.swift in Sources */,
6C8185082B8B523E0033CF46 /* NextRepetitionButtonView.swift in Sources */,
6C8185062B8A537F0033CF46 /* FlashCardView.swift in Sources */,
+ 6C7B6D5F2BE6A1C100FB6ECC /* DeckRowView.swift in Sources */,
6CEF7FA32BC88F6000E205F6 /* Flashcard+CoreDataProperties.swift in Sources */,
6C81850A2B8BA5740033CF46 /* FlashCardListView.swift in Sources */,
6CEF7FA62BC96F2B00E205F6 /* ButtonHStackView.swift in Sources */,
@@ -271,6 +278,7 @@
6CB09C2D2BE28C3E0020FE0C /* AddDeckView.swift in Sources */,
6C4A87D32BE25D260074E0A9 /* Deck+CoreDataProperties.swift in Sources */,
6CEF7F812BC4694900E205F6 /* WordAXCD.xcdatamodeld in Sources */,
+ 6C7B6D5D2BE69FA900FB6ECC /* DeckSelectView.swift in Sources */,
6C81850C2B8BA6BC0033CF46 /* FlashCardListRowView.swift in Sources */,
6CEF7F842BC46B5900E205F6 /* Flashcard+CoreDataClass.swift in Sources */,
);
diff --git a/WordAX/Views/Anki/AnkiView.swift b/WordAX/Views/Anki/AnkiView.swift
index 6666cbe..a8409b9 100644
--- a/WordAX/Views/Anki/AnkiView.swift
+++ b/WordAX/Views/Anki/AnkiView.swift
@@ -16,74 +16,97 @@ struct AnkiView: View {
@State var flashcards: [Flashcard] = []
@State var soonestFlashcards: [Flashcard] = []
@State var showDescription = false
+ @State var decksToDisplay = Set()
+ @State var deckViewVisible: Bool = false
+
var body: some View {
- Group {
- if !flashcards.isEmpty && flashcards.first != nil {
- GeometryReader { geometry in
+ GeometryReader { geometry in
+ VStack {
+ HStack {
+ Button {
+ self.deckViewVisible = true
+ } label: {
+ Image(systemName: "rectangle.stack.fill")
+ .padding([.leading, .top])
+ }
+ Spacer()
+ }
+ if !flashcards.isEmpty && flashcards.first != nil {
VStack {
if flashcards.first != nil {
FlashCardView(flashcard: flashcards.first!, showDescription: $showDescription)
}
if showDescription && flashcards.first != nil {
- // Text("How did you do?")
- // .font(.subheadline)
- // .foregroundStyle(.gray)
ButtonHStackView(flashcard: flashcards.first!, geometry: geometry, reload: refreshFlashcards, showDescription: $showDescription)
.padding([.bottom, .trailing, .leading])
}
}
- }
- } else {
- if !soonestFlashcards.isEmpty {
- Group {
- Text("Next flashcard in: \(timeRemaining.convertDurationSecondsToCountdown())")
+ } else {
+ if !soonestFlashcards.isEmpty {
+ Group {
+ Text("Next flashcard in: \(timeRemaining.convertDurationSecondsToCountdown())")
+ .foregroundStyle(.black)
+ .padding()
+ .frame(maxWidth: .infinity - 50)
+ .background(.yellow)
+ .clipShape(.buttonBorder)
+ .padding(.vertical, 50)
+ .padding(.horizontal)
+ .onReceive(timer) { time in
+ if timeRemaining > 1 {
+ timeRemaining -= 1
+ }
+ if timeRemaining <= 3 {
+ refreshFlashcards()
+ }
+ }
+ .background(.gray.opacity(0.3))
+ .clipShape(.buttonBorder)
+ .padding(.horizontal)
+ }
+ .frame(maxHeight: .infinity)
+ }
+ else {
+ Text("No flashcards available")
.foregroundStyle(.black)
.padding()
- .frame(maxWidth: .infinity - 50)
+ // .frame(maxWidth: .infinity - 50)
.background(.yellow)
.clipShape(.buttonBorder)
.padding(.vertical, 50)
.padding(.horizontal)
- .onReceive(timer) { time in
- if timeRemaining > 1 {
- timeRemaining -= 1
- }
- if timeRemaining <= 3 {
- refreshFlashcards()
- }
- }
- .background(.gray.opacity(0.3))
- .clipShape(.buttonBorder)
- .padding(.horizontal)
}
}
- else {
- Text("No flashcards available")
- .foregroundStyle(.black)
- .padding()
- // .frame(maxWidth: .infinity - 50)
- .background(.yellow)
- .clipShape(.buttonBorder)
- .padding(.vertical, 50)
- .padding(.horizontal)
- }
}
}
.onAppear {
refreshFlashcards()
}
+ .sheet(isPresented: self.$deckViewVisible) {
+ DeckSelectView(selection: $decksToDisplay, active: $deckViewVisible)
+ }
+ .onChange(of: deckViewVisible) { _ in
+ refreshFlashcards()
+ }
}
func refreshFlashcards() {
let requestAllFlashcards = NSFetchRequest(entityName: "Flashcard")
- requestAllFlashcards.predicate = NSCompoundPredicate(type: .or, subpredicates: [
+ var predicateAllFlashcards = NSCompoundPredicate(type: .or, subpredicates: [
NSCompoundPredicate(type: .and, subpredicates: [
NSPredicate(format: "%K != nil", "lastSeenOn"),
NSPredicate(format: "lastSeenOn + nextSpacedRepetitionMilestone < %@", Date() as CVarArg)
]),
NSPredicate(format: "lastSeenOn == nil")
])
+ if !self.decksToDisplay.isEmpty {
+ predicateAllFlashcards = NSCompoundPredicate(type: .and, subpredicates: [
+ predicateAllFlashcards,
+ NSPredicate(format: "deck IN %@", decksToDisplay)
+ ])
+ }
+ requestAllFlashcards.predicate = predicateAllFlashcards
requestAllFlashcards.sortDescriptors = [
NSSortDescriptor(key: "nextSpacedRepetitionMilestone", ascending: false),
NSSortDescriptor(key: "lastSeenOn", ascending: true)
@@ -97,13 +120,16 @@ struct AnkiView: View {
let requestSoonestFlashcards = NSFetchRequest(entityName: "Flashcard")
- requestSoonestFlashcards.predicate = NSPredicate(format: "%K != nil", "lastSeenOn")
+ var predicateSoonestFlashcards = NSPredicate(format: "%K != nil", "lastSeenOn")
+ if !self.decksToDisplay.isEmpty {
+ predicateSoonestFlashcards = NSCompoundPredicate(type: .and, subpredicates: [
+ predicateSoonestFlashcards,
+ NSPredicate(format: "deck IN %@", decksToDisplay)
+ ])
+ }
+ requestSoonestFlashcards.predicate = predicateSoonestFlashcards
do {
soonestFlashcards = try moc.fetch(requestSoonestFlashcards)
-// print("This is soonest flashcards")
-// for flashcard in soonestFlashcards {
-// print("\(flashcard.name ?? "") is \(Int(flashcard.lastSeenOn!.addSpacedRepetitionMilestone(milestone:flashcard.getSpacedRepetitionMilestone()).timeIntervalSinceNow.rounded()))")
-// }
} catch {
print("Something went wrong while fetching latest flashcard")
}
diff --git a/WordAX/Views/Deck/DeckRowView.swift b/WordAX/Views/Deck/DeckRowView.swift
new file mode 100644
index 0000000..7946cbc
--- /dev/null
+++ b/WordAX/Views/Deck/DeckRowView.swift
@@ -0,0 +1,27 @@
+//
+// DeckRowView.swift
+// WordAX
+//
+// Created by Oliver Hnát on 04.05.2024.
+//
+
+import SwiftUI
+
+struct DeckRowView: View {
+ var deck: Deck
+ var selected: Bool = false
+ var body: some View {
+ HStack {
+ Text(deck.name ?? "Unknown")
+ Spacer()
+ if selected {
+ Image(systemName: "checkmark")
+ }
+ }
+ }
+}
+
+#Preview {
+ let deck = try? DataController.preview.viewContext.fetch(Deck.fetchRequest()).first
+ return DeckRowView(deck: deck!)
+}
diff --git a/WordAX/Views/Deck/DeckSelectView.swift b/WordAX/Views/Deck/DeckSelectView.swift
new file mode 100644
index 0000000..1dff5e3
--- /dev/null
+++ b/WordAX/Views/Deck/DeckSelectView.swift
@@ -0,0 +1,47 @@
+//
+// DeckSelectView.swift
+// WordAX
+//
+// Created by Oliver Hnát on 04.05.2024.
+//
+
+import SwiftUI
+
+struct DeckSelectView: View {
+ @FetchRequest(sortDescriptors: [NSSortDescriptor(key: "name", ascending: true)]) var decks: FetchedResults
+ @Binding var selection: Set
+ @Binding var active: Bool
+ var body: some View {
+ NavigationStack {
+ List(decks) { deck in
+ DeckRowView(deck: deck, selected: selection.contains(deck))
+ .contentShape(Rectangle())
+ .onTapGesture {
+ if !selection.contains(deck) {
+ self.selection.insert(deck)
+ } else {
+ self.selection.remove(deck)
+ }
+ }
+ }
+ .toolbar {
+ ToolbarItemGroup(placement: .topBarTrailing) {
+ Button(action: {
+ self.active = false
+ }, label: {
+ Text("Done")
+ .bold()
+ })
+ }
+ }
+ .navigationTitle("Select Decks to display")
+ }
+ }
+}
+
+#Preview {
+ @State var active = true
+ @State var decksToDisplay = Set()
+ return DeckSelectView(selection: $decksToDisplay, active: $active)
+ .environment(\.managedObjectContext, DataController.preview.container.viewContext)
+}
diff --git a/WordAX/Views/Flashcards/FlashCardListView.swift b/WordAX/Views/Flashcards/FlashCardListView.swift
index e8a1d8e..0ad8501 100644
--- a/WordAX/Views/Flashcards/FlashCardListView.swift
+++ b/WordAX/Views/Flashcards/FlashCardListView.swift
@@ -89,7 +89,6 @@ struct FlashCardListView: View {
}
do {
flashcards = try moc.fetch(request)
- print(flashcards)
} catch {
print("Something went wrong while fetching the flashcards")
flashcards = []