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