Cards are now in a stack
This commit is contained in:
parent
32456552a1
commit
76a6232d71
@ -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>
|
||||||
|
@ -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()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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.")
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -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 {
|
||||||
|
@ -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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user