mirror of
https://github.com/luxfi/safe-ios.git
synced 2026-10-30 15:19:20 +00:00
696 lines
26 KiB
Swift
696 lines
26 KiB
Swift
//
|
|
// MainTabBarViewController.swift
|
|
// Multisig
|
|
//
|
|
// Created by Moaaz on 3/25/21.
|
|
// Copyright © 2021 Gnosis Ltd. All rights reserved.
|
|
//
|
|
|
|
import UIKit
|
|
import WhatsNewKit
|
|
|
|
class MainTabBarViewController: UITabBarController {
|
|
var onFirstAppear: (_ vc: MainTabBarViewController) -> Void = { _ in
|
|
}
|
|
|
|
private weak var transactionsSegementControl: SegmentViewController?
|
|
private var appearsFirstTime: Bool = true
|
|
private var addOwnerFlow: UpdateOwnersFromInviteLinkFlow!
|
|
private var addSafeFlow: AddSafeFlow!
|
|
private var createSafeFlow: CreateSafeFlow!
|
|
|
|
// In-memory queue of incoming requests to present. Due to limitation of UIKit,
|
|
// only one view controller can be presented at the same time.
|
|
fileprivate var requestQueue: [WebConnectionRequest] = []
|
|
fileprivate var debounceTimer: Timer?
|
|
fileprivate var presentingRequest: Bool = false
|
|
|
|
enum Path {
|
|
static let assets: IndexPath = [0]
|
|
static let balances: IndexPath = assets.appending(0)
|
|
static let collectibles: IndexPath = assets.appending(1)
|
|
|
|
static let transactions: IndexPath = [1]
|
|
static let queue: IndexPath = transactions.appending(0)
|
|
static let history: IndexPath = transactions.appending(1)
|
|
|
|
static let dapps: IndexPath = [2]
|
|
|
|
static let settings: IndexPath = [3]
|
|
static let appSettings: IndexPath = settings.appending(0)
|
|
static let safeSettings: IndexPath = settings.appending(1)
|
|
|
|
static let count = [assets, transactions, dapps, settings].count
|
|
}
|
|
|
|
lazy var balancesTabVC: BalancesUINavigationController = {
|
|
balancesTabViewController()
|
|
}()
|
|
|
|
lazy var transactionsTabVC: UIViewController = {
|
|
transactionsTabViewController()
|
|
}()
|
|
|
|
lazy var dappsTabVC: UIViewController = {
|
|
dappsTabViewController()
|
|
}()
|
|
|
|
lazy var settingsTabVC: SettingsUINavigationController = {
|
|
settingsTabViewController()
|
|
}()
|
|
|
|
override func viewDidLoad() {
|
|
super.viewDidLoad()
|
|
|
|
tabBar.isTranslucent = false
|
|
tabBar.barTintColor = .backgroundSecondary
|
|
tabBar.backgroundColor = .backgroundSecondary
|
|
|
|
updateTabs()
|
|
|
|
let notificationCenter = NotificationCenter.default
|
|
|
|
notificationCenter.addObserver(
|
|
self,
|
|
selector: #selector(showHistoryTransactions),
|
|
name: .incommingTxNotificationReceived,
|
|
object: nil)
|
|
|
|
notificationCenter.addObserver(
|
|
self,
|
|
selector: #selector(showQueuedTransactions),
|
|
name: .queuedTxNotificationReceived,
|
|
object: nil)
|
|
|
|
notificationCenter.addObserver(
|
|
self,
|
|
selector: #selector(handleConfirmTransactionNotificationReceived),
|
|
name: .confirmationTxNotificationReceived,
|
|
object: nil)
|
|
|
|
notificationCenter.addObserver(
|
|
self,
|
|
selector: #selector(handleInitiateTransactionNotificationReceived),
|
|
name: .initiateTxNotificationReceived,
|
|
object: nil)
|
|
|
|
notificationCenter.addObserver(
|
|
self,
|
|
selector: #selector(handleSafeCreated),
|
|
name: .safeCreationUpdate,
|
|
object: nil)
|
|
|
|
notificationCenter.addObserver(
|
|
self,
|
|
selector: #selector(handleDataRemoved),
|
|
name: .passcodeDeleted,
|
|
object: nil)
|
|
|
|
WebConnectionController.shared.attach(observer: self)
|
|
}
|
|
|
|
deinit {
|
|
WebConnectionController.shared.detach(observer: self)
|
|
}
|
|
|
|
override func viewDidAppear(_ animated: Bool) {
|
|
super.viewDidAppear(animated)
|
|
guard appearsFirstTime else {
|
|
return
|
|
}
|
|
appearsFirstTime = false
|
|
|
|
onFirstAppear(self)
|
|
|
|
if let whatsNewVC = WhatsNewHandler().whatsNewViewController {
|
|
present(whatsNewVC, animated: true)
|
|
}
|
|
|
|
WebConnectionController.shared.reconnect()
|
|
|
|
presentDelayedControllers()
|
|
}
|
|
|
|
private func balancesTabViewController() -> BalancesUINavigationController {
|
|
|
|
let assetsVC = AssetsViewController()
|
|
|
|
let noSafesVC = NoSafesViewController()
|
|
let loadSafeViewController = LoadSafeViewController()
|
|
loadSafeViewController.trackingEvent = .assetsNoSafe
|
|
|
|
let deploySafeVC = SafeDeployingViewController()
|
|
|
|
let ribbonVC = RibbonViewController(rootViewController: assetsVC)
|
|
noSafesVC.hasSafeViewController = ribbonVC
|
|
noSafesVC.noSafeViewController = loadSafeViewController
|
|
|
|
noSafesVC.safeDepolyingViewContoller = ViewControllerFactory.ribbonWith(viewController: deploySafeVC)
|
|
let tabRoot = HeaderViewController(rootViewController: noSafesVC)
|
|
let balances = balancesTabViewController(
|
|
root: tabRoot,
|
|
title: "Assets",
|
|
image: UIImage(named: "tab-icon-balances.pdf")!,
|
|
tag: Path.balances[0]
|
|
)
|
|
balances.assetsViewController = assetsVC
|
|
|
|
return balances
|
|
}
|
|
|
|
private func transactionsTabViewController() -> UIViewController {
|
|
let queuedTransactionsViewController = QueuedTransactionsViewController()
|
|
let historyTransactionsViewController = HistoryTransactionsViewController()
|
|
|
|
let segmentVC = SegmentViewController(namedClass: nil)
|
|
segmentVC.segmentItems = [
|
|
SegmentBarItem(image: UIImage(named: "ico-queued-transactions")!, title: "QUEUE"),
|
|
SegmentBarItem(image: UIImage(named: "ico-history-transactions")!, title: "HISTORY")
|
|
]
|
|
segmentVC.viewControllers = [
|
|
queuedTransactionsViewController,
|
|
historyTransactionsViewController
|
|
]
|
|
segmentVC.selectedIndex = Path.queue.last
|
|
|
|
let noSafesVC = NoSafesViewController()
|
|
let loadSafeViewController = LoadSafeViewController()
|
|
let deploySafeVC = SafeDeployingViewController()
|
|
|
|
loadSafeViewController.trackingEvent = .transactionsNoSafe
|
|
let ribbonVC = RibbonViewController(rootViewController: segmentVC)
|
|
noSafesVC.hasSafeViewController = ribbonVC
|
|
noSafesVC.noSafeViewController = loadSafeViewController
|
|
noSafesVC.safeDepolyingViewContoller = ViewControllerFactory.ribbonWith(viewController: deploySafeVC)
|
|
|
|
let tabRoot = HeaderViewController(rootViewController: noSafesVC)
|
|
transactionsSegementControl = segmentVC
|
|
|
|
return tabViewController(
|
|
root: tabRoot,
|
|
title: "Transactions",
|
|
image: UIImage(named: "tab-icon-transactions")!,
|
|
tag: Path.transactions[0])
|
|
}
|
|
|
|
private func dappsTabViewController() -> UIViewController {
|
|
let noSafesVC = NoSafesViewController()
|
|
let loadSafeViewController = LoadSafeViewController()
|
|
let deploySafeVC = SafeDeployingViewController()
|
|
|
|
loadSafeViewController.trackingEvent = .dappsNoSafe
|
|
noSafesVC.hasSafeViewController = RibbonViewController(rootViewController: DappsViewController())
|
|
noSafesVC.noSafeViewController = loadSafeViewController
|
|
noSafesVC.safeDepolyingViewContoller = ViewControllerFactory.ribbonWith(viewController: deploySafeVC)
|
|
|
|
let tabRoot = HeaderViewController(rootViewController: noSafesVC)
|
|
return tabViewController(
|
|
root: tabRoot,
|
|
title: "dApps",
|
|
image: UIImage(named: "tab-icon-dapps")!,
|
|
tag: Path.dapps[0]
|
|
)
|
|
}
|
|
|
|
private func settingsTabViewController() -> SettingsUINavigationController {
|
|
let noSafesVC = NoSafesViewController()
|
|
let loadSafeViewController = LoadSafeViewController()
|
|
let deploySafeVC = SafeDeployingViewController()
|
|
let safeSettingsVC = SafeSettingsViewController()
|
|
|
|
loadSafeViewController.trackingEvent = .settingsSafeNoSafe
|
|
noSafesVC.hasSafeViewController = safeSettingsVC
|
|
noSafesVC.noSafeViewController = loadSafeViewController
|
|
noSafesVC.safeDepolyingViewContoller = deploySafeVC
|
|
|
|
let appSettingsVC = AppSettingsViewController()
|
|
|
|
let segmentVC = SegmentViewController(namedClass: nil)
|
|
segmentVC.segmentItems = [
|
|
SegmentBarItem(image: UIImage(named: "ico-app-settings")!, title: "App Settings"),
|
|
SegmentBarItem(image: UIImage(named: "ico-safe-settings")!, title: "My Safe Account")
|
|
]
|
|
segmentVC.viewControllers = [
|
|
appSettingsVC,
|
|
noSafesVC
|
|
]
|
|
segmentVC.selectedIndex = Path.appSettings.last
|
|
let ribbonVC = RibbonViewController(rootViewController: segmentVC)
|
|
|
|
let tabRoot = HeaderViewController(rootViewController: ribbonVC)
|
|
let settingsTabVC = settingsTabViewController(
|
|
root: tabRoot,
|
|
title: "Settings",
|
|
image: UIImage(named: "tab-icon-settings")!,
|
|
tag: Path.settings[0]
|
|
)
|
|
settingsTabVC.segmentViewController = segmentVC
|
|
settingsTabVC.appSettingsViewController = appSettingsVC
|
|
settingsTabVC.safeSettingsViewController = safeSettingsVC
|
|
return settingsTabVC
|
|
}
|
|
|
|
private func balancesTabViewController(root: UIViewController, title: String, image: UIImage, tag: Int) -> BalancesUINavigationController {
|
|
let nav = BalancesUINavigationController(rootViewController: root)
|
|
let tabItem = UITabBarItem(title: title, image: image, tag: tag)
|
|
nav.tabBarItem = tabItem
|
|
return nav
|
|
}
|
|
|
|
private func settingsTabViewController(root: UIViewController, title: String, image: UIImage, tag: Int) -> SettingsUINavigationController {
|
|
let nav = SettingsUINavigationController(rootViewController: root)
|
|
let tabItem = UITabBarItem(title: title, image: image, tag: tag)
|
|
nav.tabBarItem = tabItem
|
|
nav.showBadge()
|
|
return nav
|
|
}
|
|
|
|
private func tabViewController(root: UIViewController, title: String, image: UIImage, tag: Int) -> UIViewController {
|
|
let nav = UINavigationController(rootViewController: root)
|
|
let tabItem = UITabBarItem(title: title, image: image, tag: tag)
|
|
nav.tabBarItem = tabItem
|
|
return nav
|
|
}
|
|
|
|
@objc private func handleInitiateTransactionNotificationReceived(_ notification: Notification) {
|
|
if let transactionDetails = notification.userInfo?["transactionDetails"] as? SCGModels.TransactionDetails {
|
|
showTransactionDetails(transactionDetails: transactionDetails)
|
|
} else {
|
|
showQueuedTransactions()
|
|
}
|
|
}
|
|
|
|
@objc private func showQueuedTransactions() {
|
|
switchTo(indexPath: Path.queue)
|
|
}
|
|
|
|
@objc private func showHistoryTransactions() {
|
|
switchTo(indexPath: Path.history)
|
|
}
|
|
|
|
@objc private func updateTabs() {
|
|
viewControllers = [balancesTabVC, transactionsTabVC, dappsTabVC, settingsTabVC]
|
|
}
|
|
|
|
@objc func handleConfirmTransactionNotificationReceived(_ notification: Notification) {
|
|
guard let safeTxHash = App.shared.notificationHandler.transactionDetailsPayload else { return }
|
|
App.shared.notificationHandler.transactionDetailsPayload = nil
|
|
showTransactionDetails(safeTxHash: safeTxHash)
|
|
}
|
|
|
|
@objc private func handleSafeCreated(_ notification: Notification) {
|
|
// get mode, txHash, and safe if creation successful from the notification
|
|
if let isSuccess = notification.userInfo?["success"] as? Bool,
|
|
let safe = notification.userInfo?["safe"] as? Safe {
|
|
if safe.isSelected && presentedViewController == nil {
|
|
if isSuccess {
|
|
presentSuccessDeployment(safe: safe)
|
|
} else {
|
|
presentFailedDeployment(safe: safe)
|
|
}
|
|
} else {
|
|
SafeDeploymentNotificationController.sendNotification(safe: safe)
|
|
}
|
|
}
|
|
}
|
|
|
|
@objc private func handleDataRemoved(_ notification: Notification) {
|
|
if presentedViewController != nil {
|
|
dismiss(animated: true)
|
|
}
|
|
}
|
|
|
|
private func presentSuccessDeployment(safe: Safe) {
|
|
SafeDeploymentFinishedViewController.present(
|
|
presenter: self,
|
|
mode: .success,
|
|
chain: safe.chain!,
|
|
txHash: nil,
|
|
safe: safe,
|
|
onClose: { },
|
|
onRetry: { })
|
|
}
|
|
|
|
private func presentFailedDeployment(safe: Safe) {
|
|
let params = SafeCreationCall.by(safe: safe).first
|
|
let txHash = params?.transactionHash
|
|
SafeDeploymentFinishedViewController.present(
|
|
presenter: self,
|
|
mode: .failure,
|
|
chain: safe.chain!,
|
|
txHash: txHash,
|
|
safe: safe,
|
|
onClose: {
|
|
Safe.remove(safe: safe)
|
|
},
|
|
onRetry: { [weak self] in
|
|
Tracker.trackEvent(.createSafeRetry)
|
|
let createSafeVC = CreateSafeViewController()
|
|
createSafeVC.txHash = txHash
|
|
if let chain = safe.chain {
|
|
createSafeVC.chain = chain
|
|
}
|
|
createSafeVC.onClose = { [weak self] in
|
|
self?.dismiss(animated: true, completion: nil)
|
|
}
|
|
let vc = ViewControllerFactory.modal(viewController: createSafeVC)
|
|
self?.present(vc, animated: true)
|
|
})
|
|
}
|
|
|
|
private func showTransactionDetails(transactionId: String) {
|
|
let vc = ViewControllerFactory.transactionDetailsViewController(transactionId: transactionId)
|
|
presentWhenReady(vc)
|
|
}
|
|
|
|
private func showTransactionDetails(safeTxHash: Data) {
|
|
let vc = ViewControllerFactory.transactionDetailsViewController(safeTxHash: safeTxHash)
|
|
presentWhenReady(vc)
|
|
}
|
|
|
|
private func showTransactionDetails(transactionDetails: SCGModels.TransactionDetails) {
|
|
let vc = ViewControllerFactory.transactionDetailsViewController(transaction: transactionDetails)
|
|
presentWhenReady(vc)
|
|
}
|
|
|
|
private var delayedPresentationViewController: UIViewController?
|
|
|
|
private func presentWhenReady(_ vc: UIViewController) {
|
|
if view.window == nil {
|
|
delayedPresentationViewController = vc
|
|
} else if presentedViewController == nil {
|
|
present(vc, animated: true)
|
|
} else {
|
|
dismiss(animated: true) { [unowned self] in
|
|
present(vc, animated: true)
|
|
}
|
|
}
|
|
}
|
|
|
|
private func presentDelayedControllers() {
|
|
if let vc = delayedPresentationViewController, presentedViewController == nil, view.window != nil {
|
|
present(vc, animated: true)
|
|
delayedPresentationViewController = nil
|
|
}
|
|
}
|
|
}
|
|
|
|
extension MainTabBarViewController: NavigationRouter {
|
|
func routeFrom(from url: URL) -> NavigationRoute? {
|
|
nil
|
|
}
|
|
|
|
func canNavigate(to route: NavigationRoute) -> Bool {
|
|
if route.path.starts(with: "/settings/") {
|
|
return true
|
|
} else if route.path.starts(with: "/transactions/") {
|
|
return true
|
|
} else if route.path == NavigationRoute.showAssets().path {
|
|
return true
|
|
} else if route.path == NavigationRoute.showCollectibles().path {
|
|
return true
|
|
} else if route.path == NavigationRoute.deploymentFailedPath {
|
|
return true
|
|
} else if route.path == NavigationRoute.requestToAddOwnerPath {
|
|
return true
|
|
} else if route.path == NavigationRoute.loadSafe().path {
|
|
return true
|
|
} else if route.path == NavigationRoute.createSafe().path {
|
|
return true
|
|
} else if route.path == NavigationRoute.dapps().path {
|
|
return true
|
|
}
|
|
|
|
return false
|
|
}
|
|
|
|
func navigate(to route: NavigationRoute) {
|
|
if route.path.starts(with: "/settings/") {
|
|
selectSafe(from: route)
|
|
|
|
if route.path == NavigationRoute.appSettings().path {
|
|
switchTo(indexPath: Path.appSettings)
|
|
} else if route.path == NavigationRoute.accountSettingsPath {
|
|
switchTo(indexPath: Path.safeSettings)
|
|
} else if route.path == NavigationRoute.accountAdvancedSettingsPath {
|
|
switchTo(indexPath: Path.safeSettings)
|
|
|
|
if let safeSettingsVC = settingsTabVC.safeSettingsViewController {
|
|
safeSettingsVC.navigateAfterDelay(to: route)
|
|
}
|
|
} else if NavigationRoute.appSettingsDetailPaths.contains(route.path) {
|
|
switchTo(indexPath: Path.appSettings)
|
|
|
|
if let appSettingsVC = settingsTabVC.appSettingsViewController {
|
|
appSettingsVC.navigateAfterDelay(to: route)
|
|
}
|
|
}
|
|
|
|
} else if route.path.starts(with: "/transactions/") {
|
|
if let rawAddress = route.info["address"] as? String,
|
|
let rawChainId = route.info["chainId"] as? String {
|
|
guard let safe = Safe.by(address: rawAddress, chainId: rawChainId) else {
|
|
// don't navigate, exit because the route can't work.
|
|
return
|
|
}
|
|
if !safe.isSelected {
|
|
safe.select()
|
|
}
|
|
if let transactionId = route.info["transactionId"] as? String {
|
|
showTransactionDetails(transactionId: transactionId)
|
|
} else if route.path.contains("history") {
|
|
switchTo(indexPath: Path.history)
|
|
} else {
|
|
switchTo(indexPath: Path.queue)
|
|
}
|
|
}
|
|
} else if route.path == NavigationRoute.showAssets().path {
|
|
guard selectSafe(from: route) else { return }
|
|
switchTo(indexPath: Path.balances)
|
|
} else if route.path == NavigationRoute.showCollectibles().path {
|
|
guard selectSafe(from: route) else { return }
|
|
switchTo(indexPath: Path.collectibles)
|
|
} else if route.path == NavigationRoute.deploymentFailedPath {
|
|
presentFailedDeployment(safe: route.info["safe"] as! Safe)
|
|
} else if route.path == NavigationRoute.requestToAddOwnerPath {
|
|
let parameters = route.info["parameters"] as! AddOwnerRequestParameters
|
|
|
|
addOwnerFlow = UpdateOwnersFromInviteLinkFlow(parameters: parameters) { [unowned self] _ in
|
|
addOwnerFlow = nil
|
|
dismiss(animated: true)
|
|
}
|
|
|
|
present(flow: addOwnerFlow)
|
|
} else if route.path == NavigationRoute.loadSafe().path {
|
|
Tracker.trackEvent(.addSafeFromURL)
|
|
|
|
let chain = route.info["chainId"] as? String
|
|
let address = route.info["address"] as? String
|
|
addSafeFlow = AddSafeFlow(chainId: chain, address: address, completion: { [weak self] _ in
|
|
self?.addSafeFlow = nil
|
|
})
|
|
present(flow: addSafeFlow)
|
|
} else if route.path == NavigationRoute.createSafe().path {
|
|
Tracker.trackEvent(.createSafeFromURL)
|
|
|
|
let chain = route.info["chainId"] as? String
|
|
|
|
createSafeFlow = CreateSafeFlow(chainId: chain, completion: { [weak self] _ in
|
|
self?.createSafeFlow = nil
|
|
})
|
|
|
|
present(flow: createSafeFlow)
|
|
} else if route.path == NavigationRoute.dapps().path {
|
|
selectSafe(from: route)
|
|
switchTo(indexPath: Path.dapps)
|
|
}
|
|
}
|
|
|
|
|
|
// if there is address and chain id, then switch to that safe.
|
|
// if such safe doesn't exist, then do nothing.
|
|
// if no parameters passed, just switch to balances.
|
|
@discardableResult
|
|
private func selectSafe(from route: NavigationRoute) -> Bool {
|
|
if let address = route.info["address"] as? String,
|
|
let chainId = route.info["chainId"] as? String,
|
|
let safe = Safe.by(address: address, chainId: chainId),
|
|
!safe.isSelected {
|
|
safe.select()
|
|
return true
|
|
}
|
|
return false
|
|
}
|
|
|
|
func switchTo(indexPath: IndexPath) {
|
|
var indexPath = indexPath
|
|
|
|
guard !indexPath.isEmpty else { return }
|
|
let index = indexPath.removeFirst()
|
|
|
|
guard index < Path.count else { return }
|
|
switchTab(index: index)
|
|
|
|
guard !indexPath.isEmpty else { return }
|
|
let segment = indexPath.removeFirst()
|
|
|
|
switch index {
|
|
case Path.assets.first:
|
|
switchAssets(segment: segment)
|
|
|
|
case Path.transactions.first:
|
|
switchTransactions(segment: segment)
|
|
|
|
case Path.settings.first:
|
|
switchSettings(segment: segment)
|
|
|
|
default:
|
|
break
|
|
}
|
|
}
|
|
|
|
func switchTransactions(segment: Int) {
|
|
if let segmentVC = transactionsSegementControl, segment < segmentVC.segmentItems.count {
|
|
segmentVC.selectSegment(at: segment)
|
|
}
|
|
}
|
|
|
|
func switchTab(index: Int) {
|
|
guard let vcs = viewControllers, index < vcs.count, index != selectedIndex else { return }
|
|
selectedIndex = index
|
|
}
|
|
|
|
func switchAssets(segment: Int) {
|
|
guard let vc = balancesTabVC.assetsViewController, segment < vc.segmentVC.segmentItems.count else { return }
|
|
vc.segmentVC.selectSegment(at: segment)
|
|
}
|
|
|
|
func switchSettings(segment: Int) {
|
|
guard let segmentVC = settingsTabVC.segmentViewController, segment < segmentVC.segmentItems.count else {
|
|
return
|
|
}
|
|
segmentVC.selectSegment(at: segment)
|
|
}
|
|
}
|
|
|
|
class SettingsUINavigationController: UINavigationController {
|
|
weak var segmentViewController: SegmentViewController?
|
|
weak var appSettingsViewController: AppSettingsViewController?
|
|
weak var safeSettingsViewController: SafeSettingsViewController?
|
|
|
|
override init(rootViewController: UIViewController) {
|
|
super.init(rootViewController: rootViewController)
|
|
NotificationCenter.default.addObserver(
|
|
self,
|
|
selector: #selector(showBadge),
|
|
name: NSNotification.Name.IntercomUnreadConversationCountDidChange,
|
|
object: nil)
|
|
NotificationCenter.default.addObserver(
|
|
self,
|
|
selector: #selector(showBadge),
|
|
name: NSNotification.Name.didReadConnectToWebBanner,
|
|
object: nil)
|
|
}
|
|
|
|
required init?(coder aDecoder: NSCoder) {
|
|
super.init(coder: aDecoder)
|
|
}
|
|
|
|
var shouldShowBadge: Bool {
|
|
IntercomConfig.unreadConversationCount() > 0 ||
|
|
AppSettingsViewController.shouldBringAttentionToDesktopPairing()
|
|
}
|
|
|
|
@objc func showBadge() {
|
|
if shouldShowBadge {
|
|
tabBarItem.badgeValue = ""
|
|
tabBarItem.badgeColor = UIColor.warning
|
|
} else {
|
|
tabBarItem.badgeValue = nil
|
|
}
|
|
}
|
|
}
|
|
|
|
class BalancesUINavigationController: UINavigationController {
|
|
weak var assetsViewController: AssetsViewController?
|
|
}
|
|
|
|
|
|
extension MainTabBarViewController: WebConnectionRequestObserver {
|
|
func didUpdate(request: WebConnectionRequest) {
|
|
guard request.status == .pending else { return }
|
|
requestQueue.append(request)
|
|
debounceTimer?.invalidate()
|
|
debounceTimer = Timer.scheduledTimer(withTimeInterval: 0.25, repeats: false, block: { [weak self] _ in
|
|
self?.presentRequests()
|
|
})
|
|
}
|
|
|
|
/// Presents next request from the queue.
|
|
/// The way how UIKit presentation works is that you can present only one
|
|
/// screen at the same time, and you can't present another one during any presentation animation.
|
|
/// That's why we queue the requests and open next from the queue when the current one is closed.
|
|
func presentRequests() {
|
|
guard !requestQueue.isEmpty && !presentingRequest else { return }
|
|
let request = requestQueue.removeLast()
|
|
|
|
// need to skip the requests that are retained in memory but for which connection is closed.
|
|
// otherwise the view controllers would crash (they expect the connection to be opened)
|
|
guard let connection = WebConnectionController.shared.connection(for: request), connection.status == .opened else {
|
|
presentRequests()
|
|
return
|
|
}
|
|
|
|
presentingRequest = true
|
|
let completion: () -> Void = { [weak self] in
|
|
self?.presentingRequest = false
|
|
self?.presentRequests()
|
|
}
|
|
|
|
switch request {
|
|
case let signRequest as WebConnectionSignatureRequest:
|
|
let vc = SignatureRequestViewController()
|
|
present(controller: vc, request: signRequest, completion: completion)
|
|
|
|
case let txRequest as WebConnectionSendTransactionRequest:
|
|
let vc = SendTransactionRequestViewController()
|
|
present(controller: vc, request: txRequest, completion: completion)
|
|
|
|
default:
|
|
presentingRequest = false
|
|
presentRequests()
|
|
break
|
|
}
|
|
}
|
|
|
|
fileprivate func present<T, R>(controller: T, request: R, completion: @escaping () -> Void) where T: WebRequestViewController, T: UIViewController, T.Request == R, R: WebConnectionRequest {
|
|
controller.request = request
|
|
controller.controller = WebConnectionController.shared
|
|
controller.connection = WebConnectionController.shared.connection(for: request)
|
|
controller.onFinish = { [weak self] in
|
|
self?.dismiss(animated: true, completion: completion)
|
|
}
|
|
let vc = ViewControllerFactory.modal(viewController: controller)
|
|
|
|
if let existing = presentedViewController {
|
|
existing.dismiss(animated: true) { [unowned self] in
|
|
present(vc, animated: true)
|
|
}
|
|
} else {
|
|
present(vc, animated: true)
|
|
}
|
|
}
|
|
}
|
|
|
|
protocol WebRequestViewController: AnyObject {
|
|
associatedtype Request
|
|
var request: Request! { get set }
|
|
var controller: WebConnectionController! { get set }
|
|
var connection: WebConnection! { get set }
|
|
var onFinish: () -> Void { get set }
|
|
}
|
|
|
|
extension SignatureRequestViewController: WebRequestViewController {}
|
|
|
|
extension SendTransactionRequestViewController: WebRequestViewController {}
|