From 431818a13fb4f499baf52e36562aaa375903cc24 Mon Sep 17 00:00:00 2001 From: Iana Date: Mon, 21 Oct 2024 18:41:29 +0300 Subject: [PATCH 1/4] [trello.com/c/F7EV0pKq] Feat: No active nodes popup --- .../Chat/View/ChatViewController.swift | 12 ++++++ .../View/Managers/ChatDialogManager.swift | 5 ++- .../Chat/ViewModel/ChatViewModel.swift | 38 ++++++++++++----- .../Chat/ViewModel/Models/ChatDialog.swift | 1 + .../Login/LoginViewController+Pinpad.swift | 25 ++++++----- .../Modules/Login/LoginViewController.swift | 20 ++++++++- .../Wallets/TransferViewControllerBase.swift | 28 ++++++++----- Adamant/ServiceProtocols/AccountService.swift | 4 +- Adamant/ServiceProtocols/DialogService.swift | 4 ++ Adamant/Services/AdamantAccountService.swift | 42 ++++++++++--------- Adamant/Services/AdamantDialogService.swift | 28 +++++++++++++ .../Localization/de.lproj/Localizable.strings | 5 ++- .../Localization/en.lproj/Localizable.strings | 5 ++- .../Localization/ru.lproj/Localizable.strings | 5 ++- .../Localization/zh.lproj/Localizable.strings | 5 ++- .../Localization/AdamantLocalized.swift | 4 ++ 16 files changed, 173 insertions(+), 58 deletions(-) diff --git a/Adamant/Modules/Chat/View/ChatViewController.swift b/Adamant/Modules/Chat/View/ChatViewController.swift index f639afeeb..b6537bd5f 100644 --- a/Adamant/Modules/Chat/View/ChatViewController.swift +++ b/Adamant/Modules/Chat/View/ChatViewController.swift @@ -477,6 +477,10 @@ private extension ChatViewController { self?.didTapSelectText(text: text) } .store(in: &subscriptions) + + viewModel.presentNodeListVC + .sink { [weak self] in self?.presentNodeListViewer() } + .store(in: &subscriptions) } } @@ -661,6 +665,14 @@ private extension ChatViewController { self.chatDropView.alpha = value ? 1.0 : .zero } } + + func presentNodeListViewer() { + let vc = screensFactory.makeNodesList() + let nav = UINavigationController(rootViewController: vc) + nav.modalPresentationStyle = .pageSheet + + self.present(nav, animated: true, completion: nil) + } } // MARK: Tap on title view diff --git a/Adamant/Modules/Chat/View/Managers/ChatDialogManager.swift b/Adamant/Modules/Chat/View/Managers/ChatDialogManager.swift index 2d3fcc368..13afe3310 100644 --- a/Adamant/Modules/Chat/View/Managers/ChatDialogManager.swift +++ b/Adamant/Modules/Chat/View/Managers/ChatDialogManager.swift @@ -25,7 +25,8 @@ final class ChatDialogManager { typealias DidSelectEmojiAction = ((_ emoji: String, _ messageId: String) -> Void)? typealias ContextMenuAction = ((_ messageId: String) -> Void)? - + typealias NoActiveNodesAction = (() -> Void) + init( viewModel: ChatViewModel, dialogService: DialogService, @@ -108,6 +109,8 @@ private extension ChatDialogManager { showRenameAlert() case .actionMenu: showActionMenu() + case .noActiveNodesAlert(let name, let action): + dialogService.showNoActiveNodesAlert(nodeName: name, completion: action) } } diff --git a/Adamant/Modules/Chat/ViewModel/ChatViewModel.swift b/Adamant/Modules/Chat/ViewModel/ChatViewModel.swift index a068075df..b0e7ee7db 100644 --- a/Adamant/Modules/Chat/ViewModel/ChatViewModel.swift +++ b/Adamant/Modules/Chat/ViewModel/ChatViewModel.swift @@ -97,6 +97,7 @@ final class ChatViewModel: NSObject { let presentDocumentPickerVC = ObservableSender() let presentDocumentViewerVC = ObservableSender<([FileResult], Int)>() let presentDropView = ObservableSender() + let presentNodeListVC = ObservableSender() @ObservableValue private(set) var isHeaderLoading = false @ObservableValue private(set) var fullscreenLoading = false @@ -294,9 +295,15 @@ final class ChatViewModel: NSObject { } guard apiServiceCompose.hasActiveNode(group: .adm) else { - dialog.send(.alert(ApiServiceError.noEndpointsAvailable( - nodeGroupName: NodeGroup.adm.name - ).localizedDescription)) + dialog.send( + .noActiveNodesAlert( + nodeName: NodeGroup.adm.name, + action: { [weak self] in + guard let self = self else { return } + self.presentNodeListVC.send() + } + ) + ) return } @@ -709,12 +716,17 @@ final class ChatViewModel: NSObject { } guard apiServiceCompose.hasActiveNode(group: .adm) else { - dialog.send(.alert(ApiServiceError.noEndpointsAvailable( - nodeGroupName: NodeGroup.adm.name - ).localizedDescription)) + dialog.send( + .noActiveNodesAlert( + nodeName: NodeGroup.adm.name, + action: { [weak self] in + guard let self = self else { return } + self.presentNodeListVC.send() + } + ) + ) return false } - return true } @@ -1063,9 +1075,15 @@ extension ChatViewModel: NSFetchedResultsControllerDelegate { private extension ChatViewModel { func sendFiles(with text: String) async throws { guard apiServiceCompose.hasActiveNode(group: .ipfs) else { - dialog.send(.alert(ApiServiceError.noEndpointsAvailable( - nodeGroupName: NodeGroup.ipfs.name - ).localizedDescription)) + dialog.send( + .noActiveNodesAlert( + nodeName: NodeGroup.adm.name, + action: { [weak self] in + guard let self = self else { return } + self.presentNodeListVC.send() + } + ) + ) return } diff --git a/Adamant/Modules/Chat/ViewModel/Models/ChatDialog.swift b/Adamant/Modules/Chat/ViewModel/Models/ChatDialog.swift index f572003a8..1c810733d 100644 --- a/Adamant/Modules/Chat/ViewModel/Models/ChatDialog.swift +++ b/Adamant/Modules/Chat/ViewModel/Models/ChatDialog.swift @@ -36,4 +36,5 @@ enum ChatDialog { case dismissMenu case renameAlert case actionMenu + case noActiveNodesAlert(nodeName: String, action: ChatDialogManager.NoActiveNodesAction) } diff --git a/Adamant/Modules/Login/LoginViewController+Pinpad.swift b/Adamant/Modules/Login/LoginViewController+Pinpad.swift index 98f99fa64..6be1d5555 100644 --- a/Adamant/Modules/Login/LoginViewController+Pinpad.swift +++ b/Adamant/Modules/Login/LoginViewController+Pinpad.swift @@ -67,16 +67,8 @@ extension LoginViewController { dialogService.showProgress(withMessage: String.adamant.login.loggingInProgressMessage, userInteractionEnable: false) Task { - do { - let result = try await accountService.loginWithStoredAccount() - handleSavedAccountLoginResult(result) - } catch { - dialogService.showRichError(error: error) - - if let pinpad = presentedViewController as? PinpadViewController { - pinpad.clearPin() - } - } + let result = await accountService.loginWithStoredAccount() + handleSavedAccountLoginResult(result) } } @@ -104,13 +96,24 @@ extension LoginViewController { } case .failure(let error): - dialogService.showRichError(error: error) + handleError(error) if let pinpad = presentedViewController as? PinpadViewController { pinpad.clearPin() } } } + + func handleError(_ error: AccountServiceError) { + guard case .apiError(let error) = error else { + dialogService.showRichError(error: error) + return + } + + dismiss(animated: true) { [weak self] in + self?.handleError(error) + } + } } // MARK: - PinpadViewControllerDelegate diff --git a/Adamant/Modules/Login/LoginViewController.swift b/Adamant/Modules/Login/LoginViewController.swift index 680a052bb..ecd9e032c 100644 --- a/Adamant/Modules/Login/LoginViewController.swift +++ b/Adamant/Modules/Login/LoginViewController.swift @@ -421,11 +421,29 @@ extension LoginViewController { loginIntoExistingAccount(passphrase: passphrase) case .failure(let error): - dialogService.showRichError(error: error) + handleError(error) } } } + func handleError(_ error: ApiServiceError) { + guard case .noEndpointsAvailable = error else { + dialogService.showRichError(error: error) + return + } + + dialogService.showNoActiveNodesAlert( + nodeName: NodeGroup.adm.name + ) { [weak self] in + guard let self = self else { return } + + let vc = self.screensFactory.makeNodesList() + let nav = UINavigationController(rootViewController: vc) + nav.modalPresentationStyle = .pageSheet + + self.present(nav, animated: true, completion: nil) + } + } func generateNewPassphrase() { let passphrase = (try? Mnemonic.generate().joined(separator: " ")) ?? .empty diff --git a/Adamant/Modules/Wallets/TransferViewControllerBase.swift b/Adamant/Modules/Wallets/TransferViewControllerBase.swift index 7d69b9c5f..4a6b066fc 100644 --- a/Adamant/Modules/Wallets/TransferViewControllerBase.swift +++ b/Adamant/Modules/Wallets/TransferViewControllerBase.swift @@ -802,20 +802,28 @@ class TransferViewControllerBase: FormViewController { guard apiServiceCompose.hasActiveNode(group: .adm) || admReportRecipient == nil else { - dialogService.showWarning( - withMessage: ApiServiceError.noEndpointsAvailable( - nodeGroupName: NodeGroup.adm.name - ).localizedDescription - ) + dialogService.showNoActiveNodesAlert( + nodeName: NodeGroup.adm.name + ) { [weak self] in + guard let self = self else { return } + let vc = self.screensFactory.makeNodesList() + vc.modalPresentationStyle = .pageSheet + + self.present(vc, animated: true, completion: nil) + } return } guard walletCore.hasActiveNode else { - dialogService.showWarning( - withMessage: ApiServiceError.noEndpointsAvailable( - nodeGroupName: walletCore.tokenName - ).localizedDescription - ) + dialogService.showNoActiveNodesAlert( + nodeName: walletCore.tokenName + ) { [weak self] in + guard let self = self else { return } + let vc = self.screensFactory.makeCoinsNodesList(context: .menu) + vc.modalPresentationStyle = .pageSheet + + self.present(vc, animated: true, completion: nil) + } return } diff --git a/Adamant/ServiceProtocols/AccountService.swift b/Adamant/ServiceProtocols/AccountService.swift index 2c1f7dbad..d051dc383 100644 --- a/Adamant/ServiceProtocols/AccountService.swift +++ b/Adamant/ServiceProtocols/AccountService.swift @@ -161,10 +161,10 @@ protocol AccountService: AnyObject { func update(_ completion: ((AccountServiceResult) -> Void)?) /// Login into Adamant using passphrase. - func loginWith(passphrase: String) async throws -> AccountServiceResult + func loginWith(passphrase: String) async -> AccountServiceResult /// Login into Adamant using previously logged account - func loginWithStoredAccount() async throws -> AccountServiceResult + func loginWithStoredAccount() async -> AccountServiceResult /// Logout func logout() diff --git a/Adamant/ServiceProtocols/DialogService.swift b/Adamant/ServiceProtocols/DialogService.swift index e7cd87f1e..a6415ee32 100644 --- a/Adamant/ServiceProtocols/DialogService.swift +++ b/Adamant/ServiceProtocols/DialogService.swift @@ -176,6 +176,10 @@ protocol DialogService: AnyObject { func showRichError(error: Error) func showNoConnectionNotification() func dissmisNoConnectionNotification() + func showNoActiveNodesAlert( + nodeName: String, + completion: @escaping () -> Void + ) // MARK: - Notifications func showNotification(title: String?, message: String?, image: UIImage?, tapHandler: (() -> Void)?) diff --git a/Adamant/Services/AdamantAccountService.swift b/Adamant/Services/AdamantAccountService.swift index ec16b222a..6224702ae 100644 --- a/Adamant/Services/AdamantAccountService.swift +++ b/Adamant/Services/AdamantAccountService.swift @@ -259,16 +259,16 @@ extension AdamantAccountService { extension AdamantAccountService { // MARK: Passphrase @MainActor - func loginWith(passphrase: String) async throws -> AccountServiceResult { + func loginWith(passphrase: String) async -> AccountServiceResult { guard AdamantUtilities.validateAdamantPassphrase(passphrase: passphrase) else { - throw AccountServiceError.invalidPassphrase + return .failure(.invalidPassphrase) } guard let keypair = adamantCore.createKeypairFor(passphrase: passphrase) else { - throw AccountServiceError.internalError(message: "Failed to generate keypair for passphrase", error: nil) + return .failure(.internalError(message: "Failed to generate keypair for passphrase", error: nil)) } - let account = try await loginWith(keypair: keypair) + let account = await loginWith(keypair: keypair) // MARK: Drop saved accs if let storedPassphrase = self.getSavedPassphrase(), @@ -286,32 +286,36 @@ extension AdamantAccountService { _ = await initWallets() - return .success(account: account, alert: nil) + return account } // MARK: Pincode - func loginWith(pincode: String) async throws -> AccountServiceResult { + func loginWith(pincode: String) async -> AccountServiceResult { guard let storePin = securedStore.get(.pin) else { - throw AccountServiceError.invalidPassphrase + return .failure(.invalidPassphrase) } guard storePin == pincode else { - throw AccountServiceError.invalidPassphrase + return .failure(.invalidPassphrase) } - return try await loginWithStoredAccount() + return await loginWithStoredAccount() } // MARK: Biometry @MainActor - func loginWithStoredAccount() async throws -> AccountServiceResult { + func loginWithStoredAccount() async -> AccountServiceResult { if let passphrase = getSavedPassphrase() { - let account = try await loginWith(passphrase: passphrase) + let account = await loginWith(passphrase: passphrase) return account } if let keypair = getSavedKeypair() { - let account = try await loginWith(keypair: keypair) + let account = await loginWith(keypair: keypair) + + guard case .success(let account, _) = account else { + return account + } let alert: (title: String, message: String)? if securedStore.get(.showedV12) != nil { @@ -329,14 +333,14 @@ extension AdamantAccountService { return .success(account: account, alert: alert) } - throw AccountServiceError.invalidPassphrase + return .failure(.invalidPassphrase) } // MARK: Keypair - private func loginWith(keypair: Keypair) async throws -> AdamantAccount { + private func loginWith(keypair: Keypair) async -> AccountServiceResult { switch state { case .isLoggingIn: - throw AccountServiceError.internalError(message: "Service is busy", error: nil) + return .failure(.internalError(message: "Service is busy", error: nil)) case .updating: fallthrough @@ -365,19 +369,19 @@ extension AdamantAccountService { ) self.state = .loggedIn - return account + return .success(account: account, alert: nil) } catch let error as ApiServiceError { self.state = .notLogged switch error { case .accountNotFound: - throw AccountServiceError.wrongPassphrase + return .failure(AccountServiceError.wrongPassphrase) default: - throw AccountServiceError.apiError(error: error) + return .failure(AccountServiceError.apiError(error: error)) } } catch { - throw AccountServiceError.internalError(message: error.localizedDescription, error: error) + return .failure(.internalError(message: error.localizedDescription, error: error)) } } diff --git a/Adamant/Services/AdamantDialogService.swift b/Adamant/Services/AdamantDialogService.swift index 577954048..971bcf373 100644 --- a/Adamant/Services/AdamantDialogService.swift +++ b/Adamant/Services/AdamantDialogService.swift @@ -282,6 +282,34 @@ extension AdamantDialogService { present(alert, animated: animated, completion: completion) } + func showNoActiveNodesAlert( + nodeName: String, + completion: @escaping () -> Void + ) { + dismissProgress() + let alert = UIAlertController( + title: "", + message: ApiServiceError.noEndpointsAvailable( + nodeGroupName: nodeName).localizedDescription, + preferredStyle: .alert + ) + + let action = UIAlertAction( + title: .adamant.sharedErrors.reviewNodeListButtonTitle(nodeName), + style: .default, + handler: { _ in completion() } + ) + + alert.addAction(action) + alert.addAction(UIAlertAction( + title: String.adamant.alert.cancel, + style: .cancel, + handler: nil) + ) + + self.present(alert, animated: true, completion: nil) + } + func presentShareAlertFor( string: String, types: [ShareType], diff --git a/CommonKit/Sources/CommonKit/Assets/Localization/de.lproj/Localizable.strings b/CommonKit/Sources/CommonKit/Assets/Localization/de.lproj/Localizable.strings index 55b8c1786..0d9f2e9e9 100644 --- a/CommonKit/Sources/CommonKit/Assets/Localization/de.lproj/Localizable.strings +++ b/CommonKit/Sources/CommonKit/Assets/Localization/de.lproj/Localizable.strings @@ -293,11 +293,14 @@ "ApiService.InternalError.ParsingFailed" = "Parsing fehlgeschlagen. Bericht senden"; /* Serious internal error: No nodes available */ -"ApiService.InternalError.NoNodesAvailable" = "Keine aktiven %@ Knoten. Überprüfen Sie die Knotenliste."; +"ApiService.InternalError.NoNodesAvailable" = "Neue Nachrichten können nicht angefordert werden — keine aktiven %@ Blockchain-Knoten. Da Sie einige davon deaktiviert haben, sollten Sie die Knotenliste überprüfen."; /* Serious internal error: No ADM nodes available */ "ApiService.InternalError.NoAdmNodesAvailable" = "Keine aktiven ADM Knoten zum Abrufen der %@ Adresse des Partners."; +/* Button title for alert when all ADM nodes are inactive */ +"AlertButton.ReviewNodeList" = "Überprüfe %@-Knotenliste"; + /* Eureka forms Cancel button */ "Cancel" = "Abbrechen"; diff --git a/CommonKit/Sources/CommonKit/Assets/Localization/en.lproj/Localizable.strings b/CommonKit/Sources/CommonKit/Assets/Localization/en.lproj/Localizable.strings index 89e3429c4..738e1bd2c 100644 --- a/CommonKit/Sources/CommonKit/Assets/Localization/en.lproj/Localizable.strings +++ b/CommonKit/Sources/CommonKit/Assets/Localization/en.lproj/Localizable.strings @@ -290,11 +290,14 @@ "ApiService.InternalError.ParsingFailed" = "Parsing failed. Report a bug"; /* Serious internal error: No nodes available */ -"ApiService.InternalError.NoNodesAvailable" = "No active %@ nodes. Review the node list"; +"ApiService.InternalError.NoNodesAvailable" = "Unable to request new messages — No active %@ blockchain nodes. As you’ve deactivated some of them, consider reviewing the node list."; /* Serious internal error: No ADM nodes available */ "ApiService.InternalError.NoAdmNodesAvailable" = "No active ADM nodes to fetch the partner's %@ address"; +/* Button title for alert when all ADM nodes are inactive */ +"AlertButton.ReviewNodeList" = "Review %@ node list"; + /* Eureka forms Cancel button */ "Cancel" = "Cancel"; diff --git a/CommonKit/Sources/CommonKit/Assets/Localization/ru.lproj/Localizable.strings b/CommonKit/Sources/CommonKit/Assets/Localization/ru.lproj/Localizable.strings index b03173776..47e4ef830 100644 --- a/CommonKit/Sources/CommonKit/Assets/Localization/ru.lproj/Localizable.strings +++ b/CommonKit/Sources/CommonKit/Assets/Localization/ru.lproj/Localizable.strings @@ -290,11 +290,14 @@ "ApiService.InternalError.ParsingFailed" = "Не удалось разобрать ответ узла блокчена. Сообщите разработчикам"; /* Serious internal error: No nodes available */ -"ApiService.InternalError.NoNodesAvailable" = "Нет доступных %@ нод. Просмотрите список узлов"; +"ApiService.InternalError.NoNodesAvailable" = "Не удается получить новые сообщения — нет активных узлов блокчейна %@. Поскольку вы отключили некоторые из них, посмотрите список узлов еще раз."; /* Serious internal error: No ADM nodes available */ "ApiService.InternalError.NoAdmNodesAvailable" = "Нет доступных ADM нод для получения адреса партнера %@"; +/* Button title for alert when all ADM nodes are inactive */ +"AlertButton.ReviewNodeList" = "К списку узлов %@"; + /* Eureka forms Cancel button */ "Cancel" = "Отмена"; diff --git a/CommonKit/Sources/CommonKit/Assets/Localization/zh.lproj/Localizable.strings b/CommonKit/Sources/CommonKit/Assets/Localization/zh.lproj/Localizable.strings index fa57aef0d..0ce7fd7e6 100644 --- a/CommonKit/Sources/CommonKit/Assets/Localization/zh.lproj/Localizable.strings +++ b/CommonKit/Sources/CommonKit/Assets/Localization/zh.lproj/Localizable.strings @@ -290,11 +290,14 @@ "ApiService.InternalError.PassingFailed" = "分析失败。报告错误"; /* Serious internal error: No nodes available */ -"ApiService.InternalError.NoNodesAvailable" = "没有活动的%@节点。查看节点列表"; +"ApiService.InternalError.NoNodesAvailable" = "无法请求新消息 — 没有活跃的%@区块链节点。由于您已停用了一些节点,请考虑检查节点列表."; /* Serious internal error: No ADM nodes available */ "ApiService.InternalError.NoAdmNodesAvailable" = "没有活动的 ADM 节点来获取合作伙伴的 %@ 地址"; +/* Button title for alert when all ADM nodes are inactive */ +"AlertButton.ReviewNodeList" = "审核%@节点列表"; + /* Eureka forms Cancel button */ "Cancel" = "取消"; diff --git a/CommonKit/Sources/CommonKit/Localization/AdamantLocalized.swift b/CommonKit/Sources/CommonKit/Localization/AdamantLocalized.swift index bd4768254..fbb01e1dd 100644 --- a/CommonKit/Sources/CommonKit/Localization/AdamantLocalized.swift +++ b/CommonKit/Sources/CommonKit/Localization/AdamantLocalized.swift @@ -88,6 +88,10 @@ public extension String.adamant { String.localizedStringWithFormat(.localized("ApiService.InternalError.NoAdmNodesAvailable", comment: "No active ADM nodes to fetch the partner's %@ address"), coin) } + public static func reviewNodeListButtonTitle(_ coin: String) -> String { + String.localizedStringWithFormat(.localized("AlertButton.ReviewNodeList", comment: "Button title for alert when all ADM nodes are inactive"), coin) + } + public static var notEnoughMoney: String { String.localized("WalletServices.SharedErrors.notEnoughMoney", comment: "Wallet Services: Shared error, user do not have enought money.") } From 9d8184c49a52f8ec8e3071d07fa9f36d6d5baa6a Mon Sep 17 00:00:00 2001 From: Iana Date: Mon, 4 Nov 2024 10:49:19 +0200 Subject: [PATCH 2/4] [trello.com/c/F7EV0pKq] Code refactoring --- .../UIViewController+NodeListPresenter.swift | 22 +++++++++++++++++++ .../Account/AccountViewController.swift | 15 ++++++++----- .../Chat/View/ChatViewController.swift | 17 +++++++------- .../Modules/ChatsList/ChatListFactory.swift | 3 ++- .../ChatsList/ChatListViewController.swift | 13 +++++++++-- .../ComplexTransferViewController.swift | 21 ++++++++++++------ .../Modules/Login/LoginViewController.swift | 10 ++++----- .../Wallets/TransferViewControllerBase.swift | 22 +++++++++++-------- Adamant/ServiceProtocols/DialogService.swift | 8 +++---- 9 files changed, 88 insertions(+), 43 deletions(-) create mode 100644 Adamant/Helpers/UIViewController+NodeListPresenter.swift diff --git a/Adamant/Helpers/UIViewController+NodeListPresenter.swift b/Adamant/Helpers/UIViewController+NodeListPresenter.swift new file mode 100644 index 000000000..25b784e9e --- /dev/null +++ b/Adamant/Helpers/UIViewController+NodeListPresenter.swift @@ -0,0 +1,22 @@ +// +// UIViewController+NodeListPresenter.swift +// Adamant +// +// Created by Yana Silosieva on 31.10.2024. +// Copyright © 2024 Adamant. All rights reserved. +// + +import UIKit +import CommonKit + +extension UIViewController { + func presentNodeListVC(screensFactory: ScreensFactory, node: NodeGroup) { + let vc = node == .adm + ? screensFactory.makeNodesList() + : screensFactory.makeCoinsNodesList(context: .menu) + + let nav = UINavigationController(rootViewController: vc) + nav.modalPresentationStyle = .pageSheet + self.present(nav, animated: true, completion: nil) + } +} diff --git a/Adamant/Modules/Account/AccountViewController.swift b/Adamant/Modules/Account/AccountViewController.swift index cd9b06907..457cdf093 100644 --- a/Adamant/Modules/Account/AccountViewController.swift +++ b/Adamant/Modules/Account/AccountViewController.swift @@ -1116,11 +1116,16 @@ final class AccountViewController: FormViewController { } if let disabledGroup { - dialogService.showWarning( - withMessage: ApiServiceError.noEndpointsAvailable( - nodeGroupName: disabledGroup.name - ).localizedDescription - ) + dialogService.showNoActiveNodesAlert( + nodeName: disabledGroup.name + ) { [weak self] in + guard let self = self else { return } + + self.presentNodeListVC( + screensFactory: self.screensFactory, + node: disabledGroup + ) + } } refreshControl.endRefreshing() diff --git a/Adamant/Modules/Chat/View/ChatViewController.swift b/Adamant/Modules/Chat/View/ChatViewController.swift index 136e91a6c..da4095ea5 100644 --- a/Adamant/Modules/Chat/View/ChatViewController.swift +++ b/Adamant/Modules/Chat/View/ChatViewController.swift @@ -479,7 +479,14 @@ private extension ChatViewController { .store(in: &subscriptions) viewModel.presentNodeListVC - .sink { [weak self] in self?.presentNodeListViewer() } + .sink { [weak self] in + guard let self = self else { return } + + self.presentNodeListVC( + screensFactory: self.screensFactory, + node: .adm + ) + } .store(in: &subscriptions) } } @@ -665,14 +672,6 @@ private extension ChatViewController { self.chatDropView.alpha = value ? 1.0 : .zero } } - - func presentNodeListViewer() { - let vc = screensFactory.makeNodesList() - let nav = UINavigationController(rootViewController: vc) - nav.modalPresentationStyle = .pageSheet - - self.present(nav, animated: true, completion: nil) - } } // MARK: Tap on title view diff --git a/Adamant/Modules/ChatsList/ChatListFactory.swift b/Adamant/Modules/ChatsList/ChatListFactory.swift index c830143a5..70be56592 100644 --- a/Adamant/Modules/ChatsList/ChatListFactory.swift +++ b/Adamant/Modules/ChatsList/ChatListFactory.swift @@ -43,7 +43,8 @@ struct ChatListFactory { addressBookService: assembler.resolve(AddressBookService.self)!, screensFactory: screensFactory, walletServiceCompose: assembler.resolve(WalletServiceCompose.self)!, - nodesStorage: assembler.resolve(NodesStorageProtocol.self)! + nodesStorage: assembler.resolve(NodesStorageProtocol.self)!, + dialogService: assembler.resolve(DialogService.self)! ) } diff --git a/Adamant/Modules/ChatsList/ChatListViewController.swift b/Adamant/Modules/ChatsList/ChatListViewController.swift index c18e87dcf..d68ae2000 100644 --- a/Adamant/Modules/ChatsList/ChatListViewController.swift +++ b/Adamant/Modules/ChatsList/ChatListViewController.swift @@ -477,8 +477,17 @@ final class ChatListViewController: KeyboardObservingViewController { case .success: tableView.reloadData() - case .failure(let error): - dialogService.showRichError(error: error) + case .failure: + dialogService.showNoActiveNodesAlert( + nodeName: NodeGroup.adm.name + ) { [weak self] in + guard let self = self else { return } + + self.presentNodeListVC( + screensFactory: self.screensFactory, + node: .adm + ) + } } refreshControl.endRefreshing() diff --git a/Adamant/Modules/ChatsList/ComplexTransferViewController.swift b/Adamant/Modules/ChatsList/ComplexTransferViewController.swift index c65750ca8..7433b7655 100644 --- a/Adamant/Modules/ChatsList/ComplexTransferViewController.swift +++ b/Adamant/Modules/ChatsList/ComplexTransferViewController.swift @@ -24,6 +24,7 @@ final class ComplexTransferViewController: UIViewController { private let screensFactory: ScreensFactory private let walletServiceCompose: WalletServiceCompose private let nodesStorage: NodesStorageProtocol + private let dialogService: DialogService // MARK: - Properties var pagingViewController: PagingViewController! @@ -44,13 +45,15 @@ final class ComplexTransferViewController: UIViewController { addressBookService: AddressBookService, screensFactory: ScreensFactory, walletServiceCompose: WalletServiceCompose, - nodesStorage: NodesStorageProtocol + nodesStorage: NodesStorageProtocol, + dialogService: DialogService ) { self.visibleWalletsService = visibleWalletsService self.addressBookService = addressBookService self.screensFactory = screensFactory self.walletServiceCompose = walletServiceCompose self.nodesStorage = nodesStorage + self.dialogService = dialogService super.init(nibName: nil, bundle: nil) } @@ -145,12 +148,16 @@ extension ComplexTransferViewController: PagingViewControllerDataSource { Task { guard service.core.hasEnabledNode else { - vc.showAlertView( - message: ApiServiceError.noEndpointsAvailable( - nodeGroupName: service.core.tokenName - ).errorDescription ?? .adamant.sharedErrors.unknownError, - animated: true - ) + dialogService.showNoActiveNodesAlert( + nodeName: NodeGroup.adm.name + ) { [weak self] in + guard let self = self else { return } + + self.presentNodeListVC( + screensFactory: self.screensFactory, + node: .adm + ) + } return } diff --git a/Adamant/Modules/Login/LoginViewController.swift b/Adamant/Modules/Login/LoginViewController.swift index 1c168875c..7b5e34de2 100644 --- a/Adamant/Modules/Login/LoginViewController.swift +++ b/Adamant/Modules/Login/LoginViewController.swift @@ -461,12 +461,10 @@ extension LoginViewController { nodeName: NodeGroup.adm.name ) { [weak self] in guard let self = self else { return } - - let vc = self.screensFactory.makeNodesList() - let nav = UINavigationController(rootViewController: vc) - nav.modalPresentationStyle = .pageSheet - - self.present(nav, animated: true, completion: nil) + self.presentNodeListVC( + screensFactory: self.screensFactory, + node: .adm + ) } } func generateNewPassphrase() { diff --git a/Adamant/Modules/Wallets/TransferViewControllerBase.swift b/Adamant/Modules/Wallets/TransferViewControllerBase.swift index bcb60fce0..bfee127f3 100644 --- a/Adamant/Modules/Wallets/TransferViewControllerBase.swift +++ b/Adamant/Modules/Wallets/TransferViewControllerBase.swift @@ -797,29 +797,33 @@ class TransferViewControllerBase: FormViewController { } guard - apiServiceCompose.get(.adm)?.hasEnabledNode == true || admReportRecipient == nil + apiServiceCompose.get(.adm)?.hasEnabledNode == true || (admReportRecipient == nil && walletCore.nodeGroups != [.adm]) else { dialogService.showNoActiveNodesAlert( nodeName: NodeGroup.adm.name ) { [weak self] in guard let self = self else { return } - let vc = self.screensFactory.makeNodesList() - vc.modalPresentationStyle = .pageSheet - self.present(vc, animated: true, completion: nil) + self.presentNodeListVC( + screensFactory: self.screensFactory, + node: .adm + ) } return } guard walletCore.hasEnabledNode else { + let network = type(of: walletCore).tokenNetworkSymbol dialogService.showNoActiveNodesAlert( - nodeName: walletCore.tokenName + nodeName: network ) { [weak self] in - guard let self = self else { return } - let vc = self.screensFactory.makeCoinsNodesList(context: .menu) - vc.modalPresentationStyle = .pageSheet + guard let self = self, + let nodeGroup = walletCore.nodeGroups.first else { return } - self.present(vc, animated: true, completion: nil) + self.presentNodeListVC( + screensFactory: self.screensFactory, + node: nodeGroup + ) } return } diff --git a/Adamant/ServiceProtocols/DialogService.swift b/Adamant/ServiceProtocols/DialogService.swift index 17057071a..5a2a4496b 100644 --- a/Adamant/ServiceProtocols/DialogService.swift +++ b/Adamant/ServiceProtocols/DialogService.swift @@ -183,10 +183,6 @@ protocol DialogService: AnyObject { func showRichError(error: Error) func showNoConnectionNotification() func dissmisNoConnectionNotification() - func showNoActiveNodesAlert( - nodeName: String, - completion: @escaping () -> Void - ) // MARK: - Notifications func showNotification(title: String?, message: String?, image: UIImage?, tapHandler: (() -> Void)?) @@ -236,4 +232,8 @@ protocol DialogService: AnyObject { func showAlert(title: String?, message: String?, style: AdamantAlertStyle, actions: [AdamantAlertAction]?, from: UIAlertController.SourceView?) func selectAllTextFields(in alert: UIAlertController) + func showNoActiveNodesAlert( + nodeName: String, + completion: @escaping () -> Void + ) } From d5e5ce34a56e8c4cc9be80a29aa5db7e3acc60e9 Mon Sep 17 00:00:00 2001 From: Iana Date: Mon, 4 Nov 2024 12:23:12 +0200 Subject: [PATCH 3/4] [trello.com/c/F7EV0pKq] Add extension for presenting Node List --- Adamant.xcodeproj/project.pbxproj | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/Adamant.xcodeproj/project.pbxproj b/Adamant.xcodeproj/project.pbxproj index b62a66148..73ec0725c 100644 --- a/Adamant.xcodeproj/project.pbxproj +++ b/Adamant.xcodeproj/project.pbxproj @@ -16,6 +16,7 @@ 2657A0CD2C707D800021E7E6 /* short-success.mp3 in Resources */ = {isa = PBXBuildFile; fileRef = 4198D57E28C8B834009337F2 /* short-success.mp3 */; }; 2657A0CE2C707D830021E7E6 /* default.mp3 in Resources */ = {isa = PBXBuildFile; fileRef = 4198D58028C8B8D1009337F2 /* default.mp3 */; }; 265AA1622B74E6B900CF98B0 /* ChatPreservation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 265AA1612B74E6B900CF98B0 /* ChatPreservation.swift */; }; + 26843D2B2CD8D7130010F047 /* UIViewController+NodeListPresenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 26843D2A2CD8D7130010F047 /* UIViewController+NodeListPresenter.swift */; }; 269B83102C74A2FF002AA1D7 /* note.mp3 in Resources */ = {isa = PBXBuildFile; fileRef = 269B830F2C74A2FF002AA1D7 /* note.mp3 */; }; 269B83112C74A34F002AA1D7 /* note.mp3 in Resources */ = {isa = PBXBuildFile; fileRef = 269B830F2C74A2FF002AA1D7 /* note.mp3 */; }; 269B831E2C74B4EC002AA1D7 /* handoff.mp3 in Resources */ = {isa = PBXBuildFile; fileRef = 269B83122C74B4EA002AA1D7 /* handoff.mp3 */; }; @@ -706,6 +707,7 @@ 2621AB382C60E7AE00046D7A /* NotificationsViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationsViewModel.swift; sourceTree = ""; }; 2621AB3A2C613C8100046D7A /* NotificationsFactory.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationsFactory.swift; sourceTree = ""; }; 265AA1612B74E6B900CF98B0 /* ChatPreservation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChatPreservation.swift; sourceTree = ""; }; + 26843D2A2CD8D7130010F047 /* UIViewController+NodeListPresenter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIViewController+NodeListPresenter.swift"; sourceTree = ""; }; 269B830F2C74A2FF002AA1D7 /* note.mp3 */ = {isa = PBXFileReference; lastKnownFileType = audio.mp3; path = note.mp3; sourceTree = ""; }; 269B83122C74B4EA002AA1D7 /* handoff.mp3 */ = {isa = PBXFileReference; lastKnownFileType = audio.mp3; path = handoff.mp3; sourceTree = ""; }; 269B83132C74B4EA002AA1D7 /* portal.mp3 */ = {isa = PBXFileReference; lastKnownFileType = audio.mp3; path = portal.mp3; sourceTree = ""; }; @@ -2349,6 +2351,7 @@ E913C9101FFFAA4B001A83F7 /* Helpers */ = { isa = PBXGroup; children = ( + 26843D2A2CD8D7130010F047 /* UIViewController+NodeListPresenter.swift */, 93775E452A674FA9009061AC /* Markdown+Adamant.swift */, E9061B96207501E40011F104 /* AdamantUserInfoKey.swift */, E94008862114F05B00CD2D67 /* AddressValidationResult.swift */, @@ -3473,6 +3476,7 @@ E9393FAA2055D03300EE6F30 /* AdamantMessage.swift in Sources */, 938F7D692955C9EC001915CA /* ChatViewModel.swift in Sources */, E90A494D204DA932009F6A65 /* LocalAuthentication.swift in Sources */, + 26843D2B2CD8D7130010F047 /* UIViewController+NodeListPresenter.swift in Sources */, 3A26D9352C3C1BE2003AD832 /* KlyWalletService.swift in Sources */, 41047B70294B5EE10039E956 /* VisibleWalletsViewController.swift in Sources */, 2621AB392C60E7AE00046D7A /* NotificationsViewModel.swift in Sources */, From a682ce58a43882abe0462cb5a45574044d5438d0 Mon Sep 17 00:00:00 2001 From: Iana Date: Fri, 8 Nov 2024 08:28:55 +0200 Subject: [PATCH 4/4] [trello.com/c/F7EV0pKq] Created NodeAvailabilityService --- Adamant.xcodeproj/project.pbxproj | 8 +- .../UIViewController+NodeListPresenter.swift | 22 ---- Adamant/Modules/Account/AccountFactory.swift | 7 +- .../Account/AccountViewController.swift | 29 ++--- .../Chat/View/ChatViewController.swift | 13 ++- .../Chat/ViewModel/ChatViewModel.swift | 8 +- .../Modules/ChatsList/ChatListFactory.swift | 15 ++- .../ChatsList/ChatListViewController.swift | 23 ++-- .../ComplexTransferViewController.swift | 22 ++-- Adamant/Modules/Login/LoginFactory.swift | 7 +- .../Modules/Login/LoginViewController.swift | 19 ++-- .../Wallets/Adamant/AdmWalletFactory.swift | 7 +- .../Wallets/Bitcoin/BtcWalletFactory.swift | 7 +- .../Wallets/Dash/DashWalletFactory.swift | 7 +- .../Wallets/Doge/DogeWalletFactory.swift | 7 +- .../Wallets/ERC20/ERC20WalletFactory.swift | 7 +- .../Wallets/Ethereum/EthWalletFactory.swift | 7 +- .../Wallets/Klayr/KlyWalletFactory.swift | 7 +- .../Wallets/TransferViewControllerBase.swift | 43 +++---- .../Services/NodeAvailabilityService.swift | 107 ++++++++++++++++++ 20 files changed, 238 insertions(+), 134 deletions(-) delete mode 100644 Adamant/Helpers/UIViewController+NodeListPresenter.swift create mode 100644 Adamant/Services/NodeAvailabilityService.swift diff --git a/Adamant.xcodeproj/project.pbxproj b/Adamant.xcodeproj/project.pbxproj index 73ec0725c..3b5d06115 100644 --- a/Adamant.xcodeproj/project.pbxproj +++ b/Adamant.xcodeproj/project.pbxproj @@ -16,7 +16,7 @@ 2657A0CD2C707D800021E7E6 /* short-success.mp3 in Resources */ = {isa = PBXBuildFile; fileRef = 4198D57E28C8B834009337F2 /* short-success.mp3 */; }; 2657A0CE2C707D830021E7E6 /* default.mp3 in Resources */ = {isa = PBXBuildFile; fileRef = 4198D58028C8B8D1009337F2 /* default.mp3 */; }; 265AA1622B74E6B900CF98B0 /* ChatPreservation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 265AA1612B74E6B900CF98B0 /* ChatPreservation.swift */; }; - 26843D2B2CD8D7130010F047 /* UIViewController+NodeListPresenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 26843D2A2CD8D7130010F047 /* UIViewController+NodeListPresenter.swift */; }; + 26843D6A2CDD29760010F047 /* NodeAvailabilityService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 26843D692CDD29710010F047 /* NodeAvailabilityService.swift */; }; 269B83102C74A2FF002AA1D7 /* note.mp3 in Resources */ = {isa = PBXBuildFile; fileRef = 269B830F2C74A2FF002AA1D7 /* note.mp3 */; }; 269B83112C74A34F002AA1D7 /* note.mp3 in Resources */ = {isa = PBXBuildFile; fileRef = 269B830F2C74A2FF002AA1D7 /* note.mp3 */; }; 269B831E2C74B4EC002AA1D7 /* handoff.mp3 in Resources */ = {isa = PBXBuildFile; fileRef = 269B83122C74B4EA002AA1D7 /* handoff.mp3 */; }; @@ -707,7 +707,7 @@ 2621AB382C60E7AE00046D7A /* NotificationsViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationsViewModel.swift; sourceTree = ""; }; 2621AB3A2C613C8100046D7A /* NotificationsFactory.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationsFactory.swift; sourceTree = ""; }; 265AA1612B74E6B900CF98B0 /* ChatPreservation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChatPreservation.swift; sourceTree = ""; }; - 26843D2A2CD8D7130010F047 /* UIViewController+NodeListPresenter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIViewController+NodeListPresenter.swift"; sourceTree = ""; }; + 26843D692CDD29710010F047 /* NodeAvailabilityService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NodeAvailabilityService.swift; sourceTree = ""; }; 269B830F2C74A2FF002AA1D7 /* note.mp3 */ = {isa = PBXFileReference; lastKnownFileType = audio.mp3; path = note.mp3; sourceTree = ""; }; 269B83122C74B4EA002AA1D7 /* handoff.mp3 */ = {isa = PBXFileReference; lastKnownFileType = audio.mp3; path = handoff.mp3; sourceTree = ""; }; 269B83132C74B4EA002AA1D7 /* portal.mp3 */ = {isa = PBXFileReference; lastKnownFileType = audio.mp3; path = portal.mp3; sourceTree = ""; }; @@ -2299,6 +2299,7 @@ 3AA2D5F8280EAF49000ED971 /* SocketService */, E9B3D39F201FA2090019EB36 /* DataProviders */, E9E7CD922002740500DFC4DB /* AdamantAccountService.swift */, + 26843D692CDD29710010F047 /* NodeAvailabilityService.swift */, 6455E9F221075D8000B2E94C /* AdamantAddressBookService.swift */, E90A494A204D9EB8009F6A65 /* AdamantAuthentication.swift */, E9E7CDBF2003AF6D00DFC4DB /* AdamantCellFactory.swift */, @@ -2351,7 +2352,6 @@ E913C9101FFFAA4B001A83F7 /* Helpers */ = { isa = PBXGroup; children = ( - 26843D2A2CD8D7130010F047 /* UIViewController+NodeListPresenter.swift */, 93775E452A674FA9009061AC /* Markdown+Adamant.swift */, E9061B96207501E40011F104 /* AdamantUserInfoKey.swift */, E94008862114F05B00CD2D67 /* AddressValidationResult.swift */, @@ -3305,6 +3305,7 @@ 3A26D93D2C3C1CC3003AD832 /* KlyNodeApiService.swift in Sources */, 93A118512993167500E144CC /* ChatMessageBackgroundColor.swift in Sources */, 93760BD72C656CF8002507C3 /* DefaultNodesProvider.swift in Sources */, + 26843D6A2CDD29760010F047 /* NodeAvailabilityService.swift in Sources */, 3A26D93B2C3C1C97003AD832 /* KlyApiCore.swift in Sources */, 2621AB372C60E74A00046D7A /* NotificationsView.swift in Sources */, 936658A32B0ADE4400BDB2D3 /* CoinsNodesListView+Row.swift in Sources */, @@ -3476,7 +3477,6 @@ E9393FAA2055D03300EE6F30 /* AdamantMessage.swift in Sources */, 938F7D692955C9EC001915CA /* ChatViewModel.swift in Sources */, E90A494D204DA932009F6A65 /* LocalAuthentication.swift in Sources */, - 26843D2B2CD8D7130010F047 /* UIViewController+NodeListPresenter.swift in Sources */, 3A26D9352C3C1BE2003AD832 /* KlyWalletService.swift in Sources */, 41047B70294B5EE10039E956 /* VisibleWalletsViewController.swift in Sources */, 2621AB392C60E7AE00046D7A /* NotificationsViewModel.swift in Sources */, diff --git a/Adamant/Helpers/UIViewController+NodeListPresenter.swift b/Adamant/Helpers/UIViewController+NodeListPresenter.swift deleted file mode 100644 index 25b784e9e..000000000 --- a/Adamant/Helpers/UIViewController+NodeListPresenter.swift +++ /dev/null @@ -1,22 +0,0 @@ -// -// UIViewController+NodeListPresenter.swift -// Adamant -// -// Created by Yana Silosieva on 31.10.2024. -// Copyright © 2024 Adamant. All rights reserved. -// - -import UIKit -import CommonKit - -extension UIViewController { - func presentNodeListVC(screensFactory: ScreensFactory, node: NodeGroup) { - let vc = node == .adm - ? screensFactory.makeNodesList() - : screensFactory.makeCoinsNodesList(context: .menu) - - let nav = UINavigationController(rootViewController: vc) - nav.modalPresentationStyle = .pageSheet - self.present(nav, animated: true, completion: nil) - } -} diff --git a/Adamant/Modules/Account/AccountFactory.swift b/Adamant/Modules/Account/AccountFactory.swift index 2c8638580..667f61564 100644 --- a/Adamant/Modules/Account/AccountFactory.swift +++ b/Adamant/Modules/Account/AccountFactory.swift @@ -26,7 +26,12 @@ struct AccountFactory { currencyInfoService: assembler.resolve(InfoServiceProtocol.self)!, languageService: assembler.resolve(LanguageStorageProtocol.self)!, walletServiceCompose: assembler.resolve(WalletServiceCompose.self)!, - apiServiceCompose: assembler.resolve(ApiServiceComposeProtocol.self)! + apiServiceCompose: assembler.resolve(ApiServiceComposeProtocol.self)!, nodeAvailabilityService: NodeAvailabilityService( + dialogService: assembler.resolve(DialogService.self)!, + apiServiceCompose: assembler.resolve(ApiServiceComposeProtocol.self)!, + screensFactory: screensFactory + ) + ) } } diff --git a/Adamant/Modules/Account/AccountViewController.swift b/Adamant/Modules/Account/AccountViewController.swift index 457cdf093..19e471650 100644 --- a/Adamant/Modules/Account/AccountViewController.swift +++ b/Adamant/Modules/Account/AccountViewController.swift @@ -155,6 +155,7 @@ final class AccountViewController: FormViewController { private let languageService: LanguageStorageProtocol private let walletServiceCompose: WalletServiceCompose private let apiServiceCompose: ApiServiceComposeProtocol + private let nodeAvailabilityService: NodeAvailabilityProtocol let accountService: AccountService let dialogService: DialogService @@ -219,7 +220,8 @@ final class AccountViewController: FormViewController { currencyInfoService: InfoServiceProtocol, languageService: LanguageStorageProtocol, walletServiceCompose: WalletServiceCompose, - apiServiceCompose: ApiServiceComposeProtocol + apiServiceCompose: ApiServiceComposeProtocol, + nodeAvailabilityService: NodeAvailabilityProtocol ) { self.visibleWalletsService = visibleWalletsService self.accountService = accountService @@ -233,6 +235,7 @@ final class AccountViewController: FormViewController { self.languageService = languageService self.walletServiceCompose = walletServiceCompose self.apiServiceCompose = apiServiceCompose + self.nodeAvailabilityService = nodeAvailabilityService super.init(style: .insetGrouped) } @@ -1111,24 +1114,12 @@ final class AccountViewController: FormViewController { } @objc private func handleRefresh(_ refreshControl: UIRefreshControl) { - let disabledGroup = NodeGroup.allCases.first { - apiServiceCompose.get($0)?.hasEnabledNode != true - } - - if let disabledGroup { - dialogService.showNoActiveNodesAlert( - nodeName: disabledGroup.name - ) { [weak self] in - guard let self = self else { return } - - self.presentNodeListVC( - screensFactory: self.screensFactory, - node: disabledGroup - ) - } - } - - refreshControl.endRefreshing() + defer { refreshControl.endRefreshing() } + guard nodeAvailabilityService.checkNodeAvailability( + in: .adm, + vc: self + ) else { return } + DispatchQueue.background.async { [accountService] in accountService.reloadWallets() } diff --git a/Adamant/Modules/Chat/View/ChatViewController.swift b/Adamant/Modules/Chat/View/ChatViewController.swift index da4095ea5..294bbdaec 100644 --- a/Adamant/Modules/Chat/View/ChatViewController.swift +++ b/Adamant/Modules/Chat/View/ChatViewController.swift @@ -479,13 +479,16 @@ private extension ChatViewController { .store(in: &subscriptions) viewModel.presentNodeListVC - .sink { [weak self] in + .sink { [weak self] node in guard let self = self else { return } - self.presentNodeListVC( - screensFactory: self.screensFactory, - node: .adm - ) + let vc = node == .adm + ? screensFactory.makeNodesList() + : screensFactory.makeCoinsNodesList(context: .menu) + + let nav = UINavigationController(rootViewController: vc) + nav.modalPresentationStyle = .pageSheet + self.present(nav, animated: true, completion: nil) } .store(in: &subscriptions) } diff --git a/Adamant/Modules/Chat/ViewModel/ChatViewModel.swift b/Adamant/Modules/Chat/ViewModel/ChatViewModel.swift index 62f493df4..22dc7f7a9 100644 --- a/Adamant/Modules/Chat/ViewModel/ChatViewModel.swift +++ b/Adamant/Modules/Chat/ViewModel/ChatViewModel.swift @@ -97,7 +97,7 @@ final class ChatViewModel: NSObject { let presentDocumentPickerVC = ObservableSender() let presentDocumentViewerVC = ObservableSender<([FileResult], Int)>() let presentDropView = ObservableSender() - let presentNodeListVC = ObservableSender() + let presentNodeListVC = ObservableSender() @ObservableValue private(set) var isHeaderLoading = false @ObservableValue private(set) var fullscreenLoading = false @@ -300,7 +300,7 @@ final class ChatViewModel: NSObject { nodeName: NodeGroup.adm.name, action: { [weak self] in guard let self = self else { return } - self.presentNodeListVC.send() + self.presentNodeListVC.send(.adm) } )) return @@ -716,7 +716,7 @@ final class ChatViewModel: NSObject { nodeName: NodeGroup.adm.name, action: { [weak self] in guard let self = self else { return } - self.presentNodeListVC.send() + self.presentNodeListVC.send(.adm) } )) return false @@ -1074,7 +1074,7 @@ private extension ChatViewModel { nodeName: NodeGroup.adm.name, action: { [weak self] in guard let self = self else { return } - self.presentNodeListVC.send() + self.presentNodeListVC.send(.ipfs) } ) ) diff --git a/Adamant/Modules/ChatsList/ChatListFactory.swift b/Adamant/Modules/ChatsList/ChatListFactory.swift index 70be56592..a3750d3d2 100644 --- a/Adamant/Modules/ChatsList/ChatListFactory.swift +++ b/Adamant/Modules/ChatsList/ChatListFactory.swift @@ -24,7 +24,11 @@ struct ChatListFactory { dialogService: assembler.resolve(DialogService.self)!, addressBook: assembler.resolve(AddressBookService.self)!, avatarService: assembler.resolve(AvatarService.self)!, - walletServiceCompose: assembler.resolve(WalletServiceCompose.self)! + walletServiceCompose: assembler.resolve(WalletServiceCompose.self)!, nodeAvailabilityService: NodeAvailabilityService( + dialogService: assembler.resolve(DialogService.self)!, + apiServiceCompose: assembler.resolve(ApiServiceComposeProtocol.self)!, + screensFactory: screensFactory + ) ) } @@ -42,9 +46,14 @@ struct ChatListFactory { visibleWalletsService: assembler.resolve(VisibleWalletsService.self)!, addressBookService: assembler.resolve(AddressBookService.self)!, screensFactory: screensFactory, - walletServiceCompose: assembler.resolve(WalletServiceCompose.self)!, + walletServiceCompose: assembler.resolve(WalletServiceCompose.self)!, nodesStorage: assembler.resolve(NodesStorageProtocol.self)!, - dialogService: assembler.resolve(DialogService.self)! + dialogService: assembler.resolve(DialogService.self)!, + nodeAvailabilityService: NodeAvailabilityService( + dialogService: assembler.resolve(DialogService.self)!, + apiServiceCompose: assembler.resolve(ApiServiceComposeProtocol.self)!, + screensFactory: screensFactory + ) ) } diff --git a/Adamant/Modules/ChatsList/ChatListViewController.swift b/Adamant/Modules/ChatsList/ChatListViewController.swift index d68ae2000..9cad44af6 100644 --- a/Adamant/Modules/ChatsList/ChatListViewController.swift +++ b/Adamant/Modules/ChatsList/ChatListViewController.swift @@ -57,6 +57,7 @@ final class ChatListViewController: KeyboardObservingViewController { private let addressBook: AddressBookService private let avatarService: AvatarService private let walletServiceCompose: WalletServiceCompose + private let nodeAvailabilityService: NodeAvailabilityProtocol // MARK: IBOutlet @IBOutlet weak var tableView: UITableView! @@ -149,7 +150,8 @@ final class ChatListViewController: KeyboardObservingViewController { dialogService: DialogService, addressBook: AddressBookService, avatarService: AvatarService, - walletServiceCompose: WalletServiceCompose + walletServiceCompose: WalletServiceCompose, + nodeAvailabilityService: NodeAvailabilityProtocol ) { self.accountService = accountService self.chatsProvider = chatsProvider @@ -160,6 +162,7 @@ final class ChatListViewController: KeyboardObservingViewController { self.addressBook = addressBook self.avatarService = avatarService self.walletServiceCompose = walletServiceCompose + self.nodeAvailabilityService = nodeAvailabilityService super.init(nibName: "ChatListViewController", bundle: nil) } @@ -467,9 +470,9 @@ final class ChatListViewController: KeyboardObservingViewController { @objc private func handleRefresh(_ refreshControl: UIRefreshControl) { Task { let result = await chatsProvider.update(notifyState: true) + defer { refreshControl.endRefreshing() } guard let result = result else { - refreshControl.endRefreshing() return } @@ -478,19 +481,11 @@ final class ChatListViewController: KeyboardObservingViewController { tableView.reloadData() case .failure: - dialogService.showNoActiveNodesAlert( - nodeName: NodeGroup.adm.name - ) { [weak self] in - guard let self = self else { return } - - self.presentNodeListVC( - screensFactory: self.screensFactory, - node: .adm - ) - } + guard nodeAvailabilityService.checkNodeAvailability( + in: .adm, + vc: self + ) else { return } } - - refreshControl.endRefreshing() } } diff --git a/Adamant/Modules/ChatsList/ComplexTransferViewController.swift b/Adamant/Modules/ChatsList/ComplexTransferViewController.swift index 7433b7655..e1dfaae99 100644 --- a/Adamant/Modules/ChatsList/ComplexTransferViewController.swift +++ b/Adamant/Modules/ChatsList/ComplexTransferViewController.swift @@ -25,6 +25,7 @@ final class ComplexTransferViewController: UIViewController { private let walletServiceCompose: WalletServiceCompose private let nodesStorage: NodesStorageProtocol private let dialogService: DialogService + private let nodeAvailabilityService: NodeAvailabilityProtocol // MARK: - Properties var pagingViewController: PagingViewController! @@ -46,7 +47,8 @@ final class ComplexTransferViewController: UIViewController { screensFactory: ScreensFactory, walletServiceCompose: WalletServiceCompose, nodesStorage: NodesStorageProtocol, - dialogService: DialogService + dialogService: DialogService, + nodeAvailabilityService: NodeAvailabilityProtocol ) { self.visibleWalletsService = visibleWalletsService self.addressBookService = addressBookService @@ -54,6 +56,7 @@ final class ComplexTransferViewController: UIViewController { self.walletServiceCompose = walletServiceCompose self.nodesStorage = nodesStorage self.dialogService = dialogService + self.nodeAvailabilityService = nodeAvailabilityService super.init(nibName: nil, bundle: nil) } @@ -147,19 +150,10 @@ extension ComplexTransferViewController: PagingViewControllerDataSource { vc.showProgressView(animated: false) Task { - guard service.core.hasEnabledNode else { - dialogService.showNoActiveNodesAlert( - nodeName: NodeGroup.adm.name - ) { [weak self] in - guard let self = self else { return } - - self.presentNodeListVC( - screensFactory: self.screensFactory, - node: .adm - ) - } - return - } + guard nodeAvailabilityService.checkNodeAvailability( + in: .adm, + vc: self + ) else { return } guard admService?.core.hasEnabledNode ?? false else { vc.showAlertView( diff --git a/Adamant/Modules/Login/LoginFactory.swift b/Adamant/Modules/Login/LoginFactory.swift index f949a7edb..8bd0f46f1 100644 --- a/Adamant/Modules/Login/LoginFactory.swift +++ b/Adamant/Modules/Login/LoginFactory.swift @@ -21,7 +21,12 @@ struct LoginFactory { dialogService: assembler.resolve(DialogService.self)!, localAuth: assembler.resolve(LocalAuthentication.self)!, screensFactory: screenFactory, - apiService: assembler.resolve(AdamantApiServiceProtocol.self)! + apiService: assembler.resolve(AdamantApiServiceProtocol.self)!, + nodeAvailabilityService: NodeAvailabilityService( + dialogService: assembler.resolve(DialogService.self)!, + apiServiceCompose: assembler.resolve(ApiServiceComposeProtocol.self)!, + screensFactory: screenFactory + ) ) } } diff --git a/Adamant/Modules/Login/LoginViewController.swift b/Adamant/Modules/Login/LoginViewController.swift index 7b5e34de2..38a122e58 100644 --- a/Adamant/Modules/Login/LoginViewController.swift +++ b/Adamant/Modules/Login/LoginViewController.swift @@ -141,6 +141,7 @@ final class LoginViewController: FormViewController { let screensFactory: ScreensFactory let apiService: AdamantApiServiceProtocol let dialogService: DialogService + let nodeAvailabilityService: NodeAvailabilityProtocol // MARK: Properties private var hideNewPassphrase: Bool = true @@ -160,7 +161,8 @@ final class LoginViewController: FormViewController { dialogService: DialogService, localAuth: LocalAuthentication, screensFactory: ScreensFactory, - apiService: AdamantApiServiceProtocol + apiService: AdamantApiServiceProtocol, + nodeAvailabilityService: NodeAvailabilityProtocol ) { self.accountService = accountService self.adamantCore = adamantCore @@ -168,6 +170,7 @@ final class LoginViewController: FormViewController { self.localAuth = localAuth self.screensFactory = screensFactory self.apiService = apiService + self.nodeAvailabilityService = nodeAvailabilityService super.init(style: .insetGrouped) } @@ -457,16 +460,12 @@ extension LoginViewController { return } - dialogService.showNoActiveNodesAlert( - nodeName: NodeGroup.adm.name - ) { [weak self] in - guard let self = self else { return } - self.presentNodeListVC( - screensFactory: self.screensFactory, - node: .adm - ) - } + guard nodeAvailabilityService.checkNodeAvailability( + in: .adm, + vc: self + ) else { return } } + func generateNewPassphrase() { let passphrase = (try? Mnemonic.generate().joined(separator: " ")) ?? .empty diff --git a/Adamant/Modules/Wallets/Adamant/AdmWalletFactory.swift b/Adamant/Modules/Wallets/Adamant/AdmWalletFactory.swift index 71c147234..8c862892f 100644 --- a/Adamant/Modules/Wallets/Adamant/AdmWalletFactory.swift +++ b/Adamant/Modules/Wallets/Adamant/AdmWalletFactory.swift @@ -53,7 +53,12 @@ struct AdmWalletFactory: WalletFactory { vibroService: assembler.resolve(VibroService.self)!, walletService: service, reachabilityMonitor: assembler.resolve(ReachabilityMonitor.self)!, - apiServiceCompose: assembler.resolve(ApiServiceComposeProtocol.self)! + apiServiceCompose: assembler.resolve(ApiServiceComposeProtocol.self)!, + nodeAvailabilityService: NodeAvailabilityService( + dialogService: assembler.resolve(DialogService.self)!, + apiServiceCompose: assembler.resolve(ApiServiceComposeProtocol.self)!, + screensFactory: screensFactory + ) ) } diff --git a/Adamant/Modules/Wallets/Bitcoin/BtcWalletFactory.swift b/Adamant/Modules/Wallets/Bitcoin/BtcWalletFactory.swift index a575d21f9..3b1cdb33f 100644 --- a/Adamant/Modules/Wallets/Bitcoin/BtcWalletFactory.swift +++ b/Adamant/Modules/Wallets/Bitcoin/BtcWalletFactory.swift @@ -49,7 +49,12 @@ struct BtcWalletFactory: WalletFactory { vibroService: assembler.resolve(VibroService.self)!, walletService: service, reachabilityMonitor: assembler.resolve(ReachabilityMonitor.self)!, - apiServiceCompose: assembler.resolve(ApiServiceComposeProtocol.self)! + apiServiceCompose: assembler.resolve(ApiServiceComposeProtocol.self)!, + nodeAvailabilityService: NodeAvailabilityService( + dialogService: assembler.resolve(DialogService.self)!, + apiServiceCompose: assembler.resolve(ApiServiceComposeProtocol.self)!, + screensFactory: screensFactory + ) ) } diff --git a/Adamant/Modules/Wallets/Dash/DashWalletFactory.swift b/Adamant/Modules/Wallets/Dash/DashWalletFactory.swift index 254644865..f8b8cf973 100644 --- a/Adamant/Modules/Wallets/Dash/DashWalletFactory.swift +++ b/Adamant/Modules/Wallets/Dash/DashWalletFactory.swift @@ -48,7 +48,12 @@ struct DashWalletFactory: WalletFactory { vibroService: assembler.resolve(VibroService.self)!, walletService: service, reachabilityMonitor: assembler.resolve(ReachabilityMonitor.self)!, - apiServiceCompose: assembler.resolve(ApiServiceComposeProtocol.self)! + apiServiceCompose: assembler.resolve(ApiServiceComposeProtocol.self)!, + nodeAvailabilityService: NodeAvailabilityService( + dialogService: assembler.resolve(DialogService.self)!, + apiServiceCompose: assembler.resolve(ApiServiceComposeProtocol.self)!, + screensFactory: screensFactory + ) ) } diff --git a/Adamant/Modules/Wallets/Doge/DogeWalletFactory.swift b/Adamant/Modules/Wallets/Doge/DogeWalletFactory.swift index 0ece9a4e8..5649f9a67 100644 --- a/Adamant/Modules/Wallets/Doge/DogeWalletFactory.swift +++ b/Adamant/Modules/Wallets/Doge/DogeWalletFactory.swift @@ -48,7 +48,12 @@ struct DogeWalletFactory: WalletFactory { vibroService: assembler.resolve(VibroService.self)!, walletService: service, reachabilityMonitor: assembler.resolve(ReachabilityMonitor.self)!, - apiServiceCompose: assembler.resolve(ApiServiceComposeProtocol.self)! + apiServiceCompose: assembler.resolve(ApiServiceComposeProtocol.self)!, + nodeAvailabilityService: NodeAvailabilityService( + dialogService: assembler.resolve(DialogService.self)!, + apiServiceCompose: assembler.resolve(ApiServiceComposeProtocol.self)!, + screensFactory: screensFactory + ) ) } diff --git a/Adamant/Modules/Wallets/ERC20/ERC20WalletFactory.swift b/Adamant/Modules/Wallets/ERC20/ERC20WalletFactory.swift index f77913123..af0dc3863 100644 --- a/Adamant/Modules/Wallets/ERC20/ERC20WalletFactory.swift +++ b/Adamant/Modules/Wallets/ERC20/ERC20WalletFactory.swift @@ -48,7 +48,12 @@ struct ERC20WalletFactory: WalletFactory { vibroService: assembler.resolve(VibroService.self)!, walletService: service, reachabilityMonitor: assembler.resolve(ReachabilityMonitor.self)!, - apiServiceCompose: assembler.resolve(ApiServiceComposeProtocol.self)! + apiServiceCompose: assembler.resolve(ApiServiceComposeProtocol.self)!, + nodeAvailabilityService: NodeAvailabilityService( + dialogService: assembler.resolve(DialogService.self)!, + apiServiceCompose: assembler.resolve(ApiServiceComposeProtocol.self)!, + screensFactory: screensFactory + ) ) } diff --git a/Adamant/Modules/Wallets/Ethereum/EthWalletFactory.swift b/Adamant/Modules/Wallets/Ethereum/EthWalletFactory.swift index 51b00bc72..2d70c16b5 100644 --- a/Adamant/Modules/Wallets/Ethereum/EthWalletFactory.swift +++ b/Adamant/Modules/Wallets/Ethereum/EthWalletFactory.swift @@ -48,7 +48,12 @@ struct EthWalletFactory: WalletFactory { vibroService: assembler.resolve(VibroService.self)!, walletService: service, reachabilityMonitor: assembler.resolve(ReachabilityMonitor.self)!, - apiServiceCompose: assembler.resolve(ApiServiceComposeProtocol.self)! + apiServiceCompose: assembler.resolve(ApiServiceComposeProtocol.self)!, + nodeAvailabilityService: NodeAvailabilityService( + dialogService: assembler.resolve(DialogService.self)!, + apiServiceCompose: assembler.resolve(ApiServiceComposeProtocol.self)!, + screensFactory: screensFactory + ) ) } diff --git a/Adamant/Modules/Wallets/Klayr/KlyWalletFactory.swift b/Adamant/Modules/Wallets/Klayr/KlyWalletFactory.swift index f09d2701a..c7b23c72f 100644 --- a/Adamant/Modules/Wallets/Klayr/KlyWalletFactory.swift +++ b/Adamant/Modules/Wallets/Klayr/KlyWalletFactory.swift @@ -49,7 +49,12 @@ struct KlyWalletFactory: WalletFactory { vibroService: assembler.resolve(VibroService.self)!, walletService: service, reachabilityMonitor: assembler.resolve(ReachabilityMonitor.self)!, - apiServiceCompose: assembler.resolve(ApiServiceComposeProtocol.self)! + apiServiceCompose: assembler.resolve(ApiServiceComposeProtocol.self)!, + nodeAvailabilityService: NodeAvailabilityService( + dialogService: assembler.resolve(DialogService.self)!, + apiServiceCompose: assembler.resolve(ApiServiceComposeProtocol.self)!, + screensFactory: screensFactory + ) ) } diff --git a/Adamant/Modules/Wallets/TransferViewControllerBase.swift b/Adamant/Modules/Wallets/TransferViewControllerBase.swift index bfee127f3..790144830 100644 --- a/Adamant/Modules/Wallets/TransferViewControllerBase.swift +++ b/Adamant/Modules/Wallets/TransferViewControllerBase.swift @@ -188,6 +188,7 @@ class TransferViewControllerBase: FormViewController { let walletCore: WalletCoreProtocol let reachabilityMonitor: ReachabilityMonitor let apiServiceCompose: ApiServiceComposeProtocol + let nodeAvailabilityService: NodeAvailabilityProtocol // MARK: - Properties @@ -319,7 +320,8 @@ class TransferViewControllerBase: FormViewController { vibroService: VibroService, walletService: WalletService, reachabilityMonitor: ReachabilityMonitor, - apiServiceCompose: ApiServiceComposeProtocol + apiServiceCompose: ApiServiceComposeProtocol, + nodeAvailabilityService: NodeAvailabilityProtocol ) { self.accountService = accountService self.accountsProvider = accountsProvider @@ -333,6 +335,7 @@ class TransferViewControllerBase: FormViewController { self.walletCore = walletService.core self.reachabilityMonitor = reachabilityMonitor self.apiServiceCompose = apiServiceCompose + self.nodeAvailabilityService = nodeAvailabilityService super.init(style: .insetGrouped) } @@ -796,37 +799,17 @@ class TransferViewControllerBase: FormViewController { return } - guard - apiServiceCompose.get(.adm)?.hasEnabledNode == true || (admReportRecipient == nil && walletCore.nodeGroups != [.adm]) - else { - dialogService.showNoActiveNodesAlert( - nodeName: NodeGroup.adm.name - ) { [weak self] in - guard let self = self else { return } - - self.presentNodeListVC( - screensFactory: self.screensFactory, - node: .adm - ) - } - return + if admReportRecipient != nil || walletCore.nodeGroups == [.adm] { + guard nodeAvailabilityService.checkNodeAvailability( + in: .adm, + vc: self + )else { return } } - guard walletCore.hasEnabledNode else { - let network = type(of: walletCore).tokenNetworkSymbol - dialogService.showNoActiveNodesAlert( - nodeName: network - ) { [weak self] in - guard let self = self, - let nodeGroup = walletCore.nodeGroups.first else { return } - - self.presentNodeListVC( - screensFactory: self.screensFactory, - node: nodeGroup - ) - } - return - } + guard nodeAvailabilityService.checkNodeAvailability( + in: walletCore, + vc: self + ) else { return } let recipient: String if let recipientName = recipientName { diff --git a/Adamant/Services/NodeAvailabilityService.swift b/Adamant/Services/NodeAvailabilityService.swift new file mode 100644 index 000000000..ce25e7745 --- /dev/null +++ b/Adamant/Services/NodeAvailabilityService.swift @@ -0,0 +1,107 @@ +// +// NodeAvailabilityService.swift +// Adamant +// +// Created by Yana Silosieva on 07.11.2024. +// Copyright © 2024 Adamant. All rights reserved. +// + +import UIKit +import CommonKit + +@MainActor +protocol NodeAvailabilityProtocol { + func checkNodeAvailability( + in walletCore: WalletCoreProtocol, + vc: UIViewController + ) -> Bool + + func checkNodeAvailability( + in nodeGroup: NodeGroup, + vc: UIViewController + ) -> Bool +} + +@MainActor +final class NodeAvailabilityService: NodeAvailabilityProtocol { + + // MARK: Dependencies + + private let dialogService: DialogService + private let apiServiceCompose: ApiServiceComposeProtocol + private let screensFactory: ScreensFactory + + init( + dialogService: DialogService, + apiServiceCompose: ApiServiceComposeProtocol, + screensFactory: ScreensFactory + ) { + self.dialogService = dialogService + self.apiServiceCompose = apiServiceCompose + self.screensFactory = screensFactory + } + + func checkNodeAvailability( + in nodeGroup: NodeGroup, + vc: UIViewController + ) -> Bool { + guard apiServiceCompose.get(nodeGroup)?.hasEnabledNode == true + else { + dialogService.showNoActiveNodesAlert( + nodeName: NodeGroup.adm.name + ) { [weak self] in + guard let self = self else { return } + + self.presentNodeListVC( + screensFactory: self.screensFactory, + node: nodeGroup, + rootVC: vc + ) + } + + return false + } + + return true + } + + func checkNodeAvailability( + in walletCore: WalletCoreProtocol, + vc: UIViewController + ) -> Bool { + guard walletCore.hasEnabledNode else { + let network = type(of: walletCore).tokenNetworkSymbol + dialogService.showNoActiveNodesAlert( + nodeName: network + ) { [weak self] in + guard let self = self, + let nodeGroup = walletCore.nodeGroups.first else { return } + + self.presentNodeListVC( + screensFactory: self.screensFactory, + node: nodeGroup, + rootVC: vc + ) + } + return false + } + + return true + } +} + +private extension NodeAvailabilityService { + func presentNodeListVC( + screensFactory: ScreensFactory, + node: NodeGroup, + rootVC: UIViewController + ) { + let vc = node == .adm + ? screensFactory.makeNodesList() + : screensFactory.makeCoinsNodesList(context: .menu) + + let nav = UINavigationController(rootViewController: vc) + nav.modalPresentationStyle = .pageSheet + rootVC.present(nav, animated: true, completion: nil) + } +}