From 817d15c149b10ea2e5f05bf977993d424b48e8ac Mon Sep 17 00:00:00 2001
From: Bruno Muniz Azevedo Filho <bruno@elixxir.io>
Date: Fri, 29 Jul 2022 00:34:59 -0300
Subject: [PATCH] Finished adding search to pending invitation when opening the
 app

---
 App/client-ios/Resources/Info.plist           |  4 +--
 Sources/App/AppDelegate.swift                 | 18 +++++------
 Sources/App/DependencyRegistrator.swift       |  1 +
 Sources/Defaults/KeyObject.swift              |  1 +
 Sources/Integration/Session/Session+UD.swift  | 17 ++++++++++-
 Sources/Integration/Session/SessionType.swift |  2 ++
 Sources/LaunchFeature/LaunchController.swift  |  2 ++
 Sources/LaunchFeature/LaunchCoordinator.swift | 10 +++++++
 Sources/LaunchFeature/LaunchViewModel.swift   | 19 ++++++++++--
 .../ViewModels/MenuViewModel.swift            |  2 +-
 Sources/MenuFeature/Views/MenuView.swift      |  2 +-
 .../Controllers/SearchLeftController.swift    | 12 ++++++++
 .../ViewModels/SearchLeftViewModel.swift      | 30 +++++++++++++++++++
 Sources/Shared/Views/SearchComponent.swift    |  4 +++
 14 files changed, 105 insertions(+), 19 deletions(-)

diff --git a/App/client-ios/Resources/Info.plist b/App/client-ios/Resources/Info.plist
index ed25d86c..dd578c50 100644
--- a/App/client-ios/Resources/Info.plist
+++ b/App/client-ios/Resources/Info.plist
@@ -34,10 +34,10 @@
 		</dict>
 		<dict>
 			<key>CFBundleURLName</key>
-			<string>xxmessenger</string>
+			<string>xxnetwork</string>
 			<key>CFBundleURLSchemes</key>
 			<array>
-				<string>xxmessenger</string>
+				<string>xxnetwork</string>
 			</array>
 		</dict>
 		<dict>
diff --git a/Sources/App/AppDelegate.swift b/Sources/App/AppDelegate.swift
index f2ba599d..fdf6090e 100644
--- a/Sources/App/AppDelegate.swift
+++ b/Sources/App/AppDelegate.swift
@@ -20,11 +20,11 @@ public class AppDelegate: UIResponder, UIApplicationDelegate {
     @Dependency private var crashReporter: CrashReporter
     @Dependency private var dropboxService: DropboxInterface
 
+    @KeyObject(.invitation, defaultValue: nil) var invitation: String?
     @KeyObject(.hideAppList, defaultValue: false) var hideAppList: Bool
     @KeyObject(.recordingLogs, defaultValue: true) var recordingLogs: Bool
     @KeyObject(.crashReporting, defaultValue: true) var isCrashReportingEnabled: Bool
 
-    var invitation: String?
     var calledStopNetwork = false
     var forceFailedPendingMessages = false
 
@@ -134,12 +134,6 @@ public class AppDelegate: UIResponder, UIApplicationDelegate {
     }
 
     public func applicationDidBecomeActive(_ application: UIApplication) {
-        // TODO:
-        /// If an invitation is set -> navigate to
-        /// search screen and perform a search
-        ///
-        invitation = nil
-
         application.applicationIconBadgeNumber = 0
         coverView?.removeFromSuperview()
     }
@@ -149,12 +143,14 @@ public class AppDelegate: UIResponder, UIApplicationDelegate {
         open url: URL,
         options: [UIApplication.OpenURLOptionsKey : Any] = [:]
     ) -> Bool {
-        if let host = url.host, host.starts(with: "invitation-") {
-            invitation = host.replacingOccurrences(of: "invitation-", with: "")
+        if let components = URLComponents(url: url, resolvingAgainstBaseURL: false),
+           let invitation = components.queryItems?.first(where: { $0.name == "invitation" }),
+            let username = invitation.value {
+            self.invitation = username
             return true
+        } else {
+            return dropboxService.handleOpenUrl(url)
         }
-
-        return dropboxService.handleOpenUrl(url)
     }
 }
 
diff --git a/Sources/App/DependencyRegistrator.swift b/Sources/App/DependencyRegistrator.swift
index 14a7b7bd..7c4d70a9 100644
--- a/Sources/App/DependencyRegistrator.swift
+++ b/Sources/App/DependencyRegistrator.swift
@@ -113,6 +113,7 @@ struct DependencyRegistrator {
 
         container.register(
             LaunchCoordinator(
+                searchFactory: SearchContainerController.init,
                 requestsFactory: RequestsContainerController.init,
                 chatListFactory: ChatListController.init,
                 onboardingFactory: OnboardingStartController.init(_:),
diff --git a/Sources/Defaults/KeyObject.swift b/Sources/Defaults/KeyObject.swift
index 7757f7ed..5714b112 100644
--- a/Sources/Defaults/KeyObject.swift
+++ b/Sources/Defaults/KeyObject.swift
@@ -21,6 +21,7 @@ public enum Key: String {
     // MARK: General
 
     case theme
+    case invitation
 
     // MARK: Requests
 
diff --git a/Sources/Integration/Session/Session+UD.swift b/Sources/Integration/Session/Session+UD.swift
index 1fe3e2e0..27add4b1 100644
--- a/Sources/Integration/Session/Session+UD.swift
+++ b/Sources/Integration/Session/Session+UD.swift
@@ -1,9 +1,24 @@
+import Retry
 import Models
+import Combine
 import XXModels
 import Foundation
-import Combine
 
 extension Session {
+    public func waitForNodes(timeout: Int) -> AnyPublisher<Void, Error> {
+        Deferred {
+            Future { promise in
+                retry(max: timeout, retryStrategy: .delay(seconds: 1)) { [weak self] in
+                    guard let self = self else { return }
+                    try self.client.bindings.nodeRegistrationStatus()
+                    promise(.success(()))
+                }.finalCatch {
+                    promise(.failure($0))
+                }
+            }
+        }.eraseToAnyPublisher()
+    }
+
     public func search(fact: String) -> AnyPublisher<Contact, Error> {
         Deferred {
             Future { promise in
diff --git a/Sources/Integration/Session/SessionType.swift b/Sources/Integration/Session/SessionType.swift
index b871332b..effd6c96 100644
--- a/Sources/Integration/Session/SessionType.swift
+++ b/Sources/Integration/Session/SessionType.swift
@@ -68,4 +68,6 @@ public protocol SessionType {
     )
 
     func search(fact: String) -> AnyPublisher<Contact, Error>
+
+    func waitForNodes(timeout: Int) -> AnyPublisher<Void, Error>
 }
diff --git a/Sources/LaunchFeature/LaunchController.swift b/Sources/LaunchFeature/LaunchController.swift
index 8db4bd63..9eeeb763 100644
--- a/Sources/LaunchFeature/LaunchController.swift
+++ b/Sources/LaunchFeature/LaunchController.swift
@@ -49,6 +49,8 @@ public final class LaunchController: UIViewController {
             .receive(on: DispatchQueue.main)
             .sink { [unowned self] in
                 switch $0 {
+                case .search:
+                    coordinator.toSearch(from: self)
                 case .chats:
                     if let pushRoute = pendingPushRoute {
                         switch pushRoute {
diff --git a/Sources/LaunchFeature/LaunchCoordinator.swift b/Sources/LaunchFeature/LaunchCoordinator.swift
index 4f5a5629..a9612d61 100644
--- a/Sources/LaunchFeature/LaunchCoordinator.swift
+++ b/Sources/LaunchFeature/LaunchCoordinator.swift
@@ -5,6 +5,7 @@ import Presentation
 
 public protocol LaunchCoordinating {
     func toChats(from: UIViewController)
+    func toSearch(from: UIViewController)
     func toRequests(from: UIViewController)
     func toOnboarding(with: String, from: UIViewController)
     func toSingleChat(with: Contact, from: UIViewController)
@@ -14,6 +15,7 @@ public protocol LaunchCoordinating {
 public struct LaunchCoordinator: LaunchCoordinating {
     var replacePresenter: Presenting = ReplacePresenter()
 
+    var searchFactory: () -> UIViewController
     var requestsFactory: () -> UIViewController
     var chatListFactory: () -> UIViewController
     var onboardingFactory: (String) -> UIViewController
@@ -21,12 +23,14 @@ public struct LaunchCoordinator: LaunchCoordinating {
     var groupChatFactory: (GroupInfo) -> UIViewController
 
     public init(
+        searchFactory: @escaping () -> UIViewController,
         requestsFactory: @escaping () -> UIViewController,
         chatListFactory: @escaping () -> UIViewController,
         onboardingFactory: @escaping (String) -> UIViewController,
         singleChatFactory: @escaping (Contact) -> UIViewController,
         groupChatFactory: @escaping (GroupInfo) -> UIViewController
     ) {
+        self.searchFactory = searchFactory
         self.requestsFactory = requestsFactory
         self.chatListFactory = chatListFactory
         self.groupChatFactory = groupChatFactory
@@ -36,6 +40,12 @@ public struct LaunchCoordinator: LaunchCoordinating {
 }
 
 public extension LaunchCoordinator {
+    func toSearch(from parent: UIViewController) {
+        let screen = searchFactory()
+        let chatListScreen = chatListFactory()
+        replacePresenter.present(chatListScreen, screen, from: parent)
+    }
+
     func toChats(from parent: UIViewController) {
         let screen = chatListFactory()
         replacePresenter.present(screen, from: parent)
diff --git a/Sources/LaunchFeature/LaunchViewModel.swift b/Sources/LaunchFeature/LaunchViewModel.swift
index fefbe7cf..42b69bb7 100644
--- a/Sources/LaunchFeature/LaunchViewModel.swift
+++ b/Sources/LaunchFeature/LaunchViewModel.swift
@@ -24,6 +24,7 @@ struct Update {
 
 enum LaunchRoute {
     case chats
+    case search
     case update(Update)
     case onboarding(String)
 }
@@ -36,6 +37,7 @@ final class LaunchViewModel {
     @Dependency private var permissionHandler: PermissionHandling
 
     @KeyObject(.username, defaultValue: nil) var username: String?
+    @KeyObject(.invitation, defaultValue: nil) var invitation: String?
     @KeyObject(.biometrics, defaultValue: false) var isBiometricsOn: Bool
 
     var hudPublisher: AnyPublisher<HUDStatus, Never> {
@@ -180,17 +182,28 @@ final class LaunchViewModel {
     private func checkBiometrics() {
         if permissionHandler.isBiometricsAvailable && isBiometricsOn {
             permissionHandler.requestBiometrics { [weak self] in
+                guard let self = self else { return }
+
                 switch $0 {
                 case .success(let granted):
                     guard granted else { return }
-                    self?.routeSubject.send(.chats)
+
+                    if self.invitation != nil {
+                        self.routeSubject.send(.search)
+                    } else {
+                        self.routeSubject.send(.chats)
+                    }
 
                 case .failure(let error):
-                    self?.hudSubject.send(.error(HUDError(with: error)))
+                    self.hudSubject.send(.error(HUDError(with: error)))
                 }
             }
         } else {
-            routeSubject.send(.chats)
+            if self.invitation != nil {
+                self.routeSubject.send(.search)
+            } else {
+                self.routeSubject.send(.chats)
+            }
         }
     }
 }
diff --git a/Sources/MenuFeature/ViewModels/MenuViewModel.swift b/Sources/MenuFeature/ViewModels/MenuViewModel.swift
index 20f91ac9..98bc5d74 100644
--- a/Sources/MenuFeature/ViewModels/MenuViewModel.swift
+++ b/Sources/MenuFeature/ViewModels/MenuViewModel.swift
@@ -42,6 +42,6 @@ final class MenuViewModel {
     }
 
     var referralDeeplink: String {
-        "xxmessenger://invitation-\(username)"
+        "xxnetwork://messenger?invitation=\(username)"
     }
 }
diff --git a/Sources/MenuFeature/Views/MenuView.swift b/Sources/MenuFeature/Views/MenuView.swift
index 7342ced7..e256782e 100644
--- a/Sources/MenuFeature/Views/MenuView.swift
+++ b/Sources/MenuFeature/Views/MenuView.swift
@@ -72,7 +72,7 @@ final class MenuView: UIView {
             requestsButton.set(color: Asset.brandPrimary.color)
         case .settings:
             settingsButton.set(color: Asset.brandPrimary.color)
-        default:
+        case .share, .join, .profile, .dashboard:
             break
         }
     }
diff --git a/Sources/SearchFeature/Controllers/SearchLeftController.swift b/Sources/SearchFeature/Controllers/SearchLeftController.swift
index 6be71bfe..b3ca0e83 100644
--- a/Sources/SearchFeature/Controllers/SearchLeftController.swift
+++ b/Sources/SearchFeature/Controllers/SearchLeftController.swift
@@ -37,6 +37,11 @@ final class SearchLeftController: UIViewController {
         setupBindings()
     }
 
+    override func viewDidAppear(_ animated: Bool) {
+        super.viewDidAppear(animated)
+        viewModel.viewDidAppear()
+    }
+
     func endEditing() {
         screenView.inputField.endEditing(true)
     }
@@ -131,6 +136,13 @@ final class SearchLeftController: UIViewController {
             .sink { [unowned self] in screenView.countryButton.setFlag($0.flag, prefix: $0.prefix) }
             .store(in: &cancellables)
 
+        viewModel.statePublisher
+            .map(\.input)
+            .removeDuplicates()
+            .receive(on: DispatchQueue.main)
+            .sink { [unowned self] in screenView.inputField.update(content: $0) }
+            .store(in: &cancellables)
+
         viewModel.statePublisher
             .compactMap(\.snapshot)
             .receive(on: DispatchQueue.main)
diff --git a/Sources/SearchFeature/ViewModels/SearchLeftViewModel.swift b/Sources/SearchFeature/ViewModels/SearchLeftViewModel.swift
index 6c0a8390..e9c6e68d 100644
--- a/Sources/SearchFeature/ViewModels/SearchLeftViewModel.swift
+++ b/Sources/SearchFeature/ViewModels/SearchLeftViewModel.swift
@@ -3,8 +3,10 @@ import UIKit
 import Shared
 import Combine
 import XXModels
+import Defaults
 import Countries
 import Integration
+import NetworkMonitor
 import DependencyInjection
 
 typealias SearchSnapshot = NSDiffableDataSourceSnapshot<SearchSection, SearchItem>
@@ -18,6 +20,9 @@ struct SearchLeftViewState {
 
 final class SearchLeftViewModel {
     @Dependency var session: SessionType
+    @Dependency var networkMonitor: NetworkMonitoring
+
+    @KeyObject(.invitation, defaultValue: nil) var invitation: String?
 
     var hudPublisher: AnyPublisher<HUDStatus, Never> {
         hudSubject.eraseToAnyPublisher()
@@ -35,6 +40,31 @@ final class SearchLeftViewModel {
     private let successSubject = PassthroughSubject<Contact, Never>()
     private let hudSubject = CurrentValueSubject<HUDStatus, Never>(.none)
     private let stateSubject = CurrentValueSubject<SearchLeftViewState, Never>(.init())
+    private var networkCancellable = Set<AnyCancellable>()
+
+    func viewDidAppear() {
+        if let pendingInvitation = invitation {
+            invitation = nil
+            stateSubject.value.input = pendingInvitation
+            hudSubject.send(.onAction(Localized.Ud.Search.cancel))
+
+            networkMonitor.statusPublisher
+                .first { $0 == .available }
+                .map { [unowned self] _ in
+                    session.waitForNodes(timeout: 5).first()
+                        .sink {
+                            if case .failure(let error) = $0 {
+                                self.hudSubject.send(.error(.init(with: error)))
+                            }
+                            networkCancellable.removeAll()
+                        } receiveValue: { _ in
+                            self.didStartSearching()
+                            networkCancellable.removeAll()
+                        }.store(in: &networkCancellable)
+                }.sink(receiveValue: { _ in })
+                .store(in: &networkCancellable)
+        }
+    }
 
     func didEnterInput(_ string: String) {
         stateSubject.value.input = string
diff --git a/Sources/Shared/Views/SearchComponent.swift b/Sources/Shared/Views/SearchComponent.swift
index 9608aad7..b3d53099 100644
--- a/Sources/Shared/Views/SearchComponent.swift
+++ b/Sources/Shared/Views/SearchComponent.swift
@@ -115,6 +115,10 @@ public final class SearchComponent: UIView {
         }
     }
 
+    public func update(content: String) {
+        inputField.text = content
+    }
+
     public func update(placeholder: String) {
         inputField.attributedPlaceholder = NSAttributedString(
             string: placeholder,
-- 
GitLab