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/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
diff --git a/BibleCore/Package.swift b/BibleCore/Package.swift
index 144aa67..da10c64 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: [
@@ -113,6 +117,7 @@ let package = Package(
name: "AppFeature",
dependencies: [
"ReaderCore",
+ "Classroom",
.product(name: "ComposableArchitecture", package: "swift-composable-architecture")
]
),
@@ -120,7 +125,9 @@ let package = Package(
name: "Classroom",
dependencies: [
"BibleCore",
+ "BibleComponents",
"BibleClient",
+ "DirectoryCore",
"UserDefaultsClient",
.product(name: "WrappingHStack", package: "WrappingHStack"),
.product(name: "ComposableArchitecture", package: "swift-composable-architecture")
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)
}
}
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..91fe3b8
--- /dev/null
+++ b/BibleCore/Sources/BibleComponents/ButtonStyle/UnselectedButtonStyle.swift
@@ -0,0 +1,38 @@
+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)
+ .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..14a9a3d
--- /dev/null
+++ b/BibleCore/Sources/BibleComponents/ProgressBar.swift
@@ -0,0 +1,36 @@
+import SwiftUI
+
+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 ProgressBar_Previews: PreviewProvider {
+ static var previews: some View {
+ ProgressBar(progress: 1.0)
+ .padding()
+ }
+}
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 43f13ad..d2af7d3 100644
--- a/BibleCore/Sources/BibleCore/Verse.swift
+++ b/BibleCore/Sources/BibleCore/Verse.swift
@@ -28,10 +28,21 @@ 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 {
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 bdaa7d6..07b353a 100644
--- a/BibleCore/Sources/Classroom/Classroom.swift
+++ b/BibleCore/Sources/Classroom/Classroom.swift
@@ -1,36 +1,107 @@
import ComposableArchitecture
+import DirectoryCore
import Foundation
import UserDefaultsClient
-struct Classroom: Reducer {
+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()
+ }
+ }
+ }
- struct State: Equatable, Codable {
+ 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
+
+ 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: Equatable {
+ public 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)
+ case path(StackAction)
}
@Dependency(\.defaults) var defaults: UserDefaultsClient
-
- var body: some ReducerOf {
+ public var body: some ReducerOf {
+ BindingReducer()
Reduce { state, action in
switch action {
- case .task
- return .run {
- defaults.get
- }
+ case .task:
+ return .none
case .select(id: let id):
state.selected = id
+ state.path.append(.lesson(state.lessons[id: 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 .directory:
+ return .none
+ case .binding:
+ return .none
+ case .path:
+ return .none
}
}
+ .ifLet(\.directory, action: /Action.directory) {
+ Directory()
+ }
+ .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 6093947..621f512 100644
--- a/BibleCore/Sources/Classroom/ClassroomView.swift
+++ b/BibleCore/Sources/Classroom/ClassroomView.swift
@@ -1,20 +1,82 @@
import ComposableArchitecture
+import DirectoryCore
import SwiftUI
-struct ClassroomView: View {
+public struct ClassroomView: View {
+
let store: StoreOf
- var body: some View {
- EmptyView()
+ @ObservedObject var viewStore: ViewStoreOf
+
+ public init(store: StoreOf) {
+ self.store = store
+ self.viewStore = ViewStoreOf(store, observe: { $0 })
+ }
+
+ public var body: some View {
+
+ 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)
+ }
+ }
+ }
+ .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()
+ }
+ })
+ } 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)
+
+ }
+ }
}
}
struct ClassroomView_Previews: PreviewProvider {
static var previews: some View {
- ClassroomView(
- store: Store(initialState: Classroom.State()) {
- Classroom()
- }
- )
+ NavigationStack {
+ ClassroomView(
+ 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
new file mode 100644
index 0000000..5f6b274
--- /dev/null
+++ b/BibleCore/Sources/Classroom/Lesson/Excercise/BuildByBabySteps/BuildByBabySteps.swift
@@ -0,0 +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)
+
+ 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..9f48601
--- /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/FITB/FillInTheBlank.swift b/BibleCore/Sources/Classroom/Lesson/Excercise/BuildByLetter/BuildByLetter.swift
similarity index 54%
rename from BibleCore/Sources/Classroom/Lesson/FITB/FillInTheBlank.swift
rename to BibleCore/Sources/Classroom/Lesson/Excercise/BuildByLetter/BuildByLetter.swift
index d78c4ba..937b28f 100644
--- a/BibleCore/Sources/Classroom/Lesson/FITB/FillInTheBlank.swift
+++ b/BibleCore/Sources/Classroom/Lesson/Excercise/BuildByLetter/BuildByLetter.swift
@@ -2,25 +2,21 @@ import ComposableArchitecture
import BibleCore
import BibleClient
-/*
- Types of learning modes
-
- FITB, fill in the blank
- - In the beginning
-
-
-
-
- */
-
-struct FillInTheBlank: Reducer {
- enum Difficulty: Equatable, Codable {
- case easy, medium, hard
- }
+public struct BuildByLetter: Reducer {
+ public init () {}
- struct State: Equatable, Codable {
- var difficulty: Difficulty
+ public struct State: Equatable, Codable, Hashable, ExerciseProtocol {
+ var isCorrect: Bool {
+ false
+ }
+
+ var score: Int {
+ 0
+ }
+
var verses: [Verse]
+ var answer: [String?]? = nil
+ var wordBank: [String] = []
var correctAnswer: [String] {
verses
@@ -29,21 +25,28 @@ struct FillInTheBlank: Reducer {
.split(separator: " ")
.map(String.init)
}
- var answer: [String?]
- var wordBank: [String]
+
+ public init(
+ verses: [Verse],
+ answer: [String?]? = nil,
+ wordBank: [String] = []
+ ) {
+ self.verses = verses
+ self.answer = answer
+ self.wordBank = wordBank
+ }
}
- 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:
-
state.wordBank = withRandomNumberGenerator {
state.correctAnswer.shuffled(using: &$0)
}
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/Piecemeal/Piecemeal.swift b/BibleCore/Sources/Classroom/Lesson/Excercise/BuildByWord/BuildByWord.swift
similarity index 65%
rename from BibleCore/Sources/Classroom/Lesson/Piecemeal/Piecemeal.swift
rename to BibleCore/Sources/Classroom/Lesson/Excercise/BuildByWord/BuildByWord.swift
index 2e42b1b..1c4a49e 100644
--- a/BibleCore/Sources/Classroom/Lesson/Piecemeal/Piecemeal.swift
+++ b/BibleCore/Sources/Classroom/Lesson/Excercise/BuildByWord/BuildByWord.swift
@@ -1,15 +1,27 @@
import ComposableArchitecture
import BibleCore
import BibleClient
+import Foundation
-public struct Piecemeal: Reducer {
+public struct BuildByWord: Reducer {
public init() {}
- public struct State: Equatable, Codable {
+ 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,10 +39,12 @@ public struct Piecemeal: Reducer {
return false
}
- return correctAnswer.elementsEqual(answer)
+ return correctAnswer.elementsEqual(answer.map(\.word))
}
- public struct ClassroomError: Equatable, Codable {
+ var score: Int { 0 }
+
+ public struct ClassroomError: Equatable, Codable, Hashable {
let title, message: String
init(title: String, message: String) {
@@ -44,12 +58,7 @@ public struct Piecemeal: 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,12 +70,12 @@ public struct Piecemeal: Reducer {
case task
case setup([Verse])
case failedSetup(error: State.ClassroomError)
- case guess(index: Int)
- case check
- case didComplete
+ 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 {
@@ -77,10 +86,9 @@ public struct Piecemeal: 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
@@ -103,9 +111,12 @@ public struct Piecemeal: 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
@@ -114,20 +125,11 @@ public struct Piecemeal: Reducer {
state.error = error
return .none
- case .guess(let index):
- let guess = state.wordBank.remove(at: index)
-
- state.answer.append(guess)
-
- return .none
- case .check:
- if state.isCorrect {
- return .send(.didComplete)
- }
-
+ case .guess(let id):
+ state.answer.append(state.wordBank.remove(id: id)!)
return .none
- case .didComplete:
- print("Hazzah!")
+ 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
new file mode 100644
index 0000000..f2e55f8
--- /dev/null
+++ b/BibleCore/Sources/Classroom/Lesson/Excercise/BuildByWord/BuildByWordView.swift
@@ -0,0 +1,67 @@
+import BibleComponents
+import ComposableArchitecture
+import SwiftUI
+
+import WrappingHStack
+
+public struct BuildByWordView: View {
+ public let store: StoreOf
+
+ public init(store: StoreOf) {
+ 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(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) { 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)
+ }
+ }
+
+ }
+ .task { viewStore.send(.task) }
+ .padding(.horizontal)
+ }
+ }
+}
+
+struct BuildByWordView_Previews: PreviewProvider {
+ static var previews: some View {
+ BuildByWordView(
+ store: Store(
+ initialState: BuildByWord.State(),
+ reducer: {
+ BuildByWord()
+ .dependency(\.bible, .testValue)
+ ._printChanges()
+ }
+ )
+ )
+ .previewDevice("iPhone 14")
+ }
+}
diff --git a/BibleCore/Sources/Classroom/Lesson/Excercise/Exercise.swift b/BibleCore/Sources/Classroom/Lesson/Excercise/Exercise.swift
new file mode 100644
index 0000000..909e6c0
--- /dev/null
+++ b/BibleCore/Sources/Classroom/Lesson/Excercise/Exercise.swift
@@ -0,0 +1,44 @@
+import BibleCore
+import ComposableArchitecture
+
+public struct Exercise: Reducer {
+ public init () {}
+
+ public 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)
+ }
+
+ public enum Action: Equatable {
+ case buildByWord(BuildByWord.Action)
+ case buildByLetter(BuildByLetter.Action)
+ }
+
+ public 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/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/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/Grade.swift b/BibleCore/Sources/Classroom/Lesson/Grade/Grade.swift
new file mode 100644
index 0000000..578c6e5
--- /dev/null
+++ b/BibleCore/Sources/Classroom/Lesson/Grade/Grade.swift
@@ -0,0 +1,21 @@
+import ComposableArchitecture
+
+public struct Grade: Reducer {
+ public init () {}
+
+ public enum State: Equatable, Codable, Hashable {
+ case disabled
+ case ready
+ case failed(String)
+ case partial(String)
+ case correct
+ }
+
+ public enum Action: Equatable {
+ case didPressButton
+ }
+
+ public var body: some ReducerOf {
+ Reduce { state, action in .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..a1cb247
--- /dev/null
+++ b/BibleCore/Sources/Classroom/Lesson/Grade/GradeView.swift
@@ -0,0 +1,132 @@
+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(.correct)
+ 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()
+ }
+ }
+
+ static func text(for grade: Grade.State) -> String {
+ switch grade {
+ case .correct:
+ return "Next"
+ case .ready:
+ return "Check"
+ default:
+ return "Check"
+ }
+ }
+}
+
+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)
+ .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
+ .edgesIgnoringSafeArea(.bottom)
+ .transition(.move(edge: .bottom))
+ default:
+ EmptyView()
+ }
+ }
+ }
+ }
+}
+
+struct GradeView_Previews: PreviewProvider {
+ static var previews: some View {
+
+ 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 4c15573..45a4de1 100644
--- a/BibleCore/Sources/Classroom/Lesson/Lesson.swift
+++ b/BibleCore/Sources/Classroom/Lesson/Lesson.swift
@@ -2,20 +2,88 @@ import BibleCore
import ComposableArchitecture
import Foundation
-struct Lesson: Reducer {
- struct State: Identifiable, Equatable, Codable {
+public struct Lesson: Reducer {
+ public init () {}
+
+ public struct State: Identifiable, Equatable, Codable, Hashable {
var verses: [Verse]
- var exercise: Exercise.State?
- var id: UUID
+ var exercise: Exercise.State? = nil
+ var grade: Grade.State = .disabled
+ var score: Double = 0
+
+ public var id: UUID = UUID()
+
+ public init(
+ verses: [Verse],
+ exercise: Exercise.State? = nil,
+ grade: Grade.State = .disabled,
+ id: UUID = UUID()
+ ) {
+ self.verses = verses
+ self.exercise = exercise
+ self.grade = grade
+ self.id = id
+ }
}
- enum Action: Equatable {
+ public enum Action: Equatable {
+ case prepare
+ case load(Exercise.State)
case excercise(Exercise.Action)
+ case grade(Grade.Action)
}
- var body: some ReducerOf {
- Reduce { state, action in
+ @Dependency(\.continuousClock) var clock
+
+ public var body: some ReducerOf {
+ Scope(state: \.grade, action: /Action.grade) {
+ Grade()
+ }
+ Reduce { state, action in
switch action {
+ case .prepare:
+ 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 {
+ if model.answer.isEmpty {
+ state.grade = Grade.State.disabled
+ } else {
+ state.grade = Grade.State.ready
+ }
+ }
+ return .none
+ 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
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..5426eb2
--- /dev/null
+++ b/BibleCore/Sources/Classroom/Lesson/LessonView.swift
@@ -0,0 +1,67 @@
+import BibleComponents
+import ComposableArchitecture
+import SwiftUI
+
+struct LessonView: View {
+
+ let store: StoreOf
+
+ init(store: StoreOf) {
+ self.store = store
+ }
+
+ var body: some View {
+ VStack {
+ HStack(spacing: 16) {
+ Button {
+
+ } label: {
+ Image(systemName: "xmark")
+ }
+ .controlSize(.large)
+ .foregroundColor(.black)
+
+ WithViewStore(store, observe: \.score) {
+ ProgressBar(progress: $0.state)
+ }
+
+ }
+ .padding(.horizontal)
+
+ IfLetStore(
+ store.scope(state: \.exercise, action: Lesson.Action.excercise),
+ then: ExerciseView.init(store:)
+ ) {
+ ProgressView()
+ .progressViewStyle(.circular)
+ .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))
+ }
+ .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/Sources/Classroom/Lesson/Piecemeal/PiecemealView.swift b/BibleCore/Sources/Classroom/Lesson/Piecemeal/PiecemealView.swift
deleted file mode 100644
index f04e917..0000000
--- a/BibleCore/Sources/Classroom/Lesson/Piecemeal/PiecemealView.swift
+++ /dev/null
@@ -1,86 +0,0 @@
-import ComposableArchitecture
-import SwiftUI
-
-import WrappingHStack
-
-public struct PiecemealView: View {
- public let store: StoreOf
-
- public init(store: StoreOf) {
- self.store = store
- }
-
- 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)
- }
- }
-
- 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)
- }
- }
-
- }
- }
- }
-
- 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) }
- .padding(.horizontal)
- }
- }
-}
-
-struct PiecemealView_Previews: PreviewProvider {
- static var previews: some View {
- PiecemealView(
- store: Store(
- initialState: Piecemeal.State(),
- reducer: {
- Piecemeal()
- .dependency(\.bible, .testValue)
- }
- )
- )
- .previewDevice("iPhone 14")
- }
-}
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
diff --git a/BibleCore/Tests/ClassroomTests/ClassroomTests.swift b/BibleCore/Tests/ClassroomTests/ClassroomTests.swift
index a878feb..75a9340 100644
--- a/BibleCore/Tests/ClassroomTests/ClassroomTests.swift
+++ b/BibleCore/Tests/ClassroomTests/ClassroomTests.swift
@@ -6,15 +6,16 @@ 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))
+ $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)
+// }
}