-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
0 parents
commit 6a90646
Showing
73 changed files
with
5,087 additions
and
0 deletions.
There are no files selected for viewing
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,5 @@ | ||
.DS_Store | ||
/.build | ||
/Packages | ||
/*.xcodeproj | ||
xcuserdata/ |
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
8 changes: 8 additions & 0 deletions
8
.swiftpm/xcode/package.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist
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,8 @@ | ||
<?xml version="1.0" encoding="UTF-8"?> | ||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> | ||
<plist version="1.0"> | ||
<dict> | ||
<key>IDEDidComputeMac32BitWarning</key> | ||
<true/> | ||
</dict> | ||
</plist> |
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,43 @@ | ||
{ | ||
"object": { | ||
"pins": [ | ||
{ | ||
"package": "CombineEx", | ||
"repositoryURL": "https://github.com/RocketLaunchpad/CombineEx.git", | ||
"state": { | ||
"branch": null, | ||
"revision": "f77496af0df6563525ae3e432d4a3fa894a6b4e1", | ||
"version": "1.0.0" | ||
} | ||
}, | ||
{ | ||
"package": "FoundationEx", | ||
"repositoryURL": "https://github.com/RocketLaunchpad/FoundationEx.git", | ||
"state": { | ||
"branch": null, | ||
"revision": "1e29902f7c26fff9828c3f488a314786fc7756ff", | ||
"version": "1.0.0" | ||
} | ||
}, | ||
{ | ||
"package": "ReducerArchitecture", | ||
"repositoryURL": "https://github.com/RocketLaunchpad/ReducerArchitecture.git", | ||
"state": { | ||
"branch": null, | ||
"revision": "99d9d0908a2e1558b34232a88f88acd870838148", | ||
"version": "1.1.1" | ||
} | ||
}, | ||
{ | ||
"package": "Tagged", | ||
"repositoryURL": "https://github.com/pointfreeco/swift-tagged.git", | ||
"state": { | ||
"branch": null, | ||
"revision": "592e1eb4255c571ebffc29955011e352217501b4", | ||
"version": "0.5.0" | ||
} | ||
} | ||
] | ||
}, | ||
"version": 1 | ||
} |
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,32 @@ | ||
// swift-tools-version:5.3 | ||
// The swift-tools-version declares the minimum version of Swift required to build this package. | ||
|
||
import PackageDescription | ||
|
||
let package = Package( | ||
name: "UIKitEx", | ||
platforms: [ | ||
.macOS(.v10_15), .iOS(.v13), .tvOS(.v13) | ||
], | ||
products: [ | ||
.library( | ||
name: "UIKitEx", | ||
targets: ["UIKitEx"]), | ||
], | ||
dependencies: [ | ||
.package(name: "FoundationEx", url: "https://github.com/RocketLaunchpad/FoundationEx.git", from: "1.0.0"), | ||
.package(name: "CombineEx", url: "https://github.com/RocketLaunchpad/CombineEx.git", from: "1.0.0"), | ||
.package(name: "ReducerArchitecture", url: "https://github.com/RocketLaunchpad/ReducerArchitecture.git", from: "1.0.0"), | ||
// .package(name: "Functional", url: "https://github.com/RocketLaunchpad/Functional.git", from: "1.0.0"), | ||
], | ||
targets: [ | ||
.target( | ||
name: "UIKitEx", | ||
dependencies: ["FoundationEx", "CombineEx", "ReducerArchitecture"] | ||
), | ||
.testTarget( | ||
name: "UIKitExTests", | ||
dependencies: ["UIKitEx"] | ||
) | ||
] | ||
) |
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,3 @@ | ||
# UIKitEx | ||
|
||
A description of this package. |
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,117 @@ | ||
// | ||
// AnyFlow.swift | ||
// Rocket Insights | ||
// | ||
// Created by Ilya Belenkiy on 2/8/21. | ||
// Copyright © 2021 Rocket Insights. All rights reserved. | ||
// | ||
|
||
#if canImport(UIKit) | ||
|
||
import UIKit | ||
import Combine | ||
import CombineEx | ||
|
||
public struct AnyFlow { | ||
public enum Mode { | ||
case presentation | ||
case inPlace | ||
case embedded | ||
|
||
public var isPresentation: Bool { | ||
switch self { | ||
case .presentation: | ||
return true | ||
default: | ||
return false | ||
} | ||
} | ||
|
||
public var canAddPromptAtEnd: Bool { | ||
switch self { | ||
case .presentation, .inPlace: | ||
return true | ||
case .embedded: | ||
return false | ||
} | ||
} | ||
} | ||
|
||
public let nc: UINavigationController | ||
public var start: () -> AppFlow.CancellableFinshedActionPublisher | ||
public var finish: () -> AppFlow.CancellableFinshedActionPublisher | ||
public var restart: () -> AppFlow.CancellableFinshedActionPublisher | ||
|
||
public init( | ||
on _vc: UINavigationController, | ||
mode: Mode, | ||
presentationContainerType: UINavigationController.Type, | ||
delayFinish: Bool = false, | ||
animateStart: Bool = true | ||
) { | ||
switch mode { | ||
case .presentation: | ||
weak var vc = _vc | ||
let nc = presentationContainerType.init() | ||
self.nc = nc | ||
|
||
start = { [weak vc] in | ||
guard let vc = vc else { return AppFlow.cancel() } | ||
vc.present(nc, animated: animateStart) | ||
return AppFlow.cancellableFinishedAction | ||
} | ||
|
||
finish = { [weak vc] in | ||
guard let vc = vc else { return AppFlow.cancel() } | ||
vc.dismiss(animated: true) | ||
let res = delayFinish ? AppFlow.standardPresentationDelay : AppFlow.finishedAction | ||
return res.addUserCanCancel().eraseType() | ||
} | ||
|
||
restart = { | ||
AppFlow.popToRoot(of: nc).addUserCanCancel().eraseType() | ||
} | ||
|
||
case .inPlace, .embedded: | ||
assert(_vc.topViewController != nil) | ||
weak var nc = _vc | ||
weak var startVC = AppFlow.getStartVC(on: _vc) | ||
self.nc = _vc | ||
|
||
start = { | ||
AppFlow.cancellableNoAction | ||
} | ||
|
||
if mode == .inPlace { | ||
finish = { [weak startVC] in | ||
guard let startVC = startVC else { | ||
assertionFailure() | ||
return AppFlow.cancellableNoAction | ||
} | ||
let delayTime = delayFinish ? Styling.standardNavigationDelay : 0 | ||
return AppFlow.pop(to: startVC, delayFor: delayTime).addUserCanCancel().eraseType() | ||
} | ||
} | ||
else { | ||
finish = { | ||
return AppFlow.cancellableNoAction | ||
} | ||
} | ||
|
||
restart = { [weak startVC, weak nc] in | ||
guard let nc = nc else { return AppFlow.cancel() } | ||
guard let startVC = startVC else { | ||
assertionFailure() | ||
return AppFlow.cancel() | ||
} | ||
guard let flowFirstVC = AppFlow.vcAfter(startVC, on: nc) else { | ||
assertionFailure() | ||
return AppFlow.cancel() | ||
} | ||
return AppFlow.pop(to: flowFirstVC).addUserCanCancel().eraseType() | ||
} | ||
} | ||
} | ||
} | ||
|
||
#endif |
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,68 @@ | ||
// | ||
// AppFlow.swift | ||
// Rocket Insights | ||
// | ||
// Created by Ilya Belenkiy on 7/19/20. | ||
// Copyright © 2021 Rocket Insights. All rights reserved. | ||
// | ||
|
||
#if canImport(UIKit) | ||
|
||
import Combine | ||
import CombineEx | ||
import UIKit | ||
|
||
public protocol SingleValueFlow { | ||
associatedtype Value | ||
associatedtype AsyncValue: SingleValuePublisher where AsyncValue.Output == Value | ||
func run() -> AsyncValue | ||
} | ||
|
||
public protocol ReplaceableInAppFlow: UIViewController {} | ||
|
||
public protocol AppFlowViewController: UIViewController { | ||
func configureBackButton(_ nc: UINavigationController) | ||
func cancel() | ||
} | ||
|
||
public typealias AsyncValueUIasFlowVC = AsyncValueUI & AppFlowViewController | ||
|
||
public enum AppFlow { | ||
public typealias FinshedActionPublisher = AnySingleValuePublisher<Void, Never> | ||
public typealias CancellableFinshedActionPublisher = AnySingleValuePublisher<Void, Cancel> | ||
public static let finishedAction: FinshedActionPublisher = Just(()).eraseType() | ||
public static let cancellableFinishedAction: CancellableFinshedActionPublisher = Just(()).addUserCanCancel().eraseType() | ||
public static let noAction = Just(()).eraseType() | ||
public static let cancellableNoAction = Just(()).addUserCanCancel().eraseType() | ||
|
||
public static func start() -> Just<Void> { | ||
Just(()) | ||
} | ||
|
||
public static func never<T, E: Error>() -> AnySingleValuePublisher<T, E> { | ||
Combine.Empty(completeImmediately: false).eraseType() | ||
} | ||
|
||
public static func cancel<T>() -> AnySingleValuePublisher<T, Cancel> { | ||
Fail(error: .cancel).eraseType() | ||
} | ||
|
||
public static func cancel<T>() -> AnyPublisher<T, Cancel> { | ||
Fail(error: .cancel).eraseToAnyPublisher() | ||
} | ||
} | ||
|
||
public extension Publisher where Failure == Never { | ||
func addUserCanCancel() -> Publishers.MapError<Self, Cancel> { | ||
addErrorType(Cancel.self) | ||
} | ||
} | ||
|
||
public extension Publisher where Output == Void, Failure == Cancel { | ||
func catchCancelAsVoid() -> Publishers.ReplaceError<Self> { | ||
replaceError(with: ()) | ||
} | ||
} | ||
|
||
#endif | ||
|
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,89 @@ | ||
// | ||
// AppFlowNavigation.swift | ||
// Rocket Insights | ||
// | ||
// Created by Ilya Belenkiy on 2/27/21. | ||
// Copyright © 2021 Rocket Insights. All rights reserved. | ||
// | ||
|
||
#if canImport(UIKit) | ||
|
||
import Combine | ||
import CombineEx | ||
import UIKit | ||
|
||
public extension AppFlow { | ||
static func delay(for interval: DispatchQueue.SchedulerTimeType.Stride) -> AnySingleValuePublisher<Void, Never> { | ||
Just(()).delay(for: interval, scheduler: DispatchQueue.main).eraseType() | ||
} | ||
|
||
static let standardPresentationDelay = delay(for: .seconds(Styling.standardPresentationDelay)) | ||
|
||
static func getStartVC(on nc: UINavigationController) -> UIViewController { | ||
if let startVC = nc.viewControllers.reversed().first(where: { vc in | ||
(vc is AppFlowViewController) && !(vc is ReplaceableInAppFlow) | ||
}) | ||
.flatMap({ $0 as? AppFlowViewController }) { | ||
return startVC | ||
} | ||
else if let vc = nc.topViewController { | ||
return vc | ||
} | ||
else { | ||
fatalError() | ||
} | ||
} | ||
|
||
static func vcAfter(_ vc: UIViewController, on nc: UINavigationController) -> UIViewController? { | ||
zip(nc.viewControllers, nc.viewControllers.dropFirst()).first { $0.0 == vc }?.1 | ||
} | ||
|
||
static func vcBefore(_ vc: UIViewController, on nc: UINavigationController) -> UIViewController? { | ||
zip(nc.viewControllers, nc.viewControllers.dropFirst()).first { $0.1 == vc }?.0 | ||
} | ||
|
||
static func show<UI: AsyncValueUIasFlowVC>(_ vc: UI, on nc: UINavigationController) -> AnyPublisher<UI.Value, Cancel> { | ||
showVC(vc, on: nc) | ||
vc.configureBackButton(nc) | ||
return vc.value | ||
} | ||
|
||
static func showVC(_ vc: UIViewController, on nc: UINavigationController) { | ||
if let topVC = nc.topViewController as? ReplaceableInAppFlow { | ||
topVC.replace(by: vc) | ||
} | ||
else { | ||
let animated = !nc.viewControllers.isEmpty | ||
nc.pushViewController(vc, animated: animated) | ||
} | ||
} | ||
|
||
static func pop(to vc: UIViewController, delayFor time: TimeInterval = 0) -> FinshedActionPublisher { | ||
guard let nc = vc.navigationController else { | ||
assertionFailure() | ||
return finishedAction | ||
} | ||
|
||
nc.viewControllers | ||
.reversed() | ||
.prefix(while: {$0 != vc}) | ||
.forEach { ($0 as? BasicViewController)?.endValue = .fromCode } | ||
|
||
nc.popToViewController(vc, animated: true) | ||
if time > 0 { | ||
return finishedAction | ||
.delay(for: .seconds(time), scheduler: DispatchQueue.main) | ||
.eraseType() | ||
} | ||
else { | ||
return finishedAction | ||
} | ||
} | ||
|
||
static func popToRoot(of nc: UINavigationController, delayFor time: TimeInterval = 0) -> FinshedActionPublisher { | ||
guard let firstVC = nc.viewControllers.first else { return finishedAction } | ||
return pop(to: firstVC, delayFor: time) | ||
} | ||
} | ||
|
||
#endif |
Oops, something went wrong.