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
- [ ] 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:)
<p align="right">(<a href="#readme-top">back to top</a>)</p>

View File

@@ -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 = "<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>"; };
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>"; };
@@ -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 */,
);

View File

@@ -16,74 +16,97 @@ struct AnkiView: View {
@State var flashcards: [Flashcard] = []
@State var soonestFlashcards: [Flashcard] = []
@State var showDescription = false
@State var decksToDisplay = Set<Deck>()
@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<Flashcard>(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<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 {
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")
}

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 {
flashcards = try moc.fetch(request)
print(flashcards)
} catch {
print("Something went wrong while fetching the flashcards")
flashcards = []