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 = []