From d9e22f113732ddfb72cb43f482e755cd8bbf4c19 Mon Sep 17 00:00:00 2001
From: Peter Larson
Date: Fri, 8 Sep 2023 09:43:33 -0500
Subject: [PATCH 01/11] Classroom, Exercise, Lesson, Grade init
---
.../xcschemes/xcschememanagement.plist | 2 +-
BibleCore/Package.swift | 1 +
BibleCore/Sources/Classroom/Classroom.swift | 16 ++++++++-----
.../Sources/Classroom/ClassroomView.swift | 4 +++-
.../BuildByLetter/BuildByLetter.swift} | 14 +----------
.../BuildByWord/BuildByWord.swift} | 2 +-
.../BuildByWord/BuildByWordView.swift} | 14 +++++------
.../Classroom/Lesson/Excercise/Exercise.swift | 23 ++++++++++++++++++
.../Sources/Classroom/Lesson/Exercise.swift | 21 ----------------
.../Sources/Classroom/Lesson/Grade.swift | 24 +++++++++++++++++++
.../Sources/Classroom/Lesson/Lesson.swift | 20 +++++++++++++++-
11 files changed, 90 insertions(+), 51 deletions(-)
rename BibleCore/Sources/Classroom/Lesson/{FITB/FillInTheBlank.swift => Excercise/BuildByLetter/BuildByLetter.swift} (88%)
rename BibleCore/Sources/Classroom/Lesson/{Piecemeal/Piecemeal.swift => Excercise/BuildByWord/BuildByWord.swift} (99%)
rename BibleCore/Sources/Classroom/Lesson/{Piecemeal/PiecemealView.swift => Excercise/BuildByWord/BuildByWordView.swift} (90%)
create mode 100644 BibleCore/Sources/Classroom/Lesson/Excercise/Exercise.swift
delete mode 100644 BibleCore/Sources/Classroom/Lesson/Exercise.swift
create mode 100644 BibleCore/Sources/Classroom/Lesson/Grade.swift
diff --git a/Bible.xcodeproj/xcuserdata/plarson.xcuserdatad/xcschemes/xcschememanagement.plist b/Bible.xcodeproj/xcuserdata/plarson.xcuserdatad/xcschemes/xcschememanagement.plist
index e3cfd41..c6c86f3 100644
--- a/Bible.xcodeproj/xcuserdata/plarson.xcuserdatad/xcschemes/xcschememanagement.plist
+++ b/Bible.xcodeproj/xcuserdata/plarson.xcuserdatad/xcschemes/xcschememanagement.plist
@@ -7,7 +7,7 @@
Bible.xcscheme_^#shared#^_
orderHint
- 1
+ 0
SuppressBuildableAutocreation
diff --git a/BibleCore/Package.swift b/BibleCore/Package.swift
index 144aa67..e70925d 100644
--- a/BibleCore/Package.swift
+++ b/BibleCore/Package.swift
@@ -121,6 +121,7 @@ let package = Package(
dependencies: [
"BibleCore",
"BibleClient",
+ "DirectoryCore",
"UserDefaultsClient",
.product(name: "WrappingHStack", package: "WrappingHStack"),
.product(name: "ComposableArchitecture", package: "swift-composable-architecture")
diff --git a/BibleCore/Sources/Classroom/Classroom.swift b/BibleCore/Sources/Classroom/Classroom.swift
index bdaa7d6..52e4bf0 100644
--- a/BibleCore/Sources/Classroom/Classroom.swift
+++ b/BibleCore/Sources/Classroom/Classroom.swift
@@ -1,30 +1,34 @@
import ComposableArchitecture
+import DirectoryCore
import Foundation
import UserDefaultsClient
struct Classroom: Reducer {
-
struct State: Equatable, Codable {
var lessons: IdentifiedArrayOf = []
var selected: Lesson.State.ID? = nil
+ var directory: Directory.State? = nil
}
enum Action: Equatable {
case task
case lesson(Lesson.Action)
case select(id: UUID)
+ case openDirectory
}
@Dependency(\.defaults) var defaults: UserDefaultsClient
-
var body: some ReducerOf {
Reduce { state, action in
switch action {
- case .task
- return .run {
- defaults.get
- }
+ case .task:
+ return .none
+ case .openDirectory:
+
+
+
+ return .none
case .select(id: let id):
state.selected = id
return .none
diff --git a/BibleCore/Sources/Classroom/ClassroomView.swift b/BibleCore/Sources/Classroom/ClassroomView.swift
index 6093947..2165eec 100644
--- a/BibleCore/Sources/Classroom/ClassroomView.swift
+++ b/BibleCore/Sources/Classroom/ClassroomView.swift
@@ -5,7 +5,9 @@ struct ClassroomView: View {
let store: StoreOf
var body: some View {
- EmptyView()
+ WithViewStore(store, observe: { $0 }) { viewStore in
+
+ }
}
}
diff --git a/BibleCore/Sources/Classroom/Lesson/FITB/FillInTheBlank.swift b/BibleCore/Sources/Classroom/Lesson/Excercise/BuildByLetter/BuildByLetter.swift
similarity index 88%
rename from BibleCore/Sources/Classroom/Lesson/FITB/FillInTheBlank.swift
rename to BibleCore/Sources/Classroom/Lesson/Excercise/BuildByLetter/BuildByLetter.swift
index d78c4ba..4dd4dc4 100644
--- a/BibleCore/Sources/Classroom/Lesson/FITB/FillInTheBlank.swift
+++ b/BibleCore/Sources/Classroom/Lesson/Excercise/BuildByLetter/BuildByLetter.swift
@@ -2,18 +2,7 @@ import ComposableArchitecture
import BibleCore
import BibleClient
-/*
- Types of learning modes
-
- FITB, fill in the blank
- - In the beginning
-
-
-
-
- */
-
-struct FillInTheBlank: Reducer {
+struct BuildByLetter: Reducer {
enum Difficulty: Equatable, Codable {
case easy, medium, hard
}
@@ -43,7 +32,6 @@ struct FillInTheBlank: Reducer {
Reduce { state, action in
switch action {
case .task:
-
state.wordBank = withRandomNumberGenerator {
state.correctAnswer.shuffled(using: &$0)
}
diff --git a/BibleCore/Sources/Classroom/Lesson/Piecemeal/Piecemeal.swift b/BibleCore/Sources/Classroom/Lesson/Excercise/BuildByWord/BuildByWord.swift
similarity index 99%
rename from BibleCore/Sources/Classroom/Lesson/Piecemeal/Piecemeal.swift
rename to BibleCore/Sources/Classroom/Lesson/Excercise/BuildByWord/BuildByWord.swift
index 2e42b1b..07bd84d 100644
--- a/BibleCore/Sources/Classroom/Lesson/Piecemeal/Piecemeal.swift
+++ b/BibleCore/Sources/Classroom/Lesson/Excercise/BuildByWord/BuildByWord.swift
@@ -2,7 +2,7 @@ import ComposableArchitecture
import BibleCore
import BibleClient
-public struct Piecemeal: Reducer {
+public struct BuildByWord: Reducer {
public init() {}
public struct State: Equatable, Codable {
diff --git a/BibleCore/Sources/Classroom/Lesson/Piecemeal/PiecemealView.swift b/BibleCore/Sources/Classroom/Lesson/Excercise/BuildByWord/BuildByWordView.swift
similarity index 90%
rename from BibleCore/Sources/Classroom/Lesson/Piecemeal/PiecemealView.swift
rename to BibleCore/Sources/Classroom/Lesson/Excercise/BuildByWord/BuildByWordView.swift
index f04e917..970c453 100644
--- a/BibleCore/Sources/Classroom/Lesson/Piecemeal/PiecemealView.swift
+++ b/BibleCore/Sources/Classroom/Lesson/Excercise/BuildByWord/BuildByWordView.swift
@@ -3,10 +3,10 @@ import SwiftUI
import WrappingHStack
-public struct PiecemealView: View {
- public let store: StoreOf
+public struct BuildByWordView: View {
+ public let store: StoreOf
- public init(store: StoreOf) {
+ public init(store: StoreOf) {
self.store = store
}
@@ -70,13 +70,13 @@ public struct PiecemealView: View {
}
}
-struct PiecemealView_Previews: PreviewProvider {
+struct BuildByWordView_Previews: PreviewProvider {
static var previews: some View {
- PiecemealView(
+ BuildByWordView(
store: Store(
- initialState: Piecemeal.State(),
+ initialState: BuildByWord.State(),
reducer: {
- Piecemeal()
+ BuildByWord()
.dependency(\.bible, .testValue)
}
)
diff --git a/BibleCore/Sources/Classroom/Lesson/Excercise/Exercise.swift b/BibleCore/Sources/Classroom/Lesson/Excercise/Exercise.swift
new file mode 100644
index 0000000..20e4789
--- /dev/null
+++ b/BibleCore/Sources/Classroom/Lesson/Excercise/Exercise.swift
@@ -0,0 +1,23 @@
+import BibleCore
+import ComposableArchitecture
+
+struct Exercise: Reducer {
+ enum State: Equatable, Codable {
+ case buildByWord(BuildByWord.State)
+ case buildByLetter(BuildByLetter.State)
+ }
+
+ enum Action: Equatable {
+ case buildByWord(BuildByWord.Action)
+ case buildByLetter(BuildByLetter.Action)
+ }
+
+ var body: some ReducerOf {
+ Scope(state: /State.buildByWord, action: /Action.buildByWord) {
+ BuildByWord()
+ }
+ Scope(state: /State.buildByLetter, action: /Action.buildByLetter) {
+ BuildByLetter()
+ }
+ }
+}
diff --git a/BibleCore/Sources/Classroom/Lesson/Exercise.swift b/BibleCore/Sources/Classroom/Lesson/Exercise.swift
deleted file mode 100644
index 8579aa5..0000000
--- a/BibleCore/Sources/Classroom/Lesson/Exercise.swift
+++ /dev/null
@@ -1,21 +0,0 @@
-import BibleCore
-import ComposableArchitecture
-
-struct Exercise: Reducer {
-
- enum State: Equatable, Codable {
- case piecemeal(Piecemeal.State)
- case fillInTheBlank(FillInTheBlank.State)
- }
-
- enum Action: Equatable {
- case piecemeal(Piecemeal.Action)
- case fillInTheBlank(FillInTheBlank.Action)
- }
-
- var body: some ReducerOf {
- Reduce { state, action in
- return .none
- }
- }
-}
diff --git a/BibleCore/Sources/Classroom/Lesson/Grade.swift b/BibleCore/Sources/Classroom/Lesson/Grade.swift
new file mode 100644
index 0000000..b720e55
--- /dev/null
+++ b/BibleCore/Sources/Classroom/Lesson/Grade.swift
@@ -0,0 +1,24 @@
+import ComposableArchitecture
+
+struct Grade: Reducer {
+ enum State: Equatable, Codable {
+ case disabled
+ case ready
+ case failed(String)
+ case partial(String)
+ case correct
+ }
+
+ enum Action: Equatable {
+ case submit
+ }
+
+ var body: some ReducerOf {
+ Reduce { state, action in
+ switch action {
+ case .submit:
+ return .none
+ }
+ }
+ }
+}
diff --git a/BibleCore/Sources/Classroom/Lesson/Lesson.swift b/BibleCore/Sources/Classroom/Lesson/Lesson.swift
index 4c15573..359eed5 100644
--- a/BibleCore/Sources/Classroom/Lesson/Lesson.swift
+++ b/BibleCore/Sources/Classroom/Lesson/Lesson.swift
@@ -5,15 +5,33 @@ import Foundation
struct Lesson: Reducer {
struct State: Identifiable, Equatable, Codable {
var verses: [Verse]
- var exercise: Exercise.State?
+ var exercise: Exercise.State? = nil
+ var grade: Grade.State = .disabled
var id: UUID
+
+ public init(
+ verses: [Verse],
+ exercise: Exercise.State? = nil,
+ grade: Grade.State,
+ id: UUID
+ ) {
+ self.verses = verses
+ self.exercise = exercise
+ self.grade = grade
+ self.id = id
+ }
}
enum Action: Equatable {
+ case prepare
case excercise(Exercise.Action)
+ case grade(Grade.Action)
}
var body: some ReducerOf {
+ Scope(state: \.grade, action: /Action.grade) {
+ Grade()
+ }
Reduce { state, action in
switch action {
default:
From 364d02603efd64df2da3d28738672fbee1bab27d Mon Sep 17 00:00:00 2001
From: Peter Larson
Date: Fri, 8 Sep 2023 09:54:27 -0500
Subject: [PATCH 02/11] Codable
---
BibleCore/Sources/DirectoryCore/Directory/Directory.swift | 4 ++--
.../Sources/DirectoryCore/Directory/Section/Section.swift | 2 +-
2 files changed, 3 insertions(+), 3 deletions(-)
diff --git a/BibleCore/Sources/DirectoryCore/Directory/Directory.swift b/BibleCore/Sources/DirectoryCore/Directory/Directory.swift
index a2eb4e7..5b69703 100644
--- a/BibleCore/Sources/DirectoryCore/Directory/Directory.swift
+++ b/BibleCore/Sources/DirectoryCore/Directory/Directory.swift
@@ -8,12 +8,12 @@ public struct Directory: Reducer {
// Do I really need to declare an explicit public initiallizer?
public init() {}
- public enum SortFilter: CaseIterable {
+ public enum SortFilter: CaseIterable, Codable {
case traditional
case alphabetical
}
- public struct State: Equatable {
+ public struct State: Equatable, Codable {
public var isDirectoryOpen: Bool
public var sections: IdentifiedArrayOf = []
public var focused: Book.ID? = nil
diff --git a/BibleCore/Sources/DirectoryCore/Directory/Section/Section.swift b/BibleCore/Sources/DirectoryCore/Directory/Section/Section.swift
index 3cd0c83..e1753c5 100644
--- a/BibleCore/Sources/DirectoryCore/Directory/Section/Section.swift
+++ b/BibleCore/Sources/DirectoryCore/Directory/Section/Section.swift
@@ -4,7 +4,7 @@ import SwiftUI
import ComposableArchitecture
public struct Section: Reducer {
- public struct State: Equatable, Hashable, Identifiable {
+ public struct State: Equatable, Hashable, Identifiable, Codable {
public var book: Book
public var chapters: [Chapter] = []
public var chapter: Chapter? = nil
From e54bd9f012c9660dc75036160cc2ba1814ff838c Mon Sep 17 00:00:00 2001
From: Peter Larson
Date: Tue, 12 Sep 2023 14:04:05 -0500
Subject: [PATCH 03/11] Classroom, BibleComponents, added Tests
---
BibleCore/Package.swift | 7 +-
.../ButtonStyle/CorrectButtonStyle.swift | 42 +++++++
.../ButtonStyle/DisabledButtonStyle.swift | 34 ++++++
.../ButtonStyle/OptionButtonStyle.swift | 49 ++++++++
.../ButtonStyle/UnselectedButtonStyle.swift | 37 ++++++
.../Sources/BibleComponents/Color+.swift | 22 ++++
.../Sources/BibleComponents/ProgressBar.swift | 20 ++++
BibleCore/Sources/BibleCore/Verse.swift | 7 ++
BibleCore/Sources/Classroom/Classroom.swift | 43 ++++++-
.../Sources/Classroom/ClassroomView.swift | 50 ++++++++-
.../BuildByBabySteps/BuildByBabySteps.swift | 79 +++++++++++++
.../BuildByBabyStepsView.swift | 25 +++++
.../BuildByLetter/BuildByLetter.swift | 20 ++--
.../BuildByLetter/BuildByLetterView.swift | 29 +++++
.../Excercise/BuildByWord/BuildByWord.swift | 18 +--
.../BuildByWord/BuildByWordView.swift | 44 ++------
.../Classroom/Lesson/Excercise/Exercise.swift | 2 +-
.../Lesson/Excercise/ExerciseProtocol.swift | 11 ++
.../Lesson/Excercise/ExerciseView.swift | 34 ++++++
.../Classroom/Lesson/{ => Grade}/Grade.swift | 6 +-
.../Classroom/Lesson/Grade/GradeView.swift | 106 ++++++++++++++++++
.../Sources/Classroom/Lesson/Lesson.swift | 15 ++-
.../Sources/Classroom/Lesson/LessonView.swift | 54 +++++++++
.../Tests/ClassroomTests/ClassroomTests.swift | 6 +-
24 files changed, 684 insertions(+), 76 deletions(-)
create mode 100644 BibleCore/Sources/BibleComponents/ButtonStyle/CorrectButtonStyle.swift
create mode 100644 BibleCore/Sources/BibleComponents/ButtonStyle/DisabledButtonStyle.swift
create mode 100644 BibleCore/Sources/BibleComponents/ButtonStyle/OptionButtonStyle.swift
create mode 100644 BibleCore/Sources/BibleComponents/ButtonStyle/UnselectedButtonStyle.swift
create mode 100644 BibleCore/Sources/BibleComponents/Color+.swift
create mode 100644 BibleCore/Sources/BibleComponents/ProgressBar.swift
create mode 100644 BibleCore/Sources/Classroom/Lesson/Excercise/BuildByBabySteps/BuildByBabySteps.swift
create mode 100644 BibleCore/Sources/Classroom/Lesson/Excercise/BuildByBabySteps/BuildByBabyStepsView.swift
create mode 100644 BibleCore/Sources/Classroom/Lesson/Excercise/BuildByLetter/BuildByLetterView.swift
create mode 100644 BibleCore/Sources/Classroom/Lesson/Excercise/ExerciseProtocol.swift
create mode 100644 BibleCore/Sources/Classroom/Lesson/Excercise/ExerciseView.swift
rename BibleCore/Sources/Classroom/Lesson/{ => Grade}/Grade.swift (81%)
create mode 100644 BibleCore/Sources/Classroom/Lesson/Grade/GradeView.swift
create mode 100644 BibleCore/Sources/Classroom/Lesson/LessonView.swift
diff --git a/BibleCore/Package.swift b/BibleCore/Package.swift
index e70925d..98a028b 100644
--- a/BibleCore/Package.swift
+++ b/BibleCore/Package.swift
@@ -13,7 +13,8 @@ let package = Package(
.library(name: "DirectoryCore", targets: ["DirectoryCore"]),
.library(name: "UserDefaultsClient", targets: ["UserDefaultsClient"]),
.library(name: "AppFeature", targets: ["AppFeature"]),
- .library(name: "Classroom", targets: ["Classroom"])
+ .library(name: "Classroom", targets: ["Classroom"]),
+ .library(name: "BibleComponents", targets: ["BibleComponents"])
],
dependencies: [
.package(
@@ -30,6 +31,9 @@ let package = Package(
)
],
targets: [
+ .target(
+ name: "BibleComponents"
+ ),
.target(
name: "ReaderCore",
dependencies: [
@@ -120,6 +124,7 @@ let package = Package(
name: "Classroom",
dependencies: [
"BibleCore",
+ "BibleComponents",
"BibleClient",
"DirectoryCore",
"UserDefaultsClient",
diff --git a/BibleCore/Sources/BibleComponents/ButtonStyle/CorrectButtonStyle.swift b/BibleCore/Sources/BibleComponents/ButtonStyle/CorrectButtonStyle.swift
new file mode 100644
index 0000000..1287bf5
--- /dev/null
+++ b/BibleCore/Sources/BibleComponents/ButtonStyle/CorrectButtonStyle.swift
@@ -0,0 +1,42 @@
+import SwiftUI
+
+public struct CorrectButtonStyle: ButtonStyle {
+
+ public func makeBody(configuration: Configuration) -> some View {
+ configuration.label
+ .kerning(0.5)
+ .textCase(.uppercase)
+ .offset(y: configuration.isPressed ? 0 : -4)
+ .font(.system(size: 14))
+ .fontWeight(.bold)
+ .foregroundColor(.white)
+ .frame(height: 50)
+ .frame(maxWidth: .infinity)
+ .background {
+ RoundedRectangle(cornerRadius: 16, style: .continuous)
+ .fill(
+ Color.correctGreen.shadow(
+ ShadowStyle.inner(
+ color: .darkGreen,
+ radius: 0,
+ x: 0,
+ y: configuration.isPressed ? 0 : -4
+ )
+ )
+ )
+ }
+ }
+}
+
+public extension ButtonStyle where Self == CorrectButtonStyle {
+ static var correct: CorrectButtonStyle { CorrectButtonStyle() }
+}
+
+struct BibleButton_Previews: PreviewProvider {
+ static var previews: some View {
+ Button("Correct") {
+
+ }
+ .buttonStyle(.correct)
+ }
+}
diff --git a/BibleCore/Sources/BibleComponents/ButtonStyle/DisabledButtonStyle.swift b/BibleCore/Sources/BibleComponents/ButtonStyle/DisabledButtonStyle.swift
new file mode 100644
index 0000000..fb0d825
--- /dev/null
+++ b/BibleCore/Sources/BibleComponents/ButtonStyle/DisabledButtonStyle.swift
@@ -0,0 +1,34 @@
+import SwiftUI
+
+public struct DisabledButtonStyle: ButtonStyle {
+ public func makeBody(configuration: Configuration) -> some View {
+ configuration.label
+ .kerning(0.5)
+ .textCase(.uppercase)
+ .font(.system(size: 14))
+ .fontWeight(.bold)
+ .foregroundColor(.softBlack)
+ .frame(height: 50)
+ .frame(maxWidth: .infinity)
+ .background {
+ RoundedRectangle(cornerRadius: 16, style: .continuous)
+ .fill(
+ Color.softGray
+ )
+ }
+ .disabled(true)
+ }
+}
+
+public extension ButtonStyle where Self == DisabledButtonStyle {
+ static var disabled: DisabledButtonStyle { DisabledButtonStyle() }
+}
+
+public struct DisabledButtonStylePreviews: PreviewProvider {
+ public static var previews: some View {
+ Button("disabled") {
+
+ }
+ .buttonStyle(.disabled)
+ }
+}
diff --git a/BibleCore/Sources/BibleComponents/ButtonStyle/OptionButtonStyle.swift b/BibleCore/Sources/BibleComponents/ButtonStyle/OptionButtonStyle.swift
new file mode 100644
index 0000000..3f1496e
--- /dev/null
+++ b/BibleCore/Sources/BibleComponents/ButtonStyle/OptionButtonStyle.swift
@@ -0,0 +1,49 @@
+//
+// SwiftUIView.swift
+//
+//
+// Created by Peter Larson on 9/12/23.
+//
+
+import SwiftUI
+
+public struct OptionButtonStyle: ButtonStyle {
+ public func makeBody(configuration: Configuration) -> some View {
+ configuration.label
+ .font(.system(size: 14))
+ .fontWeight(.bold)
+ .foregroundColor(.softBlack)
+ .padding()
+ .overlay {
+ RoundedRectangle(cornerRadius: 16, style: .continuous)
+ .stroke(lineWidth: 2.5)
+ .foregroundColor(.softGray)
+ }
+ .background {
+ RoundedRectangle(cornerRadius: 16, style: .continuous)
+ .fill(
+ Color.white.shadow(
+ ShadowStyle.inner(
+ color: .softGray,
+ radius: 0,
+ x: 0,
+ y: -4
+ )
+ )
+ )
+ }
+ }
+}
+
+public extension ButtonStyle where Self == OptionButtonStyle {
+ static var option: OptionButtonStyle { OptionButtonStyle() }
+}
+
+struct OptionButtonStyle_Previews: PreviewProvider {
+ static var previews: some View {
+ Button("Word") {
+
+ }
+ .buttonStyle(.option)
+ }
+}
diff --git a/BibleCore/Sources/BibleComponents/ButtonStyle/UnselectedButtonStyle.swift b/BibleCore/Sources/BibleComponents/ButtonStyle/UnselectedButtonStyle.swift
new file mode 100644
index 0000000..163f190
--- /dev/null
+++ b/BibleCore/Sources/BibleComponents/ButtonStyle/UnselectedButtonStyle.swift
@@ -0,0 +1,37 @@
+import SwiftUI
+
+public struct UnselecedButtonStyle: ButtonStyle {
+ public func makeBody(configuration: Configuration) -> some View {
+ configuration.label
+ .offset(y: configuration.isPressed ? 0 : -4)
+ .font(.system(size: 14))
+ .fontWeight(.bold)
+ .foregroundColor(.softBlack)
+ .frame(height: 50)
+ .frame(maxWidth: .infinity)
+ .overlay {
+ RoundedRectangle(cornerRadius: 16, style: .continuous)
+ .stroke(lineWidth: 2.5)
+ .foregroundColor(.softGray)
+ }
+ .background {
+ RoundedRectangle(cornerRadius: 16, style: .continuous)
+ .fill(
+ Color.white.shadow(
+ ShadowStyle.inner(
+ color: .softGray,
+ radius: 0,
+ x: 0,
+ y: configuration.isPressed ? 0 : -4
+ )
+ )
+ )
+ }
+ }
+}
+
+public extension ButtonStyle where Self == UnselecedButtonStyle {
+ static var unselected: UnselecedButtonStyle { UnselecedButtonStyle() }
+}
+
+
diff --git a/BibleCore/Sources/BibleComponents/Color+.swift b/BibleCore/Sources/BibleComponents/Color+.swift
new file mode 100644
index 0000000..432ad98
--- /dev/null
+++ b/BibleCore/Sources/BibleComponents/Color+.swift
@@ -0,0 +1,22 @@
+import SwiftUI
+
+public extension Color {
+ init(hex: UInt, alpha: Double = 1) {
+ self.init(
+ .sRGB,
+ red: Double((hex >> 16) & 0xff) / 255,
+ green: Double((hex >> 08) & 0xff) / 255,
+ blue: Double((hex >> 00) & 0xff) / 255,
+ opacity: alpha
+ )
+ }
+}
+
+public extension Color {
+ static var softGray: Self { .init(hex: 0xE5E5E5) }
+ static var darkGray: Self { .init(hex: 0xAFAFAF) }
+ static var softBlack: Self { .init(hex: 0x3C3C3C) }
+ static var softGreen: Self { .init(hex: 0xD7FFB8) }
+ static var correctGreen: Self { .init(hex: 0x58CC02) }
+ static var darkGreen: Self { .init(hex: 0x58A700) }
+}
diff --git a/BibleCore/Sources/BibleComponents/ProgressBar.swift b/BibleCore/Sources/BibleComponents/ProgressBar.swift
new file mode 100644
index 0000000..575de16
--- /dev/null
+++ b/BibleCore/Sources/BibleComponents/ProgressBar.swift
@@ -0,0 +1,20 @@
+//
+// SwiftUIView.swift
+//
+//
+// Created by Peter Larson on 9/12/23.
+//
+
+import SwiftUI
+
+struct SwiftUIView: View {
+ var body: some View {
+ Text(/*@START_MENU_TOKEN@*/"Hello, World!"/*@END_MENU_TOKEN@*/)
+ }
+}
+
+struct SwiftUIView_Previews: PreviewProvider {
+ static var previews: some View {
+ SwiftUIView()
+ }
+}
diff --git a/BibleCore/Sources/BibleCore/Verse.swift b/BibleCore/Sources/BibleCore/Verse.swift
index 43f13ad..54b6df6 100644
--- a/BibleCore/Sources/BibleCore/Verse.swift
+++ b/BibleCore/Sources/BibleCore/Verse.swift
@@ -34,4 +34,11 @@ public extension Array where Element == Verse {
static var mock: [Verse] {
[.mock]
}
+
+ var complete: [String] {
+ self.map(\.verse)
+ .joined(separator: " ")
+ .split(separator: " ")
+ .map(String.init)
+ }
}
diff --git a/BibleCore/Sources/Classroom/Classroom.swift b/BibleCore/Sources/Classroom/Classroom.swift
index 52e4bf0..6ddd57a 100644
--- a/BibleCore/Sources/Classroom/Classroom.swift
+++ b/BibleCore/Sources/Classroom/Classroom.swift
@@ -8,33 +8,66 @@ struct Classroom: Reducer {
var lessons: IdentifiedArrayOf = []
var selected: Lesson.State.ID? = nil
var directory: Directory.State? = nil
+
+ @BindingState var isDirectoryOpen = false
}
- enum Action: Equatable {
+ enum Action: BindableAction, Equatable {
case task
- case lesson(Lesson.Action)
+ case lesson(id: UUID, action: Lesson.Action)
case select(id: UUID)
case openDirectory
+ case directory(Directory.Action)
+ case binding(_ action: BindingAction)
}
@Dependency(\.defaults) var defaults: UserDefaultsClient
var body: some ReducerOf {
+ BindingReducer()
Reduce { state, action in
switch action {
case .task:
return .none
+ case .select(id: let id):
+ state.selected = id
+ return .none
+ case .lesson:
+ return .none
case .openDirectory:
+ state.isDirectoryOpen = true
+ state.directory = Directory.State(
+ isDirectoryOpen: true, // TODO: refactor
+ books: []
+ )
+ return .none
+ case .directory(.book(id: _, action: .select(_, _, let verses, _))):
+ state.isDirectoryOpen = false
+ var lesson: Lesson.State? = state.lessons.first { state in
+ state.verses.elementsEqual(verses)
+ }
+ if let lesson = lesson {
+ state.selected = lesson.id
+ } else {
+ lesson = Lesson.State(verses: verses)
+ state.lessons.append(lesson!)
+ state.selected = lesson!.id
+ }
return .none
- case .select(id: let id):
- state.selected = id
+ case .directory:
return .none
- case .lesson:
+ case .binding:
return .none
}
}
+ .ifLet(\.directory, action: /Action.directory) {
+ Directory()
+ }
+ .forEach(\.lessons, action: /Action.lesson) {
+ Lesson()
+ }
}
}
diff --git a/BibleCore/Sources/Classroom/ClassroomView.swift b/BibleCore/Sources/Classroom/ClassroomView.swift
index 2165eec..2061f60 100644
--- a/BibleCore/Sources/Classroom/ClassroomView.swift
+++ b/BibleCore/Sources/Classroom/ClassroomView.swift
@@ -1,12 +1,58 @@
import ComposableArchitecture
+import DirectoryCore
import SwiftUI
struct ClassroomView: View {
+
let store: StoreOf
+ @ObservedObject var viewStore: ViewStoreOf
+
+ init(store: StoreOf) {
+ self.store = store
+ self.viewStore = ViewStoreOf(store, observe: { $0 })
+ }
var body: some View {
- WithViewStore(store, observe: { $0 }) { viewStore in
-
+ NavigationStack {
+ List {
+ ForEachStore(
+ store.scope(
+ state: \.lessons,
+ action: Classroom.Action.lesson(id:action:)
+ ),
+ content: { store in
+ Text("foo")
+ }
+ )
+ }
+ .navigationDestination(for: Lesson.State.self, destination: { lesson in
+ Text("foo")
+ })
+ .listStyle(.inset)
+ .navigationTitle("Classroom")
+ .toolbar {
+ ToolbarItem(placement: .navigationBarTrailing) {
+ Button {
+ store.send(.openDirectory)
+ } label: {
+ Text("Add")
+ }
+ }
+ }
+ .popover(isPresented: viewStore.$isDirectoryOpen, content: {
+ IfLetStore(
+ store.scope(
+ state: \.directory,
+ action: Classroom.Action.directory
+ ), then: { store in
+ NavigationStack {
+ DirectoryView(store: store)
+ }
+ }
+ ) {
+ ProgressView()
+ }
+ })
}
}
}
diff --git a/BibleCore/Sources/Classroom/Lesson/Excercise/BuildByBabySteps/BuildByBabySteps.swift b/BibleCore/Sources/Classroom/Lesson/Excercise/BuildByBabySteps/BuildByBabySteps.swift
new file mode 100644
index 0000000..4b5ff6f
--- /dev/null
+++ b/BibleCore/Sources/Classroom/Lesson/Excercise/BuildByBabySteps/BuildByBabySteps.swift
@@ -0,0 +1,79 @@
+import BibleCore
+import ComposableArchitecture
+
+struct BuildByBabySteps: Reducer {
+ struct State: Equatable, Hashable, Codable {
+ var verses: [Verse]
+ var options: [String]? = nil
+ var currentPhrase: [String] = []
+
+ init(verses: [Verse], options: [String]? = nil, currentPhrase: [String] = []) {
+
+ precondition(verses.complete.count != currentPhrase.count)
+
+ self.verses = verses
+ self.options = options
+ self.currentPhrase = currentPhrase
+ }
+ }
+
+ enum Action: Equatable {
+ case setup([Verse], [String])
+ case guess(String)
+ }
+
+ @Dependency(\.withRandomNumberGenerator) var withRandomNumberGenerator
+
+ var body: some ReducerOf {
+ Reduce { state, action in
+ switch action {
+ case .guess(let guess):
+ state.currentPhrase.append(guess)
+
+ return .none
+ case .setup(let verses, let currentPhrases):
+
+ state.verses = verses
+ state.currentPhrase = currentPhrases
+
+ var options = [String]()
+ var copy = verses.complete
+
+ // Correct option
+ options.append(copy.remove(at: currentPhrases.count))
+
+ // Add 1-3 other options if available
+ repeat {
+ withRandomNumberGenerator {
+ copy.shuffle(using: &$0)
+ }
+
+ if let element = copy.popLast() {
+ options.append(element)
+ }
+
+ } while !copy.isEmpty && options.count < 4
+
+ return .none
+ }
+
+ }
+ }
+}
+
+extension BuildByBabySteps.State: ExerciseProtocol {
+ var score: Int {
+ maxScore
+ }
+
+ var isCorrect: Bool {
+
+ return verses
+ .map(\.verse)
+ .joined(separator: " ")
+ .split(separator: " ")
+ .map(String.init)
+ .elementsEqual(currentPhrase)
+
+ }
+}
diff --git a/BibleCore/Sources/Classroom/Lesson/Excercise/BuildByBabySteps/BuildByBabyStepsView.swift b/BibleCore/Sources/Classroom/Lesson/Excercise/BuildByBabySteps/BuildByBabyStepsView.swift
new file mode 100644
index 0000000..5a39662
--- /dev/null
+++ b/BibleCore/Sources/Classroom/Lesson/Excercise/BuildByBabySteps/BuildByBabyStepsView.swift
@@ -0,0 +1,25 @@
+import ComposableArchitecture
+import SwiftUI
+
+struct BuildByBabyStepsView: View {
+
+ let store: StoreOf
+
+ init(store: StoreOf) {
+ self.store = store
+ }
+
+ var body: some View {
+ Text(/*@START_MENU_TOKEN@*/"Hello, World!"/*@END_MENU_TOKEN@*/)
+ }
+}
+
+struct BuildByBabyStepsView_Previews: PreviewProvider {
+ static var previews: some View {
+ BuildByBabyStepsView(
+ store: Store(initialState: BuildByBabySteps.State(verses: .mock)) {
+ BuildByBabySteps()
+ }
+ )
+ }
+}
diff --git a/BibleCore/Sources/Classroom/Lesson/Excercise/BuildByLetter/BuildByLetter.swift b/BibleCore/Sources/Classroom/Lesson/Excercise/BuildByLetter/BuildByLetter.swift
index 4dd4dc4..7007636 100644
--- a/BibleCore/Sources/Classroom/Lesson/Excercise/BuildByLetter/BuildByLetter.swift
+++ b/BibleCore/Sources/Classroom/Lesson/Excercise/BuildByLetter/BuildByLetter.swift
@@ -3,13 +3,11 @@ import BibleCore
import BibleClient
struct BuildByLetter: Reducer {
- enum Difficulty: Equatable, Codable {
- case easy, medium, hard
- }
- struct State: Equatable, Codable {
- var difficulty: Difficulty
+ struct State: Equatable, Codable, Hashable {
var verses: [Verse]
+ var answer: [String?]? = nil
+ var wordBank: [String] = []
var correctAnswer: [String] {
verses
@@ -18,8 +16,16 @@ struct BuildByLetter: Reducer {
.split(separator: " ")
.map(String.init)
}
- var answer: [String?]
- var wordBank: [String]
+
+ init(
+ verses: [Verse],
+ answer: [String?]? = nil,
+ wordBank: [String] = []
+ ) {
+ self.verses = verses
+ self.answer = answer
+ self.wordBank = wordBank
+ }
}
enum Action {
diff --git a/BibleCore/Sources/Classroom/Lesson/Excercise/BuildByLetter/BuildByLetterView.swift b/BibleCore/Sources/Classroom/Lesson/Excercise/BuildByLetter/BuildByLetterView.swift
new file mode 100644
index 0000000..e13ab32
--- /dev/null
+++ b/BibleCore/Sources/Classroom/Lesson/Excercise/BuildByLetter/BuildByLetterView.swift
@@ -0,0 +1,29 @@
+import ComposableArchitecture
+import SwiftUI
+
+struct BuildByLetterView: View {
+
+ let store: StoreOf
+
+ init(store: StoreOf) {
+ self.store = store
+ }
+
+ var body: some View {
+ Text(/*@START_MENU_TOKEN@*/"Hello, World!"/*@END_MENU_TOKEN@*/)
+ }
+}
+
+struct BuildByLetterView_Previews: PreviewProvider {
+ static var previews: some View {
+ BuildByLetterView(
+ store: Store(
+ initialState: BuildByLetter.State(
+ verses: .mock
+ )
+ ) {
+ BuildByLetter()
+ }
+ )
+ }
+}
diff --git a/BibleCore/Sources/Classroom/Lesson/Excercise/BuildByWord/BuildByWord.swift b/BibleCore/Sources/Classroom/Lesson/Excercise/BuildByWord/BuildByWord.swift
index 07bd84d..aae084f 100644
--- a/BibleCore/Sources/Classroom/Lesson/Excercise/BuildByWord/BuildByWord.swift
+++ b/BibleCore/Sources/Classroom/Lesson/Excercise/BuildByWord/BuildByWord.swift
@@ -5,7 +5,7 @@ import BibleClient
public struct BuildByWord: Reducer {
public init() {}
- public struct State: Equatable, Codable {
+ public struct State: Equatable, Codable, Hashable {
var verses: [Verse]? = nil
var error: ClassroomError? = nil
var wordBank = [String]()
@@ -30,7 +30,7 @@ public struct BuildByWord: Reducer {
return correctAnswer.elementsEqual(answer)
}
- public struct ClassroomError: Equatable, Codable {
+ public struct ClassroomError: Equatable, Codable, Hashable {
let title, message: String
init(title: String, message: String) {
@@ -62,8 +62,6 @@ public struct BuildByWord: Reducer {
case setup([Verse])
case failedSetup(error: State.ClassroomError)
case guess(index: Int)
- case check
- case didComplete
}
@Dependency(\.bible) var bible: BibleClient
@@ -77,10 +75,9 @@ public struct BuildByWord: Reducer {
// we want to go fetch a random verse to start learning from.
guard state.verses == nil else {
- return .none
+ return .send(.setup(state.verses!))
}
-
// Grab a random verse to learn
return .run { send in
guard
@@ -119,15 +116,6 @@ public struct BuildByWord: Reducer {
state.answer.append(guess)
- return .none
- case .check:
- if state.isCorrect {
- return .send(.didComplete)
- }
-
- return .none
- case .didComplete:
- print("Hazzah!")
return .none
}
}
diff --git a/BibleCore/Sources/Classroom/Lesson/Excercise/BuildByWord/BuildByWordView.swift b/BibleCore/Sources/Classroom/Lesson/Excercise/BuildByWord/BuildByWordView.swift
index 970c453..0f13f37 100644
--- a/BibleCore/Sources/Classroom/Lesson/Excercise/BuildByWord/BuildByWordView.swift
+++ b/BibleCore/Sources/Classroom/Lesson/Excercise/BuildByWord/BuildByWordView.swift
@@ -1,3 +1,4 @@
+import BibleComponents
import ComposableArchitecture
import SwiftUI
@@ -24,44 +25,16 @@ public struct BuildByWordView: View {
Spacer()
- if viewStore.wordBank.isEmpty {
-
- } else {
- WrappingHStack {
- ForEach(viewStore.wordBank.enumerated().map(\.offset), id: \.self) { index in
- Button {
- viewStore.send(.guess(index: index))
- } label: {
- Text(viewStore.wordBank[index])
- .padding()
- .foregroundColor(.black)
- .background {
- RoundedRectangle(cornerRadius: 12, style: .circular)
- .stroke(lineWidth: 2)
- .foregroundColor(Color.black.opacity(1/5))
- .shadow(color: Color.black.opacity(1/5), radius: 5, x: 5, y: 5)
- }
- }
-
+ WrappingHStack {
+ ForEach(viewStore.wordBank.enumerated().map(\.offset), id: \.self) { index in
+
+ Button(viewStore.wordBank[index]) {
+ viewStore.send(.guess(index: index))
}
+ .buttonStyle(.option)
+
}
}
-
- Button {
- viewStore.send(.check)
- } label: {
- Text("Check")
- .textCase(.uppercase)
- .foregroundColor(.white)
- .fontWeight(.bold)
- .frame(height: 50)
- .frame(maxWidth: .infinity)
- .background {
- RoundedRectangle(cornerRadius: 12, style: .circular)
- .foregroundColor(viewStore.answer.isEmpty ? .gray : .green)
- }
- }
- .disabled(viewStore.answer.isEmpty)
}
.task { viewStore.send(.task) }
@@ -78,6 +51,7 @@ struct BuildByWordView_Previews: PreviewProvider {
reducer: {
BuildByWord()
.dependency(\.bible, .testValue)
+ ._printChanges()
}
)
)
diff --git a/BibleCore/Sources/Classroom/Lesson/Excercise/Exercise.swift b/BibleCore/Sources/Classroom/Lesson/Excercise/Exercise.swift
index 20e4789..1e6ead9 100644
--- a/BibleCore/Sources/Classroom/Lesson/Excercise/Exercise.swift
+++ b/BibleCore/Sources/Classroom/Lesson/Excercise/Exercise.swift
@@ -2,7 +2,7 @@ import BibleCore
import ComposableArchitecture
struct Exercise: Reducer {
- enum State: Equatable, Codable {
+ enum State: Equatable, Codable, Hashable {
case buildByWord(BuildByWord.State)
case buildByLetter(BuildByLetter.State)
}
diff --git a/BibleCore/Sources/Classroom/Lesson/Excercise/ExerciseProtocol.swift b/BibleCore/Sources/Classroom/Lesson/Excercise/ExerciseProtocol.swift
new file mode 100644
index 0000000..05cd048
--- /dev/null
+++ b/BibleCore/Sources/Classroom/Lesson/Excercise/ExerciseProtocol.swift
@@ -0,0 +1,11 @@
+import Foundation
+
+protocol ExerciseProtocol {
+ var isCorrect: Bool { get }
+ var score: Int { get }
+ var maxScore: Int { get }
+}
+
+extension ExerciseProtocol {
+ var maxScore: Int { return 10 }
+}
diff --git a/BibleCore/Sources/Classroom/Lesson/Excercise/ExerciseView.swift b/BibleCore/Sources/Classroom/Lesson/Excercise/ExerciseView.swift
new file mode 100644
index 0000000..b0a6c81
--- /dev/null
+++ b/BibleCore/Sources/Classroom/Lesson/Excercise/ExerciseView.swift
@@ -0,0 +1,34 @@
+import ComposableArchitecture
+import SwiftUI
+
+struct ExerciseView: View {
+ let store: StoreOf
+
+ var body: some View {
+ SwitchStore(store) { initialState in
+ switch initialState {
+ case .buildByLetter:
+ CaseLet(
+ /Exercise.State.buildByLetter, action: Exercise.Action.buildByLetter,
+ then: BuildByLetterView.init(store:)
+ )
+ case .buildByWord:
+ CaseLet(
+ /Exercise.State.buildByWord, action: Exercise.Action.buildByWord,
+ then: BuildByWordView.init(store:)
+ )
+ }
+ }
+ }
+}
+
+struct ExerciseView_Previews: PreviewProvider {
+ static var previews: some View {
+ ExerciseView(
+ store: Store(initialState: Exercise.State.buildByWord(.init(verses: .mock))) {
+ Exercise()
+ ._printChanges()
+ }
+ )
+ }
+}
diff --git a/BibleCore/Sources/Classroom/Lesson/Grade.swift b/BibleCore/Sources/Classroom/Lesson/Grade/Grade.swift
similarity index 81%
rename from BibleCore/Sources/Classroom/Lesson/Grade.swift
rename to BibleCore/Sources/Classroom/Lesson/Grade/Grade.swift
index b720e55..a6a15fc 100644
--- a/BibleCore/Sources/Classroom/Lesson/Grade.swift
+++ b/BibleCore/Sources/Classroom/Lesson/Grade/Grade.swift
@@ -1,7 +1,7 @@
import ComposableArchitecture
struct Grade: Reducer {
- enum State: Equatable, Codable {
+ enum State: Equatable, Codable, Hashable {
case disabled
case ready
case failed(String)
@@ -10,13 +10,13 @@ struct Grade: Reducer {
}
enum Action: Equatable {
- case submit
+ case next
}
var body: some ReducerOf {
Reduce { state, action in
switch action {
- case .submit:
+ case .next:
return .none
}
}
diff --git a/BibleCore/Sources/Classroom/Lesson/Grade/GradeView.swift b/BibleCore/Sources/Classroom/Lesson/Grade/GradeView.swift
new file mode 100644
index 0000000..3f2d605
--- /dev/null
+++ b/BibleCore/Sources/Classroom/Lesson/Grade/GradeView.swift
@@ -0,0 +1,106 @@
+import BibleComponents
+import ComposableArchitecture
+import SwiftUI
+
+fileprivate extension View {
+
+ @ViewBuilder
+ func buttonStyle(for grade: Grade.State) -> some View {
+ switch grade {
+ case .correct:
+ self.buttonStyle(.correct)
+ case .disabled:
+ self.buttonStyle(.disabled)
+ case .ready:
+ self.buttonStyle(.unselected)
+ case .failed(_):
+ self.buttonStyle(.unselected)
+ case .partial(_):
+ self.buttonStyle(.unselected)
+ }
+ }
+
+ @ViewBuilder
+ func foreground(for grade: Grade.State) -> some View {
+ switch grade {
+ case .correct:
+ self.foregroundColor(.darkGreen)
+ case .disabled:
+ self.foregroundColor(.darkGreen)
+ case .ready:
+ self.foregroundColor(.darkGreen)
+ case .failed(_):
+ self.foregroundColor(.darkGreen)
+ case .partial(_):
+ self.foregroundColor(.darkGreen)
+ }
+ }
+
+ @ViewBuilder
+ func hidden(for grade: Grade.State) -> some View {
+ switch grade {
+ case .correct:
+ self
+ default:
+ self.hidden()
+ }
+ }
+}
+
+fileprivate extension String {
+ static func title(for grade: Grade.State) -> String {
+ switch grade {
+ case .correct:
+ return "Nice Job"
+ default: return String()
+ }
+ }
+}
+
+struct GradeView: View {
+ let store: StoreOf
+
+ var body: some View {
+ SwitchStore(store) { initialState in
+ VStack(alignment: .leading) {
+ Text(String.title(for: initialState))
+ .font(.system(size: 24))
+ .fontWeight(.bold)
+ .foreground(for: initialState)
+ .hidden(for: initialState)
+ Button("Continue") {
+ store.send(.next)
+ }
+ .buttonStyle(for: initialState)
+ }
+ .padding()
+ .background {
+ switch initialState {
+ case .correct:
+ Color.softGreen
+ .transition(.move(edge: .bottom))
+ .edgesIgnoringSafeArea(.bottom)
+ default:
+ EmptyView()
+ }
+ }
+ }
+ }
+}
+
+struct GradeView_Previews: PreviewProvider {
+ static var previews: some View {
+ ForEach(
+ [Grade.State.correct, Grade.State.ready, Grade.State.disabled],
+ id: \.self
+ ) { grade in
+ VStack {
+ Spacer()
+ GradeView(store: Store(initialState: grade) {
+ Grade()
+ ._printChanges()
+ })
+ }
+ }
+ }
+}
diff --git a/BibleCore/Sources/Classroom/Lesson/Lesson.swift b/BibleCore/Sources/Classroom/Lesson/Lesson.swift
index 359eed5..01c82cc 100644
--- a/BibleCore/Sources/Classroom/Lesson/Lesson.swift
+++ b/BibleCore/Sources/Classroom/Lesson/Lesson.swift
@@ -3,17 +3,17 @@ import ComposableArchitecture
import Foundation
struct Lesson: Reducer {
- struct State: Identifiable, Equatable, Codable {
+ struct State: Identifiable, Equatable, Codable, Hashable {
var verses: [Verse]
var exercise: Exercise.State? = nil
var grade: Grade.State = .disabled
- var id: UUID
+ var id: UUID = UUID()
public init(
verses: [Verse],
exercise: Exercise.State? = nil,
- grade: Grade.State,
- id: UUID
+ grade: Grade.State = .disabled,
+ id: UUID = UUID()
) {
self.verses = verses
self.exercise = exercise
@@ -34,6 +34,13 @@ struct Lesson: Reducer {
}
Reduce { state, action in
switch action {
+ case .prepare:
+
+ state.exercise = .buildByWord(BuildByWord.State.init(verses: .mock))
+
+ return .none
+ case .grade(.next):
+ return .none
default:
return .none
}
diff --git a/BibleCore/Sources/Classroom/Lesson/LessonView.swift b/BibleCore/Sources/Classroom/Lesson/LessonView.swift
new file mode 100644
index 0000000..18e0fb1
--- /dev/null
+++ b/BibleCore/Sources/Classroom/Lesson/LessonView.swift
@@ -0,0 +1,54 @@
+import ComposableArchitecture
+import SwiftUI
+
+struct LessonView: View {
+
+ let store: StoreOf
+
+ init(store: StoreOf) {
+ self.store = store
+ }
+
+ var body: some View {
+ VStack {
+ HStack {
+ Button {
+
+ } label: {
+ Image(systemName: "xmark")
+ }
+
+ ProgressBar()
+
+ }
+
+ IfLetStore(
+ store.scope(state: \.exercise, action: Lesson.Action.excercise),
+ then: ExerciseView.init(store:)
+ ) {
+ ProgressView()
+ .progressViewStyle(.circular)
+ .frame(maxHeight: .infinity)
+ }
+
+ Spacer()
+
+ GradeView(store: store.scope(state: \.grade, action: Lesson.Action.grade))
+ }
+ .onAppear {
+ store.send(.prepare)
+ }
+ }
+}
+
+struct LessonView_Previews: PreviewProvider {
+ static var previews: some View {
+ LessonView(
+ store: Store(initialState: Lesson.State.init(verses: .mock)) {
+ Lesson()
+ .dependency(\.bible, .testValue)
+ ._printChanges()
+ }
+ )
+ }
+}
diff --git a/BibleCore/Tests/ClassroomTests/ClassroomTests.swift b/BibleCore/Tests/ClassroomTests/ClassroomTests.swift
index a878feb..31f5cac 100644
--- a/BibleCore/Tests/ClassroomTests/ClassroomTests.swift
+++ b/BibleCore/Tests/ClassroomTests/ClassroomTests.swift
@@ -6,13 +6,13 @@ import XCTest
@MainActor
final class ClassroomTests: XCTestCase {
- var store: TestStoreOf!
+ var store: TestStoreOf!
var wordBank: [String]!
override func setUp() async throws {
- store = TestStore(initialState: Piecemeal.State()) {
- Piecemeal()
+ store = TestStore(initialState: BuildByWord.State()) {
+ BuildByWord()
} withDependencies: {
$0.withRandomNumberGenerator = WithRandomNumberGenerator(LCRNG(seed: 0))
}
From 8ae735fd56783c29c9e2fbeb379b7ceadbe6262b Mon Sep 17 00:00:00 2001
From: Peter Larson
Date: Wed, 13 Sep 2023 11:05:52 -0500
Subject: [PATCH 04/11] ProgressBar, UnselectedButtonStyle
---
.../ButtonStyle/UnselectedButtonStyle.swift | 1 +
.../Sources/BibleComponents/ProgressBar.swift | 40 +++++++++++++------
2 files changed, 29 insertions(+), 12 deletions(-)
diff --git a/BibleCore/Sources/BibleComponents/ButtonStyle/UnselectedButtonStyle.swift b/BibleCore/Sources/BibleComponents/ButtonStyle/UnselectedButtonStyle.swift
index 163f190..91fe3b8 100644
--- a/BibleCore/Sources/BibleComponents/ButtonStyle/UnselectedButtonStyle.swift
+++ b/BibleCore/Sources/BibleComponents/ButtonStyle/UnselectedButtonStyle.swift
@@ -3,6 +3,7 @@ import SwiftUI
public struct UnselecedButtonStyle: ButtonStyle {
public func makeBody(configuration: Configuration) -> some View {
configuration.label
+ .textCase(.uppercase)
.offset(y: configuration.isPressed ? 0 : -4)
.font(.system(size: 14))
.fontWeight(.bold)
diff --git a/BibleCore/Sources/BibleComponents/ProgressBar.swift b/BibleCore/Sources/BibleComponents/ProgressBar.swift
index 575de16..14a9a3d 100644
--- a/BibleCore/Sources/BibleComponents/ProgressBar.swift
+++ b/BibleCore/Sources/BibleComponents/ProgressBar.swift
@@ -1,20 +1,36 @@
-//
-// SwiftUIView.swift
-//
-//
-// Created by Peter Larson on 9/12/23.
-//
-
import SwiftUI
-struct SwiftUIView: View {
- var body: some View {
- Text(/*@START_MENU_TOKEN@*/"Hello, World!"/*@END_MENU_TOKEN@*/)
+public struct ProgressBar: View {
+
+ private let progress: Double
+
+ public init(progress: Double) {
+ self.progress = progress
+ }
+
+ public var body: some View {
+ ZStack {
+ RoundedRectangle(cornerRadius: 16)
+ .fill(Color.softGray)
+ GeometryReader { proxy in
+ RoundedRectangle(cornerRadius: 16)
+ .fill(Color.correctGreen)
+ .frame(width: (proxy.size.width - 32) * progress + 32)
+// RoundedRectangle(cornerRadius: 16)
+// .fill(Color.white.opacity(1/10))
+// .frame(width: (proxy.size.width - 32) * progress + 16)
+// .frame(height: 8)
+// .offset(x: 8, y: 2)
+ }
+ }
+ .frame(minWidth: 48)
+ .frame(height: 16)
}
}
-struct SwiftUIView_Previews: PreviewProvider {
+struct ProgressBar_Previews: PreviewProvider {
static var previews: some View {
- SwiftUIView()
+ ProgressBar(progress: 1.0)
+ .padding()
}
}
From a1ba21fd8a87ebdfeed79451b5e0fe09402ec23c Mon Sep 17 00:00:00 2001
From: Peter Larson
Date: Wed, 13 Sep 2023 11:06:09 -0500
Subject: [PATCH 05/11] BuildByWord, Lesson overhaul
---
.../BuildByBabySteps/BuildByBabySteps.swift | 161 +++++++++---------
.../BuildByBabyStepsView.swift | 50 +++---
.../BuildByLetter/BuildByLetter.swift | 10 +-
.../Excercise/BuildByWord/BuildByWord.swift | 52 +++---
.../BuildByWord/BuildByWordView.swift | 23 ++-
.../Classroom/Lesson/Excercise/Exercise.swift | 21 ++-
.../Classroom/Lesson/Grade/GradeView.swift | 4 +-
.../Sources/Classroom/Lesson/Lesson.swift | 20 ++-
.../Sources/Classroom/Lesson/LessonView.swift | 9 +-
.../Tests/ClassroomTests/ClassroomTests.swift | 70 ++++----
10 files changed, 250 insertions(+), 170 deletions(-)
diff --git a/BibleCore/Sources/Classroom/Lesson/Excercise/BuildByBabySteps/BuildByBabySteps.swift b/BibleCore/Sources/Classroom/Lesson/Excercise/BuildByBabySteps/BuildByBabySteps.swift
index 4b5ff6f..3b071f6 100644
--- a/BibleCore/Sources/Classroom/Lesson/Excercise/BuildByBabySteps/BuildByBabySteps.swift
+++ b/BibleCore/Sources/Classroom/Lesson/Excercise/BuildByBabySteps/BuildByBabySteps.swift
@@ -1,79 +1,82 @@
-import BibleCore
-import ComposableArchitecture
-
-struct BuildByBabySteps: Reducer {
- struct State: Equatable, Hashable, Codable {
- var verses: [Verse]
- var options: [String]? = nil
- var currentPhrase: [String] = []
-
- init(verses: [Verse], options: [String]? = nil, currentPhrase: [String] = []) {
-
- precondition(verses.complete.count != currentPhrase.count)
-
- self.verses = verses
- self.options = options
- self.currentPhrase = currentPhrase
- }
- }
-
- enum Action: Equatable {
- case setup([Verse], [String])
- case guess(String)
- }
-
- @Dependency(\.withRandomNumberGenerator) var withRandomNumberGenerator
-
- var body: some ReducerOf {
- Reduce { state, action in
- switch action {
- case .guess(let guess):
- state.currentPhrase.append(guess)
-
- return .none
- case .setup(let verses, let currentPhrases):
-
- state.verses = verses
- state.currentPhrase = currentPhrases
-
- var options = [String]()
- var copy = verses.complete
-
- // Correct option
- options.append(copy.remove(at: currentPhrases.count))
-
- // Add 1-3 other options if available
- repeat {
- withRandomNumberGenerator {
- copy.shuffle(using: &$0)
- }
-
- if let element = copy.popLast() {
- options.append(element)
- }
-
- } while !copy.isEmpty && options.count < 4
-
- return .none
- }
-
- }
- }
-}
-
-extension BuildByBabySteps.State: ExerciseProtocol {
- var score: Int {
- maxScore
- }
-
- var isCorrect: Bool {
-
- return verses
- .map(\.verse)
- .joined(separator: " ")
- .split(separator: " ")
- .map(String.init)
- .elementsEqual(currentPhrase)
-
- }
-}
+//import BibleCore
+//import ComposableArchitecture
+//import Foundation
+//
+//struct BuildByBabySteps: Reducer {
+// struct State: Equatable, Hashable, Codable {
+// var verses: [Verse]
+// var options: [String]? = nil
+// var currentPhrase: [String] = []
+//
+// init(verses: [Verse], options: [String]? = nil, currentPhrase: [String] = []) {
+//
+// precondition(verses.complete.count != currentPhrase.count)
+//
+// self.verses = verses
+// self.options = options
+// self.currentPhrase = currentPhrase
+// }
+// }
+//
+// enum Action: Equatable {
+// case setup([Verse], [String])
+// case guess(id: UUID)
+// }
+//
+// @Dependency(\.withRandomNumberGenerator) var withRandomNumberGenerator
+//
+// var body: some ReducerOf {
+// Reduce { state, action in
+// switch action {
+// case .guess(id: let id):
+// state.currentPhrase.append(state.word)
+//// state.currentPhrase.append(guess)
+//// state.currentPhrase.append(<#T##newElement: String##String#>)
+//
+// return .none
+// case .setup(let verses, let currentPhrases):
+//
+// state.verses = verses
+// state.currentPhrase = currentPhrases
+//
+// var options = [String]()
+// var copy = verses.complete
+//
+// // Correct option
+// options.append(copy.remove(at: currentPhrases.count))
+//
+// // Add 1-3 other options if available
+// repeat {
+// withRandomNumberGenerator {
+// copy.shuffle(using: &$0)
+// }
+//
+// if let element = copy.popLast() {
+// options.append(element)
+// }
+//
+// } while !copy.isEmpty && options.count < 4
+//
+// return .none
+// }
+//
+// }
+// }
+//}
+//
+//extension BuildByBabySteps.State: ExerciseProtocol {
+// var score: Int {
+// maxScore
+// }
+//
+// var isCorrect: Bool {
+//
+// return verses
+// .map(\.verse)
+// .joined(separator: " ")
+// .split(separator: " ")
+// .map(String.init)
+// .elementsEqual(currentPhrase)
+//
+// }
+//}
diff --git a/BibleCore/Sources/Classroom/Lesson/Excercise/BuildByBabySteps/BuildByBabyStepsView.swift b/BibleCore/Sources/Classroom/Lesson/Excercise/BuildByBabySteps/BuildByBabyStepsView.swift
index 5a39662..9f48601 100644
--- a/BibleCore/Sources/Classroom/Lesson/Excercise/BuildByBabySteps/BuildByBabyStepsView.swift
+++ b/BibleCore/Sources/Classroom/Lesson/Excercise/BuildByBabySteps/BuildByBabyStepsView.swift
@@ -1,25 +1,25 @@
-import ComposableArchitecture
-import SwiftUI
-
-struct BuildByBabyStepsView: View {
-
- let store: StoreOf
-
- init(store: StoreOf) {
- self.store = store
- }
-
- var body: some View {
- Text(/*@START_MENU_TOKEN@*/"Hello, World!"/*@END_MENU_TOKEN@*/)
- }
-}
-
-struct BuildByBabyStepsView_Previews: PreviewProvider {
- static var previews: some View {
- BuildByBabyStepsView(
- store: Store(initialState: BuildByBabySteps.State(verses: .mock)) {
- BuildByBabySteps()
- }
- )
- }
-}
+//import ComposableArchitecture
+//import SwiftUI
+//
+//struct BuildByBabyStepsView: View {
+//
+// let store: StoreOf
+//
+// init(store: StoreOf) {
+// self.store = store
+// }
+//
+// var body: some View {
+// Text(/*@START_MENU_TOKEN@*/"Hello, World!"/*@END_MENU_TOKEN@*/)
+// }
+//}
+//
+//struct BuildByBabyStepsView_Previews: PreviewProvider {
+// static var previews: some View {
+// BuildByBabyStepsView(
+// store: Store(initialState: BuildByBabySteps.State(verses: .mock)) {
+// BuildByBabySteps()
+// }
+// )
+// }
+//}
diff --git a/BibleCore/Sources/Classroom/Lesson/Excercise/BuildByLetter/BuildByLetter.swift b/BibleCore/Sources/Classroom/Lesson/Excercise/BuildByLetter/BuildByLetter.swift
index 7007636..3f2634c 100644
--- a/BibleCore/Sources/Classroom/Lesson/Excercise/BuildByLetter/BuildByLetter.swift
+++ b/BibleCore/Sources/Classroom/Lesson/Excercise/BuildByLetter/BuildByLetter.swift
@@ -4,7 +4,15 @@ import BibleClient
struct BuildByLetter: Reducer {
- struct State: Equatable, Codable, Hashable {
+ struct State: Equatable, Codable, Hashable, ExerciseProtocol {
+ var isCorrect: Bool {
+ false
+ }
+
+ var score: Int {
+ 0
+ }
+
var verses: [Verse]
var answer: [String?]? = nil
var wordBank: [String] = []
diff --git a/BibleCore/Sources/Classroom/Lesson/Excercise/BuildByWord/BuildByWord.swift b/BibleCore/Sources/Classroom/Lesson/Excercise/BuildByWord/BuildByWord.swift
index aae084f..1c4a49e 100644
--- a/BibleCore/Sources/Classroom/Lesson/Excercise/BuildByWord/BuildByWord.swift
+++ b/BibleCore/Sources/Classroom/Lesson/Excercise/BuildByWord/BuildByWord.swift
@@ -1,15 +1,27 @@
import ComposableArchitecture
import BibleCore
import BibleClient
+import Foundation
public struct BuildByWord: Reducer {
public init() {}
- public struct State: Equatable, Codable, Hashable {
+ public struct State: Equatable, Codable, Hashable, ExerciseProtocol {
+
+ public struct Guess: Equatable, Identifiable, Codable, Hashable {
+ let word: String
+ public let id: UUID
+
+ init(word: String, id: UUID) {
+ self.word = word
+ self.id = id
+ }
+ }
+
var verses: [Verse]? = nil
var error: ClassroomError? = nil
- var wordBank = [String]()
- var answer = [String]()
+ var wordBank = IdentifiedArrayOf()
+ var answer = IdentifiedArrayOf()
var correctAnswer: [String] {
guard let verses = verses else {
@@ -27,9 +39,11 @@ public struct BuildByWord: Reducer {
return false
}
- return correctAnswer.elementsEqual(answer)
+ return correctAnswer.elementsEqual(answer.map(\.word))
}
+ var score: Int { 0 }
+
public struct ClassroomError: Equatable, Codable, Hashable {
let title, message: String
@@ -44,12 +58,7 @@ public struct BuildByWord: Reducer {
)
}
- init(
- verses: [Verse]? = nil,
- error: ClassroomError? = nil,
- wordBank: [String] = [String](),
- answer: [String] = [String]()
- ) {
+ init(verses: [Verse]? = nil, error: ClassroomError? = nil, wordBank: IdentifiedArrayOf = IdentifiedArrayOf(), answer: IdentifiedArrayOf = IdentifiedArrayOf()) {
self.verses = verses
self.error = error
self.wordBank = wordBank
@@ -61,10 +70,12 @@ public struct BuildByWord: Reducer {
case task
case setup([Verse])
case failedSetup(error: State.ClassroomError)
- case guess(index: Int)
+ case guess(id: UUID)
+ case remove(id: UUID)
}
@Dependency(\.bible) var bible: BibleClient
+ @Dependency(\.uuid) var uuid
@Dependency(\.withRandomNumberGenerator) var withRandomNumberGenerator
public var body: some ReducerOf {
@@ -100,9 +111,12 @@ public struct BuildByWord: Reducer {
case .setup(let verses):
state.verses = verses
- state.wordBank = withRandomNumberGenerator {
- state.correctAnswer.shuffled(using: &$0)
- }
+ state.wordBank = IdentifiedArray(uniqueElements: withRandomNumberGenerator { (generator) -> [State.Guess] in
+ return state.correctAnswer.shuffled(using: &generator).map { (word) -> State.Guess in
+ return State.Guess.init(word: word, id: uuid())
+ }
+ })
+
state.answer = []
return .none
@@ -111,11 +125,11 @@ public struct BuildByWord: Reducer {
state.error = error
return .none
- case .guess(let index):
- let guess = state.wordBank.remove(at: index)
-
- state.answer.append(guess)
-
+ case .guess(let id):
+ state.answer.append(state.wordBank.remove(id: id)!)
+ return .none
+ case .remove(let id):
+ state.wordBank.append(state.answer.remove(id: id)!)
return .none
}
}
diff --git a/BibleCore/Sources/Classroom/Lesson/Excercise/BuildByWord/BuildByWordView.swift b/BibleCore/Sources/Classroom/Lesson/Excercise/BuildByWord/BuildByWordView.swift
index 0f13f37..f2e55f8 100644
--- a/BibleCore/Sources/Classroom/Lesson/Excercise/BuildByWord/BuildByWordView.swift
+++ b/BibleCore/Sources/Classroom/Lesson/Excercise/BuildByWord/BuildByWordView.swift
@@ -11,28 +11,35 @@ public struct BuildByWordView: View {
self.store = store
}
+ @Namespace var namespace
+
+ @State var foo = false
+
public var body: some View {
WithViewStore(store, observe: { $0 }) { viewStore in
VStack(alignment: .leading, spacing: 32) {
Text("Classroom")
.font(.largeTitle)
- WrappingHStack {
- ForEach(viewStore.answer, id: \.self) { guess in
- Text(guess)
+ WrappingHStack(alignment: .leading) {
+ ForEach(viewStore.answer) { guess in
+ Button(guess.word) {
+ viewStore.send(.remove(id: guess.id))
+ }
+ .buttonStyle(.option)
+ .matchedGeometryEffect(id: guess.id, in: namespace, properties: .position)
}
}
Spacer()
WrappingHStack {
- ForEach(viewStore.wordBank.enumerated().map(\.offset), id: \.self) { index in
-
- Button(viewStore.wordBank[index]) {
- viewStore.send(.guess(index: index))
+ ForEach(viewStore.wordBank) { guess in
+ Button(guess.word) {
+ viewStore.send(.guess(id: guess.id), animation: .easeOut(duration: 0.3))
}
.buttonStyle(.option)
-
+ .matchedGeometryEffect(id: guess.id, in: namespace, isSource: true)
}
}
diff --git a/BibleCore/Sources/Classroom/Lesson/Excercise/Exercise.swift b/BibleCore/Sources/Classroom/Lesson/Excercise/Exercise.swift
index 1e6ead9..60e2d12 100644
--- a/BibleCore/Sources/Classroom/Lesson/Excercise/Exercise.swift
+++ b/BibleCore/Sources/Classroom/Lesson/Excercise/Exercise.swift
@@ -2,7 +2,26 @@ import BibleCore
import ComposableArchitecture
struct Exercise: Reducer {
- enum State: Equatable, Codable, Hashable {
+ enum State: Equatable, Codable, Hashable, ExerciseProtocol {
+ var isCorrect: Bool {
+ switch self {
+ case .buildByLetter(let state):
+ return state.isCorrect
+ case .buildByWord(let state):
+ return state.isCorrect
+ }
+ }
+
+ var score: Int {
+ switch self {
+ case .buildByLetter(let state):
+ return state.score
+ case .buildByWord(let state):
+ return state.score
+ }
+
+ }
+
case buildByWord(BuildByWord.State)
case buildByLetter(BuildByLetter.State)
}
diff --git a/BibleCore/Sources/Classroom/Lesson/Grade/GradeView.swift b/BibleCore/Sources/Classroom/Lesson/Grade/GradeView.swift
index 3f2d605..37b8da2 100644
--- a/BibleCore/Sources/Classroom/Lesson/Grade/GradeView.swift
+++ b/BibleCore/Sources/Classroom/Lesson/Grade/GradeView.swift
@@ -12,7 +12,7 @@ fileprivate extension View {
case .disabled:
self.buttonStyle(.disabled)
case .ready:
- self.buttonStyle(.unselected)
+ self.buttonStyle(.correct)
case .failed(_):
self.buttonStyle(.unselected)
case .partial(_):
@@ -68,7 +68,7 @@ struct GradeView: View {
.fontWeight(.bold)
.foreground(for: initialState)
.hidden(for: initialState)
- Button("Continue") {
+ Button("check") {
store.send(.next)
}
.buttonStyle(for: initialState)
diff --git a/BibleCore/Sources/Classroom/Lesson/Lesson.swift b/BibleCore/Sources/Classroom/Lesson/Lesson.swift
index 01c82cc..5ead1be 100644
--- a/BibleCore/Sources/Classroom/Lesson/Lesson.swift
+++ b/BibleCore/Sources/Classroom/Lesson/Lesson.swift
@@ -32,14 +32,28 @@ struct Lesson: Reducer {
Scope(state: \.grade, action: /Action.grade) {
Grade()
}
- Reduce { state, action in
+ Reduce { state, action in
switch action {
case .prepare:
-
state.exercise = .buildByWord(BuildByWord.State.init(verses: .mock))
-
+ return .none
+ case .excercise(.buildByWord(BuildByWord.Action.guess)), .excercise(.buildByWord(.remove)):
+ if case .buildByWord(let model) = state.exercise {
+ if model.answer.isEmpty {
+ state.grade = Grade.State.disabled
+ } else {
+ state.grade = Grade.State.ready
+ }
+ }
return .none
case .grade(.next):
+ // Test if we've actually completed the exercise
+
+
+
+
+
+
return .none
default:
return .none
diff --git a/BibleCore/Sources/Classroom/Lesson/LessonView.swift b/BibleCore/Sources/Classroom/Lesson/LessonView.swift
index 18e0fb1..3f6b365 100644
--- a/BibleCore/Sources/Classroom/Lesson/LessonView.swift
+++ b/BibleCore/Sources/Classroom/Lesson/LessonView.swift
@@ -1,3 +1,4 @@
+import BibleComponents
import ComposableArchitecture
import SwiftUI
@@ -11,16 +12,19 @@ struct LessonView: View {
var body: some View {
VStack {
- HStack {
+ HStack(spacing: 16) {
Button {
} label: {
Image(systemName: "xmark")
}
+ .controlSize(.large)
+ .foregroundColor(.black)
- ProgressBar()
+ ProgressBar(progress: 0)
}
+ .padding(.horizontal)
IfLetStore(
store.scope(state: \.exercise, action: Lesson.Action.excercise),
@@ -34,6 +38,7 @@ struct LessonView: View {
Spacer()
GradeView(store: store.scope(state: \.grade, action: Lesson.Action.grade))
+ .transaction { $0.animation = nil }
}
.onAppear {
store.send(.prepare)
diff --git a/BibleCore/Tests/ClassroomTests/ClassroomTests.swift b/BibleCore/Tests/ClassroomTests/ClassroomTests.swift
index 31f5cac..75a9340 100644
--- a/BibleCore/Tests/ClassroomTests/ClassroomTests.swift
+++ b/BibleCore/Tests/ClassroomTests/ClassroomTests.swift
@@ -15,6 +15,7 @@ final class ClassroomTests: XCTestCase {
BuildByWord()
} withDependencies: {
$0.withRandomNumberGenerator = WithRandomNumberGenerator(LCRNG(seed: 0))
+ $0.uuid = .incrementing
}
wordBank = ["In", "created", "the","heavens","the","God","and","the","earth.","beginning"]
@@ -23,7 +24,11 @@ final class ClassroomTests: XCTestCase {
await store.receive(.setup(.mock)) {
$0.verses = .mock
- $0.wordBank = self.wordBank
+ $0.wordBank = IdentifiedArray(
+ uniqueElements: self.wordBank.map { word in
+ BuildByWord.State.Guess(word: word, id: self.store.dependencies.uuid())
+ }
+ )
}
}
@@ -33,50 +38,55 @@ final class ClassroomTests: XCTestCase {
func testCorrectGuessing() async {
var copy = store.state.wordBank
- var correctGuessOrder = [Int]()
+ var correctOrder = [UUID]()
- store.state.correctAnswer.forEach {
- let index = copy.firstIndex(of: $0)!
+ store.state.correctAnswer.forEach { word in
+ let id = copy.first { guess in
+ guess.word == word
+ }?.id
- copy.remove(at: index)
+ copy.remove(id: id!)
- correctGuessOrder.append(index)
+ correctOrder.append(id!)
}
- var answer = [String]()
+ var answer = IdentifiedArrayOf()
- for guess in correctGuessOrder {
+ for id in correctOrder {
// Always fail until the last guess is made.
XCTAssertFalse(store.state.isCorrect)
- await store.send(.guess(index: guess)) {
- let word = $0.wordBank.remove(at: guess)
- answer.append(word)
+ await store.send(.guess(id: id)) {
+ let guess = $0.wordBank.remove(id: id)
+
+ answer.append(guess!)
+
$0.answer = answer
}
}
XCTAssertTrue(store.state.isCorrect)
}
-
- func testIncorrectGuessing() async {
- let incorrectGuesses = store.state.correctAnswer.map { _ in return 0 }
-
- var answer = [String]()
-
- for guess in incorrectGuesses {
- // Always fail until the last guess is made.
- XCTAssertFalse(store.state.isCorrect)
-
- await store.send(.guess(index: guess)) {
- let word = $0.wordBank.remove(at: guess)
- answer.append(word)
- $0.answer = answer
- }
- }
-
- XCTAssertFalse(store.state.isCorrect)
- }
+ // TODO: Re-implement
+//
+// func testIncorrectGuessing() async {
+// let incorrectGuesses = store.state.correctAnswer.map { _ in return 0 }
+//
+// var answer = IdentifiedArrayOf()
+//
+// for id in incorrectGuesses {
+// // Always fail until the last guess is made.
+// XCTAssertFalse(store.state.isCorrect)
+//
+// await store.send(.guess(id: id)) {
+// let word = $0.wordBank.remove(id: id)
+// answer.append(word)
+// $0.answer = answer
+// }
+// }
+//
+// XCTAssertFalse(store.state.isCorrect)
+// }
}
From 870adc87afa511a2810cd7352a74ff63e181068b Mon Sep 17 00:00:00 2001
From: Peter Larson
Date: Wed, 13 Sep 2023 11:31:24 -0500
Subject: [PATCH 06/11] Path refactor
---
BibleCore/Sources/AppFeature/App.swift | 11 +++++++----
BibleCore/Sources/AppFeature/AppView.swift | 19 +++++++++----------
2 files changed, 16 insertions(+), 14 deletions(-)
diff --git a/BibleCore/Sources/AppFeature/App.swift b/BibleCore/Sources/AppFeature/App.swift
index 98e7ccf..2c9748a 100644
--- a/BibleCore/Sources/AppFeature/App.swift
+++ b/BibleCore/Sources/AppFeature/App.swift
@@ -1,5 +1,6 @@
import ComposableArchitecture
import ReaderCore
+import Classroom
public struct AppReducer: Reducer {
@@ -8,15 +9,17 @@ public struct AppReducer: Reducer {
public enum State: Equatable {
- case reader(Reader.State = .init())
- case empty
+ case classroom(Classroom.State = .init())
}
public enum Action: Equatable {
- case reader(Reader.Action)
+ case classroom(Classroom.Action)
}
public var body: some ReducerOf {
+ Scope(state: /State.classroom, action: /Action.classroom) {
+ Classroom()
+ }
Reduce { state, action in .none }
}
}
@@ -66,7 +69,7 @@ public struct AppReducer: Reducer {
case .tabSelected(let tab):
if tab == .read {
- state.path.append(.empty)
+ state.path.append(.classroom())
}
return .none
diff --git a/BibleCore/Sources/AppFeature/AppView.swift b/BibleCore/Sources/AppFeature/AppView.swift
index 505aa0e..e965784 100644
--- a/BibleCore/Sources/AppFeature/AppView.swift
+++ b/BibleCore/Sources/AppFeature/AppView.swift
@@ -1,18 +1,19 @@
import ComposableArchitecture
+import Classroom
import ReaderCore
import SwiftUI
-struct AppView: View {
+public struct AppView: View {
let store: StoreOf
@ObservedObject var viewStore: ViewStoreOf
- init(store: StoreOf) {
+ public init(store: StoreOf) {
self.store = store
self.viewStore = ViewStore(store, observe: { $0 })
}
- var body: some View {
+ public var body: some View {
NavigationStackStore(
store.scope(state: \.path, action: AppReducer.Action.path)
) {
@@ -33,15 +34,13 @@ struct AppView: View {
}
} destination: { initialState in
switch initialState {
- case .empty:
- Text("hello")
- .navigationBarBackButtonHidden(true)
- case .reader:
+ case .classroom:
CaseLet(
- /AppReducer.Path.State.reader,
- action: AppReducer.Path.Action.reader,
- then: ReaderView.init(store:)
+ /AppReducer.Path.State.classroom,
+ action: AppReducer.Path.Action.classroom,
+ then: ClassroomView.init(store:)
)
+ .navigationBarBackButtonHidden(true)
}
}
From a39e41ca6a2584822c76ef189e19e621cb60538d Mon Sep 17 00:00:00 2001
From: Peter Larson
Date: Wed, 13 Sep 2023 11:31:51 -0500
Subject: [PATCH 07/11] Added Classroom to AppFeature dependencies
---
BibleCore/Package.swift | 1 +
1 file changed, 1 insertion(+)
diff --git a/BibleCore/Package.swift b/BibleCore/Package.swift
index 98a028b..da10c64 100644
--- a/BibleCore/Package.swift
+++ b/BibleCore/Package.swift
@@ -117,6 +117,7 @@ let package = Package(
name: "AppFeature",
dependencies: [
"ReaderCore",
+ "Classroom",
.product(name: "ComposableArchitecture", package: "swift-composable-architecture")
]
),
From 6d39cd059414a52a34a95dfdda426591b6ea671b Mon Sep 17 00:00:00 2001
From: Peter Larson
Date: Wed, 13 Sep 2023 11:33:10 -0500
Subject: [PATCH 08/11] Updated main App entry to AppView
---
Bible/BibleApp.swift | 8 +++++---
1 file changed, 5 insertions(+), 3 deletions(-)
diff --git a/Bible/BibleApp.swift b/Bible/BibleApp.swift
index d46bdc1..2105b06 100644
--- a/Bible/BibleApp.swift
+++ b/Bible/BibleApp.swift
@@ -19,6 +19,7 @@ final public class AppDelegate: NSObject, UIApplicationDelegate {
)
) {
AppReducer()
+ ._printChanges()
}
public func application(
@@ -32,6 +33,9 @@ final public class AppDelegate: NSObject, UIApplicationDelegate {
@main
struct BibleApp: App {
+ @UIApplicationDelegateAdaptor(AppDelegate.self) var delegate
+
+
var body: some Scene {
WindowGroup {
#if os(macOS)
@@ -39,9 +43,7 @@ struct BibleApp: App {
DesktopReader()
})
#elseif os(iOS)
- ReaderView(store: Store(initialState: Reader.State.init()) {
- Reader()
- })
+ AppView(store: delegate.store)
#else
fatalError("Unsupported OS")
#endif
From 486308700f83a625d6da2f7378bc3cd19072b802 Mon Sep 17 00:00:00 2001
From: Peter Larson
Date: Wed, 13 Sep 2023 11:33:42 -0500
Subject: [PATCH 09/11] Added public interface
---
BibleCore/Sources/Classroom/Classroom.swift | 17 +++-
.../Sources/Classroom/ClassroomView.swift | 91 ++++++++++---------
.../BuildByLetter/BuildByLetter.swift | 11 ++-
.../Classroom/Lesson/Excercise/Exercise.swift | 10 +-
.../Classroom/Lesson/Grade/Grade.swift | 10 +-
.../Sources/Classroom/Lesson/Lesson.swift | 12 ++-
6 files changed, 84 insertions(+), 67 deletions(-)
diff --git a/BibleCore/Sources/Classroom/Classroom.swift b/BibleCore/Sources/Classroom/Classroom.swift
index 6ddd57a..0a70775 100644
--- a/BibleCore/Sources/Classroom/Classroom.swift
+++ b/BibleCore/Sources/Classroom/Classroom.swift
@@ -3,16 +3,25 @@ import DirectoryCore
import Foundation
import UserDefaultsClient
-struct Classroom: Reducer {
- struct State: Equatable, Codable {
+public struct Classroom: Reducer {
+ public init () {}
+
+ public struct State: Equatable, Codable {
var lessons: IdentifiedArrayOf = []
var selected: Lesson.State.ID? = nil
var directory: Directory.State? = nil
@BindingState var isDirectoryOpen = false
+
+ public init(lessons: IdentifiedArrayOf = [], selected: Lesson.State.ID? = nil, directory: Directory.State? = nil, isDirectoryOpen: Bool = false) {
+ self.lessons = lessons
+ self.selected = selected
+ self.directory = directory
+ self.isDirectoryOpen = isDirectoryOpen
+ }
}
- enum Action: BindableAction, Equatable {
+ public enum Action: BindableAction, Equatable {
case task
case lesson(id: UUID, action: Lesson.Action)
case select(id: UUID)
@@ -23,7 +32,7 @@ struct Classroom: Reducer {
@Dependency(\.defaults) var defaults: UserDefaultsClient
- var body: some ReducerOf {
+ public var body: some ReducerOf {
BindingReducer()
Reduce { state, action in
switch action {
diff --git a/BibleCore/Sources/Classroom/ClassroomView.swift b/BibleCore/Sources/Classroom/ClassroomView.swift
index 2061f60..248a883 100644
--- a/BibleCore/Sources/Classroom/ClassroomView.swift
+++ b/BibleCore/Sources/Classroom/ClassroomView.swift
@@ -2,67 +2,68 @@ import ComposableArchitecture
import DirectoryCore
import SwiftUI
-struct ClassroomView: View {
+public struct ClassroomView: View {
let store: StoreOf
+
@ObservedObject var viewStore: ViewStoreOf
- init(store: StoreOf) {
+ public init(store: StoreOf) {
self.store = store
self.viewStore = ViewStoreOf(store, observe: { $0 })
}
- var body: some View {
- NavigationStack {
- List {
- ForEachStore(
- store.scope(
- state: \.lessons,
- action: Classroom.Action.lesson(id:action:)
- ),
- content: { store in
- Text("foo")
- }
- )
- }
- .navigationDestination(for: Lesson.State.self, destination: { lesson in
- Text("foo")
- })
- .listStyle(.inset)
- .navigationTitle("Classroom")
- .toolbar {
- ToolbarItem(placement: .navigationBarTrailing) {
- Button {
- store.send(.openDirectory)
- } label: {
- Text("Add")
- }
+ public var body: some View {
+ List {
+ ForEachStore(
+ store.scope(
+ state: \.lessons,
+ action: Classroom.Action.lesson(id:action:)
+ ),
+ content: { store in
+ Text("foo")
+ }
+ )
+ }
+ .navigationDestination(for: Lesson.State.self, destination: { lesson in
+ Text("foo")
+ })
+ .listStyle(.inset)
+ .navigationTitle("Classroom")
+ .toolbar {
+ ToolbarItem(placement: .navigationBarTrailing) {
+ Button {
+ store.send(.openDirectory)
+ } label: {
+ Text("Add")
}
}
- .popover(isPresented: viewStore.$isDirectoryOpen, content: {
- IfLetStore(
- store.scope(
- state: \.directory,
- action: Classroom.Action.directory
- ), then: { store in
- NavigationStack {
- DirectoryView(store: store)
- }
+ }
+ .popover(isPresented: viewStore.$isDirectoryOpen, content: {
+ IfLetStore(
+ store.scope(
+ state: \.directory,
+ action: Classroom.Action.directory
+ ), then: { store in
+ NavigationStack {
+ DirectoryView(store: store)
}
- ) {
- ProgressView()
}
- })
- }
+ ) {
+ ProgressView()
+ }
+ })
}
}
struct ClassroomView_Previews: PreviewProvider {
static var previews: some View {
- ClassroomView(
- store: Store(initialState: Classroom.State()) {
- Classroom()
- }
- )
+ NavigationStack {
+ ClassroomView(
+ store: Store(initialState: Classroom.State()) {
+ Classroom()
+ }
+ )
+ }
}
}
diff --git a/BibleCore/Sources/Classroom/Lesson/Excercise/BuildByLetter/BuildByLetter.swift b/BibleCore/Sources/Classroom/Lesson/Excercise/BuildByLetter/BuildByLetter.swift
index 3f2634c..937b28f 100644
--- a/BibleCore/Sources/Classroom/Lesson/Excercise/BuildByLetter/BuildByLetter.swift
+++ b/BibleCore/Sources/Classroom/Lesson/Excercise/BuildByLetter/BuildByLetter.swift
@@ -2,9 +2,10 @@ import ComposableArchitecture
import BibleCore
import BibleClient
-struct BuildByLetter: Reducer {
+public struct BuildByLetter: Reducer {
+ public init () {}
- struct State: Equatable, Codable, Hashable, ExerciseProtocol {
+ public struct State: Equatable, Codable, Hashable, ExerciseProtocol {
var isCorrect: Bool {
false
}
@@ -25,7 +26,7 @@ struct BuildByLetter: Reducer {
.map(String.init)
}
- init(
+ public init(
verses: [Verse],
answer: [String?]? = nil,
wordBank: [String] = []
@@ -36,13 +37,13 @@ struct BuildByLetter: Reducer {
}
}
- enum Action {
+ public enum Action {
case task
}
@Dependency(\.withRandomNumberGenerator) var withRandomNumberGenerator: WithRandomNumberGenerator
- var body: some ReducerOf {
+ public var body: some ReducerOf {
Reduce { state, action in
switch action {
case .task:
diff --git a/BibleCore/Sources/Classroom/Lesson/Excercise/Exercise.swift b/BibleCore/Sources/Classroom/Lesson/Excercise/Exercise.swift
index 60e2d12..909e6c0 100644
--- a/BibleCore/Sources/Classroom/Lesson/Excercise/Exercise.swift
+++ b/BibleCore/Sources/Classroom/Lesson/Excercise/Exercise.swift
@@ -1,8 +1,10 @@
import BibleCore
import ComposableArchitecture
-struct Exercise: Reducer {
- enum State: Equatable, Codable, Hashable, ExerciseProtocol {
+public struct Exercise: Reducer {
+ public init () {}
+
+ public enum State: Equatable, Codable, Hashable, ExerciseProtocol {
var isCorrect: Bool {
switch self {
case .buildByLetter(let state):
@@ -26,12 +28,12 @@ struct Exercise: Reducer {
case buildByLetter(BuildByLetter.State)
}
- enum Action: Equatable {
+ public enum Action: Equatable {
case buildByWord(BuildByWord.Action)
case buildByLetter(BuildByLetter.Action)
}
- var body: some ReducerOf {
+ public var body: some ReducerOf {
Scope(state: /State.buildByWord, action: /Action.buildByWord) {
BuildByWord()
}
diff --git a/BibleCore/Sources/Classroom/Lesson/Grade/Grade.swift b/BibleCore/Sources/Classroom/Lesson/Grade/Grade.swift
index a6a15fc..b04fca8 100644
--- a/BibleCore/Sources/Classroom/Lesson/Grade/Grade.swift
+++ b/BibleCore/Sources/Classroom/Lesson/Grade/Grade.swift
@@ -1,7 +1,9 @@
import ComposableArchitecture
-struct Grade: Reducer {
- enum State: Equatable, Codable, Hashable {
+public struct Grade: Reducer {
+ public init () {}
+
+ public enum State: Equatable, Codable, Hashable {
case disabled
case ready
case failed(String)
@@ -9,11 +11,11 @@ struct Grade: Reducer {
case correct
}
- enum Action: Equatable {
+ public enum Action: Equatable {
case next
}
- var body: some ReducerOf {
+ public var body: some ReducerOf {
Reduce { state, action in
switch action {
case .next:
diff --git a/BibleCore/Sources/Classroom/Lesson/Lesson.swift b/BibleCore/Sources/Classroom/Lesson/Lesson.swift
index 5ead1be..4e960b7 100644
--- a/BibleCore/Sources/Classroom/Lesson/Lesson.swift
+++ b/BibleCore/Sources/Classroom/Lesson/Lesson.swift
@@ -2,12 +2,14 @@ import BibleCore
import ComposableArchitecture
import Foundation
-struct Lesson: Reducer {
- struct State: Identifiable, Equatable, Codable, Hashable {
+public struct Lesson: Reducer {
+ public init () {}
+
+ public struct State: Identifiable, Equatable, Codable, Hashable {
var verses: [Verse]
var exercise: Exercise.State? = nil
var grade: Grade.State = .disabled
- var id: UUID = UUID()
+ public var id: UUID = UUID()
public init(
verses: [Verse],
@@ -22,13 +24,13 @@ struct Lesson: Reducer {
}
}
- enum Action: Equatable {
+ public enum Action: Equatable {
case prepare
case excercise(Exercise.Action)
case grade(Grade.Action)
}
- var body: some ReducerOf {
+ public var body: some ReducerOf {
Scope(state: \.grade, action: /Action.grade) {
Grade()
}
From 5c5267af20337d086ff3606145ae9ae6db9f1493 Mon Sep 17 00:00:00 2001
From: Peter Larson
Date: Fri, 15 Sep 2023 14:48:26 -0500
Subject: [PATCH 10/11] Added mock
---
BibleCore/Sources/BibleCore/Book.swift | 4 ++++
BibleCore/Sources/BibleCore/Verse.swift | 4 ++++
2 files changed, 8 insertions(+)
diff --git a/BibleCore/Sources/BibleCore/Book.swift b/BibleCore/Sources/BibleCore/Book.swift
index 211a499..ad7dc13 100644
--- a/BibleCore/Sources/BibleCore/Book.swift
+++ b/BibleCore/Sources/BibleCore/Book.swift
@@ -30,6 +30,10 @@ public extension Book {
static var leviticus: Self {
.init(id: 3, name: "Leviticus", testament: "ot")
}
+
+ static var john: Self {
+ .init(id: 43, name: "John", testament: "nt")
+ }
}
public extension Array where Element == Book {
diff --git a/BibleCore/Sources/BibleCore/Verse.swift b/BibleCore/Sources/BibleCore/Verse.swift
index 54b6df6..d2af7d3 100644
--- a/BibleCore/Sources/BibleCore/Verse.swift
+++ b/BibleCore/Sources/BibleCore/Verse.swift
@@ -28,6 +28,10 @@ public extension Verse {
static var mock: Self {
.init(id: 1, book: .genesis, chapterId: 1, verseId: 1, verse: "In the beginning God created the heavens and the earth.")
}
+
+ static var wept: Self {
+ .init(id: 2, book: .john, chapterId: 11, verseId: 35, verse: "Jesus wept.")
+ }
}
public extension Array where Element == Verse {
From 25d2c3c4979fc10175c6d78b5b4deec56f84a63a Mon Sep 17 00:00:00 2001
From: Peter Larson
Date: Fri, 15 Sep 2023 14:48:40 -0500
Subject: [PATCH 11/11] Added Navigation
---
BibleCore/Sources/Classroom/Classroom.swift | 29 +++-
.../Sources/Classroom/ClassroomView.swift | 81 +++++----
.../BuildByBabySteps/BuildByBabySteps.swift | 162 +++++++++---------
.../Classroom/Lesson/Grade/Grade.swift | 9 +-
.../Classroom/Lesson/Grade/GradeView.swift | 54 ++++--
.../Sources/Classroom/Lesson/Lesson.swift | 37 +++-
.../Sources/Classroom/Lesson/LessonView.swift | 14 +-
7 files changed, 239 insertions(+), 147 deletions(-)
diff --git a/BibleCore/Sources/Classroom/Classroom.swift b/BibleCore/Sources/Classroom/Classroom.swift
index 0a70775..07b353a 100644
--- a/BibleCore/Sources/Classroom/Classroom.swift
+++ b/BibleCore/Sources/Classroom/Classroom.swift
@@ -4,12 +4,29 @@ import Foundation
import UserDefaultsClient
public struct Classroom: Reducer {
+ public struct Path: Reducer {
+ public enum State: Equatable, Codable {
+ case lesson(Lesson.State)
+ }
+
+ public enum Action: Equatable {
+ case lesson(Lesson.Action)
+ }
+
+ public var body: some ReducerOf {
+ Scope(state: /State.lesson, action: /Action.lesson) {
+ Lesson()
+ }
+ }
+ }
+
public init () {}
public struct State: Equatable, Codable {
var lessons: IdentifiedArrayOf = []
var selected: Lesson.State.ID? = nil
var directory: Directory.State? = nil
+ var path = StackState()
@BindingState var isDirectoryOpen = false
@@ -28,6 +45,7 @@ public struct Classroom: Reducer {
case openDirectory
case directory(Directory.Action)
case binding(_ action: BindingAction)
+ case path(StackAction)
}
@Dependency(\.defaults) var defaults: UserDefaultsClient
@@ -40,6 +58,7 @@ public struct Classroom: Reducer {
return .none
case .select(id: let id):
state.selected = id
+ state.path.append(.lesson(state.lessons[id: id]!))
return .none
case .lesson:
return .none
@@ -70,13 +89,19 @@ public struct Classroom: Reducer {
return .none
case .binding:
return .none
+ case .path:
+ return .none
}
}
.ifLet(\.directory, action: /Action.directory) {
Directory()
}
- .forEach(\.lessons, action: /Action.lesson) {
- Lesson()
+ .forEach(\.path, action: /Action.path) {
+ Path()
}
+ // MARK: - pretty sure I don't need this
+// .forEach(\.lessons, action: /Action.lesson) {
+// Lesson()
+// }
}
}
diff --git a/BibleCore/Sources/Classroom/ClassroomView.swift b/BibleCore/Sources/Classroom/ClassroomView.swift
index 248a883..621f512 100644
--- a/BibleCore/Sources/Classroom/ClassroomView.swift
+++ b/BibleCore/Sources/Classroom/ClassroomView.swift
@@ -14,45 +14,56 @@ public struct ClassroomView: View {
}
public var body: some View {
- List {
- ForEachStore(
- store.scope(
- state: \.lessons,
- action: Classroom.Action.lesson(id:action:)
- ),
- content: { store in
- Text("foo")
+
+ NavigationStackStore(store.scope(state: \.path, action: Classroom.Action.path)) {
+ List {
+ ForEach(viewStore.lessons) { lesson in
+ Button {
+ viewStore.send(.select(id: lesson.id))
+ } label: {
+ Text(lesson.id.description)
+ }
}
- )
- }
- .navigationDestination(for: Lesson.State.self, destination: { lesson in
- Text("foo")
- })
- .listStyle(.inset)
- .navigationTitle("Classroom")
- .toolbar {
- ToolbarItem(placement: .navigationBarTrailing) {
- Button {
- store.send(.openDirectory)
- } label: {
- Text("Add")
+ }
+ .listStyle(.inset)
+ .navigationTitle("Classroom")
+ .toolbar {
+ ToolbarItem(placement: .navigationBarTrailing) {
+ Button {
+ store.send(.openDirectory)
+ } label: {
+ Text("Add")
+ }
}
}
- }
- .popover(isPresented: viewStore.$isDirectoryOpen, content: {
- IfLetStore(
- store.scope(
- state: \.directory,
- action: Classroom.Action.directory
- ), then: { store in
- NavigationStack {
- DirectoryView(store: store)
+ .popover(isPresented: viewStore.$isDirectoryOpen, content: {
+ IfLetStore(
+ store.scope(
+ state: \.directory,
+ action: Classroom.Action.directory
+ ), then: { store in
+ NavigationStack {
+ DirectoryView(store: store)
+ }
}
+ ) {
+ ProgressView()
}
- ) {
- ProgressView()
+ })
+ } destination: { store in
+ switch store {
+ case .lesson:
+ CaseLet(
+ /Classroom.Path.State.lesson,
+ action: Classroom.Path.Action.lesson,
+ then: LessonView.init(store:)
+ )
+ .navigationBarBackButtonHidden()
+ // case .message(let text):
+ // Text(text)
+
}
- })
+ }
}
}
@@ -60,7 +71,9 @@ struct ClassroomView_Previews: PreviewProvider {
static var previews: some View {
NavigationStack {
ClassroomView(
- store: Store(initialState: Classroom.State()) {
+ store: Store(initialState: Classroom.State(
+ lessons: [.init(verses: .mock)]
+ )) {
Classroom()
}
)
diff --git a/BibleCore/Sources/Classroom/Lesson/Excercise/BuildByBabySteps/BuildByBabySteps.swift b/BibleCore/Sources/Classroom/Lesson/Excercise/BuildByBabySteps/BuildByBabySteps.swift
index 3b071f6..5f6b274 100644
--- a/BibleCore/Sources/Classroom/Lesson/Excercise/BuildByBabySteps/BuildByBabySteps.swift
+++ b/BibleCore/Sources/Classroom/Lesson/Excercise/BuildByBabySteps/BuildByBabySteps.swift
@@ -1,82 +1,80 @@
-//import BibleCore
-//import ComposableArchitecture
-//import Foundation
-//
-//struct BuildByBabySteps: Reducer {
-// struct State: Equatable, Hashable, Codable {
-// var verses: [Verse]
-// var options: [String]? = nil
-// var currentPhrase: [String] = []
-//
-// init(verses: [Verse], options: [String]? = nil, currentPhrase: [String] = []) {
-//
-// precondition(verses.complete.count != currentPhrase.count)
-//
-// self.verses = verses
-// self.options = options
-// self.currentPhrase = currentPhrase
-// }
-// }
-//
-// enum Action: Equatable {
-// case setup([Verse], [String])
-// case guess(id: UUID)
-// }
-//
-// @Dependency(\.withRandomNumberGenerator) var withRandomNumberGenerator
-//
-// var body: some ReducerOf {
-// Reduce { state, action in
-// switch action {
-// case .guess(id: let id):
-// state.currentPhrase.append(state.word)
-//// state.currentPhrase.append(guess)
-//// state.currentPhrase.append(<#T##newElement: String##String#>)
-//
-// return .none
-// case .setup(let verses, let currentPhrases):
-//
-// state.verses = verses
-// state.currentPhrase = currentPhrases
-//
-// var options = [String]()
-// var copy = verses.complete
-//
-// // Correct option
-// options.append(copy.remove(at: currentPhrases.count))
-//
-// // Add 1-3 other options if available
-// repeat {
-// withRandomNumberGenerator {
-// copy.shuffle(using: &$0)
-// }
-//
-// if let element = copy.popLast() {
-// options.append(element)
-// }
-//
-// } while !copy.isEmpty && options.count < 4
-//
-// return .none
-// }
-//
-// }
-// }
-//}
-//
-//extension BuildByBabySteps.State: ExerciseProtocol {
-// var score: Int {
-// maxScore
-// }
-//
-// var isCorrect: Bool {
-//
-// return verses
-// .map(\.verse)
-// .joined(separator: " ")
-// .split(separator: " ")
-// .map(String.init)
-// .elementsEqual(currentPhrase)
-//
-// }
-//}
+import BibleCore
+import ComposableArchitecture
+import Foundation
+
+struct BuildByBabySteps: Reducer {
+ struct State: Equatable, Hashable, Codable {
+ var verses: [Verse]
+ var options: [String]? = nil
+ var currentPhrase: [String] = []
+
+ init(verses: [Verse], options: [String]? = nil, currentPhrase: [String] = []) {
+
+ precondition(verses.complete.count != currentPhrase.count)
+
+ self.verses = verses
+ self.options = options
+ self.currentPhrase = currentPhrase
+ }
+ }
+
+ enum Action: Equatable {
+ case setup([Verse], [String])
+ case guess(id: UUID)
+ }
+
+ @Dependency(\.withRandomNumberGenerator) var withRandomNumberGenerator
+
+ var body: some ReducerOf {
+ Reduce { state, action in
+ switch action {
+ case .guess(id: let id):
+ //state.currentPhrase.append(state.word)
+
+ return .none
+ case .setup(let verses, let currentPhrases):
+
+ state.verses = verses
+ state.currentPhrase = currentPhrases
+
+ var options = [String]()
+ var copy = verses.complete
+
+ // Correct option
+ options.append(copy.remove(at: currentPhrases.count))
+
+ // Add 1-3 other options if available
+ repeat {
+ withRandomNumberGenerator {
+ copy.shuffle(using: &$0)
+ }
+
+ if let element = copy.popLast() {
+ options.append(element)
+ }
+
+ } while !copy.isEmpty && options.count < 4
+
+ return .none
+ }
+
+ }
+ }
+}
+
+extension BuildByBabySteps.State: ExerciseProtocol {
+ var score: Int {
+ maxScore
+ }
+
+ var isCorrect: Bool {
+
+ return verses
+ .map(\.verse)
+ .joined(separator: " ")
+ .split(separator: " ")
+ .map(String.init)
+ .elementsEqual(currentPhrase)
+
+ }
+}
diff --git a/BibleCore/Sources/Classroom/Lesson/Grade/Grade.swift b/BibleCore/Sources/Classroom/Lesson/Grade/Grade.swift
index b04fca8..578c6e5 100644
--- a/BibleCore/Sources/Classroom/Lesson/Grade/Grade.swift
+++ b/BibleCore/Sources/Classroom/Lesson/Grade/Grade.swift
@@ -12,15 +12,10 @@ public struct Grade: Reducer {
}
public enum Action: Equatable {
- case next
+ case didPressButton
}
public var body: some ReducerOf {
- Reduce { state, action in
- switch action {
- case .next:
- return .none
- }
- }
+ Reduce { state, action in .none }
}
}
diff --git a/BibleCore/Sources/Classroom/Lesson/Grade/GradeView.swift b/BibleCore/Sources/Classroom/Lesson/Grade/GradeView.swift
index 37b8da2..a1cb247 100644
--- a/BibleCore/Sources/Classroom/Lesson/Grade/GradeView.swift
+++ b/BibleCore/Sources/Classroom/Lesson/Grade/GradeView.swift
@@ -55,6 +55,17 @@ fileprivate extension String {
default: return String()
}
}
+
+ static func text(for grade: Grade.State) -> String {
+ switch grade {
+ case .correct:
+ return "Next"
+ case .ready:
+ return "Check"
+ default:
+ return "Check"
+ }
+ }
}
struct GradeView: View {
@@ -68,18 +79,20 @@ struct GradeView: View {
.fontWeight(.bold)
.foreground(for: initialState)
.hidden(for: initialState)
- Button("check") {
- store.send(.next)
+ .transition(.move(edge: .bottom))
+ Button(String.text(for: initialState)) {
+ store.send(.didPressButton, animation: .easeOut(duration: 0.3))
}
.buttonStyle(for: initialState)
+ .transaction { $0.animation = nil }
}
.padding()
.background {
switch initialState {
case .correct:
Color.softGreen
- .transition(.move(edge: .bottom))
.edgesIgnoringSafeArea(.bottom)
+ .transition(.move(edge: .bottom))
default:
EmptyView()
}
@@ -90,17 +103,30 @@ struct GradeView: View {
struct GradeView_Previews: PreviewProvider {
static var previews: some View {
- ForEach(
- [Grade.State.correct, Grade.State.ready, Grade.State.disabled],
- id: \.self
- ) { grade in
- VStack {
- Spacer()
- GradeView(store: Store(initialState: grade) {
- Grade()
- ._printChanges()
- })
- }
+
+ VStack {
+ Spacer()
+ GradeView(store: Store(initialState: .correct) {
+ Reduce { state, action in
+
+// state = .disabled
+
+ return .none
+ }
+ })
+ }
+
+
+ VStack {
+ Spacer()
+ GradeView(store: Store(initialState: .ready) {
+ Reduce { state, action in
+
+ state = .correct
+
+ return .none
+ }
+ })
}
}
}
diff --git a/BibleCore/Sources/Classroom/Lesson/Lesson.swift b/BibleCore/Sources/Classroom/Lesson/Lesson.swift
index 4e960b7..45a4de1 100644
--- a/BibleCore/Sources/Classroom/Lesson/Lesson.swift
+++ b/BibleCore/Sources/Classroom/Lesson/Lesson.swift
@@ -9,6 +9,8 @@ public struct Lesson: Reducer {
var verses: [Verse]
var exercise: Exercise.State? = nil
var grade: Grade.State = .disabled
+ var score: Double = 0
+
public var id: UUID = UUID()
public init(
@@ -26,10 +28,13 @@ public struct Lesson: Reducer {
public enum Action: Equatable {
case prepare
+ case load(Exercise.State)
case excercise(Exercise.Action)
case grade(Grade.Action)
}
+ @Dependency(\.continuousClock) var clock
+
public var body: some ReducerOf {
Scope(state: \.grade, action: /Action.grade) {
Grade()
@@ -37,7 +42,17 @@ public struct Lesson: Reducer {
Reduce { state, action in
switch action {
case .prepare:
- state.exercise = .buildByWord(BuildByWord.State.init(verses: .mock))
+ return .run { [verses = state.verses] send in
+
+ try await clock.sleep(for: .seconds(1))
+
+ await send(.load(.buildByWord(BuildByWord.State.init(verses: verses))), animation: .easeIn(duration: 0.3))
+ }
+ case .load(let exercise):
+
+ state.score = .random(in: 0 ... 1)
+ state.exercise = exercise
+
return .none
case .excercise(.buildByWord(BuildByWord.Action.guess)), .excercise(.buildByWord(.remove)):
if case .buildByWord(let model) = state.exercise {
@@ -48,12 +63,24 @@ public struct Lesson: Reducer {
}
}
return .none
- case .grade(.next):
+ case .grade(.didPressButton):
// Test if we've actually completed the exercise
-
-
-
+ switch state.grade {
+ case .ready:
+ if state.exercise?.isCorrect ?? false {
+ state.grade = .correct
+ }
+ return .none
+ case .correct:
+ // Move to the next exercise.
+ state.exercise = nil
+ state.grade = .disabled
+
+ return .send(.prepare)
+ default:
+ break
+ }
return .none
diff --git a/BibleCore/Sources/Classroom/Lesson/LessonView.swift b/BibleCore/Sources/Classroom/Lesson/LessonView.swift
index 3f6b365..5426eb2 100644
--- a/BibleCore/Sources/Classroom/Lesson/LessonView.swift
+++ b/BibleCore/Sources/Classroom/Lesson/LessonView.swift
@@ -21,7 +21,9 @@ struct LessonView: View {
.controlSize(.large)
.foregroundColor(.black)
- ProgressBar(progress: 0)
+ WithViewStore(store, observe: \.score) {
+ ProgressBar(progress: $0.state)
+ }
}
.padding(.horizontal)
@@ -32,13 +34,19 @@ struct LessonView: View {
) {
ProgressView()
.progressViewStyle(.circular)
- .frame(maxHeight: .infinity)
+ .frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .center)
}
+ .transition(
+ .asymmetric(
+ insertion: .move(edge: .trailing),
+ removal: .move(edge: .leading)
+ )
+ .combined(with: .opacity)
+ )
Spacer()
GradeView(store: store.scope(state: \.grade, action: Lesson.Action.grade))
- .transaction { $0.animation = nil }
}
.onAppear {
store.send(.prepare)