diff --git a/Example/ExampleApp/.swiftpm/xcode/xcshareddata/xcschemes/LandingFeature.xcscheme b/Example/ExampleApp/.swiftpm/xcode/xcshareddata/xcschemes/LandingFeature.xcscheme new file mode 100644 index 0000000000000000000000000000000000000000..7d203fde1b21e22854fa66dfc0b2f4253280f862 --- /dev/null +++ b/Example/ExampleApp/.swiftpm/xcode/xcshareddata/xcschemes/LandingFeature.xcscheme @@ -0,0 +1,78 @@ +<?xml version="1.0" encoding="UTF-8"?> +<Scheme + LastUpgradeVersion = "1340" + version = "1.3"> + <BuildAction + parallelizeBuildables = "YES" + buildImplicitDependencies = "YES"> + <BuildActionEntries> + <BuildActionEntry + buildForTesting = "YES" + buildForRunning = "YES" + buildForProfiling = "YES" + buildForArchiving = "YES" + buildForAnalyzing = "YES"> + <BuildableReference + BuildableIdentifier = "primary" + BlueprintIdentifier = "LandingFeature" + BuildableName = "LandingFeature" + BlueprintName = "LandingFeature" + ReferencedContainer = "container:"> + </BuildableReference> + </BuildActionEntry> + </BuildActionEntries> + </BuildAction> + <TestAction + buildConfiguration = "Debug" + selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB" + selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB" + shouldUseLaunchSchemeArgsEnv = "YES" + codeCoverageEnabled = "YES"> + <Testables> + <TestableReference + skipped = "NO"> + <BuildableReference + BuildableIdentifier = "primary" + BlueprintIdentifier = "LandingFeatureTests" + BuildableName = "LandingFeatureTests" + BlueprintName = "LandingFeatureTests" + ReferencedContainer = "container:"> + </BuildableReference> + </TestableReference> + </Testables> + </TestAction> + <LaunchAction + buildConfiguration = "Debug" + selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB" + selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB" + launchStyle = "0" + useCustomWorkingDirectory = "NO" + ignoresPersistentStateOnLaunch = "NO" + debugDocumentVersioning = "YES" + debugServiceExtension = "internal" + allowLocationSimulation = "YES"> + </LaunchAction> + <ProfileAction + buildConfiguration = "Release" + shouldUseLaunchSchemeArgsEnv = "YES" + savedToolIdentifier = "" + useCustomWorkingDirectory = "NO" + debugDocumentVersioning = "YES"> + <MacroExpansion> + <BuildableReference + BuildableIdentifier = "primary" + BlueprintIdentifier = "LandingFeature" + BuildableName = "LandingFeature" + BlueprintName = "LandingFeature" + ReferencedContainer = "container:"> + </BuildableReference> + </MacroExpansion> + </ProfileAction> + <AnalyzeAction + buildConfiguration = "Debug"> + </AnalyzeAction> + <ArchiveAction + buildConfiguration = "Release" + revealArchiveInOrganizer = "YES"> + </ArchiveAction> +</Scheme> diff --git a/Example/ExampleApp/.swiftpm/xcode/xcshareddata/xcschemes/SessionFeature.xcscheme b/Example/ExampleApp/.swiftpm/xcode/xcshareddata/xcschemes/SessionFeature.xcscheme new file mode 100644 index 0000000000000000000000000000000000000000..9361c5551eb399da5f095ac23edd410d6b284503 --- /dev/null +++ b/Example/ExampleApp/.swiftpm/xcode/xcshareddata/xcschemes/SessionFeature.xcscheme @@ -0,0 +1,78 @@ +<?xml version="1.0" encoding="UTF-8"?> +<Scheme + LastUpgradeVersion = "1340" + version = "1.3"> + <BuildAction + parallelizeBuildables = "YES" + buildImplicitDependencies = "YES"> + <BuildActionEntries> + <BuildActionEntry + buildForTesting = "YES" + buildForRunning = "YES" + buildForProfiling = "YES" + buildForArchiving = "YES" + buildForAnalyzing = "YES"> + <BuildableReference + BuildableIdentifier = "primary" + BlueprintIdentifier = "SessionFeature" + BuildableName = "SessionFeature" + BlueprintName = "SessionFeature" + ReferencedContainer = "container:"> + </BuildableReference> + </BuildActionEntry> + </BuildActionEntries> + </BuildAction> + <TestAction + buildConfiguration = "Debug" + selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB" + selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB" + shouldUseLaunchSchemeArgsEnv = "YES" + codeCoverageEnabled = "YES"> + <Testables> + <TestableReference + skipped = "NO"> + <BuildableReference + BuildableIdentifier = "primary" + BlueprintIdentifier = "SessionFeatureTests" + BuildableName = "SessionFeatureTests" + BlueprintName = "SessionFeatureTests" + ReferencedContainer = "container:"> + </BuildableReference> + </TestableReference> + </Testables> + </TestAction> + <LaunchAction + buildConfiguration = "Debug" + selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB" + selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB" + launchStyle = "0" + useCustomWorkingDirectory = "NO" + ignoresPersistentStateOnLaunch = "NO" + debugDocumentVersioning = "YES" + debugServiceExtension = "internal" + allowLocationSimulation = "YES"> + </LaunchAction> + <ProfileAction + buildConfiguration = "Release" + shouldUseLaunchSchemeArgsEnv = "YES" + savedToolIdentifier = "" + useCustomWorkingDirectory = "NO" + debugDocumentVersioning = "YES"> + <MacroExpansion> + <BuildableReference + BuildableIdentifier = "primary" + BlueprintIdentifier = "SessionFeature" + BuildableName = "SessionFeature" + BlueprintName = "SessionFeature" + ReferencedContainer = "container:"> + </BuildableReference> + </MacroExpansion> + </ProfileAction> + <AnalyzeAction + buildConfiguration = "Debug"> + </AnalyzeAction> + <ArchiveAction + buildConfiguration = "Release" + revealArchiveInOrganizer = "YES"> + </ArchiveAction> +</Scheme> diff --git a/Example/ExampleApp/.swiftpm/xcode/xcshareddata/xcschemes/example-app.xcscheme b/Example/ExampleApp/.swiftpm/xcode/xcshareddata/xcschemes/example-app.xcscheme new file mode 100644 index 0000000000000000000000000000000000000000..6901d4337f3e45c7c983250b1a6b7c357e540062 --- /dev/null +++ b/Example/ExampleApp/.swiftpm/xcode/xcshareddata/xcschemes/example-app.xcscheme @@ -0,0 +1,126 @@ +<?xml version="1.0" encoding="UTF-8"?> +<Scheme + LastUpgradeVersion = "1340" + version = "1.3"> + <BuildAction + parallelizeBuildables = "YES" + buildImplicitDependencies = "YES"> + <BuildActionEntries> + <BuildActionEntry + buildForTesting = "YES" + buildForRunning = "YES" + buildForProfiling = "YES" + buildForArchiving = "YES" + buildForAnalyzing = "YES"> + <BuildableReference + BuildableIdentifier = "primary" + BlueprintIdentifier = "AppFeature" + BuildableName = "AppFeature" + BlueprintName = "AppFeature" + ReferencedContainer = "container:"> + </BuildableReference> + </BuildActionEntry> + <BuildActionEntry + buildForTesting = "YES" + buildForRunning = "YES" + buildForProfiling = "YES" + buildForArchiving = "YES" + buildForAnalyzing = "YES"> + <BuildableReference + BuildableIdentifier = "primary" + BlueprintIdentifier = "LandingFeature" + BuildableName = "LandingFeature" + BlueprintName = "LandingFeature" + ReferencedContainer = "container:"> + </BuildableReference> + </BuildActionEntry> + <BuildActionEntry + buildForTesting = "YES" + buildForRunning = "YES" + buildForProfiling = "YES" + buildForArchiving = "YES" + buildForAnalyzing = "YES"> + <BuildableReference + BuildableIdentifier = "primary" + BlueprintIdentifier = "SessionFeature" + BuildableName = "SessionFeature" + BlueprintName = "SessionFeature" + ReferencedContainer = "container:"> + </BuildableReference> + </BuildActionEntry> + </BuildActionEntries> + </BuildAction> + <TestAction + buildConfiguration = "Debug" + selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB" + selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB" + shouldUseLaunchSchemeArgsEnv = "YES" + codeCoverageEnabled = "YES"> + <Testables> + <TestableReference + skipped = "NO"> + <BuildableReference + BuildableIdentifier = "primary" + BlueprintIdentifier = "AppFeatureTests" + BuildableName = "AppFeatureTests" + BlueprintName = "AppFeatureTests" + ReferencedContainer = "container:"> + </BuildableReference> + </TestableReference> + <TestableReference + skipped = "NO"> + <BuildableReference + BuildableIdentifier = "primary" + BlueprintIdentifier = "LandingFeatureTests" + BuildableName = "LandingFeatureTests" + BlueprintName = "LandingFeatureTests" + ReferencedContainer = "container:"> + </BuildableReference> + </TestableReference> + <TestableReference + skipped = "NO"> + <BuildableReference + BuildableIdentifier = "primary" + BlueprintIdentifier = "SessionFeatureTests" + BuildableName = "SessionFeatureTests" + BlueprintName = "SessionFeatureTests" + ReferencedContainer = "container:"> + </BuildableReference> + </TestableReference> + </Testables> + </TestAction> + <LaunchAction + buildConfiguration = "Debug" + selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB" + selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB" + launchStyle = "0" + useCustomWorkingDirectory = "NO" + ignoresPersistentStateOnLaunch = "NO" + debugDocumentVersioning = "YES" + debugServiceExtension = "internal" + allowLocationSimulation = "YES"> + </LaunchAction> + <ProfileAction + buildConfiguration = "Release" + shouldUseLaunchSchemeArgsEnv = "YES" + savedToolIdentifier = "" + useCustomWorkingDirectory = "NO" + debugDocumentVersioning = "YES"> + <MacroExpansion> + <BuildableReference + BuildableIdentifier = "primary" + BlueprintIdentifier = "AppFeature" + BuildableName = "AppFeature" + BlueprintName = "AppFeature" + ReferencedContainer = "container:"> + </BuildableReference> + </MacroExpansion> + </ProfileAction> + <AnalyzeAction + buildConfiguration = "Debug"> + </AnalyzeAction> + <ArchiveAction + buildConfiguration = "Release" + revealArchiveInOrganizer = "YES"> + </ArchiveAction> +</Scheme> diff --git a/Example/ExampleApp/Package.swift b/Example/ExampleApp/Package.swift index 3e3cec1e459542efe3daf1f2030cf1d3afd740d9..409928d46b38efc2d747f0fdb1418c5a74e91761 100644 --- a/Example/ExampleApp/Package.swift +++ b/Example/ExampleApp/Package.swift @@ -3,7 +3,7 @@ import PackageDescription let package = Package( - name: "ExampleApp", + name: "example-app", platforms: [ .iOS(.v15), ], @@ -12,6 +12,14 @@ let package = Package( name: "AppFeature", targets: ["AppFeature"] ), + .library( + name: "LandingFeature", + targets: ["LandingFeature"] + ), + .library( + name: "SessionFeature", + targets: ["SessionFeature"] + ), ], dependencies: [ .package(path: "../../"), // elixxir-dapps-sdk-swift @@ -28,6 +36,8 @@ let package = Package( .target( name: "AppFeature", dependencies: [ + .target(name: "LandingFeature"), + .target(name: "SessionFeature"), .product( name: "ElixxirDAppsSDK", package: "elixxir-dapps-sdk-swift" @@ -48,5 +58,35 @@ let package = Package( .target(name: "AppFeature"), ] ), + .target( + name: "LandingFeature", + dependencies: [ + .product( + name: "ComposableArchitecture", + package: "swift-composable-architecture" + ), + ] + ), + .testTarget( + name: "LandingFeatureTests", + dependencies: [ + .target(name: "LandingFeature"), + ] + ), + .target( + name: "SessionFeature", + dependencies: [ + .product( + name: "ComposableArchitecture", + package: "swift-composable-architecture" + ), + ] + ), + .testTarget( + name: "SessionFeatureTests", + dependencies: [ + .target(name: "SessionFeature"), + ] + ), ] ) diff --git a/Example/ExampleApp/Sources/AppFeature/App.swift b/Example/ExampleApp/Sources/AppFeature/App.swift index 47af3dbcd406028e21210749b8a018d53272810b..0374fa69236b9ad17bcbe76cf552fe9b773625ea 100644 --- a/Example/ExampleApp/Sources/AppFeature/App.swift +++ b/Example/ExampleApp/Sources/AppFeature/App.swift @@ -1,10 +1,26 @@ +import ComposableArchitecture +import LandingFeature +import SessionFeature import SwiftUI @main struct App: SwiftUI.App { var body: some Scene { WindowGroup { - AppView() + AppView(store: Store( + initialState: AppState(), + reducer: appReducer, + environment: .live() + )) } } } + +extension AppEnvironment { + static func live() -> AppEnvironment { + AppEnvironment( + landing: LandingEnvironment(), + session: SessionEnvironment() + ) + } +} diff --git a/Example/ExampleApp/Sources/AppFeature/AppFeature.swift b/Example/ExampleApp/Sources/AppFeature/AppFeature.swift new file mode 100644 index 0000000000000000000000000000000000000000..07d6812aab59216d9397acc3404a12e2774f0e30 --- /dev/null +++ b/Example/ExampleApp/Sources/AppFeature/AppFeature.swift @@ -0,0 +1,82 @@ +import ComposableArchitecture +import ComposablePresentation +import LandingFeature +import SessionFeature + +struct AppState: Equatable { + enum Scene: Equatable { + case landing(LandingState) + case session(SessionState) + } + + var scene: Scene = .landing(LandingState()) +} + +extension AppState.Scene { + var asLanding: LandingState? { + get { + guard case .landing(let state) = self else { return nil } + return state + } + set { + guard let newValue = newValue else { return } + self = .landing(newValue) + } + } + + var asSession: SessionState? { + get { + guard case .session(let state) = self else { return nil } + return state + } + set { + guard let newValue = newValue else { return } + self = .session(newValue) + } + } +} + +enum AppAction: Equatable { + case viewDidLoad + case landing(LandingAction) + case session(SessionAction) +} + +struct AppEnvironment { + var landing: LandingEnvironment + var session: SessionEnvironment +} + +let appReducer = Reducer<AppState, AppAction, AppEnvironment> +{ state, action, env in + switch action { + case .viewDidLoad: + return .none + + case .landing(_), .session(_): + return .none + } +} +.presenting( + landingReducer, + state: .keyPath(\.scene.asLanding), + id: .notNil(), + action: /AppAction.landing, + environment: \.landing +) +.presenting( + sessionReducer, + state: .keyPath(\.scene.asSession), + id: .notNil(), + action: /AppAction.session, + environment: \.session +) + +#if DEBUG +extension AppEnvironment { + static let failing = AppEnvironment( + landing: .failing, + session: .failing + ) +} +#endif diff --git a/Example/ExampleApp/Sources/AppFeature/AppView.swift b/Example/ExampleApp/Sources/AppFeature/AppView.swift index ac856328e6fb739adca0b5b9acf2ac6b1a4f77f4..f2af2d1a349f0fef67c1c929f72dd687b4d60f3c 100644 --- a/Example/ExampleApp/Sources/AppFeature/AppView.swift +++ b/Example/ExampleApp/Sources/AppFeature/AppView.swift @@ -1,16 +1,76 @@ +import ComposableArchitecture +import LandingFeature +import SessionFeature import SwiftUI struct AppView: View { + let store: Store<AppState, AppAction> + + struct ViewState: Equatable { + enum Scene: Equatable { + case landing + case session + } + + let scene: Scene + + init(state: AppState) { + switch state.scene { + case .landing(_): + self.scene = .landing + + case .session(_): + self.scene = .session + } + } + } + var body: some View { - Text("AppView") - .padding() + WithViewStore(store.scope(state: ViewState.init)) { viewStore in + ZStack { + SwitchStore(store.scope(state: \.scene)) { + CaseLet( + state: /AppState.Scene.landing, + action: AppAction.landing, + then: { store in + NavigationView { + LandingView(store: store) + } + .navigationViewStyle(.stack) + .transition(.move(edge: .leading)) + } + ) + + CaseLet( + state: /AppState.Scene.session, + action: AppAction.session, + then: { store in + NavigationView { + SessionView(store: store) + } + .navigationViewStyle(.stack) + .transition(.move(edge: .trailing)) + } + ) + } + .frame(maxWidth: .infinity, maxHeight: .infinity) + } + .animation(.default, value: viewStore.scene) + .task { + viewStore.send(.viewDidLoad) + } + } } } #if DEBUG struct AppView_Previews: PreviewProvider { static var previews: some View { - AppView() + AppView(store: Store( + initialState: AppState(), + reducer: .empty, + environment: () + )) } } #endif diff --git a/Example/ExampleApp/Sources/LandingFeature/LandingFeature.swift b/Example/ExampleApp/Sources/LandingFeature/LandingFeature.swift new file mode 100644 index 0000000000000000000000000000000000000000..b145b2500009159d0df667e3befcf6c7e21fed53 --- /dev/null +++ b/Example/ExampleApp/Sources/LandingFeature/LandingFeature.swift @@ -0,0 +1,27 @@ +import ComposableArchitecture + +public struct LandingState: Equatable { + public init() {} +} + +public enum LandingAction: Equatable { + case viewDidLoad +} + +public struct LandingEnvironment { + public init() {} +} + +public let landingReducer = Reducer<LandingState, LandingAction, LandingEnvironment> +{ state, action, env in + switch action { + case .viewDidLoad: + return .none + } +} + +#if DEBUG +extension LandingEnvironment { + public static let failing = LandingEnvironment() +} +#endif diff --git a/Example/ExampleApp/Sources/LandingFeature/LandingView.swift b/Example/ExampleApp/Sources/LandingFeature/LandingView.swift new file mode 100644 index 0000000000000000000000000000000000000000..5ebc5e89f90862a08cf02ed96e9541c108fcb67c --- /dev/null +++ b/Example/ExampleApp/Sources/LandingFeature/LandingView.swift @@ -0,0 +1,38 @@ +import ComposableArchitecture +import SwiftUI + +public struct LandingView: View { + public init(store: Store<LandingState, LandingAction>) { + self.store = store + } + + let store: Store<LandingState, LandingAction> + + struct ViewState: Equatable { + init(state: LandingState) {} + } + + public var body: some View { + WithViewStore(store.scope(state: ViewState.init)) { viewStore in + Text("LandingView") + .task { + viewStore.send(.viewDidLoad) + } + } + } +} + +#if DEBUG +public struct LandingView_Previews: PreviewProvider { + public static var previews: some View { + NavigationView { + LandingView(store: .init( + initialState: .init(), + reducer: .empty, + environment: () + )) + } + .navigationViewStyle(.stack) + } +} +#endif diff --git a/Example/ExampleApp/Sources/SessionFeature/SessionFeature.swift b/Example/ExampleApp/Sources/SessionFeature/SessionFeature.swift new file mode 100644 index 0000000000000000000000000000000000000000..3b7dd16fe1aecf92658f3d9e2508b2fefc20927a --- /dev/null +++ b/Example/ExampleApp/Sources/SessionFeature/SessionFeature.swift @@ -0,0 +1,27 @@ +import ComposableArchitecture + +public struct SessionState: Equatable { + public init() {} +} + +public enum SessionAction: Equatable { + case viewDidLoad +} + +public struct SessionEnvironment { + public init() {} +} + +public let sessionReducer = Reducer<SessionState, SessionAction, SessionEnvironment> +{ state, action, env in + switch action { + case .viewDidLoad: + return .none + } +} + +#if DEBUG +extension SessionEnvironment { + public static let failing = SessionEnvironment() +} +#endif diff --git a/Example/ExampleApp/Sources/SessionFeature/SessionView.swift b/Example/ExampleApp/Sources/SessionFeature/SessionView.swift new file mode 100644 index 0000000000000000000000000000000000000000..781ee440613cdf84130a362ea97c4397c7a13d0f --- /dev/null +++ b/Example/ExampleApp/Sources/SessionFeature/SessionView.swift @@ -0,0 +1,35 @@ +import ComposableArchitecture +import SwiftUI + +public struct SessionView: View { + public init(store: Store<SessionState, SessionAction>) { + self.store = store + } + + let store: Store<SessionState, SessionAction> + + struct ViewState: Equatable { + init(state: SessionState) {} + } + + public var body: some View { + WithViewStore(store.scope(state: ViewState.init)) { viewStore in + Text("SessionView") + .task { + viewStore.send(.viewDidLoad) + } + } + } +} + +#if DEBUG +public struct SessionView_Previews: PreviewProvider { + public static var previews: some View { + SessionView(store: .init( + initialState: .init(), + reducer: .empty, + environment: () + )) + } +} +#endif diff --git a/Example/ExampleApp/Tests/AppFeatureTests/AppFeatureTests.swift b/Example/ExampleApp/Tests/AppFeatureTests/AppFeatureTests.swift index 2382530488092aa3472cca2e681a85b4bb2a2a79..2fe7038c8df495223184b2fa8303ecf31678a02b 100644 --- a/Example/ExampleApp/Tests/AppFeatureTests/AppFeatureTests.swift +++ b/Example/ExampleApp/Tests/AppFeatureTests/AppFeatureTests.swift @@ -1,8 +1,15 @@ +import ComposableArchitecture import XCTest @testable import AppFeature final class AppFeatureTests: XCTestCase { - func testExample() throws { - XCTAssert(true) + func testViewDidLoad() throws { + let store = TestStore( + initialState: AppState(), + reducer: appReducer, + environment: .failing + ) + + store.send(.viewDidLoad) } } diff --git a/Example/ExampleApp/Tests/LandingFeatureTests/LandingFeatureTests.swift b/Example/ExampleApp/Tests/LandingFeatureTests/LandingFeatureTests.swift new file mode 100644 index 0000000000000000000000000000000000000000..389863f66901186aecf835bc3c55da017b155bca --- /dev/null +++ b/Example/ExampleApp/Tests/LandingFeatureTests/LandingFeatureTests.swift @@ -0,0 +1,15 @@ +import ComposableArchitecture +import XCTest +@testable import LandingFeature + +final class LandingFeatureTests: XCTestCase { + func testViewDidLoad() throws { + let store = TestStore( + initialState: LandingState(), + reducer: landingReducer, + environment: .failing + ) + + store.send(.viewDidLoad) + } +} diff --git a/Example/ExampleApp/Tests/SessionFeatureTests/SessionFeatureTests.swift b/Example/ExampleApp/Tests/SessionFeatureTests/SessionFeatureTests.swift new file mode 100644 index 0000000000000000000000000000000000000000..5ed11e32b14f6ee73da18a53cee600418122f87c --- /dev/null +++ b/Example/ExampleApp/Tests/SessionFeatureTests/SessionFeatureTests.swift @@ -0,0 +1,15 @@ +import ComposableArchitecture +import XCTest +@testable import SessionFeature + +final class SessionFeatureTests: XCTestCase { + func testViewDidLoad() throws { + let store = TestStore( + initialState: SessionState(), + reducer: sessionReducer, + environment: .failing + ) + + store.send(.viewDidLoad) + } +}