diff --git a/p2p_wallet.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/p2p_wallet.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index de283aa23d..4c3db4a688 100644 --- a/p2p_wallet.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/p2p_wallet.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -15,8 +15,8 @@ "repositoryURL": "https://github.com/amplitude/Amplitude-iOS.git", "state": { "branch": null, - "revision": "1928898381581349061808be9b9b143e3c2160ed", - "version": "8.16.2" + "revision": "084a48e2ecd904c00cc5c9345d233f9395e196fa", + "version": "8.16.3" } }, { @@ -78,8 +78,8 @@ "repositoryURL": "https://github.com/krzyzanowskim/CryptoSwift.git", "state": { "branch": null, - "revision": "eee9ad754926c40a0f7e73f152357d37b119b7fa", - "version": "1.7.1" + "revision": "32f641cf24fc7abc1c591a2025e9f2f572648b0f", + "version": "1.7.2" } }, { @@ -267,8 +267,8 @@ "repositoryURL": "https://github.com/marmelroy/PhoneNumberKit.git", "state": { "branch": null, - "revision": "7e22cf3e7d40a6530bbdff255d6350de890512de", - "version": "3.6.1" + "revision": "83e68b5fea032939fd9dcb178cd5bc01fde7ee58", + "version": "3.6.2" } }, { diff --git a/p2p_wallet/AppCoordinator/Coordinator.swift b/p2p_wallet/AppCoordinator/Coordinator.swift index ea7684c5c4..00e3a956fa 100644 --- a/p2p_wallet/AppCoordinator/Coordinator.swift +++ b/p2p_wallet/AppCoordinator/Coordinator.swift @@ -1,17 +1,6 @@ -// -// Coordinator.swift -// p2p_wallet -// -// Created by Chung Tran on 21/07/2022. -// - import Combine import Foundation -enum CoordinatorError: Error { - case isAlreadyStarted -} - @MainActor open class Coordinator: NSObject { // MARK: - Properties diff --git a/p2p_wallet/AppDelegate.swift b/p2p_wallet/AppDelegate.swift index a38e80b45a..99910607ed 100644 --- a/p2p_wallet/AppDelegate.swift +++ b/p2p_wallet/AppDelegate.swift @@ -1,11 +1,5 @@ -// -// AppDelegate.swift -// p2p wallet -// -// Created by Chung Tran on 10/22/20. -// - @_exported import BEPureLayout +import Combine import Firebase import Intercom import KeyAppUI @@ -18,7 +12,8 @@ import UIKit @main class AppDelegate: UIResponder, UIApplicationDelegate { var window: UIWindow? - private var appCoordinator: AppCoordinator? + private var appCoordinator: AppCoordinator! + private var subscriptions = [AnyCancellable]() @Injected private var notificationService: NotificationService @@ -70,9 +65,9 @@ class AppDelegate: UIResponder, UIApplicationDelegate { Lokalise.shared.swizzleMainBundle() // Set app coordinator - appCoordinator = AppCoordinator() - appCoordinator!.start() - window = appCoordinator?.window + window = UIWindow(frame: UIScreen.main.bounds) + appCoordinator = AppCoordinator(window: window!) + appCoordinator.start().sink { _ in }.store(in: &subscriptions) // notify notification Service notificationService.wasAppLaunchedFromPush(launchOptions: launchOptions) diff --git a/p2p_wallet/Coordinators/AppCoordinator/AppCoordinator+AppEventHandlerDelegate.swift b/p2p_wallet/Coordinators/AppCoordinator/AppCoordinator+AppEventHandlerDelegate.swift index 5d8980a0b4..b0de8c0deb 100644 --- a/p2p_wallet/Coordinators/AppCoordinator/AppCoordinator+AppEventHandlerDelegate.swift +++ b/p2p_wallet/Coordinators/AppCoordinator/AppCoordinator+AppEventHandlerDelegate.swift @@ -12,11 +12,11 @@ import SolanaSwift extension AppCoordinator: AppEventHandlerDelegate { func didStartLoading() { - window?.showLoadingIndicatorView() + window.showLoadingIndicatorView() } func didStopLoading() { - window?.hideLoadingIndicatorView() + window.hideLoadingIndicatorView() } func userDidChangeAPIEndpoint(to _: APIEndPoint) { @@ -43,7 +43,7 @@ extension AppCoordinator: AppEventHandlerDelegate { } func userDidChangeTheme(to style: UIUserInterfaceStyle) { - window?.overrideUserInterfaceStyle = style + window.overrideUserInterfaceStyle = style } func disablePincodeOnFirstAppear() { diff --git a/p2p_wallet/Coordinators/AppCoordinator/AppCoordinator.swift b/p2p_wallet/Coordinators/AppCoordinator/AppCoordinator.swift index c515e8a60b..dc12d50a39 100644 --- a/p2p_wallet/Coordinators/AppCoordinator/AppCoordinator.swift +++ b/p2p_wallet/Coordinators/AppCoordinator/AppCoordinator.swift @@ -1,10 +1,3 @@ -// -// AppCoordinator.swift -// p2p_wallet -// -// Created by Chung Tran on 23/05/2022. -// - import AnalyticsManager import Combine import Foundation @@ -31,16 +24,15 @@ final class AppCoordinator: Coordinator { // MARK: - Properties - var window: UIWindow? + let window: UIWindow var showAuthenticationOnMainOnAppear = true var reloadEvent = PassthroughSubject() - private var walletCreated: Bool = false - // MARK: - Initializers - override init() { + init(window: UIWindow) { + self.window = window super.init() defer { appEventHandler.delegate = self } bind() @@ -48,143 +40,122 @@ final class AppCoordinator: Coordinator { // MARK: - Methods - /// Starting point for coordinator - func start() { - // set window - window = UIWindow(frame: UIScreen.main.bounds) - - // set appearance - window?.overrideUserInterfaceStyle = Defaults.appearance - - // open splash and wait for data - openSplash { [unowned self] in - userWalletManager - .$wallet - .combineLatest( - reloadEvent - .map { _ in } - .prepend(()) - ) - .receive(on: RunLoop.main) - .sink { [unowned self] wallet, _ in - if let wallet { - sendUserIdentifierToAnalyticsProviders(wallet) - if walletCreated, available(.onboardingUsernameEnabled) { - walletCreated = false - navigateToCreateUsername() - } else { - navigateToMain() - } - } else { - sendUserIdentifierToAnalyticsProviders(nil) - navigateToOnboardingFlow() - } - } - .store(in: &subscriptions) - } + enum AppCoordinatorEvent { + case onboarding + case createUsername + case wallet(UserWallet) } - // MARK: - Navigation + private var eventHandler = PassthroughSubject() - /// Open splash scene and wait for loading - private func openSplash(_ completionHandler: @escaping () -> Void) { - // TODO: - Return for new splash screen - // let vc = SplashViewController() - // window?.rootViewController = vc - // window?.makeKeyAndVisible() + /// Starting point for coordinator + override func start() -> AnyPublisher { + // set appearance + window.overrideUserInterfaceStyle = Defaults.appearance + // 1. Opening splash let vc = BaseVC() let lockView = LockView() vc.view.addSubview(lockView) lockView.autoPinEdgesToSuperviewEdges() - window?.rootViewController = vc - window?.makeKeyAndVisible() + window.rootViewController = vc + window.makeKeyAndVisible() - // warmup - Task { - await Resolver.resolve(WarmupManager.self).start() - try await userWalletManager.refresh() - - // if let splashVC = window?.rootViewController as? SplashViewController { - // splashVC.stop(completionHandler: completionHandler) - // } else { - completionHandler() - // } - } + userWalletManager.$wallet + .dropFirst() + .combineLatest( + reloadEvent.map { _ in }.prepend(()) + ) + .handleEvents(receiveOutput: { wallet, _ in + self.sendUserIdentifierToAnalyticsProviders(wallet) + }) + .map { wallet, _ in + if let wallet { + return AppCoordinatorEvent.wallet(wallet) + } else { + return AppCoordinatorEvent.onboarding + } + }.sink(receiveValue: { [unowned self] event in + self.eventHandler.send(event) + }).store(in: &subscriptions) + + // infinite handler + return eventHandler + .receive(on: RunLoop.main) + .handleEvents(receiveSubscription: { [weak self] _ in + Task { + await Resolver.resolve(WarmupManager.self).start() + try await self?.userWalletManager.refresh() + } + }) + .flatMap({ [unowned self] event in + switch event { + case .onboarding: + return self.navigateToOnboardingFlow() + case .createUsername: + return self.coordinate(to: CreateUsernameCoordinator(navigationOption: .onboarding(window: self.window))) + .withLatestFrom(self.userWalletManager.$wallet) + .compactMap { $0 } + .handleEvents(receiveOutput: { wallet in + self.eventHandler.send(.wallet(wallet)) + }) + .map { _ in Void() } + .eraseToAnyPublisher() + case .wallet(_): + return self.navigateToMain(window: self.window) + .handleEvents(receiveSubscription: { [weak self] _ in + self?.warmUpMain() + }) + .eraseToAnyPublisher() + } + }) + .eraseToAnyPublisher() } - /// Navigate to CreateUserName scene - private func navigateToCreateUsername() { - guard let window = window else { return } - coordinate(to: CreateUsernameCoordinator(navigationOption: .onboarding(window: window))) - .sink { [unowned self] in - self.navigateToMain() - }.store(in: &subscriptions) - } + // MARK: - Navigation /// Navigate to Main scene - private func navigateToMain() { - guard let window = window else { return } - - Task.detached { - await Resolver.resolve(WalletMetadataService.self).synchronize() - } - - Task { - try await Resolver.resolve(OrcaSwapType.self).load() - } - - Task { - await Resolver.resolve(JupiterTokensRepository.self).load() - } - - Task { - // load services - if available(.sellScenarioEnabled) { - await Resolver.resolve((any SellDataService).self).checkAvailability() - } - - // coordinate - await MainActor.run { [unowned self] in - let coordinator = TabBarCoordinator( - window: window, - authenticateWhenAppears: showAuthenticationOnMainOnAppear - ) - coordinate(to: coordinator) - .sink(receiveValue: {}) - .store(in: &subscriptions) - } - } + private func navigateToMain(window: UIWindow) -> AnyPublisher { + let coordinator = TabBarCoordinator( + window: window, + authenticateWhenAppears: showAuthenticationOnMainOnAppear + ) + return coordinate(to: coordinator) } /// Navigate to onboarding flow if user is not yet created - private func navigateToOnboardingFlow() { - guard let window = window else { return } + private func navigateToOnboardingFlow() -> AnyPublisher { let provider = Resolver.resolve(StartOnboardingNavigationProvider.self) let startCoordinator = provider.startCoordinator(for: window) - coordinate(to: startCoordinator) - .sinkAsync(receiveValue: { [unowned self] result in + return coordinate(to: startCoordinator) + .asyncMap { [unowned self] result -> AppCoordinatorEvent in GlobalAppState.shared.shouldPlayAnimationOnHome = true showAuthenticationOnMainOnAppear = false let userWalletManager: UserWalletManager = Resolver.resolve() switch result { case let .created(data): - walletCreated = true analyticsManager.log(event: .setupOpen(fromPage: "create_wallet")) analyticsManager.log(event: .createConfirmPin(result: true)) saveSecurity(data: data.security) // Setup user wallet - try await userWalletManager.add( - seedPhrase: data.wallet.seedPhrase.components(separatedBy: " "), - derivablePath: data.wallet.derivablePath, - name: nil, - deviceShare: data.deviceShare, - ethAddress: data.ethAddress - ) - + do { + try await userWalletManager.add( + seedPhrase: data.wallet.seedPhrase.components(separatedBy: " "), + derivablePath: data.wallet.derivablePath, + name: nil, + deviceShare: data.deviceShare, + ethAddress: data.ethAddress + ) + } catch { + fatalError("Wallet must be") + } + guard let wallet = userWalletManager.wallet else { + fatalError("Wallet must be") + } + return available(.onboardingUsernameEnabled) ? AppCoordinatorEvent.createUsername : .wallet(wallet) case let .restored(data): analyticsManager.log(event: .restoreConfirmPin(result: true)) @@ -193,23 +164,54 @@ final class AppCoordinator: Coordinator { saveSecurity(data: data.security) // Setup user wallet - try await userWalletManager.add( - seedPhrase: data.wallet.seedPhrase.components(separatedBy: " "), - derivablePath: data.wallet.derivablePath, - name: nil, - deviceShare: nil, - ethAddress: data.ethAddress - ) - + do { + try await userWalletManager.add( + seedPhrase: data.wallet.seedPhrase.components(separatedBy: " "), + derivablePath: data.wallet.derivablePath, + name: nil, + deviceShare: nil, + ethAddress: data.ethAddress + ) + } catch { + fatalError("Wallet must be") + } + guard let wallet = userWalletManager.wallet else { + fatalError("Wallet must be") + } + return AppCoordinatorEvent.wallet(wallet) case .breakProcess: - navigateToOnboardingFlow() + return .onboarding } + } + .handleEvents(receiveOutput: { [weak self] result in + self?.eventHandler.send(result) }) - .store(in: &subscriptions) + .map { _ in }.eraseToAnyPublisher() } // MARK: - Helper + private func warmUpMain() { + Task.detached { + await Resolver.resolve(WalletMetadataService.self).synchronize() + } + + Task { + try await Resolver.resolve(OrcaSwapType.self).load() + } + + Task { + await Resolver.resolve(JupiterTokensRepository.self).load() + } + + Task { + // load services + if available(.sellScenarioEnabled) { + await Resolver.resolve((any SellDataService).self).checkAvailability() + } + } + } + private func sendUserIdentifierToAnalyticsProviders(_ wallet: UserWallet?) { // Amplitude let amplitudeAnalyticsProvider: AmplitudeAnalyticsProvider = Resolver.resolve() @@ -233,8 +235,8 @@ final class AppCoordinator: Coordinator { } private func hideLoadingAndTransitionTo(_ vc: UIViewController) { - window?.rootViewController?.view.hideLoadingIndicatorView() - window?.animate(newRootViewController: vc) + window.rootViewController?.view.hideLoadingIndicatorView() + window.animate(newRootViewController: vc) } private func bind() { @@ -242,7 +244,7 @@ final class AppCoordinator: Coordinator { .receive(on: DispatchQueue.main) .sink { [weak self] isSuccess in if isSuccess { - guard let view = self?.window?.rootViewController?.view else { return } + guard let view = self?.window.rootViewController?.view else { return } SnackBar(title: "🎉", icon: nil, text: L10n.nameWasBooked).show(in: view) } else { self?.notificationService.showDefaultErrorNotification() diff --git a/p2p_wallet/Services/Storage/UserWalletManager.swift b/p2p_wallet/Services/Storage/UserWalletManager.swift index 130d0465c7..a422c33514 100644 --- a/p2p_wallet/Services/Storage/UserWalletManager.swift +++ b/p2p_wallet/Services/Storage/UserWalletManager.swift @@ -34,7 +34,10 @@ class UserWalletManager: ObservableObject { try await storage.reloadSolanaAccount() // Legacy code - guard let account = storage.account else { return } + guard let account = storage.account else { + wallet = nil + return + } let moonpayAccount = try await KeyPair( phrase: account.phrase,