Compare commits
	
		
			10 Commits
		
	
	
		
			880315ee53
			...
			65d3974d6c
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 65d3974d6c | |||
| f6c4e68698 | |||
| e11d79acb9 | |||
| a136ee333a | |||
| 492518ccdb | |||
| e0b6365eeb | |||
| 8e0fca1c36 | |||
| df267ebc4e | |||
| 5a5646648f | |||
| ecdea1de2f | 
| @@ -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 */, | ||||
| 			); | ||||
|   | ||||
| @@ -53,6 +53,7 @@ class DataController: ObservableObject { | ||||
|             flashcard.dateAdded = [Date(), Date().addingTimeInterval(-86400), Date().addingTimeInterval(-172800)].randomElement()! | ||||
|             flashcard.favorite = [true, false].randomElement()! | ||||
|             flashcard.deck = decks.randomElement() | ||||
|             flashcard.hint = ["This is a small hint", "Hint", "This is a very long hint that maybe should be even longer olorem ipsum to cover everything but I don't know what else to write Lorem Ipsum", "This is something in between hint that doesn'coveres the mid cases Lorem Ipsujm Lor"].randomElement() | ||||
|         } | ||||
|         do { | ||||
|             try viewContext.save() | ||||
|   | ||||
| @@ -48,6 +48,7 @@ public class Flashcard: NSManagedObject { | ||||
|     } | ||||
|      | ||||
|     func getSpacedRepetitionMilestone() -> SpacedRepetitionMilestoneEnum { | ||||
|         SpacedRepetitionMilestoneEnum.getMilestoneFromInt(value: self.nextSpacedRepetitionMilestone) | ||||
|         let milestone = SpacedRepetitionMilestoneEnum.getMilestoneFromInt(value: self.nextSpacedRepetitionMilestone) | ||||
|         return milestone == .Now || milestone == .OneMinute ? .TenMinutes : milestone | ||||
|     } | ||||
| } | ||||
|   | ||||
| @@ -26,6 +26,7 @@ extension Flashcard { | ||||
|     @NSManaged public var shown: Bool | ||||
|     @NSManaged public var shownCount: Int64 | ||||
|     @NSManaged public var deck: Deck? | ||||
|     @NSManaged public var hint: String? | ||||
|  | ||||
| } | ||||
|  | ||||
|   | ||||
| @@ -1,5 +1,5 @@ | ||||
| <?xml version="1.0" encoding="UTF-8" standalone="yes"?> | ||||
| <model type="com.apple.IDECoreDataModeler.DataModel" documentVersion="1.0" lastSavedToolsVersion="22757" systemVersion="23D60" minimumToolsVersion="Automatic" sourceLanguage="Swift" usedWithSwiftData="YES" userDefinedModelVersionIdentifier=""> | ||||
| <model type="com.apple.IDECoreDataModeler.DataModel" documentVersion="1.0" lastSavedToolsVersion="22757" systemVersion="23E224" minimumToolsVersion="Automatic" sourceLanguage="Swift" usedWithSwiftData="YES" userDefinedModelVersionIdentifier=""> | ||||
|     <entity name="Deck" representedClassName="Deck" syncable="YES" coreSpotlightDisplayNameExpression="Deck"> | ||||
|         <attribute name="dateAdded" attributeType="Date" defaultDateTimeInterval="736207200" usesScalarValueType="NO"/> | ||||
|         <attribute name="id" attributeType="UUID" usesScalarValueType="NO"/> | ||||
| @@ -10,6 +10,7 @@ | ||||
|         <attribute name="dateAdded" optional="YES" attributeType="Date" usesScalarValueType="NO"/> | ||||
|         <attribute name="desc" optional="YES" attributeType="String" spotlightIndexingEnabled="YES"/> | ||||
|         <attribute name="favorite" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/> | ||||
|         <attribute name="hint" optional="YES" attributeType="String"/> | ||||
|         <attribute name="id" optional="YES" attributeType="UUID" usesScalarValueType="NO"/> | ||||
|         <attribute name="lastSeenOn" optional="YES" attributeType="Date" usesScalarValueType="NO"/> | ||||
|         <attribute name="name" optional="YES" attributeType="String" spotlightIndexingEnabled="YES"/> | ||||
|   | ||||
| @@ -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) { | ||||
|             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") | ||||
|         } | ||||
|   | ||||
| @@ -11,6 +11,16 @@ struct AddDeckView: View { | ||||
|     @Binding var isShowing: Bool | ||||
|     @Environment(\.managedObjectContext) var moc | ||||
|     @State var name: String = "" | ||||
|     @State var deck: Deck? | ||||
|     var edit: Bool | ||||
|      | ||||
|     init(isShowing: Binding<Bool>, deck: Deck? = nil) { | ||||
|         self._isShowing = isShowing | ||||
|         self._name = State(initialValue: deck?.name ?? "") | ||||
|         self.deck = deck | ||||
|         self.edit = deck != nil | ||||
|     } | ||||
|      | ||||
|     var body: some View { | ||||
|         NavigationStack { | ||||
|             List { | ||||
| @@ -27,30 +37,36 @@ struct AddDeckView: View { | ||||
|                 } | ||||
|                 ToolbarItemGroup(placement: .topBarTrailing) { | ||||
|                     Button(action: { | ||||
|                         let deck = Deck(context: moc) | ||||
|                         deck.id = UUID() | ||||
|                         deck.name = name | ||||
|                         deck.dateAdded = Date() | ||||
|                         if let existingDeck = deck { | ||||
|                             existingDeck.name = name | ||||
|                         } else { | ||||
|                             let currentDeck = Deck(context: moc) | ||||
|                             currentDeck.id = UUID() | ||||
|                             currentDeck.dateAdded = Date() | ||||
|                             currentDeck.name = name | ||||
|                         } | ||||
|                         do { | ||||
|                             try moc.save() | ||||
|                             self.isShowing = false | ||||
|                         } catch { | ||||
|                            print("Something went wrong while saving the deck") | ||||
|                             print("Something went wrong while saving the deck") | ||||
|                         } | ||||
|                     }, label: { | ||||
|                         Text("Create") | ||||
|                         Text(edit ? "Edit": "Create") | ||||
|                             .bold() | ||||
|                     }) | ||||
|                     .disabled(name.isEmpty) | ||||
|                 } | ||||
|             } | ||||
|             .navigationTitle("Add Deck") | ||||
|             .navigationTitle(edit ? "Edit Deck" : "Add Deck") | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  | ||||
| #Preview { | ||||
|     @State var isShowing = true | ||||
|     return AddDeckView(isShowing: $isShowing) | ||||
|     let deck = Deck(context: DataController.preview.container.viewContext) | ||||
|     deck.id = UUID() | ||||
|     return AddDeckView(isShowing: $isShowing, deck: deck) | ||||
|         .environment(\.managedObjectContext, DataController.preview.container.viewContext) | ||||
| } | ||||
|   | ||||
| @@ -11,15 +11,37 @@ struct DeckListView: View { | ||||
|     @FetchRequest(sortDescriptors: [NSSortDescriptor(key: "dateAdded", ascending: false)]) var decks: FetchedResults<Deck> | ||||
|     @State var addDeck = false | ||||
|     @Environment(\.managedObjectContext) var moc | ||||
|     @State var addFlashcard = false | ||||
|     @State var editDeck: Bool = false | ||||
|     @State var deckToEdit: Deck? | ||||
|     var body: some View { | ||||
|         NavigationStack { | ||||
|             List { | ||||
|                 Button(action: { | ||||
|                     self.addDeck = true | ||||
|                 }) { | ||||
|                     HStack { | ||||
|                         Image(systemName: "plus.circle.fill") | ||||
|                         Text("Add new deck") | ||||
|                     } | ||||
|                 } | ||||
|                 ForEach(decks) { deck in | ||||
|                     NavigationLink { | ||||
|                         FlashCardListView(deck: deck) | ||||
|                     } label: { | ||||
|                         Text(deck.name ?? "Unknown deck name") | ||||
|                     HStack { | ||||
|                         Button { | ||||
|                             deckToEdit = deck | ||||
|                             editDeck = true | ||||
|                         } label: { | ||||
|                             Image(systemName: "pencil") | ||||
|                                 .contentShape(Rectangle()) | ||||
|                                 .foregroundStyle(.blue) | ||||
|                         } | ||||
|                         .buttonStyle(PlainButtonStyle()) | ||||
|                          | ||||
|                         NavigationLink { | ||||
|                             FlashCardListView(deck: deck) | ||||
|                         } label: { | ||||
|                             Text(deck.name ?? "Unknown deck name") | ||||
|                         } | ||||
|                         .contentShape(Rectangle()) | ||||
|                     } | ||||
|                 } | ||||
|                 .onDelete(perform: { offsets in | ||||
| @@ -37,19 +59,19 @@ struct DeckListView: View { | ||||
|             } | ||||
|             .toolbar { | ||||
|                 ToolbarItemGroup(placement: .topBarTrailing) { | ||||
|                     Button(action: { | ||||
|                         self.addDeck = true | ||||
|                     }) { | ||||
|                         Image(systemName: "plus") | ||||
|                     } | ||||
|                     EditButton() | ||||
|                 } | ||||
|             } | ||||
|             .navigationTitle("All decks") | ||||
|         } | ||||
|         .sheet(isPresented: Binding(get: {editDeck}, set: {editDeck = $0})) { | ||||
|             AddDeckView(isShowing: $editDeck, deck: deckToEdit) | ||||
|         } | ||||
|         .sheet(isPresented: $addDeck, content: { | ||||
|             AddDeckView(isShowing: $addDeck) | ||||
|         }) | ||||
|     } | ||||
|      | ||||
| } | ||||
|  | ||||
| #Preview { | ||||
|   | ||||
							
								
								
									
										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!) | ||||
| } | ||||
							
								
								
									
										40
									
								
								WordAX/Views/Deck/DeckSelectView.swift
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										40
									
								
								WordAX/Views/Deck/DeckSelectView.swift
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,40 @@ | ||||
| // | ||||
| //  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, id:\.self, selection: $selection) { deck in | ||||
|                 DeckRowView(deck: deck) | ||||
|             } | ||||
|             .environment(\.editMode, .constant(EditMode.active)) | ||||
|             .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) | ||||
| } | ||||
| @@ -16,6 +16,9 @@ struct AddFlashCardView: View { | ||||
|     @FetchRequest(sortDescriptors: []) var decks: FetchedResults<Deck> | ||||
|     @State var selectedDeck: Deck? | ||||
|     @State var createDisabled: Bool = true | ||||
|     @State var flashcard: Flashcard | ||||
|     @State var hint: String = "" | ||||
|     var edit: Bool = false | ||||
|     var body: some View { | ||||
|         NavigationStack { | ||||
|             List { | ||||
| @@ -23,6 +26,10 @@ struct AddFlashCardView: View { | ||||
|                     TextField("Name", text: $text) | ||||
|                         .focused($focus) | ||||
|                     TextField("Description", text: $description, axis: .vertical) | ||||
|                     TextField("Hint", text: $hint, axis: .vertical) | ||||
|                         .lineLimit(3, reservesSpace: true) | ||||
|                 } | ||||
|                 Section(header: Text("Deck details")) { | ||||
|                     Picker("Deck", selection: $selectedDeck) { | ||||
|                         ForEach(decks) { deck in | ||||
|                             Text(deck.name ?? "Unknown deck name") | ||||
| @@ -54,7 +61,7 @@ struct AddFlashCardView: View { | ||||
|                         self.createFlashcard() | ||||
|                         self.isShowing = false | ||||
|                     }, label: { | ||||
|                         Text("Create") | ||||
|                         Text(edit ? "Save" : "Create") | ||||
|                             .bold() | ||||
|                     }) | ||||
|                     .disabled(text.count == 0 || description.count == 0 || selectedDeck == nil) | ||||
| @@ -65,15 +72,16 @@ struct AddFlashCardView: View { | ||||
|     } | ||||
|      | ||||
|     private func createFlashcard() { | ||||
|         let flashcard = Flashcard(context: moc) | ||||
|         flashcard.id = UUID() | ||||
|         flashcard.name = self.text | ||||
|         flashcard.desc = self.description | ||||
|         flashcard.nextSpacedRepetitionMilestone = 0 | ||||
|         flashcard.lastSeenOn = nil | ||||
|         flashcard.shownCount = 0 | ||||
|         flashcard.dateAdded = Date() | ||||
|         flashcard.deck = selectedDeck | ||||
|         flashcard.hint = self.hint | ||||
|         if !edit { | ||||
|             flashcard.nextSpacedRepetitionMilestone = 0 | ||||
|             flashcard.lastSeenOn = nil | ||||
|             flashcard.shownCount = 0 | ||||
|             flashcard.dateAdded = Date() | ||||
|         } | ||||
|         try? moc.save() | ||||
|  | ||||
|     } | ||||
| @@ -81,6 +89,8 @@ struct AddFlashCardView: View { | ||||
|  | ||||
| #Preview { | ||||
|     @State var isShowing = true | ||||
|     return AddFlashCardView(isShowing: $isShowing) | ||||
|     let flashcard = Flashcard(context: DataController.preview.container.viewContext) | ||||
|     flashcard.id = UUID() | ||||
|     return AddFlashCardView(isShowing: $isShowing, flashcard: flashcard) | ||||
|         .environment(\.managedObjectContext, DataController.preview.container.viewContext) | ||||
| } | ||||
|   | ||||
| @@ -9,7 +9,7 @@ import SwiftUI | ||||
|  | ||||
| struct FlashCardListRowView: View { | ||||
|     @State var flashcard: Flashcard | ||||
|     @State private var refresh: UUID = UUID() | ||||
|     var refresh: () -> () | ||||
|     var body: some View { | ||||
|         HStack { | ||||
|             Group { | ||||
| @@ -31,9 +31,8 @@ struct FlashCardListRowView: View { | ||||
|                 } catch { | ||||
|                     print("Something went wrong while saving favorite cards, please try again") | ||||
|                 } | ||||
|                 refresh = UUID() | ||||
|                 refresh() | ||||
|             } | ||||
|             .id(refresh) | ||||
|             .padding(.trailing) | ||||
|             VStack { | ||||
|                 Text(flashcard.name ?? "Unknown") | ||||
| @@ -52,12 +51,14 @@ struct FlashCardListRowView: View { | ||||
|  | ||||
| #Preview { | ||||
|     let flashcard = try? DataController.preview.viewContext.fetch(Flashcard.fetchRequest()).first | ||||
|     func reloadPage() { | ||||
|     } | ||||
|     return Group { | ||||
|         FlashCardListRowView(flashcard: flashcard!) | ||||
|         FlashCardListRowView(flashcard: flashcard!, refresh: reloadPage) | ||||
|             .environment(\.managedObjectContext, DataController.preview.container.viewContext) | ||||
|         FlashCardListRowView(flashcard: flashcard!) | ||||
|         FlashCardListRowView(flashcard: flashcard!, refresh: reloadPage) | ||||
|             .environment(\.managedObjectContext, DataController.preview.container.viewContext) | ||||
|         FlashCardListRowView(flashcard: flashcard!) | ||||
|         FlashCardListRowView(flashcard: flashcard!, refresh: reloadPage) | ||||
|             .environment(\.managedObjectContext, DataController.preview.container.viewContext) | ||||
|     } | ||||
| } | ||||
|   | ||||
| @@ -15,44 +15,50 @@ struct FlashCardListView: View { | ||||
|     var deck: Deck? | ||||
|     @Environment(\.managedObjectContext) var moc | ||||
|      | ||||
|     private struct AddButton: View { | ||||
|         @Binding var addFlashcard: Bool | ||||
|         var text: String | ||||
|         var body: some View { | ||||
|             Button(action: { | ||||
|                 self.addFlashcard = true | ||||
|             }) { | ||||
|                 HStack { | ||||
|                     Image(systemName: "plus.circle.fill") | ||||
|                         .padding(.trailing) | ||||
|                     Text(text) | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|      | ||||
|     var body: some View { | ||||
|         GeometryReader { geometry in | ||||
|             NavigationStack { | ||||
|                 Group { | ||||
|                     if !flashcards.isEmpty { | ||||
|                         List { | ||||
|                             ForEach(flashcards) { flashcard in | ||||
|                                 NavigationLink { | ||||
|                                     FlashCardView(flashcard: flashcard, showDescription: $showDescription) | ||||
|                                 } label: { | ||||
|                                     FlashCardListRowView(flashcard: flashcard) | ||||
|                                 } | ||||
|                             } | ||||
|                             .onDelete(perform: { offsets in | ||||
|                                 for index in offsets { | ||||
|                                     let flashcard = flashcards[index] | ||||
|                                     moc.delete(flashcard) | ||||
|                                     flashcards.remove(at: index) | ||||
|                                 } | ||||
|                                  | ||||
|                                 do { | ||||
|                                     try moc.save() | ||||
|                                 } catch { | ||||
|                                     print("Something went wrong while deleting an object") | ||||
|                                 } | ||||
|                             }) | ||||
|                 List { | ||||
|                     AddButton(addFlashcard: $addFlashcard, text: "Add new Flashcard") | ||||
|                         .frame(maxHeight: geometry.size.height / 10) | ||||
|                     ForEach(flashcards) { flashcard in | ||||
|                         NavigationLink { | ||||
|                             FlashCardView(flashcard: flashcard, showDescription: $showDescription) | ||||
|                         } label: { | ||||
|                             FlashCardListRowView(flashcard: flashcard, refresh: refreshFlashcards) | ||||
|                         } | ||||
|                     } else { | ||||
|                         Group { | ||||
|                             Text("You currently don't have any flashcards. To add flashcards, either click at the '+' button at the top or you can download them from the store (coming soon)") | ||||
|                                 .padding() | ||||
|                                 .background(.purple) | ||||
|                                 .clipShape(.buttonBorder) | ||||
|                         } | ||||
|                         .frame(maxHeight: .infinity) | ||||
|                         .padding(.horizontal) | ||||
|                     } | ||||
|                     .onDelete(perform: { offsets in | ||||
|                         for index in offsets { | ||||
|                             let flashcard = flashcards[index] | ||||
|                             moc.delete(flashcard) | ||||
|                             flashcards.remove(at: index) | ||||
|                         } | ||||
|                          | ||||
|                         do { | ||||
|                             try moc.save() | ||||
|                         } catch { | ||||
|                             print("Something went wrong while deleting an object") | ||||
|                         } | ||||
|                     }) | ||||
|                 } | ||||
|                 .environment(\.defaultMinListRowHeight, geometry.size.height / 12) | ||||
|                 .onAppear { | ||||
|                     refreshFlashcards() | ||||
|                 } | ||||
| @@ -64,20 +70,22 @@ struct FlashCardListView: View { | ||||
|                 .navigationBarTitle(deck?.name ?? "All Flashcards", displayMode: deck != nil ? .inline : .automatic) | ||||
|                 .toolbar { | ||||
|                     ToolbarItemGroup(placement: .topBarTrailing) { | ||||
|                         Button(action: { | ||||
|                             self.addFlashcard = true | ||||
|                         }) { | ||||
|                             Image(systemName: "plus") | ||||
|                         } | ||||
|                         EditButton() | ||||
|                     } | ||||
|                 } | ||||
|             } | ||||
|             .sheet(isPresented: $addFlashcard, content: { | ||||
|                 AddFlashCardView(isShowing: $addFlashcard, selectedDeck: deck) | ||||
|                 AddFlashCardView(isShowing: $addFlashcard, selectedDeck: deck, flashcard: createFlashcard()) | ||||
|             }) | ||||
|         } | ||||
|     } | ||||
|      | ||||
|     private func createFlashcard() -> Flashcard { | ||||
|         let flashcard = Flashcard(context:moc) | ||||
|         flashcard.id = UUID() | ||||
|         return flashcard | ||||
|     } | ||||
|      | ||||
|     private func refreshFlashcards() { | ||||
|         let request = NSFetchRequest<Flashcard>(entityName: "Flashcard") | ||||
|         request.sortDescriptors = [NSSortDescriptor(key: "favorite", ascending: false), NSSortDescriptor(key: "dateAdded", ascending: false)] | ||||
| @@ -87,13 +95,12 @@ struct FlashCardListView: View { | ||||
|         } | ||||
|         do { | ||||
|             flashcards = try moc.fetch(request) | ||||
|             print(flashcards) | ||||
|         } catch { | ||||
|             print("Something went wrong while fetching the flashcards") | ||||
|             flashcards = [] | ||||
|         } | ||||
|     } | ||||
|  | ||||
|      | ||||
| } | ||||
|  | ||||
| #Preview { | ||||
|   | ||||
| @@ -13,11 +13,13 @@ struct FlashCardView: View { | ||||
|     var flashcard: Flashcard | ||||
|     @Binding var showDescription: Bool | ||||
|     @Environment(\.colorScheme) var colorScheme | ||||
|      | ||||
|     @State var editFlashcard: Bool = false | ||||
|     @State var showHint: Bool = false | ||||
|      | ||||
|     var body: some View { | ||||
|         let flashcardText = Text(flashcard.name ?? "Unknown") | ||||
|             .font(.title) | ||||
|             .textSelection(.enabled) | ||||
|             .bold() | ||||
|         VStack { | ||||
|             // TODO: Figure out if this and create/edit menu could be more similar? | ||||
| @@ -32,7 +34,6 @@ struct FlashCardView: View { | ||||
|                         .padding(.bottom) | ||||
|                 } | ||||
|                 flashcardText | ||||
|                     .textSelection(.enabled) | ||||
|                 Divider() | ||||
|                     .background(colorScheme == .light ? Color.black : Color.white) | ||||
|                     .padding(.horizontal) | ||||
| @@ -41,6 +42,21 @@ struct FlashCardView: View { | ||||
|             } else { | ||||
|                 flashcardText | ||||
|             } | ||||
|             if !showDescription && flashcard.hint != nil { | ||||
|                 if showHint { | ||||
|                     Text((flashcard.hint != nil) ? "Hint: \(flashcard.hint ?? "")" : "") | ||||
|                         .padding() | ||||
|                         .font(.footnote) | ||||
|                 } else { | ||||
|                 } | ||||
|                 Button { | ||||
|                     showHint.toggle() | ||||
|                 } label: { | ||||
|                     Text(showHint ? "Hide Hint" : "Show Hint") | ||||
|                         .padding() | ||||
|                 } | ||||
| //                .disabled(flashcard.hint == nil) | ||||
|             } | ||||
|         } | ||||
|         .padding([.horizontal, .top]) | ||||
|         .frame(maxWidth: .infinity, maxHeight: .infinity) | ||||
| @@ -48,11 +64,21 @@ struct FlashCardView: View { | ||||
|         .onTapGesture { | ||||
|             self.showDescription = true | ||||
|         } | ||||
|         .sheet(isPresented: $editFlashcard) { | ||||
|             AddFlashCardView(text: flashcard.name ?? "", description: flashcard.desc ?? "", isShowing: $editFlashcard, selectedDeck: flashcard.deck, flashcard: flashcard, edit: true) | ||||
|         } | ||||
|         .toolbar { | ||||
|             Button { | ||||
|                 self.editFlashcard = true | ||||
|             } label: { | ||||
|                 Text("Edit") | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  | ||||
| #Preview { | ||||
|     @State var showDescription = true | ||||
|     @State var showDescription = false | ||||
|     let flashcard = try? DataController.preview.viewContext.fetch(Flashcard.fetchRequest()).first | ||||
|     return FlashCardView(flashcard: flashcard!, showDescription: $showDescription) | ||||
|         .environment(\.managedObjectContext, DataController.preview.container.viewContext) | ||||
|   | ||||
		Reference in New Issue
	
	Block a user