Add ability to see only specific decks
This commit is contained in:
@@ -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>
|
||||
|
||||
|
||||
@@ -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 */,
|
||||
);
|
||||
|
||||
@@ -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")
|
||||
}
|
||||
|
||||
27
WordAX/Views/Deck/DeckRowView.swift
Normal file
27
WordAX/Views/Deck/DeckRowView.swift
Normal 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!)
|
||||
}
|
||||
47
WordAX/Views/Deck/DeckSelectView.swift
Normal file
47
WordAX/Views/Deck/DeckSelectView.swift
Normal 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)
|
||||
}
|
||||
@@ -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 = []
|
||||
|
||||
Reference in New Issue
Block a user