From 3029263c10efbac8a7fee432f3f61c28c2d2d348 Mon Sep 17 00:00:00 2001 From: Stephen Celis Date: Mon, 6 Mar 2023 13:46:20 -0800 Subject: [PATCH 01/10] Add 'Effect.publisher' for bridging effects from Combine (#1958) We are looking to deprecate TCA's dependence on Combine, _e.g._ the direct conformance of `Effect` to `Publisher`, as well as many Combine-centric APIs, but that doesn't mean we want to eliminate support for using Combine with TCA. Our current advice given by our soft deprecations is to iterate over `publisher.values`, but this is unavailable in iOS 13 and 14, so let's introduce a dedicated bridging mechanism, instead. --- .../Documentation.docc/Extensions/Effect.md | 4 ++ .../Effects/Publisher.swift | 53 ++++++++++++------- 2 files changed, 37 insertions(+), 20 deletions(-) diff --git a/Sources/ComposableArchitecture/Documentation.docc/Extensions/Effect.md b/Sources/ComposableArchitecture/Documentation.docc/Extensions/Effect.md index 38a739ea3afa..e33355400f05 100644 --- a/Sources/ComposableArchitecture/Documentation.docc/Extensions/Effect.md +++ b/Sources/ComposableArchitecture/Documentation.docc/Extensions/Effect.md @@ -28,6 +28,10 @@ - ``EffectPublisher/unimplemented(_:)`` +### Combine integration + +- ``EffectPublisher/publisher(_:)`` + ### SwiftUI integration - ``EffectPublisher/animation(_:)`` diff --git a/Sources/ComposableArchitecture/Effects/Publisher.swift b/Sources/ComposableArchitecture/Effects/Publisher.swift index 6bbfc190ba45..ea8a03cfcda0 100644 --- a/Sources/ComposableArchitecture/Effects/Publisher.swift +++ b/Sources/ComposableArchitecture/Effects/Publisher.swift @@ -1,5 +1,18 @@ import Combine +extension EffectPublisher where Failure == Never { + /// Creates an effect from a Combine publisher. + /// + /// - Parameter createPublisher: The closure to execute when the effect is performed. + /// - Returns: An effect wrapping a Combine publisher. + public static func publisher(_ createPublisher: @escaping () -> P) -> Self + where P.Output == Action, P.Failure == Never { + Self( + operation: .publisher(Deferred(createPublisher: createPublisher).eraseToAnyPublisher()) + ) + } +} + @available(iOS, deprecated: 9999.0) @available(macOS, deprecated: 9999.0) @available(tvOS, deprecated: 9999.0) @@ -79,19 +92,19 @@ extension EffectPublisher { /// - Parameter publisher: A publisher. @available( iOS, deprecated: 9999.0, - message: "Iterate over 'Publisher.values' in an 'EffectTask.run', instead." + message: "Iterate over 'Publisher.values' in an 'EffectTask.run', instead, or use 'EffectTask.publisher'." ) @available( macOS, deprecated: 9999.0, - message: "Iterate over 'Publisher.values' in an 'EffectTask.run', instead." + message: "Iterate over 'Publisher.values' in an 'EffectTask.run', instead, or use 'EffectTask.publisher'." ) @available( tvOS, deprecated: 9999.0, - message: "Iterate over 'Publisher.values' in an 'EffectTask.run', instead." + message: "Iterate over 'Publisher.values' in an 'EffectTask.run', instead, or use 'EffectTask.publisher'." ) @available( watchOS, deprecated: 9999.0, - message: "Iterate over 'Publisher.values' in an 'EffectTask.run', instead." + message: "Iterate over 'Publisher.values' in an 'EffectTask.run', instead, or use 'EffectTask.publisher'." ) public init(_ publisher: P) where P.Output == Output, P.Failure == Failure { self.operation = .publisher(publisher.eraseToAnyPublisher()) @@ -367,19 +380,19 @@ extension Publisher { /// - Returns: An effect that wraps `self`. @available( iOS, deprecated: 9999.0, - message: "Iterate over 'Publisher.values' in an 'EffectTask.run', instead." + message: "Iterate over 'Publisher.values' in an 'EffectTask.run', instead, or use 'EffectTask.publisher'." ) @available( macOS, deprecated: 9999.0, - message: "Iterate over 'Publisher.values' in an 'EffectTask.run', instead." + message: "Iterate over 'Publisher.values' in an 'EffectTask.run', instead, or use 'EffectTask.publisher'." ) @available( tvOS, deprecated: 9999.0, - message: "Iterate over 'Publisher.values' in an 'EffectTask.run', instead." + message: "Iterate over 'Publisher.values' in an 'EffectTask.run', instead, or use 'EffectTask.publisher'." ) @available( watchOS, deprecated: 9999.0, - message: "Iterate over 'Publisher.values' in an 'EffectTask.run', instead." + message: "Iterate over 'Publisher.values' in an 'EffectTask.run', instead, or use 'EffectTask.publisher'." ) public func eraseToEffect() -> EffectPublisher { EffectPublisher(self) @@ -402,19 +415,19 @@ extension Publisher { /// - Returns: An effect that wraps `self` after mapping `Output` values. @available( iOS, deprecated: 9999.0, - message: "Iterate over 'Publisher.values' in an 'EffectTask.run', instead." + message: "Iterate over 'Publisher.values' in an 'EffectTask.run', instead, or use 'EffectTask.publisher'." ) @available( macOS, deprecated: 9999.0, - message: "Iterate over 'Publisher.values' in an 'EffectTask.run', instead." + message: "Iterate over 'Publisher.values' in an 'EffectTask.run', instead, or use 'EffectTask.publisher'." ) @available( tvOS, deprecated: 9999.0, - message: "Iterate over 'Publisher.values' in an 'EffectTask.run', instead." + message: "Iterate over 'Publisher.values' in an 'EffectTask.run', instead, or use 'EffectTask.publisher'." ) @available( watchOS, deprecated: 9999.0, - message: "Iterate over 'Publisher.values' in an 'EffectTask.run', instead." + message: "Iterate over 'Publisher.values' in an 'EffectTask.run', instead, or use 'EffectTask.publisher'." ) public func eraseToEffect( _ transform: @escaping (Output) -> T @@ -447,19 +460,19 @@ extension Publisher { /// - Returns: An effect that wraps `self`. @available( iOS, deprecated: 9999.0, - message: "Iterate over 'Publisher.values' in an 'EffectTask.run', instead." + message: "Iterate over 'Publisher.values' in an 'EffectTask.run', instead, or use 'EffectTask.publisher'." ) @available( macOS, deprecated: 9999.0, - message: "Iterate over 'Publisher.values' in an 'EffectTask.run', instead." + message: "Iterate over 'Publisher.values' in an 'EffectTask.run', instead, or use 'EffectTask.publisher'." ) @available( tvOS, deprecated: 9999.0, - message: "Iterate over 'Publisher.values' in an 'EffectTask.run', instead." + message: "Iterate over 'Publisher.values' in an 'EffectTask.run', instead, or use 'EffectTask.publisher'." ) @available( watchOS, deprecated: 9999.0, - message: "Iterate over 'Publisher.values' in an 'EffectTask.run', instead." + message: "Iterate over 'Publisher.values' in an 'EffectTask.run', instead, or use 'EffectTask.publisher'." ) public func catchToEffect() -> EffectTask> { self.catchToEffect { $0 } @@ -482,19 +495,19 @@ extension Publisher { /// - Returns: An effect that wraps `self`. @available( iOS, deprecated: 9999.0, - message: "Iterate over 'Publisher.values' in an 'EffectTask.run', instead." + message: "Iterate over 'Publisher.values' in an 'EffectTask.run', instead, or use 'EffectTask.publisher'." ) @available( macOS, deprecated: 9999.0, - message: "Iterate over 'Publisher.values' in an 'EffectTask.run', instead." + message: "Iterate over 'Publisher.values' in an 'EffectTask.run', instead, or use 'EffectTask.publisher'." ) @available( tvOS, deprecated: 9999.0, - message: "Iterate over 'Publisher.values' in an 'EffectTask.run', instead." + message: "Iterate over 'Publisher.values' in an 'EffectTask.run', instead, or use 'EffectTask.publisher'." ) @available( watchOS, deprecated: 9999.0, - message: "Iterate over 'Publisher.values' in an 'EffectTask.run', instead." + message: "Iterate over 'Publisher.values' in an 'EffectTask.run', instead, or use 'EffectTask.publisher'." ) public func catchToEffect( _ transform: @escaping (Result) -> T From 2c0caf6fc382590c0a55168e79cd2062ca2f3427 Mon Sep 17 00:00:00 2001 From: stephencelis Date: Mon, 6 Mar 2023 22:27:35 +0000 Subject: [PATCH 02/10] Run swift-format --- .../Effects/Publisher.swift | 62 ++++++++++++------- 1 file changed, 41 insertions(+), 21 deletions(-) diff --git a/Sources/ComposableArchitecture/Effects/Publisher.swift b/Sources/ComposableArchitecture/Effects/Publisher.swift index ea8a03cfcda0..564d130a0a60 100644 --- a/Sources/ComposableArchitecture/Effects/Publisher.swift +++ b/Sources/ComposableArchitecture/Effects/Publisher.swift @@ -2,7 +2,7 @@ import Combine extension EffectPublisher where Failure == Never { /// Creates an effect from a Combine publisher. - /// + /// /// - Parameter createPublisher: The closure to execute when the effect is performed. /// - Returns: An effect wrapping a Combine publisher. public static func publisher(_ createPublisher: @escaping () -> P) -> Self @@ -92,19 +92,23 @@ extension EffectPublisher { /// - Parameter publisher: A publisher. @available( iOS, deprecated: 9999.0, - message: "Iterate over 'Publisher.values' in an 'EffectTask.run', instead, or use 'EffectTask.publisher'." + message: + "Iterate over 'Publisher.values' in an 'EffectTask.run', instead, or use 'EffectTask.publisher'." ) @available( macOS, deprecated: 9999.0, - message: "Iterate over 'Publisher.values' in an 'EffectTask.run', instead, or use 'EffectTask.publisher'." + message: + "Iterate over 'Publisher.values' in an 'EffectTask.run', instead, or use 'EffectTask.publisher'." ) @available( tvOS, deprecated: 9999.0, - message: "Iterate over 'Publisher.values' in an 'EffectTask.run', instead, or use 'EffectTask.publisher'." + message: + "Iterate over 'Publisher.values' in an 'EffectTask.run', instead, or use 'EffectTask.publisher'." ) @available( watchOS, deprecated: 9999.0, - message: "Iterate over 'Publisher.values' in an 'EffectTask.run', instead, or use 'EffectTask.publisher'." + message: + "Iterate over 'Publisher.values' in an 'EffectTask.run', instead, or use 'EffectTask.publisher'." ) public init(_ publisher: P) where P.Output == Output, P.Failure == Failure { self.operation = .publisher(publisher.eraseToAnyPublisher()) @@ -380,19 +384,23 @@ extension Publisher { /// - Returns: An effect that wraps `self`. @available( iOS, deprecated: 9999.0, - message: "Iterate over 'Publisher.values' in an 'EffectTask.run', instead, or use 'EffectTask.publisher'." + message: + "Iterate over 'Publisher.values' in an 'EffectTask.run', instead, or use 'EffectTask.publisher'." ) @available( macOS, deprecated: 9999.0, - message: "Iterate over 'Publisher.values' in an 'EffectTask.run', instead, or use 'EffectTask.publisher'." + message: + "Iterate over 'Publisher.values' in an 'EffectTask.run', instead, or use 'EffectTask.publisher'." ) @available( tvOS, deprecated: 9999.0, - message: "Iterate over 'Publisher.values' in an 'EffectTask.run', instead, or use 'EffectTask.publisher'." + message: + "Iterate over 'Publisher.values' in an 'EffectTask.run', instead, or use 'EffectTask.publisher'." ) @available( watchOS, deprecated: 9999.0, - message: "Iterate over 'Publisher.values' in an 'EffectTask.run', instead, or use 'EffectTask.publisher'." + message: + "Iterate over 'Publisher.values' in an 'EffectTask.run', instead, or use 'EffectTask.publisher'." ) public func eraseToEffect() -> EffectPublisher { EffectPublisher(self) @@ -415,19 +423,23 @@ extension Publisher { /// - Returns: An effect that wraps `self` after mapping `Output` values. @available( iOS, deprecated: 9999.0, - message: "Iterate over 'Publisher.values' in an 'EffectTask.run', instead, or use 'EffectTask.publisher'." + message: + "Iterate over 'Publisher.values' in an 'EffectTask.run', instead, or use 'EffectTask.publisher'." ) @available( macOS, deprecated: 9999.0, - message: "Iterate over 'Publisher.values' in an 'EffectTask.run', instead, or use 'EffectTask.publisher'." + message: + "Iterate over 'Publisher.values' in an 'EffectTask.run', instead, or use 'EffectTask.publisher'." ) @available( tvOS, deprecated: 9999.0, - message: "Iterate over 'Publisher.values' in an 'EffectTask.run', instead, or use 'EffectTask.publisher'." + message: + "Iterate over 'Publisher.values' in an 'EffectTask.run', instead, or use 'EffectTask.publisher'." ) @available( watchOS, deprecated: 9999.0, - message: "Iterate over 'Publisher.values' in an 'EffectTask.run', instead, or use 'EffectTask.publisher'." + message: + "Iterate over 'Publisher.values' in an 'EffectTask.run', instead, or use 'EffectTask.publisher'." ) public func eraseToEffect( _ transform: @escaping (Output) -> T @@ -460,19 +472,23 @@ extension Publisher { /// - Returns: An effect that wraps `self`. @available( iOS, deprecated: 9999.0, - message: "Iterate over 'Publisher.values' in an 'EffectTask.run', instead, or use 'EffectTask.publisher'." + message: + "Iterate over 'Publisher.values' in an 'EffectTask.run', instead, or use 'EffectTask.publisher'." ) @available( macOS, deprecated: 9999.0, - message: "Iterate over 'Publisher.values' in an 'EffectTask.run', instead, or use 'EffectTask.publisher'." + message: + "Iterate over 'Publisher.values' in an 'EffectTask.run', instead, or use 'EffectTask.publisher'." ) @available( tvOS, deprecated: 9999.0, - message: "Iterate over 'Publisher.values' in an 'EffectTask.run', instead, or use 'EffectTask.publisher'." + message: + "Iterate over 'Publisher.values' in an 'EffectTask.run', instead, or use 'EffectTask.publisher'." ) @available( watchOS, deprecated: 9999.0, - message: "Iterate over 'Publisher.values' in an 'EffectTask.run', instead, or use 'EffectTask.publisher'." + message: + "Iterate over 'Publisher.values' in an 'EffectTask.run', instead, or use 'EffectTask.publisher'." ) public func catchToEffect() -> EffectTask> { self.catchToEffect { $0 } @@ -495,19 +511,23 @@ extension Publisher { /// - Returns: An effect that wraps `self`. @available( iOS, deprecated: 9999.0, - message: "Iterate over 'Publisher.values' in an 'EffectTask.run', instead, or use 'EffectTask.publisher'." + message: + "Iterate over 'Publisher.values' in an 'EffectTask.run', instead, or use 'EffectTask.publisher'." ) @available( macOS, deprecated: 9999.0, - message: "Iterate over 'Publisher.values' in an 'EffectTask.run', instead, or use 'EffectTask.publisher'." + message: + "Iterate over 'Publisher.values' in an 'EffectTask.run', instead, or use 'EffectTask.publisher'." ) @available( tvOS, deprecated: 9999.0, - message: "Iterate over 'Publisher.values' in an 'EffectTask.run', instead, or use 'EffectTask.publisher'." + message: + "Iterate over 'Publisher.values' in an 'EffectTask.run', instead, or use 'EffectTask.publisher'." ) @available( watchOS, deprecated: 9999.0, - message: "Iterate over 'Publisher.values' in an 'EffectTask.run', instead, or use 'EffectTask.publisher'." + message: + "Iterate over 'Publisher.values' in an 'EffectTask.run', instead, or use 'EffectTask.publisher'." ) public func catchToEffect( _ transform: @escaping (Result) -> T From fba87c0f6f3075e104cb708b88dd5cc8beefc84b Mon Sep 17 00:00:00 2001 From: Stephen Celis Date: Mon, 6 Mar 2023 16:27:31 -0800 Subject: [PATCH 03/10] Detect identifiable changes in enum cases --- .../xcshareddata/swiftpm/Package.resolved | 4 ++-- .../Integration/IntegrationUITests/PresentationTests.swift | 3 --- Package.swift | 4 ++-- Sources/ComposableArchitecture/Internal/NavigationID.swift | 7 +------ .../PresentationReducerTests.swift | 3 --- 5 files changed, 5 insertions(+), 16 deletions(-) diff --git a/ComposableArchitecture.xcworkspace/xcshareddata/swiftpm/Package.resolved b/ComposableArchitecture.xcworkspace/xcshareddata/swiftpm/Package.resolved index b87e344a2e43..749332c50379 100644 --- a/ComposableArchitecture.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/ComposableArchitecture.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -32,8 +32,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/pointfreeco/swift-case-paths", "state" : { - "revision" : "f623901b4bcc97f59c36704f81583f169b228e51", - "version" : "0.13.0" + "branch" : "projection", + "revision" : "14388be3c0b6a46c62dfffb137d50bb2dde84bdd" } }, { diff --git a/Examples/Integration/IntegrationUITests/PresentationTests.swift b/Examples/Integration/IntegrationUITests/PresentationTests.swift index 5976e0e5f8c8..c72397f8fa03 100644 --- a/Examples/Integration/IntegrationUITests/PresentationTests.swift +++ b/Examples/Integration/IntegrationUITests/PresentationTests.swift @@ -52,9 +52,6 @@ final class PresentationTests: XCTestCase { } func testSheet_IdentityChange() async throws { - // TODO: Remove this XCTExpectFailure once the destination identifiable problem is fixed. - XCTExpectFailure() - self.app.buttons["Open sheet"].tap() XCTAssertEqual(true, self.app.staticTexts["Count: 0"].exists) diff --git a/Package.swift b/Package.swift index 79e8646c41b5..7712ad643c68 100644 --- a/Package.swift +++ b/Package.swift @@ -17,11 +17,11 @@ let package = Package( ) ], dependencies: [ + .package(url: "https://github.com/apple/swift-collections", from: "1.0.2"), .package(url: "https://github.com/apple/swift-docc-plugin", from: "1.0.0"), .package(url: "https://github.com/google/swift-benchmark", from: "0.1.0"), .package(url: "https://github.com/pointfreeco/combine-schedulers", from: "0.8.0"), - .package(url: "https://github.com/pointfreeco/swift-case-paths", from: "0.13.0"), - .package(url: "https://github.com/apple/swift-collections", from: "1.0.2"), + .package(url: "https://github.com/pointfreeco/swift-case-paths", branch: "projection"), .package(url: "https://github.com/pointfreeco/swift-custom-dump", from: "0.7.0"), .package(url: "https://github.com/pointfreeco/swift-dependencies", from: "0.1.2"), .package(url: "https://github.com/pointfreeco/swift-identified-collections", from: "0.7.0"), diff --git a/Sources/ComposableArchitecture/Internal/NavigationID.swift b/Sources/ComposableArchitecture/Internal/NavigationID.swift index 048580f3dd86..a846040cdad6 100644 --- a/Sources/ComposableArchitecture/Internal/NavigationID.swift +++ b/Sources/ComposableArchitecture/Internal/NavigationID.swift @@ -72,14 +72,9 @@ struct AnyID: Hashable, Identifiable, Sendable { self.objectIdentifier = ObjectIdentifier(Base.self) self.tag = EnumMetadata(Base.self)?.tag(of: base) - if let id = _id(base) { + if let id = _id(base) ?? EnumMetadata.project(base).flatMap(_id) { self.identifier = AnyHashableSendable(id) } - // TODO: Extract identifiable enum payload and assign id - // else if let metadata = EnumMetadata(type(of: base)), - // metadata.associatedValueType(forTag: metadata.tag(of: base)) is any Identifiable.Type - // { - // } } @usableFromInline diff --git a/Tests/ComposableArchitectureTests/PresentationReducerTests.swift b/Tests/ComposableArchitectureTests/PresentationReducerTests.swift index 444ee2c7dad4..05f5bdc118b0 100644 --- a/Tests/ComposableArchitectureTests/PresentationReducerTests.swift +++ b/Tests/ComposableArchitectureTests/PresentationReducerTests.swift @@ -1881,9 +1881,6 @@ import XCTest } func testPresentation_DestinationEnum_IdentityChange() async { - // TODO: Remove this XCTExpectFailure once the destination identifiable problem is fixed. - XCTExpectFailure() - struct Child: ReducerProtocol { struct State: Equatable, Identifiable { var id = DependencyValues._current.uuid() From 6e074b971f100f776599abca0e7e183cd4e5321a Mon Sep 17 00:00:00 2001 From: Stephen Celis Date: Mon, 6 Mar 2023 16:59:11 -0800 Subject: [PATCH 04/10] wip --- .../IntegrationUITests/PresentationTests.swift | 6 ------ .../Reducer/Reducers/Presentation.swift | 7 +++++-- .../ComposableArchitecture/SwiftUI/Alert.swift | 3 ++- .../SwiftUI/ConfirmationDialog.swift | 3 ++- .../PresentationReducerTests.swift | 16 ++++++++-------- 5 files changed, 17 insertions(+), 18 deletions(-) diff --git a/Examples/Integration/IntegrationUITests/PresentationTests.swift b/Examples/Integration/IntegrationUITests/PresentationTests.swift index c72397f8fa03..d6d9aa91036e 100644 --- a/Examples/Integration/IntegrationUITests/PresentationTests.swift +++ b/Examples/Integration/IntegrationUITests/PresentationTests.swift @@ -154,9 +154,6 @@ final class PresentationTests: XCTestCase { } func testAlertThenDialog() { - // TODO: Remove this XCTExpectFailure once the destination identifiable problem is fixed. - XCTExpectFailure() - self.app.buttons["Open alert"].tap() self.app.buttons["Show dialog"].tap() _ = self.app.staticTexts["Hello!"].waitForExistence(timeout: 1) @@ -178,9 +175,6 @@ final class PresentationTests: XCTestCase { } func testShowDialogThenAlert() { - // TODO: Remove this XCTExpectFailure once the destination identifiable problem is fixed. - XCTExpectFailure() - self.app.buttons["Open dialog"].tap() self.app.buttons["Show alert"].tap() _ = self.app.staticTexts["Hello!"].waitForExistence(timeout: 1) diff --git a/Sources/ComposableArchitecture/Reducer/Reducers/Presentation.swift b/Sources/ComposableArchitecture/Reducer/Reducers/Presentation.swift index 10c171bd1763..a89ee9293b79 100644 --- a/Sources/ComposableArchitecture/Reducer/Reducers/Presentation.swift +++ b/Sources/ComposableArchitecture/Reducer/Reducers/Presentation.swift @@ -228,11 +228,14 @@ public struct _PresentationReducer< .dependency(\.navigationID, id) .reduce( into: &state[keyPath: self.toPresentationState].wrappedValue!, action: destinationAction - ) + ) .map { self.toPresentationAction.embed(.presented($0)) } .cancellable(id: id) baseEffects = self.base.reduce(into: &state, action: action) - if isEphemeral(destinationState) { + if isEphemeral(destinationState), + self.id(for: destinationState) + == state[keyPath: self.toPresentationState].wrappedValue.map(self.id(for:)) + { state[keyPath: self.toPresentationState].wrappedValue = nil } diff --git a/Sources/ComposableArchitecture/SwiftUI/Alert.swift b/Sources/ComposableArchitecture/SwiftUI/Alert.swift index a3923b7072ee..09b1858d5400 100644 --- a/Sources/ComposableArchitecture/SwiftUI/Alert.swift +++ b/Sources/ComposableArchitecture/SwiftUI/Alert.swift @@ -116,13 +116,14 @@ private struct PresentationAlertModifier: ViewModif let fromDestinationAction: (ButtonAction) -> Action func body(content: Content) -> some View { + let id = self.viewStore.id let alertState = self.viewStore.wrappedValue.flatMap(self.toDestinationState) content.alert( (alertState?.title).map(Text.init) ?? Text(""), isPresented: Binding( // TODO: do proper binding get: { self.viewStore.wrappedValue.flatMap(self.toDestinationState) != nil }, set: { newState in - if !newState, self.viewStore.wrappedValue != nil { + if !newState, self.viewStore.wrappedValue != nil, self.viewStore.id == id { self.viewStore.send(.dismiss) } } diff --git a/Sources/ComposableArchitecture/SwiftUI/ConfirmationDialog.swift b/Sources/ComposableArchitecture/SwiftUI/ConfirmationDialog.swift index f871d0e5fcc0..59b197f0bc09 100644 --- a/Sources/ComposableArchitecture/SwiftUI/ConfirmationDialog.swift +++ b/Sources/ComposableArchitecture/SwiftUI/ConfirmationDialog.swift @@ -119,13 +119,14 @@ private struct PresentationConfirmationDialogModifier Action func body(content: Content) -> some View { + let id = self.viewStore.id let confirmationDialogState = self.viewStore.wrappedValue.flatMap(self.toDestinationState) content.confirmationDialog( (confirmationDialogState?.title).map(Text.init) ?? Text(""), isPresented: Binding( // TODO: do proper binding get: { self.viewStore.wrappedValue.flatMap(self.toDestinationState) != nil }, set: { newState in - if !newState, self.viewStore.wrappedValue != nil { + if !newState, self.viewStore.wrappedValue != nil, self.viewStore.id == id { self.viewStore.send(.dismiss) } } diff --git a/Tests/ComposableArchitectureTests/PresentationReducerTests.swift b/Tests/ComposableArchitectureTests/PresentationReducerTests.swift index 05f5bdc118b0..128ddc475dd2 100644 --- a/Tests/ComposableArchitectureTests/PresentationReducerTests.swift +++ b/Tests/ComposableArchitectureTests/PresentationReducerTests.swift @@ -2054,25 +2054,25 @@ import XCTest reducer: Feature() ) - // TODO: remove this XCTExpectFailure when the destination identifiable stuff is fixed - XCTExpectFailure() - await store.send(.showAlert) { $0.destination = .alert(Feature.alert) } await store.send(.destination(.presented(.alert(.showDialog)))) { - $0.destination = .dialog(Feature.dialog) + $0.destination = .dialog(ConfirmationDialogState { TextState("Hello!") } actions: {}) + } + await store.send(.destination(.dismiss)) { + $0.destination = nil } - await store.send(.destination(.dismiss)) await store.send(.showDialog) { $0.destination = .dialog(Feature.dialog) } - // TODO: remove this XCTExpectFailure when the destination identifiable stuff is fixed await store.send(.destination(.presented(.dialog(.showAlert)))) { - $0.destination = .alert(Feature.alert) + $0.destination = .alert(AlertState { TextState("Hello!") }) + } + await store.send(.destination(.dismiss)) { + $0.destination = nil } - await store.send(.destination(.dismiss)) } } From f22eca18688cc3d8036711c1692e9f6cb1e0789d Mon Sep 17 00:00:00 2001 From: Stephen Celis Date: Mon, 6 Mar 2023 17:14:52 -0800 Subject: [PATCH 05/10] wip --- .../Integration/PresentationTestCase.swift | 23 ++++++++++++++++++- .../PresentationTests.swift | 7 ++++++ .../SwiftUI/FullscreenCover.swift | 15 +++++++++--- .../SwiftUI/Popover.swift | 16 +++++++++---- .../SwiftUI/Sheet.swift | 15 +++++++++--- 5 files changed, 65 insertions(+), 11 deletions(-) diff --git a/Examples/Integration/Integration/PresentationTestCase.swift b/Examples/Integration/Integration/PresentationTestCase.swift index 5914538917a0..226e5b0ebd98 100644 --- a/Examples/Integration/Integration/PresentationTestCase.swift +++ b/Examples/Integration/Integration/PresentationTestCase.swift @@ -39,10 +39,12 @@ private struct PresentationTestCase: ReducerProtocol { enum AlertAction { case ok case showDialog + case showSheet } enum DialogAction { case ok case showAlert + case showSheet } var body: some ReducerProtocolOf { Scope(state: /State.fullScreenCover, action: /Action.fullScreenCover) { @@ -85,6 +87,9 @@ private struct PresentationTestCase: ReducerProtocol { ButtonState(action: .showDialog) { TextState("Show dialog") } + ButtonState(action: .showSheet) { + TextState("Show sheet") + } ButtonState(role: .cancel) { TextState("Cancel") } @@ -104,13 +109,20 @@ private struct PresentationTestCase: ReducerProtocol { } ) return .none - case .destination(.presented(.dialog(.showAlert))): + case .destination(.presented(.alert(.showSheet))): + state.destination = .sheet(ChildFeature.State()) + return .none + case .destination(.presented(.dialog(.showAlert))), + .destination(.presented(.sheet(.dismissAndAlert))): state.destination = .alert( AlertState { TextState("Hello!") } ) return .none + case .destination(.presented(.dialog(.showSheet))): + state.destination = .sheet(ChildFeature.State()) + return .none case .destination(.dismiss): state.message = "Dismiss action sent" return .none @@ -127,6 +139,9 @@ private struct PresentationTestCase: ReducerProtocol { ButtonState(action: .showAlert) { TextState("Show alert") } + ButtonState(action: .showSheet) { + TextState("Show sheet") + } } ) return .none @@ -162,6 +177,7 @@ private struct ChildFeature: ReducerProtocol { enum Action: BindableAction, Equatable { case binding(BindingAction) case childDismissButtonTapped + case dismissAndAlert case incrementButtonTapped case parentSendDismissActionButtonTapped case resetIdentity @@ -177,6 +193,8 @@ private struct ChildFeature: ReducerProtocol { return .none case .childDismissButtonTapped: return .fireAndForget { await self.dismiss() } + case .dismissAndAlert: + return .none case .incrementButtonTapped: state.count += 1 return .none @@ -324,6 +342,9 @@ private struct ChildView: View { Button("Parent dismiss") { viewStore.send(.parentSendDismissActionButtonTapped) } + Button("Dismiss and alert") { + viewStore.send(.dismissAndAlert) + } Button("Start effect") { viewStore.send(.startButtonTapped) } diff --git a/Examples/Integration/IntegrationUITests/PresentationTests.swift b/Examples/Integration/IntegrationUITests/PresentationTests.swift index d6d9aa91036e..a5fff24ed563 100644 --- a/Examples/Integration/IntegrationUITests/PresentationTests.swift +++ b/Examples/Integration/IntegrationUITests/PresentationTests.swift @@ -160,6 +160,13 @@ final class PresentationTests: XCTestCase { XCTAssertEqual(true, self.app.staticTexts["Hello!"].exists) } + func testAlertThenSheet() { + self.app.buttons["Open alert"].tap() + self.app.buttons["Show sheet"].tap() + _ = self.app.staticTexts["Count: 0"].waitForExistence(timeout: 1) + XCTAssertEqual(true, self.app.staticTexts["Count: 0"].exists) + } + func testDialogActionDoesNotSendExtraDismiss() { self.app.buttons["Open dialog"].tap() self.app.buttons["OK"].tap() diff --git a/Sources/ComposableArchitecture/SwiftUI/FullscreenCover.swift b/Sources/ComposableArchitecture/SwiftUI/FullscreenCover.swift index 8f04ffbe1ab9..be1415aa5ae3 100644 --- a/Sources/ComposableArchitecture/SwiftUI/FullscreenCover.swift +++ b/Sources/ComposableArchitecture/SwiftUI/FullscreenCover.swift @@ -62,10 +62,19 @@ private struct PresentationFullScreenCoverModifier< } func body(content: Content) -> some View { + let id = self.viewStore.id content.fullScreenCover( - item: self.viewStore.binding( - get: { $0.wrappedValue.flatMap(self.toDestinationState) != nil ? $0.id : nil }, - send: .dismiss + item: Binding( // TODO: do proper binding + get: { + self.viewStore.wrappedValue.flatMap(self.toDestinationState) != nil + ? self.viewStore.id + : nil + }, + set: { newState in + if newState == nil, self.viewStore.wrappedValue != nil, self.viewStore.id == id { + self.viewStore.send(.dismiss) + } + } ) ) { _ in IfLetStore( diff --git a/Sources/ComposableArchitecture/SwiftUI/Popover.swift b/Sources/ComposableArchitecture/SwiftUI/Popover.swift index eda6606c09f6..e5f9d3cf2699 100644 --- a/Sources/ComposableArchitecture/SwiftUI/Popover.swift +++ b/Sources/ComposableArchitecture/SwiftUI/Popover.swift @@ -81,11 +81,19 @@ private struct PresentationPopoverModifier< } func body(content: Content) -> some View { + let id = self.viewStore.id content.popover( - item: self.viewStore.binding( - get: { $0.wrappedValue.flatMap(self.toDestinationState) != nil ? $0.id : nil }, - send: .dismiss - + item: Binding( // TODO: do proper binding + get: { + self.viewStore.wrappedValue.flatMap(self.toDestinationState) != nil + ? self.viewStore.id + : nil + }, + set: { newState in + if newState == nil, self.viewStore.wrappedValue != nil, self.viewStore.id == id { + self.viewStore.send(.dismiss) + } + } ), attachmentAnchor: self.attachmentAnchor, arrowEdge: self.arrowEdge diff --git a/Sources/ComposableArchitecture/SwiftUI/Sheet.swift b/Sources/ComposableArchitecture/SwiftUI/Sheet.swift index cb6ccad28acd..62fcec3d8400 100644 --- a/Sources/ComposableArchitecture/SwiftUI/Sheet.swift +++ b/Sources/ComposableArchitecture/SwiftUI/Sheet.swift @@ -56,10 +56,19 @@ private struct PresentationSheetModifier< } func body(content: Content) -> some View { + let id = self.viewStore.id content.sheet( - item: self.viewStore.binding( - get: { $0.wrappedValue.flatMap(self.toDestinationState) != nil ? $0.id : nil }, - send: .dismiss + item: Binding( // TODO: do proper binding + get: { + self.viewStore.wrappedValue.flatMap(self.toDestinationState) != nil + ? self.viewStore.id + : nil + }, + set: { newState in + if newState == nil, self.viewStore.wrappedValue != nil, self.viewStore.id == id { + self.viewStore.send(.dismiss) + } + } ) ) { _ in IfLetStore( From 79c64d2f7a71e07878d32c0e63c2cc698df42348 Mon Sep 17 00:00:00 2001 From: Brandon Williams Date: Mon, 6 Mar 2023 20:35:03 -0800 Subject: [PATCH 06/10] wip --- .../Integration/PresentationTestCase.swift | 13 +++++++- .../PresentationTests.swift | 22 ++++++++++++++ .../SwiftUI/NavigationDestination.swift | 5 +++- .../PresentationReducerTests.swift | 30 ++++++++++--------- 4 files changed, 54 insertions(+), 16 deletions(-) diff --git a/Examples/Integration/Integration/PresentationTestCase.swift b/Examples/Integration/Integration/PresentationTestCase.swift index 226e5b0ebd98..2f4ca014865b 100644 --- a/Examples/Integration/Integration/PresentationTestCase.swift +++ b/Examples/Integration/Integration/PresentationTestCase.swift @@ -112,7 +112,11 @@ private struct PresentationTestCase: ReducerProtocol { case .destination(.presented(.alert(.showSheet))): state.destination = .sheet(ChildFeature.State()) return .none - case .destination(.presented(.dialog(.showAlert))), + case + .destination(.presented(.dialog(.showAlert))), + .destination(.presented(.fullScreenCover(.dismissAndAlert))), + .destination(.presented(.popover(.dismissAndAlert))), + .destination(.presented(.navigationDestination(.dismissAndAlert))), .destination(.presented(.sheet(.dismissAndAlert))): state.destination = .alert( AlertState { @@ -172,6 +176,7 @@ private struct ChildFeature: ReducerProtocol { struct State: Equatable, Identifiable { var id = UUID() var count = 0 + var isDismissed = false @BindingState var text = "" } enum Action: BindableAction, Equatable { @@ -192,6 +197,7 @@ private struct ChildFeature: ReducerProtocol { case .binding: return .none case .childDismissButtonTapped: + state.isDismissed = true return .fireAndForget { await self.dismiss() } case .dismissAndAlert: return .none @@ -199,6 +205,7 @@ private struct ChildFeature: ReducerProtocol { state.count += 1 return .none case .parentSendDismissActionButtonTapped: + state.isDismissed = true return .none case .resetIdentity: state.id = UUID() @@ -326,6 +333,7 @@ struct PresentationTestCaseView: View { } private struct ChildView: View { + @Environment(\.dismiss) var dismiss let store: StoreOf var body: some View { @@ -352,6 +360,9 @@ private struct ChildView: View { viewStore.send(.resetIdentity) } } + .onChange(of: viewStore.isDismissed) { _ in + self.dismiss() + } } } } diff --git a/Examples/Integration/IntegrationUITests/PresentationTests.swift b/Examples/Integration/IntegrationUITests/PresentationTests.swift index a5fff24ed563..99e422eb4594 100644 --- a/Examples/Integration/IntegrationUITests/PresentationTests.swift +++ b/Examples/Integration/IntegrationUITests/PresentationTests.swift @@ -251,4 +251,26 @@ final class PresentationTests: XCTestCase { self.app.buttons["Parent dismiss"].tap() XCTAssertEqual(self.app.staticTexts["Action sent while state nil."].exists, false) } + + + func testNavigationDestination_ChildDismiss() { + self.app.buttons["Open navigation destination"].tap() + XCTAssertEqual(true, self.app.staticTexts["Count: 0"].exists) + + self.app.buttons["Increment"].tap() + XCTAssertEqual(true, self.app.staticTexts["Count: 1"].exists) + self.app.buttons["Increment"].tap() + XCTAssertEqual(true, self.app.staticTexts["Count: 2"].exists) + + self.app.buttons["Child dismiss"].tap() + XCTAssertEqual(false, self.app.staticTexts["Count: 2"].exists) + } + + func testNavigationDestination_ParentDismiss() { + self.app.buttons["Open navigation destination"].tap() + XCTAssertEqual(true, self.app.staticTexts["Count: 0"].exists) + + self.app.buttons["Parent dismiss"].tap() + XCTAssertEqual(false, self.app.staticTexts["Count: 0"].exists) + } } diff --git a/Sources/ComposableArchitecture/SwiftUI/NavigationDestination.swift b/Sources/ComposableArchitecture/SwiftUI/NavigationDestination.swift index fa9e3cb8295f..77856900ef24 100644 --- a/Sources/ComposableArchitecture/SwiftUI/NavigationDestination.swift +++ b/Sources/ComposableArchitecture/SwiftUI/NavigationDestination.swift @@ -67,7 +67,10 @@ import SwiftUI } func body(content: Content) -> some View { - content.navigationDestination(isPresented: self.viewStore.binding(send: .dismiss)) { + content.navigationDestination( + // TODO: do binding with ID check + isPresented: self.viewStore.binding(send: .dismiss) + ) { IfLetStore( self.store.scope( state: returningLastNonNilValue { $0.wrappedValue.flatMap(self.toDestinationState) }, diff --git a/Tests/ComposableArchitectureTests/PresentationReducerTests.swift b/Tests/ComposableArchitectureTests/PresentationReducerTests.swift index 128ddc475dd2..f68a65e842f3 100644 --- a/Tests/ComposableArchitectureTests/PresentationReducerTests.swift +++ b/Tests/ComposableArchitectureTests/PresentationReducerTests.swift @@ -1049,21 +1049,23 @@ import XCTest } } - let store = TestStore( - initialState: Parent.State(), - reducer: Parent() - ) - let childPresentationTask = await store.send(.presentChild) { - $0.child = Child.State() - } - let grandchildPresentationTask = await store.send(.child(.presented(.presentGrandchild))) { - $0.child?.grandchild = Grandchild.State() + await _withMainSerialExecutor { + let store = TestStore( + initialState: Parent.State(), + reducer: Parent() + ) + let childPresentationTask = await store.send(.presentChild) { + $0.child = Child.State() + } + let grandchildPresentationTask = await store.send(.child(.presented(.presentGrandchild))) { + $0.child?.grandchild = Grandchild.State() + } + await store.send(.child(.presented(.startButtonTapped))) + await store.send(.child(.presented(.grandchild(.presented(.startButtonTapped))))) + await store.send(.stopButtonTapped) + await grandchildPresentationTask.cancel() + await childPresentationTask.cancel() } - await store.send(.child(.presented(.startButtonTapped))) - await store.send(.child(.presented(.grandchild(.presented(.startButtonTapped))))) - await store.send(.stopButtonTapped) - await grandchildPresentationTask.cancel() - await childPresentationTask.cancel() } func testNavigation_cancelID_parentCancelTwoChildren() async { From a5f285a7bcff8f2b706181a66b97e8fd0327f405 Mon Sep 17 00:00:00 2001 From: Brandon Williams Date: Mon, 6 Mar 2023 20:44:47 -0800 Subject: [PATCH 07/10] wip --- .../Integration/PresentationTestCase.swift | 1 - .../IntegrationUITests/PresentationTests.swift | 16 ++++++++++++++++ 2 files changed, 16 insertions(+), 1 deletion(-) diff --git a/Examples/Integration/Integration/PresentationTestCase.swift b/Examples/Integration/Integration/PresentationTestCase.swift index 2f4ca014865b..8e3e58ca740f 100644 --- a/Examples/Integration/Integration/PresentationTestCase.swift +++ b/Examples/Integration/Integration/PresentationTestCase.swift @@ -97,7 +97,6 @@ private struct PresentationTestCase: ReducerProtocol { ) return .none case .destination(.presented(.fullScreenCover(.parentSendDismissActionButtonTapped))), - .destination(.presented(.navigationDestination(.parentSendDismissActionButtonTapped))), .destination(.presented(.sheet(.parentSendDismissActionButtonTapped))), .destination(.presented(.popover(.parentSendDismissActionButtonTapped))): return .send(.destination(.dismiss)) diff --git a/Examples/Integration/IntegrationUITests/PresentationTests.swift b/Examples/Integration/IntegrationUITests/PresentationTests.swift index 99e422eb4594..968be515cc51 100644 --- a/Examples/Integration/IntegrationUITests/PresentationTests.swift +++ b/Examples/Integration/IntegrationUITests/PresentationTests.swift @@ -273,4 +273,20 @@ final class PresentationTests: XCTestCase { self.app.buttons["Parent dismiss"].tap() XCTAssertEqual(false, self.app.staticTexts["Count: 0"].exists) } + + func testNavigationDestination_EffectsCancelOnDismiss() async throws { + self.app.buttons["Open navigation destination"].tap() + XCTAssertEqual(true, self.app.staticTexts["Count: 0"].exists) + + self.app.buttons["Start effect"].tap() + XCTAssertEqual(true, self.app.staticTexts["Count: 1"].exists) + + self.app.buttons["Parent dismiss"].tap() + XCTAssertEqual(false, self.app.staticTexts["Count: 1"].exists) + + self.app.buttons["Open navigation destination"].tap() + XCTAssertEqual(true, self.app.staticTexts["Count: 0"].exists) + try await Task.sleep(for: .seconds(3)) + XCTAssertEqual(false, self.app.staticTexts["Count: 999"].exists) + } } From 016725483eb9754d0a4480c3012ba025da1c5334 Mon Sep 17 00:00:00 2001 From: Matteo Girardi Date: Tue, 7 Mar 2023 22:51:11 +0100 Subject: [PATCH 08/10] Fix grammar in doc (#1963) * Fix documentation - improve grammar in doc * Revert to previous sentence --- Sources/ComposableArchitecture/SwiftUI/WithViewStore.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Sources/ComposableArchitecture/SwiftUI/WithViewStore.swift b/Sources/ComposableArchitecture/SwiftUI/WithViewStore.swift index dcd6a45e9cb3..2515970c5f5e 100644 --- a/Sources/ComposableArchitecture/SwiftUI/WithViewStore.swift +++ b/Sources/ComposableArchitecture/SwiftUI/WithViewStore.swift @@ -46,7 +46,7 @@ import SwiftUI /// instead of using ``WithViewStore``: /// /// 1. When ``WithViewStore`` wraps complex views the Swift compiler can quickly become bogged down, -/// leading to degraded compiler performance and diagnostics. If you are experience such instability +/// leading to degraded compiler performance and diagnostics. If you are experiencing such instability /// you should consider manually setting up observation with an `@ObservedObject` property as /// described above. /// From b46e4bef0a6ef68a45aa27e36f37911c86a7feb8 Mon Sep 17 00:00:00 2001 From: Brandon Williams Date: Tue, 7 Mar 2023 16:21:08 -0800 Subject: [PATCH 09/10] fix case path dependency --- .../xcshareddata/swiftpm/Package.resolved | 4 ++-- Package.swift | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/ComposableArchitecture.xcworkspace/xcshareddata/swiftpm/Package.resolved b/ComposableArchitecture.xcworkspace/xcshareddata/swiftpm/Package.resolved index 749332c50379..7d01bd9fe054 100644 --- a/ComposableArchitecture.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/ComposableArchitecture.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -32,8 +32,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/pointfreeco/swift-case-paths", "state" : { - "branch" : "projection", - "revision" : "14388be3c0b6a46c62dfffb137d50bb2dde84bdd" + "revision" : "870133b7b2387df136ad301ec67b2e864b51dda1", + "version" : "0.14.0" } }, { diff --git a/Package.swift b/Package.swift index 7712ad643c68..7a8406cf2df3 100644 --- a/Package.swift +++ b/Package.swift @@ -21,7 +21,7 @@ let package = Package( .package(url: "https://github.com/apple/swift-docc-plugin", from: "1.0.0"), .package(url: "https://github.com/google/swift-benchmark", from: "0.1.0"), .package(url: "https://github.com/pointfreeco/combine-schedulers", from: "0.8.0"), - .package(url: "https://github.com/pointfreeco/swift-case-paths", branch: "projection"), + .package(url: "https://github.com/pointfreeco/swift-case-paths", from: "0.14.0"), .package(url: "https://github.com/pointfreeco/swift-custom-dump", from: "0.7.0"), .package(url: "https://github.com/pointfreeco/swift-dependencies", from: "0.1.2"), .package(url: "https://github.com/pointfreeco/swift-identified-collections", from: "0.7.0"), From 3e8eee1efe99d06e99426d421733b858b332186b Mon Sep 17 00:00:00 2001 From: Stephen Celis Date: Tue, 7 Mar 2023 21:47:47 -0800 Subject: [PATCH 10/10] Bump Dependencies (#1964) * Bump Dependencies * wip --- .../xcshareddata/swiftpm/Package.resolved | 16 ++++++++-------- Package.resolved | 16 ++++++++-------- Package.swift | 8 ++++---- 3 files changed, 20 insertions(+), 20 deletions(-) diff --git a/ComposableArchitecture.xcworkspace/xcshareddata/swiftpm/Package.resolved b/ComposableArchitecture.xcworkspace/xcshareddata/swiftpm/Package.resolved index b87e344a2e43..d75b887b5e29 100644 --- a/ComposableArchitecture.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/ComposableArchitecture.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -32,8 +32,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/pointfreeco/swift-case-paths", "state" : { - "revision" : "f623901b4bcc97f59c36704f81583f169b228e51", - "version" : "0.13.0" + "revision" : "870133b7b2387df136ad301ec67b2e864b51dda1", + "version" : "0.14.0" } }, { @@ -59,8 +59,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/pointfreeco/swift-custom-dump", "state" : { - "revision" : "dd86159e25c749873f144577e5d18309bf57534f", - "version" : "0.8.0" + "revision" : "de8ba65649e7ee317b9daf27dd5eebf34bd4be57", + "version" : "0.9.1" } }, { @@ -68,8 +68,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/pointfreeco/swift-dependencies", "state" : { - "revision" : "8282b0c59662eb38946afe30eb403663fc2ecf76", - "version" : "0.1.4" + "revision" : "6bb1034e8a1bfbf46dfb766b6c09b7b17e1cba10", + "version" : "0.2.0" } }, { @@ -104,8 +104,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/pointfreeco/swiftui-navigation", "state" : { - "revision" : "270a754308f5440be52fc295242eb7031638bd15", - "version" : "0.6.1" + "revision" : "0a0e1b321d70ee6a464ecfe6b0136d9eff77ebfc", + "version" : "0.7.0" } }, { diff --git a/Package.resolved b/Package.resolved index b87e344a2e43..d75b887b5e29 100644 --- a/Package.resolved +++ b/Package.resolved @@ -32,8 +32,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/pointfreeco/swift-case-paths", "state" : { - "revision" : "f623901b4bcc97f59c36704f81583f169b228e51", - "version" : "0.13.0" + "revision" : "870133b7b2387df136ad301ec67b2e864b51dda1", + "version" : "0.14.0" } }, { @@ -59,8 +59,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/pointfreeco/swift-custom-dump", "state" : { - "revision" : "dd86159e25c749873f144577e5d18309bf57534f", - "version" : "0.8.0" + "revision" : "de8ba65649e7ee317b9daf27dd5eebf34bd4be57", + "version" : "0.9.1" } }, { @@ -68,8 +68,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/pointfreeco/swift-dependencies", "state" : { - "revision" : "8282b0c59662eb38946afe30eb403663fc2ecf76", - "version" : "0.1.4" + "revision" : "6bb1034e8a1bfbf46dfb766b6c09b7b17e1cba10", + "version" : "0.2.0" } }, { @@ -104,8 +104,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/pointfreeco/swiftui-navigation", "state" : { - "revision" : "270a754308f5440be52fc295242eb7031638bd15", - "version" : "0.6.1" + "revision" : "0a0e1b321d70ee6a464ecfe6b0136d9eff77ebfc", + "version" : "0.7.0" } }, { diff --git a/Package.swift b/Package.swift index 79e8646c41b5..fe815ed2fb97 100644 --- a/Package.swift +++ b/Package.swift @@ -20,12 +20,12 @@ let package = Package( .package(url: "https://github.com/apple/swift-docc-plugin", from: "1.0.0"), .package(url: "https://github.com/google/swift-benchmark", from: "0.1.0"), .package(url: "https://github.com/pointfreeco/combine-schedulers", from: "0.8.0"), - .package(url: "https://github.com/pointfreeco/swift-case-paths", from: "0.13.0"), + .package(url: "https://github.com/pointfreeco/swift-case-paths", from: "0.14.0"), .package(url: "https://github.com/apple/swift-collections", from: "1.0.2"), - .package(url: "https://github.com/pointfreeco/swift-custom-dump", from: "0.7.0"), - .package(url: "https://github.com/pointfreeco/swift-dependencies", from: "0.1.2"), + .package(url: "https://github.com/pointfreeco/swift-custom-dump", from: "0.9.1"), + .package(url: "https://github.com/pointfreeco/swift-dependencies", from: "0.2.0"), .package(url: "https://github.com/pointfreeco/swift-identified-collections", from: "0.7.0"), - .package(url: "https://github.com/pointfreeco/swiftui-navigation", from: "0.6.0"), + .package(url: "https://github.com/pointfreeco/swiftui-navigation", from: "0.7.0"), .package(url: "https://github.com/pointfreeco/xctest-dynamic-overlay", from: "0.5.0"), ], targets: [