-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Implemented ComposedState and ComposedAction
- Loading branch information
Showing
12 changed files
with
365 additions
and
21 deletions.
There are no files selected for viewing
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,95 @@ | ||
{ | ||
"pins" : [ | ||
{ | ||
"identity" : "combine-schedulers", | ||
"kind" : "remoteSourceControl", | ||
"location" : "https://github.com/pointfreeco/combine-schedulers", | ||
"state" : { | ||
"revision" : "0625932976b3ae23949f6b816d13bd97f3b40b7c", | ||
"version" : "0.10.0" | ||
} | ||
}, | ||
{ | ||
"identity" : "swift-case-paths", | ||
"kind" : "remoteSourceControl", | ||
"location" : "https://github.com/pointfreeco/swift-case-paths", | ||
"state" : { | ||
"revision" : "fc45e7b2cfece9dd80b5a45e6469ffe67fe67984", | ||
"version" : "0.14.1" | ||
} | ||
}, | ||
{ | ||
"identity" : "swift-clocks", | ||
"kind" : "remoteSourceControl", | ||
"location" : "https://github.com/pointfreeco/swift-clocks", | ||
"state" : { | ||
"revision" : "f9acfa1a45f4483fe0f2c434a74e6f68f865d12d", | ||
"version" : "0.3.0" | ||
} | ||
}, | ||
{ | ||
"identity" : "swift-collections", | ||
"kind" : "remoteSourceControl", | ||
"location" : "https://github.com/apple/swift-collections", | ||
"state" : { | ||
"revision" : "937e904258d22af6e447a0b72c0bc67583ef64a2", | ||
"version" : "1.0.4" | ||
} | ||
}, | ||
{ | ||
"identity" : "swift-composable-architecture", | ||
"kind" : "remoteSourceControl", | ||
"location" : "https://github.com/pointfreeco/swift-composable-architecture.git", | ||
"state" : { | ||
"revision" : "89f80fe2400d21a853abc9556a060a2fa50eb2cb", | ||
"version" : "0.55.0" | ||
} | ||
}, | ||
{ | ||
"identity" : "swift-custom-dump", | ||
"kind" : "remoteSourceControl", | ||
"location" : "https://github.com/pointfreeco/swift-custom-dump", | ||
"state" : { | ||
"revision" : "3a35f7892e7cf6ba28a78cd46a703c0be4e0c6dc", | ||
"version" : "0.11.0" | ||
} | ||
}, | ||
{ | ||
"identity" : "swift-dependencies", | ||
"kind" : "remoteSourceControl", | ||
"location" : "https://github.com/pointfreeco/swift-dependencies", | ||
"state" : { | ||
"revision" : "de1a984a71e51f6e488e98ce3652035563eb8acb", | ||
"version" : "0.5.1" | ||
} | ||
}, | ||
{ | ||
"identity" : "swift-identified-collections", | ||
"kind" : "remoteSourceControl", | ||
"location" : "https://github.com/pointfreeco/swift-identified-collections", | ||
"state" : { | ||
"revision" : "d01446a78fb768adc9a78cbb6df07767c8ccfc29", | ||
"version" : "0.8.0" | ||
} | ||
}, | ||
{ | ||
"identity" : "swiftui-navigation", | ||
"kind" : "remoteSourceControl", | ||
"location" : "https://github.com/pointfreeco/swiftui-navigation", | ||
"state" : { | ||
"revision" : "2aa885e719087ee19df251c08a5980ad3e787f12", | ||
"version" : "0.8.0" | ||
} | ||
}, | ||
{ | ||
"identity" : "xctest-dynamic-overlay", | ||
"kind" : "remoteSourceControl", | ||
"location" : "https://github.com/pointfreeco/xctest-dynamic-overlay", | ||
"state" : { | ||
"revision" : "4af50b38daf0037cfbab15514a241224c3f62f98", | ||
"version" : "0.8.5" | ||
} | ||
} | ||
], | ||
"version" : 2 | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,3 +1,3 @@ | ||
# ComposableComposition | ||
![ComposableComposition Package Header](Docs/Header.png) | ||
|
||
A description of this package. | ||
ComposableComposition is a Swift package 📦 that extends the fantastic [Swift Composable Architecture (TCA)](https://github.com/pointfreeco/swift-composable-architecture) to provide additional capabilities for composing and handling state and actions. It introduces the concepts of child and parent states and actions, allowing for more granular control over state mutations and action handling, without having to duplicate (and sync) parent state to child features. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,24 @@ | ||
import ComposableArchitecture | ||
|
||
/// An enum representing an action that is either local to a specific reducer or is passed to a parent reducer for handling. | ||
public enum ComposedAction<Local, Parent> { | ||
/// An action for the local reducer. | ||
case local(Local) | ||
|
||
/// An action for the parent reducer. | ||
case parent(Parent) | ||
} | ||
|
||
extension ComposedAction: Equatable where Local: Equatable, Parent: Equatable {} | ||
extension ComposedAction: Hashable where Local: Hashable, Parent: Hashable {} | ||
extension ComposedAction: CaseIterable where Local: CaseIterable, Parent: CaseIterable { | ||
public static var allCases: [Self] { | ||
Local.allCases.map(Self.local) + Parent.allCases.map(Self.parent) | ||
} | ||
} | ||
extension ComposedAction: BindableAction where Local: BindableAction { | ||
public typealias State = Local.State | ||
public static func binding(_ action: BindingAction<State>) -> ComposedAction<Local, Parent> { | ||
.local(.binding(action)) | ||
} | ||
} |
40 changes: 40 additions & 0 deletions
40
Sources/ComposableComposition/Action/ComposedActionReducer.swift
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,40 @@ | ||
import ComposableArchitecture | ||
|
||
/// A protocol for a reducer that can handle actions that are local to a specific component or pass others to a parent component for handling. | ||
/// This protocol is useful in conjunction with `ComposedState`. | ||
public protocol ComposedActionReducer: ReducerProtocol where Action == ComposedAction<LocalAction, ParentAction> { | ||
associatedtype LocalAction | ||
associatedtype ParentAction | ||
|
||
/// A method to handle actions and produce an effect task. | ||
/// | ||
/// - Parameters: | ||
/// - state: A reference to the current state that can be modified. | ||
/// - action: The action to handle. | ||
/// - Returns: An effect task that encapsulates work to be done in response to the action. | ||
func reduce(into state: inout State, action: LocalAction) -> EffectTask<Action> | ||
} | ||
|
||
public extension ComposedActionReducer { | ||
/// A function to compose multiple reducers together. Used in the body of a reducer, for example, `Reducer(core)`. | ||
/// | ||
/// - Parameters: | ||
/// - state: A reference to the current state that can be modified. | ||
/// - action: The action to handle. | ||
/// - Returns: An effect task that encapsulates work to be done in response to the action. | ||
func core(into state: inout State, action: Action) -> EffectTask<Action> { | ||
switch action { | ||
case .local(let local): | ||
return self.reduce(into: &state, action: local) | ||
case .parent: | ||
return .none | ||
} | ||
} | ||
} | ||
|
||
/// A default implementation of `reduce(into:action:)` for reducers that don't have a `body`. | ||
public extension ComposedActionReducer where Body == Never { | ||
func reduce(into state: inout State, action: Action) -> EffectTask<Action> { | ||
core(into: &state, action: action) | ||
} | ||
} |
25 changes: 25 additions & 0 deletions
25
Sources/ComposableComposition/Action/EffectTask+LocalAction.swift
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,25 @@ | ||
import ComposableArchitecture | ||
|
||
// This is really an extension to EffectTask, which is a typealias to EffectPublisher | ||
public extension EffectPublisher where Failure == Never { | ||
/// Transforms the actions of the task using the provided transform function. | ||
/// The transformed actions are wrapped in a `ComposedAction` as local actions. | ||
/// | ||
/// - Parameter transform: A function that transforms the actions of the publisher into local actions. | ||
/// - Returns: An `EffectPublisher` that publishes composed actions with the transformed local actions. | ||
func map<LocalAction, ParentAction>(_ transform: @escaping (Action) -> LocalAction) -> EffectPublisher<ComposedAction<LocalAction, ParentAction>, Failure> { | ||
return self.map { (action: Action) -> ComposedAction<LocalAction, ParentAction> in | ||
let local = transform(action) | ||
return ComposedAction<LocalAction, ParentAction>.local(local) | ||
} | ||
} | ||
|
||
/// Creates an `EffectPublisher` that sends a local action wrapped in a `ComposedAction`. | ||
/// | ||
/// - Parameter localAction: The local action to send. | ||
/// - Returns: An `EffectPublisher` that sends the composed action. | ||
static func send<Local, Parent>(_ localAction: Local) -> Self where Action == ComposedAction<Local, Parent> { | ||
Self.send(ComposedAction.local(localAction)) | ||
} | ||
} | ||
|
70 changes: 70 additions & 0 deletions
70
Sources/ComposableComposition/Action/ViewStore+LocalAction.swift
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,70 @@ | ||
import ComposableArchitecture | ||
import SwiftUI | ||
|
||
/// Helpers to send LocalAction | ||
public extension ViewStore { | ||
/// Sends a local action wrapped in a `ComposedAction`. | ||
/// | ||
/// - Parameter localAction: The local action to send. | ||
/// - Returns: The task of the view store. | ||
@discardableResult | ||
func send<LocalAction, ParentAction>( | ||
_ localAction: LocalAction | ||
) -> ViewStoreTask where ViewAction == ComposedAction<LocalAction, ParentAction> { | ||
send(.local(localAction)) | ||
} | ||
|
||
/// Sends a local action wrapped in a `ComposedAction` with an animation. | ||
/// | ||
/// - Parameters: | ||
/// - localAction: The local action to send. | ||
/// - animation: The animation to apply. | ||
/// - Returns: The task of the view store. | ||
@discardableResult | ||
func send<LocalAction, ParentAction>( | ||
_ localAction: LocalAction, | ||
animation: Animation? | ||
) -> ViewStoreTask where ViewAction == ComposedAction<LocalAction, ParentAction> { | ||
send(.local(localAction), animation: animation) | ||
} | ||
|
||
/// Sends a local action wrapped in a `ComposedAction` with a transaction. | ||
/// | ||
/// - Parameters: | ||
/// - localAction: The local action to send. | ||
/// - transaction: The transaction to apply. | ||
/// - Returns: The task of the view store. | ||
@discardableResult | ||
func send<LocalAction, ParentAction>( | ||
_ localAction: LocalAction, | ||
transaction: Transaction | ||
) -> ViewStoreTask where ViewAction == ComposedAction<LocalAction, ParentAction> { | ||
send(.local(localAction), transaction: transaction) | ||
} | ||
|
||
/// Creates a binding from a state value to a local action. | ||
/// | ||
/// - Parameters: | ||
/// - get: A closure that gets a value from the state. | ||
/// - valueToLocalAction: A closure that transforms the value to a local action. | ||
/// - Returns: A binding from the state value to the local action. | ||
func binding<Value, LocalAction, ParentAction>( | ||
get: @escaping (ViewState) -> Value, | ||
send valueToLocalAction: @escaping (Value) -> LocalAction | ||
) -> Binding<Value> where ViewAction == ComposedAction<LocalAction, ParentAction> { | ||
self.binding(get: get, send: { .local(valueToLocalAction($0)) }) | ||
} | ||
|
||
/// Creates a binding from a state value to a local action. | ||
/// | ||
/// - Parameters: | ||
/// - get: A closure that gets a value from the state. | ||
/// - localAction: The local action to bind to the state value. | ||
/// - Returns: A binding from the state value to the local action. | ||
func binding<Value, LocalAction, ParentAction>( | ||
get: @escaping (ViewState) -> Value, | ||
send localAction: LocalAction | ||
) -> Binding<Value> where ViewAction == ComposedAction<LocalAction, ParentAction> { | ||
self.binding(get: get, send: .local(localAction)) | ||
} | ||
} |
This file was deleted.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,39 @@ | ||
/// A `ComposedState` is a type that composes the state of two other types, allowing access to the properties of both. | ||
/// It allows read-only access to the parent state and read-write access to the child state. | ||
@dynamicMemberLookup | ||
public struct ComposedState<Child, Parent> { | ||
/// The state that can be read from and written to. | ||
public var child: Child | ||
|
||
/// The state that can only be read from. | ||
public let parent: Parent | ||
|
||
/// Accesses the value of the parent state using the given key path. | ||
public subscript<T>(dynamicMember keyPath: KeyPath<Parent, T>) -> T { | ||
parent[keyPath: keyPath] | ||
} | ||
|
||
/// Accesses the value of the child state using the given key path. | ||
public subscript<T>(dynamicMember keyPath: KeyPath<Child, T>) -> T { | ||
child[keyPath: keyPath] | ||
} | ||
|
||
/// Accesses and modifies the value of the child state using the given key path. | ||
public subscript<T>(dynamicMember keyPath: WritableKeyPath<Child, T>) -> T { | ||
get { child[keyPath: keyPath] } | ||
set { child[keyPath: keyPath] = newValue } | ||
} | ||
|
||
/// Initializes a new instance of `ComposedState` with the given child and parent states. | ||
/// | ||
/// - Parameters: | ||
/// - child: The child state that can be read from and written to. | ||
/// - parent: The parent state that can only be read from. | ||
public init(child: Child, parent: Parent) { | ||
self.child = child | ||
self.parent = parent | ||
} | ||
} | ||
|
||
extension ComposedState: Equatable where Child: Equatable, Parent: Equatable {} | ||
extension ComposedState: Hashable where Child: Hashable, Parent: Hashable {} |
11 changes: 0 additions & 11 deletions
11
Tests/ComposableCompositionTests/ComposableCompositionTests.swift
This file was deleted.
Oops, something went wrong.
Oops, something went wrong.