Add some docstrings and clean some things up
This commit is contained in:
parent
d02ffb9ab4
commit
2695aab46e
@ -85,7 +85,6 @@
|
|||||||
C3BB763D2ED0C38800D56534 /* App */,
|
C3BB763D2ED0C38800D56534 /* App */,
|
||||||
C3BB764B2ED0C38900D56534 /* AppTests */,
|
C3BB764B2ED0C38900D56534 /* AppTests */,
|
||||||
C3BB76552ED0C38900D56534 /* AppUITests */,
|
C3BB76552ED0C38900D56534 /* AppUITests */,
|
||||||
C3C75C592ED2C672000FD10A /* Frameworks */,
|
|
||||||
C3BB763C2ED0C38800D56534 /* Products */,
|
C3BB763C2ED0C38800D56534 /* Products */,
|
||||||
);
|
);
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
@ -100,13 +99,6 @@
|
|||||||
name = Products;
|
name = Products;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
};
|
};
|
||||||
C3C75C592ED2C672000FD10A /* Frameworks */ = {
|
|
||||||
isa = PBXGroup;
|
|
||||||
children = (
|
|
||||||
);
|
|
||||||
name = Frameworks;
|
|
||||||
sourceTree = "<group>";
|
|
||||||
};
|
|
||||||
/* End PBXGroup section */
|
/* End PBXGroup section */
|
||||||
|
|
||||||
/* Begin PBXNativeTarget section */
|
/* Begin PBXNativeTarget section */
|
||||||
|
|||||||
@ -82,6 +82,13 @@
|
|||||||
ReferencedContainer = "container:Meow.xcodeproj">
|
ReferencedContainer = "container:Meow.xcodeproj">
|
||||||
</BuildableReference>
|
</BuildableReference>
|
||||||
</BuildableProductRunnable>
|
</BuildableProductRunnable>
|
||||||
|
<EnvironmentVariables>
|
||||||
|
<EnvironmentVariable
|
||||||
|
key = "OS_ACTIVITY_MODE"
|
||||||
|
value = "disable"
|
||||||
|
isEnabled = "YES">
|
||||||
|
</EnvironmentVariable>
|
||||||
|
</EnvironmentVariables>
|
||||||
</LaunchAction>
|
</LaunchAction>
|
||||||
<ProfileAction
|
<ProfileAction
|
||||||
buildConfiguration = "Release"
|
buildConfiguration = "Release"
|
||||||
|
|||||||
@ -8,6 +8,7 @@ import Core
|
|||||||
import CatFactsKit
|
import CatFactsKit
|
||||||
import ComposableArchitecture
|
import ComposableArchitecture
|
||||||
|
|
||||||
|
/// Handles the logic for fetching and displaying a `List` of cat facts from a remote API.
|
||||||
@Reducer
|
@Reducer
|
||||||
public struct FactsFeature {
|
public struct FactsFeature {
|
||||||
@ObservationIgnored
|
@ObservationIgnored
|
||||||
@ -18,7 +19,7 @@ public struct FactsFeature {
|
|||||||
public var baseURL: URL
|
public var baseURL: URL
|
||||||
public var mode: Mode = .notLoaded
|
public var mode: Mode = .notLoaded
|
||||||
|
|
||||||
public init(baseURL: URL = URL(string: "https://invalid.barf")!) {
|
public init(baseURL: URL = URL(string: "https://invalid.barf")!, mode: Mode = .notLoaded) {
|
||||||
self.baseURL = baseURL
|
self.baseURL = baseURL
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -32,17 +33,30 @@ public struct FactsFeature {
|
|||||||
|
|
||||||
public enum Action {
|
public enum Action {
|
||||||
case viewAppeared
|
case viewAppeared
|
||||||
|
case pulledToRefresh
|
||||||
|
case fetchNeeded
|
||||||
case receivedFacts([String])
|
case receivedFacts([String])
|
||||||
case receivedError(Error)
|
case receivedError(Error)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// MARK: - Public functionality
|
||||||
|
|
||||||
public init() { }
|
public init() { }
|
||||||
|
|
||||||
public var body: some ReducerOf<Self> {
|
public var body: some ReducerOf<Self> {
|
||||||
Reduce { state, action in
|
Reduce { state, action in
|
||||||
switch action {
|
switch action {
|
||||||
case .viewAppeared:
|
case .viewAppeared:
|
||||||
state.mode = .loading
|
switch state.mode {
|
||||||
|
case .loaded:
|
||||||
|
return .none
|
||||||
|
default:
|
||||||
|
state.mode = .loading
|
||||||
|
return .send(.fetchNeeded)
|
||||||
|
}
|
||||||
|
case .pulledToRefresh:
|
||||||
|
return .send(.fetchNeeded)
|
||||||
|
case .fetchNeeded:
|
||||||
let client = clientFactory.createClient(baseURL: state.baseURL)
|
let client = clientFactory.createClient(baseURL: state.baseURL)
|
||||||
return .run { send in
|
return .run { send in
|
||||||
do {
|
do {
|
||||||
|
|||||||
@ -7,6 +7,8 @@ import SwiftUI
|
|||||||
import Foundation
|
import Foundation
|
||||||
import ComposableArchitecture
|
import ComposableArchitecture
|
||||||
|
|
||||||
|
/// A view that displays either a progress spinner, a list of cat facts provided by an underlying feature reducer, or
|
||||||
|
/// an error message, depending on the state. Supports pull-to-refresh.
|
||||||
public struct FactsView: View {
|
public struct FactsView: View {
|
||||||
let store: StoreOf<FactsFeature>
|
let store: StoreOf<FactsFeature>
|
||||||
|
|
||||||
@ -29,6 +31,11 @@ public struct FactsView: View {
|
|||||||
Text(error.localizedDescription)
|
Text(error.localizedDescription)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
.refreshable {
|
||||||
|
// Tie the lifecycle of the PTR to the effect.
|
||||||
|
// https://github.com/pointfreeco/swift-composable-architecture/discussions/2542
|
||||||
|
await Task { await store.send(.pulledToRefresh).finish() }.value
|
||||||
|
}
|
||||||
.onAppear {
|
.onAppear {
|
||||||
store.send(.viewAppeared)
|
store.send(.viewAppeared)
|
||||||
}
|
}
|
||||||
@ -36,7 +43,7 @@ public struct FactsView: View {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#Preview {
|
#Preview {
|
||||||
FactsView(store: .init(initialState: FactsFeature.State()) {
|
FactsView(store: .init(initialState: FactsFeature.State(mode: .loaded(facts: ["One", "Two", "Three"]))) {
|
||||||
FactsFeature()
|
FactsFeature()
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@ -8,6 +8,9 @@ import Facts
|
|||||||
import CatFactsKit
|
import CatFactsKit
|
||||||
import ComposableArchitecture
|
import ComposableArchitecture
|
||||||
|
|
||||||
|
/// Handles interaction logic for the root of the application. Doesn't otherwise do much itself but it does respond to
|
||||||
|
/// a `viewAppeared` action so that it can show how one might change the state that drives _another_ feature (in this
|
||||||
|
/// example, `FactsFeature`).
|
||||||
@Reducer
|
@Reducer
|
||||||
public struct RootFeature {
|
public struct RootFeature {
|
||||||
@ObservableState
|
@ObservableState
|
||||||
@ -26,6 +29,11 @@ public struct RootFeature {
|
|||||||
public init() { }
|
public init() { }
|
||||||
|
|
||||||
public var body: some ReducerOf<Self> {
|
public var body: some ReducerOf<Self> {
|
||||||
|
// This fun DSL lets me provide a concrete Feature reducer when the Root feature is asked for a scoped store
|
||||||
|
Scope(state: \.facts, action: \.facts) {
|
||||||
|
FactsFeature()
|
||||||
|
}
|
||||||
|
// And then this is everything that's provided by the root.
|
||||||
Reduce { state, action in
|
Reduce { state, action in
|
||||||
switch action {
|
switch action {
|
||||||
case .viewAppeared:
|
case .viewAppeared:
|
||||||
@ -35,8 +43,5 @@ public struct RootFeature {
|
|||||||
return .none
|
return .none
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Scope(state: \.facts, action: \.facts) {
|
|
||||||
FactsFeature()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -7,6 +7,7 @@ import SwiftUI
|
|||||||
import Facts
|
import Facts
|
||||||
import ComposableArchitecture
|
import ComposableArchitecture
|
||||||
|
|
||||||
|
/// The root view of the entire app.
|
||||||
public struct RootView: View {
|
public struct RootView: View {
|
||||||
@Bindable var store: StoreOf<RootFeature>
|
@Bindable var store: StoreOf<RootFeature>
|
||||||
|
|
||||||
@ -16,6 +17,7 @@ public struct RootView: View {
|
|||||||
|
|
||||||
public var body: some View {
|
public var body: some View {
|
||||||
NavigationStack {
|
NavigationStack {
|
||||||
|
// Super basic here; I want to show a FactsView scoped to just that feature's `State` and `Action`s.
|
||||||
FactsView(store: store.scope(state: \.facts, action: \.facts))
|
FactsView(store: store.scope(state: \.facts, action: \.facts))
|
||||||
}.onAppear {
|
}.onAppear {
|
||||||
store.send(.viewAppeared)
|
store.send(.viewAppeared)
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user