From 5b765fd94e76e2e4f454d204bbd7749f386dfed7 Mon Sep 17 00:00:00 2001
From: Dariusz Rybicki <dariusz@elixxir.io>
Date: Wed, 1 Jun 2022 16:19:14 +0200
Subject: [PATCH] Present errors in landing feature

---
 Example/ExampleApp/Package.swift              |  6 ++++
 .../ExampleApp/Sources/AppFeature/App.swift   |  4 ++-
 .../Sources/ErrorFeature/ErrorFeature.swift   |  2 +-
 .../LandingFeature/LandingFeature.swift       | 31 ++++++++++++++++---
 .../Sources/LandingFeature/LandingView.swift  | 12 +++++++
 .../LandingFeatureTests.swift                 |  3 ++
 6 files changed, 51 insertions(+), 7 deletions(-)

diff --git a/Example/ExampleApp/Package.swift b/Example/ExampleApp/Package.swift
index 9f02c271..3b02bf97 100644
--- a/Example/ExampleApp/Package.swift
+++ b/Example/ExampleApp/Package.swift
@@ -56,6 +56,7 @@ let package = Package(
     .target(
       name: "AppFeature",
       dependencies: [
+        .target(name: "ErrorFeature"),
         .target(name: "LandingFeature"),
         .target(name: "SessionFeature"),
         .product(
@@ -108,10 +109,15 @@ let package = Package(
     .target(
       name: "LandingFeature",
       dependencies: [
+        .target(name: "ErrorFeature"),
         .product(
           name: "ComposableArchitecture",
           package: "swift-composable-architecture"
         ),
+        .product(
+          name: "ComposablePresentation",
+          package: "swift-composable-presentation"
+        ),
         .product(
           name: "ElixxirDAppsSDK",
           package: "elixxir-dapps-sdk-swift"
diff --git a/Example/ExampleApp/Sources/AppFeature/App.swift b/Example/ExampleApp/Sources/AppFeature/App.swift
index b7446b7e..1353882a 100644
--- a/Example/ExampleApp/Sources/AppFeature/App.swift
+++ b/Example/ExampleApp/Sources/AppFeature/App.swift
@@ -1,6 +1,7 @@
 import Combine
 import ComposableArchitecture
 import ElixxirDAppsSDK
+import ErrorFeature
 import LandingFeature
 import SessionFeature
 import SwiftUI
@@ -36,7 +37,8 @@ extension AppEnvironment {
         ),
         setClient: { clientSubject.send($0) },
         bgScheduler: bgScheduler,
-        mainScheduler: mainScheduler
+        mainScheduler: mainScheduler,
+        error: ErrorEnvironment()
       ),
       session: SessionEnvironment()
     )
diff --git a/Example/ExampleApp/Sources/ErrorFeature/ErrorFeature.swift b/Example/ExampleApp/Sources/ErrorFeature/ErrorFeature.swift
index 756260a9..dc7d8b69 100644
--- a/Example/ExampleApp/Sources/ErrorFeature/ErrorFeature.swift
+++ b/Example/ExampleApp/Sources/ErrorFeature/ErrorFeature.swift
@@ -6,7 +6,7 @@ public struct ErrorState: Equatable {
     self.error = error
   }
 
-  var error: NSError
+  public var error: NSError
 }
 
 public enum ErrorAction: Equatable {}
diff --git a/Example/ExampleApp/Sources/LandingFeature/LandingFeature.swift b/Example/ExampleApp/Sources/LandingFeature/LandingFeature.swift
index a3b1c3b8..dfca1d37 100644
--- a/Example/ExampleApp/Sources/LandingFeature/LandingFeature.swift
+++ b/Example/ExampleApp/Sources/LandingFeature/LandingFeature.swift
@@ -1,21 +1,25 @@
 import Combine
 import ComposableArchitecture
 import ElixxirDAppsSDK
+import ErrorFeature
 
 public struct LandingState: Equatable {
   public init(
     hasStoredClient: Bool = false,
     isMakingClient: Bool = false,
-    isRemovingClient: Bool = false
+    isRemovingClient: Bool = false,
+    error: ErrorState? = nil
   ) {
     self.hasStoredClient = hasStoredClient
     self.isMakingClient = isMakingClient
     self.isRemovingClient = isRemovingClient
+    self.error = error
   }
 
   var hasStoredClient: Bool
   var isMakingClient: Bool
   var isRemovingClient: Bool
+  var error: ErrorState?
 }
 
 public enum LandingAction: Equatable {
@@ -26,6 +30,8 @@ public enum LandingAction: Equatable {
   case removeStoredClient
   case didRemoveStoredClient
   case didFailRemovingStoredClient(NSError)
+  case didDismissError
+  case error(ErrorAction)
 }
 
 public struct LandingEnvironment {
@@ -33,18 +39,21 @@ public struct LandingEnvironment {
     clientStorage: ClientStorage,
     setClient: @escaping (Client) -> Void,
     bgScheduler: AnySchedulerOf<DispatchQueue>,
-    mainScheduler: AnySchedulerOf<DispatchQueue>
+    mainScheduler: AnySchedulerOf<DispatchQueue>,
+    error: ErrorEnvironment
   ) {
     self.clientStorage = clientStorage
     self.setClient = setClient
     self.bgScheduler = bgScheduler
     self.mainScheduler = mainScheduler
+    self.error = error
   }
 
   public var clientStorage: ClientStorage
   public var setClient: (Client) -> Void
   public var bgScheduler: AnySchedulerOf<DispatchQueue>
   public var mainScheduler: AnySchedulerOf<DispatchQueue>
+  public var error: ErrorEnvironment
 }
 
 public let landingReducer = Reducer<LandingState, LandingAction, LandingEnvironment>
@@ -80,7 +89,7 @@ public let landingReducer = Reducer<LandingState, LandingAction, LandingEnvironm
   case .didFailMakingClient(let error):
     state.isMakingClient = false
     state.hasStoredClient = env.clientStorage.hasStoredClient()
-    // TODO: handle error
+    state.error = ErrorState(error: error)
     return .none
 
   case .removeStoredClient:
@@ -105,10 +114,21 @@ public let landingReducer = Reducer<LandingState, LandingAction, LandingEnvironm
   case .didFailRemovingStoredClient(let error):
     state.isRemovingClient = false
     state.hasStoredClient = env.clientStorage.hasStoredClient()
-    // TODO: handle error
+    state.error = ErrorState(error: error)
+    return .none
+
+  case .didDismissError:
+    state.error = nil
     return .none
   }
 }
+.presenting(
+  errorReducer,
+  state: .keyPath(\.error),
+  id: .keyPath(\.?.error),
+  action: /LandingAction.error,
+  environment: \.error
+)
 
 #if DEBUG
 extension LandingEnvironment {
@@ -116,7 +136,8 @@ extension LandingEnvironment {
     clientStorage: .failing,
     setClient: { _ in fatalError() },
     bgScheduler: .failing,
-    mainScheduler: .failing
+    mainScheduler: .failing,
+    error: .failing
   )
 }
 #endif
diff --git a/Example/ExampleApp/Sources/LandingFeature/LandingView.swift b/Example/ExampleApp/Sources/LandingFeature/LandingView.swift
index 325cbe63..a4dd2b73 100644
--- a/Example/ExampleApp/Sources/LandingFeature/LandingView.swift
+++ b/Example/ExampleApp/Sources/LandingFeature/LandingView.swift
@@ -1,4 +1,6 @@
 import ComposableArchitecture
+import ComposablePresentation
+import ErrorFeature
 import SwiftUI
 
 public struct LandingView: View {
@@ -59,6 +61,16 @@ public struct LandingView: View {
       .task {
         viewStore.send(.viewDidLoad)
       }
+      .sheet(
+        store.scope(
+          state: \.error,
+          action: LandingAction.error
+        ),
+        onDismiss: {
+          viewStore.send(.didDismissError)
+        },
+        content: ErrorView.init(store:)
+      )
     }
   }
 }
diff --git a/Example/ExampleApp/Tests/LandingFeatureTests/LandingFeatureTests.swift b/Example/ExampleApp/Tests/LandingFeatureTests/LandingFeatureTests.swift
index 3083d97a..df9791e7 100644
--- a/Example/ExampleApp/Tests/LandingFeatureTests/LandingFeatureTests.swift
+++ b/Example/ExampleApp/Tests/LandingFeatureTests/LandingFeatureTests.swift
@@ -1,4 +1,5 @@
 import ComposableArchitecture
+import ErrorFeature
 import XCTest
 @testable import LandingFeature
 
@@ -115,6 +116,7 @@ final class LandingFeatureTests: XCTestCase {
     store.receive(.didFailMakingClient(error)) {
       $0.isMakingClient = false
       $0.hasStoredClient = false
+      $0.error = ErrorState(error: error)
     }
   }
 
@@ -180,6 +182,7 @@ final class LandingFeatureTests: XCTestCase {
     store.receive(.didFailRemovingStoredClient(error)) {
       $0.isRemovingClient = false
       $0.hasStoredClient = true
+      $0.error = ErrorState(error: error)
     }
   }
 }
-- 
GitLab