Add ability to see only specific decks

This commit is contained in:
2024-05-04 20:34:45 +02:00
parent df267ebc4e
commit 8e0fca1c36
6 changed files with 149 additions and 41 deletions

View File

@@ -61,7 +61,7 @@ Why settle for pricey apps when you can have WordAX? We're here to prove that ef
- [x] Create flashcards - [x] Create flashcards
- [ ] Add tags to cards - [ ] Add tags to cards
- [ ] Specify how often you want each card to be seen (per folder/tag?) - [ ] 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] Store cards persistently
- [x] For start store them on the phone - stored using Core Data - [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? - [ ] 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 - [ ] Make an apple watch version of the app
- [ ] Option to add a hint - [ ] Option to add a hint
- [ ] Add haptic touch - [ ] Add haptic touch
- [ ] Add emoji to decks? design:)
<p align="right">(<a href="#readme-top">back to top</a>)</p> <p align="right">(<a href="#readme-top">back to top</a>)</p>

View File

@@ -9,6 +9,8 @@
/* Begin PBXBuildFile section */ /* Begin PBXBuildFile section */
6C4A87D22BE25D260074E0A9 /* Deck+CoreDataClass.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6C4A87D02BE25D260074E0A9 /* Deck+CoreDataClass.swift */; }; 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 */; }; 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 */; }; 6C8184FE2B88C9580033CF46 /* WordAX.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6C8184FD2B88C9580033CF46 /* WordAX.swift */; };
6C8185002B88C9660033CF46 /* Miscellaneous.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6C8184FF2B88C9660033CF46 /* Miscellaneous.swift */; }; 6C8185002B88C9660033CF46 /* Miscellaneous.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6C8184FF2B88C9660033CF46 /* Miscellaneous.swift */; };
6C8185022B88C9FB0033CF46 /* SettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6C8185012B88C9FB0033CF46 /* SettingsView.swift */; }; 6C8185022B88C9FB0033CF46 /* SettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6C8185012B88C9FB0033CF46 /* SettingsView.swift */; };
@@ -34,6 +36,8 @@
/* Begin PBXFileReference section */ /* Begin PBXFileReference section */
6C4A87D02BE25D260074E0A9 /* Deck+CoreDataClass.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Deck+CoreDataClass.swift"; sourceTree = "<group>"; }; 6C4A87D02BE25D260074E0A9 /* Deck+CoreDataClass.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Deck+CoreDataClass.swift"; sourceTree = "<group>"; };
6C4A87D12BE25D260074E0A9 /* Deck+CoreDataProperties.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Deck+CoreDataProperties.swift"; sourceTree = "<group>"; }; 6C4A87D12BE25D260074E0A9 /* Deck+CoreDataProperties.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Deck+CoreDataProperties.swift"; sourceTree = "<group>"; };
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 = "<group>"; };
6C8184FD2B88C9580033CF46 /* WordAX.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WordAX.swift; sourceTree = "<group>"; }; 6C8184FD2B88C9580033CF46 /* WordAX.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WordAX.swift; sourceTree = "<group>"; };
6C8184FF2B88C9660033CF46 /* Miscellaneous.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Miscellaneous.swift; sourceTree = "<group>"; }; 6C8184FF2B88C9660033CF46 /* Miscellaneous.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Miscellaneous.swift; sourceTree = "<group>"; };
6C8185012B88C9FB0033CF46 /* SettingsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsView.swift; sourceTree = "<group>"; }; 6C8185012B88C9FB0033CF46 /* SettingsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsView.swift; sourceTree = "<group>"; };
@@ -109,7 +113,9 @@
6CD2D7C92BE26FFB0079C4FA /* Deck */ = { 6CD2D7C92BE26FFB0079C4FA /* Deck */ = {
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
6C7B6D5E2BE6A1C100FB6ECC /* DeckRowView.swift */,
6CB09C2C2BE28C3E0020FE0C /* AddDeckView.swift */, 6CB09C2C2BE28C3E0020FE0C /* AddDeckView.swift */,
6C7B6D5C2BE69FA900FB6ECC /* DeckSelectView.swift */,
6CD2D7CA2BE270240079C4FA /* DeckListView.swift */, 6CD2D7CA2BE270240079C4FA /* DeckListView.swift */,
); );
path = Deck; path = Deck;
@@ -257,6 +263,7 @@
6CF439542B83541D004C3543 /* MainView.swift in Sources */, 6CF439542B83541D004C3543 /* MainView.swift in Sources */,
6C8185082B8B523E0033CF46 /* NextRepetitionButtonView.swift in Sources */, 6C8185082B8B523E0033CF46 /* NextRepetitionButtonView.swift in Sources */,
6C8185062B8A537F0033CF46 /* FlashCardView.swift in Sources */, 6C8185062B8A537F0033CF46 /* FlashCardView.swift in Sources */,
6C7B6D5F2BE6A1C100FB6ECC /* DeckRowView.swift in Sources */,
6CEF7FA32BC88F6000E205F6 /* Flashcard+CoreDataProperties.swift in Sources */, 6CEF7FA32BC88F6000E205F6 /* Flashcard+CoreDataProperties.swift in Sources */,
6C81850A2B8BA5740033CF46 /* FlashCardListView.swift in Sources */, 6C81850A2B8BA5740033CF46 /* FlashCardListView.swift in Sources */,
6CEF7FA62BC96F2B00E205F6 /* ButtonHStackView.swift in Sources */, 6CEF7FA62BC96F2B00E205F6 /* ButtonHStackView.swift in Sources */,
@@ -271,6 +278,7 @@
6CB09C2D2BE28C3E0020FE0C /* AddDeckView.swift in Sources */, 6CB09C2D2BE28C3E0020FE0C /* AddDeckView.swift in Sources */,
6C4A87D32BE25D260074E0A9 /* Deck+CoreDataProperties.swift in Sources */, 6C4A87D32BE25D260074E0A9 /* Deck+CoreDataProperties.swift in Sources */,
6CEF7F812BC4694900E205F6 /* WordAXCD.xcdatamodeld in Sources */, 6CEF7F812BC4694900E205F6 /* WordAXCD.xcdatamodeld in Sources */,
6C7B6D5D2BE69FA900FB6ECC /* DeckSelectView.swift in Sources */,
6C81850C2B8BA6BC0033CF46 /* FlashCardListRowView.swift in Sources */, 6C81850C2B8BA6BC0033CF46 /* FlashCardListRowView.swift in Sources */,
6CEF7F842BC46B5900E205F6 /* Flashcard+CoreDataClass.swift in Sources */, 6CEF7F842BC46B5900E205F6 /* Flashcard+CoreDataClass.swift in Sources */,
); );

View File

@@ -16,24 +16,32 @@ struct AnkiView: View {
@State var flashcards: [Flashcard] = [] @State var flashcards: [Flashcard] = []
@State var soonestFlashcards: [Flashcard] = [] @State var soonestFlashcards: [Flashcard] = []
@State var showDescription = false @State var showDescription = false
@State var decksToDisplay = Set<Deck>()
@State var deckViewVisible: Bool = false
var body: some View { 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 { VStack {
if flashcards.first != nil { if flashcards.first != nil {
FlashCardView(flashcard: flashcards.first!, showDescription: $showDescription) FlashCardView(flashcard: flashcards.first!, showDescription: $showDescription)
} }
if showDescription && flashcards.first != nil { if showDescription && flashcards.first != nil {
// Text("How did you do?")
// .font(.subheadline)
// .foregroundStyle(.gray)
ButtonHStackView(flashcard: flashcards.first!, geometry: geometry, reload: refreshFlashcards, showDescription: $showDescription) ButtonHStackView(flashcard: flashcards.first!, geometry: geometry, reload: refreshFlashcards, showDescription: $showDescription)
.padding([.bottom, .trailing, .leading]) .padding([.bottom, .trailing, .leading])
} }
} }
}
} else { } else {
if !soonestFlashcards.isEmpty { if !soonestFlashcards.isEmpty {
Group { Group {
@@ -57,6 +65,7 @@ struct AnkiView: View {
.clipShape(.buttonBorder) .clipShape(.buttonBorder)
.padding(.horizontal) .padding(.horizontal)
} }
.frame(maxHeight: .infinity)
} }
else { else {
Text("No flashcards available") Text("No flashcards available")
@@ -70,20 +79,34 @@ struct AnkiView: View {
} }
} }
} }
}
.onAppear { .onAppear {
refreshFlashcards() refreshFlashcards()
} }
.sheet(isPresented: self.$deckViewVisible) {
DeckSelectView(selection: $decksToDisplay, active: $deckViewVisible)
}
.onChange(of: deckViewVisible) { _ in
refreshFlashcards()
}
} }
func refreshFlashcards() { func refreshFlashcards() {
let requestAllFlashcards = NSFetchRequest<Flashcard>(entityName: "Flashcard") let requestAllFlashcards = NSFetchRequest<Flashcard>(entityName: "Flashcard")
requestAllFlashcards.predicate = NSCompoundPredicate(type: .or, subpredicates: [ var predicateAllFlashcards = NSCompoundPredicate(type: .or, subpredicates: [
NSCompoundPredicate(type: .and, subpredicates: [ NSCompoundPredicate(type: .and, subpredicates: [
NSPredicate(format: "%K != nil", "lastSeenOn"), NSPredicate(format: "%K != nil", "lastSeenOn"),
NSPredicate(format: "lastSeenOn + nextSpacedRepetitionMilestone < %@", Date() as CVarArg) NSPredicate(format: "lastSeenOn + nextSpacedRepetitionMilestone < %@", Date() as CVarArg)
]), ]),
NSPredicate(format: "lastSeenOn == nil") NSPredicate(format: "lastSeenOn == nil")
]) ])
if !self.decksToDisplay.isEmpty {
predicateAllFlashcards = NSCompoundPredicate(type: .and, subpredicates: [
predicateAllFlashcards,
NSPredicate(format: "deck IN %@", decksToDisplay)
])
}
requestAllFlashcards.predicate = predicateAllFlashcards
requestAllFlashcards.sortDescriptors = [ requestAllFlashcards.sortDescriptors = [
NSSortDescriptor(key: "nextSpacedRepetitionMilestone", ascending: false), NSSortDescriptor(key: "nextSpacedRepetitionMilestone", ascending: false),
NSSortDescriptor(key: "lastSeenOn", ascending: true) NSSortDescriptor(key: "lastSeenOn", ascending: true)
@@ -97,13 +120,16 @@ struct AnkiView: View {
let requestSoonestFlashcards = NSFetchRequest<Flashcard>(entityName: "Flashcard") let requestSoonestFlashcards = NSFetchRequest<Flashcard>(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 { do {
soonestFlashcards = try moc.fetch(requestSoonestFlashcards) 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 { } catch {
print("Something went wrong while fetching latest flashcard") print("Something went wrong while fetching latest flashcard")
} }

View File

@@ -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!)
}

View File

@@ -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<Deck>
@Binding var selection: Set<Deck>
@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<Deck>()
return DeckSelectView(selection: $decksToDisplay, active: $active)
.environment(\.managedObjectContext, DataController.preview.container.viewContext)
}

View File

@@ -89,7 +89,6 @@ struct FlashCardListView: View {
} }
do { do {
flashcards = try moc.fetch(request) flashcards = try moc.fetch(request)
print(flashcards)
} catch { } catch {
print("Something went wrong while fetching the flashcards") print("Something went wrong while fetching the flashcards")
flashcards = [] flashcards = []