Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
ebc902f
[launch-with-workflow] - Added FlowRepresentableMetadataTests, made F…
morganzellers Nov 2, 2021
21a62c8
[launch-with-workflow] - Added tests for new launcher initializer usi…
morganzellers Nov 2, 2021
41474c3
Adding support for .last computed property to AnyWorkflow
morganzellers Nov 3, 2021
e5b0e64
Adds support for workflows with more than one item.
morganzellers Nov 3, 2021
b3c2c6b
Refactor of ExtendedFlowRepresentableMetadata inits and function to f…
morganzellers Nov 3, 2021
402a9fe
Added test for fatalError scenario in which no items in a workflow
morganzellers Nov 4, 2021
e67d734
WIP - Working through the tests and adding thenProceeds as I figure i…
morganzellers Nov 4, 2021
3e70c53
WIP still - added more thenProceeds but still have a lot to drive out.
morganzellers Nov 5, 2021
686cf28
Adds support for launching a workflow using an AnyWorkflow with start…
morganzellers Nov 9, 2021
1d51a0a
Added generic constraint test file for AnyWorkflow launched Workflows.
morganzellers Nov 9, 2021
4000010
WIP - Using generic constraints tests to drive out more thenProceeds.
morganzellers Nov 9, 2021
3a2a9c4
[launch-with-workflow] - Adding missing init that was causing tests t…
Richard-Gist Nov 10, 2021
f87bbe7
[launch-with-workflow] - Fixing linting error - RAG
Richard-Gist Nov 10, 2021
d9e7845
[launch-with-workflow] - Fixed some linting and got some tests passin…
Richard-Gist Nov 10, 2021
9c786ca
Merge branch 'data-driven' into launch-with-workflow
morganzellers Nov 11, 2021
f585656
[launch-with-workflow] - Removed tests for thenProceed functions only…
morganzellers Nov 11, 2021
ff16c99
[launch-with-workflow] - Adding more descriptive docs - RAG MZ
morganzellers Nov 11, 2021
1f62b9b
[launch-with-workflow] - Added TODO to make public Workflow inits pri…
morganzellers Nov 11, 2021
9a283fd
Taking Richard's whitespace fix
morganzellers Nov 11, 2021
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ import Foundation
### Discussion
Every time a `Workflow` is created, the defining characteristics about a `FlowRepresentable` are stored in the `FlowRepresentableMetadata` to be used later.
*/
public class FlowRepresentableMetadata {
open class FlowRepresentableMetadata {
/// Preferred `LaunchStyle` of the associated `FlowRepresentable`.
public private(set) var launchStyle: LaunchStyle
/// Preferred `FlowPersistence` of the associated `FlowRepresentable`; set when `FlowRepresentableMetadata` instantiates an instance.
Expand Down
5 changes: 5 additions & 0 deletions Sources/SwiftCurrent/TypeErased/AnyWorkflow.swift
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,9 @@ public class AnyWorkflow {
/// The first `LinkedList.Node` of the wrapped `Workflow`.
public var first: Element? { storageBase.first }

/// The last `LinkedList.Node` of the wrapped `Workflow`.
public var last: Element? { storageBase.last }

fileprivate var storageBase: AnyWorkflowStorageBase

/// Creates a type erased `Workflow`.
Expand Down Expand Up @@ -111,6 +114,7 @@ fileprivate class AnyWorkflowStorageBase {
var orchestrationResponder: OrchestrationResponder?
var count: Int { fatalError("count not overridden by AnyWorkflowStorage") }
var first: LinkedList<_WorkflowItem>.Element? { fatalError("first not overridden by AnyWorkflowStorage") }
var last: LinkedList<_WorkflowItem>.Element? { fatalError("last not overridden by AnyWorkflowStorage") }

// https://github.com/wwt/SwiftCurrent/blob/main/.github/STYLEGUIDE.md#type-erasure
// swiftlint:disable:next unavailable_function
Expand Down Expand Up @@ -157,6 +161,7 @@ fileprivate final class AnyWorkflowStorage<F: FlowRepresentable>: AnyWorkflowSto
override var count: Int { workflow.count }

override var first: LinkedList<_WorkflowItem>.Element? { workflow.first }
override var last: LinkedList<_WorkflowItem>.Element? { workflow.last }

init(_ workflow: Workflow<F>) {
self.workflow = workflow
Expand Down
240 changes: 240 additions & 0 deletions Sources/SwiftCurrent_SwiftUI/Extensions/WorkflowExtensions.swift

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
//
// ExtendedFlowRepresentableMetadata.swift
// SwiftCurrent
//
// Created by Morgan Zellers on 11/2/21.
// Copyright © 2021 WWT and Tyler Thompson. All rights reserved.
//

import SwiftUI
import SwiftCurrent

@available(iOS 14.0, macOS 11, tvOS 14.0, watchOS 7.0, *)
class ExtendedFlowRepresentableMetadata: FlowRepresentableMetadata {
private(set) var workflowItemFactory: (AnyWorkflowItem?) -> AnyWorkflowItem

init<FR: FlowRepresentable & View>(flowRepresentableType: FR.Type,
launchStyle: LaunchStyle = .default,
flowPersistence: @escaping (AnyWorkflow.PassedArgs) -> FlowPersistence,
flowRepresentableFactory: @escaping (AnyWorkflow.PassedArgs) -> AnyFlowRepresentable) {
workflowItemFactory = {
guard let wrappedWorkflowItem = $0 else { return AnyWorkflowItem(view: WorkflowItem(FR.self)) }
return AnyWorkflowItem(view: WorkflowItem(FR.self) { wrappedWorkflowItem })
}

super.init(flowRepresentableType, launchStyle: launchStyle, flowPersistence: flowPersistence, flowRepresentableFactory: flowRepresentableFactory)
}

init<FR: FlowRepresentable & View>(flowRepresentableType: FR.Type,
launchStyle: LaunchStyle = .default,
flowPersistence: @escaping (AnyWorkflow.PassedArgs) -> FlowPersistence) {
workflowItemFactory = {
guard let wrappedWorkflowItem = $0 else { return AnyWorkflowItem(view: WorkflowItem(FR.self)) }
return AnyWorkflowItem(view: WorkflowItem(FR.self) { wrappedWorkflowItem })
}

super.init(flowRepresentableType, launchStyle: launchStyle, flowPersistence: flowPersistence) { args in
AnyFlowRepresentableView(type: FR.self, args: args)
}
}
}
23 changes: 23 additions & 0 deletions Sources/SwiftCurrent_SwiftUI/TypeErased/AnyWorkflowItem.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
//
// AnyWorkflowItem.swift
// SwiftCurrent
//
// Created by Morgan Zellers on 11/2/21.
// Copyright © 2021 WWT and Tyler Thompson. All rights reserved.
//

import SwiftUI

@available(iOS 14.0, macOS 11, tvOS 14.0, watchOS 7.0, *)
public struct AnyWorkflowItem: View {
let inspection = Inspection<Self>()
private let _body: AnyView

public var body: some View {
_body.onReceive(inspection.notice) { inspection.visit(self, $0) }
}

init<F, W, C>(view: WorkflowItem<F, W, C>) {
_body = AnyView(view)
}
}
24 changes: 12 additions & 12 deletions Sources/SwiftCurrent_SwiftUI/Views/WorkflowItem.swift
Original file line number Diff line number Diff line change
Expand Up @@ -89,26 +89,26 @@ public struct WorkflowItem<F: FlowRepresentable & View, Wrapped: View, Content:
_modifierClosure = State(initialValue: modifierClosure)
_flowPersistenceClosure = State(initialValue: flowPersistenceClosure)
_launchStyle = State(initialValue: launchStyle)
let metadata = FlowRepresentableMetadata(F.self,
launchStyle: launchStyle.rawValue,
flowPersistence: flowPersistenceClosure,
flowRepresentableFactory: factory)
let metadata = ExtendedFlowRepresentableMetadata(flowRepresentableType: F.self,
launchStyle: launchStyle.rawValue,
flowPersistence: flowPersistenceClosure,
flowRepresentableFactory: factory)
_metadata = State(initialValue: metadata)
}

init(_ item: F.Type) where Wrapped == Never, Content == F {
let metadata = FlowRepresentableMetadata(Content.self,
launchStyle: .new,
flowPersistence: flowPersistenceClosure,
flowRepresentableFactory: factory)
let metadata = ExtendedFlowRepresentableMetadata(flowRepresentableType: Content.self,
launchStyle: .new,
flowPersistence: flowPersistenceClosure,
flowRepresentableFactory: factory)
_metadata = State(initialValue: metadata)
}

init(_ item: F.Type, wrapped: () -> Wrapped) where Content == F {
let metadata = FlowRepresentableMetadata(Content.self,
launchStyle: .new,
flowPersistence: flowPersistenceClosure,
flowRepresentableFactory: factory)
let metadata = ExtendedFlowRepresentableMetadata(flowRepresentableType: Content.self,
launchStyle: .new,
flowPersistence: flowPersistenceClosure,
flowRepresentableFactory: factory)
_metadata = State(initialValue: metadata)
_wrapped = State(initialValue: wrapped())
}
Expand Down
52 changes: 52 additions & 0 deletions Sources/SwiftCurrent_SwiftUI/Views/WorkflowLauncher.swift
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,38 @@ public struct WorkflowLauncher<Content: View>: View {
.onReceive(inspection.notice) { inspection.visit(self, $0) }
}

/**
Creates a base for proceeding with a `WorkflowItem`.
- Parameter isLaunched: binding that controls launching the underlying `Workflow`.
- Parameter workflow: workflow to be launched; must contain `FlowRepresentable`s of type `View`
*/
public init<F: FlowRepresentable & View>(isLaunched: Binding<Bool>, workflow: Workflow<F>) where Content == AnyWorkflowItem {
self.init(isLaunched: isLaunched, startingArgs: .none, workflow: AnyWorkflow(workflow))
}

/**
Creates a base for proceeding with a `WorkflowItem`.
- Parameter isLaunched: binding that controls launching the underlying `Workflow`.
- Parameter startingArgs: arguments passed to the first loaded `FlowRepresentable` in the underlying `Workflow`.
- Parameter workflow: workflow to be launched; must contain `FlowRepresentable`s of type `View`
*/
public init<F: FlowRepresentable & View>(isLaunched: Binding<Bool>, startingArgs: AnyWorkflow.PassedArgs, workflow: Workflow<F>) where Content == AnyWorkflowItem {
self.init(isLaunched: isLaunched, startingArgs: startingArgs, workflow: AnyWorkflow(workflow))
}

private init(isLaunched: Binding<Bool>, startingArgs: AnyWorkflow.PassedArgs, workflow: AnyWorkflow) where Content == AnyWorkflowItem {
workflow.forEach {
assert($0.value.metadata is ExtendedFlowRepresentableMetadata)
}
_isLaunched = isLaunched
let model = WorkflowViewModel(isLaunched: isLaunched, launchArgs: startingArgs)
_model = StateObject(wrappedValue: model)
_launcher = StateObject(wrappedValue: Launcher(workflow: workflow,
responder: model,
launchArgs: startingArgs))
_content = State(wrappedValue: WorkflowLauncher.itemToLaunch(from: workflow))
}

/**
Creates a base for proceeding with a `WorkflowItem`.
- Parameter isLaunched: binding that controls launching the underlying `Workflow`.
Expand Down Expand Up @@ -167,6 +199,26 @@ public struct WorkflowLauncher<Content: View>: View {
onFinish.forEach { $0(args) }
}

private static func itemToLaunch(from workflow: AnyWorkflow) -> AnyWorkflowItem {
let lastMetadata = workflow.last?.value.metadata as? ExtendedFlowRepresentableMetadata
let lastItem = lastMetadata?.workflowItemFactory(nil)

if let headItem = WorkflowLauncher.findHeadItem(element: workflow.last, item: lastItem) {
return headItem
} else if let lastItem = lastItem {
return lastItem
}

fatalError("Workflow has no items to launch")
}

private static func findHeadItem(element: AnyWorkflow.Element?, item: AnyWorkflowItem?) -> AnyWorkflowItem? {
guard let previous = element?.previous,
let previousItem = (previous.value.metadata as? ExtendedFlowRepresentableMetadata)?.workflowItemFactory(item) else { return item }

return findHeadItem(element: previous, item: previousItem)
}

/// Adds an action to perform when this `Workflow` has finished.
public func onFinish(closure: @escaping (AnyWorkflow.PassedArgs) -> Void) -> Self {
var onFinish = self.onFinish
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
//
// FlowRepresentableMetadataConsumerTests.swift
// SwiftCurrent
//
// Created by Morgan Zellers on 11/2/21.
// Copyright © 2021 WWT and Tyler Thompson. All rights reserved.
//

import XCTest
import SwiftCurrent

class FlowRepresentableMetadataConsumerTests: XCTestCase {
func testOverridingFlowRepresentbleMetadata() {
class SpecialConformanceClass { }

class NewMetadata: FlowRepresentableMetadata {
var wf: String? // AnyWFItem

private override init<FR>(_ flowRepresentableType: FR.Type, launchStyle: LaunchStyle = .default, flowPersistence: @escaping (AnyWorkflow.PassedArgs) -> FlowPersistence, flowRepresentableFactory: @escaping (AnyWorkflow.PassedArgs) -> AnyFlowRepresentable) where FR : FlowRepresentable {


super.init(flowRepresentableType, launchStyle: launchStyle, flowPersistence: flowPersistence, flowRepresentableFactory: flowRepresentableFactory)
}

convenience init<FR: FlowRepresentable & SpecialConformanceClass>(flowRepresentableType: FR.Type, launchStyle: LaunchStyle = .default, flowPersistence: @escaping (AnyWorkflow.PassedArgs) -> FlowPersistence, flowRepresentableFactory: @escaping (AnyWorkflow.PassedArgs) -> AnyFlowRepresentable) {

self.init(flowRepresentableType, launchStyle: launchStyle, flowPersistence: flowPersistence, flowRepresentableFactory: flowRepresentableFactory)

wf = String(describing: flowRepresentableType)
}
}

final class FR1: SpecialConformanceClass, FlowRepresentable {
var _workflowPointer: AnyFlowRepresentable?
}

let _ = NewMetadata(flowRepresentableType: FR1.self,
flowPersistence: { _ in .default }) { _ in
AnyFlowRepresentable(FR1.self, args: .none)
}
}
}
Loading