From a537f0f3074a678140627f213feb48e93fc0e587 Mon Sep 17 00:00:00 2001
From: Dariusz Rybicki <dariusz@elixxir.io>
Date: Wed, 31 Aug 2022 13:25:15 +0100
Subject: [PATCH 01/10] Update dependencies

---
 .../xcshareddata/swiftpm/Package.resolved            | 12 ++++++------
 1 file changed, 6 insertions(+), 6 deletions(-)

diff --git a/Examples/xx-messenger/XXMessenger.xcworkspace/xcshareddata/swiftpm/Package.resolved b/Examples/xx-messenger/XXMessenger.xcworkspace/xcshareddata/swiftpm/Package.resolved
index c330aa94..b9909a30 100644
--- a/Examples/xx-messenger/XXMessenger.xcworkspace/xcshareddata/swiftpm/Package.resolved
+++ b/Examples/xx-messenger/XXMessenger.xcworkspace/xcshareddata/swiftpm/Package.resolved
@@ -14,8 +14,8 @@
       "kind" : "remoteSourceControl",
       "location" : "https://github.com/pointfreeco/combine-schedulers",
       "state" : {
-        "revision" : "8fee20f993e64bbbf22bc3e3f444758ac2d05692",
-        "version" : "0.7.2"
+        "revision" : "9e42b4b0453da417a44daa17174103e7d1c5be07",
+        "version" : "0.7.3"
       }
     },
     {
@@ -59,8 +59,8 @@
       "kind" : "remoteSourceControl",
       "location" : "https://github.com/pointfreeco/swift-composable-architecture.git",
       "state" : {
-        "revision" : "108e3a536fcebb16c4f247ef92c2d7326baf9fe3",
-        "version" : "0.39.0"
+        "revision" : "a518935116b2bada7234f47073159b433d432af1",
+        "version" : "0.39.1"
       }
     },
     {
@@ -68,8 +68,8 @@
       "kind" : "remoteSourceControl",
       "location" : "https://github.com/darrarski/swift-composable-presentation.git",
       "state" : {
-        "revision" : "1f4d17fae1f7ed41cbed17929083190fd9a78ee6",
-        "version" : "0.5.2"
+        "revision" : "bdb7df9476cf29e8379fc50aa03848dd6c8033d9",
+        "version" : "0.5.3"
       }
     },
     {
-- 
GitLab


From f1f148bbd7fd50d483e94fe500de06dcf327b811 Mon Sep 17 00:00:00 2001
From: Dariusz Rybicki <dariusz@elixxir.io>
Date: Wed, 31 Aug 2022 13:32:29 +0100
Subject: [PATCH 02/10] Add missing imports

---
 Examples/xx-messenger/Sources/HomeFeature/HomeFeature.swift     | 1 +
 Examples/xx-messenger/Sources/LaunchFeature/LaunchFeature.swift | 1 +
 2 files changed, 2 insertions(+)

diff --git a/Examples/xx-messenger/Sources/HomeFeature/HomeFeature.swift b/Examples/xx-messenger/Sources/HomeFeature/HomeFeature.swift
index 6b61b099..499bed78 100644
--- a/Examples/xx-messenger/Sources/HomeFeature/HomeFeature.swift
+++ b/Examples/xx-messenger/Sources/HomeFeature/HomeFeature.swift
@@ -1,4 +1,5 @@
 import ComposableArchitecture
+import Foundation
 import XXClient
 import XXMessengerClient
 
diff --git a/Examples/xx-messenger/Sources/LaunchFeature/LaunchFeature.swift b/Examples/xx-messenger/Sources/LaunchFeature/LaunchFeature.swift
index 39e3efdf..a7bf89ce 100644
--- a/Examples/xx-messenger/Sources/LaunchFeature/LaunchFeature.swift
+++ b/Examples/xx-messenger/Sources/LaunchFeature/LaunchFeature.swift
@@ -2,6 +2,7 @@ import AppCore
 import Combine
 import ComposableArchitecture
 import ComposablePresentation
+import Foundation
 import RegisterFeature
 import RestoreFeature
 import WelcomeFeature
-- 
GitLab


From 4197d79d240a0398ff1ed021f6289396f09d73c3 Mon Sep 17 00:00:00 2001
From: Dariusz Rybicki <dariusz@elixxir.io>
Date: Wed, 31 Aug 2022 14:55:30 +0100
Subject: [PATCH 03/10] Move app launch logic to AppFeature

---
 Examples/xx-messenger/Package.swift           |   2 -
 .../AppFeature/AppEnvironment+Live.swift      |  32 ++--
 .../Sources/AppFeature/AppFeature.swift       |  99 +++++++++--
 .../Sources/AppFeature/AppView.swift          |  97 +++++++++--
 .../AppFeatureTests/AppFeatureTests.swift     | 160 +++++++++++++++++-
 5 files changed, 333 insertions(+), 57 deletions(-)

diff --git a/Examples/xx-messenger/Package.swift b/Examples/xx-messenger/Package.swift
index 5eb7bede..b91c4340 100644
--- a/Examples/xx-messenger/Package.swift
+++ b/Examples/xx-messenger/Package.swift
@@ -69,8 +69,6 @@ let package = Package(
       dependencies: [
         .target(name: "AppCore"),
         .target(name: "HomeFeature"),
-        .target(name: "LaunchFeature"),
-        .target(name: "RegisterFeature"),
         .target(name: "RestoreFeature"),
         .target(name: "WelcomeFeature"),
         .product(name: "ComposableArchitecture", package: "swift-composable-architecture"),
diff --git a/Examples/xx-messenger/Sources/AppFeature/AppEnvironment+Live.swift b/Examples/xx-messenger/Sources/AppFeature/AppEnvironment+Live.swift
index ff97f803..eec6f199 100644
--- a/Examples/xx-messenger/Sources/AppFeature/AppEnvironment+Live.swift
+++ b/Examples/xx-messenger/Sources/AppFeature/AppEnvironment+Live.swift
@@ -1,7 +1,6 @@
 import AppCore
 import Foundation
 import HomeFeature
-import LaunchFeature
 import RegisterFeature
 import RestoreFeature
 import WelcomeFeature
@@ -17,31 +16,20 @@ extension AppEnvironment {
     let bgQueue = DispatchQueue.global(qos: .background).eraseToAnyScheduler()
 
     return AppEnvironment(
-      launch: {
-        LaunchEnvironment(
-          dbManager: dbManager,
+      dbManager: dbManager,
+      messenger: messenger,
+      mainQueue: mainQueue,
+      bgQueue: bgQueue,
+      welcome: {
+        WelcomeEnvironment(
           messenger: messenger,
           mainQueue: mainQueue,
-          bgQueue: bgQueue,
-          welcome: {
-            WelcomeEnvironment(
-              messenger: messenger,
-              mainQueue: mainQueue,
-              bgQueue: bgQueue
-            )
-          },
-          restore: {
-            RestoreEnvironment()
-          },
-          register: {
-            RegisterEnvironment(
-              messenger: messenger,
-              mainQueue: mainQueue,
-              bgQueue: bgQueue
-            )
-          }
+          bgQueue: bgQueue
         )
       },
+      restore: {
+        RestoreEnvironment()
+      },
       home: {
         HomeEnvironment(
           messenger: messenger,
diff --git a/Examples/xx-messenger/Sources/AppFeature/AppFeature.swift b/Examples/xx-messenger/Sources/AppFeature/AppFeature.swift
index 18927c0b..535d69fc 100644
--- a/Examples/xx-messenger/Sources/AppFeature/AppFeature.swift
+++ b/Examples/xx-messenger/Sources/AppFeature/AppFeature.swift
@@ -1,21 +1,33 @@
+import AppCore
+import Combine
 import ComposableArchitecture
 import ComposablePresentation
+import Foundation
 import HomeFeature
-import LaunchFeature
+import RestoreFeature
+import WelcomeFeature
+import XXMessengerClient
 
 struct AppState: Equatable {
   enum Screen: Equatable {
-    case launch(LaunchState)
+    case loading
+    case welcome(WelcomeState)
+    case restore(RestoreState)
     case home(HomeState)
+    case failure(String)
   }
 
-  var screen: Screen = .launch(LaunchState())
+  @BindableState var screen: Screen = .loading
 }
 
 extension AppState.Screen {
-  var asLaunch: LaunchState? {
-    get { (/AppState.Screen.launch).extract(from: self) }
-    set { if let newValue = newValue { self = .launch(newValue) } }
+  var asWelcome: WelcomeState? {
+    get { (/AppState.Screen.welcome).extract(from: self) }
+    set { if let newValue = newValue { self = .welcome(newValue) } }
+  }
+  var asRestore: RestoreState? {
+    get { (/AppState.Screen.restore).extract(from: self) }
+    set { if let state = newValue { self = .restore(state) } }
   }
   var asHome: HomeState? {
     get { (/AppState.Screen.home).extract(from: self) }
@@ -23,19 +35,32 @@ extension AppState.Screen {
   }
 }
 
-enum AppAction: Equatable {
+enum AppAction: Equatable, BindableAction {
+  case start
+  case binding(BindingAction<AppState>)
+  case welcome(WelcomeAction)
+  case restore(RestoreAction)
   case home(HomeAction)
-  case launch(LaunchAction)
 }
 
 struct AppEnvironment {
-  var launch: () -> LaunchEnvironment
+  var dbManager: DBManager
+  var messenger: Messenger
+  var mainQueue: AnySchedulerOf<DispatchQueue>
+  var bgQueue: AnySchedulerOf<DispatchQueue>
+  var welcome: () -> WelcomeEnvironment
+  var restore: () -> RestoreEnvironment
   var home: () -> HomeEnvironment
 }
 
 extension AppEnvironment {
   static let unimplemented = AppEnvironment(
-    launch: { .unimplemented },
+    dbManager: .unimplemented,
+    messenger: .unimplemented,
+    mainQueue: .unimplemented,
+    bgQueue: .unimplemented,
+    welcome: { .unimplemented },
+    restore: { .unimplemented },
     home: { .unimplemented }
   )
 }
@@ -43,20 +68,60 @@ extension AppEnvironment {
 let appReducer = Reducer<AppState, AppAction, AppEnvironment>
 { state, action, env in
   switch action {
-  case .launch(.finished):
-    state.screen = .home(HomeState())
+  case .start, .welcome(.finished), .restore(.finished):
+    state.screen = .loading
+    return .run { subscriber in
+      do {
+        if env.dbManager.hasDB() == false {
+          try env.dbManager.makeDB()
+        }
+
+        if env.messenger.isLoaded() == false {
+          if env.messenger.isCreated() == false {
+            subscriber.send(.set(\.$screen, .welcome(WelcomeState())))
+            subscriber.send(completion: .finished)
+            return AnyCancellable {}
+          }
+          try env.messenger.load()
+        }
+
+        subscriber.send(.set(\.$screen, .home(HomeState())))
+      } catch {
+        subscriber.send(.set(\.$screen, .failure(error.localizedDescription)))
+      }
+      subscriber.send(completion: .finished)
+      return AnyCancellable {}
+    }
+    .subscribe(on: env.bgQueue)
+    .receive(on: env.mainQueue)
+    .eraseToEffect()
+
+  case .welcome(.restoreTapped):
+    state.screen = .restore(RestoreState())
     return .none
 
-  case .launch(_), .home(_):
+  case .welcome(.failed(let failure)):
+    state.screen = .failure(failure)
+    return .none
+
+  case .binding(_), .welcome(_), .restore(_), .home(_):
     return .none
   }
 }
+.binding()
+.presenting(
+  welcomeReducer,
+  state: .keyPath(\.screen.asWelcome),
+  id: .notNil(),
+  action: /AppAction.welcome,
+  environment: { $0.welcome() }
+)
 .presenting(
-  launchReducer,
-  state: .keyPath(\.screen.asLaunch),
+  restoreReducer,
+  state: .keyPath(\.screen.asRestore),
   id: .notNil(),
-  action: /AppAction.launch,
-  environment: { $0.launch() }
+  action: /AppAction.restore,
+  environment: { $0.restore() }
 )
 .presenting(
   homeReducer,
diff --git a/Examples/xx-messenger/Sources/AppFeature/AppView.swift b/Examples/xx-messenger/Sources/AppFeature/AppView.swift
index 317560a3..2af6d352 100644
--- a/Examples/xx-messenger/Sources/AppFeature/AppView.swift
+++ b/Examples/xx-messenger/Sources/AppFeature/AppView.swift
@@ -1,19 +1,26 @@
 import ComposableArchitecture
-import SwiftUI
 import HomeFeature
-import LaunchFeature
+import RestoreFeature
+import SwiftUI
+import WelcomeFeature
 
 struct AppView: View {
   let store: Store<AppState, AppAction>
 
   enum ViewState: Equatable {
-    case launch
+    case loading
+    case welcome
+    case restore
     case home
+    case failure(String)
 
     init(_ state: AppState) {
       switch state.screen {
-      case .launch(_): self = .launch
+      case .loading: self = .loading
+      case .welcome(_): self = .welcome
+      case .restore(_): self = .restore
       case .home(_): self = .home
+      case .failure(let failure): self = .failure(failure)
       }
     }
   }
@@ -21,20 +28,53 @@ struct AppView: View {
   var body: some View {
     WithViewStore(store.scope(state: ViewState.init)) { viewStore in
       ZStack {
-        SwitchStore(store.scope(state: \.screen)) {
-          CaseLet(
-            state: /AppState.Screen.launch,
-            action: AppAction.launch,
+        switch viewStore.state {
+        case .loading:
+          ProgressView {
+            Text("Loading")
+          }
+          .controlSize(.large)
+          .frame(maxWidth: .infinity, maxHeight: .infinity)
+          .transition(.opacity)
+
+        case .welcome:
+          IfLetStore(
+            store.scope(
+              state: { (/AppState.Screen.welcome).extract(from: $0.screen) },
+              action: AppAction.welcome
+            ),
+            then: { store in
+              WelcomeView(store: store)
+                .frame(maxWidth: .infinity, maxHeight: .infinity)
+                .transition(.asymmetric(
+                  insertion: .move(edge: .trailing),
+                  removal: .opacity
+                ))
+            }
+          )
+
+        case .restore:
+          IfLetStore(
+            store.scope(
+              state: { (/AppState.Screen.restore).extract(from: $0.screen) },
+              action: AppAction.restore
+            ),
             then: { store in
-              LaunchView(store: store)
+              RestoreView(store: store)
                 .frame(maxWidth: .infinity, maxHeight: .infinity)
-                .transition(.opacity)
+                .transition(.asymmetric(
+                  insertion: .move(edge: .trailing),
+                  removal: .opacity
+                ))
             }
           )
 
-          CaseLet(
-            state: /AppState.Screen.home,
-            action: AppAction.home,
+        case .home:
+          IfLetStore(
+            store.scope(
+              state: { (/AppState.Screen.home).extract(from: $0.screen) },
+              action: AppAction.home
+            ),
             then: { store in
               HomeView(store: store)
                 .frame(maxWidth: .infinity, maxHeight: .infinity)
@@ -44,9 +84,40 @@ struct AppView: View {
                 ))
             }
           )
+
+        case .failure(let failure):
+          NavigationView {
+            VStack(spacing: 0) {
+              ScrollView {
+                Text(failure)
+                  .frame(maxWidth: .infinity, alignment: .leading)
+                  .padding()
+              }
+
+              Divider()
+
+              Button {
+                viewStore.send(.start)
+              } label: {
+                Text("Retry")
+                  .frame(maxWidth: .infinity)
+              }
+              .buttonStyle(.borderedProminent)
+              .controlSize(.large)
+              .padding()
+            }
+            .navigationTitle("Error")
+          }
+          .navigationViewStyle(.stack)
+          .frame(maxWidth: .infinity, maxHeight: .infinity)
+          .transition(.asymmetric(
+            insertion: .move(edge: .trailing),
+            removal: .opacity
+          ))
         }
       }
       .animation(.default, value: viewStore.state)
+      .task { viewStore.send(.start) }
     }
   }
 }
diff --git a/Examples/xx-messenger/Tests/AppFeatureTests/AppFeatureTests.swift b/Examples/xx-messenger/Tests/AppFeatureTests/AppFeatureTests.swift
index 6563da86..166b6325 100644
--- a/Examples/xx-messenger/Tests/AppFeatureTests/AppFeatureTests.swift
+++ b/Examples/xx-messenger/Tests/AppFeatureTests/AppFeatureTests.swift
@@ -1,19 +1,173 @@
 import ComposableArchitecture
 import HomeFeature
+import RestoreFeature
+import WelcomeFeature
 import XCTest
 @testable import AppFeature
 
-@MainActor
 final class AppFeatureTests: XCTestCase {
-  func testLaunchFinished() async throws {
+  func testStartWithoutMessengerCreated() {
     let store = TestStore(
       initialState: AppState(),
       reducer: appReducer,
       environment: .unimplemented
     )
 
-    await store.send(.launch(.finished)) {
+    let mainQueue = DispatchQueue.test
+    let bgQueue = DispatchQueue.test
+    var didMakeDB = 0
+
+    store.environment.mainQueue = mainQueue.eraseToAnyScheduler()
+    store.environment.bgQueue = bgQueue.eraseToAnyScheduler()
+    store.environment.dbManager.hasDB.run = { false }
+    store.environment.dbManager.makeDB.run = { didMakeDB += 1 }
+    store.environment.messenger.isLoaded.run = { false }
+    store.environment.messenger.isCreated.run = { false }
+
+    store.send(.start)
+
+    bgQueue.advance()
+
+    XCTAssertNoDifference(didMakeDB, 1)
+
+    mainQueue.advance()
+
+    store.receive(.set(\.$screen, .welcome(WelcomeState()))) {
+      $0.screen = .welcome(WelcomeState())
+    }
+  }
+
+  func testStartWithMessengerCreated() {
+    let store = TestStore(
+      initialState: AppState(),
+      reducer: appReducer,
+      environment: .unimplemented
+    )
+
+    let mainQueue = DispatchQueue.test
+    let bgQueue = DispatchQueue.test
+    var didMakeDB = 0
+    var messengerDidLoad = 0
+
+    store.environment.mainQueue = mainQueue.eraseToAnyScheduler()
+    store.environment.bgQueue = bgQueue.eraseToAnyScheduler()
+    store.environment.dbManager.hasDB.run = { false }
+    store.environment.dbManager.makeDB.run = { didMakeDB += 1 }
+    store.environment.messenger.isLoaded.run = { false }
+    store.environment.messenger.isCreated.run = { true }
+    store.environment.messenger.load.run = { messengerDidLoad += 1 }
+
+    store.send(.start)
+
+    bgQueue.advance()
+
+    XCTAssertNoDifference(didMakeDB, 1)
+    XCTAssertNoDifference(messengerDidLoad, 1)
+
+    mainQueue.advance()
+
+    store.receive(.set(\.$screen, .home(HomeState()))) {
+      $0.screen = .home(HomeState())
+    }
+  }
+
+  func testWelcomeFinished() {
+    let store = TestStore(
+      initialState: AppState(
+        screen: .welcome(WelcomeState())
+      ),
+      reducer: appReducer,
+      environment: .unimplemented
+    )
+
+    let mainQueue = DispatchQueue.test
+    let bgQueue = DispatchQueue.test
+    var messengerDidLoad = 0
+
+    store.environment.mainQueue = mainQueue.eraseToAnyScheduler()
+    store.environment.bgQueue = bgQueue.eraseToAnyScheduler()
+    store.environment.dbManager.hasDB.run = { true }
+    store.environment.messenger.isLoaded.run = { false }
+    store.environment.messenger.isCreated.run = { true }
+    store.environment.messenger.load.run = { messengerDidLoad += 1 }
+
+    store.send(.welcome(.finished)) {
+      $0.screen = .loading
+    }
+
+    bgQueue.advance()
+
+    XCTAssertNoDifference(messengerDidLoad, 1)
+
+    mainQueue.advance()
+
+    store.receive(.set(\.$screen, .home(HomeState()))) {
+      $0.screen = .home(HomeState())
+    }
+  }
+
+  func testRestoreFinished() {
+    let store = TestStore(
+      initialState: AppState(
+        screen: .restore(RestoreState())
+      ),
+      reducer: appReducer,
+      environment: .unimplemented
+    )
+
+    let mainQueue = DispatchQueue.test
+    let bgQueue = DispatchQueue.test
+    var messengerDidLoad = 0
+
+    store.environment.mainQueue = mainQueue.eraseToAnyScheduler()
+    store.environment.bgQueue = bgQueue.eraseToAnyScheduler()
+    store.environment.dbManager.hasDB.run = { true }
+    store.environment.messenger.isLoaded.run = { false }
+    store.environment.messenger.isCreated.run = { true }
+    store.environment.messenger.load.run = { messengerDidLoad += 1 }
+
+    store.send(.restore(.finished)) {
+      $0.screen = .loading
+    }
+
+    bgQueue.advance()
+
+    XCTAssertNoDifference(messengerDidLoad, 1)
+
+    mainQueue.advance()
+
+    store.receive(.set(\.$screen, .home(HomeState()))) {
       $0.screen = .home(HomeState())
     }
   }
+
+  func testWelcomeRestoreTapped() {
+    let store = TestStore(
+      initialState: AppState(
+        screen: .welcome(WelcomeState())
+      ),
+      reducer: appReducer,
+      environment: .unimplemented
+    )
+
+    store.send(.welcome(.restoreTapped)) {
+      $0.screen = .restore(RestoreState())
+    }
+  }
+
+  func testWelcomeFailed() {
+    let store = TestStore(
+      initialState: AppState(
+        screen: .welcome(WelcomeState())
+      ),
+      reducer: appReducer,
+      environment: .unimplemented
+    )
+
+    let failure = "Something went wrong"
+
+    store.send(.welcome(.failed(failure))) {
+      $0.screen = .failure(failure)
+    }
+  }
 }
-- 
GitLab


From 8014ffc3be3e89de0e0e7981a95bf1fd6c9dabb7 Mon Sep 17 00:00:00 2001
From: Dariusz Rybicki <dariusz@elixxir.io>
Date: Wed, 31 Aug 2022 15:41:41 +0100
Subject: [PATCH 04/10] Register from Home

---
 Examples/xx-messenger/Package.swift           |   3 +
 .../AppFeature/AppEnvironment+Live.swift      |   9 +-
 .../Sources/HomeFeature/HomeFeature.swift     |  74 +++++-
 .../Sources/HomeFeature/HomeView.swift        |  39 ++-
 .../HomeFeatureTests/HomeFeatureTests.swift   | 222 +++++++++++++++++-
 5 files changed, 338 insertions(+), 9 deletions(-)

diff --git a/Examples/xx-messenger/Package.swift b/Examples/xx-messenger/Package.swift
index b91c4340..4ed6cfd8 100644
--- a/Examples/xx-messenger/Package.swift
+++ b/Examples/xx-messenger/Package.swift
@@ -69,6 +69,7 @@ let package = Package(
       dependencies: [
         .target(name: "AppCore"),
         .target(name: "HomeFeature"),
+        .target(name: "RegisterFeature"),
         .target(name: "RestoreFeature"),
         .target(name: "WelcomeFeature"),
         .product(name: "ComposableArchitecture", package: "swift-composable-architecture"),
@@ -89,7 +90,9 @@ let package = Package(
       name: "HomeFeature",
       dependencies: [
         .target(name: "AppCore"),
+        .target(name: "RegisterFeature"),
         .product(name: "ComposableArchitecture", package: "swift-composable-architecture"),
+        .product(name: "ComposablePresentation", package: "swift-composable-presentation"),
         .product(name: "XXMessengerClient", package: "elixxir-dapps-sdk-swift"),
       ],
       swiftSettings: swiftSettings
diff --git a/Examples/xx-messenger/Sources/AppFeature/AppEnvironment+Live.swift b/Examples/xx-messenger/Sources/AppFeature/AppEnvironment+Live.swift
index eec6f199..d3cc134a 100644
--- a/Examples/xx-messenger/Sources/AppFeature/AppEnvironment+Live.swift
+++ b/Examples/xx-messenger/Sources/AppFeature/AppEnvironment+Live.swift
@@ -34,7 +34,14 @@ extension AppEnvironment {
         HomeEnvironment(
           messenger: messenger,
           mainQueue: mainQueue,
-          bgQueue: bgQueue
+          bgQueue: bgQueue,
+          register: {
+            RegisterEnvironment(
+              messenger: messenger,
+              mainQueue: mainQueue,
+              bgQueue: bgQueue
+            )
+          }
         )
       }
     )
diff --git a/Examples/xx-messenger/Sources/HomeFeature/HomeFeature.swift b/Examples/xx-messenger/Sources/HomeFeature/HomeFeature.swift
index 499bed78..29abfb6b 100644
--- a/Examples/xx-messenger/Sources/HomeFeature/HomeFeature.swift
+++ b/Examples/xx-messenger/Sources/HomeFeature/HomeFeature.swift
@@ -1,37 +1,58 @@
+import Combine
 import ComposableArchitecture
+import ComposablePresentation
 import Foundation
+import RegisterFeature
 import XXClient
 import XXMessengerClient
 
 public struct HomeState: Equatable {
-  public init() {}
+  public init(
+    username: String? = nil,
+    failure: String? = nil,
+    register: RegisterState? = nil
+  ) {
+    self.username = username
+    self.failure = failure
+    self.register = register
+  }
+
+  @BindableState public var username: String?
+  @BindableState public var failure: String?
+  @BindableState public var register: RegisterState?
 }
 
-public enum HomeAction: Equatable {
+public enum HomeAction: Equatable, BindableAction {
   case start
+  case binding(BindingAction<HomeState>)
+  case register(RegisterAction)
 }
 
 public struct HomeEnvironment {
   public init(
     messenger: Messenger,
     mainQueue: AnySchedulerOf<DispatchQueue>,
-    bgQueue: AnySchedulerOf<DispatchQueue>
+    bgQueue: AnySchedulerOf<DispatchQueue>,
+    register: @escaping () -> RegisterEnvironment
   ) {
     self.messenger = messenger
     self.mainQueue = mainQueue
     self.bgQueue = bgQueue
+    self.register = register
   }
 
   public var messenger: Messenger
   public var mainQueue: AnySchedulerOf<DispatchQueue>
   public var bgQueue: AnySchedulerOf<DispatchQueue>
+  public var register: () -> RegisterEnvironment
 }
 
 extension HomeEnvironment {
   public static let unimplemented = HomeEnvironment(
     messenger: .unimplemented,
     mainQueue: .unimplemented,
-    bgQueue: .unimplemented
+    bgQueue: .unimplemented,
+    register: { .unimplemented }
   )
 }
 
@@ -39,6 +60,51 @@ public let homeReducer = Reducer<HomeState, HomeAction, HomeEnvironment>
 { state, action, env in
   switch action {
   case .start:
+    return .run { subscriber in
+      do {
+        try env.messenger.start()
+
+        if env.messenger.isConnected() == false {
+          try env.messenger.connect()
+        }
+
+        if env.messenger.isLoggedIn() == false {
+          if try env.messenger.isRegistered() == false {
+            subscriber.send(.set(\.$register, RegisterState()))
+            subscriber.send(completion: .finished)
+            return AnyCancellable {}
+          }
+          try env.messenger.logIn()
+        }
+
+        if let contact = env.messenger.e2e()?.getContact(),
+           let facts = try? contact.getFacts(),
+           let username = facts.first(where: { $0.type == 0 })?.fact {
+          subscriber.send(.set(\.$username, username))
+        }
+      } catch {
+        subscriber.send(.set(\.$failure, error.localizedDescription))
+      }
+      subscriber.send(completion: .finished)
+      return AnyCancellable {}
+    }
+    .subscribe(on: env.bgQueue)
+    .receive(on: env.mainQueue)
+    .eraseToEffect()
+
+  case .register(.finished):
+    state.register = nil
+    return Effect(value: .start)
+
+  case .binding(_), .register(_):
     return .none
   }
 }
+.binding()
+.presenting(
+  registerReducer,
+  state: .keyPath(\.register),
+  id: .notNil(),
+  action: /HomeAction.register,
+  environment: { $0.register() }
+)
diff --git a/Examples/xx-messenger/Sources/HomeFeature/HomeView.swift b/Examples/xx-messenger/Sources/HomeFeature/HomeView.swift
index 0bda29bd..e40bb1db 100644
--- a/Examples/xx-messenger/Sources/HomeFeature/HomeView.swift
+++ b/Examples/xx-messenger/Sources/HomeFeature/HomeView.swift
@@ -1,4 +1,6 @@
 import ComposableArchitecture
+import ComposablePresentation
+import RegisterFeature
 import SwiftUI
 
 public struct HomeView: View {
@@ -9,19 +11,54 @@ public struct HomeView: View {
   let store: Store<HomeState, HomeAction>
 
   struct ViewState: Equatable {
-    init(state: HomeState) {}
+    var username: String?
+    var failure: String?
+
+    init(state: HomeState) {
+      username = state.username
+      failure = state.failure
+    }
   }
 
   public var body: some View {
     WithViewStore(store.scope(state: ViewState.init)) { viewStore in
       NavigationView {
         Form {
+          if let username = viewStore.username {
+            Section {
+              Text(username)
+            } header: {
+              Text("Username")
+            }
+          }
 
+          if let failure = viewStore.failure {
+            Section {
+              Text(failure)
+              Button {
+                viewStore.send(.start)
+              } label: {
+                Text("Retry")
+              }
+            } header: {
+              Text("Error")
+            }
+          }
         }
         .navigationTitle("Home")
       }
       .navigationViewStyle(.stack)
       .task { viewStore.send(.start) }
+      .fullScreenCover(
+        store.scope(
+          state: \.register,
+          action: HomeAction.register
+        ),
+        onDismiss: {
+          viewStore.send(.set(\.$register, nil))
+        },
+        content: RegisterView.init(store:)
+      )
     }
   }
 }
diff --git a/Examples/xx-messenger/Tests/HomeFeatureTests/HomeFeatureTests.swift b/Examples/xx-messenger/Tests/HomeFeatureTests/HomeFeatureTests.swift
index 21a25bb6..efdd867f 100644
--- a/Examples/xx-messenger/Tests/HomeFeatureTests/HomeFeatureTests.swift
+++ b/Examples/xx-messenger/Tests/HomeFeatureTests/HomeFeatureTests.swift
@@ -1,16 +1,232 @@
 import ComposableArchitecture
+import RegisterFeature
 import XCTest
+import XXClient
+import XXMessengerClient
 @testable import HomeFeature
 
-@MainActor
 final class HomeFeatureTests: XCTestCase {
-  func testStart() async throws {
+  func testStartUnregistered() {
     let store = TestStore(
       initialState: HomeState(),
       reducer: homeReducer,
       environment: .unimplemented
     )
 
-    await store.send(.start)
+    let bgQueue = DispatchQueue.test
+    let mainQueue = DispatchQueue.test
+    var messengerDidStartWithTimeout: [Int] = []
+    var messengerDidConnect = 0
+
+    store.environment.bgQueue = bgQueue.eraseToAnyScheduler()
+    store.environment.mainQueue = mainQueue.eraseToAnyScheduler()
+    store.environment.messenger.start.run = { messengerDidStartWithTimeout.append($0) }
+    store.environment.messenger.isConnected.run = { false }
+    store.environment.messenger.connect.run = { messengerDidConnect += 1 }
+    store.environment.messenger.isLoggedIn.run = { false }
+    store.environment.messenger.isRegistered.run = { false }
+
+    store.send(.start)
+
+    bgQueue.advance()
+
+    XCTAssertNoDifference(messengerDidStartWithTimeout, [30_000])
+    XCTAssertNoDifference(messengerDidConnect, 1)
+
+    mainQueue.advance()
+
+    store.receive(.set(\.$register, RegisterState())) {
+      $0.register = RegisterState()
+    }
+  }
+
+  func testStartRegistered() {
+    let store = TestStore(
+      initialState: HomeState(),
+      reducer: homeReducer,
+      environment: .unimplemented
+    )
+
+    let username = "test_username"
+    let bgQueue = DispatchQueue.test
+    let mainQueue = DispatchQueue.test
+    var messengerDidStartWithTimeout: [Int] = []
+    var messengerDidConnect = 0
+    var messengerDidLogIn = 0
+
+    store.environment.bgQueue = bgQueue.eraseToAnyScheduler()
+    store.environment.mainQueue = mainQueue.eraseToAnyScheduler()
+    store.environment.messenger.start.run = { messengerDidStartWithTimeout.append($0) }
+    store.environment.messenger.isConnected.run = { false }
+    store.environment.messenger.connect.run = { messengerDidConnect += 1 }
+    store.environment.messenger.isLoggedIn.run = { false }
+    store.environment.messenger.isRegistered.run = { true }
+    store.environment.messenger.logIn.run = { messengerDidLogIn += 1 }
+    store.environment.messenger.e2e.get = {
+      var e2e = E2E.unimplemented
+      e2e.getContact.run = {
+        var contact = Contact.unimplemented(Data())
+        contact.getFactsFromContact.run = { _ in [Fact(fact: username, type: 0)] }
+        return contact
+      }
+      return e2e
+    }
+
+    store.send(.start)
+
+    bgQueue.advance()
+
+    XCTAssertNoDifference(messengerDidStartWithTimeout, [30_000])
+    XCTAssertNoDifference(messengerDidConnect, 1)
+    XCTAssertNoDifference(messengerDidLogIn, 1)
+
+    mainQueue.advance()
+
+    store.receive(.set(\.$username, username)) {
+      $0.username = username
+    }
+  }
+
+  func testRegisterFinished() {
+    let store = TestStore(
+      initialState: HomeState(
+        register: RegisterState()
+      ),
+      reducer: homeReducer,
+      environment: .unimplemented
+    )
+
+    let username = "test_username"
+    let bgQueue = DispatchQueue.test
+    let mainQueue = DispatchQueue.test
+    var messengerDidStartWithTimeout: [Int] = []
+    var messengerDidLogIn = 0
+
+    store.environment.bgQueue = bgQueue.eraseToAnyScheduler()
+    store.environment.mainQueue = mainQueue.eraseToAnyScheduler()
+    store.environment.messenger.start.run = { messengerDidStartWithTimeout.append($0) }
+    store.environment.messenger.isConnected.run = { true }
+    store.environment.messenger.isLoggedIn.run = { false }
+    store.environment.messenger.isRegistered.run = { true }
+    store.environment.messenger.logIn.run = { messengerDidLogIn += 1 }
+    store.environment.messenger.e2e.get = {
+      var e2e = E2E.unimplemented
+      e2e.getContact.run = {
+        var contact = Contact.unimplemented(Data())
+        contact.getFactsFromContact.run = { _ in [Fact(fact: username, type: 0)] }
+        return contact
+      }
+      return e2e
+    }
+
+    store.send(.register(.finished)) {
+      $0.register = nil
+    }
+
+    store.receive(.start)
+
+    bgQueue.advance()
+
+    XCTAssertNoDifference(messengerDidStartWithTimeout, [30_000])
+    XCTAssertNoDifference(messengerDidLogIn, 1)
+
+    mainQueue.advance()
+
+    store.receive(.set(\.$username, username)) {
+      $0.username = username
+    }
+  }
+
+  func testStartMessengerStartFailure() {
+    let store = TestStore(
+      initialState: HomeState(),
+      reducer: homeReducer,
+      environment: .unimplemented
+    )
+
+    struct Failure: Error {}
+    let error = Failure()
+
+    store.environment.bgQueue = .immediate
+    store.environment.mainQueue = .immediate
+    store.environment.messenger.start.run = { _ in throw error }
+
+    store.send(.start)
+
+    store.receive(.set(\.$failure, error.localizedDescription)) {
+      $0.failure = error.localizedDescription
+    }
+  }
+
+  func testStartMessengerConnectFailure() {
+    let store = TestStore(
+      initialState: HomeState(),
+      reducer: homeReducer,
+      environment: .unimplemented
+    )
+
+    struct Failure: Error {}
+    let error = Failure()
+
+    store.environment.bgQueue = .immediate
+    store.environment.mainQueue = .immediate
+    store.environment.messenger.start.run = { _ in }
+    store.environment.messenger.isConnected.run = { false }
+    store.environment.messenger.connect.run = { throw error }
+
+    store.send(.start)
+
+    store.receive(.set(\.$failure, error.localizedDescription)) {
+      $0.failure = error.localizedDescription
+    }
+  }
+
+  func testStartMessengerIsRegisteredFailure() {
+    let store = TestStore(
+      initialState: HomeState(),
+      reducer: homeReducer,
+      environment: .unimplemented
+    )
+
+    struct Failure: Error {}
+    let error = Failure()
+
+    store.environment.bgQueue = .immediate
+    store.environment.mainQueue = .immediate
+    store.environment.messenger.start.run = { _ in }
+    store.environment.messenger.isConnected.run = { true }
+    store.environment.messenger.isLoggedIn.run = { false }
+    store.environment.messenger.isRegistered.run = { throw error }
+
+    store.send(.start)
+
+    store.receive(.set(\.$failure, error.localizedDescription)) {
+      $0.failure = error.localizedDescription
+    }
+  }
+
+  func testStartMessengerLogInFailure() {
+    let store = TestStore(
+      initialState: HomeState(),
+      reducer: homeReducer,
+      environment: .unimplemented
+    )
+
+    struct Failure: Error {}
+    let error = Failure()
+
+    store.environment.bgQueue = .immediate
+    store.environment.mainQueue = .immediate
+    store.environment.messenger.start.run = { _ in }
+    store.environment.messenger.isConnected.run = { true }
+    store.environment.messenger.isLoggedIn.run = { false }
+    store.environment.messenger.isRegistered.run = { true }
+    store.environment.messenger.logIn.run = { throw error }
+
+    store.send(.start)
+
+    store.receive(.set(\.$failure, error.localizedDescription)) {
+      $0.failure = error.localizedDescription
+    }
   }
 }
-- 
GitLab


From 98b9898ba931f002aef449c511e95a6f0a3f9c38 Mon Sep 17 00:00:00 2001
From: Dariusz Rybicki <dariusz@elixxir.io>
Date: Wed, 31 Aug 2022 15:46:39 +0100
Subject: [PATCH 05/10] Add missing tests

---
 .../AppFeatureTests/AppFeatureTests.swift     | 46 +++++++++++++++++++
 1 file changed, 46 insertions(+)

diff --git a/Examples/xx-messenger/Tests/AppFeatureTests/AppFeatureTests.swift b/Examples/xx-messenger/Tests/AppFeatureTests/AppFeatureTests.swift
index 166b6325..5f055492 100644
--- a/Examples/xx-messenger/Tests/AppFeatureTests/AppFeatureTests.swift
+++ b/Examples/xx-messenger/Tests/AppFeatureTests/AppFeatureTests.swift
@@ -170,4 +170,50 @@ final class AppFeatureTests: XCTestCase {
       $0.screen = .failure(failure)
     }
   }
+
+  func testStartDatabaseMakeFailure() {
+    let store = TestStore(
+      initialState: AppState(),
+      reducer: appReducer,
+      environment: .unimplemented
+    )
+
+    struct Failure: Error {}
+    let error = Failure()
+
+    store.environment.mainQueue = .immediate
+    store.environment.bgQueue = .immediate
+    store.environment.dbManager.hasDB.run = { false }
+    store.environment.dbManager.makeDB.run = { throw error }
+
+    store.send(.start)
+
+    store.receive(.set(\.$screen, .failure(error.localizedDescription))) {
+      $0.screen = .failure(error.localizedDescription)
+    }
+  }
+
+  func testStartMessengerLoadFailure() {
+    let store = TestStore(
+      initialState: AppState(),
+      reducer: appReducer,
+      environment: .unimplemented
+    )
+
+    struct Failure: Error {}
+    let error = Failure()
+
+    store.environment.mainQueue = .immediate
+    store.environment.bgQueue = .immediate
+    store.environment.dbManager.hasDB.run = { true }
+    store.environment.messenger.isLoaded.run = { false }
+    store.environment.messenger.isCreated.run = { true }
+    store.environment.messenger.load.run = { throw error }
+
+    store.send(.start)
+
+    store.receive(.set(\.$screen, .failure(error.localizedDescription))) {
+      $0.screen = .failure(error.localizedDescription)
+    }
+  }
 }
-- 
GitLab


From d883eb9c917f5be62db1db70612ff98ef643631b Mon Sep 17 00:00:00 2001
From: Dariusz Rybicki <dariusz@elixxir.io>
Date: Wed, 31 Aug 2022 15:47:38 +0100
Subject: [PATCH 06/10] Remove LaunchFeature

---
 Examples/xx-messenger/Package.swift           |  21 --
 .../LaunchFeature/LaunchErrorView.swift       |  44 ---
 .../Sources/LaunchFeature/LaunchFeature.swift | 173 ---------
 .../Sources/LaunchFeature/LaunchView.swift    | 118 -------
 .../LaunchFeatureTests.swift                  | 327 ------------------
 5 files changed, 683 deletions(-)
 delete mode 100644 Examples/xx-messenger/Sources/LaunchFeature/LaunchErrorView.swift
 delete mode 100644 Examples/xx-messenger/Sources/LaunchFeature/LaunchFeature.swift
 delete mode 100644 Examples/xx-messenger/Sources/LaunchFeature/LaunchView.swift
 delete mode 100644 Examples/xx-messenger/Tests/LaunchFeatureTests/LaunchFeatureTests.swift

diff --git a/Examples/xx-messenger/Package.swift b/Examples/xx-messenger/Package.swift
index 4ed6cfd8..f7e0d039 100644
--- a/Examples/xx-messenger/Package.swift
+++ b/Examples/xx-messenger/Package.swift
@@ -21,7 +21,6 @@ let package = Package(
     .library(name: "AppCore", targets: ["AppCore"]),
     .library(name: "AppFeature", targets: ["AppFeature"]),
     .library(name: "HomeFeature", targets: ["HomeFeature"]),
-    .library(name: "LaunchFeature", targets: ["LaunchFeature"]),
     .library(name: "RegisterFeature", targets: ["RegisterFeature"]),
     .library(name: "RestoreFeature", targets: ["RestoreFeature"]),
     .library(name: "WelcomeFeature", targets: ["WelcomeFeature"]),
@@ -104,26 +103,6 @@ let package = Package(
       ],
       swiftSettings: swiftSettings
     ),
-    .target(
-      name: "LaunchFeature",
-      dependencies: [
-        .target(name: "AppCore"),
-        .target(name: "RegisterFeature"),
-        .target(name: "RestoreFeature"),
-        .target(name: "WelcomeFeature"),
-        .product(name: "ComposableArchitecture", package: "swift-composable-architecture"),
-        .product(name: "ComposablePresentation", package: "swift-composable-presentation"),
-        .product(name: "XXMessengerClient", package: "elixxir-dapps-sdk-swift"),
-      ],
-      swiftSettings: swiftSettings
-    ),
-    .testTarget(
-      name: "LaunchFeatureTests",
-      dependencies: [
-        .target(name: "LaunchFeature"),
-      ],
-      swiftSettings: swiftSettings
-    ),
     .target(
       name: "RegisterFeature",
       dependencies: [
diff --git a/Examples/xx-messenger/Sources/LaunchFeature/LaunchErrorView.swift b/Examples/xx-messenger/Sources/LaunchFeature/LaunchErrorView.swift
deleted file mode 100644
index 893492c0..00000000
--- a/Examples/xx-messenger/Sources/LaunchFeature/LaunchErrorView.swift
+++ /dev/null
@@ -1,44 +0,0 @@
-import ComposableArchitecture
-import SwiftUI
-
-struct LaunchErrorView: View {
-  var failure: String
-  var onRetry: () -> Void
-
-  var body: some View {
-    NavigationView {
-      VStack(spacing: 0) {
-        ScrollView {
-          Text(failure)
-            .frame(maxWidth: .infinity, alignment: .leading)
-            .padding()
-        }
-
-        Divider()
-
-        Button {
-          onRetry()
-        } label: {
-          Text("Retry")
-            .frame(maxWidth: .infinity)
-        }
-        .buttonStyle(.borderedProminent)
-        .controlSize(.large)
-        .padding()
-      }
-      .navigationTitle("Error")
-    }
-    .navigationViewStyle(.stack)
-  }
-}
-
-#if DEBUG
-struct LaunchErrorView_Previews: PreviewProvider {
-  static var previews: some View {
-    LaunchErrorView(
-      failure: "Something went wrong...",
-      onRetry: {}
-    )
-  }
-}
-#endif
diff --git a/Examples/xx-messenger/Sources/LaunchFeature/LaunchFeature.swift b/Examples/xx-messenger/Sources/LaunchFeature/LaunchFeature.swift
deleted file mode 100644
index a7bf89ce..00000000
--- a/Examples/xx-messenger/Sources/LaunchFeature/LaunchFeature.swift
+++ /dev/null
@@ -1,173 +0,0 @@
-import AppCore
-import Combine
-import ComposableArchitecture
-import ComposablePresentation
-import Foundation
-import RegisterFeature
-import RestoreFeature
-import WelcomeFeature
-import XXMessengerClient
-import XXModels
-
-public struct LaunchState: Equatable {
-  public enum Screen: Equatable {
-    case loading
-    case welcome(WelcomeState)
-    case restore(RestoreState)
-    case register(RegisterState)
-    case failure(String)
-  }
-
-  public init(
-    screen: Screen = .loading
-  ) {
-    self.screen = screen
-  }
-
-  @BindableState public var screen: Screen
-}
-
-extension LaunchState.Screen {
-  var asWelcome: WelcomeState? {
-    get { (/LaunchState.Screen.welcome).extract(from: self) }
-    set { if let state = newValue { self = .welcome(state) } }
-  }
-  var asRestore: RestoreState? {
-    get { (/LaunchState.Screen.restore).extract(from: self) }
-    set { if let state = newValue { self = .restore(state) } }
-  }
-  var asRegister: RegisterState? {
-    get { (/LaunchState.Screen.register).extract(from: self) }
-    set { if let state = newValue { self = .register(state) } }
-  }
-}
-
-public enum LaunchAction: Equatable, BindableAction {
-  case start
-  case finished
-  case binding(BindingAction<LaunchState>)
-  case welcome(WelcomeAction)
-  case restore(RestoreAction)
-  case register(RegisterAction)
-}
-
-public struct LaunchEnvironment {
-  public init(
-    dbManager: DBManager,
-    messenger: Messenger,
-    mainQueue: AnySchedulerOf<DispatchQueue>,
-    bgQueue: AnySchedulerOf<DispatchQueue>,
-    welcome: @escaping () -> WelcomeEnvironment,
-    restore: @escaping () -> RestoreEnvironment,
-    register: @escaping () -> RegisterEnvironment
-  ) {
-    self.dbManager = dbManager
-    self.messenger = messenger
-    self.mainQueue = mainQueue
-    self.bgQueue = bgQueue
-    self.welcome = welcome
-    self.restore = restore
-    self.register = register
-  }
-
-  public var dbManager: DBManager
-  public var messenger: Messenger
-  public var mainQueue: AnySchedulerOf<DispatchQueue>
-  public var bgQueue: AnySchedulerOf<DispatchQueue>
-  public var welcome: () -> WelcomeEnvironment
-  public var restore: () -> RestoreEnvironment
-  public var register: () -> RegisterEnvironment
-}
-
-extension LaunchEnvironment {
-  public static let unimplemented = LaunchEnvironment(
-    dbManager: .unimplemented,
-    messenger: .unimplemented,
-    mainQueue: .unimplemented,
-    bgQueue: .unimplemented,
-    welcome: { .unimplemented },
-    restore: { .unimplemented },
-    register: { .unimplemented }
-  )
-}
-
-public let launchReducer = Reducer<LaunchState, LaunchAction, LaunchEnvironment>
-{ state, action, env in
-  switch action {
-  case .start, .welcome(.finished), .restore(.finished), .register(.finished):
-    state.screen = .loading
-    return .future { fulfill in
-      do {
-        if env.dbManager.hasDB() == false {
-          try env.dbManager.makeDB()
-        }
-
-        if env.messenger.isLoaded() == false {
-          if env.messenger.isCreated() == false {
-            fulfill(.success(.set(\.$screen, .welcome(WelcomeState()))))
-            return
-          }
-          try env.messenger.load()
-        }
-
-        try env.messenger.start()
-
-        if env.messenger.isConnected() == false {
-          try env.messenger.connect()
-        }
-
-        if env.messenger.isLoggedIn() == false {
-          if try env.messenger.isRegistered() == false {
-            fulfill(.success(.set(\.$screen, .register(RegisterState()))))
-            return
-          }
-          try env.messenger.logIn()
-        }
-
-        fulfill(.success(.finished))
-      }
-      catch {
-        fulfill(.success(.set(\.$screen, .failure(error.localizedDescription))))
-      }
-    }
-    .subscribe(on: env.bgQueue)
-    .receive(on: env.mainQueue)
-    .eraseToEffect()
-
-  case .finished:
-    return .none
-
-  case .welcome(.restoreTapped):
-    state.screen = .restore(RestoreState())
-    return .none
-
-  case .welcome(.failed(let failure)):
-    state.screen = .failure(failure)
-    return .none
-
-  case .binding(_), .welcome(_), .restore(_), .register(_):
-    return .none
-  }
-}
-.binding()
-.presenting(
-  welcomeReducer,
-  state: .keyPath(\.screen.asWelcome),
-  id: .notNil(),
-  action: /LaunchAction.welcome,
-  environment: { $0.welcome() }
-)
-.presenting(
-  restoreReducer,
-  state: .keyPath(\.screen.asRestore),
-  id: .notNil(),
-  action: /LaunchAction.restore,
-  environment: { $0.restore() }
-)
-.presenting(
-  registerReducer,
-  state: .keyPath(\.screen.asRegister),
-  id: .notNil(),
-  action: /LaunchAction.register,
-  environment: { $0.register() }
-)
diff --git a/Examples/xx-messenger/Sources/LaunchFeature/LaunchView.swift b/Examples/xx-messenger/Sources/LaunchFeature/LaunchView.swift
deleted file mode 100644
index ed110302..00000000
--- a/Examples/xx-messenger/Sources/LaunchFeature/LaunchView.swift
+++ /dev/null
@@ -1,118 +0,0 @@
-import ComposableArchitecture
-import RegisterFeature
-import RestoreFeature
-import SwiftUI
-import WelcomeFeature
-
-public struct LaunchView: View {
-  public init(store: Store<LaunchState, LaunchAction>) {
-    self.store = store
-  }
-
-  struct ViewState: Equatable {
-    enum Screen: Equatable {
-      case loading
-      case welcome
-      case restore
-      case register
-      case failure(String)
-    }
-
-    init(_ state: LaunchState) {
-      switch state.screen {
-      case .loading: screen = .loading
-      case .welcome(_): screen = .welcome
-      case .restore(_): screen = .restore
-      case .register(_): screen = .register
-      case .failure(let failure): screen = .failure(failure)
-      }
-    }
-
-    var screen: Screen
-  }
-
-  let store: Store<LaunchState, LaunchAction>
-
-  public var body: some View {
-    WithViewStore(store.scope(state: ViewState.init)) { viewStore in
-      ZStack {
-        switch viewStore.screen {
-        case .loading:
-          ProgressView {
-            Text("Loading")
-          }
-          .controlSize(.large)
-          .frame(maxWidth: .infinity, maxHeight: .infinity)
-          .transition(.opacity)
-
-        case .welcome:
-          IfLetStore(
-            store.scope(
-              state: { (/LaunchState.Screen.welcome).extract(from: $0.screen) },
-              action: LaunchAction.welcome
-            ),
-            then: WelcomeView.init(store:)
-          )
-          .frame(maxWidth: .infinity, maxHeight: .infinity)
-          .transition(.asymmetric(
-            insertion: .move(edge: .trailing),
-            removal: .opacity
-          ))
-
-        case .restore:
-          IfLetStore(
-            store.scope(
-              state: { (/LaunchState.Screen.restore).extract(from: $0.screen) },
-              action: LaunchAction.restore
-            ),
-            then: RestoreView.init(store:)
-          )
-          .frame(maxWidth: .infinity, maxHeight: .infinity)
-          .transition(.asymmetric(
-            insertion: .move(edge: .trailing),
-            removal: .opacity
-          ))
-
-        case .register:
-          IfLetStore(
-            store.scope(
-              state: { (/LaunchState.Screen.register).extract(from: $0.screen) },
-              action: LaunchAction.register
-            ),
-            then: RegisterView.init(store:)
-          )
-          .frame(maxWidth: .infinity, maxHeight: .infinity)
-          .transition(.asymmetric(
-            insertion: .move(edge: .trailing),
-            removal: .opacity
-          ))
-
-        case .failure(let failure):
-          LaunchErrorView(
-            failure: failure,
-            onRetry: { viewStore.send(.start) }
-          )
-          .frame(maxWidth: .infinity, maxHeight: .infinity)
-          .transition(.asymmetric(
-            insertion: .move(edge: .trailing),
-            removal: .opacity
-          ))
-        }
-      }
-      .animation(.default, value: viewStore.screen)
-      .task { viewStore.send(.start) }
-    }
-  }
-}
-
-#if DEBUG
-public struct LaunchView_Previews: PreviewProvider {
-  public static var previews: some View {
-    LaunchView(store: Store(
-      initialState: LaunchState(),
-      reducer: .empty,
-      environment: ()
-    ))
-  }
-}
-#endif
diff --git a/Examples/xx-messenger/Tests/LaunchFeatureTests/LaunchFeatureTests.swift b/Examples/xx-messenger/Tests/LaunchFeatureTests/LaunchFeatureTests.swift
deleted file mode 100644
index b2b3f856..00000000
--- a/Examples/xx-messenger/Tests/LaunchFeatureTests/LaunchFeatureTests.swift
+++ /dev/null
@@ -1,327 +0,0 @@
-import AppCore
-import ComposableArchitecture
-import RegisterFeature
-import RestoreFeature
-import WelcomeFeature
-import XCTest
-import XXModels
-@testable import LaunchFeature
-
-@MainActor
-final class LaunchFeatureTests: XCTestCase {
-  func testStart() {
-    let store = TestStore(
-      initialState: LaunchState(),
-      reducer: launchReducer,
-      environment: .unimplemented
-    )
-
-    let mainQueue = DispatchQueue.test
-    let bgQueue = DispatchQueue.test
-    var didMakeDB = 0
-    var messengerDidLoad = 0
-    var messengerDidStart = 0
-    var messengerDidConnect = 0
-    var messengerDidLogIn = 0
-
-    store.environment.mainQueue = mainQueue.eraseToAnyScheduler()
-    store.environment.bgQueue = bgQueue.eraseToAnyScheduler()
-    store.environment.dbManager.hasDB.run = { false }
-    store.environment.dbManager.makeDB.run = { didMakeDB += 1 }
-    store.environment.messenger.isLoaded.run = { false }
-    store.environment.messenger.isCreated.run = { true }
-    store.environment.messenger.load.run = { messengerDidLoad += 1 }
-    store.environment.messenger.start.run = { _ in messengerDidStart += 1 }
-    store.environment.messenger.isConnected.run = { false }
-    store.environment.messenger.connect.run = { messengerDidConnect += 1 }
-    store.environment.messenger.isLoggedIn.run = { false }
-    store.environment.messenger.isRegistered.run = { true }
-    store.environment.messenger.logIn.run = { messengerDidLogIn += 1 }
-
-    store.send(.start)
-
-    bgQueue.advance()
-
-    XCTAssertNoDifference(didMakeDB, 1)
-    XCTAssertNoDifference(messengerDidLoad, 1)
-    XCTAssertNoDifference(messengerDidStart, 1)
-    XCTAssertNoDifference(messengerDidConnect, 1)
-    XCTAssertNoDifference(messengerDidLogIn, 1)
-
-    mainQueue.advance()
-
-    store.receive(.finished)
-  }
-
-  func testStartWithoutMessengerCreated() {
-    let store = TestStore(
-      initialState: LaunchState(),
-      reducer: launchReducer,
-      environment: .unimplemented
-    )
-
-    let mainQueue = DispatchQueue.test
-    let bgQueue = DispatchQueue.test
-
-    store.environment.mainQueue = mainQueue.eraseToAnyScheduler()
-    store.environment.bgQueue = bgQueue.eraseToAnyScheduler()
-    store.environment.dbManager.hasDB.run = { true }
-    store.environment.messenger.isLoaded.run = { false }
-    store.environment.messenger.isCreated.run = { false }
-
-    store.send(.start)
-
-    bgQueue.advance()
-    mainQueue.advance()
-
-    store.receive(.set(\.$screen, .welcome(WelcomeState()))) {
-      $0.screen = .welcome(WelcomeState())
-    }
-  }
-
-  func testStartUnregistered() {
-    let store = TestStore(
-      initialState: LaunchState(),
-      reducer: launchReducer,
-      environment: .unimplemented
-    )
-
-    let mainQueue = DispatchQueue.test
-    let bgQueue = DispatchQueue.test
-
-    store.environment.mainQueue = mainQueue.eraseToAnyScheduler()
-    store.environment.bgQueue = bgQueue.eraseToAnyScheduler()
-    store.environment.dbManager.hasDB.run = { true }
-    store.environment.messenger.isLoaded.run = { true }
-    store.environment.messenger.start.run = { _ in }
-    store.environment.messenger.isConnected.run = { true }
-    store.environment.messenger.isLoggedIn.run = { false }
-    store.environment.messenger.isRegistered.run = { false }
-
-    store.send(.start)
-
-    bgQueue.advance()
-    mainQueue.advance()
-
-    store.receive(.set(\.$screen, .register(RegisterState()))) {
-      $0.screen = .register(RegisterState())
-    }
-  }
-
-  func testStartMakeDBFailure() {
-    let store = TestStore(
-      initialState: LaunchState(),
-      reducer: launchReducer,
-      environment: .unimplemented
-    )
-
-    let mainQueue = DispatchQueue.test
-    let bgQueue = DispatchQueue.test
-    struct Error: Swift.Error {}
-    let error = Error()
-
-    store.environment.mainQueue = mainQueue.eraseToAnyScheduler()
-    store.environment.bgQueue = bgQueue.eraseToAnyScheduler()
-    store.environment.dbManager.hasDB.run = { false }
-    store.environment.dbManager.makeDB.run = { throw error }
-
-    store.send(.start)
-
-    bgQueue.advance()
-    mainQueue.advance()
-
-    store.receive(.set(\.$screen, .failure(error.localizedDescription))) {
-      $0.screen = .failure(error.localizedDescription)
-    }
-  }
-
-  func testStartMessengerLoadFailure() {
-    let store = TestStore(
-      initialState: LaunchState(),
-      reducer: launchReducer,
-      environment: .unimplemented
-    )
-
-    let mainQueue = DispatchQueue.test
-    let bgQueue = DispatchQueue.test
-    struct Error: Swift.Error {}
-    let error = Error()
-
-    store.environment.mainQueue = mainQueue.eraseToAnyScheduler()
-    store.environment.bgQueue = bgQueue.eraseToAnyScheduler()
-    store.environment.dbManager.hasDB.run = { true }
-    store.environment.messenger.isLoaded.run = { false }
-    store.environment.messenger.isCreated.run = { true }
-    store.environment.messenger.load.run = { throw error }
-
-    store.send(.start)
-
-    bgQueue.advance()
-    mainQueue.advance()
-
-    store.receive(.set(\.$screen, .failure(error.localizedDescription))) {
-      $0.screen = .failure(error.localizedDescription)
-    }
-  }
-
-  func testStartMessengerStartFailure() {
-    let store = TestStore(
-      initialState: LaunchState(),
-      reducer: launchReducer,
-      environment: .unimplemented
-    )
-
-    let mainQueue = DispatchQueue.test
-    let bgQueue = DispatchQueue.test
-    struct Error: Swift.Error {}
-    let error = Error()
-
-    store.environment.mainQueue = mainQueue.eraseToAnyScheduler()
-    store.environment.bgQueue = bgQueue.eraseToAnyScheduler()
-    store.environment.dbManager.hasDB.run = { true }
-    store.environment.messenger.isLoaded.run = { true }
-    store.environment.messenger.start.run = { _ in throw error }
-
-    store.send(.start)
-
-    bgQueue.advance()
-    mainQueue.advance()
-
-    store.receive(.set(\.$screen, .failure(error.localizedDescription))) {
-      $0.screen = .failure(error.localizedDescription)
-    }
-  }
-
-  func testStartMessengerConnectFailure() {
-    let store = TestStore(
-      initialState: LaunchState(),
-      reducer: launchReducer,
-      environment: .unimplemented
-    )
-
-    let mainQueue = DispatchQueue.test
-    let bgQueue = DispatchQueue.test
-    struct Error: Swift.Error {}
-    let error = Error()
-
-    store.environment.mainQueue = mainQueue.eraseToAnyScheduler()
-    store.environment.bgQueue = bgQueue.eraseToAnyScheduler()
-    store.environment.dbManager.hasDB.run = { true }
-    store.environment.messenger.isLoaded.run = { true }
-    store.environment.messenger.start.run = { _ in }
-    store.environment.messenger.isConnected.run = { false }
-    store.environment.messenger.connect.run = { throw error }
-
-    store.send(.start)
-
-    bgQueue.advance()
-    mainQueue.advance()
-
-    store.receive(.set(\.$screen, .failure(error.localizedDescription))) {
-      $0.screen = .failure(error.localizedDescription)
-    }
-  }
-
-  func testStartMessengerIsRegisteredFailure() {
-    let store = TestStore(
-      initialState: LaunchState(),
-      reducer: launchReducer,
-      environment: .unimplemented
-    )
-
-    let mainQueue = DispatchQueue.test
-    let bgQueue = DispatchQueue.test
-    struct Error: Swift.Error {}
-    let error = Error()
-
-    store.environment.mainQueue = mainQueue.eraseToAnyScheduler()
-    store.environment.bgQueue = bgQueue.eraseToAnyScheduler()
-    store.environment.dbManager.hasDB.run = { true }
-    store.environment.messenger.isLoaded.run = { true }
-    store.environment.messenger.start.run = { _ in }
-    store.environment.messenger.isConnected.run = { true }
-    store.environment.messenger.isLoggedIn.run = { false }
-    store.environment.messenger.isRegistered.run = { throw error }
-
-    store.send(.start)
-
-    bgQueue.advance()
-    mainQueue.advance()
-
-    store.receive(.set(\.$screen, .failure(error.localizedDescription))) {
-      $0.screen = .failure(error.localizedDescription)
-    }
-  }
-
-  func testStartMessengerLogInFailure() {
-    let store = TestStore(
-      initialState: LaunchState(),
-      reducer: launchReducer,
-      environment: .unimplemented
-    )
-
-    let mainQueue = DispatchQueue.test
-    let bgQueue = DispatchQueue.test
-    struct Error: Swift.Error {}
-    let error = Error()
-
-    store.environment.mainQueue = mainQueue.eraseToAnyScheduler()
-    store.environment.bgQueue = bgQueue.eraseToAnyScheduler()
-    store.environment.dbManager.hasDB.run = { true }
-    store.environment.messenger.isLoaded.run = { true }
-    store.environment.messenger.start.run = { _ in }
-    store.environment.messenger.isConnected.run = { true }
-    store.environment.messenger.isLoggedIn.run = { false }
-    store.environment.messenger.isRegistered.run = { true }
-    store.environment.messenger.logIn.run = { throw error }
-
-    store.send(.start)
-
-    bgQueue.advance()
-    mainQueue.advance()
-
-    store.receive(.set(\.$screen, .failure(error.localizedDescription))) {
-      $0.screen = .failure(error.localizedDescription)
-    }
-  }
-
-  func testWelcomeRestoreTapped() {
-    let store = TestStore(
-      initialState: LaunchState(
-        screen: .welcome(WelcomeState())
-      ),
-      reducer: launchReducer,
-      environment: .unimplemented
-    )
-
-    store.send(.welcome(.restoreTapped)) {
-      $0.screen = .restore(RestoreState())
-    }
-  }
-
-  func testWelcomeFailed() {
-    let store = TestStore(
-      initialState: LaunchState(
-        screen: .welcome(WelcomeState())
-      ),
-      reducer: launchReducer,
-      environment: .unimplemented
-    )
-
-    let failure = "Something went wrong"
-
-    store.send(.welcome(.failed(failure))) {
-      $0.screen = .failure(failure)
-    }
-  }
-
-  func testFinished() {
-    let store = TestStore(
-      initialState: LaunchState(),
-      reducer: launchReducer,
-      environment: .unimplemented
-    )
-
-    store.send(.finished)
-  }
-}
-- 
GitLab


From e39a275f2dd9d69a337c24297a0204da7c66dce9 Mon Sep 17 00:00:00 2001
From: Dariusz Rybicki <dariusz@elixxir.io>
Date: Wed, 31 Aug 2022 16:00:33 +0100
Subject: [PATCH 07/10] Update GitLab CI config

---
 .gitlab-ci.yml | 2 ++
 1 file changed, 2 insertions(+)

diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index 9e87f241..855b6566 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -22,5 +22,7 @@ test-examples-ios:
   tags: 
     - ios
   script:
+    - for ip in $(dig @8.8.8.8 github.com +short); do ssh-keyscan github.com,$ip; ssh-keyscan $ip; done 2>/dev/null >> ~/.ssh/known_hosts
+    - for ip in $(dig @8.8.8.8 git.xx.network +short); do ssh-keyscan git.xx.network,$ip; ssh-keyscan $ip; done 2>/dev/null >> ~/.ssh/known_hosts
     - ./run-tests.sh examples-ios
   retry: 1
-- 
GitLab


From 16e19de3b3746e8a5af85a21dbbcd9b187107c6c Mon Sep 17 00:00:00 2001
From: Dariusz Rybicki <dariusz@elixxir.io>
Date: Wed, 31 Aug 2022 16:03:31 +0100
Subject: [PATCH 08/10] Update build schemes

---
 .../xcschemes/LaunchFeature.xcscheme          | 78 -------------------
 .../xcschemes/XXMessenger.xcscheme            | 10 ---
 2 files changed, 88 deletions(-)
 delete mode 100644 Examples/xx-messenger/.swiftpm/xcode/xcshareddata/xcschemes/LaunchFeature.xcscheme

diff --git a/Examples/xx-messenger/.swiftpm/xcode/xcshareddata/xcschemes/LaunchFeature.xcscheme b/Examples/xx-messenger/.swiftpm/xcode/xcshareddata/xcschemes/LaunchFeature.xcscheme
deleted file mode 100644
index 0b5b716c..00000000
--- a/Examples/xx-messenger/.swiftpm/xcode/xcshareddata/xcschemes/LaunchFeature.xcscheme
+++ /dev/null
@@ -1,78 +0,0 @@
-<?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 = "LaunchFeature"
-               BuildableName = "LaunchFeature"
-               BlueprintName = "LaunchFeature"
-               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 = "LaunchFeatureTests"
-               BuildableName = "LaunchFeatureTests"
-               BlueprintName = "LaunchFeatureTests"
-               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 = "LaunchFeature"
-            BuildableName = "LaunchFeature"
-            BlueprintName = "LaunchFeature"
-            ReferencedContainer = "container:">
-         </BuildableReference>
-      </MacroExpansion>
-   </ProfileAction>
-   <AnalyzeAction
-      buildConfiguration = "Debug">
-   </AnalyzeAction>
-   <ArchiveAction
-      buildConfiguration = "Release"
-      revealArchiveInOrganizer = "YES">
-   </ArchiveAction>
-</Scheme>
diff --git a/Examples/xx-messenger/Project/XXMessenger.xcodeproj/xcshareddata/xcschemes/XXMessenger.xcscheme b/Examples/xx-messenger/Project/XXMessenger.xcodeproj/xcshareddata/xcschemes/XXMessenger.xcscheme
index 9e83a72f..669debfc 100644
--- a/Examples/xx-messenger/Project/XXMessenger.xcodeproj/xcshareddata/xcschemes/XXMessenger.xcscheme
+++ b/Examples/xx-messenger/Project/XXMessenger.xcodeproj/xcshareddata/xcschemes/XXMessenger.xcscheme
@@ -59,16 +59,6 @@
                ReferencedContainer = "container:..">
             </BuildableReference>
          </TestableReference>
-         <TestableReference
-            skipped = "NO">
-            <BuildableReference
-               BuildableIdentifier = "primary"
-               BlueprintIdentifier = "LaunchFeatureTests"
-               BuildableName = "LaunchFeatureTests"
-               BlueprintName = "LaunchFeatureTests"
-               ReferencedContainer = "container:..">
-            </BuildableReference>
-         </TestableReference>
          <TestableReference
             skipped = "NO">
             <BuildableReference
-- 
GitLab


From 4b4193260d4418fb9cee348a59593b26fe72dbc7 Mon Sep 17 00:00:00 2001
From: Dariusz Rybicki <dariusz@elixxir.io>
Date: Wed, 31 Aug 2022 16:12:56 +0100
Subject: [PATCH 09/10] Update run-tests script

---
 run-tests.sh | 3 ---
 1 file changed, 3 deletions(-)

diff --git a/run-tests.sh b/run-tests.sh
index 1f9d60c4..1a87f661 100755
--- a/run-tests.sh
+++ b/run-tests.sh
@@ -1,9 +1,6 @@
 #!/bin/sh
 set -e
 
-echo "\n\033[1;32m▶ Xcode version\033[0m"
-xcodebuild -version
-
 if [ "$1" = "macos" ]; then
 
   echo "\n\033[1;32m▶ Running package tests on macOS...\033[0m"
-- 
GitLab


From 3dc278e6b26c8bcf70353ef4b92b87d3c805b31c Mon Sep 17 00:00:00 2001
From: Dariusz Rybicki <dariusz@elixxir.io>
Date: Wed, 31 Aug 2022 16:13:22 +0100
Subject: [PATCH 10/10] Update GitLab CI config

---
 .gitlab-ci.yml | 7 +++++--
 1 file changed, 5 insertions(+), 2 deletions(-)

diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index 855b6566..10f45cd3 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -1,3 +1,8 @@
+before_script:
+  - for ip in $(dig @8.8.8.8 github.com +short); do ssh-keyscan github.com,$ip; ssh-keyscan $ip; done 2>/dev/null >> ~/.ssh/known_hosts
+  - for ip in $(dig @8.8.8.8 git.xx.network +short); do ssh-keyscan git.xx.network,$ip; ssh-keyscan $ip; done 2>/dev/null >> ~/.ssh/known_hosts
+  - xcodebuild -version
+
 stages:
   - test
 
@@ -22,7 +27,5 @@ test-examples-ios:
   tags: 
     - ios
   script:
-    - for ip in $(dig @8.8.8.8 github.com +short); do ssh-keyscan github.com,$ip; ssh-keyscan $ip; done 2>/dev/null >> ~/.ssh/known_hosts
-    - for ip in $(dig @8.8.8.8 git.xx.network +short); do ssh-keyscan git.xx.network,$ip; ssh-keyscan $ip; done 2>/dev/null >> ~/.ssh/known_hosts
     - ./run-tests.sh examples-ios
   retry: 1
-- 
GitLab