Cards are now in a stack

This commit is contained in:
Avery Pace 2021-11-07 01:25:18 -05:00
parent 32456552a1
commit 76a6232d71
5 changed files with 125 additions and 102 deletions

View File

@ -4,38 +4,6 @@
type = "1" type = "1"
version = "2.0"> version = "2.0">
<Breakpoints> <Breakpoints>
<BreakpointProxy
BreakpointExtensionID = "Xcode.Breakpoint.FileBreakpoint">
<BreakpointContent
uuid = "B690606A-775E-4310-8057-014628DF0882"
shouldBeEnabled = "No"
ignoreCount = "0"
continueAfterRunningActions = "No"
filePath = "Toki Trainer/Views/ContentView.swift"
startingColumnNumber = "9223372036854775807"
endingColumnNumber = "9223372036854775807"
startingLineNumber = "39"
endingLineNumber = "39"
landmarkName = "body"
landmarkType = "24">
</BreakpointContent>
</BreakpointProxy>
<BreakpointProxy
BreakpointExtensionID = "Xcode.Breakpoint.FileBreakpoint">
<BreakpointContent
uuid = "A4ACF1DD-9390-41EC-9DB6-3B2DEBF59EFD"
shouldBeEnabled = "No"
ignoreCount = "0"
continueAfterRunningActions = "No"
filePath = "Toki Trainer/Views/ContentView.swift"
startingColumnNumber = "9223372036854775807"
endingColumnNumber = "9223372036854775807"
startingLineNumber = "59"
endingLineNumber = "59"
landmarkName = "body"
landmarkType = "24">
</BreakpointContent>
</BreakpointProxy>
<BreakpointProxy <BreakpointProxy
BreakpointExtensionID = "Xcode.Breakpoint.FileBreakpoint"> BreakpointExtensionID = "Xcode.Breakpoint.FileBreakpoint">
<BreakpointContent <BreakpointContent
@ -46,10 +14,10 @@
filePath = "Toki Trainer/Views/FlashCardView.swift" filePath = "Toki Trainer/Views/FlashCardView.swift"
startingColumnNumber = "9223372036854775807" startingColumnNumber = "9223372036854775807"
endingColumnNumber = "9223372036854775807" endingColumnNumber = "9223372036854775807"
startingLineNumber = "145" startingLineNumber = "194"
endingLineNumber = "145" endingLineNumber = "194"
landmarkName = "init(isFaceDown:frontText:backText:)" landmarkName = "CardFlipModifier"
landmarkType = "7"> landmarkType = "14">
</BreakpointContent> </BreakpointContent>
</BreakpointProxy> </BreakpointProxy>
</Breakpoints> </Breakpoints>

View File

@ -18,7 +18,7 @@ class FlashCardsViewModel: ObservableObject {
if let safeDictionary = jsonLoader.loadDictionary() { if let safeDictionary = jsonLoader.loadDictionary() {
fullDictionary = safeDictionary fullDictionary = safeDictionary
randomDictionary = safeDictionary randomDictionary = safeDictionary
//randomDictionary.shuffle() randomDictionary.shuffle()
} }
} }

View File

@ -15,10 +15,33 @@ extension String: Identifiable {
struct ContentView: View { struct ContentView: View {
@Environment(\.managedObjectContext) private var viewContext @Environment(\.managedObjectContext) private var viewContext
@State private var tokiInput: String = ""
var body: some View {
TabView {
TranslatorView()
.tabItem {
Image(systemName: "pencil")
Text("Phrase Lookup")
}
FlashCardView()
.tabItem {
Image(systemName: "character.textbox")
Text("Flash Cards")
}
}
}
func openPartsOfSpeechView() {
print("Button pressed.")
}
}
struct TranslatorView: View {
@ObservedObject var tokiDictViewModel = TokiDictionaryViewModel() @ObservedObject var tokiDictViewModel = TokiDictionaryViewModel()
@State private var selectedPartOfSpeech: String? @State private var selectedPartOfSpeech: String?
@State private var tokiInput: String = "" @State private var tokiInput: String = ""
var body: some View { var body: some View {
VStack { VStack {
TextField("Enter Toki Pona Word or Phrase", text: $tokiInput) TextField("Enter Toki Pona Word or Phrase", text: $tokiInput)
@ -53,25 +76,11 @@ struct ContentView: View {
} }
} }
} }
.safeAreaInset(edge: .bottom) {
HStack() {
Button("Parts of Speech") {
self.selectedPartOfSpeech = ""
}
.padding(8)
}
.frame(maxWidth: .infinity)
.background(.thinMaterial)
}
.sheet(item: $selectedPartOfSpeech) { selectedPOS in .sheet(item: $selectedPartOfSpeech) { selectedPOS in
PartsOfSpeechView(selectedPartOfSpeech: selectedPOS, partsOfSpeech: tokiDictViewModel.partsOfSpeech) PartsOfSpeechView(selectedPartOfSpeech: selectedPOS, partsOfSpeech: tokiDictViewModel.partsOfSpeech)
} }
} }
} }
func openPartsOfSpeechView() {
print("Button pressed.")
}
} }

View File

@ -7,32 +7,46 @@
import SwiftUI import SwiftUI
enum FlashCardResult {
case Correct
case Incorrect
case Unanswered
}
struct FlashCardView: View { struct FlashCardView: View {
//@ObservedObject var tokiDictViewModel = TokiDictionaryViewModel()
@ObservedObject var flashCardsViewModel = FlashCardsViewModel() @ObservedObject var flashCardsViewModel = FlashCardsViewModel()
var body: some View { var body: some View {
VStack { VStack {
//FlashCard(canBeFlipped: true, dictionaryEntry: tokiDictViewModel.dictionary.first!)
FlashCardStack(dictionary: flashCardsViewModel.randomDictionary) FlashCardStack(dictionary: flashCardsViewModel.randomDictionary)
} }
} }
} }
extension Binding {
func onChange(_ handler: @escaping () -> ()) -> Binding<Value> {
Binding(
get: { self.wrappedValue },
set: { newValue in
self.wrappedValue = newValue
handler()
})
}
}
struct FlashCardStack: View { struct FlashCardStack: View {
var dictionary: [TokiDictEntry] var dictionary: [TokiDictEntry]
@State private var flashCards: [FlashCard] = [] @State private var flashCards: [FlashCard] = []
@State private var topFlashCard: FlashCard? = nil @State private var topFlashCard: FlashCard? = nil
@State private var flashCardStack: [FlashCard] = [] @State private var flashCardStack: [FlashCard] = []
@State private var flashCardsCanBeFlipped: [Bool] = [] @State private var flashCardsAreInteractive: [Bool] = []
@State private var flashCardsVertOffset: [CGFloat] = [] @State private var flashCardsVertOffset: [CGFloat] = []
@State private var flashCardsResults: [FlashCardResult] = []
@State private var currentFlashCard = 0 @State private var currentFlashCard = 0
let timer = Timer.publish(every: 1, tolerance: 0.1, on: .main, in: .common, options: nil).autoconnect()
var body: some View { var body: some View {
VStack { VStack {
ZStack { ZStack {
@ -40,55 +54,66 @@ struct FlashCardStack: View {
ForEach(flashCards.indices, id: \.self) { index in ForEach(flashCards.indices, id: \.self) { index in
flashCards[index] flashCards[index]
.offset(x: 0, y: flashCardsVertOffset[index]) .offset(x: 0, y: flashCardsVertOffset[index])
.zIndex(-(CGFloat(index * 100)))
} }
} }
// if(flashCards.count > 1) {
// flashCards[currentFlashCard]
// .offset(x: 0, y: flashCardsVertOffset[currentFlashCard])
// .animation(.default)
// flashCards[currentFlashCard + 1]
// .offset(x: 0, y: flashCardsVertOffset[currentFlashCard + 1])
// .animation(.default)
// }
} }
Spacer()
Button {
//self.currentFlashCard += 1
nextFlashCard()
} label: {
Text("Next Card")
}
.background(.white)
} }
Spacer()
.onAppear { .onAppear {
initFlashCardsArray() initFlashCards()
print(currentFlashCard)
} }
} }
func initFlashCardsArray() { func initFlashCards() {
flashCards = [] flashCards = []
for index in dictionary.indices { for index in dictionary.indices {
flashCardsCanBeFlipped.append(false) flashCardsAreInteractive.append(false)
flashCards.append(FlashCard(canBeFlipped: $flashCardsCanBeFlipped[index], dictionaryEntry: dictionary[index])) flashCardsResults.append(FlashCardResult.Unanswered)
flashCardsVertOffset.append(800) flashCards.append(FlashCard(isInteractive: $flashCardsAreInteractive[index], result: $flashCardsResults[index].onChange(nextFlashCard), dictionaryEntry: dictionary[index]))
flashCardsVertOffset.append(470)
} }
if flashCards.count - currentFlashCard >= 3 {
flashCardsVertOffset[currentFlashCard + 1] = 410
flashCardsVertOffset[currentFlashCard + 2] = 440
flashCardsVertOffset[currentFlashCard + 3] = 470
} else if flashCards.count - currentFlashCard == 2 {
flashCardsVertOffset[currentFlashCard + 1] = 410
flashCardsVertOffset[currentFlashCard + 2] = 440
} else if flashCards.count - currentFlashCard == 1 {
flashCardsVertOffset[currentFlashCard + 1] = 410
}
flashCardsVertOffset[currentFlashCard] = 100
flashCardsAreInteractive[currentFlashCard] = true
} }
func nextFlashCard() { func nextFlashCard() {
currentFlashCard += 1
if(currentFlashCard > 0 ) { if(currentFlashCard > 0 ) {
flashCardsVertOffset[currentFlashCard - 1] = -1000 flashCardsVertOffset[currentFlashCard - 1] = -1000
} }
flashCardsVertOffset[currentFlashCard] = 300 flashCardsVertOffset[currentFlashCard] = 100
flashCards[currentFlashCard].setCanBeFlipped(true) flashCardsAreInteractive[currentFlashCard] = true
currentFlashCard += 1
//flashCardsVertOffset[currentFlashCard + 1] = 300 if flashCards.count - currentFlashCard >= 3 {
flashCardsVertOffset[currentFlashCard + 1] = 410
flashCardsVertOffset[currentFlashCard + 2] = 440
flashCardsVertOffset[currentFlashCard + 3] = 470
} else if flashCards.count - currentFlashCard == 2 {
flashCardsVertOffset[currentFlashCard + 1] = 410
flashCardsVertOffset[currentFlashCard + 2] = 440
} else if flashCards.count - currentFlashCard == 1 {
flashCardsVertOffset[currentFlashCard + 1] = 410
}
} }
func setTopFlashCard(card: FlashCard?) { func setTopFlashCard(card: FlashCard?) {
if let safeCard = card { if let safeCard = card {
self.topFlashCard?.canBeFlipped = false self.topFlashCard?.isInteractive = false
self.topFlashCard = safeCard self.topFlashCard = safeCard
self.topFlashCard?.canBeFlipped = true self.topFlashCard?.isInteractive = true
} }
} }
} }
@ -98,40 +123,64 @@ struct FlashCard: View {
@State var isFaceDown = false @State var isFaceDown = false
@State var rotationAngle: Double = 0 @State var rotationAngle: Double = 0
@Binding var canBeFlipped: Bool @Binding var isInteractive: Bool
@Binding var result: FlashCardResult
var dictionaryEntry: TokiDictEntry var dictionaryEntry: TokiDictEntry
@State private var dragAmount = CGFloat(0)
var drag: some Gesture {
DragGesture()
.onChanged { gesture in
if isInteractive {
self.dragAmount = gesture.translation.width
}
}
.onEnded { gesture in
withAnimation {
if isInteractive {
if self.dragAmount < -20 {
self.dragAmount = -500
self.result = FlashCardResult.Incorrect
} else if self.dragAmount > 20 {
self.dragAmount = 500
self.result = FlashCardResult.Correct
}
}
}
}
}
var body: some View { var body: some View {
let flipDegrees = isFaceDown ? 0.0 : 180.0
Text("") Text("")
.modifier(CardFlipModifier(isFaceDown: isFaceDown, frontText: dictionaryEntry.word, backText: concatenateDefinitions())) .modifier(CardFlipModifier(isFaceDown: isFaceDown, frontText: dictionaryEntry.word, backText: concatenateDefinitions()))
.frame(width: 0.8 * screen.width, height: 200.0) .frame(width: 0.8 * screen.width, height: 200.0)
.offset(x: isFaceDown ? -dragAmount : dragAmount, y: abs(dragAmount) / 10)
.rotationEffect(.degrees(isFaceDown ? -(dragAmount / 50) : dragAmount / 50))
.font(.title) .font(.title)
.rotation3DEffect(self.isFaceDown ? Angle(degrees: 180) : Angle(degrees: 0), axis: (x: 0.0, y: 10.0, z: 0.0)) .rotation3DEffect(self.isFaceDown ? Angle(degrees: 180) : Angle(degrees: 0), axis: (x: 0.0, y: 10.0, z: 0.0))
.animation(.default) .animation(.default)
.onTapGesture { .onTapGesture {
print("onTapGesture called") if self.isInteractive == true {
if self.canBeFlipped == true {
self.isFaceDown.toggle() self.isFaceDown.toggle()
} }
} }
.gesture(drag)
} }
func concatenateDefinitions() -> String { func concatenateDefinitions() -> String {
var result = String() var result = String()
for definition in dictionaryEntry.definitions { for definition in dictionaryEntry.definitions {
result.append(contentsOf: definition.definition) result.append(contentsOf: "\(definition.definition)\n")
} }
return result return result
} }
func setCanBeFlipped(_ input: Bool) { func setCanBeFlipped(_ input: Bool) {
print("setCanBeFlipped called") self.isInteractive.toggle()
self.canBeFlipped.toggle()
} }
} }
@ -158,26 +207,23 @@ struct CardFlipModifier: AnimatableModifier {
return ZStack { return ZStack {
RoundedRectangle(cornerRadius: 20.0) RoundedRectangle(cornerRadius: 20.0)
.fill(rotationAngle < 90 ? Color.blue : Color.cyan) .fill(rotationAngle < 90 ? Color.blue : Color.cyan)
.animation(.none) .animation(.none, value: rotationAngle)
.overlay( .overlay(
RoundedRectangle(cornerRadius: 20) RoundedRectangle(cornerRadius: 20)
.stroke(.cyan, lineWidth: 5)) .stroke(.cyan, lineWidth: 5))
.animation(.none, value: rotationAngle)
Text(frontText) Text(frontText)
.font(.title) .font(.title)
.opacity(rotationAngle < 90 ? 1.0 : 0.0) .opacity(rotationAngle < 90 ? 1.0 : 0.0)
.animation(.none) .animation(.none, value: rotationAngle)
Text(backText) Text(backText)
.font(.subheadline) .font(.subheadline)
.padding() .padding()
.opacity(rotationAngle < 90 ? 0.0 : 1.0) .opacity(rotationAngle < 90 ? 0.0 : 1.0)
.animation(.none, value: rotationAngle)
.scaleEffect(CGSize(width: -1.0, height: 1.0)) .scaleEffect(CGSize(width: -1.0, height: 1.0))
.animation(.none)
} }
} }
private func getCardColor() -> Color {
rotationAngle < 90 ? Color.blue : Color.cyan
}
} }
struct FlashCardView_Previews: PreviewProvider { struct FlashCardView_Previews: PreviewProvider {

View File

@ -13,9 +13,9 @@ struct Toki_TrainerApp: App {
var body: some Scene { var body: some Scene {
WindowGroup { WindowGroup {
FlashCardView() //FlashCardView()
//ContentView() ContentView()
// .environment(\.managedObjectContext, persistenceController.container.viewContext) .environment(\.managedObjectContext, persistenceController.container.viewContext)
} }
} }
} }