From 52f708241025b5bca5c45bfafdc4074490e8a716 Mon Sep 17 00:00:00 2001 From: StanislavDevIOS Date: Thu, 7 Sep 2023 13:50:28 +0300 Subject: [PATCH 01/96] [trello.com/c/EdiYUEfs] feat: vibro on actions --- Adamant.xcodeproj/project.pbxproj | 32 ++++++++++++ Adamant/Models/AdamantVibroType.swift | 25 ++++++++++ Adamant/ServiceProtocols/VibroService.swift | 13 +++++ Adamant/Services/AdamantDialogService.swift | 7 ++- Adamant/Services/AdamantVibroService.swift | 35 +++++++++++++ .../Account/AccountViewController.swift | 34 ++++++++++++- Adamant/Stories/Settings/SettingsRoutes.swift | 4 ++ .../VibrationSelectionFactory.swift | 23 +++++++++ .../VibrationSelectionView.swift | 50 +++++++++++++++++++ .../VibrationSelectionViewModel.swift | 30 +++++++++++ Adamant/SwinjectDependencies.swift | 15 +++++- Adamant/Wallets/Adamant/AdmWalletRoutes.swift | 3 +- Adamant/Wallets/Bitcoin/BtcWalletRoutes.swift | 3 +- Adamant/Wallets/Dash/DashWalletRouter.swift | 3 +- Adamant/Wallets/Doge/DogeWalletRoutes.swift | 3 +- Adamant/Wallets/ERC20/ERC20WalletRouter.swift | 3 +- .../Wallets/Ethereum/EthWalletRoutes.swift | 3 +- Adamant/Wallets/Lisk/LskWalletRoutes.swift | 3 +- .../TransferViewControllerBase+QR.swift | 19 +++---- .../Wallets/TransferViewControllerBase.swift | 5 +- 20 files changed, 293 insertions(+), 20 deletions(-) create mode 100644 Adamant/Models/AdamantVibroType.swift create mode 100644 Adamant/ServiceProtocols/VibroService.swift create mode 100644 Adamant/Services/AdamantVibroService.swift create mode 100644 Adamant/Stories/Settings/TestVibration/VibrationSelectionFactory.swift create mode 100644 Adamant/Stories/Settings/TestVibration/VibrationSelectionView.swift create mode 100644 Adamant/Stories/Settings/TestVibration/VibrationSelectionViewModel.swift diff --git a/Adamant.xcodeproj/project.pbxproj b/Adamant.xcodeproj/project.pbxproj index b93d0223e..a8be09054 100644 --- a/Adamant.xcodeproj/project.pbxproj +++ b/Adamant.xcodeproj/project.pbxproj @@ -12,6 +12,12 @@ 3A41938F2A580C57006A6B22 /* AdamantRichTransactionReactService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3A41938E2A580C57006A6B22 /* AdamantRichTransactionReactService.swift */; }; 3A4193912A580C85006A6B22 /* RichTransactionReactService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3A4193902A580C85006A6B22 /* RichTransactionReactService.swift */; }; 3A41939A2A5D554A006A6B22 /* Reaction.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3A4193992A5D554A006A6B22 /* Reaction.swift */; }; + 3A7BD00E2AA9BCE80045AAB0 /* VibroService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3A7BD00D2AA9BCE80045AAB0 /* VibroService.swift */; }; + 3A7BD0102AA9BD030045AAB0 /* AdamantVibroService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3A7BD00F2AA9BD030045AAB0 /* AdamantVibroService.swift */; }; + 3A7BD0122AA9BD5A0045AAB0 /* AdamantVibroType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3A7BD0112AA9BD5A0045AAB0 /* AdamantVibroType.swift */; }; + 3A7BD0152AA9BF020045AAB0 /* VibrationSelectionView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3A7BD0142AA9BF020045AAB0 /* VibrationSelectionView.swift */; }; + 3A7BD0172AA9BF100045AAB0 /* VibrationSelectionFactory.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3A7BD0162AA9BF100045AAB0 /* VibrationSelectionFactory.swift */; }; + 3A7BD0192AA9BF3F0045AAB0 /* VibrationSelectionViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3A7BD0182AA9BF3F0045AAB0 /* VibrationSelectionViewModel.swift */; }; 3A8875EF27BBF38D00436195 /* Parchment in Frameworks */ = {isa = PBXBuildFile; productRef = 3A8875EE27BBF38D00436195 /* Parchment */; }; 3A9015A52A614A18002A2464 /* EmojiService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3A9015A42A614A18002A2464 /* EmojiService.swift */; }; 3A9015A72A614A62002A2464 /* AdamantEmojiService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3A9015A62A614A62002A2464 /* AdamantEmojiService.swift */; }; @@ -585,6 +591,12 @@ 3A41938E2A580C57006A6B22 /* AdamantRichTransactionReactService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AdamantRichTransactionReactService.swift; sourceTree = ""; }; 3A4193902A580C85006A6B22 /* RichTransactionReactService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RichTransactionReactService.swift; sourceTree = ""; }; 3A4193992A5D554A006A6B22 /* Reaction.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Reaction.swift; sourceTree = ""; }; + 3A7BD00D2AA9BCE80045AAB0 /* VibroService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VibroService.swift; sourceTree = ""; }; + 3A7BD00F2AA9BD030045AAB0 /* AdamantVibroService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AdamantVibroService.swift; sourceTree = ""; }; + 3A7BD0112AA9BD5A0045AAB0 /* AdamantVibroType.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AdamantVibroType.swift; sourceTree = ""; }; + 3A7BD0142AA9BF020045AAB0 /* VibrationSelectionView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VibrationSelectionView.swift; sourceTree = ""; }; + 3A7BD0162AA9BF100045AAB0 /* VibrationSelectionFactory.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VibrationSelectionFactory.swift; sourceTree = ""; }; + 3A7BD0182AA9BF3F0045AAB0 /* VibrationSelectionViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VibrationSelectionViewModel.swift; sourceTree = ""; }; 3A9015A42A614A18002A2464 /* EmojiService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EmojiService.swift; sourceTree = ""; }; 3A9015A62A614A62002A2464 /* AdamantEmojiService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AdamantEmojiService.swift; sourceTree = ""; }; 3A9015A82A615893002A2464 /* ChatMessagesListViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChatMessagesListViewModel.swift; sourceTree = ""; }; @@ -1165,6 +1177,16 @@ path = RichTransactionReactService; sourceTree = ""; }; + 3A7BD0132AA9BEE20045AAB0 /* TestVibration */ = { + isa = PBXGroup; + children = ( + 3A7BD0162AA9BF100045AAB0 /* VibrationSelectionFactory.swift */, + 3A7BD0182AA9BF3F0045AAB0 /* VibrationSelectionViewModel.swift */, + 3A7BD0142AA9BF020045AAB0 /* VibrationSelectionView.swift */, + ); + path = TestVibration; + sourceTree = ""; + }; 3AA2D5F8280EAF49000ED971 /* SocketService */ = { isa = PBXGroup; children = ( @@ -1634,6 +1656,7 @@ 4153045A29C09C6C000E4BEA /* IncreaseFeeService.swift */, 4184F1742A33106200D7B8B9 /* CrashlysticsService.swift */, 3A9015A42A614A18002A2464 /* EmojiService.swift */, + 3A7BD00D2AA9BCE80045AAB0 /* VibroService.swift */, 41C1698B29E7F34900FEB3CB /* RichTransactionReplyService.swift */, 3A4193902A580C85006A6B22 /* RichTransactionReactService.swift */, ); @@ -1661,6 +1684,7 @@ 4153045829C09902000E4BEA /* AdamantIncreaseFeeService.swift */, 4184F1722A33102800D7B8B9 /* AdamantCrashlysticsService.swift */, 3A9015A62A614A62002A2464 /* AdamantEmojiService.swift */, + 3A7BD00F2AA9BD030045AAB0 /* AdamantVibroService.swift */, E921597420611A6A0000CA5C /* AdamantReachability.swift */, E950273F202E257E002C1098 /* RepeaterService.swift */, E9E7CDB42002BA6900DFC4DB /* SwinjectedRouter.swift */, @@ -1695,6 +1719,7 @@ 9304F8C5292F971600173F18 /* ApiServiceResult.swift */, 9304F8C7292F972600173F18 /* ApiServiceError.swift */, 3A4193992A5D554A006A6B22 /* Reaction.swift */, + 3A7BD0112AA9BD5A0045AAB0 /* AdamantVibroType.swift */, 3A33F9F92A7A53DA002B8003 /* EmojiUpdateType.swift */, ); path = Models; @@ -2041,6 +2066,7 @@ E982F69820235AF000566AC7 /* Settings */ = { isa = PBXGroup; children = ( + 3A7BD0132AA9BEE20045AAB0 /* TestVibration */, 411742FE2A39B1B1008CD98A /* Contribute */, 4197B9C72952FAA2004CAF64 /* VisibleWallets */, E948E03A20235E2300975D6B /* SettingsRoutes.swift */, @@ -2681,6 +2707,7 @@ 644EC35E20F34F1E00F40C73 /* DelegateDetailsViewController.swift in Sources */, E9942B80203C058C00C163AF /* QRGeneratorViewController.swift in Sources */, 4184F1772A33173100D7B8B9 /* ContributeView.swift in Sources */, + 3A7BD0122AA9BD5A0045AAB0 /* AdamantVibroType.swift in Sources */, E921597520611A6A0000CA5C /* AdamantReachability.swift in Sources */, E9960B3321F5154300C840A8 /* BaseAccount+CoreDataClass.swift in Sources */, E9FCA1E6218334C00005E83D /* SimpleTransactionDetails.swift in Sources */, @@ -2711,6 +2738,7 @@ 93547BCA29E2262D00B0914B /* WelcomeViewController.swift in Sources */, 41047B74294C61D10039E956 /* VisibleWalletsService.swift in Sources */, 648CE3AC229AD2190070A2CC /* DashTransferViewController.swift in Sources */, + 3A7BD0102AA9BD030045AAB0 /* AdamantVibroService.swift in Sources */, A5E04227282A8BDC0076CD13 /* BtcBalanceResponse.swift in Sources */, A50A41072822F8CE006BDFE1 /* BtcWalletRoutes.swift in Sources */, 64F085D920E2D7600006DE68 /* AdmTransactionsViewController.swift in Sources */, @@ -2832,6 +2860,7 @@ E9DFB71C21624C9200CF8C7C /* AdmTransactionDetailsViewController.swift in Sources */, 6403F5E022723F6400D58779 /* DashWalletRouter.swift in Sources */, E94008722114EACF00CD2D67 /* WalletAccount.swift in Sources */, + 3A7BD00E2AA9BCE80045AAB0 /* VibroService.swift in Sources */, E93B0D742028B21400126346 /* ChatsProvider.swift in Sources */, E9CAE8D42018AC1800345E76 /* AdamantApi+Keys.swift in Sources */, E9C51EEF20139DC600385EB7 /* TransactionIdResponse.swift in Sources */, @@ -2890,6 +2919,7 @@ E940086B2114A70600CD2D67 /* LskAccount.swift in Sources */, 6416B19D21AD7B92006089AC /* LskWalletRoutes.swift in Sources */, E9B3D3A1201FA26B0019EB36 /* AdamantAccountsProvider.swift in Sources */, + 3A7BD0192AA9BF3F0045AAB0 /* VibrationSelectionViewModel.swift in Sources */, E9FAE5DA203DBFEF008D3A6B /* Comparable+clamped.swift in Sources */, 93A91FD329799298001DB1F8 /* ChatStartPosition.swift in Sources */, E94008802114EE2000CD2D67 /* AdmWallet.swift in Sources */, @@ -2927,6 +2957,7 @@ 4186B334294200C5006594A3 /* EthWalletService+DynamicConstants.swift in Sources */, 644EC34F20EFA77A00F40C73 /* Delegate.swift in Sources */, 64EAB37622463F680018D9B2 /* AdamantCurrencyInfoService.swift in Sources */, + 3A7BD0172AA9BF100045AAB0 /* VibrationSelectionFactory.swift in Sources */, 938F7D5B2955C8DA001915CA /* ChatDisplayManager.swift in Sources */, E9722068201F42CC004F2AAD /* InMemoryCoreDataStack.swift in Sources */, 4133AED429769EEC00F3D017 /* UpdatingIndicatorView.swift in Sources */, @@ -2960,6 +2991,7 @@ E96E86B821679C120061F80A /* EthTransactionDetailsViewController.swift in Sources */, 4164A9D728F17D4000EEF16D /* ChatTransactionService.swift in Sources */, E90A4943204C5ED6009F6A65 /* EurekaPassphraseRow.swift in Sources */, + 3A7BD0152AA9BF020045AAB0 /* VibrationSelectionView.swift in Sources */, E90847302196FEA80095825D /* ChatTransaction+CoreDataClass.swift in Sources */, E913C9081FFFA943001A83F7 /* AdamantCore.swift in Sources */, 41935848287841E20083363B /* MacOSDeterminer.swift in Sources */, diff --git a/Adamant/Models/AdamantVibroType.swift b/Adamant/Models/AdamantVibroType.swift new file mode 100644 index 000000000..67bee0641 --- /dev/null +++ b/Adamant/Models/AdamantVibroType.swift @@ -0,0 +1,25 @@ +// +// AdamantVibroType.swift +// Adamant +// +// Created by Stanislav Jelezoglo on 07.09.2023. +// Copyright © 2023 Adamant. All rights reserved. +// + +import Foundation + +enum AdamantVibroType { + case light + case rigid + case heavy + case medium + case soft + case selection + case success + case warning + case error + + static var allCases: [AdamantVibroType] { + return [.light, .rigid, .heavy, .medium, .soft, .selection, .success, .warning, .error] + } +} diff --git a/Adamant/ServiceProtocols/VibroService.swift b/Adamant/ServiceProtocols/VibroService.swift new file mode 100644 index 000000000..3bc7bc0fc --- /dev/null +++ b/Adamant/ServiceProtocols/VibroService.swift @@ -0,0 +1,13 @@ +// +// VibroService.swift +// Adamant +// +// Created by Stanislav Jelezoglo on 07.09.2023. +// Copyright © 2023 Adamant. All rights reserved. +// + +import Foundation + +protocol VibroService: AnyObject { + func applyVibration(_ type: AdamantVibroType) +} diff --git a/Adamant/Services/AdamantDialogService.swift b/Adamant/Services/AdamantDialogService.swift index 68ea21543..509956e09 100644 --- a/Adamant/Services/AdamantDialogService.swift +++ b/Adamant/Services/AdamantDialogService.swift @@ -16,13 +16,15 @@ import CommonKit final class AdamantDialogService: DialogService { // MARK: Dependencies private let router: Router + private let vibroService: VibroService private let popupManager = PopupManager() private let mailDelegate = MailDelegate() private weak var window: UIWindow? // Configure notifications - nonisolated init(router: Router) { + nonisolated init(router: Router, vibroService: VibroService) { self.router = router + self.vibroService = vibroService } func setup(window: UIWindow) { @@ -85,10 +87,12 @@ extension AdamantDialogService { } func showSuccess(withMessage message: String) { + vibroService.applyVibration(.success) popupManager.showSuccessAlert(message: message) } func showWarning(withMessage message: String) { + vibroService.applyVibration(.error) popupManager.showWarningAlert(message: message) } @@ -101,6 +105,7 @@ extension AdamantDialogService { supportEmail: Bool, error: Error? = nil ) { + vibroService.applyVibration(.error) popupManager.showAdvancedAlert(model: .init( icon: .asset(named: "error") ?? .init(), title: .adamant.alert.error, diff --git a/Adamant/Services/AdamantVibroService.swift b/Adamant/Services/AdamantVibroService.swift new file mode 100644 index 000000000..9cb8e4be5 --- /dev/null +++ b/Adamant/Services/AdamantVibroService.swift @@ -0,0 +1,35 @@ +// +// AdamantVibroService.swift +// Adamant +// +// Created by Stanislav Jelezoglo on 07.09.2023. +// Copyright © 2023 Adamant. All rights reserved. +// + +import Foundation +import UIKit + +final class AdamantVibroService: VibroService { + func applyVibration(_ type: AdamantVibroType) { + switch type { + case .light: + UIImpactFeedbackGenerator(style: .light).impactOccurred() + case .rigid: + UIImpactFeedbackGenerator(style: .rigid).impactOccurred() + case .heavy: + UIImpactFeedbackGenerator(style: .heavy).impactOccurred() + case .medium: + UIImpactFeedbackGenerator(style: .medium).impactOccurred() + case .soft: + UIImpactFeedbackGenerator(style: .soft).impactOccurred() + case .success: + UINotificationFeedbackGenerator().notificationOccurred(.success) + case .warning: + UINotificationFeedbackGenerator().notificationOccurred(.warning) + case .error: + UINotificationFeedbackGenerator().notificationOccurred(.error) + case .selection: + UISelectionFeedbackGenerator().selectionChanged() + } + } +} diff --git a/Adamant/Stories/Account/AccountViewController.swift b/Adamant/Stories/Account/AccountViewController.swift index f26a820a1..f7bf13e15 100644 --- a/Adamant/Stories/Account/AccountViewController.swift +++ b/Adamant/Stories/Account/AccountViewController.swift @@ -59,7 +59,7 @@ class AccountViewController: FormViewController { enum Rows { case balance, sendTokens // Wallet - case security, nodes, theme, currency, about, visibleWallets, contribute // Application + case security, nodes, theme, currency, about, visibleWallets, contribute, vibration // Application case voteForDelegates, generateQr, generatePk, logout // Actions case stayIn, biometry, notifications // Security @@ -81,6 +81,7 @@ class AccountViewController: FormViewController { case .notifications: return "notifications" case .visibleWallets: return "visibleWallets" case .contribute: return "contribute" + case .vibration: return "vibration" } } @@ -102,6 +103,7 @@ class AccountViewController: FormViewController { case .notifications: return SecurityViewController.Rows.notificationsMode.localized case .visibleWallets: return .localized("VisibleWallets.Title", comment: "Visible Wallets page: scene title") case .contribute: return .localized("AccountTab.Row.Contribute", comment: "Account tab: 'Contribute' row") + case .vibration: return "Vibrations" } } @@ -123,6 +125,7 @@ class AccountViewController: FormViewController { case .notifications: return .asset(named: "row_Notifications.png") case .visibleWallets: return .asset(named: "row_balance") case .contribute: return .asset(named: "row_contribute") + case .vibration: return .asset(named: "row_contribute") } } } @@ -374,6 +377,35 @@ class AccountViewController: FormViewController { appSection.append(contributeRow) + // Contribute + let vibrationRow = LabelRow { + $0.title = Rows.vibration.localized + $0.tag = Rows.vibration.tag + $0.cell.imageView?.image = Rows.vibration.image + $0.cell.selectionStyle = .gray + }.cellUpdate { (cell, _) in + cell.accessoryType = .disclosureIndicator + }.onCellSelection { [weak self] (_, _) in + guard let vc = self?.router.get(scene: AdamantScene.Settings.vibration) + else { + return + } + + if let split = self?.splitViewController { + let details = UINavigationController(rootViewController:vc) + split.showDetailViewController(details, sender: self) + } else if let nav = self?.navigationController { + nav.pushViewController(vc, animated: true) + } else { + vc.modalPresentationStyle = .overFullScreen + self?.present(vc, animated: true, completion: nil) + } + + self?.deselectWalletViewControllers() + } + + appSection.append(vibrationRow) + // About let aboutRow = LabelRow { $0.title = Rows.about.localized diff --git a/Adamant/Stories/Settings/SettingsRoutes.swift b/Adamant/Stories/Settings/SettingsRoutes.swift index ae0dd3e0a..1b2361674 100644 --- a/Adamant/Stories/Settings/SettingsRoutes.swift +++ b/Adamant/Stories/Settings/SettingsRoutes.swift @@ -61,6 +61,10 @@ extension AdamantScene { r.resolve(ContributeFactory.self)!.makeViewController() }) + static let vibration = AdamantScene(identifier: "Vibration", factory: { r in + r.resolve(VibrationSelectionFactory.self)!.makeViewController() + }) + private init() {} } } diff --git a/Adamant/Stories/Settings/TestVibration/VibrationSelectionFactory.swift b/Adamant/Stories/Settings/TestVibration/VibrationSelectionFactory.swift new file mode 100644 index 000000000..ff1a24cae --- /dev/null +++ b/Adamant/Stories/Settings/TestVibration/VibrationSelectionFactory.swift @@ -0,0 +1,23 @@ +// +// VibrationSelectionFactory.swift +// Adamant +// +// Created by Stanislav Jelezoglo on 07.09.2023. +// Copyright © 2023 Adamant. All rights reserved. +// + +import UIKit +import SwiftUI + +@MainActor +struct VibrationSelectionFactory { + let vibroService: VibroService + + func makeViewController() -> UIViewController { + UIHostingController( + rootView: VibrationSelectionView( + viewModel: .init(vibroService: vibroService) + ) + ) + } +} diff --git a/Adamant/Stories/Settings/TestVibration/VibrationSelectionView.swift b/Adamant/Stories/Settings/TestVibration/VibrationSelectionView.swift new file mode 100644 index 000000000..15be1e638 --- /dev/null +++ b/Adamant/Stories/Settings/TestVibration/VibrationSelectionView.swift @@ -0,0 +1,50 @@ +// +// VibrationSelectionView.swift +// Adamant +// +// Created by Stanislav Jelezoglo on 07.09.2023. +// Copyright © 2023 Adamant. All rights reserved. +// + +import SwiftUI + +struct VibrationSelectionView: View { + @ObservedObject var viewModel: VibrationSelectionViewModel + + init(viewModel: VibrationSelectionViewModel) { + _viewModel = .init(wrappedValue: viewModel) + } + + var body: some View { + List { + ForEach(AdamantVibroType.allCases, id: \.self) { type in + Button(vibrationTypeDescription(type)) { + viewModel.type = type + } + } + } + } + + private func vibrationTypeDescription(_ type: AdamantVibroType) -> String { + switch type { + case .light: + return "Light Vibration" + case .rigid: + return "Rigid Vibration" + case .heavy: + return "Heavy Vibration" + case .medium: + return "Medium Vibration" + case .soft: + return "Soft Vibration" + case .selection: + return "Selection Vibration" + case .success: + return "Success Vibration" + case .warning: + return "Warning Vibration" + case .error: + return "Error Vibration" + } + } +} diff --git a/Adamant/Stories/Settings/TestVibration/VibrationSelectionViewModel.swift b/Adamant/Stories/Settings/TestVibration/VibrationSelectionViewModel.swift new file mode 100644 index 000000000..089b3eb62 --- /dev/null +++ b/Adamant/Stories/Settings/TestVibration/VibrationSelectionViewModel.swift @@ -0,0 +1,30 @@ +// +// VibrationSelectionViewModel.swift +// Adamant +// +// Created by Stanislav Jelezoglo on 07.09.2023. +// Copyright © 2023 Adamant. All rights reserved. +// + +import SwiftUI +import Combine +import CommonKit + +@MainActor +final class VibrationSelectionViewModel: ObservableObject { + private let vibroService: VibroService + private var subscriptions = Set() + + @Published var type: AdamantVibroType? + + init(vibroService: VibroService) { + self.vibroService = vibroService + + $type + .sink { [weak vibroService] in + guard let type = $0 else { return } + vibroService?.applyVibration(type) + } + .store(in: &subscriptions) + } +} diff --git a/Adamant/SwinjectDependencies.swift b/Adamant/SwinjectDependencies.swift index 50d8a98a5..96687fc64 100644 --- a/Adamant/SwinjectDependencies.swift +++ b/Adamant/SwinjectDependencies.swift @@ -42,7 +42,10 @@ extension Container { // MARK: - Services with dependencies // MARK: DialogService self.register(DialogService.self) { r in - AdamantDialogService(router: r.resolve(Router.self)!) + AdamantDialogService( + router: r.resolve(Router.self)!, + vibroService: r.resolve(VibroService.self)! + ) }.inObjectScope(.container) // MARK: Notifications @@ -77,6 +80,11 @@ extension Container { ) }.inObjectScope(.container) + // MARK: VibroService + self.register(VibroService.self) { r in + AdamantVibroService() + }.inObjectScope(.container) + // MARK: CrashlysticsService self.register(CrashlyticsService.self) { r in AdamantCrashlyticsService( @@ -239,6 +247,11 @@ extension Container { ContributeFactory(crashliticsService: r.resolve(CrashlyticsService.self)!) }.inObjectScope(.container) + // MARK: Vibration screen factory + self.register(VibrationSelectionFactory.self) { r in + VibrationSelectionFactory(vibroService: r.resolve(VibroService.self)!) + }.inObjectScope(.container) + // MARK: Rich transaction status service self.register(RichTransactionStatusService.self) { r in let accountService = r.resolve(AccountService.self)! diff --git a/Adamant/Wallets/Adamant/AdmWalletRoutes.swift b/Adamant/Wallets/Adamant/AdmWalletRoutes.swift index a30bf523a..a6b61f52f 100644 --- a/Adamant/Wallets/Adamant/AdmWalletRoutes.swift +++ b/Adamant/Wallets/Adamant/AdmWalletRoutes.swift @@ -29,7 +29,8 @@ extension AdamantScene.Wallets { dialogService: r.resolve(DialogService.self)!, router: r.resolve(Router.self)!, currencyInfoService: r.resolve(CurrencyInfoService.self)!, - increaseFeeService: r.resolve(IncreaseFeeService.self)! + increaseFeeService: r.resolve(IncreaseFeeService.self)!, + vibroService: r.resolve(VibroService.self)! ) } diff --git a/Adamant/Wallets/Bitcoin/BtcWalletRoutes.swift b/Adamant/Wallets/Bitcoin/BtcWalletRoutes.swift index b04c0809e..9674b633f 100644 --- a/Adamant/Wallets/Bitcoin/BtcWalletRoutes.swift +++ b/Adamant/Wallets/Bitcoin/BtcWalletRoutes.swift @@ -28,7 +28,8 @@ extension AdamantScene.Wallets { dialogService: r.resolve(DialogService.self)!, router: r.resolve(Router.self)!, currencyInfoService: r.resolve(CurrencyInfoService.self)!, - increaseFeeService: r.resolve(IncreaseFeeService.self)! + increaseFeeService: r.resolve(IncreaseFeeService.self)!, + vibroService: r.resolve(VibroService.self)! ) } diff --git a/Adamant/Wallets/Dash/DashWalletRouter.swift b/Adamant/Wallets/Dash/DashWalletRouter.swift index a4e11270a..f2ee88cae 100644 --- a/Adamant/Wallets/Dash/DashWalletRouter.swift +++ b/Adamant/Wallets/Dash/DashWalletRouter.swift @@ -28,7 +28,8 @@ extension AdamantScene.Wallets { dialogService: r.resolve(DialogService.self)!, router: r.resolve(Router.self)!, currencyInfoService: r.resolve(CurrencyInfoService.self)!, - increaseFeeService: r.resolve(IncreaseFeeService.self)! + increaseFeeService: r.resolve(IncreaseFeeService.self)!, + vibroService: r.resolve(VibroService.self)! ) } diff --git a/Adamant/Wallets/Doge/DogeWalletRoutes.swift b/Adamant/Wallets/Doge/DogeWalletRoutes.swift index b9d2c77bb..1ac8fdafd 100644 --- a/Adamant/Wallets/Doge/DogeWalletRoutes.swift +++ b/Adamant/Wallets/Doge/DogeWalletRoutes.swift @@ -28,7 +28,8 @@ extension AdamantScene.Wallets { dialogService: r.resolve(DialogService.self)!, router: r.resolve(Router.self)!, currencyInfoService: r.resolve(CurrencyInfoService.self)!, - increaseFeeService: r.resolve(IncreaseFeeService.self)! + increaseFeeService: r.resolve(IncreaseFeeService.self)!, + vibroService: r.resolve(VibroService.self)! ) } diff --git a/Adamant/Wallets/ERC20/ERC20WalletRouter.swift b/Adamant/Wallets/ERC20/ERC20WalletRouter.swift index dc223279c..d916d1f80 100644 --- a/Adamant/Wallets/ERC20/ERC20WalletRouter.swift +++ b/Adamant/Wallets/ERC20/ERC20WalletRouter.swift @@ -28,7 +28,8 @@ extension AdamantScene.Wallets { dialogService: r.resolve(DialogService.self)!, router: r.resolve(Router.self)!, currencyInfoService: r.resolve(CurrencyInfoService.self)!, - increaseFeeService: r.resolve(IncreaseFeeService.self)! + increaseFeeService: r.resolve(IncreaseFeeService.self)!, + vibroService: r.resolve(VibroService.self)! ) } diff --git a/Adamant/Wallets/Ethereum/EthWalletRoutes.swift b/Adamant/Wallets/Ethereum/EthWalletRoutes.swift index 573322002..7c30dee27 100644 --- a/Adamant/Wallets/Ethereum/EthWalletRoutes.swift +++ b/Adamant/Wallets/Ethereum/EthWalletRoutes.swift @@ -28,7 +28,8 @@ extension AdamantScene.Wallets { dialogService: r.resolve(DialogService.self)!, router: r.resolve(Router.self)!, currencyInfoService: r.resolve(CurrencyInfoService.self)!, - increaseFeeService: r.resolve(IncreaseFeeService.self)! + increaseFeeService: r.resolve(IncreaseFeeService.self)!, + vibroService: r.resolve(VibroService.self)! ) } diff --git a/Adamant/Wallets/Lisk/LskWalletRoutes.swift b/Adamant/Wallets/Lisk/LskWalletRoutes.swift index 200ed1259..58379365d 100644 --- a/Adamant/Wallets/Lisk/LskWalletRoutes.swift +++ b/Adamant/Wallets/Lisk/LskWalletRoutes.swift @@ -28,7 +28,8 @@ extension AdamantScene.Wallets { dialogService: r.resolve(DialogService.self)!, router: r.resolve(Router.self)!, currencyInfoService: r.resolve(CurrencyInfoService.self)!, - increaseFeeService: r.resolve(IncreaseFeeService.self)! + increaseFeeService: r.resolve(IncreaseFeeService.self)!, + vibroService: r.resolve(VibroService.self)! ) } diff --git a/Adamant/Wallets/TransferViewControllerBase+QR.swift b/Adamant/Wallets/TransferViewControllerBase+QR.swift index b55c49ad9..6d93ceca1 100644 --- a/Adamant/Wallets/TransferViewControllerBase+QR.swift +++ b/Adamant/Wallets/TransferViewControllerBase+QR.swift @@ -105,16 +105,16 @@ extension TransferViewControllerBase: UINavigationControllerDelegate, UIImagePic } let codes = EFQRCode.recognize(cgImage) - if codes.count > 0 { - for aCode in codes { - if handleRawAddress(aCode) { - return - } - } - - dialogService.showWarning(withMessage: String.adamant.newChat.wrongQrError) - } else { + + if codes.contains(where: handleRawAddress) { + vibroService.applyVibration(.medium) + return + } + + if codes.isEmpty { dialogService.showWarning(withMessage: String.adamant.login.noQrError) + } else { + dialogService.showWarning(withMessage: String.adamant.newChat.wrongQrError) } } } @@ -123,6 +123,7 @@ extension TransferViewControllerBase: UINavigationControllerDelegate, UIImagePic extension TransferViewControllerBase: QRCodeReaderViewControllerDelegate { func reader(_ reader: QRCodeReaderViewController, didScanResult result: QRCodeReaderResult) { if handleRawAddress(result.value) { + vibroService.applyVibration(.medium) dismiss(animated: true, completion: nil) } else { dialogService.showWarning(withMessage: String.adamant.newChat.wrongQrError) diff --git a/Adamant/Wallets/TransferViewControllerBase.swift b/Adamant/Wallets/TransferViewControllerBase.swift index 2adfcd558..fed0861ba 100644 --- a/Adamant/Wallets/TransferViewControllerBase.swift +++ b/Adamant/Wallets/TransferViewControllerBase.swift @@ -137,6 +137,7 @@ class TransferViewControllerBase: FormViewController { let currencyInfoService: CurrencyInfoService var increaseFeeService: IncreaseFeeService var chatsProvider: ChatsProvider + let vibroService: VibroService // MARK: - Properties @@ -275,7 +276,8 @@ class TransferViewControllerBase: FormViewController { dialogService: DialogService, router: Router, currencyInfoService: CurrencyInfoService, - increaseFeeService: IncreaseFeeService + increaseFeeService: IncreaseFeeService, + vibroService: VibroService ) { self.accountService = accountService self.accountsProvider = accountsProvider @@ -284,6 +286,7 @@ class TransferViewControllerBase: FormViewController { self.currencyInfoService = currencyInfoService self.increaseFeeService = increaseFeeService self.chatsProvider = chatsProvider + self.vibroService = vibroService super.init(nibName: nil, bundle: nil) } From 29af3c8444348b453030aa5beccd41285b307ac0 Mon Sep 17 00:00:00 2001 From: StanislavDevIOS Date: Thu, 7 Sep 2023 19:54:01 +0300 Subject: [PATCH 02/96] [trello.com/c/3RorlQxg] feat: vibro on income tx --- Adamant.xcodeproj/project.pbxproj | 8 + Adamant/AppDelegate.swift | 5 + .../NotificationInAppService.swift | 13 + .../AdamantNotificationInAppService.swift | 291 ++++++++++++++++++ .../ChatsList/ChatListViewController.swift | 53 +--- Adamant/SwinjectDependencies.swift | 9 + .../Wallets/Adamant/AdmWalletService.swift | 10 + .../Wallets/Bitcoin/BtcWalletService.swift | 8 + Adamant/Wallets/Dash/DashWalletService.swift | 7 + Adamant/Wallets/Doge/DogeWalletService.swift | 7 + .../Wallets/ERC20/ERC20WalletService.swift | 7 + .../Wallets/Ethereum/EthWalletService.swift | 8 + Adamant/Wallets/Lisk/LskWalletService.swift | 8 + 13 files changed, 383 insertions(+), 51 deletions(-) create mode 100644 Adamant/ServiceProtocols/NotificationInAppService.swift create mode 100644 Adamant/Services/AdamantNotificationInAppService.swift diff --git a/Adamant.xcodeproj/project.pbxproj b/Adamant.xcodeproj/project.pbxproj index a8be09054..dbf081881 100644 --- a/Adamant.xcodeproj/project.pbxproj +++ b/Adamant.xcodeproj/project.pbxproj @@ -18,6 +18,8 @@ 3A7BD0152AA9BF020045AAB0 /* VibrationSelectionView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3A7BD0142AA9BF020045AAB0 /* VibrationSelectionView.swift */; }; 3A7BD0172AA9BF100045AAB0 /* VibrationSelectionFactory.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3A7BD0162AA9BF100045AAB0 /* VibrationSelectionFactory.swift */; }; 3A7BD0192AA9BF3F0045AAB0 /* VibrationSelectionViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3A7BD0182AA9BF3F0045AAB0 /* VibrationSelectionViewModel.swift */; }; + 3A7BD01B2AAA01930045AAB0 /* NotificationInAppService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3A7BD01A2AAA01930045AAB0 /* NotificationInAppService.swift */; }; + 3A7BD01D2AAA01B50045AAB0 /* AdamantNotificationInAppService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3A7BD01C2AAA01B50045AAB0 /* AdamantNotificationInAppService.swift */; }; 3A8875EF27BBF38D00436195 /* Parchment in Frameworks */ = {isa = PBXBuildFile; productRef = 3A8875EE27BBF38D00436195 /* Parchment */; }; 3A9015A52A614A18002A2464 /* EmojiService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3A9015A42A614A18002A2464 /* EmojiService.swift */; }; 3A9015A72A614A62002A2464 /* AdamantEmojiService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3A9015A62A614A62002A2464 /* AdamantEmojiService.swift */; }; @@ -597,6 +599,8 @@ 3A7BD0142AA9BF020045AAB0 /* VibrationSelectionView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VibrationSelectionView.swift; sourceTree = ""; }; 3A7BD0162AA9BF100045AAB0 /* VibrationSelectionFactory.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VibrationSelectionFactory.swift; sourceTree = ""; }; 3A7BD0182AA9BF3F0045AAB0 /* VibrationSelectionViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VibrationSelectionViewModel.swift; sourceTree = ""; }; + 3A7BD01A2AAA01930045AAB0 /* NotificationInAppService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationInAppService.swift; sourceTree = ""; }; + 3A7BD01C2AAA01B50045AAB0 /* AdamantNotificationInAppService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AdamantNotificationInAppService.swift; sourceTree = ""; }; 3A9015A42A614A18002A2464 /* EmojiService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EmojiService.swift; sourceTree = ""; }; 3A9015A62A614A62002A2464 /* AdamantEmojiService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AdamantEmojiService.swift; sourceTree = ""; }; 3A9015A82A615893002A2464 /* ChatMessagesListViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChatMessagesListViewModel.swift; sourceTree = ""; }; @@ -1657,6 +1661,7 @@ 4184F1742A33106200D7B8B9 /* CrashlysticsService.swift */, 3A9015A42A614A18002A2464 /* EmojiService.swift */, 3A7BD00D2AA9BCE80045AAB0 /* VibroService.swift */, + 3A7BD01A2AAA01930045AAB0 /* NotificationInAppService.swift */, 41C1698B29E7F34900FEB3CB /* RichTransactionReplyService.swift */, 3A4193902A580C85006A6B22 /* RichTransactionReactService.swift */, ); @@ -1685,6 +1690,7 @@ 4184F1722A33102800D7B8B9 /* AdamantCrashlysticsService.swift */, 3A9015A62A614A62002A2464 /* AdamantEmojiService.swift */, 3A7BD00F2AA9BD030045AAB0 /* AdamantVibroService.swift */, + 3A7BD01C2AAA01B50045AAB0 /* AdamantNotificationInAppService.swift */, E921597420611A6A0000CA5C /* AdamantReachability.swift */, E950273F202E257E002C1098 /* RepeaterService.swift */, E9E7CDB42002BA6900DFC4DB /* SwinjectedRouter.swift */, @@ -2912,6 +2918,7 @@ 41A1995629D56EAA0031AD75 /* ChatMessageReplyCell+Model.swift in Sources */, E90847312196FEA80095825D /* ChatTransaction+CoreDataProperties.swift in Sources */, E99330262136B0E500CD5200 /* TransferViewControllerBase+QR.swift in Sources */, + 3A7BD01D2AAA01B50045AAB0 /* AdamantNotificationInAppService.swift in Sources */, E9B1AA5B21283E0F00080A2A /* AdmTransferViewController.swift in Sources */, 648C697322916192006645F5 /* DashTransactionsViewController.swift in Sources */, 55E69E172868D7920025D82E /* CheckmarkView.swift in Sources */, @@ -3024,6 +3031,7 @@ E9AA8BF82129F13000F9249F /* ComplexTransferViewController.swift in Sources */, E9A174B52057EDCE003667CD /* AdamantTransfersProvider+backgroundFetch.swift in Sources */, 9382F61329DEC0A3005E6216 /* ChatModelView.swift in Sources */, + 3A7BD01B2AAA01930045AAB0 /* NotificationInAppService.swift in Sources */, E9147B5F20500E9300145913 /* MyLittlePinpad+adamant.swift in Sources */, E9981896212095CA0018C84C /* EthWalletViewController.swift in Sources */, 648DD7A22237D9A000B811FD /* DogeTransaction.swift in Sources */, diff --git a/Adamant/AppDelegate.swift b/Adamant/AppDelegate.swift index 74820c1a2..1584e7ee7 100644 --- a/Adamant/AppDelegate.swift +++ b/Adamant/AppDelegate.swift @@ -219,6 +219,11 @@ class AppDelegate: UIResponder, UIApplicationDelegate { Task { await service.startObserving() } } + // Setup notifications in app observing + if let service = container.resolve(NotificationInAppService.self) { + service.startObserving() + } + // Register repeater services if let chatsProvider = container.resolve(ChatsProvider.self) { repeater.registerForegroundCall(label: "chatsProvider", interval: 10, queue: .global(qos: .utility), callback: { diff --git a/Adamant/ServiceProtocols/NotificationInAppService.swift b/Adamant/ServiceProtocols/NotificationInAppService.swift new file mode 100644 index 000000000..88cd57eeb --- /dev/null +++ b/Adamant/ServiceProtocols/NotificationInAppService.swift @@ -0,0 +1,13 @@ +// +// NotificationInAppService.swift +// Adamant +// +// Created by Stanislav Jelezoglo on 07.09.2023. +// Copyright © 2023 Adamant. All rights reserved. +// + +import Foundation + +protocol NotificationInAppService: AnyObject { + func startObserving() +} diff --git a/Adamant/Services/AdamantNotificationInAppService.swift b/Adamant/Services/AdamantNotificationInAppService.swift new file mode 100644 index 000000000..3fc06b627 --- /dev/null +++ b/Adamant/Services/AdamantNotificationInAppService.swift @@ -0,0 +1,291 @@ +// +// AdamantNotificationInAppService.swift +// Adamant +// +// Created by Stanislav Jelezoglo on 07.09.2023. +// Copyright © 2023 Adamant. All rights reserved. +// + +import Foundation +import CoreData +import UIKit +import CommonKit + +final class AdamantNotificationInAppService: NSObject, NotificationInAppService { + + // MARK: Dependencies + + private let chatsProvider: ChatsProvider + private let dialogService: DialogService + private let accountService: AccountService + private var richMessageProviders: [String: RichMessageProvider] = [:] + + // MARK: Proprieties + + private var unreadController: NSFetchedResultsController? + private let defaultAvatar = UIImage.asset(named: "avatar-chat-placeholder") ?? .init() + + // MARK: Init + + init( + chatsProvider: ChatsProvider, + dialogService: DialogService, + accountService: AccountService + ) { + self.chatsProvider = chatsProvider + self.dialogService = dialogService + self.accountService = accountService + super.init() + + self.richMessageProviders = self.makeRichMessageProviders() + } + + func makeRichMessageProviders() -> [String: RichMessageProvider] { + .init( + uniqueKeysWithValues: accountService + .wallets + .compactMap { $0 as? RichMessageProvider } + .map { ($0.dynamicRichMessageType, $0) } + ) + } + + func startObserving() { + Task { + unreadController = await chatsProvider.getUnreadMessagesController() + unreadController?.delegate = self + try? unreadController?.performFetch() + } + } +} + +// MARK: Core Data + +extension AdamantNotificationInAppService: NSFetchedResultsControllerDelegate { + func controller( + _: NSFetchedResultsController, + didChange object: Any, + at _: IndexPath?, + for type_: NSFetchedResultsChangeType, + newIndexPath _: IndexPath? + ) { + guard type_ == .insert else { return } + + if let transaction = object as? ChatTransaction { + showNotification(for: transaction) + } + + let forceUpdate = object is TransferTransaction + || object is RichMessageTransaction + + guard forceUpdate else { return } + + NotificationCenter.default.post( + name: .AdamantAccountService.forceUpdateBalance, + object: nil + ) + + Task { + await Task.sleep(interval: 4) + + NotificationCenter.default.post( + name: .AdamantAccountService.forceUpdateBalance, + object: nil + ) + } + } +} + +// MARK: Working with in-app notifications + +private extension AdamantNotificationInAppService { + func showNotification(for transaction: ChatTransaction) { + Task { + // MARK: Do not show notifications for initial sync + + guard await chatsProvider.isInitiallySynced else { + return + } + + // MARK: Show notification only for incomming transactions + + guard !transaction.silentNotification, + !transaction.isOutgoing, + let chatroom = transaction.chatroom, + await shoudPresentNotification(chatroom: chatroom), + let partner = chatroom.partner + else { + return + } + + // MARK: Prepare notification + + let title = partner.name ?? partner.address + let text = shortDescription(for: transaction) + + let image: UIImage + if let ava = partner.avatar, let img = UIImage.asset(named: ava) { + image = img + } else { + image = defaultAvatar + } + + // MARK: Show notification with tap handler + await dialogService.showNotification( + title: title?.checkAndReplaceSystemWallets(), + message: text, + image: image + ) { [weak self, chatroom] in + self?.presentChatroom(chatroom) + } + } + } + + func shortDescription(for transaction: ChatTransaction) -> String? { + switch transaction { + case let message as MessageTransaction: + guard let text = message.message else { + return nil + } + + let raw: String + if message.isOutgoing { + raw = "\(String.adamant.chatList.sentMessagePrefix)\(text)" + } else { + raw = text + } + + return raw + + case let transfer as TransferTransaction: + if let admService = richMessageProviders[AdmWalletService.richMessageType] as? AdmWalletService { + return admService.shortDescription(for: transfer) + } else { + return nil + } + + case let richMessage as RichMessageTransaction: + if let type = richMessage.richType, + let provider = richMessageProviders[type] { + return provider.shortDescription(for: richMessage).string + } + + if richMessage.additionalType == .reply, + let content = richMessage.richContent, + let text = content[RichContentKeys.reply.replyMessage] as? String { + + let prefix = richMessage.isOutgoing + ? "\(String.adamant.chatList.sentMessagePrefix)" + : "" + + let extraSpace = richMessage.isOutgoing ? " " : "" + return "\(prefix)\(extraSpace)\(text)" + } + + if richMessage.additionalType == .reaction, + let content = richMessage.richContent, + let reaction = content[RichContentKeys.react.react_message] as? String { + let prefix = richMessage.isOutgoing + ? "\(String.adamant.chatList.sentMessagePrefix)" + : "" + + let text = reaction.isEmpty + ? "\(prefix)\(String.adamant.chatList.removedReaction) \(reaction)" + : "\(prefix)\(String.adamant.chatList.reacted) \(reaction)" + + return text + } + + if let serialized = richMessage.serializedMessage() { + return serialized + } + + return nil + default: + return nil + } + } + + @MainActor func shoudPresentNotification(chatroom: Chatroom) -> Bool { + guard chatroom != presentedChatroom(), + !chatroom.isHidden, + !(rootViewController() is ChatListViewController) + else { return false } + + return true + } + + @MainActor func presentedChatroom() -> Chatroom? { + guard let vc = rootViewController() as? ChatViewController + else { return nil } + + return vc.viewModel.chatroom + } + + func rootViewController() -> UIViewController? { + let allScenes = UIApplication.shared.connectedScenes + let scene = allScenes.first { $0.activationState == .foregroundActive } + + guard let windowScene = scene as? UIWindowScene else { + return nil + } + + var topController = windowScene.keyWindow?.rootViewController + + while (topController?.presentedViewController != nil) { + topController = topController?.presentedViewController + } + + if let tabbar = topController as? UITabBarController, + let split = tabbar.viewControllers?[tabbar.selectedIndex] as? UISplitViewController, + let navigation = split.viewControllers.first as? UINavigationController { + return navigation.visibleViewController + } + + if let tabbar = topController as? UITabBarController, + let navigation = tabbar.viewControllers?[tabbar.selectedIndex] as? UINavigationController { + return navigation.visibleViewController + } + + return topController + } + + func getTabBarController() -> UITabBarController? { + let allScenes = UIApplication.shared.connectedScenes + let scene = allScenes.first { $0.activationState == .foregroundActive } + + guard let windowScene = scene as? UIWindowScene else { + return nil + } + + var topController = windowScene.keyWindow?.rootViewController + + while (topController?.presentedViewController != nil) { + topController = topController?.presentedViewController + } + + return topController as? UITabBarController + } + + func presentChatroom(_ chatroom: Chatroom) { + guard let tabbar = getTabBarController() else { return } + + if let split = tabbar.viewControllers?.first as? UISplitViewController, + let navigation = split.viewControllers.first as? UINavigationController, + let chatListVC = navigation.viewControllers.first as? ChatListViewController { + navigation.popToRootViewController(animated: true) + Task { + await chatListVC.presentChatroom(chatroom) + await chatListVC.selectChatroomRow(chatroom: chatroom) + } + } + + if let navigation = tabbar.viewControllers?.first as? UINavigationController, + let chatListVC = navigation.viewControllers.first as? ChatListViewController { + navigation.popToRootViewController(animated: true) + Task { + await chatListVC.presentChatroom(chatroom) + await chatListVC.selectChatroomRow(chatroom: chatroom) + } + } + } +} diff --git a/Adamant/Stories/ChatsList/ChatListViewController.swift b/Adamant/Stories/ChatsList/ChatListViewController.swift index 691dfc6ee..b7cac0b4e 100644 --- a/Adamant/Stories/ChatsList/ChatListViewController.swift +++ b/Adamant/Stories/ChatsList/ChatListViewController.swift @@ -675,22 +675,6 @@ extension ChatListViewController: NSFetchedResultsControllerDelegate { break } - // MARK: Unread controller - case let c where c == unreadController: - guard type == .insert else { - break - } - - if let transaction = anObject as? ChatTransaction { - if self.view.window == nil { - showNotification(for: transaction) - } - } - if let _ = anObject as? TransferTransaction { - DispatchQueue.main.asyncAfter(deadline: .now() + 4) { - NotificationCenter.default.post(name: .AdamantAccountService.forceUpdateBalance, object: nil) - } - } default: break } @@ -773,41 +757,8 @@ extension ChatListViewController: ChatPreservationDelegate { // MARK: - Working with in-app notifications extension ChatListViewController { - private func showNotification(for transaction: ChatTransaction) { - Task { - // MARK: 0. Do not show notifications for initial sync - guard await chatsProvider.isInitiallySynced else { - return - } - - // MARK: 1. Show notification only for incomming transactions - guard !transaction.silentNotification, !transaction.isOutgoing, - let chatroom = transaction.chatroom, chatroom != presentedChatroom(), !chatroom.isHidden, - let partner = chatroom.partner else { - return - } - - // MARK: 2. Prepare notification - let title = partner.name ?? partner.address - let text = shortDescription(for: transaction) - - let image: UIImage - if let ava = partner.avatar, let img = UIImage.asset(named: ava) { - image = img - } else { - image = defaultAvatar - } - - // MARK: 4. Show notification with tap handler - dialogService.showNotification(title: title?.checkAndReplaceSystemWallets(), message: text?.string, image: image) { [weak self] in - DispatchQueue.main.async { - self?.presentChatroom(chatroom) - } - } - } - } - - private func presentChatroom(_ chatroom: Chatroom, with message: MessageTransaction? = nil) { + @MainActor + func presentChatroom(_ chatroom: Chatroom, with message: MessageTransaction? = nil) { // MARK: 1. Create and config ViewController let vc = chatViewController(for: chatroom, with: message?.transactionId) diff --git a/Adamant/SwinjectDependencies.swift b/Adamant/SwinjectDependencies.swift index 96687fc64..d9a223a52 100644 --- a/Adamant/SwinjectDependencies.swift +++ b/Adamant/SwinjectDependencies.swift @@ -286,6 +286,15 @@ extension Container { ) }.inObjectScope(.container) + // MARK: Notifications in app service + self.register(NotificationInAppService.self) { r in + AdamantNotificationInAppService( + chatsProvider: r.resolve(ChatsProvider.self)!, + dialogService: r.resolve(DialogService.self)!, + accountService: r.resolve(AccountService.self)! + ) + }.inObjectScope(.container) + // MARK: Bitcoin AddressConverterFactory self.register(AddressConverterFactory.self) { _ in AddressConverterFactory() diff --git a/Adamant/Wallets/Adamant/AdmWalletService.swift b/Adamant/Wallets/Adamant/AdmWalletService.swift index 8c8b98c03..f9c8cce04 100644 --- a/Adamant/Wallets/Adamant/AdmWalletService.swift +++ b/Adamant/Wallets/Adamant/AdmWalletService.swift @@ -58,6 +58,7 @@ class AdmWalletService: NSObject, WalletService { var apiService: ApiService! var transfersProvider: TransfersProvider! var router: Router! + var vibroService: VibroService! // MARK: - Notifications let walletUpdatedNotification = Notification.Name("adamant.admWallet.updated") @@ -129,8 +130,12 @@ class AdmWalletService: NSObject, WalletService { } let notify: Bool + + let isRaised: Bool + if let wallet = wallet as? AdmWallet { wallet.isBalanceInitialized = true + isRaised = (wallet.balance < account.balance) && wallet.isBalanceInitialized if wallet.balance != account.balance { wallet.balance = account.balance notify = true @@ -144,8 +149,12 @@ class AdmWalletService: NSObject, WalletService { self.wallet = wallet notify = true + isRaised = false } + if isRaised { + vibroService.applyVibration(.success) + } if notify, let wallet = wallet { postUpdateNotification(with: wallet) } @@ -198,6 +207,7 @@ extension AdmWalletService: SwinjectDependentService { apiService = container.resolve(ApiService.self) transfersProvider = container.resolve(TransfersProvider.self) router = container.resolve(Router.self) + vibroService = container.resolve(VibroService.self) Task { let controller = await transfersProvider.unreadTransfersController() diff --git a/Adamant/Wallets/Bitcoin/BtcWalletService.swift b/Adamant/Wallets/Bitcoin/BtcWalletService.swift index 61c74dc4c..b1a7938ad 100644 --- a/Adamant/Wallets/Bitcoin/BtcWalletService.swift +++ b/Adamant/Wallets/Bitcoin/BtcWalletService.swift @@ -120,6 +120,7 @@ final class BtcWalletService: WalletService { var router: Router! var increaseFeeService: IncreaseFeeService! var addressConverter: AddressConverter! + var vibroService: VibroService! // MARK: - Constants static var currencyLogo = UIImage.asset(named: "bitcoin_wallet") ?? .init() @@ -239,6 +240,8 @@ final class BtcWalletService: WalletService { wallet.isBalanceInitialized = true let notification: Notification.Name? + let isRaised = (wallet.balance < balance) && !initialBalanceCheck + if wallet.balance != balance { wallet.balance = balance notification = walletUpdatedNotification @@ -250,6 +253,10 @@ final class BtcWalletService: WalletService { notification = nil } + if isRaised { + vibroService.applyVibration(.success) + } + if let notification = notification { NotificationCenter.default.post( name: notification, @@ -448,6 +455,7 @@ extension BtcWalletService: SwinjectDependentService { router = container.resolve(Router.self) increaseFeeService = container.resolve(IncreaseFeeService.self) addressConverter = container.resolve(AddressConverterFactory.self)?.make(network: network) + vibroService = container.resolve(VibroService.self) } } diff --git a/Adamant/Wallets/Dash/DashWalletService.swift b/Adamant/Wallets/Dash/DashWalletService.swift index aeeb0084b..bf3ecfb45 100644 --- a/Adamant/Wallets/Dash/DashWalletService.swift +++ b/Adamant/Wallets/Dash/DashWalletService.swift @@ -64,6 +64,7 @@ final class DashWalletService: WalletService { var dialogService: DialogService! var router: Router! var addressConverter: AddressConverter! + var vibroService: VibroService! // MARK: - Constants static var currencyLogo = UIImage.asset(named: "dash_wallet") ?? .init() @@ -216,6 +217,7 @@ final class DashWalletService: WalletService { if let balance = try? await getBalance() { wallet.isBalanceInitialized = true let notification: Notification.Name? + let isRaised = (wallet.balance < balance) && !initialBalanceCheck if wallet.balance != balance { wallet.balance = balance @@ -228,6 +230,10 @@ final class DashWalletService: WalletService { notification = nil } + if isRaised { + vibroService.applyVibration(.success) + } + if let notification = notification { NotificationCenter.default.post( name: notification, @@ -340,6 +346,7 @@ extension DashWalletService: SwinjectDependentService { router = container.resolve(Router.self) addressConverter = container.resolve(AddressConverterFactory.self)? .make(network: network) + vibroService = container.resolve(VibroService.self) } } diff --git a/Adamant/Wallets/Doge/DogeWalletService.swift b/Adamant/Wallets/Doge/DogeWalletService.swift index e3af97d18..bb1838f05 100644 --- a/Adamant/Wallets/Doge/DogeWalletService.swift +++ b/Adamant/Wallets/Doge/DogeWalletService.swift @@ -60,6 +60,7 @@ class DogeWalletService: WalletService { var dialogService: DialogService! var router: Router! var addressConverter: AddressConverter! + var vibroService: VibroService! // MARK: - Constants static var currencyLogo = UIImage.asset(named: "doge_wallet") ?? .init() @@ -206,6 +207,7 @@ class DogeWalletService: WalletService { if let balance = try? await getBalance() { wallet.isBalanceInitialized = true let notification: Notification.Name? + let isRaised = (wallet.balance < balance) && !initialBalanceCheck if wallet.balance != balance { wallet.balance = balance @@ -218,6 +220,10 @@ class DogeWalletService: WalletService { notification = nil } + if isRaised { + vibroService.applyVibration(.success) + } + if let notification = notification { NotificationCenter.default.post(name: notification, object: self, userInfo: [AdamantUserInfoKey.WalletService.wallet: wallet]) } @@ -321,6 +327,7 @@ extension DogeWalletService: SwinjectDependentService { router = container.resolve(Router.self) addressConverter = container.resolve(AddressConverterFactory.self)? .make(network: network) + vibroService = container.resolve(VibroService.self) } } diff --git a/Adamant/Wallets/ERC20/ERC20WalletService.swift b/Adamant/Wallets/ERC20/ERC20WalletService.swift index d1b2cd1b2..e39a9ac02 100644 --- a/Adamant/Wallets/ERC20/ERC20WalletService.swift +++ b/Adamant/Wallets/ERC20/ERC20WalletService.swift @@ -102,6 +102,7 @@ class ERC20WalletService: WalletService { var dialogService: DialogService! var router: Router! var increaseFeeService: IncreaseFeeService! + var vibroService: VibroService! // MARK: - Notifications var walletUpdatedNotification = Notification.Name("adamant.erc20Wallet.walletUpdated") @@ -268,6 +269,7 @@ class ERC20WalletService: WalletService { if let balance = try? await getBalance(forAddress: wallet.ethAddress) { wallet.isBalanceInitialized = true let notification: Notification.Name? + let isRaised = (wallet.balance < balance) && !initialBalanceCheck if wallet.balance != balance { wallet.balance = balance @@ -280,6 +282,10 @@ class ERC20WalletService: WalletService { notification = nil } + if isRaised { + vibroService.applyVibration(.success) + } + if let notification = notification { NotificationCenter.default.post(name: notification, object: self, userInfo: [AdamantUserInfoKey.WalletService.wallet: wallet]) } @@ -447,6 +453,7 @@ extension ERC20WalletService: SwinjectDependentService { dialogService = container.resolve(DialogService.self) router = container.resolve(Router.self) increaseFeeService = container.resolve(IncreaseFeeService.self) + vibroService = container.resolve(VibroService.self) } } diff --git a/Adamant/Wallets/Ethereum/EthWalletService.swift b/Adamant/Wallets/Ethereum/EthWalletService.swift index 54b0461bb..e2d41b3b9 100644 --- a/Adamant/Wallets/Ethereum/EthWalletService.swift +++ b/Adamant/Wallets/Ethereum/EthWalletService.swift @@ -125,6 +125,7 @@ class EthWalletService: WalletService { var dialogService: DialogService! var router: Router! var increaseFeeService: IncreaseFeeService! + var vibroService: VibroService! // MARK: - Notifications let walletUpdatedNotification = Notification.Name("adamant.ethWallet.walletUpdated") @@ -290,6 +291,8 @@ class EthWalletService: WalletService { let notification: Notification.Name? + let isRaised = (wallet.balance < balance) && !initialBalanceCheck + if wallet.balance != balance { wallet.balance = balance notification = walletUpdatedNotification @@ -301,6 +304,10 @@ class EthWalletService: WalletService { notification = nil } + if isRaised { + vibroService.applyVibration(.success) + } + if let notification = notification { NotificationCenter.default.post(name: notification, object: self, userInfo: [AdamantUserInfoKey.WalletService.wallet: wallet]) } @@ -539,6 +546,7 @@ extension EthWalletService: SwinjectDependentService { dialogService = container.resolve(DialogService.self) router = container.resolve(Router.self) increaseFeeService = container.resolve(IncreaseFeeService.self) + vibroService = container.resolve(VibroService.self) } } diff --git a/Adamant/Wallets/Lisk/LskWalletService.swift b/Adamant/Wallets/Lisk/LskWalletService.swift index 3303a4805..1e9ef2ae0 100644 --- a/Adamant/Wallets/Lisk/LskWalletService.swift +++ b/Adamant/Wallets/Lisk/LskWalletService.swift @@ -44,6 +44,7 @@ class LskWalletService: WalletService { var accountService: AccountService! var dialogService: DialogService! var router: Router! + var vibroService: VibroService! // MARK: - Constants var transactionFee: Decimal { @@ -210,6 +211,8 @@ class LskWalletService: WalletService { } if let balance = try? await getBalance() { + let isRaised = (wallet.balance < balance) && !initialBalanceCheck + wallet.isBalanceInitialized = true let notification: Notification.Name? @@ -224,6 +227,10 @@ class LskWalletService: WalletService { notification = nil } + if isRaised { + vibroService.applyVibration(.success) + } + if let notification = notification { NotificationCenter.default.post(name: notification, object: self, userInfo: [AdamantUserInfoKey.WalletService.wallet: wallet]) } @@ -486,6 +493,7 @@ extension LskWalletService: SwinjectDependentService { apiService = container.resolve(ApiService.self) dialogService = container.resolve(DialogService.self) router = container.resolve(Router.self) + vibroService = container.resolve(VibroService.self) } } From b3620d85a4669b7c10e66780d130326f7192e3ce Mon Sep 17 00:00:00 2001 From: StanislavDevIOS Date: Fri, 8 Sep 2023 12:31:05 +0300 Subject: [PATCH 03/96] [trello.com/c/9dFX7nxq] feat: option to see a passphrase --- Adamant/Helpers/UITextField+adamant.swift | 21 ++++++++++++++++ .../Stories/Login/LoginViewController.swift | 1 + .../Settings/PKGeneratorViewController.swift | 4 +-- .../Settings/QRGeneratorViewController.swift | 4 +-- .../Buttons/eye_close.imageset/Contents.json | 23 ++++++++++++++++++ .../Buttons/eye_close.imageset/invisible.png | Bin 0 -> 663 bytes .../eye_close.imageset/invisible@2x.png | Bin 0 -> 1101 bytes .../eye_close.imageset/invisible@3x.png | Bin 0 -> 1718 bytes .../Buttons/eye_open.imageset/Contents.json | 23 ++++++++++++++++++ .../Buttons/eye_open.imageset/visible.png | Bin 0 -> 619 bytes .../Buttons/eye_open.imageset/visible@2x.png | Bin 0 -> 1051 bytes .../Buttons/eye_open.imageset/visible@3x.png | Bin 0 -> 1838 bytes 12 files changed, 72 insertions(+), 4 deletions(-) create mode 100644 CommonKit/Sources/CommonKit/Assets/Shared.xcassets/Buttons/eye_close.imageset/Contents.json create mode 100644 CommonKit/Sources/CommonKit/Assets/Shared.xcassets/Buttons/eye_close.imageset/invisible.png create mode 100644 CommonKit/Sources/CommonKit/Assets/Shared.xcassets/Buttons/eye_close.imageset/invisible@2x.png create mode 100644 CommonKit/Sources/CommonKit/Assets/Shared.xcassets/Buttons/eye_close.imageset/invisible@3x.png create mode 100644 CommonKit/Sources/CommonKit/Assets/Shared.xcassets/Buttons/eye_open.imageset/Contents.json create mode 100644 CommonKit/Sources/CommonKit/Assets/Shared.xcassets/Buttons/eye_open.imageset/visible.png create mode 100644 CommonKit/Sources/CommonKit/Assets/Shared.xcassets/Buttons/eye_open.imageset/visible@2x.png create mode 100644 CommonKit/Sources/CommonKit/Assets/Shared.xcassets/Buttons/eye_open.imageset/visible@3x.png diff --git a/Adamant/Helpers/UITextField+adamant.swift b/Adamant/Helpers/UITextField+adamant.swift index daa3dd685..16ab6e6b7 100644 --- a/Adamant/Helpers/UITextField+adamant.swift +++ b/Adamant/Helpers/UITextField+adamant.swift @@ -137,3 +137,24 @@ extension UITextField { self.defaultTextAttributes[.paragraphStyle] = style } } + +extension UITextField { + func enablePasswordToggle() { + let button = UIButton(type: .custom) + updatePasswordToggleImage(button) + button.frame = CGRect(x: .zero, y: .zero, width: 25, height: 25) + button.addTarget(self, action: #selector(togglePasswordView(_:)), for: .touchUpInside) + rightView = button + rightViewMode = .always + } + + private func updatePasswordToggleImage(_ button: UIButton) { + let imageName = isSecureTextEntry ? "eye_close" : "eye_open" + button.setImage(.asset(named: imageName), for: .normal) + } + + @objc private func togglePasswordView(_ sender: UIButton) { + isSecureTextEntry.toggle() + updatePasswordToggleImage(sender) + } +} diff --git a/Adamant/Stories/Login/LoginViewController.swift b/Adamant/Stories/Login/LoginViewController.swift index 097beff71..30ce47aa1 100644 --- a/Adamant/Stories/Login/LoginViewController.swift +++ b/Adamant/Stories/Login/LoginViewController.swift @@ -218,6 +218,7 @@ class LoginViewController: FormViewController { $0.tag = Rows.passphrase.tag $0.placeholder = Rows.passphrase.localized $0.placeholderColor = UIColor.adamant.secondary + $0.cell.textField.enablePasswordToggle() $0.keyboardReturnType = KeyboardReturnTypeConfiguration(nextKeyboardType: .go, defaultKeyboardType: .go) } diff --git a/Adamant/Stories/Settings/PKGeneratorViewController.swift b/Adamant/Stories/Settings/PKGeneratorViewController.swift index 53ad3aa19..0842cd210 100644 --- a/Adamant/Stories/Settings/PKGeneratorViewController.swift +++ b/Adamant/Stories/Settings/PKGeneratorViewController.swift @@ -105,10 +105,10 @@ class PKGeneratorViewController: FormViewController { cell.textView.attributedText = mutableText } - let passphraseRow = TextAreaRow { + let passphraseRow = PasswordRow { $0.placeholder = String.adamant.qrGenerator.passphrasePlaceholder $0.tag = Rows.passphrase.tag - $0.textAreaHeight = .dynamic(initialTextViewHeight: 28.0) // 28 for textView and 8+8 for insets + $0.cell.textField.enablePasswordToggle() } let generateButton = ButtonRow { diff --git a/Adamant/Stories/Settings/QRGeneratorViewController.swift b/Adamant/Stories/Settings/QRGeneratorViewController.swift index 21474685c..59314cd3d 100644 --- a/Adamant/Stories/Settings/QRGeneratorViewController.swift +++ b/Adamant/Stories/Settings/QRGeneratorViewController.swift @@ -137,10 +137,10 @@ class QRGeneratorViewController: FormViewController { // MARK: Passphrase section form +++ Section { $0.tag = Sections.passphrase.tag } - <<< TextAreaRow { + <<< PasswordRow { $0.placeholder = String.adamant.qrGenerator.passphrasePlaceholder + $0.cell.textField.enablePasswordToggle() $0.tag = Rows.passphrase.tag - $0.textAreaHeight = .dynamic(initialTextViewHeight: 28.0) // 28 for textView and 8+8 for insets } <<< ButtonRow { diff --git a/CommonKit/Sources/CommonKit/Assets/Shared.xcassets/Buttons/eye_close.imageset/Contents.json b/CommonKit/Sources/CommonKit/Assets/Shared.xcassets/Buttons/eye_close.imageset/Contents.json new file mode 100644 index 000000000..c16284299 --- /dev/null +++ b/CommonKit/Sources/CommonKit/Assets/Shared.xcassets/Buttons/eye_close.imageset/Contents.json @@ -0,0 +1,23 @@ +{ + "images" : [ + { + "filename" : "invisible.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "invisible@2x.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "invisible@3x.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/CommonKit/Sources/CommonKit/Assets/Shared.xcassets/Buttons/eye_close.imageset/invisible.png b/CommonKit/Sources/CommonKit/Assets/Shared.xcassets/Buttons/eye_close.imageset/invisible.png new file mode 100644 index 0000000000000000000000000000000000000000..efda9a00b00838494d44c8b0a4d21c03281920f5 GIT binary patch literal 663 zcmV;I0%-k-P)@RUPVb5TP{D^modNnZUVH$^x!UmP4BJ3M% z=j6P2C%|c0`_d5#U=Gs)%xW|myHOM!&~KV%uVq;TqzP4kgw3?_O^v#JSgKa5eE_Ty zkH_Z#8&<+tViqb_bwCbF0rKC^07~3ft7A|!oJb^A0o;Z^0|*6V+JVShgykjwmr^*m zX9bkHcUvwmKTRgz*tWe6-vj^*S(f;WS~7uB8G(25=S53O&O_l9ft8?Ifx;#DRs@(A zU{(i|%3d>{vE*5zB6U+LO%mujNmur8N1Amg!1g_4b?HhF%-)o4FPKjbd4#^J*XwHp zQcluwnoWU9!aNO-3JbDsJhAOw&deu=JVHOlVzD_YJoyi^*O(E#_Ab)y;t$DEl@ z4ta#Gz)rh;f4kd%-sd{bx|CdOJBMmhm@|caJZ^0n^U2}bgYHYYoz7uFI{XRo%?5^H zIEyBI2XF&@7;1H2J@{!kZ=NWOIP<$zs>I}A(aXO6uc&M!)jx2A@A7ZwRP#9K&S7(~ zWZbqjD06Y!-b=|@c?YymEfAMt3$ROEeisQYZrfY<8{7)Oa({aI9y|`d=XGJe=RQJJ x4?r(0)AaHC8otl4c>s4G^CoT@|JC>@zyK-J3>lChMOy#>002ovPDHLkV1m$5BHjQ1 literal 0 HcmV?d00001 diff --git a/CommonKit/Sources/CommonKit/Assets/Shared.xcassets/Buttons/eye_close.imageset/invisible@2x.png b/CommonKit/Sources/CommonKit/Assets/Shared.xcassets/Buttons/eye_close.imageset/invisible@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..8fed53a1480ce54f71f2c9cf3e04e30f35756cd2 GIT binary patch literal 1101 zcmV-T1hV^yP)&Zcc_1MRq~rR-W(k0k zq_1`FWwY6J(rwZm+puLlBjfi>)9lqe{G32zULZYz5+ne_0x=^a=d^6f&NR+5b6pjR<5O)K~P6^VzE3QE$BqhhjuY-dTc+mk80OJD0Z3%LMRFQ+d zg`%K^AQA4n4gQaOD{AAgC^aG0J`VSA36M&z!I}<*drd)p+uH1x*PnEDE%Ba=@xB94 zkU>InPl|%=3KHwtKo=FlVEeg7VQGo8EIlb9k=-J3#s%c(NLm3_hqQ0JsFMa~x7?GSAX$Ry zAJmkPnZow^yf4vpjPvvJ>An5^4IL64M-=m@04)Vl+He#wfQ5ffg3%HgzP`D; z^Umo()fOQl%lkY+=5jy|CDOJOuz*R~waS?8yh5%yjDfDjQpS+cT^)}moNiJBhDbbv z>!er6PnwM@wxxhYRvC+H)vP^5*VaybgD3U!{>WHOmmr^E#VCvS||E<2M`Z7XA*)FJWgV;p%MGzo(S zoe?$YNT)chSFnYOmR1gAl&{5kZ{k`HGMZZShz2<)_T`lpY8DyqN|`5@MdIYbtqp4PtdAajCPhJ!3$<*md7~zH&TU%PDYAkCa&-N+_v>eDJO3>eQdJ)+Tz~(?;fZh-y z+Uzi_z3Y1h^)#x<-i?lj{0000PbVXQnQ*UN; zcVTj606}DLVr3vnZDD6+Qe|Oed2z{QJOBU&SV=@dRCwC#n!QsSM-;&CPBI`_C|Dwl z9fKiBm7$?^71G$0WQJjqDlj3nea;_%^amt@IwZ(N8pbnLI_xH-B2`dBm&C&0U^^Bl zXN(Zmhxd^^1ex1aCN*0M&2pJ{%nzBY(y?9~J)CDHOT{pX0orrpv%zcnUg6#>Zq#&|xQeI({Kz z!{LO8z_Vn``(}9p3~n|}#w~}iP9|fSj2xMWk}PnLuw*MQQgF+SrqO6@lRw6PCQyfo z6-ESKpP+yMo|6SG1)f^^HU)17Rg~R&z5bw9t!|bpmF*V?#a+?01{2AVSTs5t3We@4 zAI%f1O9k&5o`6qNC0I)vwR(N+q;j(M^!f8`_IL4Ef4n~y8#iCSJUZGoj$Rt!a41f| z7dYTq+29?od1~!d>f$X{uD*W#ZOJg!0R~(2VkB~tfK92YdfnpE@SC_F;2pseaM-=? zGuAe0wc5%-sq`I&zIyq}_1>PIuWf)u;wiXmcmh606~LR(xo($q4B^cPzKnt zxp@Q*GDkjFL=&J1z*A1aA)INNHp{qrj?-M&5`bqVf*U85$_!1P@dR*}UPutmGETrj z-c2NzNE|n@L_Q&U4bjgg69WS~hyRrFLd(-| zlffCA6*y#WOah~GEI#KKxEXywU(g%$N8ia%zIJ$&vYMHjaa=zBQUneHDJu?XOgNhm z6g!@RH{T?BgZ`*`JmaIBZI;jA`up_sROb~XEo+A4>geTN=40vL2*MKF6nGyv7=vV5 z;AwT%RDe`uE4%xDt=lFEtuDX8C^+o_ID4PCoq>P@c$f|FOgELf!6S1WZbG^|9uUSG3wBZ~@6@nDUM5*XKAUf=>g zd~FRY2gwTL=pDe1`3Nq$pQQj7lZ<6G8k?J4(P&>J3L#^%ryxX3e2t3WW{3OmoUzN)bMBtz|=#TU?dkiIR zVMBXgI_h+c3?>ry%n9%rJj;pixZ2zl9e|uQSBTzBk3na9du=5xlzpW9Q_ZynWg(P@ zWEOuXJLZo*T3qM!7(PSMMkhP{o)ElsgM%K!lH)cOuUjj*y)4??ma(g&+T%V>#vbiH z)&<}{Fg1s;mF233C%LblV&Zt7w`rp0%0RQnP%Pfy_7U4HZrQ_g-Mv+OI#58azGzp% zb^`7Qo|yn&!0E2u(qB;AofXLG+vCiNa}wgV0|TyWcVcskO_#ydp)13Jk6s;K2#3RI zH|^?<;5}$B7ZmYXoq{*F;^E;Ud+HmH#WI`&P@ZQ>hnLk?7Yf9263)JP@^pU_uWb^B zzoKXOY<7gQ{RDjd@6k5!X3`zD>*iE^9o){msRv+1}``7 z>+PEj{ac5#lV)`!=19VRxjH<&P_5PsvkWC;lh+rF^!AR2!eKZCx@qx{5^QoyomjgQ z%ew(@$;P literal 0 HcmV?d00001 diff --git a/CommonKit/Sources/CommonKit/Assets/Shared.xcassets/Buttons/eye_open.imageset/Contents.json b/CommonKit/Sources/CommonKit/Assets/Shared.xcassets/Buttons/eye_open.imageset/Contents.json new file mode 100644 index 000000000..b6f69963f --- /dev/null +++ b/CommonKit/Sources/CommonKit/Assets/Shared.xcassets/Buttons/eye_open.imageset/Contents.json @@ -0,0 +1,23 @@ +{ + "images" : [ + { + "filename" : "visible.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "visible@2x.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "visible@3x.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/CommonKit/Sources/CommonKit/Assets/Shared.xcassets/Buttons/eye_open.imageset/visible.png b/CommonKit/Sources/CommonKit/Assets/Shared.xcassets/Buttons/eye_open.imageset/visible.png new file mode 100644 index 0000000000000000000000000000000000000000..03284f7c4b6501c3da155ddd51c1b06489fd334c GIT binary patch literal 619 zcmV-x0+juUP)hx>J4te*m=$d())42}2o8ER9KTSWu;HLY-bb5ATVy5D_L#-hTbw z_j#W0y?kG7wtxLmJ`0Qv4s~IfgH6GPETJ6i7`Atj&pmuw6oPA57GY|$+1zinT37FA zMF&AwRdp7-CTt7BYhQ{=b}86My-or3Z)+0-QUl5IGtXQg7Af4 zN!ThdC9Ip$&k*Cp5f9;O@wTyZfA_GBXNgFAEW~wBZ2{OYg%^`cAu%4 zADM9zPd;_1rQ)F*=;)rMPK&GCQp2nCV1!aH%J_>MVGKIXKf<10`92|q!OO^W- zfTN!Q0u000U>Wj~0__dFL={X%2$X1T(CfkTi;vW zkk@g9Hk33CeaU2J+u1k2d0#WThEb7w98tsU)BP zhCqYRUePrli9!2}R{%e_QBVRLhxSgjUN)OuftG}};VKs5DJX9=O|w^H<@eBL3IR3? zfJDF$1GN7*&O#=WiEr*~uiy{9`UYP^eZ3$1het=N_R*n@{m!=bUXRBUxFf_64r9P! zfSoA{C|U6nv_1(4aG?X+jw1*{RC_!_LWlr^kYYQI6HjL{3%D3uNR!VO0O&9qi}CyV zD(jk8Kw^R{N-Kf|XZQB^rx38Mwbk#ftqW^hs}&55)X8yZXLolMfm@oJC&9vF%)0#m zYZVL%kZ<4;k(N0*J$)-(+||)J1Xh_=5@jB(BP~OoKrallgmb;IzO}g^L!!2(W{z2R z1fKtxHzCFCYmZ za!EbCNEQx@F$?;Ga*zhLpKlbVE^$;#vUrNp?Q^u0VkLKRkmX;n4^?1*P0AOBW542$ z5cfeD_Zrxo1QX0H5f{=hk}4z$mLQopEGA{#+39q86g{xEk(|PYeFVY(XbhG(gPx5k zI?po==ES*?fx;(3Z0Dd<$lVE3$Ei^jk}MRU?mUZ0*WCa)k=)*jtIwdduv*YJAEOa}L zjDltON@8uA2+8umwg$xtvE4bi_@6C_CMq_s4XD z@PMK0{XBLDuAD>&xy>%CI=F5w$ilYg5ka-eJ;78}RZU`1*9ABQx4{l4FBw ze~?(#*n}}fhpkIoWIz_W#pB?TbbXj=ozGk6{h$kQJOxpA_@6RUT|KRXWqY|n=KzcA zU^;UW7a7>@7ryC&&n=0`^XNJSzgSx+Kd)YA3!P(&({G_gIF$5IoqR^S%JPjJO3Lb( zSHQuUzfP3q;9OR(B4zb*S5~jf1$}@pj9*#D`Bu^o5M}jAr<5PbDpHY(+&1|uzyM7l VB8NZ}s>c8T002ovPDHLkV1k-u)@uL& literal 0 HcmV?d00001 diff --git a/CommonKit/Sources/CommonKit/Assets/Shared.xcassets/Buttons/eye_open.imageset/visible@3x.png b/CommonKit/Sources/CommonKit/Assets/Shared.xcassets/Buttons/eye_open.imageset/visible@3x.png new file mode 100644 index 0000000000000000000000000000000000000000..fdcb518fa4321982a23326a23f44f9f4c3d63b6b GIT binary patch literal 1838 zcmV+}2hsS6P)j{0000PbVXQnQ*UN; zcVTj606}DLVr3vnZDD6+Qe|Oed2z{QJOBU&&`Cr=RCwC#TFp~hR}|+xP=qKYkT@m= zL)vaSZfG{GtD22*+0HmlyNeUMR?@lJimXiKmYdK zyCr^~ud~b7)!A9y>7Cu(rTp$rp8u|=ySu-`-7)G^)ls}n!h|s}K;l%vi`{-GC|v8q`wvEdjSO*cc=%xb?VA)eLcs6;9*j87n1H~) z0RtpY3_Jk8BX+z-$^7nalDQy4_);nBSyq1KtjBj&u(o^aVGgXJL3|gA_L}{ z4jzCb=sWaUu~?j1USFH#e(Lpjf0ocQBaSRQyVH{SBO)i5;7Jg=(_UfkE{`AAd#{bV zT&@_KY7XWfYZ`b7@cjG2-UwF)fnV@=7Z@cGXtWVZ3fGFWJfV>_Eg3**6gZyI8=IRW zJUZ(rfoJmlW1J@U*%2x=0J__67W(hHHwr-+{a9^>|W{ z9Iep_;AlK3m&!@_?iT!}TU?Idy)Z}86ueM0ZUs2XoL3@rZ;! zsQenaUz1@_I1CCuCI(2tHB72{DvCRup{?!hd9Du{pj0Y9W(F99dzaCOy0jO*_)Mdv zo`2Ev_M@(@=j75Y>bsJ>AP>jm15}#N*>|;fR4_<4V2V@Vnf7^mXXi`3T0s(razekV z0aQuKs`+Cc&sII)D44RcW<-+BSHmDsb_nwV*_>pC(fxRyasd%;>CN zTU@{zs6VM5EBJ(7wcz==dU$vkl@%p5J_ETr`8dPAo}z|?Eby*TMW-o@E{2R_^EhPd zu=VQ%^+z1!QiN`ZN~*JJWQU4ucJ7TZ4S>ZFormrgUB^nz zfH)8jo3B!kDPrPRRX+#X{kgI{WHu@K)a79CC84CkV#f>ZGtl`nBlpocab6Pt=dt9d z(YZhFyZ)5q0Odo&r8qg6N@?Wn z@Wic5hOH+V%{eVGC}DhPxXV(TqbK#;is_*%3>WGbRYm(}j4m1u^~J12Wl4gh1-~>k z0JfkS0gefeq%5vmN2Z5N5DznErR}8(WfPSCPE{QbUAa1JAtO1yh?$$!-N)Zy zuZ|TaIoUdFeW@T0#KY!G@FXWnE!^JT9yjxUY&MdQ6O#6_rgWv;G&4Z8EUhh#dGa)& zv%b{N5eILq)Ma>t{#Avs9c832c3COAMDDH4mk|c2x)bCDzY9uBsb0y&Yv~!Ixwx1= zth0WdAyI$gJ~`gyRZN=)CRH6I4+|vI!m93bv)&)ND$ZMm0kwC(0PHjC#lc)Jv`CMX zhh%KTkU{l+K4)0cN^0kBEDy0v#B#ANih8`OE|*K&%^qGmV_AyzIhLv9HaC{B0Q{yX zXWiUoK+&kAH2#*9u2Ejk8vw8CG3YikMC}~btc(Wmcs!qCqktO#6@X|SB8%14&e)iM zYrl#kDs0fum=pc?gi((HlNG1}XLW%80=nE}42qtm9^N47*XKkaO3R+Q{4YJcmhaxS zeBZw1pV%1m@WTH_5&iv|fxzB=`HZ*8%~7F%qw#THx6 crTi_x0N~%(NP>0{_5c6?07*qoM6N<$f~ypM8UO$Q literal 0 HcmV?d00001 From 163fc03cde809fb0bd79b243a486681d287c711b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=80=9Cjust-software-dev=E2=80=9D?= Date: Sun, 10 Sep 2023 03:49:58 +0100 Subject: [PATCH 04/96] [trello.com/c/ZqOGnQZU] DI fix --- Adamant.xcodeproj/project.pbxproj | 317 ++++++++++-------- .../Adamant.xcdatamodel/contents | 0 Adamant/{ => App}/AppDelegate.swift | 35 +- .../DI/AppAssembly.swift} | 98 ++---- Adamant/App/DI/AppContainer.swift | 17 + Adamant/Helpers/Assembler+Extension.swift | 15 + .../CoreData/AdamantResources+CoreData.swift | 0 .../CoreData/BaseAccount+CoreDataClass.swift | 0 .../BaseAccount+CoreDataProperties.swift | 0 .../BaseTransaction+CoreDataClass.swift | 0 .../BaseTransaction+CoreDataProperties.swift | 0 .../BaseTransaction+TransactionDetails.swift | 0 .../ChatTransaction+CoreDataClass.swift | 0 .../ChatTransaction+CoreDataProperties.swift | 0 .../CoreData/Chatroom+CoreDataClass.swift | 0 .../Chatroom+CoreDataProperties.swift | 0 .../CoreDataAccount+CoreDataClass.swift | 0 .../CoreDataAccount+CoreDataProperties.swift | 0 .../CoreData/DummyAccount+CoreDataClass.swift | 0 .../DummyAccount+CoreDataProperties.swift | 0 .../MessageTransaction+CoreDataClass.swift | 0 ...essageTransaction+CoreDataProperties.swift | 0 ...RichMessageTransaction+CoreDataClass.swift | 0 ...essageTransaction+CoreDataProperties.swift | 0 .../TransferTransaction+CoreDataClass.swift | 0 ...ansferTransaction+CoreDataProperties.swift | 0 Adamant/Models/DashTransaction.swift | 2 +- Adamant/Models/Delegate.swift | 2 +- Adamant/Models/DogeTransaction.swift | 2 +- .../BTCRPCServerResponce.swift | 4 +- .../DogeGetTransactionsResponse.swift | 2 +- .../GetPublicKeyResponse.swift | 0 .../ServerResponseWithTimestamp.swift | 0 .../TransactionIdResponse.swift | 0 Adamant/Modules/Account/AccountFactory.swift | 28 ++ .../Account/AccountFooter.xib | 0 .../Account/AccountHeader.xib | 0 .../Account/AccountHeaderView.swift | 2 +- .../AccountViewController+StayIn.swift | 0 .../Account/AccountViewController.swift | 131 ++++---- .../Account/WalletCollectionViewCell.swift | 0 .../Account/WalletCollectionViewCell.xib | 0 .../Account/WalletPagingItem.swift | 0 .../Chat/ChatFactory.swift | 34 +- .../Chat/ChatLocalization.swift | 0 .../Chat/View/ChatViewController.swift | 34 +- .../View/Helpers/AdamantCellAnimation.swift | 0 .../Chat/View/Helpers/ChatReactionsView.swift | 0 .../Chat/View/Managers/ChatAction.swift | 0 .../Chat/View/Managers/ChatCellManager.swift | 0 .../View/Managers/ChatDataSourceManager.swift | 0 .../View/Managers/ChatDialogManager.swift | 0 .../View/Managers/ChatDisplayManager.swift | 0 .../View/Managers/ChatInputBarManager.swift | 0 .../View/Managers/ChatKeyboardManager.swift | 0 .../View/Managers/ChatLayoutManager.swift | 0 .../Chat/View/Managers/ChatMenuManager.swift | 0 .../FixedTextMessageSizeCalculator.swift | 0 .../ChatMessageCell+Model.swift | 0 .../ChatBaseMessage/ChatMessageCell.swift | 0 .../Chat/View/Subviews/ChatInputBar.swift | 0 .../Subviews/ChatMessagesCollection.swift | 0 .../Chat/View/Subviews/ChatModelView.swift | 0 .../Chat/View/Subviews/ChatRefreshMock.swift | 0 .../ChatMessageReplyCell+Model.swift | 0 .../ChatReply/ChatMessageReplyCell.swift | 0 .../View/Subviews/ChatScrollDownButton.swift | 0 .../ChatTransaction/ChatTransactionCell.swift | 0 .../ChatTransactionContainerView+Model.swift | 0 .../ChatTransactionContainerView.swift | 0 .../ChatTransactionContentView+Model.swift | 0 .../Content/ChatTransactionContentView.swift | 0 .../Chat/ViewModel/ChatCacheService.swift | 16 +- .../Chat/ViewModel/ChatMessageFactory.swift | 0 .../ViewModel/ChatMessagesListFactory.swift | 0 .../ViewModel/ChatMessagesListViewModel.swift | 0 .../ViewModel/ChatPreservationDelegate.swift | 0 .../Chat/ViewModel/ChatViewModel.swift | 0 .../Chat/ViewModel/Models/ChatDialog.swift | 0 .../Chat/ViewModel/Models/ChatMessage.swift | 0 .../Models/ChatMessageBackgroundColor.swift | 0 .../Chat/ViewModel/Models/ChatSender.swift | 0 .../ViewModel/Models/ChatStartPosition.swift | 0 .../Chat/ViewModel/Models/MessageModel.swift | 0 .../Modules/ChatsList/ChatListFactory.swift | 63 ++++ .../ChatsList/ChatListViewController.swift | 27 +- .../ChatsList/ChatListViewController.xib | 0 .../ChatsList/ChatTableViewCell.swift | 2 +- .../ChatsList/ChatTableViewCell.xib | 0 .../ComplexTransferViewController.swift | 5 +- .../ChatsList/NewChatViewController.swift | 24 +- .../SearchResultsViewController.swift | 8 +- .../ChatsList/SearchResultsViewController.xib | 0 .../Delegates/AdamantDelegateCell.swift | 2 +- .../DelegateDetailsViewController.swift | 2 +- .../DelegateDetailsViewController.xib | 0 .../DelegatesBottomPanel+Model.swift | 0 .../DelegatesBottomPanel.swift | 0 .../Modules/Delegates/DelegatesFactory.swift | 31 ++ .../DelegatesListViewController.swift | 11 +- .../Login/EurekaPassphraseRow.swift | 0 Adamant/Modules/Login/LoginFactory.swift | 25 ++ .../Login/LoginViewController+Pinpad.swift | 0 .../Login/LoginViewController+QR.swift | 0 .../Login/LoginViewController.swift | 16 +- .../Login/PassphraseCell.xib | 0 .../NodesEditor/EurekaNodeRow.swift | 2 +- .../NodeEditorViewController.swift | 2 +- .../NodesEditor/NodesEditorFactory.swift | 33 ++ .../NodesEditor/NodesListViewController.swift | 8 +- .../Onboard/EulaViewController.swift | 2 +- .../Onboard/EulaViewController.xib | 0 Adamant/Modules/Onboard/OnboardFactory.swift | 20 ++ .../Onboard/OnboardOverlay.swift | 2 +- .../Onboard/OnboardPage.swift | 2 +- .../Onboard/OnboardPage.xib | 0 .../Onboard/OnboardViewController.swift | 2 +- .../Onboard/OnboardViewController.xib | 0 .../Onboard/ReadonlyTextView.swift | 2 +- .../AdamantScreensFactory.swift | 166 +++++++++ .../ScreensFactory/ScreensFactory.swift | 61 ++++ .../Settings/AboutViewController.swift | 26 +- .../Contribute/ContributeFactory.swift | 36 ++ .../Settings/Contribute/ContributeState.swift | 0 .../Settings/Contribute/ContributeView.swift | 0 .../Contribute/ContributeViewModel.swift | 20 +- .../NotificationSoundsViewController.swift | 2 +- .../NotificationsViewController.swift | 2 +- .../Settings/PKGeneratorViewController.swift | 2 +- .../Settings/PrivateKeyGenerator.swift | 0 .../Settings/QRGeneratorViewController.swift | 2 +- .../SecurityViewController+StayIn.swift | 0 ...SecurityViewController+notifications.swift | 0 .../Settings/SecurityViewController.swift | 11 +- .../Modules/Settings/SettingsFactory.swift | 61 ++++ .../VisibleWalletsCheckmarkView.swift | 0 .../VisibleWalletsResetTableViewCell.swift | 2 +- .../VisibleWalletsTableViewCell.swift | 2 +- .../VisibleWalletsViewController.swift | 0 Adamant/Modules/ShareQR/ShareQRFactory.swift | 18 + .../ShareQR}/ShareQrViewController.swift | 13 +- .../ShareQR}/ShareQrViewController.xib | 0 .../SwiftyOnboard/SwiftyOnboard.swift | 0 .../SwiftyOnboard/SwiftyOnboardOverlay.swift | 0 .../SwiftyOnboard/SwiftyOnboardPage.swift | 0 .../AdmTransactionDetailsViewController.swift | 14 +- .../AdmTransactionsViewController.swift | 66 ++-- .../Adamant/AdmTransferViewController.swift | 22 +- .../Wallets/Adamant/AdmWallet.swift | 2 +- .../Wallets/Adamant/AdmWalletFactory.swift | 95 ++++++ .../AdmWalletService+DynamicConstants.swift | 0 ...AdmWalletService+RichMessageProvider.swift | 18 - .../Adamant/AdmWalletService+Send.swift | 9 - .../Wallets/Adamant/AdmWalletService.swift | 19 +- .../Adamant/AdmWalletViewController.swift | 15 +- .../Adamant/BuyAndSellViewController.swift | 2 +- .../Wallets/BalanceTableViewCell.swift | 0 .../Wallets/BalanceTableViewCell.xib | 0 .../BtcTransactionDetailsViewController.swift | 2 +- .../BtcTransactionsViewController.swift | 10 +- .../Bitcoin/BtcTransferViewController.swift | 34 +- .../Wallets/Bitcoin/BtcWallet.swift | 0 .../Wallets/Bitcoin/BtcWalletFactory.swift | 135 ++++++++ .../BtcWalletService+DynamicConstants.swift | 0 ...BtcWalletService+RichMessageProvider.swift | 64 ++++ ...e+RichMessageProviderWithStatusCheck.swift | 0 .../Bitcoin/BtcWalletService+Send.swift | 9 - .../Wallets/Bitcoin/BtcWalletService.swift | 24 +- .../Bitcoin/BtcWalletViewController.swift | 2 +- .../Bitcoin/Models/BtcBalanceResponse.swift | 0 .../Models/BtcTransactionResponse.swift | 0 .../BtcUnspentTransactionResponse.swift | 0 .../DI/AdamantWalletFactoryCompose.swift | 161 +++++++++ .../Modules/Wallets/DI/WalletFactory.swift | 20 ++ .../Wallets/DI/WalletFactoryCompose.swift | 22 ++ .../Wallets/Dash/DashMainnet.swift | 2 +- ...DashTransactionDetailsViewController.swift | 2 +- .../Dash/DashTransactionsViewController.swift | 12 +- .../Dash/DashTransferViewController.swift | 7 +- .../Wallets/Dash/DashWallet.swift | 2 +- .../Wallets/Dash/DashWalletFactory.swift | 144 ++++++++ .../DashWalletService+DynamicConstants.swift | 0 ...ashWalletService+RichMessageProvider.swift | 64 ++++ ...e+RichMessageProviderWithStatusCheck.swift | 0 .../Wallets/Dash/DashWalletService+Send.swift | 9 - .../Dash/DashWalletService+Transactions.swift | 0 .../Wallets/Dash/DashWalletService.swift | 23 -- .../Dash/DashWalletViewController.swift | 2 +- .../Wallets/Doge/DogeMainnet.swift | 2 +- ...DogeTransactionDetailsViewController.swift | 2 +- .../Doge/DogeTransactionsViewController.swift | 12 +- .../Doge/DogeTransferViewController.swift | 7 +- .../Wallets/Doge/DogeWallet.swift | 0 .../Wallets/Doge/DogeWalletFactory.swift | 134 ++++++++ .../DogeWalletService+DynamicConstants.swift | 0 ...ogeWalletService+RichMessageProvider.swift | 64 ++++ ...e+RichMessageProviderWithStatusCheck.swift | 0 .../Wallets/Doge/DogeWalletService+Send.swift | 9 - .../Wallets/Doge/DogeWalletService.swift | 26 +- .../Doge/DogeWalletViewController.swift | 2 +- ...RC20TransactionDetailsViewController.swift | 2 +- .../ERC20TransactionsViewController.swift | 11 +- .../ERC20/ERC20TransferViewController.swift | 27 +- .../Wallets/ERC20/ERC20Wallet.swift | 2 +- .../Wallets/ERC20/ERC20WalletFactory.swift | 136 ++++++++ ...C20WalletService+RichMessageProvider.swift | 60 ++++ ...e+RichMessageProviderWithStatusCheck.swift | 0 .../ERC20/ERC20WalletService+Send.swift | 9 - .../Wallets/ERC20/ERC20WalletService.swift | 26 +- .../ERC20/ERC20WalletViewController.swift | 2 +- .../EthTransactionDetailsViewController.swift | 2 +- .../EthTransactionsViewController.swift | 11 +- .../Ethereum/EthTransferViewController.swift | 27 +- .../Wallets/Ethereum/EthWallet.swift | 2 +- .../Wallets/Ethereum/EthWalletFactory.swift | 134 ++++++++ .../EthWalletService+DynamicConstants.swift | 0 ...EthWalletService+RichMessageProvider.swift | 64 ++++ ...e+RichMessageProviderWithStatusCheck.swift | 0 .../Ethereum/EthWalletService+Send.swift | 9 - .../Wallets/Ethereum/EthWalletService.swift | 16 +- .../Ethereum/EthWalletViewController.swift | 2 +- .../LskTransactionDetailsViewController.swift | 2 +- .../Lisk/LskTransactionsViewController.swift | 10 +- .../Lisk/LskTransferViewController.swift | 38 +-- .../Wallets/Lisk/LskWallet.swift | 2 +- .../Wallets/Lisk/LskWalletFactory.swift | 134 ++++++++ .../LskWalletService+DynamicConstants.swift | 0 ...LskWalletService+RichMessageProvider.swift | 65 ++++ ...e+RichMessageProviderWithStatusCheck.swift | 0 .../Wallets/Lisk/LskWalletService+Send.swift | 9 - .../Wallets/Lisk/LskWalletService.swift | 14 +- .../Lisk/LskWalletViewController.swift | 2 +- .../Wallets/TransactionDetails.swift | 0 ...TransactionDetailsViewControllerBase.swift | 0 .../Wallets/TransactionTableViewCell.swift | 0 .../Wallets/TransactionTableViewCell.xib | 0 .../TransactionsListViewControllerBase.swift | 0 .../TransactionsListViewControllerBase.xib | 0 .../TransferViewControllerBase+Alert.swift | 0 .../TransferViewControllerBase+QR.swift | 0 .../Wallets/TransferViewControllerBase.swift | 6 +- .../{ => Modules}/Wallets/WalletAccount.swift | 0 .../{ => Modules}/Wallets/WalletService.swift | 8 - .../Wallets/WalletViewControllerBase.swift | 95 +++--- .../Wallets/WalletViewControllerBase.xib | 0 .../Welcome}/WelcomeViewController.swift | 0 .../Welcome}/WelcomeViewController.xib | 0 .../RichMessageProvider.swift | 5 +- Adamant/ServiceProtocols/Router.swift | 26 -- Adamant/Services/AdamantAuthentication.swift | 2 +- Adamant/Services/AdamantCellFactory.swift | 2 +- .../Services/AdamantCurrencyInfoService.swift | 2 +- Adamant/Services/AdamantDialogService.swift | 23 +- .../DataProviders/InMemoryCoreDataStack.swift | 2 +- Adamant/Services/SwinjectedRouter.swift | 18 - .../Shared => SharedViews}/ButtonsStripe.xib | 0 .../ButtonsStripeView.swift | 2 +- Adamant/SharedViews/FullscreenAlertView.swift | 2 +- Adamant/SharedViews/SwipeableView.swift | 2 +- Adamant/Stories/Account/AccountRoutes.swift | 29 -- Adamant/Stories/ChatsList/ChatsRoutes.swift | 69 ---- .../Stories/Delegates/DelegateRoutes.swift | 33 -- Adamant/Stories/Login/LoginRoutes.swift | 26 -- .../NodesEditor/NodesEditorRoutes.swift | 34 -- Adamant/Stories/Onboard/OnboardRoutes.swift | 23 -- .../Contribute/ContributeFactory.swift | 23 -- Adamant/Stories/Settings/SettingsRoutes.swift | 66 ---- Adamant/Stories/Shared/SharedRoutes.swift | 21 -- Adamant/Utilities/AdamantQRTools.swift | 2 +- Adamant/Utilities/AdamantUriTools.swift | 2 +- Adamant/Wallets/Adamant/AdmWalletRoutes.swift | 73 ---- Adamant/Wallets/Bitcoin/BtcWalletRoutes.swift | 54 --- ...BtcWalletService+RichMessageProvider.swift | 142 -------- Adamant/Wallets/Dash/DashWalletRouter.swift | 53 --- ...ashWalletService+RichMessageProvider.swift | 151 --------- Adamant/Wallets/Doge/DogeWalletRoutes.swift | 53 --- ...ogeWalletService+RichMessageProvider.swift | 144 -------- Adamant/Wallets/ERC20/ERC20WalletRouter.swift | 53 --- ...C20WalletService+RichMessageProvider.swift | 138 -------- .../Wallets/Ethereum/EthWalletRoutes.swift | 53 --- ...EthWalletService+RichMessageProvider.swift | 142 -------- .../Ethereum/EthWalletService+Transfers.swift | 20 -- Adamant/Wallets/Lisk/LskWalletRoutes.swift | 53 --- ...LskWalletService+RichMessageProvider.swift | 145 -------- .../Lisk/LskWalletService+Transfers.swift | 20 -- Adamant/Wallets/WalletsRoutes.swift | 16 - 286 files changed, 2759 insertions(+), 2616 deletions(-) rename Adamant/{CoreData => }/Adamant.xcdatamodeld/Adamant.xcdatamodel/contents (100%) rename Adamant/{ => App}/AppDelegate.swift (96%) rename Adamant/{SwinjectDependencies.swift => App/DI/AppAssembly.swift} (71%) create mode 100644 Adamant/App/DI/AppContainer.swift create mode 100644 Adamant/Helpers/Assembler+Extension.swift rename Adamant/{ => Models}/CoreData/AdamantResources+CoreData.swift (100%) rename Adamant/{ => Models}/CoreData/BaseAccount+CoreDataClass.swift (100%) rename Adamant/{ => Models}/CoreData/BaseAccount+CoreDataProperties.swift (100%) rename Adamant/{ => Models}/CoreData/BaseTransaction+CoreDataClass.swift (100%) rename Adamant/{ => Models}/CoreData/BaseTransaction+CoreDataProperties.swift (100%) rename Adamant/{ => Models}/CoreData/BaseTransaction+TransactionDetails.swift (100%) rename Adamant/{ => Models}/CoreData/ChatTransaction+CoreDataClass.swift (100%) rename Adamant/{ => Models}/CoreData/ChatTransaction+CoreDataProperties.swift (100%) rename Adamant/{ => Models}/CoreData/Chatroom+CoreDataClass.swift (100%) rename Adamant/{ => Models}/CoreData/Chatroom+CoreDataProperties.swift (100%) rename Adamant/{ => Models}/CoreData/CoreDataAccount+CoreDataClass.swift (100%) rename Adamant/{ => Models}/CoreData/CoreDataAccount+CoreDataProperties.swift (100%) rename Adamant/{ => Models}/CoreData/DummyAccount+CoreDataClass.swift (100%) rename Adamant/{ => Models}/CoreData/DummyAccount+CoreDataProperties.swift (100%) rename Adamant/{ => Models}/CoreData/MessageTransaction+CoreDataClass.swift (100%) rename Adamant/{ => Models}/CoreData/MessageTransaction+CoreDataProperties.swift (100%) rename Adamant/{ => Models}/CoreData/RichMessageTransaction+CoreDataClass.swift (100%) rename Adamant/{ => Models}/CoreData/RichMessageTransaction+CoreDataProperties.swift (100%) rename Adamant/{ => Models}/CoreData/TransferTransaction+CoreDataClass.swift (100%) rename Adamant/{ => Models}/CoreData/TransferTransaction+CoreDataProperties.swift (100%) rename Adamant/{ => Models}/ServerResponses/BTCRPCServerResponce.swift (74%) rename Adamant/{ => Models}/ServerResponses/DogeGetTransactionsResponse.swift (87%) rename Adamant/{ => Models}/ServerResponses/GetPublicKeyResponse.swift (100%) rename Adamant/{ => Models}/ServerResponses/ServerResponseWithTimestamp.swift (100%) rename Adamant/{ => Models}/ServerResponses/TransactionIdResponse.swift (100%) create mode 100644 Adamant/Modules/Account/AccountFactory.swift rename Adamant/{Stories => Modules}/Account/AccountFooter.xib (100%) rename Adamant/{Stories => Modules}/Account/AccountHeader.xib (100%) rename Adamant/{Stories => Modules}/Account/AccountHeaderView.swift (93%) rename Adamant/{Stories => Modules}/Account/AccountViewController+StayIn.swift (100%) rename Adamant/{Stories => Modules}/Account/AccountViewController.swift (91%) rename Adamant/{Stories => Modules}/Account/WalletCollectionViewCell.swift (100%) rename Adamant/{Stories => Modules}/Account/WalletCollectionViewCell.xib (100%) rename Adamant/{Stories => Modules}/Account/WalletPagingItem.swift (100%) rename Adamant/{Stories => Modules}/Chat/ChatFactory.swift (79%) rename Adamant/{Stories => Modules}/Chat/ChatLocalization.swift (100%) rename Adamant/{Stories => Modules}/Chat/View/ChatViewController.swift (95%) rename Adamant/{Stories => Modules}/Chat/View/Helpers/AdamantCellAnimation.swift (100%) rename Adamant/{Stories => Modules}/Chat/View/Helpers/ChatReactionsView.swift (100%) rename Adamant/{Stories => Modules}/Chat/View/Managers/ChatAction.swift (100%) rename Adamant/{Stories => Modules}/Chat/View/Managers/ChatCellManager.swift (100%) rename Adamant/{Stories => Modules}/Chat/View/Managers/ChatDataSourceManager.swift (100%) rename Adamant/{Stories => Modules}/Chat/View/Managers/ChatDialogManager.swift (100%) rename Adamant/{Stories => Modules}/Chat/View/Managers/ChatDisplayManager.swift (100%) rename Adamant/{Stories => Modules}/Chat/View/Managers/ChatInputBarManager.swift (100%) rename Adamant/{Stories => Modules}/Chat/View/Managers/ChatKeyboardManager.swift (100%) rename Adamant/{Stories => Modules}/Chat/View/Managers/ChatLayoutManager.swift (100%) rename Adamant/{Stories => Modules}/Chat/View/Managers/ChatMenuManager.swift (100%) rename Adamant/{Stories => Modules}/Chat/View/Managers/FixedTextMessageSizeCalculator.swift (100%) rename Adamant/{Stories => Modules}/Chat/View/Subviews/ChatBaseMessage/ChatMessageCell+Model.swift (100%) rename Adamant/{Stories => Modules}/Chat/View/Subviews/ChatBaseMessage/ChatMessageCell.swift (100%) rename Adamant/{Stories => Modules}/Chat/View/Subviews/ChatInputBar.swift (100%) rename Adamant/{Stories => Modules}/Chat/View/Subviews/ChatMessagesCollection.swift (100%) rename Adamant/{Stories => Modules}/Chat/View/Subviews/ChatModelView.swift (100%) rename Adamant/{Stories => Modules}/Chat/View/Subviews/ChatRefreshMock.swift (100%) rename Adamant/{Stories => Modules}/Chat/View/Subviews/ChatReply/ChatMessageReplyCell+Model.swift (100%) rename Adamant/{Stories => Modules}/Chat/View/Subviews/ChatReply/ChatMessageReplyCell.swift (100%) rename Adamant/{Stories => Modules}/Chat/View/Subviews/ChatScrollDownButton.swift (100%) rename Adamant/{Stories => Modules}/Chat/View/Subviews/ChatTransaction/ChatTransactionCell.swift (100%) rename Adamant/{Stories => Modules}/Chat/View/Subviews/ChatTransaction/Container/ChatTransactionContainerView+Model.swift (100%) rename Adamant/{Stories => Modules}/Chat/View/Subviews/ChatTransaction/Container/ChatTransactionContainerView.swift (100%) rename Adamant/{Stories => Modules}/Chat/View/Subviews/ChatTransaction/Content/ChatTransactionContentView+Model.swift (100%) rename Adamant/{Stories => Modules}/Chat/View/Subviews/ChatTransaction/Content/ChatTransactionContentView.swift (100%) rename Adamant/{Stories => Modules}/Chat/ViewModel/ChatCacheService.swift (86%) rename Adamant/{Stories => Modules}/Chat/ViewModel/ChatMessageFactory.swift (100%) rename Adamant/{Stories => Modules}/Chat/ViewModel/ChatMessagesListFactory.swift (100%) rename Adamant/{Stories => Modules}/Chat/ViewModel/ChatMessagesListViewModel.swift (100%) rename Adamant/{Stories => Modules}/Chat/ViewModel/ChatPreservationDelegate.swift (100%) rename Adamant/{Stories => Modules}/Chat/ViewModel/ChatViewModel.swift (100%) rename Adamant/{Stories => Modules}/Chat/ViewModel/Models/ChatDialog.swift (100%) rename Adamant/{Stories => Modules}/Chat/ViewModel/Models/ChatMessage.swift (100%) rename Adamant/{Stories => Modules}/Chat/ViewModel/Models/ChatMessageBackgroundColor.swift (100%) rename Adamant/{Stories => Modules}/Chat/ViewModel/Models/ChatSender.swift (100%) rename Adamant/{Stories => Modules}/Chat/ViewModel/Models/ChatStartPosition.swift (100%) rename Adamant/{Stories => Modules}/Chat/ViewModel/Models/MessageModel.swift (100%) create mode 100644 Adamant/Modules/ChatsList/ChatListFactory.swift rename Adamant/{Stories => Modules}/ChatsList/ChatListViewController.swift (98%) rename Adamant/{Stories => Modules}/ChatsList/ChatListViewController.xib (100%) rename Adamant/{Stories => Modules}/ChatsList/ChatTableViewCell.swift (97%) rename Adamant/{Stories => Modules}/ChatsList/ChatTableViewCell.xib (100%) rename Adamant/{Stories => Modules}/ChatsList/ComplexTransferViewController.swift (97%) rename Adamant/{Stories => Modules}/ChatsList/NewChatViewController.swift (96%) rename Adamant/{Stories => Modules}/ChatsList/SearchResultsViewController.swift (98%) rename Adamant/{Stories => Modules}/ChatsList/SearchResultsViewController.xib (100%) rename Adamant/{Stories => Modules}/Delegates/AdamantDelegateCell.swift (98%) rename Adamant/{Stories => Modules}/Delegates/DelegateDetailsViewController.swift (99%) rename Adamant/{Stories => Modules}/Delegates/DelegateDetailsViewController.xib (100%) rename Adamant/{Stories => Modules}/Delegates/DelegatesBottomPanel/DelegatesBottomPanel+Model.swift (100%) rename Adamant/{Stories => Modules}/Delegates/DelegatesBottomPanel/DelegatesBottomPanel.swift (100%) create mode 100644 Adamant/Modules/Delegates/DelegatesFactory.swift rename Adamant/{Stories => Modules}/Delegates/DelegatesListViewController.swift (98%) rename Adamant/{Stories => Modules}/Login/EurekaPassphraseRow.swift (100%) create mode 100644 Adamant/Modules/Login/LoginFactory.swift rename Adamant/{Stories => Modules}/Login/LoginViewController+Pinpad.swift (100%) rename Adamant/{Stories => Modules}/Login/LoginViewController+QR.swift (100%) rename Adamant/{Stories => Modules}/Login/LoginViewController.swift (98%) rename Adamant/{Stories => Modules}/Login/PassphraseCell.xib (100%) rename Adamant/{Stories => Modules}/NodesEditor/EurekaNodeRow.swift (98%) rename Adamant/{Stories => Modules}/NodesEditor/NodeEditorViewController.swift (99%) create mode 100644 Adamant/Modules/NodesEditor/NodesEditorFactory.swift rename Adamant/{Stories => Modules}/NodesEditor/NodesListViewController.swift (98%) rename Adamant/{Stories => Modules}/Onboard/EulaViewController.swift (96%) rename Adamant/{Stories => Modules}/Onboard/EulaViewController.xib (100%) create mode 100644 Adamant/Modules/Onboard/OnboardFactory.swift rename Adamant/{Stories => Modules}/Onboard/OnboardOverlay.swift (97%) rename Adamant/{Stories => Modules}/Onboard/OnboardPage.swift (98%) rename Adamant/{Stories => Modules}/Onboard/OnboardPage.xib (100%) rename Adamant/{Stories => Modules}/Onboard/OnboardViewController.swift (99%) rename Adamant/{Stories => Modules}/Onboard/OnboardViewController.xib (100%) rename Adamant/{Stories => Modules}/Onboard/ReadonlyTextView.swift (86%) create mode 100644 Adamant/Modules/ScreensFactory/AdamantScreensFactory.swift create mode 100644 Adamant/Modules/ScreensFactory/ScreensFactory.swift rename Adamant/{Stories => Modules}/Settings/AboutViewController.swift (94%) create mode 100644 Adamant/Modules/Settings/Contribute/ContributeFactory.swift rename Adamant/{Stories => Modules}/Settings/Contribute/ContributeState.swift (100%) rename Adamant/{Stories => Modules}/Settings/Contribute/ContributeView.swift (100%) rename Adamant/{Stories => Modules}/Settings/Contribute/ContributeViewModel.swift (87%) rename Adamant/{Stories => Modules}/Settings/NotificationSoundsViewController.swift (98%) rename Adamant/{Stories => Modules}/Settings/NotificationsViewController.swift (99%) rename Adamant/{Stories => Modules}/Settings/PKGeneratorViewController.swift (99%) rename Adamant/{Stories => Modules}/Settings/PrivateKeyGenerator.swift (100%) rename Adamant/{Stories => Modules}/Settings/QRGeneratorViewController.swift (99%) rename Adamant/{Stories => Modules}/Settings/SecurityViewController+StayIn.swift (100%) rename Adamant/{Stories => Modules}/Settings/SecurityViewController+notifications.swift (100%) rename Adamant/{Stories => Modules}/Settings/SecurityViewController.swift (97%) create mode 100644 Adamant/Modules/Settings/SettingsFactory.swift rename Adamant/{Stories => Modules}/Settings/VisibleWallets/VisibleWalletsCheckmarkView.swift (100%) rename Adamant/{Stories => Modules}/Settings/VisibleWallets/VisibleWalletsResetTableViewCell.swift (93%) rename Adamant/{Stories => Modules}/Settings/VisibleWallets/VisibleWalletsTableViewCell.swift (97%) rename Adamant/{Stories => Modules}/Settings/VisibleWallets/VisibleWalletsViewController.swift (100%) create mode 100644 Adamant/Modules/ShareQR/ShareQRFactory.swift rename Adamant/{Stories/Shared => Modules/ShareQR}/ShareQrViewController.swift (94%) rename Adamant/{Stories/Shared => Modules/ShareQR}/ShareQrViewController.xib (100%) rename Adamant/{Stories => Modules}/SwiftyOnboard/SwiftyOnboard.swift (100%) rename Adamant/{Stories => Modules}/SwiftyOnboard/SwiftyOnboardOverlay.swift (100%) rename Adamant/{Stories => Modules}/SwiftyOnboard/SwiftyOnboardPage.swift (100%) rename Adamant/{ => Modules}/Wallets/Adamant/AdmTransactionDetailsViewController.swift (92%) rename Adamant/{ => Modules}/Wallets/Adamant/AdmTransactionsViewController.swift (90%) rename Adamant/{ => Modules}/Wallets/Adamant/AdmTransferViewController.swift (94%) rename Adamant/{ => Modules}/Wallets/Adamant/AdmWallet.swift (91%) create mode 100644 Adamant/Modules/Wallets/Adamant/AdmWalletFactory.swift rename Adamant/{ => Modules}/Wallets/Adamant/AdmWalletService+DynamicConstants.swift (100%) rename Adamant/{ => Modules}/Wallets/Adamant/AdmWalletService+RichMessageProvider.swift (73%) rename Adamant/{ => Modules}/Wallets/Adamant/AdmWalletService+Send.swift (86%) rename Adamant/{ => Modules}/Wallets/Adamant/AdmWalletService.swift (90%) rename Adamant/{ => Modules}/Wallets/Adamant/AdmWalletViewController.swift (95%) rename Adamant/{ => Modules}/Wallets/Adamant/BuyAndSellViewController.swift (99%) rename Adamant/{ => Modules}/Wallets/BalanceTableViewCell.swift (100%) rename Adamant/{ => Modules}/Wallets/BalanceTableViewCell.xib (100%) rename Adamant/{ => Modules}/Wallets/Bitcoin/BtcTransactionDetailsViewController.swift (96%) rename Adamant/{ => Modules}/Wallets/Bitcoin/BtcTransactionsViewController.swift (92%) rename Adamant/{ => Modules}/Wallets/Bitcoin/BtcTransferViewController.swift (84%) rename Adamant/{ => Modules}/Wallets/Bitcoin/BtcWallet.swift (100%) create mode 100644 Adamant/Modules/Wallets/Bitcoin/BtcWalletFactory.swift rename Adamant/{ => Modules}/Wallets/Bitcoin/BtcWalletService+DynamicConstants.swift (100%) create mode 100644 Adamant/Modules/Wallets/Bitcoin/BtcWalletService+RichMessageProvider.swift rename Adamant/{ => Modules}/Wallets/Bitcoin/BtcWalletService+RichMessageProviderWithStatusCheck.swift (100%) rename Adamant/{ => Modules}/Wallets/Bitcoin/BtcWalletService+Send.swift (95%) rename Adamant/{ => Modules}/Wallets/Bitcoin/BtcWalletService.swift (96%) rename Adamant/{ => Modules}/Wallets/Bitcoin/BtcWalletViewController.swift (93%) rename Adamant/{ => Modules}/Wallets/Bitcoin/Models/BtcBalanceResponse.swift (100%) rename Adamant/{ => Modules}/Wallets/Bitcoin/Models/BtcTransactionResponse.swift (100%) rename Adamant/{ => Modules}/Wallets/Bitcoin/Models/BtcUnspentTransactionResponse.swift (100%) create mode 100644 Adamant/Modules/Wallets/DI/AdamantWalletFactoryCompose.swift create mode 100644 Adamant/Modules/Wallets/DI/WalletFactory.swift create mode 100644 Adamant/Modules/Wallets/DI/WalletFactoryCompose.swift rename Adamant/{ => Modules}/Wallets/Dash/DashMainnet.swift (96%) rename Adamant/{ => Modules}/Wallets/Dash/DashTransactionDetailsViewController.swift (97%) rename Adamant/{ => Modules}/Wallets/Dash/DashTransactionsViewController.swift (93%) rename Adamant/{ => Modules}/Wallets/Dash/DashTransferViewController.swift (94%) rename Adamant/{ => Modules}/Wallets/Dash/DashWallet.swift (95%) create mode 100644 Adamant/Modules/Wallets/Dash/DashWalletFactory.swift rename Adamant/{ => Modules}/Wallets/Dash/DashWalletService+DynamicConstants.swift (100%) create mode 100644 Adamant/Modules/Wallets/Dash/DashWalletService+RichMessageProvider.swift rename Adamant/{ => Modules}/Wallets/Dash/DashWalletService+RichMessageProviderWithStatusCheck.swift (100%) rename Adamant/{ => Modules}/Wallets/Dash/DashWalletService+Send.swift (93%) rename Adamant/{ => Modules}/Wallets/Dash/DashWalletService+Transactions.swift (100%) rename Adamant/{ => Modules}/Wallets/Dash/DashWalletService.swift (95%) rename Adamant/{ => Modules}/Wallets/Dash/DashWalletViewController.swift (93%) rename Adamant/{ => Modules}/Wallets/Doge/DogeMainnet.swift (96%) rename Adamant/{ => Modules}/Wallets/Doge/DogeTransactionDetailsViewController.swift (97%) rename Adamant/{ => Modules}/Wallets/Doge/DogeTransactionsViewController.swift (92%) rename Adamant/{ => Modules}/Wallets/Doge/DogeTransferViewController.swift (93%) rename Adamant/{ => Modules}/Wallets/Doge/DogeWallet.swift (100%) create mode 100644 Adamant/Modules/Wallets/Doge/DogeWalletFactory.swift rename Adamant/{ => Modules}/Wallets/Doge/DogeWalletService+DynamicConstants.swift (100%) create mode 100644 Adamant/Modules/Wallets/Doge/DogeWalletService+RichMessageProvider.swift rename Adamant/{ => Modules}/Wallets/Doge/DogeWalletService+RichMessageProviderWithStatusCheck.swift (100%) rename Adamant/{ => Modules}/Wallets/Doge/DogeWalletService+Send.swift (94%) rename Adamant/{ => Modules}/Wallets/Doge/DogeWalletService.swift (96%) rename Adamant/{ => Modules}/Wallets/Doge/DogeWalletViewController.swift (92%) rename Adamant/{ => Modules}/Wallets/ERC20/ERC20TransactionDetailsViewController.swift (96%) rename Adamant/{ => Modules}/Wallets/ERC20/ERC20TransactionsViewController.swift (93%) rename Adamant/{ => Modules}/Wallets/ERC20/ERC20TransferViewController.swift (91%) rename Adamant/{ => Modules}/Wallets/ERC20/ERC20Wallet.swift (94%) create mode 100644 Adamant/Modules/Wallets/ERC20/ERC20WalletFactory.swift create mode 100644 Adamant/Modules/Wallets/ERC20/ERC20WalletService+RichMessageProvider.swift rename Adamant/{ => Modules}/Wallets/ERC20/ERC20WalletService+RichMessageProviderWithStatusCheck.swift (100%) rename Adamant/{ => Modules}/Wallets/ERC20/ERC20WalletService+Send.swift (89%) rename Adamant/{ => Modules}/Wallets/ERC20/ERC20WalletService.swift (96%) rename Adamant/{ => Modules}/Wallets/ERC20/ERC20WalletViewController.swift (96%) rename Adamant/{ => Modules}/Wallets/Ethereum/EthTransactionDetailsViewController.swift (96%) rename Adamant/{ => Modules}/Wallets/Ethereum/EthTransactionsViewController.swift (92%) rename Adamant/{ => Modules}/Wallets/Ethereum/EthTransferViewController.swift (90%) rename Adamant/{ => Modules}/Wallets/Ethereum/EthWallet.swift (94%) create mode 100644 Adamant/Modules/Wallets/Ethereum/EthWalletFactory.swift rename Adamant/{ => Modules}/Wallets/Ethereum/EthWalletService+DynamicConstants.swift (100%) create mode 100644 Adamant/Modules/Wallets/Ethereum/EthWalletService+RichMessageProvider.swift rename Adamant/{ => Modules}/Wallets/Ethereum/EthWalletService+RichMessageProviderWithStatusCheck.swift (100%) rename Adamant/{ => Modules}/Wallets/Ethereum/EthWalletService+Send.swift (92%) rename Adamant/{ => Modules}/Wallets/Ethereum/EthWalletService.swift (98%) rename Adamant/{ => Modules}/Wallets/Ethereum/EthWalletViewController.swift (93%) rename Adamant/{ => Modules}/Wallets/Lisk/LskTransactionDetailsViewController.swift (96%) rename Adamant/{ => Modules}/Wallets/Lisk/LskTransactionsViewController.swift (96%) rename Adamant/{ => Modules}/Wallets/Lisk/LskTransferViewController.swift (83%) rename Adamant/{ => Modules}/Wallets/Lisk/LskWallet.swift (96%) create mode 100644 Adamant/Modules/Wallets/Lisk/LskWalletFactory.swift rename Adamant/{ => Modules}/Wallets/Lisk/LskWalletService+DynamicConstants.swift (100%) create mode 100644 Adamant/Modules/Wallets/Lisk/LskWalletService+RichMessageProvider.swift rename Adamant/{ => Modules}/Wallets/Lisk/LskWalletService+RichMessageProviderWithStatusCheck.swift (100%) rename Adamant/{ => Modules}/Wallets/Lisk/LskWalletService+Send.swift (86%) rename Adamant/{ => Modules}/Wallets/Lisk/LskWalletService.swift (98%) rename Adamant/{ => Modules}/Wallets/Lisk/LskWalletViewController.swift (93%) rename Adamant/{ => Modules}/Wallets/TransactionDetails.swift (100%) rename Adamant/{ => Modules}/Wallets/TransactionDetailsViewControllerBase.swift (100%) rename Adamant/{ => Modules}/Wallets/TransactionTableViewCell.swift (100%) rename Adamant/{ => Modules}/Wallets/TransactionTableViewCell.xib (100%) rename Adamant/{ => Modules}/Wallets/TransactionsListViewControllerBase.swift (100%) rename Adamant/{ => Modules}/Wallets/TransactionsListViewControllerBase.xib (100%) rename Adamant/{ => Modules}/Wallets/TransferViewControllerBase+Alert.swift (100%) rename Adamant/{ => Modules}/Wallets/TransferViewControllerBase+QR.swift (100%) rename Adamant/{ => Modules}/Wallets/TransferViewControllerBase.swift (99%) rename Adamant/{ => Modules}/Wallets/WalletAccount.swift (100%) rename Adamant/{ => Modules}/Wallets/WalletService.swift (97%) rename Adamant/{ => Modules}/Wallets/WalletViewControllerBase.swift (85%) rename Adamant/{ => Modules}/Wallets/WalletViewControllerBase.xib (100%) rename Adamant/{Stories/Shared => Modules/Welcome}/WelcomeViewController.swift (100%) rename Adamant/{Stories/Shared => Modules/Welcome}/WelcomeViewController.xib (100%) delete mode 100644 Adamant/ServiceProtocols/Router.swift delete mode 100644 Adamant/Services/SwinjectedRouter.swift rename Adamant/{Stories/Shared => SharedViews}/ButtonsStripe.xib (100%) rename Adamant/{Stories/Shared => SharedViews}/ButtonsStripeView.swift (99%) delete mode 100644 Adamant/Stories/Account/AccountRoutes.swift delete mode 100644 Adamant/Stories/ChatsList/ChatsRoutes.swift delete mode 100644 Adamant/Stories/Delegates/DelegateRoutes.swift delete mode 100644 Adamant/Stories/Login/LoginRoutes.swift delete mode 100644 Adamant/Stories/NodesEditor/NodesEditorRoutes.swift delete mode 100644 Adamant/Stories/Onboard/OnboardRoutes.swift delete mode 100644 Adamant/Stories/Settings/Contribute/ContributeFactory.swift delete mode 100644 Adamant/Stories/Settings/SettingsRoutes.swift delete mode 100644 Adamant/Stories/Shared/SharedRoutes.swift delete mode 100644 Adamant/Wallets/Adamant/AdmWalletRoutes.swift delete mode 100644 Adamant/Wallets/Bitcoin/BtcWalletRoutes.swift delete mode 100644 Adamant/Wallets/Bitcoin/BtcWalletService+RichMessageProvider.swift delete mode 100644 Adamant/Wallets/Dash/DashWalletRouter.swift delete mode 100644 Adamant/Wallets/Dash/DashWalletService+RichMessageProvider.swift delete mode 100644 Adamant/Wallets/Doge/DogeWalletRoutes.swift delete mode 100644 Adamant/Wallets/Doge/DogeWalletService+RichMessageProvider.swift delete mode 100644 Adamant/Wallets/ERC20/ERC20WalletRouter.swift delete mode 100644 Adamant/Wallets/ERC20/ERC20WalletService+RichMessageProvider.swift delete mode 100644 Adamant/Wallets/Ethereum/EthWalletRoutes.swift delete mode 100644 Adamant/Wallets/Ethereum/EthWalletService+RichMessageProvider.swift delete mode 100644 Adamant/Wallets/Ethereum/EthWalletService+Transfers.swift delete mode 100644 Adamant/Wallets/Lisk/LskWalletRoutes.swift delete mode 100644 Adamant/Wallets/Lisk/LskWalletService+RichMessageProvider.swift delete mode 100644 Adamant/Wallets/Lisk/LskWalletService+Transfers.swift delete mode 100644 Adamant/Wallets/WalletsRoutes.swift diff --git a/Adamant.xcodeproj/project.pbxproj b/Adamant.xcodeproj/project.pbxproj index b93d0223e..e010c9b5e 100644 --- a/Adamant.xcodeproj/project.pbxproj +++ b/Adamant.xcodeproj/project.pbxproj @@ -91,17 +91,16 @@ 55FBAAFB28C550920066E629 /* NodesAllowanceTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 55FBAAFA28C550920066E629 /* NodesAllowanceTests.swift */; }; 6403F5DB2272389800D58779 /* (null) in Sources */ = {isa = PBXBuildFile; }; 6403F5DE22723C6800D58779 /* DashMainnet.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6403F5DD22723C6800D58779 /* DashMainnet.swift */; }; - 6403F5E022723F6400D58779 /* DashWalletRouter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6403F5DF22723F6400D58779 /* DashWalletRouter.swift */; }; + 6403F5E022723F6400D58779 /* DashWalletFactory.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6403F5DF22723F6400D58779 /* DashWalletFactory.swift */; }; 6403F5E222723F7500D58779 /* DashWallet.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6403F5E122723F7500D58779 /* DashWallet.swift */; }; 6403F5E422723F8C00D58779 /* DashWalletService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6403F5E322723F8C00D58779 /* DashWalletService.swift */; }; 6403F5E622723FDA00D58779 /* DashWalletViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6403F5E522723FDA00D58779 /* DashWalletViewController.swift */; }; 6406D74A21C7F06000196713 /* SearchResultsViewController.xib in Resources */ = {isa = PBXBuildFile; fileRef = 6406D74821C7F06000196713 /* SearchResultsViewController.xib */; }; 6414C18E217DF43100373FA6 /* String+adamant.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6414C18D217DF43100373FA6 /* String+adamant.swift */; }; - 6416B19D21AD7B92006089AC /* LskWalletRoutes.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6416B19C21AD7B92006089AC /* LskWalletRoutes.swift */; }; + 6416B19D21AD7B92006089AC /* LskWalletFactory.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6416B19C21AD7B92006089AC /* LskWalletFactory.swift */; }; 6416B19F21AD7CBE006089AC /* LskWalletViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6416B19E21AD7CBE006089AC /* LskWalletViewController.swift */; }; 6416B1A121AD7D93006089AC /* LskTransferViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6416B1A021AD7D93006089AC /* LskTransferViewController.swift */; }; 6416B1A321AD7EA1006089AC /* LskTransactionDetailsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6416B1A221AD7EA1006089AC /* LskTransactionDetailsViewController.swift */; }; - 6416B1A521AEE157006089AC /* LskWalletService+Transfers.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6416B1A421AEE157006089AC /* LskWalletService+Transfers.swift */; }; 6416B1A721B024B6006089AC /* LskWalletService+Send.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6416B1A621B024B6006089AC /* LskWalletService+Send.swift */; }; 644793C32166314A00FC4CF5 /* OnboardPage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 644793C22166314A00FC4CF5 /* OnboardPage.swift */; }; 644793C52166315900FC4CF5 /* OnboardPage.xib in Resources */ = {isa = PBXBuildFile; fileRef = 644793C42166315900FC4CF5 /* OnboardPage.xib */; }; @@ -113,11 +112,11 @@ 6449BA6C235CA0930033B936 /* ERC20WalletViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6449BA62235CA0930033B936 /* ERC20WalletViewController.swift */; }; 6449BA6D235CA0930033B936 /* ERC20TransactionsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6449BA63235CA0930033B936 /* ERC20TransactionsViewController.swift */; }; 6449BA6F235CA0930033B936 /* ERC20WalletService+Send.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6449BA65235CA0930033B936 /* ERC20WalletService+Send.swift */; }; - 6449BA70235CA0930033B936 /* ERC20WalletRouter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6449BA66235CA0930033B936 /* ERC20WalletRouter.swift */; }; + 6449BA70235CA0930033B936 /* ERC20WalletFactory.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6449BA66235CA0930033B936 /* ERC20WalletFactory.swift */; }; 6449BA71235CA0930033B936 /* ERC20WalletService+RichMessageProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6449BA67235CA0930033B936 /* ERC20WalletService+RichMessageProvider.swift */; }; 644EC34D20EFA60900F40C73 /* AdamantApi+Delegates.swift in Sources */ = {isa = PBXBuildFile; fileRef = 644EC34C20EFA60900F40C73 /* AdamantApi+Delegates.swift */; }; 644EC34F20EFA77A00F40C73 /* Delegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 644EC34E20EFA77A00F40C73 /* Delegate.swift */; }; - 644EC35220EFA9A300F40C73 /* DelegateRoutes.swift in Sources */ = {isa = PBXBuildFile; fileRef = 644EC35120EFA9A300F40C73 /* DelegateRoutes.swift */; }; + 644EC35220EFA9A300F40C73 /* DelegatesFactory.swift in Sources */ = {isa = PBXBuildFile; fileRef = 644EC35120EFA9A300F40C73 /* DelegatesFactory.swift */; }; 644EC35720EFAAB700F40C73 /* DelegatesListViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 644EC35520EFAAB700F40C73 /* DelegatesListViewController.swift */; }; 644EC35B20EFB8E900F40C73 /* AdamantDelegateCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 644EC35920EFB8E900F40C73 /* AdamantDelegateCell.swift */; }; 644EC35E20F34F1E00F40C73 /* DelegateDetailsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 644EC35D20F34F1E00F40C73 /* DelegateDetailsViewController.swift */; }; @@ -129,9 +128,6 @@ 645AE06621E67D3300AD3623 /* UITextField+adamant.swift in Sources */ = {isa = PBXBuildFile; fileRef = 645AE06521E67D3300AD3623 /* UITextField+adamant.swift */; }; 645FEB34213E72C100D6BA2D /* OnboardViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 645FEB32213E72C100D6BA2D /* OnboardViewController.swift */; }; 645FEB35213E72C100D6BA2D /* OnboardViewController.xib in Resources */ = {isa = PBXBuildFile; fileRef = 645FEB33213E72C100D6BA2D /* OnboardViewController.xib */; }; - 6489794D24CE00C000C33A68 /* SwiftyOnboardPage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6489794A24CE00C000C33A68 /* SwiftyOnboardPage.swift */; }; - 6489794E24CE00C000C33A68 /* SwiftyOnboard.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6489794B24CE00C000C33A68 /* SwiftyOnboard.swift */; }; - 6489794F24CE00C000C33A68 /* SwiftyOnboardOverlay.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6489794C24CE00C000C33A68 /* SwiftyOnboardOverlay.swift */; }; 648BCA6D213D384F00875EB5 /* AvatarService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 648BCA6C213D384F00875EB5 /* AvatarService.swift */; }; 648C696F22915A12006645F5 /* DashTransaction.swift in Sources */ = {isa = PBXBuildFile; fileRef = 648C696E22915A12006645F5 /* DashTransaction.swift */; }; 648C697122915CB8006645F5 /* BTCRPCServerResponce.swift in Sources */ = {isa = PBXBuildFile; fileRef = 648C697022915CB8006645F5 /* BTCRPCServerResponce.swift */; }; @@ -162,7 +158,7 @@ 64BD2B7720E2820300E2CD36 /* TransactionDetails.swift in Sources */ = {isa = PBXBuildFile; fileRef = 64BD2B7620E2820300E2CD36 /* TransactionDetails.swift */; }; 64C65F4523893C7600DC0425 /* OnboardOverlay.swift in Sources */ = {isa = PBXBuildFile; fileRef = 64C65F4423893C7600DC0425 /* OnboardOverlay.swift */; }; 64D059FF20D3116B003AD655 /* NodesListViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 64D059FE20D3116A003AD655 /* NodesListViewController.swift */; }; - 64E1C82D222E95E2006C4DA7 /* DogeWalletRoutes.swift in Sources */ = {isa = PBXBuildFile; fileRef = 64E1C82C222E95E2006C4DA7 /* DogeWalletRoutes.swift */; }; + 64E1C82D222E95E2006C4DA7 /* DogeWalletFactory.swift in Sources */ = {isa = PBXBuildFile; fileRef = 64E1C82C222E95E2006C4DA7 /* DogeWalletFactory.swift */; }; 64E1C82F222E95F6006C4DA7 /* DogeWallet.swift in Sources */ = {isa = PBXBuildFile; fileRef = 64E1C82E222E95F6006C4DA7 /* DogeWallet.swift */; }; 64E1C831222E9617006C4DA7 /* DogeWalletService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 64E1C830222E9617006C4DA7 /* DogeWalletService.swift */; }; 64E1C833222EA0F0006C4DA7 /* DogeWalletViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 64E1C832222EA0F0006C4DA7 /* DogeWalletViewController.swift */; }; @@ -183,6 +179,17 @@ 9322E87B2970431200B8357C /* ChatMessageFactory.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9322E87A2970431200B8357C /* ChatMessageFactory.swift */; }; 9324C75E297170600022D7EA /* RichTransactionStatusService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9324C75D297170600022D7EA /* RichTransactionStatusService.swift */; }; 9324C760297171040022D7EA /* AdamantRichTransactionStatusService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9324C75F297171040022D7EA /* AdamantRichTransactionStatusService.swift */; }; + 93294B7D2AAD067000911109 /* AppContainer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 93294B7C2AAD067000911109 /* AppContainer.swift */; }; + 93294B822AAD0BB400911109 /* BtcWalletFactory.swift in Sources */ = {isa = PBXBuildFile; fileRef = 93294B812AAD0BB400911109 /* BtcWalletFactory.swift */; }; + 93294B842AAD0C8F00911109 /* Assembler+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 93294B832AAD0C8F00911109 /* Assembler+Extension.swift */; }; + 93294B872AAD0E0A00911109 /* AdmWallet.swift in Sources */ = {isa = PBXBuildFile; fileRef = 93294B852AAD0E0A00911109 /* AdmWallet.swift */; }; + 93294B882AAD0E0A00911109 /* AdmWalletService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 93294B862AAD0E0A00911109 /* AdmWalletService.swift */; }; + 93294B8E2AAD2C6B00911109 /* SwiftyOnboardPage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 93294B8B2AAD2C6B00911109 /* SwiftyOnboardPage.swift */; }; + 93294B8F2AAD2C6B00911109 /* SwiftyOnboard.swift in Sources */ = {isa = PBXBuildFile; fileRef = 93294B8C2AAD2C6B00911109 /* SwiftyOnboard.swift */; }; + 93294B902AAD2C6B00911109 /* SwiftyOnboardOverlay.swift in Sources */ = {isa = PBXBuildFile; fileRef = 93294B8D2AAD2C6B00911109 /* SwiftyOnboardOverlay.swift */; }; + 93294B962AAD320B00911109 /* ScreensFactory.swift in Sources */ = {isa = PBXBuildFile; fileRef = 93294B952AAD320B00911109 /* ScreensFactory.swift */; }; + 93294B982AAD364F00911109 /* AdamantScreensFactory.swift in Sources */ = {isa = PBXBuildFile; fileRef = 93294B972AAD364F00911109 /* AdamantScreensFactory.swift */; }; + 93294B9A2AAD624100911109 /* WalletFactoryCompose.swift in Sources */ = {isa = PBXBuildFile; fileRef = 93294B992AAD624100911109 /* WalletFactoryCompose.swift */; }; 932B34E92974AA4A002A75BA /* ChatPreservationDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 932B34E82974AA4A002A75BA /* ChatPreservationDelegate.swift */; }; 932BD15B29D2F75200AA1947 /* RichMessageProviderWithStatusCheck+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 932BD15A29D2F75200AA1947 /* RichMessageProviderWithStatusCheck+Extension.swift */; }; 932F77592989F999006D8801 /* ChatCellManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 932F77582989F999006D8801 /* ChatCellManager.swift */; }; @@ -231,6 +238,8 @@ 9399F5ED29A85A48006C3E30 /* ChatCacheService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9399F5EC29A85A48006C3E30 /* ChatCacheService.swift */; }; 93A118512993167500E144CC /* ChatMessageBackgroundColor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 93A118502993167500E144CC /* ChatMessageBackgroundColor.swift */; }; 93A118532993241D00E144CC /* ChatMessagesListFactory.swift in Sources */ = {isa = PBXBuildFile; fileRef = 93A118522993241D00E144CC /* ChatMessagesListFactory.swift */; }; + 93A18C862AAEACC100D0AB98 /* AdamantWalletFactoryCompose.swift in Sources */ = {isa = PBXBuildFile; fileRef = 93A18C852AAEACC100D0AB98 /* AdamantWalletFactoryCompose.swift */; }; + 93A18C892AAEAE7700D0AB98 /* WalletFactory.swift in Sources */ = {isa = PBXBuildFile; fileRef = 93A18C882AAEAE7700D0AB98 /* WalletFactory.swift */; }; 93A91FD1297972B7001DB1F8 /* ChatScrollDownButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 93A91FD0297972B7001DB1F8 /* ChatScrollDownButton.swift */; }; 93A91FD329799298001DB1F8 /* ChatStartPosition.swift in Sources */ = {isa = PBXBuildFile; fileRef = 93A91FD229799298001DB1F8 /* ChatStartPosition.swift */; }; 93BF4A6629E4859900505CD0 /* DelegatesBottomPanel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 93BF4A6529E4859900505CD0 /* DelegatesBottomPanel.swift */; }; @@ -257,7 +266,6 @@ 93EE9C3329C2666200D9853F /* RichTransactionStatusSubscription.swift in Sources */ = {isa = PBXBuildFile; fileRef = 93EE9C3229C2666200D9853F /* RichTransactionStatusSubscription.swift */; }; 93F391502962F5D400BFD6AE /* SpinnerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 93F3914F2962F5D400BFD6AE /* SpinnerView.swift */; }; 93FA403629401BFC00D20DB6 /* PopupKit in Frameworks */ = {isa = PBXBuildFile; productRef = 93FA403529401BFC00D20DB6 /* PopupKit */; }; - A50A41072822F8CE006BDFE1 /* BtcWalletRoutes.swift in Sources */ = {isa = PBXBuildFile; fileRef = A50A41032822F8CE006BDFE1 /* BtcWalletRoutes.swift */; }; A50A41082822F8CE006BDFE1 /* BtcWalletService.swift in Sources */ = {isa = PBXBuildFile; fileRef = A50A41042822F8CE006BDFE1 /* BtcWalletService.swift */; }; A50A41092822F8CE006BDFE1 /* BtcWalletViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = A50A41052822F8CE006BDFE1 /* BtcWalletViewController.swift */; }; A50A410A2822F8CE006BDFE1 /* BtcWallet.swift in Sources */ = {isa = PBXBuildFile; fileRef = A50A41062822F8CE006BDFE1 /* BtcWallet.swift */; }; @@ -361,24 +369,22 @@ E926E02E213EAABF005E536B /* TransferViewControllerBase+Alert.swift in Sources */ = {isa = PBXBuildFile; fileRef = E926E02D213EAABF005E536B /* TransferViewControllerBase+Alert.swift */; }; E926E032213EC43B005E536B /* FullscreenAlertView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E926E031213EC43B005E536B /* FullscreenAlertView.swift */; }; E926E034213EC454005E536B /* FullscreenAlertView.xib in Resources */ = {isa = PBXBuildFile; fileRef = E926E033213EC454005E536B /* FullscreenAlertView.xib */; }; - E9332B8921F1FA4400D56E72 /* OnboardRoutes.swift in Sources */ = {isa = PBXBuildFile; fileRef = E9332B8821F1FA4400D56E72 /* OnboardRoutes.swift */; }; + E9332B8921F1FA4400D56E72 /* OnboardFactory.swift in Sources */ = {isa = PBXBuildFile; fileRef = E9332B8821F1FA4400D56E72 /* OnboardFactory.swift */; }; E933475B225539390083839E /* DogeGetTransactionsResponse.swift in Sources */ = {isa = PBXBuildFile; fileRef = E933475A225539390083839E /* DogeGetTransactionsResponse.swift */; }; E9393FAA2055D03300EE6F30 /* AdamantMessage.swift in Sources */ = {isa = PBXBuildFile; fileRef = E9393FA92055D03300EE6F30 /* AdamantMessage.swift */; }; E93B0D742028B21400126346 /* ChatsProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = E93B0D732028B21400126346 /* ChatsProvider.swift */; }; E93B0D762028B28E00126346 /* AdamantChatsProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = E93B0D752028B28E00126346 /* AdamantChatsProvider.swift */; }; E93D7ABE2052CEE1005D19DC /* NotificationsService.swift in Sources */ = {isa = PBXBuildFile; fileRef = E93D7ABD2052CEE1005D19DC /* NotificationsService.swift */; }; E93D7AC02052CF63005D19DC /* AdamantNotificationService.swift in Sources */ = {isa = PBXBuildFile; fileRef = E93D7ABF2052CF63005D19DC /* AdamantNotificationService.swift */; }; - E93EB09F20DA3FA4001F9601 /* NodesEditorRoutes.swift in Sources */ = {isa = PBXBuildFile; fileRef = E93EB09E20DA3FA4001F9601 /* NodesEditorRoutes.swift */; }; + E93EB09F20DA3FA4001F9601 /* NodesEditorFactory.swift in Sources */ = {isa = PBXBuildFile; fileRef = E93EB09E20DA3FA4001F9601 /* NodesEditorFactory.swift */; }; E940086B2114A70600CD2D67 /* LskAccount.swift in Sources */ = {isa = PBXBuildFile; fileRef = E940086A2114A70600CD2D67 /* LskAccount.swift */; }; E940086E2114AA2E00CD2D67 /* WalletService.swift in Sources */ = {isa = PBXBuildFile; fileRef = E940086D2114AA2E00CD2D67 /* WalletService.swift */; }; E94008722114EACF00CD2D67 /* WalletAccount.swift in Sources */ = {isa = PBXBuildFile; fileRef = E94008712114EACF00CD2D67 /* WalletAccount.swift */; }; E940087B2114ED0600CD2D67 /* EthWalletService.swift in Sources */ = {isa = PBXBuildFile; fileRef = E940087A2114ED0600CD2D67 /* EthWalletService.swift */; }; E940087D2114EDEE00CD2D67 /* EthWallet.swift in Sources */ = {isa = PBXBuildFile; fileRef = E940087C2114EDEE00CD2D67 /* EthWallet.swift */; }; - E94008802114EE2000CD2D67 /* AdmWallet.swift in Sources */ = {isa = PBXBuildFile; fileRef = E940087F2114EE2000CD2D67 /* AdmWallet.swift */; }; E94008832114EE4700CD2D67 /* LskWallet.swift in Sources */ = {isa = PBXBuildFile; fileRef = E94008822114EE4700CD2D67 /* LskWallet.swift */; }; E94008852114EE7500CD2D67 /* LskWalletService.swift in Sources */ = {isa = PBXBuildFile; fileRef = E94008842114EE7500CD2D67 /* LskWalletService.swift */; }; E94008872114F05B00CD2D67 /* AddressValidationResult.swift in Sources */ = {isa = PBXBuildFile; fileRef = E94008862114F05B00CD2D67 /* AddressValidationResult.swift */; }; - E94008892114F0F700CD2D67 /* AdmWalletService.swift in Sources */ = {isa = PBXBuildFile; fileRef = E94008882114F0F700CD2D67 /* AdmWalletService.swift */; }; E940088B2114F63000CD2D67 /* NSRegularExpression+adamant.swift in Sources */ = {isa = PBXBuildFile; fileRef = E940088A2114F63000CD2D67 /* NSRegularExpression+adamant.swift */; }; E940088F2119A9E800CD2D67 /* BigInt+Decimal.swift in Sources */ = {isa = PBXBuildFile; fileRef = E940088E2119A9E800CD2D67 /* BigInt+Decimal.swift */; }; E941CCDB20E786D800C96220 /* AccountHeader.xib in Resources */ = {isa = PBXBuildFile; fileRef = E941CCDA20E786D700C96220 /* AccountHeader.xib */; }; @@ -389,10 +395,10 @@ E9484B7D2285BAD9008E10F0 /* PrivateKeyGenerator.swift in Sources */ = {isa = PBXBuildFile; fileRef = E9484B7C2285BAD8008E10F0 /* PrivateKeyGenerator.swift */; }; E9484B7F2285C016008E10F0 /* PKGeneratorViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = E9484B7E2285C016008E10F0 /* PKGeneratorViewController.swift */; }; E94883E7203F07CD00F6E1B0 /* PassphraseValidation.swift in Sources */ = {isa = PBXBuildFile; fileRef = E94883E6203F07CD00F6E1B0 /* PassphraseValidation.swift */; }; - E948E03B20235E2300975D6B /* SettingsRoutes.swift in Sources */ = {isa = PBXBuildFile; fileRef = E948E03A20235E2300975D6B /* SettingsRoutes.swift */; }; + E948E03B20235E2300975D6B /* SettingsFactory.swift in Sources */ = {isa = PBXBuildFile; fileRef = E948E03A20235E2300975D6B /* SettingsFactory.swift */; }; E948E0482024F02700975D6B /* VersionFooter.xib in Resources */ = {isa = PBXBuildFile; fileRef = E948E0472024F02700975D6B /* VersionFooter.xib */; }; E94E7B01205D3F090042B639 /* ChatListViewController.xib in Resources */ = {isa = PBXBuildFile; fileRef = E94E7B00205D3F090042B639 /* ChatListViewController.xib */; }; - E94E7B08205D4CB80042B639 /* SharedRoutes.swift in Sources */ = {isa = PBXBuildFile; fileRef = E94E7B07205D4CB80042B639 /* SharedRoutes.swift */; }; + E94E7B08205D4CB80042B639 /* ShareQRFactory.swift in Sources */ = {isa = PBXBuildFile; fileRef = E94E7B07205D4CB80042B639 /* ShareQRFactory.swift */; }; E94E7B0C205D5E4A0042B639 /* TransactionsListViewControllerBase.xib in Resources */ = {isa = PBXBuildFile; fileRef = E94E7B0B205D5E4A0042B639 /* TransactionsListViewControllerBase.xib */; }; E9502740202E257E002C1098 /* RepeaterService.swift in Sources */ = {isa = PBXBuildFile; fileRef = E950273F202E257E002C1098 /* RepeaterService.swift */; }; E950652120404BF0008352E5 /* AdamantUriBuilding.swift in Sources */ = {isa = PBXBuildFile; fileRef = E950652020404BF0008352E5 /* AdamantUriBuilding.swift */; }; @@ -409,7 +415,7 @@ E95F856F2007B61D0070534A /* GetPublicKeyResponse.swift in Sources */ = {isa = PBXBuildFile; fileRef = E95F856E2007B61D0070534A /* GetPublicKeyResponse.swift */; }; E95F85712007D98D0070534A /* CurrencyFormatterTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = E95F85702007D98D0070534A /* CurrencyFormatterTests.swift */; }; E95F85752007E4790070534A /* HexAndBytesUtilitiesTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = E95F85742007E4790070534A /* HexAndBytesUtilitiesTest.swift */; }; - E95F85802008C8D70070534A /* ChatsRoutes.swift in Sources */ = {isa = PBXBuildFile; fileRef = E95F857E2008C8D60070534A /* ChatsRoutes.swift */; }; + E95F85802008C8D70070534A /* ChatListFactory.swift in Sources */ = {isa = PBXBuildFile; fileRef = E95F857E2008C8D60070534A /* ChatListFactory.swift */; }; E95F85852008CB3A0070534A /* ChatListViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = E95F85842008CB3A0070534A /* ChatListViewController.swift */; }; E95F85B7200A4D8F0070534A /* TestTools.swift in Sources */ = {isa = PBXBuildFile; fileRef = E95F85B6200A4D8F0070534A /* TestTools.swift */; }; E95F85BC200A4E670070534A /* ParsingModelsTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = E95F85BB200A4E670070534A /* ParsingModelsTests.swift */; }; @@ -449,8 +455,8 @@ E987024920C2B1F700E393F4 /* AdamantChatsProvider+fakeMessages.swift in Sources */ = {isa = PBXBuildFile; fileRef = E987024820C2B1F700E393F4 /* AdamantChatsProvider+fakeMessages.swift */; }; E98FC34420F920BD00032D65 /* UIFont+adamant.swift in Sources */ = {isa = PBXBuildFile; fileRef = E98FC34320F920BD00032D65 /* UIFont+adamant.swift */; }; E993301E212EF39700CD5200 /* EthTransferViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = E993301D212EF39700CD5200 /* EthTransferViewController.swift */; }; - E993302021354B1800CD5200 /* AdmWalletRoutes.swift in Sources */ = {isa = PBXBuildFile; fileRef = E993301F21354B1800CD5200 /* AdmWalletRoutes.swift */; }; - E993302221354BC300CD5200 /* EthWalletRoutes.swift in Sources */ = {isa = PBXBuildFile; fileRef = E993302121354BC300CD5200 /* EthWalletRoutes.swift */; }; + E993302021354B1800CD5200 /* AdmWalletFactory.swift in Sources */ = {isa = PBXBuildFile; fileRef = E993301F21354B1800CD5200 /* AdmWalletFactory.swift */; }; + E993302221354BC300CD5200 /* EthWalletFactory.swift in Sources */ = {isa = PBXBuildFile; fileRef = E993302121354BC300CD5200 /* EthWalletFactory.swift */; }; E99330262136B0E500CD5200 /* TransferViewControllerBase+QR.swift in Sources */ = {isa = PBXBuildFile; fileRef = E99330252136B0E500CD5200 /* TransferViewControllerBase+QR.swift */; }; E9942B80203C058C00C163AF /* QRGeneratorViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = E9942B7F203C058C00C163AF /* QRGeneratorViewController.swift */; }; E9942B84203CBFCE00C163AF /* AdamantQRTools.swift in Sources */ = {isa = PBXBuildFile; fileRef = E9942B83203CBFCE00C163AF /* AdamantQRTools.swift */; }; @@ -474,10 +480,8 @@ E9A174B920587B84003667CD /* notification.mp3 in Resources */ = {isa = PBXBuildFile; fileRef = E9A174B820587B83003667CD /* notification.mp3 */; }; E9AA8BF82129F13000F9249F /* ComplexTransferViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = E9AA8BF72129F13000F9249F /* ComplexTransferViewController.swift */; }; E9AA8BFA212C166600F9249F /* EthWalletService+Send.swift in Sources */ = {isa = PBXBuildFile; fileRef = E9AA8BF9212C166600F9249F /* EthWalletService+Send.swift */; }; - E9AA8BFC212C169200F9249F /* EthWalletService+Transfers.swift in Sources */ = {isa = PBXBuildFile; fileRef = E9AA8BFB212C169200F9249F /* EthWalletService+Transfers.swift */; }; E9AA8C02212C5BF500F9249F /* AdmWalletService+Send.swift in Sources */ = {isa = PBXBuildFile; fileRef = E9AA8C01212C5BF500F9249F /* AdmWalletService+Send.swift */; }; E9B1AA572121ACC000080A2A /* AdmWalletViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = E9B1AA562121ACBF00080A2A /* AdmWalletViewController.swift */; }; - E9B1AA592122D59600080A2A /* WalletsRoutes.swift in Sources */ = {isa = PBXBuildFile; fileRef = E9B1AA582122D59600080A2A /* WalletsRoutes.swift */; }; E9B1AA5B21283E0F00080A2A /* AdmTransferViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = E9B1AA5A21283E0F00080A2A /* AdmTransferViewController.swift */; }; E9B3D39A201F90570019EB36 /* AccountsProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = E9B3D399201F90570019EB36 /* AccountsProvider.swift */; }; E9B3D39E201F99F40019EB36 /* DataProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = E9B3D39D201F99F40019EB36 /* DataProvider.swift */; }; @@ -498,12 +502,10 @@ E9E7CD8B20026B0600DFC4DB /* AccountService.swift in Sources */ = {isa = PBXBuildFile; fileRef = E9E7CD8A20026B0600DFC4DB /* AccountService.swift */; }; E9E7CD8D20026B6600DFC4DB /* DialogService.swift in Sources */ = {isa = PBXBuildFile; fileRef = E9E7CD8C20026B6600DFC4DB /* DialogService.swift */; }; E9E7CD8F20026CD300DFC4DB /* AdamantDialogService.swift in Sources */ = {isa = PBXBuildFile; fileRef = E9E7CD8E20026CD300DFC4DB /* AdamantDialogService.swift */; }; - E9E7CD9120026FA100DFC4DB /* SwinjectDependencies.swift in Sources */ = {isa = PBXBuildFile; fileRef = E9E7CD9020026FA100DFC4DB /* SwinjectDependencies.swift */; }; + E9E7CD9120026FA100DFC4DB /* AppAssembly.swift in Sources */ = {isa = PBXBuildFile; fileRef = E9E7CD9020026FA100DFC4DB /* AppAssembly.swift */; }; E9E7CD932002740500DFC4DB /* AdamantAccountService.swift in Sources */ = {isa = PBXBuildFile; fileRef = E9E7CD922002740500DFC4DB /* AdamantAccountService.swift */; }; - E9E7CDAF2002B8A100DFC4DB /* Router.swift in Sources */ = {isa = PBXBuildFile; fileRef = E9E7CDAE2002B8A100DFC4DB /* Router.swift */; }; - E9E7CDB12002B97B00DFC4DB /* AccountRoutes.swift in Sources */ = {isa = PBXBuildFile; fileRef = E9E7CDB02002B97B00DFC4DB /* AccountRoutes.swift */; }; - E9E7CDB32002B9FB00DFC4DB /* LoginRoutes.swift in Sources */ = {isa = PBXBuildFile; fileRef = E9E7CDB22002B9FB00DFC4DB /* LoginRoutes.swift */; }; - E9E7CDB52002BA6900DFC4DB /* SwinjectedRouter.swift in Sources */ = {isa = PBXBuildFile; fileRef = E9E7CDB42002BA6900DFC4DB /* SwinjectedRouter.swift */; }; + E9E7CDB12002B97B00DFC4DB /* AccountFactory.swift in Sources */ = {isa = PBXBuildFile; fileRef = E9E7CDB02002B97B00DFC4DB /* AccountFactory.swift */; }; + E9E7CDB32002B9FB00DFC4DB /* LoginFactory.swift in Sources */ = {isa = PBXBuildFile; fileRef = E9E7CDB22002B9FB00DFC4DB /* LoginFactory.swift */; }; E9E7CDB72003994E00DFC4DB /* AdamantUtilities+extended.swift in Sources */ = {isa = PBXBuildFile; fileRef = E9E7CDB62003994E00DFC4DB /* AdamantUtilities+extended.swift */; }; E9E7CDBE2003AEFB00DFC4DB /* CellFactory.swift in Sources */ = {isa = PBXBuildFile; fileRef = E9E7CDBD2003AEFB00DFC4DB /* CellFactory.swift */; }; E9E7CDC02003AF6D00DFC4DB /* AdamantCellFactory.swift in Sources */ = {isa = PBXBuildFile; fileRef = E9E7CDBF2003AF6D00DFC4DB /* AdamantCellFactory.swift */; }; @@ -655,17 +657,16 @@ 55E69E162868D7920025D82E /* CheckmarkView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CheckmarkView.swift; sourceTree = ""; }; 55FBAAFA28C550920066E629 /* NodesAllowanceTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NodesAllowanceTests.swift; sourceTree = ""; }; 6403F5DD22723C6800D58779 /* DashMainnet.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DashMainnet.swift; sourceTree = ""; }; - 6403F5DF22723F6400D58779 /* DashWalletRouter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DashWalletRouter.swift; sourceTree = ""; }; + 6403F5DF22723F6400D58779 /* DashWalletFactory.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DashWalletFactory.swift; sourceTree = ""; }; 6403F5E122723F7500D58779 /* DashWallet.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DashWallet.swift; sourceTree = ""; }; 6403F5E322723F8C00D58779 /* DashWalletService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DashWalletService.swift; sourceTree = ""; }; 6403F5E522723FDA00D58779 /* DashWalletViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DashWalletViewController.swift; sourceTree = ""; }; 6406D74821C7F06000196713 /* SearchResultsViewController.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = SearchResultsViewController.xib; sourceTree = ""; }; 6414C18D217DF43100373FA6 /* String+adamant.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "String+adamant.swift"; sourceTree = ""; }; - 6416B19C21AD7B92006089AC /* LskWalletRoutes.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LskWalletRoutes.swift; sourceTree = ""; }; + 6416B19C21AD7B92006089AC /* LskWalletFactory.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LskWalletFactory.swift; sourceTree = ""; }; 6416B19E21AD7CBE006089AC /* LskWalletViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LskWalletViewController.swift; sourceTree = ""; }; 6416B1A021AD7D93006089AC /* LskTransferViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LskTransferViewController.swift; sourceTree = ""; }; 6416B1A221AD7EA1006089AC /* LskTransactionDetailsViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LskTransactionDetailsViewController.swift; sourceTree = ""; }; - 6416B1A421AEE157006089AC /* LskWalletService+Transfers.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "LskWalletService+Transfers.swift"; sourceTree = ""; }; 6416B1A621B024B6006089AC /* LskWalletService+Send.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "LskWalletService+Send.swift"; sourceTree = ""; }; 644793C22166314A00FC4CF5 /* OnboardPage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OnboardPage.swift; sourceTree = ""; }; 644793C42166315900FC4CF5 /* OnboardPage.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = OnboardPage.xib; sourceTree = ""; }; @@ -677,11 +678,11 @@ 6449BA63235CA0930033B936 /* ERC20TransactionsViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ERC20TransactionsViewController.swift; sourceTree = ""; }; 6449BA64235CA0930033B936 /* ERC20WalletService+RichMessageProviderWithStatusCheck.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "ERC20WalletService+RichMessageProviderWithStatusCheck.swift"; sourceTree = ""; }; 6449BA65235CA0930033B936 /* ERC20WalletService+Send.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "ERC20WalletService+Send.swift"; sourceTree = ""; }; - 6449BA66235CA0930033B936 /* ERC20WalletRouter.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ERC20WalletRouter.swift; sourceTree = ""; }; + 6449BA66235CA0930033B936 /* ERC20WalletFactory.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ERC20WalletFactory.swift; sourceTree = ""; }; 6449BA67235CA0930033B936 /* ERC20WalletService+RichMessageProvider.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "ERC20WalletService+RichMessageProvider.swift"; sourceTree = ""; }; 644EC34C20EFA60900F40C73 /* AdamantApi+Delegates.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "AdamantApi+Delegates.swift"; sourceTree = ""; }; 644EC34E20EFA77A00F40C73 /* Delegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Delegate.swift; sourceTree = ""; }; - 644EC35120EFA9A300F40C73 /* DelegateRoutes.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DelegateRoutes.swift; sourceTree = ""; }; + 644EC35120EFA9A300F40C73 /* DelegatesFactory.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DelegatesFactory.swift; sourceTree = ""; }; 644EC35520EFAAB700F40C73 /* DelegatesListViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DelegatesListViewController.swift; sourceTree = ""; }; 644EC35920EFB8E900F40C73 /* AdamantDelegateCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AdamantDelegateCell.swift; sourceTree = ""; }; 644EC35D20F34F1E00F40C73 /* DelegateDetailsViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DelegateDetailsViewController.swift; sourceTree = ""; }; @@ -693,9 +694,6 @@ 645AE06521E67D3300AD3623 /* UITextField+adamant.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "UITextField+adamant.swift"; sourceTree = ""; }; 645FEB32213E72C100D6BA2D /* OnboardViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OnboardViewController.swift; sourceTree = ""; }; 645FEB33213E72C100D6BA2D /* OnboardViewController.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = OnboardViewController.xib; sourceTree = ""; }; - 6489794A24CE00C000C33A68 /* SwiftyOnboardPage.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SwiftyOnboardPage.swift; sourceTree = ""; }; - 6489794B24CE00C000C33A68 /* SwiftyOnboard.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SwiftyOnboard.swift; sourceTree = ""; }; - 6489794C24CE00C000C33A68 /* SwiftyOnboardOverlay.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SwiftyOnboardOverlay.swift; sourceTree = ""; }; 648BCA6C213D384F00875EB5 /* AvatarService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AvatarService.swift; sourceTree = ""; }; 648C696E22915A12006645F5 /* DashTransaction.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DashTransaction.swift; sourceTree = ""; }; 648C697022915CB8006645F5 /* BTCRPCServerResponce.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BTCRPCServerResponce.swift; sourceTree = ""; }; @@ -728,7 +726,7 @@ 64BD2B7620E2820300E2CD36 /* TransactionDetails.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TransactionDetails.swift; sourceTree = ""; }; 64C65F4423893C7600DC0425 /* OnboardOverlay.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OnboardOverlay.swift; sourceTree = ""; }; 64D059FE20D3116A003AD655 /* NodesListViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NodesListViewController.swift; sourceTree = ""; }; - 64E1C82C222E95E2006C4DA7 /* DogeWalletRoutes.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DogeWalletRoutes.swift; sourceTree = ""; }; + 64E1C82C222E95E2006C4DA7 /* DogeWalletFactory.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DogeWalletFactory.swift; sourceTree = ""; }; 64E1C82E222E95F6006C4DA7 /* DogeWallet.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DogeWallet.swift; sourceTree = ""; }; 64E1C830222E9617006C4DA7 /* DogeWalletService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DogeWalletService.swift; sourceTree = ""; }; 64E1C832222EA0F0006C4DA7 /* DogeWalletViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DogeWalletViewController.swift; sourceTree = ""; }; @@ -750,6 +748,17 @@ 9322E87A2970431200B8357C /* ChatMessageFactory.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChatMessageFactory.swift; sourceTree = ""; }; 9324C75D297170600022D7EA /* RichTransactionStatusService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RichTransactionStatusService.swift; sourceTree = ""; }; 9324C75F297171040022D7EA /* AdamantRichTransactionStatusService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AdamantRichTransactionStatusService.swift; sourceTree = ""; }; + 93294B7C2AAD067000911109 /* AppContainer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppContainer.swift; sourceTree = ""; }; + 93294B812AAD0BB400911109 /* BtcWalletFactory.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BtcWalletFactory.swift; sourceTree = ""; }; + 93294B832AAD0C8F00911109 /* Assembler+Extension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Assembler+Extension.swift"; sourceTree = ""; }; + 93294B852AAD0E0A00911109 /* AdmWallet.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AdmWallet.swift; sourceTree = ""; }; + 93294B862AAD0E0A00911109 /* AdmWalletService.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AdmWalletService.swift; sourceTree = ""; }; + 93294B8B2AAD2C6B00911109 /* SwiftyOnboardPage.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SwiftyOnboardPage.swift; sourceTree = ""; }; + 93294B8C2AAD2C6B00911109 /* SwiftyOnboard.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SwiftyOnboard.swift; sourceTree = ""; }; + 93294B8D2AAD2C6B00911109 /* SwiftyOnboardOverlay.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SwiftyOnboardOverlay.swift; sourceTree = ""; }; + 93294B952AAD320B00911109 /* ScreensFactory.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ScreensFactory.swift; sourceTree = ""; }; + 93294B972AAD364F00911109 /* AdamantScreensFactory.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AdamantScreensFactory.swift; sourceTree = ""; }; + 93294B992AAD624100911109 /* WalletFactoryCompose.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WalletFactoryCompose.swift; sourceTree = ""; }; 932B34E82974AA4A002A75BA /* ChatPreservationDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChatPreservationDelegate.swift; sourceTree = ""; }; 932BD15A29D2F75200AA1947 /* RichMessageProviderWithStatusCheck+Extension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "RichMessageProviderWithStatusCheck+Extension.swift"; sourceTree = ""; }; 932F77582989F999006D8801 /* ChatCellManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChatCellManager.swift; sourceTree = ""; }; @@ -793,6 +802,8 @@ 9399F5EC29A85A48006C3E30 /* ChatCacheService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChatCacheService.swift; sourceTree = ""; }; 93A118502993167500E144CC /* ChatMessageBackgroundColor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChatMessageBackgroundColor.swift; sourceTree = ""; }; 93A118522993241D00E144CC /* ChatMessagesListFactory.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChatMessagesListFactory.swift; sourceTree = ""; }; + 93A18C852AAEACC100D0AB98 /* AdamantWalletFactoryCompose.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AdamantWalletFactoryCompose.swift; sourceTree = ""; }; + 93A18C882AAEAE7700D0AB98 /* WalletFactory.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WalletFactory.swift; sourceTree = ""; }; 93A91FD0297972B7001DB1F8 /* ChatScrollDownButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChatScrollDownButton.swift; sourceTree = ""; }; 93A91FD229799298001DB1F8 /* ChatStartPosition.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChatStartPosition.swift; sourceTree = ""; }; 93BF4A6529E4859900505CD0 /* DelegatesBottomPanel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DelegatesBottomPanel.swift; sourceTree = ""; }; @@ -813,7 +824,6 @@ 93E5D4DF2930029300439298 /* AdamantCore+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "AdamantCore+Extensions.swift"; sourceTree = ""; }; 93EE9C3229C2666200D9853F /* RichTransactionStatusSubscription.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RichTransactionStatusSubscription.swift; sourceTree = ""; }; 93F3914F2962F5D400BFD6AE /* SpinnerView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SpinnerView.swift; sourceTree = ""; }; - A50A41032822F8CE006BDFE1 /* BtcWalletRoutes.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BtcWalletRoutes.swift; sourceTree = ""; }; A50A41042822F8CE006BDFE1 /* BtcWalletService.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BtcWalletService.swift; sourceTree = ""; }; A50A41052822F8CE006BDFE1 /* BtcWalletViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BtcWalletViewController.swift; sourceTree = ""; }; A50A41062822F8CE006BDFE1 /* BtcWallet.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BtcWallet.swift; sourceTree = ""; }; @@ -895,24 +905,22 @@ E926E02D213EAABF005E536B /* TransferViewControllerBase+Alert.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "TransferViewControllerBase+Alert.swift"; sourceTree = ""; }; E926E031213EC43B005E536B /* FullscreenAlertView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FullscreenAlertView.swift; sourceTree = ""; }; E926E033213EC454005E536B /* FullscreenAlertView.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = FullscreenAlertView.xib; sourceTree = ""; }; - E9332B8821F1FA4400D56E72 /* OnboardRoutes.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OnboardRoutes.swift; sourceTree = ""; }; + E9332B8821F1FA4400D56E72 /* OnboardFactory.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OnboardFactory.swift; sourceTree = ""; }; E933475A225539390083839E /* DogeGetTransactionsResponse.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DogeGetTransactionsResponse.swift; sourceTree = ""; }; E9393FA92055D03300EE6F30 /* AdamantMessage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AdamantMessage.swift; sourceTree = ""; }; E93B0D732028B21400126346 /* ChatsProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChatsProvider.swift; sourceTree = ""; }; E93B0D752028B28E00126346 /* AdamantChatsProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AdamantChatsProvider.swift; sourceTree = ""; }; E93D7ABD2052CEE1005D19DC /* NotificationsService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationsService.swift; sourceTree = ""; }; E93D7ABF2052CF63005D19DC /* AdamantNotificationService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AdamantNotificationService.swift; sourceTree = ""; }; - E93EB09E20DA3FA4001F9601 /* NodesEditorRoutes.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NodesEditorRoutes.swift; sourceTree = ""; }; + E93EB09E20DA3FA4001F9601 /* NodesEditorFactory.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NodesEditorFactory.swift; sourceTree = ""; }; E940086A2114A70600CD2D67 /* LskAccount.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LskAccount.swift; sourceTree = ""; }; E940086D2114AA2E00CD2D67 /* WalletService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WalletService.swift; sourceTree = ""; }; E94008712114EACF00CD2D67 /* WalletAccount.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WalletAccount.swift; sourceTree = ""; }; E940087A2114ED0600CD2D67 /* EthWalletService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EthWalletService.swift; sourceTree = ""; }; E940087C2114EDEE00CD2D67 /* EthWallet.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EthWallet.swift; sourceTree = ""; }; - E940087F2114EE2000CD2D67 /* AdmWallet.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = AdmWallet.swift; path = Adamant/Wallets/Adamant/AdmWallet.swift; sourceTree = SOURCE_ROOT; }; E94008822114EE4700CD2D67 /* LskWallet.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LskWallet.swift; sourceTree = ""; }; E94008842114EE7500CD2D67 /* LskWalletService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LskWalletService.swift; sourceTree = ""; }; E94008862114F05B00CD2D67 /* AddressValidationResult.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AddressValidationResult.swift; sourceTree = ""; }; - E94008882114F0F700CD2D67 /* AdmWalletService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = AdmWalletService.swift; path = Adamant/Wallets/Adamant/AdmWalletService.swift; sourceTree = SOURCE_ROOT; }; E940088A2114F63000CD2D67 /* NSRegularExpression+adamant.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "NSRegularExpression+adamant.swift"; sourceTree = ""; }; E940088E2119A9E800CD2D67 /* BigInt+Decimal.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "BigInt+Decimal.swift"; sourceTree = ""; }; E941CCDA20E786D700C96220 /* AccountHeader.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = AccountHeader.xib; sourceTree = ""; }; @@ -923,10 +931,10 @@ E9484B7C2285BAD8008E10F0 /* PrivateKeyGenerator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PrivateKeyGenerator.swift; sourceTree = ""; }; E9484B7E2285C016008E10F0 /* PKGeneratorViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PKGeneratorViewController.swift; sourceTree = ""; }; E94883E6203F07CD00F6E1B0 /* PassphraseValidation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PassphraseValidation.swift; sourceTree = ""; }; - E948E03A20235E2300975D6B /* SettingsRoutes.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsRoutes.swift; sourceTree = ""; }; + E948E03A20235E2300975D6B /* SettingsFactory.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsFactory.swift; sourceTree = ""; }; E948E0472024F02700975D6B /* VersionFooter.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = VersionFooter.xib; sourceTree = ""; }; E94E7B00205D3F090042B639 /* ChatListViewController.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = ChatListViewController.xib; sourceTree = ""; }; - E94E7B07205D4CB80042B639 /* SharedRoutes.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SharedRoutes.swift; sourceTree = ""; }; + E94E7B07205D4CB80042B639 /* ShareQRFactory.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ShareQRFactory.swift; sourceTree = ""; }; E94E7B0B205D5E4A0042B639 /* TransactionsListViewControllerBase.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = TransactionsListViewControllerBase.xib; sourceTree = ""; }; E950273F202E257E002C1098 /* RepeaterService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RepeaterService.swift; sourceTree = ""; }; E950652020404BF0008352E5 /* AdamantUriBuilding.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AdamantUriBuilding.swift; sourceTree = ""; }; @@ -948,7 +956,7 @@ E95F85702007D98D0070534A /* CurrencyFormatterTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CurrencyFormatterTests.swift; sourceTree = ""; }; E95F85742007E4790070534A /* HexAndBytesUtilitiesTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HexAndBytesUtilitiesTest.swift; sourceTree = ""; }; E95F85762007E8EC0070534A /* JSAdamantCoreTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = JSAdamantCoreTests.swift; sourceTree = ""; }; - E95F857E2008C8D60070534A /* ChatsRoutes.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChatsRoutes.swift; sourceTree = ""; }; + E95F857E2008C8D60070534A /* ChatListFactory.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChatListFactory.swift; sourceTree = ""; }; E95F85842008CB3A0070534A /* ChatListViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChatListViewController.swift; sourceTree = ""; }; E95F85B6200A4D8F0070534A /* TestTools.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TestTools.swift; sourceTree = ""; }; E95F85B9200A4DC90070534A /* TransactionSend.json */ = {isa = PBXFileReference; lastKnownFileType = text.json; path = TransactionSend.json; sourceTree = ""; }; @@ -983,8 +991,8 @@ E987024820C2B1F700E393F4 /* AdamantChatsProvider+fakeMessages.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "AdamantChatsProvider+fakeMessages.swift"; sourceTree = ""; }; E98FC34320F920BD00032D65 /* UIFont+adamant.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIFont+adamant.swift"; sourceTree = ""; }; E993301D212EF39700CD5200 /* EthTransferViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EthTransferViewController.swift; sourceTree = ""; }; - E993301F21354B1800CD5200 /* AdmWalletRoutes.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AdmWalletRoutes.swift; sourceTree = ""; }; - E993302121354BC300CD5200 /* EthWalletRoutes.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EthWalletRoutes.swift; sourceTree = ""; }; + E993301F21354B1800CD5200 /* AdmWalletFactory.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AdmWalletFactory.swift; sourceTree = ""; }; + E993302121354BC300CD5200 /* EthWalletFactory.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EthWalletFactory.swift; sourceTree = ""; }; E99330252136B0E500CD5200 /* TransferViewControllerBase+QR.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "TransferViewControllerBase+QR.swift"; sourceTree = ""; }; E9942B7F203C058C00C163AF /* QRGeneratorViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = QRGeneratorViewController.swift; sourceTree = ""; }; E9942B83203CBFCE00C163AF /* AdamantQRTools.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AdamantQRTools.swift; sourceTree = ""; }; @@ -1008,10 +1016,8 @@ E9A174B820587B83003667CD /* notification.mp3 */ = {isa = PBXFileReference; lastKnownFileType = audio.mp3; path = notification.mp3; sourceTree = ""; }; E9AA8BF72129F13000F9249F /* ComplexTransferViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ComplexTransferViewController.swift; sourceTree = ""; }; E9AA8BF9212C166600F9249F /* EthWalletService+Send.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "EthWalletService+Send.swift"; sourceTree = ""; }; - E9AA8BFB212C169200F9249F /* EthWalletService+Transfers.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "EthWalletService+Transfers.swift"; sourceTree = ""; }; E9AA8C01212C5BF500F9249F /* AdmWalletService+Send.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "AdmWalletService+Send.swift"; sourceTree = ""; }; E9B1AA562121ACBF00080A2A /* AdmWalletViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AdmWalletViewController.swift; sourceTree = ""; }; - E9B1AA582122D59600080A2A /* WalletsRoutes.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WalletsRoutes.swift; sourceTree = ""; }; E9B1AA5A21283E0F00080A2A /* AdmTransferViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AdmTransferViewController.swift; sourceTree = ""; }; E9B3D399201F90570019EB36 /* AccountsProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccountsProvider.swift; sourceTree = ""; }; E9B3D39D201F99F40019EB36 /* DataProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DataProvider.swift; sourceTree = ""; }; @@ -1036,12 +1042,10 @@ E9E7CD8A20026B0600DFC4DB /* AccountService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccountService.swift; sourceTree = ""; }; E9E7CD8C20026B6600DFC4DB /* DialogService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DialogService.swift; sourceTree = ""; }; E9E7CD8E20026CD300DFC4DB /* AdamantDialogService.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AdamantDialogService.swift; sourceTree = ""; }; - E9E7CD9020026FA100DFC4DB /* SwinjectDependencies.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SwinjectDependencies.swift; sourceTree = ""; }; + E9E7CD9020026FA100DFC4DB /* AppAssembly.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppAssembly.swift; sourceTree = ""; }; E9E7CD922002740500DFC4DB /* AdamantAccountService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AdamantAccountService.swift; sourceTree = ""; }; - E9E7CDAE2002B8A100DFC4DB /* Router.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Router.swift; sourceTree = ""; }; - E9E7CDB02002B97B00DFC4DB /* AccountRoutes.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AccountRoutes.swift; sourceTree = ""; }; - E9E7CDB22002B9FB00DFC4DB /* LoginRoutes.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoginRoutes.swift; sourceTree = ""; }; - E9E7CDB42002BA6900DFC4DB /* SwinjectedRouter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SwinjectedRouter.swift; sourceTree = ""; }; + E9E7CDB02002B97B00DFC4DB /* AccountFactory.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AccountFactory.swift; sourceTree = ""; }; + E9E7CDB22002B9FB00DFC4DB /* LoginFactory.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoginFactory.swift; sourceTree = ""; }; E9E7CDB62003994E00DFC4DB /* AdamantUtilities+extended.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "AdamantUtilities+extended.swift"; sourceTree = ""; }; E9E7CDBD2003AEFB00DFC4DB /* CellFactory.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CellFactory.swift; sourceTree = ""; }; E9E7CDBF2003AF6D00DFC4DB /* AdamantCellFactory.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AdamantCellFactory.swift; sourceTree = ""; }; @@ -1242,7 +1246,7 @@ isa = PBXGroup; children = ( 6403F5DD22723C6800D58779 /* DashMainnet.swift */, - 6403F5DF22723F6400D58779 /* DashWalletRouter.swift */, + 6403F5DF22723F6400D58779 /* DashWalletFactory.swift */, 6403F5E122723F7500D58779 /* DashWallet.swift */, 6403F5E322723F8C00D58779 /* DashWalletService.swift */, 4186B339294200F4006594A3 /* DashWalletService+DynamicConstants.swift */, @@ -1261,7 +1265,7 @@ 6449BA5D235CA0930033B936 /* ERC20 */ = { isa = PBXGroup; children = ( - 6449BA66235CA0930033B936 /* ERC20WalletRouter.swift */, + 6449BA66235CA0930033B936 /* ERC20WalletFactory.swift */, 6449BA60235CA0930033B936 /* ERC20Wallet.swift */, 6449BA5E235CA0930033B936 /* ERC20WalletService.swift */, 6449BA65235CA0930033B936 /* ERC20WalletService+Send.swift */, @@ -1279,7 +1283,7 @@ isa = PBXGroup; children = ( 93BF4A6A29E4B4B600505CD0 /* DelegatesBottomPanel */, - 644EC35120EFA9A300F40C73 /* DelegateRoutes.swift */, + 644EC35120EFA9A300F40C73 /* DelegatesFactory.swift */, 644EC35520EFAAB700F40C73 /* DelegatesListViewController.swift */, 644EC35920EFB8E900F40C73 /* AdamantDelegateCell.swift */, 644EC35D20F34F1E00F40C73 /* DelegateDetailsViewController.swift */, @@ -1288,22 +1292,11 @@ path = Delegates; sourceTree = ""; }; - 6489794924CE00C000C33A68 /* SwiftyOnboard */ = { - isa = PBXGroup; - children = ( - 6489794A24CE00C000C33A68 /* SwiftyOnboardPage.swift */, - 6489794B24CE00C000C33A68 /* SwiftyOnboard.swift */, - 6489794C24CE00C000C33A68 /* SwiftyOnboardOverlay.swift */, - ); - name = SwiftyOnboard; - path = Adamant/Stories/SwiftyOnboard; - sourceTree = SOURCE_ROOT; - }; 64E1C82B222E958C006C4DA7 /* Doge */ = { isa = PBXGroup; children = ( E907350D2256779C00BF02CC /* DogeMainnet.swift */, - 64E1C82C222E95E2006C4DA7 /* DogeWalletRoutes.swift */, + 64E1C82C222E95E2006C4DA7 /* DogeWalletFactory.swift */, 64E1C82E222E95F6006C4DA7 /* DogeWallet.swift */, 64E1C830222E9617006C4DA7 /* DogeWalletService.swift */, 4186B337294200E8006594A3 /* DogeWalletService+DynamicConstants.swift */, @@ -1331,6 +1324,62 @@ path = Models; sourceTree = ""; }; + 93294B7A2AAD054C00911109 /* App */ = { + isa = PBXGroup; + children = ( + 93294B7B2AAD060600911109 /* DI */, + E913C8F11FFFA51D001A83F7 /* AppDelegate.swift */, + ); + path = App; + sourceTree = ""; + }; + 93294B7B2AAD060600911109 /* DI */ = { + isa = PBXGroup; + children = ( + E9E7CD9020026FA100DFC4DB /* AppAssembly.swift */, + 93294B7C2AAD067000911109 /* AppContainer.swift */, + ); + path = DI; + sourceTree = ""; + }; + 93294B892AAD2BD900911109 /* ShareQR */ = { + isa = PBXGroup; + children = ( + E94E7B07205D4CB80042B639 /* ShareQRFactory.swift */, + E9FAE5E0203ED1AE008D3A6B /* ShareQrViewController.swift */, + E9FAE5E1203ED1AE008D3A6B /* ShareQrViewController.xib */, + ); + path = ShareQR; + sourceTree = ""; + }; + 93294B8A2AAD2C6B00911109 /* SwiftyOnboard */ = { + isa = PBXGroup; + children = ( + 93294B8B2AAD2C6B00911109 /* SwiftyOnboardPage.swift */, + 93294B8C2AAD2C6B00911109 /* SwiftyOnboard.swift */, + 93294B8D2AAD2C6B00911109 /* SwiftyOnboardOverlay.swift */, + ); + path = SwiftyOnboard; + sourceTree = ""; + }; + 93294B912AAD2CA500911109 /* Welcome */ = { + isa = PBXGroup; + children = ( + 6458548A211B3AB1004C5909 /* WelcomeViewController.xib */, + 93547BC929E2262D00B0914B /* WelcomeViewController.swift */, + ); + path = Welcome; + sourceTree = ""; + }; + 93294B942AAD31F200911109 /* ScreensFactory */ = { + isa = PBXGroup; + children = ( + 93294B952AAD320B00911109 /* ScreensFactory.swift */, + 93294B972AAD364F00911109 /* AdamantScreensFactory.swift */, + ); + path = ScreensFactory; + sourceTree = ""; + }; 932BD15929D2F74500AA1947 /* RichMessageProviderWithStatusCheck */ = { isa = PBXGroup; children = ( @@ -1446,6 +1495,16 @@ path = Subviews; sourceTree = ""; }; + 93A18C872AAEAE5600D0AB98 /* DI */ = { + isa = PBXGroup; + children = ( + 93A18C882AAEAE7700D0AB98 /* WalletFactory.swift */, + 93294B992AAD624100911109 /* WalletFactoryCompose.swift */, + 93A18C852AAEACC100D0AB98 /* AdamantWalletFactoryCompose.swift */, + ); + path = DI; + sourceTree = ""; + }; 93BF4A6A29E4B4B600505CD0 /* DelegatesBottomPanel */ = { isa = PBXGroup; children = ( @@ -1505,7 +1564,7 @@ children = ( A5E04225282A8BC70076CD13 /* Models */, A50A41062822F8CE006BDFE1 /* BtcWallet.swift */, - A50A41032822F8CE006BDFE1 /* BtcWalletRoutes.swift */, + 93294B812AAD0BB400911109 /* BtcWalletFactory.swift */, A50A41042822F8CE006BDFE1 /* BtcWalletService.swift */, 4186B331294200B4006594A3 /* BtcWalletService+DynamicConstants.swift */, A50A410F2822FC35006BDFE1 /* BtcWalletService+Send.swift */, @@ -1583,19 +1642,16 @@ E913C8F01FFFA51D001A83F7 /* Adamant */ = { isa = PBXGroup; children = ( - E95F859220094B8E0070534A /* CoreData */, + 93294B7A2AAD054C00911109 /* App */, E913C9101FFFAA4B001A83F7 /* Helpers */, E950651F20404997008352E5 /* Utilities */, E913C9091FFFA95A001A83F7 /* Models */, - E91947B72000326B001362F8 /* ServerResponses */, E913C9041FFFA8FE001A83F7 /* ServiceProtocols */, E913C9061FFFA92E001A83F7 /* Services */, - E940086C2114A8FD00CD2D67 /* Wallets */, E9E7CDB82003AA8E00DFC4DB /* SharedViews */, - E919479920000FFD001362F8 /* Stories */, + E919479920000FFD001362F8 /* Modules */, E913C9111FFFAB05001A83F7 /* Assets */, - E913C8F11FFFA51D001A83F7 /* AppDelegate.swift */, - E9E7CD9020026FA100DFC4DB /* SwinjectDependencies.swift */, + E90847192196FE590095825D /* Adamant.xcdatamodeld */, E913C8FD1FFFA51E001A83F7 /* Info.plist */, 93E123312A6DF8EF004DF33B /* InfoPlist.strings */, E9B994C222BFD73F004CD645 /* Release.entitlements */, @@ -1627,7 +1683,6 @@ E9215972206119FB0000CA5C /* ReachabilityMonitor.swift */, E9FEECA321413659007DD7C8 /* RichMessageProvider.swift */, 550066C4284D65DB0044C0B1 /* HealthCheckService.swift */, - E9E7CDAE2002B8A100DFC4DB /* Router.swift */, 9304F8C1292F895C00173F18 /* PushNotificationsTokenService.swift */, 9324C75D297170600022D7EA /* RichTransactionStatusService.swift */, 41047B73294C61D10039E956 /* VisibleWalletsService.swift */, @@ -1663,7 +1718,6 @@ 3A9015A62A614A62002A2464 /* AdamantEmojiService.swift */, E921597420611A6A0000CA5C /* AdamantReachability.swift */, E950273F202E257E002C1098 /* RepeaterService.swift */, - E9E7CDB42002BA6900DFC4DB /* SwinjectedRouter.swift */, E9771D9D22997A6F0099AAC7 /* NativeCore+AdamantCore.swift */, 550066C6284D682D0044C0B1 /* AdamantHealthCheckService.swift */, 9304F8C3292F8A3100173F18 /* AdamantPushNotificationsTokenService.swift */, @@ -1674,6 +1728,8 @@ E913C9091FFFA95A001A83F7 /* Models */ = { isa = PBXGroup; children = ( + E91947B72000326B001362F8 /* ServerResponses */, + E95F859220094B8E0070534A /* CoreData */, E91947B320002809001362F8 /* AdamantAccount.swift */, E9393FA92055D03300EE6F30 /* AdamantMessage.swift */, 644EC34E20EFA77A00F40C73 /* Delegate.swift */, @@ -1727,6 +1783,7 @@ 4133AF232A1CE1A3001A0A1E /* UITableView+Adamant.swift */, 41A1994129D2D3920031AD75 /* SwipePanGestureRecognizer.swift */, 4193AE1529FBEFBF002F21BE /* NSAttributedText+Adamant.swift */, + 93294B832AAD0C8F00911109 /* Assembler+Extension.swift */, ); path = Helpers; sourceTree = ""; @@ -1748,9 +1805,14 @@ path = Assets; sourceTree = ""; }; - E919479920000FFD001362F8 /* Stories */ = { + E919479920000FFD001362F8 /* Modules */ = { isa = PBXGroup; children = ( + 93294B942AAD31F200911109 /* ScreensFactory */, + 93294B912AAD2CA500911109 /* Welcome */, + 93294B8A2AAD2C6B00911109 /* SwiftyOnboard */, + 93294B892AAD2BD900911109 /* ShareQR */, + E940086C2114A8FD00CD2D67 /* Wallets */, 938F7D552955C05D001915CA /* Chat */, E9E7CDA52002AE1C00DFC4DB /* Account */, E95F857B2008C8B20070534A /* ChatsList */, @@ -1759,15 +1821,14 @@ E93EB09D20DA3F3A001F9601 /* NodesEditor */, E982F69820235AF000566AC7 /* Settings */, E9332B8721F1F9D100D56E72 /* Onboard */, - E9FAE5DB203ECD41008D3A6B /* Shared */, ); - path = Stories; + path = Modules; sourceTree = ""; }; E919479A20001007001362F8 /* Login */ = { isa = PBXGroup; children = ( - E9E7CDB22002B9FB00DFC4DB /* LoginRoutes.swift */, + E9E7CDB22002B9FB00DFC4DB /* LoginFactory.swift */, E905D39E204C281400DDB504 /* LoginViewController.swift */, E9147B602050599000145913 /* LoginViewController+QR.swift */, E9147B6E205088DE00145913 /* LoginViewController+Pinpad.swift */, @@ -1799,9 +1860,8 @@ E9332B8721F1F9D100D56E72 /* Onboard */ = { isa = PBXGroup; children = ( - 6489794924CE00C000C33A68 /* SwiftyOnboard */, 64C65F4423893C7600DC0425 /* OnboardOverlay.swift */, - E9332B8821F1FA4400D56E72 /* OnboardRoutes.swift */, + E9332B8821F1FA4400D56E72 /* OnboardFactory.swift */, 645938922378395E00A2BE7C /* EulaViewController.swift */, 645938932378395E00A2BE7C /* EulaViewController.xib */, 645FEB32213E72C100D6BA2D /* OnboardViewController.swift */, @@ -1817,7 +1877,7 @@ isa = PBXGroup; children = ( 64D059FE20D3116A003AD655 /* NodesListViewController.swift */, - E93EB09E20DA3FA4001F9601 /* NodesEditorRoutes.swift */, + E93EB09E20DA3FA4001F9601 /* NodesEditorFactory.swift */, E9A03FD120DBC0F2007653A1 /* NodeEditorViewController.swift */, E91E5BF120DAF05500B06B3C /* EurekaNodeRow.swift */, ); @@ -1827,6 +1887,7 @@ E940086C2114A8FD00CD2D67 /* Wallets */ = { isa = PBXGroup; children = ( + 93A18C872AAEAE5600D0AB98 /* DI */, A50A41022822F8CE006BDFE1 /* Bitcoin */, E94008902119D22400CD2D67 /* Adamant */, E94008792114ECF100CD2D67 /* Ethereum */, @@ -1836,7 +1897,6 @@ 6449BA5D235CA0930033B936 /* ERC20 */, E94008712114EACF00CD2D67 /* WalletAccount.swift */, E940086D2114AA2E00CD2D67 /* WalletService.swift */, - E9B1AA582122D59600080A2A /* WalletsRoutes.swift */, E99818932120892F0018C84C /* WalletViewControllerBase.swift */, E9981897212096ED0018C84C /* WalletViewControllerBase.xib */, E9EC342020052ABB00C0E546 /* TransferViewControllerBase.swift */, @@ -1858,11 +1918,10 @@ isa = PBXGroup; children = ( E940087C2114EDEE00CD2D67 /* EthWallet.swift */, - E993302121354BC300CD5200 /* EthWalletRoutes.swift */, + E993302121354BC300CD5200 /* EthWalletFactory.swift */, E940087A2114ED0600CD2D67 /* EthWalletService.swift */, 4186B333294200C5006594A3 /* EthWalletService+DynamicConstants.swift */, E9AA8BF9212C166600F9249F /* EthWalletService+Send.swift */, - E9AA8BFB212C169200F9249F /* EthWalletService+Transfers.swift */, E9FEECA52143C300007DD7C8 /* EthWalletService+RichMessageProvider.swift */, E971591B2168209800A5F904 /* EthWalletService+RichMessageProviderWithStatusCheck.swift */, E9981895212095CA0018C84C /* EthWalletViewController.swift */, @@ -1877,11 +1936,10 @@ isa = PBXGroup; children = ( E94008822114EE4700CD2D67 /* LskWallet.swift */, - 6416B19C21AD7B92006089AC /* LskWalletRoutes.swift */, + 6416B19C21AD7B92006089AC /* LskWalletFactory.swift */, E94008842114EE7500CD2D67 /* LskWalletService.swift */, 4186B335294200D2006594A3 /* LskWalletService+DynamicConstants.swift */, 6416B1A621B024B6006089AC /* LskWalletService+Send.swift */, - 6416B1A421AEE157006089AC /* LskWalletService+Transfers.swift */, 649D6BE721B95DB7009E727B /* LskWalletService+RichMessageProvider.swift */, 649D6BE921B9627B009E727B /* LskWalletService+RichMessageProviderWithStatusCheck.swift */, 6416B19E21AD7CBE006089AC /* LskWalletViewController.swift */, @@ -1895,9 +1953,9 @@ E94008902119D22400CD2D67 /* Adamant */ = { isa = PBXGroup; children = ( - E940087F2114EE2000CD2D67 /* AdmWallet.swift */, - E993301F21354B1800CD5200 /* AdmWalletRoutes.swift */, - E94008882114F0F700CD2D67 /* AdmWalletService.swift */, + 93294B852AAD0E0A00911109 /* AdmWallet.swift */, + 93294B862AAD0E0A00911109 /* AdmWalletService.swift */, + E993301F21354B1800CD5200 /* AdmWalletFactory.swift */, 4186B32F2941E642006594A3 /* AdmWalletService+DynamicConstants.swift */, E9AA8C01212C5BF500F9249F /* AdmWalletService+Send.swift */, E9240BF4215D686500187B09 /* AdmWalletService+RichMessageProvider.swift */, @@ -1952,7 +2010,7 @@ E95F857B2008C8B20070534A /* ChatsList */ = { isa = PBXGroup; children = ( - E95F857E2008C8D60070534A /* ChatsRoutes.swift */, + E95F857E2008C8D60070534A /* ChatListFactory.swift */, E95F85842008CB3A0070534A /* ChatListViewController.swift */, E94E7B00205D3F090042B639 /* ChatListViewController.xib */, E9C51EF02013F18000385EB7 /* NewChatViewController.swift */, @@ -1988,7 +2046,6 @@ E908471F2196FEA80095825D /* CoreDataAccount+CoreDataProperties.swift */, E90847282196FEA80095825D /* Chatroom+CoreDataClass.swift */, E90847292196FEA80095825D /* Chatroom+CoreDataProperties.swift */, - E90847192196FE590095825D /* Adamant.xcdatamodeld */, ); path = CoreData; sourceTree = ""; @@ -2043,7 +2100,7 @@ children = ( 411742FE2A39B1B1008CD98A /* Contribute */, 4197B9C72952FAA2004CAF64 /* VisibleWallets */, - E948E03A20235E2300975D6B /* SettingsRoutes.swift */, + E948E03A20235E2300975D6B /* SettingsFactory.swift */, E90055F420EBF5DA00D0CB2D /* AboutViewController.swift */, E90055F620EC200900D0CB2D /* SecurityViewController.swift */, E90055F820ECD86800D0CB2D /* SecurityViewController+StayIn.swift */, @@ -2105,7 +2162,7 @@ E9E7CDA52002AE1C00DFC4DB /* Account */ = { isa = PBXGroup; children = ( - E9E7CDB02002B97B00DFC4DB /* AccountRoutes.swift */, + E9E7CDB02002B97B00DFC4DB /* AccountFactory.swift */, E983AE2820E65F3200497E1A /* AccountViewController.swift */, E908473A219707200095825D /* AccountViewController+StayIn.swift */, E983AE2020E655C500497E1A /* AccountHeaderView.swift */, @@ -2121,6 +2178,8 @@ E9E7CDB82003AA8E00DFC4DB /* SharedViews */ = { isa = PBXGroup; children = ( + E921597A206503000000CA5C /* ButtonsStripeView.swift */, + E921597C2065031D0000CA5C /* ButtonsStripe.xib */, 93496B9F2A6CAE9300DD062F /* LogoFullHeader.xib */, E9942B86203D9E5100C163AF /* EurekaQRRow.swift */, E9942B88203D9ECA00C163AF /* QrCell.xib */, @@ -2164,20 +2223,6 @@ path = AdamantTests; sourceTree = ""; }; - E9FAE5DB203ECD41008D3A6B /* Shared */ = { - isa = PBXGroup; - children = ( - E94E7B07205D4CB80042B639 /* SharedRoutes.swift */, - 6458548A211B3AB1004C5909 /* WelcomeViewController.xib */, - E9FAE5E0203ED1AE008D3A6B /* ShareQrViewController.swift */, - 93547BC929E2262D00B0914B /* WelcomeViewController.swift */, - E9FAE5E1203ED1AE008D3A6B /* ShareQrViewController.xib */, - E921597A206503000000CA5C /* ButtonsStripeView.swift */, - E921597C2065031D0000CA5C /* ButtonsStripe.xib */, - ); - path = Shared; - sourceTree = ""; - }; /* End PBXGroup section */ /* Begin PBXNativeTarget section */ @@ -2642,7 +2687,7 @@ 64A223D620F760BB005157CB /* Localization.swift in Sources */, 64E1C82F222E95F6006C4DA7 /* DogeWallet.swift in Sources */, E9484B7D2285BAD9008E10F0 /* PrivateKeyGenerator.swift in Sources */, - E94E7B08205D4CB80042B639 /* SharedRoutes.swift in Sources */, + E94E7B08205D4CB80042B639 /* ShareQRFactory.swift in Sources */, 9377FBDF296C2A2F00C9211B /* ChatTransactionContentView.swift in Sources */, E9960B3621F5154300C840A8 /* DummyAccount+CoreDataProperties.swift in Sources */, 4186B332294200B4006594A3 /* BtcWalletService+DynamicConstants.swift in Sources */, @@ -2651,7 +2696,9 @@ 648CE3A6229AD1CD0070A2CC /* DashWalletService+Send.swift in Sources */, E987024920C2B1F700E393F4 /* AdamantChatsProvider+fakeMessages.swift in Sources */, 6403F5E222723F7500D58779 /* DashWallet.swift in Sources */, + 93294B822AAD0BB400911109 /* BtcWalletFactory.swift in Sources */, 648DD7A42237DB9E00B811FD /* DogeWalletService+Send.swift in Sources */, + 93294B7D2AAD067000911109 /* AppContainer.swift in Sources */, 41A1994229D2D3920031AD75 /* SwipePanGestureRecognizer.swift in Sources */, E9942B84203CBFCE00C163AF /* AdamantQRTools.swift in Sources */, 4184F1732A33102800D7B8B9 /* AdamantCrashlysticsService.swift in Sources */, @@ -2671,6 +2718,7 @@ E90847362196FEA80095825D /* Chatroom+CoreDataClass.swift in Sources */, E91947B020002393001362F8 /* AdamantApiService.swift in Sources */, E921597B206503000000CA5C /* ButtonsStripeView.swift in Sources */, + 93294B9A2AAD624100911109 /* WalletFactoryCompose.swift in Sources */, E9A03FD820DC0ABA007653A1 /* AdamantNodesSource.swift in Sources */, 41C1698E29E7F36900FEB3CB /* AdamantRichTransactionReplyService.swift in Sources */, 649D6BF221C27D5C009E727B /* SearchResultsViewController.swift in Sources */, @@ -2688,7 +2736,6 @@ 932F77592989F999006D8801 /* ChatCellManager.swift in Sources */, 9377FBE2296C2ACA00C9211B /* ChatTransactionContentView+Model.swift in Sources */, E933475B225539390083839E /* DogeGetTransactionsResponse.swift in Sources */, - 6489794D24CE00C000C33A68 /* SwiftyOnboardPage.swift in Sources */, 9340078029AC341100A20622 /* ChatAction.swift in Sources */, 550066C5284D65DB0044C0B1 /* HealthCheckService.swift in Sources */, 648DD7A02236A59200B811FD /* DogeTransactionDetailsViewController.swift in Sources */, @@ -2698,7 +2745,7 @@ 9324C75E297170600022D7EA /* RichTransactionStatusService.swift in Sources */, 9304F8BE292F88F900173F18 /* ANSPayload.swift in Sources */, 41CA598C29A0D84F002BFDE4 /* TaskManager.swift in Sources */, - E9E7CD9120026FA100DFC4DB /* SwinjectDependencies.swift in Sources */, + E9E7CD9120026FA100DFC4DB /* AppAssembly.swift in Sources */, E96D64CA2295C4A800CA5587 /* WordList.swift in Sources */, 64BD2B7520E2814B00E2CD36 /* EthTransaction.swift in Sources */, E908472B2196FEA80095825D /* RichMessageTransaction+CoreDataProperties.swift in Sources */, @@ -2712,7 +2759,6 @@ 41047B74294C61D10039E956 /* VisibleWalletsService.swift in Sources */, 648CE3AC229AD2190070A2CC /* DashTransferViewController.swift in Sources */, A5E04227282A8BDC0076CD13 /* BtcBalanceResponse.swift in Sources */, - A50A41072822F8CE006BDFE1 /* BtcWalletRoutes.swift in Sources */, 64F085D920E2D7600006DE68 /* AdmTransactionsViewController.swift in Sources */, 9322E87B2970431200B8357C /* ChatMessageFactory.swift in Sources */, 648DD7AA2239150E00B811FD /* DogeWalletService+RichMessageProviderWithStatusCheck.swift in Sources */, @@ -2730,7 +2776,7 @@ 6455E9F121075D3600B2E94C /* AddressBookService.swift in Sources */, 6449BA6D235CA0930033B936 /* ERC20TransactionsViewController.swift in Sources */, E983AE2A20E65F3200497E1A /* AccountViewController.swift in Sources */, - 6449BA70235CA0930033B936 /* ERC20WalletRouter.swift in Sources */, + 6449BA70235CA0930033B936 /* ERC20WalletFactory.swift in Sources */, 4184F1752A33106200D7B8B9 /* CrashlysticsService.swift in Sources */, 4197B9C92952FAFF004CAF64 /* VisibleWalletsCheckmarkView.swift in Sources */, E9E7CD932002740500DFC4DB /* AdamantAccountService.swift in Sources */, @@ -2739,10 +2785,10 @@ 64FA53CD20E1300B006783C9 /* EthTransactionsViewController.swift in Sources */, 6449BA6A235CA0930033B936 /* ERC20Wallet.swift in Sources */, E9147B612050599000145913 /* LoginViewController+QR.swift in Sources */, - 6489794E24CE00C000C33A68 /* SwiftyOnboard.swift in Sources */, 9399F5ED29A85A48006C3E30 /* ChatCacheService.swift in Sources */, 3A9015A92A615893002A2464 /* ChatMessagesListViewModel.swift in Sources */, 41A1995429D56E340031AD75 /* ChatMessageReplyCell.swift in Sources */, + 93294B872AAD0E0A00911109 /* AdmWallet.swift in Sources */, 6449BA6B235CA0930033B936 /* ERC20TransactionDetailsViewController.swift in Sources */, E907350E2256779C00BF02CC /* DogeMainnet.swift in Sources */, 41CE153A297FF98200CC9254 /* Web3Swift+Adamant.swift in Sources */, @@ -2751,13 +2797,12 @@ E9FAE5E2203ED1AE008D3A6B /* ShareQrViewController.swift in Sources */, E983AE2120E655C500497E1A /* AccountHeaderView.swift in Sources */, E971591C2168209800A5F904 /* EthWalletService+RichMessageProviderWithStatusCheck.swift in Sources */, - 644EC35220EFA9A300F40C73 /* DelegateRoutes.swift in Sources */, + 644EC35220EFA9A300F40C73 /* DelegatesFactory.swift in Sources */, E96BBE3121F70F5E009AA738 /* ReadonlyTextView.swift in Sources */, A50A41112822FC35006BDFE1 /* BtcWalletService+RichMessageProviderWithStatusCheck.swift in Sources */, E926E032213EC43B005E536B /* FullscreenAlertView.swift in Sources */, 644EC35B20EFB8E900F40C73 /* AdamantDelegateCell.swift in Sources */, 6403F5DB2272389800D58779 /* (null) in Sources */, - 6416B1A521AEE157006089AC /* LskWalletService+Transfers.swift in Sources */, 648DD7A62237DC4000B811FD /* DogeTransferViewController.swift in Sources */, 93E1234B2A6DFEF7004DF33B /* NotificationStrings.swift in Sources */, E9960B3421F5154300C840A8 /* BaseAccount+CoreDataProperties.swift in Sources */, @@ -2769,13 +2814,12 @@ 6449BA6C235CA0930033B936 /* ERC20WalletViewController.swift in Sources */, E9E7CDC22003F5A400DFC4DB /* TransactionsListViewControllerBase.swift in Sources */, E905D39F204C281400DDB504 /* LoginViewController.swift in Sources */, - E9B1AA592122D59600080A2A /* WalletsRoutes.swift in Sources */, A50A41142822FC35006BDFE1 /* BtcTransferViewController.swift in Sources */, + 93294B8E2AAD2C6B00911109 /* SwiftyOnboardPage.swift in Sources */, E971591A21681D6900A5F904 /* TransactionStatus.swift in Sources */, E96D64B62295BED700CA5587 /* NormalizedTransaction.swift in Sources */, 648CE3A022999C890070A2CC /* BaseBtcTransaction.swift in Sources */, A50A410A2822F8CE006BDFE1 /* BtcWallet.swift in Sources */, - E9E7CDB52002BA6900DFC4DB /* SwinjectedRouter.swift in Sources */, E908472C2196FEA80095825D /* CoreDataAccount+CoreDataClass.swift in Sources */, E9A03FD220DBC0F2007653A1 /* NodeEditorViewController.swift in Sources */, E9393FAA2055D03300EE6F30 /* AdamantMessage.swift in Sources */, @@ -2787,7 +2831,6 @@ 648BCA6D213D384F00875EB5 /* AvatarService.swift in Sources */, E95F856F2007B61D0070534A /* GetPublicKeyResponse.swift in Sources */, 644EC34D20EFA60900F40C73 /* AdamantApi+Delegates.swift in Sources */, - E9E7CDAF2002B8A100DFC4DB /* Router.swift in Sources */, E940088F2119A9E800CD2D67 /* BigInt+Decimal.swift in Sources */, E9E7CDC72003F6D200DFC4DB /* TransactionTableViewCell.swift in Sources */, 938F7D5D2955C8F9001915CA /* ChatLayoutManager.swift in Sources */, @@ -2803,7 +2846,7 @@ E96BBE3321F71290009AA738 /* BuyAndSellViewController.swift in Sources */, 64EAB37422463E020018D9B2 /* CurrencyInfoService.swift in Sources */, 93CC8DC7296F00D6003772BF /* ChatTransactionContainerView.swift in Sources */, - E9E7CDB32002B9FB00DFC4DB /* LoginRoutes.swift in Sources */, + E9E7CDB32002B9FB00DFC4DB /* LoginFactory.swift in Sources */, E941CCDE20E7B70200C96220 /* WalletCollectionViewCell.swift in Sources */, 4186B33A294200F4006594A3 /* DashWalletService+DynamicConstants.swift in Sources */, E9AA8BFA212C166600F9249F /* EthWalletService+Send.swift in Sources */, @@ -2817,7 +2860,7 @@ E91947B22000246A001362F8 /* AdamantError.swift in Sources */, 3A4193912A580C85006A6B22 /* RichTransactionReactService.swift in Sources */, 3AA2D5F7280EADE3000ED971 /* SocketService.swift in Sources */, - E95F85802008C8D70070534A /* ChatsRoutes.swift in Sources */, + E95F85802008C8D70070534A /* ChatListFactory.swift in Sources */, 41A1994429D2D3CF0031AD75 /* MessageModel.swift in Sources */, 93775E462A674FA9009061AC /* Markdown+Adamant.swift in Sources */, 6416B1A721B024B6006089AC /* LskWalletService+Send.swift in Sources */, @@ -2827,10 +2870,11 @@ E99818942120892F0018C84C /* WalletViewControllerBase.swift in Sources */, E9B3D39E201F99F40019EB36 /* DataProvider.swift in Sources */, 93BF4A6C29E4B4BF00505CD0 /* DelegatesBottomPanel+Model.swift in Sources */, + 93294B882AAD0E0A00911109 /* AdmWalletService.swift in Sources */, 648DD7A82239147800B811FD /* DogeWalletService+RichMessageProvider.swift in Sources */, E9E7CDC02003AF6D00DFC4DB /* AdamantCellFactory.swift in Sources */, E9DFB71C21624C9200CF8C7C /* AdmTransactionDetailsViewController.swift in Sources */, - 6403F5E022723F6400D58779 /* DashWalletRouter.swift in Sources */, + 6403F5E022723F6400D58779 /* DashWalletFactory.swift in Sources */, E94008722114EACF00CD2D67 /* WalletAccount.swift in Sources */, E93B0D742028B21400126346 /* ChatsProvider.swift in Sources */, E9CAE8D42018AC1800345E76 /* AdamantApi+Keys.swift in Sources */, @@ -2843,9 +2887,8 @@ 648DD79E2236A0B500B811FD /* DogeTransactionsViewController.swift in Sources */, 64B5736F2209B892005DC968 /* BtcTransactionDetailsViewController.swift in Sources */, 938F7D612955C92B001915CA /* ChatDataSourceManager.swift in Sources */, - E9AA8BFC212C169200F9249F /* EthWalletService+Transfers.swift in Sources */, E96D64C82295C44400CA5587 /* Data+utilites.swift in Sources */, - 64E1C82D222E95E2006C4DA7 /* DogeWalletRoutes.swift in Sources */, + 64E1C82D222E95E2006C4DA7 /* DogeWalletFactory.swift in Sources */, E90055F920ECD86800D0CB2D /* SecurityViewController+StayIn.swift in Sources */, E90847322196FEA80095825D /* TransferTransaction+CoreDataClass.swift in Sources */, 9304F8C6292F971600173F18 /* ApiServiceResult.swift in Sources */, @@ -2860,7 +2903,8 @@ E9204B5220C9762400F3B9AB /* MessageStatus.swift in Sources */, E908471B2196FE590095825D /* Adamant.xcdatamodeld in Sources */, E940087B2114ED0600CD2D67 /* EthWalletService.swift in Sources */, - E948E03B20235E2300975D6B /* SettingsRoutes.swift in Sources */, + 93294B902AAD2C6B00911109 /* SwiftyOnboardOverlay.swift in Sources */, + E948E03B20235E2300975D6B /* SettingsFactory.swift in Sources */, E9CAE8D82018ACA700345E76 /* AdamantApi+Transfers.swift in Sources */, 4186B3302941E642006594A3 /* AdmWalletService+DynamicConstants.swift in Sources */, E95F85852008CB3A0070534A /* ChatListViewController.swift in Sources */, @@ -2871,7 +2915,7 @@ 5558A438282AB9390024DDD6 /* NodeStatus.swift in Sources */, E91947AC20001A9A001362F8 /* ApiService.swift in Sources */, 4164A9D928F17DA700EEF16D /* AdamantChatTransactionService.swift in Sources */, - E993302221354BC300CD5200 /* EthWalletRoutes.swift in Sources */, + E993302221354BC300CD5200 /* EthWalletFactory.swift in Sources */, 418FDE502A25CA340055E3CD /* ChatMenuManager.swift in Sources */, E90055F520EBF5DA00D0CB2D /* AboutViewController.swift in Sources */, E965A53020B594120041A3EA /* AdamantApi+States.swift in Sources */, @@ -2888,11 +2932,10 @@ 55E69E172868D7920025D82E /* CheckmarkView.swift in Sources */, 9304F8C2292F895C00173F18 /* PushNotificationsTokenService.swift in Sources */, E940086B2114A70600CD2D67 /* LskAccount.swift in Sources */, - 6416B19D21AD7B92006089AC /* LskWalletRoutes.swift in Sources */, + 6416B19D21AD7B92006089AC /* LskWalletFactory.swift in Sources */, E9B3D3A1201FA26B0019EB36 /* AdamantAccountsProvider.swift in Sources */, E9FAE5DA203DBFEF008D3A6B /* Comparable+clamped.swift in Sources */, 93A91FD329799298001DB1F8 /* ChatStartPosition.swift in Sources */, - E94008802114EE2000CD2D67 /* AdmWallet.swift in Sources */, 93A91FD1297972B7001DB1F8 /* ChatScrollDownButton.swift in Sources */, 41C1698C29E7F34900FEB3CB /* RichTransactionReplyService.swift in Sources */, E9A03FDA20DC0B14007653A1 /* NodesSource.swift in Sources */, @@ -2907,6 +2950,7 @@ E9E7CDBE2003AEFB00DFC4DB /* CellFactory.swift in Sources */, 411743022A39B208008CD98A /* ContributeState.swift in Sources */, 93F391502962F5D400BFD6AE /* SpinnerView.swift in Sources */, + 93A18C892AAEAE7700D0AB98 /* WalletFactory.swift in Sources */, E923222621135F9000A7E5AF /* EthAccount.swift in Sources */, E9061B97207501E40011F104 /* AdamantUserInfoKey.swift in Sources */, E9CAE8D62018AC5300345E76 /* AdamantApi+Transactions.swift in Sources */, @@ -2915,6 +2959,7 @@ 6416B1A121AD7D93006089AC /* LskTransferViewController.swift in Sources */, A5E0422B282AB18B0076CD13 /* BtcUnspentTransactionResponse.swift in Sources */, E972206B201F44CA004F2AAD /* TransfersProvider.swift in Sources */, + 93294B962AAD320B00911109 /* ScreensFactory.swift in Sources */, E9FEECA421413659007DD7C8 /* RichMessageProvider.swift in Sources */, 3A9015A72A614A62002A2464 /* AdamantEmojiService.swift in Sources */, 648C696F22915A12006645F5 /* DashTransaction.swift in Sources */, @@ -2927,6 +2972,7 @@ 4186B334294200C5006594A3 /* EthWalletService+DynamicConstants.swift in Sources */, 644EC34F20EFA77A00F40C73 /* Delegate.swift in Sources */, 64EAB37622463F680018D9B2 /* AdamantCurrencyInfoService.swift in Sources */, + 93294B842AAD0C8F00911109 /* Assembler+Extension.swift in Sources */, 938F7D5B2955C8DA001915CA /* ChatDisplayManager.swift in Sources */, E9722068201F42CC004F2AAD /* InMemoryCoreDataStack.swift in Sources */, 4133AED429769EEC00F3D017 /* UpdatingIndicatorView.swift in Sources */, @@ -2946,13 +2992,14 @@ 649D6BEC21BD5A53009E727B /* UISuffixTextField.swift in Sources */, E93B0D762028B28E00126346 /* AdamantChatsProvider.swift in Sources */, 3A33F9FA2A7A53DA002B8003 /* EmojiUpdateType.swift in Sources */, - E993302021354B1800CD5200 /* AdmWalletRoutes.swift in Sources */, - E9332B8921F1FA4400D56E72 /* OnboardRoutes.swift in Sources */, + E993302021354B1800CD5200 /* AdmWalletFactory.swift in Sources */, + E9332B8921F1FA4400D56E72 /* OnboardFactory.swift in Sources */, 938F7D722955CE72001915CA /* ChatFactory.swift in Sources */, 938F7D5F2955C90D001915CA /* ChatInputBarManager.swift in Sources */, E908472D2196FEA80095825D /* CoreDataAccount+CoreDataProperties.swift in Sources */, 64FA53D120E24942006783C9 /* TransactionDetailsViewControllerBase.swift in Sources */, 41047B76294C62710039E956 /* AdamantVisibleWalletsService.swift in Sources */, + 93294B982AAD364F00911109 /* AdamantScreensFactory.swift in Sources */, 64BD2B7720E2820300E2CD36 /* TransactionDetails.swift in Sources */, 3A9015A52A614A18002A2464 /* EmojiService.swift in Sources */, 9322E875297042F000B8357C /* ChatSender.swift in Sources */, @@ -2971,6 +3018,7 @@ 5558A436282AAFCC0024DDD6 /* AdamantApi+Status.swift in Sources */, E921534E20EE1E8700C0843F /* EurekaAlertLabelRow.swift in Sources */, 64C65F4523893C7600DC0425 /* OnboardOverlay.swift in Sources */, + 93A18C862AAEACC100D0AB98 /* AdamantWalletFactoryCompose.swift in Sources */, E9484B79227C617E008E10F0 /* BalanceTableViewCell.swift in Sources */, E90847352196FEA80095825D /* MessageTransaction+CoreDataProperties.swift in Sources */, 4186B336294200D2006594A3 /* LskWalletService+DynamicConstants.swift in Sources */, @@ -2981,14 +3029,13 @@ 41A1994629D2FCF80031AD75 /* ReplyView.swift in Sources */, E90847342196FEA80095825D /* MessageTransaction+CoreDataClass.swift in Sources */, E9960B3521F5154300C840A8 /* DummyAccount+CoreDataClass.swift in Sources */, - E94008892114F0F700CD2D67 /* AdmWalletService.swift in Sources */, 64EE46B220FE0C8D00194DDA /* LskTransactionsViewController.swift in Sources */, E94008832114EE4700CD2D67 /* LskWallet.swift in Sources */, 64E1C833222EA0F0006C4DA7 /* DogeWalletViewController.swift in Sources */, - E93EB09F20DA3FA4001F9601 /* NodesEditorRoutes.swift in Sources */, - 6489794F24CE00C000C33A68 /* SwiftyOnboardOverlay.swift in Sources */, + E93EB09F20DA3FA4001F9601 /* NodesEditorFactory.swift in Sources */, + 93294B8F2AAD2C6B00911109 /* SwiftyOnboard.swift in Sources */, 41BCB310295C6082004B12AB /* VisibleWalletsResetTableViewCell.swift in Sources */, - E9E7CDB12002B97B00DFC4DB /* AccountRoutes.swift in Sources */, + E9E7CDB12002B97B00DFC4DB /* AccountFactory.swift in Sources */, E9AA8BF82129F13000F9249F /* ComplexTransferViewController.swift in Sources */, E9A174B52057EDCE003667CD /* AdamantTransfersProvider+backgroundFetch.swift in Sources */, 9382F61329DEC0A3005E6216 /* ChatModelView.swift in Sources */, diff --git a/Adamant/CoreData/Adamant.xcdatamodeld/Adamant.xcdatamodel/contents b/Adamant/Adamant.xcdatamodeld/Adamant.xcdatamodel/contents similarity index 100% rename from Adamant/CoreData/Adamant.xcdatamodeld/Adamant.xcdatamodel/contents rename to Adamant/Adamant.xcdatamodeld/Adamant.xcdatamodel/contents diff --git a/Adamant/AppDelegate.swift b/Adamant/App/AppDelegate.swift similarity index 96% rename from Adamant/AppDelegate.swift rename to Adamant/App/AppDelegate.swift index 74820c1a2..d668499db 100644 --- a/Adamant/AppDelegate.swift +++ b/Adamant/App/AppDelegate.swift @@ -40,7 +40,8 @@ extension StoreKey { class AppDelegate: UIResponder, UIApplicationDelegate { var window: UIWindow? var repeater: RepeaterService! - var container: Container! + var container: AppContainer! + var screensFactory: ScreensFactory! // MARK: Dependencies var accountService: AccountService! @@ -57,8 +58,8 @@ class AppDelegate: UIResponder, UIApplicationDelegate { KeychainStore.migrateIfNeeded() // MARK: 1. Initiating Swinject - container = Container() - container.registerAdamantServices() + container = AppContainer() + screensFactory = AdamantScreensFactory(assembler: container.assembler) accountService = container.resolve(AccountService.self) notificationService = container.resolve(NotificationsService.self) dialogService = container.resolve(DialogService.self) @@ -93,16 +94,12 @@ class AppDelegate: UIResponder, UIApplicationDelegate { window.tintColor = UIColor.adamant.primary // MARK: 3. Prepare pages - guard let router = container.resolve(Router.self) else { - fatalError("Failed to get Router") - } - let chatList = UINavigationController( - rootViewController: router.get(scene: AdamantScene.Chats.chatList) + rootViewController: screensFactory.makeChatList() ) let account = UINavigationController( - rootViewController: router.get(scene: AdamantScene.Account.account) + rootViewController: screensFactory.makeAccount() ) let tabScreens: TabScreens = UIScreen.main.traitCollection.userInterfaceIdiom == .pad @@ -156,7 +153,7 @@ class AppDelegate: UIResponder, UIApplicationDelegate { dialogService.setup(window: window) // MARK: 5. Show login - let login = router.get(scene: AdamantScene.Login.login) as! LoginViewController + let login = screensFactory.makeLogin() let welcomeIsShown = UserDefaults.standard.bool(forKey: StoreKey.application.welcomeScreensIsShown) login.requestBiometryOnFirstTimeActive = welcomeIsShown @@ -164,7 +161,7 @@ class AppDelegate: UIResponder, UIApplicationDelegate { window.rootViewController?.present(login, animated: false, completion: nil) if !welcomeIsShown { - let welcome = router.get(scene: AdamantScene.Onboard.welcome) + let welcome = screensFactory.makeOnboard() welcome.modalPresentationStyle = .overFullScreen login.present(welcome, animated: true, completion: nil) UserDefaults.standard.set(true, forKey: StoreKey.application.welcomeScreensIsShown) @@ -443,8 +440,7 @@ extension AppDelegate: UNUserNotificationCenterDelegate { // MARK: - Background Fetch extension AppDelegate { func application(_ application: UIApplication, performFetchWithCompletionHandler completionHandler: @escaping (UIBackgroundFetchResult) -> Void) { - let container = Container() - container.registerAdamantServices() + let container = AppContainer() guard let notificationsService = container.resolve(NotificationsService.self) else { UIApplication.shared.setMinimumBackgroundFetchInterval(UIApplication.backgroundFetchIntervalNever) @@ -621,11 +617,7 @@ extension AppDelegate { var chatList: UINavigationController? var chatDetail: ChatListViewController? - guard let tabbar = window?.rootViewController as? UITabBarController, - let router = container.resolve(Router.self) - else { - return - } + guard let tabbar = window?.rootViewController as? UITabBarController else { return } if let split = tabbar.viewControllers?.first as? UISplitViewController, let navigation = split.viewControllers.first as? UINavigationController, @@ -652,7 +644,6 @@ extension AppDelegate { with: adamantAdr, chatList: chatList, tabbar: tabbar, - router: router, chatDetail: chatDetail ) } @@ -663,17 +654,13 @@ extension AppDelegate { with adamantAdr: AdamantAddress, chatList: UINavigationController, tabbar: UITabBarController, - router: Router, chatDetail: ChatListViewController ) { chatList.popToRootViewController(animated: false) chatList.dismiss(animated: false, completion: nil) tabbar.selectedIndex = 0 - let newChat = router.get(scene: AdamantScene.Chats.newChat) as? NewChatViewController - - guard let newChat = newChat else { return } - + let newChat = screensFactory.makeNewChat() newChat.delegate = chatDetail.self if let split = chatDetail.splitViewController { diff --git a/Adamant/SwinjectDependencies.swift b/Adamant/App/DI/AppAssembly.swift similarity index 71% rename from Adamant/SwinjectDependencies.swift rename to Adamant/App/DI/AppAssembly.swift index 50d8a98a5..545daf49d 100644 --- a/Adamant/SwinjectDependencies.swift +++ b/Adamant/App/DI/AppAssembly.swift @@ -1,5 +1,5 @@ // -// SwinjectDependencies.swift +// AppAssembly.swift // Adamant // // Created by Anokhov Pavel on 07.01.2018. @@ -10,43 +10,35 @@ import Swinject import BitcoinKit import CommonKit -// MARK: - Services -extension Container { - func registerAdamantServices() { +struct AppAssembly: Assembly { + func assemble(container: Container) { // MARK: - Standalone services // MARK: AdamantCore - self.register(AdamantCore.self) { _ in NativeAdamantCore() }.inObjectScope(.container) - - // MARK: Router - self.register(Router.self) { _ in - let router = SwinjectedRouter() - router.container = self - return router - }.inObjectScope(.container) + container.register(AdamantCore.self) { _ in NativeAdamantCore() }.inObjectScope(.container) // MARK: CellFactory - self.register(CellFactory.self) { _ in AdamantCellFactory() }.inObjectScope(.container) + container.register(CellFactory.self) { _ in AdamantCellFactory() }.inObjectScope(.container) // MARK: Secured Store - self.register(SecuredStore.self) { _ in KeychainStore() }.inObjectScope(.container) + container.register(SecuredStore.self) { _ in KeychainStore() }.inObjectScope(.container) // MARK: LocalAuthentication - self.register(LocalAuthentication.self) { _ in AdamantAuthentication() }.inObjectScope(.container) + container.register(LocalAuthentication.self) { _ in AdamantAuthentication() }.inObjectScope(.container) // MARK: Reachability - self.register(ReachabilityMonitor.self) { _ in AdamantReachability() }.inObjectScope(.container) + container.register(ReachabilityMonitor.self) { _ in AdamantReachability() }.inObjectScope(.container) // MARK: AdamantAvatarService - self.register(AvatarService.self) { _ in AdamantAvatarService() }.inObjectScope(.container) + container.register(AvatarService.self) { _ in AdamantAvatarService() }.inObjectScope(.container) // MARK: - Services with dependencies // MARK: DialogService - self.register(DialogService.self) { r in - AdamantDialogService(router: r.resolve(Router.self)!) + container.register(DialogService.self) { r in + AdamantDialogService() }.inObjectScope(.container) // MARK: Notifications - self.register(NotificationsService.self) { r in + container.register(NotificationsService.self) { r in AdamantNotificationsService(securedStore: r.resolve(SecuredStore.self)!) }.initCompleted { (r, c) in // Weak reference Task { @MainActor in @@ -56,7 +48,7 @@ extension Container { }.inObjectScope(.container) // MARK: VisibleWalletsService - self.register(VisibleWalletsService.self) { r in + container.register(VisibleWalletsService.self) { r in AdamantVisibleWalletsService( securedStore: r.resolve(SecuredStore.self)!, accountService: r.resolve(AccountService.self)! @@ -64,28 +56,28 @@ extension Container { }.inObjectScope(.container) // MARK: IncreaseFeeService - self.register(IncreaseFeeService.self) { r in + container.register(IncreaseFeeService.self) { r in AdamantIncreaseFeeService( securedStore: r.resolve(SecuredStore.self)! ) }.inObjectScope(.container) // MARK: EmojiService - self.register(EmojiService.self) { r in + container.register(EmojiService.self) { r in AdamantEmojiService( securedStore: r.resolve(SecuredStore.self)! ) }.inObjectScope(.container) // MARK: CrashlysticsService - self.register(CrashlyticsService.self) { r in + container.register(CrashlyticsService.self) { r in AdamantCrashlyticsService( securedStore: r.resolve(SecuredStore.self)! ) }.inObjectScope(.container) // MARK: PushNotificationsTokenService - self.register(PushNotificationsTokenService.self) { r in + container.register(PushNotificationsTokenService.self) { r in AdamantPushNotificationsTokenService( securedStore: r.resolve(SecuredStore.self)!, apiService: r.resolve(ApiService.self)!, @@ -95,7 +87,7 @@ extension Container { }.inObjectScope(.container) // MARK: NodesSource - self.register(NodesSource.self) { r in + container.register(NodesSource.self) { r in AdamantNodesSource( apiService: r.resolve(ApiService.self)!, healthCheckService: r.resolve(HealthCheckService.self)!, @@ -105,7 +97,7 @@ extension Container { }.inObjectScope(.container) // MARK: ApiService - self.register(ApiService.self) { r in + container.register(ApiService.self) { r in AdamantApiService(adamantCore: r.resolve(AdamantCore.self)!) }.initCompleted { (r, c) in // Weak reference Task { @MainActor in @@ -115,12 +107,12 @@ extension Container { }.inObjectScope(.container) // MARK: HealthCheckService - self.register(HealthCheckService.self) { r in + container.register(HealthCheckService.self) { r in AdamantHealthCheckService(apiService: r.resolve(ApiService.self)!) }.inObjectScope(.container) // MARK: SocketService - self.register(SocketService.self) { _ in + container.register(SocketService.self) { _ in AdamantSocketService() }.initCompleted { (r, c) in // Weak reference guard let service = c as? AdamantSocketService else { return } @@ -128,7 +120,7 @@ extension Container { }.inObjectScope(.container) // MARK: AccountService - self.register(AccountService.self) { r in + container.register(AccountService.self) { r in AdamantAccountService( apiService: r.resolve(ApiService.self)!, adamantCore: r.resolve(AdamantCore.self)!, @@ -143,13 +135,13 @@ extension Container { service.currencyInfoService = r.resolve(CurrencyInfoService.self)! service.visibleWalletService = r.resolve(VisibleWalletsService.self)! for case let wallet as SwinjectDependentService in service.wallets { - wallet.injectDependencies(from: self) + wallet.injectDependencies(from: container) } } } // MARK: AddressBookServeice - self.register(AddressBookService.self) { r in + container.register(AddressBookService.self) { r in AdamantAddressBookService( apiService: r.resolve(ApiService.self)!, adamantCore: r.resolve(AdamantCore.self)!, @@ -159,7 +151,7 @@ extension Container { }.inObjectScope(.container) // MARK: CurrencyInfoService - self.register(CurrencyInfoService.self) { r in + container.register(CurrencyInfoService.self) { r in AdamantCurrencyInfoService(securedStore: r.resolve(SecuredStore.self)!) }.inObjectScope(.container).initCompleted { (r, c) in guard let service = c as? AdamantCurrencyInfoService else { return } @@ -168,12 +160,12 @@ extension Container { // MARK: - Data Providers // MARK: CoreData Stack - self.register(CoreDataStack.self) { _ in + container.register(CoreDataStack.self) { _ in try! InMemoryCoreDataStack(modelUrl: AdamantResources.coreDataModel) }.inObjectScope(.container) // MARK: Accounts - self.register(AccountsProvider.self) { r in + container.register(AccountsProvider.self) { r in AdamantAccountsProvider( stack: r.resolve(CoreDataStack.self)!, apiService: r.resolve(ApiService.self)!, @@ -182,7 +174,7 @@ extension Container { }.inObjectScope(.container) // MARK: Transfers - self.register(TransfersProvider.self) { r in + container.register(TransfersProvider.self) { r in AdamantTransfersProvider( apiService: r.resolve(ApiService.self)!, stack: r.resolve(CoreDataStack.self)!, @@ -196,7 +188,7 @@ extension Container { }.inObjectScope(.container) // MARK: Chats - self.register(ChatsProvider.self) { r in + container.register(ChatsProvider.self) { r in AdamantChatsProvider( accountService: r.resolve(AccountService.self)!, apiService: r.resolve(ApiService.self)!, @@ -210,37 +202,15 @@ extension Container { }.inObjectScope(.container) // MARK: Chat Transaction Service - self.register(ChatTransactionService.self) { r in + container.register(ChatTransactionService.self) { r in AdamantChatTransactionService( adamantCore: r.resolve(AdamantCore.self)!, accountService: r.resolve(AccountService.self)! ) }.inObjectScope(.container) - // MARK: Chat screen factory - self.register(ChatFactory.self) { r in - ChatFactory( - chatsProvider: r.resolve(ChatsProvider.self)!, - dialogService: r.resolve(DialogService.self)!, - transferProvider: r.resolve(TransfersProvider.self)!, - accountService: r.resolve(AccountService.self)!, - accountProvider: r.resolve(AccountsProvider.self)!, - richTransactionStatusService: r.resolve(RichTransactionStatusService.self)!, - addressBookService: r.resolve(AddressBookService.self)!, - visibleWalletService: r.resolve(VisibleWalletsService.self)!, - avatarService: r.resolve(AvatarService.self)!, - emojiService: r.resolve(EmojiService.self)!, - router: r.resolve(Router.self)! - ) - }.inObjectScope(.container) - - // MARK: Contribute screen factory - self.register(ContributeFactory.self) { r in - ContributeFactory(crashliticsService: r.resolve(CrashlyticsService.self)!) - }.inObjectScope(.container) - // MARK: Rich transaction status service - self.register(RichTransactionStatusService.self) { r in + container.register(RichTransactionStatusService.self) { r in let accountService = r.resolve(AccountService.self)! let richProviders = accountService.wallets @@ -254,7 +224,7 @@ extension Container { }.inObjectScope(.container) // MARK: Rich transaction reply service - self.register(RichTransactionReplyService.self) { r in + container.register(RichTransactionReplyService.self) { r in AdamantRichTransactionReplyService( coreDataStack: r.resolve(CoreDataStack.self)!, apiService: r.resolve(ApiService.self)!, @@ -264,7 +234,7 @@ extension Container { }.inObjectScope(.container) // MARK: Rich transaction react service - self.register(RichTransactionReactService.self) { r in + container.register(RichTransactionReactService.self) { r in AdamantRichTransactionReactService( coreDataStack: r.resolve(CoreDataStack.self)!, apiService: r.resolve(ApiService.self)!, @@ -274,7 +244,7 @@ extension Container { }.inObjectScope(.container) // MARK: Bitcoin AddressConverterFactory - self.register(AddressConverterFactory.self) { _ in + container.register(AddressConverterFactory.self) { _ in AddressConverterFactory() }.inObjectScope(.container) } diff --git a/Adamant/App/DI/AppContainer.swift b/Adamant/App/DI/AppContainer.swift new file mode 100644 index 000000000..3f8fa7602 --- /dev/null +++ b/Adamant/App/DI/AppContainer.swift @@ -0,0 +1,17 @@ +// +// AppContainer.swift +// Adamant +// +// Created by Andrew G on 09.09.2023. +// Copyright © 2023 Adamant. All rights reserved. +// + +import Swinject + +struct AppContainer { + let assembler = Assembler([AppAssembly()]) + + func resolve(_ type: T.Type) -> T? { + assembler.resolve(T.self) + } +} diff --git a/Adamant/Helpers/Assembler+Extension.swift b/Adamant/Helpers/Assembler+Extension.swift new file mode 100644 index 000000000..8f2b58079 --- /dev/null +++ b/Adamant/Helpers/Assembler+Extension.swift @@ -0,0 +1,15 @@ +// +// Assembler+Extension.swift +// Adamant +// +// Created by Andrew G on 09.09.2023. +// Copyright © 2023 Adamant. All rights reserved. +// + +import Swinject + +extension Assembler { + func resolve(_ type: T.Type) -> T? { + resolver.resolve(T.self) + } +} diff --git a/Adamant/CoreData/AdamantResources+CoreData.swift b/Adamant/Models/CoreData/AdamantResources+CoreData.swift similarity index 100% rename from Adamant/CoreData/AdamantResources+CoreData.swift rename to Adamant/Models/CoreData/AdamantResources+CoreData.swift diff --git a/Adamant/CoreData/BaseAccount+CoreDataClass.swift b/Adamant/Models/CoreData/BaseAccount+CoreDataClass.swift similarity index 100% rename from Adamant/CoreData/BaseAccount+CoreDataClass.swift rename to Adamant/Models/CoreData/BaseAccount+CoreDataClass.swift diff --git a/Adamant/CoreData/BaseAccount+CoreDataProperties.swift b/Adamant/Models/CoreData/BaseAccount+CoreDataProperties.swift similarity index 100% rename from Adamant/CoreData/BaseAccount+CoreDataProperties.swift rename to Adamant/Models/CoreData/BaseAccount+CoreDataProperties.swift diff --git a/Adamant/CoreData/BaseTransaction+CoreDataClass.swift b/Adamant/Models/CoreData/BaseTransaction+CoreDataClass.swift similarity index 100% rename from Adamant/CoreData/BaseTransaction+CoreDataClass.swift rename to Adamant/Models/CoreData/BaseTransaction+CoreDataClass.swift diff --git a/Adamant/CoreData/BaseTransaction+CoreDataProperties.swift b/Adamant/Models/CoreData/BaseTransaction+CoreDataProperties.swift similarity index 100% rename from Adamant/CoreData/BaseTransaction+CoreDataProperties.swift rename to Adamant/Models/CoreData/BaseTransaction+CoreDataProperties.swift diff --git a/Adamant/CoreData/BaseTransaction+TransactionDetails.swift b/Adamant/Models/CoreData/BaseTransaction+TransactionDetails.swift similarity index 100% rename from Adamant/CoreData/BaseTransaction+TransactionDetails.swift rename to Adamant/Models/CoreData/BaseTransaction+TransactionDetails.swift diff --git a/Adamant/CoreData/ChatTransaction+CoreDataClass.swift b/Adamant/Models/CoreData/ChatTransaction+CoreDataClass.swift similarity index 100% rename from Adamant/CoreData/ChatTransaction+CoreDataClass.swift rename to Adamant/Models/CoreData/ChatTransaction+CoreDataClass.swift diff --git a/Adamant/CoreData/ChatTransaction+CoreDataProperties.swift b/Adamant/Models/CoreData/ChatTransaction+CoreDataProperties.swift similarity index 100% rename from Adamant/CoreData/ChatTransaction+CoreDataProperties.swift rename to Adamant/Models/CoreData/ChatTransaction+CoreDataProperties.swift diff --git a/Adamant/CoreData/Chatroom+CoreDataClass.swift b/Adamant/Models/CoreData/Chatroom+CoreDataClass.swift similarity index 100% rename from Adamant/CoreData/Chatroom+CoreDataClass.swift rename to Adamant/Models/CoreData/Chatroom+CoreDataClass.swift diff --git a/Adamant/CoreData/Chatroom+CoreDataProperties.swift b/Adamant/Models/CoreData/Chatroom+CoreDataProperties.swift similarity index 100% rename from Adamant/CoreData/Chatroom+CoreDataProperties.swift rename to Adamant/Models/CoreData/Chatroom+CoreDataProperties.swift diff --git a/Adamant/CoreData/CoreDataAccount+CoreDataClass.swift b/Adamant/Models/CoreData/CoreDataAccount+CoreDataClass.swift similarity index 100% rename from Adamant/CoreData/CoreDataAccount+CoreDataClass.swift rename to Adamant/Models/CoreData/CoreDataAccount+CoreDataClass.swift diff --git a/Adamant/CoreData/CoreDataAccount+CoreDataProperties.swift b/Adamant/Models/CoreData/CoreDataAccount+CoreDataProperties.swift similarity index 100% rename from Adamant/CoreData/CoreDataAccount+CoreDataProperties.swift rename to Adamant/Models/CoreData/CoreDataAccount+CoreDataProperties.swift diff --git a/Adamant/CoreData/DummyAccount+CoreDataClass.swift b/Adamant/Models/CoreData/DummyAccount+CoreDataClass.swift similarity index 100% rename from Adamant/CoreData/DummyAccount+CoreDataClass.swift rename to Adamant/Models/CoreData/DummyAccount+CoreDataClass.swift diff --git a/Adamant/CoreData/DummyAccount+CoreDataProperties.swift b/Adamant/Models/CoreData/DummyAccount+CoreDataProperties.swift similarity index 100% rename from Adamant/CoreData/DummyAccount+CoreDataProperties.swift rename to Adamant/Models/CoreData/DummyAccount+CoreDataProperties.swift diff --git a/Adamant/CoreData/MessageTransaction+CoreDataClass.swift b/Adamant/Models/CoreData/MessageTransaction+CoreDataClass.swift similarity index 100% rename from Adamant/CoreData/MessageTransaction+CoreDataClass.swift rename to Adamant/Models/CoreData/MessageTransaction+CoreDataClass.swift diff --git a/Adamant/CoreData/MessageTransaction+CoreDataProperties.swift b/Adamant/Models/CoreData/MessageTransaction+CoreDataProperties.swift similarity index 100% rename from Adamant/CoreData/MessageTransaction+CoreDataProperties.swift rename to Adamant/Models/CoreData/MessageTransaction+CoreDataProperties.swift diff --git a/Adamant/CoreData/RichMessageTransaction+CoreDataClass.swift b/Adamant/Models/CoreData/RichMessageTransaction+CoreDataClass.swift similarity index 100% rename from Adamant/CoreData/RichMessageTransaction+CoreDataClass.swift rename to Adamant/Models/CoreData/RichMessageTransaction+CoreDataClass.swift diff --git a/Adamant/CoreData/RichMessageTransaction+CoreDataProperties.swift b/Adamant/Models/CoreData/RichMessageTransaction+CoreDataProperties.swift similarity index 100% rename from Adamant/CoreData/RichMessageTransaction+CoreDataProperties.swift rename to Adamant/Models/CoreData/RichMessageTransaction+CoreDataProperties.swift diff --git a/Adamant/CoreData/TransferTransaction+CoreDataClass.swift b/Adamant/Models/CoreData/TransferTransaction+CoreDataClass.swift similarity index 100% rename from Adamant/CoreData/TransferTransaction+CoreDataClass.swift rename to Adamant/Models/CoreData/TransferTransaction+CoreDataClass.swift diff --git a/Adamant/CoreData/TransferTransaction+CoreDataProperties.swift b/Adamant/Models/CoreData/TransferTransaction+CoreDataProperties.swift similarity index 100% rename from Adamant/CoreData/TransferTransaction+CoreDataProperties.swift rename to Adamant/Models/CoreData/TransferTransaction+CoreDataProperties.swift diff --git a/Adamant/Models/DashTransaction.swift b/Adamant/Models/DashTransaction.swift index b06362f3d..d0ec40287 100644 --- a/Adamant/Models/DashTransaction.swift +++ b/Adamant/Models/DashTransaction.swift @@ -9,7 +9,7 @@ import Foundation import BitcoinKit -class DashTransaction: BaseBtcTransaction { +final class DashTransaction: BaseBtcTransaction { override var defaultCurrencySymbol: String? { DashWalletService.currencySymbol } } diff --git a/Adamant/Models/Delegate.swift b/Adamant/Models/Delegate.swift index be2241248..4c1869d7d 100644 --- a/Adamant/Models/Delegate.swift +++ b/Adamant/Models/Delegate.swift @@ -9,7 +9,7 @@ import Foundation import CommonKit -class Delegate: Decodable { +final class Delegate: Decodable { let username: String let address: String let publicKey: String diff --git a/Adamant/Models/DogeTransaction.swift b/Adamant/Models/DogeTransaction.swift index 753e7242b..dbe88fbe6 100644 --- a/Adamant/Models/DogeTransaction.swift +++ b/Adamant/Models/DogeTransaction.swift @@ -23,7 +23,7 @@ extension String.adamant { } } -class DogeTransaction: BaseBtcTransaction { +final class DogeTransaction: BaseBtcTransaction { override var defaultCurrencySymbol: String? { DogeWalletService.currencySymbol } } diff --git a/Adamant/ServerResponses/BTCRPCServerResponce.swift b/Adamant/Models/ServerResponses/BTCRPCServerResponce.swift similarity index 74% rename from Adamant/ServerResponses/BTCRPCServerResponce.swift rename to Adamant/Models/ServerResponses/BTCRPCServerResponce.swift index c4d3d2d6f..cc251bbe5 100644 --- a/Adamant/ServerResponses/BTCRPCServerResponce.swift +++ b/Adamant/Models/ServerResponses/BTCRPCServerResponce.swift @@ -8,13 +8,13 @@ import Foundation -class BTCRPCServerResponce: Decodable { +final class BTCRPCServerResponce: Decodable { let result: T? let error: BTCRPCError? let id: String? } -class BTCRPCError: Decodable { +final class BTCRPCError: Decodable { let code: Int let message: String } diff --git a/Adamant/ServerResponses/DogeGetTransactionsResponse.swift b/Adamant/Models/ServerResponses/DogeGetTransactionsResponse.swift similarity index 87% rename from Adamant/ServerResponses/DogeGetTransactionsResponse.swift rename to Adamant/Models/ServerResponses/DogeGetTransactionsResponse.swift index 6ed3a6109..460e3cf25 100644 --- a/Adamant/ServerResponses/DogeGetTransactionsResponse.swift +++ b/Adamant/Models/ServerResponses/DogeGetTransactionsResponse.swift @@ -8,7 +8,7 @@ import Foundation -class DogeGetTransactionsResponse: Decodable { +final class DogeGetTransactionsResponse: Decodable { let totalItems: Int let from: Int let to: Int diff --git a/Adamant/ServerResponses/GetPublicKeyResponse.swift b/Adamant/Models/ServerResponses/GetPublicKeyResponse.swift similarity index 100% rename from Adamant/ServerResponses/GetPublicKeyResponse.swift rename to Adamant/Models/ServerResponses/GetPublicKeyResponse.swift diff --git a/Adamant/ServerResponses/ServerResponseWithTimestamp.swift b/Adamant/Models/ServerResponses/ServerResponseWithTimestamp.swift similarity index 100% rename from Adamant/ServerResponses/ServerResponseWithTimestamp.swift rename to Adamant/Models/ServerResponses/ServerResponseWithTimestamp.swift diff --git a/Adamant/ServerResponses/TransactionIdResponse.swift b/Adamant/Models/ServerResponses/TransactionIdResponse.swift similarity index 100% rename from Adamant/ServerResponses/TransactionIdResponse.swift rename to Adamant/Models/ServerResponses/TransactionIdResponse.swift diff --git a/Adamant/Modules/Account/AccountFactory.swift b/Adamant/Modules/Account/AccountFactory.swift new file mode 100644 index 000000000..dbc7a18a1 --- /dev/null +++ b/Adamant/Modules/Account/AccountFactory.swift @@ -0,0 +1,28 @@ +// +// AccountFactory.swift +// Adamant +// +// Created by Anokhov Pavel on 07.01.2018. +// Copyright © 2018 Adamant. All rights reserved. +// + +import Swinject +import UIKit + +struct AccountFactory { + let assembler: Assembler + + func makeViewController(screensFactory: ScreensFactory) -> UIViewController { + let c = AccountViewController() + c.accountService = assembler.resolve(AccountService.self) + c.dialogService = assembler.resolve(DialogService.self) + c.notificationsService = assembler.resolve(NotificationsService.self) + c.transfersProvider = assembler.resolve(TransfersProvider.self) + c.localAuth = assembler.resolve(LocalAuthentication.self) + c.avatarService = assembler.resolve(AvatarService.self) + c.currencyInfoService = assembler.resolve(CurrencyInfoService.self) + c.visibleWalletsService = assembler.resolve(VisibleWalletsService.self) + c.screensFactory = screensFactory + return c + } +} diff --git a/Adamant/Stories/Account/AccountFooter.xib b/Adamant/Modules/Account/AccountFooter.xib similarity index 100% rename from Adamant/Stories/Account/AccountFooter.xib rename to Adamant/Modules/Account/AccountFooter.xib diff --git a/Adamant/Stories/Account/AccountHeader.xib b/Adamant/Modules/Account/AccountHeader.xib similarity index 100% rename from Adamant/Stories/Account/AccountHeader.xib rename to Adamant/Modules/Account/AccountHeader.xib diff --git a/Adamant/Stories/Account/AccountHeaderView.swift b/Adamant/Modules/Account/AccountHeaderView.swift similarity index 93% rename from Adamant/Stories/Account/AccountHeaderView.swift rename to Adamant/Modules/Account/AccountHeaderView.swift index d8e0b166e..f1b4b3d35 100644 --- a/Adamant/Stories/Account/AccountHeaderView.swift +++ b/Adamant/Modules/Account/AccountHeaderView.swift @@ -12,7 +12,7 @@ protocol AccountHeaderViewDelegate: AnyObject { func addressLabelTapped(from: UIView) } -class AccountHeaderView: UIView { +final class AccountHeaderView: UIView { // MARK: - IBOutlets @IBOutlet weak var avatarImageView: UIImageView! diff --git a/Adamant/Stories/Account/AccountViewController+StayIn.swift b/Adamant/Modules/Account/AccountViewController+StayIn.swift similarity index 100% rename from Adamant/Stories/Account/AccountViewController+StayIn.swift rename to Adamant/Modules/Account/AccountViewController+StayIn.swift diff --git a/Adamant/Stories/Account/AccountViewController.swift b/Adamant/Modules/Account/AccountViewController.swift similarity index 91% rename from Adamant/Stories/Account/AccountViewController.swift rename to Adamant/Modules/Account/AccountViewController.swift index f26a820a1..df8ee24e8 100644 --- a/Adamant/Stories/Account/AccountViewController.swift +++ b/Adamant/Modules/Account/AccountViewController.swift @@ -31,7 +31,7 @@ extension String.adamant.alert { } // MARK: AccountViewController -class AccountViewController: FormViewController { +final class AccountViewController: FormViewController { // MARK: - Rows & Sections enum Sections { case wallet, application, delegates, actions, security @@ -131,7 +131,7 @@ class AccountViewController: FormViewController { var visibleWalletsService: VisibleWalletsService! var accountService: AccountService! var dialogService: DialogService! - var router: Router! + var screensFactory: ScreensFactory! var notificationsService: NotificationsService! var transfersProvider: TransfersProvider! var localAuth: LocalAuthentication! @@ -272,22 +272,21 @@ class AccountViewController: FormViewController { }.cellUpdate { (cell, _) in cell.accessoryType = .disclosureIndicator }.onCellSelection { [weak self] (_, _) in - guard let vc = self?.router.get(scene: AdamantScene.Settings.visibleWallets) else { - return - } + guard let self = self else { return } + let vc = screensFactory.makeVisibleWallets() - if let split = self?.splitViewController { + if let split = splitViewController { let details = UINavigationController(rootViewController:vc) details.definesPresentationContext = true split.showDetailViewController(details, sender: self) - } else if let nav = self?.navigationController { + } else if let nav = navigationController { nav.pushViewController(vc, animated: true) } else { vc.modalPresentationStyle = .overFullScreen - self?.present(vc, animated: true, completion: nil) + present(vc, animated: true, completion: nil) } - self?.deselectWalletViewControllers() + deselectWalletViewControllers() } appSection.append(visibleWalletsRow) @@ -301,21 +300,20 @@ class AccountViewController: FormViewController { }.cellUpdate { (cell, _) in cell.accessoryType = .disclosureIndicator }.onCellSelection { [weak self] (_, _) in - guard let vc = self?.router.get(scene: AdamantScene.NodesEditor.nodesList) else { - return - } + guard let self = self else { return } + let vc = screensFactory.makeNodesList() - if let split = self?.splitViewController { + if let split = splitViewController { let details = UINavigationController(rootViewController:vc) split.showDetailViewController(details, sender: self) - } else if let nav = self?.navigationController { + } else if let nav = navigationController { nav.pushViewController(vc, animated: true) } else { vc.modalPresentationStyle = .overFullScreen - self?.present(vc, animated: true, completion: nil) + present(vc, animated: true, completion: nil) } - self?.deselectWalletViewControllers() + deselectWalletViewControllers() } appSection.append(nodesRow) @@ -354,22 +352,20 @@ class AccountViewController: FormViewController { }.cellUpdate { (cell, _) in cell.accessoryType = .disclosureIndicator }.onCellSelection { [weak self] (_, _) in - guard let vc = self?.router.get(scene: AdamantScene.Settings.contribute) - else { - return - } + guard let self = self else { return } + let vc = screensFactory.makeContribute() - if let split = self?.splitViewController { - let details = UINavigationController(rootViewController:vc) + if let split = splitViewController { + let details = UINavigationController(rootViewController: vc) split.showDetailViewController(details, sender: self) - } else if let nav = self?.navigationController { + } else if let nav = navigationController { nav.pushViewController(vc, animated: true) } else { vc.modalPresentationStyle = .overFullScreen - self?.present(vc, animated: true, completion: nil) + present(vc, animated: true, completion: nil) } - self?.deselectWalletViewControllers() + deselectWalletViewControllers() } appSection.append(contributeRow) @@ -383,21 +379,20 @@ class AccountViewController: FormViewController { }.cellUpdate { (cell, _) in cell.accessoryType = .disclosureIndicator }.onCellSelection { [weak self] (_, _) in - guard let vc = self?.router.get(scene: AdamantScene.Settings.about) else { - return - } + guard let self = self else { return } + let vc = screensFactory.makeAbout() - if let split = self?.splitViewController { + if let split = splitViewController { let details = UINavigationController(rootViewController:vc) split.showDetailViewController(details, sender: self) - } else if let nav = self?.navigationController { + } else if let nav = navigationController { nav.pushViewController(vc, animated: true) } else { vc.modalPresentationStyle = .overFullScreen - self?.present(vc, animated: true, completion: nil) + present(vc, animated: true, completion: nil) } - self?.deselectWalletViewControllers() + deselectWalletViewControllers() } appSection.append(aboutRow) @@ -416,22 +411,21 @@ class AccountViewController: FormViewController { }.cellUpdate { (cell, _) in cell.accessoryType = .disclosureIndicator }.onCellSelection { [weak self] (_, _) in - guard let vc = self?.router.get(scene: AdamantScene.Delegates.delegates) else { - return - } + guard let self = self else { return } + let vc = screensFactory.makeDelegatesList() - if let split = self?.splitViewController { + if let split = splitViewController { let details = UINavigationController(rootViewController:vc) details.definesPresentationContext = true split.showDetailViewController(details, sender: self) - } else if let nav = self?.navigationController { + } else if let nav = navigationController { nav.pushViewController(vc, animated: true) } else { vc.modalPresentationStyle = .overFullScreen - self?.present(vc, animated: true, completion: nil) + present(vc, animated: true, completion: nil) } - self?.deselectWalletViewControllers() + deselectWalletViewControllers() } actionsSection.append(delegatesRow) @@ -445,21 +439,20 @@ class AccountViewController: FormViewController { }.cellUpdate { (cell, _) in cell.accessoryType = .disclosureIndicator }.onCellSelection { [weak self] (_, _) in - guard let vc = self?.router.get(scene: AdamantScene.Settings.qRGenerator) else { - return - } + guard let self = self else { return } + let vc = screensFactory.makeQRGenerator() - if let split = self?.splitViewController { + if let split = splitViewController { let details = UINavigationController(rootViewController:vc) split.showDetailViewController(details, sender: self) - } else if let nav = self?.navigationController { + } else if let nav = navigationController { nav.pushViewController(vc, animated: true) } else { vc.modalPresentationStyle = .overFullScreen - self?.present(vc, animated: true, completion: nil) + present(vc, animated: true, completion: nil) } - self?.deselectWalletViewControllers() + deselectWalletViewControllers() } actionsSection.append(generateQrRow) @@ -473,21 +466,20 @@ class AccountViewController: FormViewController { }.cellUpdate { (cell, _) in cell.accessoryType = .disclosureIndicator }.onCellSelection { [weak self] (_, _) in - guard let vc = self?.router.get(scene: AdamantScene.Settings.pkGenerator) else { - return - } + guard let self = self else { return } + let vc = screensFactory.makePKGenerator() - if let split = self?.splitViewController { + if let split = splitViewController { let details = UINavigationController(rootViewController:vc) split.showDetailViewController(details, sender: self) - } else if let nav = self?.navigationController { + } else if let nav = navigationController { nav.pushViewController(vc, animated: true) } else { vc.modalPresentationStyle = .overFullScreen - self?.present(vc, animated: true, completion: nil) + present(vc, animated: true, completion: nil) } - self?.deselectWalletViewControllers() + deselectWalletViewControllers() } actionsSection.append(generatePkRow) @@ -513,12 +505,15 @@ class AccountViewController: FormViewController { self?.tableView.deselectRow(at: indexPath, animated: true) } - let logout = UIAlertAction(title: String.adamant.alert.logoutButton, style: .default) { [weak self] _ in - self?.accountService.logout() - if let vc = self?.router.get(scene: AdamantScene.Login.login) { - vc.modalPresentationStyle = .overFullScreen - self?.dialogService.present(vc, animated: true, completion: nil) - } + let logout = UIAlertAction( + title: .adamant.alert.logoutButton, + style: .default + ) { [weak self] _ in + guard let self = self else { return } + accountService.logout() + let vc = screensFactory.makeLogin() + vc.modalPresentationStyle = .overFullScreen + dialogService.present(vc, animated: true, completion: nil) } alert.addAction(cancel) @@ -556,11 +551,12 @@ class AccountViewController: FormViewController { // Biometry let biometryRow = SwitchRow { [weak self] in + guard let self = self else { return } $0.tag = Rows.biometry.tag $0.title = localAuth.biometryType.localized $0.value = accountService.useBiometry - if let auth = self?.localAuth { + if let auth = localAuth { switch auth.biometryType { case .none: $0.cell.imageView?.image = nil case .touchID: $0.cell.imageView?.image = .asset(named: "row_touchid.png") @@ -601,21 +597,20 @@ class AccountViewController: FormViewController { }.cellUpdate { (cell, _) in cell.accessoryType = .disclosureIndicator }.onCellSelection { [weak self] (_, _) in - guard let vc = self?.router.get(scene: AdamantScene.Settings.notifications) else { - return - } + guard let self = self else { return } + let vc = screensFactory.makeNotifications() - if let split = self?.splitViewController { + if let split = splitViewController { let details = UINavigationController(rootViewController:vc) split.showDetailViewController(details, sender: self) - } else if let nav = self?.navigationController { + } else if let nav = navigationController { nav.pushViewController(vc, animated: true) } else { vc.modalPresentationStyle = .overFullScreen - self?.present(vc, animated: true, completion: nil) + present(vc, animated: true, completion: nil) } - self?.deselectWalletViewControllers() + deselectWalletViewControllers() } securitySection.append(notificationsRow) @@ -799,7 +794,7 @@ class AccountViewController: FormViewController { walletViewControllers.removeAll() let availableServices: [WalletService] = visibleWalletsService.sorted(includeInvisible: false) availableServices.forEach { walletService in - walletViewControllers.append(walletService.walletViewController) + walletViewControllers.append(screensFactory.makeWalletVC(service: walletService)) } } diff --git a/Adamant/Stories/Account/WalletCollectionViewCell.swift b/Adamant/Modules/Account/WalletCollectionViewCell.swift similarity index 100% rename from Adamant/Stories/Account/WalletCollectionViewCell.swift rename to Adamant/Modules/Account/WalletCollectionViewCell.swift diff --git a/Adamant/Stories/Account/WalletCollectionViewCell.xib b/Adamant/Modules/Account/WalletCollectionViewCell.xib similarity index 100% rename from Adamant/Stories/Account/WalletCollectionViewCell.xib rename to Adamant/Modules/Account/WalletCollectionViewCell.xib diff --git a/Adamant/Stories/Account/WalletPagingItem.swift b/Adamant/Modules/Account/WalletPagingItem.swift similarity index 100% rename from Adamant/Stories/Account/WalletPagingItem.swift rename to Adamant/Modules/Account/WalletPagingItem.swift diff --git a/Adamant/Stories/Chat/ChatFactory.swift b/Adamant/Modules/Chat/ChatFactory.swift similarity index 79% rename from Adamant/Stories/Chat/ChatFactory.swift rename to Adamant/Modules/Chat/ChatFactory.swift index 48eecc356..abdd8e7e1 100644 --- a/Adamant/Stories/Chat/ChatFactory.swift +++ b/Adamant/Modules/Chat/ChatFactory.swift @@ -10,6 +10,7 @@ import UIKit import MessageKit import InputBarAccessoryView import Combine +import Swinject @MainActor struct ChatFactory { @@ -24,9 +25,21 @@ struct ChatFactory { let visibleWalletService: VisibleWalletsService let avatarService: AvatarService let emojiService: EmojiService - let router: Router - func makeViewController() -> UIViewController { + nonisolated init(assembler: Assembler) { + chatsProvider = assembler.resolve(ChatsProvider.self)! + dialogService = assembler.resolve(DialogService.self)! + transferProvider = assembler.resolve(TransfersProvider.self)! + accountService = assembler.resolve(AccountService.self)! + accountProvider = assembler.resolve(AccountsProvider.self)! + richTransactionStatusService = assembler.resolve(RichTransactionStatusService.self)! + addressBookService = assembler.resolve(AddressBookService.self)! + visibleWalletService = assembler.resolve(VisibleWalletsService.self)! + avatarService = assembler.resolve(AvatarService.self)! + emojiService = assembler.resolve(EmojiService.self)! + } + + func makeViewController(screensFactory: ScreensFactory) -> ChatViewController { let richMessageProviders = makeRichMessageProviders() let viewModel = makeViewModel(richMessageProviders: richMessageProviders) let delegates = makeDelegates(viewModel: viewModel) @@ -38,14 +51,18 @@ struct ChatFactory { let admService = accountService.wallets.first { wallet in return wallet is AdmWalletService - } as? AdmWalletService + } as! AdmWalletService let viewController = ChatViewController( viewModel: viewModel, richMessageProviders: richMessageProviders, storedObjects: delegates.asArray + [dialogManager], - sendTransaction: makeSendTransactionAction(viewModel: viewModel), - admService: admService + admService: admService, + screensFactory: screensFactory, + sendTransaction: makeSendTransactionAction( + viewModel: viewModel, + screensFactory: screensFactory + ) ) viewController.setupDelegates(delegates) @@ -113,10 +130,11 @@ private extension ChatFactory { } func makeSendTransactionAction( - viewModel: ChatViewModel + viewModel: ChatViewModel, + screensFactory: ScreensFactory ) -> ChatViewController.SendTransaction { - { [router, viewModel] parentVC, messageId in - guard let vc = router.get(scene: AdamantScene.Chats.complexTransfer) as? ComplexTransferViewController + { [screensFactory, viewModel] parentVC, messageId in + guard let vc = screensFactory.makeComplexTransfer() as? ComplexTransferViewController else { return } vc.partner = viewModel.chatroom?.partner diff --git a/Adamant/Stories/Chat/ChatLocalization.swift b/Adamant/Modules/Chat/ChatLocalization.swift similarity index 100% rename from Adamant/Stories/Chat/ChatLocalization.swift rename to Adamant/Modules/Chat/ChatLocalization.swift diff --git a/Adamant/Stories/Chat/View/ChatViewController.swift b/Adamant/Modules/Chat/View/ChatViewController.swift similarity index 95% rename from Adamant/Stories/Chat/View/ChatViewController.swift rename to Adamant/Modules/Chat/View/ChatViewController.swift index 3a40bf6ea..05108b9e6 100644 --- a/Adamant/Stories/Chat/View/ChatViewController.swift +++ b/Adamant/Modules/Chat/View/ChatViewController.swift @@ -16,13 +16,17 @@ import CommonKit @MainActor final class ChatViewController: MessagesViewController { typealias SpinnerCell = MessageCellWrapper - typealias SendTransaction = ( _ parentVC: UIViewController & ComplexTransferViewControllerDelegate, _ replyToMessageId: String?) -> Void + typealias SendTransaction = @MainActor ( + _ parentVC: UIViewController & ComplexTransferViewControllerDelegate, + _ replyToMessageId: String? + ) -> Void // MARK: Dependencies private let storedObjects: [AnyObject] private let richMessageProviders: [String: RichMessageProvider] - private let admService: AdmWalletService? + private let admService: AdmWalletService + private let screensFactory: ScreensFactory let viewModel: ChatViewModel @@ -71,13 +75,15 @@ final class ChatViewController: MessagesViewController { viewModel: ChatViewModel, richMessageProviders: [String: RichMessageProvider], storedObjects: [AnyObject], - sendTransaction: @escaping SendTransaction, - admService: AdmWalletService? + admService: AdmWalletService, + screensFactory: ScreensFactory, + sendTransaction: @escaping SendTransaction ) { self.viewModel = viewModel self.storedObjects = storedObjects self.richMessageProviders = richMessageProviders self.admService = admService + self.screensFactory = screensFactory super.init(nibName: nil, bundle: nil) inputBar.onAttachmentButtonTap = { [weak self] in self.map { sendTransaction($0, viewModel.replyMessage?.id) } @@ -618,13 +624,15 @@ private extension ChatViewController { } func didTapTransferTransaction(_ transaction: TransferTransaction) { - admService?.richMessageTapped(for: transaction, in: self) + let vc = screensFactory.makeAdmTransactionDetails(transaction: transaction) + navigationController?.pushViewController(vc, animated: true) } func didTapRichMessageTransaction(_ transaction: RichMessageTransaction) { guard let type = transaction.richType, - let provider = richMessageProviders[type] + let provider = richMessageProviders[type], + let vc = screensFactory.makeDetailsVC(service: provider, transaction: transaction) else { return } switch transaction.transactionStatus { @@ -635,9 +643,9 @@ private extension ChatViewController { return } - provider.richMessageTapped(for: transaction, in: self) + navigationController?.pushViewController(vc, animated: true) case .notInitiated, .pending, .success, .none, .inconsistent, .registered, .noNetwork, .noNetworkFinal: - provider.richMessageTapped(for: transaction, in: self) + navigationController?.pushViewController(vc, animated: true) } } @@ -709,12 +717,10 @@ private extension ChatViewController { } func didTapAdmSend(to adm: AdamantAddress) { - guard let vc = admService?.transferViewController() else { return } - if let v = vc as? TransferViewControllerBase { - v.recipientAddress = adm.address - v.recipientName = adm.name - v.delegate = self - } + let vc = screensFactory.makeTransferVC(service: admService) + vc.recipientAddress = adm.address + vc.recipientName = adm.name + vc.delegate = self self.navigationController?.pushViewController(vc, animated: true) } } diff --git a/Adamant/Stories/Chat/View/Helpers/AdamantCellAnimation.swift b/Adamant/Modules/Chat/View/Helpers/AdamantCellAnimation.swift similarity index 100% rename from Adamant/Stories/Chat/View/Helpers/AdamantCellAnimation.swift rename to Adamant/Modules/Chat/View/Helpers/AdamantCellAnimation.swift diff --git a/Adamant/Stories/Chat/View/Helpers/ChatReactionsView.swift b/Adamant/Modules/Chat/View/Helpers/ChatReactionsView.swift similarity index 100% rename from Adamant/Stories/Chat/View/Helpers/ChatReactionsView.swift rename to Adamant/Modules/Chat/View/Helpers/ChatReactionsView.swift diff --git a/Adamant/Stories/Chat/View/Managers/ChatAction.swift b/Adamant/Modules/Chat/View/Managers/ChatAction.swift similarity index 100% rename from Adamant/Stories/Chat/View/Managers/ChatAction.swift rename to Adamant/Modules/Chat/View/Managers/ChatAction.swift diff --git a/Adamant/Stories/Chat/View/Managers/ChatCellManager.swift b/Adamant/Modules/Chat/View/Managers/ChatCellManager.swift similarity index 100% rename from Adamant/Stories/Chat/View/Managers/ChatCellManager.swift rename to Adamant/Modules/Chat/View/Managers/ChatCellManager.swift diff --git a/Adamant/Stories/Chat/View/Managers/ChatDataSourceManager.swift b/Adamant/Modules/Chat/View/Managers/ChatDataSourceManager.swift similarity index 100% rename from Adamant/Stories/Chat/View/Managers/ChatDataSourceManager.swift rename to Adamant/Modules/Chat/View/Managers/ChatDataSourceManager.swift diff --git a/Adamant/Stories/Chat/View/Managers/ChatDialogManager.swift b/Adamant/Modules/Chat/View/Managers/ChatDialogManager.swift similarity index 100% rename from Adamant/Stories/Chat/View/Managers/ChatDialogManager.swift rename to Adamant/Modules/Chat/View/Managers/ChatDialogManager.swift diff --git a/Adamant/Stories/Chat/View/Managers/ChatDisplayManager.swift b/Adamant/Modules/Chat/View/Managers/ChatDisplayManager.swift similarity index 100% rename from Adamant/Stories/Chat/View/Managers/ChatDisplayManager.swift rename to Adamant/Modules/Chat/View/Managers/ChatDisplayManager.swift diff --git a/Adamant/Stories/Chat/View/Managers/ChatInputBarManager.swift b/Adamant/Modules/Chat/View/Managers/ChatInputBarManager.swift similarity index 100% rename from Adamant/Stories/Chat/View/Managers/ChatInputBarManager.swift rename to Adamant/Modules/Chat/View/Managers/ChatInputBarManager.swift diff --git a/Adamant/Stories/Chat/View/Managers/ChatKeyboardManager.swift b/Adamant/Modules/Chat/View/Managers/ChatKeyboardManager.swift similarity index 100% rename from Adamant/Stories/Chat/View/Managers/ChatKeyboardManager.swift rename to Adamant/Modules/Chat/View/Managers/ChatKeyboardManager.swift diff --git a/Adamant/Stories/Chat/View/Managers/ChatLayoutManager.swift b/Adamant/Modules/Chat/View/Managers/ChatLayoutManager.swift similarity index 100% rename from Adamant/Stories/Chat/View/Managers/ChatLayoutManager.swift rename to Adamant/Modules/Chat/View/Managers/ChatLayoutManager.swift diff --git a/Adamant/Stories/Chat/View/Managers/ChatMenuManager.swift b/Adamant/Modules/Chat/View/Managers/ChatMenuManager.swift similarity index 100% rename from Adamant/Stories/Chat/View/Managers/ChatMenuManager.swift rename to Adamant/Modules/Chat/View/Managers/ChatMenuManager.swift diff --git a/Adamant/Stories/Chat/View/Managers/FixedTextMessageSizeCalculator.swift b/Adamant/Modules/Chat/View/Managers/FixedTextMessageSizeCalculator.swift similarity index 100% rename from Adamant/Stories/Chat/View/Managers/FixedTextMessageSizeCalculator.swift rename to Adamant/Modules/Chat/View/Managers/FixedTextMessageSizeCalculator.swift diff --git a/Adamant/Stories/Chat/View/Subviews/ChatBaseMessage/ChatMessageCell+Model.swift b/Adamant/Modules/Chat/View/Subviews/ChatBaseMessage/ChatMessageCell+Model.swift similarity index 100% rename from Adamant/Stories/Chat/View/Subviews/ChatBaseMessage/ChatMessageCell+Model.swift rename to Adamant/Modules/Chat/View/Subviews/ChatBaseMessage/ChatMessageCell+Model.swift diff --git a/Adamant/Stories/Chat/View/Subviews/ChatBaseMessage/ChatMessageCell.swift b/Adamant/Modules/Chat/View/Subviews/ChatBaseMessage/ChatMessageCell.swift similarity index 100% rename from Adamant/Stories/Chat/View/Subviews/ChatBaseMessage/ChatMessageCell.swift rename to Adamant/Modules/Chat/View/Subviews/ChatBaseMessage/ChatMessageCell.swift diff --git a/Adamant/Stories/Chat/View/Subviews/ChatInputBar.swift b/Adamant/Modules/Chat/View/Subviews/ChatInputBar.swift similarity index 100% rename from Adamant/Stories/Chat/View/Subviews/ChatInputBar.swift rename to Adamant/Modules/Chat/View/Subviews/ChatInputBar.swift diff --git a/Adamant/Stories/Chat/View/Subviews/ChatMessagesCollection.swift b/Adamant/Modules/Chat/View/Subviews/ChatMessagesCollection.swift similarity index 100% rename from Adamant/Stories/Chat/View/Subviews/ChatMessagesCollection.swift rename to Adamant/Modules/Chat/View/Subviews/ChatMessagesCollection.swift diff --git a/Adamant/Stories/Chat/View/Subviews/ChatModelView.swift b/Adamant/Modules/Chat/View/Subviews/ChatModelView.swift similarity index 100% rename from Adamant/Stories/Chat/View/Subviews/ChatModelView.swift rename to Adamant/Modules/Chat/View/Subviews/ChatModelView.swift diff --git a/Adamant/Stories/Chat/View/Subviews/ChatRefreshMock.swift b/Adamant/Modules/Chat/View/Subviews/ChatRefreshMock.swift similarity index 100% rename from Adamant/Stories/Chat/View/Subviews/ChatRefreshMock.swift rename to Adamant/Modules/Chat/View/Subviews/ChatRefreshMock.swift diff --git a/Adamant/Stories/Chat/View/Subviews/ChatReply/ChatMessageReplyCell+Model.swift b/Adamant/Modules/Chat/View/Subviews/ChatReply/ChatMessageReplyCell+Model.swift similarity index 100% rename from Adamant/Stories/Chat/View/Subviews/ChatReply/ChatMessageReplyCell+Model.swift rename to Adamant/Modules/Chat/View/Subviews/ChatReply/ChatMessageReplyCell+Model.swift diff --git a/Adamant/Stories/Chat/View/Subviews/ChatReply/ChatMessageReplyCell.swift b/Adamant/Modules/Chat/View/Subviews/ChatReply/ChatMessageReplyCell.swift similarity index 100% rename from Adamant/Stories/Chat/View/Subviews/ChatReply/ChatMessageReplyCell.swift rename to Adamant/Modules/Chat/View/Subviews/ChatReply/ChatMessageReplyCell.swift diff --git a/Adamant/Stories/Chat/View/Subviews/ChatScrollDownButton.swift b/Adamant/Modules/Chat/View/Subviews/ChatScrollDownButton.swift similarity index 100% rename from Adamant/Stories/Chat/View/Subviews/ChatScrollDownButton.swift rename to Adamant/Modules/Chat/View/Subviews/ChatScrollDownButton.swift diff --git a/Adamant/Stories/Chat/View/Subviews/ChatTransaction/ChatTransactionCell.swift b/Adamant/Modules/Chat/View/Subviews/ChatTransaction/ChatTransactionCell.swift similarity index 100% rename from Adamant/Stories/Chat/View/Subviews/ChatTransaction/ChatTransactionCell.swift rename to Adamant/Modules/Chat/View/Subviews/ChatTransaction/ChatTransactionCell.swift diff --git a/Adamant/Stories/Chat/View/Subviews/ChatTransaction/Container/ChatTransactionContainerView+Model.swift b/Adamant/Modules/Chat/View/Subviews/ChatTransaction/Container/ChatTransactionContainerView+Model.swift similarity index 100% rename from Adamant/Stories/Chat/View/Subviews/ChatTransaction/Container/ChatTransactionContainerView+Model.swift rename to Adamant/Modules/Chat/View/Subviews/ChatTransaction/Container/ChatTransactionContainerView+Model.swift diff --git a/Adamant/Stories/Chat/View/Subviews/ChatTransaction/Container/ChatTransactionContainerView.swift b/Adamant/Modules/Chat/View/Subviews/ChatTransaction/Container/ChatTransactionContainerView.swift similarity index 100% rename from Adamant/Stories/Chat/View/Subviews/ChatTransaction/Container/ChatTransactionContainerView.swift rename to Adamant/Modules/Chat/View/Subviews/ChatTransaction/Container/ChatTransactionContainerView.swift diff --git a/Adamant/Stories/Chat/View/Subviews/ChatTransaction/Content/ChatTransactionContentView+Model.swift b/Adamant/Modules/Chat/View/Subviews/ChatTransaction/Content/ChatTransactionContentView+Model.swift similarity index 100% rename from Adamant/Stories/Chat/View/Subviews/ChatTransaction/Content/ChatTransactionContentView+Model.swift rename to Adamant/Modules/Chat/View/Subviews/ChatTransaction/Content/ChatTransactionContentView+Model.swift diff --git a/Adamant/Stories/Chat/View/Subviews/ChatTransaction/Content/ChatTransactionContentView.swift b/Adamant/Modules/Chat/View/Subviews/ChatTransaction/Content/ChatTransactionContentView.swift similarity index 100% rename from Adamant/Stories/Chat/View/Subviews/ChatTransaction/Content/ChatTransactionContentView.swift rename to Adamant/Modules/Chat/View/Subviews/ChatTransaction/Content/ChatTransactionContentView.swift diff --git a/Adamant/Stories/Chat/ViewModel/ChatCacheService.swift b/Adamant/Modules/Chat/ViewModel/ChatCacheService.swift similarity index 86% rename from Adamant/Stories/Chat/ViewModel/ChatCacheService.swift rename to Adamant/Modules/Chat/ViewModel/ChatCacheService.swift index 10c57483c..e1e940c0d 100644 --- a/Adamant/Stories/Chat/ViewModel/ChatCacheService.swift +++ b/Adamant/Modules/Chat/ViewModel/ChatCacheService.swift @@ -14,11 +14,8 @@ final class ChatCacheService { private var messages: [String: [ChatMessage]] = [:] private var subscriptions = Set() - init() { - NotificationCenter.default - .publisher(for: .AdamantAccountService.userLoggedOut) - .sink { [weak self] _ in self?.messages = .init() } - .store(in: &subscriptions) + nonisolated init() { + Task { await setup() } } func setMessages(address: String, messages: [ChatMessage]) { @@ -29,3 +26,12 @@ final class ChatCacheService { messages[address] } } + +private extension ChatCacheService { + func setup() { + NotificationCenter.default + .publisher(for: .AdamantAccountService.userLoggedOut) + .sink { [weak self] _ in self?.messages = .init() } + .store(in: &subscriptions) + } +} diff --git a/Adamant/Stories/Chat/ViewModel/ChatMessageFactory.swift b/Adamant/Modules/Chat/ViewModel/ChatMessageFactory.swift similarity index 100% rename from Adamant/Stories/Chat/ViewModel/ChatMessageFactory.swift rename to Adamant/Modules/Chat/ViewModel/ChatMessageFactory.swift diff --git a/Adamant/Stories/Chat/ViewModel/ChatMessagesListFactory.swift b/Adamant/Modules/Chat/ViewModel/ChatMessagesListFactory.swift similarity index 100% rename from Adamant/Stories/Chat/ViewModel/ChatMessagesListFactory.swift rename to Adamant/Modules/Chat/ViewModel/ChatMessagesListFactory.swift diff --git a/Adamant/Stories/Chat/ViewModel/ChatMessagesListViewModel.swift b/Adamant/Modules/Chat/ViewModel/ChatMessagesListViewModel.swift similarity index 100% rename from Adamant/Stories/Chat/ViewModel/ChatMessagesListViewModel.swift rename to Adamant/Modules/Chat/ViewModel/ChatMessagesListViewModel.swift diff --git a/Adamant/Stories/Chat/ViewModel/ChatPreservationDelegate.swift b/Adamant/Modules/Chat/ViewModel/ChatPreservationDelegate.swift similarity index 100% rename from Adamant/Stories/Chat/ViewModel/ChatPreservationDelegate.swift rename to Adamant/Modules/Chat/ViewModel/ChatPreservationDelegate.swift diff --git a/Adamant/Stories/Chat/ViewModel/ChatViewModel.swift b/Adamant/Modules/Chat/ViewModel/ChatViewModel.swift similarity index 100% rename from Adamant/Stories/Chat/ViewModel/ChatViewModel.swift rename to Adamant/Modules/Chat/ViewModel/ChatViewModel.swift diff --git a/Adamant/Stories/Chat/ViewModel/Models/ChatDialog.swift b/Adamant/Modules/Chat/ViewModel/Models/ChatDialog.swift similarity index 100% rename from Adamant/Stories/Chat/ViewModel/Models/ChatDialog.swift rename to Adamant/Modules/Chat/ViewModel/Models/ChatDialog.swift diff --git a/Adamant/Stories/Chat/ViewModel/Models/ChatMessage.swift b/Adamant/Modules/Chat/ViewModel/Models/ChatMessage.swift similarity index 100% rename from Adamant/Stories/Chat/ViewModel/Models/ChatMessage.swift rename to Adamant/Modules/Chat/ViewModel/Models/ChatMessage.swift diff --git a/Adamant/Stories/Chat/ViewModel/Models/ChatMessageBackgroundColor.swift b/Adamant/Modules/Chat/ViewModel/Models/ChatMessageBackgroundColor.swift similarity index 100% rename from Adamant/Stories/Chat/ViewModel/Models/ChatMessageBackgroundColor.swift rename to Adamant/Modules/Chat/ViewModel/Models/ChatMessageBackgroundColor.swift diff --git a/Adamant/Stories/Chat/ViewModel/Models/ChatSender.swift b/Adamant/Modules/Chat/ViewModel/Models/ChatSender.swift similarity index 100% rename from Adamant/Stories/Chat/ViewModel/Models/ChatSender.swift rename to Adamant/Modules/Chat/ViewModel/Models/ChatSender.swift diff --git a/Adamant/Stories/Chat/ViewModel/Models/ChatStartPosition.swift b/Adamant/Modules/Chat/ViewModel/Models/ChatStartPosition.swift similarity index 100% rename from Adamant/Stories/Chat/ViewModel/Models/ChatStartPosition.swift rename to Adamant/Modules/Chat/ViewModel/Models/ChatStartPosition.swift diff --git a/Adamant/Stories/Chat/ViewModel/Models/MessageModel.swift b/Adamant/Modules/Chat/ViewModel/Models/MessageModel.swift similarity index 100% rename from Adamant/Stories/Chat/ViewModel/Models/MessageModel.swift rename to Adamant/Modules/Chat/ViewModel/Models/MessageModel.swift diff --git a/Adamant/Modules/ChatsList/ChatListFactory.swift b/Adamant/Modules/ChatsList/ChatListFactory.swift new file mode 100644 index 000000000..5cb8d9d51 --- /dev/null +++ b/Adamant/Modules/ChatsList/ChatListFactory.swift @@ -0,0 +1,63 @@ +// +// ChatListFactory.swift +// Adamant +// +// Created by Anokhov Pavel on 12.01.2018. +// Copyright © 2018 Adamant. All rights reserved. +// + +import UIKit +import Swinject + +struct ChatListFactory { + let assembler: Assembler + + func makeChatListVC(screensFactory: ScreensFactory) -> UIViewController { + let c = ChatListViewController(nibName: "ChatListViewController", bundle: nil) + c.accountService = assembler.resolve(AccountService.self) + c.chatsProvider = assembler.resolve(ChatsProvider.self) + c.transfersProvider = assembler.resolve(TransfersProvider.self) + c.screensFactory = screensFactory + c.notificationsService = assembler.resolve(NotificationsService.self) + c.dialogService = assembler.resolve(DialogService.self) + c.addressBook = assembler.resolve(AddressBookService.self) + c.avatarService = assembler.resolve(AvatarService.self) + + // MARK: RichMessage handlers + // Transfer handlers from accountService' wallet services + if let accountService = assembler.resolve(AccountService.self) { + for case let provider as RichMessageProvider in accountService.wallets { + c.richMessageProviders[provider.dynamicRichMessageType] = provider + } + } + + return c + } + + func makeNewChatVC(screensFactory: ScreensFactory) -> NewChatViewController { + let c = NewChatViewController() + c.dialogService = assembler.resolve(DialogService.self) + c.accountService = assembler.resolve(AccountService.self) + c.accountsProvider = assembler.resolve(AccountsProvider.self) + c.screensFactory = screensFactory + return c + } + + func makeComplexTransferVC(screensFactory: ScreensFactory) -> UIViewController { + let c = ComplexTransferViewController() + c.accountService = assembler.resolve(AccountService.self) + c.visibleWalletsService = assembler.resolve(VisibleWalletsService.self) + c.addressBookService = assembler.resolve(AddressBookService.self) + c.screensFactory = screensFactory + return c + } + + func makeSearchResultsViewController(screensFactory: ScreensFactory) -> SearchResultsViewController { + SearchResultsViewController( + screensFactory: screensFactory, + avatarService: assembler.resolve(AvatarService.self)!, + addressBookService: assembler.resolve(AddressBookService.self)!, + accountsProvider: assembler.resolve(AccountsProvider.self)! + ) + } +} diff --git a/Adamant/Stories/ChatsList/ChatListViewController.swift b/Adamant/Modules/ChatsList/ChatListViewController.swift similarity index 98% rename from Adamant/Stories/ChatsList/ChatListViewController.swift rename to Adamant/Modules/ChatsList/ChatListViewController.swift index 691dfc6ee..52f988e0c 100644 --- a/Adamant/Stories/ChatsList/ChatListViewController.swift +++ b/Adamant/Modules/ChatsList/ChatListViewController.swift @@ -30,7 +30,7 @@ extension String.adamant { } } -class ChatListViewController: KeyboardObservingViewController { +final class ChatListViewController: KeyboardObservingViewController { typealias SpinnerCell = TableCellWrapper let cellIdentifier = "cell" @@ -41,7 +41,7 @@ class ChatListViewController: KeyboardObservingViewController { var accountService: AccountService! var chatsProvider: ChatsProvider! var transfersProvider: TransfersProvider! - var router: Router! + var screensFactory: ScreensFactory! var notificationsService: NotificationsService! var dialogService: DialogService! var addressBook: AddressBookService! @@ -189,11 +189,7 @@ class ChatListViewController: KeyboardObservingViewController { } loadNewChatTask?.cancel() - - guard let searchResultController = router.get(scene: AdamantScene.Chats.searchResults) as? SearchResultsViewController else { - fatalError("Can't get SearchResultsViewController") - } - + let searchResultController = screensFactory.makeSearchResults() searchResultController.delegate = self searchController = UISearchController(searchResultsController: searchResultController) @@ -303,11 +299,8 @@ class ChatListViewController: KeyboardObservingViewController { // MARK: IB Actions @IBAction func newChat(sender: Any) { - let controller = router.get(scene: AdamantScene.Chats.newChat) - - if let c = controller as? NewChatViewController { - c.delegate = self - } + let controller = screensFactory.makeNewChat() + controller.delegate = self if let split = splitViewController { let nav = UINavigationController(rootViewController: controller) @@ -318,11 +311,11 @@ class ChatListViewController: KeyboardObservingViewController { } // MARK: Helpers - func chatViewController(for chatroom: Chatroom, with messageId: String? = nil) -> ChatViewController { - guard let vc = router.get(scene: AdamantScene.Chats.chat) as? ChatViewController else { - fatalError("Can't get ChatViewController") - } - + func chatViewController( + for chatroom: Chatroom, + with messageId: String? = nil + ) -> ChatViewController { + let vc = screensFactory.makeChat() vc.hidesBottomBarWhenPushed = true vc.viewModel.setup( account: accountService.account, diff --git a/Adamant/Stories/ChatsList/ChatListViewController.xib b/Adamant/Modules/ChatsList/ChatListViewController.xib similarity index 100% rename from Adamant/Stories/ChatsList/ChatListViewController.xib rename to Adamant/Modules/ChatsList/ChatListViewController.xib diff --git a/Adamant/Stories/ChatsList/ChatTableViewCell.swift b/Adamant/Modules/ChatsList/ChatTableViewCell.swift similarity index 97% rename from Adamant/Stories/ChatsList/ChatTableViewCell.swift rename to Adamant/Modules/ChatsList/ChatTableViewCell.swift index d1ca901c5..5119ace32 100644 --- a/Adamant/Stories/ChatsList/ChatTableViewCell.swift +++ b/Adamant/Modules/ChatsList/ChatTableViewCell.swift @@ -10,7 +10,7 @@ import UIKit import FreakingSimpleRoundImageView import CommonKit -class ChatTableViewCell: UITableViewCell { +final class ChatTableViewCell: UITableViewCell { static var defaultAvatar: UIImage = .asset(named: "avatar-chat-placeholder") ?? .init() static let shortDescriptionTextSize: CGFloat = 15.0 diff --git a/Adamant/Stories/ChatsList/ChatTableViewCell.xib b/Adamant/Modules/ChatsList/ChatTableViewCell.xib similarity index 100% rename from Adamant/Stories/ChatsList/ChatTableViewCell.xib rename to Adamant/Modules/ChatsList/ChatTableViewCell.xib diff --git a/Adamant/Stories/ChatsList/ComplexTransferViewController.swift b/Adamant/Modules/ChatsList/ComplexTransferViewController.swift similarity index 97% rename from Adamant/Stories/ChatsList/ComplexTransferViewController.swift rename to Adamant/Modules/ChatsList/ComplexTransferViewController.swift index 27576290f..dc8df14b9 100644 --- a/Adamant/Stories/ChatsList/ComplexTransferViewController.swift +++ b/Adamant/Modules/ChatsList/ComplexTransferViewController.swift @@ -16,12 +16,13 @@ protocol ComplexTransferViewControllerDelegate: AnyObject { func complexTransferViewController(_ viewController: ComplexTransferViewController, didFinishWithTransfer: TransactionDetails?, detailsViewController: UIViewController?) } -class ComplexTransferViewController: UIViewController { +final class ComplexTransferViewController: UIViewController { // MARK: - Dependencies var accountService: AccountService! var visibleWalletsService: VisibleWalletsService! var addressBookService: AddressBookService! + var screensFactory: ScreensFactory! // MARK: - Properties var pagingViewController: PagingViewController! @@ -96,7 +97,7 @@ extension ComplexTransferViewController: PagingViewControllerDataSource { func pagingViewController(_ pagingViewController: PagingViewController, viewControllerAt index: Int) -> UIViewController { let service = services[index] - let vc = service.transferViewController() + let vc = screensFactory.makeTransferVC(service: service) guard let v = vc as? TransferViewControllerBase else { return vc } diff --git a/Adamant/Stories/ChatsList/NewChatViewController.swift b/Adamant/Modules/ChatsList/NewChatViewController.swift similarity index 96% rename from Adamant/Stories/ChatsList/NewChatViewController.swift rename to Adamant/Modules/ChatsList/NewChatViewController.swift index 4dde3fa50..bb527cff7 100644 --- a/Adamant/Stories/ChatsList/NewChatViewController.swift +++ b/Adamant/Modules/ChatsList/NewChatViewController.swift @@ -43,7 +43,7 @@ protocol NewChatViewControllerDelegate: AnyObject { } // MARK: - -class NewChatViewController: FormViewController { +final class NewChatViewController: FormViewController { static let faqUrl = "https://medium.com/adamant-im/chats-and-uninitialized-accounts-in-adamant-5035438e2fcd" private enum Rows { @@ -77,7 +77,7 @@ class NewChatViewController: FormViewController { var dialogService: DialogService! var accountService: AccountService! var accountsProvider: AccountsProvider! - var router: Router! + var screensFactory: ScreensFactory! // MARK: Properties private var skipValueChange = false @@ -191,21 +191,27 @@ class NewChatViewController: FormViewController { }.cellUpdate { (cell, _) in cell.textLabel?.textColor = UIColor.adamant.primary }.onCellSelection { [weak self] (_, _) in - let encodedAddress = AdamantUriTools.encode(request: AdamantUri.address(address: address, params: nil)) + guard let self = self else { return } + let encodedAddress = AdamantUriTools.encode(request: AdamantUri.address( + address: address, + params: nil + )) + switch AdamantQRTools.generateQrFrom(string: encodedAddress, withLogo: true) { case .success(let qr): - guard let vc = self?.router.get(scene: AdamantScene.Shared.shareQr) as? ShareQrViewController else { - fatalError("Can't find ShareQrViewController") - } - + let vc = screensFactory.makeShareQr() vc.qrCode = qr vc.sharingTip = address vc.excludedActivityTypes = ShareContentType.address.excludedActivityTypes vc.modalPresentationStyle = .overFullScreen - self?.present(vc, animated: true, completion: nil) + present(vc, animated: true, completion: nil) case .failure(error: let error): - self?.dialogService.showError(withMessage: error.localizedDescription, supportEmail: true, error: error) + dialogService.showError( + withMessage: error.localizedDescription, + supportEmail: true, + error: error + ) } } diff --git a/Adamant/Stories/ChatsList/SearchResultsViewController.swift b/Adamant/Modules/ChatsList/SearchResultsViewController.swift similarity index 98% rename from Adamant/Stories/ChatsList/SearchResultsViewController.swift rename to Adamant/Modules/ChatsList/SearchResultsViewController.swift index f796b0b44..76635c830 100644 --- a/Adamant/Stories/ChatsList/SearchResultsViewController.swift +++ b/Adamant/Modules/ChatsList/SearchResultsViewController.swift @@ -26,10 +26,10 @@ protocol SearchResultDelegate: AnyObject { func didSelected(_ account: CoreDataAccount) } -class SearchResultsViewController: UITableViewController { +final class SearchResultsViewController: UITableViewController { // MARK: - Dependencies - let router: Router + let screensFactory: ScreensFactory let avatarService: AvatarService let addressBookService: AddressBookService let accountsProvider: AccountsProvider @@ -47,12 +47,12 @@ class SearchResultsViewController: UITableViewController { // MARK: Init init( - router: Router, + screensFactory: ScreensFactory, avatarService: AvatarService, addressBookService: AddressBookService, accountsProvider: AccountsProvider ) { - self.router = router + self.screensFactory = screensFactory self.avatarService = avatarService self.addressBookService = addressBookService self.accountsProvider = accountsProvider diff --git a/Adamant/Stories/ChatsList/SearchResultsViewController.xib b/Adamant/Modules/ChatsList/SearchResultsViewController.xib similarity index 100% rename from Adamant/Stories/ChatsList/SearchResultsViewController.xib rename to Adamant/Modules/ChatsList/SearchResultsViewController.xib diff --git a/Adamant/Stories/Delegates/AdamantDelegateCell.swift b/Adamant/Modules/Delegates/AdamantDelegateCell.swift similarity index 98% rename from Adamant/Stories/Delegates/AdamantDelegateCell.swift rename to Adamant/Modules/Delegates/AdamantDelegateCell.swift index e06e87b75..4cadf52a5 100644 --- a/Adamant/Stories/Delegates/AdamantDelegateCell.swift +++ b/Adamant/Modules/Delegates/AdamantDelegateCell.swift @@ -15,7 +15,7 @@ protocol AdamantDelegateCellDelegate: AnyObject { } // MARK: - -class AdamantDelegateCell: UITableViewCell { +final class AdamantDelegateCell: UITableViewCell { private let checkmarkRowView = CheckmarkRowView() weak var delegate: AdamantDelegateCellDelegate? { diff --git a/Adamant/Stories/Delegates/DelegateDetailsViewController.swift b/Adamant/Modules/Delegates/DelegateDetailsViewController.swift similarity index 99% rename from Adamant/Stories/Delegates/DelegateDetailsViewController.swift rename to Adamant/Modules/Delegates/DelegateDetailsViewController.swift index e8cf20889..72f2ce654 100644 --- a/Adamant/Stories/Delegates/DelegateDetailsViewController.swift +++ b/Adamant/Modules/Delegates/DelegateDetailsViewController.swift @@ -19,7 +19,7 @@ extension String.adamant { } // MARK: - -class DelegateDetailsViewController: UIViewController { +final class DelegateDetailsViewController: UIViewController { // MARK: - Rows fileprivate enum Row: Int { diff --git a/Adamant/Stories/Delegates/DelegateDetailsViewController.xib b/Adamant/Modules/Delegates/DelegateDetailsViewController.xib similarity index 100% rename from Adamant/Stories/Delegates/DelegateDetailsViewController.xib rename to Adamant/Modules/Delegates/DelegateDetailsViewController.xib diff --git a/Adamant/Stories/Delegates/DelegatesBottomPanel/DelegatesBottomPanel+Model.swift b/Adamant/Modules/Delegates/DelegatesBottomPanel/DelegatesBottomPanel+Model.swift similarity index 100% rename from Adamant/Stories/Delegates/DelegatesBottomPanel/DelegatesBottomPanel+Model.swift rename to Adamant/Modules/Delegates/DelegatesBottomPanel/DelegatesBottomPanel+Model.swift diff --git a/Adamant/Stories/Delegates/DelegatesBottomPanel/DelegatesBottomPanel.swift b/Adamant/Modules/Delegates/DelegatesBottomPanel/DelegatesBottomPanel.swift similarity index 100% rename from Adamant/Stories/Delegates/DelegatesBottomPanel/DelegatesBottomPanel.swift rename to Adamant/Modules/Delegates/DelegatesBottomPanel/DelegatesBottomPanel.swift diff --git a/Adamant/Modules/Delegates/DelegatesFactory.swift b/Adamant/Modules/Delegates/DelegatesFactory.swift new file mode 100644 index 000000000..f079897ab --- /dev/null +++ b/Adamant/Modules/Delegates/DelegatesFactory.swift @@ -0,0 +1,31 @@ +// +// DelegatesFactory.swift +// Adamant +// +// Created by Anton Boyarkin on 06/07/2018. +// Copyright © 2018 Adamant. All rights reserved. +// + +import UIKit +import Swinject + +struct DelegatesFactory { + let assembler: Assembler + + func makeDelegatesListVC(screensFactory: ScreensFactory) -> UIViewController { + DelegatesListViewController( + apiService: assembler.resolve(ApiService.self)!, + accountService: assembler.resolve(AccountService.self)!, + dialogService: assembler.resolve(DialogService.self)!, + screensFactory: screensFactory + ) + } + + func makeDelegateDetails() -> DelegateDetailsViewController { + let c = DelegateDetailsViewController(nibName: "DelegateDetailsViewController", bundle: nil) + c.apiService = assembler.resolve(ApiService.self) + c.accountService = assembler.resolve(AccountService.self) + c.dialogService = assembler.resolve(DialogService.self) + return c + } +} diff --git a/Adamant/Stories/Delegates/DelegatesListViewController.swift b/Adamant/Modules/Delegates/DelegatesListViewController.swift similarity index 98% rename from Adamant/Stories/Delegates/DelegatesListViewController.swift rename to Adamant/Modules/Delegates/DelegatesListViewController.swift index b3927d9ca..f76885391 100644 --- a/Adamant/Stories/Delegates/DelegatesListViewController.swift +++ b/Adamant/Modules/Delegates/DelegatesListViewController.swift @@ -42,7 +42,7 @@ final class DelegatesListViewController: KeyboardObservingViewController { private let apiService: ApiService private let accountService: AccountService private let dialogService: DialogService - private let router: Router + private let screensFactory: ScreensFactory // MARK: - Constants @@ -99,12 +99,12 @@ final class DelegatesListViewController: KeyboardObservingViewController { apiService: ApiService, accountService: AccountService, dialogService: DialogService, - router: Router + screensFactory: ScreensFactory ) { self.apiService = apiService self.accountService = accountService self.dialogService = dialogService - self.router = router + self.screensFactory = screensFactory super.init(nibName: nil, bundle: nil) } @@ -239,10 +239,7 @@ extension DelegatesListViewController: UITableViewDataSource, UITableViewDelegat } func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { - guard let controller = router.get(scene: AdamantScene.Delegates.delegateDetails) as? DelegateDetailsViewController else { - return - } - + let controller = screensFactory.makeDelegateDetails() controller.delegate = checkedDelegateFor(indexPath: indexPath).delegate navigationController?.pushViewController(controller, animated: true) diff --git a/Adamant/Stories/Login/EurekaPassphraseRow.swift b/Adamant/Modules/Login/EurekaPassphraseRow.swift similarity index 100% rename from Adamant/Stories/Login/EurekaPassphraseRow.swift rename to Adamant/Modules/Login/EurekaPassphraseRow.swift diff --git a/Adamant/Modules/Login/LoginFactory.swift b/Adamant/Modules/Login/LoginFactory.swift new file mode 100644 index 000000000..dd1fb2c78 --- /dev/null +++ b/Adamant/Modules/Login/LoginFactory.swift @@ -0,0 +1,25 @@ +// +// LoginFactory.swift +// Adamant +// +// Created by Anokhov Pavel on 07.01.2018. +// Copyright © 2018 Adamant. All rights reserved. +// + +import UIKit +import Swinject + +struct LoginFactory { + let assembler: Assembler + + func makeViewController(screenFactory: ScreensFactory) -> LoginViewController { + LoginViewController( + accountService: assembler.resolve(AccountService.self)!, + adamantCore: assembler.resolve(AdamantCore.self)!, + dialogService: assembler.resolve(DialogService.self)!, + localAuth: assembler.resolve(LocalAuthentication.self)!, + screensFactory: screenFactory, + apiService: assembler.resolve(ApiService.self)! + ) + } +} diff --git a/Adamant/Stories/Login/LoginViewController+Pinpad.swift b/Adamant/Modules/Login/LoginViewController+Pinpad.swift similarity index 100% rename from Adamant/Stories/Login/LoginViewController+Pinpad.swift rename to Adamant/Modules/Login/LoginViewController+Pinpad.swift diff --git a/Adamant/Stories/Login/LoginViewController+QR.swift b/Adamant/Modules/Login/LoginViewController+QR.swift similarity index 100% rename from Adamant/Stories/Login/LoginViewController+QR.swift rename to Adamant/Modules/Login/LoginViewController+QR.swift diff --git a/Adamant/Stories/Login/LoginViewController.swift b/Adamant/Modules/Login/LoginViewController.swift similarity index 98% rename from Adamant/Stories/Login/LoginViewController.swift rename to Adamant/Modules/Login/LoginViewController.swift index 097beff71..007bd9636 100644 --- a/Adamant/Stories/Login/LoginViewController.swift +++ b/Adamant/Modules/Login/LoginViewController.swift @@ -34,7 +34,7 @@ extension String.adamant { } // MARK: - ViewController -class LoginViewController: FormViewController { +final class LoginViewController: FormViewController { // MARK: Rows & Sections @@ -122,7 +122,7 @@ class LoginViewController: FormViewController { let accountService: AccountService let adamantCore: AdamantCore let localAuth: LocalAuthentication - let router: Router + let screensFactory: ScreensFactory let apiService: ApiService let dialogService: DialogService @@ -141,14 +141,14 @@ class LoginViewController: FormViewController { adamantCore: AdamantCore, dialogService: DialogService, localAuth: LocalAuthentication, - router: Router, + screensFactory: ScreensFactory, apiService: ApiService ) { self.accountService = accountService self.adamantCore = adamantCore self.dialogService = dialogService self.localAuth = localAuth - self.router = router + self.screensFactory = screensFactory self.apiService = apiService super.init(nibName: nil, bundle: nil) @@ -317,13 +317,11 @@ class LoginViewController: FormViewController { }.cellUpdate { (cell, _) in cell.textLabel?.textColor = UIColor.adamant.primary }.onCellSelection { [weak self] (_, _) in - guard let vc = self?.router.get(scene: AdamantScene.NodesEditor.nodesList) else { - return - } - + guard let self = self else { return } + let vc = screensFactory.makeNodesList() let nav = UINavigationController(rootViewController: vc) nav.modalPresentationStyle = .overFullScreen - self?.present(nav, animated: true, completion: nil) + present(nav, animated: true, completion: nil) } // MARK: tableView position tuning diff --git a/Adamant/Stories/Login/PassphraseCell.xib b/Adamant/Modules/Login/PassphraseCell.xib similarity index 100% rename from Adamant/Stories/Login/PassphraseCell.xib rename to Adamant/Modules/Login/PassphraseCell.xib diff --git a/Adamant/Stories/NodesEditor/EurekaNodeRow.swift b/Adamant/Modules/NodesEditor/EurekaNodeRow.swift similarity index 98% rename from Adamant/Stories/NodesEditor/EurekaNodeRow.swift rename to Adamant/Modules/NodesEditor/EurekaNodeRow.swift index 38a1afc96..80a3850eb 100644 --- a/Adamant/Stories/NodesEditor/EurekaNodeRow.swift +++ b/Adamant/Modules/NodesEditor/EurekaNodeRow.swift @@ -11,7 +11,7 @@ import SnapKit import Eureka import CommonKit -class NodeCell: Cell, CellType { +final class NodeCell: Cell, CellType { struct Model: Equatable { enum NodeActivity { case webSockets diff --git a/Adamant/Stories/NodesEditor/NodeEditorViewController.swift b/Adamant/Modules/NodesEditor/NodeEditorViewController.swift similarity index 99% rename from Adamant/Stories/NodesEditor/NodeEditorViewController.swift rename to Adamant/Modules/NodesEditor/NodeEditorViewController.swift index 978776fa9..064418396 100644 --- a/Adamant/Stories/NodesEditor/NodeEditorViewController.swift +++ b/Adamant/Modules/NodesEditor/NodeEditorViewController.swift @@ -34,7 +34,7 @@ protocol NodeEditorDelegate: AnyObject { } // MARK: - NodeEditorViewController -class NodeEditorViewController: FormViewController { +final class NodeEditorViewController: FormViewController { // MARK: - Rows private enum Rows { diff --git a/Adamant/Modules/NodesEditor/NodesEditorFactory.swift b/Adamant/Modules/NodesEditor/NodesEditorFactory.swift new file mode 100644 index 000000000..9096f13db --- /dev/null +++ b/Adamant/Modules/NodesEditor/NodesEditorFactory.swift @@ -0,0 +1,33 @@ +// +// NodesEditorFactory.swift +// Adamant +// +// Created by Anokhov Pavel on 20.06.2018. +// Copyright © 2018 Adamant. All rights reserved. +// + +import UIKit +import Swinject +import CommonKit + +struct NodesEditorFactory { + let assembler: Assembler + + func makeNodesListVC(screensFactory: ScreensFactory) -> UIViewController { + let c = NodesListViewController() + c.screensFactory = screensFactory + c.dialogService = assembler.resolve(DialogService.self) + c.securedStore = assembler.resolve(SecuredStore.self) + c.apiService = assembler.resolve(ApiService.self) + c.socketService = assembler.resolve(SocketService.self) + c.nodesSource = assembler.resolve(NodesSource.self) + return c + } + + func makeNodeEditorVC() -> NodeEditorViewController { + let c = NodeEditorViewController() + c.dialogService = assembler.resolve(DialogService.self) + c.apiService = assembler.resolve(ApiService.self) + return c + } +} diff --git a/Adamant/Stories/NodesEditor/NodesListViewController.swift b/Adamant/Modules/NodesEditor/NodesListViewController.swift similarity index 98% rename from Adamant/Stories/NodesEditor/NodesListViewController.swift rename to Adamant/Modules/NodesEditor/NodesListViewController.swift index be7260038..927d63dd2 100644 --- a/Adamant/Stories/NodesEditor/NodesListViewController.swift +++ b/Adamant/Modules/NodesEditor/NodesListViewController.swift @@ -25,7 +25,7 @@ extension String.adamant { } // MARK: - NodesListViewController -class NodesListViewController: FormViewController { +final class NodesListViewController: FormViewController { // Rows & Sections private enum Sections { @@ -69,7 +69,7 @@ class NodesListViewController: FormViewController { var dialogService: DialogService! var securedStore: SecuredStore! - var router: Router! + var screensFactory: ScreensFactory! var nodesSource: NodesSource! var apiService: ApiService! { @@ -380,9 +380,7 @@ extension NodesListViewController { } private func presentEditor(forNode node: Node?) { - guard let editor = router.get(scene: AdamantScene.NodesEditor.nodeEditor) as? NodeEditorViewController else { - fatalError("Failed to get editor") - } + let editor = screensFactory.makeNodeEditor() editor.delegate = self editor.node = node diff --git a/Adamant/Stories/Onboard/EulaViewController.swift b/Adamant/Modules/Onboard/EulaViewController.swift similarity index 96% rename from Adamant/Stories/Onboard/EulaViewController.swift rename to Adamant/Modules/Onboard/EulaViewController.swift index eeac0b792..a67c939ce 100644 --- a/Adamant/Stories/Onboard/EulaViewController.swift +++ b/Adamant/Modules/Onboard/EulaViewController.swift @@ -8,7 +8,7 @@ import UIKit -class EulaViewController: UIViewController { +final class EulaViewController: UIViewController { // MARK: Outlets @IBOutlet weak var eulaTextView: UITextView! diff --git a/Adamant/Stories/Onboard/EulaViewController.xib b/Adamant/Modules/Onboard/EulaViewController.xib similarity index 100% rename from Adamant/Stories/Onboard/EulaViewController.xib rename to Adamant/Modules/Onboard/EulaViewController.xib diff --git a/Adamant/Modules/Onboard/OnboardFactory.swift b/Adamant/Modules/Onboard/OnboardFactory.swift new file mode 100644 index 000000000..e05814cf1 --- /dev/null +++ b/Adamant/Modules/Onboard/OnboardFactory.swift @@ -0,0 +1,20 @@ +// +// OnboardFactory.swift +// Adamant +// +// Created by Anokhov Pavel on 18/01/2019. +// Copyright © 2019 Adamant. All rights reserved. +// + +import UIKit +import Swinject + +struct OnboardFactory { + func makeOnboardVC() -> UIViewController { + OnboardViewController(nibName: "OnboardViewController", bundle: nil) + } + + func makeEulaVC() -> UIViewController { + EulaViewController(nibName: "EulaViewController", bundle: nil) + } +} diff --git a/Adamant/Stories/Onboard/OnboardOverlay.swift b/Adamant/Modules/Onboard/OnboardOverlay.swift similarity index 97% rename from Adamant/Stories/Onboard/OnboardOverlay.swift rename to Adamant/Modules/Onboard/OnboardOverlay.swift index 4f391d91f..434e95bb0 100644 --- a/Adamant/Stories/Onboard/OnboardOverlay.swift +++ b/Adamant/Modules/Onboard/OnboardOverlay.swift @@ -9,7 +9,7 @@ import UIKit import CommonKit -class OnboardOverlay: SwiftyOnboardOverlay { +final class OnboardOverlay: SwiftyOnboardOverlay { lazy var agreeSwitch: UISwitch = { let view = UISwitch() diff --git a/Adamant/Stories/Onboard/OnboardPage.swift b/Adamant/Modules/Onboard/OnboardPage.swift similarity index 98% rename from Adamant/Stories/Onboard/OnboardPage.swift rename to Adamant/Modules/Onboard/OnboardPage.swift index 89f2c2ea9..88fcd2679 100644 --- a/Adamant/Stories/Onboard/OnboardPage.swift +++ b/Adamant/Modules/Onboard/OnboardPage.swift @@ -10,7 +10,7 @@ import UIKit import MarkdownKit import CommonKit -class OnboardPage: SwiftyOnboardPage { +final class OnboardPage: SwiftyOnboardPage { @IBOutlet weak var image: UIImageView! @IBOutlet weak var text: UITextView! diff --git a/Adamant/Stories/Onboard/OnboardPage.xib b/Adamant/Modules/Onboard/OnboardPage.xib similarity index 100% rename from Adamant/Stories/Onboard/OnboardPage.xib rename to Adamant/Modules/Onboard/OnboardPage.xib diff --git a/Adamant/Stories/Onboard/OnboardViewController.swift b/Adamant/Modules/Onboard/OnboardViewController.swift similarity index 99% rename from Adamant/Stories/Onboard/OnboardViewController.swift rename to Adamant/Modules/Onboard/OnboardViewController.swift index 33cad5a6d..e9347e293 100644 --- a/Adamant/Stories/Onboard/OnboardViewController.swift +++ b/Adamant/Modules/Onboard/OnboardViewController.swift @@ -30,7 +30,7 @@ fileprivate extension String.adamant { } } -class OnboardViewController: UIViewController { +final class OnboardViewController: UIViewController { // MARK: Constants private static let titleFont = UIFont.adamantPrimary(ofSize: 18) diff --git a/Adamant/Stories/Onboard/OnboardViewController.xib b/Adamant/Modules/Onboard/OnboardViewController.xib similarity index 100% rename from Adamant/Stories/Onboard/OnboardViewController.xib rename to Adamant/Modules/Onboard/OnboardViewController.xib diff --git a/Adamant/Stories/Onboard/ReadonlyTextView.swift b/Adamant/Modules/Onboard/ReadonlyTextView.swift similarity index 86% rename from Adamant/Stories/Onboard/ReadonlyTextView.swift rename to Adamant/Modules/Onboard/ReadonlyTextView.swift index 4baa31817..af7084085 100644 --- a/Adamant/Stories/Onboard/ReadonlyTextView.swift +++ b/Adamant/Modules/Onboard/ReadonlyTextView.swift @@ -8,7 +8,7 @@ import UIKit -class ReadonlyTextView: UITextView { +final class ReadonlyTextView: UITextView { override var selectedTextRange: UITextRange? { get { return nil diff --git a/Adamant/Modules/ScreensFactory/AdamantScreensFactory.swift b/Adamant/Modules/ScreensFactory/AdamantScreensFactory.swift new file mode 100644 index 000000000..a4e85a041 --- /dev/null +++ b/Adamant/Modules/ScreensFactory/AdamantScreensFactory.swift @@ -0,0 +1,166 @@ +// +// AdamantScreensFactory.swift +// Adamant +// +// Created by Andrew G on 10.09.2023. +// Copyright © 2023 Adamant. All rights reserved. +// + +import UIKit +import Swinject + +@MainActor +struct AdamantScreensFactory: ScreensFactory { + private let walletFactoryCompose: WalletFactoryCompose + private let admWalletFactory: AdmWalletFactory + private let chatListFactory: ChatListFactory + private let chatFactory: ChatFactory + private let nodesEditorFactory: NodesEditorFactory + private let delegatesFactory: DelegatesFactory + private let settingsFactory: SettingsFactory + private let contributeFactory: ContributeFactory + private let loginFactory: LoginFactory + private let onboardFactory: OnboardFactory + private let shareQRFactory: ShareQRFactory + private let accountFactory: AccountFactory + + init(assembler: Assembler) { + admWalletFactory = .init(assembler: assembler) + chatListFactory = .init(assembler: assembler) + chatFactory = .init(assembler: assembler) + nodesEditorFactory = .init(assembler: assembler) + delegatesFactory = .init(assembler: assembler) + settingsFactory = .init(assembler: assembler) + contributeFactory = .init(parent: assembler) + loginFactory = .init(assembler: assembler) + onboardFactory = .init() + shareQRFactory = .init(assembler: assembler) + accountFactory = .init(assembler: assembler) + + walletFactoryCompose = AdamantWalletFactoryCompose( + lskWalletFactory: .init(assembler: assembler), + dogeWalletFactory: .init(assembler: assembler), + dashWalletFactory: .init(assembler: assembler), + btcWalletFactory: .init(assembler: assembler), + ethWalletFactory: .init(assembler: assembler), + erc20WalletFactory: .init(assembler: assembler), + admWalletFactory: admWalletFactory + ) + } + + func makeWalletVC(service: WalletService) -> WalletViewController { + walletFactoryCompose.makeWalletVC(service: service, screensFactory: self) + } + + func makeTransferListVC(service: WalletService) -> UIViewController { + walletFactoryCompose.makeTransferListVC(service: service, screenFactory: self) + } + + func makeTransferVC(service: WalletService) -> TransferViewControllerBase { + walletFactoryCompose.makeTransferVC(service: service, screenFactory: self) + } + + func makeDetailsVC(service: WalletService) -> TransactionDetailsViewControllerBase { + walletFactoryCompose.makeDetailsVC(service: service) + } + + func makeDetailsVC(service: WalletService, transaction: RichMessageTransaction) -> UIViewController? { + walletFactoryCompose.makeDetailsVC(service: service, transaction: transaction) + } + + func makeAdmTransactionDetails(transaction: TransferTransaction) -> UIViewController { + admWalletFactory.makeDetailsVC(transaction: transaction, screensFactory: self) + } + + func makeAdmTransactionDetails() -> AdmTransactionDetailsViewController { + admWalletFactory.makeDetailsVC(screensFactory: self) + } + + func makeBuyAndSell() -> UIViewController { + admWalletFactory.makeBuyAndSellVC() + } + + func makeChatList() -> UIViewController { + chatListFactory.makeChatListVC(screensFactory: self) + } + + func makeChat() -> ChatViewController { + chatFactory.makeViewController(screensFactory: self) + } + + func makeNewChat() -> NewChatViewController { + chatListFactory.makeNewChatVC(screensFactory: self) + } + + func makeDelegatesList() -> UIViewController { + delegatesFactory.makeDelegatesListVC(screensFactory: self) + } + + func makeDelegateDetails() -> DelegateDetailsViewController { + delegatesFactory.makeDelegateDetails() + } + + func makeNodesList() -> UIViewController { + nodesEditorFactory.makeNodesListVC(screensFactory: self) + } + + func makeNodeEditor() -> NodeEditorViewController { + nodesEditorFactory.makeNodeEditorVC() + } + + func makeEula() -> UIViewController { + onboardFactory.makeEulaVC() + } + + func makeOnboard() -> UIViewController { + onboardFactory.makeOnboardVC() + } + + func makeShareQr() -> ShareQrViewController { + shareQRFactory.makeViewController() + } + + func makeAccount() -> UIViewController { + accountFactory.makeViewController(screensFactory: self) + } + + func makeComplexTransfer() -> UIViewController { + chatListFactory.makeComplexTransferVC(screensFactory: self) + } + + func makeSearchResults() -> SearchResultsViewController { + chatListFactory.makeSearchResultsViewController(screensFactory: self) + } + + func makeSecurity() -> UIViewController { + settingsFactory.makeSecurityVC(screensFactory: self) + } + + func makeQRGenerator() -> UIViewController { + settingsFactory.makeQRGeneratorVC() + } + + func makePKGenerator() -> UIViewController { + settingsFactory.makePKGeneratorVC() + } + + func makeAbout() -> UIViewController { + settingsFactory.makeAboutVC(screensFactory: self) + } + + func makeNotifications() -> UIViewController { + settingsFactory.makeNotificationsVC() + } + + func makeVisibleWallets() -> UIViewController { + settingsFactory.makeVisibleWalletsVC() + } + + func makeContribute() -> UIViewController { + contributeFactory.makeViewController() + } + + func makeLogin() -> LoginViewController { + loginFactory.makeViewController(screenFactory: self) + } +} diff --git a/Adamant/Modules/ScreensFactory/ScreensFactory.swift b/Adamant/Modules/ScreensFactory/ScreensFactory.swift new file mode 100644 index 000000000..fd46c9796 --- /dev/null +++ b/Adamant/Modules/ScreensFactory/ScreensFactory.swift @@ -0,0 +1,61 @@ +// +// ScreensFactory.swift +// Adamant +// +// Created by Andrew G on 10.09.2023. +// Copyright © 2023 Adamant. All rights reserved. +// + +import UIKit + +@MainActor +protocol ScreensFactory { + // MARK: Wallets + + func makeWalletVC(service: WalletService) -> WalletViewController + func makeTransferListVC(service: WalletService) -> UIViewController + func makeTransferVC(service: WalletService) -> TransferViewControllerBase + func makeDetailsVC(service: WalletService) -> TransactionDetailsViewControllerBase + + func makeDetailsVC( + service: WalletService, + transaction: RichMessageTransaction + ) -> UIViewController? + + func makeAdmTransactionDetails(transaction: TransferTransaction) -> UIViewController + func makeAdmTransactionDetails() -> AdmTransactionDetailsViewController + func makeBuyAndSell() -> UIViewController + + // MARK: Chats + + func makeChat() -> ChatViewController + func makeChatList() -> UIViewController + func makeNewChat() -> NewChatViewController + func makeComplexTransfer() -> UIViewController + func makeSearchResults() -> SearchResultsViewController + + // MARK: Delegates + + func makeDelegatesList() -> UIViewController + func makeDelegateDetails() -> DelegateDetailsViewController + + // MARK: Nodes + + func makeNodesList() -> UIViewController + func makeNodeEditor() -> NodeEditorViewController + + // MARK: Other + + func makeEula() -> UIViewController + func makeOnboard() -> UIViewController + func makeShareQr() -> ShareQrViewController + func makeAccount() -> UIViewController + func makeSecurity() -> UIViewController + func makeQRGenerator() -> UIViewController + func makePKGenerator() -> UIViewController + func makeAbout() -> UIViewController + func makeNotifications() -> UIViewController + func makeVisibleWallets() -> UIViewController + func makeContribute() -> UIViewController + func makeLogin() -> LoginViewController +} diff --git a/Adamant/Stories/Settings/AboutViewController.swift b/Adamant/Modules/Settings/AboutViewController.swift similarity index 94% rename from Adamant/Stories/Settings/AboutViewController.swift rename to Adamant/Modules/Settings/AboutViewController.swift index 31839b4b3..7f9225c09 100644 --- a/Adamant/Stories/Settings/AboutViewController.swift +++ b/Adamant/Modules/Settings/AboutViewController.swift @@ -22,7 +22,7 @@ extension String.adamant { } // MARK: - AboutViewController -class AboutViewController: FormViewController { +final class AboutViewController: FormViewController { // MARK: Section & Rows enum Sections { @@ -105,7 +105,7 @@ class AboutViewController: FormViewController { var accountService: AccountService! var accountsProvider: AccountsProvider! var dialogService: DialogService! - var router: Router! + var screensFactory: ScreensFactory! // MARK: Properties private var storedIOSSupportMessage: String? @@ -186,14 +186,10 @@ class AboutViewController: FormViewController { }.cellUpdate { (cell, _) in cell.accessoryType = .disclosureIndicator }.onCellSelection { [weak self] (_, _) in - guard let vc = self?.router.get(scene: AdamantScene.Onboard.welcome) else { - if let tableView = self?.tableView, let indexPath = tableView.indexPathForSelectedRow { - tableView.deselectRow(at: indexPath, animated: true) - } - return - } + guard let self = self else { return } + let vc = self.screensFactory.makeOnboard() vc.modalPresentationStyle = .overFullScreen - self?.present(vc, animated: true, completion: nil) + self.present(vc, animated: true, completion: nil) } // MARK: Contact @@ -261,13 +257,13 @@ class AboutViewController: FormViewController { do { let account = try await accountsProvider.getAccount(byAddress: AdamantContacts.adamantSupport.address) - guard let chatroom = account.chatroom, - let nav = self.navigationController, - let account = self.accountService.account, - let chat = router.get(scene: AdamantScene.Chats.chat) as? ChatViewController else { - return - } + guard + let chatroom = account.chatroom, + let nav = navigationController, + let account = accountService.account + else { return } + let chat = screensFactory.makeChat() chat.hidesBottomBarWhenPushed = true chat.viewModel.setup( account: account, diff --git a/Adamant/Modules/Settings/Contribute/ContributeFactory.swift b/Adamant/Modules/Settings/Contribute/ContributeFactory.swift new file mode 100644 index 000000000..a8c13a4ec --- /dev/null +++ b/Adamant/Modules/Settings/Contribute/ContributeFactory.swift @@ -0,0 +1,36 @@ +// +// ContributeFactory.swift +// Adamant +// +// Created by Stanislav Jelezoglo on 14.06.2023. +// Copyright © 2023 Adamant. All rights reserved. +// + +import Swinject +import SwiftUI + +struct ContributeFactory { + private let assembler: Assembler + + init(parent: Assembler) { + assembler = .init([ContributeAssembly()], parent: parent) + } + + func makeViewController() -> UIViewController { + UIHostingController( + rootView: ContributeView( + viewModel: assembler.resolve(ContributeViewModel.self)! + ) + ) + } +} + +private struct ContributeAssembly: Assembly { + func assemble(container: Container) { + container.register(ContributeViewModel.self) { + ContributeViewModel( + crashliticsService: $0.resolve(CrashlyticsService.self)! + ) + }.inObjectScope(.weak) + } +} diff --git a/Adamant/Stories/Settings/Contribute/ContributeState.swift b/Adamant/Modules/Settings/Contribute/ContributeState.swift similarity index 100% rename from Adamant/Stories/Settings/Contribute/ContributeState.swift rename to Adamant/Modules/Settings/Contribute/ContributeState.swift diff --git a/Adamant/Stories/Settings/Contribute/ContributeView.swift b/Adamant/Modules/Settings/Contribute/ContributeView.swift similarity index 100% rename from Adamant/Stories/Settings/Contribute/ContributeView.swift rename to Adamant/Modules/Settings/Contribute/ContributeView.swift diff --git a/Adamant/Stories/Settings/Contribute/ContributeViewModel.swift b/Adamant/Modules/Settings/Contribute/ContributeViewModel.swift similarity index 87% rename from Adamant/Stories/Settings/Contribute/ContributeViewModel.swift rename to Adamant/Modules/Settings/Contribute/ContributeViewModel.swift index 32e042772..b3011378b 100644 --- a/Adamant/Stories/Settings/Contribute/ContributeViewModel.swift +++ b/Adamant/Modules/Settings/Contribute/ContributeViewModel.swift @@ -17,14 +17,9 @@ final class ContributeViewModel: ObservableObject { @Published var state: ContributeState = .initial - init(crashliticsService: CrashlyticsService) { + nonisolated init(crashliticsService: CrashlyticsService) { self.crashliticsService = crashliticsService - state.isCrashlyticsOn = crashliticsService.isCrashlyticsEnabled() - - $state.map(\.isCrashlyticsOn) - .removeDuplicates() - .sink { [weak crashliticsService] in crashliticsService?.setCrashlyticsEnabled($0) } - .store(in: &subscriptions) + Task { await setup() } } func enableCrashButton() { @@ -41,3 +36,14 @@ final class ContributeViewModel: ObservableObject { fatalError("Test crash") } } + +private extension ContributeViewModel { + func setup() { + state.isCrashlyticsOn = crashliticsService.isCrashlyticsEnabled() + + $state.map(\.isCrashlyticsOn) + .removeDuplicates() + .sink { [weak crashliticsService] in crashliticsService?.setCrashlyticsEnabled($0) } + .store(in: &subscriptions) + } +} diff --git a/Adamant/Stories/Settings/NotificationSoundsViewController.swift b/Adamant/Modules/Settings/NotificationSoundsViewController.swift similarity index 98% rename from Adamant/Stories/Settings/NotificationSoundsViewController.swift rename to Adamant/Modules/Settings/NotificationSoundsViewController.swift index 2aca3ba84..4d5f1e2b8 100644 --- a/Adamant/Stories/Settings/NotificationSoundsViewController.swift +++ b/Adamant/Modules/Settings/NotificationSoundsViewController.swift @@ -11,7 +11,7 @@ import Eureka import AudioToolbox import AVFoundation -class NotificationSoundsViewController: FormViewController { +final class NotificationSoundsViewController: FormViewController { // MARK: Sections & Rows enum Sections { diff --git a/Adamant/Stories/Settings/NotificationsViewController.swift b/Adamant/Modules/Settings/NotificationsViewController.swift similarity index 99% rename from Adamant/Stories/Settings/NotificationsViewController.swift rename to Adamant/Modules/Settings/NotificationsViewController.swift index e6d100d66..66d1b2e58 100644 --- a/Adamant/Stories/Settings/NotificationsViewController.swift +++ b/Adamant/Modules/Settings/NotificationsViewController.swift @@ -13,7 +13,7 @@ import MarkdownKit import ProcedureKit import CommonKit -class NotificationsViewController: FormViewController { +final class NotificationsViewController: FormViewController { // MARK: Sections & Rows enum Sections { diff --git a/Adamant/Stories/Settings/PKGeneratorViewController.swift b/Adamant/Modules/Settings/PKGeneratorViewController.swift similarity index 99% rename from Adamant/Stories/Settings/PKGeneratorViewController.swift rename to Adamant/Modules/Settings/PKGeneratorViewController.swift index 53ad3aa19..f970fe9f5 100644 --- a/Adamant/Stories/Settings/PKGeneratorViewController.swift +++ b/Adamant/Modules/Settings/PKGeneratorViewController.swift @@ -25,7 +25,7 @@ extension String.adamant { } // MARK: - -class PKGeneratorViewController: FormViewController { +final class PKGeneratorViewController: FormViewController { // MARK: Dependencies var accountService: AccountService! diff --git a/Adamant/Stories/Settings/PrivateKeyGenerator.swift b/Adamant/Modules/Settings/PrivateKeyGenerator.swift similarity index 100% rename from Adamant/Stories/Settings/PrivateKeyGenerator.swift rename to Adamant/Modules/Settings/PrivateKeyGenerator.swift diff --git a/Adamant/Stories/Settings/QRGeneratorViewController.swift b/Adamant/Modules/Settings/QRGeneratorViewController.swift similarity index 99% rename from Adamant/Stories/Settings/QRGeneratorViewController.swift rename to Adamant/Modules/Settings/QRGeneratorViewController.swift index 21474685c..5a0df561c 100644 --- a/Adamant/Stories/Settings/QRGeneratorViewController.swift +++ b/Adamant/Modules/Settings/QRGeneratorViewController.swift @@ -28,7 +28,7 @@ extension String.adamant { } // MARK: - -class QRGeneratorViewController: FormViewController { +final class QRGeneratorViewController: FormViewController { // MARK: Dependencies var dialogService: DialogService! diff --git a/Adamant/Stories/Settings/SecurityViewController+StayIn.swift b/Adamant/Modules/Settings/SecurityViewController+StayIn.swift similarity index 100% rename from Adamant/Stories/Settings/SecurityViewController+StayIn.swift rename to Adamant/Modules/Settings/SecurityViewController+StayIn.swift diff --git a/Adamant/Stories/Settings/SecurityViewController+notifications.swift b/Adamant/Modules/Settings/SecurityViewController+notifications.swift similarity index 100% rename from Adamant/Stories/Settings/SecurityViewController+notifications.swift rename to Adamant/Modules/Settings/SecurityViewController+notifications.swift diff --git a/Adamant/Stories/Settings/SecurityViewController.swift b/Adamant/Modules/Settings/SecurityViewController.swift similarity index 97% rename from Adamant/Stories/Settings/SecurityViewController.swift rename to Adamant/Modules/Settings/SecurityViewController.swift index ddf9928f5..914aeb37b 100644 --- a/Adamant/Stories/Settings/SecurityViewController.swift +++ b/Adamant/Modules/Settings/SecurityViewController.swift @@ -33,7 +33,7 @@ extension NotificationsMode: CustomStringConvertible { } // MARK: - SecurityViewController -class SecurityViewController: FormViewController { +final class SecurityViewController: FormViewController { enum PinpadRequest { case createPin @@ -101,7 +101,7 @@ class SecurityViewController: FormViewController { var dialogService: DialogService! var notificationsService: NotificationsService! var localAuth: LocalAuthentication! - var router: Router! + var screensFactory: ScreensFactory! // MARK: - Properties var showLoggedInOptions: Bool { @@ -137,11 +137,8 @@ class SecurityViewController: FormViewController { }.cellUpdate { (cell, _) in cell.accessoryType = .disclosureIndicator }.onCellSelection { [weak self] (_, _) in - guard let nav = self?.navigationController, let vc = self?.router.get(scene: AdamantScene.Settings.qRGenerator) else { - return - } - - nav.pushViewController(vc, animated: true) + guard let vc = self?.screensFactory.makeQRGenerator() else { return } + self?.navigationController?.pushViewController(vc, animated: true) } // Stay logged in diff --git a/Adamant/Modules/Settings/SettingsFactory.swift b/Adamant/Modules/Settings/SettingsFactory.swift new file mode 100644 index 000000000..bf74ee0e5 --- /dev/null +++ b/Adamant/Modules/Settings/SettingsFactory.swift @@ -0,0 +1,61 @@ +// +// SettingsFactory.swift +// Adamant +// +// Created by Anokhov Pavel on 01.02.2018. +// Copyright © 2018 Adamant. All rights reserved. +// + +import UIKit +import CommonKit +import Swinject + +struct SettingsFactory { + let assembler: Assembler + + func makeSecurityVC(screensFactory: ScreensFactory) -> UIViewController { + let c = SecurityViewController() + c.accountService = assembler.resolve(AccountService.self) + c.dialogService = assembler.resolve(DialogService.self) + c.notificationsService = assembler.resolve(NotificationsService.self) + c.localAuth = assembler.resolve(LocalAuthentication.self) + c.screensFactory = screensFactory + return c + } + + func makeQRGeneratorVC() -> UIViewController { + let c = QRGeneratorViewController() + c.dialogService = assembler.resolve(DialogService.self) + return c + } + + func makePKGeneratorVC() -> UIViewController { + let c = PKGeneratorViewController() + c.dialogService = assembler.resolve(DialogService.self) + c.accountService = assembler.resolve(AccountService.self) + return c + } + + func makeAboutVC(screensFactory: ScreensFactory) -> UIViewController { + let c = AboutViewController() + c.accountService = assembler.resolve(AccountService.self) + c.accountsProvider = assembler.resolve(AccountsProvider.self) + c.dialogService = assembler.resolve(DialogService.self) + c.screensFactory = screensFactory + return c + } + + func makeNotificationsVC() -> UIViewController { + let c = NotificationsViewController() + c.notificationsService = assembler.resolve(NotificationsService.self) + c.dialogService = assembler.resolve(DialogService.self) + return c + } + + func makeVisibleWalletsVC() -> UIViewController { + VisibleWalletsViewController( + visibleWalletsService: assembler.resolve(VisibleWalletsService.self)!, + accountService: assembler.resolve(AccountService.self)! + ) + } +} diff --git a/Adamant/Stories/Settings/VisibleWallets/VisibleWalletsCheckmarkView.swift b/Adamant/Modules/Settings/VisibleWallets/VisibleWalletsCheckmarkView.swift similarity index 100% rename from Adamant/Stories/Settings/VisibleWallets/VisibleWalletsCheckmarkView.swift rename to Adamant/Modules/Settings/VisibleWallets/VisibleWalletsCheckmarkView.swift diff --git a/Adamant/Stories/Settings/VisibleWallets/VisibleWalletsResetTableViewCell.swift b/Adamant/Modules/Settings/VisibleWallets/VisibleWalletsResetTableViewCell.swift similarity index 93% rename from Adamant/Stories/Settings/VisibleWallets/VisibleWalletsResetTableViewCell.swift rename to Adamant/Modules/Settings/VisibleWallets/VisibleWalletsResetTableViewCell.swift index d54024c40..f4527162b 100644 --- a/Adamant/Stories/Settings/VisibleWallets/VisibleWalletsResetTableViewCell.swift +++ b/Adamant/Modules/Settings/VisibleWallets/VisibleWalletsResetTableViewCell.swift @@ -9,7 +9,7 @@ import UIKit import CommonKit -class VisibleWalletsResetTableViewCell: UITableViewCell { +final class VisibleWalletsResetTableViewCell: UITableViewCell { private lazy var titleLabel: UILabel = { let label = UILabel() diff --git a/Adamant/Stories/Settings/VisibleWallets/VisibleWalletsTableViewCell.swift b/Adamant/Modules/Settings/VisibleWallets/VisibleWalletsTableViewCell.swift similarity index 97% rename from Adamant/Stories/Settings/VisibleWallets/VisibleWalletsTableViewCell.swift rename to Adamant/Modules/Settings/VisibleWallets/VisibleWalletsTableViewCell.swift index 1d6eba394..0e5490cd5 100644 --- a/Adamant/Stories/Settings/VisibleWallets/VisibleWalletsTableViewCell.swift +++ b/Adamant/Modules/Settings/VisibleWallets/VisibleWalletsTableViewCell.swift @@ -15,7 +15,7 @@ protocol AdamantVisibleWalletsCellDelegate: AnyObject { } // MARK: - Cell -class VisibleWalletsTableViewCell: UITableViewCell { +final class VisibleWalletsTableViewCell: UITableViewCell { private let checkmarkRowView = VisibleWalletsCheckmarkRowView() weak var delegate: AdamantVisibleWalletsCellDelegate? { diff --git a/Adamant/Stories/Settings/VisibleWallets/VisibleWalletsViewController.swift b/Adamant/Modules/Settings/VisibleWallets/VisibleWalletsViewController.swift similarity index 100% rename from Adamant/Stories/Settings/VisibleWallets/VisibleWalletsViewController.swift rename to Adamant/Modules/Settings/VisibleWallets/VisibleWalletsViewController.swift diff --git a/Adamant/Modules/ShareQR/ShareQRFactory.swift b/Adamant/Modules/ShareQR/ShareQRFactory.swift new file mode 100644 index 000000000..eb30dd85b --- /dev/null +++ b/Adamant/Modules/ShareQR/ShareQRFactory.swift @@ -0,0 +1,18 @@ +// +// ShareQRFactory.swift +// Adamant +// +// Created by Anokhov Pavel on 17.03.2018. +// Copyright © 2018 Adamant. All rights reserved. +// + +import Swinject +import UIKit + +struct ShareQRFactory { + let assembler: Assembler + + func makeViewController() -> ShareQrViewController { + ShareQrViewController(dialogService: assembler.resolve(DialogService.self)!) + } +} diff --git a/Adamant/Stories/Shared/ShareQrViewController.swift b/Adamant/Modules/ShareQR/ShareQrViewController.swift similarity index 94% rename from Adamant/Stories/Shared/ShareQrViewController.swift rename to Adamant/Modules/ShareQR/ShareQrViewController.swift index bb47803de..7eab26f34 100644 --- a/Adamant/Stories/Shared/ShareQrViewController.swift +++ b/Adamant/Modules/ShareQR/ShareQrViewController.swift @@ -15,9 +15,9 @@ extension String.adamant.shared { static let photolibraryNotAuthorized = String.localized("ShareQR.photolibraryNotAuthorized", comment: "ShareQR scene: User had not authorized access to write images to photolibrary") } -class ShareQrViewController: FormViewController { +final class ShareQrViewController: FormViewController { // MARK: - Dependencies - var dialogService: DialogService! + private let dialogService: DialogService // MARK: - Rows private enum Rows { @@ -79,6 +79,15 @@ class ShareQrViewController: FormViewController { var excludedActivityTypes: [UIActivity.ActivityType]? + init(dialogService: DialogService) { + self.dialogService = dialogService + super.init(nibName: "ShareQrViewController", bundle: nil) + } + + required init?(coder aDecoder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + // MARK: - Lifecycle override func viewDidLoad() { super.viewDidLoad() diff --git a/Adamant/Stories/Shared/ShareQrViewController.xib b/Adamant/Modules/ShareQR/ShareQrViewController.xib similarity index 100% rename from Adamant/Stories/Shared/ShareQrViewController.xib rename to Adamant/Modules/ShareQR/ShareQrViewController.xib diff --git a/Adamant/Stories/SwiftyOnboard/SwiftyOnboard.swift b/Adamant/Modules/SwiftyOnboard/SwiftyOnboard.swift similarity index 100% rename from Adamant/Stories/SwiftyOnboard/SwiftyOnboard.swift rename to Adamant/Modules/SwiftyOnboard/SwiftyOnboard.swift diff --git a/Adamant/Stories/SwiftyOnboard/SwiftyOnboardOverlay.swift b/Adamant/Modules/SwiftyOnboard/SwiftyOnboardOverlay.swift similarity index 100% rename from Adamant/Stories/SwiftyOnboard/SwiftyOnboardOverlay.swift rename to Adamant/Modules/SwiftyOnboard/SwiftyOnboardOverlay.swift diff --git a/Adamant/Stories/SwiftyOnboard/SwiftyOnboardPage.swift b/Adamant/Modules/SwiftyOnboard/SwiftyOnboardPage.swift similarity index 100% rename from Adamant/Stories/SwiftyOnboard/SwiftyOnboardPage.swift rename to Adamant/Modules/SwiftyOnboard/SwiftyOnboardPage.swift diff --git a/Adamant/Wallets/Adamant/AdmTransactionDetailsViewController.swift b/Adamant/Modules/Wallets/Adamant/AdmTransactionDetailsViewController.swift similarity index 92% rename from Adamant/Wallets/Adamant/AdmTransactionDetailsViewController.swift rename to Adamant/Modules/Wallets/Adamant/AdmTransactionDetailsViewController.swift index cc0d2d236..dc6205062 100644 --- a/Adamant/Wallets/Adamant/AdmTransactionDetailsViewController.swift +++ b/Adamant/Modules/Wallets/Adamant/AdmTransactionDetailsViewController.swift @@ -10,12 +10,12 @@ import UIKit import Eureka import CommonKit -class AdmTransactionDetailsViewController: TransactionDetailsViewControllerBase { +final class AdmTransactionDetailsViewController: TransactionDetailsViewControllerBase { // MARK: - Dependencies let transfersProvider: TransfersProvider - let router: Router + let screensFactory: ScreensFactory // MARK: - Properties private let autoupdateInterval: TimeInterval = 5.0 @@ -36,13 +36,13 @@ class AdmTransactionDetailsViewController: TransactionDetailsViewControllerBase init( accountService: AccountService, transfersProvider: TransfersProvider, - router: Router, + screensFactory: ScreensFactory, dialogService: DialogService, currencyInfo: CurrencyInfoService, addressBookService: AddressBookService ) { self.transfersProvider = transfersProvider - self.router = router + self.screensFactory = screensFactory super.init( dialogService: dialogService, @@ -117,11 +117,6 @@ class AdmTransactionDetailsViewController: TransactionDetailsViewControllerBase guard let transfer = transaction as? TransferTransaction else { return } - - guard let vc = self.router.get(scene: AdamantScene.Chats.chat) as? ChatViewController else { - dialogService.showError(withMessage: "AdmTransactionDetailsViewController: Failed to get ChatViewController", supportEmail: true, error: nil) - return - } guard let chatroom = transfer.chatroom else { dialogService.showError(withMessage: "AdmTransactionDetailsViewController: Failed to get chatroom for transaction.", supportEmail: true, error: nil) @@ -133,6 +128,7 @@ class AdmTransactionDetailsViewController: TransactionDetailsViewControllerBase return } + let vc = screensFactory.makeChat() vc.hidesBottomBarWhenPushed = true vc.viewModel.setup( account: account, diff --git a/Adamant/Wallets/Adamant/AdmTransactionsViewController.swift b/Adamant/Modules/Wallets/Adamant/AdmTransactionsViewController.swift similarity index 90% rename from Adamant/Wallets/Adamant/AdmTransactionsViewController.swift rename to Adamant/Modules/Wallets/Adamant/AdmTransactionsViewController.swift index 120d3ce43..6229d9107 100644 --- a/Adamant/Wallets/Adamant/AdmTransactionsViewController.swift +++ b/Adamant/Modules/Wallets/Adamant/AdmTransactionsViewController.swift @@ -10,16 +10,17 @@ import UIKit import CoreData import CommonKit -class AdmTransactionsViewController: TransactionsListViewControllerBase { +final class AdmTransactionsViewController: TransactionsListViewControllerBase { // MARK: - Dependencies - var accountService: AccountService - var transfersProvider: TransfersProvider - var chatsProvider: ChatsProvider - var dialogService: DialogService - var stack: CoreDataStack - var router: Router - var addressBookService: AddressBookService + let accountService: AccountService + let transfersProvider: TransfersProvider + let chatsProvider: ChatsProvider + let dialogService: DialogService + let stack: CoreDataStack + let screensFactory: ScreensFactory + let addressBookService: AddressBookService + let admService: AdmWalletService // MARK: - Properties @@ -43,16 +44,18 @@ class AdmTransactionsViewController: TransactionsListViewControllerBase { chatsProvider: ChatsProvider, dialogService: DialogService, stack: CoreDataStack, - router: Router, - addressBookService: AddressBookService + screensFactory: ScreensFactory, + addressBookService: AddressBookService, + admService: AdmWalletService ) { self.accountService = accountService self.transfersProvider = transfersProvider self.chatsProvider = chatsProvider self.dialogService = dialogService self.stack = stack - self.router = router + self.screensFactory = screensFactory self.addressBookService = addressBookService + self.admService = admService super.init(nibName: nibNameOrNil, bundle: nibBundleOrNil) } @@ -208,10 +211,7 @@ class AdmTransactionsViewController: TransactionsListViewControllerBase { return } - guard let controller = router.get(scene: AdamantScene.Wallets.Adamant.transactionDetails) as? AdmTransactionDetailsViewController else { - return - } - + let controller = screensFactory.makeAdmTransactionDetails() controller.transaction = transaction controller.comment = transaction.comment @@ -291,16 +291,13 @@ class AdmTransactionsViewController: TransactionsListViewControllerBase { let title = (messeges != nil) ? String.adamant.transactionList.toChat : String.adamant.transactionList.startChat - let toChat = UITableViewRowAction(style: .normal, title: title) { _, _ in - guard let vc = self.router.get(scene: AdamantScene.Chats.chat) as? ChatViewController else { - // TODO: Log this - return - } - - guard let account = self.accountService.account else { - return - } + let toChat = UITableViewRowAction(style: .normal, title: title) { [weak self] _, _ in + guard + let self = self, + let account = accountService.account + else { return } + let vc = screensFactory.makeChat() vc.hidesBottomBarWhenPushed = true vc.viewModel.setup( account: account, @@ -313,7 +310,7 @@ class AdmTransactionsViewController: TransactionsListViewControllerBase { nav.pushViewController(vc, animated: true) } else { vc.modalPresentationStyle = .overFullScreen - self.present(vc, animated: true) + present(vc, animated: true) } } @@ -342,16 +339,13 @@ class AdmTransactionsViewController: TransactionsListViewControllerBase { let title = (messeges != nil) ? String.adamant.transactionList.toChat : String.adamant.transactionList.startChat - let toChat = UIContextualAction(style: .normal, title: title, handler: { (_, _, _) in - guard let vc = self.router.get(scene: AdamantScene.Chats.chat) as? ChatViewController else { - // TODO: Log this - return - } - - guard let account = self.accountService.account else { - return - } + let toChat = UIContextualAction(style: .normal, title: title) { [weak self] (_, _, _) in + guard + let self = self, + let account = accountService.account + else { return } + let vc = screensFactory.makeChat() vc.hidesBottomBarWhenPushed = true vc.viewModel.setup( account: account, @@ -364,9 +358,9 @@ class AdmTransactionsViewController: TransactionsListViewControllerBase { nav.pushViewController(vc, animated: true) } else { vc.modalPresentationStyle = .overFullScreen - self.present(vc, animated: true) + present(vc, animated: true) } - }) + } toChat.image = .asset(named: "chats_tab") toChat.backgroundColor = UIColor.adamant.primary diff --git a/Adamant/Wallets/Adamant/AdmTransferViewController.swift b/Adamant/Modules/Wallets/Adamant/AdmTransferViewController.swift similarity index 94% rename from Adamant/Wallets/Adamant/AdmTransferViewController.swift rename to Adamant/Modules/Wallets/Adamant/AdmTransferViewController.swift index 6a2379dd1..0b96a1805 100644 --- a/Adamant/Wallets/Adamant/AdmTransferViewController.swift +++ b/Adamant/Modules/Wallets/Adamant/AdmTransferViewController.swift @@ -128,26 +128,32 @@ final class AdmTransferViewController: TransferViewControllerBase { } } - private func openDetailVC(result: TransactionDetails, vc: AdmTransferViewController, recipient: String, comments: String) { - let detailsVC = router.get(scene: AdamantScene.Wallets.Adamant.transactionDetails) as? AdmTransactionDetailsViewController - detailsVC?.transaction = result + private func openDetailVC( + result: TransactionDetails, + vc: AdmTransferViewController, + recipient: String, + comments: String + ) { + guard let service = service else { return } + let detailsVC = screensFactory.makeDetailsVC(service: service) + detailsVC.transaction = result if comments.count > 0 { - detailsVC?.comment = comments + detailsVC.comment = comments } // MARK: Sender, you - detailsVC?.senderName = String.adamant.transactionDetails.yourAddress + detailsVC.senderName = String.adamant.transactionDetails.yourAddress // MARK: Get recipient if let recipientName = recipientName { - detailsVC?.recipientName = recipientName + detailsVC.recipientName = recipientName vc.delegate?.transferViewController(vc, didFinishWithTransfer: result, detailsViewController: detailsVC) } else { - Task { + Task { do { let account = try await accountsProvider.getAccount(byAddress: recipient) - detailsVC?.recipientName = account.name + detailsVC.recipientName = account.name vc.delegate?.transferViewController(vc, didFinishWithTransfer: result, detailsViewController: detailsVC) } catch { vc.delegate?.transferViewController(vc, didFinishWithTransfer: result, detailsViewController: detailsVC) diff --git a/Adamant/Wallets/Adamant/AdmWallet.swift b/Adamant/Modules/Wallets/Adamant/AdmWallet.swift similarity index 91% rename from Adamant/Wallets/Adamant/AdmWallet.swift rename to Adamant/Modules/Wallets/Adamant/AdmWallet.swift index 53a7e412f..804a31084 100644 --- a/Adamant/Wallets/Adamant/AdmWallet.swift +++ b/Adamant/Modules/Wallets/Adamant/AdmWallet.swift @@ -8,7 +8,7 @@ import Foundation -class AdmWallet: WalletAccount { +final class AdmWallet: WalletAccount { let address: String var balance: Decimal = 0 var notifications: Int = 0 diff --git a/Adamant/Modules/Wallets/Adamant/AdmWalletFactory.swift b/Adamant/Modules/Wallets/Adamant/AdmWalletFactory.swift new file mode 100644 index 000000000..006a55fee --- /dev/null +++ b/Adamant/Modules/Wallets/Adamant/AdmWalletFactory.swift @@ -0,0 +1,95 @@ +// +// AdmWalletFactory.swift +// Adamant +// +// Created by Anokhov Pavel on 28.08.2018. +// Copyright © 2018 Adamant. All rights reserved. +// + +import Swinject +import UIKit + +struct AdmWalletFactory: WalletFactory { + typealias Service = AdmWalletService + + let assembler: Assembler + + func makeWalletVC(service: AdmWalletService, screensFactory: ScreensFactory) -> WalletViewController { + let c = AdmWalletViewController(nibName: "WalletViewControllerBase", bundle: nil) + c.dialogService = assembler.resolve(DialogService.self) + c.currencyInfoService = assembler.resolve(CurrencyInfoService.self) + c.accountService = assembler.resolve(AccountService.self) + c.service = service + c.screensFactory = screensFactory + return c + } + + func makeTransferListVC(service: Service, screensFactory: ScreensFactory) -> UIViewController { + AdmTransactionsViewController( + nibName: "TransactionsListViewControllerBase", + bundle: nil, + accountService: assembler.resolve(AccountService.self)!, + transfersProvider: assembler.resolve(TransfersProvider.self)!, + chatsProvider: assembler.resolve(ChatsProvider.self)!, + dialogService: assembler.resolve(DialogService.self)!, + stack: assembler.resolve(CoreDataStack.self)!, + screensFactory: screensFactory, + addressBookService: assembler.resolve(AddressBookService.self)!, + admService: service + ) + } + + func makeTransferVC(service: Service, screensFactory: ScreensFactory) -> TransferViewControllerBase { + let vc = AdmTransferViewController( + chatsProvider: assembler.resolve(ChatsProvider.self)!, + accountService: assembler.resolve(AccountService.self)!, + accountsProvider: assembler.resolve(AccountsProvider.self)!, + dialogService: assembler.resolve(DialogService.self)!, + screensFactory: screensFactory, + currencyInfoService: assembler.resolve(CurrencyInfoService.self)!, + increaseFeeService: assembler.resolve(IncreaseFeeService.self)! + ) + + vc.service = service + return vc + } + + func makeDetailsVC(service: Service, transaction: RichMessageTransaction) -> UIViewController? { nil } + + func makeDetailsVC(service: Service) -> TransactionDetailsViewControllerBase { + fatalError("ScreensFactory in necessary for AdmTransactionDetailsViewController") + } + + func makeDetailsVC(screensFactory: ScreensFactory) -> AdmTransactionDetailsViewController { + makeTransactionDetailsVC(screensFactory: screensFactory) + } + + func makeDetailsVC(transaction: TransferTransaction, screensFactory: ScreensFactory) -> UIViewController { + let controller = makeTransactionDetailsVC(screensFactory: screensFactory) + controller.transaction = transaction + controller.comment = transaction.comment + controller.senderId = transaction.senderId + controller.recipientId = transaction.recipientId + return controller + } + + func makeBuyAndSellVC() -> UIViewController { + let c = BuyAndSellViewController() + c.accountService = assembler.resolve(AccountService.self) + c.dialogService = assembler.resolve(DialogService.self) + return c + } +} + +private extension AdmWalletFactory { + func makeTransactionDetailsVC(screensFactory: ScreensFactory) -> AdmTransactionDetailsViewController { + AdmTransactionDetailsViewController( + accountService: assembler.resolve(AccountService.self)!, + transfersProvider: assembler.resolve(TransfersProvider.self)!, + screensFactory: screensFactory, + dialogService: assembler.resolve(DialogService.self)!, + currencyInfo: assembler.resolve(CurrencyInfoService.self)!, + addressBookService: assembler.resolve(AddressBookService.self)! + ) + } +} diff --git a/Adamant/Wallets/Adamant/AdmWalletService+DynamicConstants.swift b/Adamant/Modules/Wallets/Adamant/AdmWalletService+DynamicConstants.swift similarity index 100% rename from Adamant/Wallets/Adamant/AdmWalletService+DynamicConstants.swift rename to Adamant/Modules/Wallets/Adamant/AdmWalletService+DynamicConstants.swift diff --git a/Adamant/Wallets/Adamant/AdmWalletService+RichMessageProvider.swift b/Adamant/Modules/Wallets/Adamant/AdmWalletService+RichMessageProvider.swift similarity index 73% rename from Adamant/Wallets/Adamant/AdmWalletService+RichMessageProvider.swift rename to Adamant/Modules/Wallets/Adamant/AdmWalletService+RichMessageProvider.swift index 48b0bb480..3d8cd67c2 100644 --- a/Adamant/Wallets/Adamant/AdmWalletService+RichMessageProvider.swift +++ b/Adamant/Modules/Wallets/Adamant/AdmWalletService+RichMessageProvider.swift @@ -42,24 +42,6 @@ extension AdmWalletService: RichMessageProvider { return } - func richMessageTapped(for transaction: TransferTransaction, in chat: ChatViewController) { - guard let controller = router.get(scene: AdamantScene.Wallets.Adamant.transactionDetails) as? TransactionDetailsViewControllerBase else { - fatalError("Can't get TransactionDetails scene") - } - - controller.transaction = transaction - controller.comment = transaction.comment - controller.senderId = transaction.senderId - controller.recipientId = transaction.recipientId - - if let nav = chat.navigationController { - nav.pushViewController(controller, animated: true) - } else { - controller.modalPresentationStyle = .overFullScreen - chat.present(controller, animated: true, completion: nil) - } - } - // MARK: Short description private static var formatter: NumberFormatter = { return AdamantBalanceFormat.currencyFormatter(for: .full, currencySymbol: currencySymbol) diff --git a/Adamant/Wallets/Adamant/AdmWalletService+Send.swift b/Adamant/Modules/Wallets/Adamant/AdmWalletService+Send.swift similarity index 86% rename from Adamant/Wallets/Adamant/AdmWalletService+Send.swift rename to Adamant/Modules/Wallets/Adamant/AdmWalletService+Send.swift index 82c32bc9d..db097a38d 100644 --- a/Adamant/Wallets/Adamant/AdmWalletService+Send.swift +++ b/Adamant/Modules/Wallets/Adamant/AdmWalletService+Send.swift @@ -13,15 +13,6 @@ extension AdmWalletService: WalletServiceSimpleSend { /// Transaction ID typealias T = Int - func transferViewController() -> UIViewController { - guard let vc = router.get(scene: AdamantScene.Wallets.Adamant.transfer) as? AdmTransferViewController else { - fatalError("Can't get AdmTransferViewController") - } - - vc.service = self - return vc - } - func sendMoney( recipient: String, amount: Decimal, diff --git a/Adamant/Wallets/Adamant/AdmWalletService.swift b/Adamant/Modules/Wallets/Adamant/AdmWalletService.swift similarity index 90% rename from Adamant/Wallets/Adamant/AdmWalletService.swift rename to Adamant/Modules/Wallets/Adamant/AdmWalletService.swift index 8c8b98c03..aff6f7373 100644 --- a/Adamant/Wallets/Adamant/AdmWalletService.swift +++ b/Adamant/Modules/Wallets/Adamant/AdmWalletService.swift @@ -14,7 +14,7 @@ import MessageKit import Combine import CommonKit -class AdmWalletService: NSObject, WalletService { +final class AdmWalletService: NSObject, WalletService { // MARK: - Constants let addressRegex = try! NSRegularExpression(pattern: "^U([0-9]{6,20})$") @@ -57,7 +57,6 @@ class AdmWalletService: NSObject, WalletService { weak var accountService: AccountService? var apiService: ApiService! var transfersProvider: TransfersProvider! - var router: Router! // MARK: - Notifications let walletUpdatedNotification = Notification.Name("adamant.admWallet.updated") @@ -71,15 +70,6 @@ class AdmWalletService: NSObject, WalletService { // MARK: - Properties let enabled: Bool = true - var walletViewController: WalletViewController { - guard let vc = router.get(scene: AdamantScene.Wallets.Adamant.wallet) as? AdmWalletViewController else { - fatalError("Can't get AdmWalletViewController") - } - - vc.service = self - return vc - } - private var transfersController: NSFetchedResultsController? private (set) var isWarningGasPrice = false private var subscriptions = Set() @@ -170,12 +160,6 @@ class AdmWalletService: NSObject, WalletService { } } -extension AdmWalletService: WalletServiceWithTransfers { - func transferListViewController() -> UIViewController { - return router.get(scene: AdamantScene.Wallets.Adamant.transactionsList) - } -} - // MARK: - NSFetchedResultsControllerDelegate extension AdmWalletService: NSFetchedResultsControllerDelegate { func controller(_ controller: NSFetchedResultsController, didChange anObject: Any, at indexPath: IndexPath?, for type: NSFetchedResultsChangeType, newIndexPath: IndexPath?) { @@ -197,7 +181,6 @@ extension AdmWalletService: SwinjectDependentService { accountService = container.resolve(AccountService.self) apiService = container.resolve(ApiService.self) transfersProvider = container.resolve(TransfersProvider.self) - router = container.resolve(Router.self) Task { let controller = await transfersProvider.unreadTransfersController() diff --git a/Adamant/Wallets/Adamant/AdmWalletViewController.swift b/Adamant/Modules/Wallets/Adamant/AdmWalletViewController.swift similarity index 95% rename from Adamant/Wallets/Adamant/AdmWalletViewController.swift rename to Adamant/Modules/Wallets/Adamant/AdmWalletViewController.swift index 7a9d5ea7a..5fb118b3f 100644 --- a/Adamant/Wallets/Adamant/AdmWalletViewController.swift +++ b/Adamant/Modules/Wallets/Adamant/AdmWalletViewController.swift @@ -32,7 +32,7 @@ extension String.adamant.wallets { static let buyTokensUrlFormat = "" } -class AdmWalletViewController: WalletViewControllerBase { +final class AdmWalletViewController: WalletViewControllerBase { // MARK: - Rows & Sections enum Rows { case buyTokens, freeTokens @@ -60,9 +60,6 @@ class AdmWalletViewController: WalletViewControllerBase { } // MARK: - Props & Deps - - var router: Router! - var hideFreeTokensRow = false // MARK: - Lifecycle @@ -97,17 +94,15 @@ class AdmWalletViewController: WalletViewControllerBase { cell.separatorInset = .zero } }.onCellSelection { [weak self] (_, row) in - guard let vc = self?.router.get(scene: AdamantScene.Wallets.Adamant.buyAndSell) else { - fatalError("Failed to get BuyAndSell scele") - } - + guard let self = self else { return } + let vc = screensFactory.makeBuyAndSell() row.deselect() - if let split = self?.splitViewController { + if let split = splitViewController { let details = UINavigationController(rootViewController:vc) split.showDetailViewController(details, sender: self) } else { - self?.navigationController?.pushViewController(vc, animated: true ) + navigationController?.pushViewController(vc, animated: true) } } diff --git a/Adamant/Wallets/Adamant/BuyAndSellViewController.swift b/Adamant/Modules/Wallets/Adamant/BuyAndSellViewController.swift similarity index 99% rename from Adamant/Wallets/Adamant/BuyAndSellViewController.swift rename to Adamant/Modules/Wallets/Adamant/BuyAndSellViewController.swift index 00289a363..6ddae0071 100644 --- a/Adamant/Wallets/Adamant/BuyAndSellViewController.swift +++ b/Adamant/Modules/Wallets/Adamant/BuyAndSellViewController.swift @@ -11,7 +11,7 @@ import Eureka import SafariServices import CommonKit -class BuyAndSellViewController: FormViewController { +final class BuyAndSellViewController: FormViewController { // MARK: Rows enum Rows { case adamantMessage diff --git a/Adamant/Wallets/BalanceTableViewCell.swift b/Adamant/Modules/Wallets/BalanceTableViewCell.swift similarity index 100% rename from Adamant/Wallets/BalanceTableViewCell.swift rename to Adamant/Modules/Wallets/BalanceTableViewCell.swift diff --git a/Adamant/Wallets/BalanceTableViewCell.xib b/Adamant/Modules/Wallets/BalanceTableViewCell.xib similarity index 100% rename from Adamant/Wallets/BalanceTableViewCell.xib rename to Adamant/Modules/Wallets/BalanceTableViewCell.xib diff --git a/Adamant/Wallets/Bitcoin/BtcTransactionDetailsViewController.swift b/Adamant/Modules/Wallets/Bitcoin/BtcTransactionDetailsViewController.swift similarity index 96% rename from Adamant/Wallets/Bitcoin/BtcTransactionDetailsViewController.swift rename to Adamant/Modules/Wallets/Bitcoin/BtcTransactionDetailsViewController.swift index 318a498cd..b6b62df74 100644 --- a/Adamant/Wallets/Bitcoin/BtcTransactionDetailsViewController.swift +++ b/Adamant/Modules/Wallets/Bitcoin/BtcTransactionDetailsViewController.swift @@ -9,7 +9,7 @@ import UIKit import CommonKit -class BtcTransactionDetailsViewController: TransactionDetailsViewControllerBase { +final class BtcTransactionDetailsViewController: TransactionDetailsViewControllerBase { // MARK: - Dependencies weak var service: BtcWalletService? diff --git a/Adamant/Wallets/Bitcoin/BtcTransactionsViewController.swift b/Adamant/Modules/Wallets/Bitcoin/BtcTransactionsViewController.swift similarity index 92% rename from Adamant/Wallets/Bitcoin/BtcTransactionsViewController.swift rename to Adamant/Modules/Wallets/Bitcoin/BtcTransactionsViewController.swift index 44c49091e..ebe16a1a3 100644 --- a/Adamant/Wallets/Bitcoin/BtcTransactionsViewController.swift +++ b/Adamant/Modules/Wallets/Bitcoin/BtcTransactionsViewController.swift @@ -10,12 +10,12 @@ import UIKit import BitcoinKit import CommonKit -class BtcTransactionsViewController: TransactionsListViewControllerBase { +final class BtcTransactionsViewController: TransactionsListViewControllerBase { // MARK: - Dependencies var btcWalletService: BtcWalletService! var dialogService: DialogService! - var router: Router! + var screensFactory: ScreensFactory! var addressBook: AddressBookService! // MARK: - Properties @@ -69,12 +69,8 @@ class BtcTransactionsViewController: TransactionsListViewControllerBase { let transaction = transactions[indexPath.row] - guard let controller = router.get(scene: AdamantScene.Wallets.Bitcoin.transactionDetails) as? BtcTransactionDetailsViewController else { - return - } - + let controller = screensFactory.makeDetailsVC(service: btcWalletService) controller.transaction = transaction - controller.service = btcWalletService if let address = btcWalletService.wallet?.address { if transaction.senderAddress.caseInsensitiveCompare(address) == .orderedSame { diff --git a/Adamant/Wallets/Bitcoin/BtcTransferViewController.swift b/Adamant/Modules/Wallets/Bitcoin/BtcTransferViewController.swift similarity index 84% rename from Adamant/Wallets/Bitcoin/BtcTransferViewController.swift rename to Adamant/Modules/Wallets/Bitcoin/BtcTransferViewController.swift index 88c490e9d..8132dc588 100644 --- a/Adamant/Wallets/Bitcoin/BtcTransferViewController.swift +++ b/Adamant/Modules/Wallets/Bitcoin/BtcTransferViewController.swift @@ -99,25 +99,25 @@ final class BtcTransferViewController: TransferViewControllerBase { ) { vc.dialogService.showSuccess(withMessage: String.adamant.transfer.transferSuccess) - if let detailsVc = vc.router.get(scene: AdamantScene.Wallets.Bitcoin.transactionDetails) as? BtcTransactionDetailsViewController { - detailsVc.transaction = localTransaction ?? transaction - detailsVc.service = service - detailsVc.senderName = String.adamant.transactionDetails.yourAddress - - if recipientAddress == service.wallet?.address { - detailsVc.recipientName = String.adamant.transactionDetails.yourAddress - } else { - detailsVc.recipientName = self.recipientName - } - - if comments.count > 0 { - detailsVc.comment = comments - } - - vc.delegate?.transferViewController(vc, didFinishWithTransfer: transaction, detailsViewController: detailsVc) + let detailsVc = screensFactory.makeDetailsVC(service: service) + detailsVc.transaction = localTransaction ?? transaction + detailsVc.senderName = String.adamant.transactionDetails.yourAddress + + if recipientAddress == service.wallet?.address { + detailsVc.recipientName = String.adamant.transactionDetails.yourAddress } else { - vc.delegate?.transferViewController(vc, didFinishWithTransfer: transaction, detailsViewController: nil) + detailsVc.recipientName = self.recipientName + } + + if comments.count > 0 { + detailsVc.comment = comments } + + vc.delegate?.transferViewController( + vc, + didFinishWithTransfer: transaction, + detailsViewController: detailsVc + ) } // MARK: Overrides diff --git a/Adamant/Wallets/Bitcoin/BtcWallet.swift b/Adamant/Modules/Wallets/Bitcoin/BtcWallet.swift similarity index 100% rename from Adamant/Wallets/Bitcoin/BtcWallet.swift rename to Adamant/Modules/Wallets/Bitcoin/BtcWallet.swift diff --git a/Adamant/Modules/Wallets/Bitcoin/BtcWalletFactory.swift b/Adamant/Modules/Wallets/Bitcoin/BtcWalletFactory.swift new file mode 100644 index 000000000..2cb06d6bc --- /dev/null +++ b/Adamant/Modules/Wallets/Bitcoin/BtcWalletFactory.swift @@ -0,0 +1,135 @@ +// +// BtcWalletFactory.swift +// Adamant +// +// Created by Andrew G on 09.09.2023. +// Copyright © 2023 Adamant. All rights reserved. +// + +import Swinject +import UIKit +import CommonKit + +struct BtcWalletFactory: WalletFactory { + typealias Service = BtcWalletService + + let assembler: Assembler + + func makeWalletVC(service: Service, screensFactory: ScreensFactory) -> WalletViewController { + let c = BtcWalletViewController(nibName: "WalletViewControllerBase", bundle: nil) + c.dialogService = assembler.resolve(DialogService.self) + c.currencyInfoService = assembler.resolve(CurrencyInfoService.self) + c.accountService = assembler.resolve(AccountService.self) + c.screensFactory = screensFactory + c.service = service + return c + } + + func makeTransferListVC(service: Service, screensFactory: ScreensFactory) -> UIViewController { + let c = BtcTransactionsViewController(nibName: "TransactionsListViewControllerBase", bundle: nil) + c.dialogService = assembler.resolve(DialogService.self) + c.btcWalletService = service + c.addressBook = assembler.resolve(AddressBookService.self) + c.screensFactory = screensFactory + return c + } + + func makeTransferVC(service: Service, screensFactory: ScreensFactory) -> TransferViewControllerBase { + let vc = BtcTransferViewController( + chatsProvider: assembler.resolve(ChatsProvider.self)!, + accountService: assembler.resolve(AccountService.self)!, + accountsProvider: assembler.resolve(AccountsProvider.self)!, + dialogService: assembler.resolve(DialogService.self)!, + screensFactory: screensFactory, + currencyInfoService: assembler.resolve(CurrencyInfoService.self)!, + increaseFeeService: assembler.resolve(IncreaseFeeService.self)! + ) + + vc.service = service + return vc + } + + func makeDetailsVC(service: Service, transaction: RichMessageTransaction) -> UIViewController? { + guard let hash = transaction.getRichValue(for: RichContentKeys.transfer.hash) + else { return nil } + + let comment: String? + if let raw = transaction.getRichValue(for: RichContentKeys.transfer.comments), raw.count > 0 { + comment = raw + } else { + comment = nil + } + + return makeTransactionDetailsVC( + hash: hash, + senderId: transaction.senderId, + recipientId: transaction.recipientId, + senderAddress: "", + recipientAddress: "", + comment: comment, + transaction: nil, + richTransaction: transaction, + service: service + ) + } + + func makeDetailsVC(service: Service) -> TransactionDetailsViewControllerBase { + makeTransactionDetailsVC(service: service) + } +} + +private extension BtcWalletFactory { + func makeTransactionDetailsVC( + hash: String, + senderId: String?, + recipientId: String?, + senderAddress: String, + recipientAddress: String, + comment: String?, + transaction: BtcTransaction?, + richTransaction: RichMessageTransaction, + service: Service + ) -> UIViewController { + let vc = makeTransactionDetailsVC(service: service) + + let amount: Decimal + if let amountRaw = richTransaction.getRichValue(for: RichContentKeys.transfer.amount), + let decimal = Decimal(string: amountRaw) { + amount = decimal + } else { + amount = 0 + } + + let failedTransaction = SimpleTransactionDetails( + txId: hash, + senderAddress: senderAddress, + recipientAddress: recipientAddress, + dateValue: nil, + amountValue: amount, + feeValue: nil, + confirmationsValue: nil, + blockValue: nil, + isOutgoing: richTransaction.isOutgoing, + transactionStatus: nil + ) + + vc.senderId = senderId + vc.recipientId = recipientId + vc.comment = comment + vc.transaction = transaction ?? failedTransaction + vc.richTransaction = richTransaction + return vc + } + + func makeTransactionDetailsVC(service: Service) -> BtcTransactionDetailsViewController { + let vc = BtcTransactionDetailsViewController( + dialogService: assembler.resolve(DialogService.self)!, + currencyInfo: assembler.resolve(CurrencyInfoService.self)!, + addressBookService: assembler.resolve(AddressBookService.self)!, + accountService: assembler.resolve(AccountService.self)! + ) + + vc.service = service + return vc + } +} diff --git a/Adamant/Wallets/Bitcoin/BtcWalletService+DynamicConstants.swift b/Adamant/Modules/Wallets/Bitcoin/BtcWalletService+DynamicConstants.swift similarity index 100% rename from Adamant/Wallets/Bitcoin/BtcWalletService+DynamicConstants.swift rename to Adamant/Modules/Wallets/Bitcoin/BtcWalletService+DynamicConstants.swift diff --git a/Adamant/Modules/Wallets/Bitcoin/BtcWalletService+RichMessageProvider.swift b/Adamant/Modules/Wallets/Bitcoin/BtcWalletService+RichMessageProvider.swift new file mode 100644 index 000000000..924d63d9c --- /dev/null +++ b/Adamant/Modules/Wallets/Bitcoin/BtcWalletService+RichMessageProvider.swift @@ -0,0 +1,64 @@ +// +// BtcWalletService+RichMessageProvider.swift +// Adamant +// +// Created by Anton Boyarkin on 20/02/2019. +// Copyright © 2019 Adamant. All rights reserved. +// + +import Foundation +import MessageKit +import UIKit +import CommonKit + +extension BtcWalletService: RichMessageProvider { + var newPendingInterval: TimeInterval { + .init(milliseconds: type(of: self).newPendingInterval) + } + + var oldPendingInterval: TimeInterval { + .init(milliseconds: type(of: self).oldPendingInterval) + } + + var registeredInterval: TimeInterval { + .init(milliseconds: type(of: self).registeredInterval) + } + + var newPendingAttempts: Int { + type(of: self).newPendingAttempts + } + + var oldPendingAttempts: Int { + type(of: self).oldPendingAttempts + } + + var dynamicRichMessageType: String { + return type(of: self).richMessageType + } + + // MARK: Short description + + func shortDescription(for transaction: RichMessageTransaction) -> NSAttributedString { + let amount: String + + guard let raw = transaction.getRichValue(for: RichContentKeys.transfer.amount) + else { + return NSAttributedString(string: "⬅️ \(BtcWalletService.currencySymbol)") + } + + if let decimal = Decimal(string: raw) { + amount = AdamantBalanceFormat.full.format(decimal) + } else { + amount = raw + } + + let string: String + if transaction.isOutgoing { + string = "⬅️ \(amount) \(BtcWalletService.currencySymbol)" + } else { + string = "➡️ \(amount) \(BtcWalletService.currencySymbol)" + } + + return NSAttributedString(string: string) + } +} diff --git a/Adamant/Wallets/Bitcoin/BtcWalletService+RichMessageProviderWithStatusCheck.swift b/Adamant/Modules/Wallets/Bitcoin/BtcWalletService+RichMessageProviderWithStatusCheck.swift similarity index 100% rename from Adamant/Wallets/Bitcoin/BtcWalletService+RichMessageProviderWithStatusCheck.swift rename to Adamant/Modules/Wallets/Bitcoin/BtcWalletService+RichMessageProviderWithStatusCheck.swift diff --git a/Adamant/Wallets/Bitcoin/BtcWalletService+Send.swift b/Adamant/Modules/Wallets/Bitcoin/BtcWalletService+Send.swift similarity index 95% rename from Adamant/Wallets/Bitcoin/BtcWalletService+Send.swift rename to Adamant/Modules/Wallets/Bitcoin/BtcWalletService+Send.swift index 49dbca503..f7ea59a4b 100644 --- a/Adamant/Wallets/Bitcoin/BtcWalletService+Send.swift +++ b/Adamant/Modules/Wallets/Bitcoin/BtcWalletService+Send.swift @@ -13,15 +13,6 @@ import BitcoinKit extension BtcWalletService: WalletServiceTwoStepSend { typealias T = BitcoinKit.Transaction - func transferViewController() -> UIViewController { - guard let vc = router.get(scene: AdamantScene.Wallets.Bitcoin.transfer) as? BtcTransferViewController else { - fatalError("Can't get BtcTransferViewController") - } - - vc.service = self - return vc - } - // MARK: Create & Send func createTransaction(recipient: String, amount: Decimal) async throws -> BitcoinKit.Transaction { // MARK: 1. Prepare diff --git a/Adamant/Wallets/Bitcoin/BtcWalletService.swift b/Adamant/Modules/Wallets/Bitcoin/BtcWalletService.swift similarity index 96% rename from Adamant/Wallets/Bitcoin/BtcWalletService.swift rename to Adamant/Modules/Wallets/Bitcoin/BtcWalletService.swift index 61c74dc4c..3aaadbe81 100644 --- a/Adamant/Wallets/Bitcoin/BtcWalletService.swift +++ b/Adamant/Modules/Wallets/Bitcoin/BtcWalletService.swift @@ -101,15 +101,6 @@ final class BtcWalletService: WalletService { var wallet: WalletAccount? { return btcWallet } - var walletViewController: WalletViewController { - guard let vc = router.get(scene: AdamantScene.Wallets.Bitcoin.wallet) as? BtcWalletViewController else { - fatalError("Can't get BtcWalletViewController") - } - - vc.service = self - return vc - } - // MARK: RichMessageProvider properties static let richMessageType = "btc_transaction" @@ -117,7 +108,6 @@ final class BtcWalletService: WalletService { var apiService: ApiService! var accountService: AccountService! var dialogService: DialogService! - var router: Router! var increaseFeeService: IncreaseFeeService! var addressConverter: AddressConverter! @@ -445,7 +435,6 @@ extension BtcWalletService: SwinjectDependentService { accountService = container.resolve(AccountService.self) apiService = container.resolve(ApiService.self) dialogService = container.resolve(DialogService.self) - router = container.resolve(Router.self) increaseFeeService = container.resolve(IncreaseFeeService.self) addressConverter = container.resolve(AddressConverterFactory.self)?.make(network: network) } @@ -707,17 +696,6 @@ extension BtcWalletService { } -extension BtcWalletService: WalletServiceWithTransfers { - func transferListViewController() -> UIViewController { - guard let vc = router.get(scene: AdamantScene.Wallets.Bitcoin.transactionsList) as? BtcTransactionsViewController else { - fatalError("Can't get BtcTransactionsViewController") - } - - vc.btcWalletService = self - return vc - } -} - // MARK: - PrivateKey generator extension BtcWalletService: PrivateKeyGenerator { var rowTitle: String { @@ -743,6 +721,6 @@ extension BtcWalletService: PrivateKeyGenerator { } } -class BtcTransaction: BaseBtcTransaction { +final class BtcTransaction: BaseBtcTransaction { override var defaultCurrencySymbol: String? { BtcWalletService.currencySymbol } } diff --git a/Adamant/Wallets/Bitcoin/BtcWalletViewController.swift b/Adamant/Modules/Wallets/Bitcoin/BtcWalletViewController.swift similarity index 93% rename from Adamant/Wallets/Bitcoin/BtcWalletViewController.swift rename to Adamant/Modules/Wallets/Bitcoin/BtcWalletViewController.swift index fc17b0d77..d8ec655e1 100644 --- a/Adamant/Wallets/Bitcoin/BtcWalletViewController.swift +++ b/Adamant/Modules/Wallets/Bitcoin/BtcWalletViewController.swift @@ -15,7 +15,7 @@ extension String.adamant { static let sendBtc = String.localized("AccountTab.Row.SendBtc", comment: "Account tab: 'Send BTC tokens' button") } -class BtcWalletViewController: WalletViewControllerBase { +final class BtcWalletViewController: WalletViewControllerBase { // MARK: Lifecycle override func viewDidLoad() { diff --git a/Adamant/Wallets/Bitcoin/Models/BtcBalanceResponse.swift b/Adamant/Modules/Wallets/Bitcoin/Models/BtcBalanceResponse.swift similarity index 100% rename from Adamant/Wallets/Bitcoin/Models/BtcBalanceResponse.swift rename to Adamant/Modules/Wallets/Bitcoin/Models/BtcBalanceResponse.swift diff --git a/Adamant/Wallets/Bitcoin/Models/BtcTransactionResponse.swift b/Adamant/Modules/Wallets/Bitcoin/Models/BtcTransactionResponse.swift similarity index 100% rename from Adamant/Wallets/Bitcoin/Models/BtcTransactionResponse.swift rename to Adamant/Modules/Wallets/Bitcoin/Models/BtcTransactionResponse.swift diff --git a/Adamant/Wallets/Bitcoin/Models/BtcUnspentTransactionResponse.swift b/Adamant/Modules/Wallets/Bitcoin/Models/BtcUnspentTransactionResponse.swift similarity index 100% rename from Adamant/Wallets/Bitcoin/Models/BtcUnspentTransactionResponse.swift rename to Adamant/Modules/Wallets/Bitcoin/Models/BtcUnspentTransactionResponse.swift diff --git a/Adamant/Modules/Wallets/DI/AdamantWalletFactoryCompose.swift b/Adamant/Modules/Wallets/DI/AdamantWalletFactoryCompose.swift new file mode 100644 index 000000000..f24a943b5 --- /dev/null +++ b/Adamant/Modules/Wallets/DI/AdamantWalletFactoryCompose.swift @@ -0,0 +1,161 @@ +// +// AdamantWalletFactoryCompose.swift +// Adamant +// +// Created by Andrew G on 11.09.2023. +// Copyright © 2023 Adamant. All rights reserved. +// + +import UIKit + +struct AdamantWalletFactoryCompose: WalletFactoryCompose { + private let factories: [any WalletFactory] + + init( + lskWalletFactory: LskWalletFactory, + dogeWalletFactory: DogeWalletFactory, + dashWalletFactory: DashWalletFactory, + btcWalletFactory: BtcWalletFactory, + ethWalletFactory: EthWalletFactory, + erc20WalletFactory: ERC20WalletFactory, + admWalletFactory: AdmWalletFactory + ) { + factories = [ + lskWalletFactory, + dogeWalletFactory, + dashWalletFactory, + btcWalletFactory, + ethWalletFactory, + erc20WalletFactory, + admWalletFactory + ] + } + + func makeWalletVC(service: WalletService, screensFactory: ScreensFactory) -> WalletViewController { + for factory in factories { + guard let result = tryMakeWalletVC( + factory: factory, + service: service, + screensFactory: screensFactory + ) else { continue } + + return result + } + + fatalError("No suitable factory") + } + + func makeTransferListVC(service: WalletService, screenFactory: ScreensFactory) -> UIViewController { + for factory in factories { + guard let result = tryMakeTransferListVC( + factory: factory, + service: service, + screenFactory: screenFactory + ) else { continue } + + return result + } + + fatalError("No suitable factory") + } + + func makeTransferVC(service: WalletService, screenFactory: ScreensFactory) -> TransferViewControllerBase { + for factory in factories { + guard let result = tryMakeTransferVC( + factory: factory, + service: service, + screenFactory: screenFactory + ) else { continue } + + return result + } + + fatalError("No suitable factory") + } + + func makeDetailsVC(service: WalletService) -> TransactionDetailsViewControllerBase { + for factory in factories { + guard let result = tryMakeDetailsVC( + factory: factory, + service: service + ) else { continue } + + return result + } + + fatalError("No suitable factory") + } + + func makeDetailsVC(service: WalletService, transaction: RichMessageTransaction) -> UIViewController? { + for factory in factories { + guard let result = tryMakeDetailsVC( + factory: factory, + service: service, + transaction: transaction + ) else { continue } + + return result + } + + fatalError("No suitable factory") + } +} + +private extension AdamantWalletFactoryCompose { + func tryMakeWalletVC( + factory: Factory, + service: WalletService, + screensFactory: ScreensFactory + ) -> WalletViewController? { + tryExecuteFactoryMethod(factory: factory, service: service) { + factory.makeWalletVC(service: $0, screensFactory: screensFactory) + } + } + + func tryMakeTransferListVC( + factory: Factory, + service: WalletService, + screenFactory: ScreensFactory + ) -> UIViewController? { + tryExecuteFactoryMethod(factory: factory, service: service) { + factory.makeTransferListVC(service: $0, screensFactory: screenFactory) + } + } + + func tryMakeTransferVC( + factory: Factory, + service: WalletService, + screenFactory: ScreensFactory + ) -> TransferViewControllerBase? { + tryExecuteFactoryMethod(factory: factory, service: service) { + factory.makeTransferVC(service: $0, screensFactory: screenFactory) + } + } + + func tryMakeDetailsVC( + factory: Factory, + service: WalletService, + transaction: RichMessageTransaction + ) -> UIViewController?? { + tryExecuteFactoryMethod(factory: factory, service: service) { + factory.makeDetailsVC(service: $0, transaction: transaction) + } + } + + func tryMakeDetailsVC( + factory: Factory, + service: WalletService + ) -> TransactionDetailsViewControllerBase? { + tryExecuteFactoryMethod(factory: factory, service: service) { + factory.makeDetailsVC(service: $0) + } + } + + func tryExecuteFactoryMethod( + factory _: Factory, + service: WalletService, + method: (Factory.Service) -> Result + ) -> Result? { + (service as? Factory.Service).map { method($0) } + } +} diff --git a/Adamant/Modules/Wallets/DI/WalletFactory.swift b/Adamant/Modules/Wallets/DI/WalletFactory.swift new file mode 100644 index 000000000..648a29b05 --- /dev/null +++ b/Adamant/Modules/Wallets/DI/WalletFactory.swift @@ -0,0 +1,20 @@ +// +// WalletFactory.swift +// Adamant +// +// Created by Andrew G on 11.09.2023. +// Copyright © 2023 Adamant. All rights reserved. +// + +import UIKit + +@MainActor +protocol WalletFactory { + associatedtype Service = WalletService + + func makeWalletVC(service: Service, screensFactory: ScreensFactory) -> WalletViewController + func makeTransferListVC(service: Service, screensFactory: ScreensFactory) -> UIViewController + func makeTransferVC(service: Service, screensFactory: ScreensFactory) -> TransferViewControllerBase + func makeDetailsVC(service: Service) -> TransactionDetailsViewControllerBase + func makeDetailsVC(service: Service, transaction: RichMessageTransaction) -> UIViewController? +} diff --git a/Adamant/Modules/Wallets/DI/WalletFactoryCompose.swift b/Adamant/Modules/Wallets/DI/WalletFactoryCompose.swift new file mode 100644 index 000000000..b22b1b40b --- /dev/null +++ b/Adamant/Modules/Wallets/DI/WalletFactoryCompose.swift @@ -0,0 +1,22 @@ +// +// WalletFactoryCompose.swift +// Adamant +// +// Created by Andrew G on 10.09.2023. +// Copyright © 2023 Adamant. All rights reserved. +// + +import UIKit + +@MainActor +protocol WalletFactoryCompose { + func makeWalletVC(service: WalletService, screensFactory: ScreensFactory) -> WalletViewController + func makeTransferListVC(service: WalletService, screenFactory: ScreensFactory) -> UIViewController + func makeTransferVC(service: WalletService, screenFactory: ScreensFactory) -> TransferViewControllerBase + func makeDetailsVC(service: WalletService) -> TransactionDetailsViewControllerBase + + func makeDetailsVC( + service: WalletService, + transaction: RichMessageTransaction + ) -> UIViewController? +} diff --git a/Adamant/Wallets/Dash/DashMainnet.swift b/Adamant/Modules/Wallets/Dash/DashMainnet.swift similarity index 96% rename from Adamant/Wallets/Dash/DashMainnet.swift rename to Adamant/Modules/Wallets/Dash/DashMainnet.swift index e05a1dc0f..a95f117ea 100644 --- a/Adamant/Wallets/Dash/DashMainnet.swift +++ b/Adamant/Modules/Wallets/Dash/DashMainnet.swift @@ -9,7 +9,7 @@ import Foundation import BitcoinKit -class DashMainnet: Network { +final class DashMainnet: Network { override var name: String { return "livenet" } diff --git a/Adamant/Wallets/Dash/DashTransactionDetailsViewController.swift b/Adamant/Modules/Wallets/Dash/DashTransactionDetailsViewController.swift similarity index 97% rename from Adamant/Wallets/Dash/DashTransactionDetailsViewController.swift rename to Adamant/Modules/Wallets/Dash/DashTransactionDetailsViewController.swift index 65681cfaa..46edaf5ed 100644 --- a/Adamant/Wallets/Dash/DashTransactionDetailsViewController.swift +++ b/Adamant/Modules/Wallets/Dash/DashTransactionDetailsViewController.swift @@ -10,7 +10,7 @@ import UIKit import Eureka import CommonKit -class DashTransactionDetailsViewController: TransactionDetailsViewControllerBase { +final class DashTransactionDetailsViewController: TransactionDetailsViewControllerBase { // MARK: - Dependencies weak var service: DashWalletService? diff --git a/Adamant/Wallets/Dash/DashTransactionsViewController.swift b/Adamant/Modules/Wallets/Dash/DashTransactionsViewController.swift similarity index 93% rename from Adamant/Wallets/Dash/DashTransactionsViewController.swift rename to Adamant/Modules/Wallets/Dash/DashTransactionsViewController.swift index 239b5ab93..fff19edb2 100644 --- a/Adamant/Wallets/Dash/DashTransactionsViewController.swift +++ b/Adamant/Modules/Wallets/Dash/DashTransactionsViewController.swift @@ -9,12 +9,12 @@ import UIKit import ProcedureKit -class DashTransactionsViewController: TransactionsListViewControllerBase { +final class DashTransactionsViewController: TransactionsListViewControllerBase { // MARK: - Dependencies var walletService: DashWalletService! var dialogService: DialogService! - var router: Router! + var screensFactory: ScreensFactory! // MARK: - Properties var transactions: [DashTransaction] = [] @@ -90,17 +90,11 @@ class DashTransactionsViewController: TransactionsListViewControllerBase { } func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { - guard let controller = router.get(scene: AdamantScene.Wallets.Dash.transactionDetails) as? DashTransactionDetailsViewController else { - fatalError("Failed to getDashTransactionDetailsViewController") - } - - // Hold reference guard let address = walletService.wallet?.address else { return } - controller.service = self.walletService - + let controller = screensFactory.makeDetailsVC(service: walletService) let transaction = transactions[indexPath.row] let isOutgoing: Bool = transaction.recipientAddress != address diff --git a/Adamant/Wallets/Dash/DashTransferViewController.swift b/Adamant/Modules/Wallets/Dash/DashTransferViewController.swift similarity index 94% rename from Adamant/Wallets/Dash/DashTransferViewController.swift rename to Adamant/Modules/Wallets/Dash/DashTransferViewController.swift index 27a9b3e1e..57732ac74 100644 --- a/Adamant/Wallets/Dash/DashTransferViewController.swift +++ b/Adamant/Modules/Wallets/Dash/DashTransferViewController.swift @@ -97,13 +97,8 @@ final class DashTransferViewController: TransferViewControllerBase { comments: String, service: DashWalletService ) { - guard let detailsVc = router.get(scene: AdamantScene.Wallets.Dash.transactionDetails) as? DashTransactionDetailsViewController else { - delegate?.transferViewController(self, didFinishWithTransfer: transaction, detailsViewController: nil) - return - } - + let detailsVc = screensFactory.makeDetailsVC(service: service) detailsVc.transaction = transaction - detailsVc.service = service detailsVc.senderName = String.adamant.transactionDetails.yourAddress detailsVc.recipientName = recipientName diff --git a/Adamant/Wallets/Dash/DashWallet.swift b/Adamant/Modules/Wallets/Dash/DashWallet.swift similarity index 95% rename from Adamant/Wallets/Dash/DashWallet.swift rename to Adamant/Modules/Wallets/Dash/DashWallet.swift index df50db49a..77876b482 100644 --- a/Adamant/Wallets/Dash/DashWallet.swift +++ b/Adamant/Modules/Wallets/Dash/DashWallet.swift @@ -9,7 +9,7 @@ import Foundation import BitcoinKit -class DashWallet: WalletAccount { +final class DashWallet: WalletAccount { let addressEntity: Address let privateKey: PrivateKey let publicKey: PublicKey diff --git a/Adamant/Modules/Wallets/Dash/DashWalletFactory.swift b/Adamant/Modules/Wallets/Dash/DashWalletFactory.swift new file mode 100644 index 000000000..289e28e85 --- /dev/null +++ b/Adamant/Modules/Wallets/Dash/DashWalletFactory.swift @@ -0,0 +1,144 @@ +// +// DashWalletFactory.swift +// Adamant +// +// Created by Anton Boyarkin on 25/04/2019. +// Copyright © 2019 Adamant. All rights reserved. +// + +import Swinject +import CommonKit +import UIKit + +struct DashWalletFactory: WalletFactory { + typealias Service = DashWalletService + + let assembler: Assembler + + func makeWalletVC(service: Service, screensFactory: ScreensFactory) -> WalletViewController { + let c = DashWalletViewController(nibName: "WalletViewControllerBase", bundle: nil) + c.dialogService = assembler.resolve(DialogService.self) + c.currencyInfoService = assembler.resolve(CurrencyInfoService.self) + c.accountService = assembler.resolve(AccountService.self) + c.service = service + c.screensFactory = screensFactory + return c + } + + func makeTransferListVC(service: Service, screensFactory: ScreensFactory) -> UIViewController { + let c = DashTransactionsViewController(nibName: "TransactionsListViewControllerBase", bundle: nil) + c.dialogService = assembler.resolve(DialogService.self) + c.screensFactory = screensFactory + c.walletService = service + return c + } + + func makeTransferVC(service: Service, screensFactory: ScreensFactory) -> TransferViewControllerBase { + let vc = DashTransferViewController( + chatsProvider: assembler.resolve(ChatsProvider.self)!, + accountService: assembler.resolve(AccountService.self)!, + accountsProvider: assembler.resolve(AccountsProvider.self)!, + dialogService: assembler.resolve(DialogService.self)!, + screensFactory: screensFactory, + currencyInfoService: assembler.resolve(CurrencyInfoService.self)!, + increaseFeeService: assembler.resolve(IncreaseFeeService.self)! + ) + + vc.service = service + return vc + } + + func makeDetailsVC(service: Service, transaction: RichMessageTransaction) -> UIViewController? { + guard + let hash = transaction.getRichValue(for: RichContentKeys.transfer.hash), + let address = assembler.resolve(AccountService.self)?.account?.address + else { return nil } + + let comment: String? + if let raw = transaction.getRichValue(for: RichContentKeys.transfer.comments), raw.count > 0 { + comment = raw + } else { + comment = nil + } + + return makeTransactionDetailsVC( + hash: hash, + senderId: transaction.senderId, + recipientId: transaction.recipientId, + senderAddress: "", + recipientAddress: "", + comment: comment, + address: address, + blockId: nil, + transaction: nil, + richTransaction: transaction, + service: service + ) + } + + func makeDetailsVC(service: Service) -> TransactionDetailsViewControllerBase { + makeTransactionDetailsVC(service: service) + } +} + +private extension DashWalletFactory { + func makeTransactionDetailsVC( + hash: String, + senderId: String?, + recipientId: String?, + senderAddress: String, + recipientAddress: String, + comment: String?, + address: String, + blockId: String?, + transaction: BTCRawTransaction?, + richTransaction: RichMessageTransaction, + service: Service + ) -> UIViewController { + let vc = makeTransactionDetailsVC(service: service) + + let amount: Decimal + if let amountRaw = richTransaction.getRichValue(for: RichContentKeys.transfer.amount), + let decimal = Decimal(string: amountRaw) { + amount = decimal + } else { + amount = 0 + } + + var dashTransaction = transaction?.asBtcTransaction(DashTransaction.self, for: address) + if let blockId = blockId { + dashTransaction = transaction?.asBtcTransaction(DashTransaction.self, for: address, blockId: blockId) + } + let failedTransaction = SimpleTransactionDetails( + txId: hash, + senderAddress: senderAddress, + recipientAddress: recipientAddress, + dateValue: nil, + amountValue: amount, + feeValue: nil, + confirmationsValue: nil, + blockValue: nil, + isOutgoing: richTransaction.isOutgoing, + transactionStatus: nil + ) + + vc.senderId = senderId + vc.recipientId = recipientId + vc.comment = comment + vc.transaction = dashTransaction ?? failedTransaction + vc.richTransaction = richTransaction + return vc + } + + func makeTransactionDetailsVC(service: Service) -> DashTransactionDetailsViewController { + let vc = DashTransactionDetailsViewController( + dialogService: assembler.resolve(DialogService.self)!, + currencyInfo: assembler.resolve(CurrencyInfoService.self)!, + addressBookService: assembler.resolve(AddressBookService.self)!, + accountService: assembler.resolve(AccountService.self)! + ) + + vc.service = service + return vc + } +} diff --git a/Adamant/Wallets/Dash/DashWalletService+DynamicConstants.swift b/Adamant/Modules/Wallets/Dash/DashWalletService+DynamicConstants.swift similarity index 100% rename from Adamant/Wallets/Dash/DashWalletService+DynamicConstants.swift rename to Adamant/Modules/Wallets/Dash/DashWalletService+DynamicConstants.swift diff --git a/Adamant/Modules/Wallets/Dash/DashWalletService+RichMessageProvider.swift b/Adamant/Modules/Wallets/Dash/DashWalletService+RichMessageProvider.swift new file mode 100644 index 000000000..4a114b464 --- /dev/null +++ b/Adamant/Modules/Wallets/Dash/DashWalletService+RichMessageProvider.swift @@ -0,0 +1,64 @@ +// +// DashWalletService+RichMessageProvider.swift +// Adamant +// +// Created by Anton Boyarkin on 26/05/2019. +// Copyright © 2019 Adamant. All rights reserved. +// + +import Foundation +import MessageKit +import UIKit +import CommonKit + +extension DashWalletService: RichMessageProvider { + var newPendingInterval: TimeInterval { + .init(milliseconds: type(of: self).newPendingInterval) + } + + var oldPendingInterval: TimeInterval { + .init(milliseconds: type(of: self).oldPendingInterval) + } + + var registeredInterval: TimeInterval { + .init(milliseconds: type(of: self).registeredInterval) + } + + var newPendingAttempts: Int { + type(of: self).newPendingAttempts + } + + var oldPendingAttempts: Int { + type(of: self).oldPendingAttempts + } + + var dynamicRichMessageType: String { + return type(of: self).richMessageType + } + + // MARK: Short description + + func shortDescription(for transaction: RichMessageTransaction) -> NSAttributedString { + let amount: String + + guard let raw = transaction.getRichValue(for: RichContentKeys.transfer.amount) + else { + return NSAttributedString(string: "⬅️ \(DashWalletService.currencySymbol)") + } + + if let decimal = Decimal(string: raw) { + amount = AdamantBalanceFormat.full.format(decimal) + } else { + amount = raw + } + + let string: String + if transaction.isOutgoing { + string = "⬅️ \(amount) \(DashWalletService.currencySymbol)" + } else { + string = "➡️ \(amount) \(DashWalletService.currencySymbol)" + } + + return NSAttributedString(string: string) + } +} diff --git a/Adamant/Wallets/Dash/DashWalletService+RichMessageProviderWithStatusCheck.swift b/Adamant/Modules/Wallets/Dash/DashWalletService+RichMessageProviderWithStatusCheck.swift similarity index 100% rename from Adamant/Wallets/Dash/DashWalletService+RichMessageProviderWithStatusCheck.swift rename to Adamant/Modules/Wallets/Dash/DashWalletService+RichMessageProviderWithStatusCheck.swift diff --git a/Adamant/Wallets/Dash/DashWalletService+Send.swift b/Adamant/Modules/Wallets/Dash/DashWalletService+Send.swift similarity index 93% rename from Adamant/Wallets/Dash/DashWalletService+Send.swift rename to Adamant/Modules/Wallets/Dash/DashWalletService+Send.swift index 74f67b726..9f921231f 100644 --- a/Adamant/Wallets/Dash/DashWalletService+Send.swift +++ b/Adamant/Modules/Wallets/Dash/DashWalletService+Send.swift @@ -13,15 +13,6 @@ import Alamofire extension DashWalletService: WalletServiceTwoStepSend { typealias T = BitcoinKit.Transaction - func transferViewController() -> UIViewController { - guard let vc = router.get(scene: AdamantScene.Wallets.Dash.transfer) as? DashTransferViewController else { - fatalError("Can't get DashTransferViewController") - } - - vc.service = self - return vc - } - // MARK: Create & Send func create(recipient: String, amount: Decimal) async throws -> BitcoinKit.Transaction { guard let lastTransaction = self.lastTransactionId else { diff --git a/Adamant/Wallets/Dash/DashWalletService+Transactions.swift b/Adamant/Modules/Wallets/Dash/DashWalletService+Transactions.swift similarity index 100% rename from Adamant/Wallets/Dash/DashWalletService+Transactions.swift rename to Adamant/Modules/Wallets/Dash/DashWalletService+Transactions.swift diff --git a/Adamant/Wallets/Dash/DashWalletService.swift b/Adamant/Modules/Wallets/Dash/DashWalletService.swift similarity index 95% rename from Adamant/Wallets/Dash/DashWalletService.swift rename to Adamant/Modules/Wallets/Dash/DashWalletService.swift index aeeb0084b..bea2fe470 100644 --- a/Adamant/Wallets/Dash/DashWalletService.swift +++ b/Adamant/Modules/Wallets/Dash/DashWalletService.swift @@ -45,15 +45,6 @@ final class DashWalletService: WalletService { var wallet: WalletAccount? { return dashWallet } - var walletViewController: WalletViewController { - guard let vc = router.get(scene: AdamantScene.Wallets.Dash.wallet) as? DashWalletViewController else { - fatalError("Can't get DashWalletViewController") - } - - vc.service = self - return vc - } - // MARK: RichMessageProvider properties static let richMessageType = "dash_transaction" @@ -62,7 +53,6 @@ final class DashWalletService: WalletService { var accountService: AccountService! var securedStore: SecuredStore! var dialogService: DialogService! - var router: Router! var addressConverter: AddressConverter! // MARK: - Constants @@ -337,7 +327,6 @@ extension DashWalletService: SwinjectDependentService { apiService = container.resolve(ApiService.self) securedStore = container.resolve(SecuredStore.self) dialogService = container.resolve(DialogService.self) - router = container.resolve(Router.self) addressConverter = container.resolve(AddressConverterFactory.self)? .make(network: network) } @@ -481,18 +470,6 @@ extension DashWalletService { } } -// MARK: - WalletServiceWithTransfers -extension DashWalletService: WalletServiceWithTransfers { - func transferListViewController() -> UIViewController { - guard let vc = router.get(scene: AdamantScene.Wallets.Dash.transactionsList) as? DashTransactionsViewController else { - fatalError("Can't get DashTransactionsViewController") - } - - vc.walletService = self - return vc - } -} - // MARK: - PrivateKey generator extension DashWalletService: PrivateKeyGenerator { var rowTitle: String { diff --git a/Adamant/Wallets/Dash/DashWalletViewController.swift b/Adamant/Modules/Wallets/Dash/DashWalletViewController.swift similarity index 93% rename from Adamant/Wallets/Dash/DashWalletViewController.swift rename to Adamant/Modules/Wallets/Dash/DashWalletViewController.swift index 6791ddf7c..6bda5de7d 100644 --- a/Adamant/Wallets/Dash/DashWalletViewController.swift +++ b/Adamant/Modules/Wallets/Dash/DashWalletViewController.swift @@ -16,7 +16,7 @@ extension String.adamant { static let sendDash = String.localized("AccountTab.Row.SendDash", comment: "Account tab: 'Send Dash tokens' button") } -class DashWalletViewController: WalletViewControllerBase { +final class DashWalletViewController: WalletViewControllerBase { // MARK: Lifecycle override func viewDidLoad() { diff --git a/Adamant/Wallets/Doge/DogeMainnet.swift b/Adamant/Modules/Wallets/Doge/DogeMainnet.swift similarity index 96% rename from Adamant/Wallets/Doge/DogeMainnet.swift rename to Adamant/Modules/Wallets/Doge/DogeMainnet.swift index fdae743aa..fbc2907d0 100644 --- a/Adamant/Wallets/Doge/DogeMainnet.swift +++ b/Adamant/Modules/Wallets/Doge/DogeMainnet.swift @@ -9,7 +9,7 @@ import Foundation import BitcoinKit -class DogeMainnet: Network { +final class DogeMainnet: Network { override var name: String { return "livenet" } diff --git a/Adamant/Wallets/Doge/DogeTransactionDetailsViewController.swift b/Adamant/Modules/Wallets/Doge/DogeTransactionDetailsViewController.swift similarity index 97% rename from Adamant/Wallets/Doge/DogeTransactionDetailsViewController.swift rename to Adamant/Modules/Wallets/Doge/DogeTransactionDetailsViewController.swift index dbf098b86..fc3fdfa8d 100644 --- a/Adamant/Wallets/Doge/DogeTransactionDetailsViewController.swift +++ b/Adamant/Modules/Wallets/Doge/DogeTransactionDetailsViewController.swift @@ -10,7 +10,7 @@ import UIKit import Eureka import CommonKit -class DogeTransactionDetailsViewController: TransactionDetailsViewControllerBase { +final class DogeTransactionDetailsViewController: TransactionDetailsViewControllerBase { // MARK: - Dependencies weak var service: DogeWalletService? diff --git a/Adamant/Wallets/Doge/DogeTransactionsViewController.swift b/Adamant/Modules/Wallets/Doge/DogeTransactionsViewController.swift similarity index 92% rename from Adamant/Wallets/Doge/DogeTransactionsViewController.swift rename to Adamant/Modules/Wallets/Doge/DogeTransactionsViewController.swift index 37beae152..118a6b6c4 100644 --- a/Adamant/Wallets/Doge/DogeTransactionsViewController.swift +++ b/Adamant/Modules/Wallets/Doge/DogeTransactionsViewController.swift @@ -10,12 +10,12 @@ import UIKit import ProcedureKit import CommonKit -class DogeTransactionsViewController: TransactionsListViewControllerBase { +final class DogeTransactionsViewController: TransactionsListViewControllerBase { // MARK: - Dependencies var walletService: DogeWalletService! var dialogService: DialogService! - var router: Router! + var screensFactory: ScreensFactory! // MARK: - Properties var transactions: [DogeTransaction] = [] @@ -68,17 +68,11 @@ class DogeTransactionsViewController: TransactionsListViewControllerBase { } func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { - guard let controller = router.get(scene: AdamantScene.Wallets.Doge.transactionDetails) as? DogeTransactionDetailsViewController else { - fatalError("Failed to get DogeTransactionDetailsViewController") - } - - // Hold reference guard let address = walletService.wallet?.address else { return } - controller.service = self.walletService - + let controller = screensFactory.makeDetailsVC(service: walletService) let transaction = transactions[indexPath.row] let isOutgoing: Bool = transaction.recipientAddress != address diff --git a/Adamant/Wallets/Doge/DogeTransferViewController.swift b/Adamant/Modules/Wallets/Doge/DogeTransferViewController.swift similarity index 93% rename from Adamant/Wallets/Doge/DogeTransferViewController.swift rename to Adamant/Modules/Wallets/Doge/DogeTransferViewController.swift index 7edca0b74..76df81396 100644 --- a/Adamant/Wallets/Doge/DogeTransferViewController.swift +++ b/Adamant/Modules/Wallets/Doge/DogeTransferViewController.swift @@ -88,13 +88,8 @@ final class DogeTransferViewController: TransferViewControllerBase { comments: String, service: DogeWalletService ) { - guard let detailsVc = router.get(scene: AdamantScene.Wallets.Doge.transactionDetails) as? DogeTransactionDetailsViewController else { - delegate?.transferViewController(self, didFinishWithTransfer: transaction, detailsViewController: nil) - return - } - + let detailsVc = screensFactory.makeDetailsVC(service: service) detailsVc.transaction = transaction - detailsVc.service = service detailsVc.senderName = String.adamant.transactionDetails.yourAddress detailsVc.recipientName = recipientName diff --git a/Adamant/Wallets/Doge/DogeWallet.swift b/Adamant/Modules/Wallets/Doge/DogeWallet.swift similarity index 100% rename from Adamant/Wallets/Doge/DogeWallet.swift rename to Adamant/Modules/Wallets/Doge/DogeWallet.swift diff --git a/Adamant/Modules/Wallets/Doge/DogeWalletFactory.swift b/Adamant/Modules/Wallets/Doge/DogeWalletFactory.swift new file mode 100644 index 000000000..2e2dbb1b8 --- /dev/null +++ b/Adamant/Modules/Wallets/Doge/DogeWalletFactory.swift @@ -0,0 +1,134 @@ +// +// DogeWalletFactory.swift +// Adamant +// +// Created by Anton Boyarkin on 05/03/2019. +// Copyright © 2019 Adamant. All rights reserved. +// + +import Swinject +import CommonKit +import UIKit + +struct DogeWalletFactory: WalletFactory { + typealias Service = DogeWalletService + + let assembler: Assembler + + func makeWalletVC(service: Service, screensFactory: ScreensFactory) -> WalletViewController { + let c = DogeWalletViewController(nibName: "WalletViewControllerBase", bundle: nil) + c.dialogService = assembler.resolve(DialogService.self) + c.currencyInfoService = assembler.resolve(CurrencyInfoService.self) + c.accountService = assembler.resolve(AccountService.self) + c.service = service + c.screensFactory = screensFactory + return c + } + + func makeTransferListVC(service: Service, screensFactory: ScreensFactory) -> UIViewController { + let vc = DogeTransactionsViewController(nibName: "TransactionsListViewControllerBase", bundle: nil) + vc.dialogService = assembler.resolve(DialogService.self) + vc.screensFactory = screensFactory + vc.walletService = service + return vc + } + + func makeTransferVC(service: Service, screensFactory: ScreensFactory) -> TransferViewControllerBase { + let vc = DogeTransferViewController( + chatsProvider: assembler.resolve(ChatsProvider.self)!, + accountService: assembler.resolve(AccountService.self)!, + accountsProvider: assembler.resolve(AccountsProvider.self)!, + dialogService: assembler.resolve(DialogService.self)!, + screensFactory: screensFactory, + currencyInfoService: assembler.resolve(CurrencyInfoService.self)!, + increaseFeeService: assembler.resolve(IncreaseFeeService.self)! + ) + + vc.service = service + return vc + } + + func makeDetailsVC(service: Service, transaction: RichMessageTransaction) -> UIViewController? { + guard let hash = transaction.getRichValue(for: RichContentKeys.transfer.hash) + else { return nil } + + let comment: String? + if let raw = transaction.getRichValue(for: RichContentKeys.transfer.comments), raw.count > 0 { + comment = raw + } else { + comment = nil + } + + return makeTransactionDetailsVC( + hash: hash, + senderId: transaction.senderId, + recipientId: transaction.recipientId, + comment: comment, + senderAddress: "", + recipientAddress: "", + transaction: nil, + richTransaction: transaction, + service: service + ) + } + + func makeDetailsVC(service: Service) -> TransactionDetailsViewControllerBase { + makeTransactionDetailsVC(service: service) + } +} + +private extension DogeWalletFactory { + func makeTransactionDetailsVC( + hash: String, + senderId: String?, + recipientId: String?, + comment: String?, + senderAddress: String, + recipientAddress: String, + transaction: DogeTransaction?, + richTransaction: RichMessageTransaction, + service: Service + ) -> UIViewController { + let vc = makeTransactionDetailsVC(service: service) + vc.senderId = senderId + vc.recipientId = recipientId + vc.comment = comment + + let amount: Decimal + if let amountRaw = richTransaction.getRichValue(for: RichContentKeys.transfer.amount), + let decimal = Decimal(string: amountRaw) { + amount = decimal + } else { + amount = 0 + } + + let failedTransaction = SimpleTransactionDetails( + txId: hash, + senderAddress: senderAddress, + recipientAddress: recipientAddress, + dateValue: nil, + amountValue: amount, + feeValue: nil, + confirmationsValue: nil, + blockValue: nil, + isOutgoing: richTransaction.isOutgoing, + transactionStatus: nil + ) + + vc.transaction = transaction ?? failedTransaction + vc.richTransaction = richTransaction + return vc + } + + func makeTransactionDetailsVC(service: Service) -> DogeTransactionDetailsViewController { + let vc = DogeTransactionDetailsViewController( + dialogService: assembler.resolve(DialogService.self)!, + currencyInfo: assembler.resolve(CurrencyInfoService.self)!, + addressBookService: assembler.resolve(AddressBookService.self)!, + accountService: assembler.resolve(AccountService.self)! + ) + + vc.service = service + return vc + } +} diff --git a/Adamant/Wallets/Doge/DogeWalletService+DynamicConstants.swift b/Adamant/Modules/Wallets/Doge/DogeWalletService+DynamicConstants.swift similarity index 100% rename from Adamant/Wallets/Doge/DogeWalletService+DynamicConstants.swift rename to Adamant/Modules/Wallets/Doge/DogeWalletService+DynamicConstants.swift diff --git a/Adamant/Modules/Wallets/Doge/DogeWalletService+RichMessageProvider.swift b/Adamant/Modules/Wallets/Doge/DogeWalletService+RichMessageProvider.swift new file mode 100644 index 000000000..f23784ea1 --- /dev/null +++ b/Adamant/Modules/Wallets/Doge/DogeWalletService+RichMessageProvider.swift @@ -0,0 +1,64 @@ +// +// DogeWalletService+RichMessageProvider.swift +// Adamant +// +// Created by Anton Boyarkin on 13/03/2019. +// Copyright © 2019 Adamant. All rights reserved. +// + +import Foundation +import MessageKit +import UIKit +import CommonKit + +extension DogeWalletService: RichMessageProvider { + var newPendingInterval: TimeInterval { + .init(milliseconds: type(of: self).newPendingInterval) + } + + var oldPendingInterval: TimeInterval { + .init(milliseconds: type(of: self).oldPendingInterval) + } + + var registeredInterval: TimeInterval { + .init(milliseconds: type(of: self).registeredInterval) + } + + var newPendingAttempts: Int { + type(of: self).newPendingAttempts + } + + var oldPendingAttempts: Int { + type(of: self).oldPendingAttempts + } + + var dynamicRichMessageType: String { + return type(of: self).richMessageType + } + + // MARK: Short description + + func shortDescription(for transaction: RichMessageTransaction) -> NSAttributedString { + let amount: String + + guard let raw = transaction.getRichValue(for: RichContentKeys.transfer.amount) + else { + return NSAttributedString(string: "⬅️ \(DogeWalletService.currencySymbol)") + } + + if let decimal = Decimal(string: raw) { + amount = AdamantBalanceFormat.full.format(decimal) + } else { + amount = raw + } + + let string: String + if transaction.isOutgoing { + string = "⬅️ \(amount) \(DogeWalletService.currencySymbol)" + } else { + string = "➡️ \(amount) \(DogeWalletService.currencySymbol)" + } + + return NSAttributedString(string: string) + } +} diff --git a/Adamant/Wallets/Doge/DogeWalletService+RichMessageProviderWithStatusCheck.swift b/Adamant/Modules/Wallets/Doge/DogeWalletService+RichMessageProviderWithStatusCheck.swift similarity index 100% rename from Adamant/Wallets/Doge/DogeWalletService+RichMessageProviderWithStatusCheck.swift rename to Adamant/Modules/Wallets/Doge/DogeWalletService+RichMessageProviderWithStatusCheck.swift diff --git a/Adamant/Wallets/Doge/DogeWalletService+Send.swift b/Adamant/Modules/Wallets/Doge/DogeWalletService+Send.swift similarity index 94% rename from Adamant/Wallets/Doge/DogeWalletService+Send.swift rename to Adamant/Modules/Wallets/Doge/DogeWalletService+Send.swift index caaf4f960..ae6799dc6 100644 --- a/Adamant/Wallets/Doge/DogeWalletService+Send.swift +++ b/Adamant/Modules/Wallets/Doge/DogeWalletService+Send.swift @@ -19,15 +19,6 @@ extension BitcoinKit.Transaction: RawTransaction { extension DogeWalletService: WalletServiceTwoStepSend { typealias T = BitcoinKit.Transaction - func transferViewController() -> UIViewController { - guard let vc = router.get(scene: AdamantScene.Wallets.Doge.transfer) as? DogeTransferViewController else { - fatalError("Can't get DogeTransferViewController") - } - - vc.service = self - return vc - } - // MARK: Create & Send func createTransaction(recipient: String, amount: Decimal) async throws -> BitcoinKit.Transaction { // Prepare diff --git a/Adamant/Wallets/Doge/DogeWalletService.swift b/Adamant/Modules/Wallets/Doge/DogeWalletService.swift similarity index 96% rename from Adamant/Wallets/Doge/DogeWalletService.swift rename to Adamant/Modules/Wallets/Doge/DogeWalletService.swift index e3af97d18..f2226527a 100644 --- a/Adamant/Wallets/Doge/DogeWalletService.swift +++ b/Adamant/Modules/Wallets/Doge/DogeWalletService.swift @@ -39,18 +39,9 @@ struct DogeApiCommands { } } -class DogeWalletService: WalletService { +final class DogeWalletService: WalletService { var wallet: WalletAccount? { return dogeWallet } - var walletViewController: WalletViewController { - guard let vc = router.get(scene: AdamantScene.Wallets.Doge.wallet) as? DogeWalletViewController else { - fatalError("Can't get DogeWalletViewController") - } - - vc.service = self - return vc - } - // MARK: RichMessageProvider properties static let richMessageType = "doge_transaction" @@ -58,7 +49,6 @@ class DogeWalletService: WalletService { var apiService: ApiService! var accountService: AccountService! var dialogService: DialogService! - var router: Router! var addressConverter: AddressConverter! // MARK: - Constants @@ -313,12 +303,10 @@ extension DogeWalletService: InitiatedWithPassphraseService { // MARK: - Dependencies extension DogeWalletService: SwinjectDependentService { - @MainActor func injectDependencies(from container: Container) { accountService = container.resolve(AccountService.self) apiService = container.resolve(ApiService.self) dialogService = container.resolve(DialogService.self) - router = container.resolve(Router.self) addressConverter = container.resolve(AddressConverterFactory.self)? .make(network: network) } @@ -614,18 +602,6 @@ extension DogeWalletService { } } -// MARK: - WalletServiceWithTransfers -extension DogeWalletService: WalletServiceWithTransfers { - func transferListViewController() -> UIViewController { - guard let vc = router.get(scene: AdamantScene.Wallets.Doge.transactionsList) as? DogeTransactionsViewController else { - fatalError("Can't get DogeTransactionsViewController") - } - - vc.walletService = self - return vc - } -} - // MARK: - PrivateKey generator extension DogeWalletService: PrivateKeyGenerator { var rowTitle: String { diff --git a/Adamant/Wallets/Doge/DogeWalletViewController.swift b/Adamant/Modules/Wallets/Doge/DogeWalletViewController.swift similarity index 92% rename from Adamant/Wallets/Doge/DogeWalletViewController.swift rename to Adamant/Modules/Wallets/Doge/DogeWalletViewController.swift index fba1beb88..7499bbb50 100644 --- a/Adamant/Wallets/Doge/DogeWalletViewController.swift +++ b/Adamant/Modules/Wallets/Doge/DogeWalletViewController.swift @@ -15,7 +15,7 @@ extension String.adamant { static let sendDoge = String.localized("AccountTab.Row.SendDoge", comment: "Account tab: 'Send DOGE tokens' button") } -class DogeWalletViewController: WalletViewControllerBase { +final class DogeWalletViewController: WalletViewControllerBase { // MARK: Lifecycle override func viewDidLoad() { diff --git a/Adamant/Wallets/ERC20/ERC20TransactionDetailsViewController.swift b/Adamant/Modules/Wallets/ERC20/ERC20TransactionDetailsViewController.swift similarity index 96% rename from Adamant/Wallets/ERC20/ERC20TransactionDetailsViewController.swift rename to Adamant/Modules/Wallets/ERC20/ERC20TransactionDetailsViewController.swift index 3bd8b6341..a9361c81b 100644 --- a/Adamant/Wallets/ERC20/ERC20TransactionDetailsViewController.swift +++ b/Adamant/Modules/Wallets/ERC20/ERC20TransactionDetailsViewController.swift @@ -9,7 +9,7 @@ import UIKit import CommonKit -class ERC20TransactionDetailsViewController: TransactionDetailsViewControllerBase { +final class ERC20TransactionDetailsViewController: TransactionDetailsViewControllerBase { // MARK: - Dependencies weak var service: ERC20WalletService? diff --git a/Adamant/Wallets/ERC20/ERC20TransactionsViewController.swift b/Adamant/Modules/Wallets/ERC20/ERC20TransactionsViewController.swift similarity index 93% rename from Adamant/Wallets/ERC20/ERC20TransactionsViewController.swift rename to Adamant/Modules/Wallets/ERC20/ERC20TransactionsViewController.swift index b3e4b8667..9237089b5 100644 --- a/Adamant/Wallets/ERC20/ERC20TransactionsViewController.swift +++ b/Adamant/Modules/Wallets/ERC20/ERC20TransactionsViewController.swift @@ -10,7 +10,7 @@ import UIKit import web3swift import CommonKit -class ERC20TransactionsViewController: TransactionsListViewControllerBase { +final class ERC20TransactionsViewController: TransactionsListViewControllerBase { // MARK: - Dependencies var walletService: ERC20WalletService! { @@ -19,7 +19,7 @@ class ERC20TransactionsViewController: TransactionsListViewControllerBase { } } var dialogService: DialogService! - var router: Router! + var screensFactory: ScreensFactory! // MARK: - Properties var transactions: [EthTransactionShort] = [] @@ -102,12 +102,7 @@ class ERC20TransactionsViewController: TransactionsListViewControllerBase { let transaction = transactions[indexPath.row] - guard let vc = router.get(scene: AdamantScene.Wallets.ERC20.transactionDetails) as? ERC20TransactionDetailsViewController else { - fatalError("Failed to get ERC20TransactionDetailsViewController") - } - - vc.service = walletService - + let vc = screensFactory.makeDetailsVC(service: walletService) let isOutgoing: Bool = transaction.to != address let emptyTransaction = SimpleTransactionDetails( diff --git a/Adamant/Wallets/ERC20/ERC20TransferViewController.swift b/Adamant/Modules/Wallets/ERC20/ERC20TransferViewController.swift similarity index 91% rename from Adamant/Wallets/ERC20/ERC20TransferViewController.swift rename to Adamant/Modules/Wallets/ERC20/ERC20TransferViewController.swift index 4e61e9695..0d9f4e135 100644 --- a/Adamant/Wallets/ERC20/ERC20TransferViewController.swift +++ b/Adamant/Modules/Wallets/ERC20/ERC20TransferViewController.swift @@ -107,20 +107,21 @@ final class ERC20TransferViewController: TransferViewControllerBase { recipientAddress: recipient, isOutgoing: true ) - if let detailsVc = router.get(scene: AdamantScene.Wallets.ERC20.transactionDetails) as? ERC20TransactionDetailsViewController { - detailsVc.transaction = transaction - detailsVc.service = service - detailsVc.senderName = String.adamant.transactionDetails.yourAddress - detailsVc.recipientName = recipientName - - if comments.count > 0 { - detailsVc.comment = comments - } - - delegate?.transferViewController(self, didFinishWithTransfer: transaction, detailsViewController: detailsVc) - } else { - delegate?.transferViewController(self, didFinishWithTransfer: transaction, detailsViewController: nil) + + let detailsVc = screensFactory.makeDetailsVC(service: service) + detailsVc.transaction = transaction + detailsVc.senderName = String.adamant.transactionDetails.yourAddress + detailsVc.recipientName = recipientName + + if comments.count > 0 { + detailsVc.comment = comments } + + delegate?.transferViewController( + self, + didFinishWithTransfer: transaction, + detailsViewController: detailsVc + ) } // MARK: Overrides diff --git a/Adamant/Wallets/ERC20/ERC20Wallet.swift b/Adamant/Modules/Wallets/ERC20/ERC20Wallet.swift similarity index 94% rename from Adamant/Wallets/ERC20/ERC20Wallet.swift rename to Adamant/Modules/Wallets/ERC20/ERC20Wallet.swift index 0b594a9c5..d0eed70f5 100644 --- a/Adamant/Wallets/ERC20/ERC20Wallet.swift +++ b/Adamant/Modules/Wallets/ERC20/ERC20Wallet.swift @@ -10,7 +10,7 @@ import Foundation import web3swift import Web3Core -class ERC20Wallet: WalletAccount { +final class ERC20Wallet: WalletAccount { let address: String let ethAddress: EthereumAddress let keystore: BIP32Keystore diff --git a/Adamant/Modules/Wallets/ERC20/ERC20WalletFactory.swift b/Adamant/Modules/Wallets/ERC20/ERC20WalletFactory.swift new file mode 100644 index 000000000..8cd282f20 --- /dev/null +++ b/Adamant/Modules/Wallets/ERC20/ERC20WalletFactory.swift @@ -0,0 +1,136 @@ +// +// ERC20WalletFactory.swift +// Adamant +// +// Created by Anton Boyarkin on 26/06/2019. +// Copyright © 2019 Adamant. All rights reserved. +// + +import Swinject +import CommonKit +import UIKit + +struct ERC20WalletFactory: WalletFactory { + typealias Service = ERC20WalletService + + let assembler: Assembler + + func makeWalletVC(service: Service, screensFactory: ScreensFactory) -> WalletViewController { + let c = ERC20WalletViewController(nibName: "WalletViewControllerBase", bundle: nil) + c.dialogService = assembler.resolve(DialogService.self) + c.currencyInfoService = assembler.resolve(CurrencyInfoService.self) + c.accountService = assembler.resolve(AccountService.self) + c.service = service + c.screensFactory = screensFactory + return c + } + + func makeTransferListVC(service: Service, screensFactory: ScreensFactory) -> UIViewController { + let vc = ERC20TransactionsViewController(nibName: "TransactionsListViewControllerBase", bundle: nil) + vc.dialogService = assembler.resolve(DialogService.self) + vc.screensFactory = screensFactory + vc.walletService = service + return vc + } + + func makeTransferVC(service: Service, screensFactory: ScreensFactory) -> TransferViewControllerBase { + let vc = ERC20TransferViewController( + chatsProvider: assembler.resolve(ChatsProvider.self)!, + accountService: assembler.resolve(AccountService.self)!, + accountsProvider: assembler.resolve(AccountsProvider.self)!, + dialogService: assembler.resolve(DialogService.self)!, + screensFactory: screensFactory, + currencyInfoService: assembler.resolve(CurrencyInfoService.self)!, + increaseFeeService: assembler.resolve(IncreaseFeeService.self)! + ) + + vc.service = service + return vc + } + + func makeDetailsVC(service: Service, transaction: RichMessageTransaction) -> UIViewController? { + guard let hash = transaction.getRichValue(for: RichContentKeys.transfer.hash) + else { return nil } + + let comment: String? + if let raw = transaction.getRichValue(for: RichContentKeys.transfer.comments), raw.count > 0 { + comment = raw + } else { + comment = nil + } + + // MARK: Go to transaction + + return makeTransactionDetailsVC( + hash: hash, + senderId: transaction.senderId, + recipientId: transaction.recipientId, + senderAddress: "", + recipientAddress: "", + comment: comment, + transaction: nil, + richTransaction: transaction, + service: service + ) + } + + func makeDetailsVC(service: Service) -> TransactionDetailsViewControllerBase { + makeTransactionDetailsVC(service: service) + } +} + +private extension ERC20WalletFactory { + func makeTransactionDetailsVC( + hash: String, + senderId: String?, + recipientId: String?, + senderAddress: String, + recipientAddress: String, + comment: String?, + transaction: EthTransaction?, + richTransaction: RichMessageTransaction, + service: Service + ) -> UIViewController { + let vc = makeTransactionDetailsVC(service: service) + + let amount: Decimal + if let amountRaw = richTransaction.getRichValue(for: RichContentKeys.transfer.amount), + let decimal = Decimal(string: amountRaw) { + amount = decimal + } else { + amount = 0 + } + + let failedTransaction = SimpleTransactionDetails( + txId: hash, + senderAddress: senderAddress, + recipientAddress: recipientAddress, + dateValue: nil, + amountValue: amount, + feeValue: nil, + confirmationsValue: nil, + blockValue: nil, + isOutgoing: richTransaction.isOutgoing, + transactionStatus: nil + ) + + vc.senderId = senderId + vc.recipientId = recipientId + vc.comment = comment + vc.transaction = transaction ?? failedTransaction + vc.richTransaction = richTransaction + return vc + } + + func makeTransactionDetailsVC(service: Service) -> ERC20TransactionDetailsViewController { + let vc = ERC20TransactionDetailsViewController( + dialogService: assembler.resolve(DialogService.self)!, + currencyInfo: assembler.resolve(CurrencyInfoService.self)!, + addressBookService: assembler.resolve(AddressBookService.self)!, + accountService: assembler.resolve(AccountService.self)! + ) + + vc.service = service + return vc + } +} diff --git a/Adamant/Modules/Wallets/ERC20/ERC20WalletService+RichMessageProvider.swift b/Adamant/Modules/Wallets/ERC20/ERC20WalletService+RichMessageProvider.swift new file mode 100644 index 000000000..0325ae204 --- /dev/null +++ b/Adamant/Modules/Wallets/ERC20/ERC20WalletService+RichMessageProvider.swift @@ -0,0 +1,60 @@ +// +// ERC20WalletService+RichMessageProvider.swift +// Adamant +// +// Created by Anton Boyarkin on 06/07/2019. +// Copyright © 2019 Adamant. All rights reserved. +// + +import Foundation +import MessageKit +import UIKit +import CommonKit + +extension ERC20WalletService: RichMessageProvider { + var newPendingInterval: TimeInterval { + .init(milliseconds: EthWalletService.newPendingInterval) + } + + var oldPendingInterval: TimeInterval { + .init(milliseconds: EthWalletService.oldPendingInterval) + } + + var registeredInterval: TimeInterval { + .init(milliseconds: EthWalletService.registeredInterval) + } + + var newPendingAttempts: Int { + EthWalletService.newPendingAttempts + } + + var oldPendingAttempts: Int { + EthWalletService.oldPendingAttempts + } + + // MARK: Short description + + func shortDescription(for transaction: RichMessageTransaction) -> NSAttributedString { + let amount: String + + guard let raw = transaction.getRichValue(for: RichContentKeys.transfer.amount) + else { + return NSAttributedString(string: "⬅️ \(self.tokenSymbol)") + } + + if let decimal = Decimal(string: raw) { + amount = AdamantBalanceFormat.full.format(decimal) + } else { + amount = raw + } + + let string: String + if transaction.isOutgoing { + string = "⬅️ \(amount) \(self.tokenSymbol)" + } else { + string = "➡️ \(amount) \(self.tokenSymbol)" + } + + return NSAttributedString(string: string) + } +} diff --git a/Adamant/Wallets/ERC20/ERC20WalletService+RichMessageProviderWithStatusCheck.swift b/Adamant/Modules/Wallets/ERC20/ERC20WalletService+RichMessageProviderWithStatusCheck.swift similarity index 100% rename from Adamant/Wallets/ERC20/ERC20WalletService+RichMessageProviderWithStatusCheck.swift rename to Adamant/Modules/Wallets/ERC20/ERC20WalletService+RichMessageProviderWithStatusCheck.swift diff --git a/Adamant/Wallets/ERC20/ERC20WalletService+Send.swift b/Adamant/Modules/Wallets/ERC20/ERC20WalletService+Send.swift similarity index 89% rename from Adamant/Wallets/ERC20/ERC20WalletService+Send.swift rename to Adamant/Modules/Wallets/ERC20/ERC20WalletService+Send.swift index 37b0b93ac..47aeda74f 100644 --- a/Adamant/Wallets/ERC20/ERC20WalletService+Send.swift +++ b/Adamant/Modules/Wallets/ERC20/ERC20WalletService+Send.swift @@ -15,15 +15,6 @@ import CommonKit extension ERC20WalletService: WalletServiceTwoStepSend { typealias T = CodableTransaction - func transferViewController() -> UIViewController { - guard let vc = router.get(scene: AdamantScene.Wallets.ERC20.transfer) as? ERC20TransferViewController else { - fatalError("Can't get ERC20TransferViewController") - } - - vc.service = self - return vc - } - // MARK: Create & Send func createTransaction(recipient: String, amount: Decimal) async throws -> CodableTransaction { guard let ethWallet = ethWallet, diff --git a/Adamant/Wallets/ERC20/ERC20WalletService.swift b/Adamant/Modules/Wallets/ERC20/ERC20WalletService.swift similarity index 96% rename from Adamant/Wallets/ERC20/ERC20WalletService.swift rename to Adamant/Modules/Wallets/ERC20/ERC20WalletService.swift index d1b2cd1b2..66930e2c4 100644 --- a/Adamant/Wallets/ERC20/ERC20WalletService.swift +++ b/Adamant/Modules/Wallets/ERC20/ERC20WalletService.swift @@ -16,7 +16,7 @@ import Web3Core import Combine import CommonKit -class ERC20WalletService: WalletService { +final class ERC20WalletService: WalletService { // MARK: - Constants let addressRegex = try! NSRegularExpression(pattern: "^0x[a-fA-F0-9]{40}$") @@ -100,7 +100,6 @@ class ERC20WalletService: WalletService { weak var accountService: AccountService? var apiService: ApiService! var dialogService: DialogService! - var router: Router! var increaseFeeService: IncreaseFeeService! // MARK: - Notifications @@ -137,15 +136,6 @@ class ERC20WalletService: WalletService { } } - var walletViewController: WalletViewController { - guard let vc = router.get(scene: AdamantScene.Wallets.ERC20.wallet) as? ERC20WalletViewController else { - fatalError("Can't get erc20WalletViewController") - } - - vc.service = self - return vc - } - private var initialBalanceCheck = false // MARK: - State @@ -445,7 +435,6 @@ extension ERC20WalletService: SwinjectDependentService { accountService = container.resolve(AccountService.self) apiService = container.resolve(ApiService.self) dialogService = container.resolve(DialogService.self) - router = container.resolve(Router.self) increaseFeeService = container.resolve(IncreaseFeeService.self) } } @@ -587,7 +576,7 @@ extension ERC20WalletService { } return result - } catch let error as ApiServiceError { + } catch { throw WalletServiceError.remoteServiceError( message: "ETH Wallet: failed to get address from KVS" ) @@ -630,14 +619,3 @@ extension ERC20WalletService { return transactions } } - -extension ERC20WalletService: WalletServiceWithTransfers { - func transferListViewController() -> UIViewController { - guard let vc = router.get(scene: AdamantScene.Wallets.ERC20.transactionsList) as? ERC20TransactionsViewController else { - fatalError("Can't get ERC20TransactionsViewController") - } - - vc.walletService = self - return vc - } -} diff --git a/Adamant/Wallets/ERC20/ERC20WalletViewController.swift b/Adamant/Modules/Wallets/ERC20/ERC20WalletViewController.swift similarity index 96% rename from Adamant/Wallets/ERC20/ERC20WalletViewController.swift rename to Adamant/Modules/Wallets/ERC20/ERC20WalletViewController.swift index f01ffe177..6906fd6a3 100644 --- a/Adamant/Wallets/ERC20/ERC20WalletViewController.swift +++ b/Adamant/Modules/Wallets/ERC20/ERC20WalletViewController.swift @@ -22,7 +22,7 @@ extension String.adamant.wallets { } } -class ERC20WalletViewController: WalletViewControllerBase { +final class ERC20WalletViewController: WalletViewControllerBase { // MARK: Lifecycle override func viewDidLoad() { diff --git a/Adamant/Wallets/Ethereum/EthTransactionDetailsViewController.swift b/Adamant/Modules/Wallets/Ethereum/EthTransactionDetailsViewController.swift similarity index 96% rename from Adamant/Wallets/Ethereum/EthTransactionDetailsViewController.swift rename to Adamant/Modules/Wallets/Ethereum/EthTransactionDetailsViewController.swift index 603fc89b2..b9a863fc0 100644 --- a/Adamant/Wallets/Ethereum/EthTransactionDetailsViewController.swift +++ b/Adamant/Modules/Wallets/Ethereum/EthTransactionDetailsViewController.swift @@ -9,7 +9,7 @@ import UIKit import CommonKit -class EthTransactionDetailsViewController: TransactionDetailsViewControllerBase { +final class EthTransactionDetailsViewController: TransactionDetailsViewControllerBase { // MARK: - Dependencies weak var service: EthWalletService? diff --git a/Adamant/Wallets/Ethereum/EthTransactionsViewController.swift b/Adamant/Modules/Wallets/Ethereum/EthTransactionsViewController.swift similarity index 92% rename from Adamant/Wallets/Ethereum/EthTransactionsViewController.swift rename to Adamant/Modules/Wallets/Ethereum/EthTransactionsViewController.swift index ab4f47762..2848ff42c 100644 --- a/Adamant/Wallets/Ethereum/EthTransactionsViewController.swift +++ b/Adamant/Modules/Wallets/Ethereum/EthTransactionsViewController.swift @@ -10,7 +10,7 @@ import UIKit import web3swift import CommonKit -class EthTransactionsViewController: TransactionsListViewControllerBase { +final class EthTransactionsViewController: TransactionsListViewControllerBase { // MARK: - Dependencies var ethWalletService: EthWalletService! { @@ -19,7 +19,7 @@ class EthTransactionsViewController: TransactionsListViewControllerBase { } } var dialogService: DialogService! - var router: Router! + var screensFactory: ScreensFactory! // MARK: - Properties var transactions: [EthTransactionShort] = [] @@ -93,12 +93,7 @@ class EthTransactionsViewController: TransactionsListViewControllerBase { tableView.deselectRow(at: indexPath, animated: true) let transaction = transactions[indexPath.row] - - guard let vc = router.get(scene: AdamantScene.Wallets.Ethereum.transactionDetails) as? EthTransactionDetailsViewController else { - fatalError("Failed to get EthTransactionDetailsViewController") - } - - vc.service = ethWalletService + let vc = screensFactory.makeDetailsVC(service: ethWalletService) let isOutgoing: Bool = transaction.to != address diff --git a/Adamant/Wallets/Ethereum/EthTransferViewController.swift b/Adamant/Modules/Wallets/Ethereum/EthTransferViewController.swift similarity index 90% rename from Adamant/Wallets/Ethereum/EthTransferViewController.swift rename to Adamant/Modules/Wallets/Ethereum/EthTransferViewController.swift index fca6e627c..a5083907a 100644 --- a/Adamant/Wallets/Ethereum/EthTransferViewController.swift +++ b/Adamant/Modules/Wallets/Ethereum/EthTransferViewController.swift @@ -101,20 +101,21 @@ final class EthTransferViewController: TransferViewControllerBase { recipientAddress: recipient, isOutgoing: true ) - if let detailsVc = router.get(scene: AdamantScene.Wallets.Ethereum.transactionDetails) as? EthTransactionDetailsViewController { - detailsVc.transaction = transaction - detailsVc.service = service - detailsVc.senderName = String.adamant.transactionDetails.yourAddress - detailsVc.recipientName = recipientName - - if comments.count > 0 { - detailsVc.comment = comments - } - - delegate?.transferViewController(self, didFinishWithTransfer: transaction, detailsViewController: detailsVc) - } else { - delegate?.transferViewController(self, didFinishWithTransfer: transaction, detailsViewController: nil) + + let detailsVc = screensFactory.makeDetailsVC(service: service) + detailsVc.transaction = transaction + detailsVc.senderName = String.adamant.transactionDetails.yourAddress + detailsVc.recipientName = recipientName + + if comments.count > 0 { + detailsVc.comment = comments } + + delegate?.transferViewController( + self, + didFinishWithTransfer: transaction, + detailsViewController: detailsVc + ) } // MARK: Overrides diff --git a/Adamant/Wallets/Ethereum/EthWallet.swift b/Adamant/Modules/Wallets/Ethereum/EthWallet.swift similarity index 94% rename from Adamant/Wallets/Ethereum/EthWallet.swift rename to Adamant/Modules/Wallets/Ethereum/EthWallet.swift index 56a01fabb..d279d07fc 100644 --- a/Adamant/Wallets/Ethereum/EthWallet.swift +++ b/Adamant/Modules/Wallets/Ethereum/EthWallet.swift @@ -10,7 +10,7 @@ import Foundation import web3swift import Web3Core -class EthWallet: WalletAccount { +final class EthWallet: WalletAccount { let address: String let ethAddress: EthereumAddress let keystore: BIP32Keystore diff --git a/Adamant/Modules/Wallets/Ethereum/EthWalletFactory.swift b/Adamant/Modules/Wallets/Ethereum/EthWalletFactory.swift new file mode 100644 index 000000000..1c19d8633 --- /dev/null +++ b/Adamant/Modules/Wallets/Ethereum/EthWalletFactory.swift @@ -0,0 +1,134 @@ +// +// EthWalletFactory.swift +// Adamant +// +// Created by Anokhov Pavel on 28.08.2018. +// Copyright © 2018 Adamant. All rights reserved. +// + +import Swinject +import UIKit +import CommonKit + +struct EthWalletFactory: WalletFactory { + typealias Service = EthWalletService + + let assembler: Assembler + + func makeWalletVC(service: Service, screensFactory: ScreensFactory) -> WalletViewController { + let c = EthWalletViewController(nibName: "WalletViewControllerBase", bundle: nil) + c.dialogService = assembler.resolve(DialogService.self) + c.currencyInfoService = assembler.resolve(CurrencyInfoService.self) + c.accountService = assembler.resolve(AccountService.self) + c.service = service + c.screensFactory = screensFactory + return c + } + + func makeTransferListVC(service: Service, screensFactory: ScreensFactory) -> UIViewController { + let c = EthTransactionsViewController(nibName: "TransactionsListViewControllerBase", bundle: nil) + c.dialogService = assembler.resolve(DialogService.self) + c.ethWalletService = service + c.screensFactory = screensFactory + return c + } + + func makeTransferVC(service: Service, screensFactory: ScreensFactory) -> TransferViewControllerBase { + let vc = EthTransferViewController( + chatsProvider: assembler.resolve(ChatsProvider.self)!, + accountService: assembler.resolve(AccountService.self)!, + accountsProvider: assembler.resolve(AccountsProvider.self)!, + dialogService: assembler.resolve(DialogService.self)!, + screensFactory: screensFactory, + currencyInfoService: assembler.resolve(CurrencyInfoService.self)!, + increaseFeeService: assembler.resolve(IncreaseFeeService.self)! + ) + + vc.service = service + return vc + } + + func makeDetailsVC(service: Service, transaction: RichMessageTransaction) -> UIViewController? { + guard let hash = transaction.getRichValue(for: RichContentKeys.transfer.hash) + else { return nil } + + let comment: String? + if let raw = transaction.getRichValue(for: RichContentKeys.transfer.comments), raw.count > 0 { + comment = raw + } else { + comment = nil + } + + return makeTransactionDetailsVC( + hash: hash, + senderId: transaction.senderId, + recipientId: transaction.recipientId, + senderAddress: "", + recipientAddress: "", + comment: comment, + transaction: nil, + richTransaction: transaction, + service: service + ) + } + + func makeDetailsVC(service: Service) -> TransactionDetailsViewControllerBase { + makeTransactionDetailsVC(service: service) + } +} + +private extension EthWalletFactory { + func makeTransactionDetailsVC( + hash: String, + senderId: String?, + recipientId: String?, + senderAddress: String, + recipientAddress: String, + comment: String?, + transaction: EthTransaction?, + richTransaction: RichMessageTransaction, + service: Service + ) -> UIViewController { + let vc = makeTransactionDetailsVC(service: service) + + let amount: Decimal + if let amountRaw = richTransaction.getRichValue(for: RichContentKeys.transfer.amount), + let decimal = Decimal(string: amountRaw) { + amount = decimal + } else { + amount = 0 + } + + let failedTransaction = SimpleTransactionDetails( + txId: hash, + senderAddress: senderAddress, + recipientAddress: recipientAddress, + dateValue: nil, + amountValue: amount, + feeValue: nil, + confirmationsValue: nil, + blockValue: nil, + isOutgoing: richTransaction.isOutgoing, + transactionStatus: nil + ) + + vc.senderId = senderId + vc.recipientId = recipientId + vc.comment = comment + vc.transaction = transaction ?? failedTransaction + vc.richTransaction = richTransaction + return vc + } + + func makeTransactionDetailsVC(service: Service) -> EthTransactionDetailsViewController { + let vc = EthTransactionDetailsViewController( + dialogService: assembler.resolve(DialogService.self)!, + currencyInfo: assembler.resolve(CurrencyInfoService.self)!, + addressBookService: assembler.resolve(AddressBookService.self)!, + accountService: assembler.resolve(AccountService.self)! + ) + + vc.service = service + return vc + } +} diff --git a/Adamant/Wallets/Ethereum/EthWalletService+DynamicConstants.swift b/Adamant/Modules/Wallets/Ethereum/EthWalletService+DynamicConstants.swift similarity index 100% rename from Adamant/Wallets/Ethereum/EthWalletService+DynamicConstants.swift rename to Adamant/Modules/Wallets/Ethereum/EthWalletService+DynamicConstants.swift diff --git a/Adamant/Modules/Wallets/Ethereum/EthWalletService+RichMessageProvider.swift b/Adamant/Modules/Wallets/Ethereum/EthWalletService+RichMessageProvider.swift new file mode 100644 index 000000000..79ab8f426 --- /dev/null +++ b/Adamant/Modules/Wallets/Ethereum/EthWalletService+RichMessageProvider.swift @@ -0,0 +1,64 @@ +// +// EthWalletService+RichMessageProvider.swift +// Adamant +// +// Created by Anokhov Pavel on 08.09.2018. +// Copyright © 2018 Adamant. All rights reserved. +// + +import Foundation +import MessageKit +import UIKit +import CommonKit + +extension EthWalletService: RichMessageProvider { + var newPendingInterval: TimeInterval { + .init(milliseconds: type(of: self).newPendingInterval) + } + + var oldPendingInterval: TimeInterval { + .init(milliseconds: type(of: self).oldPendingInterval) + } + + var registeredInterval: TimeInterval { + .init(milliseconds: type(of: self).registeredInterval) + } + + var newPendingAttempts: Int { + type(of: self).newPendingAttempts + } + + var oldPendingAttempts: Int { + type(of: self).oldPendingAttempts + } + + var dynamicRichMessageType: String { + return type(of: self).richMessageType + } + + // MARK: Short description + + func shortDescription(for transaction: RichMessageTransaction) -> NSAttributedString { + let amount: String + + guard let raw = transaction.getRichValue(for: RichContentKeys.transfer.amount) + else { + return NSAttributedString(string: "⬅️ \(EthWalletService.currencySymbol)") + } + + if let decimal = Decimal(string: raw) { + amount = AdamantBalanceFormat.full.format(decimal) + } else { + amount = raw + } + + let string: String + if transaction.isOutgoing { + string = "⬅️ \(amount) \(EthWalletService.currencySymbol)" + } else { + string = "➡️ \(amount) \(EthWalletService.currencySymbol)" + } + + return NSAttributedString(string: string) + } +} diff --git a/Adamant/Wallets/Ethereum/EthWalletService+RichMessageProviderWithStatusCheck.swift b/Adamant/Modules/Wallets/Ethereum/EthWalletService+RichMessageProviderWithStatusCheck.swift similarity index 100% rename from Adamant/Wallets/Ethereum/EthWalletService+RichMessageProviderWithStatusCheck.swift rename to Adamant/Modules/Wallets/Ethereum/EthWalletService+RichMessageProviderWithStatusCheck.swift diff --git a/Adamant/Wallets/Ethereum/EthWalletService+Send.swift b/Adamant/Modules/Wallets/Ethereum/EthWalletService+Send.swift similarity index 92% rename from Adamant/Wallets/Ethereum/EthWalletService+Send.swift rename to Adamant/Modules/Wallets/Ethereum/EthWalletService+Send.swift index 03c226a6f..2e1404e0c 100644 --- a/Adamant/Wallets/Ethereum/EthWalletService+Send.swift +++ b/Adamant/Modules/Wallets/Ethereum/EthWalletService+Send.swift @@ -81,15 +81,6 @@ extension EthWalletService: WalletServiceTwoStepSend { } } - func transferViewController() -> UIViewController { - guard let vc = router.get(scene: AdamantScene.Wallets.Ethereum.transfer) as? EthTransferViewController else { - fatalError("Can't get EthTransferViewController") - } - - vc.service = self - return vc - } - func sendTransaction(_ transaction: CodableTransaction) async throws { guard let txEncoded = transaction.encode() else { throw WalletServiceError.internalError(message: String.adamant.sharedErrors.unknownError, error: nil) diff --git a/Adamant/Wallets/Ethereum/EthWalletService.swift b/Adamant/Modules/Wallets/Ethereum/EthWalletService.swift similarity index 98% rename from Adamant/Wallets/Ethereum/EthWalletService.swift rename to Adamant/Modules/Wallets/Ethereum/EthWalletService.swift index 54b0461bb..59dfb0d3e 100644 --- a/Adamant/Wallets/Ethereum/EthWalletService.swift +++ b/Adamant/Modules/Wallets/Ethereum/EthWalletService.swift @@ -65,7 +65,7 @@ extension Web3Error { } } -class EthWalletService: WalletService { +final class EthWalletService: WalletService { // MARK: - Constants let addressRegex = try! NSRegularExpression(pattern: "^0x[a-fA-F0-9]{40}$") @@ -123,7 +123,6 @@ class EthWalletService: WalletService { weak var accountService: AccountService? var apiService: ApiService! var dialogService: DialogService! - var router: Router! var increaseFeeService: IncreaseFeeService! // MARK: - Notifications @@ -154,17 +153,7 @@ class EthWalletService: WalletService { } } - private (set) var enabled = true - - var walletViewController: WalletViewController { - guard let vc = router.get(scene: AdamantScene.Wallets.Ethereum.wallet) as? EthWalletViewController else { - fatalError("Can't get EthWalletViewController") - } - - vc.service = self - return vc - } - + private(set) var enabled = true private var initialBalanceCheck = false private var subscriptions = Set() @@ -537,7 +526,6 @@ extension EthWalletService: SwinjectDependentService { accountService = container.resolve(AccountService.self) apiService = container.resolve(ApiService.self) dialogService = container.resolve(DialogService.self) - router = container.resolve(Router.self) increaseFeeService = container.resolve(IncreaseFeeService.self) } } diff --git a/Adamant/Wallets/Ethereum/EthWalletViewController.swift b/Adamant/Modules/Wallets/Ethereum/EthWalletViewController.swift similarity index 93% rename from Adamant/Wallets/Ethereum/EthWalletViewController.swift rename to Adamant/Modules/Wallets/Ethereum/EthWalletViewController.swift index d2d8a7afa..ae755ec1a 100644 --- a/Adamant/Wallets/Ethereum/EthWalletViewController.swift +++ b/Adamant/Modules/Wallets/Ethereum/EthWalletViewController.swift @@ -15,7 +15,7 @@ extension String.adamant.wallets { static let sendEth = String.localized("AccountTab.Row.SendEth", comment: "Account tab: 'Send ETH tokens' button") } -class EthWalletViewController: WalletViewControllerBase { +final class EthWalletViewController: WalletViewControllerBase { // MARK: Lifecycle override func viewDidLoad() { diff --git a/Adamant/Wallets/Lisk/LskTransactionDetailsViewController.swift b/Adamant/Modules/Wallets/Lisk/LskTransactionDetailsViewController.swift similarity index 96% rename from Adamant/Wallets/Lisk/LskTransactionDetailsViewController.swift rename to Adamant/Modules/Wallets/Lisk/LskTransactionDetailsViewController.swift index 3d6a90c73..72c65fa1c 100644 --- a/Adamant/Wallets/Lisk/LskTransactionDetailsViewController.swift +++ b/Adamant/Modules/Wallets/Lisk/LskTransactionDetailsViewController.swift @@ -9,7 +9,7 @@ import UIKit import CommonKit -class LskTransactionDetailsViewController: TransactionDetailsViewControllerBase { +final class LskTransactionDetailsViewController: TransactionDetailsViewControllerBase { // MARK: - Dependencies weak var service: LskWalletService? diff --git a/Adamant/Wallets/Lisk/LskTransactionsViewController.swift b/Adamant/Modules/Wallets/Lisk/LskTransactionsViewController.swift similarity index 96% rename from Adamant/Wallets/Lisk/LskTransactionsViewController.swift rename to Adamant/Modules/Wallets/Lisk/LskTransactionsViewController.swift index e72258853..f660da43c 100644 --- a/Adamant/Wallets/Lisk/LskTransactionsViewController.swift +++ b/Adamant/Modules/Wallets/Lisk/LskTransactionsViewController.swift @@ -12,12 +12,12 @@ import web3swift import BigInt import CommonKit -class LskTransactionsViewController: TransactionsListViewControllerBase { +final class LskTransactionsViewController: TransactionsListViewControllerBase { // MARK: - Dependencies var lskWalletService: LskWalletService! var dialogService: DialogService! - var router: Router! + var screensFactory: ScreensFactory! // MARK: - Properties var transactions: [Transactions.TransactionModel] = [] @@ -74,10 +74,7 @@ class LskTransactionsViewController: TransactionsListViewControllerBase { tableView.deselectRow(at: indexPath, animated: true) let transaction = transactions[indexPath.row] - - guard let controller = router.get(scene: AdamantScene.Wallets.Lisk.transactionDetails) as? LskTransactionDetailsViewController else { - return - } + let controller = screensFactory.makeDetailsVC(service: lskWalletService) let emptyTransaction = SimpleTransactionDetails( txId: transaction.txId, @@ -93,7 +90,6 @@ class LskTransactionsViewController: TransactionsListViewControllerBase { ) controller.transaction = emptyTransaction - controller.service = lskWalletService if let address = lskWalletService.wallet?.address { if transaction.senderAddress.caseInsensitiveCompare(address) == .orderedSame { diff --git a/Adamant/Wallets/Lisk/LskTransferViewController.swift b/Adamant/Modules/Wallets/Lisk/LskTransferViewController.swift similarity index 83% rename from Adamant/Wallets/Lisk/LskTransferViewController.swift rename to Adamant/Modules/Wallets/Lisk/LskTransferViewController.swift index 018349115..ac301fc61 100644 --- a/Adamant/Wallets/Lisk/LskTransferViewController.swift +++ b/Adamant/Modules/Wallets/Lisk/LskTransferViewController.swift @@ -96,30 +96,22 @@ final class LskTransferViewController: TransferViewControllerBase { service: LskWalletService, comments: String ) { - if let detailsVc = router.get(scene: AdamantScene.Wallets.Lisk.transactionDetails) as? LskTransactionDetailsViewController { - var transaction: TransactionEntity = transaction - transaction.id = transactionId - detailsVc.transaction = transaction - detailsVc.service = service - detailsVc.senderName = String.adamant.transactionDetails.yourAddress - detailsVc.recipientName = recipientName - - if comments.count > 0 { - detailsVc.comment = comments - } - - delegate?.transferViewController( - self, - didFinishWithTransfer: transaction, - detailsViewController: detailsVc - ) - } else { - delegate?.transferViewController( - self, - didFinishWithTransfer: transaction, - detailsViewController: nil - ) + let detailsVc = screensFactory.makeDetailsVC(service: service) + var transaction: TransactionEntity = transaction + transaction.id = transactionId + detailsVc.transaction = transaction + detailsVc.senderName = String.adamant.transactionDetails.yourAddress + detailsVc.recipientName = recipientName + + if comments.count > 0 { + detailsVc.comment = comments } + + delegate?.transferViewController( + self, + didFinishWithTransfer: transaction, + detailsViewController: detailsVc + ) } // MARK: Overrides diff --git a/Adamant/Wallets/Lisk/LskWallet.swift b/Adamant/Modules/Wallets/Lisk/LskWallet.swift similarity index 96% rename from Adamant/Wallets/Lisk/LskWallet.swift rename to Adamant/Modules/Wallets/Lisk/LskWallet.swift index d1963ef8d..f4bfa7afd 100644 --- a/Adamant/Wallets/Lisk/LskWallet.swift +++ b/Adamant/Modules/Wallets/Lisk/LskWallet.swift @@ -9,7 +9,7 @@ import Foundation import LiskKit -class LskWallet: WalletAccount { +final class LskWallet: WalletAccount { var address: String { return isNewApi ? lisk32Address : legacyAddress diff --git a/Adamant/Modules/Wallets/Lisk/LskWalletFactory.swift b/Adamant/Modules/Wallets/Lisk/LskWalletFactory.swift new file mode 100644 index 000000000..a21ec0b3c --- /dev/null +++ b/Adamant/Modules/Wallets/Lisk/LskWalletFactory.swift @@ -0,0 +1,134 @@ +// +// LskWalletFactory.swift +// Adamant +// +// Created by Anton Boyarkin on 27/11/2018. +// Copyright © 2018 Adamant. All rights reserved. +// + +import Swinject +import UIKit +import CommonKit +import LiskKit + +struct LskWalletFactory: WalletFactory { + typealias Service = LskWalletService + + let assembler: Assembler + + func makeWalletVC(service: Service, screensFactory: ScreensFactory) -> WalletViewController { + let c = LskWalletViewController(nibName: "WalletViewControllerBase", bundle: nil) + c.dialogService = assembler.resolve(DialogService.self) + c.currencyInfoService = assembler.resolve(CurrencyInfoService.self) + c.accountService = assembler.resolve(AccountService.self) + c.service = service + c.screensFactory = screensFactory + return c + } + + func makeTransferListVC(service: Service, screensFactory: ScreensFactory) -> UIViewController { + let c = LskTransactionsViewController(nibName: "TransactionsListViewControllerBase", bundle: nil) + c.dialogService = assembler.resolve(DialogService.self) + c.screensFactory = screensFactory + c.lskWalletService = service + return c + } + + func makeTransferVC(service: Service, screensFactory: ScreensFactory) -> TransferViewControllerBase { + let vc = LskTransferViewController( + chatsProvider: assembler.resolve(ChatsProvider.self)!, + accountService: assembler.resolve(AccountService.self)!, + accountsProvider: assembler.resolve(AccountsProvider.self)!, + dialogService: assembler.resolve(DialogService.self)!, + screensFactory: screensFactory, + currencyInfoService: assembler.resolve(CurrencyInfoService.self)!, + increaseFeeService: assembler.resolve(IncreaseFeeService.self)! + ) + + vc.service = service + return vc + } + + func makeDetailsVC(service: Service, transaction: RichMessageTransaction) -> UIViewController? { + guard let hash = transaction.getRichValue(for: RichContentKeys.transfer.hash) + else { return nil } + + let comment: String? + if let raw = transaction.getRichValue(for: RichContentKeys.transfer.comments), raw.count > 0 { + comment = raw + } else { + comment = nil + } + + return makeTransactionDetailsVC( + hash: hash, + senderId: transaction.senderId, + recipientId: transaction.recipientId, + comment: comment, + senderAddress: "", + recipientAddress: "", + transaction: nil, + richTransaction: transaction, + service: service + ) + } + + func makeDetailsVC(service: Service) -> TransactionDetailsViewControllerBase { + makeTransactionDetailsVC(service: service) + } +} + +private extension LskWalletFactory { + private func makeTransactionDetailsVC( + hash: String, + senderId: String?, + recipientId: String?, + comment: String?, + senderAddress: String, + recipientAddress: String, + transaction: Transactions.TransactionModel?, + richTransaction: RichMessageTransaction, + service: Service + ) -> UIViewController { + let vc = makeTransactionDetailsVC(service: service) + vc.senderId = senderId + vc.recipientId = recipientId + vc.comment = comment + + let amount: Decimal + if let amountRaw = richTransaction.getRichValue(for: RichContentKeys.transfer.amount), + let decimal = Decimal(string: amountRaw) { + amount = decimal + } else { + amount = 0 + } + + let failedTransaction = SimpleTransactionDetails( + txId: hash, + senderAddress: senderAddress, + recipientAddress: recipientAddress, + dateValue: nil, + amountValue: amount, + feeValue: nil, + confirmationsValue: nil, + blockValue: nil, + isOutgoing: richTransaction.isOutgoing, + transactionStatus: nil) + + vc.transaction = transaction ?? failedTransaction + vc.richTransaction = richTransaction + return vc + } + + func makeTransactionDetailsVC(service: Service) -> LskTransactionDetailsViewController { + let vc = LskTransactionDetailsViewController( + dialogService: assembler.resolve(DialogService.self)!, + currencyInfo: assembler.resolve(CurrencyInfoService.self)!, + addressBookService: assembler.resolve(AddressBookService.self)!, + accountService: assembler.resolve(AccountService.self)! + ) + + vc.service = service + return vc + } +} diff --git a/Adamant/Wallets/Lisk/LskWalletService+DynamicConstants.swift b/Adamant/Modules/Wallets/Lisk/LskWalletService+DynamicConstants.swift similarity index 100% rename from Adamant/Wallets/Lisk/LskWalletService+DynamicConstants.swift rename to Adamant/Modules/Wallets/Lisk/LskWalletService+DynamicConstants.swift diff --git a/Adamant/Modules/Wallets/Lisk/LskWalletService+RichMessageProvider.swift b/Adamant/Modules/Wallets/Lisk/LskWalletService+RichMessageProvider.swift new file mode 100644 index 000000000..f27e9d3bf --- /dev/null +++ b/Adamant/Modules/Wallets/Lisk/LskWalletService+RichMessageProvider.swift @@ -0,0 +1,65 @@ +// +// LskWalletService+RichMessageProvider.swift +// Adamant +// +// Created by Anton Boyarkin on 06/12/2018. +// Copyright © 2018 Adamant. All rights reserved. +// + +import Foundation +import MessageKit +import UIKit +import LiskKit +import CommonKit + +extension LskWalletService: RichMessageProvider { + var newPendingInterval: TimeInterval { + .init(milliseconds: type(of: self).newPendingInterval) + } + + var oldPendingInterval: TimeInterval { + .init(milliseconds: type(of: self).oldPendingInterval) + } + + var registeredInterval: TimeInterval { + .init(milliseconds: type(of: self).registeredInterval) + } + + var newPendingAttempts: Int { + type(of: self).newPendingAttempts + } + + var oldPendingAttempts: Int { + type(of: self).oldPendingAttempts + } + + var dynamicRichMessageType: String { + return type(of: self).richMessageType + } + + // MARK: Short description + + func shortDescription(for transaction: RichMessageTransaction) -> NSAttributedString { + let amount: String + + guard let raw = transaction.getRichValue(for: RichContentKeys.transfer.amount) + else { + return NSAttributedString(string: "⬅️ \(LskWalletService.currencySymbol)") + } + + if let decimal = Decimal(string: raw) { + amount = AdamantBalanceFormat.full.format(decimal) + } else { + amount = raw + } + + let string: String + if transaction.isOutgoing { + string = "⬅️ \(amount) \(LskWalletService.currencySymbol)" + } else { + string = "➡️ \(amount) \(LskWalletService.currencySymbol)" + } + + return NSAttributedString(string: string) + } +} diff --git a/Adamant/Wallets/Lisk/LskWalletService+RichMessageProviderWithStatusCheck.swift b/Adamant/Modules/Wallets/Lisk/LskWalletService+RichMessageProviderWithStatusCheck.swift similarity index 100% rename from Adamant/Wallets/Lisk/LskWalletService+RichMessageProviderWithStatusCheck.swift rename to Adamant/Modules/Wallets/Lisk/LskWalletService+RichMessageProviderWithStatusCheck.swift diff --git a/Adamant/Wallets/Lisk/LskWalletService+Send.swift b/Adamant/Modules/Wallets/Lisk/LskWalletService+Send.swift similarity index 86% rename from Adamant/Wallets/Lisk/LskWalletService+Send.swift rename to Adamant/Modules/Wallets/Lisk/LskWalletService+Send.swift index 712c46887..26cbd422e 100644 --- a/Adamant/Wallets/Lisk/LskWalletService+Send.swift +++ b/Adamant/Modules/Wallets/Lisk/LskWalletService+Send.swift @@ -25,15 +25,6 @@ extension TransactionEntity: RawTransaction { extension LskWalletService: WalletServiceTwoStepSend { typealias T = TransactionEntity - func transferViewController() -> UIViewController { - guard let vc = router.get(scene: AdamantScene.Wallets.Lisk.transfer) as? LskTransferViewController else { - fatalError("Can't get LskTransferViewController") - } - - vc.service = self - return vc - } - // MARK: Create & Send func createTransaction(recipient: String, amount: Decimal) async throws -> TransactionEntity { // MARK: 1. Prepare diff --git a/Adamant/Wallets/Lisk/LskWalletService.swift b/Adamant/Modules/Wallets/Lisk/LskWalletService.swift similarity index 98% rename from Adamant/Wallets/Lisk/LskWalletService.swift rename to Adamant/Modules/Wallets/Lisk/LskWalletService.swift index 3303a4805..ea5070779 100644 --- a/Adamant/Wallets/Lisk/LskWalletService.swift +++ b/Adamant/Modules/Wallets/Lisk/LskWalletService.swift @@ -17,19 +17,9 @@ import Web3Core import Combine import CommonKit -class LskWalletService: WalletService { - +final class LskWalletService: WalletService { var wallet: WalletAccount? { return lskWallet } - var walletViewController: WalletViewController { - guard let vc = router.get(scene: AdamantScene.Wallets.Lisk.wallet) as? LskWalletViewController else { - fatalError("Can't get LskWalletViewController") - } - - vc.service = self - return vc - } - // MARK: - Notifications let walletUpdatedNotification = Notification.Name("adamant.lskWallet.walletUpdated") let serviceEnabledChanged = Notification.Name("adamant.lskWallet.enabledChanged") @@ -43,7 +33,6 @@ class LskWalletService: WalletService { var apiService: ApiService! var accountService: AccountService! var dialogService: DialogService! - var router: Router! // MARK: - Constants var transactionFee: Decimal { @@ -485,7 +474,6 @@ extension LskWalletService: SwinjectDependentService { accountService = container.resolve(AccountService.self) apiService = container.resolve(ApiService.self) dialogService = container.resolve(DialogService.self) - router = container.resolve(Router.self) } } diff --git a/Adamant/Wallets/Lisk/LskWalletViewController.swift b/Adamant/Modules/Wallets/Lisk/LskWalletViewController.swift similarity index 93% rename from Adamant/Wallets/Lisk/LskWalletViewController.swift rename to Adamant/Modules/Wallets/Lisk/LskWalletViewController.swift index 053c38075..80a91c5da 100644 --- a/Adamant/Wallets/Lisk/LskWalletViewController.swift +++ b/Adamant/Modules/Wallets/Lisk/LskWalletViewController.swift @@ -15,7 +15,7 @@ extension String.adamant { static let sendLsk = String.localized("AccountTab.Row.SendLsk", comment: "Account tab: 'Send LSK tokens' button") } -class LskWalletViewController: WalletViewControllerBase { +final class LskWalletViewController: WalletViewControllerBase { // MARK: Lifecycle override func viewDidLoad() { diff --git a/Adamant/Wallets/TransactionDetails.swift b/Adamant/Modules/Wallets/TransactionDetails.swift similarity index 100% rename from Adamant/Wallets/TransactionDetails.swift rename to Adamant/Modules/Wallets/TransactionDetails.swift diff --git a/Adamant/Wallets/TransactionDetailsViewControllerBase.swift b/Adamant/Modules/Wallets/TransactionDetailsViewControllerBase.swift similarity index 100% rename from Adamant/Wallets/TransactionDetailsViewControllerBase.swift rename to Adamant/Modules/Wallets/TransactionDetailsViewControllerBase.swift diff --git a/Adamant/Wallets/TransactionTableViewCell.swift b/Adamant/Modules/Wallets/TransactionTableViewCell.swift similarity index 100% rename from Adamant/Wallets/TransactionTableViewCell.swift rename to Adamant/Modules/Wallets/TransactionTableViewCell.swift diff --git a/Adamant/Wallets/TransactionTableViewCell.xib b/Adamant/Modules/Wallets/TransactionTableViewCell.xib similarity index 100% rename from Adamant/Wallets/TransactionTableViewCell.xib rename to Adamant/Modules/Wallets/TransactionTableViewCell.xib diff --git a/Adamant/Wallets/TransactionsListViewControllerBase.swift b/Adamant/Modules/Wallets/TransactionsListViewControllerBase.swift similarity index 100% rename from Adamant/Wallets/TransactionsListViewControllerBase.swift rename to Adamant/Modules/Wallets/TransactionsListViewControllerBase.swift diff --git a/Adamant/Wallets/TransactionsListViewControllerBase.xib b/Adamant/Modules/Wallets/TransactionsListViewControllerBase.xib similarity index 100% rename from Adamant/Wallets/TransactionsListViewControllerBase.xib rename to Adamant/Modules/Wallets/TransactionsListViewControllerBase.xib diff --git a/Adamant/Wallets/TransferViewControllerBase+Alert.swift b/Adamant/Modules/Wallets/TransferViewControllerBase+Alert.swift similarity index 100% rename from Adamant/Wallets/TransferViewControllerBase+Alert.swift rename to Adamant/Modules/Wallets/TransferViewControllerBase+Alert.swift diff --git a/Adamant/Wallets/TransferViewControllerBase+QR.swift b/Adamant/Modules/Wallets/TransferViewControllerBase+QR.swift similarity index 100% rename from Adamant/Wallets/TransferViewControllerBase+QR.swift rename to Adamant/Modules/Wallets/TransferViewControllerBase+QR.swift diff --git a/Adamant/Wallets/TransferViewControllerBase.swift b/Adamant/Modules/Wallets/TransferViewControllerBase.swift similarity index 99% rename from Adamant/Wallets/TransferViewControllerBase.swift rename to Adamant/Modules/Wallets/TransferViewControllerBase.swift index 2adfcd558..5cd363451 100644 --- a/Adamant/Wallets/TransferViewControllerBase.swift +++ b/Adamant/Modules/Wallets/TransferViewControllerBase.swift @@ -133,7 +133,7 @@ class TransferViewControllerBase: FormViewController { let accountService: AccountService let accountsProvider: AccountsProvider let dialogService: DialogService - let router: Router + let screensFactory: ScreensFactory let currencyInfoService: CurrencyInfoService var increaseFeeService: IncreaseFeeService var chatsProvider: ChatsProvider @@ -273,14 +273,14 @@ class TransferViewControllerBase: FormViewController { accountService: AccountService, accountsProvider: AccountsProvider, dialogService: DialogService, - router: Router, + screensFactory: ScreensFactory, currencyInfoService: CurrencyInfoService, increaseFeeService: IncreaseFeeService ) { self.accountService = accountService self.accountsProvider = accountsProvider self.dialogService = dialogService - self.router = router + self.screensFactory = screensFactory self.currencyInfoService = currencyInfoService self.increaseFeeService = increaseFeeService self.chatsProvider = chatsProvider diff --git a/Adamant/Wallets/WalletAccount.swift b/Adamant/Modules/Wallets/WalletAccount.swift similarity index 100% rename from Adamant/Wallets/WalletAccount.swift rename to Adamant/Modules/Wallets/WalletAccount.swift diff --git a/Adamant/Wallets/WalletService.swift b/Adamant/Modules/Wallets/WalletService.swift similarity index 97% rename from Adamant/Wallets/WalletService.swift rename to Adamant/Modules/Wallets/WalletService.swift index 351eb9a78..a576e6d93 100644 --- a/Adamant/Wallets/WalletService.swift +++ b/Adamant/Modules/Wallets/WalletService.swift @@ -237,9 +237,6 @@ protocol WalletService: AnyObject { // MARK: Logic func update() - // MARK: Account UI - var walletViewController: WalletViewController { get } - // MARK: Tools func validate(address: String) -> AddressValidationResult func getWalletAddress(byAdamantAddress address: String) async throws -> String @@ -256,10 +253,6 @@ protocol InitiatedWithPassphraseService: WalletService { func setInitiationFailed(reason: String) } -protocol WalletServiceWithTransfers: WalletService { - func transferListViewController() -> UIViewController -} - // MARK: Send protocol WalletServiceWithSend: WalletService { @@ -277,7 +270,6 @@ protocol WalletServiceWithSend: WalletService { var isSupportIncreaseFee: Bool { get } var isIncreaseFeeEnabled: Bool { get } var defaultIncreaseFee: Decimal { get } - func transferViewController() -> UIViewController } extension WalletServiceWithSend { diff --git a/Adamant/Wallets/WalletViewControllerBase.swift b/Adamant/Modules/Wallets/WalletViewControllerBase.swift similarity index 85% rename from Adamant/Wallets/WalletViewControllerBase.swift rename to Adamant/Modules/Wallets/WalletViewControllerBase.swift index 3214ad88c..bad99840c 100644 --- a/Adamant/Wallets/WalletViewControllerBase.swift +++ b/Adamant/Modules/Wallets/WalletViewControllerBase.swift @@ -50,6 +50,7 @@ class WalletViewControllerBase: FormViewController, WalletViewController { var dialogService: DialogService! var currencyInfoService: CurrencyInfoService! var accountService: AccountService! + var screensFactory: ScreensFactory! // MARK: - Properties, WalletViewController @@ -123,26 +124,26 @@ class WalletViewControllerBase: FormViewController, WalletViewController { cell.height = { height } } - if service is WalletServiceWithTransfers { - balanceRow.cell.selectionStyle = .gray - balanceRow.cellUpdate { (cell, _) in - cell.accessoryType = .disclosureIndicator - }.onCellSelection { [weak self] (_, _) in - guard let service = self?.service as? WalletServiceWithTransfers else { - return - } - - let vc = service.transferListViewController() - if let split = self?.splitViewController { - let details = UINavigationController(rootViewController:vc) - split.showDetailViewController(details, sender: self) - } else { - self?.navigationController?.pushViewController(vc, animated: true ) - } - - if let vc = self, let delegate = vc.delegate { - delegate.walletViewControllerSelectedRow(vc) - } + balanceRow.cell.selectionStyle = .gray + balanceRow.cellUpdate { (cell, _) in + cell.accessoryType = .disclosureIndicator + }.onCellSelection { [weak self] (_, _) in + guard + let self = self, + let service = service + else { return } + + let vc = screensFactory.makeTransferListVC(service: service) + + if let split = splitViewController { + let details = UINavigationController(rootViewController:vc) + split.showDetailViewController(details, sender: self) + } else { + navigationController?.pushViewController(vc, animated: true ) + } + + if let delegate = delegate { + delegate.walletViewControllerSelectedRow(self) } } @@ -170,37 +171,33 @@ class WalletViewControllerBase: FormViewController, WalletViewController { cell.textLabel?.attributedText = label } }.onCellSelection { [weak self] (_, _) in - guard let service = self?.service as? WalletServiceWithSend else { - return - } + guard let self = self, let service = service else { return } - let vc = service.transferViewController() - if let v = vc as? TransferViewControllerBase { - v.delegate = self - if ERC20Token.supportedTokens.contains(where: { token in - return token.symbol == service.tokenSymbol - }) { - let ethWallet = self?.accountService.wallets.first { wallet in - return wallet.tokenSymbol == "ETH" - } - v.rootCoinBalance = ethWallet?.wallet?.balance + let vc = screensFactory.makeTransferVC(service: service) + vc.delegate = self + if ERC20Token.supportedTokens.contains(where: { token in + return token.symbol == service.tokenSymbol + }) { + let ethWallet = accountService.wallets.first { wallet in + return wallet.tokenSymbol == "ETH" } + vc.rootCoinBalance = ethWallet?.wallet?.balance } - if let split = self?.splitViewController { + if let split = splitViewController { let details = UINavigationController(rootViewController:vc) split.showDetailViewController(details, sender: self) } else { - if let nav = self?.navigationController { + if let nav = navigationController { nav.pushViewController(vc, animated: true) } else { vc.modalPresentationStyle = .overFullScreen - self?.present(vc, animated: true) + present(vc, animated: true) } } - if let vc = self, let delegate = vc.delegate { - delegate.walletViewControllerSelectedRow(vc) + if let delegate = delegate { + delegate.walletViewControllerSelectedRow(self) } } @@ -436,16 +433,20 @@ class WalletViewControllerBase: FormViewController, WalletViewController { // MARK: - TransferViewControllerDelegate extension WalletViewControllerBase: TransferViewControllerDelegate { - func transferViewController(_ viewController: TransferViewControllerBase, didFinishWithTransfer transfer: TransactionDetails?, detailsViewController: UIViewController?) { - DispatchQueue.onMainAsync { [weak self] in - if let split = self?.splitViewController { + func transferViewController( + _ viewController: TransferViewControllerBase, + didFinishWithTransfer transfer: TransactionDetails?, + detailsViewController: UIViewController? + ) { + DispatchQueue.onMainAsync { [self] in + if let split = splitViewController { if let nav = split.viewControllers.last as? UINavigationController { if let detailsViewController = detailsViewController { var viewControllers = nav.viewControllers viewControllers.removeLast() - if let service = self?.service as? WalletServiceWithTransfers { - viewControllers.append(service.transferListViewController()) + if let service = service { + viewControllers.append(screensFactory.makeTransferListVC(service: service)) } viewControllers.append(detailsViewController) @@ -456,7 +457,7 @@ extension WalletViewControllerBase: TransferViewControllerDelegate { } else { split.showDetailViewController(viewController, sender: nil) } - } else if let nav = self?.navigationController { + } else if let nav = navigationController { if let detailsViewController = detailsViewController { var viewControllers = nav.viewControllers viewControllers.removeLast() @@ -465,12 +466,12 @@ extension WalletViewControllerBase: TransferViewControllerDelegate { } else { nav.popViewController(animated: true) } - } else if self?.presentedViewController == viewController { - self?.dismiss(animated: true, completion: nil) + } else if presentedViewController == viewController { + dismiss(animated: true, completion: nil) if let detailsViewController = detailsViewController { detailsViewController.modalPresentationStyle = .overFullScreen - self?.present(detailsViewController, animated: true, completion: nil) + present(detailsViewController, animated: true, completion: nil) } } } diff --git a/Adamant/Wallets/WalletViewControllerBase.xib b/Adamant/Modules/Wallets/WalletViewControllerBase.xib similarity index 100% rename from Adamant/Wallets/WalletViewControllerBase.xib rename to Adamant/Modules/Wallets/WalletViewControllerBase.xib diff --git a/Adamant/Stories/Shared/WelcomeViewController.swift b/Adamant/Modules/Welcome/WelcomeViewController.swift similarity index 100% rename from Adamant/Stories/Shared/WelcomeViewController.swift rename to Adamant/Modules/Welcome/WelcomeViewController.swift diff --git a/Adamant/Stories/Shared/WelcomeViewController.xib b/Adamant/Modules/Welcome/WelcomeViewController.xib similarity index 100% rename from Adamant/Stories/Shared/WelcomeViewController.xib rename to Adamant/Modules/Welcome/WelcomeViewController.xib diff --git a/Adamant/ServiceProtocols/RichMessageProvider.swift b/Adamant/ServiceProtocols/RichMessageProvider.swift index 53e0e517a..8d8e137e8 100644 --- a/Adamant/ServiceProtocols/RichMessageProvider.swift +++ b/Adamant/ServiceProtocols/RichMessageProvider.swift @@ -10,7 +10,7 @@ import Foundation import MessageKit import UIKit -protocol RichMessageProvider: AnyObject { +protocol RichMessageProvider: WalletService { /// Lowercased!! static var richMessageType: String { get } @@ -28,9 +28,6 @@ protocol RichMessageProvider: AnyObject { var tokenSymbol: String { get } var tokenLogo: UIImage { get } - // MARK: Events - func richMessageTapped(for transaction: RichMessageTransaction, in chat: ChatViewController) - // MARK: Chats list func shortDescription(for transaction: RichMessageTransaction) -> NSAttributedString } diff --git a/Adamant/ServiceProtocols/Router.swift b/Adamant/ServiceProtocols/Router.swift deleted file mode 100644 index 8a357db05..000000000 --- a/Adamant/ServiceProtocols/Router.swift +++ /dev/null @@ -1,26 +0,0 @@ -// -// Router.swift -// Adamant -// -// Created by Anokhov Pavel on 07.01.2018. -// Copyright © 2018 Adamant. All rights reserved. -// - -import UIKit -import Swinject - -// MARK: - Adamant Scene -struct AdamantScene { - let identifier: String - let factory: @MainActor (Resolver) -> UIViewController - - init(identifier: String, factory: @MainActor @escaping (Resolver) -> UIViewController) { - self.identifier = identifier - self.factory = factory - } -} - -// MARK: - Adamant Router -protocol Router: AnyObject { - func get(scene: AdamantScene) -> UIViewController -} diff --git a/Adamant/Services/AdamantAuthentication.swift b/Adamant/Services/AdamantAuthentication.swift index 406312d42..601bb7cfc 100644 --- a/Adamant/Services/AdamantAuthentication.swift +++ b/Adamant/Services/AdamantAuthentication.swift @@ -9,7 +9,7 @@ import Foundation import LocalAuthentication -class AdamantAuthentication: LocalAuthentication { +final class AdamantAuthentication: LocalAuthentication { var biometryType: BiometryType { let context = LAContext() var error: NSError? diff --git a/Adamant/Services/AdamantCellFactory.swift b/Adamant/Services/AdamantCellFactory.swift index d6e1dca2c..296d13b89 100644 --- a/Adamant/Services/AdamantCellFactory.swift +++ b/Adamant/Services/AdamantCellFactory.swift @@ -8,7 +8,7 @@ import UIKit -class AdamantCellFactory: CellFactory { +final class AdamantCellFactory: CellFactory { func nib(for sharedCell: SharedCell) -> UINib? { /* UINib.init actually can throw an exception do { diff --git a/Adamant/Services/AdamantCurrencyInfoService.swift b/Adamant/Services/AdamantCurrencyInfoService.swift index 2ec4c0f0a..957e01fad 100644 --- a/Adamant/Services/AdamantCurrencyInfoService.swift +++ b/Adamant/Services/AdamantCurrencyInfoService.swift @@ -18,7 +18,7 @@ extension StoreKey { } // MARK: - Service -class AdamantCurrencyInfoService: CurrencyInfoService { +final class AdamantCurrencyInfoService: CurrencyInfoService { // MARK: - API private lazy var infoServiceUrl: URL = { return URL(string: AdamantResources.coinsInfoSrvice)! diff --git a/Adamant/Services/AdamantDialogService.swift b/Adamant/Services/AdamantDialogService.swift index 68ea21543..a3ffb0fcb 100644 --- a/Adamant/Services/AdamantDialogService.swift +++ b/Adamant/Services/AdamantDialogService.swift @@ -15,15 +15,12 @@ import CommonKit @MainActor final class AdamantDialogService: DialogService { // MARK: Dependencies - private let router: Router private let popupManager = PopupManager() private let mailDelegate = MailDelegate() + private weak var window: UIWindow? - // Configure notifications - nonisolated init(router: Router) { - self.router = router - } + nonisolated init() {} func setup(window: UIWindow) { self.window = window @@ -356,20 +353,22 @@ extension AdamantDialogService { case .generateQr(let encodedContent, let sharingTip, let withLogo): alert.addAction(UIAlertAction(title: type.localized, style: .default) { [weak self] _ in - switch AdamantQRTools.generateQrFrom(string: encodedContent ?? stringForQR, withLogo: withLogo) { + guard let self = self else { return } + + switch AdamantQRTools.generateQrFrom( + string: encodedContent ?? stringForQR, + withLogo: withLogo + ) { case .success(let qr): - guard let vc = self?.router.get(scene: AdamantScene.Shared.shareQr) as? ShareQrViewController else { - fatalError("Can't find ShareQrViewController") - } - + let vc = ShareQrViewController(dialogService: self) vc.qrCode = qr vc.sharingTip = sharingTip vc.excludedActivityTypes = excludedActivityTypes vc.modalPresentationStyle = .overFullScreen - self?.present(vc, animated: true, completion: completion) + present(vc, animated: true, completion: completion) case .failure(error: let error): - self?.showError( + showError( withMessage: error.localizedDescription, supportEmail: true, error: error diff --git a/Adamant/Services/DataProviders/InMemoryCoreDataStack.swift b/Adamant/Services/DataProviders/InMemoryCoreDataStack.swift index 16c96b00f..44ef0bb4c 100644 --- a/Adamant/Services/DataProviders/InMemoryCoreDataStack.swift +++ b/Adamant/Services/DataProviders/InMemoryCoreDataStack.swift @@ -9,7 +9,7 @@ import Foundation import CoreData -class InMemoryCoreDataStack: CoreDataStack { +final class InMemoryCoreDataStack: CoreDataStack { let container: NSPersistentContainer init(modelUrl url: URL) throws { diff --git a/Adamant/Services/SwinjectedRouter.swift b/Adamant/Services/SwinjectedRouter.swift deleted file mode 100644 index 2446552e1..000000000 --- a/Adamant/Services/SwinjectedRouter.swift +++ /dev/null @@ -1,18 +0,0 @@ -// -// SwinjectedRouter.swift -// Adamant -// -// Created by Anokhov Pavel on 07.01.2018. -// Copyright © 2018 Adamant. All rights reserved. -// - -import UIKit -import Swinject - -final class SwinjectedRouter: Router { - weak var container: Container? - - @MainActor func get(scene: AdamantScene) -> UIViewController { - return scene.factory(container!) - } -} diff --git a/Adamant/Stories/Shared/ButtonsStripe.xib b/Adamant/SharedViews/ButtonsStripe.xib similarity index 100% rename from Adamant/Stories/Shared/ButtonsStripe.xib rename to Adamant/SharedViews/ButtonsStripe.xib diff --git a/Adamant/Stories/Shared/ButtonsStripeView.swift b/Adamant/SharedViews/ButtonsStripeView.swift similarity index 99% rename from Adamant/Stories/Shared/ButtonsStripeView.swift rename to Adamant/SharedViews/ButtonsStripeView.swift index df4ad3c02..f75f6ce8e 100644 --- a/Adamant/Stories/Shared/ButtonsStripeView.swift +++ b/Adamant/SharedViews/ButtonsStripeView.swift @@ -61,7 +61,7 @@ protocol ButtonsStripeViewDelegate: AnyObject { } // MARK: - View -class ButtonsStripeView: UIView { +final class ButtonsStripeView: UIView { // MARK: IBOutlet @IBOutlet weak var stripeStackView: UIStackView! diff --git a/Adamant/SharedViews/FullscreenAlertView.swift b/Adamant/SharedViews/FullscreenAlertView.swift index 71c7c80f8..87ac2d84c 100644 --- a/Adamant/SharedViews/FullscreenAlertView.swift +++ b/Adamant/SharedViews/FullscreenAlertView.swift @@ -9,7 +9,7 @@ import UIKit import CommonKit -class FullscreenAlertView: UIView { +final class FullscreenAlertView: UIView { // MARK: IBOutlets diff --git a/Adamant/SharedViews/SwipeableView.swift b/Adamant/SharedViews/SwipeableView.swift index 80afb5e62..d74db0338 100644 --- a/Adamant/SharedViews/SwipeableView.swift +++ b/Adamant/SharedViews/SwipeableView.swift @@ -9,7 +9,7 @@ import UIKit import SnapKit -class SwipeableView: UIView { +final class SwipeableView: UIView { // MARK: Proprieties diff --git a/Adamant/Stories/Account/AccountRoutes.swift b/Adamant/Stories/Account/AccountRoutes.swift deleted file mode 100644 index df9d76436..000000000 --- a/Adamant/Stories/Account/AccountRoutes.swift +++ /dev/null @@ -1,29 +0,0 @@ -// -// AccountRoutes.swift -// Adamant -// -// Created by Anokhov Pavel on 07.01.2018. -// Copyright © 2018 Adamant. All rights reserved. -// - -import Foundation - -extension AdamantScene { - struct Account { - static let account = AdamantScene(identifier: "AccountViewController") { r in - let c = AccountViewController() - c.accountService = r.resolve(AccountService.self) - c.dialogService = r.resolve(DialogService.self) - c.router = r.resolve(Router.self) - c.notificationsService = r.resolve(NotificationsService.self) - c.transfersProvider = r.resolve(TransfersProvider.self) - c.localAuth = r.resolve(LocalAuthentication.self) - c.avatarService = r.resolve(AvatarService.self) - c.currencyInfoService = r.resolve(CurrencyInfoService.self) - c.visibleWalletsService = r.resolve(VisibleWalletsService.self) - return c - } - - private init() {} - } -} diff --git a/Adamant/Stories/ChatsList/ChatsRoutes.swift b/Adamant/Stories/ChatsList/ChatsRoutes.swift deleted file mode 100644 index 0022ce43c..000000000 --- a/Adamant/Stories/ChatsList/ChatsRoutes.swift +++ /dev/null @@ -1,69 +0,0 @@ -// -// ChatsRoutes.swift -// Adamant -// -// Created by Anokhov Pavel on 12.01.2018. -// Copyright © 2018 Adamant. All rights reserved. -// - -import UIKit -import CommonKit - -extension AdamantScene { - struct Chats { - static let chatList = AdamantScene(identifier: "ChatListViewController", factory: { r in - let c = ChatListViewController(nibName: "ChatListViewController", bundle: nil) - c.accountService = r.resolve(AccountService.self) - c.chatsProvider = r.resolve(ChatsProvider.self) - c.transfersProvider = r.resolve(TransfersProvider.self) - c.router = r.resolve(Router.self) - c.notificationsService = r.resolve(NotificationsService.self) - c.dialogService = r.resolve(DialogService.self) - c.addressBook = r.resolve(AddressBookService.self) - c.avatarService = r.resolve(AvatarService.self) - - // MARK: RichMessage handlers - // Transfer handlers from accountService' wallet services - if let accountService = r.resolve(AccountService.self) { - for case let provider as RichMessageProvider in accountService.wallets { - c.richMessageProviders[provider.dynamicRichMessageType] = provider - } - } - - return c - }) - - static let chat = AdamantScene(identifier: "ChatViewController", factory: { r in - r.resolve(ChatFactory.self)!.makeViewController() - }) - - static let newChat = AdamantScene(identifier: "NewChatViewController", factory: { r in - let c = NewChatViewController() - c.dialogService = r.resolve(DialogService.self) - c.accountService = r.resolve(AccountService.self) - c.accountsProvider = r.resolve(AccountsProvider.self) - c.router = r.resolve(Router.self) - return c - }) - - static let complexTransfer = AdamantScene(identifier: "ComplexTransferViewController", factory: { r in - let c = ComplexTransferViewController() - c.accountService = r.resolve(AccountService.self) - c.visibleWalletsService = r.resolve(VisibleWalletsService.self) - c.addressBookService = r.resolve(AddressBookService.self) - return c - }) - - static let searchResults = AdamantScene(identifier: "SearchResultsViewController", factory: { r in - let c = SearchResultsViewController( - router: r.resolve(Router.self)!, - avatarService: r.resolve(AvatarService.self)!, - addressBookService: r.resolve(AddressBookService.self)!, - accountsProvider: r.resolve(AccountsProvider.self)! - ) - return c - }) - - private init() {} - } -} diff --git a/Adamant/Stories/Delegates/DelegateRoutes.swift b/Adamant/Stories/Delegates/DelegateRoutes.swift deleted file mode 100644 index 4ac1fb98d..000000000 --- a/Adamant/Stories/Delegates/DelegateRoutes.swift +++ /dev/null @@ -1,33 +0,0 @@ -// -// DelegateRoutes.swift -// Adamant -// -// Created by Anton Boyarkin on 06/07/2018. -// Copyright © 2018 Adamant. All rights reserved. -// - -import Foundation -import CommonKit - -extension AdamantScene { - struct Delegates { - static let delegates = AdamantScene(identifier: "DelegatesListViewController", factory: { r in - DelegatesListViewController( - apiService: r.resolve(ApiService.self)!, - accountService: r.resolve(AccountService.self)!, - dialogService: r.resolve(DialogService.self)!, - router: r.resolve(Router.self)! - ) - }) - - static let delegateDetails = AdamantScene(identifier: "DelegateDetailsViewController", factory: { r in - let c = DelegateDetailsViewController(nibName: "DelegateDetailsViewController", bundle: nil) - c.apiService = r.resolve(ApiService.self) - c.accountService = r.resolve(AccountService.self) - c.dialogService = r.resolve(DialogService.self) - return c - }) - - private init() {} - } -} diff --git a/Adamant/Stories/Login/LoginRoutes.swift b/Adamant/Stories/Login/LoginRoutes.swift deleted file mode 100644 index f638070dc..000000000 --- a/Adamant/Stories/Login/LoginRoutes.swift +++ /dev/null @@ -1,26 +0,0 @@ -// -// LoginRoutes.swift -// Adamant -// -// Created by Anokhov Pavel on 07.01.2018. -// Copyright © 2018 Adamant. All rights reserved. -// - -import Foundation - -extension AdamantScene { - struct Login { - static let login = AdamantScene(identifier: "LoginViewController", factory: { r in - LoginViewController( - accountService: r.resolve(AccountService.self)!, - adamantCore: r.resolve(AdamantCore.self)!, - dialogService: r.resolve(DialogService.self)!, - localAuth: r.resolve(LocalAuthentication.self)!, - router: r.resolve(Router.self)!, - apiService: r.resolve(ApiService.self)! - ) - }) - - private init() {} - } -} diff --git a/Adamant/Stories/NodesEditor/NodesEditorRoutes.swift b/Adamant/Stories/NodesEditor/NodesEditorRoutes.swift deleted file mode 100644 index fa79b3d5a..000000000 --- a/Adamant/Stories/NodesEditor/NodesEditorRoutes.swift +++ /dev/null @@ -1,34 +0,0 @@ -// -// NodesEditorRoutes.swift -// Adamant -// -// Created by Anokhov Pavel on 20.06.2018. -// Copyright © 2018 Adamant. All rights reserved. -// - -import UIKit -import CommonKit - -extension AdamantScene { - struct NodesEditor { - static let nodesList = AdamantScene(identifier: "NodesListViewController", factory: { r in - let c = NodesListViewController() - c.dialogService = r.resolve(DialogService.self) - c.securedStore = r.resolve(SecuredStore.self) - c.apiService = r.resolve(ApiService.self) - c.socketService = r.resolve(SocketService.self) - c.router = r.resolve(Router.self) - c.nodesSource = r.resolve(NodesSource.self) - return c - }) - - static let nodeEditor = AdamantScene(identifier: "", factory: { r in - let c = NodeEditorViewController() - c.dialogService = r.resolve(DialogService.self) - c.apiService = r.resolve(ApiService.self) - return c - }) - - private init() {} - } -} diff --git a/Adamant/Stories/Onboard/OnboardRoutes.swift b/Adamant/Stories/Onboard/OnboardRoutes.swift deleted file mode 100644 index e0a64512a..000000000 --- a/Adamant/Stories/Onboard/OnboardRoutes.swift +++ /dev/null @@ -1,23 +0,0 @@ -// -// OnboardRoutes.swift -// Adamant -// -// Created by Anokhov Pavel on 18/01/2019. -// Copyright © 2019 Adamant. All rights reserved. -// - -import Foundation - -extension AdamantScene { - struct Onboard { - static let welcome = AdamantScene(identifier: "OnboardViewController") { _ in - let c = OnboardViewController(nibName: "OnboardViewController", bundle: nil) - return c - } - - static let eula = AdamantScene(identifier: "EulaViewController") { _ in - let c = EulaViewController(nibName: "EulaViewController", bundle: nil) - return c - } - } -} diff --git a/Adamant/Stories/Settings/Contribute/ContributeFactory.swift b/Adamant/Stories/Settings/Contribute/ContributeFactory.swift deleted file mode 100644 index bd4b3c670..000000000 --- a/Adamant/Stories/Settings/Contribute/ContributeFactory.swift +++ /dev/null @@ -1,23 +0,0 @@ -// -// ContributeFactory.swift -// Adamant -// -// Created by Stanislav Jelezoglo on 14.06.2023. -// Copyright © 2023 Adamant. All rights reserved. -// - -import UIKit -import SwiftUI - -@MainActor -struct ContributeFactory { - let crashliticsService: CrashlyticsService - - func makeViewController() -> UIViewController { - UIHostingController( - rootView: ContributeView( - viewModel: .init(crashliticsService: crashliticsService) - ) - ) - } -} diff --git a/Adamant/Stories/Settings/SettingsRoutes.swift b/Adamant/Stories/Settings/SettingsRoutes.swift deleted file mode 100644 index ae0dd3e0a..000000000 --- a/Adamant/Stories/Settings/SettingsRoutes.swift +++ /dev/null @@ -1,66 +0,0 @@ -// -// SettingsRoutes.swift -// Adamant -// -// Created by Anokhov Pavel on 01.02.2018. -// Copyright © 2018 Adamant. All rights reserved. -// - -import UIKit -import CommonKit - -extension AdamantScene { - struct Settings { - static let security = AdamantScene(identifier: "SecurityViewController") { r in - let c = SecurityViewController() - c.accountService = r.resolve(AccountService.self) - c.dialogService = r.resolve(DialogService.self) - c.notificationsService = r.resolve(NotificationsService.self) - c.localAuth = r.resolve(LocalAuthentication.self) - c.router = r.resolve(Router.self) - return c - } - - static let qRGenerator = AdamantScene(identifier: "QRGeneratorViewController") { r in - let c = QRGeneratorViewController() - c.dialogService = r.resolve(DialogService.self) - return c - } - - static let pkGenerator = AdamantScene(identifier: "PKGeneratorViewController") { r in - let c = PKGeneratorViewController() - c.dialogService = r.resolve(DialogService.self) - c.accountService = r.resolve(AccountService.self) - return c - } - - static let about = AdamantScene(identifier: "About") { r in - let c = AboutViewController() - c.accountService = r.resolve(AccountService.self) - c.accountsProvider = r.resolve(AccountsProvider.self) - c.dialogService = r.resolve(DialogService.self) - c.router = r.resolve(Router.self) - return c - } - - static let notifications = AdamantScene(identifier: "Notifications") { r in - let c = NotificationsViewController() - c.notificationsService = r.resolve(NotificationsService.self) - c.dialogService = r.resolve(DialogService.self) - return c - } - - static let visibleWallets = AdamantScene(identifier: "VisibleWallets") { r in - VisibleWalletsViewController( - visibleWalletsService: r.resolve(VisibleWalletsService.self)!, - accountService: r.resolve(AccountService.self)! - ) - } - - static let contribute = AdamantScene(identifier: "Contribute", factory: { r in - r.resolve(ContributeFactory.self)!.makeViewController() - }) - - private init() {} - } -} diff --git a/Adamant/Stories/Shared/SharedRoutes.swift b/Adamant/Stories/Shared/SharedRoutes.swift deleted file mode 100644 index 25164d9dd..000000000 --- a/Adamant/Stories/Shared/SharedRoutes.swift +++ /dev/null @@ -1,21 +0,0 @@ -// -// SharedRoutes.swift -// Adamant -// -// Created by Anokhov Pavel on 17.03.2018. -// Copyright © 2018 Adamant. All rights reserved. -// - -import Foundation - -extension AdamantScene { - struct Shared { - static let shareQr = AdamantScene(identifier: "ShareQrViewController", factory: { r in - let controller = ShareQrViewController(nibName: "ShareQrViewController", bundle: nil) - controller.dialogService = r.resolve(DialogService.self) - return controller - }) - - private init() {} - } -} diff --git a/Adamant/Utilities/AdamantQRTools.swift b/Adamant/Utilities/AdamantQRTools.swift index ea7aa0e6c..57725b279 100644 --- a/Adamant/Utilities/AdamantQRTools.swift +++ b/Adamant/Utilities/AdamantQRTools.swift @@ -21,7 +21,7 @@ enum QRToolDecodeResult { case failure(error: Error) } -class AdamantQRTools { +final class AdamantQRTools { static func generateQrFrom(string: String, withLogo: Bool = false ) -> QRToolGenerateResult { let generator = EFQRCodeGenerator( content: string, diff --git a/Adamant/Utilities/AdamantUriTools.swift b/Adamant/Utilities/AdamantUriTools.swift index 79c21fcf0..23cd24b8f 100644 --- a/Adamant/Utilities/AdamantUriTools.swift +++ b/Adamant/Utilities/AdamantUriTools.swift @@ -50,7 +50,7 @@ enum AdamantAddressParam { } } -class AdamantUriTools { +final class AdamantUriTools { static let AdamantProtocol = "adm" static func encode(request: AdamantUri) -> String { diff --git a/Adamant/Wallets/Adamant/AdmWalletRoutes.swift b/Adamant/Wallets/Adamant/AdmWalletRoutes.swift deleted file mode 100644 index a30bf523a..000000000 --- a/Adamant/Wallets/Adamant/AdmWalletRoutes.swift +++ /dev/null @@ -1,73 +0,0 @@ -// -// AdmWalletRoutes.swift -// Adamant -// -// Created by Anokhov Pavel on 28.08.2018. -// Copyright © 2018 Adamant. All rights reserved. -// - -import Foundation - -extension AdamantScene.Wallets { - struct Adamant { - /// Wallet preview - static let wallet = AdamantScene(identifier: "AdmWalletViewController") { r in - let c = AdmWalletViewController(nibName: "WalletViewControllerBase", bundle: nil) - c.dialogService = r.resolve(DialogService.self) - c.currencyInfoService = r.resolve(CurrencyInfoService.self) - c.router = r.resolve(Router.self) - c.accountService = r.resolve(AccountService.self) - return c - } - - /// Send money - static let transfer = AdamantScene(identifier: "AdmTransferViewController") { r in - AdmTransferViewController( - chatsProvider: r.resolve(ChatsProvider.self)!, - accountService: r.resolve(AccountService.self)!, - accountsProvider: r.resolve(AccountsProvider.self)!, - dialogService: r.resolve(DialogService.self)!, - router: r.resolve(Router.self)!, - currencyInfoService: r.resolve(CurrencyInfoService.self)!, - increaseFeeService: r.resolve(IncreaseFeeService.self)! - ) - } - - /// Transactions list - static let transactionsList = AdamantScene(identifier: "AdmTransactionsViewController", factory: { r in - AdmTransactionsViewController( - nibName: "TransactionsListViewControllerBase", - bundle: nil, - accountService: r.resolve(AccountService.self)!, - transfersProvider: r.resolve(TransfersProvider.self)!, - chatsProvider: r.resolve(ChatsProvider.self)!, - dialogService: r.resolve(DialogService.self)!, - stack: r.resolve(CoreDataStack.self)!, - router: r.resolve(Router.self)!, - addressBookService: r.resolve(AddressBookService.self)! - ) - }) - - /// Adamant transaction details - static let transactionDetails = AdamantScene(identifier: "TransactionDetailsViewController", factory: { r in - AdmTransactionDetailsViewController( - accountService: r.resolve(AccountService.self)!, - transfersProvider: r.resolve(TransfersProvider.self)!, - router: r.resolve(Router.self)!, - dialogService: r.resolve(DialogService.self)!, - currencyInfo: r.resolve(CurrencyInfoService.self)!, - addressBookService: r.resolve(AddressBookService.self)! - ) - }) - - /// Buy and Sell options - static let buyAndSell = AdamantScene(identifier: "BuyAndSell") { r in - let c = BuyAndSellViewController() - c.accountService = r.resolve(AccountService.self) - c.dialogService = r.resolve(DialogService.self) - return c - } - - private init() {} - } -} diff --git a/Adamant/Wallets/Bitcoin/BtcWalletRoutes.swift b/Adamant/Wallets/Bitcoin/BtcWalletRoutes.swift deleted file mode 100644 index b04c0809e..000000000 --- a/Adamant/Wallets/Bitcoin/BtcWalletRoutes.swift +++ /dev/null @@ -1,54 +0,0 @@ -// -// BtcWalletRoutes.swift -// Adamant -// -// Created by Anton Boyarkin on 14/01/2019. -// Copyright © 2019 Adamant. All rights reserved. -// - -import Foundation - -extension AdamantScene.Wallets { - struct Bitcoin { - /// Wallet preview - static let wallet = AdamantScene(identifier: "BtcWalletViewController") { r in - let c = BtcWalletViewController(nibName: "WalletViewControllerBase", bundle: nil) - c.dialogService = r.resolve(DialogService.self) - c.currencyInfoService = r.resolve(CurrencyInfoService.self) - c.accountService = r.resolve(AccountService.self) - return c - } - - /// Send BTC tokens - static let transfer = AdamantScene(identifier: "BtcTransferViewController") { r in - BtcTransferViewController( - chatsProvider: r.resolve(ChatsProvider.self)!, - accountService: r.resolve(AccountService.self)!, - accountsProvider: r.resolve(AccountsProvider.self)!, - dialogService: r.resolve(DialogService.self)!, - router: r.resolve(Router.self)!, - currencyInfoService: r.resolve(CurrencyInfoService.self)!, - increaseFeeService: r.resolve(IncreaseFeeService.self)! - ) - } - - /// List of BTC transactions - static let transactionsList = AdamantScene(identifier: "BtcTransactionsViewController") { r in - let c = BtcTransactionsViewController(nibName: "TransactionsListViewControllerBase", bundle: nil) - c.dialogService = r.resolve(DialogService.self) - c.router = r.resolve(Router.self) - c.addressBook = r.resolve(AddressBookService.self) - return c - } - - /// BTC transaction details - static let transactionDetails = AdamantScene(identifier: "TransactionDetailsViewControllerBase") { r in - BtcTransactionDetailsViewController( - dialogService: r.resolve(DialogService.self)!, - currencyInfo: r.resolve(CurrencyInfoService.self)!, - addressBookService: r.resolve(AddressBookService.self)!, - accountService: r.resolve(AccountService.self)! - ) - } - } -} diff --git a/Adamant/Wallets/Bitcoin/BtcWalletService+RichMessageProvider.swift b/Adamant/Wallets/Bitcoin/BtcWalletService+RichMessageProvider.swift deleted file mode 100644 index 62c34138f..000000000 --- a/Adamant/Wallets/Bitcoin/BtcWalletService+RichMessageProvider.swift +++ /dev/null @@ -1,142 +0,0 @@ -// -// BtcWalletService+RichMessageProvider.swift -// Adamant -// -// Created by Anton Boyarkin on 20/02/2019. -// Copyright © 2019 Adamant. All rights reserved. -// - -import Foundation -import MessageKit -import UIKit -import CommonKit - -extension BtcWalletService: RichMessageProvider { - var newPendingInterval: TimeInterval { - .init(milliseconds: type(of: self).newPendingInterval) - } - - var oldPendingInterval: TimeInterval { - .init(milliseconds: type(of: self).oldPendingInterval) - } - - var registeredInterval: TimeInterval { - .init(milliseconds: type(of: self).registeredInterval) - } - - var newPendingAttempts: Int { - type(of: self).newPendingAttempts - } - - var oldPendingAttempts: Int { - type(of: self).oldPendingAttempts - } - - var dynamicRichMessageType: String { - return type(of: self).richMessageType - } - - // MARK: Events - - @MainActor - func richMessageTapped(for transaction: RichMessageTransaction, in chat: ChatViewController) { - // MARK: 0. Prepare - guard let hash = transaction.getRichValue(for: RichContentKeys.transfer.hash) - else { - return - } - - let comment: String? - if let raw = transaction.getRichValue(for: RichContentKeys.transfer.comments), raw.count > 0 { - comment = raw - } else { - comment = nil - } - - // MARK: Go to transaction - - presentDetailTransactionVC( - hash: hash, - senderId: transaction.senderId, - recipientId: transaction.recipientId, - senderAddress: "", - recipientAddress: "", - comment: comment, - transaction: nil, - richTransaction: transaction, - in: chat - ) - } - - private func presentDetailTransactionVC( - hash: String, - senderId: String?, - recipientId: String?, - senderAddress: String, - recipientAddress: String, - comment: String?, - transaction: BtcTransaction?, - richTransaction: RichMessageTransaction, - in chat: ChatViewController - ) { - guard let vc = router.get(scene: AdamantScene.Wallets.Bitcoin.transactionDetails) as? BtcTransactionDetailsViewController else { - return - } - - let amount: Decimal - if let amountRaw = richTransaction.getRichValue(for: RichContentKeys.transfer.amount), - let decimal = Decimal(string: amountRaw) { - amount = decimal - } else { - amount = 0 - } - - let failedTransaction = SimpleTransactionDetails( - txId: hash, - senderAddress: senderAddress, - recipientAddress: recipientAddress, - dateValue: nil, - amountValue: amount, - feeValue: nil, - confirmationsValue: nil, - blockValue: nil, - isOutgoing: richTransaction.isOutgoing, - transactionStatus: nil - ) - - vc.service = self - vc.senderId = senderId - vc.recipientId = recipientId - vc.comment = comment - vc.transaction = transaction ?? failedTransaction - vc.richTransaction = richTransaction - - chat.navigationController?.pushViewController(vc, animated: true) - } - - // MARK: Short description - - func shortDescription(for transaction: RichMessageTransaction) -> NSAttributedString { - let amount: String - - guard let raw = transaction.getRichValue(for: RichContentKeys.transfer.amount) - else { - return NSAttributedString(string: "⬅️ \(BtcWalletService.currencySymbol)") - } - - if let decimal = Decimal(string: raw) { - amount = AdamantBalanceFormat.full.format(decimal) - } else { - amount = raw - } - - let string: String - if transaction.isOutgoing { - string = "⬅️ \(amount) \(BtcWalletService.currencySymbol)" - } else { - string = "➡️ \(amount) \(BtcWalletService.currencySymbol)" - } - - return NSAttributedString(string: string) - } -} diff --git a/Adamant/Wallets/Dash/DashWalletRouter.swift b/Adamant/Wallets/Dash/DashWalletRouter.swift deleted file mode 100644 index a4e11270a..000000000 --- a/Adamant/Wallets/Dash/DashWalletRouter.swift +++ /dev/null @@ -1,53 +0,0 @@ -// -// DashWalletRouter.swift -// Adamant -// -// Created by Anton Boyarkin on 25/04/2019. -// Copyright © 2019 Adamant. All rights reserved. -// - -import Foundation - -extension AdamantScene.Wallets { - struct Dash { - /// Wallet preview - static let wallet = AdamantScene(identifier: "DashWalletViewController") { r in - let c = DashWalletViewController(nibName: "WalletViewControllerBase", bundle: nil) - c.dialogService = r.resolve(DialogService.self) - c.currencyInfoService = r.resolve(CurrencyInfoService.self) - c.accountService = r.resolve(AccountService.self) - return c - } - - /// Send tokens - static let transfer = AdamantScene(identifier: "DashTransferViewController") { r in - DashTransferViewController( - chatsProvider: r.resolve(ChatsProvider.self)!, - accountService: r.resolve(AccountService.self)!, - accountsProvider: r.resolve(AccountsProvider.self)!, - dialogService: r.resolve(DialogService.self)!, - router: r.resolve(Router.self)!, - currencyInfoService: r.resolve(CurrencyInfoService.self)!, - increaseFeeService: r.resolve(IncreaseFeeService.self)! - ) - } - - /// List of transactions - static let transactionsList = AdamantScene(identifier: "DashTransactionsViewController") { r in - let c = DashTransactionsViewController(nibName: "TransactionsListViewControllerBase", bundle: nil) - c.dialogService = r.resolve(DialogService.self) - c.router = r.resolve(Router.self) - return c - } - - /// Transaction details - static let transactionDetails = AdamantScene(identifier: "TransactionDetailsViewControllerBase") { r in - DashTransactionDetailsViewController( - dialogService: r.resolve(DialogService.self)!, - currencyInfo: r.resolve(CurrencyInfoService.self)!, - addressBookService: r.resolve(AddressBookService.self)!, - accountService: r.resolve(AccountService.self)! - ) - } - } -} diff --git a/Adamant/Wallets/Dash/DashWalletService+RichMessageProvider.swift b/Adamant/Wallets/Dash/DashWalletService+RichMessageProvider.swift deleted file mode 100644 index a9a38a16f..000000000 --- a/Adamant/Wallets/Dash/DashWalletService+RichMessageProvider.swift +++ /dev/null @@ -1,151 +0,0 @@ -// -// DashWalletService+RichMessageProvider.swift -// Adamant -// -// Created by Anton Boyarkin on 26/05/2019. -// Copyright © 2019 Adamant. All rights reserved. -// - -import Foundation -import MessageKit -import UIKit -import CommonKit - -extension DashWalletService: RichMessageProvider { - var newPendingInterval: TimeInterval { - .init(milliseconds: type(of: self).newPendingInterval) - } - - var oldPendingInterval: TimeInterval { - .init(milliseconds: type(of: self).oldPendingInterval) - } - - var registeredInterval: TimeInterval { - .init(milliseconds: type(of: self).registeredInterval) - } - - var newPendingAttempts: Int { - type(of: self).newPendingAttempts - } - - var oldPendingAttempts: Int { - type(of: self).oldPendingAttempts - } - - var dynamicRichMessageType: String { - return type(of: self).richMessageType - } - - // MARK: Events - - @MainActor - func richMessageTapped(for transaction: RichMessageTransaction, in chat: ChatViewController) { - // MARK: 0. Prepare - guard let hash = transaction.getRichValue(for: RichContentKeys.transfer.hash), - let address = accountService.account?.address - else { - return - } - - let comment: String? - if let raw = transaction.getRichValue(for: RichContentKeys.transfer.comments), raw.count > 0 { - comment = raw - } else { - comment = nil - } - - // MARK: Go to transaction - - presentDetailTransactionVC( - hash: hash, - senderId: transaction.senderId, - recipientId: transaction.recipientId, - senderAddress: "", - recipientAddress: "", - comment: comment, - address: address, - blockId: nil, - transaction: nil, - richTransaction: transaction, - in: chat - ) - } - - private func presentDetailTransactionVC( - hash: String, - senderId: String?, - recipientId: String?, - senderAddress: String, - recipientAddress: String, - comment: String?, - address: String, - blockId: String?, - transaction: BTCRawTransaction?, - richTransaction: RichMessageTransaction, - in chat: ChatViewController - ) { - guard let vc = router.get(scene: AdamantScene.Wallets.Dash.transactionDetails) as? DashTransactionDetailsViewController else { - return - } - - let amount: Decimal - if let amountRaw = richTransaction.getRichValue(for: RichContentKeys.transfer.amount), - let decimal = Decimal(string: amountRaw) { - amount = decimal - } else { - amount = 0 - } - - var dashTransaction = transaction?.asBtcTransaction(DashTransaction.self, for: address) - if let blockId = blockId { - dashTransaction = transaction?.asBtcTransaction(DashTransaction.self, for: address, blockId: blockId) - } - let failedTransaction = SimpleTransactionDetails( - txId: hash, - senderAddress: senderAddress, - recipientAddress: recipientAddress, - dateValue: nil, - amountValue: amount, - feeValue: nil, - confirmationsValue: nil, - blockValue: nil, - isOutgoing: richTransaction.isOutgoing, - transactionStatus: nil - ) - - vc.service = self - vc.senderId = senderId - vc.recipientId = recipientId - vc.comment = comment - vc.transaction = dashTransaction ?? failedTransaction - vc.richTransaction = richTransaction - - chat.navigationController?.pushViewController(vc, animated: true) - } - - // MARK: Short description - - func shortDescription(for transaction: RichMessageTransaction) -> NSAttributedString { - let amount: String - - guard let raw = transaction.getRichValue(for: RichContentKeys.transfer.amount) - else { - return NSAttributedString(string: "⬅️ \(DashWalletService.currencySymbol)") - } - - if let decimal = Decimal(string: raw) { - amount = AdamantBalanceFormat.full.format(decimal) - } else { - amount = raw - } - - let string: String - if transaction.isOutgoing { - string = "⬅️ \(amount) \(DashWalletService.currencySymbol)" - } else { - string = "➡️ \(amount) \(DashWalletService.currencySymbol)" - } - - return NSAttributedString(string: string) - } -} diff --git a/Adamant/Wallets/Doge/DogeWalletRoutes.swift b/Adamant/Wallets/Doge/DogeWalletRoutes.swift deleted file mode 100644 index b9d2c77bb..000000000 --- a/Adamant/Wallets/Doge/DogeWalletRoutes.swift +++ /dev/null @@ -1,53 +0,0 @@ -// -// DogeWalletRoutes.swift -// Adamant -// -// Created by Anton Boyarkin on 05/03/2019. -// Copyright © 2019 Adamant. All rights reserved. -// - -import Foundation - -extension AdamantScene.Wallets { - struct Doge { - /// Wallet preview - static let wallet = AdamantScene(identifier: "DogeWalletViewController") { r in - let c = DogeWalletViewController(nibName: "WalletViewControllerBase", bundle: nil) - c.dialogService = r.resolve(DialogService.self) - c.currencyInfoService = r.resolve(CurrencyInfoService.self) - c.accountService = r.resolve(AccountService.self) - return c - } - - /// Send tokens - static let transfer = AdamantScene(identifier: "DogeTransferViewController") { r in - DogeTransferViewController( - chatsProvider: r.resolve(ChatsProvider.self)!, - accountService: r.resolve(AccountService.self)!, - accountsProvider: r.resolve(AccountsProvider.self)!, - dialogService: r.resolve(DialogService.self)!, - router: r.resolve(Router.self)!, - currencyInfoService: r.resolve(CurrencyInfoService.self)!, - increaseFeeService: r.resolve(IncreaseFeeService.self)! - ) - } - - /// List of transactions - static let transactionsList = AdamantScene(identifier: "DogeTransactionsViewController") { r in - let c = DogeTransactionsViewController(nibName: "TransactionsListViewControllerBase", bundle: nil) - c.dialogService = r.resolve(DialogService.self) - c.router = r.resolve(Router.self) - return c - } - - /// Transaction details - static let transactionDetails = AdamantScene(identifier: "TransactionDetailsViewControllerBase") { r in - DogeTransactionDetailsViewController( - dialogService: r.resolve(DialogService.self)!, - currencyInfo: r.resolve(CurrencyInfoService.self)!, - addressBookService: r.resolve(AddressBookService.self)!, - accountService: r.resolve(AccountService.self)! - ) - } - } -} diff --git a/Adamant/Wallets/Doge/DogeWalletService+RichMessageProvider.swift b/Adamant/Wallets/Doge/DogeWalletService+RichMessageProvider.swift deleted file mode 100644 index 7cce76917..000000000 --- a/Adamant/Wallets/Doge/DogeWalletService+RichMessageProvider.swift +++ /dev/null @@ -1,144 +0,0 @@ -// -// DogeWalletService+RichMessageProvider.swift -// Adamant -// -// Created by Anton Boyarkin on 13/03/2019. -// Copyright © 2019 Adamant. All rights reserved. -// - -import Foundation -import MessageKit -import UIKit -import CommonKit - -extension DogeWalletService: RichMessageProvider { - var newPendingInterval: TimeInterval { - .init(milliseconds: type(of: self).newPendingInterval) - } - - var oldPendingInterval: TimeInterval { - .init(milliseconds: type(of: self).oldPendingInterval) - } - - var registeredInterval: TimeInterval { - .init(milliseconds: type(of: self).registeredInterval) - } - - var newPendingAttempts: Int { - type(of: self).newPendingAttempts - } - - var oldPendingAttempts: Int { - type(of: self).oldPendingAttempts - } - - var dynamicRichMessageType: String { - return type(of: self).richMessageType - } - - // MARK: Events - - @MainActor - func richMessageTapped(for transaction: RichMessageTransaction, in chat: ChatViewController) { - // MARK: 0. Prepare - guard let hash = transaction.getRichValue(for: RichContentKeys.transfer.hash) - else { - return - } - - let comment: String? - if let raw = transaction.getRichValue(for: RichContentKeys.transfer.comments), raw.count > 0 { - comment = raw - } else { - comment = nil - } - - // MARK: 2. Go to transaction - - presentDetailTransactionVC( - hash: hash, - senderId: transaction.senderId, - recipientId: transaction.recipientId, - comment: comment, - senderAddress: "", - recipientAddress: "", - transaction: nil, - richTransaction: transaction, - in: chat - ) - } - - @MainActor - private func presentDetailTransactionVC( - hash: String, - senderId: String?, - recipientId: String?, - comment: String?, - senderAddress: String, - recipientAddress: String, - transaction: DogeTransaction?, - richTransaction: RichMessageTransaction, - in chat: ChatViewController - ) { - guard let vc = router.get(scene: AdamantScene.Wallets.Doge.transactionDetails) as? DogeTransactionDetailsViewController else { - dialogService.dismissProgress() - return - } - - vc.service = self - vc.senderId = senderId - vc.recipientId = recipientId - vc.comment = comment - - let amount: Decimal - if let amountRaw = richTransaction.getRichValue(for: RichContentKeys.transfer.amount), - let decimal = Decimal(string: amountRaw) { - amount = decimal - } else { - amount = 0 - } - - let failedTransaction = SimpleTransactionDetails( - txId: hash, - senderAddress: senderAddress, - recipientAddress: recipientAddress, - dateValue: nil, - amountValue: amount, - feeValue: nil, - confirmationsValue: nil, - blockValue: nil, - isOutgoing: richTransaction.isOutgoing, - transactionStatus: nil) - - vc.transaction = transaction ?? failedTransaction - vc.richTransaction = richTransaction - - chat.navigationController?.pushViewController(vc, animated: true) - } - - // MARK: Short description - - func shortDescription(for transaction: RichMessageTransaction) -> NSAttributedString { - let amount: String - - guard let raw = transaction.getRichValue(for: RichContentKeys.transfer.amount) - else { - return NSAttributedString(string: "⬅️ \(DogeWalletService.currencySymbol)") - } - - if let decimal = Decimal(string: raw) { - amount = AdamantBalanceFormat.full.format(decimal) - } else { - amount = raw - } - - let string: String - if transaction.isOutgoing { - string = "⬅️ \(amount) \(DogeWalletService.currencySymbol)" - } else { - string = "➡️ \(amount) \(DogeWalletService.currencySymbol)" - } - - return NSAttributedString(string: string) - } -} diff --git a/Adamant/Wallets/ERC20/ERC20WalletRouter.swift b/Adamant/Wallets/ERC20/ERC20WalletRouter.swift deleted file mode 100644 index dc223279c..000000000 --- a/Adamant/Wallets/ERC20/ERC20WalletRouter.swift +++ /dev/null @@ -1,53 +0,0 @@ -// -// ERC20WalletRouter.swift -// Adamant -// -// Created by Anton Boyarkin on 26/06/2019. -// Copyright © 2019 Adamant. All rights reserved. -// - -import Foundation - -extension AdamantScene.Wallets { - struct ERC20 { - /// Wallet preview - static let wallet = AdamantScene(identifier: "ERC20WalletViewController") { r in - let c = ERC20WalletViewController(nibName: "WalletViewControllerBase", bundle: nil) - c.dialogService = r.resolve(DialogService.self) - c.currencyInfoService = r.resolve(CurrencyInfoService.self) - c.accountService = r.resolve(AccountService.self) - return c - } - - /// Send money - static let transfer = AdamantScene(identifier: "ERC20TransferViewController") { r in - ERC20TransferViewController( - chatsProvider: r.resolve(ChatsProvider.self)!, - accountService: r.resolve(AccountService.self)!, - accountsProvider: r.resolve(AccountsProvider.self)!, - dialogService: r.resolve(DialogService.self)!, - router: r.resolve(Router.self)!, - currencyInfoService: r.resolve(CurrencyInfoService.self)!, - increaseFeeService: r.resolve(IncreaseFeeService.self)! - ) - } - - /// List of Ethereum transactions - static let transactionsList = AdamantScene(identifier: "ERC20TransactionsViewController") { r in - let c = ERC20TransactionsViewController(nibName: "TransactionsListViewControllerBase", bundle: nil) - c.dialogService = r.resolve(DialogService.self) - c.router = r.resolve(Router.self) - return c - } - - /// Ethereum transaction details - static let transactionDetails = AdamantScene(identifier: "TransactionDetailsViewControllerBase") { r in - ERC20TransactionDetailsViewController( - dialogService: r.resolve(DialogService.self)!, - currencyInfo: r.resolve(CurrencyInfoService.self)!, - addressBookService: r.resolve(AddressBookService.self)!, - accountService: r.resolve(AccountService.self)! - ) - } - } -} diff --git a/Adamant/Wallets/ERC20/ERC20WalletService+RichMessageProvider.swift b/Adamant/Wallets/ERC20/ERC20WalletService+RichMessageProvider.swift deleted file mode 100644 index 543da44ab..000000000 --- a/Adamant/Wallets/ERC20/ERC20WalletService+RichMessageProvider.swift +++ /dev/null @@ -1,138 +0,0 @@ -// -// ERC20WalletService+RichMessageProvider.swift -// Adamant -// -// Created by Anton Boyarkin on 06/07/2019. -// Copyright © 2019 Adamant. All rights reserved. -// - -import Foundation -import MessageKit -import UIKit -import CommonKit - -extension ERC20WalletService: RichMessageProvider { - var newPendingInterval: TimeInterval { - .init(milliseconds: EthWalletService.newPendingInterval) - } - - var oldPendingInterval: TimeInterval { - .init(milliseconds: EthWalletService.oldPendingInterval) - } - - var registeredInterval: TimeInterval { - .init(milliseconds: EthWalletService.registeredInterval) - } - - var newPendingAttempts: Int { - EthWalletService.newPendingAttempts - } - - var oldPendingAttempts: Int { - EthWalletService.oldPendingAttempts - } - - // MARK: Events - - @MainActor - func richMessageTapped(for transaction: RichMessageTransaction, in chat: ChatViewController) { - // MARK: 0. Prepare - guard let hash = transaction.getRichValue(for: RichContentKeys.transfer.hash) - else { - return - } - - let comment: String? - if let raw = transaction.getRichValue(for: RichContentKeys.transfer.comments), raw.count > 0 { - comment = raw - } else { - comment = nil - } - - // MARK: Go to transaction - - presentDetailTransactionVC( - hash: hash, - senderId: transaction.senderId, - recipientId: transaction.recipientId, - senderAddress: "", - recipientAddress: "", - comment: comment, - transaction: nil, - richTransaction: transaction, - in: chat - ) - } - - private func presentDetailTransactionVC( - hash: String, - senderId: String?, - recipientId: String?, - senderAddress: String, - recipientAddress: String, - comment: String?, - transaction: EthTransaction?, - richTransaction: RichMessageTransaction, - in chat: ChatViewController - ) { - guard let vc = router.get(scene: AdamantScene.Wallets.ERC20.transactionDetails) as? ERC20TransactionDetailsViewController else { - return - } - - let amount: Decimal - if let amountRaw = richTransaction.getRichValue(for: RichContentKeys.transfer.amount), - let decimal = Decimal(string: amountRaw) { - amount = decimal - } else { - amount = 0 - } - - let failedTransaction = SimpleTransactionDetails( - txId: hash, - senderAddress: senderAddress, - recipientAddress: recipientAddress, - dateValue: nil, - amountValue: amount, - feeValue: nil, - confirmationsValue: nil, - blockValue: nil, - isOutgoing: richTransaction.isOutgoing, - transactionStatus: nil - ) - - vc.service = self - vc.senderId = senderId - vc.recipientId = recipientId - vc.comment = comment - vc.transaction = transaction ?? failedTransaction - vc.richTransaction = richTransaction - - chat.navigationController?.pushViewController(vc, animated: true) - } - - // MARK: Short description - - func shortDescription(for transaction: RichMessageTransaction) -> NSAttributedString { - let amount: String - - guard let raw = transaction.getRichValue(for: RichContentKeys.transfer.amount) - else { - return NSAttributedString(string: "⬅️ \(self.tokenSymbol)") - } - - if let decimal = Decimal(string: raw) { - amount = AdamantBalanceFormat.full.format(decimal) - } else { - amount = raw - } - - let string: String - if transaction.isOutgoing { - string = "⬅️ \(amount) \(self.tokenSymbol)" - } else { - string = "➡️ \(amount) \(self.tokenSymbol)" - } - - return NSAttributedString(string: string) - } -} diff --git a/Adamant/Wallets/Ethereum/EthWalletRoutes.swift b/Adamant/Wallets/Ethereum/EthWalletRoutes.swift deleted file mode 100644 index 573322002..000000000 --- a/Adamant/Wallets/Ethereum/EthWalletRoutes.swift +++ /dev/null @@ -1,53 +0,0 @@ -// -// EthWalletRoutes.swift -// Adamant -// -// Created by Anokhov Pavel on 28.08.2018. -// Copyright © 2018 Adamant. All rights reserved. -// - -import Foundation - -extension AdamantScene.Wallets { - struct Ethereum { - /// Wallet preview - static let wallet = AdamantScene(identifier: "EthWalletViewController") { r in - let c = EthWalletViewController(nibName: "WalletViewControllerBase", bundle: nil) - c.dialogService = r.resolve(DialogService.self) - c.currencyInfoService = r.resolve(CurrencyInfoService.self) - c.accountService = r.resolve(AccountService.self) - return c - } - - /// Send money - static let transfer = AdamantScene(identifier: "EthTransferViewController") { r in - EthTransferViewController( - chatsProvider: r.resolve(ChatsProvider.self)!, - accountService: r.resolve(AccountService.self)!, - accountsProvider: r.resolve(AccountsProvider.self)!, - dialogService: r.resolve(DialogService.self)!, - router: r.resolve(Router.self)!, - currencyInfoService: r.resolve(CurrencyInfoService.self)!, - increaseFeeService: r.resolve(IncreaseFeeService.self)! - ) - } - - /// List of Ethereum transactions - static let transactionsList = AdamantScene(identifier: "EthTransactionsViewController") { r in - let c = EthTransactionsViewController(nibName: "TransactionsListViewControllerBase", bundle: nil) - c.dialogService = r.resolve(DialogService.self) - c.router = r.resolve(Router.self) - return c - } - - /// Ethereum transaction details - static let transactionDetails = AdamantScene(identifier: "TransactionDetailsViewControllerBase") { r in - EthTransactionDetailsViewController( - dialogService: r.resolve(DialogService.self)!, - currencyInfo: r.resolve(CurrencyInfoService.self)!, - addressBookService: r.resolve(AddressBookService.self)!, - accountService: r.resolve(AccountService.self)! - ) - } - } -} diff --git a/Adamant/Wallets/Ethereum/EthWalletService+RichMessageProvider.swift b/Adamant/Wallets/Ethereum/EthWalletService+RichMessageProvider.swift deleted file mode 100644 index c4106688f..000000000 --- a/Adamant/Wallets/Ethereum/EthWalletService+RichMessageProvider.swift +++ /dev/null @@ -1,142 +0,0 @@ -// -// EthWalletService+RichMessageProvider.swift -// Adamant -// -// Created by Anokhov Pavel on 08.09.2018. -// Copyright © 2018 Adamant. All rights reserved. -// - -import Foundation -import MessageKit -import UIKit -import CommonKit - -extension EthWalletService: RichMessageProvider { - var newPendingInterval: TimeInterval { - .init(milliseconds: type(of: self).newPendingInterval) - } - - var oldPendingInterval: TimeInterval { - .init(milliseconds: type(of: self).oldPendingInterval) - } - - var registeredInterval: TimeInterval { - .init(milliseconds: type(of: self).registeredInterval) - } - - var newPendingAttempts: Int { - type(of: self).newPendingAttempts - } - - var oldPendingAttempts: Int { - type(of: self).oldPendingAttempts - } - - var dynamicRichMessageType: String { - return type(of: self).richMessageType - } - - // MARK: Events - - @MainActor - func richMessageTapped(for transaction: RichMessageTransaction, in chat: ChatViewController) { - // MARK: 0. Prepare - guard let hash = transaction.getRichValue(for: RichContentKeys.transfer.hash) - else { - return - } - - let comment: String? - if let raw = transaction.getRichValue(for: RichContentKeys.transfer.comments), raw.count > 0 { - comment = raw - } else { - comment = nil - } - - // MARK: Go to transaction - - presentDetailTransactionVC( - hash: hash, - senderId: transaction.senderId, - recipientId: transaction.recipientId, - senderAddress: "", - recipientAddress: "", - comment: comment, - transaction: nil, - richTransaction: transaction, - in: chat - ) - } - - private func presentDetailTransactionVC( - hash: String, - senderId: String?, - recipientId: String?, - senderAddress: String, - recipientAddress: String, - comment: String?, - transaction: EthTransaction?, - richTransaction: RichMessageTransaction, - in chat: ChatViewController - ) { - guard let vc = router.get(scene: AdamantScene.Wallets.Ethereum.transactionDetails) as? EthTransactionDetailsViewController else { - return - } - - let amount: Decimal - if let amountRaw = richTransaction.getRichValue(for: RichContentKeys.transfer.amount), - let decimal = Decimal(string: amountRaw) { - amount = decimal - } else { - amount = 0 - } - - let failedTransaction = SimpleTransactionDetails( - txId: hash, - senderAddress: senderAddress, - recipientAddress: recipientAddress, - dateValue: nil, - amountValue: amount, - feeValue: nil, - confirmationsValue: nil, - blockValue: nil, - isOutgoing: richTransaction.isOutgoing, - transactionStatus: nil - ) - - vc.service = self - vc.senderId = senderId - vc.recipientId = recipientId - vc.comment = comment - vc.transaction = transaction ?? failedTransaction - vc.richTransaction = richTransaction - - chat.navigationController?.pushViewController(vc, animated: true) - } - - // MARK: Short description - - func shortDescription(for transaction: RichMessageTransaction) -> NSAttributedString { - let amount: String - - guard let raw = transaction.getRichValue(for: RichContentKeys.transfer.amount) - else { - return NSAttributedString(string: "⬅️ \(EthWalletService.currencySymbol)") - } - - if let decimal = Decimal(string: raw) { - amount = AdamantBalanceFormat.full.format(decimal) - } else { - amount = raw - } - - let string: String - if transaction.isOutgoing { - string = "⬅️ \(amount) \(EthWalletService.currencySymbol)" - } else { - string = "➡️ \(amount) \(EthWalletService.currencySymbol)" - } - - return NSAttributedString(string: string) - } -} diff --git a/Adamant/Wallets/Ethereum/EthWalletService+Transfers.swift b/Adamant/Wallets/Ethereum/EthWalletService+Transfers.swift deleted file mode 100644 index 43fefb6e7..000000000 --- a/Adamant/Wallets/Ethereum/EthWalletService+Transfers.swift +++ /dev/null @@ -1,20 +0,0 @@ -// -// EthWalletService+Transfers.swift -// Adamant -// -// Created by Anokhov Pavel on 21.08.2018. -// Copyright © 2018 Adamant. All rights reserved. -// - -import UIKit - -extension EthWalletService: WalletServiceWithTransfers { - func transferListViewController() -> UIViewController { - guard let vc = router.get(scene: AdamantScene.Wallets.Ethereum.transactionsList) as? EthTransactionsViewController else { - fatalError("Can't get EthTransactionsViewController") - } - - vc.ethWalletService = self - return vc - } -} diff --git a/Adamant/Wallets/Lisk/LskWalletRoutes.swift b/Adamant/Wallets/Lisk/LskWalletRoutes.swift deleted file mode 100644 index 200ed1259..000000000 --- a/Adamant/Wallets/Lisk/LskWalletRoutes.swift +++ /dev/null @@ -1,53 +0,0 @@ -// -// LskWalletRoutes.swift -// Adamant -// -// Created by Anton Boyarkin on 27/11/2018. -// Copyright © 2018 Adamant. All rights reserved. -// - -import Foundation - -extension AdamantScene.Wallets { - struct Lisk { - /// Wallet preview - static let wallet = AdamantScene(identifier: "LskWalletViewController") { r in - let c = LskWalletViewController(nibName: "WalletViewControllerBase", bundle: nil) - c.dialogService = r.resolve(DialogService.self) - c.currencyInfoService = r.resolve(CurrencyInfoService.self) - c.accountService = r.resolve(AccountService.self) - return c - } - - /// Send LSK tokens - static let transfer = AdamantScene(identifier: "LskTransferViewController") { r in - LskTransferViewController( - chatsProvider: r.resolve(ChatsProvider.self)!, - accountService: r.resolve(AccountService.self)!, - accountsProvider: r.resolve(AccountsProvider.self)!, - dialogService: r.resolve(DialogService.self)!, - router: r.resolve(Router.self)!, - currencyInfoService: r.resolve(CurrencyInfoService.self)!, - increaseFeeService: r.resolve(IncreaseFeeService.self)! - ) - } - - /// List of Lisk transactions - static let transactionsList = AdamantScene(identifier: "LskTransactionsViewController") { r in - let c = LskTransactionsViewController(nibName: "TransactionsListViewControllerBase", bundle: nil) - c.dialogService = r.resolve(DialogService.self) - c.router = r.resolve(Router.self) - return c - } - - /// Lisk transaction details - static let transactionDetails = AdamantScene(identifier: "TransactionDetailsViewControllerBase") { r in - LskTransactionDetailsViewController( - dialogService: r.resolve(DialogService.self)!, - currencyInfo: r.resolve(CurrencyInfoService.self)!, - addressBookService: r.resolve(AddressBookService.self)!, - accountService: r.resolve(AccountService.self)! - ) - } - } -} diff --git a/Adamant/Wallets/Lisk/LskWalletService+RichMessageProvider.swift b/Adamant/Wallets/Lisk/LskWalletService+RichMessageProvider.swift deleted file mode 100644 index 7b8c0190b..000000000 --- a/Adamant/Wallets/Lisk/LskWalletService+RichMessageProvider.swift +++ /dev/null @@ -1,145 +0,0 @@ -// -// LskWalletService+RichMessageProvider.swift -// Adamant -// -// Created by Anton Boyarkin on 06/12/2018. -// Copyright © 2018 Adamant. All rights reserved. -// - -import Foundation -import MessageKit -import UIKit -import LiskKit -import CommonKit - -extension LskWalletService: RichMessageProvider { - var newPendingInterval: TimeInterval { - .init(milliseconds: type(of: self).newPendingInterval) - } - - var oldPendingInterval: TimeInterval { - .init(milliseconds: type(of: self).oldPendingInterval) - } - - var registeredInterval: TimeInterval { - .init(milliseconds: type(of: self).registeredInterval) - } - - var newPendingAttempts: Int { - type(of: self).newPendingAttempts - } - - var oldPendingAttempts: Int { - type(of: self).oldPendingAttempts - } - - var dynamicRichMessageType: String { - return type(of: self).richMessageType - } - - // MARK: Events - - @MainActor - func richMessageTapped(for transaction: RichMessageTransaction, in chat: ChatViewController) { - // MARK: 0. Prepare - guard let hash = transaction.getRichValue(for: RichContentKeys.transfer.hash) - else { - return - } - - let comment: String? - if let raw = transaction.getRichValue(for: RichContentKeys.transfer.comments), raw.count > 0 { - comment = raw - } else { - comment = nil - } - - // MARK: Go to transaction - - presentDetailTransactionVC( - hash: hash, - senderId: transaction.senderId, - recipientId: transaction.recipientId, - comment: comment, - senderAddress: "", - recipientAddress: "", - transaction: nil, - richTransaction: transaction, - in: chat - ) - } - - @MainActor - private func presentDetailTransactionVC( - hash: String, - senderId: String?, - recipientId: String?, - comment: String?, - senderAddress: String, - recipientAddress: String, - transaction: Transactions.TransactionModel?, - richTransaction: RichMessageTransaction, - in chat: ChatViewController - ) { - guard let vc = router.get(scene: AdamantScene.Wallets.Lisk.transactionDetails) as? LskTransactionDetailsViewController else { - dialogService.dismissProgress() - return - } - - vc.service = self - vc.senderId = senderId - vc.recipientId = recipientId - vc.comment = comment - - let amount: Decimal - if let amountRaw = richTransaction.getRichValue(for: RichContentKeys.transfer.amount), - let decimal = Decimal(string: amountRaw) { - amount = decimal - } else { - amount = 0 - } - - let failedTransaction = SimpleTransactionDetails( - txId: hash, - senderAddress: senderAddress, - recipientAddress: recipientAddress, - dateValue: nil, - amountValue: amount, - feeValue: nil, - confirmationsValue: nil, - blockValue: nil, - isOutgoing: richTransaction.isOutgoing, - transactionStatus: nil) - - vc.transaction = transaction ?? failedTransaction - vc.richTransaction = richTransaction - - chat.navigationController?.pushViewController(vc, animated: true) - } - - // MARK: Short description - - func shortDescription(for transaction: RichMessageTransaction) -> NSAttributedString { - let amount: String - - guard let raw = transaction.getRichValue(for: RichContentKeys.transfer.amount) - else { - return NSAttributedString(string: "⬅️ \(LskWalletService.currencySymbol)") - } - - if let decimal = Decimal(string: raw) { - amount = AdamantBalanceFormat.full.format(decimal) - } else { - amount = raw - } - - let string: String - if transaction.isOutgoing { - string = "⬅️ \(amount) \(LskWalletService.currencySymbol)" - } else { - string = "➡️ \(amount) \(LskWalletService.currencySymbol)" - } - - return NSAttributedString(string: string) - } -} diff --git a/Adamant/Wallets/Lisk/LskWalletService+Transfers.swift b/Adamant/Wallets/Lisk/LskWalletService+Transfers.swift deleted file mode 100644 index bac4e86b8..000000000 --- a/Adamant/Wallets/Lisk/LskWalletService+Transfers.swift +++ /dev/null @@ -1,20 +0,0 @@ -// -// LskWalletService+Transfers.swift -// Adamant -// -// Created by Anton Boyarkin on 28/11/2018. -// Copyright © 2018 Adamant. All rights reserved. -// - -import UIKit - -extension LskWalletService: WalletServiceWithTransfers { - func transferListViewController() -> UIViewController { - guard let vc = router.get(scene: AdamantScene.Wallets.Lisk.transactionsList) as? LskTransactionsViewController else { - fatalError("Can't get LskTransactionsViewController") - } - - vc.lskWalletService = self - return vc - } -} diff --git a/Adamant/Wallets/WalletsRoutes.swift b/Adamant/Wallets/WalletsRoutes.swift deleted file mode 100644 index d8a98a97d..000000000 --- a/Adamant/Wallets/WalletsRoutes.swift +++ /dev/null @@ -1,16 +0,0 @@ -// -// WalletsRoutes.swift -// Adamant -// -// Created by Anokhov Pavel on 14.08.2018. -// Copyright © 2018 Adamant. All rights reserved. -// - -import Foundation - -extension AdamantScene { - struct Wallets { - - private init() { } - } -} From 6d7b0feacaeed2a07b057e4efcc39da157b6b1fd Mon Sep 17 00:00:00 2001 From: StanislavDevIOS Date: Sun, 17 Sep 2023 10:17:05 +0200 Subject: [PATCH 05/96] [trello.com/c/VjQFNWRO] fix: show popup if message too big --- Adamant/Stories/Chat/ChatLocalization.swift | 1 + .../Chat/View/Managers/ChatInputBarManager.swift | 1 + Adamant/Stories/Chat/ViewModel/ChatViewModel.swift | 10 ++++++++++ .../Assets/Localization/de.lproj/Localizable.strings | 3 +++ .../Assets/Localization/en.lproj/Localizable.strings | 3 +++ .../Assets/Localization/ru.lproj/Localizable.strings | 3 +++ 6 files changed, 21 insertions(+) diff --git a/Adamant/Stories/Chat/ChatLocalization.swift b/Adamant/Stories/Chat/ChatLocalization.swift index f172487a9..441fad9c3 100644 --- a/Adamant/Stories/Chat/ChatLocalization.swift +++ b/Adamant/Stories/Chat/ChatLocalization.swift @@ -41,5 +41,6 @@ extension String.adamant { static let transactionSent = String.localized("ChatScene.Sent", comment: "Chat: 'Sent funds' bubble title") static let transactionReceived = String.localized("ChatScene.Received", comment: "Chat: 'Received funds' bubble title") static let messageWasDeleted = String.localized("ChatScene.Error.messageWasDeleted", comment: "Chat: Error scrolling to message, this message has been deleted and is no longer accessible") + static let messageIsTooBig = String.localized("ChatScene.Error.messageIsTooBig", comment: "Chat: Error message is too big") } } diff --git a/Adamant/Stories/Chat/View/Managers/ChatInputBarManager.swift b/Adamant/Stories/Chat/View/Managers/ChatInputBarManager.swift index 994e5c076..8e960067f 100644 --- a/Adamant/Stories/Chat/View/Managers/ChatInputBarManager.swift +++ b/Adamant/Stories/Chat/View/Managers/ChatInputBarManager.swift @@ -18,6 +18,7 @@ final class ChatInputBarManager: InputBarAccessoryViewDelegate { } func inputBar(_ inputBar: InputBarAccessoryView, didPressSendButtonWith text: String) { + guard viewModel.canSendMessage(withText: text) else { return } inputBar.inputTextView.text = "" viewModel.sendMessage(text: text) } diff --git a/Adamant/Stories/Chat/ViewModel/ChatViewModel.swift b/Adamant/Stories/Chat/ViewModel/ChatViewModel.swift index c764ae4c3..a7687719d 100644 --- a/Adamant/Stories/Chat/ViewModel/ChatViewModel.swift +++ b/Adamant/Stories/Chat/ViewModel/ChatViewModel.swift @@ -60,6 +60,7 @@ final class ChatViewModel: NSObject { private let minDiffCountForOffset = 5 private let minDiffCountForAnimateScroll = 20 private let partnerImageSize: CGFloat = 25 + private let maxMessageLenght: Int = 10000 private var previousArg: ChatContextMenuArguments? let minIndexForStartLoadNewMessages = 4 @@ -569,6 +570,15 @@ final class ChatViewModel: NSObject { ) ) } + + func canSendMessage(withText text: String) -> Bool { + guard text.count <= maxMessageLenght else { + dialog.send(.alert(.adamant.chat.messageIsTooBig)) + return false + } + + return true + } } extension ChatViewModel { diff --git a/CommonKit/Sources/CommonKit/Assets/Localization/de.lproj/Localizable.strings b/CommonKit/Sources/CommonKit/Assets/Localization/de.lproj/Localizable.strings index 8e63f3473..ebe348c80 100644 --- a/CommonKit/Sources/CommonKit/Assets/Localization/de.lproj/Localizable.strings +++ b/CommonKit/Sources/CommonKit/Assets/Localization/de.lproj/Localizable.strings @@ -427,6 +427,9 @@ /* Chat: Error scrolling to message, this message has been deleted and is no longer accessible */ "ChatScene.Error.messageWasDeleted" = "Entschuldigung, aber diese Nachricht wurde gelöscht und ist nicht mehr zugänglich"; +/* Chat: Error message is too big */ +"ChatScene.Error.messageIsTooBig" = "Die Botschaft ist zu groß. Senden Sie es in Teilen."; + /* Delegates page: scene title */ "Delegates.Title" = "Delegierte"; diff --git a/CommonKit/Sources/CommonKit/Assets/Localization/en.lproj/Localizable.strings b/CommonKit/Sources/CommonKit/Assets/Localization/en.lproj/Localizable.strings index ea1976c2b..fc2e364ba 100644 --- a/CommonKit/Sources/CommonKit/Assets/Localization/en.lproj/Localizable.strings +++ b/CommonKit/Sources/CommonKit/Assets/Localization/en.lproj/Localizable.strings @@ -424,6 +424,9 @@ /* Chat: Error scrolling to message, this message has been deleted and is no longer accessible */ "ChatScene.Error.messageWasDeleted" = "Sorry, but this message has been deleted and is no longer accessible"; +/* Chat: Error message is too big */ +"ChatScene.Error.messageIsTooBig" = "The message is too big. Send it in parts."; + /* Delegates page: scene title */ "Delegates.Title" = "Delegates"; diff --git a/CommonKit/Sources/CommonKit/Assets/Localization/ru.lproj/Localizable.strings b/CommonKit/Sources/CommonKit/Assets/Localization/ru.lproj/Localizable.strings index e92083edf..f05f00773 100644 --- a/CommonKit/Sources/CommonKit/Assets/Localization/ru.lproj/Localizable.strings +++ b/CommonKit/Sources/CommonKit/Assets/Localization/ru.lproj/Localizable.strings @@ -424,6 +424,9 @@ /* Chat: Error scrolling to message, this message has been deleted and is no longer accessible */ "ChatScene.Error.messageWasDeleted" = "Извините, но это сообщение было удалено и больше недоступно"; +/* Chat: Error message is too big */ +"ChatScene.Error.messageIsTooBig" = "Сообщение слишком большое. Отправьте его частями."; + /* Delegates page: scene title */ "Delegates.Title" = "Делегаты"; From 58e7f303724e7b643c7617419af515802a51f572 Mon Sep 17 00:00:00 2001 From: StanislavDevIOS Date: Sun, 17 Sep 2023 11:02:16 +0200 Subject: [PATCH 06/96] [trello.com/c/rPkNd6IB] fix: lsk recipient address after sending --- .../Wallets/Lisk/LskWalletService+Send.swift | 3 +- Adamant/Wallets/Lisk/LskWalletService.swift | 3 +- .../API/Transactions/LocalTransaction.swift | 45 +++++++++++++++---- 3 files changed, 40 insertions(+), 11 deletions(-) diff --git a/Adamant/Wallets/Lisk/LskWalletService+Send.swift b/Adamant/Wallets/Lisk/LskWalletService+Send.swift index 712c46887..0a14147a0 100644 --- a/Adamant/Wallets/Lisk/LskWalletService+Send.swift +++ b/Adamant/Wallets/Lisk/LskWalletService+Send.swift @@ -50,7 +50,8 @@ extension LskWalletService: WalletServiceTwoStepSend { fee: self.transactionFee, nonce: wallet.nounce, senderPublicKey: wallet.keyPair.publicKeyString, - recipientAddress: binaryAddress + recipientAddress: recipient, + recipientAddressBinary: binaryAddress ) var signedTransaction = transaction.signed(with: keys, for: self.netHash) diff --git a/Adamant/Wallets/Lisk/LskWalletService.swift b/Adamant/Wallets/Lisk/LskWalletService.swift index 3303a4805..239f765c6 100644 --- a/Adamant/Wallets/Lisk/LskWalletService.swift +++ b/Adamant/Wallets/Lisk/LskWalletService.swift @@ -267,7 +267,8 @@ class LskWalletService: WalletService { fee: 0.00141, nonce: wallet.nounce, senderPublicKey: wallet.keyPair.publicKeyString, - recipientAddress: wallet.binaryAddress + recipientAddress: wallet.address, + recipientAddressBinary: wallet.binaryAddress ).signed( with: wallet.keyPair, for: self.netHash diff --git a/LiskKit/Sources/API/Transactions/LocalTransaction.swift b/LiskKit/Sources/API/Transactions/LocalTransaction.swift index f6c269a1b..35e801213 100644 --- a/LiskKit/Sources/API/Transactions/LocalTransaction.swift +++ b/LiskKit/Sources/API/Transactions/LocalTransaction.swift @@ -65,6 +65,7 @@ public struct TransactionEntity { public struct Asset { public var amount: UInt64 public var recipientAddress: String + public var recipientAddressBinary: String public var data: String = "" public func bytes() -> [UInt8] { @@ -72,8 +73,8 @@ public struct TransactionEntity { value += generateKey(for: 1, with: 0) value += writeUInt64(amount) value += generateKey(for: 2, with: 2) - value += writeUInt32(UInt32(recipientAddress.allHexBytes().count)) - value += recipientAddress.allHexBytes() + value += writeUInt32(UInt32(recipientAddressBinary.allHexBytes().count)) + value += recipientAddressBinary.allHexBytes() value += generateKey(for: 3, with: 2) value += writeUInt32(UInt32(data.bytes.count)) if data.count > 0 { @@ -85,7 +86,7 @@ public struct TransactionEntity { public var requestOptions: RequestOptions { let options: RequestOptions = [ "amount": "\(amount)", - "recipientAddress": recipientAddress, + "recipientAddress": recipientAddressBinary, "data": data ] @@ -111,17 +112,43 @@ public struct TransactionEntity { self.signatures = signatures } - public init(amount: Decimal, fee: Decimal, nonce: String, senderPublicKey: String, recipientAddress: String) { + public init( + amount: Decimal, + fee: Decimal, + nonce: String, + senderPublicKey: String, + recipientAddress: String, + recipientAddressBinary: String + ) { let amount = Crypto.fixedPoint(amount: amount) let fee = Crypto.fixedPoint(amount: fee) - self.init(amount: amount, fee: fee, nonce: nonce, senderPublicKey: senderPublicKey, recipientAddress: recipientAddress) - } - - public init(amount: UInt64, fee: UInt64, nonce: String, senderPublicKey: String, recipientAddress: String, signatures: [String] = []) { + self.init( + amount: amount, + fee: fee, + nonce: nonce, + senderPublicKey: senderPublicKey, + recipientAddress: recipientAddress, + recipientAddressBinary: recipientAddressBinary + ) + } + + public init( + amount: UInt64, + fee: UInt64, + nonce: String, + senderPublicKey: String, + recipientAddress: String, + recipientAddressBinary: String, + signatures: [String] = [] + ) { self.fee = fee self.nonce = UInt64(nonce) ?? 0 self.senderPublicKey = senderPublicKey - self.asset = .init(amount: amount, recipientAddress: recipientAddress) + self.asset = .init( + amount: amount, + recipientAddress: recipientAddress, + recipientAddressBinary: recipientAddressBinary + ) self.signatures = signatures } From a0815cf9a56be23b115f646297fb0dc4e17c8e84 Mon Sep 17 00:00:00 2001 From: StanislavDevIOS Date: Thu, 21 Sep 2023 11:19:06 +0200 Subject: [PATCH 07/96] [trello.com/c/EdiYUEfs] code refactoring --- Adamant/Models/AdamantVibroType.swift | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/Adamant/Models/AdamantVibroType.swift b/Adamant/Models/AdamantVibroType.swift index 67bee0641..0fcb8e64e 100644 --- a/Adamant/Models/AdamantVibroType.swift +++ b/Adamant/Models/AdamantVibroType.swift @@ -8,7 +8,7 @@ import Foundation -enum AdamantVibroType { +enum AdamantVibroType: CaseIterable { case light case rigid case heavy @@ -18,8 +18,4 @@ enum AdamantVibroType { case success case warning case error - - static var allCases: [AdamantVibroType] { - return [.light, .rigid, .heavy, .medium, .soft, .selection, .success, .warning, .error] - } } From 3ec347c10ab7da42ee0429a9a55fe301f07c7fc0 Mon Sep 17 00:00:00 2001 From: StanislavDevIOS Date: Thu, 21 Sep 2023 11:27:44 +0200 Subject: [PATCH 08/96] [trello.com/c/9dFX7nxq] fix: added space from text to icon --- Adamant/Helpers/UITextField+adamant.swift | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/Adamant/Helpers/UITextField+adamant.swift b/Adamant/Helpers/UITextField+adamant.swift index 16ab6e6b7..ec4846f7a 100644 --- a/Adamant/Helpers/UITextField+adamant.swift +++ b/Adamant/Helpers/UITextField+adamant.swift @@ -142,9 +142,18 @@ extension UITextField { func enablePasswordToggle() { let button = UIButton(type: .custom) updatePasswordToggleImage(button) - button.frame = CGRect(x: .zero, y: .zero, width: 25, height: 25) button.addTarget(self, action: #selector(togglePasswordView(_:)), for: .touchUpInside) - rightView = button + + let contanerView = UIView() + contanerView.addSubview(button) + button.snp.makeConstraints { make in + make.directionalEdges.equalToSuperview().inset(3) + } + contanerView.snp.makeConstraints { make in + make.size.equalTo(28) + } + + rightView = contanerView rightViewMode = .always } From b3b8eb10c788172c9ae452bec93067fd63e95775 Mon Sep 17 00:00:00 2001 From: StanislavDevIOS Date: Thu, 21 Sep 2023 12:05:36 +0200 Subject: [PATCH 09/96] [trello.com/c/rPkNd6IB] feat: make names more clean --- .../Wallets/Lisk/LskTransactionsViewController.swift | 2 +- Adamant/Wallets/Lisk/LskWalletService+Send.swift | 2 +- Adamant/Wallets/Lisk/LskWalletService.swift | 2 +- .../Sources/API/Transactions/LocalTransaction.swift | 10 +++++----- 4 files changed, 8 insertions(+), 8 deletions(-) diff --git a/Adamant/Wallets/Lisk/LskTransactionsViewController.swift b/Adamant/Wallets/Lisk/LskTransactionsViewController.swift index e72258853..471e3788b 100644 --- a/Adamant/Wallets/Lisk/LskTransactionsViewController.swift +++ b/Adamant/Wallets/Lisk/LskTransactionsViewController.swift @@ -281,7 +281,7 @@ extension TransactionEntity: TransactionDetails { } var recipientAddress: String { - return self.asset.recipientAddress + return self.asset.recipientAddressBase32 } var dateValue: Date? { diff --git a/Adamant/Wallets/Lisk/LskWalletService+Send.swift b/Adamant/Wallets/Lisk/LskWalletService+Send.swift index 0a14147a0..92766dc55 100644 --- a/Adamant/Wallets/Lisk/LskWalletService+Send.swift +++ b/Adamant/Wallets/Lisk/LskWalletService+Send.swift @@ -50,7 +50,7 @@ extension LskWalletService: WalletServiceTwoStepSend { fee: self.transactionFee, nonce: wallet.nounce, senderPublicKey: wallet.keyPair.publicKeyString, - recipientAddress: recipient, + recipientAddressBase32: recipient, recipientAddressBinary: binaryAddress ) diff --git a/Adamant/Wallets/Lisk/LskWalletService.swift b/Adamant/Wallets/Lisk/LskWalletService.swift index 239f765c6..2dc0b4a3b 100644 --- a/Adamant/Wallets/Lisk/LskWalletService.swift +++ b/Adamant/Wallets/Lisk/LskWalletService.swift @@ -267,7 +267,7 @@ class LskWalletService: WalletService { fee: 0.00141, nonce: wallet.nounce, senderPublicKey: wallet.keyPair.publicKeyString, - recipientAddress: wallet.address, + recipientAddressBase32: wallet.address, recipientAddressBinary: wallet.binaryAddress ).signed( with: wallet.keyPair, diff --git a/LiskKit/Sources/API/Transactions/LocalTransaction.swift b/LiskKit/Sources/API/Transactions/LocalTransaction.swift index 35e801213..09fd4dad1 100644 --- a/LiskKit/Sources/API/Transactions/LocalTransaction.swift +++ b/LiskKit/Sources/API/Transactions/LocalTransaction.swift @@ -64,7 +64,7 @@ public struct TransactionEntity { public struct Asset { public var amount: UInt64 - public var recipientAddress: String + public var recipientAddressBase32: String public var recipientAddressBinary: String public var data: String = "" @@ -117,7 +117,7 @@ public struct TransactionEntity { fee: Decimal, nonce: String, senderPublicKey: String, - recipientAddress: String, + recipientAddressBase32: String, recipientAddressBinary: String ) { let amount = Crypto.fixedPoint(amount: amount) @@ -127,7 +127,7 @@ public struct TransactionEntity { fee: fee, nonce: nonce, senderPublicKey: senderPublicKey, - recipientAddress: recipientAddress, + recipientAddressBase32: recipientAddressBase32, recipientAddressBinary: recipientAddressBinary ) } @@ -137,7 +137,7 @@ public struct TransactionEntity { fee: UInt64, nonce: String, senderPublicKey: String, - recipientAddress: String, + recipientAddressBase32: String, recipientAddressBinary: String, signatures: [String] = [] ) { @@ -146,7 +146,7 @@ public struct TransactionEntity { self.senderPublicKey = senderPublicKey self.asset = .init( amount: amount, - recipientAddress: recipientAddress, + recipientAddressBase32: recipientAddressBase32, recipientAddressBinary: recipientAddressBinary ) self.signatures = signatures From 996dabb6350b911c41a17f5cb18175970185049d Mon Sep 17 00:00:00 2001 From: StanislavDevIOS Date: Thu, 21 Sep 2023 12:54:56 +0200 Subject: [PATCH 10/96] [trello.com/c/3RorlQxg] fix: revert NotificationInAppService --- Adamant.xcodeproj/project.pbxproj | 8 - Adamant/AppDelegate.swift | 5 - .../NotificationInAppService.swift | 13 - .../AdamantNotificationInAppService.swift | 291 ------------------ .../ChatsList/ChatListViewController.swift | 69 +++++ Adamant/SwinjectDependencies.swift | 9 - 6 files changed, 69 insertions(+), 326 deletions(-) delete mode 100644 Adamant/ServiceProtocols/NotificationInAppService.swift delete mode 100644 Adamant/Services/AdamantNotificationInAppService.swift diff --git a/Adamant.xcodeproj/project.pbxproj b/Adamant.xcodeproj/project.pbxproj index dbf081881..a8be09054 100644 --- a/Adamant.xcodeproj/project.pbxproj +++ b/Adamant.xcodeproj/project.pbxproj @@ -18,8 +18,6 @@ 3A7BD0152AA9BF020045AAB0 /* VibrationSelectionView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3A7BD0142AA9BF020045AAB0 /* VibrationSelectionView.swift */; }; 3A7BD0172AA9BF100045AAB0 /* VibrationSelectionFactory.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3A7BD0162AA9BF100045AAB0 /* VibrationSelectionFactory.swift */; }; 3A7BD0192AA9BF3F0045AAB0 /* VibrationSelectionViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3A7BD0182AA9BF3F0045AAB0 /* VibrationSelectionViewModel.swift */; }; - 3A7BD01B2AAA01930045AAB0 /* NotificationInAppService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3A7BD01A2AAA01930045AAB0 /* NotificationInAppService.swift */; }; - 3A7BD01D2AAA01B50045AAB0 /* AdamantNotificationInAppService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3A7BD01C2AAA01B50045AAB0 /* AdamantNotificationInAppService.swift */; }; 3A8875EF27BBF38D00436195 /* Parchment in Frameworks */ = {isa = PBXBuildFile; productRef = 3A8875EE27BBF38D00436195 /* Parchment */; }; 3A9015A52A614A18002A2464 /* EmojiService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3A9015A42A614A18002A2464 /* EmojiService.swift */; }; 3A9015A72A614A62002A2464 /* AdamantEmojiService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3A9015A62A614A62002A2464 /* AdamantEmojiService.swift */; }; @@ -599,8 +597,6 @@ 3A7BD0142AA9BF020045AAB0 /* VibrationSelectionView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VibrationSelectionView.swift; sourceTree = ""; }; 3A7BD0162AA9BF100045AAB0 /* VibrationSelectionFactory.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VibrationSelectionFactory.swift; sourceTree = ""; }; 3A7BD0182AA9BF3F0045AAB0 /* VibrationSelectionViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VibrationSelectionViewModel.swift; sourceTree = ""; }; - 3A7BD01A2AAA01930045AAB0 /* NotificationInAppService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationInAppService.swift; sourceTree = ""; }; - 3A7BD01C2AAA01B50045AAB0 /* AdamantNotificationInAppService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AdamantNotificationInAppService.swift; sourceTree = ""; }; 3A9015A42A614A18002A2464 /* EmojiService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EmojiService.swift; sourceTree = ""; }; 3A9015A62A614A62002A2464 /* AdamantEmojiService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AdamantEmojiService.swift; sourceTree = ""; }; 3A9015A82A615893002A2464 /* ChatMessagesListViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChatMessagesListViewModel.swift; sourceTree = ""; }; @@ -1661,7 +1657,6 @@ 4184F1742A33106200D7B8B9 /* CrashlysticsService.swift */, 3A9015A42A614A18002A2464 /* EmojiService.swift */, 3A7BD00D2AA9BCE80045AAB0 /* VibroService.swift */, - 3A7BD01A2AAA01930045AAB0 /* NotificationInAppService.swift */, 41C1698B29E7F34900FEB3CB /* RichTransactionReplyService.swift */, 3A4193902A580C85006A6B22 /* RichTransactionReactService.swift */, ); @@ -1690,7 +1685,6 @@ 4184F1722A33102800D7B8B9 /* AdamantCrashlysticsService.swift */, 3A9015A62A614A62002A2464 /* AdamantEmojiService.swift */, 3A7BD00F2AA9BD030045AAB0 /* AdamantVibroService.swift */, - 3A7BD01C2AAA01B50045AAB0 /* AdamantNotificationInAppService.swift */, E921597420611A6A0000CA5C /* AdamantReachability.swift */, E950273F202E257E002C1098 /* RepeaterService.swift */, E9E7CDB42002BA6900DFC4DB /* SwinjectedRouter.swift */, @@ -2918,7 +2912,6 @@ 41A1995629D56EAA0031AD75 /* ChatMessageReplyCell+Model.swift in Sources */, E90847312196FEA80095825D /* ChatTransaction+CoreDataProperties.swift in Sources */, E99330262136B0E500CD5200 /* TransferViewControllerBase+QR.swift in Sources */, - 3A7BD01D2AAA01B50045AAB0 /* AdamantNotificationInAppService.swift in Sources */, E9B1AA5B21283E0F00080A2A /* AdmTransferViewController.swift in Sources */, 648C697322916192006645F5 /* DashTransactionsViewController.swift in Sources */, 55E69E172868D7920025D82E /* CheckmarkView.swift in Sources */, @@ -3031,7 +3024,6 @@ E9AA8BF82129F13000F9249F /* ComplexTransferViewController.swift in Sources */, E9A174B52057EDCE003667CD /* AdamantTransfersProvider+backgroundFetch.swift in Sources */, 9382F61329DEC0A3005E6216 /* ChatModelView.swift in Sources */, - 3A7BD01B2AAA01930045AAB0 /* NotificationInAppService.swift in Sources */, E9147B5F20500E9300145913 /* MyLittlePinpad+adamant.swift in Sources */, E9981896212095CA0018C84C /* EthWalletViewController.swift in Sources */, 648DD7A22237D9A000B811FD /* DogeTransaction.swift in Sources */, diff --git a/Adamant/AppDelegate.swift b/Adamant/AppDelegate.swift index 1584e7ee7..74820c1a2 100644 --- a/Adamant/AppDelegate.swift +++ b/Adamant/AppDelegate.swift @@ -219,11 +219,6 @@ class AppDelegate: UIResponder, UIApplicationDelegate { Task { await service.startObserving() } } - // Setup notifications in app observing - if let service = container.resolve(NotificationInAppService.self) { - service.startObserving() - } - // Register repeater services if let chatsProvider = container.resolve(ChatsProvider.self) { repeater.registerForegroundCall(label: "chatsProvider", interval: 10, queue: .global(qos: .utility), callback: { diff --git a/Adamant/ServiceProtocols/NotificationInAppService.swift b/Adamant/ServiceProtocols/NotificationInAppService.swift deleted file mode 100644 index 88cd57eeb..000000000 --- a/Adamant/ServiceProtocols/NotificationInAppService.swift +++ /dev/null @@ -1,13 +0,0 @@ -// -// NotificationInAppService.swift -// Adamant -// -// Created by Stanislav Jelezoglo on 07.09.2023. -// Copyright © 2023 Adamant. All rights reserved. -// - -import Foundation - -protocol NotificationInAppService: AnyObject { - func startObserving() -} diff --git a/Adamant/Services/AdamantNotificationInAppService.swift b/Adamant/Services/AdamantNotificationInAppService.swift deleted file mode 100644 index 3fc06b627..000000000 --- a/Adamant/Services/AdamantNotificationInAppService.swift +++ /dev/null @@ -1,291 +0,0 @@ -// -// AdamantNotificationInAppService.swift -// Adamant -// -// Created by Stanislav Jelezoglo on 07.09.2023. -// Copyright © 2023 Adamant. All rights reserved. -// - -import Foundation -import CoreData -import UIKit -import CommonKit - -final class AdamantNotificationInAppService: NSObject, NotificationInAppService { - - // MARK: Dependencies - - private let chatsProvider: ChatsProvider - private let dialogService: DialogService - private let accountService: AccountService - private var richMessageProviders: [String: RichMessageProvider] = [:] - - // MARK: Proprieties - - private var unreadController: NSFetchedResultsController? - private let defaultAvatar = UIImage.asset(named: "avatar-chat-placeholder") ?? .init() - - // MARK: Init - - init( - chatsProvider: ChatsProvider, - dialogService: DialogService, - accountService: AccountService - ) { - self.chatsProvider = chatsProvider - self.dialogService = dialogService - self.accountService = accountService - super.init() - - self.richMessageProviders = self.makeRichMessageProviders() - } - - func makeRichMessageProviders() -> [String: RichMessageProvider] { - .init( - uniqueKeysWithValues: accountService - .wallets - .compactMap { $0 as? RichMessageProvider } - .map { ($0.dynamicRichMessageType, $0) } - ) - } - - func startObserving() { - Task { - unreadController = await chatsProvider.getUnreadMessagesController() - unreadController?.delegate = self - try? unreadController?.performFetch() - } - } -} - -// MARK: Core Data - -extension AdamantNotificationInAppService: NSFetchedResultsControllerDelegate { - func controller( - _: NSFetchedResultsController, - didChange object: Any, - at _: IndexPath?, - for type_: NSFetchedResultsChangeType, - newIndexPath _: IndexPath? - ) { - guard type_ == .insert else { return } - - if let transaction = object as? ChatTransaction { - showNotification(for: transaction) - } - - let forceUpdate = object is TransferTransaction - || object is RichMessageTransaction - - guard forceUpdate else { return } - - NotificationCenter.default.post( - name: .AdamantAccountService.forceUpdateBalance, - object: nil - ) - - Task { - await Task.sleep(interval: 4) - - NotificationCenter.default.post( - name: .AdamantAccountService.forceUpdateBalance, - object: nil - ) - } - } -} - -// MARK: Working with in-app notifications - -private extension AdamantNotificationInAppService { - func showNotification(for transaction: ChatTransaction) { - Task { - // MARK: Do not show notifications for initial sync - - guard await chatsProvider.isInitiallySynced else { - return - } - - // MARK: Show notification only for incomming transactions - - guard !transaction.silentNotification, - !transaction.isOutgoing, - let chatroom = transaction.chatroom, - await shoudPresentNotification(chatroom: chatroom), - let partner = chatroom.partner - else { - return - } - - // MARK: Prepare notification - - let title = partner.name ?? partner.address - let text = shortDescription(for: transaction) - - let image: UIImage - if let ava = partner.avatar, let img = UIImage.asset(named: ava) { - image = img - } else { - image = defaultAvatar - } - - // MARK: Show notification with tap handler - await dialogService.showNotification( - title: title?.checkAndReplaceSystemWallets(), - message: text, - image: image - ) { [weak self, chatroom] in - self?.presentChatroom(chatroom) - } - } - } - - func shortDescription(for transaction: ChatTransaction) -> String? { - switch transaction { - case let message as MessageTransaction: - guard let text = message.message else { - return nil - } - - let raw: String - if message.isOutgoing { - raw = "\(String.adamant.chatList.sentMessagePrefix)\(text)" - } else { - raw = text - } - - return raw - - case let transfer as TransferTransaction: - if let admService = richMessageProviders[AdmWalletService.richMessageType] as? AdmWalletService { - return admService.shortDescription(for: transfer) - } else { - return nil - } - - case let richMessage as RichMessageTransaction: - if let type = richMessage.richType, - let provider = richMessageProviders[type] { - return provider.shortDescription(for: richMessage).string - } - - if richMessage.additionalType == .reply, - let content = richMessage.richContent, - let text = content[RichContentKeys.reply.replyMessage] as? String { - - let prefix = richMessage.isOutgoing - ? "\(String.adamant.chatList.sentMessagePrefix)" - : "" - - let extraSpace = richMessage.isOutgoing ? " " : "" - return "\(prefix)\(extraSpace)\(text)" - } - - if richMessage.additionalType == .reaction, - let content = richMessage.richContent, - let reaction = content[RichContentKeys.react.react_message] as? String { - let prefix = richMessage.isOutgoing - ? "\(String.adamant.chatList.sentMessagePrefix)" - : "" - - let text = reaction.isEmpty - ? "\(prefix)\(String.adamant.chatList.removedReaction) \(reaction)" - : "\(prefix)\(String.adamant.chatList.reacted) \(reaction)" - - return text - } - - if let serialized = richMessage.serializedMessage() { - return serialized - } - - return nil - default: - return nil - } - } - - @MainActor func shoudPresentNotification(chatroom: Chatroom) -> Bool { - guard chatroom != presentedChatroom(), - !chatroom.isHidden, - !(rootViewController() is ChatListViewController) - else { return false } - - return true - } - - @MainActor func presentedChatroom() -> Chatroom? { - guard let vc = rootViewController() as? ChatViewController - else { return nil } - - return vc.viewModel.chatroom - } - - func rootViewController() -> UIViewController? { - let allScenes = UIApplication.shared.connectedScenes - let scene = allScenes.first { $0.activationState == .foregroundActive } - - guard let windowScene = scene as? UIWindowScene else { - return nil - } - - var topController = windowScene.keyWindow?.rootViewController - - while (topController?.presentedViewController != nil) { - topController = topController?.presentedViewController - } - - if let tabbar = topController as? UITabBarController, - let split = tabbar.viewControllers?[tabbar.selectedIndex] as? UISplitViewController, - let navigation = split.viewControllers.first as? UINavigationController { - return navigation.visibleViewController - } - - if let tabbar = topController as? UITabBarController, - let navigation = tabbar.viewControllers?[tabbar.selectedIndex] as? UINavigationController { - return navigation.visibleViewController - } - - return topController - } - - func getTabBarController() -> UITabBarController? { - let allScenes = UIApplication.shared.connectedScenes - let scene = allScenes.first { $0.activationState == .foregroundActive } - - guard let windowScene = scene as? UIWindowScene else { - return nil - } - - var topController = windowScene.keyWindow?.rootViewController - - while (topController?.presentedViewController != nil) { - topController = topController?.presentedViewController - } - - return topController as? UITabBarController - } - - func presentChatroom(_ chatroom: Chatroom) { - guard let tabbar = getTabBarController() else { return } - - if let split = tabbar.viewControllers?.first as? UISplitViewController, - let navigation = split.viewControllers.first as? UINavigationController, - let chatListVC = navigation.viewControllers.first as? ChatListViewController { - navigation.popToRootViewController(animated: true) - Task { - await chatListVC.presentChatroom(chatroom) - await chatListVC.selectChatroomRow(chatroom: chatroom) - } - } - - if let navigation = tabbar.viewControllers?.first as? UINavigationController, - let chatListVC = navigation.viewControllers.first as? ChatListViewController { - navigation.popToRootViewController(animated: true) - Task { - await chatListVC.presentChatroom(chatroom) - await chatListVC.selectChatroomRow(chatroom: chatroom) - } - } - } -} diff --git a/Adamant/Stories/ChatsList/ChatListViewController.swift b/Adamant/Stories/ChatsList/ChatListViewController.swift index b7cac0b4e..c2ba0b91e 100644 --- a/Adamant/Stories/ChatsList/ChatListViewController.swift +++ b/Adamant/Stories/ChatsList/ChatListViewController.swift @@ -58,6 +58,7 @@ class ChatListViewController: KeyboardObservingViewController { var unreadController: NSFetchedResultsController? var searchController: UISearchController? + private var transactionsRequiringBalanceUpdate: [String] = [] private var preservedMessagess = [String:String]() let defaultAvatar = UIImage.asset(named: "avatar-chat-placeholder") ?? .init() @@ -675,6 +676,40 @@ extension ChatListViewController: NSFetchedResultsControllerDelegate { break } + // MARK: Unread controller + + case let c where c == unreadController: + guard let transaction = anObject as? ChatTransaction else { break } + + if self.view.window == nil, + type == .insert { + showNotification(for: transaction) + } + + let shouldForceUpdate = anObject is TransferTransaction + || anObject is RichMessageTransaction + + if shouldForceUpdate, type == .insert { + transactionsRequiringBalanceUpdate.append(transaction.txId) + } + + guard shouldForceUpdate, + let blockId = transaction.blockId, + !blockId.isEmpty, + transactionsRequiringBalanceUpdate.contains(transaction.txId) + else { + break + } + + if let index = transactionsRequiringBalanceUpdate.firstIndex(of: transaction.txId) { + transactionsRequiringBalanceUpdate.remove(at: index) + } + + NotificationCenter.default.post( + name: .AdamantAccountService.forceUpdateBalance, + object: nil + ) + default: break } @@ -757,6 +792,40 @@ extension ChatListViewController: ChatPreservationDelegate { // MARK: - Working with in-app notifications extension ChatListViewController { + private func showNotification(for transaction: ChatTransaction) { + Task { + // MARK: 0. Do not show notifications for initial sync + guard await chatsProvider.isInitiallySynced else { + return + } + + // MARK: 1. Show notification only for incomming transactions + guard !transaction.silentNotification, !transaction.isOutgoing, + let chatroom = transaction.chatroom, chatroom != presentedChatroom(), !chatroom.isHidden, + let partner = chatroom.partner else { + return + } + + // MARK: 2. Prepare notification + let title = partner.name ?? partner.address + let text = shortDescription(for: transaction) + + let image: UIImage + if let ava = partner.avatar, let img = UIImage.asset(named: ava) { + image = img + } else { + image = defaultAvatar + } + + // MARK: 4. Show notification with tap handler + dialogService.showNotification(title: title?.checkAndReplaceSystemWallets(), message: text?.string, image: image) { [weak self] in + DispatchQueue.main.async { + self?.presentChatroom(chatroom) + } + } + } + } + @MainActor func presentChatroom(_ chatroom: Chatroom, with message: MessageTransaction? = nil) { // MARK: 1. Create and config ViewController diff --git a/Adamant/SwinjectDependencies.swift b/Adamant/SwinjectDependencies.swift index d9a223a52..96687fc64 100644 --- a/Adamant/SwinjectDependencies.swift +++ b/Adamant/SwinjectDependencies.swift @@ -286,15 +286,6 @@ extension Container { ) }.inObjectScope(.container) - // MARK: Notifications in app service - self.register(NotificationInAppService.self) { r in - AdamantNotificationInAppService( - chatsProvider: r.resolve(ChatsProvider.self)!, - dialogService: r.resolve(DialogService.self)!, - accountService: r.resolve(AccountService.self)! - ) - }.inObjectScope(.container) - // MARK: Bitcoin AddressConverterFactory self.register(AddressConverterFactory.self) { _ in AddressConverterFactory() From bd52c6c7c8dc8fba7621175ad232a196aeeff32b Mon Sep 17 00:00:00 2001 From: StanislavDevIOS Date: Thu, 21 Sep 2023 14:19:00 +0200 Subject: [PATCH 11/96] [trello.com/c/txp4J6DE] fix: menu actions in embedded messages --- .../Adamant.xcdatamodel/contents | 1 + .../ChatTransaction+CoreDataProperties.swift | 1 + .../AdamantChatsProvider+fakeMessages.swift | 1 + .../View/Managers/ChatDialogManager.swift | 27 +++++++++++++------ .../ChatMessageCell+Model.swift | 2 ++ .../ChatBaseMessage/ChatMessageCell.swift | 4 +++ .../Chat/ViewModel/ChatMessageFactory.swift | 1 + .../Chat/ViewModel/ChatViewModel.swift | 4 +++ .../Chat/ViewModel/Models/ChatDialog.swift | 1 + 9 files changed, 34 insertions(+), 8 deletions(-) diff --git a/Adamant/CoreData/Adamant.xcdatamodeld/Adamant.xcdatamodel/contents b/Adamant/CoreData/Adamant.xcdatamodeld/Adamant.xcdatamodel/contents index bce0ebf0e..c760e8106 100644 --- a/Adamant/CoreData/Adamant.xcdatamodeld/Adamant.xcdatamodel/contents +++ b/Adamant/CoreData/Adamant.xcdatamodeld/Adamant.xcdatamodel/contents @@ -42,6 +42,7 @@ + diff --git a/Adamant/CoreData/ChatTransaction+CoreDataProperties.swift b/Adamant/CoreData/ChatTransaction+CoreDataProperties.swift index d5da8484a..accc3697f 100644 --- a/Adamant/CoreData/ChatTransaction+CoreDataProperties.swift +++ b/Adamant/CoreData/ChatTransaction+CoreDataProperties.swift @@ -24,5 +24,6 @@ extension ChatTransaction { @NSManaged public var status: Int16 @NSManaged public var chatroom: Chatroom? @NSManaged public var lastIn: Chatroom? + @NSManaged public var isFake: Bool } diff --git a/Adamant/Services/DataProviders/AdamantChatsProvider+fakeMessages.swift b/Adamant/Services/DataProviders/AdamantChatsProvider+fakeMessages.swift index 9b30aca53..1fd62a949 100644 --- a/Adamant/Services/DataProviders/AdamantChatsProvider+fakeMessages.swift +++ b/Adamant/Services/DataProviders/AdamantChatsProvider+fakeMessages.swift @@ -226,6 +226,7 @@ extension AdamantChatsProvider { transaction.senderId = sender.address transaction.type = Int16(ChatType.message.rawValue) transaction.isOutgoing = false + transaction.isFake = true transaction.message = text transaction.isUnread = unread transaction.silentNotification = silent diff --git a/Adamant/Stories/Chat/View/Managers/ChatDialogManager.swift b/Adamant/Stories/Chat/View/Managers/ChatDialogManager.swift index bb8a0a486..9d571ab3c 100644 --- a/Adamant/Stories/Chat/View/Managers/ChatDialogManager.swift +++ b/Adamant/Stories/Chat/View/Managers/ChatDialogManager.swift @@ -87,6 +87,7 @@ private extension ChatDialogManager { case let .failedMessageAlert(id, sender): showFailedMessageAlert(id: id, sender: sender) case let .presentMenu( + presentReactions, arg, didSelectEmojiDelegate, didSelectEmojiAction, @@ -94,6 +95,7 @@ private extension ChatDialogManager { didDismissMenuAction ): presentMenu( + presentReactions: presentReactions, arg: arg, didSelectEmojiDelegate: didSelectEmojiDelegate, didSelectEmojiAction: didSelectEmojiAction, @@ -215,7 +217,7 @@ private extension ChatDialogManager { makeCancelSendingAction(id: id), makeCancelAction() ], - from: sender + from: nil ) } } @@ -433,6 +435,7 @@ private extension ChatDialogManager { } func presentMenu( + presentReactions: Bool, arg: ChatContextMenuArguments, didSelectEmojiDelegate: ElegantEmojiPickerDelegate?, didSelectEmojiAction: DidSelectEmojiAction, @@ -442,15 +445,23 @@ private extension ChatDialogManager { contextMenu.didPresentMenuAction = didPresentMenuAction contextMenu.didDismissMenuAction = didDismissMenuAction + let reactionsContentView = !presentReactions + ? nil + : getUpperContentView( + messageId: arg.messageId, + selectedEmoji: arg.selectedEmoji, + didSelectEmojiAction: didSelectEmojiAction, + didSelectEmojiDelegate: didSelectEmojiDelegate + ) + + let reactionsContentViewSize: CGSize = !presentReactions + ? .zero + : getUpperContentViewSize() + contextMenu.presentMenu( arg: arg, - upperView: getUpperContentView( - messageId: arg.messageId, - selectedEmoji: arg.selectedEmoji, - didSelectEmojiAction: didSelectEmojiAction, - didSelectEmojiDelegate: didSelectEmojiDelegate - ), - upperViewSize: getUpperContentViewSize() + upperView: reactionsContentView, + upperViewSize: reactionsContentViewSize ) } diff --git a/Adamant/Stories/Chat/View/Subviews/ChatBaseMessage/ChatMessageCell+Model.swift b/Adamant/Stories/Chat/View/Subviews/ChatBaseMessage/ChatMessageCell+Model.swift index d5b4e4811..cb166e91a 100644 --- a/Adamant/Stories/Chat/View/Subviews/ChatBaseMessage/ChatMessageCell+Model.swift +++ b/Adamant/Stories/Chat/View/Subviews/ChatBaseMessage/ChatMessageCell+Model.swift @@ -17,6 +17,7 @@ extension ChatMessageCell { let reactions: Set? let address: String let opponentAddress: String + let isFake: Bool var isHidden: Bool static let `default` = Self( @@ -27,6 +28,7 @@ extension ChatMessageCell { reactions: nil, address: "", opponentAddress: "", + isFake: false, isHidden: false ) diff --git a/Adamant/Stories/Chat/View/Subviews/ChatBaseMessage/ChatMessageCell.swift b/Adamant/Stories/Chat/View/Subviews/ChatBaseMessage/ChatMessageCell.swift index 42b1d9e5b..ed97e50c9 100644 --- a/Adamant/Stories/Chat/View/Subviews/ChatBaseMessage/ChatMessageCell.swift +++ b/Adamant/Stories/Chat/View/Subviews/ChatBaseMessage/ChatMessageCell.swift @@ -424,6 +424,10 @@ extension ChatMessageCell { actionHandler(.copy(text: model.text.string)) } + guard !model.isFake else { + return AMenuSection([copy]) + } + return AMenuSection([reply, copy, report, remove]) } diff --git a/Adamant/Stories/Chat/ViewModel/ChatMessageFactory.swift b/Adamant/Stories/Chat/ViewModel/ChatMessageFactory.swift index c2eb6f6ac..83e710273 100644 --- a/Adamant/Stories/Chat/ViewModel/ChatMessageFactory.swift +++ b/Adamant/Stories/Chat/ViewModel/ChatMessageFactory.swift @@ -186,6 +186,7 @@ private extension ChatMessageFactory { reactions: reactions, address: address, opponentAddress: opponentAddress, + isFake: transaction.isFake, isHidden: false ) )) diff --git a/Adamant/Stories/Chat/ViewModel/ChatViewModel.swift b/Adamant/Stories/Chat/ViewModel/ChatViewModel.swift index c764ae4c3..03c66f749 100644 --- a/Adamant/Stories/Chat/ViewModel/ChatViewModel.swift +++ b/Adamant/Stories/Chat/ViewModel/ChatViewModel.swift @@ -559,8 +559,12 @@ final class ChatViewModel: NSObject { previousArg = arg + let tx = chatTransactions.first(where: { $0.txId == arg.messageId }) + guard tx?.statusEnum == .delivered else { return } + dialog.send( .presentMenu( + presentReactions: tx?.isFake == false, arg: arg, didSelectEmojiDelegate: self, didSelectEmojiAction: didSelectEmojiAction, diff --git a/Adamant/Stories/Chat/ViewModel/Models/ChatDialog.swift b/Adamant/Stories/Chat/ViewModel/Models/ChatDialog.swift index 00d99d3c4..e2954f08d 100644 --- a/Adamant/Stories/Chat/ViewModel/Models/ChatDialog.swift +++ b/Adamant/Stories/Chat/ViewModel/Models/ChatDialog.swift @@ -26,6 +26,7 @@ enum ChatDialog { case progress(Bool) case failedMessageAlert(id: String, sender: UIAlertController.SourceView) case presentMenu( + presentReactions: Bool, arg: ChatContextMenuArguments, didSelectEmojiDelegate: ElegantEmojiPickerDelegate?, didSelectEmojiAction: ChatDialogManager.DidSelectEmojiAction, From 2bd1e5d6e4870aa9698d0878634f7ccf6f35b889 Mon Sep 17 00:00:00 2001 From: StanislavDevIOS Date: Sat, 23 Sep 2023 11:01:41 +0200 Subject: [PATCH 12/96] [trello.com/c/62gzq6Sj] fix: avatar position on push notification in app --- Adamant/Stories/ChatsList/ChatListViewController.swift | 2 ++ .../PopupKit/Implementation/Views/NotificationView.swift | 7 ++++--- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/Adamant/Stories/ChatsList/ChatListViewController.swift b/Adamant/Stories/ChatsList/ChatListViewController.swift index 691dfc6ee..de6c149e7 100644 --- a/Adamant/Stories/ChatsList/ChatListViewController.swift +++ b/Adamant/Stories/ChatsList/ChatListViewController.swift @@ -794,6 +794,8 @@ extension ChatListViewController { let image: UIImage if let ava = partner.avatar, let img = UIImage.asset(named: ava) { image = img + } else if let publicKey = partner.publicKey { + image = avatarService.avatar(for: publicKey, size: 30) } else { image = defaultAvatar } diff --git a/PopupKit/Sources/PopupKit/Implementation/Views/NotificationView.swift b/PopupKit/Sources/PopupKit/Implementation/Views/NotificationView.swift index 889ff59c5..153b25dfb 100644 --- a/PopupKit/Sources/PopupKit/Implementation/Views/NotificationView.swift +++ b/PopupKit/Sources/PopupKit/Implementation/Views/NotificationView.swift @@ -17,14 +17,15 @@ struct NotificationView: View { let dismissAction: () -> Void var body: some View { - HStack(alignment: .bottom, spacing: 7) { + HStack(alignment: .top, spacing: 8) { if let icon = model.icon { makeIcon(image: icon) } textStack Spacer(minLength: .zero) } - .padding(10) + .padding([.leading, .trailing], 15) + .padding([.top, .bottom], 10) .background(GeometryReader(content: processGeometry)) .padding(.top, safeAreaInsets.top) .expanded(axes: .horizontal) @@ -39,7 +40,7 @@ private extension NotificationView { func makeIcon(image: UIImage) -> some View { Image(uiImage: image) .resizable() - .renderingMode(.template) + .renderingMode(.original) .foregroundColor(.secondary) .scaledToFit() .frame(squareSize: 30) From 70c2cdf38c437f56de220dfc6041c5a636e4fdb7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=80=9Cjust-software-dev=E2=80=9D?= Date: Sun, 24 Sep 2023 23:05:20 +0100 Subject: [PATCH 13/96] [mastertodev/trello.com/c/XDMPLVfx] Version 3.3.0 --- Adamant.xcodeproj/project.pbxproj | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Adamant.xcodeproj/project.pbxproj b/Adamant.xcodeproj/project.pbxproj index 04495afb2..b476fc0d4 100644 --- a/Adamant.xcodeproj/project.pbxproj +++ b/Adamant.xcodeproj/project.pbxproj @@ -3354,7 +3354,7 @@ "$(inherited)", "@executable_path/Frameworks", ); - MARKETING_VERSION = 3.2.0; + MARKETING_VERSION = 3.3.0; PRODUCT_BUNDLE_IDENTIFIER = "im.adamant.adamant-messenger-dev"; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; @@ -3385,7 +3385,7 @@ "$(inherited)", "@executable_path/Frameworks", ); - MARKETING_VERSION = 3.2.0; + MARKETING_VERSION = 3.3.0; PRODUCT_BUNDLE_IDENTIFIER = "im.adamant.adamant-messenger"; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; From 1dc23b5b672b4d481499fc8926a0c284513f9718 Mon Sep 17 00:00:00 2001 From: StanislavDevIOS Date: Sat, 30 Sep 2023 14:26:28 +0200 Subject: [PATCH 14/96] [trello.com/c/FkbZOCIe] feat: save all tx localy --- Adamant.xcodeproj/project.pbxproj | 16 +++ .../Adamant.xcdatamodel/contents | 27 ++-- .../BaseTransaction+CoreDataClass.swift | 2 +- .../BaseTransaction+CoreDataProperties.swift | 6 - .../CoinTransaction+CoreDataClass.swift | 16 +++ .../CoinTransaction+CoreDataProperties.swift | 32 +++++ Adamant/ServiceProtocols/CoinStorage.swift | 14 ++ .../Services/AdamantCoinStorageService.swift | 84 ++++++++++++ .../AdmTransactionsViewController.swift | 3 +- .../Wallets/Adamant/AdmWalletService.swift | 31 +++++ .../BtcTransactionsViewController.swift | 97 +++---------- .../Bitcoin/BtcTransferViewController.swift | 1 + .../Wallets/Bitcoin/BtcWalletService.swift | 44 ++++++ .../Dash/DashTransactionsViewController.swift | 127 ++---------------- .../Dash/DashTransferViewController.swift | 1 + Adamant/Wallets/Dash/DashWalletService.swift | 59 ++++++++ .../Doge/DogeTransactionsViewController.swift | 100 ++------------ .../Doge/DogeTransferViewController.swift | 1 + Adamant/Wallets/Doge/DogeWalletService.swift | 44 ++++++ .../ERC20TransactionsViewController.swift | 120 ++--------------- .../ERC20/ERC20TransferViewController.swift | 12 +- .../Wallets/ERC20/ERC20WalletService.swift | 71 ++++++++++ .../EthTransactionsViewController.swift | 106 +-------------- .../Ethereum/EthTransferViewController.swift | 12 +- .../Ethereum/EthWalletService+Transfers.swift | 1 + .../Wallets/Ethereum/EthWalletService.swift | 64 +++++++++ .../Lisk/LskTransactionsViewController.swift | 97 ++----------- .../Lisk/LskTransferViewController.swift | 2 + .../Lisk/LskWalletService+Transfers.swift | 1 + Adamant/Wallets/Lisk/LskWalletService.swift | 44 +++++- .../TransactionsListViewControllerBase.swift | 103 +++++++++++++- Adamant/Wallets/WalletService.swift | 9 ++ 32 files changed, 731 insertions(+), 616 deletions(-) create mode 100644 Adamant/CoreData/CoinTransaction+CoreDataClass.swift create mode 100644 Adamant/CoreData/CoinTransaction+CoreDataProperties.swift create mode 100644 Adamant/ServiceProtocols/CoinStorage.swift create mode 100644 Adamant/Services/AdamantCoinStorageService.swift diff --git a/Adamant.xcodeproj/project.pbxproj b/Adamant.xcodeproj/project.pbxproj index b476fc0d4..f9ea9f530 100644 --- a/Adamant.xcodeproj/project.pbxproj +++ b/Adamant.xcodeproj/project.pbxproj @@ -7,6 +7,10 @@ objects = { /* Begin PBXBuildFile section */ + 3A2F55F92AC6F308000A3F26 /* CoinTransaction+CoreDataClass.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3A2F55F72AC6F308000A3F26 /* CoinTransaction+CoreDataClass.swift */; }; + 3A2F55FA2AC6F308000A3F26 /* CoinTransaction+CoreDataProperties.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3A2F55F82AC6F308000A3F26 /* CoinTransaction+CoreDataProperties.swift */; }; + 3A2F55FC2AC6F885000A3F26 /* CoinStorage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3A2F55FB2AC6F885000A3F26 /* CoinStorage.swift */; }; + 3A2F55FE2AC6F90E000A3F26 /* AdamantCoinStorageService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3A2F55FD2AC6F90E000A3F26 /* AdamantCoinStorageService.swift */; }; 3A33F9FA2A7A53DA002B8003 /* EmojiUpdateType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3A33F9F92A7A53DA002B8003 /* EmojiUpdateType.swift */; }; 3A41938F2A580C57006A6B22 /* AdamantRichTransactionReactService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3A41938E2A580C57006A6B22 /* AdamantRichTransactionReactService.swift */; }; 3A4193912A580C85006A6B22 /* RichTransactionReactService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3A4193902A580C85006A6B22 /* RichTransactionReactService.swift */; }; @@ -587,6 +591,10 @@ /* Begin PBXFileReference section */ 33975C0D891698AA7E74EBCC /* Pods_Adamant.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Adamant.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 36AB8CE9537B3B873972548B /* Pods_AdmCore.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_AdmCore.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + 3A2F55F72AC6F308000A3F26 /* CoinTransaction+CoreDataClass.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "CoinTransaction+CoreDataClass.swift"; sourceTree = ""; }; + 3A2F55F82AC6F308000A3F26 /* CoinTransaction+CoreDataProperties.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "CoinTransaction+CoreDataProperties.swift"; sourceTree = ""; }; + 3A2F55FB2AC6F885000A3F26 /* CoinStorage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CoinStorage.swift; sourceTree = ""; }; + 3A2F55FD2AC6F90E000A3F26 /* AdamantCoinStorageService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AdamantCoinStorageService.swift; sourceTree = ""; }; 3A33F9F92A7A53DA002B8003 /* EmojiUpdateType.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EmojiUpdateType.swift; sourceTree = ""; }; 3A41938E2A580C57006A6B22 /* AdamantRichTransactionReactService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AdamantRichTransactionReactService.swift; sourceTree = ""; }; 3A4193902A580C85006A6B22 /* RichTransactionReactService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RichTransactionReactService.swift; sourceTree = ""; }; @@ -1659,6 +1667,7 @@ 3A7BD00D2AA9BCE80045AAB0 /* VibroService.swift */, 41C1698B29E7F34900FEB3CB /* RichTransactionReplyService.swift */, 3A4193902A580C85006A6B22 /* RichTransactionReactService.swift */, + 3A2F55FB2AC6F885000A3F26 /* CoinStorage.swift */, ); path = ServiceProtocols; sourceTree = ""; @@ -1691,6 +1700,7 @@ E9771D9D22997A6F0099AAC7 /* NativeCore+AdamantCore.swift */, 550066C6284D682D0044C0B1 /* AdamantHealthCheckService.swift */, 9304F8C3292F8A3100173F18 /* AdamantPushNotificationsTokenService.swift */, + 3A2F55FD2AC6F90E000A3F26 /* AdamantCoinStorageService.swift */, ); path = Services; sourceTree = ""; @@ -2013,6 +2023,8 @@ E908471F2196FEA80095825D /* CoreDataAccount+CoreDataProperties.swift */, E90847282196FEA80095825D /* Chatroom+CoreDataClass.swift */, E90847292196FEA80095825D /* Chatroom+CoreDataProperties.swift */, + 3A2F55F72AC6F308000A3F26 /* CoinTransaction+CoreDataClass.swift */, + 3A2F55F82AC6F308000A3F26 /* CoinTransaction+CoreDataProperties.swift */, E90847192196FE590095825D /* Adamant.xcdatamodeld */, ); path = CoreData; @@ -2793,6 +2805,7 @@ 4153045929C09902000E4BEA /* AdamantIncreaseFeeService.swift in Sources */, E90A494B204D9EB8009F6A65 /* AdamantAuthentication.swift in Sources */, E9215973206119FB0000CA5C /* ReachabilityMonitor.swift in Sources */, + 3A2F55FC2AC6F885000A3F26 /* CoinStorage.swift in Sources */, E91947B420002809001362F8 /* AdamantAccount.swift in Sources */, 6449BA6C235CA0930033B936 /* ERC20WalletViewController.swift in Sources */, E9E7CDC22003F5A400DFC4DB /* TransactionsListViewControllerBase.swift in Sources */, @@ -2911,6 +2924,7 @@ 4133AF242A1CE1A3001A0A1E /* UITableView+Adamant.swift in Sources */, 41A1995629D56EAA0031AD75 /* ChatMessageReplyCell+Model.swift in Sources */, E90847312196FEA80095825D /* ChatTransaction+CoreDataProperties.swift in Sources */, + 3A2F55FA2AC6F308000A3F26 /* CoinTransaction+CoreDataProperties.swift in Sources */, E99330262136B0E500CD5200 /* TransferViewControllerBase+QR.swift in Sources */, E9B1AA5B21283E0F00080A2A /* AdmTransferViewController.swift in Sources */, 648C697322916192006645F5 /* DashTransactionsViewController.swift in Sources */, @@ -2922,6 +2936,7 @@ 3A7BD0192AA9BF3F0045AAB0 /* VibrationSelectionViewModel.swift in Sources */, E9FAE5DA203DBFEF008D3A6B /* Comparable+clamped.swift in Sources */, 93A91FD329799298001DB1F8 /* ChatStartPosition.swift in Sources */, + 3A2F55F92AC6F308000A3F26 /* CoinTransaction+CoreDataClass.swift in Sources */, E94008802114EE2000CD2D67 /* AdmWallet.swift in Sources */, 93A91FD1297972B7001DB1F8 /* ChatScrollDownButton.swift in Sources */, 41C1698C29E7F34900FEB3CB /* RichTransactionReplyService.swift in Sources */, @@ -3032,6 +3047,7 @@ E90055FB20ECE78A00D0CB2D /* SecurityViewController+notifications.swift in Sources */, 938F7D662955C966001915CA /* ChatInputBar.swift in Sources */, A50A41122822FC35006BDFE1 /* BtcWalletService+RichMessageProvider.swift in Sources */, + 3A2F55FE2AC6F90E000A3F26 /* AdamantCoinStorageService.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; diff --git a/Adamant/CoreData/Adamant.xcdatamodeld/Adamant.xcdatamodel/contents b/Adamant/CoreData/Adamant.xcdatamodeld/Adamant.xcdatamodel/contents index bce0ebf0e..cf85baf62 100644 --- a/Adamant/CoreData/Adamant.xcdatamodeld/Adamant.xcdatamodel/contents +++ b/Adamant/CoreData/Adamant.xcdatamodeld/Adamant.xcdatamodel/contents @@ -8,26 +8,15 @@ - - + - - - - - - - - - - @@ -50,6 +39,20 @@ + + + + + + + + + + + + + + diff --git a/Adamant/CoreData/BaseTransaction+CoreDataClass.swift b/Adamant/CoreData/BaseTransaction+CoreDataClass.swift index 084cd19fa..e68bb1073 100644 --- a/Adamant/CoreData/BaseTransaction+CoreDataClass.swift +++ b/Adamant/CoreData/BaseTransaction+CoreDataClass.swift @@ -11,7 +11,7 @@ import Foundation import CoreData @objc(BaseTransaction) -public class BaseTransaction: NSManagedObject { +public class BaseTransaction: CoinTransaction { var transactionStatus: TransactionStatus? { return nil } diff --git a/Adamant/CoreData/BaseTransaction+CoreDataProperties.swift b/Adamant/CoreData/BaseTransaction+CoreDataProperties.swift index bbb300c29..280c2c5c3 100644 --- a/Adamant/CoreData/BaseTransaction+CoreDataProperties.swift +++ b/Adamant/CoreData/BaseTransaction+CoreDataProperties.swift @@ -16,17 +16,11 @@ extension BaseTransaction { return NSFetchRequest(entityName: "BaseTransaction") } - @NSManaged public var amount: NSDecimalNumber? @NSManaged public var blockId: String? @NSManaged public var confirmations: Int64 - @NSManaged public var date: NSDate? @NSManaged public var fee: NSDecimalNumber? @NSManaged public var height: Int64 @NSManaged public var isConfirmed: Bool - @NSManaged public var isOutgoing: Bool - @NSManaged public var recipientId: String? - @NSManaged public var senderId: String? - @NSManaged public var transactionId: String @NSManaged public var type: Int16 @NSManaged public var partner: BaseAccount? @NSManaged public var senderPublicKey: String? diff --git a/Adamant/CoreData/CoinTransaction+CoreDataClass.swift b/Adamant/CoreData/CoinTransaction+CoreDataClass.swift new file mode 100644 index 000000000..626606434 --- /dev/null +++ b/Adamant/CoreData/CoinTransaction+CoreDataClass.swift @@ -0,0 +1,16 @@ +// +// CoinTransaction+CoreDataClass.swift +// Adamant +// +// Created by Stanislav Jelezoglo on 26.09.2023. +// Copyright © 2023 Adamant. All rights reserved. +// +// + +import Foundation +import CoreData + +@objc(CoinTransaction) +public class CoinTransaction: NSManagedObject { + static let entityCoinName = "CoinTransaction" +} diff --git a/Adamant/CoreData/CoinTransaction+CoreDataProperties.swift b/Adamant/CoreData/CoinTransaction+CoreDataProperties.swift new file mode 100644 index 000000000..f2dc8da7e --- /dev/null +++ b/Adamant/CoreData/CoinTransaction+CoreDataProperties.swift @@ -0,0 +1,32 @@ +// +// CoinTransaction+CoreDataProperties.swift +// Adamant +// +// Created by Stanislav Jelezoglo on 26.09.2023. +// Copyright © 2023 Adamant. All rights reserved. +// +// + +import Foundation +import CoreData + +extension CoinTransaction { + + @nonobjc public class func fetchRequest() -> NSFetchRequest { + return NSFetchRequest(entityName: "CoinTransaction") + } + + @NSManaged public var amount: NSDecimalNumber? + @NSManaged public var transactionId: String + @NSManaged public var coinId: String? + @NSManaged public var senderId: String? + @NSManaged public var recipientId: String? + @NSManaged public var date: NSDate? + @NSManaged public var isOutgoing: Bool + +// public var transactionStatus: TransactionStatus? +} + +extension CoinTransaction : Identifiable { + +} diff --git a/Adamant/ServiceProtocols/CoinStorage.swift b/Adamant/ServiceProtocols/CoinStorage.swift new file mode 100644 index 000000000..0331cedf6 --- /dev/null +++ b/Adamant/ServiceProtocols/CoinStorage.swift @@ -0,0 +1,14 @@ +// +// CoinStorage.swift +// Adamant +// +// Created by Stanislav Jelezoglo on 26.09.2023. +// Copyright © 2023 Adamant. All rights reserved. +// + +import Foundation + +protocol CoinStorageService: AnyObject { + func append(_ transaction: TransactionDetails) + func append(_ transactions: [TransactionDetails]) +} diff --git a/Adamant/Services/AdamantCoinStorageService.swift b/Adamant/Services/AdamantCoinStorageService.swift new file mode 100644 index 000000000..942052336 --- /dev/null +++ b/Adamant/Services/AdamantCoinStorageService.swift @@ -0,0 +1,84 @@ +// +// AdamantCoinStorageService.swift +// Adamant +// +// Created by Stanislav Jelezoglo on 26.09.2023. +// Copyright © 2023 Adamant. All rights reserved. +// + +import Foundation +import CoreData +import Combine +import CommonKit + +final class AdamantCoinStorageService: NSObject, CoinStorageService { + + // MARK: Proprieties + + private let coinId: String + private let coreDataStack: CoreDataStack + private lazy var transactionController = getTransactionController() + + @ObservableValue private(set) var transactions: [CoinTransaction] = [] + + // MARK: Init + + init(coinId: String, coreDataStack: CoreDataStack) { + self.coinId = coinId + self.coreDataStack = coreDataStack + super.init() + + try? transactionController.performFetch() + transactions = transactionController.fetchedObjects ?? [] + } + + func append(_ transaction: TransactionDetails) { + append([transaction]) + } + + func append(_ transactions: [TransactionDetails]) { + let privateContext = coreDataStack.container.viewContext + privateContext.mergePolicy = NSMergePolicy(merge: NSMergePolicyType.mergeByPropertyObjectTrumpMergePolicyType) + + var coinTransactions: [CoinTransaction] = [] + + transactions.forEach { transaction in + let coinTransaction = CoinTransaction(context: privateContext) + coinTransaction.amount = NSDecimalNumber(decimal: transaction.amountValue ?? 0) + coinTransaction.date = transaction.dateValue as? NSDate + coinTransaction.recipientId = transaction.recipientAddress + coinTransaction.senderId = transaction.senderAddress + coinTransaction.isOutgoing = transaction.isOutgoing + coinTransaction.coinId = coinId + coinTransaction.transactionId = transaction.txId + + coinTransactions.append(coinTransaction) + } + + try? privateContext.save() + + self.transactions.append(contentsOf: coinTransactions) + } +} + +private extension AdamantCoinStorageService { + func getTransactionController() -> NSFetchedResultsController { + let request: NSFetchRequest = NSFetchRequest( + entityName: CoinTransaction.entityCoinName + ) + request.predicate = NSCompoundPredicate(andPredicateWithSubpredicates: [ + NSPredicate(format: "coinId = %@", coinId) + ]) + request.sortDescriptors = [ + NSSortDescriptor(key: "date", ascending: true), + NSSortDescriptor(key: "transactionId", ascending: true) + ] + + return NSFetchedResultsController( + fetchRequest: request, + managedObjectContext: coreDataStack.container.viewContext, + sectionNameKeyPath: nil, + cacheName: nil + ) + } +} diff --git a/Adamant/Wallets/Adamant/AdmTransactionsViewController.swift b/Adamant/Wallets/Adamant/AdmTransactionsViewController.swift index 120d3ce43..236e36bf7 100644 --- a/Adamant/Wallets/Adamant/AdmTransactionsViewController.swift +++ b/Adamant/Wallets/Adamant/AdmTransactionsViewController.swift @@ -16,7 +16,6 @@ class AdmTransactionsViewController: TransactionsListViewControllerBase { var accountService: AccountService var transfersProvider: TransfersProvider var chatsProvider: ChatsProvider - var dialogService: DialogService var stack: CoreDataStack var router: Router var addressBookService: AddressBookService @@ -49,12 +48,12 @@ class AdmTransactionsViewController: TransactionsListViewControllerBase { self.accountService = accountService self.transfersProvider = transfersProvider self.chatsProvider = chatsProvider - self.dialogService = dialogService self.stack = stack self.router = router self.addressBookService = addressBookService super.init(nibName: nibNameOrNil, bundle: nibBundleOrNil) + self.dialogService = dialogService } required init?(coder: NSCoder) { diff --git a/Adamant/Wallets/Adamant/AdmWalletService.swift b/Adamant/Wallets/Adamant/AdmWalletService.swift index 8c8b98c03..60fc7aaaa 100644 --- a/Adamant/Wallets/Adamant/AdmWalletService.swift +++ b/Adamant/Wallets/Adamant/AdmWalletService.swift @@ -58,6 +58,7 @@ class AdmWalletService: NSObject, WalletService { var apiService: ApiService! var transfersProvider: TransfersProvider! var router: Router! + var coreDataStack: CoreDataStack! // MARK: - Notifications let walletUpdatedNotification = Notification.Name("adamant.admWallet.updated") @@ -84,6 +85,22 @@ class AdmWalletService: NSObject, WalletService { private (set) var isWarningGasPrice = false private var subscriptions = Set() + @Published private(set) var transactions: [CoinTransaction] = [] + @Published private(set) var hasMoreOldTransactions: Bool = true + + var transactionsPublisher: Published<[CoinTransaction]>.Publisher { + $transactions + } + + var hasMoreOldTransactionsPublisher: Published.Publisher { + $hasMoreOldTransactions + } + + lazy var coinStorage = AdamantCoinStorageService( + coinId: tokenUnicID, + coreDataStack: coreDataStack + ) + // MARK: - State private (set) var state: WalletServiceState = .upToDate private (set) var wallet: WalletAccount? @@ -122,6 +139,15 @@ class AdmWalletService: NSObject, WalletService { .store(in: &subscriptions) } + func addTransactionObserver() { + coinStorage.$transactions + .removeDuplicates() + .sink { [weak self] transactions in + self?.transactions = transactions + } + .store(in: &subscriptions) + } + func update() { guard let accountService = accountService, let account = accountService.account else { wallet = nil @@ -168,6 +194,9 @@ class AdmWalletService: NSObject, WalletService { func getWalletAddress(byAdamantAddress address: String) async throws -> String { return address } + + func loadTransactions(offset: Int, limit: Int) async throws { + } } extension AdmWalletService: WalletServiceWithTransfers { @@ -198,7 +227,9 @@ extension AdmWalletService: SwinjectDependentService { apiService = container.resolve(ApiService.self) transfersProvider = container.resolve(TransfersProvider.self) router = container.resolve(Router.self) + coreDataStack = container.resolve(CoreDataStack.self) + addTransactionObserver() Task { let controller = await transfersProvider.unreadTransfersController() diff --git a/Adamant/Wallets/Bitcoin/BtcTransactionsViewController.swift b/Adamant/Wallets/Bitcoin/BtcTransactionsViewController.swift index 44c49091e..526ff76d3 100644 --- a/Adamant/Wallets/Bitcoin/BtcTransactionsViewController.swift +++ b/Adamant/Wallets/Bitcoin/BtcTransactionsViewController.swift @@ -14,56 +14,16 @@ class BtcTransactionsViewController: TransactionsListViewControllerBase { // MARK: - Dependencies var btcWalletService: BtcWalletService! - var dialogService: DialogService! var router: Router! var addressBook: AddressBookService! - // MARK: - Properties - var transactions: [BtcTransaction] = [] - override func viewDidLoad() { super.viewDidLoad() - updateLoadingView(isHidden: false) currencySymbol = BtcWalletService.currencySymbol - handleRefresh() - } - - override func handleRefresh() { - transactions.removeAll() - tableView.reloadData() - loadData(silent: false) - } - - override func loadData(silent: Bool) { - isBusy = true - - Task { @MainActor in - do { - let trs = try await btcWalletService.getTransactions(fromTx: transactions.last?.txId) - transactions.append(contentsOf: trs) - isNeedToLoadMoore = trs.count > 0 - } catch { - isNeedToLoadMoore = false - if !silent { - dialogService.showRichError(error: error) - } - } - - isBusy = false - emptyLabel.isHidden = transactions.count > 0 - stopBottomIndicator() - refreshControl.endRefreshing() - tableView.reloadData() - updateLoadingView(isHidden: true) - }.stored(in: taskManager) } // MARK: - UITableView - override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { - return transactions.count - } - func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { tableView.deselectRow(at: indexPath, animated: true) @@ -73,56 +33,31 @@ class BtcTransactionsViewController: TransactionsListViewControllerBase { return } - controller.transaction = transaction + let emptyTransaction = SimpleTransactionDetails( + txId: transaction.transactionId, + senderAddress: transaction.senderId ?? "", + recipientAddress: transaction.recipientId ?? "", + dateValue: transaction.date as? Date, + amountValue: transaction.amount?.decimalValue, + feeValue: nil, + confirmationsValue: nil, + blockValue: nil, + isOutgoing: transaction.isOutgoing, + transactionStatus: nil + ) + + controller.transaction = emptyTransaction controller.service = btcWalletService if let address = btcWalletService.wallet?.address { - if transaction.senderAddress.caseInsensitiveCompare(address) == .orderedSame { + if transaction.senderId?.caseInsensitiveCompare(address) == .orderedSame { controller.senderName = String.adamant.transactionDetails.yourAddress } - if transaction.recipientAddress.caseInsensitiveCompare(address) == .orderedSame { + if transaction.recipientId?.caseInsensitiveCompare(address) == .orderedSame { controller.recipientName = String.adamant.transactionDetails.yourAddress } } navigationController?.pushViewController(controller, animated: true) } - - override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { - guard let cell = tableView.dequeueReusableCell(withIdentifier: cellIdentifierCompact, for: indexPath) as? TransactionTableViewCell else { - // TODO: Display & Log error - return UITableViewCell(style: .default, reuseIdentifier: "cell") - } - - let transaction = transactions[indexPath.row] - - cell.accessoryType = .disclosureIndicator - cell.separatorInset = indexPath.row == transactions.count - 1 - ? .zero - : UITableView.defaultTransactionsSeparatorInset - - configureCell(cell, for: transaction) - return cell - } - - func configureCell(_ cell: TransactionTableViewCell, for transaction: BtcTransaction) { - let outgoing = transaction.isOutgoing - let partnerId = outgoing ? transaction.recipientAddress : transaction.senderAddress - - var partnerName: String? - if let address = btcWalletService.wallet?.address { - if partnerId == address { - partnerName = String.adamant.transactionDetails.yourAddress - } else { - partnerName = addressBook.getName(for: address) - } - } - - configureCell(cell, - isOutgoing: outgoing, - partnerId: partnerId, - partnerName: partnerName, - amount: transaction.amountValue ?? 0, - date: transaction.dateValue) - } } diff --git a/Adamant/Wallets/Bitcoin/BtcTransferViewController.swift b/Adamant/Wallets/Bitcoin/BtcTransferViewController.swift index cd800f8c4..74875e767 100644 --- a/Adamant/Wallets/Bitcoin/BtcTransferViewController.swift +++ b/Adamant/Wallets/Bitcoin/BtcTransferViewController.swift @@ -58,6 +58,7 @@ final class BtcTransferViewController: TransferViewControllerBase { Task { do { + service.coinStorage.append(transaction) try await service.sendTransaction(transaction) } catch { dialogService.showRichError(error: error) diff --git a/Adamant/Wallets/Bitcoin/BtcWalletService.swift b/Adamant/Wallets/Bitcoin/BtcWalletService.swift index 61c74dc4c..aa10d7351 100644 --- a/Adamant/Wallets/Bitcoin/BtcWalletService.swift +++ b/Adamant/Wallets/Bitcoin/BtcWalletService.swift @@ -120,6 +120,7 @@ final class BtcWalletService: WalletService { var router: Router! var increaseFeeService: IncreaseFeeService! var addressConverter: AddressConverter! + var coreDataStack: CoreDataStack! // MARK: - Constants static var currencyLogo = UIImage.asset(named: "bitcoin_wallet") ?? .init() @@ -158,6 +159,22 @@ final class BtcWalletService: WalletService { private var subscriptions = Set() + @Published private(set) var transactions: [CoinTransaction] = [] + @Published private(set) var hasMoreOldTransactions: Bool = true + + var transactionsPublisher: Published<[CoinTransaction]>.Publisher { + $transactions + } + + var hasMoreOldTransactionsPublisher: Published.Publisher { + $hasMoreOldTransactions + } + + lazy var coinStorage = AdamantCoinStorageService( + coinId: tokenUnicID, + coreDataStack: coreDataStack + ) + // MARK: - State private (set) var state: WalletServiceState = .notInitiated @@ -214,6 +231,15 @@ final class BtcWalletService: WalletService { .store(in: &subscriptions) } + func addTransactionObserver() { + coinStorage.$transactions + .removeDuplicates() + .sink { [weak self] transactions in + self?.transactions = transactions + } + .store(in: &subscriptions) + } + func update() { Task { await update() @@ -448,6 +474,9 @@ extension BtcWalletService: SwinjectDependentService { router = container.resolve(Router.self) increaseFeeService = container.resolve(IncreaseFeeService.self) addressConverter = container.resolve(AddressConverterFactory.self)?.make(network: network) + coreDataStack = container.resolve(CoreDataStack.self) + + addTransactionObserver() } } @@ -705,6 +734,20 @@ extension BtcWalletService { } } + func loadTransactions(offset: Int, limit: Int) async throws { + let txId = offset == .zero + ? transactions.first?.transactionId + : transactions.last?.transactionId + + let trs = try await getTransactions(fromTx: txId) + + guard trs.count > 0 else { + hasMoreOldTransactions = false + return + } + + coinStorage.append(trs) + } } extension BtcWalletService: WalletServiceWithTransfers { @@ -713,6 +756,7 @@ extension BtcWalletService: WalletServiceWithTransfers { fatalError("Can't get BtcTransactionsViewController") } + vc.walletService = self vc.btcWalletService = self return vc } diff --git a/Adamant/Wallets/Dash/DashTransactionsViewController.swift b/Adamant/Wallets/Dash/DashTransactionsViewController.swift index 239b5ab93..aebe7377b 100644 --- a/Adamant/Wallets/Dash/DashTransactionsViewController.swift +++ b/Adamant/Wallets/Dash/DashTransactionsViewController.swift @@ -12,109 +12,40 @@ import ProcedureKit class DashTransactionsViewController: TransactionsListViewControllerBase { // MARK: - Dependencies - var walletService: DashWalletService! - var dialogService: DialogService! + var dashWalletService: DashWalletService! var router: Router! - // MARK: - Properties - var transactions: [DashTransaction] = [] - private var allTransactionsIds: [String] = [] - private var offset = 0 - private var maxPerRequest = 25 - override func viewDidLoad() { super.viewDidLoad() - updateLoadingView(isHidden: false) currencySymbol = DashWalletService.currencySymbol - handleRefresh() - } - - override func handleRefresh() { - transactions.removeAll() - tableView.reloadData() - allTransactionsIds.removeAll() - offset = 0 - - loadData(silent: true) - } - - override func loadData(silent: Bool) { - guard let address = walletService.wallet?.address else { - transactions = [] - return - } - - isBusy = true - emptyLabel.isHidden = true - - Task { @MainActor in - do { - if allTransactionsIds.isEmpty { - allTransactionsIds = try await walletService.requestTransactionsIds(for: address).reversed() - } - - let availableToLoad = allTransactionsIds.count - offset - - let maxPerRequest = availableToLoad > maxPerRequest - ? maxPerRequest - : availableToLoad - - let ids = Array(allTransactionsIds[offset..<(offset + maxPerRequest)]) - - let trs = try await walletService.getTransactions(by: ids) - - transactions.append(contentsOf: trs) - offset += trs.count - isNeedToLoadMoore = allTransactionsIds.count - offset > 0 - } catch { - isNeedToLoadMoore = false - - if !silent { - dialogService.showRichError(error: error) - } - } - - isBusy = false - emptyLabel.isHidden = transactions.count > 0 - stopBottomIndicator() - refreshControl.endRefreshing() - tableView.reloadData() - updateLoadingView(isHidden: true) - }.stored(in: taskManager) } // MARK: - UITableView - override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { - return transactions.count - } - func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { guard let controller = router.get(scene: AdamantScene.Wallets.Dash.transactionDetails) as? DashTransactionDetailsViewController else { fatalError("Failed to getDashTransactionDetailsViewController") } - + // Hold reference - guard let address = walletService.wallet?.address else { + guard let address = dashWalletService.wallet?.address else { return } - - controller.service = self.walletService - let transaction = transactions[indexPath.row] + controller.service = self.dashWalletService - let isOutgoing: Bool = transaction.recipientAddress != address + let transaction = transactions[indexPath.row] let emptyTransaction = SimpleTransactionDetails( - txId: transaction.txId, - senderAddress: transaction.senderAddress, - recipientAddress: transaction.recipientAddress, - dateValue: nil, - amountValue: transaction.amountValue, + txId: transaction.transactionId, + senderAddress: transaction.senderId ?? "", + recipientAddress: transaction.recipientId ?? "", + dateValue: transaction.date as? Date, + amountValue: transaction.amount?.decimalValue, feeValue: nil, confirmationsValue: nil, blockValue: nil, - isOutgoing: isOutgoing, + isOutgoing: transaction.isOutgoing, transactionStatus: nil ) @@ -130,42 +61,6 @@ class DashTransactionsViewController: TransactionsListViewControllerBase { navigationController?.pushViewController(controller, animated: true) } - - override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { - guard let cell = tableView.dequeueReusableCell(withIdentifier: cellIdentifierCompact, for: indexPath) as? TransactionTableViewCell else { - // TODO: Display & Log error - return UITableViewCell(style: .default, reuseIdentifier: "cell") - } - - let transaction = transactions[indexPath.row] - - cell.accessoryType = .disclosureIndicator - cell.separatorInset = indexPath.row == transactions.count - 1 - ? .zero - : UITableView.defaultTransactionsSeparatorInset - - configureCell(cell, for: transaction) - return cell - } - - func configureCell(_ cell: TransactionTableViewCell, for transaction: BaseBtcTransaction) { - let outgoing = transaction.isOutgoing - let partnerId = outgoing ? transaction.recipientAddress : transaction.senderAddress - - let partnerName: String? - if let address = walletService.wallet?.address, partnerId == address { - partnerName = String.adamant.transactionDetails.yourAddress - } else { - partnerName = nil - } - - configureCell(cell, - isOutgoing: outgoing, - partnerId: partnerId, - partnerName: partnerName, - amount: transaction.amountValue ?? 0, - date: transaction.dateValue) - } } private class LoadMoreDashTransactionsProcedure: Procedure { diff --git a/Adamant/Wallets/Dash/DashTransferViewController.swift b/Adamant/Wallets/Dash/DashTransferViewController.swift index a7c954185..2ceae87ac 100644 --- a/Adamant/Wallets/Dash/DashTransferViewController.swift +++ b/Adamant/Wallets/Dash/DashTransferViewController.swift @@ -64,6 +64,7 @@ final class DashTransferViewController: TransferViewControllerBase { Task { do { + service.coinStorage.append(transaction) try await service.sendTransaction(transaction) } catch { dialogService.showRichError(error: error) diff --git a/Adamant/Wallets/Dash/DashWalletService.swift b/Adamant/Wallets/Dash/DashWalletService.swift index aeeb0084b..a3628da27 100644 --- a/Adamant/Wallets/Dash/DashWalletService.swift +++ b/Adamant/Wallets/Dash/DashWalletService.swift @@ -64,6 +64,7 @@ final class DashWalletService: WalletService { var dialogService: DialogService! var router: Router! var addressConverter: AddressConverter! + var coreDataStack: CoreDataStack! // MARK: - Constants static var currencyLogo = UIImage.asset(named: "dash_wallet") ?? .init() @@ -135,6 +136,22 @@ final class DashWalletService: WalletService { static let jsonDecoder = JSONDecoder() private var subscriptions = Set() + @Published private(set) var historyTransactions: [CoinTransaction] = [] + @Published private(set) var hasMoreOldTransactions: Bool = true + + var transactionsPublisher: Published<[CoinTransaction]>.Publisher { + $historyTransactions + } + + var hasMoreOldTransactionsPublisher: Published.Publisher { + $hasMoreOldTransactions + } + + lazy var coinStorage = AdamantCoinStorageService( + coinId: tokenUnicID, + coreDataStack: coreDataStack + ) + // MARK: - State private (set) var state: WalletServiceState = .notInitiated @@ -192,6 +209,15 @@ final class DashWalletService: WalletService { .store(in: &subscriptions) } + func addTransactionObserver() { + coinStorage.$transactions + .removeDuplicates() + .sink { [weak self] transactions in + self?.historyTransactions = transactions + } + .store(in: &subscriptions) + } + func update() { Task { await update() @@ -340,6 +366,9 @@ extension DashWalletService: SwinjectDependentService { router = container.resolve(Router.self) addressConverter = container.resolve(AddressConverterFactory.self)? .make(network: network) + coreDataStack = container.resolve(CoreDataStack.self) + + addTransactionObserver() } } @@ -414,6 +443,35 @@ extension DashWalletService { ) } } + + func loadTransactions(offset: Int, limit: Int) async throws { + guard let address = wallet?.address else { + return + } + + let allTransactionsIds = try await requestTransactionsIds(for: address).reversed() + + let availableToLoad = allTransactionsIds.count - offset + + let maxPerRequest = availableToLoad > limit + ? limit + : availableToLoad + + //let ids = Array(allTransactionsIds[offset..<(offset + maxPerRequest)]) + + let startIndex = allTransactionsIds.index(allTransactionsIds.startIndex, offsetBy: offset) + let endIndex = allTransactionsIds.index(startIndex, offsetBy: maxPerRequest) + let ids = Array(allTransactionsIds[startIndex.. 0 else { + hasMoreOldTransactions = false + return + } + + coinStorage.append(trs) + } } // MARK: - KVS @@ -489,6 +547,7 @@ extension DashWalletService: WalletServiceWithTransfers { } vc.walletService = self + vc.dashWalletService = self return vc } } diff --git a/Adamant/Wallets/Doge/DogeTransactionsViewController.swift b/Adamant/Wallets/Doge/DogeTransactionsViewController.swift index 37beae152..b5ee65b94 100644 --- a/Adamant/Wallets/Doge/DogeTransactionsViewController.swift +++ b/Adamant/Wallets/Doge/DogeTransactionsViewController.swift @@ -13,86 +13,40 @@ import CommonKit class DogeTransactionsViewController: TransactionsListViewControllerBase { // MARK: - Dependencies - var walletService: DogeWalletService! - var dialogService: DialogService! + var dogeWalletService: DogeWalletService! var router: Router! - // MARK: - Properties - var transactions: [DogeTransaction] = [] - private var offset = 0 - override func viewDidLoad() { super.viewDidLoad() - updateLoadingView(isHidden: false) currencySymbol = DogeWalletService.currencySymbol - handleRefresh() - } - - override func handleRefresh() { - offset = 0 - transactions.removeAll() - tableView.reloadData() - loadData(silent: false) - } - - override func loadData(silent: Bool) { - isBusy = true - - Task { - do { - let tuple = try await walletService.getTransactions(from: offset) - transactions.append(contentsOf: tuple.transactions) - offset += tuple.transactions.count - isNeedToLoadMoore = tuple.hasMore - } catch { - isNeedToLoadMoore = false - - if !silent { - dialogService.showRichError(error: error) - } - } - - isBusy = false - emptyLabel.isHidden = transactions.count > 0 - stopBottomIndicator() - refreshControl.endRefreshing() - tableView.reloadData() - updateLoadingView(isHidden: true) - }.stored(in: taskManager) } // MARK: - UITableView - override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { - return transactions.count - } - func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { guard let controller = router.get(scene: AdamantScene.Wallets.Doge.transactionDetails) as? DogeTransactionDetailsViewController else { fatalError("Failed to get DogeTransactionDetailsViewController") } // Hold reference - guard let address = walletService.wallet?.address else { + guard let address = dogeWalletService.wallet?.address else { return } - controller.service = self.walletService + controller.service = self.dogeWalletService let transaction = transactions[indexPath.row] - let isOutgoing: Bool = transaction.recipientAddress != address - let emptyTransaction = SimpleTransactionDetails( - txId: transaction.txId, - senderAddress: transaction.senderAddress, - recipientAddress: transaction.recipientAddress, - dateValue: nil, - amountValue: transaction.amountValue, + txId: transaction.transactionId, + senderAddress: transaction.senderId ?? "", + recipientAddress: transaction.recipientId ?? "", + dateValue: transaction.date as? Date, + amountValue: transaction.amount?.decimalValue, feeValue: nil, confirmationsValue: nil, blockValue: nil, - isOutgoing: isOutgoing, + isOutgoing: transaction.isOutgoing, transactionStatus: nil ) @@ -108,42 +62,6 @@ class DogeTransactionsViewController: TransactionsListViewControllerBase { navigationController?.pushViewController(controller, animated: true) } - - override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { - guard let cell = tableView.dequeueReusableCell(withIdentifier: cellIdentifierCompact, for: indexPath) as? TransactionTableViewCell else { - // TODO: Display & Log error - return UITableViewCell(style: .default, reuseIdentifier: "cell") - } - - let transaction = transactions[indexPath.row] - - cell.accessoryType = .disclosureIndicator - cell.separatorInset = indexPath.row == transactions.count - 1 - ? .zero - : UITableView.defaultTransactionsSeparatorInset - - configureCell(cell, for: transaction) - return cell - } - - func configureCell(_ cell: TransactionTableViewCell, for transaction: DogeTransaction) { - let outgoing = transaction.isOutgoing - let partnerId = outgoing ? transaction.recipientAddress : transaction.senderAddress - - let partnerName: String? - if let address = walletService.wallet?.address, partnerId == address { - partnerName = String.adamant.transactionDetails.yourAddress - } else { - partnerName = nil - } - - configureCell(cell, - isOutgoing: outgoing, - partnerId: partnerId, - partnerName: partnerName, - amount: transaction.amountValue ?? 0, - date: transaction.dateValue) - } } private class LoadMoreDogeTransactionsProcedure: Procedure { diff --git a/Adamant/Wallets/Doge/DogeTransferViewController.swift b/Adamant/Wallets/Doge/DogeTransferViewController.swift index 2867d5d4b..9baf4d24d 100644 --- a/Adamant/Wallets/Doge/DogeTransferViewController.swift +++ b/Adamant/Wallets/Doge/DogeTransferViewController.swift @@ -55,6 +55,7 @@ final class DogeTransferViewController: TransferViewControllerBase { Task { do { + service.coinStorage.append(transaction) try await service.sendTransaction(transaction) } catch { dialogService.showRichError(error: error) diff --git a/Adamant/Wallets/Doge/DogeWalletService.swift b/Adamant/Wallets/Doge/DogeWalletService.swift index e3af97d18..bfe05b8cb 100644 --- a/Adamant/Wallets/Doge/DogeWalletService.swift +++ b/Adamant/Wallets/Doge/DogeWalletService.swift @@ -60,6 +60,7 @@ class DogeWalletService: WalletService { var dialogService: DialogService! var router: Router! var addressConverter: AddressConverter! + var coreDataStack: CoreDataStack! // MARK: - Constants static var currencyLogo = UIImage.asset(named: "doge_wallet") ?? .init() @@ -125,6 +126,22 @@ class DogeWalletService: WalletService { private static let jsonDecoder = JSONDecoder() private var subscriptions = Set() + @Published private(set) var historyTransactions: [CoinTransaction] = [] + @Published private(set) var hasMoreOldTransactions: Bool = true + + var transactionsPublisher: Published<[CoinTransaction]>.Publisher { + $historyTransactions + } + + var hasMoreOldTransactionsPublisher: Published.Publisher { + $hasMoreOldTransactions + } + + lazy var coinStorage = AdamantCoinStorageService( + coinId: tokenUnicID, + coreDataStack: coreDataStack + ) + // MARK: - State private (set) var state: WalletServiceState = .notInitiated @@ -182,6 +199,15 @@ class DogeWalletService: WalletService { .store(in: &subscriptions) } + func addTransactionObserver() { + coinStorage.$transactions + .removeDuplicates() + .sink { [weak self] transactions in + self?.historyTransactions = transactions + } + .store(in: &subscriptions) + } + func update() { Task { await update() @@ -321,6 +347,9 @@ extension DogeWalletService: SwinjectDependentService { router = container.resolve(Router.self) addressConverter = container.resolve(AddressConverterFactory.self)? .make(network: network) + coreDataStack = container.resolve(CoreDataStack.self) + + addTransactionObserver() } } @@ -612,6 +641,20 @@ extension DogeWalletService { throw WalletServiceError.remoteServiceError(message: "Failed to parse block") } } + + func loadTransactions(offset: Int, limit: Int) async throws { + let tuple = try await getTransactions(from: offset) + + let trs = tuple.transactions + hasMoreOldTransactions = tuple.hasMore + + guard trs.count > 0 else { + hasMoreOldTransactions = false + return + } + + coinStorage.append(trs) + } } // MARK: - WalletServiceWithTransfers @@ -622,6 +665,7 @@ extension DogeWalletService: WalletServiceWithTransfers { } vc.walletService = self + vc.dogeWalletService = self return vc } } diff --git a/Adamant/Wallets/ERC20/ERC20TransactionsViewController.swift b/Adamant/Wallets/ERC20/ERC20TransactionsViewController.swift index b3e4b8667..f11e6a3b0 100644 --- a/Adamant/Wallets/ERC20/ERC20TransactionsViewController.swift +++ b/Adamant/Wallets/ERC20/ERC20TransactionsViewController.swift @@ -13,88 +13,24 @@ import CommonKit class ERC20TransactionsViewController: TransactionsListViewControllerBase { // MARK: - Dependencies - var walletService: ERC20WalletService! { + var ercWalletService: ERC20WalletService! { didSet { - ethAddress = walletService.wallet?.address ?? "" + ethAddress = ercWalletService.wallet?.address ?? "" } } - var dialogService: DialogService! var router: Router! // MARK: - Properties - var transactions: [EthTransactionShort] = [] + private var ethAddress: String = "" - private lazy var exponent: Int = { - var exponent = EthWalletService.currencyExponent - if let naturalUnits = walletService.token?.naturalUnits { - exponent = -1 * naturalUnits - } - return exponent - }() - private var offset = 0 override func viewDidLoad() { super.viewDidLoad() - updateLoadingView(isHidden: false) currencySymbol = walletService.tokenSymbol - handleRefresh() - } - - // MARK: - Overrides - - override func handleRefresh() { - offset = 0 - transactions.removeAll() - tableView.reloadData() - loadData(silent: false) - } - - override func loadData(silent: Bool) { - isBusy = true - emptyLabel.isHidden = true - - guard let address = walletService.wallet?.address else { - transactions = [] - return - } - - Task { @MainActor in - do { - let trs = try await walletService.getTransactionsHistory( - address: address, - offset: offset - ) - - transactions.append(contentsOf: trs) - offset += trs.count - isNeedToLoadMoore = trs.count > 0 - } catch { - isNeedToLoadMoore = false - - if !silent { - dialogService.showRichError(error: error) - } - } - - isBusy = false - emptyLabel.isHidden = transactions.count > 0 - stopBottomIndicator() - refreshControl.endRefreshing() - tableView.reloadData() - updateLoadingView(isHidden: true) - }.stored(in: taskManager) - } - - override func reloadData() { - handleRefresh() } // MARK: - UITableView - override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { - return transactions.count - } - func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { guard let address = walletService.wallet?.address else { return } @@ -106,20 +42,18 @@ class ERC20TransactionsViewController: TransactionsListViewControllerBase { fatalError("Failed to get ERC20TransactionDetailsViewController") } - vc.service = walletService - - let isOutgoing: Bool = transaction.to != address + vc.service = ercWalletService let emptyTransaction = SimpleTransactionDetails( - txId: transaction.hash, - senderAddress: transaction.from, - recipientAddress: transaction.to, - dateValue: nil, - amountValue: transaction.value, + txId: transaction.transactionId, + senderAddress: transaction.senderId ?? "", + recipientAddress: transaction.recipientId ?? "", + dateValue: transaction.date as? Date, + amountValue: transaction.amount?.decimalValue, feeValue: nil, confirmationsValue: nil, blockValue: nil, - isOutgoing: isOutgoing, + isOutgoing: transaction.isOutgoing, transactionStatus: nil ) @@ -135,38 +69,4 @@ class ERC20TransactionsViewController: TransactionsListViewControllerBase { navigationController?.pushViewController(vc, animated: true) } - - override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { - guard let cell = tableView.dequeueReusableCell(withIdentifier: cellIdentifierCompact, for: indexPath) as? TransactionTableViewCell else { - return UITableViewCell(style: .default, reuseIdentifier: "cell") - } - - let transaction = transactions[indexPath.row] - cell.accessoryType = .disclosureIndicator - cell.separatorInset = indexPath.row == transactions.count - 1 - ? .zero - : UITableView.defaultTransactionsSeparatorInset - - configureCell(cell, for: transaction) - return cell - } - - func configureCell(_ cell: TransactionTableViewCell, for transaction: EthTransactionShort) { - let outgoing = isOutgoing(transaction) - let partnerId = outgoing ? transaction.to : transaction.from - - configureCell(cell, - isOutgoing: outgoing, - partnerId: partnerId, - partnerName: nil, - amount: transaction.contract_value.asDecimal(exponent: exponent), - date: transaction.date) - } -} - -// MARK: - Tools -extension ERC20TransactionsViewController { - private func isOutgoing(_ transaction: EthTransactionShort) -> Bool { - return transaction.from.lowercased() == ethAddress.lowercased() - } } diff --git a/Adamant/Wallets/ERC20/ERC20TransferViewController.swift b/Adamant/Wallets/ERC20/ERC20TransferViewController.swift index a21f9a5ca..f104f2be7 100644 --- a/Adamant/Wallets/ERC20/ERC20TransferViewController.swift +++ b/Adamant/Wallets/ERC20/ERC20TransferViewController.swift @@ -82,6 +82,7 @@ final class ERC20TransferViewController: TransferViewControllerBase { transaction: transaction, recipient: recipient, comments: comments, + amount: amount, service: service ) } catch { @@ -96,14 +97,23 @@ final class ERC20TransferViewController: TransferViewControllerBase { transaction: CodableTransaction, recipient: String, comments: String, + amount: Decimal, service: ERC20WalletService ) { let transaction = SimpleTransactionDetails( txId: hash, senderAddress: transaction.sender?.address ?? "", recipientAddress: recipient, - isOutgoing: true + amountValue: amount, + feeValue: nil, + confirmationsValue: nil, + blockValue: nil, + isOutgoing: true, + transactionStatus: nil ) + + service.coinStorage.append(transaction) + if let detailsVc = router.get(scene: AdamantScene.Wallets.ERC20.transactionDetails) as? ERC20TransactionDetailsViewController { detailsVc.transaction = transaction detailsVc.service = service diff --git a/Adamant/Wallets/ERC20/ERC20WalletService.swift b/Adamant/Wallets/ERC20/ERC20WalletService.swift index d1b2cd1b2..3d7fa012b 100644 --- a/Adamant/Wallets/ERC20/ERC20WalletService.swift +++ b/Adamant/Wallets/ERC20/ERC20WalletService.swift @@ -102,6 +102,7 @@ class ERC20WalletService: WalletService { var dialogService: DialogService! var router: Router! var increaseFeeService: IncreaseFeeService! + var coreDataStack: CoreDataStack! // MARK: - Notifications var walletUpdatedNotification = Notification.Name("adamant.erc20Wallet.walletUpdated") @@ -171,6 +172,22 @@ class ERC20WalletService: WalletService { private (set) var contract: Web3.Contract? private var balanceObserver: NSObjectProtocol? + @Published private(set) var historyTransactions: [CoinTransaction] = [] + @Published private(set) var hasMoreOldTransactions: Bool = true + + var transactionsPublisher: Published<[CoinTransaction]>.Publisher { + $historyTransactions + } + + var hasMoreOldTransactionsPublisher: Published.Publisher { + $hasMoreOldTransactions + } + + lazy var coinStorage = AdamantCoinStorageService( + coinId: tokenUnicID, + coreDataStack: coreDataStack + ) + init(token: ERC20Token) { self.token = token @@ -225,6 +242,15 @@ class ERC20WalletService: WalletService { .store(in: &subscriptions) } + func addTransactionObserver() { + coinStorage.$transactions + .removeDuplicates() + .sink { [weak self] transactions in + self?.historyTransactions = transactions + } + .store(in: &subscriptions) + } + func setupEthNode(with apiUrl: String) async -> Web3? { guard let url = URL(string: apiUrl), @@ -447,6 +473,9 @@ extension ERC20WalletService: SwinjectDependentService { dialogService = container.resolve(DialogService.self) router = container.resolve(Router.self) increaseFeeService = container.resolve(IncreaseFeeService.self) + coreDataStack = container.resolve(CoreDataStack.self) + + addTransactionObserver() } } @@ -629,6 +658,47 @@ extension ERC20WalletService { transactions.sort { $0.date.compare($1.date) == .orderedDescending } return transactions } + + func loadTransactions(offset: Int, limit: Int) async throws { + guard let address = wallet?.address else { + return + } + + let trs = try await getTransactionsHistory( + address: address, + offset: offset, + limit: limit + ) + + guard trs.count > 0 else { + hasMoreOldTransactions = false + return + } + + let newTrs = trs.map { transaction in + let isOutgoing: Bool = transaction.to != address + + var exponent = EthWalletService.currencyExponent + if let naturalUnits = token?.naturalUnits { + exponent = -1 * naturalUnits + } + + return SimpleTransactionDetails( + txId: transaction.hash, + senderAddress: transaction.from, + recipientAddress: transaction.to, + dateValue: transaction.date, + amountValue: transaction.contract_value.asDecimal(exponent: exponent), + feeValue: nil, + confirmationsValue: nil, + blockValue: nil, + isOutgoing: isOutgoing, + transactionStatus: nil + ) + } + + coinStorage.append(newTrs) + } } extension ERC20WalletService: WalletServiceWithTransfers { @@ -638,6 +708,7 @@ extension ERC20WalletService: WalletServiceWithTransfers { } vc.walletService = self + vc.ercWalletService = self return vc } } diff --git a/Adamant/Wallets/Ethereum/EthTransactionsViewController.swift b/Adamant/Wallets/Ethereum/EthTransactionsViewController.swift index ab4f47762..cc3ce8481 100644 --- a/Adamant/Wallets/Ethereum/EthTransactionsViewController.swift +++ b/Adamant/Wallets/Ethereum/EthTransactionsViewController.swift @@ -18,76 +18,18 @@ class EthTransactionsViewController: TransactionsListViewControllerBase { ethAddress = ethWalletService.wallet?.address ?? "" } } - var dialogService: DialogService! var router: Router! // MARK: - Properties - var transactions: [EthTransactionShort] = [] private var ethAddress: String = "" - private var offset = 0 override func viewDidLoad() { super.viewDidLoad() - updateLoadingView(isHidden: false) currencySymbol = EthWalletService.currencySymbol - handleRefresh() - } - - // MARK: - Overrides - - override func handleRefresh() { - offset = 0 - transactions.removeAll() - tableView.reloadData() - loadData(silent: false) - } - - override func loadData(silent: Bool) { - isBusy = true - emptyLabel.isHidden = true - - guard let address = ethWalletService.wallet?.address else { - transactions = [] - return - } - - Task { @MainActor in - do { - let trs = try await ethWalletService.getTransactionsHistory( - address: address, - offset: offset - ) - - transactions.append(contentsOf: trs) - offset += trs.count - isNeedToLoadMoore = trs.count > 0 - } catch { - isNeedToLoadMoore = false - - if !silent { - dialogService.showRichError(error: error) - } - } - - isBusy = false - emptyLabel.isHidden = transactions.count > 0 - refreshControl.endRefreshing() - stopBottomIndicator() - tableView.reloadData() - updateLoadingView(isHidden: true) - }.stored(in: taskManager) - } - - override func reloadData() { - handleRefresh() } // MARK: - UITableView - override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { - return transactions.count - } - func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { guard let address = ethWalletService.wallet?.address else { return } @@ -100,18 +42,16 @@ class EthTransactionsViewController: TransactionsListViewControllerBase { vc.service = ethWalletService - let isOutgoing: Bool = transaction.to != address - let emptyTransaction = SimpleTransactionDetails( - txId: transaction.hash, - senderAddress: transaction.from, - recipientAddress: transaction.to, - dateValue: nil, - amountValue: transaction.value, + txId: transaction.transactionId, + senderAddress: transaction.senderId ?? "", + recipientAddress: transaction.recipientId ?? "", + dateValue: transaction.date as? Date, + amountValue: transaction.amount?.decimalValue, feeValue: nil, confirmationsValue: nil, blockValue: nil, - isOutgoing: isOutgoing, + isOutgoing: transaction.isOutgoing, transactionStatus: nil ) @@ -127,38 +67,4 @@ class EthTransactionsViewController: TransactionsListViewControllerBase { navigationController?.pushViewController(vc, animated: true) } - - override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { - guard let cell = tableView.dequeueReusableCell(withIdentifier: cellIdentifierCompact, for: indexPath) as? TransactionTableViewCell else { - return UITableViewCell(style: .default, reuseIdentifier: "cell") - } - - let transaction = transactions[indexPath.row] - cell.accessoryType = .disclosureIndicator - cell.separatorInset = indexPath.row == transactions.count - 1 - ? .zero - : UITableView.defaultTransactionsSeparatorInset - - configureCell(cell, for: transaction) - return cell - } - - func configureCell(_ cell: TransactionTableViewCell, for transaction: EthTransactionShort) { - let outgoing = isOutgoing(transaction) - let partnerId = outgoing ? transaction.to : transaction.from - - configureCell(cell, - isOutgoing: outgoing, - partnerId: partnerId, - partnerName: nil, - amount: transaction.value, - date: transaction.date) - } -} - -// MARK: - Tools -extension EthTransactionsViewController { - private func isOutgoing(_ transaction: EthTransactionShort) -> Bool { - return transaction.from.lowercased() == ethAddress.lowercased() - } } diff --git a/Adamant/Wallets/Ethereum/EthTransferViewController.swift b/Adamant/Wallets/Ethereum/EthTransferViewController.swift index 95514772a..21aa54d02 100644 --- a/Adamant/Wallets/Ethereum/EthTransferViewController.swift +++ b/Adamant/Wallets/Ethereum/EthTransferViewController.swift @@ -76,6 +76,7 @@ final class EthTransferViewController: TransferViewControllerBase { transaction: transaction, recipient: recipient, comments: comments, + amount: amount, service: service ) } catch { @@ -90,14 +91,23 @@ final class EthTransferViewController: TransferViewControllerBase { transaction: CodableTransaction, recipient: String, comments: String, + amount: Decimal, service: EthWalletService ) { let transaction = SimpleTransactionDetails( txId: hash, senderAddress: transaction.sender?.address ?? "", recipientAddress: recipient, - isOutgoing: true + amountValue: amount, + feeValue: nil, + confirmationsValue: nil, + blockValue: nil, + isOutgoing: true, + transactionStatus: nil ) + + service.coinStorage.append(transaction) + if let detailsVc = router.get(scene: AdamantScene.Wallets.Ethereum.transactionDetails) as? EthTransactionDetailsViewController { detailsVc.transaction = transaction detailsVc.service = service diff --git a/Adamant/Wallets/Ethereum/EthWalletService+Transfers.swift b/Adamant/Wallets/Ethereum/EthWalletService+Transfers.swift index 43fefb6e7..aa3db15e7 100644 --- a/Adamant/Wallets/Ethereum/EthWalletService+Transfers.swift +++ b/Adamant/Wallets/Ethereum/EthWalletService+Transfers.swift @@ -14,6 +14,7 @@ extension EthWalletService: WalletServiceWithTransfers { fatalError("Can't get EthTransactionsViewController") } + vc.walletService = self vc.ethWalletService = self return vc } diff --git a/Adamant/Wallets/Ethereum/EthWalletService.swift b/Adamant/Wallets/Ethereum/EthWalletService.swift index 54b0461bb..4d6f48a84 100644 --- a/Adamant/Wallets/Ethereum/EthWalletService.swift +++ b/Adamant/Wallets/Ethereum/EthWalletService.swift @@ -125,6 +125,7 @@ class EthWalletService: WalletService { var dialogService: DialogService! var router: Router! var increaseFeeService: IncreaseFeeService! + var coreDataStack: CoreDataStack! // MARK: - Notifications let walletUpdatedNotification = Notification.Name("adamant.ethWallet.walletUpdated") @@ -167,7 +168,23 @@ class EthWalletService: WalletService { private var initialBalanceCheck = false private var subscriptions = Set() + + @Published private(set) var historyTransactions: [CoinTransaction] = [] + @Published private(set) var hasMoreOldTransactions: Bool = true + var transactionsPublisher: Published<[CoinTransaction]>.Publisher { + $historyTransactions + } + + var hasMoreOldTransactionsPublisher: Published.Publisher { + $hasMoreOldTransactions + } + + lazy var coinStorage = AdamantCoinStorageService( + coinId: tokenUnicID, + coreDataStack: coreDataStack + ) + // MARK: - State private (set) var state: WalletServiceState = .notInitiated @@ -230,6 +247,15 @@ class EthWalletService: WalletService { .store(in: &subscriptions) } + func addTransactionObserver() { + coinStorage.$transactions + .removeDuplicates() + .sink { [weak self] transactions in + self?.historyTransactions = transactions + } + .store(in: &subscriptions) + } + func initiateNetwork(apiUrl: String, completion: @escaping (WalletServiceSimpleResult) -> Void) { Task { self._ethNodeUrl = apiUrl @@ -539,6 +565,9 @@ extension EthWalletService: SwinjectDependentService { dialogService = container.resolve(DialogService.self) router = container.resolve(Router.self) increaseFeeService = container.resolve(IncreaseFeeService.self) + coreDataStack = container.resolve(CoreDataStack.self) + + addTransactionObserver() } } @@ -755,6 +784,41 @@ extension EthWalletService { transactions.sort { $0.date.compare($1.date) == .orderedDescending } return transactions } + + func loadTransactions(offset: Int, limit: Int) async throws { + guard let address = wallet?.address else { + return + } + + let trs = try await getTransactionsHistory( + address: address, + offset: offset, + limit: limit + ) + + guard trs.count > 0 else { + hasMoreOldTransactions = false + return + } + + let newTrs = trs.map { transaction in + let isOutgoing: Bool = transaction.to != address + return SimpleTransactionDetails( + txId: transaction.hash, + senderAddress: transaction.from, + recipientAddress: transaction.to, + dateValue: transaction.date, + amountValue: transaction.value, + feeValue: nil, + confirmationsValue: nil, + blockValue: nil, + isOutgoing: isOutgoing, + transactionStatus: nil + ) + } + + coinStorage.append(newTrs) + } } // MARK: - PrivateKey generator diff --git a/Adamant/Wallets/Lisk/LskTransactionsViewController.swift b/Adamant/Wallets/Lisk/LskTransactionsViewController.swift index 471e3788b..f9eaccf97 100644 --- a/Adamant/Wallets/Lisk/LskTransactionsViewController.swift +++ b/Adamant/Wallets/Lisk/LskTransactionsViewController.swift @@ -11,65 +11,23 @@ import LiskKit import web3swift import BigInt import CommonKit +import Combine class LskTransactionsViewController: TransactionsListViewControllerBase { // MARK: - Dependencies var lskWalletService: LskWalletService! - var dialogService: DialogService! var router: Router! // MARK: - Properties - var transactions: [Transactions.TransactionModel] = [] - private var offset: UInt = 0 - override func viewDidLoad() { super.viewDidLoad() - updateLoadingView(isHidden: false) currencySymbol = LskWalletService.currencySymbol - handleRefresh() - } - - override func handleRefresh() { - emptyLabel.isHidden = true - transactions.removeAll() - tableView.reloadData() - offset = 0 - loadData(silent: false) - } - - override func loadData(silent: Bool) { - isBusy = true - Task { @MainActor in - do { - let trs = try await lskWalletService.getTransactions(offset: offset) - transactions.append(contentsOf: trs) - offset += UInt(trs.count) - isNeedToLoadMoore = trs.count > 0 - } catch { - isNeedToLoadMoore = false - - if !silent { - dialogService.showRichError(error: error) - } - } - - isBusy = false - emptyLabel.isHidden = self.transactions.count > 0 - stopBottomIndicator() - refreshControl.endRefreshing() - tableView.reloadData() - updateLoadingView(isHidden: true) - }.stored(in: taskManager) } // MARK: - UITableView - override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { - return transactions.count - } - func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { tableView.deselectRow(at: indexPath, animated: true) @@ -80,14 +38,14 @@ class LskTransactionsViewController: TransactionsListViewControllerBase { } let emptyTransaction = SimpleTransactionDetails( - txId: transaction.txId, - senderAddress: transaction.senderAddress, - recipientAddress: transaction.recipientAddress, - dateValue: transaction.dateValue, - amountValue: transaction.amountValue, - feeValue: transaction.feeValue, - confirmationsValue: transaction.confirmationsValue, - blockValue: transaction.blockValue, + txId: transaction.transactionId, + senderAddress: transaction.senderId ?? "", + recipientAddress: transaction.recipientId ?? "", + dateValue: transaction.date as? Date, + amountValue: transaction.amount?.decimalValue, + feeValue: nil, + confirmationsValue: nil, + blockValue: nil, isOutgoing: transaction.isOutgoing, transactionStatus: nil ) @@ -96,48 +54,15 @@ class LskTransactionsViewController: TransactionsListViewControllerBase { controller.service = lskWalletService if let address = lskWalletService.wallet?.address { - if transaction.senderAddress.caseInsensitiveCompare(address) == .orderedSame { + if transaction.senderId?.caseInsensitiveCompare(address) == .orderedSame { controller.senderName = String.adamant.transactionDetails.yourAddress - } else if transaction.recipientAddress.caseInsensitiveCompare(address) == .orderedSame { + } else if transaction.recipientId?.caseInsensitiveCompare(address) == .orderedSame { controller.recipientName = String.adamant.transactionDetails.yourAddress } } navigationController?.pushViewController(controller, animated: true) } - - override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { - guard let cell = tableView.dequeueReusableCell(withIdentifier: cellIdentifierCompact, for: indexPath) as? TransactionTableViewCell else { - // TODO: Display & Log error - return UITableViewCell(style: .default, reuseIdentifier: "cell") - } - - let transaction = transactions[indexPath.row] - - cell.accessoryType = .disclosureIndicator - cell.separatorInset = indexPath.row == transactions.count - 1 - ? .zero - : UITableView.defaultTransactionsSeparatorInset - - configureCell(cell, for: transaction) - return cell - } - - func configureCell(_ cell: TransactionTableViewCell, for transaction: Transactions.TransactionModel) { - let outgoing = isOutgoing(transaction) - let partnerId = outgoing ? transaction.recipientId : transaction.senderId - - configureCell(cell, - isOutgoing: outgoing, - partnerId: partnerId ?? "", - partnerName: nil, - amount: transaction.amountValue ?? 0, - date: transaction.dateValue) - } - - private func isOutgoing(_ transaction: Transactions.TransactionModel) -> Bool { - return transaction.senderId.lowercased() == lskWalletService.wallet?.address.lowercased() - } } extension Transactions.TransactionModel: TransactionDetails { diff --git a/Adamant/Wallets/Lisk/LskTransferViewController.swift b/Adamant/Wallets/Lisk/LskTransferViewController.swift index db79a0a75..d5c1cece0 100644 --- a/Adamant/Wallets/Lisk/LskTransferViewController.swift +++ b/Adamant/Wallets/Lisk/LskTransferViewController.swift @@ -11,6 +11,7 @@ import Eureka import LiskKit import CommonKit +@MainActor final class LskTransferViewController: TransferViewControllerBase { // MARK: Properties @@ -67,6 +68,7 @@ final class LskTransferViewController: TransferViewControllerBase { Task { do { + service.coinStorage.append(transaction) try await service.sendTransaction(transaction) } catch { dialogService.showRichError(error: error) diff --git a/Adamant/Wallets/Lisk/LskWalletService+Transfers.swift b/Adamant/Wallets/Lisk/LskWalletService+Transfers.swift index bac4e86b8..ca22433e0 100644 --- a/Adamant/Wallets/Lisk/LskWalletService+Transfers.swift +++ b/Adamant/Wallets/Lisk/LskWalletService+Transfers.swift @@ -14,6 +14,7 @@ extension LskWalletService: WalletServiceWithTransfers { fatalError("Can't get LskTransactionsViewController") } + vc.walletService = self vc.lskWalletService = self return vc } diff --git a/Adamant/Wallets/Lisk/LskWalletService.swift b/Adamant/Wallets/Lisk/LskWalletService.swift index 2dc0b4a3b..3a18b3e7a 100644 --- a/Adamant/Wallets/Lisk/LskWalletService.swift +++ b/Adamant/Wallets/Lisk/LskWalletService.swift @@ -44,6 +44,7 @@ class LskWalletService: WalletService { var accountService: AccountService! var dialogService: DialogService! var router: Router! + var coreDataStack: CoreDataStack! // MARK: - Constants var transactionFee: Decimal { @@ -106,6 +107,22 @@ class LskWalletService: WalletService { private let nodes: [APINode] private var subscriptions = Set() + @Published private(set) var transactions: [CoinTransaction] = [] + @Published private(set) var hasMoreOldTransactions: Bool = true + + var transactionsPublisher: Published<[CoinTransaction]>.Publisher { + $transactions + } + + var hasMoreOldTransactionsPublisher: Published.Publisher { + $hasMoreOldTransactions + } + + lazy var coinStorage = AdamantCoinStorageService( + coinId: tokenUnicID, + coreDataStack: coreDataStack + ) + // MARK: - State private (set) var state: WalletServiceState = .notInitiated @@ -181,6 +198,15 @@ class LskWalletService: WalletService { .store(in: &subscriptions) } + func addTransactionObserver() { + coinStorage.$transactions + .removeDuplicates() + .sink { [weak self] transactions in + self?.transactions = transactions + } + .store(in: &subscriptions) + } + func update() { Task { await update() @@ -487,6 +513,9 @@ extension LskWalletService: SwinjectDependentService { apiService = container.resolve(ApiService.self) dialogService = container.resolve(DialogService.self) router = container.resolve(Router.self) + coreDataStack = container.resolve(CoreDataStack.self) + + addTransactionObserver() } } @@ -576,6 +605,17 @@ extension LskWalletService { ) } } + + func loadTransactions(offset: Int, limit: Int) async throws { + let trs = try await getTransactions(offset: UInt(offset), limit: UInt(limit)) + + guard trs.count > 0 else { + hasMoreOldTransactions = false + return + } + + coinStorage.append(trs) + } } // MARK: - KVS @@ -617,7 +657,7 @@ extension LskWalletService { // MARK: - Transactions extension LskWalletService { - func getTransactions(offset: UInt) async throws -> [Transactions.TransactionModel] { + func getTransactions(offset: UInt, limit: UInt = 100) async throws -> [Transactions.TransactionModel] { guard let address = self.lskWallet?.address, let transactionApi = serviceApi else { @@ -627,7 +667,7 @@ extension LskWalletService { return try await withUnsafeThrowingContinuation { (continuation: UnsafeContinuation<[Transactions.TransactionModel], Error>) in transactionApi.transactions( senderIdOrRecipientId: address, - limit: 100, + limit: limit, offset: offset, sort: APIRequest.Sort("timestamp", direction: .descending) ) { (response) in diff --git a/Adamant/Wallets/TransactionsListViewControllerBase.swift b/Adamant/Wallets/TransactionsListViewControllerBase.swift index 5becbda9a..fc4d0a001 100644 --- a/Adamant/Wallets/TransactionsListViewControllerBase.swift +++ b/Adamant/Wallets/TransactionsListViewControllerBase.swift @@ -9,6 +9,7 @@ import UIKit import CoreData import CommonKit +import Combine extension String.adamant { struct transactionList { @@ -37,13 +38,24 @@ class TransactionsListViewControllerBase: UIViewController { return refreshControl }() + // MARK: - Dependencies + + var walletService: WalletService! + var dialogService: DialogService! + + // MARK: - Proprieties + var taskManager = TaskManager() var isNeedToLoadMoore = true var isBusy = true + var transactions: [CoinTransaction] = [] private(set) lazy var loadingView = LoadingView() + private var subscriptions = Set() + private var limit = 25 + // MARK: - IBOutlets @IBOutlet weak var tableView: UITableView! @IBOutlet weak var emptyLabel: UILabel! @@ -81,6 +93,9 @@ class TransactionsListViewControllerBase: UIViewController { setColors() configureLayout() + addObservers() + + handleRefresh() } override func viewWillAppear(_ animated: Bool) { @@ -96,6 +111,29 @@ class TransactionsListViewControllerBase: UIViewController { } } + func addObservers() { + walletService.transactionsPublisher + .sink { [weak self] transactions in + self?.update(transactions) + } + .store(in: &subscriptions) + + walletService.hasMoreOldTransactionsPublisher + .sink { [weak self] isNeedToLoadMoore in + self?.isNeedToLoadMoore = isNeedToLoadMoore + } + .store(in: &subscriptions) + } + + @MainActor + func update(_ transactions: [CoinTransaction]) { + DispatchQueue.main.async { + self.transactions = transactions + self.tableView.reloadData() + self.updateLoadingView(isHidden: true) + } + } + // MARK: - Other private func setColors() { @@ -121,6 +159,36 @@ class TransactionsListViewControllerBase: UIViewController { } } + @MainActor + func loadData(silent: Bool) { + loadData(offset: transactions.count, silent: true) + } + + @MainActor + func loadData(offset: Int, silent: Bool) { + isBusy = true + print("loadData, ofs=\(offset)") + Task { @MainActor in + do { + try await walletService.loadTransactions( + offset: offset, + limit: limit + ) + } catch { + if !silent { + dialogService.showRichError(error: error) + } + } + + isBusy = false + emptyLabel.isHidden = self.transactions.count > 0 + stopBottomIndicator() + refreshControl.endRefreshing() + tableView.reloadData() + updateLoadingView(isHidden: true) + }.stored(in: taskManager) + } + // MARK: - To override func tableView(_ tableView: UITableView, viewForFooterInSection section: Int) -> UIView? { @@ -132,11 +200,35 @@ class TransactionsListViewControllerBase: UIViewController { } func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { - return UITableViewCell(style: .default, reuseIdentifier: "cell") + guard let cell = tableView.dequeueReusableCell(withIdentifier: cellIdentifierCompact, for: indexPath) as? TransactionTableViewCell else { + return UITableViewCell(style: .default, reuseIdentifier: "cell") + } + + let transaction = transactions[indexPath.row] + + cell.accessoryType = .disclosureIndicator + cell.separatorInset = indexPath.row == transactions.count - 1 + ? .zero + : UITableView.defaultTransactionsSeparatorInset + + let partnerId = transaction.isOutgoing + ? transaction.recipientId + : transaction.senderId + + configureCell( + cell, + isOutgoing: transaction.isOutgoing, + partnerId: partnerId ?? "", + partnerName: nil, + amount: (transaction.amount ?? 0).decimalValue, + date: transaction.date as? Date + ) + + return cell } func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { - return 0 + transactions.count } func tableView(_ tableView: UITableView, heightForFooterInSection section: Int) -> CGFloat { @@ -156,11 +248,8 @@ class TransactionsListViewControllerBase: UIViewController { } @objc func handleRefresh() { - - } - - func loadData(silent: Bool) { - + emptyLabel.isHidden = true + loadData(offset: .zero, silent: true) } func reloadData() { diff --git a/Adamant/Wallets/WalletService.swift b/Adamant/Wallets/WalletService.swift index 351eb9a78..c286ea65c 100644 --- a/Adamant/Wallets/WalletService.swift +++ b/Adamant/Wallets/WalletService.swift @@ -217,6 +217,14 @@ protocol WalletService: AnyObject { var defaultOrdinalLevel: Int? { get } var richMessageType: String { get } + var transactionsPublisher: Published<[CoinTransaction]>.Publisher { + get + } + + var hasMoreOldTransactionsPublisher: Published.Publisher { + get + } + // MARK: Notifications /// Wallet updated. @@ -244,6 +252,7 @@ protocol WalletService: AnyObject { func validate(address: String) -> AddressValidationResult func getWalletAddress(byAdamantAddress address: String) async throws -> String func getBalance(address: String) async throws -> Decimal + func loadTransactions(offset: Int, limit: Int) async throws } protocol SwinjectDependentService: WalletService { From fef92e7fcf53eb4457dc551e04fa43a996b5f429 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=80=9Cjust-software-dev=E2=80=9D?= Date: Sun, 1 Oct 2023 23:12:51 +0100 Subject: [PATCH 15/96] [mastertodev/trello.com/c/huLvtGS4] Extensions' versions 3.3.0 --- Adamant.xcodeproj/project.pbxproj | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/Adamant.xcodeproj/project.pbxproj b/Adamant.xcodeproj/project.pbxproj index d12d5e294..5e38ef13a 100644 --- a/Adamant.xcodeproj/project.pbxproj +++ b/Adamant.xcodeproj/project.pbxproj @@ -3172,7 +3172,7 @@ "@executable_path/Frameworks", "@executable_path/../../Frameworks", ); - MARKETING_VERSION = 3.2.1; + MARKETING_VERSION = 3.3.0; MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; MTL_FAST_MATH = YES; PRODUCT_BUNDLE_IDENTIFIER = "im.adamant.adamant-messenger-dev.MessageNotificationContentExtension"; @@ -3202,7 +3202,7 @@ "@executable_path/Frameworks", "@executable_path/../../Frameworks", ); - MARKETING_VERSION = 3.2.1; + MARKETING_VERSION = 3.3.0; MTL_FAST_MATH = YES; PRODUCT_BUNDLE_IDENTIFIER = "im.adamant.adamant-messenger.MessageNotificationContentExtension"; PRODUCT_NAME = "$(TARGET_NAME)"; @@ -3413,7 +3413,7 @@ "@executable_path/Frameworks", "@executable_path/../../Frameworks", ); - MARKETING_VERSION = 3.2.1; + MARKETING_VERSION = 3.3.0; MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; MTL_FAST_MATH = YES; PRODUCT_BUNDLE_IDENTIFIER = "im.adamant.adamant-messenger-dev.TransferNotificationContentExtension"; @@ -3443,7 +3443,7 @@ "@executable_path/Frameworks", "@executable_path/../../Frameworks", ); - MARKETING_VERSION = 3.2.1; + MARKETING_VERSION = 3.3.0; MTL_FAST_MATH = YES; PRODUCT_BUNDLE_IDENTIFIER = "im.adamant.adamant-messenger.TransferNotificationContentExtension"; PRODUCT_NAME = "$(TARGET_NAME)"; @@ -3474,7 +3474,7 @@ "@executable_path/Frameworks", "@executable_path/../../Frameworks", ); - MARKETING_VERSION = 3.2.1; + MARKETING_VERSION = 3.3.0; MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; MTL_FAST_MATH = YES; PRODUCT_BUNDLE_IDENTIFIER = "im.adamant.adamant-messenger-dev.NotificationServiceExtension"; @@ -3504,7 +3504,7 @@ "@executable_path/Frameworks", "@executable_path/../../Frameworks", ); - MARKETING_VERSION = 3.2.1; + MARKETING_VERSION = 3.3.0; MTL_FAST_MATH = YES; PRODUCT_BUNDLE_IDENTIFIER = "im.adamant.adamant-messenger.NotificationServiceExtension"; PRODUCT_NAME = "$(TARGET_NAME)"; From 50d4c9934f13e44394a07d43d65eaf9380bb6fd6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=80=9Cjust-software-dev=E2=80=9D?= Date: Mon, 2 Oct 2023 04:03:42 +0100 Subject: [PATCH 16/96] [trello.com/c/ZqOGnQZU] Moved Vibration feature to new DI --- Adamant.xcodeproj/project.pbxproj | 40 +++++++++---------- Adamant/App/DI/AppAssembly.swift | 2 +- .../Account/AccountViewController.swift | 2 +- .../AdamantScreensFactory.swift | 6 +++ .../ScreensFactory/ScreensFactory.swift | 1 + .../VibrationSelectionFactory.swift | 36 +++++++++++++++++ .../VibrationSelectionView.swift | 2 +- .../VibrationSelectionViewModel.swift | 14 ++++--- .../Wallets/Adamant/AdmWalletFactory.swift | 3 +- .../Wallets/Bitcoin/BtcWalletFactory.swift | 3 +- .../Wallets/Dash/DashWalletFactory.swift | 3 +- .../Wallets/Doge/DogeWalletFactory.swift | 3 +- .../Wallets/ERC20/ERC20WalletFactory.swift | 3 +- .../Wallets/Ethereum/EthWalletFactory.swift | 3 +- .../Wallets/Lisk/LskWalletFactory.swift | 3 +- .../VibrationSelectionFactory.swift | 23 ----------- 16 files changed, 88 insertions(+), 59 deletions(-) create mode 100644 Adamant/Modules/TestVibration/VibrationSelectionFactory.swift rename Adamant/{Stories/Settings => Modules}/TestVibration/VibrationSelectionView.swift (95%) rename Adamant/{Stories/Settings => Modules}/TestVibration/VibrationSelectionViewModel.swift (69%) delete mode 100644 Adamant/Stories/Settings/TestVibration/VibrationSelectionFactory.swift diff --git a/Adamant.xcodeproj/project.pbxproj b/Adamant.xcodeproj/project.pbxproj index 81b040913..ef66fbdd6 100644 --- a/Adamant.xcodeproj/project.pbxproj +++ b/Adamant.xcodeproj/project.pbxproj @@ -14,9 +14,6 @@ 3A7BD00E2AA9BCE80045AAB0 /* VibroService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3A7BD00D2AA9BCE80045AAB0 /* VibroService.swift */; }; 3A7BD0102AA9BD030045AAB0 /* AdamantVibroService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3A7BD00F2AA9BD030045AAB0 /* AdamantVibroService.swift */; }; 3A7BD0122AA9BD5A0045AAB0 /* AdamantVibroType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3A7BD0112AA9BD5A0045AAB0 /* AdamantVibroType.swift */; }; - 3A7BD0152AA9BF020045AAB0 /* VibrationSelectionView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3A7BD0142AA9BF020045AAB0 /* VibrationSelectionView.swift */; }; - 3A7BD0172AA9BF100045AAB0 /* VibrationSelectionFactory.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3A7BD0162AA9BF100045AAB0 /* VibrationSelectionFactory.swift */; }; - 3A7BD0192AA9BF3F0045AAB0 /* VibrationSelectionViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3A7BD0182AA9BF3F0045AAB0 /* VibrationSelectionViewModel.swift */; }; 3A8875EF27BBF38D00436195 /* Parchment in Frameworks */ = {isa = PBXBuildFile; productRef = 3A8875EE27BBF38D00436195 /* Parchment */; }; 3A9015A52A614A18002A2464 /* EmojiService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3A9015A42A614A18002A2464 /* EmojiService.swift */; }; 3A9015A72A614A62002A2464 /* AdamantEmojiService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3A9015A62A614A62002A2464 /* AdamantEmojiService.swift */; }; @@ -248,6 +245,9 @@ 93A18C892AAEAE7700D0AB98 /* WalletFactory.swift in Sources */ = {isa = PBXBuildFile; fileRef = 93A18C882AAEAE7700D0AB98 /* WalletFactory.swift */; }; 93A91FD1297972B7001DB1F8 /* ChatScrollDownButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 93A91FD0297972B7001DB1F8 /* ChatScrollDownButton.swift */; }; 93A91FD329799298001DB1F8 /* ChatStartPosition.swift in Sources */ = {isa = PBXBuildFile; fileRef = 93A91FD229799298001DB1F8 /* ChatStartPosition.swift */; }; + 93ADE0712ACA66AF008ED641 /* VibrationSelectionViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 93ADE06E2ACA66AF008ED641 /* VibrationSelectionViewModel.swift */; }; + 93ADE0722ACA66AF008ED641 /* VibrationSelectionView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 93ADE06F2ACA66AF008ED641 /* VibrationSelectionView.swift */; }; + 93ADE0732ACA66AF008ED641 /* VibrationSelectionFactory.swift in Sources */ = {isa = PBXBuildFile; fileRef = 93ADE0702ACA66AF008ED641 /* VibrationSelectionFactory.swift */; }; 93BF4A6629E4859900505CD0 /* DelegatesBottomPanel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 93BF4A6529E4859900505CD0 /* DelegatesBottomPanel.swift */; }; 93BF4A6C29E4B4BF00505CD0 /* DelegatesBottomPanel+Model.swift in Sources */ = {isa = PBXBuildFile; fileRef = 93BF4A6B29E4B4BF00505CD0 /* DelegatesBottomPanel+Model.swift */; }; 93CC8DC7296F00D6003772BF /* ChatTransactionContainerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 93CC8DC6296F00D6003772BF /* ChatTransactionContainerView.swift */; }; @@ -596,9 +596,6 @@ 3A7BD00D2AA9BCE80045AAB0 /* VibroService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VibroService.swift; sourceTree = ""; }; 3A7BD00F2AA9BD030045AAB0 /* AdamantVibroService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AdamantVibroService.swift; sourceTree = ""; }; 3A7BD0112AA9BD5A0045AAB0 /* AdamantVibroType.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AdamantVibroType.swift; sourceTree = ""; }; - 3A7BD0142AA9BF020045AAB0 /* VibrationSelectionView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VibrationSelectionView.swift; sourceTree = ""; }; - 3A7BD0162AA9BF100045AAB0 /* VibrationSelectionFactory.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VibrationSelectionFactory.swift; sourceTree = ""; }; - 3A7BD0182AA9BF3F0045AAB0 /* VibrationSelectionViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VibrationSelectionViewModel.swift; sourceTree = ""; }; 3A9015A42A614A18002A2464 /* EmojiService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EmojiService.swift; sourceTree = ""; }; 3A9015A62A614A62002A2464 /* AdamantEmojiService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AdamantEmojiService.swift; sourceTree = ""; }; 3A9015A82A615893002A2464 /* ChatMessagesListViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChatMessagesListViewModel.swift; sourceTree = ""; }; @@ -818,6 +815,9 @@ 93A18C882AAEAE7700D0AB98 /* WalletFactory.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WalletFactory.swift; sourceTree = ""; }; 93A91FD0297972B7001DB1F8 /* ChatScrollDownButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChatScrollDownButton.swift; sourceTree = ""; }; 93A91FD229799298001DB1F8 /* ChatStartPosition.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChatStartPosition.swift; sourceTree = ""; }; + 93ADE06E2ACA66AF008ED641 /* VibrationSelectionViewModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = VibrationSelectionViewModel.swift; sourceTree = ""; }; + 93ADE06F2ACA66AF008ED641 /* VibrationSelectionView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = VibrationSelectionView.swift; sourceTree = ""; }; + 93ADE0702ACA66AF008ED641 /* VibrationSelectionFactory.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = VibrationSelectionFactory.swift; sourceTree = ""; }; 93BF4A6529E4859900505CD0 /* DelegatesBottomPanel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DelegatesBottomPanel.swift; sourceTree = ""; }; 93BF4A6B29E4B4BF00505CD0 /* DelegatesBottomPanel+Model.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "DelegatesBottomPanel+Model.swift"; sourceTree = ""; }; 93CC8DC6296F00D6003772BF /* ChatTransactionContainerView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChatTransactionContainerView.swift; sourceTree = ""; }; @@ -1181,16 +1181,6 @@ path = RichTransactionReactService; sourceTree = ""; }; - 3A7BD0132AA9BEE20045AAB0 /* TestVibration */ = { - isa = PBXGroup; - children = ( - 3A7BD0162AA9BF100045AAB0 /* VibrationSelectionFactory.swift */, - 3A7BD0182AA9BF3F0045AAB0 /* VibrationSelectionViewModel.swift */, - 3A7BD0142AA9BF020045AAB0 /* VibrationSelectionView.swift */, - ); - path = TestVibration; - sourceTree = ""; - }; 3AA2D5F8280EAF49000ED971 /* SocketService */ = { isa = PBXGroup; children = ( @@ -1527,6 +1517,16 @@ path = DI; sourceTree = ""; }; + 93ADE06D2ACA66AF008ED641 /* TestVibration */ = { + isa = PBXGroup; + children = ( + 93ADE06E2ACA66AF008ED641 /* VibrationSelectionViewModel.swift */, + 93ADE06F2ACA66AF008ED641 /* VibrationSelectionView.swift */, + 93ADE0702ACA66AF008ED641 /* VibrationSelectionFactory.swift */, + ); + path = TestVibration; + sourceTree = ""; + }; 93BF4A6A29E4B4B600505CD0 /* DelegatesBottomPanel */ = { isa = PBXGroup; children = ( @@ -1833,6 +1833,7 @@ E919479920000FFD001362F8 /* Modules */ = { isa = PBXGroup; children = ( + 93ADE06D2ACA66AF008ED641 /* TestVibration */, 93294B942AAD31F200911109 /* ScreensFactory */, 93294B912AAD2CA500911109 /* Welcome */, 93294B8A2AAD2C6B00911109 /* SwiftyOnboard */, @@ -2123,7 +2124,6 @@ E982F69820235AF000566AC7 /* Settings */ = { isa = PBXGroup; children = ( - 3A7BD0132AA9BEE20045AAB0 /* TestVibration */, 411742FE2A39B1B1008CD98A /* Contribute */, 4197B9C72952FAA2004CAF64 /* VisibleWallets */, E948E03A20235E2300975D6B /* SettingsFactory.swift */, @@ -2709,6 +2709,7 @@ 93A118532993241D00E144CC /* ChatMessagesListFactory.swift in Sources */, E908472A2196FEA80095825D /* RichMessageTransaction+CoreDataClass.swift in Sources */, 4E9EE86F28CE793D008359F7 /* SafeDecimalRow.swift in Sources */, + 93ADE0732ACA66AF008ED641 /* VibrationSelectionFactory.swift in Sources */, 937751AB2A68BB390054BD65 /* ChatTransactionCell.swift in Sources */, 64A223D620F760BB005157CB /* Localization.swift in Sources */, 64E1C82F222E95F6006C4DA7 /* DogeWallet.swift in Sources */, @@ -2781,6 +2782,7 @@ 93684A2A29EFA28A00F9EFFE /* FixedTextMessageSizeCalculator.swift in Sources */, E9E7CD8B20026B0600DFC4DB /* AccountService.swift in Sources */, 41E3C9CC2A0E20F500AF0985 /* AdamantCoinTools.swift in Sources */, + 93ADE0712ACA66AF008ED641 /* VibrationSelectionViewModel.swift in Sources */, 9371130F2996EDA900F64CF9 /* ChatRefreshMock.swift in Sources */, 93547BCA29E2262D00B0914B /* WelcomeViewController.swift in Sources */, 41047B74294C61D10039E956 /* VisibleWalletsService.swift in Sources */, @@ -2963,7 +2965,6 @@ E940086B2114A70600CD2D67 /* LskAccount.swift in Sources */, 6416B19D21AD7B92006089AC /* LskWalletFactory.swift in Sources */, E9B3D3A1201FA26B0019EB36 /* AdamantAccountsProvider.swift in Sources */, - 3A7BD0192AA9BF3F0045AAB0 /* VibrationSelectionViewModel.swift in Sources */, E9FAE5DA203DBFEF008D3A6B /* Comparable+clamped.swift in Sources */, 93A91FD329799298001DB1F8 /* ChatStartPosition.swift in Sources */, 93A91FD1297972B7001DB1F8 /* ChatScrollDownButton.swift in Sources */, @@ -2992,6 +2993,7 @@ 93294B962AAD320B00911109 /* ScreensFactory.swift in Sources */, E9FEECA421413659007DD7C8 /* RichMessageProvider.swift in Sources */, 3A9015A72A614A62002A2464 /* AdamantEmojiService.swift in Sources */, + 93ADE0722ACA66AF008ED641 /* VibrationSelectionView.swift in Sources */, 648C696F22915A12006645F5 /* DashTransaction.swift in Sources */, 3A41939A2A5D554A006A6B22 /* Reaction.swift in Sources */, 6416B1A321AD7EA1006089AC /* LskTransactionDetailsViewController.swift in Sources */, @@ -3003,7 +3005,6 @@ 644EC34F20EFA77A00F40C73 /* Delegate.swift in Sources */, 64EAB37622463F680018D9B2 /* AdamantCurrencyInfoService.swift in Sources */, 93294B842AAD0C8F00911109 /* Assembler+Extension.swift in Sources */, - 3A7BD0172AA9BF100045AAB0 /* VibrationSelectionFactory.swift in Sources */, 938F7D5B2955C8DA001915CA /* ChatDisplayManager.swift in Sources */, E9722068201F42CC004F2AAD /* InMemoryCoreDataStack.swift in Sources */, 4133AED429769EEC00F3D017 /* UpdatingIndicatorView.swift in Sources */, @@ -3038,7 +3039,6 @@ E96E86B821679C120061F80A /* EthTransactionDetailsViewController.swift in Sources */, 4164A9D728F17D4000EEF16D /* ChatTransactionService.swift in Sources */, E90A4943204C5ED6009F6A65 /* EurekaPassphraseRow.swift in Sources */, - 3A7BD0152AA9BF020045AAB0 /* VibrationSelectionView.swift in Sources */, E90847302196FEA80095825D /* ChatTransaction+CoreDataClass.swift in Sources */, E913C9081FFFA943001A83F7 /* AdamantCore.swift in Sources */, 41935848287841E20083363B /* MacOSDeterminer.swift in Sources */, diff --git a/Adamant/App/DI/AppAssembly.swift b/Adamant/App/DI/AppAssembly.swift index bedc400c5..094fed956 100644 --- a/Adamant/App/DI/AppAssembly.swift +++ b/Adamant/App/DI/AppAssembly.swift @@ -70,7 +70,7 @@ struct AppAssembly: Assembly { }.inObjectScope(.container) // MARK: VibroService - self.register(VibroService.self) { r in + container.register(VibroService.self) { r in AdamantVibroService() }.inObjectScope(.container) diff --git a/Adamant/Modules/Account/AccountViewController.swift b/Adamant/Modules/Account/AccountViewController.swift index 2c4b12d84..fef6276b5 100644 --- a/Adamant/Modules/Account/AccountViewController.swift +++ b/Adamant/Modules/Account/AccountViewController.swift @@ -382,7 +382,7 @@ final class AccountViewController: FormViewController { }.cellUpdate { (cell, _) in cell.accessoryType = .disclosureIndicator }.onCellSelection { [weak self] (_, _) in - guard let vc = self?.router.get(scene: AdamantScene.Settings.vibration) + guard let vc = self?.screensFactory.makeVibrationSelection() else { return } diff --git a/Adamant/Modules/ScreensFactory/AdamantScreensFactory.swift b/Adamant/Modules/ScreensFactory/AdamantScreensFactory.swift index a4e85a041..124e609c9 100644 --- a/Adamant/Modules/ScreensFactory/AdamantScreensFactory.swift +++ b/Adamant/Modules/ScreensFactory/AdamantScreensFactory.swift @@ -23,6 +23,7 @@ struct AdamantScreensFactory: ScreensFactory { private let onboardFactory: OnboardFactory private let shareQRFactory: ShareQRFactory private let accountFactory: AccountFactory + private let vibrationSelectionFactory: VibrationSelectionFactory init(assembler: Assembler) { admWalletFactory = .init(assembler: assembler) @@ -36,6 +37,7 @@ struct AdamantScreensFactory: ScreensFactory { onboardFactory = .init() shareQRFactory = .init(assembler: assembler) accountFactory = .init(assembler: assembler) + vibrationSelectionFactory = .init(parent: assembler) walletFactoryCompose = AdamantWalletFactoryCompose( lskWalletFactory: .init(assembler: assembler), @@ -163,4 +165,8 @@ struct AdamantScreensFactory: ScreensFactory { func makeLogin() -> LoginViewController { loginFactory.makeViewController(screenFactory: self) } + + func makeVibrationSelection() -> UIViewController { + vibrationSelectionFactory.makeViewController() + } } diff --git a/Adamant/Modules/ScreensFactory/ScreensFactory.swift b/Adamant/Modules/ScreensFactory/ScreensFactory.swift index fd46c9796..749488b32 100644 --- a/Adamant/Modules/ScreensFactory/ScreensFactory.swift +++ b/Adamant/Modules/ScreensFactory/ScreensFactory.swift @@ -58,4 +58,5 @@ protocol ScreensFactory { func makeVisibleWallets() -> UIViewController func makeContribute() -> UIViewController func makeLogin() -> LoginViewController + func makeVibrationSelection() -> UIViewController } diff --git a/Adamant/Modules/TestVibration/VibrationSelectionFactory.swift b/Adamant/Modules/TestVibration/VibrationSelectionFactory.swift new file mode 100644 index 000000000..3c3e87b05 --- /dev/null +++ b/Adamant/Modules/TestVibration/VibrationSelectionFactory.swift @@ -0,0 +1,36 @@ +// +// VibrationSelectionFactory.swift +// Adamant +// +// Created by Stanislav Jelezoglo on 07.09.2023. +// Copyright © 2023 Adamant. All rights reserved. +// + +import Swinject +import SwiftUI + +struct VibrationSelectionFactory { + private let assembler: Assembler + + init(parent: Assembler) { + assembler = .init([VibrationSelectionAssembly()], parent: parent) + } + + func makeViewController() -> UIViewController { + UIHostingController( + rootView: VibrationSelectionView( + viewModel: assembler.resolve(VibrationSelectionViewModel.self)! + ) + ) + } +} + +private struct VibrationSelectionAssembly: Assembly { + func assemble(container: Container) { + container.register(VibrationSelectionViewModel.self) { + VibrationSelectionViewModel( + vibroService: $0.resolve(VibroService.self)! + ) + }.inObjectScope(.weak) + } +} diff --git a/Adamant/Stories/Settings/TestVibration/VibrationSelectionView.swift b/Adamant/Modules/TestVibration/VibrationSelectionView.swift similarity index 95% rename from Adamant/Stories/Settings/TestVibration/VibrationSelectionView.swift rename to Adamant/Modules/TestVibration/VibrationSelectionView.swift index 15be1e638..e706929f6 100644 --- a/Adamant/Stories/Settings/TestVibration/VibrationSelectionView.swift +++ b/Adamant/Modules/TestVibration/VibrationSelectionView.swift @@ -9,7 +9,7 @@ import SwiftUI struct VibrationSelectionView: View { - @ObservedObject var viewModel: VibrationSelectionViewModel + @StateObject var viewModel: VibrationSelectionViewModel init(viewModel: VibrationSelectionViewModel) { _viewModel = .init(wrappedValue: viewModel) diff --git a/Adamant/Stories/Settings/TestVibration/VibrationSelectionViewModel.swift b/Adamant/Modules/TestVibration/VibrationSelectionViewModel.swift similarity index 69% rename from Adamant/Stories/Settings/TestVibration/VibrationSelectionViewModel.swift rename to Adamant/Modules/TestVibration/VibrationSelectionViewModel.swift index 089b3eb62..ccef28def 100644 --- a/Adamant/Stories/Settings/TestVibration/VibrationSelectionViewModel.swift +++ b/Adamant/Modules/TestVibration/VibrationSelectionViewModel.swift @@ -17,14 +17,16 @@ final class VibrationSelectionViewModel: ObservableObject { @Published var type: AdamantVibroType? - init(vibroService: VibroService) { + nonisolated init(vibroService: VibroService) { self.vibroService = vibroService - + } +} + +private extension VibrationSelectionViewModel { + func setup() { $type - .sink { [weak vibroService] in - guard let type = $0 else { return } - vibroService?.applyVibration(type) - } + .compactMap { $0 } + .sink { [weak vibroService] in vibroService?.applyVibration($0) } .store(in: &subscriptions) } } diff --git a/Adamant/Modules/Wallets/Adamant/AdmWalletFactory.swift b/Adamant/Modules/Wallets/Adamant/AdmWalletFactory.swift index 006a55fee..45dfbe682 100644 --- a/Adamant/Modules/Wallets/Adamant/AdmWalletFactory.swift +++ b/Adamant/Modules/Wallets/Adamant/AdmWalletFactory.swift @@ -47,7 +47,8 @@ struct AdmWalletFactory: WalletFactory { dialogService: assembler.resolve(DialogService.self)!, screensFactory: screensFactory, currencyInfoService: assembler.resolve(CurrencyInfoService.self)!, - increaseFeeService: assembler.resolve(IncreaseFeeService.self)! + increaseFeeService: assembler.resolve(IncreaseFeeService.self)!, + vibroService: assembler.resolve(VibroService.self)! ) vc.service = service diff --git a/Adamant/Modules/Wallets/Bitcoin/BtcWalletFactory.swift b/Adamant/Modules/Wallets/Bitcoin/BtcWalletFactory.swift index 2cb06d6bc..30a22a367 100644 --- a/Adamant/Modules/Wallets/Bitcoin/BtcWalletFactory.swift +++ b/Adamant/Modules/Wallets/Bitcoin/BtcWalletFactory.swift @@ -42,7 +42,8 @@ struct BtcWalletFactory: WalletFactory { dialogService: assembler.resolve(DialogService.self)!, screensFactory: screensFactory, currencyInfoService: assembler.resolve(CurrencyInfoService.self)!, - increaseFeeService: assembler.resolve(IncreaseFeeService.self)! + increaseFeeService: assembler.resolve(IncreaseFeeService.self)!, + vibroService: assembler.resolve(VibroService.self)! ) vc.service = service diff --git a/Adamant/Modules/Wallets/Dash/DashWalletFactory.swift b/Adamant/Modules/Wallets/Dash/DashWalletFactory.swift index 289e28e85..7b06abf73 100644 --- a/Adamant/Modules/Wallets/Dash/DashWalletFactory.swift +++ b/Adamant/Modules/Wallets/Dash/DashWalletFactory.swift @@ -41,7 +41,8 @@ struct DashWalletFactory: WalletFactory { dialogService: assembler.resolve(DialogService.self)!, screensFactory: screensFactory, currencyInfoService: assembler.resolve(CurrencyInfoService.self)!, - increaseFeeService: assembler.resolve(IncreaseFeeService.self)! + increaseFeeService: assembler.resolve(IncreaseFeeService.self)!, + vibroService: assembler.resolve(VibroService.self)! ) vc.service = service diff --git a/Adamant/Modules/Wallets/Doge/DogeWalletFactory.swift b/Adamant/Modules/Wallets/Doge/DogeWalletFactory.swift index 2e2dbb1b8..b29af5b36 100644 --- a/Adamant/Modules/Wallets/Doge/DogeWalletFactory.swift +++ b/Adamant/Modules/Wallets/Doge/DogeWalletFactory.swift @@ -41,7 +41,8 @@ struct DogeWalletFactory: WalletFactory { dialogService: assembler.resolve(DialogService.self)!, screensFactory: screensFactory, currencyInfoService: assembler.resolve(CurrencyInfoService.self)!, - increaseFeeService: assembler.resolve(IncreaseFeeService.self)! + increaseFeeService: assembler.resolve(IncreaseFeeService.self)!, + vibroService: assembler.resolve(VibroService.self)! ) vc.service = service diff --git a/Adamant/Modules/Wallets/ERC20/ERC20WalletFactory.swift b/Adamant/Modules/Wallets/ERC20/ERC20WalletFactory.swift index 8cd282f20..22e72d191 100644 --- a/Adamant/Modules/Wallets/ERC20/ERC20WalletFactory.swift +++ b/Adamant/Modules/Wallets/ERC20/ERC20WalletFactory.swift @@ -41,7 +41,8 @@ struct ERC20WalletFactory: WalletFactory { dialogService: assembler.resolve(DialogService.self)!, screensFactory: screensFactory, currencyInfoService: assembler.resolve(CurrencyInfoService.self)!, - increaseFeeService: assembler.resolve(IncreaseFeeService.self)! + increaseFeeService: assembler.resolve(IncreaseFeeService.self)!, + vibroService: assembler.resolve(VibroService.self)! ) vc.service = service diff --git a/Adamant/Modules/Wallets/Ethereum/EthWalletFactory.swift b/Adamant/Modules/Wallets/Ethereum/EthWalletFactory.swift index 1c19d8633..caaffeeba 100644 --- a/Adamant/Modules/Wallets/Ethereum/EthWalletFactory.swift +++ b/Adamant/Modules/Wallets/Ethereum/EthWalletFactory.swift @@ -41,7 +41,8 @@ struct EthWalletFactory: WalletFactory { dialogService: assembler.resolve(DialogService.self)!, screensFactory: screensFactory, currencyInfoService: assembler.resolve(CurrencyInfoService.self)!, - increaseFeeService: assembler.resolve(IncreaseFeeService.self)! + increaseFeeService: assembler.resolve(IncreaseFeeService.self)!, + vibroService: assembler.resolve(VibroService.self)! ) vc.service = service diff --git a/Adamant/Modules/Wallets/Lisk/LskWalletFactory.swift b/Adamant/Modules/Wallets/Lisk/LskWalletFactory.swift index a21ec0b3c..558d7514e 100644 --- a/Adamant/Modules/Wallets/Lisk/LskWalletFactory.swift +++ b/Adamant/Modules/Wallets/Lisk/LskWalletFactory.swift @@ -42,7 +42,8 @@ struct LskWalletFactory: WalletFactory { dialogService: assembler.resolve(DialogService.self)!, screensFactory: screensFactory, currencyInfoService: assembler.resolve(CurrencyInfoService.self)!, - increaseFeeService: assembler.resolve(IncreaseFeeService.self)! + increaseFeeService: assembler.resolve(IncreaseFeeService.self)!, + vibroService: assembler.resolve(VibroService.self)! ) vc.service = service diff --git a/Adamant/Stories/Settings/TestVibration/VibrationSelectionFactory.swift b/Adamant/Stories/Settings/TestVibration/VibrationSelectionFactory.swift deleted file mode 100644 index ff1a24cae..000000000 --- a/Adamant/Stories/Settings/TestVibration/VibrationSelectionFactory.swift +++ /dev/null @@ -1,23 +0,0 @@ -// -// VibrationSelectionFactory.swift -// Adamant -// -// Created by Stanislav Jelezoglo on 07.09.2023. -// Copyright © 2023 Adamant. All rights reserved. -// - -import UIKit -import SwiftUI - -@MainActor -struct VibrationSelectionFactory { - let vibroService: VibroService - - func makeViewController() -> UIViewController { - UIHostingController( - rootView: VibrationSelectionView( - viewModel: .init(vibroService: vibroService) - ) - ) - } -} From 0d551eac4ea0786cd554f7fd08804d6b72961888 Mon Sep 17 00:00:00 2001 From: StanislavDevIOS Date: Tue, 3 Oct 2023 14:50:41 +0200 Subject: [PATCH 17/96] [dev/trello.com/c/FkbZOCIe] feat: process transfer for itself, fix bugs --- .../AdmTransactionsViewController.swift | 3 +- .../Wallets/Adamant/AdmWalletService.swift | 7 +- .../BtcTransactionsViewController.swift | 16 +-- .../Wallets/Bitcoin/BtcWalletService.swift | 12 +- .../Wallets/Dash/DashWalletService.swift | 14 ++- .../Wallets/Doge/DogeWalletService.swift | 12 +- .../Wallets/ERC20/ERC20WalletService.swift | 14 ++- .../Wallets/Ethereum/EthWalletService.swift | 14 ++- .../Lisk/LskTransactionsViewController.swift | 17 ++- .../Wallets/Lisk/LskWalletService.swift | 19 ++- .../Wallets/TransactionTableViewCell.swift | 5 +- .../TransactionsListViewControllerBase.swift | 111 +++++++++++------- Adamant/Modules/Wallets/WalletService.swift | 3 +- Adamant/ServiceProtocols/CoinStorage.swift | 4 + .../Services/AdamantCoinStorageService.swift | 18 ++- .../transfer-self_bot.imageset/Contents.json | 23 ++++ .../transfer-in_bot_transparent.png | Bin 0 -> 425 bytes .../transfer-in_bot_transparent@2x.png | Bin 0 -> 748 bytes .../transfer-in_bot_transparent@3x.png | Bin 0 -> 998 bytes LiskKit/Sources/API/Service/Service.swift | 42 ++++--- .../Models/TransactionModel.swift | 2 + 21 files changed, 233 insertions(+), 103 deletions(-) create mode 100644 CommonKit/Sources/CommonKit/Assets/Shared.xcassets/Transfers/transfer-self_bot.imageset/Contents.json create mode 100644 CommonKit/Sources/CommonKit/Assets/Shared.xcassets/Transfers/transfer-self_bot.imageset/transfer-in_bot_transparent.png create mode 100644 CommonKit/Sources/CommonKit/Assets/Shared.xcassets/Transfers/transfer-self_bot.imageset/transfer-in_bot_transparent@2x.png create mode 100644 CommonKit/Sources/CommonKit/Assets/Shared.xcassets/Transfers/transfer-self_bot.imageset/transfer-in_bot_transparent@3x.png diff --git a/Adamant/Modules/Wallets/Adamant/AdmTransactionsViewController.swift b/Adamant/Modules/Wallets/Adamant/AdmTransactionsViewController.swift index 313a990e8..c15e40e6d 100644 --- a/Adamant/Modules/Wallets/Adamant/AdmTransactionsViewController.swift +++ b/Adamant/Modules/Wallets/Adamant/AdmTransactionsViewController.swift @@ -57,6 +57,7 @@ final class AdmTransactionsViewController: TransactionsListViewControllerBase { super.init(nibName: nibNameOrNil, bundle: nibBundleOrNil) self.dialogService = dialogService + self.walletService = admService } required init?(coder: NSCoder) { @@ -251,7 +252,7 @@ final class AdmTransactionsViewController: TransactionsListViewControllerBase { configureCell( cell, - isOutgoing: transaction.isOutgoing, + transactionType: transaction.isOutgoing ? .outcome : .income, partnerId: partnerId, partnerName: partnerName, amount: amount, diff --git a/Adamant/Modules/Wallets/Adamant/AdmWalletService.swift b/Adamant/Modules/Wallets/Adamant/AdmWalletService.swift index 9431abe3e..d2013ae17 100644 --- a/Adamant/Modules/Wallets/Adamant/AdmWalletService.swift +++ b/Adamant/Modules/Wallets/Adamant/AdmWalletService.swift @@ -130,7 +130,7 @@ final class AdmWalletService: NSObject, WalletService { } func addTransactionObserver() { - coinStorage.$transactions + coinStorage.transactionsPublisher .removeDuplicates() .sink { [weak self] transactions in self?.transactions = transactions @@ -185,8 +185,9 @@ final class AdmWalletService: NSObject, WalletService { return address } - func loadTransactions(offset: Int, limit: Int) async throws { - } + func loadTransactions(offset: Int, limit: Int) async throws -> Int { .zero } + + func getLocalTransactionHistory() -> [CoinTransaction] { [] } } // MARK: - NSFetchedResultsControllerDelegate diff --git a/Adamant/Modules/Wallets/Bitcoin/BtcTransactionsViewController.swift b/Adamant/Modules/Wallets/Bitcoin/BtcTransactionsViewController.swift index ebb173f26..4c07a7b51 100644 --- a/Adamant/Modules/Wallets/Bitcoin/BtcTransactionsViewController.swift +++ b/Adamant/Modules/Wallets/Bitcoin/BtcTransactionsViewController.swift @@ -26,6 +26,7 @@ final class BtcTransactionsViewController: TransactionsListViewControllerBase { func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { tableView.deselectRow(at: indexPath, animated: true) + guard let address = btcWalletService.wallet?.address else { return } let transaction = transactions[indexPath.row] @@ -46,15 +47,14 @@ final class BtcTransactionsViewController: TransactionsListViewControllerBase { controller.transaction = emptyTransaction - if let address = btcWalletService.wallet?.address { - if transaction.senderId?.caseInsensitiveCompare(address) == .orderedSame { - controller.senderName = String.adamant.transactionDetails.yourAddress - } - if transaction.recipientId?.caseInsensitiveCompare(address) == .orderedSame { - controller.recipientName = String.adamant.transactionDetails.yourAddress - } + if emptyTransaction.senderAddress.caseInsensitiveCompare(address) == .orderedSame { + controller.senderName = String.adamant.transactionDetails.yourAddress } - + + if emptyTransaction.recipientAddress.caseInsensitiveCompare(address) == .orderedSame { + controller.recipientName = String.adamant.transactionDetails.yourAddress + } + navigationController?.pushViewController(controller, animated: true) } } diff --git a/Adamant/Modules/Wallets/Bitcoin/BtcWalletService.swift b/Adamant/Modules/Wallets/Bitcoin/BtcWalletService.swift index 0e39eb6ce..3cb7c6688 100644 --- a/Adamant/Modules/Wallets/Bitcoin/BtcWalletService.swift +++ b/Adamant/Modules/Wallets/Bitcoin/BtcWalletService.swift @@ -222,7 +222,7 @@ final class BtcWalletService: WalletService { } func addTransactionObserver() { - coinStorage.$transactions + coinStorage.transactionsPublisher .removeDuplicates() .sink { [weak self] transactions in self?.transactions = transactions @@ -723,7 +723,7 @@ extension BtcWalletService { } } - func loadTransactions(offset: Int, limit: Int) async throws { + func loadTransactions(offset: Int, limit: Int) async throws -> Int { let txId = offset == .zero ? transactions.first?.transactionId : transactions.last?.transactionId @@ -732,10 +732,16 @@ extension BtcWalletService { guard trs.count > 0 else { hasMoreOldTransactions = false - return + return .zero } coinStorage.append(trs) + + return trs.count + } + + func getLocalTransactionHistory() -> [CoinTransaction] { + transactions } } diff --git a/Adamant/Modules/Wallets/Dash/DashWalletService.swift b/Adamant/Modules/Wallets/Dash/DashWalletService.swift index e8cae90de..1d89fc7c1 100644 --- a/Adamant/Modules/Wallets/Dash/DashWalletService.swift +++ b/Adamant/Modules/Wallets/Dash/DashWalletService.swift @@ -200,7 +200,7 @@ final class DashWalletService: WalletService { } func addTransactionObserver() { - coinStorage.$transactions + coinStorage.transactionsPublisher .removeDuplicates() .sink { [weak self] transactions in self?.historyTransactions = transactions @@ -433,9 +433,9 @@ extension DashWalletService { } } - func loadTransactions(offset: Int, limit: Int) async throws { + func loadTransactions(offset: Int, limit: Int) async throws -> Int { guard let address = wallet?.address else { - return + return .zero } let allTransactionsIds = try await requestTransactionsIds(for: address).reversed() @@ -456,10 +456,16 @@ extension DashWalletService { guard trs.count > 0 else { hasMoreOldTransactions = false - return + return .zero } coinStorage.append(trs) + + return trs.count + } + + func getLocalTransactionHistory() -> [CoinTransaction] { + historyTransactions } } diff --git a/Adamant/Modules/Wallets/Doge/DogeWalletService.swift b/Adamant/Modules/Wallets/Doge/DogeWalletService.swift index b0452bfa2..4bfd912f4 100644 --- a/Adamant/Modules/Wallets/Doge/DogeWalletService.swift +++ b/Adamant/Modules/Wallets/Doge/DogeWalletService.swift @@ -190,7 +190,7 @@ final class DogeWalletService: WalletService { } func addTransactionObserver() { - coinStorage.$transactions + coinStorage.transactionsPublisher .removeDuplicates() .sink { [weak self] transactions in self?.historyTransactions = transactions @@ -630,7 +630,7 @@ extension DogeWalletService { } } - func loadTransactions(offset: Int, limit: Int) async throws { + func loadTransactions(offset: Int, limit: Int) async throws -> Int { let tuple = try await getTransactions(from: offset) let trs = tuple.transactions @@ -638,10 +638,16 @@ extension DogeWalletService { guard trs.count > 0 else { hasMoreOldTransactions = false - return + return .zero } coinStorage.append(trs) + + return trs.count + } + + func getLocalTransactionHistory() -> [CoinTransaction] { + historyTransactions } } diff --git a/Adamant/Modules/Wallets/ERC20/ERC20WalletService.swift b/Adamant/Modules/Wallets/ERC20/ERC20WalletService.swift index 660e39865..da39bfcb2 100644 --- a/Adamant/Modules/Wallets/ERC20/ERC20WalletService.swift +++ b/Adamant/Modules/Wallets/ERC20/ERC20WalletService.swift @@ -233,7 +233,7 @@ final class ERC20WalletService: WalletService { } func addTransactionObserver() { - coinStorage.$transactions + coinStorage.transactionsPublisher .removeDuplicates() .sink { [weak self] transactions in self?.historyTransactions = transactions @@ -648,9 +648,9 @@ extension ERC20WalletService { return transactions } - func loadTransactions(offset: Int, limit: Int) async throws { + func loadTransactions(offset: Int, limit: Int) async throws -> Int { guard let address = wallet?.address else { - return + return .zero } let trs = try await getTransactionsHistory( @@ -661,7 +661,7 @@ extension ERC20WalletService { guard trs.count > 0 else { hasMoreOldTransactions = false - return + return .zero } let newTrs = trs.map { transaction in @@ -687,5 +687,11 @@ extension ERC20WalletService { } coinStorage.append(newTrs) + + return trs.count + } + + func getLocalTransactionHistory() -> [CoinTransaction] { + historyTransactions } } diff --git a/Adamant/Modules/Wallets/Ethereum/EthWalletService.swift b/Adamant/Modules/Wallets/Ethereum/EthWalletService.swift index ac0d58803..a0f669714 100644 --- a/Adamant/Modules/Wallets/Ethereum/EthWalletService.swift +++ b/Adamant/Modules/Wallets/Ethereum/EthWalletService.swift @@ -237,7 +237,7 @@ final class EthWalletService: WalletService { } func addTransactionObserver() { - coinStorage.$transactions + coinStorage.transactionsPublisher .removeDuplicates() .sink { [weak self] transactions in self?.historyTransactions = transactions @@ -773,9 +773,9 @@ extension EthWalletService { return transactions } - func loadTransactions(offset: Int, limit: Int) async throws { + func loadTransactions(offset: Int, limit: Int) async throws -> Int { guard let address = wallet?.address else { - return + return . zero } let trs = try await getTransactionsHistory( @@ -786,7 +786,7 @@ extension EthWalletService { guard trs.count > 0 else { hasMoreOldTransactions = false - return + return .zero } let newTrs = trs.map { transaction in @@ -806,6 +806,12 @@ extension EthWalletService { } coinStorage.append(newTrs) + + return trs.count + } + + func getLocalTransactionHistory() -> [CoinTransaction] { + historyTransactions } } diff --git a/Adamant/Modules/Wallets/Lisk/LskTransactionsViewController.swift b/Adamant/Modules/Wallets/Lisk/LskTransactionsViewController.swift index e9172803a..2395ff117 100644 --- a/Adamant/Modules/Wallets/Lisk/LskTransactionsViewController.swift +++ b/Adamant/Modules/Wallets/Lisk/LskTransactionsViewController.swift @@ -30,6 +30,7 @@ final class LskTransactionsViewController: TransactionsListViewControllerBase { func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { tableView.deselectRow(at: indexPath, animated: true) + guard let address = lskWalletService.wallet?.address else { return } let transaction = transactions[indexPath.row] let controller = screensFactory.makeDetailsVC(service: lskWalletService) @@ -49,12 +50,12 @@ final class LskTransactionsViewController: TransactionsListViewControllerBase { controller.transaction = emptyTransaction - if let address = lskWalletService.wallet?.address { - if transaction.senderId?.caseInsensitiveCompare(address) == .orderedSame { - controller.senderName = String.adamant.transactionDetails.yourAddress - } else if transaction.recipientId?.caseInsensitiveCompare(address) == .orderedSame { - controller.recipientName = String.adamant.transactionDetails.yourAddress - } + if emptyTransaction.senderAddress.caseInsensitiveCompare(address) == .orderedSame { + controller.senderName = String.adamant.transactionDetails.yourAddress + } + + if emptyTransaction.recipientAddress.caseInsensitiveCompare(address) == .orderedSame { + controller.recipientName = String.adamant.transactionDetails.yourAddress } navigationController?.pushViewController(controller, animated: true) @@ -103,10 +104,6 @@ extension Transactions.TransactionModel: TransactionDetails { return self.blockId } - var isOutgoing: Bool { - return false - } - var transactionStatus: TransactionStatus? { guard let confirmations = confirmations, let height = height else { return .registered } if confirmations < height { return .registered } diff --git a/Adamant/Modules/Wallets/Lisk/LskWalletService.swift b/Adamant/Modules/Wallets/Lisk/LskWalletService.swift index c5d983246..a60aa0013 100644 --- a/Adamant/Modules/Wallets/Lisk/LskWalletService.swift +++ b/Adamant/Modules/Wallets/Lisk/LskWalletService.swift @@ -188,7 +188,7 @@ final class LskWalletService: WalletService { } func addTransactionObserver() { - coinStorage.$transactions + coinStorage.transactionsPublisher .removeDuplicates() .sink { [weak self] transactions in self?.transactions = transactions @@ -594,15 +594,20 @@ extension LskWalletService { } } - func loadTransactions(offset: Int, limit: Int) async throws { + func loadTransactions(offset: Int, limit: Int) async throws -> Int { let trs = try await getTransactions(offset: UInt(offset), limit: UInt(limit)) guard trs.count > 0 else { hasMoreOldTransactions = false - return + return .zero } coinStorage.append(trs) + return trs.count + } + + func getLocalTransactionHistory() -> [CoinTransaction] { + transactions } } @@ -654,6 +659,7 @@ extension LskWalletService { return try await withUnsafeThrowingContinuation { (continuation: UnsafeContinuation<[Transactions.TransactionModel], Error>) in transactionApi.transactions( + ownerAddress: address, senderIdOrRecipientId: address, limit: limit, offset: offset, @@ -680,7 +686,12 @@ extension LskWalletService { } return try await withUnsafeThrowingContinuation { (continuation: UnsafeContinuation) in - api.transactions(id: hash, limit: 1, offset: 0) { (response) in + api.transactions( + ownerAddress: wallet?.address, + id: hash, + limit: 1, + offset: 0 + ) { (response) in switch response { case .success(response: let result): if let transaction = result.first { diff --git a/Adamant/Modules/Wallets/TransactionTableViewCell.swift b/Adamant/Modules/Wallets/TransactionTableViewCell.swift index 5ac3f7a4c..85507d17d 100644 --- a/Adamant/Modules/Wallets/TransactionTableViewCell.swift +++ b/Adamant/Modules/Wallets/TransactionTableViewCell.swift @@ -11,12 +11,13 @@ import CommonKit class TransactionTableViewCell: UITableViewCell { enum TransactionType { - case income, outcome + case income, outcome, myself var imageTop: UIImage { switch self { case .income: return .asset(named: "transfer-in_top") ?? .init() case .outcome: return .asset(named: "transfer-out_top") ?? .init() + case .myself: return .asset(named: "transfer-in_top")?.withTintColor(.lightGray) ?? .init() } } @@ -24,6 +25,7 @@ class TransactionTableViewCell: UITableViewCell { switch self { case .income: return .asset(named: "transfer-in_bot") ?? .init() case .outcome: return .asset(named: "transfer-out_bot") ?? .init() + case .myself: return .asset(named: "transfer-self_bot") ?? .init() } } @@ -31,6 +33,7 @@ class TransactionTableViewCell: UITableViewCell { switch self { case .income: return UIColor.adamant.transferIncomeIconBackground case .outcome: return UIColor.adamant.transferOutcomeIconBackground + case .myself: return UIColor.adamant.transferIncomeIconBackground } } } diff --git a/Adamant/Modules/Wallets/TransactionsListViewControllerBase.swift b/Adamant/Modules/Wallets/TransactionsListViewControllerBase.swift index fc4d0a001..264762edc 100644 --- a/Adamant/Modules/Wallets/TransactionsListViewControllerBase.swift +++ b/Adamant/Modules/Wallets/TransactionsListViewControllerBase.swift @@ -48,13 +48,14 @@ class TransactionsListViewControllerBase: UIViewController { var taskManager = TaskManager() var isNeedToLoadMoore = true - var isBusy = true + var isBusy = false var transactions: [CoinTransaction] = [] private(set) lazy var loadingView = LoadingView() private var subscriptions = Set() private var limit = 25 + private var offset = 0 // MARK: - IBOutlets @IBOutlet weak var tableView: UITableView! @@ -69,32 +70,11 @@ class TransactionsListViewControllerBase: UIViewController { navigationItem.title = String.adamant.transactionList.title emptyLabel.text = String.adamant.transactionList.noTransactionYet - // MARK: Configure tableView - let nib = UINib.init(nibName: "TransactionTableViewCell", bundle: nil) - tableView.register(nib, forCellReuseIdentifier: cellIdentifierFull) - tableView.register(nib, forCellReuseIdentifier: cellIdentifierCompact) - tableView.dataSource = self - tableView.delegate = self - tableView.refreshControl = refreshControl - tableView.tableHeaderView = UIView() - - // MARK: Notifications - NotificationCenter.default.addObserver(forName: Notification.Name.AdamantAccountService.userLoggedIn, object: nil, queue: OperationQueue.main) { [weak self] _ in - self?.reloadData() - } - - NotificationCenter.default.addObserver(forName: Notification.Name.AdamantAccountService.userLoggedOut, object: nil, queue: OperationQueue.main) { [weak self] _ in - self?.reloadData() - } - - NotificationCenter.default.addObserver(forName: Notification.Name.AdamantAddressBookService.addressBookUpdated, object: nil, queue: OperationQueue.main) { [weak self] _ in - self?.reloadData() - } - + transactions = walletService.getLocalTransactionHistory() + configureTableView() setColors() configureLayout() addObservers() - handleRefresh() } @@ -112,7 +92,32 @@ class TransactionsListViewControllerBase: UIViewController { } func addObservers() { + NotificationCenter.default + .publisher(for: .AdamantAddressBookService.addressBookUpdated, object: nil) + .receive(on: OperationQueue.main) + .sink { [weak self] _ in + self?.reloadData() + } + .store(in: &subscriptions) + + NotificationCenter.default + .publisher(for: .AdamantAccountService.userLoggedOut, object: nil) + .receive(on: OperationQueue.main) + .sink { [weak self] _ in + self?.reloadData() + } + .store(in: &subscriptions) + + NotificationCenter.default + .publisher(for: .AdamantAccountService.userLoggedIn, object: nil) + .receive(on: OperationQueue.main) + .sink { [weak self] _ in + self?.reloadData() + } + .store(in: &subscriptions) + walletService.transactionsPublisher + .receive(on: OperationQueue.main) .sink { [weak self] transactions in self?.update(transactions) } @@ -125,13 +130,25 @@ class TransactionsListViewControllerBase: UIViewController { .store(in: &subscriptions) } + func configureTableView() { + let nib = UINib.init(nibName: "TransactionTableViewCell", bundle: nil) + tableView.register(nib, forCellReuseIdentifier: cellIdentifierFull) + tableView.register(nib, forCellReuseIdentifier: cellIdentifierCompact) + tableView.dataSource = self + tableView.delegate = self + tableView.refreshControl = refreshControl + tableView.tableHeaderView = UIView() + } + @MainActor func update(_ transactions: [CoinTransaction]) { - DispatchQueue.main.async { - self.transactions = transactions - self.tableView.reloadData() - self.updateLoadingView(isHidden: true) - } + self.transactions = transactions.sorted(by: { + (($0.date ?? NSDate()) as Date) > (($1.date ?? NSDate()) as Date) + }) + self.tableView.reloadData() + + guard !isBusy else { return } + self.updateLoadingView(isHidden: true) } // MARK: - Other @@ -150,6 +167,11 @@ class TransactionsListViewControllerBase: UIViewController { } } + func presentLoadingViewIfNeeded() { + guard transactions.count == 0 else { return } + updateLoadingView(isHidden: false) + } + func updateLoadingView(isHidden: Bool) { loadingView.isHidden = isHidden if !isHidden { @@ -161,20 +183,23 @@ class TransactionsListViewControllerBase: UIViewController { @MainActor func loadData(silent: Bool) { - loadData(offset: transactions.count, silent: true) + loadData(offset: offset, silent: true) } @MainActor func loadData(offset: Int, silent: Bool) { + guard !isBusy else { return } isBusy = true - print("loadData, ofs=\(offset)") - Task { @MainActor in + Task { do { - try await walletService.loadTransactions( + let count = try await walletService.loadTransactions( offset: offset, limit: limit ) + self.offset += count } catch { + isNeedToLoadMoore = false + if !silent { dialogService.showRichError(error: error) } @@ -215,9 +240,18 @@ class TransactionsListViewControllerBase: UIViewController { ? transaction.recipientId : transaction.senderId + let transactionType: TransactionTableViewCell.TransactionType + if transaction.recipientId == transaction.senderId { + transactionType = .myself + } else if transaction.isOutgoing { + transactionType = .outcome + } else { + transactionType = .income + } + configureCell( cell, - isOutgoing: transaction.isOutgoing, + transactionType: transactionType, partnerId: partnerId ?? "", partnerName: nil, amount: (transaction.amount ?? 0).decimalValue, @@ -248,6 +282,7 @@ class TransactionsListViewControllerBase: UIViewController { } @objc func handleRefresh() { + presentLoadingViewIfNeeded() emptyLabel.isHidden = true loadData(offset: .zero, silent: true) } @@ -269,7 +304,7 @@ extension TransactionsListViewControllerBase: UITableViewDataSource, UITableView func configureCell( _ cell: TransactionTableViewCell, - isOutgoing: Bool, + transactionType: TransactionTableViewCell.TransactionType, partnerId: String, partnerName: String?, amount: Decimal, @@ -280,11 +315,7 @@ extension TransactionsListViewControllerBase: UITableViewDataSource, UITableView cell.ammountLabel.tintColor = UIColor.adamant.primary cell.dateLabel.tintColor = UIColor.adamant.secondary - if isOutgoing { - cell.transactionType = .outcome - } else { - cell.transactionType = .income - } + cell.transactionType = transactionType if let partnerName = partnerName { cell.accountLabel.text = partnerName diff --git a/Adamant/Modules/Wallets/WalletService.swift b/Adamant/Modules/Wallets/WalletService.swift index d4f6f779c..67150c49f 100644 --- a/Adamant/Modules/Wallets/WalletService.swift +++ b/Adamant/Modules/Wallets/WalletService.swift @@ -249,7 +249,8 @@ protocol WalletService: AnyObject { func validate(address: String) -> AddressValidationResult func getWalletAddress(byAdamantAddress address: String) async throws -> String func getBalance(address: String) async throws -> Decimal - func loadTransactions(offset: Int, limit: Int) async throws + func loadTransactions(offset: Int, limit: Int) async throws -> Int + func getLocalTransactionHistory() -> [CoinTransaction] } protocol SwinjectDependentService: WalletService { diff --git a/Adamant/ServiceProtocols/CoinStorage.swift b/Adamant/ServiceProtocols/CoinStorage.swift index 0331cedf6..7b320f3dc 100644 --- a/Adamant/ServiceProtocols/CoinStorage.swift +++ b/Adamant/ServiceProtocols/CoinStorage.swift @@ -9,6 +9,10 @@ import Foundation protocol CoinStorageService: AnyObject { + var transactionsPublisher: Published<[CoinTransaction]>.Publisher { + get + } + func append(_ transaction: TransactionDetails) func append(_ transactions: [TransactionDetails]) } diff --git a/Adamant/Services/AdamantCoinStorageService.swift b/Adamant/Services/AdamantCoinStorageService.swift index 942052336..cb1eac794 100644 --- a/Adamant/Services/AdamantCoinStorageService.swift +++ b/Adamant/Services/AdamantCoinStorageService.swift @@ -18,8 +18,12 @@ final class AdamantCoinStorageService: NSObject, CoinStorageService { private let coinId: String private let coreDataStack: CoreDataStack private lazy var transactionController = getTransactionController() - - @ObservableValue private(set) var transactions: [CoinTransaction] = [] + + @Published private var transactions: [CoinTransaction] = [] + + var transactionsPublisher: Published<[CoinTransaction]>.Publisher { + $transactions + } // MARK: Init @@ -43,9 +47,17 @@ final class AdamantCoinStorageService: NSObject, CoinStorageService { var coinTransactions: [CoinTransaction] = [] transactions.forEach { transaction in + let isExist = self.transactions.contains { tx in + tx.transactionId == transaction.txId + } + let isLocalExist = coinTransactions.contains { tx in + tx.transactionId == transaction.txId + } + guard !isExist, !isLocalExist else { return } + let coinTransaction = CoinTransaction(context: privateContext) coinTransaction.amount = NSDecimalNumber(decimal: transaction.amountValue ?? 0) - coinTransaction.date = transaction.dateValue as? NSDate + coinTransaction.date = (transaction.dateValue ?? Date()) as? NSDate coinTransaction.recipientId = transaction.recipientAddress coinTransaction.senderId = transaction.senderAddress coinTransaction.isOutgoing = transaction.isOutgoing diff --git a/CommonKit/Sources/CommonKit/Assets/Shared.xcassets/Transfers/transfer-self_bot.imageset/Contents.json b/CommonKit/Sources/CommonKit/Assets/Shared.xcassets/Transfers/transfer-self_bot.imageset/Contents.json new file mode 100644 index 000000000..585ef3f55 --- /dev/null +++ b/CommonKit/Sources/CommonKit/Assets/Shared.xcassets/Transfers/transfer-self_bot.imageset/Contents.json @@ -0,0 +1,23 @@ +{ + "images" : [ + { + "filename" : "transfer-in_bot_transparent.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "transfer-in_bot_transparent@2x.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "transfer-in_bot_transparent@3x.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/CommonKit/Sources/CommonKit/Assets/Shared.xcassets/Transfers/transfer-self_bot.imageset/transfer-in_bot_transparent.png b/CommonKit/Sources/CommonKit/Assets/Shared.xcassets/Transfers/transfer-self_bot.imageset/transfer-in_bot_transparent.png new file mode 100644 index 0000000000000000000000000000000000000000..38c526d51e31707a22e0dc2e50145987cde594f2 GIT binary patch literal 425 zcmV;a0apHrP)_q&ak=!M>WFdSUYkNSle{!i{u;1=$cPKUM&$Ni~Bm4cq zz>$ODxxD+>gLQb8H z6^8M2AI8O(V8&rQjzg%w2%{+ETgpd15ao%{FRhx=HrhYDs{hib(I za!N)P8?0t@;7rNMz4K5m{3^4WQ2~-Nn$agjrPYsV1_}l8!aV&upiUUbzXXD@prsMv zH-Jd35X==E&~SiiA)pJZ%4nKP4b=}bHmmIE;*<$qP3c3YL}~+6wE_^p$zaPYyG7tBcYP-0cADW}NF^5E1M|5A9=tIe)Dd3i-Z&r%E8F4p zA;rc3Q9{myJHmtGM0Yd2faoYcI9QV+oDUNYI$Cm+FOh@?oN5C^p(Tae$|wrd0;2dm z2=^5Z^QS5Yr4EPIP)XlKfg<;$!Y^ei9klnzJ$)C2ssQ=soad<2~{mP#my(CKUyOr45jL2SV&12E>4@1=KO1X*CRp9n>raq`R}r z618iSbi)psR_!^UngLDZSZv{AKr?^aDnQgtT6qenQgxH|5l{;c|62(lYPD^BbdSnf z?Cw)?hE*yBL~U%oBIdKg;T08t0~)un=z9;z?`sF#m&N8nti=F4<<+GBHrt&N?taI^y=Ncait$udZo zjBn3OCYqY|dGsMmOHQk&Z~jIH>ZlJn+sI9Lc;v5+X>Kub7!f2>vm70j4~y!>&4zjn zfNH5KFV{>#Z$1`I`6;0lD{u>jQ1y5}XRWOI#{vXPivP3o(acfZ(q5|=181>VEEbE! eV*TnzfB^vM>(NF_RMX}F0000yG4{_mtL@3eqAfwasK-TK)(v?4Y*2-YbD; zguN$fCX(i!KYFL-DRFy*wM$bilFGop&Pn7ou2YvMEUIpCEb@(()wmRwCnifvT0|)7 zp17@&){Ih^5~Ul6rF3{Hp}WhC5_cGlFePg#%U{aqC2})pVM>%v(<;GKJxkkKi8~HX zn6&Au1ddhj9NX4?P6P4?Wz9v_kkQh1$qO6E@yh9OEssdnD!`SB(Cn17b(bF~B1+cS zWNGH|x2Gdaj#+g!UTUd9{KAZGaO|w47V!!*&a>cG+BD{b4e>my50f@u;381EQoJtc zgXya#qzE9Z8hMk^rU|KHO4t%7@7JjCoUWRsu=Ubd2NfoFiWj(O%|dDxEMYO;(vQ7s ztb+(Mp2L9d4ag)!!bq2;l=%`ir}lPg=UkXvBP*%<+mz;YMjr{AQFD7WEF7n6!g6SL zS#+JSidtJev^W>Wa%j1hBrF{+wagtxSa*hhsIyhRT_%i+ybfxLFoYp2RbgB^bWl?g zM#4x~stE%JAPixEFhCf>0Ab))*qQR$uc=F6N9t?8rX-Ank+4(~hA@O7%$YEGq49|t zTjQi_>iRtFK%MQ@(zUQXb+%he5|#+pSN0W-KSZC4?I?F|erj0wiZa-#Vc{vtbc_AE z!@bXhWh{&KYUfLset$dH7Q1sDT;NxjG#?H1@BT%bG@-9pU$IyU0Z!`|k2oq@=md1xm;nNi;w#8Ux=_5+>3ZshE;5k;c%{l!OiJxHkN;z&FyWjKSOK2s6Yn z+CH_l#TZ%;nAz!cP1)%VRudgW|{vtcxy@fK5hCa zg*}j0IY^#P(!sZ+K?}Q)S4wAj-6qEV)v@Sk>@S=A&l>;$000000000$^!yWG0JaY} U(M2YmZU6uP07*qoM6N<$g4jgQvj6}9 literal 0 HcmV?d00001 diff --git a/LiskKit/Sources/API/Service/Service.swift b/LiskKit/Sources/API/Service/Service.swift index c325f00e6..bee73e0ed 100644 --- a/LiskKit/Sources/API/Service/Service.swift +++ b/LiskKit/Sources/API/Service/Service.swift @@ -38,7 +38,18 @@ extension Service { } /// List transaction objects - public func transactions(id: String? = nil, block: String? = nil, sender: String? = nil, recipient: String? = nil, senderIdOrRecipientId: String? = nil, limit: UInt? = nil, offset: UInt? = nil, sort: APIRequest.Sort? = nil, completionHandler: @escaping (Result<[Transactions.TransactionModel]>) -> Void) { + public func transactions( + ownerAddress: String?, + id: String? = nil, + block: String? = nil, + sender: String? = nil, + recipient: String? = nil, + senderIdOrRecipientId: String? = nil, + limit: UInt? = nil, + offset: UInt? = nil, + sort: APIRequest.Sort? = nil, + completionHandler: @escaping (Result<[Transactions.TransactionModel]>) -> Void + ) { if version == .v1 { transactionsV1(id: id, block: block, sender: sender, recipient: recipient, senderIdOrRecipientId: senderIdOrRecipientId, limit: limit, offset: offset, sort: sort) { result in switch result { @@ -53,19 +64,22 @@ extension Service { switch result { case .success(response: let value): let transaction = value.data.map { - Transactions.TransactionModel(id: $0.id, - height: $0.height, - blockId: $0.blockId, - type: $0.type, - timestamp: $0.timestamp, - senderPublicKey: $0.senderPublicKey, - senderId: $0.senderId, - recipientId: $0.recipientId, - recipientPublicKey: $0.recipientPublicKey, - amount: $0.amount, - fee: $0.fee, - signature: $0.signature, - confirmations: $0.confirmations) + Transactions.TransactionModel( + id: $0.id, + height: $0.height, + blockId: $0.blockId, + type: $0.type, + timestamp: $0.timestamp, + senderPublicKey: $0.senderPublicKey, + senderId: $0.senderId, + recipientId: $0.recipientId, + recipientPublicKey: $0.recipientPublicKey, + amount: $0.amount, + fee: $0.fee, + signature: $0.signature, + confirmations: $0.confirmations, + isOutgoing: $0.senderId.lowercased() == ownerAddress?.lowercased() + ) } completionHandler(.success(response: transaction)) case .error(response: let error): diff --git a/LiskKit/Sources/API/Transactions/Models/TransactionModel.swift b/LiskKit/Sources/API/Transactions/Models/TransactionModel.swift index fbfdc0f9b..51a9b3abc 100644 --- a/LiskKit/Sources/API/Transactions/Models/TransactionModel.swift +++ b/LiskKit/Sources/API/Transactions/Models/TransactionModel.swift @@ -50,6 +50,8 @@ extension Transactions { public let signature: String public var confirmations: UInt64? + + public var isOutgoing: Bool = false // MARK: - Hashable From 1f047e0bc257150b50477913f6684023fc27a281 Mon Sep 17 00:00:00 2001 From: StanislavDevIOS Date: Wed, 4 Oct 2023 12:06:26 +0200 Subject: [PATCH 18/96] [dev/trello.com/c/FkbZOCIe] feat: transaction status in tx list --- .../BaseTransaction+CoreDataClass.swift | 3 -- .../ChatTransaction+CoreDataClass.swift | 9 +++-- .../CoinTransaction+CoreDataClass.swift | 2 + .../CoinTransaction+CoreDataProperties.swift | 2 - .../AdmTransactionsViewController.swift | 1 + .../Bitcoin/BtcTransferViewController.swift | 28 +++++++++++++- .../Dash/DashTransferViewController.swift | 24 +++++++++++- .../Doge/DogeTransferViewController.swift | 24 +++++++++++- .../ERC20/ERC20TransferViewController.swift | 8 ++++ .../Ethereum/EthTransferViewController.swift | 8 ++++ .../Wallets/Ethereum/EthWalletService.swift | 2 +- .../Lisk/LskTransferViewController.swift | 8 ++++ .../TransactionsListViewControllerBase.swift | 35 +++++++++++++---- .../Services/AdamantCoinStorageService.swift | 38 ++++++++++++++++++- 14 files changed, 169 insertions(+), 23 deletions(-) diff --git a/Adamant/Models/CoreData/BaseTransaction+CoreDataClass.swift b/Adamant/Models/CoreData/BaseTransaction+CoreDataClass.swift index e68bb1073..c99b70b3e 100644 --- a/Adamant/Models/CoreData/BaseTransaction+CoreDataClass.swift +++ b/Adamant/Models/CoreData/BaseTransaction+CoreDataClass.swift @@ -12,7 +12,4 @@ import CoreData @objc(BaseTransaction) public class BaseTransaction: CoinTransaction { - var transactionStatus: TransactionStatus? { - return nil - } } diff --git a/Adamant/Models/CoreData/ChatTransaction+CoreDataClass.swift b/Adamant/Models/CoreData/ChatTransaction+CoreDataClass.swift index c52f4a125..75780eeb0 100644 --- a/Adamant/Models/CoreData/ChatTransaction+CoreDataClass.swift +++ b/Adamant/Models/CoreData/ChatTransaction+CoreDataClass.swift @@ -26,8 +26,11 @@ public class ChatTransaction: BaseTransaction { } override var transactionStatus: TransactionStatus? { - return confirmations > 0 - ? .success - : .pending + get { + return confirmations > 0 + ? .success + : .pending + } + set { } } } diff --git a/Adamant/Models/CoreData/CoinTransaction+CoreDataClass.swift b/Adamant/Models/CoreData/CoinTransaction+CoreDataClass.swift index 626606434..6683ebeee 100644 --- a/Adamant/Models/CoreData/CoinTransaction+CoreDataClass.swift +++ b/Adamant/Models/CoreData/CoinTransaction+CoreDataClass.swift @@ -13,4 +13,6 @@ import CoreData @objc(CoinTransaction) public class CoinTransaction: NSManagedObject { static let entityCoinName = "CoinTransaction" + + var transactionStatus: TransactionStatus? } diff --git a/Adamant/Models/CoreData/CoinTransaction+CoreDataProperties.swift b/Adamant/Models/CoreData/CoinTransaction+CoreDataProperties.swift index f2dc8da7e..a2b694f09 100644 --- a/Adamant/Models/CoreData/CoinTransaction+CoreDataProperties.swift +++ b/Adamant/Models/CoreData/CoinTransaction+CoreDataProperties.swift @@ -23,8 +23,6 @@ extension CoinTransaction { @NSManaged public var recipientId: String? @NSManaged public var date: NSDate? @NSManaged public var isOutgoing: Bool - -// public var transactionStatus: TransactionStatus? } extension CoinTransaction : Identifiable { diff --git a/Adamant/Modules/Wallets/Adamant/AdmTransactionsViewController.swift b/Adamant/Modules/Wallets/Adamant/AdmTransactionsViewController.swift index c15e40e6d..7471eb483 100644 --- a/Adamant/Modules/Wallets/Adamant/AdmTransactionsViewController.swift +++ b/Adamant/Modules/Wallets/Adamant/AdmTransactionsViewController.swift @@ -253,6 +253,7 @@ final class AdmTransactionsViewController: TransactionsListViewControllerBase { configureCell( cell, transactionType: transaction.isOutgoing ? .outcome : .income, + transactionStatus: transaction.transactionStatus, partnerId: partnerId, partnerName: partnerName, amount: amount, diff --git a/Adamant/Modules/Wallets/Bitcoin/BtcTransferViewController.swift b/Adamant/Modules/Wallets/Bitcoin/BtcTransferViewController.swift index b8637bde9..12abf9316 100644 --- a/Adamant/Modules/Wallets/Bitcoin/BtcTransferViewController.swift +++ b/Adamant/Modules/Wallets/Bitcoin/BtcTransferViewController.swift @@ -35,7 +35,11 @@ final class BtcTransferViewController: TransferViewControllerBase { comments = "" } - guard let service = service as? BtcWalletService, let recipient = recipientAddress, let amount = amount else { + guard let service = service as? BtcWalletService, + let recipient = recipientAddress, + let amount = amount, + let wallet = service.wallet + else { return } @@ -58,10 +62,30 @@ final class BtcTransferViewController: TransferViewControllerBase { Task { do { - service.coinStorage.append(transaction) + let simpleTransaction = SimpleTransactionDetails( + txId: transaction.txID, + senderAddress: wallet.address, + recipientAddress: recipient, + amountValue: amount, + feeValue: nil, + confirmationsValue: nil, + blockValue: nil, + isOutgoing: true, + transactionStatus: nil + ) + + service.coinStorage.append(simpleTransaction) try await service.sendTransaction(transaction) + service.coinStorage.updateStatus( + for: transaction.txId, + status: .registered + ) } catch { dialogService.showRichError(error: error) + service.coinStorage.updateStatus( + for: transaction.txId, + status: .failed + ) } await service.update() diff --git a/Adamant/Modules/Wallets/Dash/DashTransferViewController.swift b/Adamant/Modules/Wallets/Dash/DashTransferViewController.swift index 24da2d6ec..6731084c1 100644 --- a/Adamant/Modules/Wallets/Dash/DashTransferViewController.swift +++ b/Adamant/Modules/Wallets/Dash/DashTransferViewController.swift @@ -40,7 +40,7 @@ final class DashTransferViewController: TransferViewControllerBase { return } - guard service.wallet != nil else { + guard let wallet = service.wallet else { return } @@ -64,10 +64,30 @@ final class DashTransferViewController: TransferViewControllerBase { Task { do { - service.coinStorage.append(transaction) + let simpleTransaction = SimpleTransactionDetails( + txId: transaction.txID, + senderAddress: wallet.address, + recipientAddress: recipient, + amountValue: amount, + feeValue: nil, + confirmationsValue: nil, + blockValue: nil, + isOutgoing: true, + transactionStatus: nil + ) + + service.coinStorage.append(simpleTransaction) try await service.sendTransaction(transaction) + service.coinStorage.updateStatus( + for: transaction.txId, + status: .registered + ) } catch { dialogService.showRichError(error: error) + service.coinStorage.updateStatus( + for: transaction.txId, + status: .failed + ) } await service.update() diff --git a/Adamant/Modules/Wallets/Doge/DogeTransferViewController.swift b/Adamant/Modules/Wallets/Doge/DogeTransferViewController.swift index ba52b21fb..08aa7ef26 100644 --- a/Adamant/Modules/Wallets/Doge/DogeTransferViewController.swift +++ b/Adamant/Modules/Wallets/Doge/DogeTransferViewController.swift @@ -31,7 +31,7 @@ final class DogeTransferViewController: TransferViewControllerBase { return } - guard service.wallet != nil else { + guard let wallet = service.wallet else { return } @@ -55,10 +55,30 @@ final class DogeTransferViewController: TransferViewControllerBase { Task { do { - service.coinStorage.append(transaction) + let simpleTransaction = SimpleTransactionDetails( + txId: transaction.txID, + senderAddress: wallet.address, + recipientAddress: recipient, + amountValue: amount, + feeValue: nil, + confirmationsValue: nil, + blockValue: nil, + isOutgoing: true, + transactionStatus: nil + ) + + service.coinStorage.append(simpleTransaction) try await service.sendTransaction(transaction) + service.coinStorage.updateStatus( + for: transaction.txId, + status: .registered + ) } catch { dialogService.showRichError(error: error) + service.coinStorage.updateStatus( + for: transaction.txId, + status: .failed + ) } await service.update() diff --git a/Adamant/Modules/Wallets/ERC20/ERC20TransferViewController.swift b/Adamant/Modules/Wallets/ERC20/ERC20TransferViewController.swift index 0f7577535..8b98ed018 100644 --- a/Adamant/Modules/Wallets/ERC20/ERC20TransferViewController.swift +++ b/Adamant/Modules/Wallets/ERC20/ERC20TransferViewController.swift @@ -66,8 +66,16 @@ final class ERC20TransferViewController: TransferViewControllerBase { Task { do { try await service.sendTransaction(transaction) + service.coinStorage.updateStatus( + for: txHash, + status: .registered + ) } catch { dialogService.showRichError(error: error) + service.coinStorage.updateStatus( + for: txHash, + status: .failed + ) } await service.update() diff --git a/Adamant/Modules/Wallets/Ethereum/EthTransferViewController.swift b/Adamant/Modules/Wallets/Ethereum/EthTransferViewController.swift index 4021619bd..89821ea25 100644 --- a/Adamant/Modules/Wallets/Ethereum/EthTransferViewController.swift +++ b/Adamant/Modules/Wallets/Ethereum/EthTransferViewController.swift @@ -60,8 +60,16 @@ final class EthTransferViewController: TransferViewControllerBase { Task { do { try await service.sendTransaction(transaction) + service.coinStorage.updateStatus( + for: txHash, + status: .registered + ) } catch { dialogService.showRichError(error: error) + service.coinStorage.updateStatus( + for: txHash, + status: .failed + ) } await service.update() diff --git a/Adamant/Modules/Wallets/Ethereum/EthWalletService.swift b/Adamant/Modules/Wallets/Ethereum/EthWalletService.swift index a0f669714..0c6bff1ab 100644 --- a/Adamant/Modules/Wallets/Ethereum/EthWalletService.swift +++ b/Adamant/Modules/Wallets/Ethereum/EthWalletService.swift @@ -801,7 +801,7 @@ extension EthWalletService { confirmationsValue: nil, blockValue: nil, isOutgoing: isOutgoing, - transactionStatus: nil + transactionStatus: TransactionStatus.registered ) } diff --git a/Adamant/Modules/Wallets/Lisk/LskTransferViewController.swift b/Adamant/Modules/Wallets/Lisk/LskTransferViewController.swift index f7c0a769a..1ddc52fe5 100644 --- a/Adamant/Modules/Wallets/Lisk/LskTransferViewController.swift +++ b/Adamant/Modules/Wallets/Lisk/LskTransferViewController.swift @@ -70,8 +70,16 @@ final class LskTransferViewController: TransferViewControllerBase { do { service.coinStorage.append(transaction) try await service.sendTransaction(transaction) + service.coinStorage.updateStatus( + for: transaction.id, + status: .registered + ) } catch { dialogService.showRichError(error: error) + service.coinStorage.updateStatus( + for: transaction.id, + status: .failed + ) } await service.update() diff --git a/Adamant/Modules/Wallets/TransactionsListViewControllerBase.swift b/Adamant/Modules/Wallets/TransactionsListViewControllerBase.swift index 264762edc..d6386388d 100644 --- a/Adamant/Modules/Wallets/TransactionsListViewControllerBase.swift +++ b/Adamant/Modules/Wallets/TransactionsListViewControllerBase.swift @@ -252,6 +252,7 @@ class TransactionsListViewControllerBase: UIViewController { configureCell( cell, transactionType: transactionType, + transactionStatus: transaction.transactionStatus, partnerId: partnerId ?? "", partnerName: nil, amount: (transaction.amount ?? 0).decimalValue, @@ -305,6 +306,7 @@ extension TransactionsListViewControllerBase: UITableViewDataSource, UITableView func configureCell( _ cell: TransactionTableViewCell, transactionType: TransactionTableViewCell.TransactionType, + transactionStatus: TransactionStatus?, partnerId: String, partnerName: String?, amount: Decimal, @@ -313,7 +315,21 @@ extension TransactionsListViewControllerBase: UITableViewDataSource, UITableView cell.backgroundColor = .clear cell.accountLabel.tintColor = UIColor.adamant.primary cell.ammountLabel.tintColor = UIColor.adamant.primary - cell.dateLabel.tintColor = UIColor.adamant.secondary + + cell.dateLabel.textColor = transactionStatus?.color ?? .adamant.secondary + + switch transactionStatus { + case .success, .inconsistent, .registered: + if let date = date { + cell.dateLabel.text = date.humanizedDateTime() + } else { + cell.dateLabel.text = nil + } + case .failed: + cell.dateLabel.text = TransactionStatus.failed.localized + default: + cell.dateLabel.text = TransactionStatus.pending.localized + } cell.transactionType = transactionType @@ -334,12 +350,6 @@ extension TransactionsListViewControllerBase: UITableViewDataSource, UITableView } cell.ammountLabel.text = AdamantBalanceFormat.full.format(amount, withCurrencySymbol: currencySymbol) - - if let date = date { - cell.dateLabel.text = date.humanizedDateTime() - } else { - cell.dateLabel.text = nil - } } func bottomIndicatorView() -> UIActivityIndicatorView { @@ -375,3 +385,14 @@ extension TransactionsListViewControllerBase: UITableViewDataSource, UITableView tableView.tableFooterView = nil } } + +// MARK: - TransactionStatus UI +private extension TransactionStatus { + var color: UIColor { + switch self { + case .failed: return .adamant.danger + case .notInitiated, .inconsistent, .noNetwork, .noNetworkFinal, .pending: return .adamant.alert + case .success, .registered: return .adamant.secondary + } + } +} diff --git a/Adamant/Services/AdamantCoinStorageService.swift b/Adamant/Services/AdamantCoinStorageService.swift index cb1eac794..a6df8e930 100644 --- a/Adamant/Services/AdamantCoinStorageService.swift +++ b/Adamant/Services/AdamantCoinStorageService.swift @@ -57,12 +57,13 @@ final class AdamantCoinStorageService: NSObject, CoinStorageService { let coinTransaction = CoinTransaction(context: privateContext) coinTransaction.amount = NSDecimalNumber(decimal: transaction.amountValue ?? 0) - coinTransaction.date = (transaction.dateValue ?? Date()) as? NSDate + coinTransaction.date = (transaction.dateValue ?? Date()) as NSDate coinTransaction.recipientId = transaction.recipientAddress coinTransaction.senderId = transaction.senderAddress coinTransaction.isOutgoing = transaction.isOutgoing coinTransaction.coinId = coinId coinTransaction.transactionId = transaction.txId + coinTransaction.transactionStatus = transaction.transactionStatus coinTransactions.append(coinTransaction) } @@ -71,6 +72,24 @@ final class AdamantCoinStorageService: NSObject, CoinStorageService { self.transactions.append(contentsOf: coinTransactions) } + + func updateStatus(for transactionId: String, status: TransactionStatus?) { + let privateContext = coreDataStack.container.viewContext + + guard let transaction = getTransactionFromDB( + id: transactionId, + context: privateContext + ) else { return } + + transaction.transactionStatus = status + try? privateContext.save() + + guard let index = transactions.firstIndex(where: { + $0.transactionId == transactionId + }) else { return } + + transactions[index].transactionStatus = status + } } private extension AdamantCoinStorageService { @@ -93,4 +112,21 @@ private extension AdamantCoinStorageService { cacheName: nil ) } + + /// Search transaction in local storage + /// + /// - Parameter id: Transacton ID + /// - Returns: Transaction, if found + func getTransactionFromDB(id: String, context: NSManagedObjectContext) -> CoinTransaction? { + let request = NSFetchRequest(entityName: CoinTransaction.entityCoinName) + request.predicate = NSPredicate(format: "transactionId == %@", String(id)) + request.fetchLimit = 1 + + do { + let result = try context.fetch(request) + return result.first + } catch { + return nil + } + } } From 9d9034fd54bdff8d73135eb8ff7d4eeb1a37d136 Mon Sep 17 00:00:00 2001 From: StanislavDevIOS Date: Wed, 4 Oct 2023 13:03:08 +0200 Subject: [PATCH 19/96] [dev/trello.com/c/FkbZOCIe] feat: simplify code --- Adamant.xcodeproj/project.pbxproj | 6 ++- .../Adamant.xcdatamodel/contents | 10 ++--- .../BaseTransaction+CoreDataProperties.swift | 5 --- .../BaseTransaction+TransactionDetails.swift | 25 +---------- .../CoinTransaction+CoreDataProperties.swift | 5 +++ .../CoinTransaction+TransactionDetails.swift | 42 +++++++++++++++++++ .../BtcTransactionsViewController.swift | 19 ++------- .../Dash/DashTransactionsViewController.swift | 19 ++------- .../Doge/DogeTransactionsViewController.swift | 19 ++------- .../ERC20TransactionsViewController.swift | 19 ++------- .../EthTransactionsViewController.swift | 19 ++------- .../Lisk/LskTransactionsViewController.swift | 19 ++------- 12 files changed, 75 insertions(+), 132 deletions(-) create mode 100644 Adamant/Models/CoreData/CoinTransaction+TransactionDetails.swift diff --git a/Adamant.xcodeproj/project.pbxproj b/Adamant.xcodeproj/project.pbxproj index 3ff0fd2ca..4d8615592 100644 --- a/Adamant.xcodeproj/project.pbxproj +++ b/Adamant.xcodeproj/project.pbxproj @@ -12,6 +12,7 @@ 3A2F55FC2AC6F885000A3F26 /* CoinStorage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3A2F55FB2AC6F885000A3F26 /* CoinStorage.swift */; }; 3A2F55FE2AC6F90E000A3F26 /* AdamantCoinStorageService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3A2F55FD2AC6F90E000A3F26 /* AdamantCoinStorageService.swift */; }; 3A33F9FA2A7A53DA002B8003 /* EmojiUpdateType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3A33F9F92A7A53DA002B8003 /* EmojiUpdateType.swift */; }; + 3A4068342ACD7C18007E87BD /* CoinTransaction+TransactionDetails.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3A4068332ACD7C18007E87BD /* CoinTransaction+TransactionDetails.swift */; }; 3A41938F2A580C57006A6B22 /* AdamantRichTransactionReactService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3A41938E2A580C57006A6B22 /* AdamantRichTransactionReactService.swift */; }; 3A4193912A580C85006A6B22 /* RichTransactionReactService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3A4193902A580C85006A6B22 /* RichTransactionReactService.swift */; }; 3A41939A2A5D554A006A6B22 /* Reaction.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3A4193992A5D554A006A6B22 /* Reaction.swift */; }; @@ -598,6 +599,7 @@ 3A2F55FB2AC6F885000A3F26 /* CoinStorage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CoinStorage.swift; sourceTree = ""; }; 3A2F55FD2AC6F90E000A3F26 /* AdamantCoinStorageService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AdamantCoinStorageService.swift; sourceTree = ""; }; 3A33F9F92A7A53DA002B8003 /* EmojiUpdateType.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EmojiUpdateType.swift; sourceTree = ""; }; + 3A4068332ACD7C18007E87BD /* CoinTransaction+TransactionDetails.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "CoinTransaction+TransactionDetails.swift"; sourceTree = ""; }; 3A41938E2A580C57006A6B22 /* AdamantRichTransactionReactService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AdamantRichTransactionReactService.swift; sourceTree = ""; }; 3A4193902A580C85006A6B22 /* RichTransactionReactService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RichTransactionReactService.swift; sourceTree = ""; }; 3A4193992A5D554A006A6B22 /* Reaction.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Reaction.swift; sourceTree = ""; }; @@ -2084,7 +2086,7 @@ E90847292196FEA80095825D /* Chatroom+CoreDataProperties.swift */, 3A2F55F72AC6F308000A3F26 /* CoinTransaction+CoreDataClass.swift */, 3A2F55F82AC6F308000A3F26 /* CoinTransaction+CoreDataProperties.swift */, - E90847192196FE590095825D /* Adamant.xcdatamodeld */, + 3A4068332ACD7C18007E87BD /* CoinTransaction+TransactionDetails.swift */, ); path = CoreData; sourceTree = ""; @@ -2756,6 +2758,7 @@ E9CAE8DA2018ACD300345E76 /* AdamantApi+Chats.swift in Sources */, 648CE3A42299A94D0070A2CC /* DashTransactionDetailsViewController.swift in Sources */, E90847362196FEA80095825D /* Chatroom+CoreDataClass.swift in Sources */, + 3A4068342ACD7C18007E87BD /* CoinTransaction+TransactionDetails.swift in Sources */, E91947B020002393001362F8 /* AdamantApiService.swift in Sources */, E921597B206503000000CA5C /* ButtonsStripeView.swift in Sources */, 93294B9A2AAD624100911109 /* WalletFactoryCompose.swift in Sources */, @@ -2983,7 +2986,6 @@ E9FAE5DA203DBFEF008D3A6B /* Comparable+clamped.swift in Sources */, 93A91FD329799298001DB1F8 /* ChatStartPosition.swift in Sources */, 3A2F55F92AC6F308000A3F26 /* CoinTransaction+CoreDataClass.swift in Sources */, - E94008802114EE2000CD2D67 /* AdmWallet.swift in Sources */, 93A91FD1297972B7001DB1F8 /* ChatScrollDownButton.swift in Sources */, 41C1698C29E7F34900FEB3CB /* RichTransactionReplyService.swift in Sources */, E9A03FDA20DC0B14007653A1 /* NodesSource.swift in Sources */, diff --git a/Adamant/Adamant.xcdatamodeld/Adamant.xcdatamodel/contents b/Adamant/Adamant.xcdatamodeld/Adamant.xcdatamodel/contents index cf85baf62..46c694768 100644 --- a/Adamant/Adamant.xcdatamodeld/Adamant.xcdatamodel/contents +++ b/Adamant/Adamant.xcdatamodeld/Adamant.xcdatamodel/contents @@ -9,11 +9,6 @@ - - - - - @@ -41,8 +36,13 @@ + + + + + diff --git a/Adamant/Models/CoreData/BaseTransaction+CoreDataProperties.swift b/Adamant/Models/CoreData/BaseTransaction+CoreDataProperties.swift index 280c2c5c3..c2c2344b9 100644 --- a/Adamant/Models/CoreData/BaseTransaction+CoreDataProperties.swift +++ b/Adamant/Models/CoreData/BaseTransaction+CoreDataProperties.swift @@ -16,11 +16,6 @@ extension BaseTransaction { return NSFetchRequest(entityName: "BaseTransaction") } - @NSManaged public var blockId: String? - @NSManaged public var confirmations: Int64 - @NSManaged public var fee: NSDecimalNumber? - @NSManaged public var height: Int64 - @NSManaged public var isConfirmed: Bool @NSManaged public var type: Int16 @NSManaged public var partner: BaseAccount? @NSManaged public var senderPublicKey: String? diff --git a/Adamant/Models/CoreData/BaseTransaction+TransactionDetails.swift b/Adamant/Models/CoreData/BaseTransaction+TransactionDetails.swift index 39c299b91..e09933404 100644 --- a/Adamant/Models/CoreData/BaseTransaction+TransactionDetails.swift +++ b/Adamant/Models/CoreData/BaseTransaction+TransactionDetails.swift @@ -8,26 +8,7 @@ import Foundation -extension BaseTransaction: TransactionDetails { - var defaultCurrencySymbol: String? { AdmWalletService.currencySymbol } - - var txId: String { return transactionId } - var senderAddress: String { return senderId ?? "" } - var recipientAddress: String { return recipientId ?? "" } - var dateValue: Date? { return date as Date? } - var feeValue: Decimal? { return fee?.decimalValue } - - var confirmationsValue: String? { return isConfirmed ? String(confirmations) : nil } - var blockValue: String? { return isConfirmed ? blockId : nil } - - var amountValue: Decimal? { - if let amount = self.amount { - return amount.decimalValue - } else { - return 0 - } - } - +extension BaseTransaction { var block: UInt { if let raw = blockId, let id = UInt(raw) { return id @@ -35,8 +16,4 @@ extension BaseTransaction: TransactionDetails { return 0 } } - - var blockHeight: UInt64? { - return nil - } } diff --git a/Adamant/Models/CoreData/CoinTransaction+CoreDataProperties.swift b/Adamant/Models/CoreData/CoinTransaction+CoreDataProperties.swift index a2b694f09..28b39ece2 100644 --- a/Adamant/Models/CoreData/CoinTransaction+CoreDataProperties.swift +++ b/Adamant/Models/CoreData/CoinTransaction+CoreDataProperties.swift @@ -23,6 +23,11 @@ extension CoinTransaction { @NSManaged public var recipientId: String? @NSManaged public var date: NSDate? @NSManaged public var isOutgoing: Bool + @NSManaged public var confirmations: Int64 + @NSManaged public var fee: NSDecimalNumber? + @NSManaged public var blockId: String? + @NSManaged public var height: Int64 + @NSManaged public var isConfirmed: Bool } extension CoinTransaction : Identifiable { diff --git a/Adamant/Models/CoreData/CoinTransaction+TransactionDetails.swift b/Adamant/Models/CoreData/CoinTransaction+TransactionDetails.swift new file mode 100644 index 000000000..7e3144851 --- /dev/null +++ b/Adamant/Models/CoreData/CoinTransaction+TransactionDetails.swift @@ -0,0 +1,42 @@ +// +// CoinTransaction+TransactionDetails.swift +// Adamant +// +// Created by Stanislav Jelezoglo on 04.10.2023. +// Copyright © 2023 Adamant. All rights reserved. +// + +import Foundation + + +extension CoinTransaction: TransactionDetails { + var defaultCurrencySymbol: String? { AdmWalletService.currencySymbol } + + var senderAddress: String { + senderId ?? "" + } + + var recipientAddress: String { + recipientId ?? "" + } + + var dateValue: Date? { + date as? Date + } + + var amountValue: Decimal? { + amount?.decimalValue + } + + var feeValue: Decimal? { fee?.decimalValue } + + var confirmationsValue: String? { return isConfirmed ? String(confirmations) : nil } + + var blockValue: String? { return isConfirmed ? blockId : nil } + + var txId: String { return transactionId } + + var blockHeight: UInt64? { + return nil + } +} diff --git a/Adamant/Modules/Wallets/Bitcoin/BtcTransactionsViewController.swift b/Adamant/Modules/Wallets/Bitcoin/BtcTransactionsViewController.swift index 4c07a7b51..bdfedce2d 100644 --- a/Adamant/Modules/Wallets/Bitcoin/BtcTransactionsViewController.swift +++ b/Adamant/Modules/Wallets/Bitcoin/BtcTransactionsViewController.swift @@ -32,26 +32,13 @@ final class BtcTransactionsViewController: TransactionsListViewControllerBase { let controller = screensFactory.makeDetailsVC(service: btcWalletService) - let emptyTransaction = SimpleTransactionDetails( - txId: transaction.transactionId, - senderAddress: transaction.senderId ?? "", - recipientAddress: transaction.recipientId ?? "", - dateValue: transaction.date as? Date, - amountValue: transaction.amount?.decimalValue, - feeValue: nil, - confirmationsValue: nil, - blockValue: nil, - isOutgoing: transaction.isOutgoing, - transactionStatus: nil - ) - - controller.transaction = emptyTransaction + controller.transaction = transaction - if emptyTransaction.senderAddress.caseInsensitiveCompare(address) == .orderedSame { + if transaction.senderAddress.caseInsensitiveCompare(address) == .orderedSame { controller.senderName = String.adamant.transactionDetails.yourAddress } - if emptyTransaction.recipientAddress.caseInsensitiveCompare(address) == .orderedSame { + if transaction.recipientAddress.caseInsensitiveCompare(address) == .orderedSame { controller.recipientName = String.adamant.transactionDetails.yourAddress } diff --git a/Adamant/Modules/Wallets/Dash/DashTransactionsViewController.swift b/Adamant/Modules/Wallets/Dash/DashTransactionsViewController.swift index 01dedc0ab..70f99c2e6 100644 --- a/Adamant/Modules/Wallets/Dash/DashTransactionsViewController.swift +++ b/Adamant/Modules/Wallets/Dash/DashTransactionsViewController.swift @@ -30,26 +30,13 @@ final class DashTransactionsViewController: TransactionsListViewControllerBase { let controller = screensFactory.makeDetailsVC(service: dashWalletService) let transaction = transactions[indexPath.row] - let emptyTransaction = SimpleTransactionDetails( - txId: transaction.transactionId, - senderAddress: transaction.senderId ?? "", - recipientAddress: transaction.recipientId ?? "", - dateValue: transaction.date as? Date, - amountValue: transaction.amount?.decimalValue, - feeValue: nil, - confirmationsValue: nil, - blockValue: nil, - isOutgoing: transaction.isOutgoing, - transactionStatus: nil - ) + controller.transaction = transaction - controller.transaction = emptyTransaction - - if emptyTransaction.senderAddress.caseInsensitiveCompare(address) == .orderedSame { + if transaction.senderAddress.caseInsensitiveCompare(address) == .orderedSame { controller.senderName = String.adamant.transactionDetails.yourAddress } - if emptyTransaction.recipientAddress.caseInsensitiveCompare(address) == .orderedSame { + if transaction.recipientAddress.caseInsensitiveCompare(address) == .orderedSame { controller.recipientName = String.adamant.transactionDetails.yourAddress } diff --git a/Adamant/Modules/Wallets/Doge/DogeTransactionsViewController.swift b/Adamant/Modules/Wallets/Doge/DogeTransactionsViewController.swift index f3175a05f..19d9a02d6 100644 --- a/Adamant/Modules/Wallets/Doge/DogeTransactionsViewController.swift +++ b/Adamant/Modules/Wallets/Doge/DogeTransactionsViewController.swift @@ -31,26 +31,13 @@ final class DogeTransactionsViewController: TransactionsListViewControllerBase { let controller = screensFactory.makeDetailsVC(service: dogeWalletService) let transaction = transactions[indexPath.row] - let emptyTransaction = SimpleTransactionDetails( - txId: transaction.transactionId, - senderAddress: transaction.senderId ?? "", - recipientAddress: transaction.recipientId ?? "", - dateValue: transaction.date as? Date, - amountValue: transaction.amount?.decimalValue, - feeValue: nil, - confirmationsValue: nil, - blockValue: nil, - isOutgoing: transaction.isOutgoing, - transactionStatus: nil - ) + controller.transaction = transaction - controller.transaction = emptyTransaction - - if emptyTransaction.senderAddress.caseInsensitiveCompare(address) == .orderedSame { + if transaction.senderAddress.caseInsensitiveCompare(address) == .orderedSame { controller.senderName = String.adamant.transactionDetails.yourAddress } - if emptyTransaction.recipientAddress.caseInsensitiveCompare(address) == .orderedSame { + if transaction.recipientAddress.caseInsensitiveCompare(address) == .orderedSame { controller.recipientName = String.adamant.transactionDetails.yourAddress } diff --git a/Adamant/Modules/Wallets/ERC20/ERC20TransactionsViewController.swift b/Adamant/Modules/Wallets/ERC20/ERC20TransactionsViewController.swift index 5bab5c6ac..cd12b4718 100644 --- a/Adamant/Modules/Wallets/ERC20/ERC20TransactionsViewController.swift +++ b/Adamant/Modules/Wallets/ERC20/ERC20TransactionsViewController.swift @@ -40,26 +40,13 @@ final class ERC20TransactionsViewController: TransactionsListViewControllerBase let vc = screensFactory.makeDetailsVC(service: ercWalletService) - let emptyTransaction = SimpleTransactionDetails( - txId: transaction.transactionId, - senderAddress: transaction.senderId ?? "", - recipientAddress: transaction.recipientId ?? "", - dateValue: transaction.date as? Date, - amountValue: transaction.amount?.decimalValue, - feeValue: nil, - confirmationsValue: nil, - blockValue: nil, - isOutgoing: transaction.isOutgoing, - transactionStatus: nil - ) + vc.transaction = transaction - vc.transaction = emptyTransaction - - if emptyTransaction.senderAddress.caseInsensitiveCompare(address) == .orderedSame { + if transaction.senderAddress.caseInsensitiveCompare(address) == .orderedSame { vc.senderName = String.adamant.transactionDetails.yourAddress } - if emptyTransaction.recipientAddress.caseInsensitiveCompare(address) == .orderedSame { + if transaction.recipientAddress.caseInsensitiveCompare(address) == .orderedSame { vc.recipientName = String.adamant.transactionDetails.yourAddress } diff --git a/Adamant/Modules/Wallets/Ethereum/EthTransactionsViewController.swift b/Adamant/Modules/Wallets/Ethereum/EthTransactionsViewController.swift index daab5bb3c..c938e9bb8 100644 --- a/Adamant/Modules/Wallets/Ethereum/EthTransactionsViewController.swift +++ b/Adamant/Modules/Wallets/Ethereum/EthTransactionsViewController.swift @@ -37,26 +37,13 @@ final class EthTransactionsViewController: TransactionsListViewControllerBase { let transaction = transactions[indexPath.row] let vc = screensFactory.makeDetailsVC(service: ethWalletService) - let emptyTransaction = SimpleTransactionDetails( - txId: transaction.transactionId, - senderAddress: transaction.senderId ?? "", - recipientAddress: transaction.recipientId ?? "", - dateValue: transaction.date as? Date, - amountValue: transaction.amount?.decimalValue, - feeValue: nil, - confirmationsValue: nil, - blockValue: nil, - isOutgoing: transaction.isOutgoing, - transactionStatus: nil - ) + vc.transaction = transaction - vc.transaction = emptyTransaction - - if emptyTransaction.senderAddress.caseInsensitiveCompare(address) == .orderedSame { + if transaction.senderAddress.caseInsensitiveCompare(address) == .orderedSame { vc.senderName = String.adamant.transactionDetails.yourAddress } - if emptyTransaction.recipientAddress.caseInsensitiveCompare(address) == .orderedSame { + if transaction.recipientAddress.caseInsensitiveCompare(address) == .orderedSame { vc.recipientName = String.adamant.transactionDetails.yourAddress } diff --git a/Adamant/Modules/Wallets/Lisk/LskTransactionsViewController.swift b/Adamant/Modules/Wallets/Lisk/LskTransactionsViewController.swift index 2395ff117..79dc250b1 100644 --- a/Adamant/Modules/Wallets/Lisk/LskTransactionsViewController.swift +++ b/Adamant/Modules/Wallets/Lisk/LskTransactionsViewController.swift @@ -35,26 +35,13 @@ final class LskTransactionsViewController: TransactionsListViewControllerBase { let transaction = transactions[indexPath.row] let controller = screensFactory.makeDetailsVC(service: lskWalletService) - let emptyTransaction = SimpleTransactionDetails( - txId: transaction.transactionId, - senderAddress: transaction.senderId ?? "", - recipientAddress: transaction.recipientId ?? "", - dateValue: transaction.date as? Date, - amountValue: transaction.amount?.decimalValue, - feeValue: nil, - confirmationsValue: nil, - blockValue: nil, - isOutgoing: transaction.isOutgoing, - transactionStatus: nil - ) + controller.transaction = transaction - controller.transaction = emptyTransaction - - if emptyTransaction.senderAddress.caseInsensitiveCompare(address) == .orderedSame { + if transaction.senderAddress.caseInsensitiveCompare(address) == .orderedSame { controller.senderName = String.adamant.transactionDetails.yourAddress } - if emptyTransaction.recipientAddress.caseInsensitiveCompare(address) == .orderedSame { + if transaction.recipientAddress.caseInsensitiveCompare(address) == .orderedSame { controller.recipientName = String.adamant.transactionDetails.yourAddress } From 6c330118e953a5d82ba0543739c42ba597d257c5 Mon Sep 17 00:00:00 2001 From: StanislavDevIOS Date: Wed, 4 Oct 2023 13:16:38 +0200 Subject: [PATCH 20/96] [dev/trello.com/c/FkbZOCIe] fix: tx status for erc --- Adamant/Modules/Wallets/ERC20/ERC20WalletService.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Adamant/Modules/Wallets/ERC20/ERC20WalletService.swift b/Adamant/Modules/Wallets/ERC20/ERC20WalletService.swift index da39bfcb2..49a4e5d1e 100644 --- a/Adamant/Modules/Wallets/ERC20/ERC20WalletService.swift +++ b/Adamant/Modules/Wallets/ERC20/ERC20WalletService.swift @@ -682,7 +682,7 @@ extension ERC20WalletService { confirmationsValue: nil, blockValue: nil, isOutgoing: isOutgoing, - transactionStatus: nil + transactionStatus: TransactionStatus.registered ) } From efb8040777c3cba88406823d7d68dcd3f2225b3a Mon Sep 17 00:00:00 2001 From: StanislavDevIOS Date: Wed, 4 Oct 2023 14:04:15 +0200 Subject: [PATCH 21/96] [dev/trello.com/c/FkbZOCIe] fix: transaction type in adm list --- .../Adamant/AdmTransactionsViewController.swift | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/Adamant/Modules/Wallets/Adamant/AdmTransactionsViewController.swift b/Adamant/Modules/Wallets/Adamant/AdmTransactionsViewController.swift index 7471eb483..96bb76ac7 100644 --- a/Adamant/Modules/Wallets/Adamant/AdmTransactionsViewController.swift +++ b/Adamant/Modules/Wallets/Adamant/AdmTransactionsViewController.swift @@ -250,9 +250,18 @@ final class AdmTransactionsViewController: TransactionsListViewControllerBase { partnerName = String.adamant.transactionDetails.yourAddress } + let transactionType: TransactionTableViewCell.TransactionType + if transaction.recipientId == transaction.senderId { + transactionType = .myself + } else if transaction.isOutgoing { + transactionType = .outcome + } else { + transactionType = .income + } + configureCell( cell, - transactionType: transaction.isOutgoing ? .outcome : .income, + transactionType: transactionType, transactionStatus: transaction.transactionStatus, partnerId: partnerId, partnerName: partnerName, From f4566e75e871579a2d837c9e85c7acded4508c70 Mon Sep 17 00:00:00 2001 From: StanislavDevIOS Date: Fri, 6 Oct 2023 20:08:54 +0300 Subject: [PATCH 22/96] [dev/trello.com/c/FkbZOCIe] fix: clear when user logout --- Adamant/Modules/Wallets/Adamant/AdmWalletService.swift | 2 +- Adamant/Modules/Wallets/Bitcoin/BtcWalletService.swift | 3 ++- Adamant/Modules/Wallets/Dash/DashWalletService.swift | 3 ++- Adamant/Modules/Wallets/Doge/DogeWalletService.swift | 3 ++- Adamant/Modules/Wallets/ERC20/ERC20WalletService.swift | 3 ++- Adamant/Modules/Wallets/Ethereum/EthWalletService.swift | 3 ++- Adamant/Modules/Wallets/Lisk/LskWalletService.swift | 3 ++- Adamant/ServiceProtocols/CoinStorage.swift | 2 ++ Adamant/Services/AdamantCoinStorageService.swift | 4 ++++ 9 files changed, 19 insertions(+), 7 deletions(-) diff --git a/Adamant/Modules/Wallets/Adamant/AdmWalletService.swift b/Adamant/Modules/Wallets/Adamant/AdmWalletService.swift index d2013ae17..a12cd796d 100644 --- a/Adamant/Modules/Wallets/Adamant/AdmWalletService.swift +++ b/Adamant/Modules/Wallets/Adamant/AdmWalletService.swift @@ -86,7 +86,7 @@ final class AdmWalletService: NSObject, WalletService { $hasMoreOldTransactions } - lazy var coinStorage = AdamantCoinStorageService( + lazy var coinStorage: CoinStorageService = AdamantCoinStorageService( coinId: tokenUnicID, coreDataStack: coreDataStack ) diff --git a/Adamant/Modules/Wallets/Bitcoin/BtcWalletService.swift b/Adamant/Modules/Wallets/Bitcoin/BtcWalletService.swift index 3cb7c6688..dc2441cf9 100644 --- a/Adamant/Modules/Wallets/Bitcoin/BtcWalletService.swift +++ b/Adamant/Modules/Wallets/Bitcoin/BtcWalletService.swift @@ -160,7 +160,7 @@ final class BtcWalletService: WalletService { $hasMoreOldTransactions } - lazy var coinStorage = AdamantCoinStorageService( + lazy var coinStorage: CoinStorageService = AdamantCoinStorageService( coinId: tokenUnicID, coreDataStack: coreDataStack ) @@ -217,6 +217,7 @@ final class BtcWalletService: WalletService { NotificationCenter.default.removeObserver(balanceObserver) self?.balanceObserver = nil } + self?.coinStorage.clear() } .store(in: &subscriptions) } diff --git a/Adamant/Modules/Wallets/Dash/DashWalletService.swift b/Adamant/Modules/Wallets/Dash/DashWalletService.swift index 1d89fc7c1..a90c8562b 100644 --- a/Adamant/Modules/Wallets/Dash/DashWalletService.swift +++ b/Adamant/Modules/Wallets/Dash/DashWalletService.swift @@ -137,7 +137,7 @@ final class DashWalletService: WalletService { $hasMoreOldTransactions } - lazy var coinStorage = AdamantCoinStorageService( + lazy var coinStorage: CoinStorageService = AdamantCoinStorageService( coinId: tokenUnicID, coreDataStack: coreDataStack ) @@ -195,6 +195,7 @@ final class DashWalletService: WalletService { NotificationCenter.default.removeObserver(balanceObserver) self?.balanceObserver = nil } + self?.coinStorage.clear() } .store(in: &subscriptions) } diff --git a/Adamant/Modules/Wallets/Doge/DogeWalletService.swift b/Adamant/Modules/Wallets/Doge/DogeWalletService.swift index 4bfd912f4..f7c8b5690 100644 --- a/Adamant/Modules/Wallets/Doge/DogeWalletService.swift +++ b/Adamant/Modules/Wallets/Doge/DogeWalletService.swift @@ -127,7 +127,7 @@ final class DogeWalletService: WalletService { $hasMoreOldTransactions } - lazy var coinStorage = AdamantCoinStorageService( + lazy var coinStorage: CoinStorageService = AdamantCoinStorageService( coinId: tokenUnicID, coreDataStack: coreDataStack ) @@ -185,6 +185,7 @@ final class DogeWalletService: WalletService { NotificationCenter.default.removeObserver(balanceObserver) self?.balanceObserver = nil } + self?.coinStorage.clear() } .store(in: &subscriptions) } diff --git a/Adamant/Modules/Wallets/ERC20/ERC20WalletService.swift b/Adamant/Modules/Wallets/ERC20/ERC20WalletService.swift index 49a4e5d1e..1e61c6204 100644 --- a/Adamant/Modules/Wallets/ERC20/ERC20WalletService.swift +++ b/Adamant/Modules/Wallets/ERC20/ERC20WalletService.swift @@ -173,7 +173,7 @@ final class ERC20WalletService: WalletService { $hasMoreOldTransactions } - lazy var coinStorage = AdamantCoinStorageService( + lazy var coinStorage: CoinStorageService = AdamantCoinStorageService( coinId: tokenUnicID, coreDataStack: coreDataStack ) @@ -228,6 +228,7 @@ final class ERC20WalletService: WalletService { NotificationCenter.default.removeObserver(balanceObserver) self?.balanceObserver = nil } + self?.coinStorage.clear() } .store(in: &subscriptions) } diff --git a/Adamant/Modules/Wallets/Ethereum/EthWalletService.swift b/Adamant/Modules/Wallets/Ethereum/EthWalletService.swift index 0c6bff1ab..28e2a8ec8 100644 --- a/Adamant/Modules/Wallets/Ethereum/EthWalletService.swift +++ b/Adamant/Modules/Wallets/Ethereum/EthWalletService.swift @@ -169,7 +169,7 @@ final class EthWalletService: WalletService { $hasMoreOldTransactions } - lazy var coinStorage = AdamantCoinStorageService( + lazy var coinStorage: CoinStorageService = AdamantCoinStorageService( coinId: tokenUnicID, coreDataStack: coreDataStack ) @@ -232,6 +232,7 @@ final class EthWalletService: WalletService { NotificationCenter.default.removeObserver(balanceObserver) self?.balanceObserver = nil } + self?.coinStorage.clear() } .store(in: &subscriptions) } diff --git a/Adamant/Modules/Wallets/Lisk/LskWalletService.swift b/Adamant/Modules/Wallets/Lisk/LskWalletService.swift index a60aa0013..c1cf69f7f 100644 --- a/Adamant/Modules/Wallets/Lisk/LskWalletService.swift +++ b/Adamant/Modules/Wallets/Lisk/LskWalletService.swift @@ -107,7 +107,7 @@ final class LskWalletService: WalletService { $hasMoreOldTransactions } - lazy var coinStorage = AdamantCoinStorageService( + lazy var coinStorage: CoinStorageService = AdamantCoinStorageService( coinId: tokenUnicID, coreDataStack: coreDataStack ) @@ -183,6 +183,7 @@ final class LskWalletService: WalletService { NotificationCenter.default.removeObserver(balanceObserver) self?.balanceObserver = nil } + self?.coinStorage.clear() } .store(in: &subscriptions) } diff --git a/Adamant/ServiceProtocols/CoinStorage.swift b/Adamant/ServiceProtocols/CoinStorage.swift index 7b320f3dc..cce2e2dcb 100644 --- a/Adamant/ServiceProtocols/CoinStorage.swift +++ b/Adamant/ServiceProtocols/CoinStorage.swift @@ -15,4 +15,6 @@ protocol CoinStorageService: AnyObject { func append(_ transaction: TransactionDetails) func append(_ transactions: [TransactionDetails]) + func clear() + func updateStatus(for transactionId: String, status: TransactionStatus?) } diff --git a/Adamant/Services/AdamantCoinStorageService.swift b/Adamant/Services/AdamantCoinStorageService.swift index a6df8e930..64fcca6c2 100644 --- a/Adamant/Services/AdamantCoinStorageService.swift +++ b/Adamant/Services/AdamantCoinStorageService.swift @@ -90,6 +90,10 @@ final class AdamantCoinStorageService: NSObject, CoinStorageService { transactions[index].transactionStatus = status } + + func clear() { + transactions = [] + } } private extension AdamantCoinStorageService { From 272bd3c20a535d3d93324e3fe040d2f489b685d8 Mon Sep 17 00:00:00 2001 From: StanislavDevIOS Date: Fri, 13 Oct 2023 19:12:13 +0300 Subject: [PATCH 23/96] [trello.com/c/uWUl7xm7] feat: update all coins tx status in background --- Adamant.xcodeproj/project.pbxproj | 4 + .../Adamant.xcdatamodel/contents | 4 +- .../CoinTransaction+CoreDataClass.swift | 11 +- .../CoinTransaction+CoreDataProperties.swift | 2 + .../CoinTransaction+TransactionDetails.swift | 1 - Adamant/Models/SimpleTransactionDetails.swift | 36 +++- .../AdmTransactionsViewController.swift | 162 +++++++----------- .../Wallets/Adamant/AdmWalletService.swift | 10 +- .../Bitcoin/BtcTransferViewController.swift | 4 - ...e+RichMessageProviderWithStatusCheck.swift | 34 +++- .../Wallets/Bitcoin/BtcWalletService.swift | 14 +- .../Dash/DashTransactionsViewController.swift | 2 +- .../Dash/DashTransferViewController.swift | 4 - ...e+RichMessageProviderWithStatusCheck.swift | 34 +++- .../Wallets/Dash/DashWalletService.swift | 11 +- .../Doge/DogeTransferViewController.swift | 4 - ...e+RichMessageProviderWithStatusCheck.swift | 36 +++- .../Wallets/Doge/DogeWalletService.swift | 10 +- .../ERC20/ERC20TransferViewController.swift | 4 - ...e+RichMessageProviderWithStatusCheck.swift | 36 +++- .../Wallets/ERC20/ERC20WalletService.swift | 12 +- .../Ethereum/EthTransferViewController.swift | 4 - ...e+RichMessageProviderWithStatusCheck.swift | 33 +++- .../Wallets/Ethereum/EthWalletService.swift | 13 +- .../Lisk/LskTransactionsViewController.swift | 9 +- .../Lisk/LskTransferViewController.swift | 4 - ...e+RichMessageProviderWithStatusCheck.swift | 40 ++++- .../Wallets/Lisk/LskWalletService.swift | 10 +- .../Wallets/TransactionTableViewCell.swift | 84 ++++++++- .../Wallets/TransactionsListView.swift | 9 + .../TransactionsListViewControllerBase.swift | 88 +++++----- Adamant/Modules/Wallets/WalletService.swift | 4 +- Adamant/ServiceProtocols/CoinStorage.swift | 2 +- ...ageProviderWithStatusCheck+Extension.swift | 5 +- .../RichMessageProviderWithStatusCheck.swift | 2 +- .../RichTransactionStatusService.swift | 2 +- .../Services/AdamantCoinStorageService.swift | 81 +++++++-- .../AdamantChatTransactionService.swift | 2 + .../AdamantRichTransactionStatusService.swift | 36 ++-- .../RichTransactionStatusPublisher.swift | 2 +- .../RichTransactionStatusSubscription.swift | 13 +- 41 files changed, 570 insertions(+), 308 deletions(-) create mode 100644 Adamant/Modules/Wallets/TransactionsListView.swift diff --git a/Adamant.xcodeproj/project.pbxproj b/Adamant.xcodeproj/project.pbxproj index 4d8615592..dfb316df8 100644 --- a/Adamant.xcodeproj/project.pbxproj +++ b/Adamant.xcodeproj/project.pbxproj @@ -16,6 +16,7 @@ 3A41938F2A580C57006A6B22 /* AdamantRichTransactionReactService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3A41938E2A580C57006A6B22 /* AdamantRichTransactionReactService.swift */; }; 3A4193912A580C85006A6B22 /* RichTransactionReactService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3A4193902A580C85006A6B22 /* RichTransactionReactService.swift */; }; 3A41939A2A5D554A006A6B22 /* Reaction.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3A4193992A5D554A006A6B22 /* Reaction.swift */; }; + 3A5F89332AD81CAC00347B41 /* TransactionsListView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3A5F89322AD81CAB00347B41 /* TransactionsListView.swift */; }; 3A7BD00E2AA9BCE80045AAB0 /* VibroService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3A7BD00D2AA9BCE80045AAB0 /* VibroService.swift */; }; 3A7BD0102AA9BD030045AAB0 /* AdamantVibroService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3A7BD00F2AA9BD030045AAB0 /* AdamantVibroService.swift */; }; 3A7BD0122AA9BD5A0045AAB0 /* AdamantVibroType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3A7BD0112AA9BD5A0045AAB0 /* AdamantVibroType.swift */; }; @@ -603,6 +604,7 @@ 3A41938E2A580C57006A6B22 /* AdamantRichTransactionReactService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AdamantRichTransactionReactService.swift; sourceTree = ""; }; 3A4193902A580C85006A6B22 /* RichTransactionReactService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RichTransactionReactService.swift; sourceTree = ""; }; 3A4193992A5D554A006A6B22 /* Reaction.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Reaction.swift; sourceTree = ""; }; + 3A5F89322AD81CAB00347B41 /* TransactionsListView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TransactionsListView.swift; sourceTree = ""; }; 3A7BD00D2AA9BCE80045AAB0 /* VibroService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VibroService.swift; sourceTree = ""; }; 3A7BD00F2AA9BD030045AAB0 /* AdamantVibroService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AdamantVibroService.swift; sourceTree = ""; }; 3A7BD0112AA9BD5A0045AAB0 /* AdamantVibroType.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AdamantVibroType.swift; sourceTree = ""; }; @@ -1945,6 +1947,7 @@ 64BD2B7620E2820300E2CD36 /* TransactionDetails.swift */, 64FA53D020E24941006783C9 /* TransactionDetailsViewControllerBase.swift */, E9E7CDC52003F6D200DFC4DB /* TransactionTableViewCell.swift */, + 3A5F89322AD81CAB00347B41 /* TransactionsListView.swift */, E9E7CDC62003F6D200DFC4DB /* TransactionTableViewCell.xib */, E9484B77227C617D008E10F0 /* BalanceTableViewCell.swift */, E9484B78227C617E008E10F0 /* BalanceTableViewCell.xib */, @@ -2923,6 +2926,7 @@ E9DFB71C21624C9200CF8C7C /* AdmTransactionDetailsViewController.swift in Sources */, 6403F5E022723F6400D58779 /* DashWalletFactory.swift in Sources */, E94008722114EACF00CD2D67 /* WalletAccount.swift in Sources */, + 3A5F89332AD81CAC00347B41 /* TransactionsListView.swift in Sources */, 3A7BD00E2AA9BCE80045AAB0 /* VibroService.swift in Sources */, E93B0D742028B21400126346 /* ChatsProvider.swift in Sources */, E9CAE8D42018AC1800345E76 /* AdamantApi+Keys.swift in Sources */, diff --git a/Adamant/Adamant.xcdatamodeld/Adamant.xcdatamodel/contents b/Adamant/Adamant.xcdatamodeld/Adamant.xcdatamodel/contents index 46c694768..9b513e9e7 100644 --- a/Adamant/Adamant.xcdatamodeld/Adamant.xcdatamodel/contents +++ b/Adamant/Adamant.xcdatamodeld/Adamant.xcdatamodel/contents @@ -36,17 +36,19 @@ + - + + diff --git a/Adamant/Models/CoreData/CoinTransaction+CoreDataClass.swift b/Adamant/Models/CoreData/CoinTransaction+CoreDataClass.swift index 6683ebeee..17785df15 100644 --- a/Adamant/Models/CoreData/CoinTransaction+CoreDataClass.swift +++ b/Adamant/Models/CoreData/CoinTransaction+CoreDataClass.swift @@ -14,5 +14,14 @@ import CoreData public class CoinTransaction: NSManagedObject { static let entityCoinName = "CoinTransaction" - var transactionStatus: TransactionStatus? + var transactionStatus: TransactionStatus? { + get { + TransactionStatus(rawValue: transactionStatusRaw) + } + set { + let raw = newValue?.rawValue ?? .zero + guard raw != transactionStatusRaw else { return } + transactionStatusRaw = newValue?.rawValue ?? .zero + } + } } diff --git a/Adamant/Models/CoreData/CoinTransaction+CoreDataProperties.swift b/Adamant/Models/CoreData/CoinTransaction+CoreDataProperties.swift index 28b39ece2..daa1fd6b2 100644 --- a/Adamant/Models/CoreData/CoinTransaction+CoreDataProperties.swift +++ b/Adamant/Models/CoreData/CoinTransaction+CoreDataProperties.swift @@ -28,6 +28,8 @@ extension CoinTransaction { @NSManaged public var blockId: String? @NSManaged public var height: Int64 @NSManaged public var isConfirmed: Bool + @NSManaged public var blockchainType: String + @NSManaged public var transactionStatusRaw: Int16 } extension CoinTransaction : Identifiable { diff --git a/Adamant/Models/CoreData/CoinTransaction+TransactionDetails.swift b/Adamant/Models/CoreData/CoinTransaction+TransactionDetails.swift index 7e3144851..7ae29eccd 100644 --- a/Adamant/Models/CoreData/CoinTransaction+TransactionDetails.swift +++ b/Adamant/Models/CoreData/CoinTransaction+TransactionDetails.swift @@ -8,7 +8,6 @@ import Foundation - extension CoinTransaction: TransactionDetails { var defaultCurrencySymbol: String? { AdmWalletService.currencySymbol } diff --git a/Adamant/Models/SimpleTransactionDetails.swift b/Adamant/Models/SimpleTransactionDetails.swift index c1ba0d997..ceca4d85d 100644 --- a/Adamant/Models/SimpleTransactionDetails.swift +++ b/Adamant/Models/SimpleTransactionDetails.swift @@ -8,8 +8,8 @@ import Foundation -struct SimpleTransactionDetails: TransactionDetails { - var defaultCurrencySymbol: String? { nil } +struct SimpleTransactionDetails: TransactionDetails, Hashable { + var defaultCurrencySymbol: String? var txId: String @@ -34,4 +34,36 @@ struct SimpleTransactionDetails: TransactionDetails { var blockHeight: UInt64? { return nil } + + var partnerName: String? + + init(defaultCurrencySymbol: String? = nil, txId: String, senderAddress: String, recipientAddress: String, dateValue: Date? = nil, amountValue: Decimal? = nil, feeValue: Decimal? = nil, confirmationsValue: String? = nil, blockValue: String? = nil, isOutgoing: Bool, transactionStatus: TransactionStatus? = nil, partnerName: String? = nil) { + self.defaultCurrencySymbol = defaultCurrencySymbol + self.txId = txId + self.senderAddress = senderAddress + self.recipientAddress = recipientAddress + self.dateValue = dateValue + self.amountValue = amountValue + self.feeValue = feeValue + self.confirmationsValue = confirmationsValue + self.blockValue = blockValue + self.isOutgoing = isOutgoing + self.transactionStatus = transactionStatus + self.partnerName = partnerName + } + + init(_ transaction: TransactionDetails) { + self.defaultCurrencySymbol = transaction.defaultCurrencySymbol + self.txId = transaction.txId + self.senderAddress = transaction.senderAddress + self.recipientAddress = transaction.recipientAddress + self.dateValue = transaction.dateValue + self.amountValue = transaction.amountValue + self.feeValue = transaction.feeValue + self.confirmationsValue = transaction.confirmationsValue + self.blockValue = transaction.blockValue + self.isOutgoing = transaction.isOutgoing + self.transactionStatus = transaction.transactionStatus + } + } diff --git a/Adamant/Modules/Wallets/Adamant/AdmTransactionsViewController.swift b/Adamant/Modules/Wallets/Adamant/AdmTransactionsViewController.swift index 96bb76ac7..8e6e078bc 100644 --- a/Adamant/Modules/Wallets/Adamant/AdmTransactionsViewController.swift +++ b/Adamant/Modules/Wallets/Adamant/AdmTransactionsViewController.swift @@ -72,6 +72,7 @@ final class AdmTransactionsViewController: TransactionsListViewControllerBase { } currencySymbol = AdmWalletService.currencySymbol + setupObserver() } override func viewDidAppear(_ animated: Bool) { @@ -91,17 +92,20 @@ final class AdmTransactionsViewController: TransactionsListViewControllerBase { override func reloadData() { Task { controller = await transfersProvider.transfersController() - controller!.delegate = self do { try controller?.performFetch() + let transactions: [SimpleTransactionDetails] = controller?.fetchedObjects?.compactMap { + getTransactionDetails(by: $0) + } ?? [] + + update(transactions) } catch { dialogService.showError(withMessage: "Failed to get transactions. Please, report a bug", supportEmail: true, error: error) controller = nil } isBusy = false - self.tableView.reloadData() } } @@ -170,7 +174,6 @@ final class AdmTransactionsViewController: TransactionsListViewControllerBase { emptyLabel.isHidden = !isNeedToLoadMoore refreshControl.endRefreshing() stopBottomIndicator() - tableView.reloadData() }.stored(in: taskManager) } @@ -193,18 +196,31 @@ final class AdmTransactionsViewController: TransactionsListViewControllerBase { } } - // MARK: - UITableView - - override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { - if let f = controller?.fetchedObjects { - self.emptyLabel.isHidden = f.count > 0 && !refreshControl.isRefreshing - return f.count - } else { - self.emptyLabel.isHidden = false - return 0 + func getTransactionDetails(by transaction: TransferTransaction) -> SimpleTransactionDetails { + let partnerId = ( + transaction.isOutgoing + ? transaction.recipientId + : transaction.senderId + ) ?? "" + + var simple = SimpleTransactionDetails(transaction) + simple.partnerName = getPartnerName(for: partnerId) + return simple + } + + func getPartnerName(for partnerId: String) -> String? { + var partnerName = addressBookService.getName(for: partnerId) + + if let address = accountService.account?.address, + partnerId == address { + partnerName = String.adamant.transactionDetails.yourAddress } + + return partnerName } + // MARK: - UITableView + func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { guard let transaction = controller?.object(at: indexPath) else { tableView.deselectRow(at: indexPath, animated: true) @@ -236,60 +252,6 @@ final class AdmTransactionsViewController: TransactionsListViewControllerBase { navigationController?.pushViewController(controller, animated: true) } - func configureCell( - _ cell: TransactionTableViewCell, - for transaction: TransferTransaction - ) { - let partnerId = (transaction.isOutgoing ? transaction.recipientId : transaction.senderId) ?? "" - - let amount: Decimal = transaction.amount as Decimal? ?? 0 - - var partnerName = addressBookService.getName(for: transaction.partner) - - if let address = accountService.account?.address, partnerId == address { - partnerName = String.adamant.transactionDetails.yourAddress - } - - let transactionType: TransactionTableViewCell.TransactionType - if transaction.recipientId == transaction.senderId { - transactionType = .myself - } else if transaction.isOutgoing { - transactionType = .outcome - } else { - transactionType = .income - } - - configureCell( - cell, - transactionType: transactionType, - transactionStatus: transaction.transactionStatus, - partnerId: partnerId, - partnerName: partnerName, - amount: amount, - date: transaction.date as Date?) - } - - override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { - guard let transaction = controller?.object(at: indexPath) else { - return UITableViewCell(style: .default, reuseIdentifier: "cell") - } - - let identifier = transaction.chatroom?.partner?.name != nil ? cellIdentifierFull : cellIdentifierCompact - - guard let cell = tableView.dequeueReusableCell(withIdentifier: identifier, for: indexPath) as? TransactionTableViewCell else { - return UITableViewCell(style: .default, reuseIdentifier: "cell") - } - - configureCell(cell, for: transaction) - - cell.accessoryType = .disclosureIndicator - cell.separatorInset = indexPath.row == (controller?.fetchedObjects?.count ?? 0) - 1 - ? .zero - : UITableView.defaultTransactionsSeparatorInset - - return cell - } - func tableView(_ tableView: UITableView, editActionsForRowAt: IndexPath) -> [UITableViewRowAction]? { guard let transaction = controller?.object(at: editActionsForRowAt), let partner = transaction.partner as? CoreDataAccount, let chatroom = partner.chatroom, let transactions = chatroom.transactions else { return nil @@ -386,48 +348,40 @@ final class AdmTransactionsViewController: TransactionsListViewControllerBase { } } -// MARK: - NSFetchedResultsControllerDelegate -extension AdmTransactionsViewController: NSFetchedResultsControllerDelegate { - func controllerWillChangeContent(_ controller: NSFetchedResultsController) { - if isBusy { return } - tableView.beginUpdates() - } - - func controllerDidChangeContent(_ controller: NSFetchedResultsController) { - if isBusy { return } - tableView.endUpdates() - } - - func controller(_ controller: NSFetchedResultsController, didChange anObject: Any, at indexPath: IndexPath?, for type: NSFetchedResultsChangeType, newIndexPath: IndexPath?) { - if isBusy { return } - switch type { - case .insert: - if let newIndexPath = newIndexPath { - tableView.insertRows(at: [newIndexPath], with: .automatic) - - if isOnTop, let transaction = anObject as? TransferTransaction { - transaction.isUnread = false - } - } +private extension AdmTransactionsViewController { + func setupObserver() { + NotificationCenter.default.publisher( + for: .NSManagedObjectContextObjectsDidChange, + object: stack.container.viewContext + ) + .sink { [weak self] notification in + guard let self = self else { return } - case .delete: - if let indexPath = indexPath { - tableView.deleteRows(at: [indexPath], with: .automatic) - } - - case .update: - if let indexPath = indexPath, - let cell = self.tableView.cellForRow(at: indexPath) as? TransactionTableViewCell, - let transaction = anObject as? TransferTransaction { - configureCell(cell, for: transaction) + let changes = notification.managedObjectContextChanges(of: TransferTransaction.self) + + if let inserted = changes.inserted, !inserted.isEmpty { + let maped: [SimpleTransactionDetails] = inserted.map { + self.getTransactionDetails(by: $0) + } + + var transactions = self.transactions + transactions.append(contentsOf: maped) + self.update(transactions) } - case .move: - if let at = indexPath, let to = newIndexPath { - tableView.moveRow(at: at, to: to) + if let updated = changes.updated, !updated.isEmpty { + updated.forEach { transaction in + guard let index = self.transactions.firstIndex(where: { + $0.txId == transaction.txId + }) + else { return } + + var transactions: [SimpleTransactionDetails] = self.transactions + transactions[index] = self.getTransactionDetails(by: transaction) + self.update(transactions) + } } - @unknown default: - break } + .store(in: &subscriptions) } } diff --git a/Adamant/Modules/Wallets/Adamant/AdmWalletService.swift b/Adamant/Modules/Wallets/Adamant/AdmWalletService.swift index a12cd796d..e15f1d80f 100644 --- a/Adamant/Modules/Wallets/Adamant/AdmWalletService.swift +++ b/Adamant/Modules/Wallets/Adamant/AdmWalletService.swift @@ -75,10 +75,10 @@ final class AdmWalletService: NSObject, WalletService { private (set) var isWarningGasPrice = false private var subscriptions = Set() - @Published private(set) var transactions: [CoinTransaction] = [] + @Published private(set) var transactions: [TransactionDetails] = [] @Published private(set) var hasMoreOldTransactions: Bool = true - var transactionsPublisher: Published<[CoinTransaction]>.Publisher { + var transactionsPublisher: Published<[TransactionDetails]>.Publisher { $transactions } @@ -88,7 +88,8 @@ final class AdmWalletService: NSObject, WalletService { lazy var coinStorage: CoinStorageService = AdamantCoinStorageService( coinId: tokenUnicID, - coreDataStack: coreDataStack + coreDataStack: coreDataStack, + blockchainType: richMessageType ) // MARK: - State @@ -131,7 +132,6 @@ final class AdmWalletService: NSObject, WalletService { func addTransactionObserver() { coinStorage.transactionsPublisher - .removeDuplicates() .sink { [weak self] transactions in self?.transactions = transactions } @@ -187,7 +187,7 @@ final class AdmWalletService: NSObject, WalletService { func loadTransactions(offset: Int, limit: Int) async throws -> Int { .zero } - func getLocalTransactionHistory() -> [CoinTransaction] { [] } + func getLocalTransactionHistory() -> [TransactionDetails] { [] } } // MARK: - NSFetchedResultsControllerDelegate diff --git a/Adamant/Modules/Wallets/Bitcoin/BtcTransferViewController.swift b/Adamant/Modules/Wallets/Bitcoin/BtcTransferViewController.swift index 12abf9316..349c38c95 100644 --- a/Adamant/Modules/Wallets/Bitcoin/BtcTransferViewController.swift +++ b/Adamant/Modules/Wallets/Bitcoin/BtcTransferViewController.swift @@ -76,10 +76,6 @@ final class BtcTransferViewController: TransferViewControllerBase { service.coinStorage.append(simpleTransaction) try await service.sendTransaction(transaction) - service.coinStorage.updateStatus( - for: transaction.txId, - status: .registered - ) } catch { dialogService.showRichError(error: error) service.coinStorage.updateStatus( diff --git a/Adamant/Modules/Wallets/Bitcoin/BtcWalletService+RichMessageProviderWithStatusCheck.swift b/Adamant/Modules/Wallets/Bitcoin/BtcWalletService+RichMessageProviderWithStatusCheck.swift index a23c041f6..8b360d635 100644 --- a/Adamant/Modules/Wallets/Bitcoin/BtcWalletService+RichMessageProviderWithStatusCheck.swift +++ b/Adamant/Modules/Wallets/Bitcoin/BtcWalletService+RichMessageProviderWithStatusCheck.swift @@ -10,9 +10,16 @@ import Foundation import CommonKit extension BtcWalletService: RichMessageProviderWithStatusCheck { - func statusInfoFor(transaction: RichMessageTransaction) async -> TransactionStatusInfo { - guard let hash = transaction.getRichValue(for: RichContentKeys.transfer.hash) - else { + func statusInfoFor(transaction: CoinTransaction) async -> TransactionStatusInfo { + let hash: String? + + if let transaction = transaction as? RichMessageTransaction { + hash = transaction.getRichValue(for: RichContentKeys.transfer.hash) + } else { + hash = transaction.txId + } + + guard let hash = hash else { return .init(sentDate: nil, status: .inconsistent) } @@ -36,7 +43,7 @@ extension BtcWalletService: RichMessageProviderWithStatusCheck { private extension BtcWalletService { func getStatus( - transaction: RichMessageTransaction, + transaction: CoinTransaction, btcTransaction: BtcTransaction ) -> TransactionStatus { guard let status = btcTransaction.transactionStatus else { @@ -54,8 +61,7 @@ private extension BtcWalletService { else { return .inconsistent } // MARK: Check amount - if let raw = transaction.getRichValue(for: RichContentKeys.transfer.amount), - let reported = AdamantBalanceFormat.deserializeBalance(from: raw) { + if let reported = reportedValue(for: transaction) { guard reported == btcTransaction.amountValue else { return .inconsistent } @@ -63,4 +69,20 @@ private extension BtcWalletService { return .success } + + func reportedValue(for transaction: CoinTransaction) -> Decimal? { + guard let transaction = transaction as? RichMessageTransaction + else { + return transaction.amountValue + } + + guard + let raw = transaction.getRichValue(for: RichContentKeys.transfer.amount), + let reportedValue = AdamantBalanceFormat.deserializeBalance(from: raw) + else { + return nil + } + + return reportedValue + } } diff --git a/Adamant/Modules/Wallets/Bitcoin/BtcWalletService.swift b/Adamant/Modules/Wallets/Bitcoin/BtcWalletService.swift index dc2441cf9..619c549b7 100644 --- a/Adamant/Modules/Wallets/Bitcoin/BtcWalletService.swift +++ b/Adamant/Modules/Wallets/Bitcoin/BtcWalletService.swift @@ -149,10 +149,10 @@ final class BtcWalletService: WalletService { private var subscriptions = Set() - @Published private(set) var transactions: [CoinTransaction] = [] + @Published private(set) var transactions: [TransactionDetails] = [] @Published private(set) var hasMoreOldTransactions: Bool = true - var transactionsPublisher: Published<[CoinTransaction]>.Publisher { + var transactionsPublisher: Published<[TransactionDetails]>.Publisher { $transactions } @@ -162,7 +162,8 @@ final class BtcWalletService: WalletService { lazy var coinStorage: CoinStorageService = AdamantCoinStorageService( coinId: tokenUnicID, - coreDataStack: coreDataStack + coreDataStack: coreDataStack, + blockchainType: richMessageType ) // MARK: - State @@ -224,7 +225,6 @@ final class BtcWalletService: WalletService { func addTransactionObserver() { coinStorage.transactionsPublisher - .removeDuplicates() .sink { [weak self] transactions in self?.transactions = transactions } @@ -726,8 +726,8 @@ extension BtcWalletService { func loadTransactions(offset: Int, limit: Int) async throws -> Int { let txId = offset == .zero - ? transactions.first?.transactionId - : transactions.last?.transactionId + ? transactions.first?.txId + : transactions.last?.txId let trs = try await getTransactions(fromTx: txId) @@ -741,7 +741,7 @@ extension BtcWalletService { return trs.count } - func getLocalTransactionHistory() -> [CoinTransaction] { + func getLocalTransactionHistory() -> [TransactionDetails] { transactions } } diff --git a/Adamant/Modules/Wallets/Dash/DashTransactionsViewController.swift b/Adamant/Modules/Wallets/Dash/DashTransactionsViewController.swift index 70f99c2e6..0620e16e7 100644 --- a/Adamant/Modules/Wallets/Dash/DashTransactionsViewController.swift +++ b/Adamant/Modules/Wallets/Dash/DashTransactionsViewController.swift @@ -27,7 +27,7 @@ final class DashTransactionsViewController: TransactionsListViewControllerBase { return } - let controller = screensFactory.makeDetailsVC(service: dashWalletService) + let controller = screensFactory.makeDetailsVC(service: walletService) let transaction = transactions[indexPath.row] controller.transaction = transaction diff --git a/Adamant/Modules/Wallets/Dash/DashTransferViewController.swift b/Adamant/Modules/Wallets/Dash/DashTransferViewController.swift index 6731084c1..dbfd4a865 100644 --- a/Adamant/Modules/Wallets/Dash/DashTransferViewController.swift +++ b/Adamant/Modules/Wallets/Dash/DashTransferViewController.swift @@ -78,10 +78,6 @@ final class DashTransferViewController: TransferViewControllerBase { service.coinStorage.append(simpleTransaction) try await service.sendTransaction(transaction) - service.coinStorage.updateStatus( - for: transaction.txId, - status: .registered - ) } catch { dialogService.showRichError(error: error) service.coinStorage.updateStatus( diff --git a/Adamant/Modules/Wallets/Dash/DashWalletService+RichMessageProviderWithStatusCheck.swift b/Adamant/Modules/Wallets/Dash/DashWalletService+RichMessageProviderWithStatusCheck.swift index 2d3fb301c..d9cdd9701 100644 --- a/Adamant/Modules/Wallets/Dash/DashWalletService+RichMessageProviderWithStatusCheck.swift +++ b/Adamant/Modules/Wallets/Dash/DashWalletService+RichMessageProviderWithStatusCheck.swift @@ -10,9 +10,16 @@ import Foundation import CommonKit extension DashWalletService: RichMessageProviderWithStatusCheck { - func statusInfoFor(transaction: RichMessageTransaction) async -> TransactionStatusInfo { - guard let hash = transaction.getRichValue(for: RichContentKeys.transfer.hash) - else { + func statusInfoFor(transaction: CoinTransaction) async -> TransactionStatusInfo { + let hash: String? + + if let transaction = transaction as? RichMessageTransaction { + hash = transaction.getRichValue(for: RichContentKeys.transfer.hash) + } else { + hash = transaction.txId + } + + guard let hash = hash else { return .init(sentDate: nil, status: .inconsistent) } @@ -39,7 +46,7 @@ extension DashWalletService: RichMessageProviderWithStatusCheck { private extension DashWalletService { func getStatus( dashTransaction: BTCRawTransaction, - transaction: RichMessageTransaction + transaction: CoinTransaction ) -> TransactionStatus { // MARK: Check confirmations @@ -48,8 +55,7 @@ private extension DashWalletService { } // MARK: Check amount & address - guard let raw = transaction.getRichValue(for: RichContentKeys.transfer.amount), - let reportedValue = AdamantBalanceFormat.deserializeBalance(from: raw) else { + guard let reportedValue = reportedValue(for: transaction) else { return .inconsistent } @@ -91,4 +97,20 @@ private extension DashWalletService { return result } + + func reportedValue(for transaction: CoinTransaction) -> Decimal? { + guard let transaction = transaction as? RichMessageTransaction + else { + return transaction.amountValue + } + + guard + let raw = transaction.getRichValue(for: RichContentKeys.transfer.amount), + let reportedValue = AdamantBalanceFormat.deserializeBalance(from: raw) + else { + return nil + } + + return reportedValue + } } diff --git a/Adamant/Modules/Wallets/Dash/DashWalletService.swift b/Adamant/Modules/Wallets/Dash/DashWalletService.swift index a90c8562b..bc712c342 100644 --- a/Adamant/Modules/Wallets/Dash/DashWalletService.swift +++ b/Adamant/Modules/Wallets/Dash/DashWalletService.swift @@ -126,10 +126,10 @@ final class DashWalletService: WalletService { static let jsonDecoder = JSONDecoder() private var subscriptions = Set() - @Published private(set) var historyTransactions: [CoinTransaction] = [] + @Published private(set) var historyTransactions: [TransactionDetails] = [] @Published private(set) var hasMoreOldTransactions: Bool = true - var transactionsPublisher: Published<[CoinTransaction]>.Publisher { + var transactionsPublisher: Published<[TransactionDetails]>.Publisher { $historyTransactions } @@ -139,7 +139,8 @@ final class DashWalletService: WalletService { lazy var coinStorage: CoinStorageService = AdamantCoinStorageService( coinId: tokenUnicID, - coreDataStack: coreDataStack + coreDataStack: coreDataStack, + blockchainType: richMessageType ) // MARK: - State @@ -202,7 +203,7 @@ final class DashWalletService: WalletService { func addTransactionObserver() { coinStorage.transactionsPublisher - .removeDuplicates() + // .removeDuplicates() .sink { [weak self] transactions in self?.historyTransactions = transactions } @@ -465,7 +466,7 @@ extension DashWalletService { return trs.count } - func getLocalTransactionHistory() -> [CoinTransaction] { + func getLocalTransactionHistory() -> [TransactionDetails] { historyTransactions } } diff --git a/Adamant/Modules/Wallets/Doge/DogeTransferViewController.swift b/Adamant/Modules/Wallets/Doge/DogeTransferViewController.swift index 08aa7ef26..7f5483130 100644 --- a/Adamant/Modules/Wallets/Doge/DogeTransferViewController.swift +++ b/Adamant/Modules/Wallets/Doge/DogeTransferViewController.swift @@ -69,10 +69,6 @@ final class DogeTransferViewController: TransferViewControllerBase { service.coinStorage.append(simpleTransaction) try await service.sendTransaction(transaction) - service.coinStorage.updateStatus( - for: transaction.txId, - status: .registered - ) } catch { dialogService.showRichError(error: error) service.coinStorage.updateStatus( diff --git a/Adamant/Modules/Wallets/Doge/DogeWalletService+RichMessageProviderWithStatusCheck.swift b/Adamant/Modules/Wallets/Doge/DogeWalletService+RichMessageProviderWithStatusCheck.swift index 6c2d4d0c8..44d2c81b3 100644 --- a/Adamant/Modules/Wallets/Doge/DogeWalletService+RichMessageProviderWithStatusCheck.swift +++ b/Adamant/Modules/Wallets/Doge/DogeWalletService+RichMessageProviderWithStatusCheck.swift @@ -10,9 +10,16 @@ import Foundation import CommonKit extension DogeWalletService: RichMessageProviderWithStatusCheck { - func statusInfoFor(transaction: RichMessageTransaction) async -> TransactionStatusInfo { - guard let hash = transaction.getRichValue(for: RichContentKeys.transfer.hash) - else { + func statusInfoFor(transaction: CoinTransaction) async -> TransactionStatusInfo { + let hash: String? + + if let transaction = transaction as? RichMessageTransaction { + hash = transaction.getRichValue(for: RichContentKeys.transfer.hash) + } else { + hash = transaction.txId + } + + guard let hash = hash else { return .init(sentDate: nil, status: .inconsistent) } @@ -42,7 +49,7 @@ extension DogeWalletService: RichMessageProviderWithStatusCheck { private extension DogeWalletService { func getStatus( dogeTransaction: BTCRawTransaction, - transaction: RichMessageTransaction + transaction: CoinTransaction ) -> TransactionStatus { // MARK: Check confirmations guard let confirmations = dogeTransaction.confirmations, @@ -53,10 +60,7 @@ private extension DogeWalletService { } // MARK: Check amount & address - guard - let raw = transaction.getRichValue(for: RichContentKeys.transfer.amount), - let reportedValue = AdamantBalanceFormat.deserializeBalance(from: raw) - else { + guard let reportedValue = reportedValue(for: transaction) else { return .inconsistent } @@ -98,4 +102,20 @@ private extension DogeWalletService { return result } + + func reportedValue(for transaction: CoinTransaction) -> Decimal? { + guard let transaction = transaction as? RichMessageTransaction + else { + return transaction.amountValue + } + + guard + let raw = transaction.getRichValue(for: RichContentKeys.transfer.amount), + let reportedValue = AdamantBalanceFormat.deserializeBalance(from: raw) + else { + return nil + } + + return reportedValue + } } diff --git a/Adamant/Modules/Wallets/Doge/DogeWalletService.swift b/Adamant/Modules/Wallets/Doge/DogeWalletService.swift index f7c8b5690..053fd42d7 100644 --- a/Adamant/Modules/Wallets/Doge/DogeWalletService.swift +++ b/Adamant/Modules/Wallets/Doge/DogeWalletService.swift @@ -116,10 +116,10 @@ final class DogeWalletService: WalletService { private static let jsonDecoder = JSONDecoder() private var subscriptions = Set() - @Published private(set) var historyTransactions: [CoinTransaction] = [] + @Published private(set) var historyTransactions: [TransactionDetails] = [] @Published private(set) var hasMoreOldTransactions: Bool = true - var transactionsPublisher: Published<[CoinTransaction]>.Publisher { + var transactionsPublisher: Published<[TransactionDetails]>.Publisher { $historyTransactions } @@ -129,7 +129,8 @@ final class DogeWalletService: WalletService { lazy var coinStorage: CoinStorageService = AdamantCoinStorageService( coinId: tokenUnicID, - coreDataStack: coreDataStack + coreDataStack: coreDataStack, + blockchainType: richMessageType ) // MARK: - State @@ -192,7 +193,6 @@ final class DogeWalletService: WalletService { func addTransactionObserver() { coinStorage.transactionsPublisher - .removeDuplicates() .sink { [weak self] transactions in self?.historyTransactions = transactions } @@ -647,7 +647,7 @@ extension DogeWalletService { return trs.count } - func getLocalTransactionHistory() -> [CoinTransaction] { + func getLocalTransactionHistory() -> [TransactionDetails] { historyTransactions } } diff --git a/Adamant/Modules/Wallets/ERC20/ERC20TransferViewController.swift b/Adamant/Modules/Wallets/ERC20/ERC20TransferViewController.swift index 8b98ed018..11b6fd141 100644 --- a/Adamant/Modules/Wallets/ERC20/ERC20TransferViewController.swift +++ b/Adamant/Modules/Wallets/ERC20/ERC20TransferViewController.swift @@ -66,10 +66,6 @@ final class ERC20TransferViewController: TransferViewControllerBase { Task { do { try await service.sendTransaction(transaction) - service.coinStorage.updateStatus( - for: txHash, - status: .registered - ) } catch { dialogService.showRichError(error: error) service.coinStorage.updateStatus( diff --git a/Adamant/Modules/Wallets/ERC20/ERC20WalletService+RichMessageProviderWithStatusCheck.swift b/Adamant/Modules/Wallets/ERC20/ERC20WalletService+RichMessageProviderWithStatusCheck.swift index d760f40bc..0eb036261 100644 --- a/Adamant/Modules/Wallets/ERC20/ERC20WalletService+RichMessageProviderWithStatusCheck.swift +++ b/Adamant/Modules/Wallets/ERC20/ERC20WalletService+RichMessageProviderWithStatusCheck.swift @@ -12,9 +12,16 @@ import struct BigInt.BigUInt import CommonKit extension ERC20WalletService: RichMessageProviderWithStatusCheck { - func statusInfoFor(transaction: RichMessageTransaction) async -> TransactionStatusInfo { - guard let hash = transaction.getRichValue(for: RichContentKeys.transfer.hash) - else { + func statusInfoFor(transaction: CoinTransaction) async -> TransactionStatusInfo { + let hash: String? + + if let transaction = transaction as? RichMessageTransaction { + hash = transaction.getRichValue(for: RichContentKeys.transfer.hash) + } else { + hash = transaction.txId + } + + guard let hash = hash else { return .init(sentDate: nil, status: .inconsistent) } @@ -44,7 +51,7 @@ extension ERC20WalletService: RichMessageProviderWithStatusCheck { private extension ERC20WalletService { func getStatus( erc20Transaction: EthTransaction, - transaction: RichMessageTransaction + transaction: CoinTransaction ) -> TransactionStatus { let status = erc20Transaction.receiptStatus.asTransactionStatus() guard status == .success else { return status } @@ -67,10 +74,7 @@ private extension ERC20WalletService { } // MARK: Compare amounts - guard - let raw = transaction.getRichValue(for: RichContentKeys.transfer.amount), - let reportedValue = AdamantBalanceFormat.deserializeBalance(from: raw) - else { + guard let reportedValue = reportedValue(for: transaction) else { return .inconsistent } @@ -83,4 +87,20 @@ private extension ERC20WalletService { return .success } + + func reportedValue(for transaction: CoinTransaction) -> Decimal? { + guard let transaction = transaction as? RichMessageTransaction + else { + return transaction.amountValue + } + + guard + let raw = transaction.getRichValue(for: RichContentKeys.transfer.amount), + let reportedValue = AdamantBalanceFormat.deserializeBalance(from: raw) + else { + return nil + } + + return reportedValue + } } diff --git a/Adamant/Modules/Wallets/ERC20/ERC20WalletService.swift b/Adamant/Modules/Wallets/ERC20/ERC20WalletService.swift index 1e61c6204..24ebbbb32 100644 --- a/Adamant/Modules/Wallets/ERC20/ERC20WalletService.swift +++ b/Adamant/Modules/Wallets/ERC20/ERC20WalletService.swift @@ -162,10 +162,10 @@ final class ERC20WalletService: WalletService { private (set) var contract: Web3.Contract? private var balanceObserver: NSObjectProtocol? - @Published private(set) var historyTransactions: [CoinTransaction] = [] + @Published private(set) var historyTransactions: [TransactionDetails] = [] @Published private(set) var hasMoreOldTransactions: Bool = true - var transactionsPublisher: Published<[CoinTransaction]>.Publisher { + var transactionsPublisher: Published<[TransactionDetails]>.Publisher { $historyTransactions } @@ -175,7 +175,8 @@ final class ERC20WalletService: WalletService { lazy var coinStorage: CoinStorageService = AdamantCoinStorageService( coinId: tokenUnicID, - coreDataStack: coreDataStack + coreDataStack: coreDataStack, + blockchainType: dynamicRichMessageType ) init(token: ERC20Token) { @@ -235,7 +236,6 @@ final class ERC20WalletService: WalletService { func addTransactionObserver() { coinStorage.transactionsPublisher - .removeDuplicates() .sink { [weak self] transactions in self?.historyTransactions = transactions } @@ -683,7 +683,7 @@ extension ERC20WalletService { confirmationsValue: nil, blockValue: nil, isOutgoing: isOutgoing, - transactionStatus: TransactionStatus.registered + transactionStatus: TransactionStatus.notInitiated ) } @@ -692,7 +692,7 @@ extension ERC20WalletService { return trs.count } - func getLocalTransactionHistory() -> [CoinTransaction] { + func getLocalTransactionHistory() -> [TransactionDetails] { historyTransactions } } diff --git a/Adamant/Modules/Wallets/Ethereum/EthTransferViewController.swift b/Adamant/Modules/Wallets/Ethereum/EthTransferViewController.swift index 89821ea25..9bae31980 100644 --- a/Adamant/Modules/Wallets/Ethereum/EthTransferViewController.swift +++ b/Adamant/Modules/Wallets/Ethereum/EthTransferViewController.swift @@ -60,10 +60,6 @@ final class EthTransferViewController: TransferViewControllerBase { Task { do { try await service.sendTransaction(transaction) - service.coinStorage.updateStatus( - for: txHash, - status: .registered - ) } catch { dialogService.showRichError(error: error) service.coinStorage.updateStatus( diff --git a/Adamant/Modules/Wallets/Ethereum/EthWalletService+RichMessageProviderWithStatusCheck.swift b/Adamant/Modules/Wallets/Ethereum/EthWalletService+RichMessageProviderWithStatusCheck.swift index ef51352f7..d96b776c0 100644 --- a/Adamant/Modules/Wallets/Ethereum/EthWalletService+RichMessageProviderWithStatusCheck.swift +++ b/Adamant/Modules/Wallets/Ethereum/EthWalletService+RichMessageProviderWithStatusCheck.swift @@ -12,10 +12,18 @@ import Web3Core import CommonKit extension EthWalletService: RichMessageProviderWithStatusCheck { - func statusInfoFor(transaction: RichMessageTransaction) async -> TransactionStatusInfo { + func statusInfoFor(transaction: CoinTransaction) async -> TransactionStatusInfo { + let hash: String? + + if let transaction = transaction as? RichMessageTransaction { + hash = transaction.getRichValue(for: RichContentKeys.transfer.hash) + } else { + hash = transaction.txId + } + guard let web3 = await web3, - let hash = transaction.getRichValue(for: RichContentKeys.transfer.hash) + let hash = hash else { return .init(sentDate: nil, status: .inconsistent) } @@ -86,7 +94,7 @@ private extension EthWalletService { func getStatus( details: Web3Core.TransactionDetails, - transaction: RichMessageTransaction, + transaction: CoinTransaction, receipt: TransactionReceipt ) -> TransactionStatus { let status = receipt.status.asTransactionStatus() @@ -113,8 +121,7 @@ private extension EthWalletService { // MARK: Compare amounts let realAmount = eth.value.asDecimal(exponent: EthWalletService.currencyExponent) - guard let raw = transaction.getRichValue(for: RichContentKeys.transfer.amount), - let reported = AdamantBalanceFormat.deserializeBalance(from: raw) else { + guard let reported = reportedValue(for: transaction) else { return .inconsistent } let min = reported - reported*0.005 @@ -126,6 +133,22 @@ private extension EthWalletService { return .success } + + func reportedValue(for transaction: CoinTransaction) -> Decimal? { + guard let transaction = transaction as? RichMessageTransaction + else { + return transaction.amountValue + } + + guard + let raw = transaction.getRichValue(for: RichContentKeys.transfer.amount), + let reportedValue = AdamantBalanceFormat.deserializeBalance(from: raw) + else { + return nil + } + + return reportedValue + } } extension TransactionReceipt.TXStatus { diff --git a/Adamant/Modules/Wallets/Ethereum/EthWalletService.swift b/Adamant/Modules/Wallets/Ethereum/EthWalletService.swift index 28e2a8ec8..d68019584 100644 --- a/Adamant/Modules/Wallets/Ethereum/EthWalletService.swift +++ b/Adamant/Modules/Wallets/Ethereum/EthWalletService.swift @@ -158,10 +158,10 @@ final class EthWalletService: WalletService { private var initialBalanceCheck = false private var subscriptions = Set() - @Published private(set) var historyTransactions: [CoinTransaction] = [] + @Published private(set) var historyTransactions: [TransactionDetails] = [] @Published private(set) var hasMoreOldTransactions: Bool = true - var transactionsPublisher: Published<[CoinTransaction]>.Publisher { + var transactionsPublisher: Published<[TransactionDetails]>.Publisher { $historyTransactions } @@ -171,7 +171,8 @@ final class EthWalletService: WalletService { lazy var coinStorage: CoinStorageService = AdamantCoinStorageService( coinId: tokenUnicID, - coreDataStack: coreDataStack + coreDataStack: coreDataStack, + blockchainType: richMessageType ) // MARK: - State @@ -239,7 +240,6 @@ final class EthWalletService: WalletService { func addTransactionObserver() { coinStorage.transactionsPublisher - .removeDuplicates() .sink { [weak self] transactions in self?.historyTransactions = transactions } @@ -654,6 +654,7 @@ extension EthWalletService { } catch let error as Web3Error { throw error.asWalletServiceError() } catch { + print("error=\(error)") throw WalletServiceError.remoteServiceError(message: "Failed to get transaction") } @@ -802,7 +803,7 @@ extension EthWalletService { confirmationsValue: nil, blockValue: nil, isOutgoing: isOutgoing, - transactionStatus: TransactionStatus.registered + transactionStatus: TransactionStatus.notInitiated ) } @@ -811,7 +812,7 @@ extension EthWalletService { return trs.count } - func getLocalTransactionHistory() -> [CoinTransaction] { + func getLocalTransactionHistory() -> [TransactionDetails] { historyTransactions } } diff --git a/Adamant/Modules/Wallets/Lisk/LskTransactionsViewController.swift b/Adamant/Modules/Wallets/Lisk/LskTransactionsViewController.swift index 79dc250b1..3dfb0d918 100644 --- a/Adamant/Modules/Wallets/Lisk/LskTransactionsViewController.swift +++ b/Adamant/Modules/Wallets/Lisk/LskTransactionsViewController.swift @@ -92,7 +92,10 @@ extension Transactions.TransactionModel: TransactionDetails { } var transactionStatus: TransactionStatus? { - guard let confirmations = confirmations, let height = height else { return .registered } + guard let confirmations = confirmations, + let height = height + else { return .notInitiated } + if confirmations < height { return .registered } if confirmations > 0 && height > 0 { @@ -168,7 +171,7 @@ extension LocalTransaction: TransactionDetails { } var transactionStatus: TransactionStatus? { - return .pending + return .notInitiated } } @@ -222,7 +225,7 @@ extension TransactionEntity: TransactionDetails { } var transactionStatus: TransactionStatus? { - return .pending + return .notInitiated } } diff --git a/Adamant/Modules/Wallets/Lisk/LskTransferViewController.swift b/Adamant/Modules/Wallets/Lisk/LskTransferViewController.swift index 1ddc52fe5..891438077 100644 --- a/Adamant/Modules/Wallets/Lisk/LskTransferViewController.swift +++ b/Adamant/Modules/Wallets/Lisk/LskTransferViewController.swift @@ -70,10 +70,6 @@ final class LskTransferViewController: TransferViewControllerBase { do { service.coinStorage.append(transaction) try await service.sendTransaction(transaction) - service.coinStorage.updateStatus( - for: transaction.id, - status: .registered - ) } catch { dialogService.showRichError(error: error) service.coinStorage.updateStatus( diff --git a/Adamant/Modules/Wallets/Lisk/LskWalletService+RichMessageProviderWithStatusCheck.swift b/Adamant/Modules/Wallets/Lisk/LskWalletService+RichMessageProviderWithStatusCheck.swift index 037bc5b00..cfe4cc9e4 100644 --- a/Adamant/Modules/Wallets/Lisk/LskWalletService+RichMessageProviderWithStatusCheck.swift +++ b/Adamant/Modules/Wallets/Lisk/LskWalletService+RichMessageProviderWithStatusCheck.swift @@ -11,9 +11,16 @@ import LiskKit import CommonKit extension LskWalletService: RichMessageProviderWithStatusCheck { - func statusInfoFor(transaction: RichMessageTransaction) async -> TransactionStatusInfo { - guard let hash = transaction.getRichValue(for: RichContentKeys.transfer.hash) - else { + func statusInfoFor(transaction: CoinTransaction) async -> TransactionStatusInfo { + let hash: String? + + if let transaction = transaction as? RichMessageTransaction { + hash = transaction.getRichValue(for: RichContentKeys.transfer.hash) + } else { + hash = transaction.txId + } + + guard let hash = hash else { return .init(sentDate: nil, status: .inconsistent) } @@ -45,7 +52,7 @@ extension LskWalletService: RichMessageProviderWithStatusCheck { private extension LskWalletService { func getStatus( lskTransaction: Transactions.TransactionModel, - transaction: RichMessageTransaction + transaction: CoinTransaction ) -> TransactionStatus { guard lskTransaction.blockId != nil else { return .registered } @@ -69,16 +76,35 @@ private extension LskWalletService { } // MARK: Check amount - if let raw = transaction.getRichValue(for: RichContentKeys.transfer.amount), + guard isAmountCorrect( + transaction: transaction, + lskTransaction: lskTransaction + ) else { return .inconsistent } + + return .success + } + + func isAmountCorrect( + transaction: CoinTransaction, + lskTransaction: Transactions.TransactionModel + ) -> Bool { + if let transaction = transaction as? RichMessageTransaction, + let raw = transaction.getRichValue(for: RichContentKeys.transfer.amount), let reported = AdamantBalanceFormat.deserializeBalance(from: raw) { let min = reported - reported*0.005 let max = reported + reported*0.005 guard (min...max).contains(lskTransaction.amountValue ?? 0) else { - return .inconsistent + return false } + + return true } - return .success + guard transaction.amountValue == lskTransaction.amountValue else { + return false + } + + return true } } diff --git a/Adamant/Modules/Wallets/Lisk/LskWalletService.swift b/Adamant/Modules/Wallets/Lisk/LskWalletService.swift index c1cf69f7f..8e9ec39ea 100644 --- a/Adamant/Modules/Wallets/Lisk/LskWalletService.swift +++ b/Adamant/Modules/Wallets/Lisk/LskWalletService.swift @@ -96,10 +96,10 @@ final class LskWalletService: WalletService { private let nodes: [APINode] private var subscriptions = Set() - @Published private(set) var transactions: [CoinTransaction] = [] + @Published private(set) var transactions: [TransactionDetails] = [] @Published private(set) var hasMoreOldTransactions: Bool = true - var transactionsPublisher: Published<[CoinTransaction]>.Publisher { + var transactionsPublisher: Published<[TransactionDetails]>.Publisher { $transactions } @@ -109,7 +109,8 @@ final class LskWalletService: WalletService { lazy var coinStorage: CoinStorageService = AdamantCoinStorageService( coinId: tokenUnicID, - coreDataStack: coreDataStack + coreDataStack: coreDataStack, + blockchainType: richMessageType ) // MARK: - State @@ -190,7 +191,6 @@ final class LskWalletService: WalletService { func addTransactionObserver() { coinStorage.transactionsPublisher - .removeDuplicates() .sink { [weak self] transactions in self?.transactions = transactions } @@ -607,7 +607,7 @@ extension LskWalletService { return trs.count } - func getLocalTransactionHistory() -> [CoinTransaction] { + func getLocalTransactionHistory() -> [TransactionDetails] { transactions } } diff --git a/Adamant/Modules/Wallets/TransactionTableViewCell.swift b/Adamant/Modules/Wallets/TransactionTableViewCell.swift index 85507d17d..4687a44c7 100644 --- a/Adamant/Modules/Wallets/TransactionTableViewCell.swift +++ b/Adamant/Modules/Wallets/TransactionTableViewCell.swift @@ -9,7 +9,7 @@ import UIKit import CommonKit -class TransactionTableViewCell: UITableViewCell { +final class TransactionTableViewCell: UITableViewCell { enum TransactionType { case income, outcome, myself @@ -63,9 +63,91 @@ class TransactionTableViewCell: UITableViewCell { } } + var currencySymbol: String? + + var transaction: SimpleTransactionDetails? { + didSet { + updateUI() + } + } + // MARK: - Initializers override func awakeFromNib() { transactionType = .income } + + @MainActor + func updateUI() { + guard let transaction = transaction else { return } + + let partnerId = transaction.isOutgoing + ? transaction.recipientAddress + : transaction.senderAddress + + let transactionType: TransactionTableViewCell.TransactionType + if transaction.recipientAddress == transaction.senderAddress { + transactionType = .myself + } else if transaction.isOutgoing { + transactionType = .outcome + } else { + transactionType = .income + } + + self.transactionType = transactionType + + backgroundColor = .clear + accountLabel.tintColor = UIColor.adamant.primary + ammountLabel.tintColor = UIColor.adamant.primary + + dateLabel.textColor = transaction.transactionStatus?.color ?? .adamant.secondary + + switch transaction.transactionStatus { + case .success, .inconsistent: + if let date = transaction.dateValue { + dateLabel.text = date.humanizedDateTime() + } else { + dateLabel.text = nil + } + case .notInitiated: + dateLabel.text = TransactionDetailsViewControllerBase.awaitingValueString + case .failed: + dateLabel.text = TransactionStatus.failed.localized + default: + dateLabel.text = TransactionStatus.pending.localized + } + + if let partnerName = transaction.partnerName { + accountLabel.text = partnerName + addressLabel.text = partnerId + addressLabel.lineBreakMode = .byTruncatingMiddle + + if addressLabel.isHidden { + addressLabel.isHidden = false + } + } else { + accountLabel.text = partnerId + + if !addressLabel.isHidden { + addressLabel.isHidden = true + } + } + + let amount = transaction.amountValue ?? .zero //(transaction.amount ?? 0).decimalValue + ammountLabel.text = AdamantBalanceFormat.full.format(amount, withCurrencySymbol: currencySymbol) + } +} + +// MARK: - TransactionStatus UI +private extension TransactionStatus { + var color: UIColor { + switch self { + case .failed: + return .adamant.danger + case .noNetwork, .noNetworkFinal, .pending, .registered: + return .adamant.alert + case .success, .inconsistent, .notInitiated: + return .adamant.secondary + } + } } diff --git a/Adamant/Modules/Wallets/TransactionsListView.swift b/Adamant/Modules/Wallets/TransactionsListView.swift new file mode 100644 index 000000000..79bacc686 --- /dev/null +++ b/Adamant/Modules/Wallets/TransactionsListView.swift @@ -0,0 +1,9 @@ +// +// TransactionsListView.swift +// Adamant +// +// Created by Stanislav Jelezoglo on 12.10.2023. +// Copyright © 2023 Adamant. All rights reserved. +// + +import Foundation diff --git a/Adamant/Modules/Wallets/TransactionsListViewControllerBase.swift b/Adamant/Modules/Wallets/TransactionsListViewControllerBase.swift index d6386388d..444a87d6d 100644 --- a/Adamant/Modules/Wallets/TransactionsListViewControllerBase.swift +++ b/Adamant/Modules/Wallets/TransactionsListViewControllerBase.swift @@ -22,6 +22,8 @@ extension String.adamant { } } +private typealias ChatDiffableDataSource = UITableViewDiffableDataSource + // Extensions for a generic classes is limited, so delegates implemented right in class declaration class TransactionsListViewControllerBase: UIViewController { let cellIdentifierFull = "cf" @@ -50,13 +52,15 @@ class TransactionsListViewControllerBase: UIViewController { var isNeedToLoadMoore = true var isBusy = false - var transactions: [CoinTransaction] = [] + var subscriptions = Set() + var transactions: [SimpleTransactionDetails] = [] private(set) lazy var loadingView = LoadingView() - private var subscriptions = Set() private var limit = 25 private var offset = 0 + private lazy var dataSource = ChatDiffableDataSource(tableView: tableView, cellProvider: makeCell) + // MARK: - IBOutlets @IBOutlet weak var tableView: UITableView! @IBOutlet weak var emptyLabel: UILabel! @@ -70,7 +74,7 @@ class TransactionsListViewControllerBase: UIViewController { navigationItem.title = String.adamant.transactionList.title emptyLabel.text = String.adamant.transactionList.noTransactionYet - transactions = walletService.getLocalTransactionHistory() + update(walletService.getLocalTransactionHistory()) configureTableView() setColors() configureLayout() @@ -134,18 +138,31 @@ class TransactionsListViewControllerBase: UIViewController { let nib = UINib.init(nibName: "TransactionTableViewCell", bundle: nil) tableView.register(nib, forCellReuseIdentifier: cellIdentifierFull) tableView.register(nib, forCellReuseIdentifier: cellIdentifierCompact) - tableView.dataSource = self tableView.delegate = self tableView.refreshControl = refreshControl tableView.tableHeaderView = UIView() } @MainActor - func update(_ transactions: [CoinTransaction]) { - self.transactions = transactions.sorted(by: { - (($0.date ?? NSDate()) as Date) > (($1.date ?? NSDate()) as Date) - }) - self.tableView.reloadData() + func update(_ transactions: [TransactionDetails]) { + let transactions = transactions.map { + SimpleTransactionDetails($0) + } + + update(transactions) + } + + @MainActor + func update(_ transactions: [SimpleTransactionDetails]) { + self.transactions = transactions.sorted( + by: { ($0.dateValue ?? Date()) > ($1.dateValue ?? Date()) } + ) + + var snapshot = NSDiffableDataSourceSnapshot() + snapshot.appendSections([.zero]) + snapshot.appendItems(self.transactions) + snapshot.reconfigureItems(self.transactions) + dataSource.apply(snapshot, animatingDifferences: false) guard !isBusy else { return } self.updateLoadingView(isHidden: true) @@ -209,7 +226,6 @@ class TransactionsListViewControllerBase: UIViewController { emptyLabel.isHidden = self.transactions.count > 0 stopBottomIndicator() refreshControl.endRefreshing() - tableView.reloadData() updateLoadingView(isHidden: true) }.stored(in: taskManager) } @@ -224,48 +240,23 @@ class TransactionsListViewControllerBase: UIViewController { return TransactionTableViewCell.cellHeightCompact } - func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { - guard let cell = tableView.dequeueReusableCell(withIdentifier: cellIdentifierCompact, for: indexPath) as? TransactionTableViewCell else { - return UITableViewCell(style: .default, reuseIdentifier: "cell") - } - - let transaction = transactions[indexPath.row] + private func makeCell( + tableView: UITableView, + indexPath: IndexPath, + model: SimpleTransactionDetails + ) -> UITableViewCell { + let cell = tableView.dequeueReusableCell(withIdentifier: cellIdentifierCompact, for: indexPath) as! TransactionTableViewCell cell.accessoryType = .disclosureIndicator cell.separatorInset = indexPath.row == transactions.count - 1 ? .zero : UITableView.defaultTransactionsSeparatorInset - let partnerId = transaction.isOutgoing - ? transaction.recipientId - : transaction.senderId - - let transactionType: TransactionTableViewCell.TransactionType - if transaction.recipientId == transaction.senderId { - transactionType = .myself - } else if transaction.isOutgoing { - transactionType = .outcome - } else { - transactionType = .income - } - - configureCell( - cell, - transactionType: transactionType, - transactionStatus: transaction.transactionStatus, - partnerId: partnerId ?? "", - partnerName: nil, - amount: (transaction.amount ?? 0).decimalValue, - date: transaction.date as? Date - ) - + cell.transaction = model + cell.currencySymbol = currencySymbol return cell } - func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { - transactions.count - } - func tableView(_ tableView: UITableView, heightForFooterInSection section: Int) -> CGFloat { return TransactionTableViewCell.cellFooterLoadingCompact } @@ -296,7 +287,7 @@ class TransactionsListViewControllerBase: UIViewController { } // MARK: - UITableViewDataSource, UITableViewDelegate -extension TransactionsListViewControllerBase: UITableViewDataSource, UITableViewDelegate { +extension TransactionsListViewControllerBase: UITableViewDelegate { func numberOfSections(in tableView: UITableView) -> Int { return 1 } @@ -319,12 +310,14 @@ extension TransactionsListViewControllerBase: UITableViewDataSource, UITableView cell.dateLabel.textColor = transactionStatus?.color ?? .adamant.secondary switch transactionStatus { - case .success, .inconsistent, .registered: + case .success, .inconsistent: if let date = date { cell.dateLabel.text = date.humanizedDateTime() } else { cell.dateLabel.text = nil } + case .notInitiated: + cell.dateLabel.text = TransactionDetailsViewControllerBase.awaitingValueString case .failed: cell.dateLabel.text = TransactionStatus.failed.localized default: @@ -391,8 +384,9 @@ private extension TransactionStatus { var color: UIColor { switch self { case .failed: return .adamant.danger - case .notInitiated, .inconsistent, .noNetwork, .noNetworkFinal, .pending: return .adamant.alert - case .success, .registered: return .adamant.secondary + case .notInitiated, .inconsistent, .noNetwork, .noNetworkFinal, .pending, .registered: + return .adamant.alert + case .success: return .adamant.secondary } } } diff --git a/Adamant/Modules/Wallets/WalletService.swift b/Adamant/Modules/Wallets/WalletService.swift index 67150c49f..8c81ab014 100644 --- a/Adamant/Modules/Wallets/WalletService.swift +++ b/Adamant/Modules/Wallets/WalletService.swift @@ -217,7 +217,7 @@ protocol WalletService: AnyObject { var defaultOrdinalLevel: Int? { get } var richMessageType: String { get } - var transactionsPublisher: Published<[CoinTransaction]>.Publisher { + var transactionsPublisher: Published<[TransactionDetails]>.Publisher { get } @@ -250,7 +250,7 @@ protocol WalletService: AnyObject { func getWalletAddress(byAdamantAddress address: String) async throws -> String func getBalance(address: String) async throws -> Decimal func loadTransactions(offset: Int, limit: Int) async throws -> Int - func getLocalTransactionHistory() -> [CoinTransaction] + func getLocalTransactionHistory() -> [TransactionDetails] } protocol SwinjectDependentService: WalletService { diff --git a/Adamant/ServiceProtocols/CoinStorage.swift b/Adamant/ServiceProtocols/CoinStorage.swift index cce2e2dcb..92ac14ade 100644 --- a/Adamant/ServiceProtocols/CoinStorage.swift +++ b/Adamant/ServiceProtocols/CoinStorage.swift @@ -9,7 +9,7 @@ import Foundation protocol CoinStorageService: AnyObject { - var transactionsPublisher: Published<[CoinTransaction]>.Publisher { + var transactionsPublisher: Published<[TransactionDetails]>.Publisher { get } diff --git a/Adamant/ServiceProtocols/RichMessageProviderWithStatusCheck/RichMessageProviderWithStatusCheck+Extension.swift b/Adamant/ServiceProtocols/RichMessageProviderWithStatusCheck/RichMessageProviderWithStatusCheck+Extension.swift index 57ad99f64..16d6ae2e7 100644 --- a/Adamant/ServiceProtocols/RichMessageProviderWithStatusCheck/RichMessageProviderWithStatusCheck+Extension.swift +++ b/Adamant/ServiceProtocols/RichMessageProviderWithStatusCheck/RichMessageProviderWithStatusCheck+Extension.swift @@ -10,12 +10,15 @@ import Foundation extension RichMessageProviderWithStatusCheck { func statusWithFilters( - transaction: RichMessageTransaction, + transaction: RichMessageTransaction?, oldPendingAttempts: Int, info: TransactionStatusInfo ) -> TransactionStatus { switch info.status { case .success: + guard let transaction = transaction else { + return info.status + } return consistencyFilter(transaction: transaction, statusInfo: info) ? info.status : .inconsistent diff --git a/Adamant/ServiceProtocols/RichMessageProviderWithStatusCheck/RichMessageProviderWithStatusCheck.swift b/Adamant/ServiceProtocols/RichMessageProviderWithStatusCheck/RichMessageProviderWithStatusCheck.swift index 99400277d..d92993c37 100644 --- a/Adamant/ServiceProtocols/RichMessageProviderWithStatusCheck/RichMessageProviderWithStatusCheck.swift +++ b/Adamant/ServiceProtocols/RichMessageProviderWithStatusCheck/RichMessageProviderWithStatusCheck.swift @@ -14,5 +14,5 @@ struct TransactionStatusInfo { } protocol RichMessageProviderWithStatusCheck: RichMessageProvider { - func statusInfoFor(transaction: RichMessageTransaction) async -> TransactionStatusInfo + func statusInfoFor(transaction: CoinTransaction) async -> TransactionStatusInfo } diff --git a/Adamant/ServiceProtocols/RichTransactionStatusService.swift b/Adamant/ServiceProtocols/RichTransactionStatusService.swift index 19a340eb3..2b64212b7 100644 --- a/Adamant/ServiceProtocols/RichTransactionStatusService.swift +++ b/Adamant/ServiceProtocols/RichTransactionStatusService.swift @@ -9,6 +9,6 @@ import CoreData protocol RichTransactionStatusService: Actor, AnyObject { - func forceUpdate(transaction: RichMessageTransaction) async + func forceUpdate(transaction: CoinTransaction) async func startObserving() } diff --git a/Adamant/Services/AdamantCoinStorageService.swift b/Adamant/Services/AdamantCoinStorageService.swift index 64fcca6c2..9c58e749a 100644 --- a/Adamant/Services/AdamantCoinStorageService.swift +++ b/Adamant/Services/AdamantCoinStorageService.swift @@ -15,25 +15,30 @@ final class AdamantCoinStorageService: NSObject, CoinStorageService { // MARK: Proprieties + private let blockchainType: String private let coinId: String private let coreDataStack: CoreDataStack private lazy var transactionController = getTransactionController() + private var subscriptions = Set() + + @Published private var transactions: [TransactionDetails] = [] - @Published private var transactions: [CoinTransaction] = [] - - var transactionsPublisher: Published<[CoinTransaction]>.Publisher { + var transactionsPublisher: Published<[TransactionDetails]>.Publisher { $transactions } // MARK: Init - init(coinId: String, coreDataStack: CoreDataStack) { + init(coinId: String, coreDataStack: CoreDataStack, blockchainType: String) { self.coinId = coinId self.coreDataStack = coreDataStack + self.blockchainType = blockchainType super.init() try? transactionController.performFetch() transactions = transactionController.fetchedObjects ?? [] + + setupObserver() } func append(_ transaction: TransactionDetails) { @@ -48,10 +53,10 @@ final class AdamantCoinStorageService: NSObject, CoinStorageService { transactions.forEach { transaction in let isExist = self.transactions.contains { tx in - tx.transactionId == transaction.txId + tx.txId == transaction.txId } let isLocalExist = coinTransactions.contains { tx in - tx.transactionId == transaction.txId + tx.txId == transaction.txId } guard !isExist, !isLocalExist else { return } @@ -64,13 +69,12 @@ final class AdamantCoinStorageService: NSObject, CoinStorageService { coinTransaction.coinId = coinId coinTransaction.transactionId = transaction.txId coinTransaction.transactionStatus = transaction.transactionStatus + coinTransaction.blockchainType = blockchainType coinTransactions.append(coinTransaction) } try? privateContext.save() - - self.transactions.append(contentsOf: coinTransactions) } func updateStatus(for transactionId: String, status: TransactionStatus?) { @@ -83,12 +87,6 @@ final class AdamantCoinStorageService: NSObject, CoinStorageService { transaction.transactionStatus = status try? privateContext.save() - - guard let index = transactions.firstIndex(where: { - $0.transactionId == transactionId - }) else { return } - - transactions[index].transactionStatus = status } func clear() { @@ -97,6 +95,37 @@ final class AdamantCoinStorageService: NSObject, CoinStorageService { } private extension AdamantCoinStorageService { + func setupObserver() { + NotificationCenter.default.publisher( + for: .NSManagedObjectContextObjectsDidChange, + object: coreDataStack.container.viewContext + ) + .sink { [weak self] notification in + let changes = notification.managedObjectContextChanges(of: CoinTransaction.self) + + if let inserted = changes.inserted, !inserted.isEmpty { + let filteredInserted: [TransactionDetails] = inserted.filter { + $0.coinId == self?.coinId + } + self?.transactions.append(contentsOf: filteredInserted) + } + + if let updated = changes.updated, !updated.isEmpty { + let filteredUpdated = updated.filter { $0.coinId == self?.coinId } + + filteredUpdated.forEach { coinTransaction in + guard let index = self?.transactions.firstIndex(where: { + $0.txId == coinTransaction.txId + }) + else { return } + + self?.transactions[index] = coinTransaction + } + } + } + .store(in: &subscriptions) + } + func getTransactionController() -> NSFetchedResultsController { let request: NSFetchRequest = NSFetchRequest( entityName: CoinTransaction.entityCoinName @@ -134,3 +163,27 @@ private extension AdamantCoinStorageService { } } } + +struct ManagedObjectContextChanges { + var updated: Set? + var inserted: Set? + var deleted: Set? +} + +extension Notification { + func managedObjectContextChanges(of type: Object.Type) -> ManagedObjectContextChanges { + return ManagedObjectContextChanges( + updated: objects(forKey: NSUpdatedObjectsKey), + inserted: objects(forKey: NSInsertedObjectsKey), + deleted: objects(forKey: NSDeletedObjectsKey)) + } + + private func objects(forKey key: String) -> Set? { + guard let userInfo = userInfo else { + assertionFailure() + return nil + } + let objects = (userInfo[key] as? Set) ?? [] + return Set(objects.compactMap { $0 as? Object }) + } +} diff --git a/Adamant/Services/DataProviders/AdamantChatTransactionService.swift b/Adamant/Services/DataProviders/AdamantChatTransactionService.swift index 17f63f640..7d31d0b32 100644 --- a/Adamant/Services/DataProviders/AdamantChatTransactionService.swift +++ b/Adamant/Services/DataProviders/AdamantChatTransactionService.swift @@ -284,6 +284,7 @@ private extension AdamantChatTransactionService { trs.richContent = richContent trs.richType = type + trs.blockchainType = type trs.transactionStatus = richProviders[type] != nil ? .notInitiated : nil trs.additionalType = .base @@ -348,6 +349,7 @@ private extension AdamantChatTransactionService { trs.richContent = richContent trs.richType = type + trs.blockchainType = type trs.transactionStatus = richProviders[type] != nil ? .notInitiated : nil trs.additionalType = .reply diff --git a/Adamant/Services/RichTransactionStatusService/AdamantRichTransactionStatusService.swift b/Adamant/Services/RichTransactionStatusService/AdamantRichTransactionStatusService.swift index 3af57b333..a22b74674 100644 --- a/Adamant/Services/RichTransactionStatusService/AdamantRichTransactionStatusService.swift +++ b/Adamant/Services/RichTransactionStatusService/AdamantRichTransactionStatusService.swift @@ -29,7 +29,7 @@ actor AdamantRichTransactionStatusService: NSObject, RichTransactionStatusServic Task { await setupNetworkSubscription() } } - func forceUpdate(transaction: RichMessageTransaction) async { + func forceUpdate(transaction: CoinTransaction) async { setStatus(for: transaction, status: .notInitiated) guard @@ -41,7 +41,7 @@ actor AdamantRichTransactionStatusService: NSObject, RichTransactionStatusServic setStatus( for: transaction, status: provider.statusWithFilters( - transaction: transaction, + transaction: transaction as? RichMessageTransaction, oldPendingAttempts: oldPendingAttempts[id]?.wrappedValue ?? .zero, info: await provider.statusInfoFor(transaction: transaction) ) @@ -63,7 +63,7 @@ extension AdamantRichTransactionStatusService: NSFetchedResultsControllerDelegat for type: NSFetchedResultsChangeType, newIndexPath _: IndexPath? ) { - guard let transaction = object as? RichMessageTransaction else { return } + guard let transaction = object as? CoinTransaction else { return } Task { await processCoreDataChange(type: type, transaction: transaction) } } } @@ -100,9 +100,10 @@ private extension AdamantRichTransactionStatusService { } } - func add(transaction: RichMessageTransaction) { + func add(transaction: CoinTransaction) { guard - let provider = getProvider(for: transaction) + let provider = getProvider(for: transaction), + transaction.transactionStatus != .success else { return } let id = transaction.transactionId @@ -123,18 +124,17 @@ private extension AdamantRichTransactionStatusService { } } - func remove(transaction: RichMessageTransaction) { + func remove(transaction: CoinTransaction) { let id = transaction.transactionId subscriptions[id] = nil } - func getProvider(for transaction: RichMessageTransaction) -> RichMessageProviderWithStatusCheck? { - guard let transfer = transaction.transfer else { return nil } - return richProviders[transfer.type] + func getProvider(for transaction: CoinTransaction) -> RichMessageProviderWithStatusCheck? { + return richProviders[transaction.blockchainType] } func setStatus( - for transaction: RichMessageTransaction, + for transaction: CoinTransaction, status: TransactionStatus ) { let privateContext = NSManagedObjectContext( @@ -144,15 +144,19 @@ private extension AdamantRichTransactionStatusService { privateContext.parent = coreDataStack.container.viewContext let transaction = privateContext.object(with: transaction.objectID) - as? RichMessageTransaction + as? CoinTransaction + + guard let transaction = transaction else { + return + } - transaction?.transactionStatus = status + transaction.transactionStatus = status try? privateContext.save() } // MARK: Core Data - func processCoreDataChange(type: NSFetchedResultsChangeType, transaction: RichMessageTransaction) { + func processCoreDataChange(type: NSFetchedResultsChangeType, transaction: CoinTransaction) { switch type { case .insert, .update: add(transaction: transaction) @@ -165,9 +169,9 @@ private extension AdamantRichTransactionStatusService { } } - func getRichTransactionsController() -> NSFetchedResultsController { - let request: NSFetchRequest = NSFetchRequest( - entityName: RichMessageTransaction.entityName + func getRichTransactionsController() -> NSFetchedResultsController { + let request: NSFetchRequest = NSFetchRequest( + entityName: CoinTransaction.entityCoinName ) request.sortDescriptors = [] diff --git a/Adamant/Services/RichTransactionStatusService/RichTransactionStatusPublisher.swift b/Adamant/Services/RichTransactionStatusService/RichTransactionStatusPublisher.swift index a8f42e643..0342bd4da 100644 --- a/Adamant/Services/RichTransactionStatusService/RichTransactionStatusPublisher.swift +++ b/Adamant/Services/RichTransactionStatusService/RichTransactionStatusPublisher.swift @@ -15,7 +15,7 @@ struct RichTransactionStatusPublisher: Publisher { typealias Failure = Never let provider: RichMessageProviderWithStatusCheck - let transaction: RichMessageTransaction + let transaction: CoinTransaction let oldPendingAttempts: ObservableValue func receive( diff --git a/Adamant/Services/RichTransactionStatusService/RichTransactionStatusSubscription.swift b/Adamant/Services/RichTransactionStatusService/RichTransactionStatusSubscription.swift index c5194951f..f0bf4defe 100644 --- a/Adamant/Services/RichTransactionStatusService/RichTransactionStatusSubscription.swift +++ b/Adamant/Services/RichTransactionStatusService/RichTransactionStatusSubscription.swift @@ -12,10 +12,9 @@ import CommonKit actor RichTransactionStatusSubscription: Subscription where StatusSubscriber.Input == TransactionStatus, - StatusSubscriber.Failure == Never -{ + StatusSubscriber.Failure == Never { private let provider: RichMessageProviderWithStatusCheck - private let transaction: RichMessageTransaction + private let transaction: CoinTransaction private let taskManager = TaskManager() private var subscriber: StatusSubscriber? @@ -27,7 +26,7 @@ actor RichTransactionStatusSubscription: Subscript init( provider: RichMessageProviderWithStatusCheck, - transaction: RichMessageTransaction, + transaction: CoinTransaction, oldPendingAttempts: ObservableValue, subscriber: StatusSubscriber ) { @@ -55,8 +54,8 @@ actor RichTransactionStatusSubscription: Subscript break } - status = await provider.statusWithFilters( - transaction: transaction, + status = provider.statusWithFilters( + transaction: transaction as? RichMessageTransaction, oldPendingAttempts: oldPendingAttempts, info: await provider.statusInfoFor(transaction: transaction) ) @@ -84,7 +83,7 @@ private extension RichTransactionStatusSubscription { case .registered: return .registered case .pending, .notInitiated, .noNetwork: - guard let sentDate = transaction.sentDate else { return .final } + guard let sentDate = transaction.dateValue else { return .final } let sentInterval = Date.now.timeIntervalSince1970 - sentDate.timeIntervalSince1970 let oldTxInterval = TimeInterval( From 74a4c7223719b0752fae49893c365437f0760c7f Mon Sep 17 00:00:00 2001 From: StanislavDevIOS Date: Wed, 18 Oct 2023 16:54:08 +0300 Subject: [PATCH 24/96] [trello.com/c/uWUl7xm7] feat: add ADM support --- Adamant.xcodeproj/project.pbxproj | 4 - Adamant/Models/SimpleTransactionDetails.swift | 3 + .../AdmTransactionDetailsViewController.swift | 59 ++++++------- .../AdmTransactionsViewController.swift | 87 +++++-------------- .../Adamant/AdmTransferViewController.swift | 3 +- .../Wallets/Adamant/AdmWalletService.swift | 9 -- .../Wallets/TransactionsListView.swift | 9 -- .../AdamantChatTransactionService.swift | 4 +- .../AdamantTransfersProvider.swift | 18 ++-- 9 files changed, 65 insertions(+), 131 deletions(-) delete mode 100644 Adamant/Modules/Wallets/TransactionsListView.swift diff --git a/Adamant.xcodeproj/project.pbxproj b/Adamant.xcodeproj/project.pbxproj index dfb316df8..4d8615592 100644 --- a/Adamant.xcodeproj/project.pbxproj +++ b/Adamant.xcodeproj/project.pbxproj @@ -16,7 +16,6 @@ 3A41938F2A580C57006A6B22 /* AdamantRichTransactionReactService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3A41938E2A580C57006A6B22 /* AdamantRichTransactionReactService.swift */; }; 3A4193912A580C85006A6B22 /* RichTransactionReactService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3A4193902A580C85006A6B22 /* RichTransactionReactService.swift */; }; 3A41939A2A5D554A006A6B22 /* Reaction.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3A4193992A5D554A006A6B22 /* Reaction.swift */; }; - 3A5F89332AD81CAC00347B41 /* TransactionsListView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3A5F89322AD81CAB00347B41 /* TransactionsListView.swift */; }; 3A7BD00E2AA9BCE80045AAB0 /* VibroService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3A7BD00D2AA9BCE80045AAB0 /* VibroService.swift */; }; 3A7BD0102AA9BD030045AAB0 /* AdamantVibroService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3A7BD00F2AA9BD030045AAB0 /* AdamantVibroService.swift */; }; 3A7BD0122AA9BD5A0045AAB0 /* AdamantVibroType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3A7BD0112AA9BD5A0045AAB0 /* AdamantVibroType.swift */; }; @@ -604,7 +603,6 @@ 3A41938E2A580C57006A6B22 /* AdamantRichTransactionReactService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AdamantRichTransactionReactService.swift; sourceTree = ""; }; 3A4193902A580C85006A6B22 /* RichTransactionReactService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RichTransactionReactService.swift; sourceTree = ""; }; 3A4193992A5D554A006A6B22 /* Reaction.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Reaction.swift; sourceTree = ""; }; - 3A5F89322AD81CAB00347B41 /* TransactionsListView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TransactionsListView.swift; sourceTree = ""; }; 3A7BD00D2AA9BCE80045AAB0 /* VibroService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VibroService.swift; sourceTree = ""; }; 3A7BD00F2AA9BD030045AAB0 /* AdamantVibroService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AdamantVibroService.swift; sourceTree = ""; }; 3A7BD0112AA9BD5A0045AAB0 /* AdamantVibroType.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AdamantVibroType.swift; sourceTree = ""; }; @@ -1947,7 +1945,6 @@ 64BD2B7620E2820300E2CD36 /* TransactionDetails.swift */, 64FA53D020E24941006783C9 /* TransactionDetailsViewControllerBase.swift */, E9E7CDC52003F6D200DFC4DB /* TransactionTableViewCell.swift */, - 3A5F89322AD81CAB00347B41 /* TransactionsListView.swift */, E9E7CDC62003F6D200DFC4DB /* TransactionTableViewCell.xib */, E9484B77227C617D008E10F0 /* BalanceTableViewCell.swift */, E9484B78227C617E008E10F0 /* BalanceTableViewCell.xib */, @@ -2926,7 +2923,6 @@ E9DFB71C21624C9200CF8C7C /* AdmTransactionDetailsViewController.swift in Sources */, 6403F5E022723F6400D58779 /* DashWalletFactory.swift in Sources */, E94008722114EACF00CD2D67 /* WalletAccount.swift in Sources */, - 3A5F89332AD81CAC00347B41 /* TransactionsListView.swift in Sources */, 3A7BD00E2AA9BCE80045AAB0 /* VibroService.swift in Sources */, E93B0D742028B21400126346 /* ChatsProvider.swift in Sources */, E9CAE8D42018AC1800345E76 /* AdamantApi+Keys.swift in Sources */, diff --git a/Adamant/Models/SimpleTransactionDetails.swift b/Adamant/Models/SimpleTransactionDetails.swift index ceca4d85d..5af65e8d2 100644 --- a/Adamant/Models/SimpleTransactionDetails.swift +++ b/Adamant/Models/SimpleTransactionDetails.swift @@ -36,6 +36,9 @@ struct SimpleTransactionDetails: TransactionDetails, Hashable { } var partnerName: String? + var comment: String? + var showToChat: Bool? + var chatRoom: Chatroom? init(defaultCurrencySymbol: String? = nil, txId: String, senderAddress: String, recipientAddress: String, dateValue: Date? = nil, amountValue: Decimal? = nil, feeValue: Decimal? = nil, confirmationsValue: String? = nil, blockValue: String? = nil, isOutgoing: Bool, transactionStatus: TransactionStatus? = nil, partnerName: String? = nil) { self.defaultCurrencySymbol = defaultCurrencySymbol diff --git a/Adamant/Modules/Wallets/Adamant/AdmTransactionDetailsViewController.swift b/Adamant/Modules/Wallets/Adamant/AdmTransactionDetailsViewController.swift index dc6205062..e4f4f900e 100644 --- a/Adamant/Modules/Wallets/Adamant/AdmTransactionDetailsViewController.swift +++ b/Adamant/Modules/Wallets/Adamant/AdmTransactionDetailsViewController.swift @@ -61,37 +61,26 @@ final class AdmTransactionDetailsViewController: TransactionDetailsViewControlle super.viewDidLoad() - if showToChat { - let haveChatroom: Bool - - if let transfer = transaction as? TransferTransaction, let partner = transfer.partner as? CoreDataAccount, let chatroom = partner.chatroom, let transactions = chatroom.transactions { - let messeges = transactions.first(where: { (object) -> Bool in - return !(object is TransferTransaction) - }) - - haveChatroom = messeges != nil - } else { - haveChatroom = false - } - - let chatLabel = haveChatroom ? String.adamant.transactionList.toChat : String.adamant.transactionList.startChat + if showToChat, + let trs = transaction as? SimpleTransactionDetails, + trs.chatRoom != nil, + let section = form.sectionBy(tag: Sections.actions.tag) { + let chatLabel = String.adamant.transactionList.toChat // MARK: Open chat - if let trs = transaction as? TransferTransaction, trs.chatroom != nil, let section = form.sectionBy(tag: Sections.actions.tag) { - let row = LabelRow { - $0.tag = Rows.openChat.tag - $0.title = chatLabel - $0.cell.imageView?.image = Rows.openChat.image - }.cellSetup { (cell, _) in - cell.selectionStyle = .gray - }.cellUpdate { (cell, _) in - cell.accessoryType = .disclosureIndicator - }.onCellSelection { [weak self] (_, _) in - self?.goToChat() - } - - section.append(row) + let row = LabelRow { + $0.tag = Rows.openChat.tag + $0.title = chatLabel + $0.cell.imageView?.image = Rows.openChat.image + }.cellSetup { (cell, _) in + cell.selectionStyle = .gray + }.cellUpdate { (cell, _) in + cell.accessoryType = .disclosureIndicator + }.onCellSelection { [weak self] (_, _) in + self?.goToChat() } + + section.append(row) } tableView.refreshControl = refreshControl @@ -114,11 +103,18 @@ final class AdmTransactionDetailsViewController: TransactionDetailsViewControlle } func goToChat() { - guard let transfer = transaction as? TransferTransaction else { - return + var chatRoom: Chatroom? + + if let transfer = transaction as? SimpleTransactionDetails { + chatRoom = transfer.chatRoom + } + + if let transfer = transaction as? TransferTransaction { + let partner = transfer.partner as? CoreDataAccount + chatRoom = partner?.chatroom } - guard let chatroom = transfer.chatroom else { + guard let chatroom = chatRoom else { dialogService.showError(withMessage: "AdmTransactionDetailsViewController: Failed to get chatroom for transaction.", supportEmail: true, error: nil) return } @@ -154,6 +150,7 @@ final class AdmTransactionDetailsViewController: TransactionDetailsViewControlle do { try await transfersProvider.refreshTransfer(id: id) + transaction = await transfersProvider.getTransfer(id: id) refreshControl.endRefreshing() tableView.reloadData() } catch { diff --git a/Adamant/Modules/Wallets/Adamant/AdmTransactionsViewController.swift b/Adamant/Modules/Wallets/Adamant/AdmTransactionsViewController.swift index 8e6e078bc..94e71f707 100644 --- a/Adamant/Modules/Wallets/Adamant/AdmTransactionsViewController.swift +++ b/Adamant/Modules/Wallets/Adamant/AdmTransactionsViewController.swift @@ -205,6 +205,12 @@ final class AdmTransactionsViewController: TransactionsListViewControllerBase { var simple = SimpleTransactionDetails(transaction) simple.partnerName = getPartnerName(for: partnerId) + simple.showToChat = toShowChat(for: transaction) + simple.comment = transaction.comment + + let partner = transaction.partner as? CoreDataAccount + let chatroom = partner?.chatroom + simple.chatRoom = chatroom return simple } @@ -222,27 +228,23 @@ final class AdmTransactionsViewController: TransactionsListViewControllerBase { // MARK: - UITableView func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { - guard let transaction = controller?.object(at: indexPath) else { - tableView.deselectRow(at: indexPath, animated: true) - return - } + let transaction = transactions[indexPath.row] let controller = screensFactory.makeAdmTransactionDetails() controller.transaction = transaction controller.comment = transaction.comment - - controller.showToChat = toShowChat(for: transaction) - + controller.showToChat = transaction.showToChat ?? false + if let address = accountService.account?.address { - let partnerName = addressBookService.getName(for: transaction.partner) + let partnerName = transaction.partnerName - if address == transaction.senderId { + if address == transaction.senderAddress { controller.senderName = String.adamant.transactionDetails.yourAddress } else { controller.senderName = partnerName } - if address == transaction.recipientId { + if address == transaction.recipientAddress { controller.recipientName = String.adamant.transactionDetails.yourAddress } else { controller.recipientName = partnerName @@ -252,66 +254,19 @@ final class AdmTransactionsViewController: TransactionsListViewControllerBase { navigationController?.pushViewController(controller, animated: true) } - func tableView(_ tableView: UITableView, editActionsForRowAt: IndexPath) -> [UITableViewRowAction]? { - guard let transaction = controller?.object(at: editActionsForRowAt), let partner = transaction.partner as? CoreDataAccount, let chatroom = partner.chatroom, let transactions = chatroom.transactions else { - return nil - } - - let messeges = transactions.first(where: { (object) -> Bool in - return !(object is TransferTransaction) - }) + func tableView( + _ tableView: UITableView, + trailingSwipeActionsConfigurationForRowAt indexPath: IndexPath + ) -> UISwipeActionsConfiguration? { + let transaction = transactions[indexPath.row] - let title = (messeges != nil) ? String.adamant.transactionList.toChat : String.adamant.transactionList.startChat - - let toChat = UITableViewRowAction(style: .normal, title: title) { [weak self] _, _ in - guard - let self = self, - let account = accountService.account - else { return } - - let vc = screensFactory.makeChat() - vc.hidesBottomBarWhenPushed = true - vc.viewModel.setup( - account: account, - chatroom: chatroom, - messageIdToShow: nil, - preservationDelegate: nil - ) - - if let nav = self.navigationController { - nav.pushViewController(vc, animated: true) - } else { - vc.modalPresentationStyle = .overFullScreen - present(vc, animated: true) - } - } - - toChat.backgroundColor = UIColor.adamant.primary - - return [toChat] - } - - func tableView(_ tableView: UITableView, canEditRowAt indexPath: IndexPath) -> Bool { - guard let transaction = controller?.object(at: indexPath) else { - return false - } - - return toShowChat(for: transaction) - } - - @available(iOS 11.0, *) - func tableView(_ tableView: UITableView, trailingSwipeActionsConfigurationForRowAt indexPath: IndexPath) -> UISwipeActionsConfiguration? { - guard let transaction = controller?.object(at: indexPath), let partner = transaction.partner as? CoreDataAccount, let chatroom = partner.chatroom, let transactions = chatroom.transactions else { + guard transaction.showToChat == true, + let chatroom = transaction.chatRoom + else { return nil } - let messeges = transactions.first(where: { (object) -> Bool in - return !(object is TransferTransaction) - }) - - let title = (messeges != nil) ? String.adamant.transactionList.toChat : String.adamant.transactionList.startChat - - let toChat = UIContextualAction(style: .normal, title: title) { [weak self] (_, _, _) in + let toChat = UIContextualAction(style: .normal, title: "") { [weak self] (_, _, _) in guard let self = self, let account = accountService.account diff --git a/Adamant/Modules/Wallets/Adamant/AdmTransferViewController.swift b/Adamant/Modules/Wallets/Adamant/AdmTransferViewController.swift index 25d1f8883..5b9f0ea87 100644 --- a/Adamant/Modules/Wallets/Adamant/AdmTransferViewController.swift +++ b/Adamant/Modules/Wallets/Adamant/AdmTransferViewController.swift @@ -134,8 +134,7 @@ final class AdmTransferViewController: TransferViewControllerBase { recipient: String, comments: String ) { - guard let service = service else { return } - let detailsVC = screensFactory.makeDetailsVC(service: service) + let detailsVC = screensFactory.makeAdmTransactionDetails() detailsVC.transaction = result if comments.count > 0 { diff --git a/Adamant/Modules/Wallets/Adamant/AdmWalletService.swift b/Adamant/Modules/Wallets/Adamant/AdmWalletService.swift index e15f1d80f..346c4e51d 100644 --- a/Adamant/Modules/Wallets/Adamant/AdmWalletService.swift +++ b/Adamant/Modules/Wallets/Adamant/AdmWalletService.swift @@ -130,14 +130,6 @@ final class AdmWalletService: NSObject, WalletService { .store(in: &subscriptions) } - func addTransactionObserver() { - coinStorage.transactionsPublisher - .sink { [weak self] transactions in - self?.transactions = transactions - } - .store(in: &subscriptions) - } - func update() { guard let accountService = accountService, let account = accountService.account else { wallet = nil @@ -213,7 +205,6 @@ extension AdmWalletService: SwinjectDependentService { transfersProvider = container.resolve(TransfersProvider.self) coreDataStack = container.resolve(CoreDataStack.self) - addTransactionObserver() Task { let controller = await transfersProvider.unreadTransfersController() diff --git a/Adamant/Modules/Wallets/TransactionsListView.swift b/Adamant/Modules/Wallets/TransactionsListView.swift deleted file mode 100644 index 79bacc686..000000000 --- a/Adamant/Modules/Wallets/TransactionsListView.swift +++ /dev/null @@ -1,9 +0,0 @@ -// -// TransactionsListView.swift -// Adamant -// -// Created by Stanislav Jelezoglo on 12.10.2023. -// Copyright © 2023 Adamant. All rights reserved. -// - -import Foundation diff --git a/Adamant/Services/DataProviders/AdamantChatTransactionService.swift b/Adamant/Services/DataProviders/AdamantChatTransactionService.swift index 7d31d0b32..a41a2a920 100644 --- a/Adamant/Services/DataProviders/AdamantChatTransactionService.swift +++ b/Adamant/Services/DataProviders/AdamantChatTransactionService.swift @@ -234,7 +234,9 @@ actor AdamantChatTransactionService: ChatTransactionService { let transfer: TransferTransaction if let trs = getTransfer(id: String(transaction.id), context: context) { transfer = trs - transfer.confirmations = transaction.confirmations + if transfer.confirmations < transaction.confirmations { + transfer.confirmations = transaction.confirmations + } transfer.statusEnum = .delivered transfer.blockId = transaction.blockId } else { diff --git a/Adamant/Services/DataProviders/AdamantTransfersProvider.swift b/Adamant/Services/DataProviders/AdamantTransfersProvider.swift index e99abedd0..dd5b4872b 100644 --- a/Adamant/Services/DataProviders/AdamantTransfersProvider.swift +++ b/Adamant/Services/DataProviders/AdamantTransfersProvider.swift @@ -1056,7 +1056,7 @@ extension AdamantTransfersProvider { Set(unreadTransactions.compactMap { $0.chatroom }).forEach { $0.hasUnreadMessages = true } } } - + // MARK: 6. Dump transactions to viewContext do { let rooms = transfers.compactMap { $0.chatroom } @@ -1071,12 +1071,12 @@ extension AdamantTransfersProvider { } @MainActor func updateContext(rooms: [Chatroom]) async { - let viewContextChatrooms = Set(rooms).compactMap { - self.stack.container.viewContext.object(with: $0.objectID) as? Chatroom - } - - for chatroom in viewContextChatrooms { - await chatroom.updateLastTransaction() - } - } + let viewContextChatrooms = Set(rooms).compactMap { + self.stack.container.viewContext.object(with: $0.objectID) as? Chatroom + } + + for chatroom in viewContextChatrooms { + chatroom.updateLastTransaction() + } + } } From 108f57ee2848bd4af7c1cc117011c12ad2fe621c Mon Sep 17 00:00:00 2001 From: StanislavDevIOS Date: Wed, 18 Oct 2023 18:31:17 +0300 Subject: [PATCH 25/96] [trello.com/c/uWUl7xm7] code refactoring --- Adamant/Models/SimpleTransactionDetails.swift | 7 +++++++ .../Adamant/AdmTransactionsViewController.swift | 2 +- .../Modules/Wallets/Adamant/AdmWalletService.swift | 12 ++++++------ .../Modules/Wallets/Bitcoin/BtcWalletService.swift | 12 ++++++------ Adamant/Modules/Wallets/Dash/DashWalletService.swift | 12 ++++++------ Adamant/Modules/Wallets/Doge/DogeWalletService.swift | 12 ++++++------ .../Modules/Wallets/ERC20/ERC20WalletService.swift | 12 ++++++------ .../Modules/Wallets/Ethereum/EthWalletService.swift | 12 ++++++------ Adamant/Modules/Wallets/Lisk/LskWalletService.swift | 12 ++++++------ .../Modules/Wallets/TransactionTableViewCell.swift | 3 +-- Adamant/Modules/Wallets/WalletService.swift | 4 ++-- Adamant/ServiceProtocols/CoinStorage.swift | 5 +++-- Adamant/Services/AdamantCoinStorageService.swift | 4 ++-- 13 files changed, 58 insertions(+), 51 deletions(-) diff --git a/Adamant/Models/SimpleTransactionDetails.swift b/Adamant/Models/SimpleTransactionDetails.swift index 5af65e8d2..d0d0ae7c5 100644 --- a/Adamant/Models/SimpleTransactionDetails.swift +++ b/Adamant/Models/SimpleTransactionDetails.swift @@ -69,4 +69,11 @@ struct SimpleTransactionDetails: TransactionDetails, Hashable { self.transactionStatus = transaction.transactionStatus } + func hash(into hasher: inout Hasher) { + hasher.combine(txId) + } + + static func == (lhs: Self, rhs: Self) -> Bool { + lhs.txId == rhs.txId + } } diff --git a/Adamant/Modules/Wallets/Adamant/AdmTransactionsViewController.swift b/Adamant/Modules/Wallets/Adamant/AdmTransactionsViewController.swift index 94e71f707..8e50f2f38 100644 --- a/Adamant/Modules/Wallets/Adamant/AdmTransactionsViewController.swift +++ b/Adamant/Modules/Wallets/Adamant/AdmTransactionsViewController.swift @@ -171,7 +171,7 @@ final class AdmTransactionsViewController: TransactionsListViewControllerBase { } isBusy = false - emptyLabel.isHidden = !isNeedToLoadMoore + emptyLabel.isHidden = !transactions.isEmpty refreshControl.endRefreshing() stopBottomIndicator() }.stored(in: taskManager) diff --git a/Adamant/Modules/Wallets/Adamant/AdmWalletService.swift b/Adamant/Modules/Wallets/Adamant/AdmWalletService.swift index 346c4e51d..0427b2c2e 100644 --- a/Adamant/Modules/Wallets/Adamant/AdmWalletService.swift +++ b/Adamant/Modules/Wallets/Adamant/AdmWalletService.swift @@ -75,15 +75,15 @@ final class AdmWalletService: NSObject, WalletService { private (set) var isWarningGasPrice = false private var subscriptions = Set() - @Published private(set) var transactions: [TransactionDetails] = [] - @Published private(set) var hasMoreOldTransactions: Bool = true + @ObservableValue private(set) var transactions: [TransactionDetails] = [] + @ObservableValue private(set) var hasMoreOldTransactions: Bool = true - var transactionsPublisher: Published<[TransactionDetails]>.Publisher { - $transactions + var transactionsPublisher: AnyObservable<[TransactionDetails]> { + $transactions.eraseToAnyPublisher() } - var hasMoreOldTransactionsPublisher: Published.Publisher { - $hasMoreOldTransactions + var hasMoreOldTransactionsPublisher: AnyObservable { + $hasMoreOldTransactions.eraseToAnyPublisher() } lazy var coinStorage: CoinStorageService = AdamantCoinStorageService( diff --git a/Adamant/Modules/Wallets/Bitcoin/BtcWalletService.swift b/Adamant/Modules/Wallets/Bitcoin/BtcWalletService.swift index 619c549b7..6c19a8baa 100644 --- a/Adamant/Modules/Wallets/Bitcoin/BtcWalletService.swift +++ b/Adamant/Modules/Wallets/Bitcoin/BtcWalletService.swift @@ -149,15 +149,15 @@ final class BtcWalletService: WalletService { private var subscriptions = Set() - @Published private(set) var transactions: [TransactionDetails] = [] - @Published private(set) var hasMoreOldTransactions: Bool = true + @ObservableValue private(set) var transactions: [TransactionDetails] = [] + @ObservableValue private(set) var hasMoreOldTransactions: Bool = true - var transactionsPublisher: Published<[TransactionDetails]>.Publisher { - $transactions + var transactionsPublisher: AnyObservable<[TransactionDetails]> { + $transactions.eraseToAnyPublisher() } - var hasMoreOldTransactionsPublisher: Published.Publisher { - $hasMoreOldTransactions + var hasMoreOldTransactionsPublisher: AnyObservable { + $hasMoreOldTransactions.eraseToAnyPublisher() } lazy var coinStorage: CoinStorageService = AdamantCoinStorageService( diff --git a/Adamant/Modules/Wallets/Dash/DashWalletService.swift b/Adamant/Modules/Wallets/Dash/DashWalletService.swift index bc712c342..b6a7c65c4 100644 --- a/Adamant/Modules/Wallets/Dash/DashWalletService.swift +++ b/Adamant/Modules/Wallets/Dash/DashWalletService.swift @@ -126,15 +126,15 @@ final class DashWalletService: WalletService { static let jsonDecoder = JSONDecoder() private var subscriptions = Set() - @Published private(set) var historyTransactions: [TransactionDetails] = [] - @Published private(set) var hasMoreOldTransactions: Bool = true + @ObservableValue private(set) var historyTransactions: [TransactionDetails] = [] + @ObservableValue private(set) var hasMoreOldTransactions: Bool = true - var transactionsPublisher: Published<[TransactionDetails]>.Publisher { - $historyTransactions + var transactionsPublisher: AnyObservable<[TransactionDetails]> { + $historyTransactions.eraseToAnyPublisher() } - var hasMoreOldTransactionsPublisher: Published.Publisher { - $hasMoreOldTransactions + var hasMoreOldTransactionsPublisher: AnyObservable { + $hasMoreOldTransactions.eraseToAnyPublisher() } lazy var coinStorage: CoinStorageService = AdamantCoinStorageService( diff --git a/Adamant/Modules/Wallets/Doge/DogeWalletService.swift b/Adamant/Modules/Wallets/Doge/DogeWalletService.swift index 053fd42d7..7e38aceb1 100644 --- a/Adamant/Modules/Wallets/Doge/DogeWalletService.swift +++ b/Adamant/Modules/Wallets/Doge/DogeWalletService.swift @@ -116,15 +116,15 @@ final class DogeWalletService: WalletService { private static let jsonDecoder = JSONDecoder() private var subscriptions = Set() - @Published private(set) var historyTransactions: [TransactionDetails] = [] - @Published private(set) var hasMoreOldTransactions: Bool = true + @ObservableValue private(set) var historyTransactions: [TransactionDetails] = [] + @ObservableValue private(set) var hasMoreOldTransactions: Bool = true - var transactionsPublisher: Published<[TransactionDetails]>.Publisher { - $historyTransactions + var transactionsPublisher: AnyObservable<[TransactionDetails]> { + $historyTransactions.eraseToAnyPublisher() } - var hasMoreOldTransactionsPublisher: Published.Publisher { - $hasMoreOldTransactions + var hasMoreOldTransactionsPublisher: AnyObservable { + $hasMoreOldTransactions.eraseToAnyPublisher() } lazy var coinStorage: CoinStorageService = AdamantCoinStorageService( diff --git a/Adamant/Modules/Wallets/ERC20/ERC20WalletService.swift b/Adamant/Modules/Wallets/ERC20/ERC20WalletService.swift index 24ebbbb32..82f00f81f 100644 --- a/Adamant/Modules/Wallets/ERC20/ERC20WalletService.swift +++ b/Adamant/Modules/Wallets/ERC20/ERC20WalletService.swift @@ -162,15 +162,15 @@ final class ERC20WalletService: WalletService { private (set) var contract: Web3.Contract? private var balanceObserver: NSObjectProtocol? - @Published private(set) var historyTransactions: [TransactionDetails] = [] - @Published private(set) var hasMoreOldTransactions: Bool = true + @ObservableValue private(set) var historyTransactions: [TransactionDetails] = [] + @ObservableValue private(set) var hasMoreOldTransactions: Bool = true - var transactionsPublisher: Published<[TransactionDetails]>.Publisher { - $historyTransactions + var transactionsPublisher: AnyObservable<[TransactionDetails]> { + $historyTransactions.eraseToAnyPublisher() } - var hasMoreOldTransactionsPublisher: Published.Publisher { - $hasMoreOldTransactions + var hasMoreOldTransactionsPublisher: AnyObservable { + $hasMoreOldTransactions.eraseToAnyPublisher() } lazy var coinStorage: CoinStorageService = AdamantCoinStorageService( diff --git a/Adamant/Modules/Wallets/Ethereum/EthWalletService.swift b/Adamant/Modules/Wallets/Ethereum/EthWalletService.swift index d68019584..c0108f4a9 100644 --- a/Adamant/Modules/Wallets/Ethereum/EthWalletService.swift +++ b/Adamant/Modules/Wallets/Ethereum/EthWalletService.swift @@ -158,15 +158,15 @@ final class EthWalletService: WalletService { private var initialBalanceCheck = false private var subscriptions = Set() - @Published private(set) var historyTransactions: [TransactionDetails] = [] - @Published private(set) var hasMoreOldTransactions: Bool = true + @ObservableValue private(set) var historyTransactions: [TransactionDetails] = [] + @ObservableValue private(set) var hasMoreOldTransactions: Bool = true - var transactionsPublisher: Published<[TransactionDetails]>.Publisher { - $historyTransactions + var transactionsPublisher: AnyObservable<[TransactionDetails]> { + $historyTransactions.eraseToAnyPublisher() } - var hasMoreOldTransactionsPublisher: Published.Publisher { - $hasMoreOldTransactions + var hasMoreOldTransactionsPublisher: AnyObservable { + $hasMoreOldTransactions.eraseToAnyPublisher() } lazy var coinStorage: CoinStorageService = AdamantCoinStorageService( diff --git a/Adamant/Modules/Wallets/Lisk/LskWalletService.swift b/Adamant/Modules/Wallets/Lisk/LskWalletService.swift index 8e9ec39ea..c84927633 100644 --- a/Adamant/Modules/Wallets/Lisk/LskWalletService.swift +++ b/Adamant/Modules/Wallets/Lisk/LskWalletService.swift @@ -96,15 +96,15 @@ final class LskWalletService: WalletService { private let nodes: [APINode] private var subscriptions = Set() - @Published private(set) var transactions: [TransactionDetails] = [] - @Published private(set) var hasMoreOldTransactions: Bool = true + @ObservableValue private(set) var transactions: [TransactionDetails] = [] + @ObservableValue private(set) var hasMoreOldTransactions: Bool = true - var transactionsPublisher: Published<[TransactionDetails]>.Publisher { - $transactions + var transactionsPublisher: AnyObservable<[TransactionDetails]> { + $transactions.eraseToAnyPublisher() } - var hasMoreOldTransactionsPublisher: Published.Publisher { - $hasMoreOldTransactions + var hasMoreOldTransactionsPublisher: AnyObservable { + $hasMoreOldTransactions.eraseToAnyPublisher() } lazy var coinStorage: CoinStorageService = AdamantCoinStorageService( diff --git a/Adamant/Modules/Wallets/TransactionTableViewCell.swift b/Adamant/Modules/Wallets/TransactionTableViewCell.swift index 4687a44c7..3c528071c 100644 --- a/Adamant/Modules/Wallets/TransactionTableViewCell.swift +++ b/Adamant/Modules/Wallets/TransactionTableViewCell.swift @@ -77,7 +77,6 @@ final class TransactionTableViewCell: UITableViewCell { transactionType = .income } - @MainActor func updateUI() { guard let transaction = transaction else { return } @@ -133,7 +132,7 @@ final class TransactionTableViewCell: UITableViewCell { } } - let amount = transaction.amountValue ?? .zero //(transaction.amount ?? 0).decimalValue + let amount = transaction.amountValue ?? .zero ammountLabel.text = AdamantBalanceFormat.full.format(amount, withCurrencySymbol: currencySymbol) } } diff --git a/Adamant/Modules/Wallets/WalletService.swift b/Adamant/Modules/Wallets/WalletService.swift index 8c81ab014..d71d088d1 100644 --- a/Adamant/Modules/Wallets/WalletService.swift +++ b/Adamant/Modules/Wallets/WalletService.swift @@ -217,11 +217,11 @@ protocol WalletService: AnyObject { var defaultOrdinalLevel: Int? { get } var richMessageType: String { get } - var transactionsPublisher: Published<[TransactionDetails]>.Publisher { + var transactionsPublisher: AnyObservable<[TransactionDetails]> { get } - var hasMoreOldTransactionsPublisher: Published.Publisher { + var hasMoreOldTransactionsPublisher: AnyObservable { get } diff --git a/Adamant/ServiceProtocols/CoinStorage.swift b/Adamant/ServiceProtocols/CoinStorage.swift index 92ac14ade..912281324 100644 --- a/Adamant/ServiceProtocols/CoinStorage.swift +++ b/Adamant/ServiceProtocols/CoinStorage.swift @@ -6,10 +6,11 @@ // Copyright © 2023 Adamant. All rights reserved. // -import Foundation +import Combine +import CommonKit protocol CoinStorageService: AnyObject { - var transactionsPublisher: Published<[TransactionDetails]>.Publisher { + var transactionsPublisher: any Observable<[TransactionDetails]> { get } diff --git a/Adamant/Services/AdamantCoinStorageService.swift b/Adamant/Services/AdamantCoinStorageService.swift index 9c58e749a..67768591d 100644 --- a/Adamant/Services/AdamantCoinStorageService.swift +++ b/Adamant/Services/AdamantCoinStorageService.swift @@ -21,9 +21,9 @@ final class AdamantCoinStorageService: NSObject, CoinStorageService { private lazy var transactionController = getTransactionController() private var subscriptions = Set() - @Published private var transactions: [TransactionDetails] = [] + @ObservableValue private var transactions: [TransactionDetails] = [] - var transactionsPublisher: Published<[TransactionDetails]>.Publisher { + var transactionsPublisher: any Observable<[TransactionDetails]> { $transactions } From 85eb181b97aff5d586d0232e125a8128a42d0969 Mon Sep 17 00:00:00 2001 From: StanislavDevIOS Date: Thu, 19 Oct 2023 14:51:05 +0300 Subject: [PATCH 26/96] [trello.com/c/uWUl7xm7] feat: use hash for transactions datasource --- Adamant.xcodeproj/project.pbxproj | 12 +++++++ Adamant/Models/SimpleTransactionDetails.swift | 10 +----- .../SimpleTransactionDetails+Hashable.swift | 28 +++++++++++++++ .../TransactionsListViewControllerBase.swift | 15 ++++---- .../CommonKit/Helpers/HashableIDWrapper.swift | 36 +++++++++++++++++++ 5 files changed, 85 insertions(+), 16 deletions(-) create mode 100644 Adamant/Modules/Wallets/Mappers/SimpleTransactionDetails+Hashable.swift create mode 100644 CommonKit/Sources/CommonKit/Helpers/HashableIDWrapper.swift diff --git a/Adamant.xcodeproj/project.pbxproj b/Adamant.xcodeproj/project.pbxproj index 4d8615592..955956f8f 100644 --- a/Adamant.xcodeproj/project.pbxproj +++ b/Adamant.xcodeproj/project.pbxproj @@ -16,6 +16,7 @@ 3A41938F2A580C57006A6B22 /* AdamantRichTransactionReactService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3A41938E2A580C57006A6B22 /* AdamantRichTransactionReactService.swift */; }; 3A4193912A580C85006A6B22 /* RichTransactionReactService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3A4193902A580C85006A6B22 /* RichTransactionReactService.swift */; }; 3A41939A2A5D554A006A6B22 /* Reaction.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3A4193992A5D554A006A6B22 /* Reaction.swift */; }; + 3A770E4C2AE14F130008D98F /* SimpleTransactionDetails+Hashable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3A770E4B2AE14F130008D98F /* SimpleTransactionDetails+Hashable.swift */; }; 3A7BD00E2AA9BCE80045AAB0 /* VibroService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3A7BD00D2AA9BCE80045AAB0 /* VibroService.swift */; }; 3A7BD0102AA9BD030045AAB0 /* AdamantVibroService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3A7BD00F2AA9BD030045AAB0 /* AdamantVibroService.swift */; }; 3A7BD0122AA9BD5A0045AAB0 /* AdamantVibroType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3A7BD0112AA9BD5A0045AAB0 /* AdamantVibroType.swift */; }; @@ -603,6 +604,7 @@ 3A41938E2A580C57006A6B22 /* AdamantRichTransactionReactService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AdamantRichTransactionReactService.swift; sourceTree = ""; }; 3A4193902A580C85006A6B22 /* RichTransactionReactService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RichTransactionReactService.swift; sourceTree = ""; }; 3A4193992A5D554A006A6B22 /* Reaction.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Reaction.swift; sourceTree = ""; }; + 3A770E4B2AE14F130008D98F /* SimpleTransactionDetails+Hashable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "SimpleTransactionDetails+Hashable.swift"; sourceTree = ""; }; 3A7BD00D2AA9BCE80045AAB0 /* VibroService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VibroService.swift; sourceTree = ""; }; 3A7BD00F2AA9BD030045AAB0 /* AdamantVibroService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AdamantVibroService.swift; sourceTree = ""; }; 3A7BD0112AA9BD5A0045AAB0 /* AdamantVibroType.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AdamantVibroType.swift; sourceTree = ""; }; @@ -1191,6 +1193,14 @@ path = RichTransactionReactService; sourceTree = ""; }; + 3A770E4A2AE14EFD0008D98F /* Mappers */ = { + isa = PBXGroup; + children = ( + 3A770E4B2AE14F130008D98F /* SimpleTransactionDetails+Hashable.swift */, + ); + path = Mappers; + sourceTree = ""; + }; 3AA2D5F8280EAF49000ED971 /* SocketService */ = { isa = PBXGroup; children = ( @@ -1925,6 +1935,7 @@ E940086C2114A8FD00CD2D67 /* Wallets */ = { isa = PBXGroup; children = ( + 3A770E4A2AE14EFD0008D98F /* Mappers */, 93A18C872AAEAE5600D0AB98 /* DI */, A50A41022822F8CE006BDFE1 /* Bitcoin */, E94008902119D22400CD2D67 /* Adamant */, @@ -2949,6 +2960,7 @@ 932BD15B29D2F75200AA1947 /* RichMessageProviderWithStatusCheck+Extension.swift in Sources */, E908473B219707200095825D /* AccountViewController+StayIn.swift in Sources */, E9204B5220C9762400F3B9AB /* MessageStatus.swift in Sources */, + 3A770E4C2AE14F130008D98F /* SimpleTransactionDetails+Hashable.swift in Sources */, E908471B2196FE590095825D /* Adamant.xcdatamodeld in Sources */, E940087B2114ED0600CD2D67 /* EthWalletService.swift in Sources */, 93294B902AAD2C6B00911109 /* SwiftyOnboardOverlay.swift in Sources */, diff --git a/Adamant/Models/SimpleTransactionDetails.swift b/Adamant/Models/SimpleTransactionDetails.swift index d0d0ae7c5..01a054017 100644 --- a/Adamant/Models/SimpleTransactionDetails.swift +++ b/Adamant/Models/SimpleTransactionDetails.swift @@ -8,7 +8,7 @@ import Foundation -struct SimpleTransactionDetails: TransactionDetails, Hashable { +struct SimpleTransactionDetails: TransactionDetails { var defaultCurrencySymbol: String? var txId: String @@ -68,12 +68,4 @@ struct SimpleTransactionDetails: TransactionDetails, Hashable { self.isOutgoing = transaction.isOutgoing self.transactionStatus = transaction.transactionStatus } - - func hash(into hasher: inout Hasher) { - hasher.combine(txId) - } - - static func == (lhs: Self, rhs: Self) -> Bool { - lhs.txId == rhs.txId - } } diff --git a/Adamant/Modules/Wallets/Mappers/SimpleTransactionDetails+Hashable.swift b/Adamant/Modules/Wallets/Mappers/SimpleTransactionDetails+Hashable.swift new file mode 100644 index 000000000..8e51c6956 --- /dev/null +++ b/Adamant/Modules/Wallets/Mappers/SimpleTransactionDetails+Hashable.swift @@ -0,0 +1,28 @@ +// +// SimpleTransactionDetails+Hashable.swift +// Adamant +// +// Created by Stanislav Jelezoglo on 19.10.2023. +// Copyright © 2023 Adamant. All rights reserved. +// + +import CommonKit + +extension Sequence where Element == SimpleTransactionDetails { + func wrappedByHashableId() -> [HashableIDWrapper] { + var identifierTable: [String: Int] = [:] + var result: [HashableIDWrapper] = [] + + forEach { item in + let index = identifierTable[item.txId] ?? .zero + identifierTable[item.txId] = index + 1 + + result.append(.init( + identifier: .init(identifier: item.txId, index: index), + value: item + )) + } + + return result + } +} diff --git a/Adamant/Modules/Wallets/TransactionsListViewControllerBase.swift b/Adamant/Modules/Wallets/TransactionsListViewControllerBase.swift index 444a87d6d..a3668c744 100644 --- a/Adamant/Modules/Wallets/TransactionsListViewControllerBase.swift +++ b/Adamant/Modules/Wallets/TransactionsListViewControllerBase.swift @@ -22,7 +22,7 @@ extension String.adamant { } } -private typealias ChatDiffableDataSource = UITableViewDiffableDataSource +private typealias TransactionsDiffableDataSource = UITableViewDiffableDataSource> // Extensions for a generic classes is limited, so delegates implemented right in class declaration class TransactionsListViewControllerBase: UIViewController { @@ -59,7 +59,7 @@ class TransactionsListViewControllerBase: UIViewController { private var limit = 25 private var offset = 0 - private lazy var dataSource = ChatDiffableDataSource(tableView: tableView, cellProvider: makeCell) + private lazy var dataSource = TransactionsDiffableDataSource(tableView: tableView, cellProvider: makeCell) // MARK: - IBOutlets @IBOutlet weak var tableView: UITableView! @@ -158,10 +158,11 @@ class TransactionsListViewControllerBase: UIViewController { by: { ($0.dateValue ?? Date()) > ($1.dateValue ?? Date()) } ) - var snapshot = NSDiffableDataSourceSnapshot() + let list = self.transactions.wrappedByHashableId() + var snapshot = NSDiffableDataSourceSnapshot>() snapshot.appendSections([.zero]) - snapshot.appendItems(self.transactions) - snapshot.reconfigureItems(self.transactions) + snapshot.appendItems(list) + snapshot.reconfigureItems(list) dataSource.apply(snapshot, animatingDifferences: false) guard !isBusy else { return } @@ -243,7 +244,7 @@ class TransactionsListViewControllerBase: UIViewController { private func makeCell( tableView: UITableView, indexPath: IndexPath, - model: SimpleTransactionDetails + model: HashableIDWrapper ) -> UITableViewCell { let cell = tableView.dequeueReusableCell(withIdentifier: cellIdentifierCompact, for: indexPath) as! TransactionTableViewCell @@ -252,7 +253,7 @@ class TransactionsListViewControllerBase: UIViewController { ? .zero : UITableView.defaultTransactionsSeparatorInset - cell.transaction = model + cell.transaction = model.value cell.currencySymbol = currencySymbol return cell } diff --git a/CommonKit/Sources/CommonKit/Helpers/HashableIDWrapper.swift b/CommonKit/Sources/CommonKit/Helpers/HashableIDWrapper.swift new file mode 100644 index 000000000..f3c2ac52e --- /dev/null +++ b/CommonKit/Sources/CommonKit/Helpers/HashableIDWrapper.swift @@ -0,0 +1,36 @@ +// +// File.swift +// +// +// Created by Stanislav Jelezoglo on 19.10.2023. +// + +import Foundation + +public struct HashableIDWrapper: Hashable { + public let identifier: ComplexIdentifier + public let value: Value + + public init(identifier: ComplexIdentifier, value: Value) { + self.identifier = identifier + self.value = value + } + + public func hash(into hasher: inout Hasher) { + hasher.combine(identifier) + } + + public static func == (lhs: Self, rhs: Self) -> Bool { + lhs.identifier == rhs.identifier + } +} + +public struct ComplexIdentifier: Hashable { + public let identifier: String + public let index: Int + + public init(identifier: String, index: Int) { + self.identifier = identifier + self.index = index + } +} From e7d20ca882d795cc05b33c4d2bb0893ebf265bbc Mon Sep 17 00:00:00 2001 From: StanislavDevIOS Date: Thu, 19 Oct 2023 15:03:51 +0300 Subject: [PATCH 27/96] [trello.com/c/uWUl7xm7] feat: rename RichTransactionStatusService -> TransactionStatusService --- Adamant.xcodeproj/project.pbxproj | 32 +++++++++---------- Adamant/App/AppDelegate.swift | 2 +- Adamant/App/DI/AppAssembly.swift | 4 +-- Adamant/Modules/Chat/ChatFactory.swift | 4 +-- .../Chat/ViewModel/ChatViewModel.swift | 4 +-- ...e.swift => TransactionStatusService.swift} | 4 +-- ... => AdamantTransactionStatusService.swift} | 10 +++--- ...swift => TransactionStatusPublisher.swift} | 4 +-- ...ft => TransactionStatusSubscription.swift} | 4 +-- 9 files changed, 34 insertions(+), 34 deletions(-) rename Adamant/ServiceProtocols/{RichTransactionStatusService.swift => TransactionStatusService.swift} (69%) rename Adamant/Services/RichTransactionStatusService/{AdamantRichTransactionStatusService.swift => AdamantTransactionStatusService.swift} (94%) rename Adamant/Services/RichTransactionStatusService/{RichTransactionStatusPublisher.swift => TransactionStatusPublisher.swift} (88%) rename Adamant/Services/RichTransactionStatusService/{RichTransactionStatusSubscription.swift => TransactionStatusSubscription.swift} (95%) diff --git a/Adamant.xcodeproj/project.pbxproj b/Adamant.xcodeproj/project.pbxproj index 955956f8f..0600d496a 100644 --- a/Adamant.xcodeproj/project.pbxproj +++ b/Adamant.xcodeproj/project.pbxproj @@ -186,8 +186,8 @@ 9322E875297042F000B8357C /* ChatSender.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9322E874297042F000B8357C /* ChatSender.swift */; }; 9322E877297042FA00B8357C /* ChatMessage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9322E876297042FA00B8357C /* ChatMessage.swift */; }; 9322E87B2970431200B8357C /* ChatMessageFactory.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9322E87A2970431200B8357C /* ChatMessageFactory.swift */; }; - 9324C75E297170600022D7EA /* RichTransactionStatusService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9324C75D297170600022D7EA /* RichTransactionStatusService.swift */; }; - 9324C760297171040022D7EA /* AdamantRichTransactionStatusService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9324C75F297171040022D7EA /* AdamantRichTransactionStatusService.swift */; }; + 9324C75E297170600022D7EA /* TransactionStatusService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9324C75D297170600022D7EA /* TransactionStatusService.swift */; }; + 9324C760297171040022D7EA /* AdamantTransactionStatusService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9324C75F297171040022D7EA /* AdamantTransactionStatusService.swift */; }; 93294B7D2AAD067000911109 /* AppContainer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 93294B7C2AAD067000911109 /* AppContainer.swift */; }; 93294B822AAD0BB400911109 /* BtcWalletFactory.swift in Sources */ = {isa = PBXBuildFile; fileRef = 93294B812AAD0BB400911109 /* BtcWalletFactory.swift */; }; 93294B842AAD0C8F00911109 /* Assembler+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 93294B832AAD0C8F00911109 /* Assembler+Extension.swift */; }; @@ -219,7 +219,7 @@ 93496BB62A6CAED100DD062F /* Roboto_400_normal.ttf in Resources */ = {isa = PBXBuildFile; fileRef = 93496BAB2A6CAED100DD062F /* Roboto_400_normal.ttf */; }; 93496BB72A6CAED100DD062F /* Roboto_500_normal.ttf in Resources */ = {isa = PBXBuildFile; fileRef = 93496BAC2A6CAED100DD062F /* Roboto_500_normal.ttf */; }; 93547BCA29E2262D00B0914B /* WelcomeViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 93547BC929E2262D00B0914B /* WelcomeViewController.swift */; }; - 935F53D629BE8F7400779492 /* RichTransactionStatusPublisher.swift in Sources */ = {isa = PBXBuildFile; fileRef = 935F53D529BE8F7400779492 /* RichTransactionStatusPublisher.swift */; }; + 935F53D629BE8F7400779492 /* TransactionStatusPublisher.swift in Sources */ = {isa = PBXBuildFile; fileRef = 935F53D529BE8F7400779492 /* TransactionStatusPublisher.swift */; }; 93684A2A29EFA28A00F9EFFE /* FixedTextMessageSizeCalculator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 93684A2929EFA28A00F9EFFE /* FixedTextMessageSizeCalculator.swift */; }; 9371130F2996EDA900F64CF9 /* ChatRefreshMock.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9371130E2996EDA900F64CF9 /* ChatRefreshMock.swift */; }; 9371E561295CD53100438F2C /* ChatLocalization.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9371E560295CD53100438F2C /* ChatLocalization.swift */; }; @@ -275,7 +275,7 @@ 93E5D4DC293000BE00439298 /* UnregisteredTransaction.swift in Sources */ = {isa = PBXBuildFile; fileRef = 93E5D4DA293000BE00439298 /* UnregisteredTransaction.swift */; }; 93E5D4DD293000BE00439298 /* UnregisteredTransaction.swift in Sources */ = {isa = PBXBuildFile; fileRef = 93E5D4DA293000BE00439298 /* UnregisteredTransaction.swift */; }; 93E5D4E02930029300439298 /* AdamantCore+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 93E5D4DF2930029300439298 /* AdamantCore+Extensions.swift */; }; - 93EE9C3329C2666200D9853F /* RichTransactionStatusSubscription.swift in Sources */ = {isa = PBXBuildFile; fileRef = 93EE9C3229C2666200D9853F /* RichTransactionStatusSubscription.swift */; }; + 93EE9C3329C2666200D9853F /* TransactionStatusSubscription.swift in Sources */ = {isa = PBXBuildFile; fileRef = 93EE9C3229C2666200D9853F /* TransactionStatusSubscription.swift */; }; 93F391502962F5D400BFD6AE /* SpinnerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 93F3914F2962F5D400BFD6AE /* SpinnerView.swift */; }; 93FA403629401BFC00D20DB6 /* PopupKit in Frameworks */ = {isa = PBXBuildFile; productRef = 93FA403529401BFC00D20DB6 /* PopupKit */; }; A50A41082822F8CE006BDFE1 /* BtcWalletService.swift in Sources */ = {isa = PBXBuildFile; fileRef = A50A41042822F8CE006BDFE1 /* BtcWalletService.swift */; }; @@ -767,8 +767,8 @@ 9322E874297042F000B8357C /* ChatSender.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChatSender.swift; sourceTree = ""; }; 9322E876297042FA00B8357C /* ChatMessage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChatMessage.swift; sourceTree = ""; }; 9322E87A2970431200B8357C /* ChatMessageFactory.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChatMessageFactory.swift; sourceTree = ""; }; - 9324C75D297170600022D7EA /* RichTransactionStatusService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RichTransactionStatusService.swift; sourceTree = ""; }; - 9324C75F297171040022D7EA /* AdamantRichTransactionStatusService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AdamantRichTransactionStatusService.swift; sourceTree = ""; }; + 9324C75D297170600022D7EA /* TransactionStatusService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TransactionStatusService.swift; sourceTree = ""; }; + 9324C75F297171040022D7EA /* AdamantTransactionStatusService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AdamantTransactionStatusService.swift; sourceTree = ""; }; 93294B7C2AAD067000911109 /* AppContainer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppContainer.swift; sourceTree = ""; }; 93294B812AAD0BB400911109 /* BtcWalletFactory.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BtcWalletFactory.swift; sourceTree = ""; }; 93294B832AAD0C8F00911109 /* Assembler+Extension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Assembler+Extension.swift"; sourceTree = ""; }; @@ -799,7 +799,7 @@ 93496BAB2A6CAED100DD062F /* Roboto_400_normal.ttf */ = {isa = PBXFileReference; lastKnownFileType = file; path = Roboto_400_normal.ttf; sourceTree = ""; }; 93496BAC2A6CAED100DD062F /* Roboto_500_normal.ttf */ = {isa = PBXFileReference; lastKnownFileType = file; path = Roboto_500_normal.ttf; sourceTree = ""; }; 93547BC929E2262D00B0914B /* WelcomeViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WelcomeViewController.swift; sourceTree = ""; }; - 935F53D529BE8F7400779492 /* RichTransactionStatusPublisher.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RichTransactionStatusPublisher.swift; sourceTree = ""; }; + 935F53D529BE8F7400779492 /* TransactionStatusPublisher.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TransactionStatusPublisher.swift; sourceTree = ""; }; 93684A2929EFA28A00F9EFFE /* FixedTextMessageSizeCalculator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FixedTextMessageSizeCalculator.swift; sourceTree = ""; }; 9371130E2996EDA900F64CF9 /* ChatRefreshMock.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChatRefreshMock.swift; sourceTree = ""; }; 9371E560295CD53100438F2C /* ChatLocalization.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChatLocalization.swift; sourceTree = ""; }; @@ -846,7 +846,7 @@ 93E1234A2A6DFEF7004DF33B /* NotificationStrings.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationStrings.swift; sourceTree = ""; }; 93E5D4DA293000BE00439298 /* UnregisteredTransaction.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UnregisteredTransaction.swift; sourceTree = ""; }; 93E5D4DF2930029300439298 /* AdamantCore+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "AdamantCore+Extensions.swift"; sourceTree = ""; }; - 93EE9C3229C2666200D9853F /* RichTransactionStatusSubscription.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RichTransactionStatusSubscription.swift; sourceTree = ""; }; + 93EE9C3229C2666200D9853F /* TransactionStatusSubscription.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TransactionStatusSubscription.swift; sourceTree = ""; }; 93F3914F2962F5D400BFD6AE /* SpinnerView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SpinnerView.swift; sourceTree = ""; }; A50A41042822F8CE006BDFE1 /* BtcWalletService.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BtcWalletService.swift; sourceTree = ""; }; A50A41052822F8CE006BDFE1 /* BtcWalletViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BtcWalletViewController.swift; sourceTree = ""; }; @@ -1442,9 +1442,9 @@ 935F53D429BE8F4800779492 /* RichTransactionStatusService */ = { isa = PBXGroup; children = ( - 9324C75F297171040022D7EA /* AdamantRichTransactionStatusService.swift */, - 935F53D529BE8F7400779492 /* RichTransactionStatusPublisher.swift */, - 93EE9C3229C2666200D9853F /* RichTransactionStatusSubscription.swift */, + 9324C75F297171040022D7EA /* AdamantTransactionStatusService.swift */, + 935F53D529BE8F7400779492 /* TransactionStatusPublisher.swift */, + 93EE9C3229C2666200D9853F /* TransactionStatusSubscription.swift */, ); path = RichTransactionStatusService; sourceTree = ""; @@ -1726,7 +1726,7 @@ E9FEECA321413659007DD7C8 /* RichMessageProvider.swift */, 550066C4284D65DB0044C0B1 /* HealthCheckService.swift */, 9304F8C1292F895C00173F18 /* PushNotificationsTokenService.swift */, - 9324C75D297170600022D7EA /* RichTransactionStatusService.swift */, + 9324C75D297170600022D7EA /* TransactionStatusService.swift */, 41047B73294C61D10039E956 /* VisibleWalletsService.swift */, 4153045A29C09C6C000E4BEA /* IncreaseFeeService.swift */, 4184F1742A33106200D7B8B9 /* CrashlysticsService.swift */, @@ -2797,7 +2797,7 @@ 557AC308287B1365004699D7 /* CheckmarkRowView.swift in Sources */, 9390C5052976B53000270CDF /* ChatDialog.swift in Sources */, 6455E9F321075D8000B2E94C /* AdamantAddressBookService.swift in Sources */, - 9324C75E297170600022D7EA /* RichTransactionStatusService.swift in Sources */, + 9324C75E297170600022D7EA /* TransactionStatusService.swift in Sources */, 9304F8BE292F88F900173F18 /* ANSPayload.swift in Sources */, 41CA598C29A0D84F002BFDE4 /* TaskManager.swift in Sources */, E9E7CD9120026FA100DFC4DB /* AppAssembly.swift in Sources */, @@ -2940,7 +2940,7 @@ E9C51EEF20139DC600385EB7 /* TransactionIdResponse.swift in Sources */, A50A41092822F8CE006BDFE1 /* BtcWalletViewController.swift in Sources */, E9722066201F42BB004F2AAD /* CoreDataStack.swift in Sources */, - 93EE9C3329C2666200D9853F /* RichTransactionStatusSubscription.swift in Sources */, + 93EE9C3329C2666200D9853F /* TransactionStatusSubscription.swift in Sources */, E913C8F21FFFA51D001A83F7 /* AppDelegate.swift in Sources */, 648CE3A222999CE70070A2CC /* BTCRawTransaction.swift in Sources */, 648DD79E2236A0B500B811FD /* DogeTransactionsViewController.swift in Sources */, @@ -3001,7 +3001,7 @@ 93A91FD1297972B7001DB1F8 /* ChatScrollDownButton.swift in Sources */, 41C1698C29E7F34900FEB3CB /* RichTransactionReplyService.swift in Sources */, E9A03FDA20DC0B14007653A1 /* NodesSource.swift in Sources */, - 935F53D629BE8F7400779492 /* RichTransactionStatusPublisher.swift in Sources */, + 935F53D629BE8F7400779492 /* TransactionStatusPublisher.swift in Sources */, 9390C5032976B42800270CDF /* ChatDialogManager.swift in Sources */, E926E02E213EAABF005E536B /* TransferViewControllerBase+Alert.swift in Sources */, E9B1AA572121ACC000080A2A /* AdmWalletViewController.swift in Sources */, @@ -3044,7 +3044,7 @@ 644EC35720EFAAB700F40C73 /* DelegatesListViewController.swift in Sources */, 4153045B29C09C6C000E4BEA /* IncreaseFeeService.swift in Sources */, E9C51EF12013F18000385EB7 /* NewChatViewController.swift in Sources */, - 9324C760297171040022D7EA /* AdamantRichTransactionStatusService.swift in Sources */, + 9324C760297171040022D7EA /* AdamantTransactionStatusService.swift in Sources */, E9B4E1A8210F079E007E77FC /* DoubleDetailsTableViewCell.swift in Sources */, E9502740202E257E002C1098 /* RepeaterService.swift in Sources */, E93D7AC02052CF63005D19DC /* AdamantNotificationService.swift in Sources */, diff --git a/Adamant/App/AppDelegate.swift b/Adamant/App/AppDelegate.swift index d668499db..59867856a 100644 --- a/Adamant/App/AppDelegate.swift +++ b/Adamant/App/AppDelegate.swift @@ -202,7 +202,7 @@ class AppDelegate: UIResponder, UIApplicationDelegate { } // Setup transactions statuses observing - if let service = container.resolve(RichTransactionStatusService.self) { + if let service = container.resolve(TransactionStatusService.self) { Task { await service.startObserving() } } diff --git a/Adamant/App/DI/AppAssembly.swift b/Adamant/App/DI/AppAssembly.swift index 094fed956..882b1ba55 100644 --- a/Adamant/App/DI/AppAssembly.swift +++ b/Adamant/App/DI/AppAssembly.swift @@ -215,14 +215,14 @@ struct AppAssembly: Assembly { }.inObjectScope(.container) // MARK: Rich transaction status service - container.register(RichTransactionStatusService.self) { r in + container.register(TransactionStatusService.self) { r in let accountService = r.resolve(AccountService.self)! let richProviders = accountService.wallets .compactMap { $0 as? RichMessageProviderWithStatusCheck } .map { ($0.dynamicRichMessageType, $0) } - return AdamantRichTransactionStatusService( + return AdamantTransactionStatusService( coreDataStack: r.resolve(CoreDataStack.self)!, richProviders: Dictionary(uniqueKeysWithValues: richProviders) ) diff --git a/Adamant/Modules/Chat/ChatFactory.swift b/Adamant/Modules/Chat/ChatFactory.swift index 247b00454..81d9d61f0 100644 --- a/Adamant/Modules/Chat/ChatFactory.swift +++ b/Adamant/Modules/Chat/ChatFactory.swift @@ -20,7 +20,7 @@ struct ChatFactory { let transferProvider: TransfersProvider let accountService: AccountService let accountProvider: AccountsProvider - let richTransactionStatusService: RichTransactionStatusService + let richTransactionStatusService: TransactionStatusService let addressBookService: AddressBookService let visibleWalletService: VisibleWalletsService let avatarService: AvatarService @@ -32,7 +32,7 @@ struct ChatFactory { transferProvider = assembler.resolve(TransfersProvider.self)! accountService = assembler.resolve(AccountService.self)! accountProvider = assembler.resolve(AccountsProvider.self)! - richTransactionStatusService = assembler.resolve(RichTransactionStatusService.self)! + richTransactionStatusService = assembler.resolve(TransactionStatusService.self)! addressBookService = assembler.resolve(AddressBookService.self)! visibleWalletService = assembler.resolve(VisibleWalletsService.self)! avatarService = assembler.resolve(AvatarService.self)! diff --git a/Adamant/Modules/Chat/ViewModel/ChatViewModel.swift b/Adamant/Modules/Chat/ViewModel/ChatViewModel.swift index c764ae4c3..34e59d151 100644 --- a/Adamant/Modules/Chat/ViewModel/ChatViewModel.swift +++ b/Adamant/Modules/Chat/ViewModel/ChatViewModel.swift @@ -26,7 +26,7 @@ final class ChatViewModel: NSObject { private let visibleWalletService: VisibleWalletsService private let accountService: AccountService private let accountProvider: AccountsProvider - private let richTransactionStatusService: RichTransactionStatusService + private let richTransactionStatusService: TransactionStatusService private let chatCacheService: ChatCacheService private let richMessageProviders: [String: RichMessageProvider] private let avatarService: AvatarService @@ -127,7 +127,7 @@ final class ChatViewModel: NSObject { visibleWalletService: VisibleWalletsService, accountService: AccountService, accountProvider: AccountsProvider, - richTransactionStatusService: RichTransactionStatusService, + richTransactionStatusService: TransactionStatusService, chatCacheService: ChatCacheService, richMessageProviders: [String: RichMessageProvider], avatarService: AvatarService, diff --git a/Adamant/ServiceProtocols/RichTransactionStatusService.swift b/Adamant/ServiceProtocols/TransactionStatusService.swift similarity index 69% rename from Adamant/ServiceProtocols/RichTransactionStatusService.swift rename to Adamant/ServiceProtocols/TransactionStatusService.swift index 2b64212b7..a4fb8f2d8 100644 --- a/Adamant/ServiceProtocols/RichTransactionStatusService.swift +++ b/Adamant/ServiceProtocols/TransactionStatusService.swift @@ -1,5 +1,5 @@ // -// RichTransactionStatusService.swift +// TransactionStatusService.swift // Adamant // // Created by Andrey Golubenko on 13.01.2023. @@ -8,7 +8,7 @@ import CoreData -protocol RichTransactionStatusService: Actor, AnyObject { +protocol TransactionStatusService: Actor, AnyObject { func forceUpdate(transaction: CoinTransaction) async func startObserving() } diff --git a/Adamant/Services/RichTransactionStatusService/AdamantRichTransactionStatusService.swift b/Adamant/Services/RichTransactionStatusService/AdamantTransactionStatusService.swift similarity index 94% rename from Adamant/Services/RichTransactionStatusService/AdamantRichTransactionStatusService.swift rename to Adamant/Services/RichTransactionStatusService/AdamantTransactionStatusService.swift index a22b74674..8cd0f1741 100644 --- a/Adamant/Services/RichTransactionStatusService/AdamantRichTransactionStatusService.swift +++ b/Adamant/Services/RichTransactionStatusService/AdamantTransactionStatusService.swift @@ -1,5 +1,5 @@ // -// AdamantRichTransactionStatusService.swift +// AdamantTransactionStatusService.swift // Adamant // // Created by Andrey Golubenko on 13.01.2023. @@ -10,7 +10,7 @@ import CoreData import Combine import CommonKit -actor AdamantRichTransactionStatusService: NSObject, RichTransactionStatusService { +actor AdamantTransactionStatusService: NSObject, TransactionStatusService { private let richProviders: [String: RichMessageProviderWithStatusCheck] private let coreDataStack: CoreDataStack @@ -55,7 +55,7 @@ actor AdamantRichTransactionStatusService: NSObject, RichTransactionStatusServic } } -extension AdamantRichTransactionStatusService: NSFetchedResultsControllerDelegate { +extension AdamantTransactionStatusService: NSFetchedResultsControllerDelegate { nonisolated func controller( _: NSFetchedResultsController, didChange object: Any, @@ -68,7 +68,7 @@ extension AdamantRichTransactionStatusService: NSFetchedResultsControllerDelegat } } -private extension AdamantRichTransactionStatusService { +private extension AdamantTransactionStatusService { func setupNetworkSubscription() { networkSubscription = NotificationCenter.default .publisher(for: .AdamantReachabilityMonitor.reachabilityChanged) @@ -111,7 +111,7 @@ private extension AdamantRichTransactionStatusService { let oldPendingAttempts = oldPendingAttempts[id] ?? .init(wrappedValue: .zero) self.oldPendingAttempts[id] = oldPendingAttempts - let publisher = RichTransactionStatusPublisher( + let publisher = TransactionStatusPublisher( provider: provider, transaction: transaction, oldPendingAttempts: oldPendingAttempts diff --git a/Adamant/Services/RichTransactionStatusService/RichTransactionStatusPublisher.swift b/Adamant/Services/RichTransactionStatusService/TransactionStatusPublisher.swift similarity index 88% rename from Adamant/Services/RichTransactionStatusService/RichTransactionStatusPublisher.swift rename to Adamant/Services/RichTransactionStatusService/TransactionStatusPublisher.swift index 0342bd4da..32f0479b9 100644 --- a/Adamant/Services/RichTransactionStatusService/RichTransactionStatusPublisher.swift +++ b/Adamant/Services/RichTransactionStatusService/TransactionStatusPublisher.swift @@ -10,7 +10,7 @@ import Foundation import Combine import CommonKit -struct RichTransactionStatusPublisher: Publisher { +struct TransactionStatusPublisher: Publisher { typealias Output = TransactionStatus typealias Failure = Never @@ -21,7 +21,7 @@ struct RichTransactionStatusPublisher: Publisher { func receive( subscriber: S ) where S: Subscriber, Failure == S.Failure, Output == S.Input { - let subscription = RichTransactionStatusSubscription( + let subscription = TransactionStatusSubscription( provider: provider, transaction: transaction, oldPendingAttempts: oldPendingAttempts, diff --git a/Adamant/Services/RichTransactionStatusService/RichTransactionStatusSubscription.swift b/Adamant/Services/RichTransactionStatusService/TransactionStatusSubscription.swift similarity index 95% rename from Adamant/Services/RichTransactionStatusService/RichTransactionStatusSubscription.swift rename to Adamant/Services/RichTransactionStatusService/TransactionStatusSubscription.swift index f0bf4defe..af9e610bb 100644 --- a/Adamant/Services/RichTransactionStatusService/RichTransactionStatusSubscription.swift +++ b/Adamant/Services/RichTransactionStatusService/TransactionStatusSubscription.swift @@ -10,7 +10,7 @@ import Combine import Foundation import CommonKit -actor RichTransactionStatusSubscription: Subscription where +actor TransactionStatusSubscription: Subscription where StatusSubscriber.Input == TransactionStatus, StatusSubscriber.Failure == Never { private let provider: RichMessageProviderWithStatusCheck @@ -68,7 +68,7 @@ actor RichTransactionStatusSubscription: Subscript } } -private extension RichTransactionStatusSubscription { +private extension TransactionStatusSubscription { enum State { case new case old From fa0ec94df4de0856a94fcd2cedf9441c5f36ff8f Mon Sep 17 00:00:00 2001 From: StanislavDevIOS Date: Thu, 19 Oct 2023 15:12:54 +0300 Subject: [PATCH 28/96] [trello.com/c/uWUl7xm7] fix: currency symbol for transaction list --- .../AdmTransactionsViewController.swift | 1 - .../BtcTransactionsViewController.swift | 5 -- .../Dash/DashTransactionsViewController.swift | 5 -- .../Doge/DogeTransactionsViewController.swift | 5 -- .../ERC20TransactionsViewController.swift | 5 -- .../EthTransactionsViewController.swift | 5 -- .../Lisk/LskTransactionsViewController.swift | 7 -- .../TransactionsListViewControllerBase.swift | 65 ++----------------- 8 files changed, 4 insertions(+), 94 deletions(-) diff --git a/Adamant/Modules/Wallets/Adamant/AdmTransactionsViewController.swift b/Adamant/Modules/Wallets/Adamant/AdmTransactionsViewController.swift index 8e50f2f38..a4a6f4b0b 100644 --- a/Adamant/Modules/Wallets/Adamant/AdmTransactionsViewController.swift +++ b/Adamant/Modules/Wallets/Adamant/AdmTransactionsViewController.swift @@ -71,7 +71,6 @@ final class AdmTransactionsViewController: TransactionsListViewControllerBase { reloadData() } - currencySymbol = AdmWalletService.currencySymbol setupObserver() } diff --git a/Adamant/Modules/Wallets/Bitcoin/BtcTransactionsViewController.swift b/Adamant/Modules/Wallets/Bitcoin/BtcTransactionsViewController.swift index bdfedce2d..81e45d927 100644 --- a/Adamant/Modules/Wallets/Bitcoin/BtcTransactionsViewController.swift +++ b/Adamant/Modules/Wallets/Bitcoin/BtcTransactionsViewController.swift @@ -17,11 +17,6 @@ final class BtcTransactionsViewController: TransactionsListViewControllerBase { var screensFactory: ScreensFactory! var addressBook: AddressBookService! - override func viewDidLoad() { - super.viewDidLoad() - currencySymbol = BtcWalletService.currencySymbol - } - // MARK: - UITableView func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { diff --git a/Adamant/Modules/Wallets/Dash/DashTransactionsViewController.swift b/Adamant/Modules/Wallets/Dash/DashTransactionsViewController.swift index 0620e16e7..089393a54 100644 --- a/Adamant/Modules/Wallets/Dash/DashTransactionsViewController.swift +++ b/Adamant/Modules/Wallets/Dash/DashTransactionsViewController.swift @@ -15,11 +15,6 @@ final class DashTransactionsViewController: TransactionsListViewControllerBase { var screensFactory: ScreensFactory! var dashWalletService: DashWalletService! - override func viewDidLoad() { - super.viewDidLoad() - currencySymbol = DashWalletService.currencySymbol - } - // MARK: - UITableView func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { diff --git a/Adamant/Modules/Wallets/Doge/DogeTransactionsViewController.swift b/Adamant/Modules/Wallets/Doge/DogeTransactionsViewController.swift index 19d9a02d6..a54e29609 100644 --- a/Adamant/Modules/Wallets/Doge/DogeTransactionsViewController.swift +++ b/Adamant/Modules/Wallets/Doge/DogeTransactionsViewController.swift @@ -16,11 +16,6 @@ final class DogeTransactionsViewController: TransactionsListViewControllerBase { var dogeWalletService: DogeWalletService! var screensFactory: ScreensFactory! - override func viewDidLoad() { - super.viewDidLoad() - currencySymbol = DogeWalletService.currencySymbol - } - // MARK: - UITableView func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { diff --git a/Adamant/Modules/Wallets/ERC20/ERC20TransactionsViewController.swift b/Adamant/Modules/Wallets/ERC20/ERC20TransactionsViewController.swift index cd12b4718..fae19226f 100644 --- a/Adamant/Modules/Wallets/ERC20/ERC20TransactionsViewController.swift +++ b/Adamant/Modules/Wallets/ERC20/ERC20TransactionsViewController.swift @@ -24,11 +24,6 @@ final class ERC20TransactionsViewController: TransactionsListViewControllerBase private var ethAddress: String = "" - override func viewDidLoad() { - super.viewDidLoad() - currencySymbol = walletService.tokenSymbol - } - // MARK: - UITableView func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { diff --git a/Adamant/Modules/Wallets/Ethereum/EthTransactionsViewController.swift b/Adamant/Modules/Wallets/Ethereum/EthTransactionsViewController.swift index c938e9bb8..a46910aa5 100644 --- a/Adamant/Modules/Wallets/Ethereum/EthTransactionsViewController.swift +++ b/Adamant/Modules/Wallets/Ethereum/EthTransactionsViewController.swift @@ -23,11 +23,6 @@ final class EthTransactionsViewController: TransactionsListViewControllerBase { // MARK: - Properties private var ethAddress: String = "" - override func viewDidLoad() { - super.viewDidLoad() - currencySymbol = EthWalletService.currencySymbol - } - // MARK: - UITableView func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { diff --git a/Adamant/Modules/Wallets/Lisk/LskTransactionsViewController.swift b/Adamant/Modules/Wallets/Lisk/LskTransactionsViewController.swift index 3dfb0d918..5e1721e7e 100644 --- a/Adamant/Modules/Wallets/Lisk/LskTransactionsViewController.swift +++ b/Adamant/Modules/Wallets/Lisk/LskTransactionsViewController.swift @@ -19,13 +19,6 @@ final class LskTransactionsViewController: TransactionsListViewControllerBase { var lskWalletService: LskWalletService! var screensFactory: ScreensFactory! - // MARK: - Properties - - override func viewDidLoad() { - super.viewDidLoad() - currencySymbol = LskWalletService.currencySymbol - } - // MARK: - UITableView func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { diff --git a/Adamant/Modules/Wallets/TransactionsListViewControllerBase.swift b/Adamant/Modules/Wallets/TransactionsListViewControllerBase.swift index a3668c744..13de1e292 100644 --- a/Adamant/Modules/Wallets/TransactionsListViewControllerBase.swift +++ b/Adamant/Modules/Wallets/TransactionsListViewControllerBase.swift @@ -60,7 +60,9 @@ class TransactionsListViewControllerBase: UIViewController { private var offset = 0 private lazy var dataSource = TransactionsDiffableDataSource(tableView: tableView, cellProvider: makeCell) - + + var currencySymbol: String { walletService.tokenSymbol } + // MARK: - IBOutlets @IBOutlet weak var tableView: UITableView! @IBOutlet weak var emptyLabel: UILabel! @@ -253,8 +255,8 @@ class TransactionsListViewControllerBase: UIViewController { ? .zero : UITableView.defaultTransactionsSeparatorInset - cell.transaction = model.value cell.currencySymbol = currencySymbol + cell.transaction = model.value return cell } @@ -283,69 +285,10 @@ class TransactionsListViewControllerBase: UIViewController { func reloadData() { } - - var currencySymbol: String? } // MARK: - UITableViewDataSource, UITableViewDelegate extension TransactionsListViewControllerBase: UITableViewDelegate { - func numberOfSections(in tableView: UITableView) -> Int { - return 1 - } - - // MARK: Cells - - func configureCell( - _ cell: TransactionTableViewCell, - transactionType: TransactionTableViewCell.TransactionType, - transactionStatus: TransactionStatus?, - partnerId: String, - partnerName: String?, - amount: Decimal, - date: Date? - ) { - cell.backgroundColor = .clear - cell.accountLabel.tintColor = UIColor.adamant.primary - cell.ammountLabel.tintColor = UIColor.adamant.primary - - cell.dateLabel.textColor = transactionStatus?.color ?? .adamant.secondary - - switch transactionStatus { - case .success, .inconsistent: - if let date = date { - cell.dateLabel.text = date.humanizedDateTime() - } else { - cell.dateLabel.text = nil - } - case .notInitiated: - cell.dateLabel.text = TransactionDetailsViewControllerBase.awaitingValueString - case .failed: - cell.dateLabel.text = TransactionStatus.failed.localized - default: - cell.dateLabel.text = TransactionStatus.pending.localized - } - - cell.transactionType = transactionType - - if let partnerName = partnerName { - cell.accountLabel.text = partnerName - cell.addressLabel.text = partnerId - cell.addressLabel.lineBreakMode = .byTruncatingMiddle - - if cell.addressLabel.isHidden { - cell.addressLabel.isHidden = false - } - } else { - cell.accountLabel.text = partnerId - - if !cell.addressLabel.isHidden { - cell.addressLabel.isHidden = true - } - } - - cell.ammountLabel.text = AdamantBalanceFormat.full.format(amount, withCurrencySymbol: currencySymbol) - } - func bottomIndicatorView() -> UIActivityIndicatorView { var activityIndicatorView = UIActivityIndicatorView() From 884e00832f3db4a93e4878c428bd0193b678c9ea Mon Sep 17 00:00:00 2001 From: StanislavDevIOS Date: Thu, 19 Oct 2023 16:36:27 +0300 Subject: [PATCH 29/96] [trello.com/c/NnRUaDYn] feat: update web3swift from 3.1.2 to 3.2.0 --- Adamant.xcworkspace/xcshareddata/swiftpm/Package.resolved | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Adamant.xcworkspace/xcshareddata/swiftpm/Package.resolved b/Adamant.xcworkspace/xcshareddata/swiftpm/Package.resolved index 95b69cc0b..efef8f034 100644 --- a/Adamant.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/Adamant.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -330,8 +330,8 @@ "repositoryURL": "https://github.com/skywinder/web3swift.git", "state": { "branch": null, - "revision": "16a2915ebd1a0fd33bf0a0ed5a9874909a6e187d", - "version": "3.1.2" + "revision": "74c24f4d3d5f1816616e9ebd16741ca5f7a57eb0", + "version": "3.2.0" } } ] From 75f08c7cba019e6e36cecce23badf65b250abd2e Mon Sep 17 00:00:00 2001 From: StanislavDevIOS Date: Thu, 19 Oct 2023 18:22:00 +0300 Subject: [PATCH 30/96] [trello.com/c/zPsNhMkM] feat: update coins info generating script --- .../AdmWalletService+DynamicConstants.swift | 8 +++- .../BtcWalletService+DynamicConstants.swift | 8 +++- .../DashWalletService+DynamicConstants.swift | 4 ++ .../DogeWalletService+DynamicConstants.swift | 6 ++- .../EthWalletService+DynamicConstants.swift | 7 ++- .../LskWalletService+DynamicConstants.swift | 4 ++ CommonKit/Scripts/CoinsScript.rb | 46 +++++++++++++------ CommonKit/Scripts/UpdateWalletsScript.sh | 2 +- .../CommonKit/AdamantDynamicResources.swift | 4 +- CommonKit/Sources/CommonKit/Models/Node.swift | 10 ++++ .../CommonKit/Models/ethereumTokensList.swift | 4 +- 11 files changed, 79 insertions(+), 24 deletions(-) diff --git a/Adamant/Modules/Wallets/Adamant/AdmWalletService+DynamicConstants.swift b/Adamant/Modules/Wallets/Adamant/AdmWalletService+DynamicConstants.swift index e40e79e01..a98528bcb 100644 --- a/Adamant/Modules/Wallets/Adamant/AdmWalletService+DynamicConstants.swift +++ b/Adamant/Modules/Wallets/Adamant/AdmWalletService+DynamicConstants.swift @@ -45,16 +45,20 @@ extension AdmWalletService { 0 } + var minNodeVersion: String? { + "0.7.0" + } + static let explorerAddress = "https://explorer.adamant.im/tx/" static var nodes: [Node] { [ Node(url: URL(string: "https://clown.adamant.im")!), Node(url: URL(string: "https://lake.adamant.im")!), -Node(url: URL(string: "https://endless.adamant.im")!), +Node(url: URL(string: "https://endless.adamant.im")!, altUrl: URL(string: "http://149.102.157.15:36666")), Node(url: URL(string: "https://bid.adamant.im")!), Node(url: URL(string: "https://unusual.adamant.im")!), -Node(url: URL(string: "https://debate.adamant.im")!), +Node(url: URL(string: "https://debate.adamant.im")!, altUrl: URL(string: "http://95.216.161.113:36666")), Node(url: URL(string: "http://78.47.205.206:36666")!), Node(url: URL(string: "http://5.161.53.74:36666")!), Node(url: URL(string: "http://184.94.215.92:45555")!), diff --git a/Adamant/Modules/Wallets/Bitcoin/BtcWalletService+DynamicConstants.swift b/Adamant/Modules/Wallets/Bitcoin/BtcWalletService+DynamicConstants.swift index feebf7ad2..9425f8e2f 100644 --- a/Adamant/Modules/Wallets/Bitcoin/BtcWalletService+DynamicConstants.swift +++ b/Adamant/Modules/Wallets/Bitcoin/BtcWalletService+DynamicConstants.swift @@ -53,12 +53,16 @@ extension BtcWalletService { 10 } + var minNodeVersion: String? { + nil + } + static let explorerAddress = "https://explorer.btc.com/btc/transaction/" static var nodes: [Node] { [ - Node(url: URL(string: "https://btcnode1.adamant.im")!), -Node(url: URL(string: "https://btcnode2.adamant.im")!), + Node(url: URL(string: "https://btcnode1.adamant.im")!, altUrl: URL(string: "http://176.9.38.204:44099")), +Node(url: URL(string: "https://btcnode2.adamant.im")!, altUrl: URL(string: "http://176.9.32.126:44099")), ] } diff --git a/Adamant/Modules/Wallets/Dash/DashWalletService+DynamicConstants.swift b/Adamant/Modules/Wallets/Dash/DashWalletService+DynamicConstants.swift index 5ede80f03..964917edc 100644 --- a/Adamant/Modules/Wallets/Dash/DashWalletService+DynamicConstants.swift +++ b/Adamant/Modules/Wallets/Dash/DashWalletService+DynamicConstants.swift @@ -53,6 +53,10 @@ extension DashWalletService { 80 } + var minNodeVersion: String? { + nil + } + static let explorerAddress = "https://dashblockexplorer.com/tx/" static var nodes: [Node] { diff --git a/Adamant/Modules/Wallets/Doge/DogeWalletService+DynamicConstants.swift b/Adamant/Modules/Wallets/Doge/DogeWalletService+DynamicConstants.swift index 9c754f943..95ab74a36 100644 --- a/Adamant/Modules/Wallets/Doge/DogeWalletService+DynamicConstants.swift +++ b/Adamant/Modules/Wallets/Doge/DogeWalletService+DynamicConstants.swift @@ -53,12 +53,16 @@ extension DogeWalletService { 70 } + var minNodeVersion: String? { + nil + } + static let explorerAddress = "https://dogechain.info/tx/" static var nodes: [Node] { [ Node(url: URL(string: "https://dogenode1.adamant.im")!), -Node(url: URL(string: "https://dogenode2.adamant.im")!), +Node(url: URL(string: "https://dogenode2.adamant.im")!, altUrl: URL(string: "http://176.9.32.126:44098")), ] } diff --git a/Adamant/Modules/Wallets/Ethereum/EthWalletService+DynamicConstants.swift b/Adamant/Modules/Wallets/Ethereum/EthWalletService+DynamicConstants.swift index 9abe34fd9..184451ee2 100644 --- a/Adamant/Modules/Wallets/Ethereum/EthWalletService+DynamicConstants.swift +++ b/Adamant/Modules/Wallets/Ethereum/EthWalletService+DynamicConstants.swift @@ -73,11 +73,16 @@ extension EthWalletService { 20 } + var minNodeVersion: String? { + nil + } + static let explorerAddress = "https://etherscan.io/tx/" static var nodes: [Node] { [ - Node(url: URL(string: "https://ethnode1.adamant.im")!), + Node(url: URL(string: "https://ethnode1.adamant.im")!, altUrl: URL(string: "http://95.216.41.106:44099")), +Node(url: URL(string: "https://ethnode2.adamant.im")!, altUrl: URL(string: "http://95.216.114.252:44099")), ] } diff --git a/Adamant/Modules/Wallets/Lisk/LskWalletService+DynamicConstants.swift b/Adamant/Modules/Wallets/Lisk/LskWalletService+DynamicConstants.swift index ed23b215f..8d84376d7 100644 --- a/Adamant/Modules/Wallets/Lisk/LskWalletService+DynamicConstants.swift +++ b/Adamant/Modules/Wallets/Lisk/LskWalletService+DynamicConstants.swift @@ -53,6 +53,10 @@ extension LskWalletService { 60 } + var minNodeVersion: String? { + nil + } + static let explorerAddress = "https://liskscan.com/transaction/" static var nodes: [Node] { diff --git a/CommonKit/Scripts/CoinsScript.rb b/CommonKit/Scripts/CoinsScript.rb index 2b05035c8..c8ca7d70c 100755 --- a/CommonKit/Scripts/CoinsScript.rb +++ b/CommonKit/Scripts/CoinsScript.rb @@ -21,21 +21,34 @@ def writeToSwiftFile(name, json) # Read data from json + fullName = json["name"] + symbol = json["symbol"] + decimals = json["decimals"] + explorerTx = json["explorerTx"] + nodes = "" nodesArray = json["nodes"] if nodesArray != nil nodesArray.each do |node| url = node["url"] - nodes += "Node(url: URL(string: \"#{url}\")!),\n" + altUrl = node["alt_ip"] + if altUrl == nil + nodes += "Node(url: URL(string: \"#{url}\")!),\n" + else + nodes += "Node(url: URL(string: \"#{url}\")!, altUrl: URL(string: \"#{altUrl}\")),\n" + end end end serviceNodes = "" - serviceNodesArray = json["serviceNodes"] - if serviceNodesArray != nil - serviceNodesArray.each do |node| - url = node["url"] - serviceNodes += "Node(url: URL(string: \"#{url}\")!),\n" + services = json["services"] + if services != nil + serviceNodesArray = services["#{symbol.downcase}Service"] + if serviceNodesArray != nil + serviceNodesArray.each do |node| + url = node["url"] + serviceNodes += "Node(url: URL(string: \"#{url}\")!),\n" + end end end @@ -47,10 +60,6 @@ def writeToSwiftFile(name, json) fixedFee = 0.0 end - fullName = json["name"] - symbol = json["symbol"] - decimals = json["decimals"] - explorerTx = json["explorerTx"] consistencyMaxTime = json["txConsistencyMaxTime"] if consistencyMaxTime == nil consistencyMaxTime = 0 @@ -74,6 +83,13 @@ def writeToSwiftFile(name, json) defaultOrdinalLevel = json["defaultOrdinalLevel"] + minNodeVersion = json["minNodeVersion"] + if minNodeVersion == nil + minNodeVersion = "nil" + else + minNodeVersion = "\"#{minNodeVersion}\"" + end + # txFetchInfo txFetchInfo = json["txFetchInfo"] @@ -187,6 +203,10 @@ def writeToSwiftFile(name, json) #{defaultOrdinalLevel} } + var minNodeVersion: String? { + #{minNodeVersion} + } + static let explorerAddress = \"#{explorerTx.sub! '${ID}', ''}\" static var nodes: [Node] { @@ -218,9 +238,9 @@ def writeToSwiftFile(name, json) } }" File.open(Dir.pwd + "/CommonKit/Sources/CommonKit/AdamantDynamicResources.swift", 'w') { |file| file.write(textResources) } - File.open(Dir.pwd + "/Adamant/Wallets/#{name}/#{symbol}WalletService+DynamicConstants.swift", 'w') { |file| file.write(text) } + File.open(Dir.pwd + "/Adamant/Modules/Wallets/#{name}/#{symbol}WalletService+DynamicConstants.swift", 'w') { |file| file.write(text) } else - File.open(Dir.pwd + "/Adamant/Wallets/#{name}/#{symbol}WalletService+DynamicConstants.swift", 'w') { |file| file.write(text) } + File.open(Dir.pwd + "/Adamant/Modules/Wallets/#{name}/#{symbol}WalletService+DynamicConstants.swift", 'w') { |file| file.write(text) } end end @@ -246,4 +266,4 @@ def startUnpack(branch) end -Coins.new.startUnpack("master") #master #dev +Coins.new.startUnpack("dev") #master #dev diff --git a/CommonKit/Scripts/UpdateWalletsScript.sh b/CommonKit/Scripts/UpdateWalletsScript.sh index 76e702e8c..a98aab927 100755 --- a/CommonKit/Scripts/UpdateWalletsScript.sh +++ b/CommonKit/Scripts/UpdateWalletsScript.sh @@ -1,5 +1,5 @@ ROOT="$PWD" -BRANCH_NAME="master" #master #dev +BRANCH_NAME="dev" #master #dev SCRIPTS_DIR="$ROOT/scripts" WALLETS_DIR="$ROOT/scripts/wallets" WALLETS_NAME_DIR="$ROOT/scripts/wallets/adamant-wallets-$BRANCH_NAME/assets/general" diff --git a/CommonKit/Sources/CommonKit/AdamantDynamicResources.swift b/CommonKit/Sources/CommonKit/AdamantDynamicResources.swift index c538d3afc..5dbbbd79b 100644 --- a/CommonKit/Sources/CommonKit/AdamantDynamicResources.swift +++ b/CommonKit/Sources/CommonKit/AdamantDynamicResources.swift @@ -6,10 +6,10 @@ public extension AdamantResources { [ Node(url: URL(string: "https://clown.adamant.im")!), Node(url: URL(string: "https://lake.adamant.im")!), -Node(url: URL(string: "https://endless.adamant.im")!), +Node(url: URL(string: "https://endless.adamant.im")!, altUrl: URL(string: "http://149.102.157.15:36666")), Node(url: URL(string: "https://bid.adamant.im")!), Node(url: URL(string: "https://unusual.adamant.im")!), -Node(url: URL(string: "https://debate.adamant.im")!), +Node(url: URL(string: "https://debate.adamant.im")!, altUrl: URL(string: "http://95.216.161.113:36666")), Node(url: URL(string: "http://78.47.205.206:36666")!), Node(url: URL(string: "http://5.161.53.74:36666")!), Node(url: URL(string: "http://184.94.215.92:45555")!), diff --git a/CommonKit/Sources/CommonKit/Models/Node.swift b/CommonKit/Sources/CommonKit/Models/Node.swift index 23347edfa..41b5e73f8 100644 --- a/CommonKit/Sources/CommonKit/Models/Node.swift +++ b/CommonKit/Sources/CommonKit/Models/Node.swift @@ -77,12 +77,22 @@ final public class Node: Equatable { self.isEnabled = true } + public init(url: URL, altUrl: URL?) { + let schemeRaw = url.scheme ?? "https" + self.scheme = URLScheme(rawValue: schemeRaw) ?? .https + self.host = url.host ?? "" + self.port = url.port + self.isEnabled = true + self.altUrl = altUrl + } + @Atomic public var scheme: URLScheme @Atomic public var host: String @Atomic public var port: Int? @Atomic public var wsPort: Int? @Atomic public var status: Status? @Atomic public var isEnabled: Bool + @Atomic public var altUrl: URL? @Atomic private var _connectionStatus: ConnectionStatus? diff --git a/CommonKit/Sources/CommonKit/Models/ethereumTokensList.swift b/CommonKit/Sources/CommonKit/Models/ethereumTokensList.swift index 2961e7f60..425ad1367 100644 --- a/CommonKit/Sources/CommonKit/Models/ethereumTokensList.swift +++ b/CommonKit/Sources/CommonKit/Models/ethereumTokensList.swift @@ -152,8 +152,8 @@ contractAddress: "0x00c83aecc790e8a4453e5dd3b0b4b3680501a7a7", decimals: 18, naturalUnits: 18, - defaultVisibility: false, - defaultOrdinalLevel: nil, + defaultVisibility: true, + defaultOrdinalLevel: 94, reliabilityGasPricePercent: 10, reliabilityGasLimitPercent: 10, defaultGasPriceGwei: 30, From 21184073e1da4e86712c158518fa2082a8b03f1b Mon Sep 17 00:00:00 2001 From: StanislavDevIOS Date: Tue, 24 Oct 2023 16:05:15 +0300 Subject: [PATCH 31/96] [trello.com/c/FkbZOCIe] code refactoring --- Adamant.xcodeproj/project.pbxproj | 14 ++++- ...ansferTransaction+CoreDataProperties.swift | 22 +++++++ Adamant/Models/SimpleTransactionDetails.swift | 36 ++++++++++- .../AdmTransactionDetailsViewController.swift | 28 ++++----- .../AdmTransactionsViewController.swift | 18 ++---- .../Adamant/AdmTransferViewController.swift | 4 +- .../Adamant/AdmWalletService+Send.swift | 2 +- .../Wallets/Adamant/AdmWalletService.swift | 2 +- .../BtcTransactionsViewController.swift | 6 +- .../Wallets/Bitcoin/BtcWalletService.swift | 2 +- .../Dash/DashTransactionsViewController.swift | 10 ++-- .../Wallets/Dash/DashWalletService.swift | 5 +- .../Doge/DogeTransactionsViewController.swift | 8 +-- .../Wallets/Doge/DogeWalletService.swift | 2 +- .../ERC20TransactionsViewController.swift | 8 +-- .../Wallets/ERC20/ERC20WalletService.swift | 2 +- .../EthTransactionsViewController.swift | 5 +- .../Wallets/Ethereum/EthWalletService.swift | 3 +- .../Lisk/LskTransactionsViewController.swift | 5 +- ...e+RichMessageProviderWithStatusCheck.swift | 13 +--- .../Wallets/Lisk/LskWalletService.swift | 2 +- .../Models/AdamantTransactionDetails.swift | 59 +++++++++++++++++++ .../{ => Models}/TransactionDetails.swift | 0 Adamant/Modules/Wallets/WalletService.swift | 2 +- .../DataProviders/TransfersProvider.swift | 2 +- .../AdamantChatTransactionService.swift | 1 + .../AdamantTransfersProvider.swift | 8 +-- 27 files changed, 186 insertions(+), 83 deletions(-) create mode 100644 Adamant/Modules/Wallets/Models/AdamantTransactionDetails.swift rename Adamant/Modules/Wallets/{ => Models}/TransactionDetails.swift (100%) diff --git a/Adamant.xcodeproj/project.pbxproj b/Adamant.xcodeproj/project.pbxproj index 0600d496a..cca91a0e1 100644 --- a/Adamant.xcodeproj/project.pbxproj +++ b/Adamant.xcodeproj/project.pbxproj @@ -7,6 +7,7 @@ objects = { /* Begin PBXBuildFile section */ + 3A20D93B2AE7F316005475A6 /* AdamantTransactionDetails.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3A20D93A2AE7F316005475A6 /* AdamantTransactionDetails.swift */; }; 3A2F55F92AC6F308000A3F26 /* CoinTransaction+CoreDataClass.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3A2F55F72AC6F308000A3F26 /* CoinTransaction+CoreDataClass.swift */; }; 3A2F55FA2AC6F308000A3F26 /* CoinTransaction+CoreDataProperties.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3A2F55F82AC6F308000A3F26 /* CoinTransaction+CoreDataProperties.swift */; }; 3A2F55FC2AC6F885000A3F26 /* CoinStorage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3A2F55FB2AC6F885000A3F26 /* CoinStorage.swift */; }; @@ -595,6 +596,7 @@ /* Begin PBXFileReference section */ 33975C0D891698AA7E74EBCC /* Pods_Adamant.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Adamant.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 36AB8CE9537B3B873972548B /* Pods_AdmCore.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_AdmCore.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + 3A20D93A2AE7F316005475A6 /* AdamantTransactionDetails.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AdamantTransactionDetails.swift; sourceTree = ""; }; 3A2F55F72AC6F308000A3F26 /* CoinTransaction+CoreDataClass.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "CoinTransaction+CoreDataClass.swift"; sourceTree = ""; }; 3A2F55F82AC6F308000A3F26 /* CoinTransaction+CoreDataProperties.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "CoinTransaction+CoreDataProperties.swift"; sourceTree = ""; }; 3A2F55FB2AC6F885000A3F26 /* CoinStorage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CoinStorage.swift; sourceTree = ""; }; @@ -1185,6 +1187,15 @@ path = Pods; sourceTree = ""; }; + 3A20D9392AE7F305005475A6 /* Models */ = { + isa = PBXGroup; + children = ( + 64BD2B7620E2820300E2CD36 /* TransactionDetails.swift */, + 3A20D93A2AE7F316005475A6 /* AdamantTransactionDetails.swift */, + ); + path = Models; + sourceTree = ""; + }; 3A41938D2A580C3B006A6B22 /* RichTransactionReactService */ = { isa = PBXGroup; children = ( @@ -1935,6 +1946,7 @@ E940086C2114A8FD00CD2D67 /* Wallets */ = { isa = PBXGroup; children = ( + 3A20D9392AE7F305005475A6 /* Models */, 3A770E4A2AE14EFD0008D98F /* Mappers */, 93A18C872AAEAE5600D0AB98 /* DI */, A50A41022822F8CE006BDFE1 /* Bitcoin */, @@ -1953,7 +1965,6 @@ E926E02D213EAABF005E536B /* TransferViewControllerBase+Alert.swift */, E9E7CDC12003F5A400DFC4DB /* TransactionsListViewControllerBase.swift */, E94E7B0B205D5E4A0042B639 /* TransactionsListViewControllerBase.xib */, - 64BD2B7620E2820300E2CD36 /* TransactionDetails.swift */, 64FA53D020E24941006783C9 /* TransactionDetailsViewControllerBase.swift */, E9E7CDC52003F6D200DFC4DB /* TransactionTableViewCell.swift */, E9E7CDC62003F6D200DFC4DB /* TransactionTableViewCell.xib */, @@ -3021,6 +3032,7 @@ 6416B1A121AD7D93006089AC /* LskTransferViewController.swift in Sources */, A5E0422B282AB18B0076CD13 /* BtcUnspentTransactionResponse.swift in Sources */, E972206B201F44CA004F2AAD /* TransfersProvider.swift in Sources */, + 3A20D93B2AE7F316005475A6 /* AdamantTransactionDetails.swift in Sources */, 93294B962AAD320B00911109 /* ScreensFactory.swift in Sources */, E9FEECA421413659007DD7C8 /* RichMessageProvider.swift in Sources */, 3A9015A72A614A62002A2464 /* AdamantEmojiService.swift in Sources */, diff --git a/Adamant/Models/CoreData/TransferTransaction+CoreDataProperties.swift b/Adamant/Models/CoreData/TransferTransaction+CoreDataProperties.swift index 5cc72637b..bbc9cfc2f 100644 --- a/Adamant/Models/CoreData/TransferTransaction+CoreDataProperties.swift +++ b/Adamant/Models/CoreData/TransferTransaction+CoreDataProperties.swift @@ -41,3 +41,25 @@ extension TransferTransaction { } } } + +extension TransferTransaction: AdamantTransactionDetails { + var partnerName: String? { + partner?.name + } + + var showToChat: Bool? { + guard let partner = partner as? CoreDataAccount, + let chatroom = partner.chatroom, + !chatroom.isReadonly + else { + return false + } + + return true + } + + var chatRoom: Chatroom? { + let partner = partner as? CoreDataAccount + return partner?.chatroom + } +} diff --git a/Adamant/Models/SimpleTransactionDetails.swift b/Adamant/Models/SimpleTransactionDetails.swift index 01a054017..7b1be10d0 100644 --- a/Adamant/Models/SimpleTransactionDetails.swift +++ b/Adamant/Models/SimpleTransactionDetails.swift @@ -8,7 +8,7 @@ import Foundation -struct SimpleTransactionDetails: TransactionDetails { +struct SimpleTransactionDetails: AdamantTransactionDetails { var defaultCurrencySymbol: String? var txId: String @@ -40,7 +40,20 @@ struct SimpleTransactionDetails: TransactionDetails { var showToChat: Bool? var chatRoom: Chatroom? - init(defaultCurrencySymbol: String? = nil, txId: String, senderAddress: String, recipientAddress: String, dateValue: Date? = nil, amountValue: Decimal? = nil, feeValue: Decimal? = nil, confirmationsValue: String? = nil, blockValue: String? = nil, isOutgoing: Bool, transactionStatus: TransactionStatus? = nil, partnerName: String? = nil) { + init( + defaultCurrencySymbol: String? = nil, + txId: String, + senderAddress: String, + recipientAddress: String, + dateValue: Date? = nil, + amountValue: Decimal? = nil, + feeValue: Decimal? = nil, + confirmationsValue: String? = nil, + blockValue: String? = nil, + isOutgoing: Bool, + transactionStatus: TransactionStatus? = nil, + partnerName: String? = nil + ) { self.defaultCurrencySymbol = defaultCurrencySymbol self.txId = txId self.senderAddress = senderAddress @@ -68,4 +81,23 @@ struct SimpleTransactionDetails: TransactionDetails { self.isOutgoing = transaction.isOutgoing self.transactionStatus = transaction.transactionStatus } + + init(_ transaction: TransferTransaction) { + self.defaultCurrencySymbol = transaction.defaultCurrencySymbol + self.txId = transaction.txId + self.senderAddress = transaction.senderAddress + self.recipientAddress = transaction.recipientAddress + self.dateValue = transaction.dateValue + self.amountValue = transaction.amountValue + self.feeValue = transaction.feeValue + self.confirmationsValue = transaction.confirmationsValue + self.blockValue = transaction.blockValue + self.isOutgoing = transaction.isOutgoing + self.transactionStatus = transaction.transactionStatus + self.showToChat = transaction.showToChat + self.chatRoom = transaction.chatRoom + self.partnerName = transaction.partnerName + self.comment = transaction.comment + self.transactionStatus = transaction.transactionStatus + } } diff --git a/Adamant/Modules/Wallets/Adamant/AdmTransactionDetailsViewController.swift b/Adamant/Modules/Wallets/Adamant/AdmTransactionDetailsViewController.swift index e4f4f900e..9e59fd60f 100644 --- a/Adamant/Modules/Wallets/Adamant/AdmTransactionDetailsViewController.swift +++ b/Adamant/Modules/Wallets/Adamant/AdmTransactionDetailsViewController.swift @@ -31,6 +31,16 @@ final class AdmTransactionDetailsViewController: TransactionDetailsViewControlle return control }() + override var transaction: TransactionDetails? { + get { super.transaction } + set { assertionFailure("Use adamant transaction") } + } + + var adamantTransaction: AdamantTransactionDetails? { + get { super.transaction as? AdamantTransactionDetails } + set { super.transaction = newValue } + } + // MARK: - Lifecycle init( @@ -62,8 +72,7 @@ final class AdmTransactionDetailsViewController: TransactionDetailsViewControlle super.viewDidLoad() if showToChat, - let trs = transaction as? SimpleTransactionDetails, - trs.chatRoom != nil, + adamantTransaction?.chatRoom != nil, let section = form.sectionBy(tag: Sections.actions.tag) { let chatLabel = String.adamant.transactionList.toChat @@ -103,18 +112,7 @@ final class AdmTransactionDetailsViewController: TransactionDetailsViewControlle } func goToChat() { - var chatRoom: Chatroom? - - if let transfer = transaction as? SimpleTransactionDetails { - chatRoom = transfer.chatRoom - } - - if let transfer = transaction as? TransferTransaction { - let partner = transfer.partner as? CoreDataAccount - chatRoom = partner?.chatroom - } - - guard let chatroom = chatRoom else { + guard let chatroom = adamantTransaction?.chatRoom else { dialogService.showError(withMessage: "AdmTransactionDetailsViewController: Failed to get chatroom for transaction.", supportEmail: true, error: nil) return } @@ -150,7 +148,7 @@ final class AdmTransactionDetailsViewController: TransactionDetailsViewControlle do { try await transfersProvider.refreshTransfer(id: id) - transaction = await transfersProvider.getTransfer(id: id) + adamantTransaction = await transfersProvider.getTransfer(id: id) refreshControl.endRefreshing() tableView.reloadData() } catch { diff --git a/Adamant/Modules/Wallets/Adamant/AdmTransactionsViewController.swift b/Adamant/Modules/Wallets/Adamant/AdmTransactionsViewController.swift index a4a6f4b0b..008fd7a6d 100644 --- a/Adamant/Modules/Wallets/Adamant/AdmTransactionsViewController.swift +++ b/Adamant/Modules/Wallets/Adamant/AdmTransactionsViewController.swift @@ -204,12 +204,6 @@ final class AdmTransactionsViewController: TransactionsListViewControllerBase { var simple = SimpleTransactionDetails(transaction) simple.partnerName = getPartnerName(for: partnerId) - simple.showToChat = toShowChat(for: transaction) - simple.comment = transaction.comment - - let partner = transaction.partner as? CoreDataAccount - let chatroom = partner?.chatroom - simple.chatRoom = chatroom return simple } @@ -227,10 +221,10 @@ final class AdmTransactionsViewController: TransactionsListViewControllerBase { // MARK: - UITableView func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { - let transaction = transactions[indexPath.row] + guard let transaction = transactions[safe: indexPath.row] else { return } let controller = screensFactory.makeAdmTransactionDetails() - controller.transaction = transaction + controller.adamantTransaction = transaction controller.comment = transaction.comment controller.showToChat = transaction.showToChat ?? false @@ -256,10 +250,9 @@ final class AdmTransactionsViewController: TransactionsListViewControllerBase { func tableView( _ tableView: UITableView, trailingSwipeActionsConfigurationForRowAt indexPath: IndexPath - ) -> UISwipeActionsConfiguration? { - let transaction = transactions[indexPath.row] - - guard transaction.showToChat == true, + ) -> UISwipeActionsConfiguration? { + guard let transaction = transactions[safe: indexPath.row], + transaction.showToChat == true, let chatroom = transaction.chatRoom else { return nil @@ -329,7 +322,6 @@ private extension AdmTransactionsViewController { $0.txId == transaction.txId }) else { return } - var transactions: [SimpleTransactionDetails] = self.transactions transactions[index] = self.getTransactionDetails(by: transaction) self.update(transactions) diff --git a/Adamant/Modules/Wallets/Adamant/AdmTransferViewController.swift b/Adamant/Modules/Wallets/Adamant/AdmTransferViewController.swift index 5b9f0ea87..c2fe5999c 100644 --- a/Adamant/Modules/Wallets/Adamant/AdmTransferViewController.swift +++ b/Adamant/Modules/Wallets/Adamant/AdmTransferViewController.swift @@ -129,13 +129,13 @@ final class AdmTransferViewController: TransferViewControllerBase { } private func openDetailVC( - result: TransactionDetails, + result: AdamantTransactionDetails, vc: AdmTransferViewController, recipient: String, comments: String ) { let detailsVC = screensFactory.makeAdmTransactionDetails() - detailsVC.transaction = result + detailsVC.adamantTransaction = result if comments.count > 0 { detailsVC.comment = comments diff --git a/Adamant/Modules/Wallets/Adamant/AdmWalletService+Send.swift b/Adamant/Modules/Wallets/Adamant/AdmWalletService+Send.swift index db097a38d..dc5c2948b 100644 --- a/Adamant/Modules/Wallets/Adamant/AdmWalletService+Send.swift +++ b/Adamant/Modules/Wallets/Adamant/AdmWalletService+Send.swift @@ -18,7 +18,7 @@ extension AdmWalletService: WalletServiceSimpleSend { amount: Decimal, comments: String, replyToMessageId: String? - ) async throws -> TransactionDetails { + ) async throws -> AdamantTransactionDetails { do { let transaction = try await transfersProvider.transferFunds( toAddress: recipient, diff --git a/Adamant/Modules/Wallets/Adamant/AdmWalletService.swift b/Adamant/Modules/Wallets/Adamant/AdmWalletService.swift index 0427b2c2e..8df6f8ee3 100644 --- a/Adamant/Modules/Wallets/Adamant/AdmWalletService.swift +++ b/Adamant/Modules/Wallets/Adamant/AdmWalletService.swift @@ -86,7 +86,7 @@ final class AdmWalletService: NSObject, WalletService { $hasMoreOldTransactions.eraseToAnyPublisher() } - lazy var coinStorage: CoinStorageService = AdamantCoinStorageService( + private(set) lazy var coinStorage: CoinStorageService = AdamantCoinStorageService( coinId: tokenUnicID, coreDataStack: coreDataStack, blockchainType: richMessageType diff --git a/Adamant/Modules/Wallets/Bitcoin/BtcTransactionsViewController.swift b/Adamant/Modules/Wallets/Bitcoin/BtcTransactionsViewController.swift index 81e45d927..41875eebb 100644 --- a/Adamant/Modules/Wallets/Bitcoin/BtcTransactionsViewController.swift +++ b/Adamant/Modules/Wallets/Bitcoin/BtcTransactionsViewController.swift @@ -21,9 +21,9 @@ final class BtcTransactionsViewController: TransactionsListViewControllerBase { func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { tableView.deselectRow(at: indexPath, animated: true) - guard let address = btcWalletService.wallet?.address else { return } - - let transaction = transactions[indexPath.row] + guard let address = btcWalletService.wallet?.address, + let transaction = transactions[safe: indexPath.row] + else { return } let controller = screensFactory.makeDetailsVC(service: btcWalletService) diff --git a/Adamant/Modules/Wallets/Bitcoin/BtcWalletService.swift b/Adamant/Modules/Wallets/Bitcoin/BtcWalletService.swift index 6c19a8baa..b935051fb 100644 --- a/Adamant/Modules/Wallets/Bitcoin/BtcWalletService.swift +++ b/Adamant/Modules/Wallets/Bitcoin/BtcWalletService.swift @@ -160,7 +160,7 @@ final class BtcWalletService: WalletService { $hasMoreOldTransactions.eraseToAnyPublisher() } - lazy var coinStorage: CoinStorageService = AdamantCoinStorageService( + private(set) lazy var coinStorage: CoinStorageService = AdamantCoinStorageService( coinId: tokenUnicID, coreDataStack: coreDataStack, blockchainType: richMessageType diff --git a/Adamant/Modules/Wallets/Dash/DashTransactionsViewController.swift b/Adamant/Modules/Wallets/Dash/DashTransactionsViewController.swift index 089393a54..dcc0825ce 100644 --- a/Adamant/Modules/Wallets/Dash/DashTransactionsViewController.swift +++ b/Adamant/Modules/Wallets/Dash/DashTransactionsViewController.swift @@ -18,13 +18,11 @@ final class DashTransactionsViewController: TransactionsListViewControllerBase { // MARK: - UITableView func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { - guard let address = walletService.wallet?.address else { - return - } + guard let address = walletService.wallet?.address, + let transaction = transactions[safe: indexPath.row] + else { return } - let controller = screensFactory.makeDetailsVC(service: walletService) - let transaction = transactions[indexPath.row] - + let controller = screensFactory.makeDetailsVC(service: walletService) controller.transaction = transaction if transaction.senderAddress.caseInsensitiveCompare(address) == .orderedSame { diff --git a/Adamant/Modules/Wallets/Dash/DashWalletService.swift b/Adamant/Modules/Wallets/Dash/DashWalletService.swift index b6a7c65c4..21342ac30 100644 --- a/Adamant/Modules/Wallets/Dash/DashWalletService.swift +++ b/Adamant/Modules/Wallets/Dash/DashWalletService.swift @@ -137,7 +137,7 @@ final class DashWalletService: WalletService { $hasMoreOldTransactions.eraseToAnyPublisher() } - lazy var coinStorage: CoinStorageService = AdamantCoinStorageService( + private(set) lazy var coinStorage: CoinStorageService = AdamantCoinStorageService( coinId: tokenUnicID, coreDataStack: coreDataStack, blockchainType: richMessageType @@ -203,7 +203,6 @@ final class DashWalletService: WalletService { func addTransactionObserver() { coinStorage.transactionsPublisher - // .removeDuplicates() .sink { [weak self] transactions in self?.historyTransactions = transactions } @@ -448,8 +447,6 @@ extension DashWalletService { ? limit : availableToLoad - //let ids = Array(allTransactionsIds[offset..<(offset + maxPerRequest)]) - let startIndex = allTransactionsIds.index(allTransactionsIds.startIndex, offsetBy: offset) let endIndex = allTransactionsIds.index(startIndex, offsetBy: maxPerRequest) let ids = Array(allTransactionsIds[startIndex..= min } - return true + return transaction.amountValue == lskTransaction.amountValue } } diff --git a/Adamant/Modules/Wallets/Lisk/LskWalletService.swift b/Adamant/Modules/Wallets/Lisk/LskWalletService.swift index c84927633..a22ad7411 100644 --- a/Adamant/Modules/Wallets/Lisk/LskWalletService.swift +++ b/Adamant/Modules/Wallets/Lisk/LskWalletService.swift @@ -107,7 +107,7 @@ final class LskWalletService: WalletService { $hasMoreOldTransactions.eraseToAnyPublisher() } - lazy var coinStorage: CoinStorageService = AdamantCoinStorageService( + private(set) lazy var coinStorage: CoinStorageService = AdamantCoinStorageService( coinId: tokenUnicID, coreDataStack: coreDataStack, blockchainType: richMessageType diff --git a/Adamant/Modules/Wallets/Models/AdamantTransactionDetails.swift b/Adamant/Modules/Wallets/Models/AdamantTransactionDetails.swift new file mode 100644 index 000000000..6ee5b401a --- /dev/null +++ b/Adamant/Modules/Wallets/Models/AdamantTransactionDetails.swift @@ -0,0 +1,59 @@ +// +// AdamantTransactionDetails.swift +// Adamant +// +// Created by Stanislav Jelezoglo on 24.10.2023. +// Copyright © 2023 Adamant. All rights reserved. +// + +import Foundation + +protocol AdamantTransactionDetails: TransactionDetails { + /// The identifier of the transaction. + var txId: String { get } + + /// The sender of the transaction. + var senderAddress: String { get } + + /// The reciver of the transaction. + var recipientAddress: String { get } + + /// The date the transaction was sent. + var dateValue: Date? { get } + + /// The amount of currency that was sent. + var amountValue: Decimal? { get } + + /// The amount of fee that taken for transaction process. + var feeValue: Decimal? { get } + + /// The confirmations of the transaction. + var confirmationsValue: String? { get } + + var blockHeight: UInt64? { get } + + /// The block of the transaction. + var blockValue: String? { get } + + var isOutgoing: Bool { get } + + var transactionStatus: TransactionStatus? { get } + + var defaultCurrencySymbol: String? { get } + + var feeCurrencySymbol: String? { get } + + func summary( + with url: String?, + currentValue: String?, + valueAtTimeTxn: String? + ) -> String + + var partnerName: String? { get } + + var comment: String? { get } + + var showToChat: Bool? { get } + + var chatRoom: Chatroom? { get } +} diff --git a/Adamant/Modules/Wallets/TransactionDetails.swift b/Adamant/Modules/Wallets/Models/TransactionDetails.swift similarity index 100% rename from Adamant/Modules/Wallets/TransactionDetails.swift rename to Adamant/Modules/Wallets/Models/TransactionDetails.swift diff --git a/Adamant/Modules/Wallets/WalletService.swift b/Adamant/Modules/Wallets/WalletService.swift index d71d088d1..6f3a07d62 100644 --- a/Adamant/Modules/Wallets/WalletService.swift +++ b/Adamant/Modules/Wallets/WalletService.swift @@ -315,7 +315,7 @@ protocol WalletServiceSimpleSend: WalletServiceWithSend { amount: Decimal, comments: String, replyToMessageId: String? - ) async throws -> TransactionDetails + ) async throws -> AdamantTransactionDetails } protocol WalletServiceTwoStepSend: WalletServiceWithSend { diff --git a/Adamant/ServiceProtocols/DataProviders/TransfersProvider.swift b/Adamant/ServiceProtocols/DataProviders/TransfersProvider.swift index 905198748..70f4a5c70 100644 --- a/Adamant/ServiceProtocols/DataProviders/TransfersProvider.swift +++ b/Adamant/ServiceProtocols/DataProviders/TransfersProvider.swift @@ -158,7 +158,7 @@ protocol TransfersProvider: DataProvider, Actor { amount: Decimal, comment: String?, replyToMessageId: String? - ) async throws -> TransactionDetails + ) async throws -> AdamantTransactionDetails // MARK: - Transactions func getTransfer(id: String) -> TransferTransaction? diff --git a/Adamant/Services/DataProviders/AdamantChatTransactionService.swift b/Adamant/Services/DataProviders/AdamantChatTransactionService.swift index a41a2a920..b6a4f7d1a 100644 --- a/Adamant/Services/DataProviders/AdamantChatTransactionService.swift +++ b/Adamant/Services/DataProviders/AdamantChatTransactionService.swift @@ -234,6 +234,7 @@ actor AdamantChatTransactionService: ChatTransactionService { let transfer: TransferTransaction if let trs = getTransfer(id: String(transaction.id), context: context) { transfer = trs + // TODO: Fix it later. (Server side) if transfer.confirmations < transaction.confirmations { transfer.confirmations = transaction.confirmations } diff --git a/Adamant/Services/DataProviders/AdamantTransfersProvider.swift b/Adamant/Services/DataProviders/AdamantTransfersProvider.swift index dd5b4872b..079ad8c5c 100644 --- a/Adamant/Services/DataProviders/AdamantTransfersProvider.swift +++ b/Adamant/Services/DataProviders/AdamantTransfersProvider.swift @@ -378,7 +378,7 @@ extension AdamantTransfersProvider { amount: Decimal, comment: String?, replyToMessageId: String? - ) async throws -> TransactionDetails { + ) async throws -> AdamantTransactionDetails { let comment = comment ?? "" if !comment.isEmpty || replyToMessageId != nil { return try await transferFundsInternal( @@ -400,7 +400,7 @@ extension AdamantTransfersProvider { amount: Decimal, comment: String, replyToMessageId: String? - ) async throws -> TransactionDetails { + ) async throws -> AdamantTransactionDetails { // MARK: 0. Prepare guard let loggedAccount = accountService.account, let keypair = accountService.keypair else { throw TransfersProviderError.notLogged @@ -555,7 +555,7 @@ extension AdamantTransfersProvider { private func transferFundsInternal( toAddress recipient: String, amount: Decimal - ) async throws -> TransactionDetails { + ) async throws -> AdamantTransactionDetails { // MARK: 0. Prepare guard let loggedAccount = accountService.account, let keypair = accountService.keypair else { throw TransfersProviderError.notLogged @@ -686,7 +686,7 @@ extension AdamantTransfersProvider { ) } - if let trs = self.stack.container.viewContext.object(with: transaction.objectID) as? TransactionDetails { + if let trs = self.stack.container.viewContext.object(with: transaction.objectID) as? AdamantTransactionDetails { return trs } else { throw TransfersProviderError.internalError( From 09e97fd724f044560ed9c47c4b8907538ecd9621 Mon Sep 17 00:00:00 2001 From: StanislavDevIOS Date: Thu, 26 Oct 2023 17:04:31 +0300 Subject: [PATCH 32/96] [trello.com/c/nV88oxAa] fix: offset for "Balance" title row --- .../Wallets/BalanceTableViewCell.swift | 28 ++++++++++++++++- .../Modules/Wallets/BalanceTableViewCell.xib | 30 ++++++------------- 2 files changed, 36 insertions(+), 22 deletions(-) diff --git a/Adamant/Modules/Wallets/BalanceTableViewCell.swift b/Adamant/Modules/Wallets/BalanceTableViewCell.swift index d0d70b87b..81c77bfe5 100644 --- a/Adamant/Modules/Wallets/BalanceTableViewCell.swift +++ b/Adamant/Modules/Wallets/BalanceTableViewCell.swift @@ -9,6 +9,7 @@ import UIKit import Eureka import FreakingSimpleRoundImageView +import SnapKit // MARK: - Value struct public struct BalanceRowValue: Equatable { @@ -25,11 +26,18 @@ public final class BalanceTableViewCell: Cell, CellType { static let fullHeight: CGFloat = 58.0 // MARK: IBOutlets - @IBOutlet var titleLabel: UILabel! @IBOutlet var cryptoBalanceLabel: UILabel! @IBOutlet var fiatBalanceLabel: UILabel! @IBOutlet var alertLabel: RoundedLabel! + lazy var titleLabel: UILabel = { + let label = UILabel() + label.font = .systemFont(ofSize: 17) + label.text = "Balance" + label.textColor = .black + return label + }() + // MARK: Properties var cryptoValue: String? { get { @@ -68,6 +76,24 @@ public final class BalanceTableViewCell: Cell, CellType { } } + public required init?(coder aDecoder: NSCoder) { + super.init(coder: aDecoder) + setupView() + } + + required init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) { + super.init(style: style, reuseIdentifier: reuseIdentifier) + setupView() + } + + private func setupView() { + addSubview(titleLabel) + titleLabel.snp.makeConstraints { make in + make.leading.equalTo(layoutMarginsGuide) + make.centerY.equalToSuperview() + } + } + // MARK: Update public override func update() { super.update() diff --git a/Adamant/Modules/Wallets/BalanceTableViewCell.xib b/Adamant/Modules/Wallets/BalanceTableViewCell.xib index b1eca4269..9d8abe2da 100644 --- a/Adamant/Modules/Wallets/BalanceTableViewCell.xib +++ b/Adamant/Modules/Wallets/BalanceTableViewCell.xib @@ -1,37 +1,30 @@ - - - - + + - + + - + - + - - + - - - - - + @@ -64,7 +53,6 @@ - From 805b48951c8d25599965a5415e2ecb98f15f1526 Mon Sep 17 00:00:00 2001 From: StanislavDevIOS Date: Thu, 26 Oct 2023 18:25:56 +0300 Subject: [PATCH 33/96] [trello.com/c/AfIcNG0l] fix: 'isOutgoing' value for erc20 --- Adamant/Modules/Wallets/ERC20/ERC20WalletService.swift | 2 +- Adamant/Modules/Wallets/Ethereum/EthWalletService.swift | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Adamant/Modules/Wallets/ERC20/ERC20WalletService.swift b/Adamant/Modules/Wallets/ERC20/ERC20WalletService.swift index a5258d795..8519d4fa3 100644 --- a/Adamant/Modules/Wallets/ERC20/ERC20WalletService.swift +++ b/Adamant/Modules/Wallets/ERC20/ERC20WalletService.swift @@ -673,7 +673,7 @@ extension ERC20WalletService { } let newTrs = trs.map { transaction in - let isOutgoing: Bool = transaction.to != address + let isOutgoing: Bool = transaction.from == address var exponent = EthWalletService.currencyExponent if let naturalUnits = token?.naturalUnits { diff --git a/Adamant/Modules/Wallets/Ethereum/EthWalletService.swift b/Adamant/Modules/Wallets/Ethereum/EthWalletService.swift index 48916de73..473ff3674 100644 --- a/Adamant/Modules/Wallets/Ethereum/EthWalletService.swift +++ b/Adamant/Modules/Wallets/Ethereum/EthWalletService.swift @@ -799,7 +799,7 @@ extension EthWalletService { } let newTrs = trs.map { transaction in - let isOutgoing: Bool = transaction.to != address + let isOutgoing: Bool = transaction.from == address return SimpleTransactionDetails( txId: transaction.hash, senderAddress: transaction.from, From bd78d8b9b98a416f0b7e6f0b4473e5a2a448a855 Mon Sep 17 00:00:00 2001 From: StanislavDevIOS Date: Thu, 26 Oct 2023 18:37:31 +0300 Subject: [PATCH 34/96] [trello.com/c/AfIcNG0l] fix: status in transactions history --- Adamant/Modules/Wallets/TransactionTableViewCell.swift | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/Adamant/Modules/Wallets/TransactionTableViewCell.swift b/Adamant/Modules/Wallets/TransactionTableViewCell.swift index 3c528071c..4ea352f35 100644 --- a/Adamant/Modules/Wallets/TransactionTableViewCell.swift +++ b/Adamant/Modules/Wallets/TransactionTableViewCell.swift @@ -112,8 +112,10 @@ final class TransactionTableViewCell: UITableViewCell { dateLabel.text = TransactionDetailsViewControllerBase.awaitingValueString case .failed: dateLabel.text = TransactionStatus.failed.localized - default: + case .pending: dateLabel.text = TransactionStatus.pending.localized + default: + dateLabel.text = TransactionDetailsViewControllerBase.awaitingValueString } if let partnerName = transaction.partnerName { @@ -143,9 +145,9 @@ private extension TransactionStatus { switch self { case .failed: return .adamant.danger - case .noNetwork, .noNetworkFinal, .pending, .registered: + case .pending: return .adamant.alert - case .success, .inconsistent, .notInitiated: + default: return .adamant.secondary } } From 6dc5ca094c15a7da4c0aa38f12c72e76b6ab0999 Mon Sep 17 00:00:00 2001 From: StanislavDevIOS Date: Fri, 27 Oct 2023 14:24:54 +0300 Subject: [PATCH 35/96] [trello.com/c/AfIcNG0l] fix: cells sometimes not update. Removed HashableIDWrapper --- Adamant/Models/SimpleTransactionDetails.swift | 2 +- .../Wallets/TransactionsListViewControllerBase.swift | 10 +++++----- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/Adamant/Models/SimpleTransactionDetails.swift b/Adamant/Models/SimpleTransactionDetails.swift index 7b1be10d0..7ab85b879 100644 --- a/Adamant/Models/SimpleTransactionDetails.swift +++ b/Adamant/Models/SimpleTransactionDetails.swift @@ -8,7 +8,7 @@ import Foundation -struct SimpleTransactionDetails: AdamantTransactionDetails { +struct SimpleTransactionDetails: AdamantTransactionDetails, Hashable { var defaultCurrencySymbol: String? var txId: String diff --git a/Adamant/Modules/Wallets/TransactionsListViewControllerBase.swift b/Adamant/Modules/Wallets/TransactionsListViewControllerBase.swift index 13de1e292..ad6cf10f3 100644 --- a/Adamant/Modules/Wallets/TransactionsListViewControllerBase.swift +++ b/Adamant/Modules/Wallets/TransactionsListViewControllerBase.swift @@ -22,7 +22,7 @@ extension String.adamant { } } -private typealias TransactionsDiffableDataSource = UITableViewDiffableDataSource> +private typealias TransactionsDiffableDataSource = UITableViewDiffableDataSource // Extensions for a generic classes is limited, so delegates implemented right in class declaration class TransactionsListViewControllerBase: UIViewController { @@ -160,8 +160,8 @@ class TransactionsListViewControllerBase: UIViewController { by: { ($0.dateValue ?? Date()) > ($1.dateValue ?? Date()) } ) - let list = self.transactions.wrappedByHashableId() - var snapshot = NSDiffableDataSourceSnapshot>() + let list = self.transactions + var snapshot = NSDiffableDataSourceSnapshot() snapshot.appendSections([.zero]) snapshot.appendItems(list) snapshot.reconfigureItems(list) @@ -246,7 +246,7 @@ class TransactionsListViewControllerBase: UIViewController { private func makeCell( tableView: UITableView, indexPath: IndexPath, - model: HashableIDWrapper + model: SimpleTransactionDetails ) -> UITableViewCell { let cell = tableView.dequeueReusableCell(withIdentifier: cellIdentifierCompact, for: indexPath) as! TransactionTableViewCell @@ -256,7 +256,7 @@ class TransactionsListViewControllerBase: UIViewController { : UITableView.defaultTransactionsSeparatorInset cell.currencySymbol = currencySymbol - cell.transaction = model.value + cell.transaction = model return cell } From 59a609eebdf165947b0e28b6437ac5d9f5a7ce1d Mon Sep 17 00:00:00 2001 From: StanislavDevIOS Date: Fri, 27 Oct 2023 15:12:18 +0300 Subject: [PATCH 36/96] [trello.com/c/AfIcNG0l] fix: transaction status in chat --- .../RichMessageTransaction+CoreDataClass.swift | 14 ++++---------- .../DataProviders/AdamantChatsProvider.swift | 1 + 2 files changed, 5 insertions(+), 10 deletions(-) diff --git a/Adamant/Models/CoreData/RichMessageTransaction+CoreDataClass.swift b/Adamant/Models/CoreData/RichMessageTransaction+CoreDataClass.swift index 106987796..63d623a5a 100644 --- a/Adamant/Models/CoreData/RichMessageTransaction+CoreDataClass.swift +++ b/Adamant/Models/CoreData/RichMessageTransaction+CoreDataClass.swift @@ -21,18 +21,12 @@ public class RichMessageTransaction: ChatTransaction { override var transactionStatus: TransactionStatus? { get { - if let raw = transferStatusRaw { - return TransactionStatus(rawValue: raw.int16Value) - } else { - return nil - } + TransactionStatus(rawValue: transactionStatusRaw) } set { - if let raw = newValue { - transferStatusRaw = raw.rawValue as NSNumber - } else { - transferStatusRaw = nil - } + let raw = newValue?.rawValue ?? .zero + guard raw != transactionStatusRaw else { return } + transactionStatusRaw = newValue?.rawValue ?? .zero } } diff --git a/Adamant/Services/DataProviders/AdamantChatsProvider.swift b/Adamant/Services/DataProviders/AdamantChatsProvider.swift index b518b95ee..5392e8522 100644 --- a/Adamant/Services/DataProviders/AdamantChatsProvider.swift +++ b/Adamant/Services/DataProviders/AdamantChatsProvider.swift @@ -918,6 +918,7 @@ extension AdamantChatsProvider { transaction.richType = richType transaction.additionalType = additionalType transaction.richContentSerialized = richContentSerialized + transaction.blockchainType = richType transaction.transactionStatus = richProviders[richType] != nil ? .notInitiated : nil From f5d615774356f5e161e1f8103e4fe460aed8526c Mon Sep 17 00:00:00 2001 From: StanislavDevIOS Date: Sat, 28 Oct 2023 15:42:43 +0300 Subject: [PATCH 37/96] [trello.com/c/PNNpGdAn] feat: new qr screen for partner --- Adamant.xcodeproj/project.pbxproj | 28 +++ Adamant/App/DI/AppAssembly.swift | 7 + Adamant/Helpers/String+adamant.swift | 15 +- Adamant/Helpers/String+localized.swift | 5 + .../Chat/View/ChatViewController.swift | 9 + .../View/Managers/ChatDialogManager.swift | 20 +- .../Chat/ViewModel/ChatViewModel.swift | 1 + .../Modules/PartnerQR/PartnerQRFactory.swift | 43 ++++ Adamant/Modules/PartnerQR/PartnerQRView.swift | 91 ++++++++ .../PartnerQR/PartnerQRViewModel.swift | 197 ++++++++++++++++++ .../AdamantScreensFactory.swift | 8 +- .../ScreensFactory/ScreensFactory.swift | 1 + Adamant/ServiceProtocols/DialogService.swift | 12 +- .../ServiceProtocols/PartnerQRService.swift | 16 ++ Adamant/Services/AdamantDialogService.swift | 57 ++++- .../Services/AdamantPartnerQRService.swift | 72 +++++++ Adamant/Utilities/AdamantUriTools.swift | 24 ++- .../Localization/de.lproj/Localizable.strings | 6 + .../Localization/en.lproj/Localizable.strings | 6 + .../Localization/ru.lproj/Localizable.strings | 6 + .../Sources/CommonKit/Core/SecuredStore.swift | 5 + 21 files changed, 614 insertions(+), 15 deletions(-) create mode 100644 Adamant/Modules/PartnerQR/PartnerQRFactory.swift create mode 100644 Adamant/Modules/PartnerQR/PartnerQRView.swift create mode 100644 Adamant/Modules/PartnerQR/PartnerQRViewModel.swift create mode 100644 Adamant/ServiceProtocols/PartnerQRService.swift create mode 100644 Adamant/Services/AdamantPartnerQRService.swift diff --git a/Adamant.xcodeproj/project.pbxproj b/Adamant.xcodeproj/project.pbxproj index 58cba3935..c2353b069 100644 --- a/Adamant.xcodeproj/project.pbxproj +++ b/Adamant.xcodeproj/project.pbxproj @@ -25,8 +25,13 @@ 3A9015A52A614A18002A2464 /* EmojiService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3A9015A42A614A18002A2464 /* EmojiService.swift */; }; 3A9015A72A614A62002A2464 /* AdamantEmojiService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3A9015A62A614A62002A2464 /* AdamantEmojiService.swift */; }; 3A9015A92A615893002A2464 /* ChatMessagesListViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3A9015A82A615893002A2464 /* ChatMessagesListViewModel.swift */; }; + 3A96E37A2AED27D7001F5A52 /* AdamantPartnerQRService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3A96E3792AED27D7001F5A52 /* AdamantPartnerQRService.swift */; }; + 3A96E37C2AED27F8001F5A52 /* PartnerQRService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3A96E37B2AED27F8001F5A52 /* PartnerQRService.swift */; }; 3AA2D5F7280EADE3000ED971 /* SocketService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3AA2D5F6280EADE3000ED971 /* SocketService.swift */; }; 3AA2D5FA280EAF5D000ED971 /* AdamantSocketService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3AA2D5F9280EAF5D000ED971 /* AdamantSocketService.swift */; }; + 3AA50DEF2AEBE65D00C58FC8 /* PartnerQRView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3AA50DEE2AEBE65D00C58FC8 /* PartnerQRView.swift */; }; + 3AA50DF12AEBE66A00C58FC8 /* PartnerQRViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3AA50DF02AEBE66A00C58FC8 /* PartnerQRViewModel.swift */; }; + 3AA50DF32AEBE67C00C58FC8 /* PartnerQRFactory.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3AA50DF22AEBE67C00C58FC8 /* PartnerQRFactory.swift */; }; 3AC76E3D2AB09118008042C4 /* ElegantEmojiPicker in Frameworks */ = {isa = PBXBuildFile; productRef = 3AC76E3C2AB09118008042C4 /* ElegantEmojiPicker */; }; 3C06931576393125C61FB8F6 /* Pods_Adamant.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 33975C0D891698AA7E74EBCC /* Pods_Adamant.framework */; }; 41047B70294B5EE10039E956 /* VisibleWalletsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 41047B6F294B5EE10039E956 /* VisibleWalletsViewController.swift */; }; @@ -613,8 +618,13 @@ 3A9015A42A614A18002A2464 /* EmojiService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EmojiService.swift; sourceTree = ""; }; 3A9015A62A614A62002A2464 /* AdamantEmojiService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AdamantEmojiService.swift; sourceTree = ""; }; 3A9015A82A615893002A2464 /* ChatMessagesListViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChatMessagesListViewModel.swift; sourceTree = ""; }; + 3A96E3792AED27D7001F5A52 /* AdamantPartnerQRService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AdamantPartnerQRService.swift; sourceTree = ""; }; + 3A96E37B2AED27F8001F5A52 /* PartnerQRService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PartnerQRService.swift; sourceTree = ""; }; 3AA2D5F6280EADE3000ED971 /* SocketService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SocketService.swift; sourceTree = ""; }; 3AA2D5F9280EAF5D000ED971 /* AdamantSocketService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AdamantSocketService.swift; sourceTree = ""; }; + 3AA50DEE2AEBE65D00C58FC8 /* PartnerQRView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PartnerQRView.swift; sourceTree = ""; }; + 3AA50DF02AEBE66A00C58FC8 /* PartnerQRViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PartnerQRViewModel.swift; sourceTree = ""; }; + 3AA50DF22AEBE67C00C58FC8 /* PartnerQRFactory.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PartnerQRFactory.swift; sourceTree = ""; }; 41047B6F294B5EE10039E956 /* VisibleWalletsViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VisibleWalletsViewController.swift; sourceTree = ""; }; 41047B71294B5F210039E956 /* VisibleWalletsTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VisibleWalletsTableViewCell.swift; sourceTree = ""; }; 41047B73294C61D10039E956 /* VisibleWalletsService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VisibleWalletsService.swift; sourceTree = ""; }; @@ -1220,6 +1230,16 @@ path = SocketService; sourceTree = ""; }; + 3AA50DED2AEBE61C00C58FC8 /* PartnerQR */ = { + isa = PBXGroup; + children = ( + 3AA50DF22AEBE67C00C58FC8 /* PartnerQRFactory.swift */, + 3AA50DEE2AEBE65D00C58FC8 /* PartnerQRView.swift */, + 3AA50DF02AEBE66A00C58FC8 /* PartnerQRViewModel.swift */, + ); + path = PartnerQR; + sourceTree = ""; + }; 411742FE2A39B1B1008CD98A /* Contribute */ = { isa = PBXGroup; children = ( @@ -1746,6 +1766,7 @@ 41C1698B29E7F34900FEB3CB /* RichTransactionReplyService.swift */, 3A4193902A580C85006A6B22 /* RichTransactionReactService.swift */, 3A2F55FB2AC6F885000A3F26 /* CoinStorage.swift */, + 3A96E37B2AED27F8001F5A52 /* PartnerQRService.swift */, ); path = ServiceProtocols; sourceTree = ""; @@ -1778,6 +1799,7 @@ 550066C6284D682D0044C0B1 /* AdamantHealthCheckService.swift */, 9304F8C3292F8A3100173F18 /* AdamantPushNotificationsTokenService.swift */, 3A2F55FD2AC6F90E000A3F26 /* AdamantCoinStorageService.swift */, + 3A96E3792AED27D7001F5A52 /* AdamantPartnerQRService.swift */, ); path = Services; sourceTree = ""; @@ -1866,6 +1888,7 @@ E919479920000FFD001362F8 /* Modules */ = { isa = PBXGroup; children = ( + 3AA50DED2AEBE61C00C58FC8 /* PartnerQR */, 93ADE06D2ACA66AF008ED641 /* TestVibration */, 93294B942AAD31F200911109 /* ScreensFactory */, 93294B912AAD2CA500911109 /* Welcome */, @@ -2837,6 +2860,7 @@ E9B3D39A201F90570019EB36 /* AccountsProvider.swift in Sources */, 4186B338294200E8006594A3 /* DogeWalletService+DynamicConstants.swift in Sources */, 417BA7F428BF894F00DF94C5 /* NotificationSoundsViewController.swift in Sources */, + 3A96E37C2AED27F8001F5A52 /* PartnerQRService.swift in Sources */, E950652320404C84008352E5 /* AdamantUriTools.swift in Sources */, 3A41938F2A580C57006A6B22 /* AdamantRichTransactionReactService.swift in Sources */, E95F85C7200A9B070070534A /* ChatTableViewCell.swift in Sources */, @@ -2876,10 +2900,12 @@ E9960B3421F5154300C840A8 /* BaseAccount+CoreDataProperties.swift in Sources */, 550066C7284D682D0044C0B1 /* AdamantHealthCheckService.swift in Sources */, 4153045929C09902000E4BEA /* AdamantIncreaseFeeService.swift in Sources */, + 3AA50DEF2AEBE65D00C58FC8 /* PartnerQRView.swift in Sources */, E90A494B204D9EB8009F6A65 /* AdamantAuthentication.swift in Sources */, E9215973206119FB0000CA5C /* ReachabilityMonitor.swift in Sources */, 3A2F55FC2AC6F885000A3F26 /* CoinStorage.swift in Sources */, E91947B420002809001362F8 /* AdamantAccount.swift in Sources */, + 3AA50DF12AEBE66A00C58FC8 /* PartnerQRViewModel.swift in Sources */, 6449BA6C235CA0930033B936 /* ERC20WalletViewController.swift in Sources */, E9E7CDC22003F5A400DFC4DB /* TransactionsListViewControllerBase.swift in Sources */, E905D39F204C281400DDB504 /* LoginViewController.swift in Sources */, @@ -2934,6 +2960,7 @@ 93775E462A674FA9009061AC /* Markdown+Adamant.swift in Sources */, 6416B1A721B024B6006089AC /* LskWalletService+Send.swift in Sources */, E9942B87203D9E5100C163AF /* EurekaQRRow.swift in Sources */, + 3AA50DF32AEBE67C00C58FC8 /* PartnerQRFactory.swift in Sources */, E9AA8C02212C5BF500F9249F /* AdmWalletService+Send.swift in Sources */, E90847332196FEA80095825D /* TransferTransaction+CoreDataProperties.swift in Sources */, E99818942120892F0018C84C /* WalletViewControllerBase.swift in Sources */, @@ -3085,6 +3112,7 @@ E90847302196FEA80095825D /* ChatTransaction+CoreDataClass.swift in Sources */, E913C9081FFFA943001A83F7 /* AdamantCore.swift in Sources */, 41935848287841E20083363B /* MacOSDeterminer.swift in Sources */, + 3A96E37A2AED27D7001F5A52 /* AdamantPartnerQRService.swift in Sources */, E9EC342120052ABB00C0E546 /* TransferViewControllerBase.swift in Sources */, 9304F8C4292F8A3100173F18 /* AdamantPushNotificationsTokenService.swift in Sources */, 9300F94629D0149100FEDDB8 /* RichMessageProviderWithStatusCheck.swift in Sources */, diff --git a/Adamant/App/DI/AppAssembly.swift b/Adamant/App/DI/AppAssembly.swift index 882b1ba55..bd32067cc 100644 --- a/Adamant/App/DI/AppAssembly.swift +++ b/Adamant/App/DI/AppAssembly.swift @@ -81,6 +81,13 @@ struct AppAssembly: Assembly { ) }.inObjectScope(.container) + // MARK: PartnerQRService + container.register(PartnerQRService.self) { r in + AdamantPartnerQRService( + securedStore: r.resolve(SecuredStore.self)! + ) + }.inObjectScope(.container) + // MARK: PushNotificationsTokenService container.register(PushNotificationsTokenService.self) { r in AdamantPushNotificationsTokenService( diff --git a/Adamant/Helpers/String+adamant.swift b/Adamant/Helpers/String+adamant.swift index 836696c38..a69173cc3 100644 --- a/Adamant/Helpers/String+adamant.swift +++ b/Adamant/Helpers/String+adamant.swift @@ -60,7 +60,20 @@ extension String { } } } - + case .addressLegacy(address: let addr, params: let params): + address = addr + if let params = params { + for param in params { + switch param { + case .address: + break + case .label(let label): + name = label + case .message(let urlMessage): + message = urlMessage + } + } + } case .passphrase: address = nil } diff --git a/Adamant/Helpers/String+localized.swift b/Adamant/Helpers/String+localized.swift index a69593a1b..d3b940264 100644 --- a/Adamant/Helpers/String+localized.swift +++ b/Adamant/Helpers/String+localized.swift @@ -90,4 +90,9 @@ extension String.adamant { static let failedMessageError = String.localized("Reply.failedMessageError", comment: "Failed message reply error") static let pendingMessageError = String.localized("Reply.pendingMessageError", comment: "Pending message reply error") } + + enum partnerQR { + static let includePartnerName = String.localized("PartnerQR.includePartnerName", comment: "Include partner name") + static let includePartnerURL = String.localized("PartnerQR.includePartnerURL", comment: "Include partner url") + } } diff --git a/Adamant/Modules/Chat/View/ChatViewController.swift b/Adamant/Modules/Chat/View/ChatViewController.swift index 05108b9e6..beecbf236 100644 --- a/Adamant/Modules/Chat/View/ChatViewController.swift +++ b/Adamant/Modules/Chat/View/ChatViewController.swift @@ -351,6 +351,10 @@ private extension ChatViewController { viewModel.layoutIfNeeded .sink { [weak self] in self?.view.layoutIfNeeded() } .store(in: &subscriptions) + + viewModel.didTapPartnerQR + .sink { [weak self] in self?.didTapPartenerQR(partner: $0) } + .store(in: &subscriptions) } } @@ -628,6 +632,11 @@ private extension ChatViewController { navigationController?.pushViewController(vc, animated: true) } + func didTapPartenerQR(partner: CoreDataAccount) { + let vc = screensFactory.makePartnerQR(partner: partner) + navigationController?.pushViewController(vc, animated: true) + } + func didTapRichMessageTransaction(_ transaction: RichMessageTransaction) { guard let type = transaction.richType, diff --git a/Adamant/Modules/Chat/View/Managers/ChatDialogManager.swift b/Adamant/Modules/Chat/View/Managers/ChatDialogManager.swift index 9d571ab3c..cc78642f8 100644 --- a/Adamant/Modules/Chat/View/Managers/ChatDialogManager.swift +++ b/Adamant/Modules/Chat/View/Managers/ChatDialogManager.swift @@ -304,25 +304,29 @@ private extension ChatDialogManager { ) { [weak self] _ in guard let self = self, - let address = self.address, - let encodedAddress = self.encodedAddress + let address = self.address else { return } + let didSelect: ((ShareType) -> Void)? = { [weak self] type in + guard case .partnerQR = type, + let partner = self?.viewModel.chatroom?.partner + else { return } + + self?.viewModel.didTapPartnerQR.send(partner) + } + self.dialogService.presentShareAlertFor( string: address, types: [ .copyToPasteboard, .share, - .generateQr( - encodedContent: encodedAddress, - sharingTip: address, - withLogo: true - ) + .partnerQR ], excludedActivityTypes: ShareContentType.address.excludedActivityTypes, animated: true, from: sender, - completion: nil + completion: nil, + didSelect: didSelect ) } } diff --git a/Adamant/Modules/Chat/ViewModel/ChatViewModel.swift b/Adamant/Modules/Chat/ViewModel/ChatViewModel.swift index 512bceded..71857c63f 100644 --- a/Adamant/Modules/Chat/ViewModel/ChatViewModel.swift +++ b/Adamant/Modules/Chat/ViewModel/ChatViewModel.swift @@ -68,6 +68,7 @@ final class ChatViewModel: NSObject { var tempOffsets: [String] = [] var needToAnimateCellIndex: Int? + let didTapPartnerQR = ObservableSender() let didTapTransfer = ObservableSender() let dialog = ObservableSender() let didTapAdmChat = ObservableSender<(Chatroom, String?)>() diff --git a/Adamant/Modules/PartnerQR/PartnerQRFactory.swift b/Adamant/Modules/PartnerQR/PartnerQRFactory.swift new file mode 100644 index 000000000..13c8ce417 --- /dev/null +++ b/Adamant/Modules/PartnerQR/PartnerQRFactory.swift @@ -0,0 +1,43 @@ +// +// PartnerQRFactory.swift +// Adamant +// +// Created by Stanislav Jelezoglo on 27.10.2023. +// Copyright © 2023 Adamant. All rights reserved. +// + +import Swinject +import SwiftUI + +struct PartnerQRFactory { + private let assembler: Assembler + + init(parent: Assembler) { + assembler = .init([ContributeAssembly()], parent: parent) + } + + @MainActor + func makeViewController(partner: CoreDataAccount) -> UIViewController { + let view = PartnerQRView( + viewModel: assembler.resolve(PartnerQRViewModel.self)! + ) + view.viewModel.setup(partner: partner) + + return UIHostingController( + rootView: view + ) + } +} + +private struct ContributeAssembly: Assembly { + func assemble(container: Container) { + container.register(PartnerQRViewModel.self) { + PartnerQRViewModel( + dialogService: $0.resolve(DialogService.self)!, + addressBookService: $0.resolve(AddressBookService.self)!, + avatarService: $0.resolve(AvatarService.self)!, + partnerQRService: $0.resolve(PartnerQRService.self)! + ) + }.inObjectScope(.weak) + } +} diff --git a/Adamant/Modules/PartnerQR/PartnerQRView.swift b/Adamant/Modules/PartnerQR/PartnerQRView.swift new file mode 100644 index 000000000..f4d268d9f --- /dev/null +++ b/Adamant/Modules/PartnerQR/PartnerQRView.swift @@ -0,0 +1,91 @@ +// +// PartnerQRView.swift +// Adamant +// +// Created by Stanislav Jelezoglo on 27.10.2023. +// Copyright © 2023 Adamant. All rights reserved. +// + +import SwiftUI + +struct PartnerQRView: View { + @ObservedObject var viewModel: PartnerQRViewModel + + var body: some View { + Form { + infoSection() + toggleSection() + buttonSection() + } + .navigationBarTitleDisplayMode(.inline) + .toolbar { + ToolbarItem(placement: .principal) { + toolbar() + } + } + } +} + +private extension PartnerQRView { + func toolbar() -> some View { + HStack { + if let uiImage = viewModel.partnerImage { + Image(uiImage: uiImage) + .resizable() + .frame(squareSize: viewModel.partnerImageSize) + } + Text(viewModel.partnerName).font(.headline) + } + } + + func infoSection() -> some View { + Section { + if let uiImage = viewModel.image { + HStack { + Spacer() + Image(uiImage: uiImage) + .resizable() + .aspectRatio(contentMode: .fit) + .frame(height: 250) + Spacer() + } + } + + HStack { + Spacer() + Text(viewModel.title) + .padding() + Spacer() + } + } + } + + func toggleSection() -> some View { + Section { + Toggle(String.adamant.partnerQR.includePartnerName, isOn: $viewModel.includeContactsName) + .disabled(!viewModel.includeContactsNameEnabled) + .tint(.init(uiColor: .adamant.active)) + .onChange(of: viewModel.includeContactsName) { _ in + viewModel.didToggle() + } + + Toggle(String.adamant.partnerQR.includePartnerURL, isOn: $viewModel.includeWebAppLink) + .tint(.init(uiColor: .adamant.active)) + .onChange(of: viewModel.includeWebAppLink) { _ in + viewModel.didToggle() + } + } + } + + func buttonSection() -> some View { + Section { + Button(String.adamant.alert.saveToPhotolibrary) { + viewModel.saveToPhotos() + } + + Button(String.adamant.alert.share) { + viewModel.share() + } + } + } +} diff --git a/Adamant/Modules/PartnerQR/PartnerQRViewModel.swift b/Adamant/Modules/PartnerQR/PartnerQRViewModel.swift new file mode 100644 index 000000000..e5eeeff9a --- /dev/null +++ b/Adamant/Modules/PartnerQR/PartnerQRViewModel.swift @@ -0,0 +1,197 @@ +// +// PartnerQRViewModel.swift +// Adamant +// +// Created by Stanislav Jelezoglo on 27.10.2023. +// Copyright © 2023 Adamant. All rights reserved. +// + +import SwiftUI +import Combine +import CommonKit +import Photos + +@MainActor +final class PartnerQRViewModel: NSObject, ObservableObject { + @Published var includeContactsName = false + @Published var includeWebAppLink = false + @Published var includeContactsNameEnabled = true + @Published var image: UIImage? + @Published var partnerImage: UIImage? + @Published var partnerName: String = "" + + private var partner: CoreDataAccount? + private let dialogService: DialogService + private let addressBookService: AddressBookService + private let avatarService: AvatarService + private let partnerQRService: PartnerQRService + private var subscriptions = Set() + + let partnerImageSize: CGFloat = 25 + + var title: String { + partner?.address ?? "" + } + + nonisolated init( + dialogService: DialogService, + addressBookService: AddressBookService, + avatarService: AvatarService, + partnerQRService: PartnerQRService + ) { + self.dialogService = dialogService + self.addressBookService = addressBookService + self.avatarService = avatarService + self.partnerQRService = partnerQRService + } + + func setup(partner: CoreDataAccount) { + self.partner = partner + updatePartnerInfo() + generateQR() + } + + func saveToPhotos() { + guard let qrCode = image else { return } + + switch PHPhotoLibrary.authorizationStatus() { + case .authorized, .limited: + UIImageWriteToSavedPhotosAlbum( + qrCode, + self, + #selector(image(_: didFinishSavingWithError: contextInfo:)), + nil + ) + + case .notDetermined: + UIImageWriteToSavedPhotosAlbum( + qrCode, + self, + #selector(image(_: didFinishSavingWithError: contextInfo:)), + nil + ) + + case .restricted, .denied: + dialogService.presentGoToSettingsAlert( + title: nil, + message: String.adamant.shared.photolibraryNotAuthorized + ) + @unknown default: + break + } + } + + func share() { + guard let qrCode = image else { return } + + let vc = UIActivityViewController( + activityItems: [qrCode], + applicationActivities: nil + ) + + vc.completionWithItemsHandler = { [weak self] (_: UIActivity.ActivityType?, completed: Bool, _, error: Error?) in + guard completed else { return } + + if let error = error { + self?.dialogService.showWarning(withMessage: error.localizedDescription) + } else { + self?.dialogService.showSuccess(withMessage: String.adamant.alert.done) + } + } + vc.modalPresentationStyle = .overFullScreen + dialogService.present(vc, animated: true, completion: nil) + } + + func didToggle() { + partnerQRService.setIncludeURLEnabled(includeWebAppLink) + partnerQRService.setIncludeNameEnabled(includeContactsName) + generateQR() + } +} + +private extension PartnerQRViewModel { + func updatePartnerInfo() { + guard let publicKey = partner?.publicKey, + let address = partner?.address + else { + includeContactsNameEnabled = false + includeContactsName = false + includeWebAppLink = false + return + } + + if let name = addressBookService.getName(for: address) { + partnerName = name + includeContactsNameEnabled = true + includeContactsName = partnerQRService.isIncludeNameEnabled() + } else { + partnerName = address + includeContactsNameEnabled = false + includeContactsName = false + } + + includeWebAppLink = partnerQRService.isIncludeURLEnabled() + + guard let avatarName = partner?.avatar, + let avatar = UIImage.asset(named: avatarName) + else { + partnerImage = avatarService.avatar( + for: publicKey, + size: partnerImageSize + ) + return + } + + partnerImage = avatar + } + + func generateQR() { + guard let address = partner?.address else { return } + + var params: [AdamantAddressParam] = [] + + if includeContactsName, + let name = addressBookService.getName(for: address) { + params.append(.label(name)) + } + + var data: String = address + + if includeWebAppLink { + data = AdamantUriTools.encode(request: AdamantUri.address( + address: address, + params: params + )) + } else { + data = AdamantUriTools.encode(request: AdamantUri.addressLegacy( + address: address, + params: params + )) + } + + let qr = AdamantQRTools.generateQrFrom( + string: data, + withLogo: true + ) + + switch qr { + case .success(let uIImage): + image = uIImage + case .failure(let error): + dialogService.showError(withMessage: "", supportEmail: false, error: error) + } + } + + @objc private func image( + _ image: UIImage, + didFinishSavingWithError error: NSError?, + contextInfo: UnsafeRawPointer + ) { + guard error == nil else { + dialogService.presentGoToSettingsAlert(title: String.adamant.shared.photolibraryNotAuthorized, message: nil) + return + } + + dialogService.showSuccess(withMessage: String.adamant.alert.done) + } +} diff --git a/Adamant/Modules/ScreensFactory/AdamantScreensFactory.swift b/Adamant/Modules/ScreensFactory/AdamantScreensFactory.swift index 124e609c9..57904b424 100644 --- a/Adamant/Modules/ScreensFactory/AdamantScreensFactory.swift +++ b/Adamant/Modules/ScreensFactory/AdamantScreensFactory.swift @@ -24,7 +24,8 @@ struct AdamantScreensFactory: ScreensFactory { private let shareQRFactory: ShareQRFactory private let accountFactory: AccountFactory private let vibrationSelectionFactory: VibrationSelectionFactory - + private let partnerQRFactory: PartnerQRFactory + init(assembler: Assembler) { admWalletFactory = .init(assembler: assembler) chatListFactory = .init(assembler: assembler) @@ -38,6 +39,7 @@ struct AdamantScreensFactory: ScreensFactory { shareQRFactory = .init(assembler: assembler) accountFactory = .init(assembler: assembler) vibrationSelectionFactory = .init(parent: assembler) + partnerQRFactory = .init(parent: assembler) walletFactoryCompose = AdamantWalletFactoryCompose( lskWalletFactory: .init(assembler: assembler), @@ -169,4 +171,8 @@ struct AdamantScreensFactory: ScreensFactory { func makeVibrationSelection() -> UIViewController { vibrationSelectionFactory.makeViewController() } + + func makePartnerQR(partner: CoreDataAccount) -> UIViewController { + partnerQRFactory.makeViewController(partner: partner) + } } diff --git a/Adamant/Modules/ScreensFactory/ScreensFactory.swift b/Adamant/Modules/ScreensFactory/ScreensFactory.swift index 749488b32..9eda7de46 100644 --- a/Adamant/Modules/ScreensFactory/ScreensFactory.swift +++ b/Adamant/Modules/ScreensFactory/ScreensFactory.swift @@ -59,4 +59,5 @@ protocol ScreensFactory { func makeContribute() -> UIViewController func makeLogin() -> LoginViewController func makeVibrationSelection() -> UIViewController + func makePartnerQR(partner: CoreDataAccount) -> UIViewController } diff --git a/Adamant/ServiceProtocols/DialogService.swift b/Adamant/ServiceProtocols/DialogService.swift index af333d338..cf2d930e4 100644 --- a/Adamant/ServiceProtocols/DialogService.swift +++ b/Adamant/ServiceProtocols/DialogService.swift @@ -35,6 +35,7 @@ enum ShareType { case share case generateQr(encodedContent: String?, sharingTip: String?, withLogo: Bool) case saveToPhotolibrary(image: UIImage) + case partnerQR var localized: String { switch self { @@ -44,7 +45,7 @@ enum ShareType { case .share: return String.adamant.alert.share - case .generateQr: + case .generateQr, .partnerQR: return String.adamant.alert.generateQr case .saveToPhotolibrary: @@ -147,6 +148,15 @@ protocol DialogService: AnyObject { func presentShareAlertFor(string: String, types: [ShareType], excludedActivityTypes: [UIActivity.ActivityType]?, animated: Bool, from: UIView?, completion: (() -> Void)?) func presentShareAlertFor(stringForPasteboard: String, stringForShare: String, stringForQR: String, types: [ShareType], excludedActivityTypes: [UIActivity.ActivityType]?, animated: Bool, from: UIView?, completion: (() -> Void)?) func presentShareAlertFor(string: String, types: [ShareType], excludedActivityTypes: [UIActivity.ActivityType]?, animated: Bool, from: UIBarButtonItem?, completion: (() -> Void)?) + func presentShareAlertFor( + string: String, + types: [ShareType], + excludedActivityTypes: [UIActivity.ActivityType]?, + animated: Bool, + from: UIBarButtonItem?, + completion: (() -> Void)?, + didSelect: ((ShareType) -> Void)? + ) func presentGoToSettingsAlert(title: String?, message: String?) diff --git a/Adamant/ServiceProtocols/PartnerQRService.swift b/Adamant/ServiceProtocols/PartnerQRService.swift new file mode 100644 index 000000000..a193cd2d9 --- /dev/null +++ b/Adamant/ServiceProtocols/PartnerQRService.swift @@ -0,0 +1,16 @@ +// +// PartnerQRService.swift +// Adamant +// +// Created by Stanislav Jelezoglo on 28.10.2023. +// Copyright © 2023 Adamant. All rights reserved. +// + +import Foundation + +protocol PartnerQRService: AnyObject { + func setIncludeNameEnabled(_ value: Bool) + func isIncludeNameEnabled() -> Bool + func setIncludeURLEnabled(_ value: Bool) + func isIncludeURLEnabled() -> Bool +} diff --git a/Adamant/Services/AdamantDialogService.swift b/Adamant/Services/AdamantDialogService.swift index 4e34f5871..933a47a14 100644 --- a/Adamant/Services/AdamantDialogService.swift +++ b/Adamant/Services/AdamantDialogService.swift @@ -265,6 +265,33 @@ extension AdamantDialogService { present(alert, animated: animated, completion: completion) } + func presentShareAlertFor( + string: String, + types: [ShareType], + excludedActivityTypes: [UIActivity.ActivityType]?, + animated: Bool, + from: UIBarButtonItem?, + completion: (() -> Void)?, + didSelect: ((ShareType) -> Void)? + ) { + let source: UIAlertController.SourceView? = from.map { .barButtonItem($0) } + + let alert = createShareAlertFor( + stringForPasteboard: string, + stringForShare: string, + stringForQR: string, + types: types, + excludedActivityTypes: excludedActivityTypes, + animated: animated, + from: source, + completion: completion, + didSelect: didSelect + ) + + alert.modalPresentationStyle = .overFullScreen + present(alert, animated: animated, completion: completion) + } + func presentShareAlertFor(string: String, types: [ShareType], excludedActivityTypes: [UIActivity.ActivityType]?, animated: Bool, from: UIView?, completion: (() -> Void)?) { let source: UIAlertController.SourceView? = from.map { .view($0) } @@ -300,7 +327,8 @@ extension AdamantDialogService { excludedActivityTypes: [UIActivity.ActivityType]?, animated: Bool, from: UIAlertController.SourceView?, - completion: (() -> Void)? + completion: (() -> Void)?, + didSelect: ((ShareType) -> Void)? = nil ) -> UIAlertController { let alert = UIAlertController( title: nil, @@ -309,7 +337,17 @@ extension AdamantDialogService { source: from ) - addActions(to: alert, stringForPasteboard: stringForPasteboard, stringForShare: stringForShare, stringForQR: stringForQR, types: types, excludedActivityTypes: excludedActivityTypes, from: from, completion: completion) + addActions( + to: alert, + stringForPasteboard: stringForPasteboard, + stringForShare: stringForShare, + stringForQR: stringForQR, + types: types, + excludedActivityTypes: excludedActivityTypes, + from: from, + completion: completion, + didSelect: didSelect + ) return alert } @@ -322,7 +360,8 @@ extension AdamantDialogService { types: [ShareType], excludedActivityTypes: [UIActivity.ActivityType]?, from: UIAlertController.SourceView?, - completion: (() -> Void)? + completion: (() -> Void)?, + didSelect: ((ShareType) -> Void)? = nil ) { for type in types { switch type { @@ -330,10 +369,12 @@ extension AdamantDialogService { alert.addAction(UIAlertAction(title: type.localized , style: .default) { [weak self] _ in UIPasteboard.general.string = stringForPasteboard self?.showToastMessage(String.adamant.alert.copiedToPasteboardNotification) + didSelect?(.copyToPasteboard) }) case .share: alert.addAction(UIAlertAction(title: type.localized, style: .default) { [weak self] _ in + didSelect?(.share) let vc = UIActivityViewController(activityItems: [stringForShare], applicationActivities: nil) vc.excludedActivityTypes = excludedActivityTypes @@ -361,6 +402,8 @@ extension AdamantDialogService { alert.addAction(UIAlertAction(title: type.localized, style: .default) { [weak self] _ in guard let self = self else { return } + didSelect?(.generateQr(encodedContent: encodedContent, sharingTip: sharingTip, withLogo: withLogo)) + switch AdamantQRTools.generateQrFrom( string: encodedContent ?? stringForQR, withLogo: withLogo @@ -384,9 +427,17 @@ extension AdamantDialogService { case .saveToPhotolibrary(let image): let action = UIAlertAction(title: type.localized, style: .default) { [weak self] _ in + didSelect?(.saveToPhotolibrary(image: image)) UIImageWriteToSavedPhotosAlbum(image, self, #selector(self?.image(_:didFinishSavingWithError:contextInfo:)), nil) } + alert.addAction(action) + + case .partnerQR: + let action = UIAlertAction(title: type.localized, style: .default) { [didSelect] _ in + didSelect?(.partnerQR) + } + alert.addAction(action) } } diff --git a/Adamant/Services/AdamantPartnerQRService.swift b/Adamant/Services/AdamantPartnerQRService.swift new file mode 100644 index 000000000..93b878d79 --- /dev/null +++ b/Adamant/Services/AdamantPartnerQRService.swift @@ -0,0 +1,72 @@ +// +// PartnerQRService.swift +// Adamant +// +// Created by Stanislav Jelezoglo on 28.10.2023. +// Copyright © 2023 Adamant. All rights reserved. +// + +import Foundation +import CommonKit +import Combine + +final class AdamantPartnerQRService: PartnerQRService { + + // MARK: Dependencies + + let securedStore: SecuredStore + + // MARK: Proprieties + + @Atomic private var notificationsSet: Set = [] + + // MARK: Lifecycle + + init(securedStore: SecuredStore) { + self.securedStore = securedStore + + NotificationCenter.default + .publisher(for: .AdamantAccountService.userLoggedOut) + .sink { [weak self] _ in + self?.userLoggedOut() + } + .store(in: ¬ificationsSet) + } + + // MARK: Notification actions + + private func userLoggedOut() { + setIncludeNameEnabled(true) + setIncludeURLEnabled(true) + } + + // MARK: Update data + + func setIncludeNameEnabled(_ value: Bool) { + securedStore.set(value, for: StoreKey.partnerQR.includeNameEnabled) + } + + func isIncludeNameEnabled() -> Bool { + guard let result: Bool = securedStore.get( + StoreKey.partnerQR.includeNameEnabled + ) else { + return true + } + + return result + } + + func setIncludeURLEnabled(_ value: Bool) { + securedStore.set(value, for: StoreKey.partnerQR.includeURLEnabled) + } + + func isIncludeURLEnabled() -> Bool { + guard let result: Bool = securedStore.get( + StoreKey.partnerQR.includeURLEnabled + ) else { + return true + } + + return result + } +} diff --git a/Adamant/Utilities/AdamantUriTools.swift b/Adamant/Utilities/AdamantUriTools.swift index 23cd24b8f..bd860c1bc 100644 --- a/Adamant/Utilities/AdamantUriTools.swift +++ b/Adamant/Utilities/AdamantUriTools.swift @@ -12,6 +12,7 @@ import CommonKit enum AdamantUri { case passphrase(passphrase: String) case address(address: String, params: [AdamantAddressParam]?) + case addressLegacy(address: String, params: [AdamantAddressParam]?) } enum AdamantAddressParam { @@ -65,7 +66,6 @@ final class AdamantUriTools { components.queryItems = [ .init(name: "address", value: address) ] - guard let uri = components.url?.absoluteString else { return "" } params?.forEach { switch $0 { @@ -77,6 +77,28 @@ final class AdamantUriTools { components.queryItems?.append(.init(name: "message", value: value)) } } + + guard let uri = components.url?.absoluteString else { return "" } + + return uri + case .addressLegacy(address: let address, params: let params): + var components = URLComponents() + components.scheme = AdmWalletService.qqPrefix + components.host = address + components.queryItems = [] + + params?.forEach { + switch $0 { + case .address: + break + case .label(let value): + components.queryItems?.append(.init(name: "label", value: value)) + case .message(let value): + components.queryItems?.append(.init(name: "message", value: value)) + } + } + + guard let uri = components.url?.absoluteString else { return "" } return uri } diff --git a/CommonKit/Sources/CommonKit/Assets/Localization/de.lproj/Localizable.strings b/CommonKit/Sources/CommonKit/Assets/Localization/de.lproj/Localizable.strings index ebe348c80..3fa11009b 100644 --- a/CommonKit/Sources/CommonKit/Assets/Localization/de.lproj/Localizable.strings +++ b/CommonKit/Sources/CommonKit/Assets/Localization/de.lproj/Localizable.strings @@ -1143,3 +1143,9 @@ /* Pending message reply error */ "Reply.pendingMessageError" = "Sie können nicht auf eine ausstehende Nachricht antworten. Auf Bestätigungen warten (schätzungsweise 1–2 Sekunden)"; + +/* Include partner name */ +"PartnerQR.includePartnerName" = "Neem contact op met"; + +/* Include partner url */ +"PartnerQR.includePartnerURL" = "Koppeling naar webapp opnemen"; diff --git a/CommonKit/Sources/CommonKit/Assets/Localization/en.lproj/Localizable.strings b/CommonKit/Sources/CommonKit/Assets/Localization/en.lproj/Localizable.strings index fc2e364ba..9661a4533 100644 --- a/CommonKit/Sources/CommonKit/Assets/Localization/en.lproj/Localizable.strings +++ b/CommonKit/Sources/CommonKit/Assets/Localization/en.lproj/Localizable.strings @@ -1122,3 +1122,9 @@ /* Pending message reply error */ "Reply.pendingMessageError" = "You can't reply to a pending message. Wait for confirmations (estimate 1-2 seconds)"; + +/* Include partner name */ +"PartnerQR.includePartnerName" = "Include contact name"; + +/* Include partner url */ +"PartnerQR.includePartnerURL" = "Include Web app link"; diff --git a/CommonKit/Sources/CommonKit/Assets/Localization/ru.lproj/Localizable.strings b/CommonKit/Sources/CommonKit/Assets/Localization/ru.lproj/Localizable.strings index f05f00773..d2541c1b1 100644 --- a/CommonKit/Sources/CommonKit/Assets/Localization/ru.lproj/Localizable.strings +++ b/CommonKit/Sources/CommonKit/Assets/Localization/ru.lproj/Localizable.strings @@ -1119,3 +1119,9 @@ /* Pending message reply error */ "Reply.pendingMessageError" = "Вы не можете ответить на ожидающее сообщение. Дождитесь подтверждения (оценка 1-2 секунды)"; + +/* Include partner name */ +"PartnerQR.includePartnerName" = "Добавить имя контакта"; + +/* Include partner url */ +"PartnerQR.includePartnerURL" = "Добавить ссылку на веб-приложение"; diff --git a/CommonKit/Sources/CommonKit/Core/SecuredStore.swift b/CommonKit/Sources/CommonKit/Core/SecuredStore.swift index a67325f94..0c8e8d466 100644 --- a/CommonKit/Sources/CommonKit/Core/SecuredStore.swift +++ b/CommonKit/Sources/CommonKit/Core/SecuredStore.swift @@ -46,6 +46,11 @@ public extension StoreKey { enum emojis { public static let emojis = "emojis" } + + enum partnerQR { + public static let includeNameEnabled = "includeNameEnabled" + public static let includeURLEnabled = "includeURLEnabled" + } } public protocol SecuredStore: AnyObject { From 587dc39e0b46303426fad60405fa4ad466e2bf29 Mon Sep 17 00:00:00 2001 From: StanislavDevIOS Date: Mon, 30 Oct 2023 16:52:54 +0200 Subject: [PATCH 38/96] [trello.com/c/PNNpGdAn] code improvements --- Adamant/App/DI/AppAssembly.swift | 7 ------ .../View/Managers/ChatDialogManager.swift | 17 +++++++++----- .../Modules/PartnerQR/PartnerQRFactory.swift | 17 ++++++++++---- Adamant/Modules/PartnerQR/PartnerQRView.swift | 6 ----- .../PartnerQR/PartnerQRViewModel.swift | 22 +++++++++++++++---- 5 files changed, 42 insertions(+), 27 deletions(-) diff --git a/Adamant/App/DI/AppAssembly.swift b/Adamant/App/DI/AppAssembly.swift index bd32067cc..882b1ba55 100644 --- a/Adamant/App/DI/AppAssembly.swift +++ b/Adamant/App/DI/AppAssembly.swift @@ -81,13 +81,6 @@ struct AppAssembly: Assembly { ) }.inObjectScope(.container) - // MARK: PartnerQRService - container.register(PartnerQRService.self) { r in - AdamantPartnerQRService( - securedStore: r.resolve(SecuredStore.self)! - ) - }.inObjectScope(.container) - // MARK: PushNotificationsTokenService container.register(PushNotificationsTokenService.self) { r in AdamantPushNotificationsTokenService( diff --git a/Adamant/Modules/Chat/View/Managers/ChatDialogManager.swift b/Adamant/Modules/Chat/View/Managers/ChatDialogManager.swift index cc78642f8..11ad3d8cb 100644 --- a/Adamant/Modules/Chat/View/Managers/ChatDialogManager.swift +++ b/Adamant/Modules/Chat/View/Managers/ChatDialogManager.swift @@ -138,21 +138,26 @@ private extension ChatDialogManager { func showSystemPartnerMenu(sender: UIBarButtonItem) { guard let address = address, let encodedAddress = encodedAddress else { return } + let didSelect: ((ShareType) -> Void)? = { [weak self] type in + guard case .partnerQR = type, + let partner = self?.viewModel.chatroom?.partner + else { return } + + self?.viewModel.didTapPartnerQR.send(partner) + } + dialogService.presentShareAlertFor( string: address, types: [ .copyToPasteboard, .share, - .generateQr( - encodedContent: encodedAddress, - sharingTip: address, - withLogo: true - ) + .partnerQR ], excludedActivityTypes: ShareContentType.address.excludedActivityTypes, animated: true, from: sender, - completion: nil + completion: nil, + didSelect: didSelect ) } diff --git a/Adamant/Modules/PartnerQR/PartnerQRFactory.swift b/Adamant/Modules/PartnerQR/PartnerQRFactory.swift index 13c8ce417..27801872d 100644 --- a/Adamant/Modules/PartnerQR/PartnerQRFactory.swift +++ b/Adamant/Modules/PartnerQR/PartnerQRFactory.swift @@ -8,20 +8,23 @@ import Swinject import SwiftUI +import CommonKit struct PartnerQRFactory { private let assembler: Assembler init(parent: Assembler) { - assembler = .init([ContributeAssembly()], parent: parent) + assembler = .init([PartnerQRAssembly()], parent: parent) } @MainActor func makeViewController(partner: CoreDataAccount) -> UIViewController { + let viewModel = assembler.resolve(PartnerQRViewModel.self)! + viewModel.setup(partner: partner) + let view = PartnerQRView( - viewModel: assembler.resolve(PartnerQRViewModel.self)! + viewModel: viewModel ) - view.viewModel.setup(partner: partner) return UIHostingController( rootView: view @@ -29,8 +32,14 @@ struct PartnerQRFactory { } } -private struct ContributeAssembly: Assembly { +private struct PartnerQRAssembly: Assembly { func assemble(container: Container) { + container.register(PartnerQRService.self) { r in + AdamantPartnerQRService( + securedStore: r.resolve(SecuredStore.self)! + ) + }.inObjectScope(.container) + container.register(PartnerQRViewModel.self) { PartnerQRViewModel( dialogService: $0.resolve(DialogService.self)!, diff --git a/Adamant/Modules/PartnerQR/PartnerQRView.swift b/Adamant/Modules/PartnerQR/PartnerQRView.swift index f4d268d9f..f54e4ed00 100644 --- a/Adamant/Modules/PartnerQR/PartnerQRView.swift +++ b/Adamant/Modules/PartnerQR/PartnerQRView.swift @@ -65,15 +65,9 @@ private extension PartnerQRView { Toggle(String.adamant.partnerQR.includePartnerName, isOn: $viewModel.includeContactsName) .disabled(!viewModel.includeContactsNameEnabled) .tint(.init(uiColor: .adamant.active)) - .onChange(of: viewModel.includeContactsName) { _ in - viewModel.didToggle() - } Toggle(String.adamant.partnerQR.includePartnerURL, isOn: $viewModel.includeWebAppLink) .tint(.init(uiColor: .adamant.active)) - .onChange(of: viewModel.includeWebAppLink) { _ in - viewModel.didToggle() - } } } diff --git a/Adamant/Modules/PartnerQR/PartnerQRViewModel.swift b/Adamant/Modules/PartnerQR/PartnerQRViewModel.swift index e5eeeff9a..3687508f8 100644 --- a/Adamant/Modules/PartnerQR/PartnerQRViewModel.swift +++ b/Adamant/Modules/PartnerQR/PartnerQRViewModel.swift @@ -13,13 +13,23 @@ import Photos @MainActor final class PartnerQRViewModel: NSObject, ObservableObject { - @Published var includeContactsName = false - @Published var includeWebAppLink = false @Published var includeContactsNameEnabled = true @Published var image: UIImage? @Published var partnerImage: UIImage? @Published var partnerName: String = "" + @Published var includeWebAppLink = false { + didSet { + didToggle() + } + } + + @Published var includeContactsName = false { + didSet { + didToggle() + } + } + private var partner: CoreDataAccount? private let dialogService: DialogService private let addressBookService: AddressBookService @@ -120,7 +130,9 @@ private extension PartnerQRViewModel { return } - if let name = addressBookService.getName(for: address) { + let name = partner?.name ?? addressBookService.getName(for: address) + + if let name = name { partnerName = name includeContactsNameEnabled = true includeContactsName = partnerQRService.isIncludeNameEnabled() @@ -150,8 +162,10 @@ private extension PartnerQRViewModel { var params: [AdamantAddressParam] = [] + let name = partner?.name ?? addressBookService.getName(for: address) + if includeContactsName, - let name = addressBookService.getName(for: address) { + let name = name { params.append(.label(name)) } From 36dd5777ba2f798401eb7e1564f6a8bc24e4d272 Mon Sep 17 00:00:00 2001 From: StanislavDevIOS Date: Tue, 31 Oct 2023 13:44:09 +0200 Subject: [PATCH 39/96] [trello.com/c/K1MFzm7B] feat: generate ADM ID localy --- Adamant/Models/UnregisteredTransaction.swift | 3 + .../AdamantCore/AdamantCore+Extensions.swift | 134 +++++++++++++++++- Adamant/ServiceProtocols/ApiService.swift | 20 ++- .../ApiService/AdamantApi+Chats.swift | 27 ++++ .../ApiService/AdamantApi+Transfers.swift | 63 +------- .../AdamantTransfersProvider.swift | 16 ++- 6 files changed, 184 insertions(+), 79 deletions(-) diff --git a/Adamant/Models/UnregisteredTransaction.swift b/Adamant/Models/UnregisteredTransaction.swift index 593a0b084..e23543153 100644 --- a/Adamant/Models/UnregisteredTransaction.swift +++ b/Adamant/Models/UnregisteredTransaction.swift @@ -8,6 +8,7 @@ import Foundation import CommonKit +import BigInt struct UnregisteredTransaction: Hashable { let type: TransactionType @@ -18,6 +19,7 @@ struct UnregisteredTransaction: Hashable { let amount: Decimal let signature: String let asset: TransactionAsset + let requesterPublicKey: String? } extension UnregisteredTransaction: Codable { @@ -45,6 +47,7 @@ extension UnregisteredTransaction: Codable { let amount = try container.decode(Decimal.self, forKey: .amount) self.amount = amount.shiftedFromAdamant() + self.requesterPublicKey = "" } func encode(to encoder: Encoder) throws { diff --git a/Adamant/ServiceProtocols/AdamantCore/AdamantCore+Extensions.swift b/Adamant/ServiceProtocols/AdamantCore/AdamantCore+Extensions.swift index 7adee88c5..185cc2927 100644 --- a/Adamant/ServiceProtocols/AdamantCore/AdamantCore+Extensions.swift +++ b/Adamant/ServiceProtocols/AdamantCore/AdamantCore+Extensions.swift @@ -8,6 +8,7 @@ import Foundation import CommonKit +import BigInt extension AdamantCore { func makeSignedTransaction( @@ -27,7 +28,138 @@ extension AdamantCore { recipientId: transaction.recipientId, amount: transaction.amount, signature: signature, - asset: transaction.asset + asset: transaction.asset, + requesterPublicKey: transaction.requesterPublicKey ) } } + +// MARK: - Bytes + +extension UnregisteredTransaction { + var id: String { + generateId() + } +} + +private extension UnregisteredTransaction { + func generateId() -> String { + let hash = bytes.sha256() + + guard hash.count > 7 else { return UUID().uuidString } + + var temp: [UInt8] = [] + + for i in 0..<8 { + temp.insert(hash[7 - i], at: i) + } + + guard let value = bigIntFromBuffer(temp, size: 1) else { + return UUID().uuidString + } + + return String(value) + } + + func bigIntFromBuffer(_ buffer: [UInt8], size: Int) -> BigInt? { + if buffer.isEmpty || size <= 0 { + return nil + } + + var chunks: [[UInt8]] = [] + + for i in stride(from: 0, to: buffer.count, by: size) { + let chunk = buffer[i] + chunks.append([chunk]) + } + + let hexStrings = chunks.map { chunk in + return chunk.map { byte in + let hex = String(byte, radix: 16) + return hex.count == 1 ? "0" + hex : hex + }.joined() + } + + let hex = hexStrings.joined() + + return BigInt(hex, radix: 16) + } + + var bytes: [UInt8] { + return typeBytes + + timestampBytes + + senderPublicKeyBytes + + requesterPublicKeyBytes + + recipientIdBytes + + amountBytes + + assetBytes + + signatureBytes + + signSignatureBytes + } + + var typeBytes: [UInt8] { + [UInt8(type.rawValue)] + } + + var timestampBytes: [UInt8] { + ByteBackpacker.pack(UInt32(timestamp), byteOrder: .littleEndian) + } + + var senderPublicKeyBytes: [UInt8] { + senderPublicKey.hexBytes() + } + + var requesterPublicKeyBytes: [UInt8] { + requesterPublicKey?.hexBytes() ?? [] + } + + var recipientIdBytes: [UInt8] { + guard + let value = recipientId?.replacingOccurrences(of: "U", with: ""), + let number = UInt64(value) else { return Bytes(count: 8) } + return ByteBackpacker.pack(number, byteOrder: .bigEndian) + } + + var amountBytes: [UInt8] { + let value = (self.amount.shiftedToAdamant() as NSDecimalNumber).uint64Value + let bytes = ByteBackpacker.pack(value, byteOrder: .littleEndian) + return bytes + } + + var signatureBytes: [UInt8] { + signature.hexBytes() + } + + var signSignatureBytes: [UInt8] { + [] + } + + var assetBytes: [UInt8] { + switch type { + case .chatMessage: + guard let msg = asset.chat?.message, let own = asset.chat?.ownMessage, let type = asset.chat?.type else { return [] } + + return msg.hexBytes() + own.hexBytes() + ByteBackpacker.pack(UInt32(type.rawValue), byteOrder: .littleEndian) + + case .state: + guard let key = asset.state?.key, let value = asset.state?.value, let type = asset.state?.type else { return [] } + + return value.bytes + key.bytes + ByteBackpacker.pack(UInt32(type.rawValue), byteOrder: .littleEndian) + + case .vote: + guard + let votes = asset.votes?.votes + else { return [] } + + var bytes = [UInt8]() + for vote in votes { + bytes += vote.bytes + } + + return bytes + + default: + return [] + } + } +} diff --git a/Adamant/ServiceProtocols/ApiService.swift b/Adamant/ServiceProtocols/ApiService.swift index 60c72224b..18687fe16 100644 --- a/Adamant/ServiceProtocols/ApiService.swift +++ b/Adamant/ServiceProtocols/ApiService.swift @@ -136,18 +136,7 @@ protocol ApiService: Actor { // MARK: - Funds func transferFunds( - sender: String, - recipient: String, - amount: Decimal, - keypair: Keypair, - completion: @escaping (ApiServiceResult) -> Void - ) - - func transferFunds( - sender: String, - recipient: String, - amount: Decimal, - keypair: Keypair + transaction: UnregisteredTransaction ) async throws -> UInt64 // MARK: - States @@ -228,6 +217,13 @@ protocol ApiService: Actor { amount: Decimal? ) -> UnregisteredTransaction? + func createSendTransaction( + sender: String, + recipient: String, + amount: Decimal, + keypair: Keypair + ) throws -> UnregisteredTransaction + func sendTransaction( transaction: UnregisteredTransaction ) async throws -> UInt64 diff --git a/Adamant/Services/ApiService/AdamantApi+Chats.swift b/Adamant/Services/ApiService/AdamantApi+Chats.swift index 7c0a348c3..8170dd372 100644 --- a/Adamant/Services/ApiService/AdamantApi+Chats.swift +++ b/Adamant/Services/ApiService/AdamantApi+Chats.swift @@ -168,6 +168,33 @@ extension AdamantApiService { return transaction } + func createSendTransaction( + sender: String, + recipient: String, + amount: Decimal, + keypair: Keypair + ) throws -> UnregisteredTransaction { + let normalizedTransaction = NormalizedTransaction( + type: .send, + amount: amount, + senderPublicKey: keypair.publicKey, + requesterPublicKey: nil, + date: lastRequestTimeDelta.map { Date().addingTimeInterval(-$0) } ?? Date(), + recipientId: recipient, + asset: .init() + ) + + guard let transaction = adamantCore.makeSignedTransaction( + transaction: normalizedTransaction, + senderId: sender, + keypair: keypair + ) else { + throw InternalError.signTransactionFailed + } + + return transaction + } + func sendTransaction( transaction: UnregisteredTransaction ) async throws -> UInt64 { diff --git a/Adamant/Services/ApiService/AdamantApi+Transfers.swift b/Adamant/Services/ApiService/AdamantApi+Transfers.swift index 2e10d2ca1..9b3beda4f 100644 --- a/Adamant/Services/ApiService/AdamantApi+Transfers.swift +++ b/Adamant/Services/ApiService/AdamantApi+Transfers.swift @@ -8,70 +8,13 @@ import Foundation import CommonKit +import CryptoSwift +import BigInt extension AdamantApiService { - func transferFunds(sender: String, recipient: String, amount: Decimal, keypair: Keypair, completion: @escaping (ApiServiceResult) -> Void) { - let normalizedTransaction = NormalizedTransaction( - type: .send, - amount: amount, - senderPublicKey: keypair.publicKey, - requesterPublicKey: nil, - date: lastRequestTimeDelta.map { Date().addingTimeInterval(-$0) } ?? Date(), - recipientId: recipient, - asset: .init() - ) - - guard let transaction = adamantCore.makeSignedTransaction( - transaction: normalizedTransaction, - senderId: sender, - keypair: keypair - ) else { - completion(.failure(InternalError.signTransactionFailed.apiServiceErrorWith(error: nil))) - return - } - - sendTransaction( - path: ApiCommands.Transactions.processTransaction, - transaction: transaction - ) { response in - switch response { - case .success(let result): - if let id = result.transactionId { - completion(.success(id)) - } else { - completion(.failure(.internalError(message: result.error ?? "Unknown Error", error: nil))) - } - - case .failure(let error): - completion(.failure(error)) - } - } - } - func transferFunds( - sender: String, - recipient: String, - amount: Decimal, - keypair: Keypair + transaction: UnregisteredTransaction ) async throws -> UInt64 { - let normalizedTransaction = NormalizedTransaction( - type: .send, - amount: amount, - senderPublicKey: keypair.publicKey, - requesterPublicKey: nil, - date: lastRequestTimeDelta.map { Date().addingTimeInterval(-$0) } ?? Date(), - recipientId: recipient, - asset: .init() - ) - - guard let transaction = adamantCore.makeSignedTransaction( - transaction: normalizedTransaction, - senderId: sender, - keypair: keypair - ) else { - throw InternalError.signTransactionFailed.apiServiceErrorWith(error: nil) - } - return try await withUnsafeThrowingContinuation { (continuation: UnsafeContinuation) in sendTransaction( path: ApiCommands.Transactions.processTransaction, diff --git a/Adamant/Services/DataProviders/AdamantTransfersProvider.swift b/Adamant/Services/DataProviders/AdamantTransfersProvider.swift index 079ad8c5c..19dfef008 100644 --- a/Adamant/Services/DataProviders/AdamantTransfersProvider.swift +++ b/Adamant/Services/DataProviders/AdamantTransfersProvider.swift @@ -621,6 +621,13 @@ extension AdamantTransfersProvider { } // MARK: 2. Create transaction + let signedTransaction = try await apiService.createSendTransaction( + sender: loggedAccount.address, + recipient: recipient, + amount: amount, + keypair: keypair + ) + let transaction = TransferTransaction(context: context) transaction.amount = amount as NSDecimalNumber transaction.date = Date() as NSDate @@ -631,9 +638,9 @@ extension AdamantTransfersProvider { transaction.showsChatroom = false transaction.fee = Self.transferFee as NSDecimalNumber - transaction.transactionId = UUID().uuidString + transaction.transactionId = signedTransaction.id transaction.blockId = nil - transaction.chatMessageId = UUID().uuidString + transaction.chatMessageId = signedTransaction.id transaction.statusEnum = MessageStatus.pending // MARK: 3. Chatroom @@ -667,10 +674,7 @@ extension AdamantTransfersProvider { // MARK: 5. Send do { let id = try await apiService.transferFunds( - sender: loggedAccount.address, - recipient: recipient, - amount: amount, - keypair: keypair + transaction: signedTransaction ) transaction.transactionId = String(id) From 152957ace2510a513c20a7a312addbf5c5be33fe Mon Sep 17 00:00:00 2001 From: StanislavDevIOS Date: Thu, 2 Nov 2023 10:48:32 +0200 Subject: [PATCH 40/96] [trello.com/c/K1MFzm7B] code refactoring --- .../AdamantCore/AdamantCore+Extensions.swift | 10 +++------- .../DataProviders/AdamantTransfersProvider.swift | 6 ++++-- 2 files changed, 7 insertions(+), 9 deletions(-) diff --git a/Adamant/ServiceProtocols/AdamantCore/AdamantCore+Extensions.swift b/Adamant/ServiceProtocols/AdamantCore/AdamantCore+Extensions.swift index 185cc2927..f9c78ddd7 100644 --- a/Adamant/ServiceProtocols/AdamantCore/AdamantCore+Extensions.swift +++ b/Adamant/ServiceProtocols/AdamantCore/AdamantCore+Extensions.swift @@ -37,12 +37,6 @@ extension AdamantCore { // MARK: - Bytes extension UnregisteredTransaction { - var id: String { - generateId() - } -} - -private extension UnregisteredTransaction { func generateId() -> String { let hash = bytes.sha256() @@ -60,7 +54,9 @@ private extension UnregisteredTransaction { return String(value) } - +} + +private extension UnregisteredTransaction { func bigIntFromBuffer(_ buffer: [UInt8], size: Int) -> BigInt? { if buffer.isEmpty || size <= 0 { return nil diff --git a/Adamant/Services/DataProviders/AdamantTransfersProvider.swift b/Adamant/Services/DataProviders/AdamantTransfersProvider.swift index 19dfef008..0567245db 100644 --- a/Adamant/Services/DataProviders/AdamantTransfersProvider.swift +++ b/Adamant/Services/DataProviders/AdamantTransfersProvider.swift @@ -628,6 +628,8 @@ extension AdamantTransfersProvider { keypair: keypair ) + let locallyID = signedTransaction.generateId() + let transaction = TransferTransaction(context: context) transaction.amount = amount as NSDecimalNumber transaction.date = Date() as NSDate @@ -638,9 +640,9 @@ extension AdamantTransfersProvider { transaction.showsChatroom = false transaction.fee = Self.transferFee as NSDecimalNumber - transaction.transactionId = signedTransaction.id + transaction.transactionId = locallyID transaction.blockId = nil - transaction.chatMessageId = signedTransaction.id + transaction.chatMessageId = locallyID transaction.statusEnum = MessageStatus.pending // MARK: 3. Chatroom From 71cf7a642234989420b9cd43d8a94f8472fb6213 Mon Sep 17 00:00:00 2001 From: StanislavDevIOS Date: Thu, 2 Nov 2023 10:59:13 +0200 Subject: [PATCH 41/96] [trello.com/c/K1MFzm7B] made "generateId" optional --- .../AdamantCore/AdamantCore+Extensions.swift | 6 +++--- .../Services/DataProviders/AdamantTransfersProvider.swift | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/Adamant/ServiceProtocols/AdamantCore/AdamantCore+Extensions.swift b/Adamant/ServiceProtocols/AdamantCore/AdamantCore+Extensions.swift index f9c78ddd7..f89f84d9b 100644 --- a/Adamant/ServiceProtocols/AdamantCore/AdamantCore+Extensions.swift +++ b/Adamant/ServiceProtocols/AdamantCore/AdamantCore+Extensions.swift @@ -37,10 +37,10 @@ extension AdamantCore { // MARK: - Bytes extension UnregisteredTransaction { - func generateId() -> String { + func generateId() -> String? { let hash = bytes.sha256() - guard hash.count > 7 else { return UUID().uuidString } + guard hash.count > 7 else { return nil } var temp: [UInt8] = [] @@ -49,7 +49,7 @@ extension UnregisteredTransaction { } guard let value = bigIntFromBuffer(temp, size: 1) else { - return UUID().uuidString + return nil } return String(value) diff --git a/Adamant/Services/DataProviders/AdamantTransfersProvider.swift b/Adamant/Services/DataProviders/AdamantTransfersProvider.swift index 0567245db..bcd347c2f 100644 --- a/Adamant/Services/DataProviders/AdamantTransfersProvider.swift +++ b/Adamant/Services/DataProviders/AdamantTransfersProvider.swift @@ -628,7 +628,7 @@ extension AdamantTransfersProvider { keypair: keypair ) - let locallyID = signedTransaction.generateId() + let locallyID = signedTransaction.generateId() ?? UUID().uuidString let transaction = TransferTransaction(context: context) transaction.amount = amount as NSDecimalNumber From 68a62f708ec08ac778b933d62a9cae66b3997069 Mon Sep 17 00:00:00 2001 From: StanislavDevIOS Date: Thu, 2 Nov 2023 12:40:23 +0200 Subject: [PATCH 42/96] [trello.com/c/PNNpGdAn] fix: wrong state for switchers --- Adamant/Modules/PartnerQR/PartnerQRView.swift | 6 ++++++ Adamant/Modules/PartnerQR/PartnerQRViewModel.swift | 14 ++------------ 2 files changed, 8 insertions(+), 12 deletions(-) diff --git a/Adamant/Modules/PartnerQR/PartnerQRView.swift b/Adamant/Modules/PartnerQR/PartnerQRView.swift index f54e4ed00..f4d268d9f 100644 --- a/Adamant/Modules/PartnerQR/PartnerQRView.swift +++ b/Adamant/Modules/PartnerQR/PartnerQRView.swift @@ -65,9 +65,15 @@ private extension PartnerQRView { Toggle(String.adamant.partnerQR.includePartnerName, isOn: $viewModel.includeContactsName) .disabled(!viewModel.includeContactsNameEnabled) .tint(.init(uiColor: .adamant.active)) + .onChange(of: viewModel.includeContactsName) { _ in + viewModel.didToggle() + } Toggle(String.adamant.partnerQR.includePartnerURL, isOn: $viewModel.includeWebAppLink) .tint(.init(uiColor: .adamant.active)) + .onChange(of: viewModel.includeWebAppLink) { _ in + viewModel.didToggle() + } } } diff --git a/Adamant/Modules/PartnerQR/PartnerQRViewModel.swift b/Adamant/Modules/PartnerQR/PartnerQRViewModel.swift index 3687508f8..1da8120ec 100644 --- a/Adamant/Modules/PartnerQR/PartnerQRViewModel.swift +++ b/Adamant/Modules/PartnerQR/PartnerQRViewModel.swift @@ -17,18 +17,8 @@ final class PartnerQRViewModel: NSObject, ObservableObject { @Published var image: UIImage? @Published var partnerImage: UIImage? @Published var partnerName: String = "" - - @Published var includeWebAppLink = false { - didSet { - didToggle() - } - } - - @Published var includeContactsName = false { - didSet { - didToggle() - } - } + @Published var includeWebAppLink = false + @Published var includeContactsName = false private var partner: CoreDataAccount? private let dialogService: DialogService From 1b95fd721104e17bffa437a96341d39892a3db86 Mon Sep 17 00:00:00 2001 From: StanislavDevIOS Date: Thu, 2 Nov 2023 18:24:57 +0200 Subject: [PATCH 43/96] [trello.com/c/A2iaLNq6] fix open adm transaction from chat --- Adamant/Modules/Wallets/Adamant/AdmWalletFactory.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Adamant/Modules/Wallets/Adamant/AdmWalletFactory.swift b/Adamant/Modules/Wallets/Adamant/AdmWalletFactory.swift index 45dfbe682..82772bc15 100644 --- a/Adamant/Modules/Wallets/Adamant/AdmWalletFactory.swift +++ b/Adamant/Modules/Wallets/Adamant/AdmWalletFactory.swift @@ -67,7 +67,7 @@ struct AdmWalletFactory: WalletFactory { func makeDetailsVC(transaction: TransferTransaction, screensFactory: ScreensFactory) -> UIViewController { let controller = makeTransactionDetailsVC(screensFactory: screensFactory) - controller.transaction = transaction + controller.adamantTransaction = transaction controller.comment = transaction.comment controller.senderId = transaction.senderId controller.recipientId = transaction.recipientId From a43faccfac008b981276b2a35841cb716f7a4ce8 Mon Sep 17 00:00:00 2001 From: StanislavDevIOS Date: Thu, 2 Nov 2023 18:25:41 +0200 Subject: [PATCH 44/96] [trello.com/c/A2iaLNq6] apply new tokens from repository --- .../bzz_notification.imageset/Contents.json | 23 ++++++++++++++++ .../bzz_notification.png | Bin 0 -> 839 bytes .../bzz_notification@2x.png | Bin 0 -> 1425 bytes .../bzz_notification@3x.png | Bin 0 -> 2164 bytes .../Wallets/bzz_wallet.imageset/Contents.json | 23 ++++++++++++++++ .../bzz_wallet.imageset/bzz_wallet.png | Bin 0 -> 839 bytes .../bzz_wallet.imageset/bzz_wallet@2x.png | Bin 0 -> 1425 bytes .../bzz_wallet.imageset/bzz_wallet@3x.png | Bin 0 -> 2164 bytes .../bzz_wallet_row.imageset/Contents.json | 23 ++++++++++++++++ .../bzz_wallet_row.png | Bin 0 -> 839 bytes .../bzz_wallet_row@2x.png | Bin 0 -> 1425 bytes .../bzz_wallet_row@3x.png | Bin 0 -> 2164 bytes .../flux_notification.imageset/Contents.json | 23 ++++++++++++++++ .../flux_notification.png | Bin 0 -> 1277 bytes .../flux_notification@2x.png | Bin 0 -> 2493 bytes .../flux_notification@3x.png | Bin 0 -> 3621 bytes .../flux_wallet.imageset/Contents.json | 23 ++++++++++++++++ .../flux_wallet.imageset/flux_wallet.png | Bin 0 -> 1277 bytes .../flux_wallet.imageset/flux_wallet@2x.png | Bin 0 -> 2493 bytes .../flux_wallet.imageset/flux_wallet@3x.png | Bin 0 -> 3621 bytes .../flux_wallet_row.imageset/Contents.json | 23 ++++++++++++++++ .../flux_wallet_row.png | Bin 0 -> 1277 bytes .../flux_wallet_row@2x.png | Bin 0 -> 2493 bytes .../flux_wallet_row@3x.png | Bin 0 -> 3621 bytes .../CommonKit/Models/ethereumTokensList.swift | 26 +++++++++++++++++- .../WalletImages/bzz_notificationContent.png | Bin 0 -> 2164 bytes .../WalletImages/flux_notificationContent.png | Bin 0 -> 3621 bytes 27 files changed, 163 insertions(+), 1 deletion(-) create mode 100644 CommonKit/Sources/CommonKit/Assets/Shared.xcassets/Wallets/bzz_notification.imageset/Contents.json create mode 100644 CommonKit/Sources/CommonKit/Assets/Shared.xcassets/Wallets/bzz_notification.imageset/bzz_notification.png create mode 100644 CommonKit/Sources/CommonKit/Assets/Shared.xcassets/Wallets/bzz_notification.imageset/bzz_notification@2x.png create mode 100644 CommonKit/Sources/CommonKit/Assets/Shared.xcassets/Wallets/bzz_notification.imageset/bzz_notification@3x.png create mode 100644 CommonKit/Sources/CommonKit/Assets/Shared.xcassets/Wallets/bzz_wallet.imageset/Contents.json create mode 100644 CommonKit/Sources/CommonKit/Assets/Shared.xcassets/Wallets/bzz_wallet.imageset/bzz_wallet.png create mode 100644 CommonKit/Sources/CommonKit/Assets/Shared.xcassets/Wallets/bzz_wallet.imageset/bzz_wallet@2x.png create mode 100644 CommonKit/Sources/CommonKit/Assets/Shared.xcassets/Wallets/bzz_wallet.imageset/bzz_wallet@3x.png create mode 100644 CommonKit/Sources/CommonKit/Assets/Shared.xcassets/Wallets/bzz_wallet_row.imageset/Contents.json create mode 100644 CommonKit/Sources/CommonKit/Assets/Shared.xcassets/Wallets/bzz_wallet_row.imageset/bzz_wallet_row.png create mode 100644 CommonKit/Sources/CommonKit/Assets/Shared.xcassets/Wallets/bzz_wallet_row.imageset/bzz_wallet_row@2x.png create mode 100644 CommonKit/Sources/CommonKit/Assets/Shared.xcassets/Wallets/bzz_wallet_row.imageset/bzz_wallet_row@3x.png create mode 100644 CommonKit/Sources/CommonKit/Assets/Shared.xcassets/Wallets/flux_notification.imageset/Contents.json create mode 100644 CommonKit/Sources/CommonKit/Assets/Shared.xcassets/Wallets/flux_notification.imageset/flux_notification.png create mode 100644 CommonKit/Sources/CommonKit/Assets/Shared.xcassets/Wallets/flux_notification.imageset/flux_notification@2x.png create mode 100644 CommonKit/Sources/CommonKit/Assets/Shared.xcassets/Wallets/flux_notification.imageset/flux_notification@3x.png create mode 100644 CommonKit/Sources/CommonKit/Assets/Shared.xcassets/Wallets/flux_wallet.imageset/Contents.json create mode 100644 CommonKit/Sources/CommonKit/Assets/Shared.xcassets/Wallets/flux_wallet.imageset/flux_wallet.png create mode 100644 CommonKit/Sources/CommonKit/Assets/Shared.xcassets/Wallets/flux_wallet.imageset/flux_wallet@2x.png create mode 100644 CommonKit/Sources/CommonKit/Assets/Shared.xcassets/Wallets/flux_wallet.imageset/flux_wallet@3x.png create mode 100644 CommonKit/Sources/CommonKit/Assets/Shared.xcassets/Wallets/flux_wallet_row.imageset/Contents.json create mode 100644 CommonKit/Sources/CommonKit/Assets/Shared.xcassets/Wallets/flux_wallet_row.imageset/flux_wallet_row.png create mode 100644 CommonKit/Sources/CommonKit/Assets/Shared.xcassets/Wallets/flux_wallet_row.imageset/flux_wallet_row@2x.png create mode 100644 CommonKit/Sources/CommonKit/Assets/Shared.xcassets/Wallets/flux_wallet_row.imageset/flux_wallet_row@3x.png create mode 100644 NotificationServiceExtension/WalletImages/bzz_notificationContent.png create mode 100644 NotificationServiceExtension/WalletImages/flux_notificationContent.png diff --git a/CommonKit/Sources/CommonKit/Assets/Shared.xcassets/Wallets/bzz_notification.imageset/Contents.json b/CommonKit/Sources/CommonKit/Assets/Shared.xcassets/Wallets/bzz_notification.imageset/Contents.json new file mode 100644 index 000000000..055207333 --- /dev/null +++ b/CommonKit/Sources/CommonKit/Assets/Shared.xcassets/Wallets/bzz_notification.imageset/Contents.json @@ -0,0 +1,23 @@ +{ + "images" : [ + { + "idiom" : "universal", + "scale" : "1x", + "filename" : "bzz_notification.png" + }, + { + "idiom" : "universal", + "scale" : "2x", + "filename" : "bzz_notification@2x.png" + }, + { + "idiom" : "universal", + "scale" : "3x", + "filename" : "bzz_notification@3x.png" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} diff --git a/CommonKit/Sources/CommonKit/Assets/Shared.xcassets/Wallets/bzz_notification.imageset/bzz_notification.png b/CommonKit/Sources/CommonKit/Assets/Shared.xcassets/Wallets/bzz_notification.imageset/bzz_notification.png new file mode 100644 index 0000000000000000000000000000000000000000..0b5e052282a071975bce295ef000e3c8ee7ee6ff GIT binary patch literal 839 zcmV-N1GxN&P)&p{}GC1h8#^w0>N1V||v>p_)u~__q zfC=pPHz@Gq3Cj{ojivhZ9a=}75Zn12!>|AwJ6BB?v;>Q|G(Ys79`2%0}(a;bZ zEDy@RBtc@G`s{G{k_+yw(sG;RUOaG}2L>xFbsC0jCWljoldm-l^f{o;R@d`=yGlH- zbi>KVs3E>^Px}JixNevw0wbW~K(Le1FvBv(KUX{_qiq%mb&KW5EncK}Ar2VV93jsG z$G!F92pB^FTb@tX9CRk{xdKKu1eE83yLiiR6QyV`SbUcOil)#1QqR#^_X`2{yk?qg zM}fvC4ZFl)Ny+t*qp_OB%n_>cO`xwXbh?~&5eT^EXnEh|pZ`-pp*8e%2w0Am87{+R zxC}Q1+#x1o_cY0V23fR*;wkEcTFV;AF6JRg1R_P3j5W#`t5A(H8mUG|T?Hs&pw9X*BRr5i^RpgW%Y~-twXc zemUvUAnLp_sm>)sEi4VPatX7-0#!noxT@9j`8gY9KGqyCq-NN60m^qMuXGKog^Ltd zB^dO49syrz2W=S2OZHn1@VjHbtr0F^SZ}~n&u8e(z#wE@K7?W7rZVmDvkcc1_8#a2 zI{9gtxT7q{BQ5-Y1-jYDV4Y-er(C;(@kQN5PMkAbhRbjnF2iNG442_DT!!<6YbWs2 zI=+wMli=t`(qTj>+RNSdFY@>8h>*lH7s_@&A$nf0r2)hf9b%%hiBE zT(v;Ph`HSesZ?P~SXwkhVk46fc#_r7u%QZ=@rbZ+I3p)6?eZk9!hkc#j)q->`^2nK zvvk5K^HHZd*I-b$5$!n~jJ{+?(;YQsNlb6+HI~rG<4fAq5-SgZ%_s941{rCGk-|zFycPBgVSJP7i0AN4q4AFDP zV|Jygyu&^}gnY+zlN^1L&c}u&r3NL00)&uQYABo(9TXnw85$Il9^W5|2Y@{o5|QAQ zHX*5ah|c5uycHhd9CKF}prC>9<{;5FnstNNx+ zbXxas+g3jZL&T%0T>ogY?RBTRu9>-stn_!bCeOB5w|aCmJ@!V3{hs{#x=Gb*wm+S?)WhLkmKN+6nvkaaN?XO ztF0_)1G)IK$wGdnRHs^KDuNw}9t6(PM+9$f=hT|~ARc%a5tcVsRO9hzGE=1FnXk*1~l#4U^>UFxfee7HHv zvKi_@@+{`A0O=ARP2_PE$<3u=V>8pC1b(n|H9FuKF;|Kx&5=B6K3U7$=8U9=bwk<@ zl6rdwitSeF?>+vO)rOZMRU*u3_iY|fIwZg%yg=~#F`lo#;_8b*@p{ekw$N8FqoH7o z9u2oWHBnm1UY_wah%hPWEDYFt97*%bpa@DWuf(R9U7T!HmARM#+?3Z%MJ8l8O%bOm zgwt{Wh_KIa3@WT-m!R{@Q)zP47&L7bLI07`wd_efGGK)>pN}46ToX&nCe@e`pE8la zSYr?Dq*fp)NdMAk%4G$LB|mLV!hJiMu@|1eY=r3uJ7#Slue~(%8lQgLXnnWQid&Tz zDz&yLGAk!9F|>z*E%nq(0@)*@CKgzle)CR(&pG}>5RR5Z)}2fticgVF^i#AC;TcT| zu6!$$Uz`dchVs-LTs%6soZb ztZiCyp<1bdKi618+srd==`2ilT~BPIhOvWIqM)qgd5yDITR`5Mw%Ins^%#Ga=L7^R8)d5zPc&O+Hm`LC8LVDEN=0?J)jH4E;nBLBVORQKO#SwgKbRSAJVA!@T)Patwb|oXGUAdrGCxi12pBM6JEJ)@ zizjXQg9t*Ny$y;^MIg;nl3eYO@K%=nfSlo260()?Csy}+6C#9g*lj-l=7~oeVy;b} z@^aal=Ua`Dg>W0^A%T$|9rN(4X1F2UXrC^70vm)dkX>gzOv%m z<)BGIOF;d7x~#8XAmX~aXIZps?!iqZ1B~jQa?6=j6Tx>O1U{;hAJhC>1=3EoeBO3% zfQO31X$+0^8Y+wuC|*L#z)k&;W{~Qo^6OJo_z}9O3gHF^{d6bX8$Mz7`xYDDWz4kWCRk5{^ZVD+q z^Fn;lWvwosGpOyu7(?&l9M^2tNXM#%sOtf_X#PF+VD^N}*3VkO5b$}qO)jdd4J+CL ap8~!R!CTf-qK$Vx79cs2iH-Jw*?$9V^OS}F literal 0 HcmV?d00001 diff --git a/CommonKit/Sources/CommonKit/Assets/Shared.xcassets/Wallets/bzz_notification.imageset/bzz_notification@3x.png b/CommonKit/Sources/CommonKit/Assets/Shared.xcassets/Wallets/bzz_notification.imageset/bzz_notification@3x.png new file mode 100644 index 0000000000000000000000000000000000000000..e31fa972d20b7c83f26b57ab0d84e109abfb701a GIT binary patch literal 2164 zcmZ{mX)qg#7ROU=)pFIH)KU~(o?2>oPc22XC>0@52_h(M$wku&i7f=Zy{fI9#G0V> zNGXvnln}Z-OT|)+HBF;^q?SaAl$2^+oq5ygdo%CDIsgBE=6pEk!*Ax?Itz1CR@71i z007GFU>Ep~)_yN}xt;EC%z?Wjnz63_u?Xz%u?ZJ&Xn<1~_9FVA`;`mfXgK;p7$N2* z8Uz5ytGl~6`6O~h6>sCT-1cXLpD=S=0GylEO}J|JRL5~Q{57EZkE_P7;?LL0aQ=v^ zQbNe+{7o{Yr|5R-zG!Isetsw(?<%1Ez4 z*JX{4xTLX5RTpP+ea%em;OogFuFrcD0&XL~!scih5wMwice0^WyT-)E24dYIT(NWt zxLZtrY|E);Q-04P3*-rRor4L9c33*+7H_i1AV=H2W&qh!A3rt&@Y-&axol4@$@BZ7 zdMt7b@c~*xJY@q>vdmhZF}qX&YUR!a#M(0Cj5u>WFS2wgBs$`q-xr=M*EpW<0+Cqs zv2tI@RCK)a*J*|6FxF4MnrANMTfm}JH4QW?ikl%SNicTy5QZ_Q2EwLlMMse}h?Vab zpe+RA z6C{FN0vEHvQitClK%mhsqh0yHd?4T_^M7-Ok)X1KE>0>ktmRd-JZtf@3}95>uk{N| zI6$I9Xl>FAx_fi$?Cn}mA-)Nh8?!Pe)@T?F+)xgNs3EKq3zc25gMY_*nlm4;n=oKa zCs?HEgt$~0+}F5X?9$I6AZDpPCZWa29*5B#EM`-q$0?C1YDwN@?9aK)=P&d7XoVpQ z_gljd5}{w5#|Df~v_$Vc;(E~;H+LL?&?EUQEeu$TzV zDw)w%;U~=N3kDR&9w`o5HI7z0h~-n35yi=!ui8}3hJ+~j7XJF`%}tBk7XL%5fprJe zSBc2og3yn16rcW^S^AI&LMC=9bQY@Z-*=Kpqhu18sVQqHE?T26ryU&31RzrUYJ^U}?gmw5eW%Nw5evmBv;}i(HCm-{WO(DX*#-=Pe z?p$uxDg#~(!g0P`pFGq~Re|2O|P(LV^!?;zXs zG(R~ed`cKkI`=#$RgdZhV%c_0ODi_wvd~e{8|q9{I}c9atSQuCzie`m*Jt>n*r|wT z>nT-W;jxQ+?{N(+iBRs7`C5yfDz+L2+Y{7Cw^uLWMSqjA?G?W-*!)Ibfi^z~&$L0< z_R6xY4$oMBQo*fN+@qduVDx?dG@>zC+})9&(pofkXyK%8&;xPk_W8v(PW}Hh3r9ur zi6jj0CJJAPwJd4Pbbfq#g=QSCOqhQWL^?ZEzPjayW*I6@>2^~ZsQMr7JYpV|we=X1 z*cUxmSRH&E{=O+TF%$)Fis?OyM!vn%r7sxto!$)a>ed!^B>8zXz4ROcu5OXmPj(71 z4jR~dI2dHus;s{(v@*?4vvX`h&X^x0@SGBeTKseBv$;a;u%i6R`IdjYoy&BXEf;EC zC;CqNRwE|U8yPvvO~oKp3}i5{$;brT(=U1LYVJHSamIu{nofaBDlv+Klsl_iLoVe> zImQC-6{6Yu#m<^WXWJ|W1L|`^4{?cKO!hUT<>}*c+%4BG?bBee$pPdbgg2ikIy+s1 zU|5s>-BJe6D#sWPQ1VO{Dcz$DgN=#O4@=~Lhx%x>ew0hfm5l4!la$%oCl gt|@5eH-o2up*oWjh6f`dJ8uNQ-4*82&p{}GC1h8#^w0>N1V||v>p_)u~__q zfC=pPHz@Gq3Cj{ojivhZ9a=}75Zn12!>|AwJ6BB?v;>Q|G(Ys79`2%0}(a;bZ zEDy@RBtc@G`s{G{k_+yw(sG;RUOaG}2L>xFbsC0jCWljoldm-l^f{o;R@d`=yGlH- zbi>KVs3E>^Px}JixNevw0wbW~K(Le1FvBv(KUX{_qiq%mb&KW5EncK}Ar2VV93jsG z$G!F92pB^FTb@tX9CRk{xdKKu1eE83yLiiR6QyV`SbUcOil)#1QqR#^_X`2{yk?qg zM}fvC4ZFl)Ny+t*qp_OB%n_>cO`xwXbh?~&5eT^EXnEh|pZ`-pp*8e%2w0Am87{+R zxC}Q1+#x1o_cY0V23fR*;wkEcTFV;AF6JRg1R_P3j5W#`t5A(H8mUG|T?Hs&pw9X*BRr5i^RpgW%Y~-twXc zemUvUAnLp_sm>)sEi4VPatX7-0#!noxT@9j`8gY9KGqyCq-NN60m^qMuXGKog^Ltd zB^dO49syrz2W=S2OZHn1@VjHbtr0F^SZ}~n&u8e(z#wE@K7?W7rZVmDvkcc1_8#a2 zI{9gtxT7q{BQ5-Y1-jYDV4Y-er(C;(@kQN5PMkAbhRbjnF2iNG442_DT!!<6YbWs2 zI=+wMli=t`(qTj>+RNSdFY@>8h>*lH7s_@&A$nf0r2)hf9b%%hiBE zT(v;Ph`HSesZ?P~SXwkhVk46fc#_r7u%QZ=@rbZ+I3p)6?eZk9!hkc#j)q->`^2nK zvvk5K^HHZd*I-b$5$!n~jJ{+?(;YQsNlb6+HI~rG<4fAq5-SgZ%_s941{rCGk-|zFycPBgVSJP7i0AN4q4AFDP zV|Jygyu&^}gnY+zlN^1L&c}u&r3NL00)&uQYABo(9TXnw85$Il9^W5|2Y@{o5|QAQ zHX*5ah|c5uycHhd9CKF}prC>9<{;5FnstNNx+ zbXxas+g3jZL&T%0T>ogY?RBTRu9>-stn_!bCeOB5w|aCmJ@!V3{hs{#x=Gb*wm+S?)WhLkmKN+6nvkaaN?XO ztF0_)1G)IK$wGdnRHs^KDuNw}9t6(PM+9$f=hT|~ARc%a5tcVsRO9hzGE=1FnXk*1~l#4U^>UFxfee7HHv zvKi_@@+{`A0O=ARP2_PE$<3u=V>8pC1b(n|H9FuKF;|Kx&5=B6K3U7$=8U9=bwk<@ zl6rdwitSeF?>+vO)rOZMRU*u3_iY|fIwZg%yg=~#F`lo#;_8b*@p{ekw$N8FqoH7o z9u2oWHBnm1UY_wah%hPWEDYFt97*%bpa@DWuf(R9U7T!HmARM#+?3Z%MJ8l8O%bOm zgwt{Wh_KIa3@WT-m!R{@Q)zP47&L7bLI07`wd_efGGK)>pN}46ToX&nCe@e`pE8la zSYr?Dq*fp)NdMAk%4G$LB|mLV!hJiMu@|1eY=r3uJ7#Slue~(%8lQgLXnnWQid&Tz zDz&yLGAk!9F|>z*E%nq(0@)*@CKgzle)CR(&pG}>5RR5Z)}2fticgVF^i#AC;TcT| zu6!$$Uz`dchVs-LTs%6soZb ztZiCyp<1bdKi618+srd==`2ilT~BPIhOvWIqM)qgd5yDITR`5Mw%Ins^%#Ga=L7^R8)d5zPc&O+Hm`LC8LVDEN=0?J)jH4E;nBLBVORQKO#SwgKbRSAJVA!@T)Patwb|oXGUAdrGCxi12pBM6JEJ)@ zizjXQg9t*Ny$y;^MIg;nl3eYO@K%=nfSlo260()?Csy}+6C#9g*lj-l=7~oeVy;b} z@^aal=Ua`Dg>W0^A%T$|9rN(4X1F2UXrC^70vm)dkX>gzOv%m z<)BGIOF;d7x~#8XAmX~aXIZps?!iqZ1B~jQa?6=j6Tx>O1U{;hAJhC>1=3EoeBO3% zfQO31X$+0^8Y+wuC|*L#z)k&;W{~Qo^6OJo_z}9O3gHF^{d6bX8$Mz7`xYDDWz4kWCRk5{^ZVD+q z^Fn;lWvwosGpOyu7(?&l9M^2tNXM#%sOtf_X#PF+VD^N}*3VkO5b$}qO)jdd4J+CL ap8~!R!CTf-qK$Vx79cs2iH-Jw*?$9V^OS}F literal 0 HcmV?d00001 diff --git a/CommonKit/Sources/CommonKit/Assets/Shared.xcassets/Wallets/bzz_wallet.imageset/bzz_wallet@3x.png b/CommonKit/Sources/CommonKit/Assets/Shared.xcassets/Wallets/bzz_wallet.imageset/bzz_wallet@3x.png new file mode 100644 index 0000000000000000000000000000000000000000..e31fa972d20b7c83f26b57ab0d84e109abfb701a GIT binary patch literal 2164 zcmZ{mX)qg#7ROU=)pFIH)KU~(o?2>oPc22XC>0@52_h(M$wku&i7f=Zy{fI9#G0V> zNGXvnln}Z-OT|)+HBF;^q?SaAl$2^+oq5ygdo%CDIsgBE=6pEk!*Ax?Itz1CR@71i z007GFU>Ep~)_yN}xt;EC%z?Wjnz63_u?Xz%u?ZJ&Xn<1~_9FVA`;`mfXgK;p7$N2* z8Uz5ytGl~6`6O~h6>sCT-1cXLpD=S=0GylEO}J|JRL5~Q{57EZkE_P7;?LL0aQ=v^ zQbNe+{7o{Yr|5R-zG!Isetsw(?<%1Ez4 z*JX{4xTLX5RTpP+ea%em;OogFuFrcD0&XL~!scih5wMwice0^WyT-)E24dYIT(NWt zxLZtrY|E);Q-04P3*-rRor4L9c33*+7H_i1AV=H2W&qh!A3rt&@Y-&axol4@$@BZ7 zdMt7b@c~*xJY@q>vdmhZF}qX&YUR!a#M(0Cj5u>WFS2wgBs$`q-xr=M*EpW<0+Cqs zv2tI@RCK)a*J*|6FxF4MnrANMTfm}JH4QW?ikl%SNicTy5QZ_Q2EwLlMMse}h?Vab zpe+RA z6C{FN0vEHvQitClK%mhsqh0yHd?4T_^M7-Ok)X1KE>0>ktmRd-JZtf@3}95>uk{N| zI6$I9Xl>FAx_fi$?Cn}mA-)Nh8?!Pe)@T?F+)xgNs3EKq3zc25gMY_*nlm4;n=oKa zCs?HEgt$~0+}F5X?9$I6AZDpPCZWa29*5B#EM`-q$0?C1YDwN@?9aK)=P&d7XoVpQ z_gljd5}{w5#|Df~v_$Vc;(E~;H+LL?&?EUQEeu$TzV zDw)w%;U~=N3kDR&9w`o5HI7z0h~-n35yi=!ui8}3hJ+~j7XJF`%}tBk7XL%5fprJe zSBc2og3yn16rcW^S^AI&LMC=9bQY@Z-*=Kpqhu18sVQqHE?T26ryU&31RzrUYJ^U}?gmw5eW%Nw5evmBv;}i(HCm-{WO(DX*#-=Pe z?p$uxDg#~(!g0P`pFGq~Re|2O|P(LV^!?;zXs zG(R~ed`cKkI`=#$RgdZhV%c_0ODi_wvd~e{8|q9{I}c9atSQuCzie`m*Jt>n*r|wT z>nT-W;jxQ+?{N(+iBRs7`C5yfDz+L2+Y{7Cw^uLWMSqjA?G?W-*!)Ibfi^z~&$L0< z_R6xY4$oMBQo*fN+@qduVDx?dG@>zC+})9&(pofkXyK%8&;xPk_W8v(PW}Hh3r9ur zi6jj0CJJAPwJd4Pbbfq#g=QSCOqhQWL^?ZEzPjayW*I6@>2^~ZsQMr7JYpV|we=X1 z*cUxmSRH&E{=O+TF%$)Fis?OyM!vn%r7sxto!$)a>ed!^B>8zXz4ROcu5OXmPj(71 z4jR~dI2dHus;s{(v@*?4vvX`h&X^x0@SGBeTKseBv$;a;u%i6R`IdjYoy&BXEf;EC zC;CqNRwE|U8yPvvO~oKp3}i5{$;brT(=U1LYVJHSamIu{nofaBDlv+Klsl_iLoVe> zImQC-6{6Yu#m<^WXWJ|W1L|`^4{?cKO!hUT<>}*c+%4BG?bBee$pPdbgg2ikIy+s1 zU|5s>-BJe6D#sWPQ1VO{Dcz$DgN=#O4@=~Lhx%x>ew0hfm5l4!la$%oCl gt|@5eH-o2up*oWjh6f`dJ8uNQ-4*82&p{}GC1h8#^w0>N1V||v>p_)u~__q zfC=pPHz@Gq3Cj{ojivhZ9a=}75Zn12!>|AwJ6BB?v;>Q|G(Ys79`2%0}(a;bZ zEDy@RBtc@G`s{G{k_+yw(sG;RUOaG}2L>xFbsC0jCWljoldm-l^f{o;R@d`=yGlH- zbi>KVs3E>^Px}JixNevw0wbW~K(Le1FvBv(KUX{_qiq%mb&KW5EncK}Ar2VV93jsG z$G!F92pB^FTb@tX9CRk{xdKKu1eE83yLiiR6QyV`SbUcOil)#1QqR#^_X`2{yk?qg zM}fvC4ZFl)Ny+t*qp_OB%n_>cO`xwXbh?~&5eT^EXnEh|pZ`-pp*8e%2w0Am87{+R zxC}Q1+#x1o_cY0V23fR*;wkEcTFV;AF6JRg1R_P3j5W#`t5A(H8mUG|T?Hs&pw9X*BRr5i^RpgW%Y~-twXc zemUvUAnLp_sm>)sEi4VPatX7-0#!noxT@9j`8gY9KGqyCq-NN60m^qMuXGKog^Ltd zB^dO49syrz2W=S2OZHn1@VjHbtr0F^SZ}~n&u8e(z#wE@K7?W7rZVmDvkcc1_8#a2 zI{9gtxT7q{BQ5-Y1-jYDV4Y-er(C;(@kQN5PMkAbhRbjnF2iNG442_DT!!<6YbWs2 zI=+wMli=t`(qTj>+RNSdFY@>8h>*lH7s_@&A$nf0r2)hf9b%%hiBE zT(v;Ph`HSesZ?P~SXwkhVk46fc#_r7u%QZ=@rbZ+I3p)6?eZk9!hkc#j)q->`^2nK zvvk5K^HHZd*I-b$5$!n~jJ{+?(;YQsNlb6+HI~rG<4fAq5-SgZ%_s941{rCGk-|zFycPBgVSJP7i0AN4q4AFDP zV|Jygyu&^}gnY+zlN^1L&c}u&r3NL00)&uQYABo(9TXnw85$Il9^W5|2Y@{o5|QAQ zHX*5ah|c5uycHhd9CKF}prC>9<{;5FnstNNx+ zbXxas+g3jZL&T%0T>ogY?RBTRu9>-stn_!bCeOB5w|aCmJ@!V3{hs{#x=Gb*wm+S?)WhLkmKN+6nvkaaN?XO ztF0_)1G)IK$wGdnRHs^KDuNw}9t6(PM+9$f=hT|~ARc%a5tcVsRO9hzGE=1FnXk*1~l#4U^>UFxfee7HHv zvKi_@@+{`A0O=ARP2_PE$<3u=V>8pC1b(n|H9FuKF;|Kx&5=B6K3U7$=8U9=bwk<@ zl6rdwitSeF?>+vO)rOZMRU*u3_iY|fIwZg%yg=~#F`lo#;_8b*@p{ekw$N8FqoH7o z9u2oWHBnm1UY_wah%hPWEDYFt97*%bpa@DWuf(R9U7T!HmARM#+?3Z%MJ8l8O%bOm zgwt{Wh_KIa3@WT-m!R{@Q)zP47&L7bLI07`wd_efGGK)>pN}46ToX&nCe@e`pE8la zSYr?Dq*fp)NdMAk%4G$LB|mLV!hJiMu@|1eY=r3uJ7#Slue~(%8lQgLXnnWQid&Tz zDz&yLGAk!9F|>z*E%nq(0@)*@CKgzle)CR(&pG}>5RR5Z)}2fticgVF^i#AC;TcT| zu6!$$Uz`dchVs-LTs%6soZb ztZiCyp<1bdKi618+srd==`2ilT~BPIhOvWIqM)qgd5yDITR`5Mw%Ins^%#Ga=L7^R8)d5zPc&O+Hm`LC8LVDEN=0?J)jH4E;nBLBVORQKO#SwgKbRSAJVA!@T)Patwb|oXGUAdrGCxi12pBM6JEJ)@ zizjXQg9t*Ny$y;^MIg;nl3eYO@K%=nfSlo260()?Csy}+6C#9g*lj-l=7~oeVy;b} z@^aal=Ua`Dg>W0^A%T$|9rN(4X1F2UXrC^70vm)dkX>gzOv%m z<)BGIOF;d7x~#8XAmX~aXIZps?!iqZ1B~jQa?6=j6Tx>O1U{;hAJhC>1=3EoeBO3% zfQO31X$+0^8Y+wuC|*L#z)k&;W{~Qo^6OJo_z}9O3gHF^{d6bX8$Mz7`xYDDWz4kWCRk5{^ZVD+q z^Fn;lWvwosGpOyu7(?&l9M^2tNXM#%sOtf_X#PF+VD^N}*3VkO5b$}qO)jdd4J+CL ap8~!R!CTf-qK$Vx79cs2iH-Jw*?$9V^OS}F literal 0 HcmV?d00001 diff --git a/CommonKit/Sources/CommonKit/Assets/Shared.xcassets/Wallets/bzz_wallet_row.imageset/bzz_wallet_row@3x.png b/CommonKit/Sources/CommonKit/Assets/Shared.xcassets/Wallets/bzz_wallet_row.imageset/bzz_wallet_row@3x.png new file mode 100644 index 0000000000000000000000000000000000000000..e31fa972d20b7c83f26b57ab0d84e109abfb701a GIT binary patch literal 2164 zcmZ{mX)qg#7ROU=)pFIH)KU~(o?2>oPc22XC>0@52_h(M$wku&i7f=Zy{fI9#G0V> zNGXvnln}Z-OT|)+HBF;^q?SaAl$2^+oq5ygdo%CDIsgBE=6pEk!*Ax?Itz1CR@71i z007GFU>Ep~)_yN}xt;EC%z?Wjnz63_u?Xz%u?ZJ&Xn<1~_9FVA`;`mfXgK;p7$N2* z8Uz5ytGl~6`6O~h6>sCT-1cXLpD=S=0GylEO}J|JRL5~Q{57EZkE_P7;?LL0aQ=v^ zQbNe+{7o{Yr|5R-zG!Isetsw(?<%1Ez4 z*JX{4xTLX5RTpP+ea%em;OogFuFrcD0&XL~!scih5wMwice0^WyT-)E24dYIT(NWt zxLZtrY|E);Q-04P3*-rRor4L9c33*+7H_i1AV=H2W&qh!A3rt&@Y-&axol4@$@BZ7 zdMt7b@c~*xJY@q>vdmhZF}qX&YUR!a#M(0Cj5u>WFS2wgBs$`q-xr=M*EpW<0+Cqs zv2tI@RCK)a*J*|6FxF4MnrANMTfm}JH4QW?ikl%SNicTy5QZ_Q2EwLlMMse}h?Vab zpe+RA z6C{FN0vEHvQitClK%mhsqh0yHd?4T_^M7-Ok)X1KE>0>ktmRd-JZtf@3}95>uk{N| zI6$I9Xl>FAx_fi$?Cn}mA-)Nh8?!Pe)@T?F+)xgNs3EKq3zc25gMY_*nlm4;n=oKa zCs?HEgt$~0+}F5X?9$I6AZDpPCZWa29*5B#EM`-q$0?C1YDwN@?9aK)=P&d7XoVpQ z_gljd5}{w5#|Df~v_$Vc;(E~;H+LL?&?EUQEeu$TzV zDw)w%;U~=N3kDR&9w`o5HI7z0h~-n35yi=!ui8}3hJ+~j7XJF`%}tBk7XL%5fprJe zSBc2og3yn16rcW^S^AI&LMC=9bQY@Z-*=Kpqhu18sVQqHE?T26ryU&31RzrUYJ^U}?gmw5eW%Nw5evmBv;}i(HCm-{WO(DX*#-=Pe z?p$uxDg#~(!g0P`pFGq~Re|2O|P(LV^!?;zXs zG(R~ed`cKkI`=#$RgdZhV%c_0ODi_wvd~e{8|q9{I}c9atSQuCzie`m*Jt>n*r|wT z>nT-W;jxQ+?{N(+iBRs7`C5yfDz+L2+Y{7Cw^uLWMSqjA?G?W-*!)Ibfi^z~&$L0< z_R6xY4$oMBQo*fN+@qduVDx?dG@>zC+})9&(pofkXyK%8&;xPk_W8v(PW}Hh3r9ur zi6jj0CJJAPwJd4Pbbfq#g=QSCOqhQWL^?ZEzPjayW*I6@>2^~ZsQMr7JYpV|we=X1 z*cUxmSRH&E{=O+TF%$)Fis?OyM!vn%r7sxto!$)a>ed!^B>8zXz4ROcu5OXmPj(71 z4jR~dI2dHus;s{(v@*?4vvX`h&X^x0@SGBeTKseBv$;a;u%i6R`IdjYoy&BXEf;EC zC;CqNRwE|U8yPvvO~oKp3}i5{$;brT(=U1LYVJHSamIu{nofaBDlv+Klsl_iLoVe> zImQC-6{6Yu#m<^WXWJ|W1L|`^4{?cKO!hUT<>}*c+%4BG?bBee$pPdbgg2ikIy+s1 zU|5s>-BJe6D#sWPQ1VO{Dcz$DgN=#O4@=~Lhx%x>ew0hfm5l4!la$%oCl gt|@5eH-o2up*oWjh6f`dJ8uNQ-4*82ysU5p1(+1~sY(m{F+q^|pnnJpYAd%yV2ock$LFC>b9BNHdg)=+BT3R= zuQD8grFh_|HbJ0CNW)bzSRlZ#k119&7^I8OGdLV~TCG0Txo16lKH9`m{oC25`sF%L z-QyNyLN4Jxr&wu}q%F3F^&j2HVp~=&`2NY%>B1?|Ia*3d+RG?*@RAshOGt zxL2O^%X{F8RC0X=lf%TFf&{bz|1t&X;OzChYlRbJ{Rgl=K@0c_+1ao&v=6Da& zMGh5A4b72q_d(neGlIi8g0iL&_%ebA%of4L`X7mu5LJeQHsBwKo>u?xDyjU^2(WX?hqRsM?BkMndVVQyZN0tkRh zOAT7bsg!59iQS`bh3ZaWI9YiywoCaOiQ(5ml}cFUVyG<#Pc|A8*Hy9T6e*u`TCTsF)f)fQ_EtOzbMS_XbZTL@So*pDm7ceG0DJb@ZBZA}H4!2hBgW>gAPTe{Vp z2m59g<+=m(ooo?|0vi9fuJ`;F`O4id)R|a|A-@G}tcrBZ?8UiM_lv{f!N-V9wSwH| zR7fM?#~N1{TC{7-tJjGeb%mh3igOR?CAdX>OXZHURXeCu%!zwV*4m}U4KUYmvbl{} zI?mdk6+qm|FE=xT_5tanVbO(yD2Q8Qp z(rGb^UWv}U#^=3ovFdcBF7vedpxJ}e;8(up=n2OGwQtWbdMgxMG@GOAwvC$J%}~?( zP<&V7`~=A=155gW1=5@?$iNTlmJq7QgGZo3zovKX{oilt0$7QivQkjWa5m7|+T-j2 n+Rnswr{{T-O8f5ge*z2uvWjt=tB~|700000NkvXXu0mjfqCQ)X literal 0 HcmV?d00001 diff --git a/CommonKit/Sources/CommonKit/Assets/Shared.xcassets/Wallets/flux_notification.imageset/flux_notification@2x.png b/CommonKit/Sources/CommonKit/Assets/Shared.xcassets/Wallets/flux_notification.imageset/flux_notification@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..99e558b7043019ffdf89a5f0ee2e6c0cdca04b41 GIT binary patch literal 2493 zcmV;u2}1UXP)Y0^TXJ}!+uz=M{`igX~6e?{`ZiImi61N$-^-`C5* zE06_F5RlO(G>s~zvo}uSl8Kq-A5TFG6wJwSF-LSXPF&DPijR`?#}V^+F@QGGL{vYv zkb#5O?+myUGKz46ez` ztGS2SHwRXhUhkiO2ldMx6>}TE#)&i*G>vpdWOc`VZ12mDvLy@eD*x@u)qmLFr{~$& z$;*^4Jufs*DLtnAC@$*S{4Tp)RzOmga-iOSEgPs|^X|;BwNF0C)^rZ3!N=P%u) zcngp4aredi!DF#$oXGN%G?4%aV7>UvLgmkVJ^4pT1An0jV6V!mf~No)Oyo2pn92fJ zx;_l%^OLVGl?L7u5}3;NdL~naoD5L|4Swv)eI0Dah9%m>Lu%?511s3NrxsAUJ-P6# zuW{6WK7z0FY`{#j8O`plw-KR}4S4WBp>*@z_1iW({G(dz^iOg*uV~A6-F2 zrKQRTwz&cgeheltM}ROa5wRisAHs*JpRU=ygsi|~Kvm4hhcgy;x?GV zH#t+x(F%OE97W3||#P67a0+rRIu3G-QA zIq;jxoG}tY6g2oJOz=+bqpDGSEWSSb##AU439DkrvgCq%9N9%u6MhVYqJQv5B~~a+ z;By>6hgp>M4{Ge6F0MobSgF;D59j&6biuNI8jq7Q2(4Z+VaKHdu z)XWF86y|f#eio&ybuw4k-Kc;ezc_gGS0jNk*dsJIZ0STq>Go?}!|i2*CcuwD%ff($ z)rc@F;8DynN4gbZ_%4I~ueYTU%%pC@H>h|vNP{qBMnn}1!=$AXz_lgWkLfJ2V6pa{P0KcjZzw-zs^|y|6#mb_ig>ogHfX9;UB*Xvs*#-! zo2WKfJR4+w5&PM!2(t#9E>%Z7g=Dbm@`}ykS-;q>2!oHiW%{OIuHtay^67>aR+|`X zW~5LhhK!9#@T_QDTaOCWBmq~jkv-AfA3$Ko3`NOj1aL+Lqyx#>C?#vs|Je?# ziAvE5aBs*mjahW;6z*XVmcSeAtt~~ro2+EOOUqV z9|_!vbUqRzC;Y&TrSp*(`G0?$IFUE?bII^;vrJ{GbqUh8O5n!Rc_A<3bAS_ji;bAe zT{X7qh7sACu$G|a(sk=zT%&6M}B(kt#YW;uE>f3U1^)*IA>sdtRpe%Dq*H>nX#1O=bn%l`Wy>)R1v=d4pN#zyOm6^NE{ZwS0cr zq~Ym({iAcSHo2DFg7At5C63ZLT;G-hGq!f2zQxVttn3zvwuiDz;S2}O^cJS5mChD> zcS#9sdl>lWajk<>=&^NWaO5XXUn!B8F#c=rre*BymzI{a6`xfh1o%Igd}tuPqkZu_ zWvzvV#nH7Pr0nO~Hc|OUvaNmVX-s89QcNr+HLS+g^Rj&f%?vH&9rSZOce>+$Sul_RcIf9iZZ?S^|s==Fg`8pzHN(i-Gt~L7$LwTbq=aZ zr-UG4VX+Nw98&lE)WjZ&q_d}+_$gLWlnzCzd)J(bUgzP#8UA)XrMvs??A1~Lk5B*s zyo!MO%RS|m0%slB;7@H;6QvBS$Vv}WyF2N&2YG zT791hotIOUMY0e_ZH79PSRBAQ$%r>4m2Ebp|4YDryP^oR3#uPz*yE&Sh6RKq0Xuv` zA19q2;O<`R9N?aU=N>d=R#}B<;s+0>a~~aSDFtTaky#5i!N*|cXKV+Sq; z&n{@O0_O`AMp+XaKA{n(^@gSV)`0T`=>V@Y#j7NF0a_u>FO9ugR9xU1MliSOK!!zd zU5NXOJ(E{0TH}v2q3J9Ne`ahWjnVpSapCPv7VqI++f9OIT;l?W%TNRRKKQcjaS{*` zz*U3|yBWdKKTU!E+7lPeCkvba4?Yhk%J(H0Kmc}x-Cq2(zMI!`efL5Gwjrh=CwGBNPM+nJsA1m))fz6)fzu1&v^meQ636vH=SwFX`GMdC^_ygGGewK4~hr zlZPO-4?6=2DPnhQ?OI7SqxDZska)hc=fu0S^XL9K_uSt(=l6xnC2cZ$=iJZ!{+-`B zCk$A7bhy#3h#Rh|xXR+1mUpP0;v<{#-W69{-dp18{B~u!!y~?1Ap_X1O8BPbyN$)&-zz3?+le zfHIVs6O`lPT5t@+QGl+BYuTlQDRczJISFGu@K;R<=Ndy{4S4&z=Qq!0vVnE`zcawoMCXDkY zHLDpCff2Fe1%|P3t2s|0)+`YKa)GTQDXY2QwWSmTfHKbs=7F40a9{KaN;xaD!Pb$y zJD(|HHmFnpkX3fqR=IQI?GLS54V1^7NiyD|A1IeNWW|#wzst(EWLp9yyqEZe(Zt#2ZN*lTh z?4d>=%hEzt_&s^2jNS}di{ z-9CU2Kps5s-dq3el~?~6{9ygh!7Eq(;oER%=`IR20O@6>egB~k^kBUI)9)Ypch)ao z3%>W`?ckk%{g*RxQ&7r|aj)jHKPD_9V4RKVrUB#&zj(W6IXzKV#+RLNV{B;HI_Sf5Dw!<FUmU#0! zKYSy2Q$XsNrjlgiNwaU~+%`%)D<|&@R-XK1p+NS2F+}WoQDo_eV@+CWLvCEZrIg$8 zU2tWTRH+8;nxc>ZITmkX@mW(hlyKV>jvLJirK2-1{>g=S6Q`?9X@k<0sYJw$Cm*?| zSL(4%zuf$LufAz{ycuf>GK>MF^(|?L8xg0uFr%Vex~26^Bs*hU+~OoUCu{{$#f=vo zn&MMamX@~2Re~7ZWN|FzwZOvpZM-xGvsf46&91PNcc+HZ##k~=OPOo3Fy0&)TaJU$ znv6SXOPOm@EpnE`n$m4VbewJ&iaVnZ|m!z<|e-s&@vtrO9}@=!=U!O^)C!KN%aYSnaC7Kkm$ z*pu+=`SqKu9X%Nk*pD19;``sO1W$Z<(i`iD`0pF%wt~yQd1nu(ux^0q(7m?>2lnsF zJhor0f2eoIXI{T?v&gUg?$y@$^>LQ6aMz8j`n!+c>s6+l#B%AJtpf0kv#%9tHTSsi z(g4?8cr59Qt4556PV>f@-zS0sHOMbMc|}X&0$Y2kvm?d`Q3Y$@ehZaz*VC&4`f zoMX>*aw%kE<_0ePjGb%A%h5e9qs8+&qmz%T1}LWHqESx5&$4im0sOW4-S-hjNUz|c zVZBT@N8lIXX^}u7Ot2{4Ji?YTM^Rb7-3u8+ylKeEfA<;nG2oNYWKp?q# zSYvXPcU-C5H=ii0A#Q}L*3~M8+93+$y4K>T5)xecI^AB&YSevmJeg8??4H)YF8%A` z&$`y)z=9**M4U@-*~oMh$_nF^JZ{w8)G%y6BXkO3TN$0bkpR3QE?8nhE`67!mO&eqP>y2*5VLbHqqlSq}XK+w}OjW+^BnDR+jyY2=+$m($oaI?s##d z?j;3TJADqy);Ea5+*S<&NX`GIiPv;*Af?u&n~0HBZ4*Dw;_a%Zt+te-08+5Rt;JoA ztE?@RxG}8|zMzC;>2}rS2^(QdvUg4q^S@N~Gg8}C+qQimpsd>hW0u52ExgLpl~Uc$ zNQE&bTj(;VZiS`Pm3XN6U%2!*#peC$_88L0j1>5ON`_H zUAn_(Bw8hglEqN6Sl&RX&RG1_^M`Fc&Vxlg`;MzYJ&Cr|<5&(tIjS4ThI$;!At;la z&5bfow57{|enW{4YASVQpC?)g*5z^XN(Bc$2>S z?oy2#^FEuK<4uz4!hN?XT1QwiQ-{T?(|eCC8O467ggd$~KP&i-6i}DOq$ar3fzZhc1r^ zt3Y)nhr2q@IoV+qp5*jHd5On`RiG{nf|4yIiD{qs{rldt{=*E z9v@Z#S|19fFrjPg!dV5l?va*~gbOd?_>Jv^OCq)%Swews`+_^}_wZI4XG)6@*3LYH z!#53_RUJL-Gl_7%;j!mBR>O(y7V*6cK7(fuZ{eW7b-2;S8dMS+^e0*4om5ztqvN?)B*LE(Bcno;`L3j^bqP=tsrva})+}TW-+@aHj9OBY5Awl9re9*xA>DpS|1- zuHU$+<@0{h{A=*x{rilqD@r?-@CvwySorf-TjwvvwUm{B*m;dmma`1iOUe6u87e13 z^oJ-EckC6K8q{PS4*S~z%#DEP$q{i+eeHiKI?u=%Cn|fP6JFci6hO`l`X8g9oD;VT zlw+ef@n(@{78ZGGL>}i#YSw&zDQGN|5=^-83iZH!P@+NUbl1GkWgJvZAiiHxM*ulB z?0=4fazWfyY{)19(;m9t-LuYSvKneG~6*#1* zRr0VrV@gv=Hd0GuB19_Rova-VU9y%a%fL0_n!4w zFwF8(U3TM9>1^CZ?25HL?6!w1>)}rG!Oj$<(+wLa^EAYjrE%AxdgC~CB^Rj`W_%Cybno7(SN(tsUaXaBt1Rtky*YbJlG;oSRUU=!M4{>9^khzU*Niz1tnfn3ewgTp+Zn!#is z$wA3rGLR&pWH1>>l29_33?xY?C74{tv`^aWkmFP``R(Kv!vdlXziTVhF%6_~OBu14 zWoa?1{NBl`C4n@4T1Z&rxAw8S$P#Jk_OTMmNCI?@<4iK~ zV%_z8lJ(h6Lm7c{N!(5`AI616UQ1LSCZskxvTU@%R@0buY?#ijt%H)Srk~c)3MeD7 zCah+i8PcYvw2mFB4;b1oigFu5z*%9YQmZ3jY*K&F(56w9<)c#!XJQyn7hUFY7D@>w z>X|6G9}l>;IZCKGE(be>aI4O64{aWGww!KQMyH^Rz&R%^r_TuyV#Vd8dqFakX0mm_ zx!{Rwhg)8iFm_z|QlYDFOIpuaam_gfC_scYTzA1LdIjYWK%@>}(X(^7);359Nr2sUjgQOJJ2n rt*3-eS-`fu1H6t)35qWLPk;dc-M5Fedw&9900000NkvXXu0mjfi2U#e literal 0 HcmV?d00001 diff --git a/CommonKit/Sources/CommonKit/Assets/Shared.xcassets/Wallets/flux_wallet.imageset/Contents.json b/CommonKit/Sources/CommonKit/Assets/Shared.xcassets/Wallets/flux_wallet.imageset/Contents.json new file mode 100644 index 000000000..205f3d260 --- /dev/null +++ b/CommonKit/Sources/CommonKit/Assets/Shared.xcassets/Wallets/flux_wallet.imageset/Contents.json @@ -0,0 +1,23 @@ +{ + "images" : [ + { + "idiom" : "universal", + "scale" : "1x", + "filename" : "flux_wallet.png" + }, + { + "idiom" : "universal", + "scale" : "2x", + "filename" : "flux_wallet@2x.png" + }, + { + "idiom" : "universal", + "scale" : "3x", + "filename" : "flux_wallet@3x.png" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} diff --git a/CommonKit/Sources/CommonKit/Assets/Shared.xcassets/Wallets/flux_wallet.imageset/flux_wallet.png b/CommonKit/Sources/CommonKit/Assets/Shared.xcassets/Wallets/flux_wallet.imageset/flux_wallet.png new file mode 100644 index 0000000000000000000000000000000000000000..b74647910411a5211f02b2c9869409d8a5337968 GIT binary patch literal 1277 zcmVysU5p1(+1~sY(m{F+q^|pnnJpYAd%yV2ock$LFC>b9BNHdg)=+BT3R= zuQD8grFh_|HbJ0CNW)bzSRlZ#k119&7^I8OGdLV~TCG0Txo16lKH9`m{oC25`sF%L z-QyNyLN4Jxr&wu}q%F3F^&j2HVp~=&`2NY%>B1?|Ia*3d+RG?*@RAshOGt zxL2O^%X{F8RC0X=lf%TFf&{bz|1t&X;OzChYlRbJ{Rgl=K@0c_+1ao&v=6Da& zMGh5A4b72q_d(neGlIi8g0iL&_%ebA%of4L`X7mu5LJeQHsBwKo>u?xDyjU^2(WX?hqRsM?BkMndVVQyZN0tkRh zOAT7bsg!59iQS`bh3ZaWI9YiywoCaOiQ(5ml}cFUVyG<#Pc|A8*Hy9T6e*u`TCTsF)f)fQ_EtOzbMS_XbZTL@So*pDm7ceG0DJb@ZBZA}H4!2hBgW>gAPTe{Vp z2m59g<+=m(ooo?|0vi9fuJ`;F`O4id)R|a|A-@G}tcrBZ?8UiM_lv{f!N-V9wSwH| zR7fM?#~N1{TC{7-tJjGeb%mh3igOR?CAdX>OXZHURXeCu%!zwV*4m}U4KUYmvbl{} zI?mdk6+qm|FE=xT_5tanVbO(yD2Q8Qp z(rGb^UWv}U#^=3ovFdcBF7vedpxJ}e;8(up=n2OGwQtWbdMgxMG@GOAwvC$J%}~?( zP<&V7`~=A=155gW1=5@?$iNTlmJq7QgGZo3zovKX{oilt0$7QivQkjWa5m7|+T-j2 n+Rnswr{{T-O8f5ge*z2uvWjt=tB~|700000NkvXXu0mjfqCQ)X literal 0 HcmV?d00001 diff --git a/CommonKit/Sources/CommonKit/Assets/Shared.xcassets/Wallets/flux_wallet.imageset/flux_wallet@2x.png b/CommonKit/Sources/CommonKit/Assets/Shared.xcassets/Wallets/flux_wallet.imageset/flux_wallet@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..99e558b7043019ffdf89a5f0ee2e6c0cdca04b41 GIT binary patch literal 2493 zcmV;u2}1UXP)Y0^TXJ}!+uz=M{`igX~6e?{`ZiImi61N$-^-`C5* zE06_F5RlO(G>s~zvo}uSl8Kq-A5TFG6wJwSF-LSXPF&DPijR`?#}V^+F@QGGL{vYv zkb#5O?+myUGKz46ez` ztGS2SHwRXhUhkiO2ldMx6>}TE#)&i*G>vpdWOc`VZ12mDvLy@eD*x@u)qmLFr{~$& z$;*^4Jufs*DLtnAC@$*S{4Tp)RzOmga-iOSEgPs|^X|;BwNF0C)^rZ3!N=P%u) zcngp4aredi!DF#$oXGN%G?4%aV7>UvLgmkVJ^4pT1An0jV6V!mf~No)Oyo2pn92fJ zx;_l%^OLVGl?L7u5}3;NdL~naoD5L|4Swv)eI0Dah9%m>Lu%?511s3NrxsAUJ-P6# zuW{6WK7z0FY`{#j8O`plw-KR}4S4WBp>*@z_1iW({G(dz^iOg*uV~A6-F2 zrKQRTwz&cgeheltM}ROa5wRisAHs*JpRU=ygsi|~Kvm4hhcgy;x?GV zH#t+x(F%OE97W3||#P67a0+rRIu3G-QA zIq;jxoG}tY6g2oJOz=+bqpDGSEWSSb##AU439DkrvgCq%9N9%u6MhVYqJQv5B~~a+ z;By>6hgp>M4{Ge6F0MobSgF;D59j&6biuNI8jq7Q2(4Z+VaKHdu z)XWF86y|f#eio&ybuw4k-Kc;ezc_gGS0jNk*dsJIZ0STq>Go?}!|i2*CcuwD%ff($ z)rc@F;8DynN4gbZ_%4I~ueYTU%%pC@H>h|vNP{qBMnn}1!=$AXz_lgWkLfJ2V6pa{P0KcjZzw-zs^|y|6#mb_ig>ogHfX9;UB*Xvs*#-! zo2WKfJR4+w5&PM!2(t#9E>%Z7g=Dbm@`}ykS-;q>2!oHiW%{OIuHtay^67>aR+|`X zW~5LhhK!9#@T_QDTaOCWBmq~jkv-AfA3$Ko3`NOj1aL+Lqyx#>C?#vs|Je?# ziAvE5aBs*mjahW;6z*XVmcSeAtt~~ro2+EOOUqV z9|_!vbUqRzC;Y&TrSp*(`G0?$IFUE?bII^;vrJ{GbqUh8O5n!Rc_A<3bAS_ji;bAe zT{X7qh7sACu$G|a(sk=zT%&6M}B(kt#YW;uE>f3U1^)*IA>sdtRpe%Dq*H>nX#1O=bn%l`Wy>)R1v=d4pN#zyOm6^NE{ZwS0cr zq~Ym({iAcSHo2DFg7At5C63ZLT;G-hGq!f2zQxVttn3zvwuiDz;S2}O^cJS5mChD> zcS#9sdl>lWajk<>=&^NWaO5XXUn!B8F#c=rre*BymzI{a6`xfh1o%Igd}tuPqkZu_ zWvzvV#nH7Pr0nO~Hc|OUvaNmVX-s89QcNr+HLS+g^Rj&f%?vH&9rSZOce>+$Sul_RcIf9iZZ?S^|s==Fg`8pzHN(i-Gt~L7$LwTbq=aZ zr-UG4VX+Nw98&lE)WjZ&q_d}+_$gLWlnzCzd)J(bUgzP#8UA)XrMvs??A1~Lk5B*s zyo!MO%RS|m0%slB;7@H;6QvBS$Vv}WyF2N&2YG zT791hotIOUMY0e_ZH79PSRBAQ$%r>4m2Ebp|4YDryP^oR3#uPz*yE&Sh6RKq0Xuv` zA19q2;O<`R9N?aU=N>d=R#}B<;s+0>a~~aSDFtTaky#5i!N*|cXKV+Sq; z&n{@O0_O`AMp+XaKA{n(^@gSV)`0T`=>V@Y#j7NF0a_u>FO9ugR9xU1MliSOK!!zd zU5NXOJ(E{0TH}v2q3J9Ne`ahWjnVpSapCPv7VqI++f9OIT;l?W%TNRRKKQcjaS{*` zz*U3|yBWdKKTU!E+7lPeCkvba4?Yhk%J(H0Kmc}x-Cq2(zMI!`efL5Gwjrh=CwGBNPM+nJsA1m))fz6)fzu1&v^meQ636vH=SwFX`GMdC^_ygGGewK4~hr zlZPO-4?6=2DPnhQ?OI7SqxDZska)hc=fu0S^XL9K_uSt(=l6xnC2cZ$=iJZ!{+-`B zCk$A7bhy#3h#Rh|xXR+1mUpP0;v<{#-W69{-dp18{B~u!!y~?1Ap_X1O8BPbyN$)&-zz3?+le zfHIVs6O`lPT5t@+QGl+BYuTlQDRczJISFGu@K;R<=Ndy{4S4&z=Qq!0vVnE`zcawoMCXDkY zHLDpCff2Fe1%|P3t2s|0)+`YKa)GTQDXY2QwWSmTfHKbs=7F40a9{KaN;xaD!Pb$y zJD(|HHmFnpkX3fqR=IQI?GLS54V1^7NiyD|A1IeNWW|#wzst(EWLp9yyqEZe(Zt#2ZN*lTh z?4d>=%hEzt_&s^2jNS}di{ z-9CU2Kps5s-dq3el~?~6{9ygh!7Eq(;oER%=`IR20O@6>egB~k^kBUI)9)Ypch)ao z3%>W`?ckk%{g*RxQ&7r|aj)jHKPD_9V4RKVrUB#&zj(W6IXzKV#+RLNV{B;HI_Sf5Dw!<FUmU#0! zKYSy2Q$XsNrjlgiNwaU~+%`%)D<|&@R-XK1p+NS2F+}WoQDo_eV@+CWLvCEZrIg$8 zU2tWTRH+8;nxc>ZITmkX@mW(hlyKV>jvLJirK2-1{>g=S6Q`?9X@k<0sYJw$Cm*?| zSL(4%zuf$LufAz{ycuf>GK>MF^(|?L8xg0uFr%Vex~26^Bs*hU+~OoUCu{{$#f=vo zn&MMamX@~2Re~7ZWN|FzwZOvpZM-xGvsf46&91PNcc+HZ##k~=OPOo3Fy0&)TaJU$ znv6SXOPOm@EpnE`n$m4VbewJ&iaVnZ|m!z<|e-s&@vtrO9}@=!=U!O^)C!KN%aYSnaC7Kkm$ z*pu+=`SqKu9X%Nk*pD19;``sO1W$Z<(i`iD`0pF%wt~yQd1nu(ux^0q(7m?>2lnsF zJhor0f2eoIXI{T?v&gUg?$y@$^>LQ6aMz8j`n!+c>s6+l#B%AJtpf0kv#%9tHTSsi z(g4?8cr59Qt4556PV>f@-zS0sHOMbMc|}X&0$Y2kvm?d`Q3Y$@ehZaz*VC&4`f zoMX>*aw%kE<_0ePjGb%A%h5e9qs8+&qmz%T1}LWHqESx5&$4im0sOW4-S-hjNUz|c zVZBT@N8lIXX^}u7Ot2{4Ji?YTM^Rb7-3u8+ylKeEfA<;nG2oNYWKp?q# zSYvXPcU-C5H=ii0A#Q}L*3~M8+93+$y4K>T5)xecI^AB&YSevmJeg8??4H)YF8%A` z&$`y)z=9**M4U@-*~oMh$_nF^JZ{w8)G%y6BXkO3TN$0bkpR3QE?8nhE`67!mO&eqP>y2*5VLbHqqlSq}XK+w}OjW+^BnDR+jyY2=+$m($oaI?s##d z?j;3TJADqy);Ea5+*S<&NX`GIiPv;*Af?u&n~0HBZ4*Dw;_a%Zt+te-08+5Rt;JoA ztE?@RxG}8|zMzC;>2}rS2^(QdvUg4q^S@N~Gg8}C+qQimpsd>hW0u52ExgLpl~Uc$ zNQE&bTj(;VZiS`Pm3XN6U%2!*#peC$_88L0j1>5ON`_H zUAn_(Bw8hglEqN6Sl&RX&RG1_^M`Fc&Vxlg`;MzYJ&Cr|<5&(tIjS4ThI$;!At;la z&5bfow57{|enW{4YASVQpC?)g*5z^XN(Bc$2>S z?oy2#^FEuK<4uz4!hN?XT1QwiQ-{T?(|eCC8O467ggd$~KP&i-6i}DOq$ar3fzZhc1r^ zt3Y)nhr2q@IoV+qp5*jHd5On`RiG{nf|4yIiD{qs{rldt{=*E z9v@Z#S|19fFrjPg!dV5l?va*~gbOd?_>Jv^OCq)%Swews`+_^}_wZI4XG)6@*3LYH z!#53_RUJL-Gl_7%;j!mBR>O(y7V*6cK7(fuZ{eW7b-2;S8dMS+^e0*4om5ztqvN?)B*LE(Bcno;`L3j^bqP=tsrva})+}TW-+@aHj9OBY5Awl9re9*xA>DpS|1- zuHU$+<@0{h{A=*x{rilqD@r?-@CvwySorf-TjwvvwUm{B*m;dmma`1iOUe6u87e13 z^oJ-EckC6K8q{PS4*S~z%#DEP$q{i+eeHiKI?u=%Cn|fP6JFci6hO`l`X8g9oD;VT zlw+ef@n(@{78ZGGL>}i#YSw&zDQGN|5=^-83iZH!P@+NUbl1GkWgJvZAiiHxM*ulB z?0=4fazWfyY{)19(;m9t-LuYSvKneG~6*#1* zRr0VrV@gv=Hd0GuB19_Rova-VU9y%a%fL0_n!4w zFwF8(U3TM9>1^CZ?25HL?6!w1>)}rG!Oj$<(+wLa^EAYjrE%AxdgC~CB^Rj`W_%Cybno7(SN(tsUaXaBt1Rtky*YbJlG;oSRUU=!M4{>9^khzU*Niz1tnfn3ewgTp+Zn!#is z$wA3rGLR&pWH1>>l29_33?xY?C74{tv`^aWkmFP``R(Kv!vdlXziTVhF%6_~OBu14 zWoa?1{NBl`C4n@4T1Z&rxAw8S$P#Jk_OTMmNCI?@<4iK~ zV%_z8lJ(h6Lm7c{N!(5`AI616UQ1LSCZskxvTU@%R@0buY?#ijt%H)Srk~c)3MeD7 zCah+i8PcYvw2mFB4;b1oigFu5z*%9YQmZ3jY*K&F(56w9<)c#!XJQyn7hUFY7D@>w z>X|6G9}l>;IZCKGE(be>aI4O64{aWGww!KQMyH^Rz&R%^r_TuyV#Vd8dqFakX0mm_ zx!{Rwhg)8iFm_z|QlYDFOIpuaam_gfC_scYTzA1LdIjYWK%@>}(X(^7);359Nr2sUjgQOJJ2n rt*3-eS-`fu1H6t)35qWLPk;dc-M5Fedw&9900000NkvXXu0mjfi2U#e literal 0 HcmV?d00001 diff --git a/CommonKit/Sources/CommonKit/Assets/Shared.xcassets/Wallets/flux_wallet_row.imageset/Contents.json b/CommonKit/Sources/CommonKit/Assets/Shared.xcassets/Wallets/flux_wallet_row.imageset/Contents.json new file mode 100644 index 000000000..04453b061 --- /dev/null +++ b/CommonKit/Sources/CommonKit/Assets/Shared.xcassets/Wallets/flux_wallet_row.imageset/Contents.json @@ -0,0 +1,23 @@ +{ + "images" : [ + { + "idiom" : "universal", + "scale" : "1x", + "filename" : "flux_wallet_row.png" + }, + { + "idiom" : "universal", + "scale" : "2x", + "filename" : "flux_wallet_row@2x.png" + }, + { + "idiom" : "universal", + "scale" : "3x", + "filename" : "flux_wallet_row@3x.png" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} diff --git a/CommonKit/Sources/CommonKit/Assets/Shared.xcassets/Wallets/flux_wallet_row.imageset/flux_wallet_row.png b/CommonKit/Sources/CommonKit/Assets/Shared.xcassets/Wallets/flux_wallet_row.imageset/flux_wallet_row.png new file mode 100644 index 0000000000000000000000000000000000000000..b74647910411a5211f02b2c9869409d8a5337968 GIT binary patch literal 1277 zcmVysU5p1(+1~sY(m{F+q^|pnnJpYAd%yV2ock$LFC>b9BNHdg)=+BT3R= zuQD8grFh_|HbJ0CNW)bzSRlZ#k119&7^I8OGdLV~TCG0Txo16lKH9`m{oC25`sF%L z-QyNyLN4Jxr&wu}q%F3F^&j2HVp~=&`2NY%>B1?|Ia*3d+RG?*@RAshOGt zxL2O^%X{F8RC0X=lf%TFf&{bz|1t&X;OzChYlRbJ{Rgl=K@0c_+1ao&v=6Da& zMGh5A4b72q_d(neGlIi8g0iL&_%ebA%of4L`X7mu5LJeQHsBwKo>u?xDyjU^2(WX?hqRsM?BkMndVVQyZN0tkRh zOAT7bsg!59iQS`bh3ZaWI9YiywoCaOiQ(5ml}cFUVyG<#Pc|A8*Hy9T6e*u`TCTsF)f)fQ_EtOzbMS_XbZTL@So*pDm7ceG0DJb@ZBZA}H4!2hBgW>gAPTe{Vp z2m59g<+=m(ooo?|0vi9fuJ`;F`O4id)R|a|A-@G}tcrBZ?8UiM_lv{f!N-V9wSwH| zR7fM?#~N1{TC{7-tJjGeb%mh3igOR?CAdX>OXZHURXeCu%!zwV*4m}U4KUYmvbl{} zI?mdk6+qm|FE=xT_5tanVbO(yD2Q8Qp z(rGb^UWv}U#^=3ovFdcBF7vedpxJ}e;8(up=n2OGwQtWbdMgxMG@GOAwvC$J%}~?( zP<&V7`~=A=155gW1=5@?$iNTlmJq7QgGZo3zovKX{oilt0$7QivQkjWa5m7|+T-j2 n+Rnswr{{T-O8f5ge*z2uvWjt=tB~|700000NkvXXu0mjfqCQ)X literal 0 HcmV?d00001 diff --git a/CommonKit/Sources/CommonKit/Assets/Shared.xcassets/Wallets/flux_wallet_row.imageset/flux_wallet_row@2x.png b/CommonKit/Sources/CommonKit/Assets/Shared.xcassets/Wallets/flux_wallet_row.imageset/flux_wallet_row@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..99e558b7043019ffdf89a5f0ee2e6c0cdca04b41 GIT binary patch literal 2493 zcmV;u2}1UXP)Y0^TXJ}!+uz=M{`igX~6e?{`ZiImi61N$-^-`C5* zE06_F5RlO(G>s~zvo}uSl8Kq-A5TFG6wJwSF-LSXPF&DPijR`?#}V^+F@QGGL{vYv zkb#5O?+myUGKz46ez` ztGS2SHwRXhUhkiO2ldMx6>}TE#)&i*G>vpdWOc`VZ12mDvLy@eD*x@u)qmLFr{~$& z$;*^4Jufs*DLtnAC@$*S{4Tp)RzOmga-iOSEgPs|^X|;BwNF0C)^rZ3!N=P%u) zcngp4aredi!DF#$oXGN%G?4%aV7>UvLgmkVJ^4pT1An0jV6V!mf~No)Oyo2pn92fJ zx;_l%^OLVGl?L7u5}3;NdL~naoD5L|4Swv)eI0Dah9%m>Lu%?511s3NrxsAUJ-P6# zuW{6WK7z0FY`{#j8O`plw-KR}4S4WBp>*@z_1iW({G(dz^iOg*uV~A6-F2 zrKQRTwz&cgeheltM}ROa5wRisAHs*JpRU=ygsi|~Kvm4hhcgy;x?GV zH#t+x(F%OE97W3||#P67a0+rRIu3G-QA zIq;jxoG}tY6g2oJOz=+bqpDGSEWSSb##AU439DkrvgCq%9N9%u6MhVYqJQv5B~~a+ z;By>6hgp>M4{Ge6F0MobSgF;D59j&6biuNI8jq7Q2(4Z+VaKHdu z)XWF86y|f#eio&ybuw4k-Kc;ezc_gGS0jNk*dsJIZ0STq>Go?}!|i2*CcuwD%ff($ z)rc@F;8DynN4gbZ_%4I~ueYTU%%pC@H>h|vNP{qBMnn}1!=$AXz_lgWkLfJ2V6pa{P0KcjZzw-zs^|y|6#mb_ig>ogHfX9;UB*Xvs*#-! zo2WKfJR4+w5&PM!2(t#9E>%Z7g=Dbm@`}ykS-;q>2!oHiW%{OIuHtay^67>aR+|`X zW~5LhhK!9#@T_QDTaOCWBmq~jkv-AfA3$Ko3`NOj1aL+Lqyx#>C?#vs|Je?# ziAvE5aBs*mjahW;6z*XVmcSeAtt~~ro2+EOOUqV z9|_!vbUqRzC;Y&TrSp*(`G0?$IFUE?bII^;vrJ{GbqUh8O5n!Rc_A<3bAS_ji;bAe zT{X7qh7sACu$G|a(sk=zT%&6M}B(kt#YW;uE>f3U1^)*IA>sdtRpe%Dq*H>nX#1O=bn%l`Wy>)R1v=d4pN#zyOm6^NE{ZwS0cr zq~Ym({iAcSHo2DFg7At5C63ZLT;G-hGq!f2zQxVttn3zvwuiDz;S2}O^cJS5mChD> zcS#9sdl>lWajk<>=&^NWaO5XXUn!B8F#c=rre*BymzI{a6`xfh1o%Igd}tuPqkZu_ zWvzvV#nH7Pr0nO~Hc|OUvaNmVX-s89QcNr+HLS+g^Rj&f%?vH&9rSZOce>+$Sul_RcIf9iZZ?S^|s==Fg`8pzHN(i-Gt~L7$LwTbq=aZ zr-UG4VX+Nw98&lE)WjZ&q_d}+_$gLWlnzCzd)J(bUgzP#8UA)XrMvs??A1~Lk5B*s zyo!MO%RS|m0%slB;7@H;6QvBS$Vv}WyF2N&2YG zT791hotIOUMY0e_ZH79PSRBAQ$%r>4m2Ebp|4YDryP^oR3#uPz*yE&Sh6RKq0Xuv` zA19q2;O<`R9N?aU=N>d=R#}B<;s+0>a~~aSDFtTaky#5i!N*|cXKV+Sq; z&n{@O0_O`AMp+XaKA{n(^@gSV)`0T`=>V@Y#j7NF0a_u>FO9ugR9xU1MliSOK!!zd zU5NXOJ(E{0TH}v2q3J9Ne`ahWjnVpSapCPv7VqI++f9OIT;l?W%TNRRKKQcjaS{*` zz*U3|yBWdKKTU!E+7lPeCkvba4?Yhk%J(H0Kmc}x-Cq2(zMI!`efL5Gwjrh=CwGBNPM+nJsA1m))fz6)fzu1&v^meQ636vH=SwFX`GMdC^_ygGGewK4~hr zlZPO-4?6=2DPnhQ?OI7SqxDZska)hc=fu0S^XL9K_uSt(=l6xnC2cZ$=iJZ!{+-`B zCk$A7bhy#3h#Rh|xXR+1mUpP0;v<{#-W69{-dp18{B~u!!y~?1Ap_X1O8BPbyN$)&-zz3?+le zfHIVs6O`lPT5t@+QGl+BYuTlQDRczJISFGu@K;R<=Ndy{4S4&z=Qq!0vVnE`zcawoMCXDkY zHLDpCff2Fe1%|P3t2s|0)+`YKa)GTQDXY2QwWSmTfHKbs=7F40a9{KaN;xaD!Pb$y zJD(|HHmFnpkX3fqR=IQI?GLS54V1^7NiyD|A1IeNWW|#wzst(EWLp9yyqEZe(Zt#2ZN*lTh z?4d>=%hEzt_&s^2jNS}di{ z-9CU2Kps5s-dq3el~?~6{9ygh!7Eq(;oER%=`IR20O@6>egB~k^kBUI)9)Ypch)ao z3%>W`?ckk%{g*RxQ&7r|aj)jHKPD_9V4RKVrUB#&zj(W6IXzKV#+RLNV{B;HI_Sf5Dw!<FUmU#0! zKYSy2Q$XsNrjlgiNwaU~+%`%)D<|&@R-XK1p+NS2F+}WoQDo_eV@+CWLvCEZrIg$8 zU2tWTRH+8;nxc>ZITmkX@mW(hlyKV>jvLJirK2-1{>g=S6Q`?9X@k<0sYJw$Cm*?| zSL(4%zuf$LufAz{ycuf>GK>MF^(|?L8xg0uFr%Vex~26^Bs*hU+~OoUCu{{$#f=vo zn&MMamX@~2Re~7ZWN|FzwZOvpZM-xGvsf46&91PNcc+HZ##k~=OPOo3Fy0&)TaJU$ znv6SXOPOm@EpnE`n$m4VbewJ&iaVnZ|m!z<|e-s&@vtrO9}@=!=U!O^)C!KN%aYSnaC7Kkm$ z*pu+=`SqKu9X%Nk*pD19;``sO1W$Z<(i`iD`0pF%wt~yQd1nu(ux^0q(7m?>2lnsF zJhor0f2eoIXI{T?v&gUg?$y@$^>LQ6aMz8j`n!+c>s6+l#B%AJtpf0kv#%9tHTSsi z(g4?8cr59Qt4556PV>f@-zS0sHOMbMc|}X&0$Y2kvm?d`Q3Y$@ehZaz*VC&4`f zoMX>*aw%kE<_0ePjGb%A%h5e9qs8+&qmz%T1}LWHqESx5&$4im0sOW4-S-hjNUz|c zVZBT@N8lIXX^}u7Ot2{4Ji?YTM^Rb7-3u8+ylKeEfA<;nG2oNYWKp?q# zSYvXPcU-C5H=ii0A#Q}L*3~M8+93+$y4K>T5)xecI^AB&YSevmJeg8??4H)YF8%A` z&$`y)z=9**M4U@-*~oMh$_nF^JZ{w8)G%y6BXkO3TN$0bkpR3QE?8nhE`67!mO&eqP>y2*5VLbHqqlSq}XK+w}OjW+^BnDR+jyY2=+$m($oaI?s##d z?j;3TJADqy);Ea5+*S<&NX`GIiPv;*Af?u&n~0HBZ4*Dw;_a%Zt+te-08+5Rt;JoA ztE?@RxG}8|zMzC;>2}rS2^(QdvUg4q^S@N~Gg8}C+qQimpsd>hW0u52ExgLpl~Uc$ zNQE&bTj(;VZiS`Pm3XN6U%2!*#peC$_88L0j1>5ON`_H zUAn_(Bw8hglEqN6Sl&RX&RG1_^M`Fc&Vxlg`;MzYJ&Cr|<5&(tIjS4ThI$;!At;la z&5bfow57{|enW{4YASVQpC?)g*5z^XN(Bc$2>S z?oy2#^FEuK<4uz4!hN?XT1QwiQ-{T?(|eCC8O467ggd$~KP&i-6i}DOq$ar3fzZhc1r^ zt3Y)nhr2q@IoV+qp5*jHd5On`RiG{nf|4yIiD{qs{rldt{=*E z9v@Z#S|19fFrjPg!dV5l?va*~gbOd?_>Jv^OCq)%Swews`+_^}_wZI4XG)6@*3LYH z!#53_RUJL-Gl_7%;j!mBR>O(y7V*6cK7(fuZ{eW7b-2;S8dMS+^e0*4om5ztqvN?)B*LE(Bcno;`L3j^bqP=tsrva})+}TW-+@aHj9OBY5Awl9re9*xA>DpS|1- zuHU$+<@0{h{A=*x{rilqD@r?-@CvwySorf-TjwvvwUm{B*m;dmma`1iOUe6u87e13 z^oJ-EckC6K8q{PS4*S~z%#DEP$q{i+eeHiKI?u=%Cn|fP6JFci6hO`l`X8g9oD;VT zlw+ef@n(@{78ZGGL>}i#YSw&zDQGN|5=^-83iZH!P@+NUbl1GkWgJvZAiiHxM*ulB z?0=4fazWfyY{)19(;m9t-LuYSvKneG~6*#1* zRr0VrV@gv=Hd0GuB19_Rova-VU9y%a%fL0_n!4w zFwF8(U3TM9>1^CZ?25HL?6!w1>)}rG!Oj$<(+wLa^EAYjrE%AxdgC~CB^Rj`W_%Cybno7(SN(tsUaXaBt1Rtky*YbJlG;oSRUU=!M4{>9^khzU*Niz1tnfn3ewgTp+Zn!#is z$wA3rGLR&pWH1>>l29_33?xY?C74{tv`^aWkmFP``R(Kv!vdlXziTVhF%6_~OBu14 zWoa?1{NBl`C4n@4T1Z&rxAw8S$P#Jk_OTMmNCI?@<4iK~ zV%_z8lJ(h6Lm7c{N!(5`AI616UQ1LSCZskxvTU@%R@0buY?#ijt%H)Srk~c)3MeD7 zCah+i8PcYvw2mFB4;b1oigFu5z*%9YQmZ3jY*K&F(56w9<)c#!XJQyn7hUFY7D@>w z>X|6G9}l>;IZCKGE(be>aI4O64{aWGww!KQMyH^Rz&R%^r_TuyV#Vd8dqFakX0mm_ zx!{Rwhg)8iFm_z|QlYDFOIpuaam_gfC_scYTzA1LdIjYWK%@>}(X(^7);359Nr2sUjgQOJJ2n rt*3-eS-`fu1H6t)35qWLPk;dc-M5Fedw&9900000NkvXXu0mjfi2U#e literal 0 HcmV?d00001 diff --git a/CommonKit/Sources/CommonKit/Models/ethereumTokensList.swift b/CommonKit/Sources/CommonKit/Models/ethereumTokensList.swift index 425ad1367..22b1dcdf9 100644 --- a/CommonKit/Sources/CommonKit/Models/ethereumTokensList.swift +++ b/CommonKit/Sources/CommonKit/Models/ethereumTokensList.swift @@ -27,6 +27,18 @@ defaultGasPriceGwei: 30, defaultGasLimit: 58000, warningGasPriceGwei: 70), + ERC20Token(symbol: "BZZ", + name: "Swarm", + contractAddress: "0x19062190B1925b5b6689D7073fDfC8c2976EF8Cb", + decimals: 16, + naturalUnits: 16, + defaultVisibility: true, + defaultOrdinalLevel: 95, + reliabilityGasPricePercent: 10, + reliabilityGasLimitPercent: 10, + defaultGasPriceGwei: 30, + defaultGasLimit: 58000, + warningGasPriceGwei: 70), ERC20Token(symbol: "DAI", name: "Dai", contractAddress: "0x6b175474e89094c44da98b954eedeac495271d0f", @@ -51,6 +63,18 @@ defaultGasPriceGwei: 30, defaultGasLimit: 58000, warningGasPriceGwei: 70), + ERC20Token(symbol: "FLUX", + name: "Flux", + contractAddress: "0x720CD16b011b987Da3518fbf38c3071d4F0D1495", + decimals: 8, + naturalUnits: 8, + defaultVisibility: true, + defaultOrdinalLevel: 90, + reliabilityGasPricePercent: 10, + reliabilityGasLimitPercent: 10, + defaultGasPriceGwei: 30, + defaultGasLimit: 58000, + warningGasPriceGwei: 70), ERC20Token(symbol: "HOT", name: "Holo", contractAddress: "0x6c6ee5e31d828de241282b9606c8e98ea48526e2", @@ -153,7 +177,7 @@ decimals: 18, naturalUnits: 18, defaultVisibility: true, - defaultOrdinalLevel: 94, + defaultOrdinalLevel: 85, reliabilityGasPricePercent: 10, reliabilityGasLimitPercent: 10, defaultGasPriceGwei: 30, diff --git a/NotificationServiceExtension/WalletImages/bzz_notificationContent.png b/NotificationServiceExtension/WalletImages/bzz_notificationContent.png new file mode 100644 index 0000000000000000000000000000000000000000..e31fa972d20b7c83f26b57ab0d84e109abfb701a GIT binary patch literal 2164 zcmZ{mX)qg#7ROU=)pFIH)KU~(o?2>oPc22XC>0@52_h(M$wku&i7f=Zy{fI9#G0V> zNGXvnln}Z-OT|)+HBF;^q?SaAl$2^+oq5ygdo%CDIsgBE=6pEk!*Ax?Itz1CR@71i z007GFU>Ep~)_yN}xt;EC%z?Wjnz63_u?Xz%u?ZJ&Xn<1~_9FVA`;`mfXgK;p7$N2* z8Uz5ytGl~6`6O~h6>sCT-1cXLpD=S=0GylEO}J|JRL5~Q{57EZkE_P7;?LL0aQ=v^ zQbNe+{7o{Yr|5R-zG!Isetsw(?<%1Ez4 z*JX{4xTLX5RTpP+ea%em;OogFuFrcD0&XL~!scih5wMwice0^WyT-)E24dYIT(NWt zxLZtrY|E);Q-04P3*-rRor4L9c33*+7H_i1AV=H2W&qh!A3rt&@Y-&axol4@$@BZ7 zdMt7b@c~*xJY@q>vdmhZF}qX&YUR!a#M(0Cj5u>WFS2wgBs$`q-xr=M*EpW<0+Cqs zv2tI@RCK)a*J*|6FxF4MnrANMTfm}JH4QW?ikl%SNicTy5QZ_Q2EwLlMMse}h?Vab zpe+RA z6C{FN0vEHvQitClK%mhsqh0yHd?4T_^M7-Ok)X1KE>0>ktmRd-JZtf@3}95>uk{N| zI6$I9Xl>FAx_fi$?Cn}mA-)Nh8?!Pe)@T?F+)xgNs3EKq3zc25gMY_*nlm4;n=oKa zCs?HEgt$~0+}F5X?9$I6AZDpPCZWa29*5B#EM`-q$0?C1YDwN@?9aK)=P&d7XoVpQ z_gljd5}{w5#|Df~v_$Vc;(E~;H+LL?&?EUQEeu$TzV zDw)w%;U~=N3kDR&9w`o5HI7z0h~-n35yi=!ui8}3hJ+~j7XJF`%}tBk7XL%5fprJe zSBc2og3yn16rcW^S^AI&LMC=9bQY@Z-*=Kpqhu18sVQqHE?T26ryU&31RzrUYJ^U}?gmw5eW%Nw5evmBv;}i(HCm-{WO(DX*#-=Pe z?p$uxDg#~(!g0P`pFGq~Re|2O|P(LV^!?;zXs zG(R~ed`cKkI`=#$RgdZhV%c_0ODi_wvd~e{8|q9{I}c9atSQuCzie`m*Jt>n*r|wT z>nT-W;jxQ+?{N(+iBRs7`C5yfDz+L2+Y{7Cw^uLWMSqjA?G?W-*!)Ibfi^z~&$L0< z_R6xY4$oMBQo*fN+@qduVDx?dG@>zC+})9&(pofkXyK%8&;xPk_W8v(PW}Hh3r9ur zi6jj0CJJAPwJd4Pbbfq#g=QSCOqhQWL^?ZEzPjayW*I6@>2^~ZsQMr7JYpV|we=X1 z*cUxmSRH&E{=O+TF%$)Fis?OyM!vn%r7sxto!$)a>ed!^B>8zXz4ROcu5OXmPj(71 z4jR~dI2dHus;s{(v@*?4vvX`h&X^x0@SGBeTKseBv$;a;u%i6R`IdjYoy&BXEf;EC zC;CqNRwE|U8yPvvO~oKp3}i5{$;brT(=U1LYVJHSamIu{nofaBDlv+Klsl_iLoVe> zImQC-6{6Yu#m<^WXWJ|W1L|`^4{?cKO!hUT<>}*c+%4BG?bBee$pPdbgg2ikIy+s1 zU|5s>-BJe6D#sWPQ1VO{Dcz$DgN=#O4@=~Lhx%x>ew0hfm5l4!la$%oCl gt|@5eH-o2up*oWjh6f`dJ8uNQ-4*82jrh=CwGBNPM+nJsA1m))fz6)fzu1&v^meQ636vH=SwFX`GMdC^_ygGGewK4~hr zlZPO-4?6=2DPnhQ?OI7SqxDZska)hc=fu0S^XL9K_uSt(=l6xnC2cZ$=iJZ!{+-`B zCk$A7bhy#3h#Rh|xXR+1mUpP0;v<{#-W69{-dp18{B~u!!y~?1Ap_X1O8BPbyN$)&-zz3?+le zfHIVs6O`lPT5t@+QGl+BYuTlQDRczJISFGu@K;R<=Ndy{4S4&z=Qq!0vVnE`zcawoMCXDkY zHLDpCff2Fe1%|P3t2s|0)+`YKa)GTQDXY2QwWSmTfHKbs=7F40a9{KaN;xaD!Pb$y zJD(|HHmFnpkX3fqR=IQI?GLS54V1^7NiyD|A1IeNWW|#wzst(EWLp9yyqEZe(Zt#2ZN*lTh z?4d>=%hEzt_&s^2jNS}di{ z-9CU2Kps5s-dq3el~?~6{9ygh!7Eq(;oER%=`IR20O@6>egB~k^kBUI)9)Ypch)ao z3%>W`?ckk%{g*RxQ&7r|aj)jHKPD_9V4RKVrUB#&zj(W6IXzKV#+RLNV{B;HI_Sf5Dw!<FUmU#0! zKYSy2Q$XsNrjlgiNwaU~+%`%)D<|&@R-XK1p+NS2F+}WoQDo_eV@+CWLvCEZrIg$8 zU2tWTRH+8;nxc>ZITmkX@mW(hlyKV>jvLJirK2-1{>g=S6Q`?9X@k<0sYJw$Cm*?| zSL(4%zuf$LufAz{ycuf>GK>MF^(|?L8xg0uFr%Vex~26^Bs*hU+~OoUCu{{$#f=vo zn&MMamX@~2Re~7ZWN|FzwZOvpZM-xGvsf46&91PNcc+HZ##k~=OPOo3Fy0&)TaJU$ znv6SXOPOm@EpnE`n$m4VbewJ&iaVnZ|m!z<|e-s&@vtrO9}@=!=U!O^)C!KN%aYSnaC7Kkm$ z*pu+=`SqKu9X%Nk*pD19;``sO1W$Z<(i`iD`0pF%wt~yQd1nu(ux^0q(7m?>2lnsF zJhor0f2eoIXI{T?v&gUg?$y@$^>LQ6aMz8j`n!+c>s6+l#B%AJtpf0kv#%9tHTSsi z(g4?8cr59Qt4556PV>f@-zS0sHOMbMc|}X&0$Y2kvm?d`Q3Y$@ehZaz*VC&4`f zoMX>*aw%kE<_0ePjGb%A%h5e9qs8+&qmz%T1}LWHqESx5&$4im0sOW4-S-hjNUz|c zVZBT@N8lIXX^}u7Ot2{4Ji?YTM^Rb7-3u8+ylKeEfA<;nG2oNYWKp?q# zSYvXPcU-C5H=ii0A#Q}L*3~M8+93+$y4K>T5)xecI^AB&YSevmJeg8??4H)YF8%A` z&$`y)z=9**M4U@-*~oMh$_nF^JZ{w8)G%y6BXkO3TN$0bkpR3QE?8nhE`67!mO&eqP>y2*5VLbHqqlSq}XK+w}OjW+^BnDR+jyY2=+$m($oaI?s##d z?j;3TJADqy);Ea5+*S<&NX`GIiPv;*Af?u&n~0HBZ4*Dw;_a%Zt+te-08+5Rt;JoA ztE?@RxG}8|zMzC;>2}rS2^(QdvUg4q^S@N~Gg8}C+qQimpsd>hW0u52ExgLpl~Uc$ zNQE&bTj(;VZiS`Pm3XN6U%2!*#peC$_88L0j1>5ON`_H zUAn_(Bw8hglEqN6Sl&RX&RG1_^M`Fc&Vxlg`;MzYJ&Cr|<5&(tIjS4ThI$;!At;la z&5bfow57{|enW{4YASVQpC?)g*5z^XN(Bc$2>S z?oy2#^FEuK<4uz4!hN?XT1QwiQ-{T?(|eCC8O467ggd$~KP&i-6i}DOq$ar3fzZhc1r^ zt3Y)nhr2q@IoV+qp5*jHd5On`RiG{nf|4yIiD{qs{rldt{=*E z9v@Z#S|19fFrjPg!dV5l?va*~gbOd?_>Jv^OCq)%Swews`+_^}_wZI4XG)6@*3LYH z!#53_RUJL-Gl_7%;j!mBR>O(y7V*6cK7(fuZ{eW7b-2;S8dMS+^e0*4om5ztqvN?)B*LE(Bcno;`L3j^bqP=tsrva})+}TW-+@aHj9OBY5Awl9re9*xA>DpS|1- zuHU$+<@0{h{A=*x{rilqD@r?-@CvwySorf-TjwvvwUm{B*m;dmma`1iOUe6u87e13 z^oJ-EckC6K8q{PS4*S~z%#DEP$q{i+eeHiKI?u=%Cn|fP6JFci6hO`l`X8g9oD;VT zlw+ef@n(@{78ZGGL>}i#YSw&zDQGN|5=^-83iZH!P@+NUbl1GkWgJvZAiiHxM*ulB z?0=4fazWfyY{)19(;m9t-LuYSvKneG~6*#1* zRr0VrV@gv=Hd0GuB19_Rova-VU9y%a%fL0_n!4w zFwF8(U3TM9>1^CZ?25HL?6!w1>)}rG!Oj$<(+wLa^EAYjrE%AxdgC~CB^Rj`W_%Cybno7(SN(tsUaXaBt1Rtky*YbJlG;oSRUU=!M4{>9^khzU*Niz1tnfn3ewgTp+Zn!#is z$wA3rGLR&pWH1>>l29_33?xY?C74{tv`^aWkmFP``R(Kv!vdlXziTVhF%6_~OBu14 zWoa?1{NBl`C4n@4T1Z&rxAw8S$P#Jk_OTMmNCI?@<4iK~ zV%_z8lJ(h6Lm7c{N!(5`AI616UQ1LSCZskxvTU@%R@0buY?#ijt%H)Srk~c)3MeD7 zCah+i8PcYvw2mFB4;b1oigFu5z*%9YQmZ3jY*K&F(56w9<)c#!XJQyn7hUFY7D@>w z>X|6G9}l>;IZCKGE(be>aI4O64{aWGww!KQMyH^Rz&R%^r_TuyV#Vd8dqFakX0mm_ zx!{Rwhg)8iFm_z|QlYDFOIpuaam_gfC_scYTzA1LdIjYWK%@>}(X(^7);359Nr2sUjgQOJJ2n rt*3-eS-`fu1H6t)35qWLPk;dc-M5Fedw&9900000NkvXXu0mjfi2U#e literal 0 HcmV?d00001 From 040321be52523757176f13d1a7c7ae06b1c0b28d Mon Sep 17 00:00:00 2001 From: StanislavDevIOS Date: Mon, 6 Nov 2023 11:51:52 +0200 Subject: [PATCH 45/96] [trello.com/c/A2iaLNq6] fix: vibration effect on tap --- Adamant/Modules/TestVibration/VibrationSelectionView.swift | 4 +++- .../Modules/TestVibration/VibrationSelectionViewModel.swift | 3 +++ 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/Adamant/Modules/TestVibration/VibrationSelectionView.swift b/Adamant/Modules/TestVibration/VibrationSelectionView.swift index e706929f6..b3ae21e67 100644 --- a/Adamant/Modules/TestVibration/VibrationSelectionView.swift +++ b/Adamant/Modules/TestVibration/VibrationSelectionView.swift @@ -18,8 +18,10 @@ struct VibrationSelectionView: View { var body: some View { List { ForEach(AdamantVibroType.allCases, id: \.self) { type in - Button(vibrationTypeDescription(type)) { + Button { viewModel.type = type + } label: { + Text(vibrationTypeDescription(type)) } } } diff --git a/Adamant/Modules/TestVibration/VibrationSelectionViewModel.swift b/Adamant/Modules/TestVibration/VibrationSelectionViewModel.swift index ccef28def..795fab61a 100644 --- a/Adamant/Modules/TestVibration/VibrationSelectionViewModel.swift +++ b/Adamant/Modules/TestVibration/VibrationSelectionViewModel.swift @@ -19,6 +19,9 @@ final class VibrationSelectionViewModel: ObservableObject { nonisolated init(vibroService: VibroService) { self.vibroService = vibroService + Task { + await self.setup() + } } } From 88be1430e97b161a4ab1a6e746f2a886d0691932 Mon Sep 17 00:00:00 2001 From: StanislavDevIOS Date: Mon, 6 Nov 2023 12:10:12 +0200 Subject: [PATCH 46/96] [trello.com/c/A2iaLNq6] fix: dark theme --- .../Wallets/BalanceTableViewCell.swift | 2 +- .../Buttons/eye_close.imageset/Contents.json | 33 ++++++++++++++++++ .../eye_close.imageset/invisible_dark.png | Bin 0 -> 468 bytes .../eye_close.imageset/invisible_dark@2x.png | Bin 0 -> 819 bytes .../eye_close.imageset/invisible_dark@3x.png | Bin 0 -> 1146 bytes .../Buttons/eye_open.imageset/Contents.json | 33 ++++++++++++++++++ .../eye_open.imageset/visible_dark.png | Bin 0 -> 434 bytes .../eye_open.imageset/visible_dark@2x.png | Bin 0 -> 762 bytes .../eye_open.imageset/visible_dark@3x.png | Bin 0 -> 1209 bytes 9 files changed, 67 insertions(+), 1 deletion(-) create mode 100644 CommonKit/Sources/CommonKit/Assets/Shared.xcassets/Buttons/eye_close.imageset/invisible_dark.png create mode 100644 CommonKit/Sources/CommonKit/Assets/Shared.xcassets/Buttons/eye_close.imageset/invisible_dark@2x.png create mode 100644 CommonKit/Sources/CommonKit/Assets/Shared.xcassets/Buttons/eye_close.imageset/invisible_dark@3x.png create mode 100644 CommonKit/Sources/CommonKit/Assets/Shared.xcassets/Buttons/eye_open.imageset/visible_dark.png create mode 100644 CommonKit/Sources/CommonKit/Assets/Shared.xcassets/Buttons/eye_open.imageset/visible_dark@2x.png create mode 100644 CommonKit/Sources/CommonKit/Assets/Shared.xcassets/Buttons/eye_open.imageset/visible_dark@3x.png diff --git a/Adamant/Modules/Wallets/BalanceTableViewCell.swift b/Adamant/Modules/Wallets/BalanceTableViewCell.swift index 81c77bfe5..4a782dbdd 100644 --- a/Adamant/Modules/Wallets/BalanceTableViewCell.swift +++ b/Adamant/Modules/Wallets/BalanceTableViewCell.swift @@ -34,7 +34,7 @@ public final class BalanceTableViewCell: Cell, CellType { let label = UILabel() label.font = .systemFont(ofSize: 17) label.text = "Balance" - label.textColor = .black + label.textColor = .adamant.textColor return label }() diff --git a/CommonKit/Sources/CommonKit/Assets/Shared.xcassets/Buttons/eye_close.imageset/Contents.json b/CommonKit/Sources/CommonKit/Assets/Shared.xcassets/Buttons/eye_close.imageset/Contents.json index c16284299..60cc4eab0 100644 --- a/CommonKit/Sources/CommonKit/Assets/Shared.xcassets/Buttons/eye_close.imageset/Contents.json +++ b/CommonKit/Sources/CommonKit/Assets/Shared.xcassets/Buttons/eye_close.imageset/Contents.json @@ -5,15 +5,48 @@ "idiom" : "universal", "scale" : "1x" }, + { + "appearances" : [ + { + "appearance" : "luminosity", + "value" : "dark" + } + ], + "filename" : "invisible_dark.png", + "idiom" : "universal", + "scale" : "1x" + }, { "filename" : "invisible@2x.png", "idiom" : "universal", "scale" : "2x" }, + { + "appearances" : [ + { + "appearance" : "luminosity", + "value" : "dark" + } + ], + "filename" : "invisible_dark@2x.png", + "idiom" : "universal", + "scale" : "2x" + }, { "filename" : "invisible@3x.png", "idiom" : "universal", "scale" : "3x" + }, + { + "appearances" : [ + { + "appearance" : "luminosity", + "value" : "dark" + } + ], + "filename" : "invisible_dark@3x.png", + "idiom" : "universal", + "scale" : "3x" } ], "info" : { diff --git a/CommonKit/Sources/CommonKit/Assets/Shared.xcassets/Buttons/eye_close.imageset/invisible_dark.png b/CommonKit/Sources/CommonKit/Assets/Shared.xcassets/Buttons/eye_close.imageset/invisible_dark.png new file mode 100644 index 0000000000000000000000000000000000000000..90e1256454445293844e706eb862d68c3593fa34 GIT binary patch literal 468 zcmV;_0W1EAP)_3Kb0!q(T&EvyaRwg9FH}z0wHwXCB|o z&MvdrSv6zK+S@cPwwRBKLL=aDbvOXavg|oc)2F6sK25)!1YDpFNEn7UN)@UXYE2I){jFQ0fpdU> zeBZxLlH^gGmSU)j)uw1*Ed3HrFJu$~v_n@Cav;FLkhLF?iBGo<=!zUVAWv~5)ZJJP zW!?aEesJJ1Y1mmNuqcY|7!$`)g)(ng?|k8OzK2TrvQs*d!`La+5o)EQxtZ7$a=l`$ z1NL&NcYmr5WqhY9!cZju>$=|ecK@JGw<8L5ol+Hs@>7wO|LSz$au^D^Yd|<@oH)th zU&yBb-T5zy;+1!Sp)MHWcp50T&Boq7569k8$J~kQ^ZFC~2rvM_3jlT?HP8|O0000< KMNUMnLSTZX`p`uH literal 0 HcmV?d00001 diff --git a/CommonKit/Sources/CommonKit/Assets/Shared.xcassets/Buttons/eye_close.imageset/invisible_dark@2x.png b/CommonKit/Sources/CommonKit/Assets/Shared.xcassets/Buttons/eye_close.imageset/invisible_dark@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..67424cd90ec8fddd551240cde5bf5be8fd33ce5e GIT binary patch literal 819 zcmV-31I+x1P)JkQ7E{re6%$^Y8g1pBb2Nd|Pm%X{KcMt&6p!Iv^f zx1*A(%{#L5D+wH#X_xoSl?e;hByWQ@y zcDwzk)9E}H3cnzQuMA4UKGCx`RaJ~t{+#T1JpNg&Rv%}x*(-Yd7(N^hpW+^l&v1hO zZpuAdC0)aua=^u65d#8nfXcG$Grm6)_KQRIxr$Iw15T&YmlWo&7P)zG$cU@RLkvab zWaJ@VC@K=6Eu<19^oB%^X%!8SbK3%K0jVPwRfa^k*+i*Oa6mp%-zT1sxfQIM>>s2C z)Xs8pt|96AgfBLioaB(0k5!0VZ6fCvj#N^wZuXKsgIw1ESys+?;zOnUa5(&wkZ_bB z!(?e)kh~UyK}L>uEvMCuJd}`dRwqN5Caxt&RfCh6L;(YvZ+s^&m&@fl6C_^7Ce^t) z@~x99f`y=Pu-wX5`g4s>SSY3eGCxJwUBjKQ`;+ndd}i@pKd7)LST`8zpGggEpj$8L z6MD}&Ir0v@&0-RuVTEEo;EM_KnIMgesjNKtwrU2*RE|T*Gl^1Io_q?hBjlP@i+<5U zDlPa$TWDQMVh59vlT`+gqHP>lHzl6MJW+5XKACL5`C1T4dUa5>G`&jYxgLa8u`;CC z=A4?|g2wsQI1Y&%6+dC3s@=Y8KY7QHe>&Vy5kG5+((AuNLM@aay}AJiMTLr~X%o2! zBrj+D=Q0m56cm6M8{4u3md7lmdp8as0J7O^ew;ag&^^Kj^7r(<>|#pReWc^9sM=d& xmt&Tyhh%jeI!s#k*%a)!Ia+S4F?+iaU;w5X-7=dk%(Vaj002ovPDHLkV1k}abK3v_ literal 0 HcmV?d00001 diff --git a/CommonKit/Sources/CommonKit/Assets/Shared.xcassets/Buttons/eye_close.imageset/invisible_dark@3x.png b/CommonKit/Sources/CommonKit/Assets/Shared.xcassets/Buttons/eye_close.imageset/invisible_dark@3x.png new file mode 100644 index 0000000000000000000000000000000000000000..811f676ee57abbbfda0b887959f12164ea1fca9c GIT binary patch literal 1146 zcmV-=1cm#FP)j{0000PbVXQnQ*UN; zcVTj606}DLVr3vnZDD6+Qe|Oed2z{QJOBU$97#k$RCwC#n!jrsF%-vr`3K^;bnsXR zgxk7$>6}2JW4IKuyZ=CY{R4`pOdT$3yBJT+)*;aKGPFyUBwexu977<45FCA}Y?Rlh zA9p96&-fmQBe}DrkKU8slb$-AR$6JLl~!6=Ntw-NsVLob1kYywBwwomP{3y{0}w5t zgFW%+F|RJ7D7vTuE*=T<>a(afU4|xlq9jry`OHmn`F$b>s*J|xlGP2|l^u75bs3CA zXTFU9ms&BGsufrhhHV#~BI@R?UVAt2zUr$d@-ya?xz7{`u zp(}WQb#?WHsU~S6_%5gE$H&JhfXCzU8xHc|;NaEC$;sP!!Qc2lzQ=DH4KB4k;?jzau+UI4~B_~kh6lo zVXRl~&K80lnGGC;R2ma23KFtXi*o5Fcvx>FJ33JpIF}0Pd}lU4!D4mwW7C?X7P0_> zx4R);3`JSYgX81l4=!F|dwct(diY3Qs|(+L6h%jlhmA+#+kIw^Q&+WUZ*OmJ%cZ}B z3%tV(wG-3n^!v`v&Yu!7k?z#BGPrnT1=`aJxWK3UD*^nXbDNu+@5BPKRRX?OBefIo#eOW)x1Q3Hj;F-?PBhIjaTm5{|2rtAbt6_H* zlkalVNm8i@&o#EM89X(`52idoq1fJKS64GQ_igTC@}+ilru{tj$;;9>*C+I+0C=(E zGy_CP@QgD}>Wy%6qyuO21b&XPJnzm@rSuJ1*u>?GL8EcMhnRIH6sW1|JWvpD2Cf!x zX$g6ST!G&#j%F?vo#QSUn{zG!%7K%Uq~)fs3NCJ@T-~$9jm!FK5}?T{fLjG{)SY1v zkPT+cd{QMPRtvZ$f%yy=rp#r1k@weJUm4YM-_GZ`I(IXJTg3>w9?8-%98*C01nVnV!Z literal 0 HcmV?d00001 diff --git a/CommonKit/Sources/CommonKit/Assets/Shared.xcassets/Buttons/eye_open.imageset/Contents.json b/CommonKit/Sources/CommonKit/Assets/Shared.xcassets/Buttons/eye_open.imageset/Contents.json index b6f69963f..68db4069e 100644 --- a/CommonKit/Sources/CommonKit/Assets/Shared.xcassets/Buttons/eye_open.imageset/Contents.json +++ b/CommonKit/Sources/CommonKit/Assets/Shared.xcassets/Buttons/eye_open.imageset/Contents.json @@ -5,15 +5,48 @@ "idiom" : "universal", "scale" : "1x" }, + { + "appearances" : [ + { + "appearance" : "luminosity", + "value" : "dark" + } + ], + "filename" : "visible_dark.png", + "idiom" : "universal", + "scale" : "1x" + }, { "filename" : "visible@2x.png", "idiom" : "universal", "scale" : "2x" }, + { + "appearances" : [ + { + "appearance" : "luminosity", + "value" : "dark" + } + ], + "filename" : "visible_dark@2x.png", + "idiom" : "universal", + "scale" : "2x" + }, { "filename" : "visible@3x.png", "idiom" : "universal", "scale" : "3x" + }, + { + "appearances" : [ + { + "appearance" : "luminosity", + "value" : "dark" + } + ], + "filename" : "visible_dark@3x.png", + "idiom" : "universal", + "scale" : "3x" } ], "info" : { diff --git a/CommonKit/Sources/CommonKit/Assets/Shared.xcassets/Buttons/eye_open.imageset/visible_dark.png b/CommonKit/Sources/CommonKit/Assets/Shared.xcassets/Buttons/eye_open.imageset/visible_dark.png new file mode 100644 index 0000000000000000000000000000000000000000..341f4b539706a7a9a9aab1eb809d7c6585330cea GIT binary patch literal 434 zcmV;j0ZsmiP)P!w%KGNyEP%IN6k zsH0=RKOjP8{RPrLAi*I&!pV+qp#)sb;FQULkg>5DLdeJSo_ZhL7AqN=sSgg1ch9+} z_rBMg$>gY)v5!zn&1mxo7X(*OpM)i0VHid+*pv+CwB7Z6Z29fGM@iiHmW1McU||LW!XcTruTJSe<-nVMjSkJ&~sOR8lDkWND9O7yeNui z_6fiV4bBQYbo`?$t&xT<2nc=OKTVS4mJM$RmxLEij0hgb6?D)GV|S}%5D-x4ghc@x zZmNpo_*zsC;{ZD7h4JrIfW+NsL+qT0RwEc8uEE9;^GfFN9S1N~d7eM&BvgbLcM;(% zO?P-DdH8I&u_`_MoyynO#yhX?ab;@X>(g+&ZN2_z|I^`O26xvtGv9tQ^LBPqsYDV<i~6l-C_fZ+TD(BAJmX1}m0EDM@Bb zCQ7p5I2*Dg6uXtZK~jTU%6d~E{au(aneo|3f`xCyxHZ^KAdx!3fUg*QLRu$1C2g2v zS264}vs?yyfyKrxL9!!OQIji>2EeBM)KhDHu372Z0=BrdaWs=$Pr`|-1GC*=F!%sf zLFJ9Pd{NfD1?+l*$$<0u{HF#9#Wur@BkWq7%5Jy&R@?TY#_H}qQvoRHmVm+*Xpw0@1NKVahD<(r?CF7THa9Gb&-dkXr_B%jZ}-WJ4h9G;-gwGZiA3b2UD>-a9slwwSq;308JuJFDiW)gyZ`BRIn)mj05Tqr se;o7!M65pP#O@h^?f?J)07*qoM6N<$g17WzlK=n! literal 0 HcmV?d00001 diff --git a/CommonKit/Sources/CommonKit/Assets/Shared.xcassets/Buttons/eye_open.imageset/visible_dark@3x.png b/CommonKit/Sources/CommonKit/Assets/Shared.xcassets/Buttons/eye_open.imageset/visible_dark@3x.png new file mode 100644 index 0000000000000000000000000000000000000000..c9b41e0b50289500aa9a60b4cb1c2c7acfdfd5c2 GIT binary patch literal 1209 zcmV;q1V;ObP)j{0000PbVXQnQ*UN; zcVTj606}DLVr3vnZDD6+Qe|Oed2z{QJOBU$TS-JgRCwC#T03hSK@hftF|KTZyO4m4 zn}D0^f~%+rsIwuE(ufe(k@EwR^A89jg)0>@?vloG6?btk#O0}o8#l6(fE$A(WANL` zeqm1>!|d)!x|0>%4h+)0X6Dbb4xHVq)H)qA9p4cx`B+L3-L%x$WlW=GW=zX}MObyRoCPHUaxc0iOV0*&*-Vd3LIg_8H007NP69dq?}q_A37_s0KdAr`pIQyg%NI_y4^4X z0DE9pqyrEZILxTHyN4XHD08UW~1 ztsl>gQ#sQC2a}THnT!sWNS_r0P+??w z!RJ*rDF&cm0`3Goq?%vBdP@}KbOVWmh4RG7#0t)oFlBz|kZ!NQQ z!jM?<<>lph(q#!!N8aV-<$k$bKI?X!fo@OWFvoOt zI6IU?aKXM3z;{YOtJV6lxVU)G!O>7blgTVZsSmh-FK}uD;8%lTl+Wi&VT5gNZXR%9PQ!9?a&ke;%c*XSB@NA8)wCjZ zD2d=|O5E{hdt~on%=YPj{p(b!8VvuLnVC1DKIToMiDafpN>%^Q+}zw+SnuP$+1c55 z!SEFJH90**Kc5@~4&Wj3s&TLIvb2Pn%93J*=fu9ki)n3Nq-%TC6i{?$2eY=1aaUL2 z`ucbda}mFazWiW!busSei(PiE-3Qsvt2~AdNd%poilM2|cPEGG55(Kb>Y<01lhNSH z-<&EDJxC8P@!i|R_w5t^#wMnBb&db#U0vee(kA}>eB!^%7?p2wqW&7|t!#^F4ZFR) z{Vn*9e>^@u{#fu6Q)f$VyFU%>w|R;D4o?#e$ihSU1!khZy|rY?k|j%)ELj>!`YXTy XnlZ&V@Iwb<00000NkvXXu0mjfysJk; literal 0 HcmV?d00001 From 89bc9c7d8ca122c46db8cf18701b6b190937333d Mon Sep 17 00:00:00 2001 From: StanislavDevIOS Date: Wed, 8 Nov 2023 09:19:36 +0200 Subject: [PATCH 47/96] [trello.com/c/YOoBfvdT] fix: QR & PK generation --- Adamant/Modules/Settings/PKGeneratorViewController.swift | 6 +++++- Adamant/Modules/Settings/QRGeneratorViewController.swift | 9 +++++---- 2 files changed, 10 insertions(+), 5 deletions(-) diff --git a/Adamant/Modules/Settings/PKGeneratorViewController.swift b/Adamant/Modules/Settings/PKGeneratorViewController.swift index c72203520..34476f29a 100644 --- a/Adamant/Modules/Settings/PKGeneratorViewController.swift +++ b/Adamant/Modules/Settings/PKGeneratorViewController.swift @@ -115,7 +115,11 @@ final class PKGeneratorViewController: FormViewController { $0.title = String.adamant.pkGenerator.generateButton $0.tag = Rows.generateButton.tag }.onCellSelection { [weak self] (_, row) in - guard let row: TextAreaRow = self?.form.rowBy(tag: Rows.passphrase.tag), let passphrase = row.value else { + guard let row: PasswordRow = self?.form.rowBy(tag: Rows.passphrase.tag), + let passphrase = row.value, + AdamantUtilities.validateAdamantPassphrase(passphrase: passphrase) + else { + self?.dialogService.showToastMessage(String.adamant.qrGenerator.wrongPassphraseError) return } diff --git a/Adamant/Modules/Settings/QRGeneratorViewController.swift b/Adamant/Modules/Settings/QRGeneratorViewController.swift index a50a93257..4a5438b00 100644 --- a/Adamant/Modules/Settings/QRGeneratorViewController.swift +++ b/Adamant/Modules/Settings/QRGeneratorViewController.swift @@ -180,10 +180,11 @@ final class QRGeneratorViewController: FormViewController { // MARK: - QR Tools extension QRGeneratorViewController { func generateQr() { - guard let row: TextAreaRow = form.rowBy(tag: Rows.passphrase.tag), - let passphrase = row.value?.lowercased(), // Lowercased! - AdamantUtilities.validateAdamantPassphrase(passphrase: passphrase) else { - dialogService.showToastMessage(String.adamant.qrGenerator.wrongPassphraseError) + guard let row: PasswordRow = form.rowBy(tag: Rows.passphrase.tag), + let passphrase = row.value?.lowercased(), + AdamantUtilities.validateAdamantPassphrase(passphrase: passphrase) + else { + dialogService.showToastMessage(String.adamant.qrGenerator.wrongPassphraseError) return } From ee8ede9e449516236147a3bb14f4e42894d188fd Mon Sep 17 00:00:00 2001 From: StanislavDevIOS Date: Wed, 8 Nov 2023 09:44:26 +0200 Subject: [PATCH 48/96] [trello.com/c/wn2pjGrh] fix: action limits for messages --- Adamant/Modules/Chat/ViewModel/ChatViewModel.swift | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/Adamant/Modules/Chat/ViewModel/ChatViewModel.swift b/Adamant/Modules/Chat/ViewModel/ChatViewModel.swift index 71857c63f..966258bf0 100644 --- a/Adamant/Modules/Chat/ViewModel/ChatViewModel.swift +++ b/Adamant/Modules/Chat/ViewModel/ChatViewModel.swift @@ -453,6 +453,9 @@ final class ChatViewModel: NSObject { } func replyMessageIfNeeded(_ messageModel: MessageModel?) { + let tx = chatTransactions.first(where: { $0.txId == messageModel?.id }) + guard isSendingAvailable, tx?.isFake == false else { return } + let message = messages.first(where: { $0.messageId == messageModel?.id }) guard message?.status != .failed else { dialog.send(.warning(String.adamant.reply.failedMessageError)) @@ -564,9 +567,11 @@ final class ChatViewModel: NSObject { let tx = chatTransactions.first(where: { $0.txId == arg.messageId }) guard tx?.statusEnum == .delivered else { return } + let presentReactions = isSendingAvailable && tx?.isFake == false + dialog.send( .presentMenu( - presentReactions: tx?.isFake == false, + presentReactions: presentReactions, arg: arg, didSelectEmojiDelegate: self, didSelectEmojiAction: didSelectEmojiAction, From 114b0c9cd0b97902310d2772955e5d5edee8e47b Mon Sep 17 00:00:00 2001 From: StanislavDevIOS Date: Wed, 8 Nov 2023 09:44:54 +0200 Subject: [PATCH 49/96] [trello.com/c/wn2pjGrh] fix: menu color in dark theme --- .../Sources/CommonKit/Helpers/UIHelpers/UIColor+adamant.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CommonKit/Sources/CommonKit/Helpers/UIHelpers/UIColor+adamant.swift b/CommonKit/Sources/CommonKit/Helpers/UIHelpers/UIColor+adamant.swift index 10d883295..7b6f8d864 100644 --- a/CommonKit/Sources/CommonKit/Helpers/UIHelpers/UIColor+adamant.swift +++ b/CommonKit/Sources/CommonKit/Helpers/UIHelpers/UIColor+adamant.swift @@ -236,7 +236,7 @@ extension UIColor { public static var contextMenuDefaultBackgroundColor: UIColor { let colorWhiteTheme = UIColor(red: 0.95, green: 0.95, blue: 0.95, alpha: 1) - let colorDarkTheme = UIColor(red: 0.264, green: 0.264, blue: 0.264, alpha: 1) + let colorDarkTheme = UIColor(red: 0.23, green: 0.23, blue: 0.23, alpha: 1) return returnColorByTheme(colorWhiteTheme: colorWhiteTheme, colorDarkTheme: colorDarkTheme) } From 7b6e3b0a411201497ec0104d6e784bdb6f12014a Mon Sep 17 00:00:00 2001 From: StanislavDevIOS Date: Wed, 8 Nov 2023 13:27:15 +0200 Subject: [PATCH 50/96] [trello.com/c/Zj2WKNS3] Updated logic of loading messages --- .../DataProviders/AdamantChatsProvider.swift | 51 +++++++++++++------ 1 file changed, 35 insertions(+), 16 deletions(-) diff --git a/Adamant/Services/DataProviders/AdamantChatsProvider.swift b/Adamant/Services/DataProviders/AdamantChatsProvider.swift index 5392e8522..a0137af9a 100644 --- a/Adamant/Services/DataProviders/AdamantChatsProvider.swift +++ b/Adamant/Services/DataProviders/AdamantChatsProvider.swift @@ -428,6 +428,14 @@ extension AdamantChatsProvider { } func getChatMessages(with addressRecipient: String, offset: Int?) async { + await getChatMessages(with: addressRecipient, offset: offset, loadedCount: .zero) + } + + func getChatMessages( + with addressRecipient: String, + offset: Int?, + loadedCount: Int + ) async { guard let address = accountService.account?.address, let privateKey = accountService.keypair?.privateKey else { return @@ -467,7 +475,8 @@ extension AdamantChatsProvider { result: result, chatroom: chatroom, offset: offset, - addressRecipient: addressRecipient + addressRecipient: addressRecipient, + loadedCount: loadedCount ) } @@ -475,30 +484,40 @@ extension AdamantChatsProvider { result: (reactionsCount: Int, totalCount: Int), chatroom: ChatRooms?, offset: Int?, - addressRecipient: String + addressRecipient: String, + loadedCount: Int ) async { let messageCount = chatroom?.messages?.count ?? 0 let minRectionsCount = result.totalCount * minReactionsProcent / 100 - if result.reactionsCount >= minRectionsCount { - let offset = (offset ?? 0) + messageCount - - let loadedCount = chatLoadedMessages[addressRecipient] ?? 0 - chatLoadedMessages[addressRecipient] = loadedCount + messageCount + let newLoadedCount = loadedCount + (result.totalCount - result.reactionsCount) + + guard result.reactionsCount > minRectionsCount, + newLoadedCount < chatTransactionsLimit + else { + setChatDoneStatus( + for: addressRecipient, + messageCount: messageCount, + maxCount: chatroom?.count + ) - return await getChatMessages( - with: addressRecipient, - offset: offset + NotificationCenter.default.post( + name: .AdamantChatsProvider.initiallyLoadedMessages, + object: addressRecipient ) + return } - setChatDoneStatus( - for: addressRecipient, - messageCount: messageCount, - maxCount: chatroom?.count - ) + let offset = (offset ?? 0) + messageCount + + let loadedCount = chatLoadedMessages[addressRecipient] ?? 0 + chatLoadedMessages[addressRecipient] = loadedCount + messageCount - NotificationCenter.default.post(name: .AdamantChatsProvider.initiallyLoadedMessages, object: addressRecipient) + return await getChatMessages( + with: addressRecipient, + offset: offset, + loadedCount: newLoadedCount + ) } func setChatDoneStatus( From 1ecd2510f9ff31dd9b09dd9b5d49ba51dd9b6d6a Mon Sep 17 00:00:00 2001 From: StanislavDevIOS Date: Mon, 13 Nov 2023 10:58:36 +0200 Subject: [PATCH 51/96] =?UTF-8?q?[trello.com/c/62gzq6Sj]=20fix:=20partner?= =?UTF-8?q?=E2=80=99s=20name=20&=20add=20px=20top=20padding=20for=20avatar?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../ChatsList/ChatListViewController.swift | 25 +++++++++++++------ .../Views/NotificationView.swift | 1 + 2 files changed, 18 insertions(+), 8 deletions(-) diff --git a/Adamant/Modules/ChatsList/ChatListViewController.swift b/Adamant/Modules/ChatsList/ChatListViewController.swift index 24f527b30..f92838381 100644 --- a/Adamant/Modules/ChatsList/ChatListViewController.swift +++ b/Adamant/Modules/ChatsList/ChatListViewController.swift @@ -793,14 +793,21 @@ extension ChatListViewController { } // MARK: 1. Show notification only for incomming transactions - guard !transaction.silentNotification, !transaction.isOutgoing, - let chatroom = transaction.chatroom, chatroom != presentedChatroom(), !chatroom.isHidden, - let partner = chatroom.partner else { + guard !transaction.silentNotification, + !transaction.isOutgoing, + let chatroom = transaction.chatroom, + chatroom != presentedChatroom(), + !chatroom.isHidden, + let partner = chatroom.partner, + let address = partner.address + else { return } // MARK: 2. Prepare notification - let title = partner.name ?? partner.address + + let name: String? = partner.name ?? addressBook.getName(for: address) + let title = name ?? partner.address let text = shortDescription(for: transaction) let image: UIImage @@ -813,10 +820,12 @@ extension ChatListViewController { } // MARK: 4. Show notification with tap handler - dialogService.showNotification(title: title?.checkAndReplaceSystemWallets(), message: text?.string, image: image) { [weak self] in - DispatchQueue.main.async { - self?.presentChatroom(chatroom) - } + dialogService.showNotification( + title: title?.checkAndReplaceSystemWallets(), + message: text?.string, + image: image + ) { [weak self] in + self?.presentChatroom(chatroom) } } } diff --git a/PopupKit/Sources/PopupKit/Implementation/Views/NotificationView.swift b/PopupKit/Sources/PopupKit/Implementation/Views/NotificationView.swift index 153b25dfb..acc355891 100644 --- a/PopupKit/Sources/PopupKit/Implementation/Views/NotificationView.swift +++ b/PopupKit/Sources/PopupKit/Implementation/Views/NotificationView.swift @@ -44,6 +44,7 @@ private extension NotificationView { .foregroundColor(.secondary) .scaledToFit() .frame(squareSize: 30) + .padding(.top, 2) } var textStack: some View { From f39f3dbd6f207c691a2af84a5a77c6883be6e880 Mon Sep 17 00:00:00 2001 From: StanislavDevIOS Date: Mon, 13 Nov 2023 11:50:08 +0200 Subject: [PATCH 52/96] [trello.com/c/rPkNd6IB] fix: replace "updating" status --- Adamant/Models/BTCRawTransaction.swift | 2 +- Adamant/Models/TransactionStatus.swift | 2 +- .../Modules/Wallets/TransactionDetailsViewControllerBase.swift | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Adamant/Models/BTCRawTransaction.swift b/Adamant/Models/BTCRawTransaction.swift index 7c1824e23..22fa94df3 100644 --- a/Adamant/Models/BTCRawTransaction.swift +++ b/Adamant/Models/BTCRawTransaction.swift @@ -35,7 +35,7 @@ struct BTCRawTransaction { transactionStatus = confirmations > 0 ? .success : .pending } else { confirmationsValue = nil - transactionStatus = .pending + transactionStatus = .notInitiated } // Transfers diff --git a/Adamant/Models/TransactionStatus.swift b/Adamant/Models/TransactionStatus.swift index 6a1ebeba9..a97a13dae 100644 --- a/Adamant/Models/TransactionStatus.swift +++ b/Adamant/Models/TransactionStatus.swift @@ -22,7 +22,7 @@ enum TransactionStatus: Int16 { var localized: String { switch self { case .notInitiated: - return .localized("TransactionStatus.Updating", comment: "Transaction status: updating in progress") + return "⏱" case .pending, .registered: return .localized("TransactionStatus.Pending", comment: "Transaction status: transaction is pending") case .success: diff --git a/Adamant/Modules/Wallets/TransactionDetailsViewControllerBase.swift b/Adamant/Modules/Wallets/TransactionDetailsViewControllerBase.swift index d7b8d6ee9..24935028c 100644 --- a/Adamant/Modules/Wallets/TransactionDetailsViewControllerBase.swift +++ b/Adamant/Modules/Wallets/TransactionDetailsViewControllerBase.swift @@ -164,7 +164,7 @@ class TransactionDetailsViewControllerBase: FormViewController { return dateFormatter }() - static let awaitingValueString = "⏱" + static let awaitingValueString = TransactionStatus.notInitiated.localized private lazy var currencyFormatter: NumberFormatter = { return AdamantBalanceFormat.currencyFormatter(for: .full, currencySymbol: currencySymbol) From 6dd4d75b1859a0970d83fb62d34f32b6a1b7b63f Mon Sep 17 00:00:00 2001 From: StanislavDevIOS Date: Mon, 13 Nov 2023 12:19:26 +0200 Subject: [PATCH 53/96] [trello.com/c/PNNpGdAn] fix: remove slashes and question mark if no needed --- Adamant/Utilities/AdamantUriTools.swift | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/Adamant/Utilities/AdamantUriTools.swift b/Adamant/Utilities/AdamantUriTools.swift index bd860c1bc..0d7456b72 100644 --- a/Adamant/Utilities/AdamantUriTools.swift +++ b/Adamant/Utilities/AdamantUriTools.swift @@ -85,7 +85,7 @@ final class AdamantUriTools { var components = URLComponents() components.scheme = AdmWalletService.qqPrefix components.host = address - components.queryItems = [] + components.queryItems = (params?.count ?? .zero) > .zero ? [] : nil params?.forEach { switch $0 { @@ -98,7 +98,8 @@ final class AdamantUriTools { } } - guard let uri = components.url?.absoluteString else { return "" } + guard let uri = components.url?.absoluteString.replacingOccurrences(of: "://", with: ":") + else { return "" } return uri } From c090849fe0c5939de5b871dde06e9442e5465edc Mon Sep 17 00:00:00 2001 From: StanislavDevIOS Date: Mon, 13 Nov 2023 13:25:03 +0200 Subject: [PATCH 54/96] [trello.com/c/EdiYUEfs] rename vibrations name, hide vibration row --- .../Account/AccountViewController.swift | 71 +++++++++++-------- .../Settings/AboutViewController.swift | 24 +++++++ .../VibrationSelectionView.swift | 22 +++--- Adamant/ServiceProtocols/VibroService.swift | 8 +++ 4 files changed, 87 insertions(+), 38 deletions(-) diff --git a/Adamant/Modules/Account/AccountViewController.swift b/Adamant/Modules/Account/AccountViewController.swift index fef6276b5..3ef091cfe 100644 --- a/Adamant/Modules/Account/AccountViewController.swift +++ b/Adamant/Modules/Account/AccountViewController.swift @@ -13,6 +13,7 @@ import CoreData import Parchment import SnapKit import CommonKit +import Combine // MARK: - Localization extension String.adamant { @@ -154,6 +155,7 @@ final class AccountViewController: FormViewController { private var initiated = false private var walletViewControllers = [WalletViewController]() + private var notificationsSet: Set = [] // MARK: StayIn @@ -373,35 +375,6 @@ final class AccountViewController: FormViewController { appSection.append(contributeRow) - // Contribute - let vibrationRow = LabelRow { - $0.title = Rows.vibration.localized - $0.tag = Rows.vibration.tag - $0.cell.imageView?.image = Rows.vibration.image - $0.cell.selectionStyle = .gray - }.cellUpdate { (cell, _) in - cell.accessoryType = .disclosureIndicator - }.onCellSelection { [weak self] (_, _) in - guard let vc = self?.screensFactory.makeVibrationSelection() - else { - return - } - - if let split = self?.splitViewController { - let details = UINavigationController(rootViewController:vc) - split.showDetailViewController(details, sender: self) - } else if let nav = self?.navigationController { - nav.pushViewController(vc, animated: true) - } else { - vc.modalPresentationStyle = .overFullScreen - self?.present(vc, animated: true, completion: nil) - } - - self?.deselectWalletViewControllers() - } - - appSection.append(vibrationRow) - // About let aboutRow = LabelRow { $0.title = Rows.about.localized @@ -820,6 +793,13 @@ final class AccountViewController: FormViewController { queue: OperationQueue.main, using: callback) } + + NotificationCenter.default + .publisher(for: .AdamantVibroService.presentVibrationRow) + .sink { [weak self] _ in + self?.addVibrationRow() + } + .store(in: ¬ificationsSet) } private func setupWalletsVC() { @@ -925,6 +905,39 @@ final class AccountViewController: FormViewController { self.accountService.reloadWallets() } } + + private func addVibrationRow() { + guard let appSection = form.sectionBy(tag: Sections.application.tag) + else { return } + + let vibrationRow = LabelRow { + $0.title = Rows.vibration.localized + $0.tag = Rows.vibration.tag + $0.cell.imageView?.image = Rows.vibration.image + $0.cell.selectionStyle = .gray + }.cellUpdate { (cell, _) in + cell.accessoryType = .disclosureIndicator + }.onCellSelection { [weak self] (_, _) in + guard let vc = self?.screensFactory.makeVibrationSelection() + else { + return + } + + if let split = self?.splitViewController { + let details = UINavigationController(rootViewController:vc) + split.showDetailViewController(details, sender: self) + } else if let nav = self?.navigationController { + nav.pushViewController(vc, animated: true) + } else { + vc.modalPresentationStyle = .overFullScreen + self?.present(vc, animated: true, completion: nil) + } + + self?.deselectWalletViewControllers() + } + + appSection.append(vibrationRow) + } } // MARK: - AccountHeaderViewDelegate diff --git a/Adamant/Modules/Settings/AboutViewController.swift b/Adamant/Modules/Settings/AboutViewController.swift index 7f9225c09..79b6796b0 100644 --- a/Adamant/Modules/Settings/AboutViewController.swift +++ b/Adamant/Modules/Settings/AboutViewController.swift @@ -109,6 +109,8 @@ final class AboutViewController: FormViewController { // MARK: Properties private var storedIOSSupportMessage: String? + private var numerOfTap = 0 + private let maxNumerOfTap = 10 // MARK: Lifecycle @@ -120,6 +122,13 @@ final class AboutViewController: FormViewController { // MARK: Header & Footer if let header = UINib(nibName: "LogoFullHeader", bundle: nil).instantiate(withOwner: nil, options: nil).first as? UIView { + + let tapGestureRecognizer = UITapGestureRecognizer( + target: self, + action: #selector(tapAction) + ) + header.addGestureRecognizer(tapGestureRecognizer) + tableView.tableHeaderView = header if let label = header.viewWithTag(888) as? UILabel { @@ -357,3 +366,18 @@ extension AboutViewController: ChatPreservationDelegate { } } } + +private extension AboutViewController { + @objc func tapAction() { + numerOfTap += 1 + + guard numerOfTap == maxNumerOfTap else { + return + } + + NotificationCenter.default.post( + name: .AdamantVibroService.presentVibrationRow, + object: nil + ) + } +} diff --git a/Adamant/Modules/TestVibration/VibrationSelectionView.swift b/Adamant/Modules/TestVibration/VibrationSelectionView.swift index b3ae21e67..cebabd5df 100644 --- a/Adamant/Modules/TestVibration/VibrationSelectionView.swift +++ b/Adamant/Modules/TestVibration/VibrationSelectionView.swift @@ -7,6 +7,7 @@ // import SwiftUI +import CommonKit struct VibrationSelectionView: View { @StateObject var viewModel: VibrationSelectionViewModel @@ -25,28 +26,31 @@ struct VibrationSelectionView: View { } } } + .withoutListBackground() + .background(Color(.adamant.secondBackgroundColor)) + .navigationTitle("Vibrations") } private func vibrationTypeDescription(_ type: AdamantVibroType) -> String { switch type { case .light: - return "Light Vibration" + return "Single-Short-Light (1SL Vibartion)" case .rigid: - return "Rigid Vibration" + return "Single-Short-Rigid (1SR Vibartion)" case .heavy: - return "Heavy Vibration" + return "Single-Long-Rigid (1LR Vibartion)" case .medium: - return "Medium Vibration" + return "Single-Short-Medium (1SM Vibartion)" case .soft: - return "Soft Vibration" + return "Single-Long-Soft (1LS Vibartion)" case .selection: - return "Selection Vibration" + return "Single-Short-Soft (1SS Vibartion)" case .success: - return "Success Vibration" + return "Double-Short-Medium (2SM Vibartion)" case .warning: - return "Warning Vibration" + return "Double-Long-Medium (2LM Vibartion)" case .error: - return "Error Vibration" + return "Tripple-Long-Medium (3LM Vibartion)" } } } diff --git a/Adamant/ServiceProtocols/VibroService.swift b/Adamant/ServiceProtocols/VibroService.swift index 3bc7bc0fc..cbb7dcb9e 100644 --- a/Adamant/ServiceProtocols/VibroService.swift +++ b/Adamant/ServiceProtocols/VibroService.swift @@ -8,6 +8,14 @@ import Foundation +// MARK: - Notifications +extension Notification.Name { + struct AdamantVibroService { + static let presentVibrationRow = Notification.Name("adamant.vibroService.presentVibrationRow") + + } +} + protocol VibroService: AnyObject { func applyVibration(_ type: AdamantVibroType) } From 8dee0ebe5a79e4e39fc73d3b6ef91ae82e6afd55 Mon Sep 17 00:00:00 2001 From: StanislavDevIOS Date: Mon, 13 Nov 2023 17:28:36 +0200 Subject: [PATCH 55/96] [trello.com/c/IjMYWdqg] feat: sync status transaction in list and details screen --- .../AdmTransactionDetailsViewController.swift | 3 ++- .../Modules/Wallets/Adamant/AdmWalletService.swift | 2 ++ .../Modules/Wallets/Bitcoin/BtcWalletFactory.swift | 3 ++- .../Modules/Wallets/Bitcoin/BtcWalletService.swift | 4 ++++ Adamant/Modules/Wallets/Dash/DashWalletFactory.swift | 3 ++- Adamant/Modules/Wallets/Dash/DashWalletService.swift | 4 ++++ Adamant/Modules/Wallets/Doge/DogeWalletFactory.swift | 3 ++- Adamant/Modules/Wallets/Doge/DogeWalletService.swift | 4 ++++ .../Modules/Wallets/ERC20/ERC20WalletFactory.swift | 3 ++- .../Modules/Wallets/ERC20/ERC20WalletService.swift | 4 ++++ .../Modules/Wallets/Ethereum/EthWalletFactory.swift | 3 ++- .../Modules/Wallets/Ethereum/EthWalletService.swift | 4 ++++ Adamant/Modules/Wallets/Lisk/LskWalletFactory.swift | 3 ++- Adamant/Modules/Wallets/Lisk/LskWalletService.swift | 4 ++++ .../TransactionDetailsViewControllerBase.swift | 12 +++++++++++- Adamant/Modules/Wallets/WalletService.swift | 1 + 16 files changed, 52 insertions(+), 8 deletions(-) diff --git a/Adamant/Modules/Wallets/Adamant/AdmTransactionDetailsViewController.swift b/Adamant/Modules/Wallets/Adamant/AdmTransactionDetailsViewController.swift index 9e59fd60f..072bee533 100644 --- a/Adamant/Modules/Wallets/Adamant/AdmTransactionDetailsViewController.swift +++ b/Adamant/Modules/Wallets/Adamant/AdmTransactionDetailsViewController.swift @@ -58,7 +58,8 @@ final class AdmTransactionDetailsViewController: TransactionDetailsViewControlle dialogService: dialogService, currencyInfo: currencyInfo, addressBookService: addressBookService, - accountService: accountService + accountService: accountService, + walletService: nil ) } diff --git a/Adamant/Modules/Wallets/Adamant/AdmWalletService.swift b/Adamant/Modules/Wallets/Adamant/AdmWalletService.swift index 1bac34a96..68a705254 100644 --- a/Adamant/Modules/Wallets/Adamant/AdmWalletService.swift +++ b/Adamant/Modules/Wallets/Adamant/AdmWalletService.swift @@ -189,6 +189,8 @@ final class AdmWalletService: NSObject, WalletService { func loadTransactions(offset: Int, limit: Int) async throws -> Int { .zero } func getLocalTransactionHistory() -> [TransactionDetails] { [] } + + func updateStatus(for id: String, status: TransactionStatus?) { } } // MARK: - NSFetchedResultsControllerDelegate diff --git a/Adamant/Modules/Wallets/Bitcoin/BtcWalletFactory.swift b/Adamant/Modules/Wallets/Bitcoin/BtcWalletFactory.swift index 0b64544b3..45d36d3d0 100644 --- a/Adamant/Modules/Wallets/Bitcoin/BtcWalletFactory.swift +++ b/Adamant/Modules/Wallets/Bitcoin/BtcWalletFactory.swift @@ -128,7 +128,8 @@ private extension BtcWalletFactory { dialogService: assembler.resolve(DialogService.self)!, currencyInfo: assembler.resolve(CurrencyInfoService.self)!, addressBookService: assembler.resolve(AddressBookService.self)!, - accountService: assembler.resolve(AccountService.self)! + accountService: assembler.resolve(AccountService.self)!, + walletService: service ) vc.service = service diff --git a/Adamant/Modules/Wallets/Bitcoin/BtcWalletService.swift b/Adamant/Modules/Wallets/Bitcoin/BtcWalletService.swift index a59791834..a9c292135 100644 --- a/Adamant/Modules/Wallets/Bitcoin/BtcWalletService.swift +++ b/Adamant/Modules/Wallets/Bitcoin/BtcWalletService.swift @@ -752,6 +752,10 @@ extension BtcWalletService { func getLocalTransactionHistory() -> [TransactionDetails] { transactions } + + func updateStatus(for id: String, status: TransactionStatus?) { + coinStorage.updateStatus(for: id, status: status) + } } // MARK: - PrivateKey generator diff --git a/Adamant/Modules/Wallets/Dash/DashWalletFactory.swift b/Adamant/Modules/Wallets/Dash/DashWalletFactory.swift index 1622dda85..ffa3e29af 100644 --- a/Adamant/Modules/Wallets/Dash/DashWalletFactory.swift +++ b/Adamant/Modules/Wallets/Dash/DashWalletFactory.swift @@ -137,7 +137,8 @@ private extension DashWalletFactory { dialogService: assembler.resolve(DialogService.self)!, currencyInfo: assembler.resolve(CurrencyInfoService.self)!, addressBookService: assembler.resolve(AddressBookService.self)!, - accountService: assembler.resolve(AccountService.self)! + accountService: assembler.resolve(AccountService.self)!, + walletService: service ) vc.service = service diff --git a/Adamant/Modules/Wallets/Dash/DashWalletService.swift b/Adamant/Modules/Wallets/Dash/DashWalletService.swift index 1683afa85..4d126dc01 100644 --- a/Adamant/Modules/Wallets/Dash/DashWalletService.swift +++ b/Adamant/Modules/Wallets/Dash/DashWalletService.swift @@ -473,6 +473,10 @@ extension DashWalletService { func getLocalTransactionHistory() -> [TransactionDetails] { historyTransactions } + + func updateStatus(for id: String, status: TransactionStatus?) { + coinStorage.updateStatus(for: id, status: status) + } } // MARK: - KVS diff --git a/Adamant/Modules/Wallets/Doge/DogeWalletFactory.swift b/Adamant/Modules/Wallets/Doge/DogeWalletFactory.swift index 0bcc4a5a2..2f6230e57 100644 --- a/Adamant/Modules/Wallets/Doge/DogeWalletFactory.swift +++ b/Adamant/Modules/Wallets/Doge/DogeWalletFactory.swift @@ -127,7 +127,8 @@ private extension DogeWalletFactory { dialogService: assembler.resolve(DialogService.self)!, currencyInfo: assembler.resolve(CurrencyInfoService.self)!, addressBookService: assembler.resolve(AddressBookService.self)!, - accountService: assembler.resolve(AccountService.self)! + accountService: assembler.resolve(AccountService.self)!, + walletService: service ) vc.service = service diff --git a/Adamant/Modules/Wallets/Doge/DogeWalletService.swift b/Adamant/Modules/Wallets/Doge/DogeWalletService.swift index cd82b10f0..e8f7437bd 100644 --- a/Adamant/Modules/Wallets/Doge/DogeWalletService.swift +++ b/Adamant/Modules/Wallets/Doge/DogeWalletService.swift @@ -657,6 +657,10 @@ extension DogeWalletService { func getLocalTransactionHistory() -> [TransactionDetails] { historyTransactions } + + func updateStatus(for id: String, status: TransactionStatus?) { + coinStorage.updateStatus(for: id, status: status) + } } // MARK: - PrivateKey generator diff --git a/Adamant/Modules/Wallets/ERC20/ERC20WalletFactory.swift b/Adamant/Modules/Wallets/ERC20/ERC20WalletFactory.swift index e08e36865..498fa885d 100644 --- a/Adamant/Modules/Wallets/ERC20/ERC20WalletFactory.swift +++ b/Adamant/Modules/Wallets/ERC20/ERC20WalletFactory.swift @@ -129,7 +129,8 @@ private extension ERC20WalletFactory { dialogService: assembler.resolve(DialogService.self)!, currencyInfo: assembler.resolve(CurrencyInfoService.self)!, addressBookService: assembler.resolve(AddressBookService.self)!, - accountService: assembler.resolve(AccountService.self)! + accountService: assembler.resolve(AccountService.self)!, + walletService: service ) vc.service = service diff --git a/Adamant/Modules/Wallets/ERC20/ERC20WalletService.swift b/Adamant/Modules/Wallets/ERC20/ERC20WalletService.swift index 8519d4fa3..7e8deafbd 100644 --- a/Adamant/Modules/Wallets/ERC20/ERC20WalletService.swift +++ b/Adamant/Modules/Wallets/ERC20/ERC20WalletService.swift @@ -702,4 +702,8 @@ extension ERC20WalletService { func getLocalTransactionHistory() -> [TransactionDetails] { historyTransactions } + + func updateStatus(for id: String, status: TransactionStatus?) { + coinStorage.updateStatus(for: id, status: status) + } } diff --git a/Adamant/Modules/Wallets/Ethereum/EthWalletFactory.swift b/Adamant/Modules/Wallets/Ethereum/EthWalletFactory.swift index 39413f4c2..e60e6771b 100644 --- a/Adamant/Modules/Wallets/Ethereum/EthWalletFactory.swift +++ b/Adamant/Modules/Wallets/Ethereum/EthWalletFactory.swift @@ -127,7 +127,8 @@ private extension EthWalletFactory { dialogService: assembler.resolve(DialogService.self)!, currencyInfo: assembler.resolve(CurrencyInfoService.self)!, addressBookService: assembler.resolve(AddressBookService.self)!, - accountService: assembler.resolve(AccountService.self)! + accountService: assembler.resolve(AccountService.self)!, + walletService: service ) vc.service = service diff --git a/Adamant/Modules/Wallets/Ethereum/EthWalletService.swift b/Adamant/Modules/Wallets/Ethereum/EthWalletService.swift index 473ff3674..a307986b3 100644 --- a/Adamant/Modules/Wallets/Ethereum/EthWalletService.swift +++ b/Adamant/Modules/Wallets/Ethereum/EthWalletService.swift @@ -822,6 +822,10 @@ extension EthWalletService { func getLocalTransactionHistory() -> [TransactionDetails] { historyTransactions } + + func updateStatus(for id: String, status: TransactionStatus?) { + coinStorage.updateStatus(for: id, status: status) + } } // MARK: - PrivateKey generator diff --git a/Adamant/Modules/Wallets/Lisk/LskWalletFactory.swift b/Adamant/Modules/Wallets/Lisk/LskWalletFactory.swift index 868632868..936a03423 100644 --- a/Adamant/Modules/Wallets/Lisk/LskWalletFactory.swift +++ b/Adamant/Modules/Wallets/Lisk/LskWalletFactory.swift @@ -127,7 +127,8 @@ private extension LskWalletFactory { dialogService: assembler.resolve(DialogService.self)!, currencyInfo: assembler.resolve(CurrencyInfoService.self)!, addressBookService: assembler.resolve(AddressBookService.self)!, - accountService: assembler.resolve(AccountService.self)! + accountService: assembler.resolve(AccountService.self)!, + walletService: service ) vc.service = service diff --git a/Adamant/Modules/Wallets/Lisk/LskWalletService.swift b/Adamant/Modules/Wallets/Lisk/LskWalletService.swift index 7fec66c23..ba7f3fe78 100644 --- a/Adamant/Modules/Wallets/Lisk/LskWalletService.swift +++ b/Adamant/Modules/Wallets/Lisk/LskWalletService.swift @@ -725,6 +725,10 @@ extension LskWalletService { } } } + + func updateStatus(for id: String, status: TransactionStatus?) { + coinStorage.updateStatus(for: id, status: status) + } } // MARK: - PrivateKey generator diff --git a/Adamant/Modules/Wallets/TransactionDetailsViewControllerBase.swift b/Adamant/Modules/Wallets/TransactionDetailsViewControllerBase.swift index d7b8d6ee9..4fa6f488a 100644 --- a/Adamant/Modules/Wallets/TransactionDetailsViewControllerBase.swift +++ b/Adamant/Modules/Wallets/TransactionDetailsViewControllerBase.swift @@ -147,6 +147,7 @@ class TransactionDetailsViewControllerBase: FormViewController { let currencyInfo: CurrencyInfoService let addressBookService: AddressBookService let accountService: AccountService + let walletService: WalletService? // MARK: - Properties @@ -155,6 +156,13 @@ class TransactionDetailsViewControllerBase: FormViewController { if !isFiatSet { self.updateFiat() } + + guard let id = transaction?.txId else { return } + + walletService?.updateStatus( + for: id, + status: transaction?.transactionStatus + ) } } private lazy var dateFormatter: DateFormatter = { @@ -245,12 +253,14 @@ class TransactionDetailsViewControllerBase: FormViewController { dialogService: DialogService, currencyInfo: CurrencyInfoService, addressBookService: AddressBookService, - accountService: AccountService + accountService: AccountService, + walletService: WalletService? ) { self.dialogService = dialogService self.currencyInfo = currencyInfo self.addressBookService = addressBookService self.accountService = accountService + self.walletService = walletService super.init(style: .grouped) } diff --git a/Adamant/Modules/Wallets/WalletService.swift b/Adamant/Modules/Wallets/WalletService.swift index 6f3a07d62..4c231be85 100644 --- a/Adamant/Modules/Wallets/WalletService.swift +++ b/Adamant/Modules/Wallets/WalletService.swift @@ -251,6 +251,7 @@ protocol WalletService: AnyObject { func getBalance(address: String) async throws -> Decimal func loadTransactions(offset: Int, limit: Int) async throws -> Int func getLocalTransactionHistory() -> [TransactionDetails] + func updateStatus(for id: String, status: TransactionStatus?) } protocol SwinjectDependentService: WalletService { From a14d6369fb6742bc79f6abd5716bb3c887c73025 Mon Sep 17 00:00:00 2001 From: StanislavDevIOS Date: Tue, 14 Nov 2023 14:19:46 +0200 Subject: [PATCH 56/96] [trello.com/c/vxmIWHn2] feat: scroll big message to the end --- .../Helpers/UIHelpers/UIColor+adamant.swift | 4 ++-- .../ContextMenuOverlayView.swift | 24 +++++++++++++++++++ .../ContextMenuOverlayViewModel.swift | 1 + 3 files changed, 27 insertions(+), 2 deletions(-) diff --git a/CommonKit/Sources/CommonKit/Helpers/UIHelpers/UIColor+adamant.swift b/CommonKit/Sources/CommonKit/Helpers/UIHelpers/UIColor+adamant.swift index 7b6f8d864..5e4077409 100644 --- a/CommonKit/Sources/CommonKit/Helpers/UIHelpers/UIColor+adamant.swift +++ b/CommonKit/Sources/CommonKit/Helpers/UIHelpers/UIColor+adamant.swift @@ -105,7 +105,7 @@ extension UIColor { /// Reactions background color public static var reactionsBackground: UIColor { - let colorWhiteTheme = UIColor(red: 0.29, green: 0.29, blue: 0.29, alpha: 0.1) + let colorWhiteTheme = UIColor(red: 0.95, green: 0.95, blue: 0.95, alpha: 1) let colorDarkTheme = UIColor(red: 0.264, green: 0.264, blue: 0.264, alpha: 1) return returnColorByTheme(colorWhiteTheme: colorWhiteTheme, colorDarkTheme: colorDarkTheme) } @@ -236,7 +236,7 @@ extension UIColor { public static var contextMenuDefaultBackgroundColor: UIColor { let colorWhiteTheme = UIColor(red: 0.95, green: 0.95, blue: 0.95, alpha: 1) - let colorDarkTheme = UIColor(red: 0.23, green: 0.23, blue: 0.23, alpha: 1) + let colorDarkTheme = UIColor(red: 0.264, green: 0.264, blue: 0.264, alpha: 1) return returnColorByTheme(colorWhiteTheme: colorWhiteTheme, colorDarkTheme: colorDarkTheme) } diff --git a/pckg/AdvancedContextMenuKit/Sources/AdvancedContextMenuKit/Implementation/ContextMenuOverlayView.swift b/pckg/AdvancedContextMenuKit/Sources/AdvancedContextMenuKit/Implementation/ContextMenuOverlayView.swift index 2e13c1bf4..9b8624de0 100644 --- a/pckg/AdvancedContextMenuKit/Sources/AdvancedContextMenuKit/Implementation/ContextMenuOverlayView.swift +++ b/pckg/AdvancedContextMenuKit/Sources/AdvancedContextMenuKit/Implementation/ContextMenuOverlayView.swift @@ -62,6 +62,7 @@ struct ContextMenuOverlayView: View { viewModel.additionalMenuVisible.toggle() } viewModel.delegate?.didAppear() + viewModel.scrollToEnd = true } } } @@ -69,6 +70,10 @@ struct ContextMenuOverlayView: View { private extension ContextMenuOverlayView { func makeOverlayView() -> some View { + makeOverlayScrollToBottom(makeOverlayScrollView()) + } + + func makeOverlayScrollView() -> some View { ScrollView(axes, showsIndicators: false) { VStack(spacing: .zero) { makeContentView() @@ -80,11 +85,30 @@ private extension ContextMenuOverlayView { + minContentsSpace ) } + .id(1) } .fullScreen() .transition(.opacity) } + func makeOverlayScrollToBottom(_ content: some View) -> some View { + if #available(iOS 17.0, *) { + return content + .defaultScrollAnchor(.bottom) + } + + return ScrollViewReader { value in + content + .onChange(of: viewModel.scrollToEnd) { scrollToBottom in + guard scrollToBottom else { return } + + withAnimation { + value.scrollTo(1, anchor: .bottom) + } + } + } + } + func makeContentView() -> some View { HStack { UIViewWrapper(view: viewModel.contentView) diff --git a/pckg/AdvancedContextMenuKit/Sources/AdvancedContextMenuKit/Implementation/ContextMenuOverlayViewModel.swift b/pckg/AdvancedContextMenuKit/Sources/AdvancedContextMenuKit/Implementation/ContextMenuOverlayViewModel.swift index 7c3813da2..7631807ea 100644 --- a/pckg/AdvancedContextMenuKit/Sources/AdvancedContextMenuKit/Implementation/ContextMenuOverlayViewModel.swift +++ b/pckg/AdvancedContextMenuKit/Sources/AdvancedContextMenuKit/Implementation/ContextMenuOverlayViewModel.swift @@ -35,6 +35,7 @@ final class ContextMenuOverlayViewModel: ObservableObject { @Published var additionalMenuVisible = false @Published var shouldScroll: Bool = false + @Published var scrollToEnd = false init( contentView: UIView, From a69767b20b9540c489dc94e636a6932617e5c517 Mon Sep 17 00:00:00 2001 From: StanislavDevIOS Date: Wed, 15 Nov 2023 14:04:16 +0200 Subject: [PATCH 57/96] [trello.com/c/txp4J6DE] fix: tap on failed message --- .../ChatBaseMessage/ChatMessageCell.swift | 29 +++++++++++++++++++ .../ChatReply/ChatMessageReplyCell.swift | 26 +++++++++++++++-- .../DataProviders/AdamantChatsProvider.swift | 15 ++++++---- 3 files changed, 62 insertions(+), 8 deletions(-) diff --git a/Adamant/Modules/Chat/View/Subviews/ChatBaseMessage/ChatMessageCell.swift b/Adamant/Modules/Chat/View/Subviews/ChatBaseMessage/ChatMessageCell.swift index ed97e50c9..080cbad54 100644 --- a/Adamant/Modules/Chat/View/Subviews/ChatBaseMessage/ChatMessageCell.swift +++ b/Adamant/Modules/Chat/View/Subviews/ChatBaseMessage/ChatMessageCell.swift @@ -391,6 +391,35 @@ final class ChatMessageCell: TextMessageCell, ChatModelView { updateOwnReaction() updateOpponentReaction() } + + /// Handle tap gesture on contentView and its subviews. + override func handleTapGesture(_ gesture: UIGestureRecognizer) { + let touchLocation = gesture.location(in: self) + + let containerViewContains = containerView.frame.contains(touchLocation) + let canHandle = !cellContentView( + canHandle: convert(touchLocation, to: containerView) + ) + + switch true { + case containerViewContains && canHandle: + delegate?.didTapMessage(in: self) + case avatarView.frame.contains(touchLocation): + delegate?.didTapAvatar(in: self) + case cellTopLabel.frame.contains(touchLocation): + delegate?.didTapCellTopLabel(in: self) + case cellBottomLabel.frame.contains(touchLocation): + delegate?.didTapCellBottomLabel(in: self) + case messageTopLabel.frame.contains(touchLocation): + delegate?.didTapMessageTopLabel(in: self) + case messageBottomLabel.frame.contains(touchLocation): + delegate?.didTapMessageBottomLabel(in: self) + case accessoryView.frame.contains(touchLocation): + delegate?.didTapAccessoryView(in: self) + default: + delegate?.didTapBackground(in: self) + } + } } extension ChatMessageCell { diff --git a/Adamant/Modules/Chat/View/Subviews/ChatReply/ChatMessageReplyCell.swift b/Adamant/Modules/Chat/View/Subviews/ChatReply/ChatMessageReplyCell.swift index 7defceb51..4d4c4ec3b 100644 --- a/Adamant/Modules/Chat/View/Subviews/ChatReply/ChatMessageReplyCell.swift +++ b/Adamant/Modules/Chat/View/Subviews/ChatReply/ChatMessageReplyCell.swift @@ -489,13 +489,33 @@ final class ChatMessageReplyCell: MessageContentCell, ChatModelView { messageLabel.handleGesture(touchPoint) } + /// Handle tap gesture on contentView and its subviews. override func handleTapGesture(_ gesture: UIGestureRecognizer) { - super.handleTapGesture(gesture) - let touchLocation = gesture.location(in: self) - if containerView.frame.contains(touchLocation) { + let containerViewContains = containerView.frame.contains(touchLocation) + let canHandle = !cellContentView( + canHandle: convert(touchLocation, to: containerView) + ) + + switch true { + case containerViewContains && canHandle: + delegate?.didTapMessage(in: self) actionHandler(.scrollTo(message: model)) + case avatarView.frame.contains(touchLocation): + delegate?.didTapAvatar(in: self) + case cellTopLabel.frame.contains(touchLocation): + delegate?.didTapCellTopLabel(in: self) + case cellBottomLabel.frame.contains(touchLocation): + delegate?.didTapCellBottomLabel(in: self) + case messageTopLabel.frame.contains(touchLocation): + delegate?.didTapMessageTopLabel(in: self) + case messageBottomLabel.frame.contains(touchLocation): + delegate?.didTapMessageBottomLabel(in: self) + case accessoryView.frame.contains(touchLocation): + delegate?.didTapAccessoryView(in: self) + default: + delegate?.didTapBackground(in: self) } } } diff --git a/Adamant/Services/DataProviders/AdamantChatsProvider.swift b/Adamant/Services/DataProviders/AdamantChatsProvider.swift index a0137af9a..d4332021b 100644 --- a/Adamant/Services/DataProviders/AdamantChatsProvider.swift +++ b/Adamant/Services/DataProviders/AdamantChatsProvider.swift @@ -1127,14 +1127,18 @@ extension AdamantChatsProvider { } // MARK: 1. Find. Destroy. Save. + + let chatroom = message.chatroom + let privateContext = NSManagedObjectContext(concurrencyType: .privateQueueConcurrencyType) privateContext.parent = stack.container.viewContext privateContext.delete(privateContext.object(with: message.objectID)) - do { try privateContext.save() - return + if let chatroom = chatroom { + await updateLastTransactionForChatrooms([chatroom]) + } } catch { throw ChatsProviderError.internalError(error) } @@ -1695,7 +1699,7 @@ extension AdamantChatsProvider { if context.hasChanges { try context.save() - await updateContext(rooms: rooms) + await updateLastTransactionForChatrooms(rooms) } } catch { print(error) @@ -1717,13 +1721,14 @@ extension AdamantChatsProvider { receivedLastHeight = height } - @MainActor func updateContext(rooms: [Chatroom]) async { + @MainActor + func updateLastTransactionForChatrooms(_ rooms: [Chatroom]) { let viewContextChatrooms = Set(rooms).compactMap { self.stack.container.viewContext.object(with: $0.objectID) as? Chatroom } for chatroom in viewContextChatrooms { - await chatroom.updateLastTransaction() + chatroom.updateLastTransaction() } } } From a4faed5c1fac7f5964a33e1ea089242f8b69b6a0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=80=9Cjust-software-dev=E2=80=9D?= Date: Sun, 19 Nov 2023 06:39:13 +0600 Subject: [PATCH 58/96] [trello.com/c/YD9Iu6wp] Added global health check --- Adamant.xcodeproj/project.pbxproj | 226 +++++++-- .../xcshareddata/swiftpm/Package.resolved | 4 +- Adamant/App/AppDelegate.swift | 10 +- Adamant/App/DI/AppAssembly.swift | 119 ++++- .../Helpers/ApiServiceError+Extension.swift | 22 + Adamant/Helpers/ServerResponse+Resolver.swift | 60 +++ Adamant/Models/APIParametersEncoding.swift | 30 ++ Adamant/Models/APIResponseModel.swift | 15 + Adamant/Models/ApiServiceError.swift | 39 +- Adamant/Models/ApiServiceResult.swift | 5 +- Adamant/Models/BodyStringEncoding.swift | 37 ++ Adamant/Models/ForceQueryItemsEncoding.swift | 32 ++ Adamant/Models/InternalAPIError.swift | 43 ++ Adamant/Models/NodeStatusInfo.swift | 17 + .../DelegateDetailsViewController.swift | 66 ++- .../DelegatesListViewController.swift | 88 ++-- .../Modules/Login/LoginViewController.swift | 16 +- .../Modules/NodesEditor/EurekaNodeRow.swift | 156 ------ .../NodesEditor/NodeCell/NodeCell+Model.swift | 43 ++ .../NodesEditor/NodeCell/NodeCell.swift | 108 ++++ .../NodeEditorViewController.swift | 42 +- .../NodesEditor/NodesEditorFactory.swift | 18 +- .../NodesEditor/NodesListViewController.swift | 334 +++++++------ .../Settings/Contribute/ContributeState.swift | 8 +- .../Contribute/ContributeViewModel.swift | 2 +- .../AdmTransactionsViewController.swift | 24 +- .../Wallets/Adamant/AdmWalletService.swift | 10 +- .../Wallets/Bitcoin/BtcApiService.swift | 72 +++ .../Bitcoin/BtcWalletService+Send.swift | 147 ++---- .../Wallets/Bitcoin/BtcWalletService.swift | 201 +++----- .../{Models => DTO}/BtcBalanceResponse.swift | 0 .../BtcTransactionResponse.swift | 0 .../BtcUnspentTransactionResponse.swift | 0 .../Dash/DTO/DashBlockchainInfoDTO.swift | 14 + .../Wallets/Dash/DTO/DashErrorDTO.swift | 18 + .../Dash/DTO/DashGetAddressBalanceDTO.swift | 19 + .../DTO/DashGetAddressTransactionIds.swift | 19 + .../Wallets/Dash/DTO/DashGetBlockDTO.swift | 19 + .../Dash/DTO/DashGetRawTransactionDTO.swift | 26 + .../DTO/DashGetUnspentTransactionsDTO.swift | 19 + .../Wallets/Dash/DTO/DashResponseDTO.swift | 14 + .../Dash/DTO/DashSendRawTransactionDTO.swift | 19 + .../Modules/Wallets/Dash/DashApiService.swift | 79 +++ .../Wallets/Dash/DashWalletService+Send.swift | 55 +- .../Dash/DashWalletService+Transactions.swift | 179 ++----- .../Wallets/Dash/DashWalletService.swift | 89 ++-- .../Wallets/Doge/DTO/DogeBlockDTO.swift | 15 + .../Wallets/Doge/DTO/DogeBlocksDTO.swift | 13 + .../Modules/Wallets/Doge/DogeApiService.swift | 71 +++ .../Wallets/Doge/DogeWalletService+Send.swift | 63 +-- .../Wallets/Doge/DogeWalletService.swift | 208 ++++---- .../Wallets/ERC20/ERC20ApiService.swift | 25 + .../ERC20TransactionsViewController.swift | 6 +- .../ERC20/ERC20WalletService+Send.swift | 66 ++- .../Wallets/ERC20/ERC20WalletService.swift | 252 ++++------ .../Wallets/Ethereum/EthApiService.swift | 118 +++++ ...e+RichMessageProviderWithStatusCheck.swift | 14 +- .../Ethereum/EthWalletService+Send.swift | 27 +- .../Wallets/Ethereum/EthWalletService.swift | 317 +++++------- Adamant/Modules/Wallets/Lisk/LskApiCore.swift | 71 +++ .../Wallets/Lisk/LskNodeApiService.swift | 69 +++ .../Wallets/Lisk/LskServiceApiService.swift | 63 +++ .../Wallets/Lisk/LskWalletService+Send.swift | 13 +- .../Wallets/Lisk/LskWalletService.swift | 335 ++++--------- Adamant/Modules/Wallets/WalletService.swift | 46 +- .../ServiceProtocols/APICoreProtocol.swift | 96 ++++ .../AdamantCore/AdamantCore+Extensions.swift | 34 ++ Adamant/ServiceProtocols/ApiService.swift | 197 ++------ .../DataProviders/AccountsProvider.swift | 18 + .../ServiceProtocols/HealthCheckService.swift | 21 - Adamant/ServiceProtocols/LskApiService.swift | 40 -- ...NodesAdditionalParamsStorageProtocol.swift | 23 + Adamant/ServiceProtocols/NodesSource.swift | 46 -- .../NodesStorageProtocol.swift | 79 +++ Adamant/Services/APICore.swift | 75 +++ Adamant/Services/AdamantAccountService.swift | 109 +--- .../Services/AdamantAddressBookService.swift | 4 +- Adamant/Services/AdamantDialogService.swift | 4 +- .../Services/AdamantHealthCheckService.swift | 172 ------- Adamant/Services/AdamantNodesSource.swift | 161 ------ ...AdamantPushNotificationsTokenService.swift | 68 +-- .../ApiService/AdamantApi+Accounts.swift | 105 ++-- .../ApiService/AdamantApi+Chats.swift | 261 ++-------- .../ApiService/AdamantApi+Delegates.swift | 266 ++++------ .../Services/ApiService/AdamantApi+Keys.swift | 31 +- .../ApiService/AdamantApi+Peers.swift | 42 -- .../ApiService/AdamantApi+States.swift | 163 +----- .../ApiService/AdamantApi+Status.swift | 35 -- .../ApiService/AdamantApi+Transactions.swift | 147 +++--- .../ApiService/AdamantApi+Transfers.swift | 71 +-- .../Services/ApiService/AdamantApiCore.swift | 49 ++ .../ApiService/AdamantApiService.swift | 469 +----------------- .../BlockchainHealthCheckWrapper.swift | 145 ++++++ .../AdamantAccountsProvider.swift | 2 +- ...AdamantChatsProvider+backgroundFetch.swift | 2 +- .../DataProviders/AdamantChatsProvider.swift | 20 +- ...antTransfersProvider+backgroundFetch.swift | 2 +- .../AdamantTransfersProvider.swift | 18 +- Adamant/Services/HealthCheckWrapper.swift | 149 ++++++ .../NodesAdditionalParamsStorage.swift | 62 +++ Adamant/Services/NodesStorage.swift | 144 ++++++ .../AdamantRichTransactionReplyService.swift | 2 +- .../SocketService/AdamantSocketService.swift | 53 +- AdamantTests/Core/adamant-core.js | 2 +- .../CommonKit/AdamantDynamicResources.swift | 2 +- .../verse_notification.imageset/Contents.json | 23 + .../verse_notification.png | Bin 0 -> 1074 bytes .../verse_notification@2x.png | Bin 0 -> 2064 bytes .../verse_notification@3x.png | Bin 0 -> 3091 bytes .../verse_wallet.imageset/Contents.json | 23 + .../verse_wallet.imageset/verse_wallet.png | Bin 0 -> 1074 bytes .../verse_wallet.imageset/verse_wallet@2x.png | Bin 0 -> 2064 bytes .../verse_wallet.imageset/verse_wallet@3x.png | Bin 0 -> 3091 bytes .../verse_wallet_row.imageset/Contents.json | 23 + .../verse_wallet_row.png | Bin 0 -> 1074 bytes .../verse_wallet_row@2x.png | Bin 0 -> 2064 bytes .../verse_wallet_row@3x.png | Bin 0 -> 3091 bytes .../Helpers/Encodable+Dictionary.swift | 2 +- .../CommonKit/Helpers/HashableAction.swift | 30 -- .../Sources/CommonKit/Helpers/IDWrapper.swift | 31 ++ .../Helpers/IdentifiableContainer.swift | 21 - .../CommonKit/Helpers/Nodes+Allowance.swift | 6 +- .../CommonKit/Helpers/Result+Extension.swift | 19 + .../CommonKit/Helpers/URL+Extension.swift | 21 - CommonKit/Sources/CommonKit/Models/Node.swift | 192 +++---- .../Models/NodeGroup+Constants.swift | 59 +++ .../Sources/CommonKit/Models/NodeGroup.swift | 16 + .../CommonKit/Models/ethereumTokensList.swift | 12 + .../CommonKit/Services/KeychainStore.swift | 15 +- .../API/Node/Models/NodeStatusModel.swift | 2 + LiskKit/Sources/Core/APIClient.swift | 5 +- .../verse_notificationContent.png | Bin 0 -> 3091 bytes .../Models/AdvancedAlertModel.swift | 4 +- .../Models/NotificationModel.swift | 2 +- .../Models/PopupCoordinatorModel.swift | 60 ++- .../Views/AdvancedAlertView.swift | 4 +- .../Views/NotificationView.swift | 2 +- PopupKit/Sources/PopupKit/PopupManager.swift | 2 +- 138 files changed, 4260 insertions(+), 4058 deletions(-) create mode 100644 Adamant/Helpers/ApiServiceError+Extension.swift create mode 100644 Adamant/Helpers/ServerResponse+Resolver.swift create mode 100644 Adamant/Models/APIParametersEncoding.swift create mode 100644 Adamant/Models/APIResponseModel.swift create mode 100644 Adamant/Models/BodyStringEncoding.swift create mode 100644 Adamant/Models/ForceQueryItemsEncoding.swift create mode 100644 Adamant/Models/InternalAPIError.swift create mode 100644 Adamant/Models/NodeStatusInfo.swift delete mode 100644 Adamant/Modules/NodesEditor/EurekaNodeRow.swift create mode 100644 Adamant/Modules/NodesEditor/NodeCell/NodeCell+Model.swift create mode 100644 Adamant/Modules/NodesEditor/NodeCell/NodeCell.swift create mode 100644 Adamant/Modules/Wallets/Bitcoin/BtcApiService.swift rename Adamant/Modules/Wallets/Bitcoin/{Models => DTO}/BtcBalanceResponse.swift (100%) rename Adamant/Modules/Wallets/Bitcoin/{Models => DTO}/BtcTransactionResponse.swift (100%) rename Adamant/Modules/Wallets/Bitcoin/{Models => DTO}/BtcUnspentTransactionResponse.swift (100%) create mode 100644 Adamant/Modules/Wallets/Dash/DTO/DashBlockchainInfoDTO.swift create mode 100644 Adamant/Modules/Wallets/Dash/DTO/DashErrorDTO.swift create mode 100644 Adamant/Modules/Wallets/Dash/DTO/DashGetAddressBalanceDTO.swift create mode 100644 Adamant/Modules/Wallets/Dash/DTO/DashGetAddressTransactionIds.swift create mode 100644 Adamant/Modules/Wallets/Dash/DTO/DashGetBlockDTO.swift create mode 100644 Adamant/Modules/Wallets/Dash/DTO/DashGetRawTransactionDTO.swift create mode 100644 Adamant/Modules/Wallets/Dash/DTO/DashGetUnspentTransactionsDTO.swift create mode 100644 Adamant/Modules/Wallets/Dash/DTO/DashResponseDTO.swift create mode 100644 Adamant/Modules/Wallets/Dash/DTO/DashSendRawTransactionDTO.swift create mode 100644 Adamant/Modules/Wallets/Dash/DashApiService.swift create mode 100644 Adamant/Modules/Wallets/Doge/DTO/DogeBlockDTO.swift create mode 100644 Adamant/Modules/Wallets/Doge/DTO/DogeBlocksDTO.swift create mode 100644 Adamant/Modules/Wallets/Doge/DogeApiService.swift create mode 100644 Adamant/Modules/Wallets/ERC20/ERC20ApiService.swift create mode 100644 Adamant/Modules/Wallets/Ethereum/EthApiService.swift create mode 100644 Adamant/Modules/Wallets/Lisk/LskApiCore.swift create mode 100644 Adamant/Modules/Wallets/Lisk/LskNodeApiService.swift create mode 100644 Adamant/Modules/Wallets/Lisk/LskServiceApiService.swift create mode 100644 Adamant/ServiceProtocols/APICoreProtocol.swift delete mode 100644 Adamant/ServiceProtocols/HealthCheckService.swift delete mode 100644 Adamant/ServiceProtocols/LskApiService.swift create mode 100644 Adamant/ServiceProtocols/NodesAdditionalParamsStorageProtocol.swift delete mode 100644 Adamant/ServiceProtocols/NodesSource.swift create mode 100644 Adamant/ServiceProtocols/NodesStorageProtocol.swift create mode 100644 Adamant/Services/APICore.swift delete mode 100644 Adamant/Services/AdamantHealthCheckService.swift delete mode 100644 Adamant/Services/AdamantNodesSource.swift delete mode 100644 Adamant/Services/ApiService/AdamantApi+Peers.swift delete mode 100644 Adamant/Services/ApiService/AdamantApi+Status.swift create mode 100644 Adamant/Services/ApiService/AdamantApiCore.swift create mode 100644 Adamant/Services/BlockchainHealthCheckWrapper.swift create mode 100644 Adamant/Services/HealthCheckWrapper.swift create mode 100644 Adamant/Services/NodesAdditionalParamsStorage.swift create mode 100644 Adamant/Services/NodesStorage.swift create mode 100644 CommonKit/Sources/CommonKit/Assets/Shared.xcassets/Wallets/verse_notification.imageset/Contents.json create mode 100644 CommonKit/Sources/CommonKit/Assets/Shared.xcassets/Wallets/verse_notification.imageset/verse_notification.png create mode 100644 CommonKit/Sources/CommonKit/Assets/Shared.xcassets/Wallets/verse_notification.imageset/verse_notification@2x.png create mode 100644 CommonKit/Sources/CommonKit/Assets/Shared.xcassets/Wallets/verse_notification.imageset/verse_notification@3x.png create mode 100644 CommonKit/Sources/CommonKit/Assets/Shared.xcassets/Wallets/verse_wallet.imageset/Contents.json create mode 100644 CommonKit/Sources/CommonKit/Assets/Shared.xcassets/Wallets/verse_wallet.imageset/verse_wallet.png create mode 100644 CommonKit/Sources/CommonKit/Assets/Shared.xcassets/Wallets/verse_wallet.imageset/verse_wallet@2x.png create mode 100644 CommonKit/Sources/CommonKit/Assets/Shared.xcassets/Wallets/verse_wallet.imageset/verse_wallet@3x.png create mode 100644 CommonKit/Sources/CommonKit/Assets/Shared.xcassets/Wallets/verse_wallet_row.imageset/Contents.json create mode 100644 CommonKit/Sources/CommonKit/Assets/Shared.xcassets/Wallets/verse_wallet_row.imageset/verse_wallet_row.png create mode 100644 CommonKit/Sources/CommonKit/Assets/Shared.xcassets/Wallets/verse_wallet_row.imageset/verse_wallet_row@2x.png create mode 100644 CommonKit/Sources/CommonKit/Assets/Shared.xcassets/Wallets/verse_wallet_row.imageset/verse_wallet_row@3x.png delete mode 100644 CommonKit/Sources/CommonKit/Helpers/HashableAction.swift create mode 100644 CommonKit/Sources/CommonKit/Helpers/IDWrapper.swift delete mode 100644 CommonKit/Sources/CommonKit/Helpers/IdentifiableContainer.swift create mode 100644 CommonKit/Sources/CommonKit/Helpers/Result+Extension.swift delete mode 100644 CommonKit/Sources/CommonKit/Helpers/URL+Extension.swift create mode 100644 CommonKit/Sources/CommonKit/Models/NodeGroup+Constants.swift create mode 100644 CommonKit/Sources/CommonKit/Models/NodeGroup.swift create mode 100644 NotificationServiceExtension/WalletImages/verse_notificationContent.png diff --git a/Adamant.xcodeproj/project.pbxproj b/Adamant.xcodeproj/project.pbxproj index ef66fbdd6..22b1d6000 100644 --- a/Adamant.xcodeproj/project.pbxproj +++ b/Adamant.xcodeproj/project.pbxproj @@ -78,12 +78,9 @@ 41CE153A297FF98200CC9254 /* Web3Swift+Adamant.swift in Sources */ = {isa = PBXBuildFile; fileRef = 41CE1539297FF98200CC9254 /* Web3Swift+Adamant.swift */; }; 41E3C9CC2A0E20F500AF0985 /* AdamantCoinTools.swift in Sources */ = {isa = PBXBuildFile; fileRef = 41E3C9CB2A0E20F500AF0985 /* AdamantCoinTools.swift */; }; 4E9EE86F28CE793D008359F7 /* SafeDecimalRow.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4E9EE86E28CE793D008359F7 /* SafeDecimalRow.swift */; }; - 550066C5284D65DB0044C0B1 /* HealthCheckService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 550066C4284D65DB0044C0B1 /* HealthCheckService.swift */; }; - 550066C7284D682D0044C0B1 /* AdamantHealthCheckService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 550066C6284D682D0044C0B1 /* AdamantHealthCheckService.swift */; }; 551F66E628959A5300DE5D69 /* LoadingView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 551F66E528959A5200DE5D69 /* LoadingView.swift */; }; 551F66E82895B3DA00DE5D69 /* AdamantHealthCheckServiceTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 551F66E72895B3DA00DE5D69 /* AdamantHealthCheckServiceTests.swift */; }; 5551CC8F28A8B75300B52AD0 /* ApiServiceStub.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5551CC8E28A8B75300B52AD0 /* ApiServiceStub.swift */; }; - 5558A436282AAFCC0024DDD6 /* AdamantApi+Status.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5558A435282AAFCC0024DDD6 /* AdamantApi+Status.swift */; }; 5558A438282AB9390024DDD6 /* NodeStatus.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5558A437282AB9390024DDD6 /* NodeStatus.swift */; }; 557AC306287B10D8004699D7 /* SnapKit in Frameworks */ = {isa = PBXBuildFile; productRef = 557AC305287B10D8004699D7 /* SnapKit */; }; 557AC308287B1365004699D7 /* CheckmarkRowView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 557AC307287B1365004699D7 /* CheckmarkRowView.swift */; }; @@ -155,7 +152,6 @@ 649D6BF021BFF481009E727B /* AdamantChatsProvider+search.swift in Sources */ = {isa = PBXBuildFile; fileRef = 649D6BEF21BFF481009E727B /* AdamantChatsProvider+search.swift */; }; 649D6BF221C27D5C009E727B /* SearchResultsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 649D6BF121C27D5C009E727B /* SearchResultsViewController.swift */; }; 64A223D620F760BB005157CB /* Localization.swift in Sources */ = {isa = PBXBuildFile; fileRef = 64A223D520F760BB005157CB /* Localization.swift */; }; - 64A223D820F7A08E005157CB /* LskApiService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 64A223D720F7A08E005157CB /* LskApiService.swift */; }; 64B5736F2209B892005DC968 /* BtcTransactionDetailsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 64B5736E2209B892005DC968 /* BtcTransactionDetailsViewController.swift */; }; 64BD2B7520E2814B00E2CD36 /* EthTransaction.swift in Sources */ = {isa = PBXBuildFile; fileRef = 64BD2B7420E2814B00E2CD36 /* EthTransaction.swift */; }; 64BD2B7720E2820300E2CD36 /* TransactionDetails.swift in Sources */ = {isa = PBXBuildFile; fileRef = 64BD2B7620E2820300E2CD36 /* TransactionDetails.swift */; }; @@ -196,6 +192,13 @@ 932B34E92974AA4A002A75BA /* ChatPreservationDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 932B34E82974AA4A002A75BA /* ChatPreservationDelegate.swift */; }; 932BD15B29D2F75200AA1947 /* RichMessageProviderWithStatusCheck+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 932BD15A29D2F75200AA1947 /* RichMessageProviderWithStatusCheck+Extension.swift */; }; 932F77592989F999006D8801 /* ChatCellManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 932F77582989F999006D8801 /* ChatCellManager.swift */; }; + 9338AE7F2AEF43DA001D32DF /* NodesStorageProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9338AE7E2AEF43DA001D32DF /* NodesStorageProtocol.swift */; }; + 9338AE812AEF4B8E001D32DF /* NodesStorage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9338AE802AEF4B8E001D32DF /* NodesStorage.swift */; }; + 9338AE842AEF5EFA001D32DF /* APICoreProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9338AE832AEF5EFA001D32DF /* APICoreProtocol.swift */; }; + 9338AE862AEF6A97001D32DF /* APICore.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9338AE852AEF6A97001D32DF /* APICore.swift */; }; + 9338AE8B2AEF7E37001D32DF /* APIParametersEncoding.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9338AE8A2AEF7E37001D32DF /* APIParametersEncoding.swift */; }; + 9338AE8D2AEF7E9C001D32DF /* BodyStringEncoding.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9338AE8C2AEF7E9C001D32DF /* BodyStringEncoding.swift */; }; + 9338AE8F2AEF8131001D32DF /* InternalAPIError.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9338AE8E2AEF8131001D32DF /* InternalAPIError.swift */; }; 9340078029AC341100A20622 /* ChatAction.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9340077F29AC341000A20622 /* ChatAction.swift */; }; 9342F6C22A6A35E300A9B39F /* CommonKit in Frameworks */ = {isa = PBXBuildFile; productRef = 9342F6C12A6A35E300A9B39F /* CommonKit */; }; 9345769528FD0C34004E6C7A /* UIViewController+email.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9345769428FD0C34004E6C7A /* UIViewController+email.swift */; }; @@ -217,6 +220,7 @@ 93684A2A29EFA28A00F9EFFE /* FixedTextMessageSizeCalculator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 93684A2929EFA28A00F9EFFE /* FixedTextMessageSizeCalculator.swift */; }; 9371130F2996EDA900F64CF9 /* ChatRefreshMock.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9371130E2996EDA900F64CF9 /* ChatRefreshMock.swift */; }; 9371E561295CD53100438F2C /* ChatLocalization.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9371E560295CD53100438F2C /* ChatLocalization.swift */; }; + 937736822B0949C500B35C7A /* NodeCell+Model.swift in Sources */ = {isa = PBXBuildFile; fileRef = 937736812B0949C500B35C7A /* NodeCell+Model.swift */; }; 937751A52A68B3320054BD65 /* CommonKit in Frameworks */ = {isa = PBXBuildFile; productRef = 937751A42A68B3320054BD65 /* CommonKit */; }; 937751A72A68B33A0054BD65 /* CommonKit in Frameworks */ = {isa = PBXBuildFile; productRef = 937751A62A68B33A0054BD65 /* CommonKit */; }; 937751A92A68B3400054BD65 /* CommonKit in Frameworks */ = {isa = PBXBuildFile; productRef = 937751A82A68B3400054BD65 /* CommonKit */; }; @@ -226,6 +230,8 @@ 9377FBDF296C2A2F00C9211B /* ChatTransactionContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9377FBDE296C2A2F00C9211B /* ChatTransactionContentView.swift */; }; 9377FBE2296C2ACA00C9211B /* ChatTransactionContentView+Model.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9377FBE1296C2ACA00C9211B /* ChatTransactionContentView+Model.swift */; }; 9382F61329DEC0A3005E6216 /* ChatModelView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9382F61229DEC0A3005E6216 /* ChatModelView.swift */; }; + 938A46A42AE6103E00FC03DB /* HealthCheckWrapper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 938A46A32AE6103E00FC03DB /* HealthCheckWrapper.swift */; }; + 938A46A62AE6106300FC03DB /* BlockchainHealthCheckWrapper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 938A46A52AE6106300FC03DB /* BlockchainHealthCheckWrapper.swift */; }; 938F7D582955C1DA001915CA /* MessageKit in Frameworks */ = {isa = PBXBuildFile; productRef = 938F7D572955C1DA001915CA /* MessageKit */; }; 938F7D5B2955C8DA001915CA /* ChatDisplayManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 938F7D5A2955C8DA001915CA /* ChatDisplayManager.swift */; }; 938F7D5D2955C8F9001915CA /* ChatLayoutManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 938F7D5C2955C8F9001915CA /* ChatLayoutManager.swift */; }; @@ -245,13 +251,33 @@ 93A18C892AAEAE7700D0AB98 /* WalletFactory.swift in Sources */ = {isa = PBXBuildFile; fileRef = 93A18C882AAEAE7700D0AB98 /* WalletFactory.swift */; }; 93A91FD1297972B7001DB1F8 /* ChatScrollDownButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 93A91FD0297972B7001DB1F8 /* ChatScrollDownButton.swift */; }; 93A91FD329799298001DB1F8 /* ChatStartPosition.swift in Sources */ = {isa = PBXBuildFile; fileRef = 93A91FD229799298001DB1F8 /* ChatStartPosition.swift */; }; + 93ADC17B2B08283500F2DF77 /* ForceQueryItemsEncoding.swift in Sources */ = {isa = PBXBuildFile; fileRef = 93ADC17A2B08283500F2DF77 /* ForceQueryItemsEncoding.swift */; }; + 93ADC17D2B083C3B00F2DF77 /* NodesAdditionalParamsStorageProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 93ADC17C2B083C3B00F2DF77 /* NodesAdditionalParamsStorageProtocol.swift */; }; + 93ADC17F2B083D7A00F2DF77 /* NodesAdditionalParamsStorage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 93ADC17E2B083D7A00F2DF77 /* NodesAdditionalParamsStorage.swift */; }; 93ADE0712ACA66AF008ED641 /* VibrationSelectionViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 93ADE06E2ACA66AF008ED641 /* VibrationSelectionViewModel.swift */; }; 93ADE0722ACA66AF008ED641 /* VibrationSelectionView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 93ADE06F2ACA66AF008ED641 /* VibrationSelectionView.swift */; }; 93ADE0732ACA66AF008ED641 /* VibrationSelectionFactory.swift in Sources */ = {isa = PBXBuildFile; fileRef = 93ADE0702ACA66AF008ED641 /* VibrationSelectionFactory.swift */; }; + 93B28EC02B076667007F268B /* APIResponseModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 93B28EBF2B076667007F268B /* APIResponseModel.swift */; }; + 93B28EC22B076D31007F268B /* DashApiService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 93B28EC12B076D31007F268B /* DashApiService.swift */; }; + 93B28EC52B076E2C007F268B /* DashBlockchainInfoDTO.swift in Sources */ = {isa = PBXBuildFile; fileRef = 93B28EC42B076E2C007F268B /* DashBlockchainInfoDTO.swift */; }; + 93B28EC82B076E68007F268B /* DashResponseDTO.swift in Sources */ = {isa = PBXBuildFile; fileRef = 93B28EC72B076E68007F268B /* DashResponseDTO.swift */; }; + 93B28ECA2B076E88007F268B /* DashErrorDTO.swift in Sources */ = {isa = PBXBuildFile; fileRef = 93B28EC92B076E88007F268B /* DashErrorDTO.swift */; }; 93BF4A6629E4859900505CD0 /* DelegatesBottomPanel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 93BF4A6529E4859900505CD0 /* DelegatesBottomPanel.swift */; }; 93BF4A6C29E4B4BF00505CD0 /* DelegatesBottomPanel+Model.swift in Sources */ = {isa = PBXBuildFile; fileRef = 93BF4A6B29E4B4BF00505CD0 /* DelegatesBottomPanel+Model.swift */; }; + 93C794442B07725C00408826 /* DashGetAddressBalanceDTO.swift in Sources */ = {isa = PBXBuildFile; fileRef = 93C794432B07725C00408826 /* DashGetAddressBalanceDTO.swift */; }; + 93C794462B07768F00408826 /* DashGetRawTransactionDTO.swift in Sources */ = {isa = PBXBuildFile; fileRef = 93C794452B07768F00408826 /* DashGetRawTransactionDTO.swift */; }; + 93C794482B0778C700408826 /* DashGetBlockDTO.swift in Sources */ = {isa = PBXBuildFile; fileRef = 93C794472B0778C700408826 /* DashGetBlockDTO.swift */; }; + 93C7944A2B077A1C00408826 /* DashGetUnspentTransactionsDTO.swift in Sources */ = {isa = PBXBuildFile; fileRef = 93C794492B077A1C00408826 /* DashGetUnspentTransactionsDTO.swift */; }; + 93C7944C2B077B2700408826 /* DashGetAddressTransactionIds.swift in Sources */ = {isa = PBXBuildFile; fileRef = 93C7944B2B077B2700408826 /* DashGetAddressTransactionIds.swift */; }; + 93C7944E2B077C1F00408826 /* DashSendRawTransactionDTO.swift in Sources */ = {isa = PBXBuildFile; fileRef = 93C7944D2B077C1F00408826 /* DashSendRawTransactionDTO.swift */; }; 93CC8DC7296F00D6003772BF /* ChatTransactionContainerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 93CC8DC6296F00D6003772BF /* ChatTransactionContainerView.swift */; }; 93CC8DC9296F01DE003772BF /* ChatTransactionContainerView+Model.swift in Sources */ = {isa = PBXBuildFile; fileRef = 93CC8DC8296F01DE003772BF /* ChatTransactionContainerView+Model.swift */; }; + 93CCAE752B06CC3600EA5B94 /* LskNodeApiService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 93CCAE742B06CC3600EA5B94 /* LskNodeApiService.swift */; }; + 93CCAE772B06D6CC00EA5B94 /* LskServiceApiService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 93CCAE762B06D6CC00EA5B94 /* LskServiceApiService.swift */; }; + 93CCAE792B06D81D00EA5B94 /* DogeApiService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 93CCAE782B06D81D00EA5B94 /* DogeApiService.swift */; }; + 93CCAE7B2B06D9B500EA5B94 /* DogeBlocksDTO.swift in Sources */ = {isa = PBXBuildFile; fileRef = 93CCAE7A2B06D9B500EA5B94 /* DogeBlocksDTO.swift */; }; + 93CCAE7E2B06DA6C00EA5B94 /* DogeBlockDTO.swift in Sources */ = {isa = PBXBuildFile; fileRef = 93CCAE7D2B06DA6C00EA5B94 /* DogeBlockDTO.swift */; }; + 93CCAE802B06E2D100EA5B94 /* ApiServiceError+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 93CCAE7F2B06E2D100EA5B94 /* ApiServiceError+Extension.swift */; }; 93E1232F2A6DF8EF004DF33B /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = 93E123312A6DF8EF004DF33B /* InfoPlist.strings */; }; 93E123382A6DFD15004DF33B /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = 93E1233A2A6DFD15004DF33B /* Localizable.strings */; }; 93E1233F2A6DFE24004DF33B /* Localizable.stringsdict in Resources */ = {isa = PBXBuildFile; fileRef = 93E123412A6DFE24004DF33B /* Localizable.stringsdict */; }; @@ -269,9 +295,16 @@ 93E5D4DC293000BE00439298 /* UnregisteredTransaction.swift in Sources */ = {isa = PBXBuildFile; fileRef = 93E5D4DA293000BE00439298 /* UnregisteredTransaction.swift */; }; 93E5D4DD293000BE00439298 /* UnregisteredTransaction.swift in Sources */ = {isa = PBXBuildFile; fileRef = 93E5D4DA293000BE00439298 /* UnregisteredTransaction.swift */; }; 93E5D4E02930029300439298 /* AdamantCore+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 93E5D4DF2930029300439298 /* AdamantCore+Extensions.swift */; }; + 93E8EDCD2AF1BD65003E163C /* AdamantApiCore.swift in Sources */ = {isa = PBXBuildFile; fileRef = 93E8EDCC2AF1BD65003E163C /* AdamantApiCore.swift */; }; + 93E8EDCF2AF1CD9F003E163C /* NodeStatusInfo.swift in Sources */ = {isa = PBXBuildFile; fileRef = 93E8EDCE2AF1CD9F003E163C /* NodeStatusInfo.swift */; }; + 93E8EDD12AF1DF8E003E163C /* ServerResponse+Resolver.swift in Sources */ = {isa = PBXBuildFile; fileRef = 93E8EDD02AF1DF8E003E163C /* ServerResponse+Resolver.swift */; }; 93EE9C3329C2666200D9853F /* RichTransactionStatusSubscription.swift in Sources */ = {isa = PBXBuildFile; fileRef = 93EE9C3229C2666200D9853F /* RichTransactionStatusSubscription.swift */; }; 93F391502962F5D400BFD6AE /* SpinnerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 93F3914F2962F5D400BFD6AE /* SpinnerView.swift */; }; 93FA403629401BFC00D20DB6 /* PopupKit in Frameworks */ = {isa = PBXBuildFile; productRef = 93FA403529401BFC00D20DB6 /* PopupKit */; }; + 93FC169B2B0197FD0062B507 /* BtcApiService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 93FC169A2B0197FD0062B507 /* BtcApiService.swift */; }; + 93FC169D2B019F440062B507 /* EthApiService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 93FC169C2B019F440062B507 /* EthApiService.swift */; }; + 93FC169F2B01A3630062B507 /* LskApiCore.swift in Sources */ = {isa = PBXBuildFile; fileRef = 93FC169E2B01A3630062B507 /* LskApiCore.swift */; }; + 93FC16A12B01DE120062B507 /* ERC20ApiService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 93FC16A02B01DE120062B507 /* ERC20ApiService.swift */; }; A50A41082822F8CE006BDFE1 /* BtcWalletService.swift in Sources */ = {isa = PBXBuildFile; fileRef = A50A41042822F8CE006BDFE1 /* BtcWalletService.swift */; }; A50A41092822F8CE006BDFE1 /* BtcWalletViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = A50A41052822F8CE006BDFE1 /* BtcWalletViewController.swift */; }; A50A410A2822F8CE006BDFE1 /* BtcWallet.swift in Sources */ = {isa = PBXBuildFile; fileRef = A50A41062822F8CE006BDFE1 /* BtcWallet.swift */; }; @@ -360,7 +393,7 @@ E91947B020002393001362F8 /* AdamantApiService.swift in Sources */ = {isa = PBXBuildFile; fileRef = E91947AF20002393001362F8 /* AdamantApiService.swift */; }; E91947B22000246A001362F8 /* AdamantError.swift in Sources */ = {isa = PBXBuildFile; fileRef = E91947B12000246A001362F8 /* AdamantError.swift */; }; E91947B420002809001362F8 /* AdamantAccount.swift in Sources */ = {isa = PBXBuildFile; fileRef = E91947B320002809001362F8 /* AdamantAccount.swift */; }; - E91E5BF220DAF05500B06B3C /* EurekaNodeRow.swift in Sources */ = {isa = PBXBuildFile; fileRef = E91E5BF120DAF05500B06B3C /* EurekaNodeRow.swift */; }; + E91E5BF220DAF05500B06B3C /* NodeCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = E91E5BF120DAF05500B06B3C /* NodeCell.swift */; }; E9204B5220C9762400F3B9AB /* MessageStatus.swift in Sources */ = {isa = PBXBuildFile; fileRef = E9204B5120C9762300F3B9AB /* MessageStatus.swift */; }; E921534E20EE1E8700C0843F /* EurekaAlertLabelRow.swift in Sources */ = {isa = PBXBuildFile; fileRef = E921534C20EE1E8700C0843F /* EurekaAlertLabelRow.swift */; }; E921534F20EE1E8700C0843F /* AlertLabelCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = E921534D20EE1E8700C0843F /* AlertLabelCell.xib */; }; @@ -477,9 +510,6 @@ E9981898212096ED0018C84C /* WalletViewControllerBase.xib in Resources */ = {isa = PBXBuildFile; fileRef = E9981897212096ED0018C84C /* WalletViewControllerBase.xib */; }; E9A03FD220DBC0F2007653A1 /* NodeEditorViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = E9A03FD120DBC0F2007653A1 /* NodeEditorViewController.swift */; }; E9A03FD420DBC824007653A1 /* NodeVersion.swift in Sources */ = {isa = PBXBuildFile; fileRef = E9A03FD320DBC824007653A1 /* NodeVersion.swift */; }; - E9A03FD620DBC8E2007653A1 /* AdamantApi+Peers.swift in Sources */ = {isa = PBXBuildFile; fileRef = E9A03FD520DBC8E2007653A1 /* AdamantApi+Peers.swift */; }; - E9A03FD820DC0ABA007653A1 /* AdamantNodesSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = E9A03FD720DC0ABA007653A1 /* AdamantNodesSource.swift */; }; - E9A03FDA20DC0B14007653A1 /* NodesSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = E9A03FD920DC0B14007653A1 /* NodesSource.swift */; }; E9A174B32057EC47003667CD /* BackgroundFetchService.swift in Sources */ = {isa = PBXBuildFile; fileRef = E9A174B22057EC47003667CD /* BackgroundFetchService.swift */; }; E9A174B52057EDCE003667CD /* AdamantTransfersProvider+backgroundFetch.swift in Sources */ = {isa = PBXBuildFile; fileRef = E9A174B42057EDCE003667CD /* AdamantTransfersProvider+backgroundFetch.swift */; }; E9A174B72057F1B3003667CD /* AdamantChatsProvider+backgroundFetch.swift in Sources */ = {isa = PBXBuildFile; fileRef = E9A174B62057F1B3003667CD /* AdamantChatsProvider+backgroundFetch.swift */; }; @@ -654,12 +684,9 @@ 41E3C9CB2A0E20F500AF0985 /* AdamantCoinTools.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AdamantCoinTools.swift; sourceTree = ""; }; 4A4D67BD3DC89C07D1351248 /* Pods-AdmCore.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-AdmCore.release.xcconfig"; path = "Target Support Files/Pods-AdmCore/Pods-AdmCore.release.xcconfig"; sourceTree = ""; }; 4E9EE86E28CE793D008359F7 /* SafeDecimalRow.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SafeDecimalRow.swift; sourceTree = ""; }; - 550066C4284D65DB0044C0B1 /* HealthCheckService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HealthCheckService.swift; sourceTree = ""; }; - 550066C6284D682D0044C0B1 /* AdamantHealthCheckService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AdamantHealthCheckService.swift; sourceTree = ""; }; 551F66E528959A5200DE5D69 /* LoadingView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoadingView.swift; sourceTree = ""; }; 551F66E72895B3DA00DE5D69 /* AdamantHealthCheckServiceTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AdamantHealthCheckServiceTests.swift; sourceTree = ""; }; 5551CC8E28A8B75300B52AD0 /* ApiServiceStub.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ApiServiceStub.swift; sourceTree = ""; }; - 5558A435282AAFCC0024DDD6 /* AdamantApi+Status.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "AdamantApi+Status.swift"; sourceTree = ""; }; 5558A437282AB9390024DDD6 /* NodeStatus.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NodeStatus.swift; sourceTree = ""; }; 557AC307287B1365004699D7 /* CheckmarkRowView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CheckmarkRowView.swift; sourceTree = ""; }; 55D1D854287B890300F94A4E /* AddressGeneratorTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AddressGeneratorTests.swift; sourceTree = ""; }; @@ -728,7 +755,6 @@ 649D6BF121C27D5C009E727B /* SearchResultsViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SearchResultsViewController.swift; sourceTree = ""; }; 649E9A142111B3C200686B01 /* Mnemonic+extended.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Mnemonic+extended.swift"; sourceTree = ""; }; 64A223D520F760BB005157CB /* Localization.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Localization.swift; sourceTree = ""; }; - 64A223D720F7A08E005157CB /* LskApiService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LskApiService.swift; sourceTree = ""; }; 64B5736C2201E196005DC968 /* BtcTransactionsViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BtcTransactionsViewController.swift; sourceTree = ""; }; 64B5736E2209B892005DC968 /* BtcTransactionDetailsViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BtcTransactionDetailsViewController.swift; sourceTree = ""; }; 64BD2B7420E2814B00E2CD36 /* EthTransaction.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EthTransaction.swift; sourceTree = ""; }; @@ -771,6 +797,13 @@ 932B34E82974AA4A002A75BA /* ChatPreservationDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChatPreservationDelegate.swift; sourceTree = ""; }; 932BD15A29D2F75200AA1947 /* RichMessageProviderWithStatusCheck+Extension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "RichMessageProviderWithStatusCheck+Extension.swift"; sourceTree = ""; }; 932F77582989F999006D8801 /* ChatCellManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChatCellManager.swift; sourceTree = ""; }; + 9338AE7E2AEF43DA001D32DF /* NodesStorageProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NodesStorageProtocol.swift; sourceTree = ""; }; + 9338AE802AEF4B8E001D32DF /* NodesStorage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NodesStorage.swift; sourceTree = ""; }; + 9338AE832AEF5EFA001D32DF /* APICoreProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = APICoreProtocol.swift; sourceTree = ""; }; + 9338AE852AEF6A97001D32DF /* APICore.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = APICore.swift; sourceTree = ""; }; + 9338AE8A2AEF7E37001D32DF /* APIParametersEncoding.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = APIParametersEncoding.swift; sourceTree = ""; }; + 9338AE8C2AEF7E9C001D32DF /* BodyStringEncoding.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BodyStringEncoding.swift; sourceTree = ""; }; + 9338AE8E2AEF8131001D32DF /* InternalAPIError.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InternalAPIError.swift; sourceTree = ""; }; 9340077F29AC341000A20622 /* ChatAction.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChatAction.swift; sourceTree = ""; }; 9345769428FD0C34004E6C7A /* UIViewController+email.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIViewController+email.swift"; sourceTree = ""; }; 93496B822A6C85F400DD062F /* AdamantResources+CoreData.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "AdamantResources+CoreData.swift"; sourceTree = ""; }; @@ -791,12 +824,15 @@ 93684A2929EFA28A00F9EFFE /* FixedTextMessageSizeCalculator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FixedTextMessageSizeCalculator.swift; sourceTree = ""; }; 9371130E2996EDA900F64CF9 /* ChatRefreshMock.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChatRefreshMock.swift; sourceTree = ""; }; 9371E560295CD53100438F2C /* ChatLocalization.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChatLocalization.swift; sourceTree = ""; }; + 937736812B0949C500B35C7A /* NodeCell+Model.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "NodeCell+Model.swift"; sourceTree = ""; }; 937751AA2A68BB390054BD65 /* ChatTransactionCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChatTransactionCell.swift; sourceTree = ""; }; 937751AC2A68BCE10054BD65 /* MessageCellWrapper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MessageCellWrapper.swift; sourceTree = ""; }; 93775E452A674FA9009061AC /* Markdown+Adamant.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Markdown+Adamant.swift"; sourceTree = ""; }; 9377FBDE296C2A2F00C9211B /* ChatTransactionContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChatTransactionContentView.swift; sourceTree = ""; }; 9377FBE1296C2ACA00C9211B /* ChatTransactionContentView+Model.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "ChatTransactionContentView+Model.swift"; sourceTree = ""; }; 9382F61229DEC0A3005E6216 /* ChatModelView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChatModelView.swift; sourceTree = ""; }; + 938A46A32AE6103E00FC03DB /* HealthCheckWrapper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HealthCheckWrapper.swift; sourceTree = ""; }; + 938A46A52AE6106300FC03DB /* BlockchainHealthCheckWrapper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BlockchainHealthCheckWrapper.swift; sourceTree = ""; }; 938F7D5A2955C8DA001915CA /* ChatDisplayManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChatDisplayManager.swift; sourceTree = ""; }; 938F7D5C2955C8F9001915CA /* ChatLayoutManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChatLayoutManager.swift; sourceTree = ""; }; 938F7D5E2955C90D001915CA /* ChatInputBarManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChatInputBarManager.swift; sourceTree = ""; }; @@ -815,13 +851,33 @@ 93A18C882AAEAE7700D0AB98 /* WalletFactory.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WalletFactory.swift; sourceTree = ""; }; 93A91FD0297972B7001DB1F8 /* ChatScrollDownButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChatScrollDownButton.swift; sourceTree = ""; }; 93A91FD229799298001DB1F8 /* ChatStartPosition.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChatStartPosition.swift; sourceTree = ""; }; + 93ADC17A2B08283500F2DF77 /* ForceQueryItemsEncoding.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ForceQueryItemsEncoding.swift; sourceTree = ""; }; + 93ADC17C2B083C3B00F2DF77 /* NodesAdditionalParamsStorageProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NodesAdditionalParamsStorageProtocol.swift; sourceTree = ""; }; + 93ADC17E2B083D7A00F2DF77 /* NodesAdditionalParamsStorage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NodesAdditionalParamsStorage.swift; sourceTree = ""; }; 93ADE06E2ACA66AF008ED641 /* VibrationSelectionViewModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = VibrationSelectionViewModel.swift; sourceTree = ""; }; 93ADE06F2ACA66AF008ED641 /* VibrationSelectionView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = VibrationSelectionView.swift; sourceTree = ""; }; 93ADE0702ACA66AF008ED641 /* VibrationSelectionFactory.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = VibrationSelectionFactory.swift; sourceTree = ""; }; + 93B28EBF2B076667007F268B /* APIResponseModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = APIResponseModel.swift; sourceTree = ""; }; + 93B28EC12B076D31007F268B /* DashApiService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DashApiService.swift; sourceTree = ""; }; + 93B28EC42B076E2C007F268B /* DashBlockchainInfoDTO.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DashBlockchainInfoDTO.swift; sourceTree = ""; }; + 93B28EC72B076E68007F268B /* DashResponseDTO.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DashResponseDTO.swift; sourceTree = ""; }; + 93B28EC92B076E88007F268B /* DashErrorDTO.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DashErrorDTO.swift; sourceTree = ""; }; 93BF4A6529E4859900505CD0 /* DelegatesBottomPanel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DelegatesBottomPanel.swift; sourceTree = ""; }; 93BF4A6B29E4B4BF00505CD0 /* DelegatesBottomPanel+Model.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "DelegatesBottomPanel+Model.swift"; sourceTree = ""; }; + 93C794432B07725C00408826 /* DashGetAddressBalanceDTO.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DashGetAddressBalanceDTO.swift; sourceTree = ""; }; + 93C794452B07768F00408826 /* DashGetRawTransactionDTO.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DashGetRawTransactionDTO.swift; sourceTree = ""; }; + 93C794472B0778C700408826 /* DashGetBlockDTO.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DashGetBlockDTO.swift; sourceTree = ""; }; + 93C794492B077A1C00408826 /* DashGetUnspentTransactionsDTO.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DashGetUnspentTransactionsDTO.swift; sourceTree = ""; }; + 93C7944B2B077B2700408826 /* DashGetAddressTransactionIds.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DashGetAddressTransactionIds.swift; sourceTree = ""; }; + 93C7944D2B077C1F00408826 /* DashSendRawTransactionDTO.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DashSendRawTransactionDTO.swift; sourceTree = ""; }; 93CC8DC6296F00D6003772BF /* ChatTransactionContainerView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChatTransactionContainerView.swift; sourceTree = ""; }; 93CC8DC8296F01DE003772BF /* ChatTransactionContainerView+Model.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "ChatTransactionContainerView+Model.swift"; sourceTree = ""; }; + 93CCAE742B06CC3600EA5B94 /* LskNodeApiService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LskNodeApiService.swift; sourceTree = ""; }; + 93CCAE762B06D6CC00EA5B94 /* LskServiceApiService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LskServiceApiService.swift; sourceTree = ""; }; + 93CCAE782B06D81D00EA5B94 /* DogeApiService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DogeApiService.swift; sourceTree = ""; }; + 93CCAE7A2B06D9B500EA5B94 /* DogeBlocksDTO.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DogeBlocksDTO.swift; sourceTree = ""; }; + 93CCAE7D2B06DA6C00EA5B94 /* DogeBlockDTO.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DogeBlockDTO.swift; sourceTree = ""; }; + 93CCAE7F2B06E2D100EA5B94 /* ApiServiceError+Extension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "ApiServiceError+Extension.swift"; sourceTree = ""; }; 93E123302A6DF8EF004DF33B /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/InfoPlist.strings; sourceTree = ""; }; 93E123322A6DF8F1004DF33B /* de */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = de; path = de.lproj/InfoPlist.strings; sourceTree = ""; }; 93E123332A6DF8F2004DF33B /* ru */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ru; path = ru.lproj/InfoPlist.strings; sourceTree = ""; }; @@ -834,8 +890,15 @@ 93E1234A2A6DFEF7004DF33B /* NotificationStrings.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationStrings.swift; sourceTree = ""; }; 93E5D4DA293000BE00439298 /* UnregisteredTransaction.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UnregisteredTransaction.swift; sourceTree = ""; }; 93E5D4DF2930029300439298 /* AdamantCore+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "AdamantCore+Extensions.swift"; sourceTree = ""; }; + 93E8EDCC2AF1BD65003E163C /* AdamantApiCore.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AdamantApiCore.swift; sourceTree = ""; }; + 93E8EDCE2AF1CD9F003E163C /* NodeStatusInfo.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NodeStatusInfo.swift; sourceTree = ""; }; + 93E8EDD02AF1DF8E003E163C /* ServerResponse+Resolver.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "ServerResponse+Resolver.swift"; sourceTree = ""; }; 93EE9C3229C2666200D9853F /* RichTransactionStatusSubscription.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RichTransactionStatusSubscription.swift; sourceTree = ""; }; 93F3914F2962F5D400BFD6AE /* SpinnerView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SpinnerView.swift; sourceTree = ""; }; + 93FC169A2B0197FD0062B507 /* BtcApiService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BtcApiService.swift; sourceTree = ""; }; + 93FC169C2B019F440062B507 /* EthApiService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EthApiService.swift; sourceTree = ""; }; + 93FC169E2B01A3630062B507 /* LskApiCore.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LskApiCore.swift; sourceTree = ""; }; + 93FC16A02B01DE120062B507 /* ERC20ApiService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ERC20ApiService.swift; sourceTree = ""; }; A50A41042822F8CE006BDFE1 /* BtcWalletService.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BtcWalletService.swift; sourceTree = ""; }; A50A41052822F8CE006BDFE1 /* BtcWalletViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BtcWalletViewController.swift; sourceTree = ""; }; A50A41062822F8CE006BDFE1 /* BtcWallet.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BtcWallet.swift; sourceTree = ""; }; @@ -899,7 +962,7 @@ E91947AF20002393001362F8 /* AdamantApiService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AdamantApiService.swift; sourceTree = ""; }; E91947B12000246A001362F8 /* AdamantError.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AdamantError.swift; sourceTree = ""; }; E91947B320002809001362F8 /* AdamantAccount.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AdamantAccount.swift; sourceTree = ""; }; - E91E5BF120DAF05500B06B3C /* EurekaNodeRow.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EurekaNodeRow.swift; sourceTree = ""; }; + E91E5BF120DAF05500B06B3C /* NodeCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NodeCell.swift; sourceTree = ""; }; E9204B5120C9762300F3B9AB /* MessageStatus.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MessageStatus.swift; sourceTree = ""; }; E921534C20EE1E8700C0843F /* EurekaAlertLabelRow.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EurekaAlertLabelRow.swift; sourceTree = ""; }; E921534D20EE1E8700C0843F /* AlertLabelCell.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = AlertLabelCell.xib; sourceTree = ""; }; @@ -1019,9 +1082,6 @@ E9981897212096ED0018C84C /* WalletViewControllerBase.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = WalletViewControllerBase.xib; sourceTree = ""; }; E9A03FD120DBC0F2007653A1 /* NodeEditorViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NodeEditorViewController.swift; sourceTree = ""; }; E9A03FD320DBC824007653A1 /* NodeVersion.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NodeVersion.swift; sourceTree = ""; }; - E9A03FD520DBC8E2007653A1 /* AdamantApi+Peers.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "AdamantApi+Peers.swift"; sourceTree = ""; }; - E9A03FD720DC0ABA007653A1 /* AdamantNodesSource.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AdamantNodesSource.swift; sourceTree = ""; }; - E9A03FD920DC0B14007653A1 /* NodesSource.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NodesSource.swift; sourceTree = ""; }; E9A174B22057EC47003667CD /* BackgroundFetchService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BackgroundFetchService.swift; sourceTree = ""; }; E9A174B42057EDCE003667CD /* AdamantTransfersProvider+backgroundFetch.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "AdamantTransfersProvider+backgroundFetch.swift"; sourceTree = ""; }; E9A174B62057F1B3003667CD /* AdamantChatsProvider+backgroundFetch.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "AdamantChatsProvider+backgroundFetch.swift"; sourceTree = ""; }; @@ -1257,10 +1317,12 @@ 6403F5DC22723C2800D58779 /* Dash */ = { isa = PBXGroup; children = ( + 93B28EC32B076DFD007F268B /* DTO */, 6403F5DD22723C6800D58779 /* DashMainnet.swift */, 6403F5DF22723F6400D58779 /* DashWalletFactory.swift */, 6403F5E122723F7500D58779 /* DashWallet.swift */, 6403F5E322723F8C00D58779 /* DashWalletService.swift */, + 93B28EC12B076D31007F268B /* DashApiService.swift */, 4186B339294200F4006594A3 /* DashWalletService+DynamicConstants.swift */, A578BDE42623051C00090141 /* DashWalletService+Transactions.swift */, 648CE3A5229AD1CD0070A2CC /* DashWalletService+Send.swift */, @@ -1280,6 +1342,7 @@ 6449BA66235CA0930033B936 /* ERC20WalletFactory.swift */, 6449BA60235CA0930033B936 /* ERC20Wallet.swift */, 6449BA5E235CA0930033B936 /* ERC20WalletService.swift */, + 93FC16A02B01DE120062B507 /* ERC20ApiService.swift */, 6449BA65235CA0930033B936 /* ERC20WalletService+Send.swift */, 6449BA67235CA0930033B936 /* ERC20WalletService+RichMessageProvider.swift */, 6449BA64235CA0930033B936 /* ERC20WalletService+RichMessageProviderWithStatusCheck.swift */, @@ -1307,10 +1370,12 @@ 64E1C82B222E958C006C4DA7 /* Doge */ = { isa = PBXGroup; children = ( + 93CCAE7C2B06D9B900EA5B94 /* DTO */, E907350D2256779C00BF02CC /* DogeMainnet.swift */, 64E1C82C222E95E2006C4DA7 /* DogeWalletFactory.swift */, 64E1C82E222E95F6006C4DA7 /* DogeWallet.swift */, 64E1C830222E9617006C4DA7 /* DogeWalletService.swift */, + 93CCAE782B06D81D00EA5B94 /* DogeApiService.swift */, 4186B337294200E8006594A3 /* DogeWalletService+DynamicConstants.swift */, 648DD7A32237DB9E00B811FD /* DogeWalletService+Send.swift */, 648DD7A72239147800B811FD /* DogeWalletService+RichMessageProvider.swift */, @@ -1429,6 +1494,15 @@ path = RichTransactionStatusService; sourceTree = ""; }; + 937736832B0949C700B35C7A /* NodeCell */ = { + isa = PBXGroup; + children = ( + E91E5BF120DAF05500B06B3C /* NodeCell.swift */, + 937736812B0949C500B35C7A /* NodeCell+Model.swift */, + ); + path = NodeCell; + sourceTree = ""; + }; 9377FBE0296C2AB700C9211B /* ChatTransaction */ = { isa = PBXGroup; children = ( @@ -1527,6 +1601,22 @@ path = TestVibration; sourceTree = ""; }; + 93B28EC32B076DFD007F268B /* DTO */ = { + isa = PBXGroup; + children = ( + 93B28EC42B076E2C007F268B /* DashBlockchainInfoDTO.swift */, + 93B28EC72B076E68007F268B /* DashResponseDTO.swift */, + 93B28EC92B076E88007F268B /* DashErrorDTO.swift */, + 93C794432B07725C00408826 /* DashGetAddressBalanceDTO.swift */, + 93C794452B07768F00408826 /* DashGetRawTransactionDTO.swift */, + 93C794472B0778C700408826 /* DashGetBlockDTO.swift */, + 93C794492B077A1C00408826 /* DashGetUnspentTransactionsDTO.swift */, + 93C7944B2B077B2700408826 /* DashGetAddressTransactionIds.swift */, + 93C7944D2B077C1F00408826 /* DashSendRawTransactionDTO.swift */, + ); + path = DTO; + sourceTree = ""; + }; 93BF4A6A29E4B4B600505CD0 /* DelegatesBottomPanel */ = { isa = PBXGroup; children = ( @@ -1554,6 +1644,15 @@ path = Container; sourceTree = ""; }; + 93CCAE7C2B06D9B900EA5B94 /* DTO */ = { + isa = PBXGroup; + children = ( + 93CCAE7A2B06D9B500EA5B94 /* DogeBlocksDTO.swift */, + 93CCAE7D2B06DA6C00EA5B94 /* DogeBlockDTO.swift */, + ); + path = DTO; + sourceTree = ""; + }; 93E123342A6DFCA6004DF33B /* NotificationsShared */ = { isa = PBXGroup; children = ( @@ -1584,10 +1683,11 @@ A50A41022822F8CE006BDFE1 /* Bitcoin */ = { isa = PBXGroup; children = ( - A5E04225282A8BC70076CD13 /* Models */, + A5E04225282A8BC70076CD13 /* DTO */, A50A41062822F8CE006BDFE1 /* BtcWallet.swift */, 93294B812AAD0BB400911109 /* BtcWalletFactory.swift */, A50A41042822F8CE006BDFE1 /* BtcWalletService.swift */, + 93FC169A2B0197FD0062B507 /* BtcApiService.swift */, 4186B331294200B4006594A3 /* BtcWalletService+DynamicConstants.swift */, A50A410F2822FC35006BDFE1 /* BtcWalletService+Send.swift */, A50A410E2822FC35006BDFE1 /* BtcWalletService+RichMessageProvider.swift */, @@ -1600,14 +1700,14 @@ path = Bitcoin; sourceTree = ""; }; - A5E04225282A8BC70076CD13 /* Models */ = { + A5E04225282A8BC70076CD13 /* DTO */ = { isa = PBXGroup; children = ( A5E04226282A8BDC0076CD13 /* BtcBalanceResponse.swift */, A5E04228282A998C0076CD13 /* BtcTransactionResponse.swift */, A5E0422A282AB18B0076CD13 /* BtcUnspentTransactionResponse.swift */, ); - path = Models; + path = DTO; sourceTree = ""; }; B92CFC9A479739E2046C81E9 /* Frameworks */ = { @@ -1691,6 +1791,7 @@ E9E7CD8A20026B0600DFC4DB /* AccountService.swift */, 6455E9F021075D3600B2E94C /* AddressBookService.swift */, E91947AB20001A9A001362F8 /* ApiService.swift */, + 9338AE832AEF5EFA001D32DF /* APICoreProtocol.swift */, 3AA2D5F6280EADE3000ED971 /* SocketService.swift */, 4164A9D628F17D4000EEF16D /* ChatTransactionService.swift */, 648BCA6C213D384F00875EB5 /* AvatarService.swift */, @@ -1699,12 +1800,9 @@ 64EAB37322463E020018D9B2 /* CurrencyInfoService.swift */, E9E7CD8C20026B6600DFC4DB /* DialogService.swift */, E90A494C204DA932009F6A65 /* LocalAuthentication.swift */, - 64A223D720F7A08E005157CB /* LskApiService.swift */, - E9A03FD920DC0B14007653A1 /* NodesSource.swift */, E93D7ABD2052CEE1005D19DC /* NotificationsService.swift */, E9215972206119FB0000CA5C /* ReachabilityMonitor.swift */, E9FEECA321413659007DD7C8 /* RichMessageProvider.swift */, - 550066C4284D65DB0044C0B1 /* HealthCheckService.swift */, 9304F8C1292F895C00173F18 /* PushNotificationsTokenService.swift */, 9324C75D297170600022D7EA /* RichTransactionStatusService.swift */, 41047B73294C61D10039E956 /* VisibleWalletsService.swift */, @@ -1714,6 +1812,8 @@ 3A7BD00D2AA9BCE80045AAB0 /* VibroService.swift */, 41C1698B29E7F34900FEB3CB /* RichTransactionReplyService.swift */, 3A4193902A580C85006A6B22 /* RichTransactionReactService.swift */, + 9338AE7E2AEF43DA001D32DF /* NodesStorageProtocol.swift */, + 93ADC17C2B083C3B00F2DF77 /* NodesAdditionalParamsStorageProtocol.swift */, ); path = ServiceProtocols; sourceTree = ""; @@ -1733,7 +1833,6 @@ E9E7CDBF2003AF6D00DFC4DB /* AdamantCellFactory.swift */, 64EAB37522463F680018D9B2 /* AdamantCurrencyInfoService.swift */, E9E7CD8E20026CD300DFC4DB /* AdamantDialogService.swift */, - E9A03FD720DC0ABA007653A1 /* AdamantNodesSource.swift */, E93D7ABF2052CF63005D19DC /* AdamantNotificationService.swift */, 41047B75294C62710039E956 /* AdamantVisibleWalletsService.swift */, 4153045829C09902000E4BEA /* AdamantIncreaseFeeService.swift */, @@ -1743,8 +1842,12 @@ E921597420611A6A0000CA5C /* AdamantReachability.swift */, E950273F202E257E002C1098 /* RepeaterService.swift */, E9771D9D22997A6F0099AAC7 /* NativeCore+AdamantCore.swift */, - 550066C6284D682D0044C0B1 /* AdamantHealthCheckService.swift */, 9304F8C3292F8A3100173F18 /* AdamantPushNotificationsTokenService.swift */, + 9338AE852AEF6A97001D32DF /* APICore.swift */, + 938A46A32AE6103E00FC03DB /* HealthCheckWrapper.swift */, + 938A46A52AE6106300FC03DB /* BlockchainHealthCheckWrapper.swift */, + 9338AE802AEF4B8E001D32DF /* NodesStorage.swift */, + 93ADC17E2B083D7A00F2DF77 /* NodesAdditionalParamsStorage.swift */, ); path = Services; sourceTree = ""; @@ -1777,6 +1880,12 @@ 3A4193992A5D554A006A6B22 /* Reaction.swift */, 3A7BD0112AA9BD5A0045AAB0 /* AdamantVibroType.swift */, 3A33F9F92A7A53DA002B8003 /* EmojiUpdateType.swift */, + 9338AE8A2AEF7E37001D32DF /* APIParametersEncoding.swift */, + 9338AE8C2AEF7E9C001D32DF /* BodyStringEncoding.swift */, + 93ADC17A2B08283500F2DF77 /* ForceQueryItemsEncoding.swift */, + 9338AE8E2AEF8131001D32DF /* InternalAPIError.swift */, + 93E8EDCE2AF1CD9F003E163C /* NodeStatusInfo.swift */, + 93B28EBF2B076667007F268B /* APIResponseModel.swift */, ); path = Models; sourceTree = ""; @@ -1809,6 +1918,8 @@ 41A1994129D2D3920031AD75 /* SwipePanGestureRecognizer.swift */, 4193AE1529FBEFBF002F21BE /* NSAttributedText+Adamant.swift */, 93294B832AAD0C8F00911109 /* Assembler+Extension.swift */, + 93E8EDD02AF1DF8E003E163C /* ServerResponse+Resolver.swift */, + 93CCAE7F2B06E2D100EA5B94 /* ApiServiceError+Extension.swift */, ); path = Helpers; sourceTree = ""; @@ -1902,10 +2013,10 @@ E93EB09D20DA3F3A001F9601 /* NodesEditor */ = { isa = PBXGroup; children = ( + 937736832B0949C700B35C7A /* NodeCell */, 64D059FE20D3116A003AD655 /* NodesListViewController.swift */, E93EB09E20DA3FA4001F9601 /* NodesEditorFactory.swift */, E9A03FD120DBC0F2007653A1 /* NodeEditorViewController.swift */, - E91E5BF120DAF05500B06B3C /* EurekaNodeRow.swift */, ); path = NodesEditor; sourceTree = ""; @@ -1946,6 +2057,7 @@ E940087C2114EDEE00CD2D67 /* EthWallet.swift */, E993302121354BC300CD5200 /* EthWalletFactory.swift */, E940087A2114ED0600CD2D67 /* EthWalletService.swift */, + 93FC169C2B019F440062B507 /* EthApiService.swift */, 4186B333294200C5006594A3 /* EthWalletService+DynamicConstants.swift */, E9AA8BF9212C166600F9249F /* EthWalletService+Send.swift */, E9FEECA52143C300007DD7C8 /* EthWalletService+RichMessageProvider.swift */, @@ -1964,6 +2076,9 @@ E94008822114EE4700CD2D67 /* LskWallet.swift */, 6416B19C21AD7B92006089AC /* LskWalletFactory.swift */, E94008842114EE7500CD2D67 /* LskWalletService.swift */, + 93FC169E2B01A3630062B507 /* LskApiCore.swift */, + 93CCAE742B06CC3600EA5B94 /* LskNodeApiService.swift */, + 93CCAE762B06D6CC00EA5B94 /* LskServiceApiService.swift */, 4186B335294200D2006594A3 /* LskWalletService+DynamicConstants.swift */, 6416B1A621B024B6006089AC /* LskWalletService+Send.swift */, 649D6BE721B95DB7009E727B /* LskWalletService+RichMessageProvider.swift */, @@ -2171,6 +2286,7 @@ E9CAE8D02018AA5000345E76 /* ApiService */ = { isa = PBXGroup; children = ( + 93E8EDCC2AF1BD65003E163C /* AdamantApiCore.swift */, E91947AF20002393001362F8 /* AdamantApiService.swift */, E9CAE8D12018AA7700345E76 /* AdamantApi+Accounts.swift */, E9CAE8D92018ACD300345E76 /* AdamantApi+Chats.swift */, @@ -2178,9 +2294,7 @@ E9CAE8D52018AC5300345E76 /* AdamantApi+Transactions.swift */, E9CAE8D72018ACA700345E76 /* AdamantApi+Transfers.swift */, E965A52F20B594120041A3EA /* AdamantApi+States.swift */, - E9A03FD520DBC8E2007653A1 /* AdamantApi+Peers.swift */, 644EC34C20EFA60900F40C73 /* AdamantApi+Delegates.swift */, - 5558A435282AAFCC0024DDD6 /* AdamantApi+Status.swift */, ); path = ApiService; sourceTree = ""; @@ -2703,9 +2817,11 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( + 93CCAE7E2B06DA6C00EA5B94 /* DogeBlockDTO.swift in Sources */, 6448C291235CA6E100F3F15B /* ERC20WalletService+RichMessageProviderWithStatusCheck.swift in Sources */, E9256F5F2034C21100DE86E9 /* String+localized.swift in Sources */, 6414C18E217DF43100373FA6 /* String+adamant.swift in Sources */, + 9338AE812AEF4B8E001D32DF /* NodesStorage.swift in Sources */, 93A118532993241D00E144CC /* ChatMessagesListFactory.swift in Sources */, E908472A2196FEA80095825D /* RichMessageTransaction+CoreDataClass.swift in Sources */, 4E9EE86F28CE793D008359F7 /* SafeDecimalRow.swift in Sources */, @@ -2737,7 +2853,6 @@ 93496B832A6C85F400DD062F /* AdamantResources+CoreData.swift in Sources */, 41A1995229D42C460031AD75 /* ChatMessageCell.swift in Sources */, E94008872114F05B00CD2D67 /* AddressValidationResult.swift in Sources */, - 64A223D820F7A08E005157CB /* LskApiService.swift in Sources */, E9E7CD8F20026CD300DFC4DB /* AdamantDialogService.swift in Sources */, E993301E212EF39700CD5200 /* EthTransferViewController.swift in Sources */, E9CAE8DA2018ACD300345E76 /* AdamantApi+Chats.swift in Sources */, @@ -2745,8 +2860,8 @@ E90847362196FEA80095825D /* Chatroom+CoreDataClass.swift in Sources */, E91947B020002393001362F8 /* AdamantApiService.swift in Sources */, E921597B206503000000CA5C /* ButtonsStripeView.swift in Sources */, + 93FC16A12B01DE120062B507 /* ERC20ApiService.swift in Sources */, 93294B9A2AAD624100911109 /* WalletFactoryCompose.swift in Sources */, - E9A03FD820DC0ABA007653A1 /* AdamantNodesSource.swift in Sources */, 41C1698E29E7F36900FEB3CB /* AdamantRichTransactionReplyService.swift in Sources */, 649D6BF221C27D5C009E727B /* SearchResultsViewController.swift in Sources */, E9E7CD8D20026B6600DFC4DB /* DialogService.swift in Sources */, @@ -2765,8 +2880,9 @@ 9377FBE2296C2ACA00C9211B /* ChatTransactionContentView+Model.swift in Sources */, E933475B225539390083839E /* DogeGetTransactionsResponse.swift in Sources */, 9340078029AC341100A20622 /* ChatAction.swift in Sources */, - 550066C5284D65DB0044C0B1 /* HealthCheckService.swift in Sources */, 648DD7A02236A59200B811FD /* DogeTransactionDetailsViewController.swift in Sources */, + 93ADC17D2B083C3B00F2DF77 /* NodesAdditionalParamsStorageProtocol.swift in Sources */, + 938A46A42AE6103E00FC03DB /* HealthCheckWrapper.swift in Sources */, 557AC308287B1365004699D7 /* CheckmarkRowView.swift in Sources */, 9390C5052976B53000270CDF /* ChatDialog.swift in Sources */, 6455E9F321075D8000B2E94C /* AdamantAddressBookService.swift in Sources */, @@ -2776,13 +2892,16 @@ E9E7CD9120026FA100DFC4DB /* AppAssembly.swift in Sources */, E96D64CA2295C4A800CA5587 /* WordList.swift in Sources */, 64BD2B7520E2814B00E2CD36 /* EthTransaction.swift in Sources */, + 93B28EC22B076D31007F268B /* DashApiService.swift in Sources */, E908472B2196FEA80095825D /* RichMessageTransaction+CoreDataProperties.swift in Sources */, A578BDE52623051C00090141 /* DashWalletService+Transactions.swift in Sources */, + 93C794442B07725C00408826 /* DashGetAddressBalanceDTO.swift in Sources */, 6449BA69235CA0930033B936 /* ERC20TransferViewController.swift in Sources */, 93684A2A29EFA28A00F9EFFE /* FixedTextMessageSizeCalculator.swift in Sources */, E9E7CD8B20026B0600DFC4DB /* AccountService.swift in Sources */, 41E3C9CC2A0E20F500AF0985 /* AdamantCoinTools.swift in Sources */, 93ADE0712ACA66AF008ED641 /* VibrationSelectionViewModel.swift in Sources */, + 93FC169D2B019F440062B507 /* EthApiService.swift in Sources */, 9371130F2996EDA900F64CF9 /* ChatRefreshMock.swift in Sources */, 93547BCA29E2262D00B0914B /* WelcomeViewController.swift in Sources */, 41047B74294C61D10039E956 /* VisibleWalletsService.swift in Sources */, @@ -2801,9 +2920,12 @@ 417BA7F428BF894F00DF94C5 /* NotificationSoundsViewController.swift in Sources */, E950652320404C84008352E5 /* AdamantUriTools.swift in Sources */, 3A41938F2A580C57006A6B22 /* AdamantRichTransactionReactService.swift in Sources */, + 93E8EDD12AF1DF8E003E163C /* ServerResponse+Resolver.swift in Sources */, E95F85C7200A9B070070534A /* ChatTableViewCell.swift in Sources */, A50A41082822F8CE006BDFE1 /* BtcWalletService.swift in Sources */, + 937736822B0949C500B35C7A /* NodeCell+Model.swift in Sources */, 6455E9F121075D3600B2E94C /* AddressBookService.swift in Sources */, + 93C794482B0778C700408826 /* DashGetBlockDTO.swift in Sources */, 6449BA6D235CA0930033B936 /* ERC20TransactionsViewController.swift in Sources */, E983AE2A20E65F3200497E1A /* AccountViewController.swift in Sources */, 6449BA70235CA0930033B936 /* ERC20WalletFactory.swift in Sources */, @@ -2836,10 +2958,10 @@ 648DD7A62237DC4000B811FD /* DogeTransferViewController.swift in Sources */, 93E1234B2A6DFEF7004DF33B /* NotificationStrings.swift in Sources */, E9960B3421F5154300C840A8 /* BaseAccount+CoreDataProperties.swift in Sources */, - 550066C7284D682D0044C0B1 /* AdamantHealthCheckService.swift in Sources */, 4153045929C09902000E4BEA /* AdamantIncreaseFeeService.swift in Sources */, E90A494B204D9EB8009F6A65 /* AdamantAuthentication.swift in Sources */, E9215973206119FB0000CA5C /* ReachabilityMonitor.swift in Sources */, + 9338AE8B2AEF7E37001D32DF /* APIParametersEncoding.swift in Sources */, E91947B420002809001362F8 /* AdamantAccount.swift in Sources */, 6449BA6C235CA0930033B936 /* ERC20WalletViewController.swift in Sources */, E9E7CDC22003F5A400DFC4DB /* TransactionsListViewControllerBase.swift in Sources */, @@ -2857,12 +2979,14 @@ E90A494D204DA932009F6A65 /* LocalAuthentication.swift in Sources */, E96D64C62295C3ED00CA5587 /* Mnemonic+extended.swift in Sources */, 41047B70294B5EE10039E956 /* VisibleWalletsViewController.swift in Sources */, + 93B28EC82B076E68007F268B /* DashResponseDTO.swift in Sources */, A5BBD811262C657300B5C40C /* ByteBackpacker.swift in Sources */, 648BCA6D213D384F00875EB5 /* AvatarService.swift in Sources */, E95F856F2007B61D0070534A /* GetPublicKeyResponse.swift in Sources */, 644EC34D20EFA60900F40C73 /* AdamantApi+Delegates.swift in Sources */, E940088F2119A9E800CD2D67 /* BigInt+Decimal.swift in Sources */, E9E7CDC72003F6D200DFC4DB /* TransactionTableViewCell.swift in Sources */, + 9338AE862AEF6A97001D32DF /* APICore.swift in Sources */, 938F7D5D2955C8F9001915CA /* ChatLayoutManager.swift in Sources */, 6403F5DE22723C6800D58779 /* DashMainnet.swift in Sources */, 649D6BEA21B9627B009E727B /* LskWalletService+RichMessageProviderWithStatusCheck.swift in Sources */, @@ -2884,15 +3008,18 @@ 93E5D4DB293000BE00439298 /* UnregisteredTransaction.swift in Sources */, 411DB8332A14D01F006AB158 /* ChatKeyboardManager.swift in Sources */, 6449BA68235CA0930033B936 /* ERC20WalletService.swift in Sources */, + 93B28ECA2B076E88007F268B /* DashErrorDTO.swift in Sources */, 644793C32166314A00FC4CF5 /* OnboardPage.swift in Sources */, 9345769528FD0C34004E6C7A /* UIViewController+email.swift in Sources */, 64E1C831222E9617006C4DA7 /* DogeWalletService.swift in Sources */, E91947B22000246A001362F8 /* AdamantError.swift in Sources */, 3A4193912A580C85006A6B22 /* RichTransactionReactService.swift in Sources */, + 93FC169B2B0197FD0062B507 /* BtcApiService.swift in Sources */, 3AA2D5F7280EADE3000ED971 /* SocketService.swift in Sources */, E95F85802008C8D70070534A /* ChatListFactory.swift in Sources */, 41A1994429D2D3CF0031AD75 /* MessageModel.swift in Sources */, 93775E462A674FA9009061AC /* Markdown+Adamant.swift in Sources */, + 93E8EDCD2AF1BD65003E163C /* AdamantApiCore.swift in Sources */, 6416B1A721B024B6006089AC /* LskWalletService+Send.swift in Sources */, E9942B87203D9E5100C163AF /* EurekaQRRow.swift in Sources */, E9AA8C02212C5BF500F9249F /* AdmWalletService+Send.swift in Sources */, @@ -2913,8 +3040,10 @@ A50A41092822F8CE006BDFE1 /* BtcWalletViewController.swift in Sources */, E9722066201F42BB004F2AAD /* CoreDataStack.swift in Sources */, 93EE9C3329C2666200D9853F /* RichTransactionStatusSubscription.swift in Sources */, + 9338AE7F2AEF43DA001D32DF /* NodesStorageProtocol.swift in Sources */, E913C8F21FFFA51D001A83F7 /* AppDelegate.swift in Sources */, 648CE3A222999CE70070A2CC /* BTCRawTransaction.swift in Sources */, + 93C794462B07768F00408826 /* DashGetRawTransactionDTO.swift in Sources */, 648DD79E2236A0B500B811FD /* DogeTransactionsViewController.swift in Sources */, 64B5736F2209B892005DC968 /* BtcTransactionDetailsViewController.swift in Sources */, 938F7D612955C92B001915CA /* ChatDataSourceManager.swift in Sources */, @@ -2931,6 +3060,7 @@ 649D6BF021BFF481009E727B /* AdamantChatsProvider+search.swift in Sources */, 932BD15B29D2F75200AA1947 /* RichMessageProviderWithStatusCheck+Extension.swift in Sources */, E908473B219707200095825D /* AccountViewController+StayIn.swift in Sources */, + 93C7944C2B077B2700408826 /* DashGetAddressTransactionIds.swift in Sources */, E9204B5220C9762400F3B9AB /* MessageStatus.swift in Sources */, E908471B2196FE590095825D /* Adamant.xcdatamodeld in Sources */, E940087B2114ED0600CD2D67 /* EthWalletService.swift in Sources */, @@ -2941,9 +3071,12 @@ E95F85852008CB3A0070534A /* ChatListViewController.swift in Sources */, E9FEECA62143C300007DD7C8 /* EthWalletService+RichMessageProvider.swift in Sources */, 6403F5E622723FDA00D58779 /* DashWalletViewController.swift in Sources */, + 93C7944E2B077C1F00408826 /* DashSendRawTransactionDTO.swift in Sources */, 4193AE1629FBEFBF002F21BE /* NSAttributedText+Adamant.swift in Sources */, + 9338AE8F2AEF8131001D32DF /* InternalAPIError.swift in Sources */, 41A1994829D325800031AD75 /* SwipeableView.swift in Sources */, 5558A438282AB9390024DDD6 /* NodeStatus.swift in Sources */, + 93FC169F2B01A3630062B507 /* LskApiCore.swift in Sources */, E91947AC20001A9A001362F8 /* ApiService.swift in Sources */, 4164A9D928F17DA700EEF16D /* AdamantChatTransactionService.swift in Sources */, E993302221354BC300CD5200 /* EthWalletFactory.swift in Sources */, @@ -2953,14 +3086,17 @@ E908473D219713300095825D /* NotificationsViewController.swift in Sources */, E908472E2196FEA80095825D /* BaseTransaction+CoreDataClass.swift in Sources */, 64D059FF20D3116B003AD655 /* NodesListViewController.swift in Sources */, - E91E5BF220DAF05500B06B3C /* EurekaNodeRow.swift in Sources */, + E91E5BF220DAF05500B06B3C /* NodeCell.swift in Sources */, 4133AF242A1CE1A3001A0A1E /* UITableView+Adamant.swift in Sources */, 41A1995629D56EAA0031AD75 /* ChatMessageReplyCell+Model.swift in Sources */, E90847312196FEA80095825D /* ChatTransaction+CoreDataProperties.swift in Sources */, E99330262136B0E500CD5200 /* TransferViewControllerBase+QR.swift in Sources */, + 93ADC17F2B083D7A00F2DF77 /* NodesAdditionalParamsStorage.swift in Sources */, E9B1AA5B21283E0F00080A2A /* AdmTransferViewController.swift in Sources */, 648C697322916192006645F5 /* DashTransactionsViewController.swift in Sources */, + 93E8EDCF2AF1CD9F003E163C /* NodeStatusInfo.swift in Sources */, 55E69E172868D7920025D82E /* CheckmarkView.swift in Sources */, + 93B28EC02B076667007F268B /* APIResponseModel.swift in Sources */, 9304F8C2292F895C00173F18 /* PushNotificationsTokenService.swift in Sources */, E940086B2114A70600CD2D67 /* LskAccount.swift in Sources */, 6416B19D21AD7B92006089AC /* LskWalletFactory.swift in Sources */, @@ -2969,11 +3105,11 @@ 93A91FD329799298001DB1F8 /* ChatStartPosition.swift in Sources */, 93A91FD1297972B7001DB1F8 /* ChatScrollDownButton.swift in Sources */, 41C1698C29E7F34900FEB3CB /* RichTransactionReplyService.swift in Sources */, - E9A03FDA20DC0B14007653A1 /* NodesSource.swift in Sources */, 935F53D629BE8F7400779492 /* RichTransactionStatusPublisher.swift in Sources */, 9390C5032976B42800270CDF /* ChatDialogManager.swift in Sources */, E926E02E213EAABF005E536B /* TransferViewControllerBase+Alert.swift in Sources */, E9B1AA572121ACC000080A2A /* AdmWalletViewController.swift in Sources */, + 93C7944A2B077A1C00408826 /* DashGetUnspentTransactionsDTO.swift in Sources */, E9240BF5215D686500187B09 /* AdmWalletService+RichMessageProvider.swift in Sources */, 648C697122915CB8006645F5 /* BTCRPCServerResponce.swift in Sources */, E9A174B32057EC47003667CD /* BackgroundFetchService.swift in Sources */, @@ -2991,17 +3127,19 @@ A5E0422B282AB18B0076CD13 /* BtcUnspentTransactionResponse.swift in Sources */, E972206B201F44CA004F2AAD /* TransfersProvider.swift in Sources */, 93294B962AAD320B00911109 /* ScreensFactory.swift in Sources */, + 93CCAE772B06D6CC00EA5B94 /* LskServiceApiService.swift in Sources */, E9FEECA421413659007DD7C8 /* RichMessageProvider.swift in Sources */, 3A9015A72A614A62002A2464 /* AdamantEmojiService.swift in Sources */, 93ADE0722ACA66AF008ED641 /* VibrationSelectionView.swift in Sources */, 648C696F22915A12006645F5 /* DashTransaction.swift in Sources */, 3A41939A2A5D554A006A6B22 /* Reaction.swift in Sources */, + 93CCAE752B06CC3600EA5B94 /* LskNodeApiService.swift in Sources */, 6416B1A321AD7EA1006089AC /* LskTransactionDetailsViewController.swift in Sources */, - E9A03FD620DBC8E2007653A1 /* AdamantApi+Peers.swift in Sources */, 6449BA6F235CA0930033B936 /* ERC20WalletService+Send.swift in Sources */, A50A41132822FC35006BDFE1 /* BtcWalletService+Send.swift in Sources */, A5E04229282A998C0076CD13 /* BtcTransactionResponse.swift in Sources */, 4186B334294200C5006594A3 /* EthWalletService+DynamicConstants.swift in Sources */, + 93CCAE802B06E2D100EA5B94 /* ApiServiceError+Extension.swift in Sources */, 644EC34F20EFA77A00F40C73 /* Delegate.swift in Sources */, 64EAB37622463F680018D9B2 /* AdamantCurrencyInfoService.swift in Sources */, 93294B842AAD0C8F00911109 /* Assembler+Extension.swift in Sources */, @@ -3009,6 +3147,7 @@ E9722068201F42CC004F2AAD /* InMemoryCoreDataStack.swift in Sources */, 4133AED429769EEC00F3D017 /* UpdatingIndicatorView.swift in Sources */, 551F66E628959A5300DE5D69 /* LoadingView.swift in Sources */, + 93B28EC52B076E2C007F268B /* DashBlockchainInfoDTO.swift in Sources */, E98FC34420F920BD00032D65 /* UIFont+adamant.swift in Sources */, 644EC35720EFAAB700F40C73 /* DelegatesListViewController.swift in Sources */, 4153045B29C09C6C000E4BEA /* IncreaseFeeService.swift in Sources */, @@ -3027,9 +3166,11 @@ E993302021354B1800CD5200 /* AdmWalletFactory.swift in Sources */, E9332B8921F1FA4400D56E72 /* OnboardFactory.swift in Sources */, 938F7D722955CE72001915CA /* ChatFactory.swift in Sources */, + 93CCAE792B06D81D00EA5B94 /* DogeApiService.swift in Sources */, 938F7D5F2955C90D001915CA /* ChatInputBarManager.swift in Sources */, E908472D2196FEA80095825D /* CoreDataAccount+CoreDataProperties.swift in Sources */, 64FA53D120E24942006783C9 /* TransactionDetailsViewControllerBase.swift in Sources */, + 9338AE842AEF5EFA001D32DF /* APICoreProtocol.swift in Sources */, 41047B76294C62710039E956 /* AdamantVisibleWalletsService.swift in Sources */, 93294B982AAD364F00911109 /* AdamantScreensFactory.swift in Sources */, 64BD2B7720E2820300E2CD36 /* TransactionDetails.swift in Sources */, @@ -3047,8 +3188,9 @@ 9300F94629D0149100FEDDB8 /* RichMessageProviderWithStatusCheck.swift in Sources */, E9771D9E22997A6F0099AAC7 /* NativeCore+AdamantCore.swift in Sources */, 416380E12A51765F00F90E6D /* ChatReactionsView.swift in Sources */, - 5558A436282AAFCC0024DDD6 /* AdamantApi+Status.swift in Sources */, + 938A46A62AE6106300FC03DB /* BlockchainHealthCheckWrapper.swift in Sources */, E921534E20EE1E8700C0843F /* EurekaAlertLabelRow.swift in Sources */, + 9338AE8D2AEF7E9C001D32DF /* BodyStringEncoding.swift in Sources */, 64C65F4523893C7600DC0425 /* OnboardOverlay.swift in Sources */, 93A18C862AAEACC100D0AB98 /* AdamantWalletFactoryCompose.swift in Sources */, E9484B79227C617E008E10F0 /* BalanceTableViewCell.swift in Sources */, @@ -3066,7 +3208,9 @@ 64E1C833222EA0F0006C4DA7 /* DogeWalletViewController.swift in Sources */, E93EB09F20DA3FA4001F9601 /* NodesEditorFactory.swift in Sources */, 93294B8F2AAD2C6B00911109 /* SwiftyOnboard.swift in Sources */, + 93ADC17B2B08283500F2DF77 /* ForceQueryItemsEncoding.swift in Sources */, 41BCB310295C6082004B12AB /* VisibleWalletsResetTableViewCell.swift in Sources */, + 93CCAE7B2B06D9B500EA5B94 /* DogeBlocksDTO.swift in Sources */, E9E7CDB12002B97B00DFC4DB /* AccountFactory.swift in Sources */, E9AA8BF82129F13000F9249F /* ComplexTransferViewController.swift in Sources */, E9A174B52057EDCE003667CD /* AdamantTransfersProvider+backgroundFetch.swift in Sources */, @@ -3219,7 +3363,7 @@ "@executable_path/Frameworks", "@executable_path/../../Frameworks", ); - MARKETING_VERSION = 3.0.0; + MARKETING_VERSION = 3.3.0; MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; MTL_FAST_MATH = YES; PRODUCT_BUNDLE_IDENTIFIER = "im.adamant.adamant-messenger-dev.MessageNotificationContentExtension"; @@ -3249,7 +3393,7 @@ "@executable_path/Frameworks", "@executable_path/../../Frameworks", ); - MARKETING_VERSION = 3.0.0; + MARKETING_VERSION = 3.3.0; MTL_FAST_MATH = YES; PRODUCT_BUNDLE_IDENTIFIER = "im.adamant.adamant-messenger.MessageNotificationContentExtension"; PRODUCT_NAME = "$(TARGET_NAME)"; @@ -3460,7 +3604,7 @@ "@executable_path/Frameworks", "@executable_path/../../Frameworks", ); - MARKETING_VERSION = 3.0.0; + MARKETING_VERSION = 3.3.0; MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; MTL_FAST_MATH = YES; PRODUCT_BUNDLE_IDENTIFIER = "im.adamant.adamant-messenger-dev.TransferNotificationContentExtension"; @@ -3490,7 +3634,7 @@ "@executable_path/Frameworks", "@executable_path/../../Frameworks", ); - MARKETING_VERSION = 3.0.0; + MARKETING_VERSION = 3.3.0; MTL_FAST_MATH = YES; PRODUCT_BUNDLE_IDENTIFIER = "im.adamant.adamant-messenger.TransferNotificationContentExtension"; PRODUCT_NAME = "$(TARGET_NAME)"; @@ -3521,7 +3665,7 @@ "@executable_path/Frameworks", "@executable_path/../../Frameworks", ); - MARKETING_VERSION = 3.0.0; + MARKETING_VERSION = 3.3.0; MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; MTL_FAST_MATH = YES; PRODUCT_BUNDLE_IDENTIFIER = "im.adamant.adamant-messenger-dev.NotificationServiceExtension"; @@ -3551,7 +3695,7 @@ "@executable_path/Frameworks", "@executable_path/../../Frameworks", ); - MARKETING_VERSION = 3.0.0; + MARKETING_VERSION = 3.3.0; MTL_FAST_MATH = YES; PRODUCT_BUNDLE_IDENTIFIER = "im.adamant.adamant-messenger.NotificationServiceExtension"; PRODUCT_NAME = "$(TARGET_NAME)"; diff --git a/Adamant.xcworkspace/xcshareddata/swiftpm/Package.resolved b/Adamant.xcworkspace/xcshareddata/swiftpm/Package.resolved index 95b69cc0b..233c8657b 100644 --- a/Adamant.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/Adamant.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -141,8 +141,8 @@ "repositoryURL": "https://github.com/nathantannar4/InputBarAccessoryView", "state": { "branch": null, - "revision": "039a9cb3ae8c5bc4d39242a6aa688b88023633d3", - "version": "6.2.0" + "revision": "17ced92a5dccb36512b408b6276353631d7cbe57", + "version": "6.3.0" } }, { diff --git a/Adamant/App/AppDelegate.swift b/Adamant/App/AppDelegate.swift index d668499db..9f5e5eb13 100644 --- a/Adamant/App/AppDelegate.swift +++ b/Adamant/App/AppDelegate.swift @@ -520,7 +520,7 @@ extension AppDelegate { unread = true } - if let adelina = AdamantContacts.adelina.messages["chats.welcome_message"] { + if let adelina = AdamantContacts.adelina.welcomeMessage { _ = try? await chatProvider.fakeReceived( message: adelina.message, senderId: AdamantContacts.adelina.address, @@ -531,7 +531,7 @@ extension AppDelegate { ) } - if let exchenge = AdamantContacts.adamantExchange.messages["chats.welcome_message"] { + if let exchenge = AdamantContacts.adamantExchange.welcomeMessage { _ = try? await chatProvider.fakeReceived( message: exchenge.message, senderId: AdamantContacts.adamantExchange.address, @@ -542,7 +542,7 @@ extension AppDelegate { ) } - if let betOnBitcoin = AdamantContacts.betOnBitcoin.messages["chats.welcome_message"] { + if let betOnBitcoin = AdamantContacts.betOnBitcoin.welcomeMessage { _ = try? await chatProvider.fakeReceived( message: betOnBitcoin.message, senderId: AdamantContacts.betOnBitcoin.address, @@ -553,7 +553,7 @@ extension AppDelegate { ) } - if let welcome = AdamantContacts.donate.messages["chats.welcome_message"] { + if let welcome = AdamantContacts.donate.welcomeMessage { _ = try? await chatProvider.fakeReceived( message: welcome.message, senderId: AdamantContacts.donate.address, @@ -564,7 +564,7 @@ extension AppDelegate { ) } - if let welcome = AdamantContacts.adamantWelcomeWallet.messages["chats.welcome_message"] { + if let welcome = AdamantContacts.adamantWelcomeWallet.welcomeMessage { _ = try? await chatProvider.fakeReceived( message: welcome.message, senderId: AdamantContacts.adamantWelcomeWallet.name, diff --git a/Adamant/App/DI/AppAssembly.swift b/Adamant/App/DI/AppAssembly.swift index 094fed956..a78793aed 100644 --- a/Adamant/App/DI/AppAssembly.swift +++ b/Adamant/App/DI/AppAssembly.swift @@ -70,7 +70,7 @@ struct AppAssembly: Assembly { }.inObjectScope(.container) // MARK: VibroService - container.register(VibroService.self) { r in + container.register(VibroService.self) { _ in AdamantVibroService() }.inObjectScope(.container) @@ -91,37 +91,110 @@ struct AppAssembly: Assembly { ) }.inObjectScope(.container) - // MARK: NodesSource - container.register(NodesSource.self) { r in - AdamantNodesSource( - apiService: r.resolve(ApiService.self)!, - healthCheckService: r.resolve(HealthCheckService.self)!, - securedStore: r.resolve(SecuredStore.self)!, - defaultNodesGetter: { AdamantResources.nodes } - ) + // MARK: NodesStorage + container.register(NodesStorageProtocol.self) { r in + NodesStorage(securedStore: r.resolve(SecuredStore.self)!) + }.inObjectScope(.container) + + // MARK: NodesAdditionalParamsStorage + container.register(NodesAdditionalParamsStorageProtocol.self) { r in + NodesAdditionalParamsStorage(securedStore: r.resolve(SecuredStore.self)!) + }.inObjectScope(.container) + + // MARK: ApiCore + container.register(APICoreProtocol.self) { _ in + APICore() }.inObjectScope(.container) // MARK: ApiService container.register(ApiService.self) { r in - AdamantApiService(adamantCore: r.resolve(AdamantCore.self)!) - }.initCompleted { (r, c) in // Weak reference - Task { @MainActor in - guard let service = c as? AdamantApiService else { return } - await service.setupWeakDeps(nodesSource: r.resolve(NodesSource.self)!) - } + AdamantApiService( + healthCheckWrapper: .init( + service: .init(apiCore: r.resolve(APICoreProtocol.self)!), + nodesStorage: r.resolve(NodesStorageProtocol.self)!, + nodesAdditionalParamsStorage: r.resolve(NodesAdditionalParamsStorageProtocol.self)!, + nodeGroup: .adm + ), + adamantCore: r.resolve(AdamantCore.self)! + ) + }.inObjectScope(.container) + + // MARK: BtcApiService + container.register(BtcApiService.self) { r in + BtcApiService(api: .init( + service: .init(apiCore: r.resolve(APICoreProtocol.self)!), + nodesStorage: r.resolve(NodesStorageProtocol.self)!, + nodesAdditionalParamsStorage: r.resolve(NodesAdditionalParamsStorageProtocol.self)!, + nodeGroup: .btc + )) }.inObjectScope(.container) - // MARK: HealthCheckService - container.register(HealthCheckService.self) { r in - AdamantHealthCheckService(apiService: r.resolve(ApiService.self)!) + // MARK: DogeApiService + container.register(DogeApiService.self) { r in + DogeApiService(api: .init( + service: .init(apiCore: r.resolve(APICoreProtocol.self)!), + nodesStorage: r.resolve(NodesStorageProtocol.self)!, + nodesAdditionalParamsStorage: r.resolve(NodesAdditionalParamsStorageProtocol.self)!, + nodeGroup: .doge + )) + }.inObjectScope(.container) + + // MARK: DashApiService + container.register(DashApiService.self) { r in + DashApiService(api: .init( + service: .init(apiCore: r.resolve(APICoreProtocol.self)!), + nodesStorage: r.resolve(NodesStorageProtocol.self)!, + nodesAdditionalParamsStorage: r.resolve(NodesAdditionalParamsStorageProtocol.self)!, + nodeGroup: .dash + )) + }.inObjectScope(.container) + + // MARK: LskNodeApiService + container.register(LskNodeApiService.self) { r in + LskNodeApiService(api: .init( + service: .init(), + nodesStorage: r.resolve(NodesStorageProtocol.self)!, + nodesAdditionalParamsStorage: r.resolve(NodesAdditionalParamsStorageProtocol.self)!, + nodeGroup: .lskNode + )) + }.inObjectScope(.container) + + // MARK: LskServiceApiService + container.register(LskServiceApiService.self) { r in + LskServiceApiService(api: .init( + service: .init(), + nodesStorage: r.resolve(NodesStorageProtocol.self)!, + nodesAdditionalParamsStorage: r.resolve(NodesAdditionalParamsStorageProtocol.self)!, + nodeGroup: .lskService + )) + }.inObjectScope(.container) + + // MARK: EthApiService + container.register(EthApiService.self) { r in + EthApiService(api: .init( + service: .init(apiCore: r.resolve(APICoreProtocol.self)!), + nodesStorage: r.resolve(NodesStorageProtocol.self)!, + nodesAdditionalParamsStorage: r.resolve(NodesAdditionalParamsStorageProtocol.self)!, + nodeGroup: .eth + )) + }.inObjectScope(.container) + + // MARK: ERC20ApiService + container.register(ERC20ApiService.self) { r in + ERC20ApiService(api: .init( + service: .init(apiCore: r.resolve(APICoreProtocol.self)!), + nodesStorage: r.resolve(NodesStorageProtocol.self)!, + nodesAdditionalParamsStorage: r.resolve(NodesAdditionalParamsStorageProtocol.self)!, + nodeGroup: .eth + )) }.inObjectScope(.container) // MARK: SocketService - container.register(SocketService.self) { _ in - AdamantSocketService() - }.initCompleted { (r, c) in // Weak reference - guard let service = c as? AdamantSocketService else { return } - service.nodesSource = r.resolve(NodesSource.self) + container.register(SocketService.self) { r in + AdamantSocketService( + nodesStorage: r.resolve(NodesStorageProtocol.self)!, + nodesAdditionalParamsStorage: r.resolve(NodesAdditionalParamsStorageProtocol.self)! + ) }.inObjectScope(.container) // MARK: AccountService diff --git a/Adamant/Helpers/ApiServiceError+Extension.swift b/Adamant/Helpers/ApiServiceError+Extension.swift new file mode 100644 index 000000000..b07097854 --- /dev/null +++ b/Adamant/Helpers/ApiServiceError+Extension.swift @@ -0,0 +1,22 @@ +// +// ApiServiceError+Extension.swift +// Adamant +// +// Created by Andrew G on 17.11.2023. +// Copyright © 2023 Adamant. All rights reserved. +// + +import Alamofire + +extension ApiServiceError { + init(error: Error) { + let afError = error as? AFError + + switch afError { + case .explicitlyCancelled: + self = .requestCancelled + default: + self = .networkError(error: error) + } + } +} diff --git a/Adamant/Helpers/ServerResponse+Resolver.swift b/Adamant/Helpers/ServerResponse+Resolver.swift new file mode 100644 index 000000000..fef3485a3 --- /dev/null +++ b/Adamant/Helpers/ServerResponse+Resolver.swift @@ -0,0 +1,60 @@ +// +// ServerModelResponse+Mapper.swift +// Adamant +// +// Created by Andrew G on 01.11.2023. +// Copyright © 2023 Adamant. All rights reserved. +// + +import CommonKit + +extension ServerModelResponse { + func resolved() -> ApiServiceResult { + if let model = model { + return .success(model) + } else { + return .failure(translateServerError(error)) + } + } +} + +extension ServerCollectionResponse { + func resolved() -> ApiServiceResult<[T]> { + if let collection = collection { + return .success(collection) + } else { + return .failure(translateServerError(error)) + } + } +} + +extension TransactionIdResponse { + func resolved() -> ApiServiceResult { + if let ransactionId = transactionId { + return .success(ransactionId) + } else { + return .failure(translateServerError(error)) + } + } +} + +extension GetPublicKeyResponse { + func resolved() -> ApiServiceResult { + if let publicKey = publicKey { + return .success(publicKey) + } else { + return .failure(translateServerError(error)) + } + } +} + +private func translateServerError(_ error: String?) -> ApiServiceError { + guard let error = error else { return .internalError(error: InternalAPIError.unknownError) } + + switch error { + case "Account not found": + return .accountNotFound + default: + return .serverError(error: error) + } +} diff --git a/Adamant/Models/APIParametersEncoding.swift b/Adamant/Models/APIParametersEncoding.swift new file mode 100644 index 000000000..545507ebd --- /dev/null +++ b/Adamant/Models/APIParametersEncoding.swift @@ -0,0 +1,30 @@ +// +// APIParametersEncoding.swift +// Adamant +// +// Created by Andrew G on 30.10.2023. +// Copyright © 2023 Adamant. All rights reserved. +// + +import Alamofire +import Foundation + +enum APIParametersEncoding { + case url + case json + case bodyString + case forceQueryItems([URLQueryItem]) + + var parametersEncoding: ParameterEncoding { + switch self { + case .url: + return URLEncoding.default + case .json: + return JSONEncoding.default + case .bodyString: + return BodyStringEncoding() + case let .forceQueryItems(items): + return ForceQueryItemsEncoding(queryItems: items) + } + } +} diff --git a/Adamant/Models/APIResponseModel.swift b/Adamant/Models/APIResponseModel.swift new file mode 100644 index 000000000..2fe7c288b --- /dev/null +++ b/Adamant/Models/APIResponseModel.swift @@ -0,0 +1,15 @@ +// +// APIResponseModel.swift +// Adamant +// +// Created by Andrew G on 17.11.2023. +// Copyright © 2023 Adamant. All rights reserved. +// + +import Foundation + +struct APIResponseModel { + let result: ApiServiceResult + let data: Data? + let code: Int? +} diff --git a/Adamant/Models/ApiServiceError.swift b/Adamant/Models/ApiServiceError.swift index b3ff326c7..e8d013975 100644 --- a/Adamant/Models/ApiServiceError.swift +++ b/Adamant/Models/ApiServiceError.swift @@ -17,6 +17,7 @@ enum ApiServiceError: LocalizedError, Error { case networkError(error: Error) case requestCancelled case commonError(message: String) + case noEndpointsAvailable var errorDescription: String? { switch self { @@ -41,8 +42,18 @@ enum ApiServiceError: LocalizedError, Error { case let .commonError(message): return String.adamant.sharedErrors.commonError(message) + + case .noEndpointsAvailable: + return .localized( + "ApiService.InternalError.NoNodesAvailable", + comment: "Serious internal error: No nodes available" + ) } } + + static func internalError(error: InternalAPIError) -> Self { + .internalError(message: error.localizedDescription, error: error) + } } extension ApiServiceError: RichError { @@ -52,7 +63,7 @@ extension ApiServiceError: RichError { var level: ErrorLevel { switch self { - case .accountNotFound, .notLogged, .networkError, .requestCancelled: + case .accountNotFound, .notLogged, .networkError, .requestCancelled, .noEndpointsAvailable: return .warning case .serverError, .commonError: @@ -65,7 +76,7 @@ extension ApiServiceError: RichError { var internalError: Error? { switch self { - case .accountNotFound, .notLogged, .serverError, .requestCancelled, .commonError: + case .accountNotFound, .notLogged, .serverError, .requestCancelled, .commonError, .noEndpointsAvailable: return nil case .internalError(_, let error): @@ -100,3 +111,27 @@ extension ApiServiceError: Equatable { } } } + +extension ApiServiceError: HealthCheckableError { + var isNetworkError: Bool { + switch self { + case .networkError: + return true + default: + return false + } + } + + var isRequestCancelledError: Bool { + switch self { + case .requestCancelled: + return true + default: + return false + } + } + + static var noEndpointsError: ApiServiceError { + .noEndpointsAvailable + } +} diff --git a/Adamant/Models/ApiServiceResult.swift b/Adamant/Models/ApiServiceResult.swift index 3d283c696..71c88ccd3 100644 --- a/Adamant/Models/ApiServiceResult.swift +++ b/Adamant/Models/ApiServiceResult.swift @@ -6,7 +6,4 @@ // Copyright © 2022 Adamant. All rights reserved. // -enum ApiServiceResult { - case success(T) - case failure(ApiServiceError) -} +typealias ApiServiceResult = Result diff --git a/Adamant/Models/BodyStringEncoding.swift b/Adamant/Models/BodyStringEncoding.swift new file mode 100644 index 000000000..1b7242d61 --- /dev/null +++ b/Adamant/Models/BodyStringEncoding.swift @@ -0,0 +1,37 @@ +// +// BodyStringEncoding.swift +// Adamant +// +// Created by Andrew G on 30.10.2023. +// Copyright © 2023 Adamant. All rights reserved. +// + +import Alamofire +import Foundation + +struct BodyStringEncoding: ParameterEncoding { + func encode( + _ urlRequest: URLRequestConvertible, + with parameters: Parameters? + ) throws -> URLRequest { + var urlRequest = try urlRequest.asURLRequest() + + guard + let string = parameters?.first?.value as? String, + let data = string.data(using: .utf8) + else { + throw AFError.parameterEncodingFailed( + reason: .customEncodingFailed(error: AdamantError( + message: "String encoding problem" + )) + ) + } + + if parameters?.count != 1 { + assertionFailure("BodyStringEncoding uses just first parameter for encoding") + } + + urlRequest.httpBody = data + return urlRequest + } +} diff --git a/Adamant/Models/ForceQueryItemsEncoding.swift b/Adamant/Models/ForceQueryItemsEncoding.swift new file mode 100644 index 000000000..44ae8296b --- /dev/null +++ b/Adamant/Models/ForceQueryItemsEncoding.swift @@ -0,0 +1,32 @@ +// +// ForceQueryItemsEncoding.swift +// Adamant +// +// Created by Andrew G on 18.11.2023. +// Copyright © 2023 Adamant. All rights reserved. +// + +import Alamofire +import Foundation + +struct ForceQueryItemsEncoding: ParameterEncoding { + let queryItems: [URLQueryItem] + + func encode( + _ urlRequest: URLRequestConvertible, + with parameters: Parameters? + ) throws -> URLRequest { + var urlRequest = try urlRequest.asURLRequest() + + guard + let url = urlRequest.url, + var urlComponents = URLComponents(url: url, resolvingAgainstBaseURL: false) + else { + throw AFError.parameterEncodingFailed(reason: .missingURL) + } + + urlComponents.queryItems = queryItems + urlRequest.url = urlComponents.url + return urlRequest + } +} diff --git a/Adamant/Models/InternalAPIError.swift b/Adamant/Models/InternalAPIError.swift new file mode 100644 index 000000000..c28ad55af --- /dev/null +++ b/Adamant/Models/InternalAPIError.swift @@ -0,0 +1,43 @@ +// +// InternalAPIError.swift +// Adamant +// +// Created by Andrew G on 30.10.2023. +// Copyright © 2023 Adamant. All rights reserved. +// + +import CommonKit +import Foundation + +enum InternalAPIError: LocalizedError { + case endpointBuildFailed + case signTransactionFailed + case parsingFailed + case unknownError + + func apiServiceErrorWith(error: Error) -> ApiServiceError { + .internalError(message: localizedDescription, error: error) + } + + var errorDescription: String? { + switch self { + case .endpointBuildFailed: + return .localized( + "ApiService.InternalError.EndpointBuildFailed", + comment: "Serious internal error: Failed to build endpoint url" + ) + case .signTransactionFailed: + return .localized( + "ApiService.InternalError.FailedTransactionSigning", + comment: "Serious internal error: Failed to sign transaction" + ) + case .parsingFailed: + return .localized( + "ApiService.InternalError.ParsingFailed", + comment: "Serious internal error: Error parsing response" + ) + case .unknownError: + return .adamant.sharedErrors.unknownError + } + } +} diff --git a/Adamant/Models/NodeStatusInfo.swift b/Adamant/Models/NodeStatusInfo.swift new file mode 100644 index 000000000..0046b8af4 --- /dev/null +++ b/Adamant/Models/NodeStatusInfo.swift @@ -0,0 +1,17 @@ +// +// NodeStatusInfo.swift +// Adamant +// +// Created by Andrew G on 01.11.2023. +// Copyright © 2023 Adamant. All rights reserved. +// + +import Foundation + +struct NodeStatusInfo: Equatable { + let ping: TimeInterval + let height: Int + let wsEnabled: Bool + let wsPort: Int? + let version: String? +} diff --git a/Adamant/Modules/Delegates/DelegateDetailsViewController.swift b/Adamant/Modules/Delegates/DelegateDetailsViewController.swift index 72f2ce654..58d85dc2a 100644 --- a/Adamant/Modules/Delegates/DelegateDetailsViewController.swift +++ b/Adamant/Modules/Delegates/DelegateDetailsViewController.swift @@ -276,46 +276,42 @@ extension DelegateDetailsViewController { extension DelegateDetailsViewController { private func refreshData(with delegate: Delegate) { Task { - await apiService.getForgedByAccount(publicKey: delegate.publicKey) { [weak self] result in - switch result { - case .success(let details): - self?.forged = details.forged - - Task { @MainActor in - guard let tableView = self?.tableView else { - return - } - - let indexPath = Row.forged.indexPathFor(section: 0) - tableView.reloadRows(at: [indexPath], with: .none) - } - case .failure(let error): - self?.apiServiceFailed(with: error) + let result = await apiService.getForgedByAccount(publicKey: delegate.publicKey) + + switch result { + case .success(let details): + forged = details.forged + + guard let tableView = tableView else { + return } + + let indexPath = Row.forged.indexPathFor(section: 0) + tableView.reloadRows(at: [indexPath], with: .none) + case .failure(let error): + apiServiceFailed(with: error) } // Get forging time - await apiService.getForgingTime(for: delegate) { [weak self] result in - switch result { - case .success(let seconds): - if seconds >= 0 { - self?.forgingTime = TimeInterval(exactly: seconds) - } else { - self?.forgingTime = nil - } - - Task { @MainActor in - guard let tableView = self?.tableView else { - return - } - - let indexPath = Row.forgingTime.indexPathFor(section: 0) - tableView.reloadRows(at: [indexPath], with: .none) - } - - case .failure(let error): - self?.apiServiceFailed(with: error) + let forgingTimeResult = await apiService.getForgingTime(for: delegate) + + switch forgingTimeResult { + case .success(let seconds): + if seconds >= 0 { + forgingTime = TimeInterval(exactly: seconds) + } else { + forgingTime = nil } + + guard let tableView = tableView else { + return + } + + let indexPath = Row.forgingTime.indexPathFor(section: 0) + tableView.reloadRows(at: [indexPath], with: .none) + + case .failure(let error): + apiServiceFailed(with: error) } } } diff --git a/Adamant/Modules/Delegates/DelegatesListViewController.swift b/Adamant/Modules/Delegates/DelegatesListViewController.swift index f76885391..c6b1eb46d 100644 --- a/Adamant/Modules/Delegates/DelegatesListViewController.swift +++ b/Adamant/Modules/Delegates/DelegatesListViewController.swift @@ -138,37 +138,33 @@ final class DelegatesListViewController: KeyboardObservingViewController { } Task { - await apiService.getDelegatesWithVotes( + let result = await apiService.getDelegatesWithVotes( for: address, limit: activeDelegates - ) { result in - Task { @MainActor [weak self] in - guard let self = self else { return } - - switch result { - case .success(let delegates): - let checkedNames = self.delegates - .filter { $0.isChecked } - .map { $0.delegate.username } - - let checkedDelegates = delegates.map { CheckedDelegate(delegate: $0) } - for name in checkedNames { - if let i = delegates.firstIndex(where: { $0.username == name }) { - checkedDelegates[i].isChecked = true - } - } - - self.delegates = checkedDelegates - self.tableView.reloadData() - case .failure(let error): - self.dialogService.showRichError(error: error) + ) + + switch result { + case .success(let delegates): + let checkedNames = self.delegates + .filter { $0.isChecked } + .map { $0.delegate.username } + + let checkedDelegates = delegates.map { CheckedDelegate(delegate: $0) } + for name in checkedNames { + if let i = delegates.firstIndex(where: { $0.username == name }) { + checkedDelegates[i].isChecked = true } - - refreshControl.endRefreshing() - self.updateVotePanel() - self.removeLoadingView() } + + self.delegates = checkedDelegates + self.tableView.reloadData() + case .failure(let error): + self.dialogService.showRichError(error: error) } + + refreshControl.endRefreshing() + self.updateVotePanel() + self.removeLoadingView() } } @@ -318,32 +314,30 @@ private extension DelegatesListViewController { dialogService.showProgress(withMessage: nil, userInteractionEnable: false) Task { - await apiService.voteForDelegates( + let result = await apiService.voteForDelegates( from: account.address, keypair: keypair, votes: votes - ) { result in - Task { @MainActor [weak self] in - self?.dialogService.dismissProgress() - - switch result { - case .success: - self?.dialogService.showSuccess(withMessage: String.adamant.delegates.success) - - checkedDelegates.forEach { - $1.isChecked = false - $1.delegate.voted = !$1.delegate.voted - $1.isUpdating = true - } - - self?.tableView.reloadData() - self?.updateVotePanel() - self?.scheduleUpdate() + ) + + dialogService.dismissProgress() + + switch result { + case .success: + dialogService.showSuccess(withMessage: String.adamant.delegates.success) - case .failure(let error): - self?.dialogService.showRichError(error: TransfersProviderError.serverError(error)) - } + checkedDelegates.forEach { + $1.isChecked = false + $1.delegate.voted = !$1.delegate.voted + $1.isUpdating = true } + + tableView.reloadData() + updateVotePanel() + scheduleUpdate() + + case .failure(let error): + dialogService.showRichError(error: TransfersProviderError.serverError(error)) } } } diff --git a/Adamant/Modules/Login/LoginViewController.swift b/Adamant/Modules/Login/LoginViewController.swift index e467e5d04..a2dff3ccd 100644 --- a/Adamant/Modules/Login/LoginViewController.swift +++ b/Adamant/Modules/Login/LoginViewController.swift @@ -374,14 +374,14 @@ extension LoginViewController { dialogService.showProgress(withMessage: String.adamant.login.loggingInProgressMessage, userInteractionEnable: false) Task { - await apiService.getAccount(byPassphrase: passphrase) { [weak self] result in - switch result { - case .success: - self?.loginIntoExistingAccount(passphrase: passphrase) - - case .failure(let error): - self?.dialogService.showRichError(error: error) - } + let result = await apiService.getAccount(byPassphrase: passphrase) + + switch result { + case .success: + loginIntoExistingAccount(passphrase: passphrase) + + case .failure(let error): + dialogService.showRichError(error: error) } } } diff --git a/Adamant/Modules/NodesEditor/EurekaNodeRow.swift b/Adamant/Modules/NodesEditor/EurekaNodeRow.swift deleted file mode 100644 index 80a3850eb..000000000 --- a/Adamant/Modules/NodesEditor/EurekaNodeRow.swift +++ /dev/null @@ -1,156 +0,0 @@ -// -// EurekaNodeRow.swift -// Adamant -// -// Created by Anokhov Pavel on 20.06.2018. -// Copyright © 2018 Adamant. All rights reserved. -// - -import UIKit -import SnapKit -import Eureka -import CommonKit - -final class NodeCell: Cell, CellType { - struct Model: Equatable { - enum NodeActivity { - case webSockets - case rest - } - - let node: Node - let nodeUpdate: () -> Void - let nodeActivity: (Node) -> Set - - static func == (lhs: Self, rhs: Self) -> Bool { - lhs.node == rhs.node - } - } - - private let checkmarkRowView = CheckmarkRowView() - private var model: NodeCell.Model? { row.value } - - required init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) { - super.init(style: style, reuseIdentifier: reuseIdentifier) - setupView() - } - - required init?(coder: NSCoder) { - super.init(coder: coder) - setupView() - } - - public override func update() { - checkmarkRowView.checkmarkImage = .asset(named: "status_success") - checkmarkRowView.setIsChecked(model?.node.isEnabled ?? false, animated: true) - checkmarkRowView.onCheckmarkTap = { [weak self] in - guard let newValue = (self?.checkmarkRowView.isChecked).map({ !$0 }) else { - return - } - - self?.model?.node.isEnabled = newValue - self?.model?.nodeUpdate() - } - - checkmarkRowView.title = model?.node.asString() - checkmarkRowView.captionColor = getIndicatorColor(status: model?.node.connectionStatus) - checkmarkRowView.caption = ["●", makeActivitiesString()] - .compactMap { $0 } - .joined(separator: " ") - - let descriptionStrings = [ - model?.node.statusString, - model?.node.status?.version.map { "(\(NodeCell.Strings.version): \($0))" } - ] - - checkmarkRowView.subtitle = descriptionStrings.compactMap { $0 }.joined(separator: " ") - } - - private func makeActivitiesString() -> String? { - guard let model = model else { return nil } - let activities = model.nodeActivity(model.node) - - guard !activities.isEmpty else { return nil } - - return activities.map { activity in - switch activity { - case .webSockets: - return "ws" - case .rest: - return model.node.scheme.rawValue - } - }.sorted().joined(separator: ", ") - } - - private func setupView() { - contentView.addSubview(checkmarkRowView) - checkmarkRowView.snp.makeConstraints { - $0.directionalEdges.equalToSuperview() - } - } -} - -final class NodeRow: Row, RowType { - required public init(tag: String?) { - super.init(tag: tag) - cellProvider = .init() - } -} - -private extension Node { - var statusString: String? { - switch connectionStatus { - case .allowed: - let ping = status.map { Int($0.ping * 1000) } - return ping.map { "\(NodeCell.Strings.ping): \($0) \(NodeCell.Strings.milliseconds)" } - case .synchronizing: - return NodeCell.Strings.synchronizing - case .offline: - return NodeCell.Strings.offline - case .none: - return nil - } - } -} - -private extension NodeCell { - enum Strings { - static let ping = String.localized( - "NodesList.NodeCell.Ping", - comment: "NodesList.NodeCell: Node ping" - ) - - static let milliseconds = String.localized( - "NodesList.NodeCell.Milliseconds", - comment: "NodesList.NodeCell: Milliseconds" - ) - - static let synchronizing = String.localized( - "NodesList.NodeCell.Synchronizing", - comment: "NodesList.NodeCell: Node is synchronizing" - ) - - static let offline = String.localized( - "NodesList.NodeCell.Offline", - comment: "NodesList.NodeCell: Node is offline" - ) - - static let version = String.localized( - "NodesList.NodeCell.Version", - comment: "NodesList.NodeCell: Node version" - ) - } -} - -private func getIndicatorColor(status: Node.ConnectionStatus?) -> UIColor { - switch status { - case .allowed: - return .adamant.good - case .synchronizing: - return .adamant.alert - case .offline: - return .adamant.danger - case .none: - return .adamant.inactive - } -} diff --git a/Adamant/Modules/NodesEditor/NodeCell/NodeCell+Model.swift b/Adamant/Modules/NodesEditor/NodeCell/NodeCell+Model.swift new file mode 100644 index 000000000..0185cef65 --- /dev/null +++ b/Adamant/Modules/NodesEditor/NodeCell/NodeCell+Model.swift @@ -0,0 +1,43 @@ +// +// EurekaNodeRow+Model.swift +// Adamant +// +// Created by Andrew G on 19.11.2023. +// Copyright © 2023 Adamant. All rights reserved. +// + +import Foundation +import CommonKit + +extension NodeCell { + typealias NodeUpdateAction = (_ isEnabled: Bool) -> Void + + struct Model: Equatable { + let id: UUID + let title: String + let connectionStatus: Node.ConnectionStatus? + let statusString: String? + let versionString: String? + let isEnabled: Bool + let activities: Set + let nodeUpdateAction: IDWrapper + + static let `default` = Self( + id: .init(), + title: .empty, + connectionStatus: nil, + statusString: .empty, + versionString: .empty, + isEnabled: false, + activities: .init(), + nodeUpdateAction: .init(id: .empty) { _ in } + ) + } +} + +extension NodeCell.Model { + enum NodeActivity: Equatable, Hashable { + case webSockets + case rest(scheme: Node.URLScheme) + } +} diff --git a/Adamant/Modules/NodesEditor/NodeCell/NodeCell.swift b/Adamant/Modules/NodesEditor/NodeCell/NodeCell.swift new file mode 100644 index 000000000..fef3fdf2f --- /dev/null +++ b/Adamant/Modules/NodesEditor/NodeCell/NodeCell.swift @@ -0,0 +1,108 @@ +// +// NodeCell.swift +// Adamant +// +// Created by Anokhov Pavel on 20.06.2018. +// Copyright © 2018 Adamant. All rights reserved. +// + +import UIKit +import SnapKit +import Eureka +import CommonKit +import Combine + +final class NodeCell: Cell, CellType { + private let checkmarkRowView = CheckmarkRowView() + private var subscription: AnyCancellable? + + private var model: Model = .default { + didSet { + guard model != oldValue else { return } + baseRow.baseValue = model + update() + } + } + + required init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) { + super.init(style: style, reuseIdentifier: reuseIdentifier) + setupView() + } + + required init?(coder: NSCoder) { + super.init(coder: coder) + setupView() + } + + override func update() { + checkmarkRowView.setIsChecked(model.isEnabled, animated: true) + checkmarkRowView.title = model.title + checkmarkRowView.captionColor = getIndicatorColor(status: model.connectionStatus) + + checkmarkRowView.caption = ["●", makeActivitiesString()] + .compactMap { $0 } + .joined(separator: " ") + + let descriptionStrings = [ + model.statusString, + model.versionString + ] + + checkmarkRowView.subtitle = descriptionStrings + .compactMap { $0 } + .joined(separator: " ") + } + + func subscribe>(_ publisher: P) { + subscription = publisher + .removeDuplicates() + .sink { [weak self] in self?.model = $0 } + } +} + +private extension NodeCell { + func setupView() { + contentView.addSubview(checkmarkRowView) + checkmarkRowView.snp.makeConstraints { + $0.directionalEdges.equalToSuperview() + } + + checkmarkRowView.checkmarkImage = .asset(named: "status_success") + checkmarkRowView.onCheckmarkTap = { [weak self] in self?.onCheckmarkTap()} + } + + func onCheckmarkTap() { + model.nodeUpdateAction.value(!checkmarkRowView.isChecked) + } + + func makeActivitiesString() -> String? { + model.activities.map { activity in + switch activity { + case .webSockets: + return "ws" + case let .rest(scheme): + return scheme.rawValue + } + }.sorted().joined(separator: ", ") + } +} + +final class NodeRow: Row, RowType { + required public init(tag: String?) { + super.init(tag: tag) + cellProvider = .init() + } +} + +private func getIndicatorColor(status: Node.ConnectionStatus?) -> UIColor { + switch status { + case .allowed: + return .adamant.good + case .synchronizing: + return .adamant.alert + case .offline: + return .adamant.danger + case .none: + return .adamant.inactive + } +} diff --git a/Adamant/Modules/NodesEditor/NodeEditorViewController.swift b/Adamant/Modules/NodesEditor/NodeEditorViewController.swift index 064418396..5c0e8216f 100644 --- a/Adamant/Modules/NodesEditor/NodeEditorViewController.swift +++ b/Adamant/Modules/NodesEditor/NodeEditorViewController.swift @@ -12,12 +12,10 @@ import CommonKit // MARK: - Localization extension String.adamant { - struct nodesEditor { + enum nodesEditor { static let newNodeTitle = String.localized("NodesEditor.NewNodeTitle", comment: "NodesEditor: New node scene title") static let deleteNodeAlert = String.localized("NodesEditor.DeleteNodeAlert", comment: "NodesEditor: Delete node confirmation message") static let failedToBuildURL = String.localized("NodesEditor.FailedToBuildURL", comment: "NodesEditor: Failed to build URL alert") - - private init() {} } } @@ -90,6 +88,7 @@ final class NodeEditorViewController: FormViewController { // MARK: - Dependencies var dialogService: DialogService! var apiService: ApiService! + var nodesStorage: NodesStorageProtocol! // MARK: - Properties var node: Node? @@ -138,15 +137,15 @@ final class NodeEditorViewController: FormViewController { $0.value = node.port $0.placeholder = String(node.scheme.defaultPort) } else { - $0.placeholder = String(URLScheme.default.defaultPort) + $0.placeholder = String(Node.URLScheme.default.defaultPort) } } // Scheme - <<< PickerInlineRow { + <<< PickerInlineRow { $0.title = Rows.scheme.localized $0.tag = Rows.scheme.tag - $0.value = node?.scheme ?? URLScheme.default + $0.value = node?.scheme ?? Node.URLScheme.default $0.options = [.https, .http] $0.baseCell.detailTextLabel?.textColor = .adamant.textColor }.onExpandInlineRow { (cell, _, inlineRow) in @@ -156,7 +155,7 @@ final class NodeEditorViewController: FormViewController { if let scheme = row.value { portRow.placeholder = String(scheme.defaultPort) } else { - portRow.placeholder = String(URLScheme.default.defaultPort) + portRow.placeholder = String(Node.URLScheme.default.defaultPort) } portRow.updateCell() @@ -165,7 +164,7 @@ final class NodeEditorViewController: FormViewController { // MARK: - WebSockets - if let wsEnabled = node?.status?.wsEnabled { + if let wsEnabled = node?.wsEnabled { form +++ Section() <<< LabelRow { @@ -218,12 +217,15 @@ extension NodeEditorViewController { } let host = rawUrl.trimmingCharacters(in: .whitespaces) + let scheme: Node.URLScheme - let scheme: URLScheme - if let row = form.rowBy(tag: Rows.scheme.tag), let value = row.baseValue as? URLScheme { + if + let row = form.rowBy(tag: Rows.scheme.tag), + let value = row.baseValue as? Node.URLScheme + { scheme = value } else { - scheme = URLScheme.default + scheme = .default } let port: Int? @@ -235,12 +237,22 @@ extension NodeEditorViewController { let result: NodeEditorResult if let node = node { - node.scheme = scheme - node.host = host - node.port = port + nodesStorage.updateNodeParams( + id: node.id, + scheme: scheme, + host: host, + port: port + ) + result = .nodeUpdated } else { - result = .new(node: Node(scheme: scheme, host: host, port: port)) + result = .new(node: Node( + scheme: scheme, + host: host, + isEnabled: true, + wsEnabled: false, + port: port + )) } didCallDelegate = true diff --git a/Adamant/Modules/NodesEditor/NodesEditorFactory.swift b/Adamant/Modules/NodesEditor/NodesEditorFactory.swift index 9096f13db..8d6dffdfd 100644 --- a/Adamant/Modules/NodesEditor/NodesEditorFactory.swift +++ b/Adamant/Modules/NodesEditor/NodesEditorFactory.swift @@ -14,20 +14,22 @@ struct NodesEditorFactory { let assembler: Assembler func makeNodesListVC(screensFactory: ScreensFactory) -> UIViewController { - let c = NodesListViewController() - c.screensFactory = screensFactory - c.dialogService = assembler.resolve(DialogService.self) - c.securedStore = assembler.resolve(SecuredStore.self) - c.apiService = assembler.resolve(ApiService.self) - c.socketService = assembler.resolve(SocketService.self) - c.nodesSource = assembler.resolve(NodesSource.self) - return c + NodesListViewController( + dialogService: assembler.resolve(DialogService.self)!, + securedStore: assembler.resolve(SecuredStore.self)!, + screensFactory: screensFactory, + nodesStorage: assembler.resolve(NodesStorageProtocol.self)!, + nodesAdditionalParamsStorage: assembler.resolve(NodesAdditionalParamsStorageProtocol.self)!, + apiService: assembler.resolve(ApiService.self)!, + socketService: assembler.resolve(SocketService.self)! + ) } func makeNodeEditorVC() -> NodeEditorViewController { let c = NodeEditorViewController() c.dialogService = assembler.resolve(DialogService.self) c.apiService = assembler.resolve(ApiService.self) + c.nodesStorage = assembler.resolve(NodesStorageProtocol.self) return c } } diff --git a/Adamant/Modules/NodesEditor/NodesListViewController.swift b/Adamant/Modules/NodesEditor/NodesListViewController.swift index 927d63dd2..637a04865 100644 --- a/Adamant/Modules/NodesEditor/NodesListViewController.swift +++ b/Adamant/Modules/NodesEditor/NodesListViewController.swift @@ -9,6 +9,7 @@ import UIKit import Eureka import CommonKit +import Combine // MARK: - Localization extension String.adamant { @@ -67,87 +68,66 @@ final class NodesListViewController: FormViewController { // MARK: Dependencies - var dialogService: DialogService! - var securedStore: SecuredStore! - var screensFactory: ScreensFactory! - var nodesSource: NodesSource! - - var apiService: ApiService! { - didSet { - Task { - currentRestNode = await apiService.currentNodes.first - } - } - } - - var socketService: SocketService! { - didSet { - currentSocketsNode = socketService.currentNode - } - } + private let dialogService: DialogService + private let securedStore: SecuredStore + private let screensFactory: ScreensFactory + private let nodesStorage: NodesStorageProtocol + private let nodesAdditionalParamsStorage: NodesAdditionalParamsStorageProtocol + private let apiService: ApiService + private let socketService: SocketService // Properties - private var timer: Timer? - - private var currentSocketsNode: Node? { - didSet { - updateNodesRows() - } - } + @ObservableValue private var nodesList = [Node]() + @ObservableValue private var currentSocketsNodeId: UUID? + @ObservableValue private var currentRestNodesIds = [UUID]() - private var currentRestNode: Node? { - didSet { - updateNodesRows() - } - } + private var nodesHaveBeenDisplayed = false + private var timerSubsctiption: AnyCancellable? + private var subscriptions = Set() // MARK: - Lifecycle - init() { + init( + dialogService: DialogService, + securedStore: SecuredStore, + screensFactory: ScreensFactory, + nodesStorage: NodesStorageProtocol, + nodesAdditionalParamsStorage: NodesAdditionalParamsStorageProtocol, + apiService: ApiService, + socketService: SocketService + ) { + self.dialogService = dialogService + self.securedStore = securedStore + self.screensFactory = screensFactory + self.nodesStorage = nodesStorage + self.nodesAdditionalParamsStorage = nodesAdditionalParamsStorage + self.apiService = apiService + self.socketService = socketService super.init(nibName: nil, bundle: nil) setup() } required init?(coder aDecoder: NSCoder) { - super.init(coder: aDecoder) - setup() - } - - deinit { - timer?.invalidate() + fatalError("Isn't implemented") } private func setup() { - NotificationCenter.default.addObserver( - forName: Notification.Name.NodesSource.nodesUpdate, - object: nil, - queue: nil - ) { [weak self] _ in - DispatchQueue.onMainAsync { - self?.updateNodesRows() - } - } + nodesStorage.getNodesPublisher(group: nodeGroup) + .combineLatest(nodesAdditionalParamsStorage.fastestNodeMode(group: nodeGroup)) + .receive(on: DispatchQueue.main) + .sink { [weak self] in self?.setNewNodesList($0.0) } + .store(in: &subscriptions) - NotificationCenter.default.addObserver( - forName: Notification.Name.ApiService.currentNodeUpdate, - object: nil, - queue: nil - ) { [weak self] _ in - DispatchQueue.onMainAsync { - self?.currentRestNode = self?.apiService.currentNodes.first - } - } + NotificationCenter.default + .publisher(for: .SocketService.currentNodeUpdate, object: nil) + .receive(on: DispatchQueue.main) + .map { [weak self] _ in self?.socketService.currentNode?.id } + .removeDuplicates() + .assign(to: _currentSocketsNodeId) + .store(in: &subscriptions) - NotificationCenter.default.addObserver( - forName: Notification.Name.SocketService.currentNodeUpdate, - object: nil, - queue: nil - ) { [weak self] _ in - DispatchQueue.onMainAsync { - self?.currentSocketsNode = self?.socketService.currentNode - } - } + currentSocketsNodeId = socketService.currentNode?.id } override func viewDidLoad() { @@ -173,11 +153,16 @@ final class NodesListViewController: FormViewController { $0.tag = Sections.preferTheFastestNode.tag } - <<< SwitchRow { [preferTheFastestNode = nodesSource.preferTheFastestNode] in + <<< SwitchRow { [nodesAdditionalParamsStorage] in $0.title = Rows.preferTheFastestNode.localized - $0.value = preferTheFastestNode - }.onChange { [weak nodesSource] in - nodesSource?.preferTheFastestNode = $0.value ?? true + $0.value = nodesAdditionalParamsStorage.isFastestNodeMode( + group: nodeGroup + ) + }.onChange { [nodesAdditionalParamsStorage] in + nodesAdditionalParamsStorage.setFastestNodeMode( + group: nodeGroup, + value: $0.value ?? true + ) }.cellUpdate { cell, _ in cell.switchControl.onTintColor = .adamant.active } @@ -209,22 +194,31 @@ final class NodesListViewController: FormViewController { override func viewWillAppear(_ animated: Bool) { super.viewWillAppear(animated) - updateNodesRows() - nodesSource.healthCheck() + apiService.healthCheck() setHealthCheckTimer() } - override func viewWillDisappear(_ animated: Bool) { - super.viewWillDisappear(animated) - timer?.invalidate() - } - // MARK: - Other private func setColors() { view.backgroundColor = UIColor.adamant.secondBackgroundColor tableView.backgroundColor = .clear } + + private func setNewNodesList(_ newNodes: [Node]) { + nodesList = newNodes + currentRestNodesIds = apiService.preferredNodeIds + + if !nodesHaveBeenDisplayed { + UIView.performWithoutAnimation { + remakeNodesRows() + } + } else { + remakeNodesRows() + } + + nodesHaveBeenDisplayed = true + } } // MARK: - Manipulating node list @@ -234,28 +228,28 @@ extension NodesListViewController { } func addNode(node: Node) { - getNodesSection()?.append(createRowFor(node: node, tag: generateRandomTag())) - nodesSource.nodes.append(node) + getNodesSection()?.append(createRowFor(nodeId: node.id, tag: generateRandomTag())) + nodesStorage.addNode(node, group: nodeGroup) } - func removeNode(node: Node) { - guard let index = getNodeIndex(node: node) else { return } + func removeNode(nodeId: UUID) { + guard let index = getNodeIndex(nodeId: nodeId) else { return } getNodesSection()?.remove(at: index) - nodesSource.nodes.remove(at: index) + nodesStorage.removeNode(id: nodeId) } - func getNodeIndex(node: Node) -> Int? { - nodesSource.nodes.firstIndex { $0 === node } + func getNodeIndex(nodeId: UUID) -> Int? { + displayedNodesIds.firstIndex { $0 == nodeId } } func getNodesSection() -> Section? { form.sectionBy(tag: Sections.nodes.tag) } - var displayedNodes: [Node] { + var displayedNodesIds: [UUID] { getNodesSection()?.allRows.compactMap { - ($0.baseValue as? NodeCell.Model)?.node + ($0.baseValue as? NodeCell.Model)?.id } ?? [] } @@ -273,7 +267,7 @@ extension NodesListViewController { func resetToDefault(silent: Bool = false) { if silent { - nodesSource.setDefaultNodes() + nodesStorage.resetNodes(group: nodeGroup) return } @@ -282,24 +276,22 @@ extension NodesListViewController { alert.addAction(UIAlertAction( title: Rows.reset.localized, style: .destructive, - handler: { [weak self] _ in self?.nodesSource.setDefaultNodes() } + handler: { [weak self] _ in self?.nodesStorage.resetNodes(group: nodeGroup) } )) alert.modalPresentationStyle = .overFullScreen present(alert, animated: true, completion: nil) } - func updateNodesRows() { - guard let nodesSection = getNodesSection() else { return } - - guard !displayedNodes.hasTheSameReferences(as: nodesSource.nodes) else { - nodesSection.allRows.forEach { $0.updateCell() } - return - } + func remakeNodesRows() { + guard + let nodesSection = getNodesSection(), + displayedNodesIds != nodesList.map({ $0.id }) + else { return } nodesSection.removeAll() - for node in nodesSource.nodes { - let row = createRowFor(node: node, tag: generateRandomTag()) + for node in nodesList { + let row = createRowFor(nodeId: node.id, tag: generateRandomTag()) nodesSection.append(row) } } @@ -311,23 +303,16 @@ extension NodesListViewController: NodeEditorDelegate { switch result { case .new(let node): addNode(node: node) - case .delete(let editorNode): - removeNode(node: editorNode) - - case .nodeUpdated: - nodesSource.nodesUpdate() - - case .cancel: + removeNode(nodeId: editorNode.id) + case .nodeUpdated, .cancel: break } - DispatchQueue.main.async { - if UIScreen.main.traitCollection.userInterfaceIdiom == .pad { - self.navigationController?.popToViewController(self, animated: true) - } else { - self.dismiss(animated: true, completion: nil) - } + if UIScreen.main.traitCollection.userInterfaceIdiom == .pad { + navigationController?.popToViewController(self, animated: true) + } else { + dismiss(animated: true, completion: nil) } } } @@ -336,7 +321,7 @@ extension NodesListViewController: NodeEditorDelegate { extension NodesListViewController { func loadDefaultNodes(showAlert: Bool) { - nodesSource.setDefaultNodes() + nodesStorage.resetNodes(group: nodeGroup) if showAlert { dialogService.showSuccess(withMessage: String.adamant.nodesList.defaultNodesWasLoaded) @@ -346,9 +331,9 @@ extension NodesListViewController { // MARK: - Tools extension NodesListViewController { - private func createRowFor(node: Node, tag: String) -> BaseRow { + private func createRowFor(nodeId: UUID, tag: String) -> BaseRow { let row = NodeRow { - $0.value = makeNodeCellModel(node: node) + $0.cell.subscribe(makeNodeCellPublisher(nodeId: nodeId)) $0.tag = tag let deleteAction = SwipeAction( @@ -358,7 +343,7 @@ extension NodesListViewController { defer { completionHandler?(true) } guard let model = row.baseValue as? NodeCell.Model else { return } - self?.removeNode(node: model.node) + self?.removeNode(nodeId: model.id) } $0.trailingSwipe.actions = [deleteAction] @@ -369,11 +354,13 @@ extension NodesListViewController { } }.onCellSelection { [weak self] (_, row) in defer { row.deselect(animated: true) } - guard let node = row.value?.node else { - return - } - self?.editNode(node) + guard + let self = self, + let node = self.nodesList.first(where: { $0.id == row.value?.id }) + else { return } + + self.editNode(node) } return row @@ -406,35 +393,102 @@ extension NodesListViewController { } private func setHealthCheckTimer() { - timer = Timer.scheduledTimer( - withTimeInterval: regularHealthCheckTimeInteval, - repeats: true - ) { [weak nodesSource] _ in - nodesSource?.healthCheck() - } + timerSubsctiption = Timer + .publish(every: nodeGroup.crucialUpdateInterval, on: .main, in: .default) + .autoconnect() + .sink { [apiService] _ in apiService.healthCheck() } } private func makeNodeCellModel(node: Node) -> NodeCell.Model { - NodeCell.Model( - node: node, - nodeUpdate: { [weak nodesSource] in - nodesSource?.nodesUpdate() - }, - nodeActivity: { [weak self] node in - var activities = Set() - - if self?.currentRestNode === node { - activities.insert(.rest) - } - - if self?.currentSocketsNode === node { - activities.insert(.webSockets) - } - - return activities + let connectionStatus = node.isEnabled + ? node.connectionStatus + : .none + + return .init( + id: node.id, + title: node.asString(), + connectionStatus: connectionStatus, + statusString: node.statusString(connectionStatus), + versionString: node.versionString, + isEnabled: node.isEnabled, + activities: .init([ + currentRestNodesIds.contains(node.id) + ? .rest(scheme: node.scheme) + : nil, + currentSocketsNodeId == node.id + ? .webSockets + : nil + ].compactMap { $0 }), + nodeUpdateAction: .init(id: node.id.uuidString) { [nodesStorage] isEnabled in + nodesStorage.updateNodeParams(id: node.id, isEnabled: isEnabled) } ) } + + private func makeNodeCellPublisher(nodeId: UUID) -> some Observable { + $nodesList.combineLatest( + $currentSocketsNodeId, + $currentRestNodesIds + ).compactMap { [weak self] tuple in + let nodes = tuple.0 + + guard + let self = self, + let node = nodes.first(where: { $0.id == nodeId }) + else { return nil } + + return self.makeNodeCellModel(node: node) + } + } +} + +private extension Node { + func statusString(_ status: Node.ConnectionStatus?) -> String? { + switch status { + case .allowed: + let ping = ping.map { Int($0 * 1000) } + return ping.map { "\(NodeCell.Strings.ping): \($0) \(NodeCell.Strings.milliseconds)" } + case .synchronizing: + return NodeCell.Strings.synchronizing + case .offline: + return NodeCell.Strings.offline + case .none: + return nil + } + } + + var versionString: String? { + version.map { "(\(NodeCell.Strings.version): \($0))" } + } +} + +private extension NodeCell { + enum Strings { + static let ping = String.localized( + "NodesList.NodeCell.Ping", + comment: "NodesList.NodeCell: Node ping" + ) + + static let milliseconds = String.localized( + "NodesList.NodeCell.Milliseconds", + comment: "NodesList.NodeCell: Milliseconds" + ) + + static let synchronizing = String.localized( + "NodesList.NodeCell.Synchronizing", + comment: "NodesList.NodeCell: Node is synchronizing" + ) + + static let offline = String.localized( + "NodesList.NodeCell.Offline", + comment: "NodesList.NodeCell: Node is offline" + ) + + static let version = String.localized( + "NodesList.NodeCell.Version", + comment: "NodesList.NodeCell: Node version" + ) + } } -private let regularHealthCheckTimeInteval: TimeInterval = 10 +private let nodeGroup: NodeGroup = .adm diff --git a/Adamant/Modules/Settings/Contribute/ContributeState.swift b/Adamant/Modules/Settings/Contribute/ContributeState.swift index 6599182e8..cc78b0eea 100644 --- a/Adamant/Modules/Settings/Contribute/ContributeState.swift +++ b/Adamant/Modules/Settings/Contribute/ContributeState.swift @@ -12,7 +12,7 @@ import CommonKit struct ContributeState { var isCrashlyticsOn: Bool var isCrashButtonOn: Bool - var safariURL: IdentifiableContainer? + var safariURL: IDWrapper? let name: String let crashliticsRowImage: UIImage @@ -48,6 +48,12 @@ struct ContributeState { description: .localized("Contribute.Section.CodeContributeDescription", comment: .empty), link: URL(string: "https://github.com/Adamant-im") ), + .init( + image: .asset(named: "row_buy-coins") ?? .init(), + name: .localized("Contribute.Section.Donate", comment: .empty), + description: .localized("Contribute.Section.DonateDescription", comment: .empty), + link: URL(string: "https://adamant.im/donate") + ), .init( image: .asset(named: "row_rate") ?? .init(), name: .localized("Contribute.Section.Rate", comment: .empty), diff --git a/Adamant/Modules/Settings/Contribute/ContributeViewModel.swift b/Adamant/Modules/Settings/Contribute/ContributeViewModel.swift index b3011378b..ec6be779d 100644 --- a/Adamant/Modules/Settings/Contribute/ContributeViewModel.swift +++ b/Adamant/Modules/Settings/Contribute/ContributeViewModel.swift @@ -29,7 +29,7 @@ final class ContributeViewModel: ObservableObject { } func openLink(row: ContributeState.LinkRow) { - state.safariURL = row.link.map { .init(value: $0) } + state.safariURL = row.link.map { .init(id: $0.absoluteString, value: $0) } } func simulateCrash() { diff --git a/Adamant/Modules/Wallets/Adamant/AdmTransactionsViewController.swift b/Adamant/Modules/Wallets/Adamant/AdmTransactionsViewController.swift index 6229d9107..94f7db057 100644 --- a/Adamant/Modules/Wallets/Adamant/AdmTransactionsViewController.swift +++ b/Adamant/Modules/Wallets/Adamant/AdmTransactionsViewController.swift @@ -175,20 +175,18 @@ final class AdmTransactionsViewController: TransactionsListViewControllerBase { } private func markTransfersAsRead() { - DispatchQueue.global(qos: .utility).async { - let privateContext = NSManagedObjectContext(concurrencyType: .privateQueueConcurrencyType) - privateContext.parent = self.stack.container.viewContext - - let request = NSFetchRequest(entityName: TransferTransaction.entityName) - request.predicate = NSPredicate(format: "isUnread == true") - request.sortDescriptors = [NSSortDescriptor(key: "transactionId", ascending: false)] + let privateContext = NSManagedObjectContext(concurrencyType: .privateQueueConcurrencyType) + privateContext.parent = self.stack.container.viewContext + + let request = NSFetchRequest(entityName: TransferTransaction.entityName) + request.predicate = NSPredicate(format: "isUnread == true") + request.sortDescriptors = [NSSortDescriptor(key: "transactionId", ascending: false)] + + if let result = try? privateContext.fetch(request) { + result.forEach { $0.isUnread = false } - if let result = try? privateContext.fetch(request) { - result.forEach { $0.isUnread = false } - - if privateContext.hasChanges { - try? privateContext.save() - } + if privateContext.hasChanges { + try? privateContext.save() } } } diff --git a/Adamant/Modules/Wallets/Adamant/AdmWalletService.swift b/Adamant/Modules/Wallets/Adamant/AdmWalletService.swift index aff6f7373..af00dbe22 100644 --- a/Adamant/Modules/Wallets/Adamant/AdmWalletService.swift +++ b/Adamant/Modules/Wallets/Adamant/AdmWalletService.swift @@ -71,12 +71,12 @@ final class AdmWalletService: NSObject, WalletService { let enabled: Bool = true private var transfersController: NSFetchedResultsController? - private (set) var isWarningGasPrice = false - private var subscriptions = Set() + @Atomic private(set) var isWarningGasPrice = false + @Atomic private var subscriptions = Set() // MARK: - State - private (set) var state: WalletServiceState = .upToDate - private (set) var wallet: WalletAccount? + @Atomic private(set) var state: WalletServiceState = .upToDate + @Atomic private(set) var wallet: WalletAccount? // MARK: - Logic override init() { @@ -143,7 +143,7 @@ final class AdmWalletService: NSObject, WalletService { // MARK: - Tools func getBalance(address: String) async throws -> Decimal { - let account = try await apiService.getAccount(byAddress: address) + let account = try await apiService.getAccount(byAddress: address).get() return account.balance } diff --git a/Adamant/Modules/Wallets/Bitcoin/BtcApiService.swift b/Adamant/Modules/Wallets/Bitcoin/BtcApiService.swift new file mode 100644 index 000000000..bf781fc71 --- /dev/null +++ b/Adamant/Modules/Wallets/Bitcoin/BtcApiService.swift @@ -0,0 +1,72 @@ +// +// BtcApiService.swift +// Adamant +// +// Created by Andrew G on 12.11.2023. +// Copyright © 2023 Adamant. All rights reserved. +// + +import CommonKit +import Foundation + +final class BtcApiCore: BlockchainHealthCheckableService { + let apiCore: APICoreProtocol + + init(apiCore: APICoreProtocol) { + self.apiCore = apiCore + } + + func request( + node: Node, + _ request: @Sendable @escaping (APICoreProtocol, Node) async -> ApiServiceResult + ) async -> WalletServiceResult { + await request(apiCore, node).mapError { $0.asWalletServiceError() } + } + + func getStatusInfo(node: Node) async -> WalletServiceResult { + let startTimestamp = Date.now.timeIntervalSince1970 + + let response = await request(node: node) { core, node in + await core.sendRequest(node: node, path: BtcApiCommands.getHeight()) + } + + return response.flatMap { data in + guard + let raw = String(data: data, encoding: .utf8), + let height = Int(string: raw) + else { + return .failure(.internalError(.parsingFailed)) + } + + return .success(.init( + ping: Date.now.timeIntervalSince1970 - startTimestamp, + height: height, + wsEnabled: false, + wsPort: nil, + version: nil + )) + } + } +} + +final class BtcApiService { + let api: BlockchainHealthCheckWrapper + + init(api: BlockchainHealthCheckWrapper) { + self.api = api + } + + func request( + _ request: @Sendable @escaping (APICoreProtocol, Node) async -> ApiServiceResult + ) async -> WalletServiceResult { + await api.request { core, node in + await core.request(node: node, request) + } + } + + func getStatusInfo() async -> WalletServiceResult { + await api.request { core, node in + await core.getStatusInfo(node: node) + } + } +} diff --git a/Adamant/Modules/Wallets/Bitcoin/BtcWalletService+Send.swift b/Adamant/Modules/Wallets/Bitcoin/BtcWalletService+Send.swift index f7ea59a4b..e9b30cfe0 100644 --- a/Adamant/Modules/Wallets/Bitcoin/BtcWalletService+Send.swift +++ b/Adamant/Modules/Wallets/Bitcoin/BtcWalletService+Send.swift @@ -55,126 +55,73 @@ extension BtcWalletService: WalletServiceTwoStepSend { } func sendTransaction(_ transaction: BitcoinKit.Transaction) async throws { - guard let url = BtcWalletService.nodes.randomElement()?.asURL() else { - throw WalletServiceError.internalError( - message: "Failed to get BTC endpoint URL", - error: nil - ) - } - - // Request url - let endpoint = url.appendingPathComponent(BtcApiCommands.sendTransaction()) - // MARK: Prepare params let txHex = transaction.serialized().hex // MARK: Sending request - - let responseData = try await apiService.sendRequest( - url: endpoint, - method: .post, - parameters: nil, - encoding: BodyStringEncoding(body: txHex) - ) - + let responseData = try await btcApiService.request { core, node in + await core.sendRequest( + node: node, + path: BtcApiCommands.sendTransaction(), + method: .post, + parameters: [String.empty: txHex], + encoding: .bodyString + ) + }.get() + let response = String(decoding: responseData, as: UTF8.self) guard response != transaction.txId else { return } throw WalletServiceError.remoteServiceError(message: response) } func getUnspentTransactions() async throws -> [UnspentTransaction] { - guard let url = BtcWalletService.nodes.randomElement()?.asURL() else { - fatalError("Failed to get BTC endpoint URL") - } - guard let wallet = self.btcWallet else { throw WalletServiceError.notLogged } let address = wallet.address + let parameters = ["noCache": "1"] + + let responseData = try await btcApiService.request { core, node in + await core.sendRequest( + node: node, + path: BtcApiCommands.getUnspentTransactions(for: address), + method: .get, + parameters: parameters, + encoding: .url + ) + }.get() - // Headers - let headers: HTTPHeaders = [ - "Content-Type": "application/json" - ] - - // Request url - let endpoint = url.appendingPathComponent(BtcApiCommands.getUnspentTransactions(for: address)) - - let parameters: Parameters = [ - "noCache": "1" - ] + guard + let items = try? Self.jsonDecoder.decode( + [BtcUnspentTransactionResponse].self, + from: responseData + ) + else { + throw WalletServiceError.internalError(message: "BTC Wallet: not valid response", error: nil) + } - // MARK: Sending request - return try await withUnsafeThrowingContinuation { (continuation: UnsafeContinuation<[UnspentTransaction], Error>) in - AF.request(endpoint, method: .get, parameters: parameters, headers: headers).responseData(queue: defaultDispatchQueue) { response in - switch response.result { - case .success(let data): - guard - let items = try? Self.jsonDecoder.decode([BtcUnspentTransactionResponse].self, - from: data) - else { - continuation.resume(throwing: WalletServiceError.internalError(message: "BTC Wallet: not valid response", error: nil)) - break - } - - var utxos = [UnspentTransaction]() - for item in items { - guard item.status.confirmed else { - continue - } - - let value = NSDecimalNumber(decimal: item.value).uint64Value - - let lockScript = wallet.addressEntity.lockingScript - let txHash = Data(hex: item.txId).map { Data($0.reversed()) } ?? Data() - let txIndex = item.vout - - let unspentOutput = TransactionOutput(value: value, lockingScript: lockScript) - let unspentOutpoint = TransactionOutPoint(hash: txHash, index: txIndex) - let utxo = UnspentTransaction(output: unspentOutput, outpoint: unspentOutpoint) - - utxos.append(utxo) - } - continuation.resume(returning: utxos) - return - case .failure: - continuation.resume(throwing: WalletServiceError.internalError(message: "BTC Wallet: server not response", error: nil)) - return - } + var utxos = [UnspentTransaction]() + for item in items { + guard item.status.confirmed else { + continue } + + let value = NSDecimalNumber(decimal: item.value).uint64Value + + let lockScript = wallet.addressEntity.lockingScript + let txHash = Data(hex: item.txId).map { Data($0.reversed()) } ?? Data() + let txIndex = item.vout + + let unspentOutput = TransactionOutput(value: value, lockingScript: lockScript) + let unspentOutpoint = TransactionOutPoint(hash: txHash, index: txIndex) + let utxo = UnspentTransaction(output: unspentOutput, outpoint: unspentOutpoint) + + utxos.append(utxo) } + + return utxos } } - -struct BodyStringEncoding: ParameterEncoding { - - private let body: String - - init(body: String) { self.body = body } - - func encode(_ urlRequest: URLRequestConvertible, with parameters: Parameters?) throws -> URLRequest { - guard var urlRequest = urlRequest.urlRequest else { throw Errors.emptyURLRequest } - guard let data = body.data(using: .utf8) else { throw Errors.encodingProblem } - urlRequest.httpBody = data - return urlRequest - } -} - -extension BodyStringEncoding { - enum Errors: Error { - case emptyURLRequest - case encodingProblem - } -} - -extension BodyStringEncoding.Errors: LocalizedError { - var errorDescription: String? { - switch self { - case .emptyURLRequest: return "Empty url request" - case .encodingProblem: return "Encoding problem" - } - } -} diff --git a/Adamant/Modules/Wallets/Bitcoin/BtcWalletService.swift b/Adamant/Modules/Wallets/Bitcoin/BtcWalletService.swift index 3aaadbe81..31cae33a2 100644 --- a/Adamant/Modules/Wallets/Bitcoin/BtcWalletService.swift +++ b/Adamant/Modules/Wallets/Bitcoin/BtcWalletService.swift @@ -106,20 +106,20 @@ final class BtcWalletService: WalletService { // MARK: - Dependencies var apiService: ApiService! + var btcApiService: BtcApiService! var accountService: AccountService! var dialogService: DialogService! var increaseFeeService: IncreaseFeeService! var addressConverter: AddressConverter! // MARK: - Constants - static var currencyLogo = UIImage.asset(named: "bitcoin_wallet") ?? .init() - + static let currencyLogo = UIImage.asset(named: "bitcoin_wallet") ?? .init() static let multiplier = Decimal(sign: .plus, exponent: 8, significand: 1) - private (set) var currentHeight: Decimal? - private var feeRate: Decimal = 1 - private (set) var transactionFee: Decimal = DefaultBtcTransferFee.medium.rawValue / multiplier - private (set) var isWarningGasPrice = false + @Atomic private(set) var currentHeight: Decimal? + @Atomic private var feeRate: Decimal = 1 + @Atomic private(set) var transactionFee: Decimal = DefaultBtcTransferFee.medium.rawValue / multiplier + @Atomic private(set) var isWarningGasPrice = false static let kvsAddress = "btc:address" private let walletPath = "m/44'/0'/21'/0/0" @@ -131,25 +131,26 @@ final class BtcWalletService: WalletService { let transactionFeeUpdated = Notification.Name("adamant.btcWallet.feeUpdated") // MARK: - Delayed KVS save - private var balanceObserver: NSObjectProtocol? + @Atomic private var balanceObserver: NSObjectProtocol? // MARK: - Properties - private (set) var btcWallet: BtcWallet? - - private (set) var enabled = true - - public var network: Network - - private var initialBalanceCheck = false + @Atomic private(set) var btcWallet: BtcWallet? + @Atomic private(set) var enabled = true + @Atomic public var network: Network + @Atomic private var initialBalanceCheck = false static let jsonDecoder = JSONDecoder() - let defaultDispatchQueue = DispatchQueue(label: "im.adamant.btcWalletService", qos: .userInteractive, attributes: [.concurrent]) + let defaultDispatchQueue = DispatchQueue( + label: "im.adamant.btcWalletService", + qos: .userInteractive, + attributes: [.concurrent] + ) - private var subscriptions = Set() + @Atomic private var subscriptions = Set() // MARK: - State - private (set) var state: WalletServiceState = .notInitiated + @Atomic private(set) var state: WalletServiceState = .notInitiated private func setState(_ newState: WalletServiceState, silent: Bool = false) { guard newState != state else { @@ -321,7 +322,7 @@ final class BtcWalletService: WalletService { func getWalletAddress(byAdamantAddress address: String) async throws -> String { do { - let result = try await apiService.get(key: BtcWalletService.kvsAddress, sender: address) + let result = try await apiService.get(key: BtcWalletService.kvsAddress, sender: address).get() guard let result = result else { throw WalletServiceError.walletNotInitiated @@ -437,6 +438,7 @@ extension BtcWalletService: SwinjectDependentService { dialogService = container.resolve(DialogService.self) increaseFeeService = container.resolve(IncreaseFeeService.self) addressConverter = container.resolve(AddressConverterFactory.self)?.make(network: network) + btcApiService = container.resolve(BtcApiService.self) } } @@ -451,74 +453,23 @@ extension BtcWalletService { } func getBalance(address: String) async throws -> Decimal { - guard let url = BtcWalletService.nodes.randomElement()?.asURL() else { - let message = "Failed to get BTC endpoint URL" - assertionFailure(message) - throw WalletServiceError.internalError(message: message, error: nil) - } - - // Request url - let endpoint = url.appendingPathComponent(BtcApiCommands.balance(for: address)) - - // MARK: Sending request - - let response: BtcBalanceResponse = try await apiService.sendRequest( - url: endpoint, - method: .get, - parameters: nil - ) - - let balance = response.value / BtcWalletService.multiplier - - return balance + let response: BtcBalanceResponse = try await btcApiService.request { api, node in + await api.sendRequestJson(node: node, path: BtcApiCommands.balance(for: address)) + }.get() + + return response.value / BtcWalletService.multiplier } func getFeeRate() async throws -> Decimal { - guard let url = BtcWalletService.nodes.randomElement()?.asURL() else { - fatalError("Failed to get BTC endpoint URL") - } - - // Request url - let endpoint = url.appendingPathComponent(BtcApiCommands.getFeeRate()) + let response: [String: Decimal] = try await btcApiService.request { api, node in + await api.sendRequestJson(node: node, path: BtcApiCommands.getFeeRate()) + }.get() - // MARK: Sending request - - let response: [String: Decimal] = try await apiService.sendRequest( - url: endpoint, - method: .get, - parameters: nil - ) - - let value = response["2"] ?? 1 - - return value + return response["2"] ?? 1 } func getCurrentHeight() async throws -> Decimal { - guard let url = BtcWalletService.nodes.randomElement()?.asURL() else { - fatalError("Failed to get BTC endpoint URL") - } - - // Request url - let endpoint = url.appendingPathComponent(BtcApiCommands.getHeight()) - - // MARK: Sending request - let data = try await apiService.sendRequest( - url: endpoint, - method: .get, - parameters: nil - ) - - guard - let raw = String(data: data, encoding: .utf8), - let value = Decimal(string: raw) - else { - throw WalletServiceError.remoteServiceError( - message: "BTC Wallet: not a valid response" - ) - } - - return value + try await .init(btcApiService.getStatusInfo().get().height) } } @@ -541,14 +492,20 @@ extension BtcWalletService { } Task { - await apiService.store(key: BtcWalletService.kvsAddress, value: btcAddress, type: .keyValue, sender: adamant.address, keypair: keypair) { result in - switch result { - case .success: - completion(.success) - - case .failure(let error): - completion(.failure(error: .apiError(error))) - } + let result = await apiService.store( + key: BtcWalletService.kvsAddress, + value: btcAddress, + type: .keyValue, + sender: adamant.address, + keypair: keypair + ) + + switch result { + case .success: + completion(.success) + + case .failure(let error): + completion(.failure(error: .apiError(error))) } } } @@ -639,26 +596,19 @@ extension BtcWalletService { return transactions } - private func getTransactions(for address: String, fromTx: String? = nil) async throws -> [RawBtcTransactionResponse] { - guard let url = BtcWalletService.nodes.randomElement()?.asURL() else { - fatalError("Failed to get BTC endpoint URL") - } - - // Request url - let endpoint = url.appendingPathComponent(BtcApiCommands.getTransactions( - for: address, - fromTx: fromTx - )) - - // MARK: Sending request - - let transactions: [RawBtcTransactionResponse] = try await apiService.sendRequest( - url: endpoint, - method: .get, - parameters: nil - ) - - return transactions + private func getTransactions( + for address: String, + fromTx: String? = nil + ) async throws -> [RawBtcTransactionResponse] { + return try await btcApiService.request { api, node in + await api.sendRequestJson( + node: node, + path: BtcApiCommands.getTransactions( + for: address, + fromTx: fromTx + ) + ) + }.get() } func getTransaction(by hash: String) async throws -> BtcTransaction { @@ -666,34 +616,19 @@ extension BtcWalletService { throw WalletServiceError.notLogged } - guard let url = BtcWalletService.nodes.randomElement()?.asURL() else { - fatalError("Failed to get BTC endpoint URL") - } - - // Request url - let endpoint = url.appendingPathComponent(BtcApiCommands.getTransaction(by: hash)) - - // MARK: Sending request - - do { - let rawTransaction: RawBtcTransactionResponse = try await apiService.sendRequest( - url: endpoint, - method: .get, - parameters: nil + let rawTransaction: RawBtcTransactionResponse = try await btcApiService.request { api, node in + await api.sendRequestJson( + node: node, + path: BtcApiCommands.getTransaction(by: hash) ) - - let transaction = rawTransaction.asBtcTransaction( - BtcTransaction.self, - for: address, - height: self.currentHeight - ) - - return transaction - } catch let error as ApiServiceError { - throw WalletServiceError.remoteServiceError(message: error.message) - } + }.get() + + return rawTransaction.asBtcTransaction( + BtcTransaction.self, + for: address, + height: self.currentHeight + ) } - } // MARK: - PrivateKey generator diff --git a/Adamant/Modules/Wallets/Bitcoin/Models/BtcBalanceResponse.swift b/Adamant/Modules/Wallets/Bitcoin/DTO/BtcBalanceResponse.swift similarity index 100% rename from Adamant/Modules/Wallets/Bitcoin/Models/BtcBalanceResponse.swift rename to Adamant/Modules/Wallets/Bitcoin/DTO/BtcBalanceResponse.swift diff --git a/Adamant/Modules/Wallets/Bitcoin/Models/BtcTransactionResponse.swift b/Adamant/Modules/Wallets/Bitcoin/DTO/BtcTransactionResponse.swift similarity index 100% rename from Adamant/Modules/Wallets/Bitcoin/Models/BtcTransactionResponse.swift rename to Adamant/Modules/Wallets/Bitcoin/DTO/BtcTransactionResponse.swift diff --git a/Adamant/Modules/Wallets/Bitcoin/Models/BtcUnspentTransactionResponse.swift b/Adamant/Modules/Wallets/Bitcoin/DTO/BtcUnspentTransactionResponse.swift similarity index 100% rename from Adamant/Modules/Wallets/Bitcoin/Models/BtcUnspentTransactionResponse.swift rename to Adamant/Modules/Wallets/Bitcoin/DTO/BtcUnspentTransactionResponse.swift diff --git a/Adamant/Modules/Wallets/Dash/DTO/DashBlockchainInfoDTO.swift b/Adamant/Modules/Wallets/Dash/DTO/DashBlockchainInfoDTO.swift new file mode 100644 index 000000000..3811ec414 --- /dev/null +++ b/Adamant/Modules/Wallets/Dash/DTO/DashBlockchainInfoDTO.swift @@ -0,0 +1,14 @@ +// +// DashBlockchainInfoDTO.swift +// Adamant +// +// Created by Andrew G on 17.11.2023. +// Copyright © 2023 Adamant. All rights reserved. +// + +import Foundation + +struct DashBlockchainInfoDTO: Codable { + let chain: String + let blocks: Int +} diff --git a/Adamant/Modules/Wallets/Dash/DTO/DashErrorDTO.swift b/Adamant/Modules/Wallets/Dash/DTO/DashErrorDTO.swift new file mode 100644 index 000000000..b2ed0e85c --- /dev/null +++ b/Adamant/Modules/Wallets/Dash/DTO/DashErrorDTO.swift @@ -0,0 +1,18 @@ +// +// DashErrorDTO.swift +// Adamant +// +// Created by Andrew G on 17.11.2023. +// Copyright © 2023 Adamant. All rights reserved. +// + +import Foundation + +struct DashErrorDTO: Codable, LocalizedError { + let code: Int + let message: String + + var errorDescription: String? { + message + } +} diff --git a/Adamant/Modules/Wallets/Dash/DTO/DashGetAddressBalanceDTO.swift b/Adamant/Modules/Wallets/Dash/DTO/DashGetAddressBalanceDTO.swift new file mode 100644 index 000000000..4be8440c5 --- /dev/null +++ b/Adamant/Modules/Wallets/Dash/DTO/DashGetAddressBalanceDTO.swift @@ -0,0 +1,19 @@ +// +// DashGetAddressBalanceDTO.swift +// Adamant +// +// Created by Andrew G on 17.11.2023. +// Copyright © 2023 Adamant. All rights reserved. +// + +import Foundation + +struct DashGetAddressBalanceDTO: Codable { + let method: String + let params: [String] + + init(address: String) { + self.method = "getaddressbalance" + self.params = [address] + } +} diff --git a/Adamant/Modules/Wallets/Dash/DTO/DashGetAddressTransactionIds.swift b/Adamant/Modules/Wallets/Dash/DTO/DashGetAddressTransactionIds.swift new file mode 100644 index 000000000..304c48837 --- /dev/null +++ b/Adamant/Modules/Wallets/Dash/DTO/DashGetAddressTransactionIds.swift @@ -0,0 +1,19 @@ +// +// DashGetAddressTransactionIds.swift +// Adamant +// +// Created by Andrew G on 17.11.2023. +// Copyright © 2023 Adamant. All rights reserved. +// + +import Foundation + +struct DashGetAddressTransactionIds: Codable { + let method: String + let params: [String] + + init(address: String) { + self.method = "getaddresstxids" + self.params = [address] + } +} diff --git a/Adamant/Modules/Wallets/Dash/DTO/DashGetBlockDTO.swift b/Adamant/Modules/Wallets/Dash/DTO/DashGetBlockDTO.swift new file mode 100644 index 000000000..a5855c58a --- /dev/null +++ b/Adamant/Modules/Wallets/Dash/DTO/DashGetBlockDTO.swift @@ -0,0 +1,19 @@ +// +// DashGetBlockDTO.swift +// Adamant +// +// Created by Andrew G on 17.11.2023. +// Copyright © 2023 Adamant. All rights reserved. +// + +import Foundation + +struct DashGetBlockDTO: Codable { + let method: String + let params: [String] + + init(hash: String) { + self.method = "getblock" + self.params = [hash] + } +} diff --git a/Adamant/Modules/Wallets/Dash/DTO/DashGetRawTransactionDTO.swift b/Adamant/Modules/Wallets/Dash/DTO/DashGetRawTransactionDTO.swift new file mode 100644 index 000000000..94048f8e6 --- /dev/null +++ b/Adamant/Modules/Wallets/Dash/DTO/DashGetRawTransactionDTO.swift @@ -0,0 +1,26 @@ +// +// DashGetRawTransactionDTO.swift +// Adamant +// +// Created by Andrew G on 17.11.2023. +// Copyright © 2023 Adamant. All rights reserved. +// + +import Foundation + +struct DashGetRawTransactionDTO: Codable { + let method: String + let params: [Parameter] + + init(hash: String) { + self.method = "getrawtransaction" + self.params = [.hash(hash), .bool(true)] + } +} + +extension DashGetRawTransactionDTO { + enum Parameter: Codable { + case hash(String) + case bool(Bool) + } +} diff --git a/Adamant/Modules/Wallets/Dash/DTO/DashGetUnspentTransactionsDTO.swift b/Adamant/Modules/Wallets/Dash/DTO/DashGetUnspentTransactionsDTO.swift new file mode 100644 index 000000000..e00e09cf1 --- /dev/null +++ b/Adamant/Modules/Wallets/Dash/DTO/DashGetUnspentTransactionsDTO.swift @@ -0,0 +1,19 @@ +// +// DashGetUnspentTransactionsDTO.swift +// Adamant +// +// Created by Andrew G on 17.11.2023. +// Copyright © 2023 Adamant. All rights reserved. +// + +import Foundation + +struct DashGetUnspentTransactionDTO: Codable { + let method: String + let params: [String] + + init(address: String) { + self.method = "getaddressutxos" + self.params = [address] + } +} diff --git a/Adamant/Modules/Wallets/Dash/DTO/DashResponseDTO.swift b/Adamant/Modules/Wallets/Dash/DTO/DashResponseDTO.swift new file mode 100644 index 000000000..4d04e5da1 --- /dev/null +++ b/Adamant/Modules/Wallets/Dash/DTO/DashResponseDTO.swift @@ -0,0 +1,14 @@ +// +// DashResponseDTO.swift +// Adamant +// +// Created by Andrew G on 17.11.2023. +// Copyright © 2023 Adamant. All rights reserved. +// + +import Foundation + +struct DashResponseDTO: Codable { + let result: T? + let error: DashErrorDTO? +} diff --git a/Adamant/Modules/Wallets/Dash/DTO/DashSendRawTransactionDTO.swift b/Adamant/Modules/Wallets/Dash/DTO/DashSendRawTransactionDTO.swift new file mode 100644 index 000000000..cc80e2970 --- /dev/null +++ b/Adamant/Modules/Wallets/Dash/DTO/DashSendRawTransactionDTO.swift @@ -0,0 +1,19 @@ +// +// DashSendRawTransactionDTO.swift +// Adamant +// +// Created by Andrew G on 17.11.2023. +// Copyright © 2023 Adamant. All rights reserved. +// + +import Foundation + +struct DashSendRawTransactionDTO: Codable { + let method: String + let params: [String] + + init(txHex: String) { + self.method = "sendrawtransaction" + self.params = [txHex] + } +} diff --git a/Adamant/Modules/Wallets/Dash/DashApiService.swift b/Adamant/Modules/Wallets/Dash/DashApiService.swift new file mode 100644 index 000000000..0668e53fa --- /dev/null +++ b/Adamant/Modules/Wallets/Dash/DashApiService.swift @@ -0,0 +1,79 @@ +// +// DashApiService.swift +// Adamant +// +// Created by Andrew G on 17.11.2023. +// Copyright © 2023 Adamant. All rights reserved. +// + +import CommonKit +import Foundation + +final class DashApiCore: BlockchainHealthCheckableService { + let apiCore: APICoreProtocol + + init(apiCore: APICoreProtocol) { + self.apiCore = apiCore + } + + func request( + node: Node, + _ request: @Sendable @escaping (APICoreProtocol, Node) async -> ApiServiceResult + ) async -> WalletServiceResult { + await request(apiCore, node).mapError { $0.asWalletServiceError() } + } + + func getStatusInfo(node: Node) async -> WalletServiceResult { + let startTimestamp = Date.now.timeIntervalSince1970 + + let response: WalletServiceResult = await request(node: node) { core, node in + let response: ApiServiceResult> = await core.sendRequestJson( + node: node, + path: .empty, + method: .post, + parameters: ["method": "getblockchaininfo"], + encoding: .json + ) + + return response.flatMap { dto in + if let result = dto.result, dto.error == nil { + return .success(result) + } else { + return .failure(.serverError(error: dto.error?.localizedDescription ?? .empty)) + } + } + } + + return response.map { data in + return .init( + ping: Date.now.timeIntervalSince1970 - startTimestamp, + height: data.blocks, + wsEnabled: false, + wsPort: nil, + version: nil + ) + } + } +} + +final class DashApiService { + let api: BlockchainHealthCheckWrapper + + init(api: BlockchainHealthCheckWrapper) { + self.api = api + } + + func request( + _ request: @Sendable @escaping (APICoreProtocol, Node) async -> ApiServiceResult + ) async -> WalletServiceResult { + await api.request { core, node in + await core.request(node: node, request) + } + } + + func getStatusInfo() async -> WalletServiceResult { + await api.request { core, node in + await core.getStatusInfo(node: node) + } + } +} diff --git a/Adamant/Modules/Wallets/Dash/DashWalletService+Send.swift b/Adamant/Modules/Wallets/Dash/DashWalletService+Send.swift index 9f921231f..243923b3f 100644 --- a/Adamant/Modules/Wallets/Dash/DashWalletService+Send.swift +++ b/Adamant/Modules/Wallets/Dash/DashWalletService+Send.swift @@ -68,48 +68,31 @@ extension DashWalletService: WalletServiceTwoStepSend { } func sendTransaction(_ transaction: BitcoinKit.Transaction) async throws { - guard let endpoint = DashWalletService.nodes.randomElement()?.asURL() else { - throw WalletServiceError.internalError( - message: "Failed to get DASH endpoint URL", - error: nil - ) - } - let txHex = transaction.serialized().hex - let parameters: Parameters = [ - "method": "sendrawtransaction", - "params": [ - txHex - ] - ] - - // MARK: Sending request - - do { - let response: BTCRPCServerResponce = try await apiService.sendRequest( - url: endpoint, + let response: BTCRPCServerResponce = try await dashApiService.request { core, node in + await core.sendRequestJson( + node: node, + path: .empty, method: .post, - parameters: parameters, - encoding: JSONEncoding.default + parameters: DashSendRawTransactionDTO(txHex: txHex), + encoding: .json ) - - if response.result != nil { - lastTransactionId = transaction.txID - } else if let error = response.error?.message { - if error.lowercased().contains("16: tx-txlock-conflict") { - throw WalletServiceError.internalError( - message: String.adamant.sharedErrors.walletFrezzed, - error: nil - ) - } else { - throw WalletServiceError.internalError(message: error, error: nil) - } + }.get() + + if response.result != nil { + lastTransactionId = transaction.txID + } else if let error = response.error?.message { + if error.lowercased().contains("16: tx-txlock-conflict") { + throw WalletServiceError.internalError( + message: String.adamant.sharedErrors.walletFrezzed, + error: nil + ) } else { - throw WalletServiceError.internalError(message: "DASH Wallet: not valid response", error: nil) + throw WalletServiceError.internalError(message: error, error: nil) } - } catch { - throw WalletServiceError.remoteServiceError(message: error.localizedDescription) + } else { + throw WalletServiceError.internalError(message: "DASH Wallet: not valid response", error: nil) } } } diff --git a/Adamant/Modules/Wallets/Dash/DashWalletService+Transactions.swift b/Adamant/Modules/Wallets/Dash/DashWalletService+Transactions.swift index 302ceac25..b68b5df98 100644 --- a/Adamant/Modules/Wallets/Dash/DashWalletService+Transactions.swift +++ b/Adamant/Modules/Wallets/Dash/DashWalletService+Transactions.swift @@ -36,30 +36,21 @@ extension DashWalletService { } func getTransaction(by hash: String) async throws -> BTCRawTransaction { - guard let endpoint = DashWalletService.nodes.randomElement()?.asURL() else { - fatalError("Failed to get DASH endpoint URL") - } - - let parameters: Parameters = [ - "method": "getrawtransaction", - "params": [ - hash, true - ] - ] - - // MARK: Sending request - - let result: BTCRPCServerResponce = try await apiService.sendRequest( - url: endpoint, - method: .post, - parameters: parameters, - encoding: JSONEncoding.default - ) - + let result: BTCRPCServerResponce = try await dashApiService.request { + core, node in + await core.sendRequestJson( + node: node, + path: .empty, + method: .post, + parameters: DashGetRawTransactionDTO(hash: hash), + encoding: .json + ) + }.get() + if let transaction = result.result { return transaction } else { - throw ApiServiceError.internalError(message: "Unaviable transaction", error: nil) + throw ApiServiceError.serverError(error: "Unaviable transaction") } } @@ -67,47 +58,21 @@ extension DashWalletService { guard let address = wallet?.address else { throw ApiServiceError.notLogged } - - guard let endpoint = DashWalletService.nodes.randomElement()?.asURL() else { - throw ApiServiceError.internalError(message: "Failed to get DASH endpoint URL", error: nil) - } - - var parameters: [Parameters] = [] - - hashes.forEach { hash in - let params: Parameters = [ - "method": "getrawtransaction", - "params": [ - hash, - true - ] - ] - - parameters.append(params) - } - // MARK: Sending request - - guard let dataParameters = try? JSONSerialization.data(withJSONObject: parameters) else { - throw ApiServiceError.internalError(message: "Failed to create request", error: nil) - } - - var request = URLRequest(url: endpoint) - request.httpMethod = "POST" - request.httpBody = dataParameters + let parameters: [DashGetRawTransactionDTO] = hashes.map { .init(hash: $0) } - let data = try await apiService.sendRequest(request: AF.request(request)) - - do { - let model = try JSONDecoder().decode( - [BTCRPCServerResponce].self, - from: data + let result: [BTCRPCServerResponce] = try await dashApiService.request { + core, node in + await core.sendRequestJson( + node: node, + path: .empty, + method: .post, + parameters: parameters, + encoding: .json ) - - return model.compactMap { $0.result?.asBtcTransaction(DashTransaction.self, for: address) } - } catch { - throw ApiServiceError.internalError(message: error.localizedDescription, error: error) - } + }.get() + + return result.compactMap { $0.result?.asBtcTransaction(DashTransaction.self, for: address) } } func getBlockId(by hash: String?) async throws -> String { @@ -115,25 +80,15 @@ extension DashWalletService { throw ApiServiceError.internalError(message: "Hash is empty", error: nil) } - guard let endpoint = DashWalletService.nodes.randomElement()?.asURL() else { - fatalError("Failed to get DASH endpoint URL") - } - - let parameters: Parameters = [ - "method": "getblock", - "params": [ - hash - ] - ] - - // MARK: Sending request - - let result: BTCRPCServerResponce = try await apiService.sendRequest( - url: endpoint, - method: .post, - parameters: parameters, - encoding: JSONEncoding.default - ) + let result: BTCRPCServerResponce = try await dashApiService.request { core, node in + await core.sendRequestJson( + node: node, + path: .empty, + method: .post, + parameters: DashGetBlockDTO(hash: hash), + encoding: .json + ) + }.get() if let block = result.result { return String(block.height) @@ -143,30 +98,20 @@ extension DashWalletService { } func getUnspentTransactions() async throws -> [UnspentTransaction] { - guard let endpoint = DashWalletService.nodes.randomElement()?.asURL() else { - fatalError("Failed to get DASH endpoint URL") - } - - guard let wallet = self.dashWallet else { + guard let wallet = dashWallet else { throw WalletServiceError.internalError(message: "DASH Wallet not found", error: nil) } - let parameters: Parameters = [ - "method": "getaddressutxos", - "params": [ - wallet.address - ] - ] - - // MARK: Sending request - - let response: BTCRPCServerResponce<[DashUnspentTransaction]> = - try await apiService.sendRequest( - url: endpoint, - method: .post, - parameters: parameters, - encoding: JSONEncoding.default - ) + let response: BTCRPCServerResponce<[DashUnspentTransaction]> = try await dashApiService.request { + core, node in + await core.sendRequestJson( + node: node, + path: .empty, + method: .post, + parameters: DashGetUnspentTransactionDTO(address: wallet.address), + encoding: .json + ) + }.get() if let result = response.result { return result.map { @@ -175,7 +120,7 @@ extension DashWalletService { } else if let error = response.error?.message { throw WalletServiceError.internalError(message: error, error: nil) } - + throw WalletServiceError.internalError( message: "DASH Wallet: not a valid response", error: nil @@ -221,31 +166,17 @@ private extension DashWalletService { // MARK: - Network Requests extension DashWalletService { - func requestTransactionsIds(for address: String) async throws -> [String] { - guard let endpoint = DashWalletService.nodes.randomElement()?.asURL() else { - fatalError("Failed to get DASH endpoint URL") - } - - guard let address = self.dashWallet?.address else { - throw ApiServiceError.internalError(message: "DASH Wallet not found", error: nil) - } - - let parameters: Parameters = [ - "method": "getaddresstxids", - "params": [ - address - ] - ] - - // MARK: Sending request - - let response: BTCRPCServerResponce<[String]> = try await apiService.sendRequest( - url: endpoint, - method: .post, - parameters: parameters, - encoding: JSONEncoding.default - ) + let response: BTCRPCServerResponce<[String]> = try await dashApiService.request { + core, node in + await core.sendRequestJson( + node: node, + path: .empty, + method: .post, + parameters: DashGetAddressTransactionIds(address: address), + encoding: .json + ) + }.get() if let result = response.result { return result diff --git a/Adamant/Modules/Wallets/Dash/DashWalletService.swift b/Adamant/Modules/Wallets/Dash/DashWalletService.swift index bea2fe470..65fb463ce 100644 --- a/Adamant/Modules/Wallets/Dash/DashWalletService.swift +++ b/Adamant/Modules/Wallets/Dash/DashWalletService.swift @@ -50,14 +50,14 @@ final class DashWalletService: WalletService { // MARK: - Dependencies var apiService: ApiService! + var dashApiService: DashApiService! var accountService: AccountService! var securedStore: SecuredStore! var dialogService: DialogService! var addressConverter: AddressConverter! // MARK: - Constants - static var currencyLogo = UIImage.asset(named: "dash_wallet") ?? .init() - + static let currencyLogo = UIImage.asset(named: "dash_wallet") ?? .init() static let multiplier = Decimal(sign: .plus, exponent: 8, significand: 1) static let chunkSize = 20 @@ -65,13 +65,13 @@ final class DashWalletService: WalletService { return DashWalletService.fixedFee } - private (set) var isWarningGasPrice = false + @Atomic private (set) var isWarningGasPrice = false static let kvsAddress = "dash:address" - internal var transatrionsIds = [String]() + @Atomic var transatrionsIds = [String]() - internal var lastTransactionId: String? { + var lastTransactionId: String? { get { guard let hash: String = self.securedStore.get("lastDashTransactionId"), @@ -109,24 +109,21 @@ final class DashWalletService: WalletService { let transactionFeeUpdated = Notification.Name("adamant.dashWallet.feeUpdated") // MARK: - Delayed KVS save - private var balanceObserver: NSObjectProtocol? + @Atomic private var balanceObserver: NSObjectProtocol? // MARK: - Properties - private (set) var dashWallet: DashWallet? - - private (set) var enabled = true - - public var network: Network - - private var initialBalanceCheck = false + @Atomic private (set) var dashWallet: DashWallet? + @Atomic private (set) var enabled = true + @Atomic public var network: Network + @Atomic private var initialBalanceCheck = false let defaultDispatchQueue = DispatchQueue(label: "im.adamant.dashWalletService", qos: .userInteractive, attributes: [.concurrent]) static let jsonDecoder = JSONDecoder() - private var subscriptions = Set() + @Atomic private var subscriptions = Set() // MARK: - State - private (set) var state: WalletServiceState = .notInitiated + @Atomic private (set) var state: WalletServiceState = .notInitiated private func setState(_ newState: WalletServiceState, silent: Bool = false) { guard newState != state else { @@ -136,9 +133,11 @@ final class DashWalletService: WalletService { state = newState if !silent { - NotificationCenter.default.post(name: serviceStateChanged, - object: self, - userInfo: [AdamantUserInfoKey.WalletService.walletState: state]) + NotificationCenter.default.post( + name: serviceStateChanged, + object: self, + userInfo: [AdamantUserInfoKey.WalletService.walletState: state] + ) } } @@ -329,6 +328,7 @@ extension DashWalletService: SwinjectDependentService { dialogService = container.resolve(DialogService.self) addressConverter = container.resolve(AddressConverterFactory.self)? .make(network: network) + dashApiService = container.resolve(DashApiService.self) } } @@ -343,28 +343,15 @@ extension DashWalletService { } func getBalance(address: String) async throws -> Decimal { - guard let endpoint = DashWalletService.nodes.randomElement()?.asURL() else { - let message = "Failed to get DASH endpoint URL" - assertionFailure(message) - throw WalletServiceError.internalError(message: message, error: nil) - } - - // Parameters - let parameters: Parameters = [ - "method": "getaddressbalance", - "params": [ - address - ] - ] - - // MARK: Sending request - - let data = try await apiService.sendRequest( - url: endpoint, - method: .post, - parameters: parameters, - encoding: JSONEncoding.default - ) + let data: Data = try await dashApiService.request { core, node in + await core.sendRequest( + node: node, + path: .empty, + method: .post, + parameters: DashGetAddressBalanceDTO(address: address), + encoding: .json + ) + }.get() let object = try? JSONSerialization.jsonObject( with: data, @@ -390,7 +377,7 @@ extension DashWalletService { func getWalletAddress(byAdamantAddress address: String) async throws -> String { do { - let result = try await apiService.get(key: DashWalletService.kvsAddress, sender: address) + let result = try await apiService.get(key: DashWalletService.kvsAddress, sender: address).get() guard let result = result else { throw WalletServiceError.walletNotInitiated @@ -423,14 +410,20 @@ extension DashWalletService { } Task { - await apiService.store(key: DashWalletService.kvsAddress, value: dashAddress, type: .keyValue, sender: adamant.address, keypair: keypair) { result in - switch result { - case .success: - completion(.success) + let result = await apiService.store( + key: DashWalletService.kvsAddress, + value: dashAddress, + type: .keyValue, + sender: adamant.address, + keypair: keypair + ) + + switch result { + case .success: + completion(.success) - case .failure(let error): - completion(.failure(error: .apiError(error))) - } + case .failure(let error): + completion(.failure(error: .apiError(error))) } } } diff --git a/Adamant/Modules/Wallets/Doge/DTO/DogeBlockDTO.swift b/Adamant/Modules/Wallets/Doge/DTO/DogeBlockDTO.swift new file mode 100644 index 000000000..42861486f --- /dev/null +++ b/Adamant/Modules/Wallets/Doge/DTO/DogeBlockDTO.swift @@ -0,0 +1,15 @@ +// +// DogeBlockDTO.swift +// Adamant +// +// Created by Andrew G on 17.11.2023. +// Copyright © 2023 Adamant. All rights reserved. +// + +import Foundation + +struct DogeBlockDTO: Codable { + let height: Int + let hash: String + // there are more fields +} diff --git a/Adamant/Modules/Wallets/Doge/DTO/DogeBlocksDTO.swift b/Adamant/Modules/Wallets/Doge/DTO/DogeBlocksDTO.swift new file mode 100644 index 000000000..03e1439f3 --- /dev/null +++ b/Adamant/Modules/Wallets/Doge/DTO/DogeBlocksDTO.swift @@ -0,0 +1,13 @@ +// +// DogeBlocksDTO.swift +// Adamant +// +// Created by Andrew G on 17.11.2023. +// Copyright © 2023 Adamant. All rights reserved. +// + +import Foundation + +struct DogeBlocksDTO: Codable { + let blocks: [DogeBlockDTO] +} diff --git a/Adamant/Modules/Wallets/Doge/DogeApiService.swift b/Adamant/Modules/Wallets/Doge/DogeApiService.swift new file mode 100644 index 000000000..0b914089b --- /dev/null +++ b/Adamant/Modules/Wallets/Doge/DogeApiService.swift @@ -0,0 +1,71 @@ +// +// DogeApiService.swift +// Adamant +// +// Created by Andrew G on 17.11.2023. +// Copyright © 2023 Adamant. All rights reserved. +// + +import CommonKit +import Foundation + +final class DogeApiCore: BlockchainHealthCheckableService { + let apiCore: APICoreProtocol + + init(apiCore: APICoreProtocol) { + self.apiCore = apiCore + } + + func request( + node: Node, + _ request: @Sendable @escaping (APICoreProtocol, Node) async -> ApiServiceResult + ) async -> WalletServiceResult { + await request(apiCore, node).mapError { $0.asWalletServiceError() } + } + + func getStatusInfo(node: Node) async -> WalletServiceResult { + let startTimestamp = Date.now.timeIntervalSince1970 + + let response: WalletServiceResult = await request(node: node) { core, node in + await core.sendRequestJson( + node: node, + path: DogeApiCommands.getBlocks(), + method: .get, + parameters: ["limit": 0], + encoding: .url + ) + } + + return response.map { data in + return .init( + ping: Date.now.timeIntervalSince1970 - startTimestamp, + height: data.blocks.first?.height ?? .zero, + wsEnabled: false, + wsPort: nil, + version: nil + ) + } + } +} + +final class DogeApiService { + let api: BlockchainHealthCheckWrapper + + init(api: BlockchainHealthCheckWrapper) { + self.api = api + } + + func request( + _ request: @Sendable @escaping (APICoreProtocol, Node) async -> ApiServiceResult + ) async -> WalletServiceResult { + await api.request { core, node in + await core.request(node: node, request) + } + } + + func getStatusInfo() async -> WalletServiceResult { + await api.request { core, node in + await core.getStatusInfo(node: node) + } + } +} diff --git a/Adamant/Modules/Wallets/Doge/DogeWalletService+Send.swift b/Adamant/Modules/Wallets/Doge/DogeWalletService+Send.swift index ae6799dc6..0a5db47d7 100644 --- a/Adamant/Modules/Wallets/Doge/DogeWalletService+Send.swift +++ b/Adamant/Modules/Wallets/Doge/DogeWalletService+Send.swift @@ -9,6 +9,7 @@ import UIKit import BitcoinKit import Alamofire +import CommonKit extension BitcoinKit.Transaction: RawTransaction { var txHash: String? { @@ -61,56 +62,26 @@ extension DogeWalletService: WalletServiceTwoStepSend { } func sendTransaction(_ transaction: BitcoinKit.Transaction) async throws { - guard let url = DogeWalletService.nodes.randomElement()?.asURL() else { - throw WalletServiceError.internalError( - message: "Failed to get DOGE endpoint URL", - error: nil - ) - } - - // Request url - let endpoint = url.appendingPathComponent(DogeApiCommands.sendTransaction()) - - // Headers - let headers: HTTPHeaders = [ - "Content-Type": "application/json" - ] - - // MARK: Prepare params let txHex = transaction.serialized().hex - let parameters: Parameters = [ - "rawtx": txHex - ] - - // MARK: Sending request - _ = try await withUnsafeThrowingContinuation { continuation in - AF.request( - endpoint, + _ = try await dogeApiService.api.request { core, node in + let response: APIResponseModel = await core.apiCore.sendRequestBasic( + node: node, + path: DogeApiCommands.sendTransaction(), method: .post, - parameters: parameters, - encoding: JSONEncoding.default, - headers: headers + parameters: ["rawtx": txHex], + encoding: .json ) - .validate(statusCode: 200 ... 299) - .responseJSON(queue: defaultDispatchQueue) { response in - switch response.result { - case .success: - continuation.resume() - case .failure(let error): - guard let data = response.data else { - continuation.resume(throwing: WalletServiceError.remoteServiceError(message: error.localizedDescription)) - return - } - let result = String(decoding: data, as: UTF8.self) - if result.contains("dust") && result.contains("-26") { - continuation.resume(throwing: WalletServiceError.dustAmountError) - return - } - continuation.resume(throwing: WalletServiceError.remoteServiceError(message: error.localizedDescription)) - } - } - } + + guard + !(200 ... 299).contains(response.code ?? .zero), + let dataString = response.data.map({ String(decoding: $0, as: UTF8.self) }), + dataString.contains("dust"), + dataString.contains("-26") + else { return response.result.mapError { $0.asWalletServiceError() } } + + return .failure(.dustAmountError) + }.get() } } diff --git a/Adamant/Modules/Wallets/Doge/DogeWalletService.swift b/Adamant/Modules/Wallets/Doge/DogeWalletService.swift index f2226527a..7adc8416c 100644 --- a/Adamant/Modules/Wallets/Doge/DogeWalletService.swift +++ b/Adamant/Modules/Wallets/Doge/DogeWalletService.swift @@ -30,6 +30,10 @@ struct DogeApiCommands { return "/api/block/\(hash)" } + static func getBlocks() -> String { + return "/api/blocks" + } + static func getUnspentTransactions(for address: String) -> String { return "/api/addr/\(address)/utxo" } @@ -47,12 +51,13 @@ final class DogeWalletService: WalletService { // MARK: - Dependencies var apiService: ApiService! + var dogeApiService: DogeApiService! var accountService: AccountService! var dialogService: DialogService! var addressConverter: AddressConverter! // MARK: - Constants - static var currencyLogo = UIImage.asset(named: "doge_wallet") ?? .init() + static let currencyLogo = UIImage.asset(named: "doge_wallet") ?? .init() static let multiplier = Decimal(sign: .plus, exponent: 8, significand: 1) static let chunkSize = 20 @@ -90,7 +95,7 @@ final class DogeWalletService: WalletService { static let kvsAddress = "doge:address" - private (set) var isWarningGasPrice = false + @Atomic private(set) var isWarningGasPrice = false // MARK: - Notifications let walletUpdatedNotification = Notification.Name("adamant.dogeWallet.walletUpdated") @@ -99,24 +104,25 @@ final class DogeWalletService: WalletService { let transactionFeeUpdated = Notification.Name("adamant.dogeWallet.feeUpdated") // MARK: - Delayed KVS save - private var balanceObserver: NSObjectProtocol? + @Atomic private var balanceObserver: NSObjectProtocol? // MARK: - Properties - private (set) var dogeWallet: DogeWallet? - - private (set) var enabled = true - - public var network: Network + @Atomic private(set) var dogeWallet: DogeWallet? + @Atomic private(set) var enabled = true + @Atomic public var network: Network + @Atomic private var initialBalanceCheck = false - private var initialBalanceCheck = false - - let defaultDispatchQueue = DispatchQueue(label: "im.adamant.dogeWalletService", qos: .userInteractive, attributes: [.concurrent]) + let defaultDispatchQueue = DispatchQueue( + label: "im.adamant.dogeWalletService", + qos: .userInteractive, + attributes: [.concurrent] + ) private static let jsonDecoder = JSONDecoder() - private var subscriptions = Set() + @Atomic private var subscriptions = Set() // MARK: - State - private (set) var state: WalletServiceState = .notInitiated + @Atomic private (set) var state: WalletServiceState = .notInitiated private func setState(_ newState: WalletServiceState, silent: Bool = false) { guard newState != state else { @@ -126,15 +132,16 @@ final class DogeWalletService: WalletService { state = newState if !silent { - NotificationCenter.default.post(name: serviceStateChanged, - object: self, - userInfo: [AdamantUserInfoKey.WalletService.walletState: state]) + NotificationCenter.default.post( + name: serviceStateChanged, + object: self, + userInfo: [AdamantUserInfoKey.WalletService.walletState: state] + ) } } init() { self.network = DogeMainnet() - self.setState(.notInitiated) // Notifications @@ -309,6 +316,7 @@ extension DogeWalletService: SwinjectDependentService { dialogService = container.resolve(DialogService.self) addressConverter = container.resolve(AddressConverterFactory.self)? .make(network: network) + dogeApiService = container.resolve(DogeApiService.self) } } @@ -323,43 +331,27 @@ extension DogeWalletService { } func getBalance(address: String) async throws -> Decimal { - guard let url = DogeWalletService.nodes.randomElement()?.asURL() else { - let message = "Failed to get DOGE endpoint URL" - assertionFailure(message) - throw WalletServiceError.internalError(message: message, error: nil) - } - - // Headers - let headers: HTTPHeaders = [ - "Content-Type": "application/json" - ] - - // Request url - let endpoint = url.appendingPathComponent(DogeApiCommands.balance(for: address)) - - // MARK: Sending request - - return try await withUnsafeThrowingContinuation { (continuation: UnsafeContinuation) in - AF.request(endpoint, method: .get, headers: headers).responseString { response in - switch response.result { - case .success(let data): - if let raw = Decimal(string: data) { - let balance = raw / DogeWalletService.multiplier - continuation.resume(returning: balance) - } else { - continuation.resume(throwing: WalletServiceError.remoteServiceError(message: "DOGE Wallet: \(data)")) - } - - case .failure: - continuation.resume(throwing: WalletServiceError.networkError) - } - } + let data: Data = try await dogeApiService.request { core, node in + await core.sendRequest( + node: node, + path: DogeApiCommands.balance(for: address) + ) + }.get() + + if + let string = String(data: data, encoding: .utf8), + let raw = Decimal(string: string) + { + let balance = raw / DogeWalletService.multiplier + return balance + } else { + throw WalletServiceError.internalError(InternalAPIError.parsingFailed) } } func getWalletAddress(byAdamantAddress address: String) async throws -> String { do { - let result = try await apiService.get(key: DogeWalletService.kvsAddress, sender: address) + let result = try await apiService.get(key: DogeWalletService.kvsAddress, sender: address).get() guard let result = result else { throw WalletServiceError.walletNotInitiated @@ -391,14 +383,20 @@ extension DogeWalletService { } Task { - await apiService.store(key: DogeWalletService.kvsAddress, value: dogeAddress, type: .keyValue, sender: adamant.address, keypair: keypair) { result in - switch result { - case .success: - completion(.success) - - case .failure(let error): - completion(.failure(error: .apiError(error))) - } + let result = await apiService.store( + key: DogeWalletService.kvsAddress, + value: dogeAddress, + type: .keyValue, + sender: adamant.address, + keypair: keypair + ) + + switch result { + case .success: + completion(.success) + + case .failure(let error): + completion(.failure(error: .apiError(error))) } } } @@ -458,63 +456,54 @@ extension DogeWalletService { return (transactions: transactions, hasMore: hasMore) } - private func getTransactions(for address: String, from: Int, to: Int) async throws -> DogeGetTransactionsResponse { - guard let url = DogeWalletService.nodes.randomElement()?.asURL() else { - fatalError("Failed to get DOGE endpoint URL") - } - - let parameters: Parameters = [ + private func getTransactions( + for address: String, + from: Int, + to: Int + ) async throws -> DogeGetTransactionsResponse { + let parameters = [ "from": from, "to": to ] - // Request url - let endpoint = url.appendingPathComponent(DogeApiCommands.getTransactions(for: address)) - - // MARK: Sending request - do { - let dogeResponse: DogeGetTransactionsResponse = try await apiService.sendRequest( - url: endpoint, + return try await dogeApiService.request { core, node in + await core.sendRequestJson( + node: node, + path: DogeApiCommands.getTransactions(for: address), method: .get, - parameters: parameters + parameters: parameters, + encoding: .url ) - return dogeResponse - } catch { - throw WalletServiceError.remoteServiceError(message: "DOGE Wallet: not a valid response") - } + }.get() } func getUnspentTransactions() async throws -> [UnspentTransaction] { - guard let url = DogeWalletService.nodes.randomElement()?.asURL() else { - fatalError("Failed to get DOGE endpoint URL") - } - guard let wallet = self.dogeWallet else { throw WalletServiceError.notLogged } let address = wallet.address - // Request url - let endpoint = url.appendingPathComponent(DogeApiCommands.getUnspentTransactions(for: address)) - - let parameters: Parameters = [ + let parameters = [ "noCache": "1" ] // MARK: Sending request + let data = try await dogeApiService.request { core, node in + await core.sendRequest( + node: node, + path: DogeApiCommands.getUnspentTransactions(for: address), + method: .get, + parameters: parameters, + encoding: .url + ) + }.get() - let data = try await apiService.sendRequest( - url: endpoint, - method: .get, - parameters: parameters - ) - let items = try? JSONSerialization.jsonObject( with: data, options: [] ) as? [[String: Any]] - + guard let items = items else { throw WalletServiceError.remoteServiceError( message: "DOGE Wallet: not valid response" @@ -549,39 +538,18 @@ extension DogeWalletService { } func getTransaction(by hash: String) async throws -> BTCRawTransaction { - guard let url = DogeWalletService.nodes.randomElement()?.asURL() else { - fatalError("Failed to get DOGE endpoint URL") - } - - // Request url - - let endpoint = url.appendingPathComponent(DogeApiCommands.getTransaction(by: hash)) - - // MARK: Sending request - - let transaction: BTCRawTransaction = try await apiService.sendRequest( - url: endpoint, - method: .get, - parameters: nil - ) - - return transaction + try await dogeApiService.request { core, node in + await core.sendRequestJson( + node: node, + path: DogeApiCommands.getTransaction(by: hash) + ) + }.get() } func getBlockId(by hash: String) async throws -> String { - guard let url = DogeWalletService.nodes.randomElement()?.asURL() else { - fatalError("Failed to get DOGE endpoint URL") - } - - // Request url - - let endpoint = url.appendingPathComponent(DogeApiCommands.getBlock(by: hash)) - - let data = try await apiService.sendRequest( - url: endpoint, - method: .get, - parameters: nil - ) + let data = try await dogeApiService.request { core, node in + await core.sendRequest(node: node, path: DogeApiCommands.getBlock(by: hash)) + }.get() let json = try? JSONSerialization.jsonObject( with: data, diff --git a/Adamant/Modules/Wallets/ERC20/ERC20ApiService.swift b/Adamant/Modules/Wallets/ERC20/ERC20ApiService.swift new file mode 100644 index 000000000..dcfab8933 --- /dev/null +++ b/Adamant/Modules/Wallets/ERC20/ERC20ApiService.swift @@ -0,0 +1,25 @@ +// +// ERC20ApiService.swift +// Adamant +// +// Created by Andrew G on 13.11.2023. +// Copyright © 2023 Adamant. All rights reserved. +// + +import web3swift +import Web3Core +import CommonKit + +final class ERC20ApiService: EthApiService { + func requestERC20( + token: ERC20Token, + _ body: @Sendable @escaping (ERC20) async throws -> Output + ) async -> WalletServiceResult { + let contractAddress = EthereumAddress(token.contractAddress) ?? .zero + + return await requestWeb3 { web3 in + let erc20 = ERC20(web3: web3, provider: web3.provider, address: contractAddress) + return try await body(erc20) + } + } +} diff --git a/Adamant/Modules/Wallets/ERC20/ERC20TransactionsViewController.swift b/Adamant/Modules/Wallets/ERC20/ERC20TransactionsViewController.swift index 9237089b5..88c712a86 100644 --- a/Adamant/Modules/Wallets/ERC20/ERC20TransactionsViewController.swift +++ b/Adamant/Modules/Wallets/ERC20/ERC20TransactionsViewController.swift @@ -25,11 +25,7 @@ final class ERC20TransactionsViewController: TransactionsListViewControllerBase var transactions: [EthTransactionShort] = [] private var ethAddress: String = "" private lazy var exponent: Int = { - var exponent = EthWalletService.currencyExponent - if let naturalUnits = walletService.token?.naturalUnits { - exponent = -1 * naturalUnits - } - return exponent + -walletService.token.naturalUnits }() private var offset = 0 diff --git a/Adamant/Modules/Wallets/ERC20/ERC20WalletService+Send.swift b/Adamant/Modules/Wallets/ERC20/ERC20WalletService+Send.swift index 47aeda74f..f3d3c1300 100644 --- a/Adamant/Modules/Wallets/ERC20/ERC20WalletService+Send.swift +++ b/Adamant/Modules/Wallets/ERC20/ERC20WalletService+Send.swift @@ -17,9 +17,7 @@ extension ERC20WalletService: WalletServiceTwoStepSend { // MARK: Create & Send func createTransaction(recipient: String, amount: Decimal) async throws -> CodableTransaction { - guard let ethWallet = ethWallet, - let erc20 = erc20 - else { + guard let ethWallet = ethWallet else { throw WalletServiceError.notLogged } @@ -27,57 +25,49 @@ extension ERC20WalletService: WalletServiceTwoStepSend { throw WalletServiceError.accountNotFound } - guard let web3 = await web3 else { - throw WalletServiceError.internalError(message: "Failed to get web3", error: nil) - } - - guard let keystoreManager = web3.provider.attachedKeystoreManager else { + guard let keystoreManager = erc20ApiService.keystoreManager else { throw WalletServiceError.internalError(message: "Failed to get web3.provider.KeystoreManager", error: nil) } - let provider = web3.provider + let provider = try await erc20ApiService.requestWeb3 { web3 in web3.provider }.get() let resolver = PolicyResolver(provider: provider) // MARK: Create transaction - do { - var tx = try await erc20.transfer( + var tx = try await erc20ApiService.requestERC20(token: token) { erc20 in + try await erc20.transfer( from: ethWallet.ethAddress, to: ethRecipient, amount: "\(amount)" ).transaction - - await calculateFee(for: ethRecipient) - - let policies: Policies = Policies( - gasLimitPolicy: .manual(gasLimit), - gasPricePolicy: .manual(gasPrice) - ) - - try await resolver.resolveAll(for: &tx, with: policies) - - try Web3Signer.signTX( - transaction: &tx, - keystore: keystoreManager, - account: ethWallet.ethAddress, - password: ERC20WalletService.walletPassword - ) - - return tx - } catch { - throw WalletServiceError.internalError(message: "Transaction sign error", error: error) - } + }.get() + + await calculateFee(for: ethRecipient) + + let policies: Policies = Policies( + gasLimitPolicy: .manual(gasLimit), + gasPricePolicy: .manual(gasPrice) + ) + + try await resolver.resolveAll(for: &tx, with: policies) + + try Web3Signer.signTX( + transaction: &tx, + keystore: keystoreManager, + account: ethWallet.ethAddress, + password: ERC20WalletService.walletPassword + ) + + return tx } func sendTransaction(_ transaction: CodableTransaction) async throws { guard let txEncoded = transaction.encode() else { - throw WalletServiceError.internalError(message: String.adamant.sharedErrors.unknownError, error: nil) + throw WalletServiceError.internalError(message: .adamant.sharedErrors.unknownError, error: nil) } - do { - _ = try await web3?.eth.send(raw: txEncoded) - } catch { - throw WalletServiceError.internalError(message: "Error: \(error.localizedDescription)", error: nil) - } + _ = try await erc20ApiService.requestWeb3 { web3 in + try await web3.eth.send(raw: txEncoded) + }.get() } } diff --git a/Adamant/Modules/Wallets/ERC20/ERC20WalletService.swift b/Adamant/Modules/Wallets/ERC20/ERC20WalletService.swift index 66930e2c4..4b14c60dc 100644 --- a/Adamant/Modules/Wallets/ERC20/ERC20WalletService.swift +++ b/Adamant/Modules/Wallets/ERC20/ERC20WalletService.swift @@ -28,15 +28,15 @@ final class ERC20WalletService: WalletService { var minAmount: Decimal = 0 var tokenSymbol: String { - return token?.symbol ?? "" + return token.symbol } var tokenName: String { - return token?.name ?? "" + return token.name } var tokenLogo: UIImage { - return token?.logo ?? UIImage() + return token.logo } var tokenNetworkSymbol: String { @@ -48,7 +48,7 @@ final class ERC20WalletService: WalletService { } var tokenContract: String { - return token?.contractAddress ?? "" + return token.contractAddress } var tokenUnicID: String { @@ -56,11 +56,11 @@ final class ERC20WalletService: WalletService { } var defaultVisibility: Bool { - return token?.defaultVisibility ?? false + return token.defaultVisibility } var defaultOrdinalLevel: Int? { - return token?.defaultOrdinalLevel + return token.defaultOrdinalLevel } var richMessageType: String { @@ -99,47 +99,31 @@ final class ERC20WalletService: WalletService { // MARK: - Dependencies weak var accountService: AccountService? var apiService: ApiService! + var erc20ApiService: ERC20ApiService! var dialogService: DialogService! var increaseFeeService: IncreaseFeeService! // MARK: - Notifications - var walletUpdatedNotification = Notification.Name("adamant.erc20Wallet.walletUpdated") - var serviceEnabledChanged = Notification.Name("adamant.erc20Wallet.enabledChanged") - var transactionFeeUpdated = Notification.Name("adamant.erc20Wallet.feeUpdated") - var serviceStateChanged = Notification.Name("adamant.erc20Wallet.stateChanged") + let walletUpdatedNotification: Notification.Name + let serviceEnabledChanged: Notification.Name + let transactionFeeUpdated: Notification.Name + let serviceStateChanged: Notification.Name // MARK: RichMessageProvider properties static let richMessageType = "erc20_transaction" var dynamicRichMessageType: String { - return "\(self.token?.symbol.lowercased() ?? "erc20")_transaction" + return "\(self.token.symbol.lowercased())_transaction" } // MARK: - Properties - private (set) var token: ERC20Token? - private (set) var erc20: ERC20? - private (set) var enabled = true - - private var subscriptions = Set() - private var _ethNodeUrl: String? - private var _web3: Web3? - var web3: Web3? { - get async { - if _web3 != nil { - return _web3 - } - guard let url = _ethNodeUrl else { - return nil - } - - return await setupEthNode(with: url) - } - } - - private var initialBalanceCheck = false + let token: ERC20Token + @Atomic private(set) var enabled = true + @Atomic private var subscriptions = Set() + @Atomic private var initialBalanceCheck = false // MARK: - State - private (set) var state: WalletServiceState = .notInitiated + @Atomic private (set) var state: WalletServiceState = .notInitiated private func setState(_ newState: WalletServiceState, silent: Bool = false) { guard newState != state else { @@ -149,39 +133,29 @@ final class ERC20WalletService: WalletService { state = newState if !silent { - NotificationCenter.default.post(name: serviceStateChanged, - object: self, - userInfo: [AdamantUserInfoKey.WalletService.walletState: state]) + NotificationCenter.default.post( + name: serviceStateChanged, + object: self, + userInfo: [AdamantUserInfoKey.WalletService.walletState: state] + ) } } private (set) var ethWallet: EthWallet? var wallet: WalletAccount? { return ethWallet } - - private (set) var contract: Web3.Contract? private var balanceObserver: NSObjectProtocol? init(token: ERC20Token) { self.token = token - - self.setState(.notInitiated) - walletUpdatedNotification = Notification.Name("adamant.erc20Wallet.\(token.symbol).walletUpdated") serviceEnabledChanged = Notification.Name("adamant.erc20Wallet.\(token.symbol).enabledChanged") transactionFeeUpdated = Notification.Name("adamant.erc20Wallet.\(token.symbol).feeUpdated") serviceStateChanged = Notification.Name("adamant.erc20Wallet.\(token.symbol).stateChanged") + self.setState(.notInitiated) + // Notifications addObservers() - - guard let node = EthWalletService.nodes.randomElement() else { - fatalError("Failed to get ETH endpoint") - } - let apiUrl = node.asString() - _ethNodeUrl = apiUrl - Task { - _ = await self.setupEthNode(with: apiUrl) - } } func addObservers() { @@ -215,25 +189,6 @@ final class ERC20WalletService: WalletService { .store(in: &subscriptions) } - func setupEthNode(with apiUrl: String) async -> Web3? { - guard - let url = URL(string: apiUrl), - let web3 = try? await Web3.new(url), - let token = self.token else { - return nil - } - - self._web3 = web3 - - if let address = EthereumAddress(token.contractAddress) { - self.contract = web3.contract(Web3.Utils.erc20ABI, at: address, abiVersion: 2) - - self.erc20 = ERC20(web3: web3, provider: web3.provider, address: address) - } - - return web3 - } - func update() { Task { await update() @@ -281,8 +236,6 @@ final class ERC20WalletService: WalletService { } func calculateFee(for address: EthereumAddress? = nil) async { - guard let token = token else { return } - let priceRaw = try? await getGasPrices() let gasLimitRaw = try? await getGasLimit(to: address) @@ -326,52 +279,27 @@ final class ERC20WalletService: WalletService { } func getGasPrices() async throws -> BigUInt { - guard let web3 = await self.web3 else { - throw WalletServiceError.internalError(message: "Can't get web3 service", error: nil) - } - - do { - let price = try await web3.eth.gasPrice() - return price - } catch { - throw WalletServiceError.remoteServiceError( - message: error.localizedDescription - ) - } + try await erc20ApiService.requestWeb3 { web3 in + try await web3.eth.gasPrice() + }.get() } func getGasLimit(to address: EthereumAddress?) async throws -> BigUInt { - guard let web3 = await self.web3, - let ethWallet = ethWallet, - let erc20 = erc20 - else { - throw WalletServiceError.internalError(message: "Can't get web3 service", error: nil) + guard let ethWallet = ethWallet else { + throw WalletServiceError.internalError(message: "Can't get ethWallet service", error: nil) } - do { - let transaction = try await erc20.transfer( + let transaction = try await erc20ApiService.requestERC20(token: token) { erc20 in + try await erc20.transfer( from: ethWallet.ethAddress, to: address ?? ethWallet.ethAddress, amount: "\(ethWallet.balance)" ).transaction - - let price = try await web3.eth.estimateGas(for: transaction) - return price - } catch { - throw WalletServiceError.remoteServiceError( - message: error.localizedDescription - ) - } - } - - private func buildUrl(url: URL, queryItems: [URLQueryItem]? = nil) throws -> URL { - guard var components = URLComponents(url: url, resolvingAgainstBaseURL: false) else { - throw AdamantApiService.InternalError.endpointBuildFailed - } + }.get() - components.queryItems = queryItems - - return try components.asURL() + return try await erc20ApiService.requestWeb3 { web3 in + try await web3.eth.estimateGas(for: transaction) + }.get() } } @@ -399,7 +327,7 @@ extension ERC20WalletService: InitiatedWithPassphraseService { throw WalletServiceError.internalError(message: "ETH Wallet: failed to create Keystore", error: error) } - await web3?.addKeystoreManager(KeystoreManager([keystore])) + erc20ApiService.keystoreManager = .init([keystore]) guard let ethAddress = keystore.addresses?.first else { throw WalletServiceError.internalError(message: "ETH Wallet: failed to create Keystore", error: nil) @@ -436,6 +364,7 @@ extension ERC20WalletService: SwinjectDependentService { apiService = container.resolve(ApiService.self) dialogService = container.resolve(DialogService.self) increaseFeeService = container.resolve(IncreaseFeeService.self) + erc20ApiService = container.resolve(ERC20ApiService.self) } } @@ -443,16 +372,14 @@ extension ERC20WalletService: SwinjectDependentService { extension ERC20WalletService { func getTransaction(by hash: String) async throws -> EthTransaction { let sender = wallet?.address - guard let eth = await web3?.eth else { - throw WalletServiceError.internalError(message: "Failed to get transaction", error: nil) - } - let isOutgoing: Bool let details: Web3Core.TransactionDetails // MARK: 1. Transaction details do { - details = try await eth.transactionDetails(hash) + details = try await erc20ApiService.requestWeb3 { web3 in + try await web3.eth.transactionDetails(hash) + }.get() } catch let error as Web3Error { throw error.asWalletServiceError() } catch _ as URLError { @@ -463,7 +390,9 @@ extension ERC20WalletService { // MARK: 2. Transaction receipt do { - let receipt = try await eth.transactionReceipt(hash) + let receipt = try await erc20ApiService.requestWeb3 { web3 in + try await web3.eth.transactionReceipt(hash) + }.get() // MARK: 3. Check if transaction is delivered guard receipt.status == .ok, @@ -482,8 +411,13 @@ extension ERC20WalletService { } // MARK: 4. Block timestamp & confirmations - let currentBlock = try await eth.blockNumber() - let block = try await eth.block(by: receipt.blockHash) + let currentBlock = try await erc20ApiService.requestWeb3 { web3 in + try await web3.eth.blockNumber() + }.get() + + let block = try await erc20ApiService.requestWeb3 { web3 in + try await web3.eth.block(by: receipt.blockHash) + }.get() guard currentBlock >= blockNumber else { throw WalletServiceError.remoteServiceError( @@ -529,7 +463,7 @@ extension ERC20WalletService { return transaction default: - throw error.asWalletServiceError() + throw error } } catch _ as URLError { throw WalletServiceError.networkError @@ -547,74 +481,60 @@ extension ERC20WalletService { } func getBalance(forAddress address: EthereumAddress) async throws -> Decimal { - guard let erc20 = self.erc20 else { - throw WalletServiceError.internalError(message: "Can't get address", error: nil) - } + let exponent = -token.naturalUnits - var exponent = EthWalletService.currencyExponent - if let naturalUnits = self.token?.naturalUnits { - exponent = -1 * naturalUnits - } + let balance = try await erc20ApiService.requestERC20(token: token) { erc20 in + try await erc20.getBalance(account: address) + }.get() - do { - let balance = try await erc20.getBalance(account: address) - let value = balance.asDecimal(exponent: exponent) - return value - } catch { - throw WalletServiceError.remoteServiceError( - message: "ERC 20 Service - Fail to get balance" - ) - } + let value = balance.asDecimal(exponent: exponent) + return value } func getWalletAddress(byAdamantAddress address: String) async throws -> String { - do { - let result = try await apiService.get(key: EthWalletService.kvsAddress, sender: address) - - guard let result = result else { - throw WalletServiceError.walletNotInitiated - } - - return result - } catch { - throw WalletServiceError.remoteServiceError( - message: "ETH Wallet: failed to get address from KVS" - ) + let result = try await apiService.get(key: EthWalletService.kvsAddress, sender: address) + .mapError { $0.asWalletServiceError() } + .get() + + guard let result = result else { + throw WalletServiceError.walletNotInitiated } + + return result } } extension ERC20WalletService { - func getTransactionsHistory(address: String, offset: Int = 0, limit: Int = 100) async throws -> [EthTransactionShort] { - guard let node = EthWalletService.nodes.randomElement(), let url = node.asURL() else { - fatalError("Failed to build ETH endpoint URL") - } - - guard let address = self.ethWallet?.address, let contract = self.token?.contractAddress else { + func getTransactionsHistory( + address: String, + offset: Int = .zero, + limit: Int = 100 + ) async throws -> [EthTransactionShort] { + guard let address = self.ethWallet?.address else { throw WalletServiceError.internalError(message: "Can't get address", error: nil) } // Request - let request = "(txto.eq.\(contract),or(txfrom.eq.\(address.lowercased()),contract_to.eq.000000000000000000000000\(address.lowercased().replacingOccurrences(of: "0x", with: ""))))" + let request = "(txto.eq.\(token.contractAddress),or(txfrom.eq.\(address.lowercased()),contract_to.eq.000000000000000000000000\(address.lowercased().replacingOccurrences(of: "0x", with: ""))))" // MARK: Request - let txQueryItems: [URLQueryItem] = [URLQueryItem(name: "limit", value: String(limit)), - URLQueryItem(name: "and", value: request), - URLQueryItem(name: "offset", value: String(offset)), - URLQueryItem(name: "order", value: "time.desc") + let txQueryParameters = [ + "limit": String(limit), + "and": request, + "offset": String(offset), + "order": "time.desc" ] - let txEndpoint: URL - do { - txEndpoint = try buildUrl(url: url.appendingPathComponent(EthWalletService.transactionsListApiSubpath), queryItems: txQueryItems) - } catch { - let err = AdamantApiService.InternalError.endpointBuildFailed.apiServiceErrorWith(error: error) - throw WalletServiceError.apiError(err) - } - - // MARK: Sending requests + var transactions: [EthTransactionShort] = try await erc20ApiService.requestApiCore { core, node in + await core.sendRequestJson( + node: node, + path: EthWalletService.transactionsListApiSubpath, + method: .get, + parameters: txQueryParameters, + encoding: .url + ) + }.get() - var transactions: [EthTransactionShort] = try await apiService.sendRequest(url: txEndpoint, method: .get, parameters: nil) transactions.sort { $0.date.compare($1.date) == .orderedDescending } return transactions } diff --git a/Adamant/Modules/Wallets/Ethereum/EthApiService.swift b/Adamant/Modules/Wallets/Ethereum/EthApiService.swift new file mode 100644 index 000000000..64bf2c0c4 --- /dev/null +++ b/Adamant/Modules/Wallets/Ethereum/EthApiService.swift @@ -0,0 +1,118 @@ +// +// EthApiService.swift +// Adamant +// +// Created by Andrew G on 13.11.2023. +// Copyright © 2023 Adamant. All rights reserved. +// + +import CommonKit +import Foundation +import web3swift +import Web3Core + +final class EthApiCore: BlockchainHealthCheckableService { + let apiCore: APICoreProtocol + var keystoreManager: KeystoreManager? + + func makeWeb3(node: Node) async -> WalletServiceResult { + do { + guard let url = node.asURL() else { throw InternalAPIError.endpointBuildFailed } + let web3 = try await Web3.new(url) + web3.addKeystoreManager(keystoreManager) + return .success(web3) + } catch { + return .failure(.internalError(message: error.localizedDescription, error: error)) + } + } + + func performRequest( + node: Node, + _ body: @escaping (_ web3: Web3) async throws -> Success + ) async -> WalletServiceResult { + await makeWeb3(node: node).asyncMap { web3 in + do { + return .success(try await body(web3)) + } catch { + return .failure(mapError(error)) + } + }.flatMap { $0 } + } + + func performRequest( + _ body: @escaping () async throws -> Success + ) async -> WalletServiceResult { + do { + return .success(try await body()) + } catch { + return .failure(mapError(error)) + } + } + + func getStatusInfo(node: Node) async -> WalletServiceResult { + await performRequest(node: node) { web3 in + let startTimestamp = Date.now.timeIntervalSince1970 + let height = try await web3.eth.blockNumber() + let ping = Date.now.timeIntervalSince1970 - startTimestamp + + return .init( + ping: ping, + height: Int(height.asDouble()), + wsEnabled: false, + wsPort: nil, + version: nil + ) + } + } + + init(apiCore: APICoreProtocol) { + self.apiCore = apiCore + } +} + +class EthApiService { + let api: BlockchainHealthCheckWrapper + + var keystoreManager: KeystoreManager? { + get { api.service.keystoreManager } + set { api.service.keystoreManager = newValue } + } + + init(api: BlockchainHealthCheckWrapper) { + self.api = api + } + + func requestWeb3( + _ request: @Sendable @escaping (Web3) async throws -> Output + ) async -> WalletServiceResult { + await api.request { core, node in + await core.performRequest(node: node, request) + } + } + + func requestApiCore( + _ request: @Sendable @escaping (APICoreProtocol, Node) async -> ApiServiceResult + ) async -> WalletServiceResult { + await api.request { core, node in + await request(core.apiCore, node).mapError { $0.asWalletServiceError() } + } + } + + func getStatusInfo() async -> WalletServiceResult { + await api.request { core, node in + await core.getStatusInfo(node: node) + } + } +} + +private func mapError(_ error: Error) -> WalletServiceError { + if let error = error as? Web3Error { + return error.asWalletServiceError() + } else if let error = error as? ApiServiceError { + return error.asWalletServiceError() + } else if let error = error as? WalletServiceError { + return error + } else { + return .remoteServiceError(message: error.localizedDescription) + } +} diff --git a/Adamant/Modules/Wallets/Ethereum/EthWalletService+RichMessageProviderWithStatusCheck.swift b/Adamant/Modules/Wallets/Ethereum/EthWalletService+RichMessageProviderWithStatusCheck.swift index ef51352f7..77173d606 100644 --- a/Adamant/Modules/Wallets/Ethereum/EthWalletService+RichMessageProviderWithStatusCheck.swift +++ b/Adamant/Modules/Wallets/Ethereum/EthWalletService+RichMessageProviderWithStatusCheck.swift @@ -13,17 +13,17 @@ import CommonKit extension EthWalletService: RichMessageProviderWithStatusCheck { func statusInfoFor(transaction: RichMessageTransaction) async -> TransactionStatusInfo { - guard - let web3 = await web3, - let hash = transaction.getRichValue(for: RichContentKeys.transfer.hash) - else { + guard let hash = transaction.getRichValue(for: RichContentKeys.transfer.hash) else { return .init(sentDate: nil, status: .inconsistent) } let transactionInfo: EthTransactionInfo do { - transactionInfo = try await getTransactionInfo(hash: hash, web3: web3) + transactionInfo = try await ethApiService.requestWeb3 { [weak self] web3 in + guard let self = self else { throw WalletServiceError.internalError(.unknownError) } + return try await getTransactionInfo(hash: hash, web3: web3) + }.get() } catch _ as URLError { return .init(sentDate: nil, status: .noNetwork) } catch { @@ -39,7 +39,9 @@ extension EthWalletService: RichMessageProviderWithStatusCheck { var sentDate: Date? if let blockHash = details.blockHash { - sentDate = try? await web3.eth.block(by: blockHash).timestamp + sentDate = try? await ethApiService.requestWeb3 { web3 in + try await web3.eth.block(by: blockHash).timestamp + }.get() } return .init( diff --git a/Adamant/Modules/Wallets/Ethereum/EthWalletService+Send.swift b/Adamant/Modules/Wallets/Ethereum/EthWalletService+Send.swift index 2e1404e0c..f843009cd 100644 --- a/Adamant/Modules/Wallets/Ethereum/EthWalletService+Send.swift +++ b/Adamant/Modules/Wallets/Ethereum/EthWalletService+Send.swift @@ -21,9 +21,20 @@ extension CodableTransaction: RawTransaction { extension EthWalletService: WalletServiceTwoStepSend { typealias T = CodableTransaction + + func createTransaction(recipient: String, amount: Decimal) async throws -> CodableTransaction { + try await ethApiService.requestWeb3 { [weak self] web3 in + guard let self = self else { throw WalletServiceError.internalError(.unknownError) } + return try await createTransaction(recipient: recipient, amount: amount, web3: web3) + }.get() + } // MARK: Create & Send - func createTransaction(recipient: String, amount: Decimal) async throws -> CodableTransaction { + private func createTransaction( + recipient: String, + amount: Decimal, + web3: Web3 + ) async throws -> CodableTransaction { guard let ethWallet = ethWallet else { throw WalletServiceError.notLogged } @@ -36,10 +47,6 @@ extension EthWalletService: WalletServiceTwoStepSend { throw WalletServiceError.invalidAmount(amount) } - guard let web3 = await web3 else { - throw WalletServiceError.internalError(message: "Failed to get web3", error: nil) - } - guard let keystoreManager = web3.provider.attachedKeystoreManager else { throw WalletServiceError.internalError(message: "Failed to get web3.provider.KeystoreManager", error: nil) } @@ -83,13 +90,11 @@ extension EthWalletService: WalletServiceTwoStepSend { func sendTransaction(_ transaction: CodableTransaction) async throws { guard let txEncoded = transaction.encode() else { - throw WalletServiceError.internalError(message: String.adamant.sharedErrors.unknownError, error: nil) + throw WalletServiceError.internalError(message: .adamant.sharedErrors.unknownError, error: nil) } - do { - _ = try await web3?.eth.send(raw: txEncoded) - } catch { - throw WalletServiceError.internalError(message: "Error: \(error.localizedDescription)", error: nil) - } + _ = try await ethApiService.requestWeb3 { web3 in + try await web3.eth.send(raw: txEncoded) + }.get() } } diff --git a/Adamant/Modules/Wallets/Ethereum/EthWalletService.swift b/Adamant/Modules/Wallets/Ethereum/EthWalletService.swift index 59dfb0d3e..c76bcf4e7 100644 --- a/Adamant/Modules/Wallets/Ethereum/EthWalletService.swift +++ b/Adamant/Modules/Wallets/Ethereum/EthWalletService.swift @@ -19,9 +19,7 @@ import CommonKit struct EthWalletStorage { let keystore: BIP32Keystore - func getWalet(with web3: Web3) -> EthWallet? { - web3.addKeystoreManager(KeystoreManager([keystore])) - + func getWallet() -> EthWallet? { guard let ethAddress = keystore.addresses?.first else { return nil } @@ -107,11 +105,11 @@ final class EthWalletService: WalletService { return increaseFeeService.isIncreaseFeeEnabled(for: tokenUnicID) } - private (set) var isDynamicFee: Bool = true - private (set) var transactionFee: Decimal = 0.0 - private (set) var gasPrice: BigUInt = 0 - private (set) var gasLimit: BigUInt = 0 - private (set) var isWarningGasPrice = false + @Atomic private(set) var isDynamicFee: Bool = true + @Atomic private(set) var transactionFee: Decimal = 0.0 + @Atomic private(set) var gasPrice: BigUInt = 0 + @Atomic private(set) var gasLimit: BigUInt = 0 + @Atomic private(set) var isWarningGasPrice = false static let transferGas: Decimal = 21000 static let kvsAddress = "eth:address" @@ -122,6 +120,7 @@ final class EthWalletService: WalletService { // MARK: - Dependencies weak var accountService: AccountService? var apiService: ApiService! + var ethApiService: EthApiService! var dialogService: DialogService! var increaseFeeService: IncreaseFeeService! @@ -137,28 +136,12 @@ final class EthWalletService: WalletService { // MARK: - Properties public static let transactionsListApiSubpath = "ethtxs" - - private var _ethNodeUrl: String? - private var _web3: Web3? - var web3: Web3? { - get async { - if _web3 != nil { - return _web3 - } - guard let url = _ethNodeUrl else { - return nil - } - - return await setupEthNode(with: url) - } - } - - private(set) var enabled = true - private var initialBalanceCheck = false - private var subscriptions = Set() + @Atomic private(set) var enabled = true + @Atomic private var initialBalanceCheck = false + @Atomic private var subscriptions = Set() // MARK: - State - private (set) var state: WalletServiceState = .notInitiated + @Atomic private(set) var state: WalletServiceState = .notInitiated private func setState(_ newState: WalletServiceState, silent: Bool = false) { guard newState != state else { @@ -168,19 +151,21 @@ final class EthWalletService: WalletService { state = newState if !silent { - NotificationCenter.default.post(name: serviceStateChanged, - object: self, - userInfo: [AdamantUserInfoKey.WalletService.walletState: state]) + NotificationCenter.default.post( + name: serviceStateChanged, + object: self, + userInfo: [AdamantUserInfoKey.WalletService.walletState: state] + ) } } - private (set) var ethWallet: EthWallet? - private var waletStorage: EthWalletStorage? + @Atomic private(set) var ethWallet: EthWallet? + @Atomic private var walletStorage: EthWalletStorage? var wallet: WalletAccount? { return ethWallet } // MARK: - Delayed KVS save - private var balanceObserver: NSObjectProtocol? + @Atomic private var balanceObserver: NSObjectProtocol? // MARK: - Logic init() { @@ -219,37 +204,13 @@ final class EthWalletService: WalletService { .store(in: &subscriptions) } - func initiateNetwork(apiUrl: String, completion: @escaping (WalletServiceSimpleResult) -> Void) { - Task { - self._ethNodeUrl = apiUrl - guard await self.setupEthNode(with: apiUrl) != nil else { - completion(.failure(error: WalletServiceError.networkError)) - return - } - } - } - - func setupEthNode(with apiUrl: String) async -> Web3? { - guard let url = URL(string: apiUrl), - let web3 = try? await Web3.new(url) else { - return nil - } - - self._web3 = web3 - - return web3 - } - func getWallet() async -> EthWallet? { if let wallet = ethWallet { return wallet } - guard let storage = waletStorage, - let web3 = await web3 - else { - return nil - } - return storage.getWalet(with: web3) + + guard let storage = walletStorage else { return nil } + return storage.getWallet() } func update() { @@ -346,49 +307,20 @@ final class EthWalletService: WalletService { } func getGasPrices() async throws -> BigUInt { - guard let web3 = await self.web3 else { - throw WalletServiceError.internalError(message: "Can't get web3 service", error: nil) - } - - do { - let price = try await web3.eth.gasPrice() - return price - } catch { - throw WalletServiceError.remoteServiceError( - message: error.localizedDescription - ) - } + try await ethApiService.requestWeb3 { web3 in + try await web3.eth.gasPrice() + }.get() } func getGasLimit(to address: EthereumAddress?) async throws -> BigUInt { - guard let web3 = await self.web3, - let ethWallet = ethWallet - else { - throw WalletServiceError.internalError(message: "Can't get web3 service", error: nil) - } - - do { - var transaction: CodableTransaction = .emptyTransaction - transaction.from = ethWallet.ethAddress - transaction.to = address ?? ethWallet.ethAddress - - let price = try await web3.eth.estimateGas(for: transaction) - return price - } catch { - throw WalletServiceError.remoteServiceError( - message: error.localizedDescription - ) - } - } - - private func buildUrl(url: URL, queryItems: [URLQueryItem]? = nil) throws -> URL { - guard var components = URLComponents(url: url, resolvingAgainstBaseURL: false) else { - throw AdamantApiService.InternalError.endpointBuildFailed - } - - components.queryItems = queryItems - - return try components.asURL() + guard let ethWallet = ethWallet else { throw WalletServiceError.internalError(.endpointBuildFailed) } + var transaction: CodableTransaction = .emptyTransaction + transaction.from = ethWallet.ethAddress + transaction.to = address ?? ethWallet.ethAddress + + return try await ethApiService.requestWeb3 { [transaction] web3 in + try await web3.eth.estimateGas(for: transaction) + }.get() } } @@ -418,14 +350,15 @@ extension EthWalletService: InitiatedWithPassphraseService { throw WalletServiceError.internalError(message: "ETH Wallet: failed to create Keystore", error: nil) } - waletStorage = .init(keystore: store) + walletStorage = .init(keystore: store) + ethApiService.keystoreManager = .init([store]) } catch { throw WalletServiceError.internalError(message: "ETH Wallet: failed to create Keystore", error: error) } - guard let web3 = await web3, - let eWallet = waletStorage?.getWalet(with: web3) - else { + let eWallet = walletStorage?.getWallet() + + guard let eWallet = eWallet else { throw WalletServiceError.internalError(message: "ETH Wallet: failed to create Keystore", error: nil) } @@ -527,6 +460,7 @@ extension EthWalletService: SwinjectDependentService { apiService = container.resolve(ApiService.self) dialogService = container.resolve(DialogService.self) increaseFeeService = container.resolve(IncreaseFeeService.self) + ethApiService = container.resolve(EthApiService.self) } } @@ -541,21 +475,16 @@ extension EthWalletService { } func getBalance(forAddress address: EthereumAddress) async throws -> Decimal { - guard let web3 = await self.web3 else { - throw WalletServiceError.internalError(message: "Can't get web3 service", error: nil) - } + let balance = try await ethApiService.requestWeb3 { web3 in + try await web3.eth.getBalance(for: address) + }.get() - do { - let balance = try await web3.eth.getBalance(for: address) - return balance.asDecimal(exponent: EthWalletService.currencyExponent) - } catch { - throw WalletServiceError.remoteServiceError(message: error.localizedDescription) - } + return balance.asDecimal(exponent: EthWalletService.currencyExponent) } func getWalletAddress(byAdamantAddress address: String) async throws -> String { do { - let result = try await apiService.get(key: EthWalletService.kvsAddress, sender: address) + let result = try await apiService.get(key: EthWalletService.kvsAddress, sender: address).get() guard let result = result else { throw WalletServiceError.walletNotInitiated @@ -588,14 +517,20 @@ extension EthWalletService { } Task { - await apiService.store(key: EthWalletService.kvsAddress, value: ethAddress, type: .keyValue, sender: adamant.address, keypair: keypair) { result in - switch result { - case .success: - completion(.success) - - case .failure(let error): - completion(.failure(error: .apiError(error))) - } + let result = await apiService.store( + key: EthWalletService.kvsAddress, + value: ethAddress, + type: .keyValue, + sender: adamant.address, + keypair: keypair + ) + + switch result { + case .success: + completion(.success) + + case .failure(let error): + completion(.failure(error: .apiError(error))) } } } @@ -605,31 +540,24 @@ extension EthWalletService { extension EthWalletService { func getTransaction(by hash: String) async throws -> EthTransaction { let sender = wallet?.address - guard let eth = await web3?.eth else { - throw WalletServiceError.internalError(message: "Failed to get transaction", error: nil) - } - - let isOutgoing: Bool - let details: Web3Core.TransactionDetails // MARK: 1. Transaction details - do { - details = try await eth.transactionDetails(hash) - - if let sender = sender { - isOutgoing = details.transaction.to.address != sender - } else { - isOutgoing = false - } - } catch let error as Web3Error { - throw error.asWalletServiceError() - } catch { - throw WalletServiceError.remoteServiceError(message: "Failed to get transaction") + let details = try await ethApiService.requestWeb3 { web3 in + try await web3.eth.transactionDetails(hash) + }.get() + + let isOutgoing: Bool + if let sender = sender { + isOutgoing = details.transaction.to.address != sender + } else { + isOutgoing = false } // MARK: 2. Transaction receipt do { - let receipt = try await eth.transactionReceipt(hash) + let receipt = try await ethApiService.requestWeb3 { web3 in + try await web3.eth.transactionReceipt(hash) + }.get() // MARK: 3. Check if transaction is delivered guard receipt.status == .ok, @@ -648,8 +576,14 @@ extension EthWalletService { } // MARK: 4. Block timestamp & confirmations - let currentBlock = try await eth.blockNumber() - let block = try await eth.block(by: receipt.blockHash) + let currentBlock = try await ethApiService.requestWeb3 { web3 in + try await web3.eth.blockNumber() + }.get() + + let block = try await ethApiService.requestWeb3 { web3 in + try await web3.eth.block(by: receipt.blockHash) + }.get() + let confirmations = currentBlock - blockNumber let transaction = details.transaction.asEthTransaction( @@ -680,68 +614,65 @@ extension EthWalletService { return transaction default: - throw error.asWalletServiceError() + throw error } + } catch _ as URLError { + throw WalletServiceError.networkError } catch { - throw WalletServiceError.remoteServiceError(message: "Failed to get transaction") + throw error } } - func getTransactionsHistory(address: String, offset: Int = 0, limit: Int = 100) async throws -> [EthTransactionShort] { - guard let node = EthWalletService.nodes.randomElement(), let url = node.asURL() else { - fatalError("Failed to build ETH endpoint URL") - } - - // Request + func getTransactionsHistory( + address: String, + offset: Int, + limit: Int = 100 + ) async throws -> [EthTransactionShort] { let columns = "time,txfrom,txto,gas,gasprice,block,txhash,value" let order = "time.desc" - // MARK: Request txFrom - let txFromQueryItems: [URLQueryItem] = [URLQueryItem(name: "select", value: columns), - URLQueryItem(name: "limit", value: String(limit)), - URLQueryItem(name: "txfrom", value: "eq.\(address)"), - URLQueryItem(name: "offset", value: String(offset)), - URLQueryItem(name: "order", value: order), - URLQueryItem(name: "contract_to", value: "eq.") + let txFromQueryParameters = [ + "select": columns, + "limit": String(limit), + "txfrom": "eq.\(address)", + "offset": String(offset), + "order": order, + "contract_to": "eq." ] - let txFromEndpoint: URL - do { - txFromEndpoint = try buildUrl(url: url.appendingPathComponent(EthWalletService.transactionsListApiSubpath), queryItems: txFromQueryItems) - } catch { - let err = AdamantApiService.InternalError.endpointBuildFailed.apiServiceErrorWith(error: error) - throw WalletServiceError.apiError(err) - } - - // MARK: Request txTo - let txToQueryItems: [URLQueryItem] = [URLQueryItem(name: "select", value: columns), - URLQueryItem(name: "limit", value: String(limit)), - URLQueryItem(name: "txto", value: "eq.\(address)"), - URLQueryItem(name: "offset", value: String(offset)), - URLQueryItem(name: "order", value: order), - URLQueryItem(name: "contract_to", value: "eq.") + let txToQueryParameters = [ + "select": columns, + "limit": String(limit), + "txto": "eq.\(address)", + "offset": String(offset), + "order": order, + "contract_to": "eq." ] - let txToEndpoint: URL - do { - txToEndpoint = try buildUrl(url: url.appendingPathComponent(EthWalletService.transactionsListApiSubpath), queryItems: txToQueryItems) - } catch { - let err = AdamantApiService.InternalError.endpointBuildFailed.apiServiceErrorWith(error: error) - throw WalletServiceError.apiError(err) - } - - // MARK: Sending requests - - var transactions = [EthTransactionShort]() - - let transactionsFrom: [EthTransactionShort] = try await apiService.sendRequest(url: txFromEndpoint, method: .get, parameters: nil) - transactions.append(contentsOf: transactionsFrom) - - let transactionsTo: [EthTransactionShort] = try await apiService.sendRequest(url: txToEndpoint, method: .get, parameters: nil) - transactions.append(contentsOf: transactionsTo) + let transactionsFrom: [EthTransactionShort] = try await ethApiService.requestApiCore { + core, node in + await core.sendRequestJson( + node: node, + path: EthWalletService.transactionsListApiSubpath, + method: .get, + parameters: txFromQueryParameters, + encoding: .url + ) + }.get() + + let transactionsTo: [EthTransactionShort] = try await ethApiService.requestApiCore { + core, node in + await core.sendRequestJson( + node: node, + path: EthWalletService.transactionsListApiSubpath, + method: .get, + parameters: txToQueryParameters, + encoding: .url + ) + }.get() - transactions.sort { $0.date.compare($1.date) == .orderedDescending } - return transactions + let transactions = transactionsFrom + transactionsTo + return transactions.sorted { $0.date.compare($1.date) == .orderedDescending } } } diff --git a/Adamant/Modules/Wallets/Lisk/LskApiCore.swift b/Adamant/Modules/Wallets/Lisk/LskApiCore.swift new file mode 100644 index 000000000..b1a9d8c5f --- /dev/null +++ b/Adamant/Modules/Wallets/Lisk/LskApiCore.swift @@ -0,0 +1,71 @@ +// +// LskApiCore.swift +// Adamant +// +// Created by Andrew G on 13.11.2023. +// Copyright © 2023 Adamant. All rights reserved. +// + +import CommonKit +import Foundation +import LiskKit + +class LskApiCore: BlockchainHealthCheckableService { + func makeClient(node: CommonKit.Node) -> APIClient { + .init(options: .init( + nodes: [.init(origin: node.asString())], + nethash: .mainnet, + randomNode: false + )) + } + + func request( + node: CommonKit.Node, + body: @escaping @Sendable ( + _ client: APIClient, + _ completion: @escaping @Sendable (LiskKit.Result) -> Void + ) -> Void + ) async -> WalletServiceResult { + await withCheckedContinuation { continuation in + body(makeClient(node: node)) { result in + continuation.resume(returning: result.asWalletServiceResult()) + } + } + } + + func getStatusInfo(node: CommonKit.Node) async -> WalletServiceResult { + let startTimestamp = Date.now.timeIntervalSince1970 + + return await request(node: node) { client, completion in + LiskKit.Node(client: client).info { completion($0) } + }.map { model in + .init( + ping: Date.now.timeIntervalSince1970 - startTimestamp, + height: model.data.height ?? .zero, + wsEnabled: false, + wsPort: nil, + version: nil + ) + } + } +} + +private extension LiskKit.Result { + func asWalletServiceResult() -> WalletServiceResult { + switch self { + case let .success(response): + return .success(response) + case let .error(error): + return .failure(mapError(error)) + } + } +} + +private func mapError(_ error: APIError) -> WalletServiceError { + switch error { + case .noNetwork: + return .networkError + default: + return .remoteServiceError(message: error.message) + } +} diff --git a/Adamant/Modules/Wallets/Lisk/LskNodeApiService.swift b/Adamant/Modules/Wallets/Lisk/LskNodeApiService.swift new file mode 100644 index 000000000..64104fd49 --- /dev/null +++ b/Adamant/Modules/Wallets/Lisk/LskNodeApiService.swift @@ -0,0 +1,69 @@ +// +// LskNodeApiService.swift +// Adamant +// +// Created by Andrew G on 17.11.2023. +// Copyright © 2023 Adamant. All rights reserved. +// + +import LiskKit + +final class LskNodeApiService { + let api: BlockchainHealthCheckWrapper + + init(api: BlockchainHealthCheckWrapper) { + self.api = api + } + + func requestNodeApi( + body: @escaping @Sendable ( + _ api: LiskKit.Node, + _ completion: @escaping @Sendable (LiskKit.Result) -> Void + ) -> Void + ) async -> WalletServiceResult { + await requestClient { client, completion in + body(.init(client: client), completion) + } + } + + func requestTransactionsApi( + body: @escaping @Sendable ( + _ api: Transactions, + _ completion: @escaping @Sendable (LiskKit.Result) -> Void + ) -> Void + ) async -> WalletServiceResult { + await requestClient { client, completion in + body(.init(client: client), completion) + } + } + + func requestAccountsApi( + body: @escaping @Sendable ( + _ api: Accounts, + _ completion: @escaping @Sendable (LiskKit.Result) -> Void + ) -> Void + ) async -> WalletServiceResult { + await requestClient { client, completion in + body(.init(client: client), completion) + } + } + + func getStatusInfo() async -> WalletServiceResult { + await api.request { core, node in + await core.getStatusInfo(node: node) + } + } +} + +private extension LskNodeApiService { + func requestClient( + body: @escaping @Sendable ( + _ client: APIClient, + _ completion: @escaping @Sendable (LiskKit.Result) -> Void + ) -> Void + ) async -> WalletServiceResult { + await api.request { core, node in + await core.request(node: node, body: body) + } + } +} diff --git a/Adamant/Modules/Wallets/Lisk/LskServiceApiService.swift b/Adamant/Modules/Wallets/Lisk/LskServiceApiService.swift new file mode 100644 index 000000000..255abad2b --- /dev/null +++ b/Adamant/Modules/Wallets/Lisk/LskServiceApiService.swift @@ -0,0 +1,63 @@ +// +// LskServiceApiService.swift +// Adamant +// +// Created by Andrew G on 17.11.2023. +// Copyright © 2023 Adamant. All rights reserved. +// + +import LiskKit +import Foundation +import CommonKit + +final class LskServiceApiCore: LskApiCore { + override func getStatusInfo( + node: CommonKit.Node + ) async -> WalletServiceResult { + let startTimestamp = Date.now.timeIntervalSince1970 + + return await request(node: node) { client, completion in + LiskKit.Service(client: client).getFees { completion($0) } + }.map { model in + .init( + ping: Date.now.timeIntervalSince1970 - startTimestamp, + height: .init(model.meta.lastBlockHeight), + wsEnabled: false, + wsPort: nil, + version: nil + ) + } + } +} + +final class LskServiceApiService { + let api: BlockchainHealthCheckWrapper + + init(api: BlockchainHealthCheckWrapper) { + self.api = api + } + + func requestServiceApi( + body: @escaping @Sendable ( + _ api: LiskKit.Service, + _ completion: @escaping @Sendable (LiskKit.Result) -> Void + ) -> Void + ) async -> WalletServiceResult { + await requestClient { client, completion in + body(.init(client: client, version: .v2), completion) + } + } +} + +private extension LskServiceApiService { + func requestClient( + body: @escaping @Sendable ( + _ client: APIClient, + _ completion: @escaping @Sendable (LiskKit.Result) -> Void + ) -> Void + ) async -> WalletServiceResult { + await api.request { core, node in + await core.request(node: node, body: body) + } + } +} diff --git a/Adamant/Modules/Wallets/Lisk/LskWalletService+Send.swift b/Adamant/Modules/Wallets/Lisk/LskWalletService+Send.swift index 9ebe95013..7e8ba319a 100644 --- a/Adamant/Modules/Wallets/Lisk/LskWalletService+Send.swift +++ b/Adamant/Modules/Wallets/Lisk/LskWalletService+Send.swift @@ -51,15 +51,8 @@ extension LskWalletService: WalletServiceTwoStepSend { } func sendTransaction(_ transaction: TransactionEntity) async throws { - _ = try await withUnsafeThrowingContinuation { (continuation: UnsafeContinuation) in - transactionApi.submit(signedTransaction: transaction.requestOptions) { response in - switch response { - case .success: - continuation.resume() - case .error(let error): - continuation.resume(throwing: WalletServiceError.internalError(message: error.message, error: nil)) - } - } - } + _ = try await lskNodeApiService.requestTransactionsApi { api, completion in + api.submit(signedTransaction: transaction.requestOptions, completionHandler: completion) + }.get() } } diff --git a/Adamant/Modules/Wallets/Lisk/LskWalletService.swift b/Adamant/Modules/Wallets/Lisk/LskWalletService.swift index 5f870517b..2dc18c22f 100644 --- a/Adamant/Modules/Wallets/Lisk/LskWalletService.swift +++ b/Adamant/Modules/Wallets/Lisk/LskWalletService.swift @@ -31,23 +31,25 @@ final class LskWalletService: WalletService { // MARK: - Dependencies var apiService: ApiService! + var lskNodeApiService: LskNodeApiService! + var lskServiceApiService: LskServiceApiService! var accountService: AccountService! var dialogService: DialogService! // MARK: - Constants var transactionFee: Decimal { - return transactionFeeRaw.asDecimal(exponent: LskWalletService.currencyExponent) + transactionFeeRaw.asDecimal(exponent: LskWalletService.currencyExponent) } - var transactionFeeRaw: BigUInt = BigUInt(integerLiteral: 141000) - private (set) var enabled = true - private (set) var isWarningGasPrice = false - static var currencyLogo = UIImage.asset(named: "lisk_wallet") ?? .init() + @Atomic var transactionFeeRaw: BigUInt = BigUInt(integerLiteral: 141000) + @Atomic private(set) var enabled = true + @Atomic private(set) var isWarningGasPrice = false + static let currencyLogo = UIImage.asset(named: "lisk_wallet") ?? .init() static let kvsAddress = "lsk:address" static let defaultFee: BigUInt = 141000 - var lastHeight: UInt64 = 0 + @Atomic var lastHeight: UInt64 = .zero var tokenSymbol: String { return type(of: self).currencySymbol @@ -79,24 +81,20 @@ final class LskWalletService: WalletService { // MARK: - Properties let transferAvailable: Bool = true - private var initialBalanceCheck = false + @Atomic private var initialBalanceCheck = false + @Atomic var netHash: String = "" + @Atomic private(set) var lskWallet: LskWallet? - internal var accountApi: Accounts! - internal var transactionApi: Transactions! - internal var serviceApi: Service! - internal var nodeApi: LiskKit.Node! - internal var netHash: String = "" + let defaultDispatchQueue = DispatchQueue( + label: "im.adamant.lskWalletService", + qos: .utility, + attributes: [.concurrent] + ) - private (set) var lskWallet: LskWallet? - - let defaultDispatchQueue = DispatchQueue(label: "im.adamant.lskWalletService", qos: .utility, attributes: [.concurrent]) - - private let mainnet: Bool - private let nodes: [APINode] - private var subscriptions = Set() + @Atomic private var subscriptions = Set() // MARK: - State - private (set) var state: WalletServiceState = .notInitiated + @Atomic private (set) var state: WalletServiceState = .notInitiated private func setState(_ newState: WalletServiceState, silent: Bool = false) { guard newState != state else { @@ -106,36 +104,18 @@ final class LskWalletService: WalletService { state = newState if !silent { - NotificationCenter.default.post(name: serviceStateChanged, - object: self, - userInfo: [AdamantUserInfoKey.WalletService.walletState: state]) + NotificationCenter.default.post( + name: serviceStateChanged, + object: self, + userInfo: [AdamantUserInfoKey.WalletService.walletState: state] + ) } } // MARK: - Delayed KVS save - private var balanceObserver: NSObjectProtocol? - - // MARK: - Logic - convenience init(mainnet: Bool = true) { - let nodes = mainnet ? APIOptions.mainnet.nodes : APIOptions.testnet.nodes - let serviceNode = mainnet ? APIOptions.Service.mainnet.nodes : APIOptions.Service.testnet.nodes - self.init(mainnet: mainnet, nodes: nodes, serviceNode: serviceNode) - } + @Atomic private var balanceObserver: NSObjectProtocol? - convenience init(mainnet: Bool, nodes: [CommonKit.Node], services: [CommonKit.Node]) { - self.init(mainnet: mainnet, nodes: nodes.map { APINode(origin: $0.asString()) }, serviceNode: services.map { APINode(origin: $0.asString()) }) - } - - init(mainnet: Bool, nodes: [APINode], serviceNode: [APINode]) { - self.mainnet = mainnet - self.nodes = nodes - - let client = APIClient(options: APIOptions(nodes: serviceNode, nethash: mainnet ? .mainnet : .testnet, randomNode: true)) - self.serviceApi = Service(client: client, version: .v2) - - setupApi() - - // Notifications + init() { addObservers() } @@ -214,7 +194,11 @@ final class LskWalletService: WalletService { } if let notification = notification { - NotificationCenter.default.post(name: notification, object: self, userInfo: [AdamantUserInfoKey.WalletService.wallet: wallet]) + NotificationCenter.default.post( + name: notification, + object: self, + userInfo: [AdamantUserInfoKey.WalletService.wallet: wallet] + ) } } @@ -247,105 +231,26 @@ final class LskWalletService: WalletService { throw WalletServiceError.notLogged } - return try await withUnsafeThrowingContinuation { (continuation: UnsafeContinuation<(fee: BigUInt, lastHeight: UInt64), Error>) in - serviceApi.getFees { result in - switch result { - case .success(response: let value): - let tempTransaction = TransactionEntity( - amount: 100000000.0, - fee: 0.00141, - nonce: wallet.nounce, - senderPublicKey: wallet.keyPair.publicKeyString, - recipientAddressBase32: wallet.address, - recipientAddressBinary: wallet.binaryAddress - ).signed( - with: wallet.keyPair, - for: self.netHash - ) - - let feeValue = tempTransaction.getFee(with: value.data.minFeePerByte) - let fee = BigUInt(feeValue) - - continuation.resume(returning: (fee: fee, lastHeight: value.meta.lastBlockHeight)) - case .error(response: let error): - continuation.resume( - throwing: WalletServiceError.remoteServiceError( - message: error.message - ) - ) - } - } - } - } -} - -// MARK: - Nodes -extension LskWalletService { - private func initiateNodes(completion: @escaping (Bool) -> Void) { - if nodes.count > 0 { - netHash = Constants.Nethash.main - let client = APIClient(options: APIOptions(nodes: nodes, nethash: APINethash.mainnet, randomNode: true)) - self.accountApi = Accounts(client: client) - self.transactionApi = Transactions(client: client) - self.nodeApi = LiskKit.Node(client: client) - completion(true) - } else { - self.accountApi = nil - self.transactionApi = nil - self.nodeApi = nil - self.serviceApi = nil - completion(false) - } - } - - private func getAliveNodes(from nodes: [APINode], timeout: TimeInterval, completion: @escaping ([APINode]) -> Void) { - let group = DispatchGroup() - var aliveNodes = [APINode]() + let value = try await lskServiceApiService.requestServiceApi { api, completion in + api.getFees(completionHandler: completion) + }.get() - for node in nodes { - if let url = URL(string: "\(node.origin)/api/node/status") { - - var request = URLRequest(url: url) - request.httpMethod = "GET" - request.timeoutInterval = timeout - - group.enter() // Enter 1 - - AF.request(request).responseData { response in - defer { group.leave() } // Leave 1 - - switch response.result { - case .success: - aliveNodes.append(node) - - case .failure: - break - } - } - } - } + let tempTransaction = TransactionEntity( + amount: 100000000.0, + fee: 0.00141, + nonce: wallet.nounce, + senderPublicKey: wallet.keyPair.publicKeyString, + recipientAddressBase32: wallet.address, + recipientAddressBinary: wallet.binaryAddress + ).signed( + with: wallet.keyPair, + for: self.netHash + ) - group.notify(queue: defaultDispatchQueue) { - completion(aliveNodes) - } - } - - func setupApi() { - if mainnet { - let group = DispatchGroup() - group.enter() - - initiateNodes { _ in - group.leave() - } - - group.wait() - } else { - netHash = Constants.Nethash.test - accountApi = Accounts(client: .testnet) - transactionApi = Transactions(client: .testnet) - nodeApi = LiskKit.Node(client: .testnet) - } + let feeValue = tempTransaction.getFee(with: value.data.minFeePerByte) + let fee = BigUInt(feeValue) + + return (fee: fee, lastHeight: value.meta.lastBlockHeight) } } @@ -475,6 +380,8 @@ extension LskWalletService: SwinjectDependentService { accountService = container.resolve(AccountService.self) apiService = container.resolve(ApiService.self) dialogService = container.resolve(DialogService.self) + lskServiceApiService = container.resolve(LskServiceApiService.self) + lskNodeApiService = container.resolve(LskNodeApiService.self) } } @@ -489,70 +396,59 @@ extension LskWalletService { } func getBalance(address: String) async throws -> Decimal { - guard - let accountApi = accountApi, - let address = LiskKit.Crypto.getBinaryAddressFromBase32(address) - else { + guard let address = LiskKit.Crypto.getBinaryAddressFromBase32(address) else { throw WalletServiceError.notLogged } - return try await withUnsafeThrowingContinuation { (continuation: UnsafeContinuation) in - accountApi.accounts(address: address) { [weak lskWallet] response in - switch response { - case .success(response: let response): - if lskWallet?.binaryAddress == address { - lskWallet?.nounce = response.data.nonce - } - - let balance = BigUInt(response.data.balance ?? "0") ?? BigUInt(0) - continuation.resume( - returning: balance.asDecimal( - exponent: LskWalletService.currencyExponent - ) - ) - - case .error(response: let error): - if error == .noNetwork { - continuation.resume(throwing: WalletServiceError.networkError) - } else if error.code == 404 { - continuation.resume(returning: .zero) - } else { - continuation.resume( - throwing: WalletServiceError.remoteServiceError( - message: error.message - ) - ) - } - } + let result = await lskNodeApiService.requestAccountsApi { api, completion in + api.accounts(address: address, completionHandler: completion) + } + + switch result { + case let .success(response): + if lskWallet?.binaryAddress == address { + lskWallet?.nounce = response.data.nonce } + + let balance = BigUInt(response.data.balance ?? "0") ?? BigUInt(0) + return balance.asDecimal(exponent: LskWalletService.currencyExponent) + case let .failure(error): + guard + case let .remoteServiceError(_, lskError) = error, + let lskError = lskError as? APIError, + lskError.code == 404 + else { throw error } + + return .zero } } func handleAccountSuccess(with balance: String?, completion: @escaping (WalletServiceResult) -> Void) { let balance = BigUInt(balance ?? "0") ?? BigUInt(0) - completion(.success(result: balance.asDecimal(exponent: LskWalletService.currencyExponent))) + completion(.success(balance.asDecimal(exponent: LskWalletService.currencyExponent))) } func handleAccountError(with error: APIError, completion: @escaping (WalletServiceResult) -> Void) { if error == .noNetwork { - completion(.failure(error: .networkError)) + completion(.failure(.networkError)) } else { - completion(.failure(error: .remoteServiceError(message: error.message))) + completion(.failure(.remoteServiceError(message: error.message))) } } func getLskAddress(byAdamandAddress address: String, completion: @escaping (ApiServiceResult) -> Void) { Task { - await apiService.get( + let result = await apiService.get( key: LskWalletService.kvsAddress, - sender: address, - completion: completion + sender: address ) + + completion(result) } } func getWalletAddress(byAdamantAddress address: String) async throws -> String { do { - let result = try await apiService.get(key: LskWalletService.kvsAddress, sender: address) + let result = try await apiService.get(key: LskWalletService.kvsAddress, sender: address).get() guard let result = result else { throw WalletServiceError.walletNotInitiated @@ -584,20 +480,20 @@ extension LskWalletService { } Task { - await apiService.store( + let result = await apiService.store( key: LskWalletService.kvsAddress, value: lskAddress, type: .keyValue, sender: adamant.address, keypair: keypair - ) { result in - switch result { - case .success: - completion(.success) - - case .failure(let error): - completion(.failure(error: .apiError(error))) - } + ) + + switch result { + case .success: + completion(.success) + + case .failure(let error): + completion(.failure(error: .apiError(error))) } } } @@ -606,28 +502,19 @@ extension LskWalletService { // MARK: - Transactions extension LskWalletService { func getTransactions(offset: UInt) async throws -> [Transactions.TransactionModel] { - guard let address = self.lskWallet?.address, - let transactionApi = serviceApi - else { + guard let address = self.lskWallet?.address else { throw WalletServiceError.internalError(message: "LSK Wallet: not found", error: nil) } - return try await withUnsafeThrowingContinuation { (continuation: UnsafeContinuation<[Transactions.TransactionModel], Error>) in - transactionApi.transactions( + return try await lskServiceApiService.requestServiceApi { api, completion in + api.transactions( senderIdOrRecipientId: address, limit: 100, offset: offset, - sort: APIRequest.Sort("timestamp", direction: .descending) - ) { (response) in - switch response { - case .success(response: let result): - continuation.resume(returning: result) - - case .error(response: let error): - continuation.resume(throwing: WalletServiceError.remoteServiceError(message: error.message)) - } - } - } + sort: APIRequest.Sort("timestamp", direction: .descending), + completionHandler: completion + ) + }.get() } func getTransaction(by hash: String) async throws -> Transactions.TransactionModel { @@ -635,34 +522,14 @@ extension LskWalletService { throw ApiServiceError.internalError(message: "No hash", error: nil) } - guard let api = serviceApi else { - throw ApiServiceError.internalError(message: "Problem with accessing LSK nodes, try later", error: nil) - } + let result = try await lskServiceApiService.requestServiceApi { api, completion in + api.transactions(id: hash, limit: 1, offset: 0, completionHandler: completion) + }.get() - return try await withUnsafeThrowingContinuation { (continuation: UnsafeContinuation) in - api.transactions(id: hash, limit: 1, offset: 0) { (response) in - switch response { - case .success(response: let result): - if let transaction = result.first { - continuation.resume(returning: transaction) - } else { - continuation.resume(throwing: WalletServiceError.remoteServiceError(message: "No transaction") - ) - } - case .error(response: let error): - if error == .noNetwork { - continuation.resume( - throwing: ApiServiceError.networkError(error: error) - ) - } else { - continuation.resume( - throwing: WalletServiceError.remoteServiceError( - message: error.message - ) - ) - } - } - } + if let transaction = result.first { + return transaction + } else { + throw WalletServiceError.remoteServiceError(message: "No transaction") } } } diff --git a/Adamant/Modules/Wallets/WalletService.swift b/Adamant/Modules/Wallets/WalletService.swift index a576e6d93..e3260f42e 100644 --- a/Adamant/Modules/Wallets/WalletService.swift +++ b/Adamant/Modules/Wallets/WalletService.swift @@ -20,10 +20,7 @@ enum WalletServiceSimpleResult { case failure(error: WalletServiceError) } -enum WalletServiceResult { - case success(result: T) - case failure(error: WalletServiceError) -} +typealias WalletServiceResult = Result // MARK: - Errors @@ -34,7 +31,7 @@ enum WalletServiceError: Error { case accountNotFound case walletNotInitiated case invalidAmount(Decimal) - case remoteServiceError(message: String) + case remoteServiceError(message: String, error: Error?) case apiError(ApiServiceError) case internalError(message: String, error: Error?) case transactionNotFound(reason: String) @@ -60,7 +57,7 @@ extension WalletServiceError: RichError { case .walletNotInitiated: return .localized("WalletServices.SharedErrors.WalletNotInitiated", comment: "Wallet Services: Shared error, user has not yet initiated a specific wallet.") - case .remoteServiceError(let message): + case .remoteServiceError(let message, let error): return String.adamant.sharedErrors.remoteServerError(message: message) case .apiError(let error): @@ -104,6 +101,41 @@ extension WalletServiceError: RichError { return error.level } } + + static func internalError(_ error: InternalAPIError) -> Self { + .internalError(message: error.localizedDescription, error: error) + } + + static func remoteServiceError(message: String? = nil, error: Error? = nil) -> Self { + .remoteServiceError( + message: message ?? error?.localizedDescription ?? .empty, + error: error + ) + } +} + +extension WalletServiceError: HealthCheckableError { + var isNetworkError: Bool { + switch self { + case .networkError: + return true + default: + return false + } + } + + var isRequestCancelledError: Bool { + switch self { + case .requestCancelled: + return true + default: + return false + } + } + + static var noEndpointsError: WalletServiceError { + .apiError(.noEndpointsError) + } } extension ApiServiceError { @@ -121,7 +153,7 @@ extension ApiServiceError { case .requestCancelled: return .requestCancelled - case .serverError, .internalError, .commonError: + case .serverError, .internalError, .commonError, .noEndpointsAvailable: return .apiError(self) } } diff --git a/Adamant/ServiceProtocols/APICoreProtocol.swift b/Adamant/ServiceProtocols/APICoreProtocol.swift new file mode 100644 index 000000000..f8e283b3c --- /dev/null +++ b/Adamant/ServiceProtocols/APICoreProtocol.swift @@ -0,0 +1,96 @@ +// +// APICoreProtocol.swift +// Adamant +// +// Created by Andrew G on 30.10.2023. +// Copyright © 2023 Adamant. All rights reserved. +// + +import Foundation +import Alamofire +import CommonKit +import UIKit + +enum ApiCommands {} + +protocol APICoreProtocol: Actor { + func sendRequestBasic( + node: Node, + path: String, + method: HTTPMethod, + parameters: Parameters, + encoding: APIParametersEncoding + ) async -> APIResponseModel +} + +extension APICoreProtocol { + func sendRequest( + node: Node, + path: String, + method: HTTPMethod, + parameters: Parameters, + encoding: APIParametersEncoding + ) async -> ApiServiceResult { + await sendRequestBasic( + node: node, + path: path, + method: method, + parameters: parameters, + encoding: encoding + ).result + } + + func sendRequestJson( + node: Node, + path: String, + method: HTTPMethod, + parameters: Parameters, + encoding: APIParametersEncoding + ) async -> ApiServiceResult { + switch await sendRequest( + node: node, + path: path, + method: method, + parameters: parameters, + encoding: encoding + ) { + case let .success(data): + do { + let output = try JSONDecoder().decode(JSONOutput.self, from: data) + return .success(output) + } catch { + return .failure(.internalError(error: InternalAPIError.parsingFailed)) + } + case let .failure(error): + return .failure(error) + } + } + + func sendRequestJson( + node: Node, + path: String + ) async -> ApiServiceResult { + await sendRequestJson( + node: node, + path: path, + method: .get, + parameters: emptyParameters, + encoding: .url + ) + } + + func sendRequest( + node: Node, + path: String + ) async -> ApiServiceResult { + await sendRequest( + node: node, + path: path, + method: .get, + parameters: emptyParameters, + encoding: .url + ) + } +} + +private let emptyParameters: [String: Bool] = [:] diff --git a/Adamant/ServiceProtocols/AdamantCore/AdamantCore+Extensions.swift b/Adamant/ServiceProtocols/AdamantCore/AdamantCore+Extensions.swift index 7adee88c5..46a1f2d69 100644 --- a/Adamant/ServiceProtocols/AdamantCore/AdamantCore+Extensions.swift +++ b/Adamant/ServiceProtocols/AdamantCore/AdamantCore+Extensions.swift @@ -30,4 +30,38 @@ extension AdamantCore { asset: transaction.asset ) } + + func makeSendMessageTransaction( + senderId: String, + recipientId: String, + keypair: Keypair, + message: String, + type: ChatType, + nonce: String, + amount: Decimal? + ) throws -> UnregisteredTransaction { + let normalizedTransaction = NormalizedTransaction( + type: .chatMessage, + amount: amount ?? .zero, + senderPublicKey: keypair.publicKey, + requesterPublicKey: nil, + date: Date(), + recipientId: recipientId, + asset: TransactionAsset( + chat: ChatAsset(message: message, ownMessage: nonce, type: type), + state: nil, + votes: nil + ) + ) + + guard let transaction = makeSignedTransaction( + transaction: normalizedTransaction, + senderId: senderId, + keypair: keypair + ) else { + throw InternalAPIError.signTransactionFailed + } + + return transaction + } } diff --git a/Adamant/ServiceProtocols/ApiService.swift b/Adamant/ServiceProtocols/ApiService.swift index 60c72224b..a887e253e 100644 --- a/Adamant/ServiceProtocols/ApiService.swift +++ b/Adamant/ServiceProtocols/ApiService.swift @@ -10,91 +10,23 @@ import Foundation import Alamofire import CommonKit -// MARK: - Notifications -extension Notification.Name { - enum ApiService { - static let currentNodeUpdate = Notification.Name("adamant.apiService.currentNodeUpdate") - } -} - -// - MARK: ApiService -protocol ApiService: Actor { - /// Time interval between node (lhs) and client (rhs) - /// Substract this from client time to get server time - var lastRequestTimeDelta: TimeInterval? { get } - - var currentNodes: [Node] { get } - - // MARK: - Async/Await - - func sendRequest( - url: URLConvertible, - method: HTTPMethod, - parameters: Parameters? - ) async throws -> Output - - func sendRequest( - url: URLConvertible, - method: HTTPMethod, - parameters: Parameters?, - encoding: ParameterEncoding - ) async throws -> Output - - func sendRequest( - url: URLConvertible, - method: HTTPMethod, - parameters: Parameters? - ) async throws -> Data - - func sendRequest( - url: URLConvertible, - method: HTTPMethod, - parameters: Parameters?, - encoding: ParameterEncoding - ) async throws -> Data - - func sendRequest(request: DataRequest) async throws -> Data - - // MARK: - Peers - - func getNodeVersion(url: URL, completion: @escaping (ApiServiceResult) -> Void) - - // MARK: - Status - - @discardableResult - func getNodeStatus( - url: URL, - completion: @escaping (ApiServiceResult) -> Void - ) -> DataRequest? +protocol ApiService { + var preferredNodeIds: [UUID] { get } // MARK: - Accounts - - func getAccount(byPassphrase passphrase: String, completion: @escaping (ApiServiceResult) -> Void) - func getAccount(byPublicKey publicKey: String, completion: @escaping (ApiServiceResult) -> Void) - - func getAccount(byPublicKey publicKey: String) async throws -> AdamantAccount - - func getAccount( - byAddress address: String, - completion: @escaping (ApiServiceResult) -> Void - ) - - func getAccount(byAddress address: String) async throws -> AdamantAccount + func healthCheck() + func getAccount(byPassphrase passphrase: String) async -> ApiServiceResult + func getAccount(byPublicKey publicKey: String) async -> ApiServiceResult + func getAccount(byAddress address: String) async -> ApiServiceResult // MARK: - Keys - func getPublicKey( - byAddress address: String, - completion: @escaping (ApiServiceResult) -> Void - ) + func getPublicKey(byAddress address: String) async -> ApiServiceResult // MARK: - Transactions - func getTransaction(id: UInt64, completion: @escaping (ApiServiceResult) -> Void) - - func getTransaction(id: UInt64) async throws -> Transaction - - func getTransaction(id: UInt64, withAsset: Bool) async throws -> Transaction + func getTransaction(id: UInt64) async -> ApiServiceResult + func getTransaction(id: UInt64, withAsset: Bool) async -> ApiServiceResult func getTransactions( forAccount: String, @@ -102,7 +34,7 @@ protocol ApiService: Actor { fromHeight: Int64?, offset: Int?, limit: Int? - ) async throws -> [Transaction] + ) async -> ApiServiceResult<[Transaction]> func getTransactions( forAccount account: String, @@ -111,57 +43,33 @@ protocol ApiService: Actor { offset: Int?, limit: Int?, orderByTime: Bool? - ) async throws -> [Transaction] + ) async -> ApiServiceResult<[Transaction]> // MARK: - Chats Rooms - - func getChatRooms( - address: String, - offset: Int?, - completion: @escaping (ApiServiceResult) -> Void - ) func getChatRooms( address: String, offset: Int? - ) async throws -> ChatRooms + ) async -> ApiServiceResult func getChatMessages( address: String, addressRecipient: String, offset: Int?, limit: Int? - ) async throws -> ChatRooms + ) async -> ApiServiceResult // MARK: - Funds - func transferFunds( - sender: String, - recipient: String, - amount: Decimal, - keypair: Keypair, - completion: @escaping (ApiServiceResult) -> Void - ) - func transferFunds( sender: String, recipient: String, amount: Decimal, keypair: Keypair - ) async throws -> UInt64 + ) async -> ApiServiceResult // MARK: - States - /// - Returns: Transaction ID - func store( - key: String, - value: String, - type: StateType, - sender: String, - keypair: Keypair, - completion: @escaping (ApiServiceResult) -> Void - ) - /// - Returns: Transaction ID func store( key: String, @@ -169,97 +77,54 @@ protocol ApiService: Actor { type: StateType, sender: String, keypair: Keypair - ) async throws -> UInt64 - - func get(key: String, sender: String, completion: @escaping (ApiServiceResult) -> Void) + ) async -> ApiServiceResult func get( key: String, sender: String - ) async throws -> String? + ) async -> ApiServiceResult // MARK: - Chats - /// Get chat transactions (type 8) - /// - /// - Parameters: - /// - address: Transactions for specified account - /// - height: From this height. Minimal value is 1. func getMessageTransactions( address: String, height: Int64?, - offset: Int?, - completion: @escaping (ApiServiceResult<[Transaction]>) -> Void - ) - - func getMessageTransactions(address: String, - height: Int64?, - offset: Int? - ) async throws -> [Transaction] - - /// Send text message - /// - completion: Contains processed transactionId, if success, or AdamantError, if fails. - /// - Returns: Signed unregistered transaction - @discardableResult - func sendMessage( - senderId: String, - recipientId: String, - keypair: Keypair, - message: String, - type: ChatType, - nonce: String, - amount: Decimal?, - completion: @escaping (ApiServiceResult) -> Void - ) -> UnregisteredTransaction? + offset: Int? + ) async -> ApiServiceResult<[Transaction]> func sendTransaction( path: String, - transaction: UnregisteredTransaction, - completion: @escaping (ApiServiceResult) -> Void - ) - - func createSendTransaction( - senderId: String, - recipientId: String, - keypair: Keypair, - message: String, - type: ChatType, - nonce: String, - amount: Decimal? - ) -> UnregisteredTransaction? + transaction: UnregisteredTransaction + ) async -> ApiServiceResult - func sendTransaction( + func sendMessageTransaction( transaction: UnregisteredTransaction - ) async throws -> UInt64 + ) async -> ApiServiceResult // MARK: - Delegates /// Get delegates - func getDelegates(limit: Int, completion: @escaping (ApiServiceResult<[Delegate]>) -> Void) + func getDelegates(limit: Int) async -> ApiServiceResult<[Delegate]> func getDelegatesWithVotes( for address: String, - limit: Int, - completion: @escaping (ApiServiceResult<[Delegate]>) -> Void - ) + limit: Int + ) async -> ApiServiceResult<[Delegate]> /// Get delegate forge details func getForgedByAccount( - publicKey: String, - completion: @escaping (ApiServiceResult) -> Void - ) + publicKey: String + ) async -> ApiServiceResult /// Get delegate forgeing time func getForgingTime( - for delegate: Delegate, - completion: @escaping (ApiServiceResult) -> Void - ) + for delegate: Delegate + ) async -> ApiServiceResult /// Send vote transaction for delegates func voteForDelegates( from address: String, keypair: Keypair, - votes: [DelegateVote], - completion: @escaping (ApiServiceResult) -> Void - ) + votes: [DelegateVote] + ) async -> ApiServiceResult } diff --git a/Adamant/ServiceProtocols/DataProviders/AccountsProvider.swift b/Adamant/ServiceProtocols/DataProviders/AccountsProvider.swift index 0146a2ff6..d346ba60f 100644 --- a/Adamant/ServiceProtocols/DataProviders/AccountsProvider.swift +++ b/Adamant/ServiceProtocols/DataProviders/AccountsProvider.swift @@ -105,6 +105,15 @@ extension AdamantContacts { } } + var welcomeMessage: SystemMessage? { + if + let regionCode = Locale.current.regionCode, + prohibitedRegions.contains(regionCode) + { return nil } + + return messages["chats.welcome_message"] + } + var messages: [String: SystemMessage] { switch self { case .adamantBountyWallet, .adamantNewBountyWallet: @@ -168,4 +177,13 @@ extension AdamantContacts { )] } } + + var prohibitedRegions: [String] { + switch self { + case .adelina: + return ["CN"] + case .adamantBountyWallet, .adamantNewBountyWallet, .adamantExchange, .betOnBitcoin, .donate, .pwaBountyBot, .adamantIco, .adamantWelcomeWallet, .adamantSupport: + return [] + } + } } diff --git a/Adamant/ServiceProtocols/HealthCheckService.swift b/Adamant/ServiceProtocols/HealthCheckService.swift deleted file mode 100644 index 260df5bcc..000000000 --- a/Adamant/ServiceProtocols/HealthCheckService.swift +++ /dev/null @@ -1,21 +0,0 @@ -// -// HealthCheckService.swift -// Adamant -// -// Created by Андрей on 06.06.2022. -// Copyright © 2022 Adamant. All rights reserved. -// - -import Foundation -import CommonKit - -protocol HealthCheckDelegate: AnyObject { - func healthCheckUpdate() -} - -protocol HealthCheckService: AnyObject { - var nodes: [Node] { get set } - var delegate: HealthCheckDelegate? { get set } - - func healthCheck() -} diff --git a/Adamant/ServiceProtocols/LskApiService.swift b/Adamant/ServiceProtocols/LskApiService.swift deleted file mode 100644 index f850ee421..000000000 --- a/Adamant/ServiceProtocols/LskApiService.swift +++ /dev/null @@ -1,40 +0,0 @@ -// -// LskApiServerProtocol.swift -// Adamant -// -// Created by Anton Boyarkin on 12/07/2018. -// Copyright © 2018 Adamant. All rights reserved. -// - -import Foundation -import LiskKit - -// MARK: - Notifications -extension Notification.Name { - struct LskApiService { - /// Raised when user has logged out. - static let userLoggedOut = Notification.Name("adamant.lskApiService.userHasLoggedOut") - - /// Raised when user has successfully logged in. - static let userLoggedIn = Notification.Name("adamant.lskApiService.userHasLoggedIn") - - private init() {} - } -} - -protocol LskApiService: AnyObject { - - var account: LskAccount? { get } - - // MARK: - Transactions - func createTransaction(toAddress address: String, amount: Double, completion: @escaping (ApiServiceResult) -> Void) - func sendTransaction(transaction: LocalTransaction, completion: @escaping (ApiServiceResult) -> Void) - - func sendFunds(toAddress address: String, amount: Double, completion: @escaping (ApiServiceResult) -> Void) - func getTransactions(_ completion: @escaping (ApiServiceResult<[Transactions.TransactionModel]>) -> Void) - func getTransaction(byHash hash: String, completion: @escaping (ApiServiceResult) -> Void) - - // MARK: - Tools - func getBalance(_ completion: @escaping (ApiServiceResult) -> Void) - func getLskAddress(byAdamandAddress address: String, completion: @escaping (ApiServiceResult) -> Void) -} diff --git a/Adamant/ServiceProtocols/NodesAdditionalParamsStorageProtocol.swift b/Adamant/ServiceProtocols/NodesAdditionalParamsStorageProtocol.swift new file mode 100644 index 000000000..16a5e4df8 --- /dev/null +++ b/Adamant/ServiceProtocols/NodesAdditionalParamsStorageProtocol.swift @@ -0,0 +1,23 @@ +// +// NodesAdditionalParamsStorageProtocol.swift +// Adamant +// +// Created by Andrew G on 18.11.2023. +// Copyright © 2023 Adamant. All rights reserved. +// + +import CommonKit + +// MARK: - SecuredStore keys +extension StoreKey { + enum NodesAdditionalParamsStorage { + static let fastestNodeMode = "nodesAdditionalParamsStorage.fastestNodeMode" + } +} + +protocol NodesAdditionalParamsStorageProtocol { + func isFastestNodeMode(group: NodeGroup) -> Bool + func fastestNodeMode(group: NodeGroup) -> AnyObservable + func setFastestNodeMode(groups: Set, value: Bool) + func setFastestNodeMode(group: NodeGroup, value: Bool) +} diff --git a/Adamant/ServiceProtocols/NodesSource.swift b/Adamant/ServiceProtocols/NodesSource.swift deleted file mode 100644 index b1e5f8a81..000000000 --- a/Adamant/ServiceProtocols/NodesSource.swift +++ /dev/null @@ -1,46 +0,0 @@ -// -// NodesSource.swift -// Adamant -// -// Created by Anokhov Pavel on 21.06.2018. -// Copyright © 2018 Adamant. All rights reserved. -// - -import Foundation -import CommonKit - -// MARK: - Notifications -extension Notification.Name { - struct NodesSource { - /// Raised by NodesSource when need to update current node or list of nodes - static let nodesUpdate = Notification.Name("adamant.nodesSource.nodesUpdate") - - private init() {} - } -} - -// MARK: - SecuredStore keys -extension StoreKey { - struct NodesSource { - static let nodes = "nodesSource.nodes" - - private init() {} - } -} - -// MARK: - UserDefaults -extension UserDefaults { - enum NodesSource { - static let preferTheFastestNodeKey = "nodesSource.preferTheFastestNode" - } -} - -protocol NodesSource: AnyObject { - var nodes: [Node] { get set } - var preferTheFastestNode: Bool { get set } - - func setDefaultNodes() - func getAllowedNodes(needWS: Bool) -> [Node] - func healthCheck() - func nodesUpdate() -} diff --git a/Adamant/ServiceProtocols/NodesStorageProtocol.swift b/Adamant/ServiceProtocols/NodesStorageProtocol.swift new file mode 100644 index 000000000..e460bf745 --- /dev/null +++ b/Adamant/ServiceProtocols/NodesStorageProtocol.swift @@ -0,0 +1,79 @@ +// +// NodesStorageProtocol.swift +// Adamant +// +// Created by Andrew G on 30.10.2023. +// Copyright © 2023 Adamant. All rights reserved. +// + +import CommonKit +import Foundation + +// MARK: - SecuredStore keys +extension StoreKey { + enum NodesStorage { + static let nodes = "nodesStorage.nodes" + } +} + +protocol NodesStorageProtocol { + func getNodesPublisher(group: NodeGroup) -> AnyObservable<[Node]> + func addNode(_ node: Node, group: NodeGroup) + func resetNodes(group: NodeGroup) + func removeNode(id: UUID) + + func updateNode( + id: UUID, + scheme: CommonKit.Node.URLScheme?, + host: String?, + isEnabled: Bool?, + wsEnabled: Bool?, + port: Int??, + wsPort: Int??, + version: String??, + height: Int??, + ping: TimeInterval??, + connectionStatus: CommonKit.Node.ConnectionStatus?? + ) +} + +extension NodesStorageProtocol { + func updateNodeStatus(id: UUID, statusInfo: NodeStatusInfo?) { + updateNodeParams( + id: id, + wsEnabled: .some(statusInfo?.wsEnabled ?? false), + wsPort: .some(statusInfo?.wsPort), + version: .some(statusInfo?.version), + height: .some(statusInfo?.height), + ping: .some(statusInfo?.ping) + ) + } + + func updateNodeParams( + id: UUID, + scheme: CommonKit.Node.URLScheme? = nil, + host: String? = nil, + isEnabled: Bool? = nil, + wsEnabled: Bool? = nil, + port: Int?? = nil, + wsPort: Int?? = nil, + version: String?? = nil, + height: Int?? = nil, + ping: TimeInterval?? = nil, + connectionStatus: CommonKit.Node.ConnectionStatus?? = nil + ) { + updateNode( + id: id, + scheme: scheme, + host: host, + isEnabled: isEnabled, + wsEnabled: wsEnabled, + port: port, + wsPort: wsPort, + version: version, + height: height, + ping: ping, + connectionStatus: connectionStatus + ) + } +} diff --git a/Adamant/Services/APICore.swift b/Adamant/Services/APICore.swift new file mode 100644 index 000000000..b0ed33fb4 --- /dev/null +++ b/Adamant/Services/APICore.swift @@ -0,0 +1,75 @@ +// +// APICore.swift +// Adamant +// +// Created by Andrew G on 30.10.2023. +// Copyright © 2023 Adamant. All rights reserved. +// + +import Foundation +import Alamofire +import CommonKit + +actor APICore: APICoreProtocol { + private let responseQueue = DispatchQueue( + label: "com.adamant.response-queue", + qos: .userInteractive + ) + + private lazy var session: Session = { + let configuration = AF.sessionConfiguration + configuration.waitsForConnectivity = true + configuration.timeoutIntervalForRequest = timeoutInterval + configuration.timeoutIntervalForResource = timeoutInterval + configuration.requestCachePolicy = .reloadIgnoringLocalCacheData + return Alamofire.Session.init(configuration: configuration) + }() + + func sendRequestBasic( + node: Node, + path: String, + method: HTTPMethod, + parameters: Parameters, + encoding: APIParametersEncoding + ) async -> APIResponseModel { + do { + let request = session.request( + try buildUrl(node: node, path: path), + method: method, + parameters: parameters.asDictionary(), + encoding: encoding.parametersEncoding, + headers: HTTPHeaders(["Content-Type": "application/json"]) + ) + + return await sendRequest(request: request) + } catch { + return .init( + result: .failure(.internalError(message: error.localizedDescription, error: error)), + data: nil, + code: nil + ) + } + } +} + +private extension APICore { + func sendRequest(request: DataRequest) async -> APIResponseModel { + await withCheckedContinuation { continuation in + request.responseData(queue: responseQueue) { response in + continuation.resume(returning: .init( + result: response.result.mapError { .init(error: $0) }, + data: response.data, + code: response.response?.statusCode + )) + } + } + } + + func buildUrl(node: Node, path: String) throws -> URL { + guard let url = node.asURL()?.appendingPathComponent(path, conformingTo: .url) + else { throw InternalAPIError.endpointBuildFailed } + return url + } +} + +private let timeoutInterval: TimeInterval = 15 diff --git a/Adamant/Services/AdamantAccountService.swift b/Adamant/Services/AdamantAccountService.swift index 0df4d036b..76a935b5c 100644 --- a/Adamant/Services/AdamantAccountService.swift +++ b/Adamant/Services/AdamantAccountService.swift @@ -42,17 +42,12 @@ final class AdamantAccountService: AccountService { AdmWalletService(), BtcWalletService(), EthWalletService(), - LskWalletService(mainnet: true, nodes: LskWalletService.nodes, services: LskWalletService.serviceNodes), + LskWalletService(), DogeWalletService(), DashWalletService() ] let erc20WalletServices = ERC20Token.supportedTokens.map { ERC20WalletService(token: $0) } wallets.append(contentsOf: erc20WalletServices) - - //LskWalletService(mainnet: false) - // Testnet - // wallets.append(contentsOf: LskWalletService(mainnet: false)) - return wallets }() @@ -67,58 +62,6 @@ final class AdamantAccountService: AccountService { self.dialogService = dialogService self.securedStore = securedStore - guard let ethWallet = wallets[2] as? EthWalletService else { - fatalError("Failed to get EthWalletService") - } - - guard let node = EthWalletService.nodes.randomElement() else { - fatalError("Failed to get ETH endpoint") - } - - let url = node.asString() - - ethWallet.initiateNetwork(apiUrl: url) { result in - switch result { - case .success: - break - - case .failure(let error): - switch error { - case .networkError: - NotificationCenter.default.addObserver( - forName: Notification.Name.AdamantReachabilityMonitor.reachabilityChanged, - object: nil, queue: nil - ) { notification in - guard (notification.userInfo?[AdamantUserInfoKey.ReachabilityMonitor.connection] as? Bool) == true - else { return } - - ethWallet.initiateNetwork(apiUrl: url) { result in - switch result { - case .success: - NotificationCenter.default.removeObserver( - self, - name: Notification.Name.AdamantReachabilityMonitor.reachabilityChanged, - object: nil - ) - - case .failure(let error): - print(error) - } - } - } - - case .notLogged, .transactionNotFound, .notEnoughMoney, .accountNotFound, .walletNotInitiated, .invalidAmount, .requestCancelled, .dustAmountError: - break - - case .remoteServiceError, .apiError, .internalError: - Task { @MainActor [dialogService] in - dialogService.showRichError(error: error) - } - self.wallets.remove(at: 1) - } - } - } - NotificationCenter.default.addObserver(forName: .AdamantAccountService.forceUpdateBalance, object: nil, queue: OperationQueue.main) { [weak self] _ in self?.update() } @@ -280,31 +223,31 @@ extension AdamantAccountService { } Task { - await apiService.getAccount(byPublicKey: publicKey) { [weak self] result in - switch result { - case .success(let account): - guard let acc = self?.account, acc.address == account.address else { - // User has logged out, we not interested anymore - self?.state = .notLogged - return - } - - if loggedAccount.balance != account.balance { - self?.account = account - NotificationCenter.default.post(name: Notification.Name.AdamantAccountService.accountDataUpdated, object: self) - } - - self?.state = .loggedIn - completion?(.success(account: account, alert: nil)) - - if let adm = self?.wallets.first(where: { $0 is AdmWalletService }) { - adm.update() - } - - case .failure(let error): - completion?(.failure(.apiError(error: error))) - self?.state = prevState + let result = await apiService.getAccount(byPublicKey: publicKey) + + switch result { + case .success(let account): + guard let acc = self.account, acc.address == account.address else { + // User has logged out, we not interested anymore + state = .notLogged + return + } + + if loggedAccount.balance != account.balance { + self.account = account + NotificationCenter.default.post(name: Notification.Name.AdamantAccountService.accountDataUpdated, object: self) } + + state = .loggedIn + completion?(.success(account: account, alert: nil)) + + if let adm = wallets.first(where: { $0 is AdmWalletService }) { + adm.update() + } + + case .failure(let error): + completion?(.failure(.apiError(error: error))) + state = prevState } } @@ -417,7 +360,7 @@ extension AdamantAccountService { state = .isLoggingIn do { - let account = try await apiService.getAccount(byPublicKey: keypair.publicKey) + let account = try await apiService.getAccount(byPublicKey: keypair.publicKey).get() self.account = account self.keypair = keypair diff --git a/Adamant/Services/AdamantAddressBookService.swift b/Adamant/Services/AdamantAddressBookService.swift index 3aea23d20..c53e89829 100644 --- a/Adamant/Services/AdamantAddressBookService.swift +++ b/Adamant/Services/AdamantAddressBookService.swift @@ -288,7 +288,7 @@ final class AdamantAddressBookService: AddressBookService { type: .keyValue, sender: address, keypair: keypair - ) + ).get() return id } catch let error as ApiServiceError { @@ -313,7 +313,7 @@ final class AdamantAddressBookService: AddressBookService { let address = loggedAccount.address do { - let rawValue = try await apiService.get(key: addressBookKey, sender: address) + let rawValue = try await apiService.get(key: addressBookKey, sender: address).get() guard let value = rawValue, let object = value.toDictionary(), let message = object["message"] as? String, diff --git a/Adamant/Services/AdamantDialogService.swift b/Adamant/Services/AdamantDialogService.swift index 4e34f5871..152f6bdaf 100644 --- a/Adamant/Services/AdamantDialogService.swift +++ b/Adamant/Services/AdamantDialogService.swift @@ -111,7 +111,7 @@ extension AdamantDialogService { secondaryButton: supportEmail ? .init( title: AdamantResources.supportEmail, - action: .init(id: .zero) { [weak self] in + action: .init(id: .empty) { [weak self] in self?.sendErrorEmail(errorDescription: message) self?.popupManager.dismissAdvancedAlert() } @@ -119,7 +119,7 @@ extension AdamantDialogService { : nil, primaryButton: .init( title: .adamant.alert.ok, - action: .init(id: .zero) { [weak popupManager] in + action: .init(id: .empty) { [weak popupManager] in popupManager?.dismissAdvancedAlert() } ) diff --git a/Adamant/Services/AdamantHealthCheckService.swift b/Adamant/Services/AdamantHealthCheckService.swift deleted file mode 100644 index 5d5a15c5d..000000000 --- a/Adamant/Services/AdamantHealthCheckService.swift +++ /dev/null @@ -1,172 +0,0 @@ -// -// AdamantHealthCheckService.swift -// Adamant -// -// Created by Андрей on 06.06.2022. -// Copyright © 2022 Adamant. All rights reserved. -// - -import Foundation -import Alamofire -import CommonKit - -final class AdamantHealthCheckService: HealthCheckService { - // MARK: - Dependencies - - private let apiService: ApiService - - // MARK: - Properties - - @Atomic private var _nodes = [Node]() - @Atomic private var currentRequests = Set() - private let notifyingQueue = DispatchQueue(label: "com.adamant.health-check-notification") - - weak var delegate: HealthCheckDelegate? - - init(apiService: ApiService) { - self.apiService = apiService - } - - var nodes: [Node] { - get { _nodes } - set { _nodes = newValue } - } - - // MARK: - Tools - - func healthCheck() { - updateNodesAvailability() - - nodes.filter { $0.isEnabled && !isRequestInProgress(for: $0) }.forEach { node in - Task { await updateNodeStatus(node: node) } - } - } - - private func isRequestInProgress(for node: Node) -> Bool { - guard let nodeURL = node.asURL() else { - return false - } - - return currentRequests.contains(nodeURL) - } - - private func updateNodesAvailability() { - let workingNodes = nodes.filter { $0.isWorking } - - let actualHeightsRange = getActualNodeHeightsRange( - heights: workingNodes.compactMap { $0.status?.height } - ) - - for node in workingNodes { - node.connectionStatus = node.status?.height.map { height in - actualHeightsRange?.contains(height) ?? false - ? .allowed - : .synchronizing - } ?? .synchronizing - } - - notifyingQueue.async { [weak delegate] in - delegate?.healthCheckUpdate() - } - } - - private func updateNodeStatus(node: Node) async { - guard let nodeURL = node.asURL() else { - node.connectionStatus = .offline - node.status = nil - return - } - - let startTimestamp = Date().timeIntervalSince1970 - currentRequests.insert(nodeURL) - - await apiService.getNodeStatus(url: nodeURL) { [weak self] result in - self?.currentRequests.remove(nodeURL) - - switch result { - case let .success(status): - node.status = Node.Status( - status: status, - ping: Date().timeIntervalSince1970 - startTimestamp - ) - if !node.isWorking { - node.connectionStatus = .synchronizing - } - node.wsPort = status.wsClient?.port - self?.updateNodesAvailability() - case let .failure(error): - self?.processError(error: error, node: node) - } - } - } - - private func processError(error: ApiServiceError, node: Node) { - switch error { - case .requestCancelled: - break - case .networkError, .serverError, .internalError, .notLogged, .accountNotFound, .commonError: - node.connectionStatus = .offline - node.status = nil - updateNodesAvailability() - } - } -} - -private extension Node { - var isWorking: Bool { - switch connectionStatus { - case .allowed, .synchronizing: - return true - case .offline, .none: - return false - } - } -} - -private extension Node.Status { - init(status: NodeStatus, ping: TimeInterval) { - self.init( - ping: ping, - wsEnabled: status.wsClient?.enabled ?? false, - height: status.network?.height, - version: status.version?.version - ) - } -} - -private struct NodeHeightsInterval { - let range: ClosedRange - var count: Int -} - -private func getActualNodeHeightsRange(heights: [Int]) -> ClosedRange? { - guard heights.count > 2 else { return heights.max().map { $0...$0 } } - - let heights = heights.sorted() - var bestInterval: NodeHeightsInterval? - - for i in heights.indices { - var currentInterval = NodeHeightsInterval( - range: heights[i] - nodeHeightEpsilon ... heights[i] + nodeHeightEpsilon, - count: 1 - ) - - for j in stride(from: i + 1, to: heights.endIndex, by: 1) { - guard currentInterval.range.contains(heights[j]) else { break } - currentInterval.count += 1 - } - - for j in stride(from: i - 1, through: 0, by: -1) { - guard currentInterval.range.contains(heights[j]) else { break } - currentInterval.count += 1 - } - - if currentInterval.count >= bestInterval?.count ?? 0 { - bestInterval = currentInterval - } - } - - return bestInterval?.range -} - -private let nodeHeightEpsilon = 10 diff --git a/Adamant/Services/AdamantNodesSource.swift b/Adamant/Services/AdamantNodesSource.swift deleted file mode 100644 index 252895bf1..000000000 --- a/Adamant/Services/AdamantNodesSource.swift +++ /dev/null @@ -1,161 +0,0 @@ -// -// AdamantNodesSource.swift -// Adamant -// -// Created by Anokhov Pavel on 21.06.2018. -// Copyright © 2018 Adamant. All rights reserved. -// - -import Foundation -import CommonKit - -final class AdamantNodesSource: NodesSource { - // MARK: - Dependencies - - private let apiService: ApiService - private let healthCheckService: HealthCheckService - private let securedStore: SecuredStore - - // MARK: - Properties - - @Atomic private var _nodes: [Node] = [] { - didSet { - healthCheckService.nodes = nodes - nodesUpdate() - } - } - - @Atomic private var _preferTheFastestNode = preferTheFastestNodeDefault { - didSet { - savePreferTheFastestNode(preferTheFastestNode) - - guard preferTheFastestNode else { return } - sendNodesUpdateNotification() - } - } - - var nodes: [Node] { - get { _nodes } - set { _nodes = newValue } - } - - var preferTheFastestNode: Bool { - get { _preferTheFastestNode } - set { _preferTheFastestNode = newValue } - } - - private let defaultNodesGetter: () -> [Node] - @Atomic private var timer: Timer? - @Atomic private var savedNodes: [Node] = [] - - // MARK: - Ctor - - init( - apiService: ApiService, - healthCheckService: HealthCheckService, - securedStore: SecuredStore, - defaultNodesGetter: @escaping () -> [Node] - ) { - self.apiService = apiService - self.healthCheckService = healthCheckService - self.securedStore = securedStore - self.defaultNodesGetter = defaultNodesGetter - - NotificationCenter.default.addObserver( - forName: Notification.Name.AdamantReachabilityMonitor.reachabilityChanged, - object: nil, - queue: nil - ) { [weak self] _ in - self?.healthCheck() - } - - let savedPreferTheFastestNode = UserDefaults.standard.object( - forKey: UserDefaults.NodesSource.preferTheFastestNodeKey - ) as? Bool - - let preferTheFastestNode = savedPreferTheFastestNode ?? preferTheFastestNodeDefault - - if savedPreferTheFastestNode == nil { - savePreferTheFastestNode(preferTheFastestNodeDefault) - } - - self.preferTheFastestNode = preferTheFastestNode - healthCheckService.delegate = self - healthCheckService.nodes = nodes - setHealthCheckTimer() - loadNodes() - } - - deinit { - timer?.invalidate() - } - - // MARK: - Tools - - func setDefaultNodes() { - nodes = defaultNodesGetter() - } - - func getAllowedNodes(needWS: Bool) -> [Node] { - healthCheckService.nodes.getAllowedNodes( - sortedBySpeedDescending: preferTheFastestNode, - needWS: needWS - ) - } - - func nodesUpdate() { - healthCheck() - saveNodes() - } - - func healthCheck() { - healthCheckService.healthCheck() - } - - private func savePreferTheFastestNode(_ newValue: Bool) { - UserDefaults.standard.set( - newValue, - forKey: UserDefaults.NodesSource.preferTheFastestNodeKey - ) - } - - private func sendNodesUpdateNotification() { - NotificationCenter.default.post( - name: Notification.Name.NodesSource.nodesUpdate, - object: self, - userInfo: [:] - ) - } - - private func saveNodes() { - $savedNodes.mutate { - guard nodes != $0 else { return } - securedStore.set(nodes, for: StoreKey.NodesSource.nodes) - $0 = securedStore.get(StoreKey.NodesSource.nodes) ?? [] - } - } - - private func loadNodes() { - savedNodes = securedStore.get(StoreKey.NodesSource.nodes) ?? defaultNodesGetter() - nodes = securedStore.get(StoreKey.NodesSource.nodes) ?? defaultNodesGetter() - } - - private func setHealthCheckTimer() { - timer = Timer.scheduledTimer( - withTimeInterval: regularHealthCheckTimeInteval, - repeats: true - ) { [weak healthCheckService] _ in - healthCheckService?.healthCheck() - } - } -} - -extension AdamantNodesSource: HealthCheckDelegate { - func healthCheckUpdate() { - sendNodesUpdateNotification() - saveNodes() - } -} - -private let regularHealthCheckTimeInteval: TimeInterval = 300 -private let preferTheFastestNodeDefault = true diff --git a/Adamant/Services/AdamantPushNotificationsTokenService.swift b/Adamant/Services/AdamantPushNotificationsTokenService.swift index bcc5b0422..00131f3aa 100644 --- a/Adamant/Services/AdamantPushNotificationsTokenService.swift +++ b/Adamant/Services/AdamantPushNotificationsTokenService.swift @@ -46,16 +46,16 @@ final class AdamantPushNotificationsTokenService: PushNotificationsTokenService func sendTokenDeletionTransactions() { for transaction in getTokenDeletionTransactions() { Task { - await apiService.sendTransaction( - path: AdamantApiService.ApiCommands.Chats.processTransaction, + let result = await apiService.sendTransaction( + path: ApiCommands.Chats.processTransaction, transaction: transaction - ) { [weak self] result in - switch result { - case .success, .failure(.accountNotFound), .failure(.notLogged): - self?.removeTokenDeletionTransaction(transaction) - case .failure(.internalError), .failure(.networkError), .failure(.requestCancelled), .failure(.serverError), .failure(.commonError): - break - } + ) + + switch result { + case .success, .failure(.accountNotFound), .failure(.notLogged): + removeTokenDeletionTransaction(transaction) + case .failure(.internalError), .failure(.networkError), .failure(.requestCancelled), .failure(.serverError), .failure(.commonError), .failure(.noEndpointsAvailable): + break } } } @@ -116,16 +116,14 @@ private extension AdamantPushNotificationsTokenService { return completion() } - removeCurrentToken(keypair: keypair) { - Task { [weak self] in - await self?.sendMessageToANS( - keypair: keypair, - encodedPayload: encodedPayload - ) { success in - defer { completion() } - guard success else { return } - self?.setTokenToStorage(newToken) - } + removeCurrentToken(keypair: keypair) { [weak self] in + self?.sendMessageToANS( + keypair: keypair, + encodedPayload: encodedPayload + ) { success in + defer { completion() } + guard success else { return } + self?.setTokenToStorage(newToken) } } } @@ -142,17 +140,15 @@ private extension AdamantPushNotificationsTokenService { setTokenToStorage(nil) - Task { - var transaction: UnregisteredTransaction? - - transaction = await sendMessageToANS( - keypair: keypair, - encodedPayload: encodedPayload - ) { [weak self] success in - defer { completion() } - guard !success, let self = self, let transaction = transaction else { return } - self.addTokenDeletionTransaction(transaction) - } + var transaction: UnregisteredTransaction? + + transaction = sendMessageToANS( + keypair: keypair, + encodedPayload: encodedPayload + ) { [weak self] success in + defer { completion() } + guard !success, let self = self, let transaction = transaction else { return } + self.addTokenDeletionTransaction(transaction) } } @@ -181,8 +177,8 @@ private extension AdamantPushNotificationsTokenService { keypair: Keypair, encodedPayload: EncodedPayload, completion: @escaping (_ success: Bool) -> Void - ) async -> UnregisteredTransaction? { - await apiService.sendMessage( + ) -> UnregisteredTransaction? { + guard let messageTransaction = try? adamantCore.makeSendMessageTransaction( senderId: AdamantUtilities.generateAddress(publicKey: keypair.publicKey), recipientId: AdamantResources.contacts.ansAddress, keypair: keypair, @@ -190,14 +186,18 @@ private extension AdamantPushNotificationsTokenService { type: ChatType.signal, nonce: encodedPayload.nonce, amount: nil - ) { result in - switch result { + ) else { return nil } + + Task { + switch await apiService.sendMessageTransaction(transaction: messageTransaction) { case .success: completion(true) case .failure: completion(false) } } + + return messageTransaction } } diff --git a/Adamant/Services/ApiService/AdamantApi+Accounts.swift b/Adamant/Services/ApiService/AdamantApi+Accounts.swift index 38c3f641b..d999f7f3e 100644 --- a/Adamant/Services/ApiService/AdamantApi+Accounts.swift +++ b/Adamant/Services/ApiService/AdamantApi+Accounts.swift @@ -9,7 +9,7 @@ import Foundation import CommonKit -extension AdamantApiService.ApiCommands { +extension ApiCommands { static let Accounts = ( root: "/api/accounts", getPublicKey: "/api/accounts/getPublicKey", @@ -20,91 +20,52 @@ extension AdamantApiService.ApiCommands { // MARK: - Accounts extension AdamantApiService { /// Get account by passphrase. - func getAccount(byPassphrase passphrase: String, completion: @escaping (ApiServiceResult) -> Void) { - // MARK: 1. Get keypair from passphrase + func getAccount(byPassphrase passphrase: String) async -> ApiServiceResult { guard let keypair = adamantCore.createKeypairFor(passphrase: passphrase) else { - completion(.failure(.accountNotFound)) - return + return .failure(.accountNotFound) } - // MARK: 2. Send - getAccount(byPublicKey: keypair.publicKey, completion: completion) + return await getAccount(byPublicKey: keypair.publicKey) } /// Get account by publicKey - func getAccount(byPublicKey publicKey: String, completion: @escaping (ApiServiceResult) -> Void) { - sendRequest( - path: ApiCommands.Accounts.root, - queryItems: [URLQueryItem(name: "publicKey", value: publicKey)], - completion: makeCompletionWrapper(publicKey: publicKey, completion: completion) - ) - } - - /// Get account by publicKey - func getAccount(byPublicKey publicKey: String) async throws -> AdamantAccount { - return try await withUnsafeThrowingContinuation { (continuation: UnsafeContinuation) in - sendRequest( + func getAccount(byPublicKey publicKey: String) async -> ApiServiceResult { + switch await request({ apiCore, node in + let response: ApiServiceResult> = await apiCore.sendRequestJson( + node: node, path: ApiCommands.Accounts.root, - queryItems: [URLQueryItem(name: "publicKey", value: publicKey)], - completion: makeCompletionWrapper(publicKey: publicKey) { response in - switch response { - case .success(let t): - continuation.resume(returning: t) - case .failure(let apiServiceError): - continuation.resume(throwing: apiServiceError) - } - } + method: .get, + parameters: ["publicKey": publicKey], + encoding: .url ) + + return response.flatMap { $0.resolved() } + }) { + case let .success(value): + return .success(value) + case let .failure(error): + switch error { + case .accountNotFound: + return .success(.makeEmptyAccount(publicKey: publicKey)) + default: + return .failure(error) + } } } - func getAccount(byAddress address: String, completion: @escaping (ApiServiceResult) -> Void) { - sendRequest( - path: ApiCommands.Accounts.root, - queryItems: [URLQueryItem(name: "address", value: address)], - completion: makeCompletionWrapper(publicKey: nil, completion: completion) - ) - } - - func getAccount(byAddress address: String) async throws -> AdamantAccount { - return try await withUnsafeThrowingContinuation { (continuation: UnsafeContinuation) in - sendRequest( + func getAccount(byAddress address: String) async -> ApiServiceResult { + await request { apiCore, node in + let response: ApiServiceResult< + ServerModelResponse + > = await apiCore.sendRequestJson( + node: node, path: ApiCommands.Accounts.root, - queryItems: [URLQueryItem(name: "address", value: address)], - completion: makeCompletionWrapper(publicKey: nil) { response in - switch response { - case .success(let t): - continuation.resume(returning: t) - case .failure(let apiServiceError): - continuation.resume(throwing: apiServiceError) - } - } + method: .get, + parameters: ["address": address], + encoding: .url ) - } - } -} - -private func makeCompletionWrapper( - publicKey: String?, - completion: @escaping (ApiServiceResult) -> Void -) -> (ApiServiceResult>) -> Void { - { serverResponse in - switch serverResponse { - case .success(let response): - if let model = response.model { - completion(.success(model)) - return - } - - let error = AdamantApiService.translateServerError(response.error) - guard let publicKey = publicKey, error == .accountNotFound else { - completion(.failure(error)) - return - } - completion(.success(.makeEmptyAccount(publicKey: publicKey))) - case .failure(let error): - completion(.failure(.networkError(error: error))) + return response.flatMap { $0.resolved() } } } } diff --git a/Adamant/Services/ApiService/AdamantApi+Chats.swift b/Adamant/Services/ApiService/AdamantApi+Chats.swift index 7c0a348c3..2c8c60552 100644 --- a/Adamant/Services/ApiService/AdamantApi+Chats.swift +++ b/Adamant/Services/ApiService/AdamantApi+Chats.swift @@ -10,7 +10,7 @@ import Foundation import UIKit import CommonKit -extension AdamantApiService.ApiCommands { +extension ApiCommands { static let Chats = ( root: "/api/chats", get: "/api/chats/get", @@ -21,220 +21,65 @@ extension AdamantApiService.ApiCommands { } extension AdamantApiService { - func getMessageTransactions(address: String, height: Int64?, offset: Int?, completion: @escaping (ApiServiceResult<[Transaction]>) -> Void) { - // MARK: 1. Prepare params - var queryItems: [URLQueryItem] = [URLQueryItem(name: "isIn", value: address), - URLQueryItem(name: "orderBy", value: "timestamp:desc")] - if let height = height, height > 0 { queryItems.append(URLQueryItem(name: "fromHeight", value: String(height))) } - if let offset = offset { queryItems.append(URLQueryItem(name: "offset", value: String(offset))) } - - // MARK: 2. Send - sendRequest( - path: ApiCommands.Chats.get, - queryItems: queryItems - ) { (serverResponse: ApiServiceResult>) in - switch serverResponse { - case .success(let response): - if let collection = response.collection { - completion(.success(collection)) - } else { - let error = AdamantApiService.translateServerError(response.error) - completion(.failure(error)) - } - - case .failure(let error): - completion(.failure(.networkError(error: error))) - } - } - } - func getMessageTransactions( address: String, height: Int64?, offset: Int? - ) async throws -> [Transaction] { - // MARK: 1. Prepare params - var queryItems: [URLQueryItem] = [URLQueryItem(name: "isIn", value: address), - URLQueryItem(name: "orderBy", value: "timestamp:desc")] - if let height = height, height > 0 { queryItems.append(URLQueryItem(name: "fromHeight", value: String(height))) } - if let offset = offset { queryItems.append(URLQueryItem(name: "offset", value: String(offset))) } + ) async -> ApiServiceResult<[Transaction]> { + var parameters = [ + "isIn": address, + "orderBy": "timestamp:desc" + ] - // MARK: 2. Send - return try await withUnsafeThrowingContinuation { (continuation: UnsafeContinuation<[Transaction], Error>) in - sendRequest( - path: ApiCommands.Chats.get, - queryItems: queryItems, - waitsForConnectivity: true - ) { (serverResponse: ApiServiceResult>) in - switch serverResponse { - case .success(let response): - if let collection = response.collection { - continuation.resume(returning: collection) - } else { - let error = AdamantApiService.translateServerError(response.error) - continuation.resume(throwing: error) - } - - case .failure(let error): - continuation.resume(throwing: ApiServiceError.networkError(error: error)) - } - } - } - } - - @discardableResult - func sendMessage( - senderId: String, - recipientId: String, - keypair: Keypair, - message: String, - type: ChatType, - nonce: String, - amount: Decimal?, - completion: @escaping (ApiServiceResult) -> Void - ) -> UnregisteredTransaction? { - let normalizedTransaction = NormalizedTransaction( - type: .chatMessage, - amount: amount ?? .zero, - senderPublicKey: keypair.publicKey, - requesterPublicKey: nil, - date: lastRequestTimeDelta.map { Date().addingTimeInterval(-$0) } ?? Date(), - recipientId: recipientId, - asset: TransactionAsset( - chat: ChatAsset(message: message, ownMessage: nonce, type: type), - state: nil, - votes: nil - ) - ) - - guard let transaction = adamantCore.makeSignedTransaction( - transaction: normalizedTransaction, - senderId: senderId, - keypair: keypair - ) else { - completion(.failure(InternalError.signTransactionFailed.apiServiceErrorWith(error: nil))) - return nil + if let height = height, height > .zero { + parameters["fromHeight"] = String(height) } - sendTransaction(path: ApiCommands.Chats.processTransaction, transaction: transaction) { response in - switch response { - case .success(let response): - if let id = response.transactionId { - completion(.success(id)) - } else { - let error = AdamantApiService.translateServerError(response.error) - completion(.failure(error)) - } - - case .failure(let error): - completion(.failure(.networkError(error: error))) - } + if let offset = offset { + parameters["offset"] = String(offset) } - return transaction - } - - func createSendTransaction( - senderId: String, - recipientId: String, - keypair: Keypair, - message: String, - type: ChatType, - nonce: String, - amount: Decimal? - ) -> UnregisteredTransaction? { - let normalizedTransaction = NormalizedTransaction( - type: .chatMessage, - amount: amount ?? .zero, - senderPublicKey: keypair.publicKey, - requesterPublicKey: nil, - date: lastRequestTimeDelta.map { Date().addingTimeInterval(-$0) } ?? Date(), - recipientId: recipientId, - asset: TransactionAsset( - chat: ChatAsset(message: message, ownMessage: nonce, type: type), - state: nil, - votes: nil + let response: ApiServiceResult> + response = await request { [parameters] service, node in + await service.sendRequestJson( + node: node, + path: ApiCommands.Chats.get, + method: .get, + parameters: parameters, + encoding: .url ) - ) - - guard let transaction = adamantCore.makeSignedTransaction( - transaction: normalizedTransaction, - senderId: senderId, - keypair: keypair - ) else { - return nil } - return transaction + return response.flatMap { $0.resolved() } } - func sendTransaction( + func sendMessageTransaction( transaction: UnregisteredTransaction - ) async throws -> UInt64 { - return try await withUnsafeThrowingContinuation { (continuation: UnsafeContinuation) in - sendTransaction(path: ApiCommands.Chats.processTransaction, transaction: transaction) { response in - switch response { - case .success(let response): - if let id = response.transactionId { - continuation.resume(returning: id) - } else { - let error = AdamantApiService.translateServerError(response.error) - continuation.resume(throwing: error) - } - - case .failure(let error): - continuation.resume(throwing: ApiServiceError.networkError(error: error)) - } - } - } - } - - // new api - - func getChatRooms(address: String, offset: Int?, completion: @escaping (ApiServiceResult) -> Void) { - // MARK: 1. Prepare params - var queryItems: [URLQueryItem] = [] - if let offset = offset { queryItems.append(URLQueryItem(name: "offset", value: String(offset))) } - queryItems.append(URLQueryItem(name: "limit", value: "20")) - - // MARK: 2. Send - sendRequest( - path: ApiCommands.Chats.getChatRooms + "/\(address)", - queryItems: queryItems - ) { (serverResponse: ApiServiceResult) in - switch serverResponse { - case .success(let response): - completion(.success(response)) - - case .failure(let error): - completion(.failure(.networkError(error: error))) - } - } + ) async -> ApiServiceResult { + await sendTransaction( + path: ApiCommands.Chats.processTransaction, + transaction: transaction + ) } func getChatRooms( address: String, offset: Int? - ) async throws -> ChatRooms { - // MARK: 1. Prepare params - var queryItems: [URLQueryItem] = [] - if let offset = offset { queryItems.append(URLQueryItem(name: "offset", value: String(offset))) } - queryItems.append(URLQueryItem(name: "limit", value: "20")) + ) async -> ApiServiceResult { + var parameters = ["limit": "20"] + + if let offset = offset { + parameters["offset"] = String(offset) + } - // MARK: 2. Send - return try await withUnsafeThrowingContinuation { (continuation: UnsafeContinuation) in - sendRequest( + return await request { [parameters] service, node in + await service.sendRequestJson( + node: node, path: ApiCommands.Chats.getChatRooms + "/\(address)", - queryItems: queryItems, - waitsForConnectivity: true - ) { (serverResponse: ApiServiceResult) in - switch serverResponse { - case .success(let response): - continuation.resume(returning: response) - case .failure(let error): - continuation.resume(throwing: ApiServiceError.networkError(error: error)) - } - } + method: .get, + parameters: parameters, + encoding: .url + ) } } @@ -243,31 +88,25 @@ extension AdamantApiService { addressRecipient: String, offset: Int?, limit: Int? - ) async throws -> ChatRooms { - // MARK: 1. Prepare params - var queryItems: [URLQueryItem] = [] + ) async -> ApiServiceResult { + var parameters: [String: String] = [:] + if let offset = offset { - queryItems.append(URLQueryItem(name: "offset", value: String(offset))) + parameters["offset"] = String(offset) } if let limit = limit { - queryItems.append(URLQueryItem(name: "limit", value: String(limit))) + parameters["limit"] = String(limit) } - // MARK: 2. Send - return try await withUnsafeThrowingContinuation { (continuation: UnsafeContinuation) in - sendRequest( + return await request { [parameters] service, node in + await service.sendRequestJson( + node: node, path: ApiCommands.Chats.getChatRooms + "/\(address)/\(addressRecipient)", - queryItems: queryItems, - waitsForConnectivity: true - ) { (serverResponse: ApiServiceResult) in - switch serverResponse { - case .success(let response): - continuation.resume(returning: response) - case .failure(let error): - continuation.resume(throwing: ApiServiceError.networkError(error: error)) - } - } + method: .get, + parameters: parameters, + encoding: .url + ) } } } diff --git a/Adamant/Services/ApiService/AdamantApi+Delegates.swift b/Adamant/Services/ApiService/AdamantApi+Delegates.swift index 8383a97de..357f7cd4a 100644 --- a/Adamant/Services/ApiService/AdamantApi+Delegates.swift +++ b/Adamant/Services/ApiService/AdamantApi+Delegates.swift @@ -10,7 +10,7 @@ import Foundation import UIKit import CommonKit -extension AdamantApiService.ApiCommands { +extension ApiCommands { static let Delegates = ( root: "/api/delegates", getDelegates: "/api/delegates", @@ -23,149 +23,127 @@ extension AdamantApiService.ApiCommands { } extension AdamantApiService { - func getDelegates(limit: Int, completion: @escaping (ApiServiceResult<[Delegate]>) -> Void) { - self.getDelegates(limit: limit, offset: 0, currentDelegates: [Delegate](), completion: completion) + func getDelegates(limit: Int) async -> ApiServiceResult<[Delegate]> { + await getDelegates(limit: limit, offset: .zero, currentDelegates: [Delegate]()) } - func getDelegates(limit: Int, offset: Int, currentDelegates: [Delegate], completion: @escaping (ApiServiceResult<[Delegate]>) -> Void) { - sendRequest( - path: ApiCommands.Delegates.getDelegates, - queryItems: [ - URLQueryItem(name: "limit", value: String(limit)), - URLQueryItem(name: "offset", value: String(offset)) - ], - method: .get - ) { (serverResponse: ApiServiceResult>) in - switch serverResponse { - case .success(let delegates): - if let delegates = delegates.collection { - var currentDelegates = currentDelegates - currentDelegates.append(contentsOf: delegates) - - if delegates.count < limit { - completion(.success(currentDelegates)) - } else { - self.getDelegates(limit: limit, offset: offset+limit, currentDelegates: currentDelegates, completion: completion) - } - } else { - completion(.failure(.serverError(error: "No delegates"))) - } - - case .failure(let error): - completion(.failure(.networkError(error: error))) - } + func getDelegates( + limit: Int, + offset: Int, + currentDelegates: [Delegate] + ) async -> ApiServiceResult<[Delegate]> { + let response: ApiServiceResult> + response = await request { core, node in + await core.sendRequestJson( + node: node, + path: ApiCommands.Delegates.getDelegates, + method: .get, + parameters: ["limit": String(limit), "offset": String(offset)], + encoding: .url + ) + } + + let result = response.flatMap { $0.resolved() } + guard let delegates = try? result.get() else { return result } + let currentDelegates = currentDelegates + delegates + + if delegates.count < limit { + return .success(currentDelegates) + } else { + return await getDelegates( + limit: limit, + offset: offset + limit, + currentDelegates: currentDelegates + ) } } - func getDelegatesWithVotes(for address: String, limit: Int, completion: @escaping (ApiServiceResult<[Delegate]>) -> Void) { - self.getVotes(for: address) { (result) in - switch result { - case .success(let delegates): - let votes = delegates.map({ (delegate) -> String in - return delegate.address - }) + func getDelegatesWithVotes(for address: String, limit: Int) async -> ApiServiceResult<[Delegate]> { + let response = await getVotes(for: address) + + switch response { + case let .success(delegates): + let votes = delegates.map { $0.address } + let delegatesResponse = await getDelegates(limit: limit) + + return delegatesResponse.map { delegates in + var delegatesWithVotes = [Delegate]() - self.getDelegates(limit: limit, completion: { (result) in - switch result { - case .success(let delegates): - var delegatesWithVotes = [Delegate]() - delegates.forEach({ (delegate) in - delegate.voted = votes.contains(delegate.address) - delegatesWithVotes.append(delegate) - }) - - completion(.success(delegatesWithVotes)) - case .failure(let error): - completion(.failure(.networkError(error: error))) - } - }) + delegates.forEach { delegate in + delegate.voted = votes.contains(delegate.address) + delegatesWithVotes.append(delegate) + } - case .failure(let error): - completion(.failure(.networkError(error: error))) + return delegatesWithVotes } + case let .failure(error): + return .failure(error) } } - func getForgedByAccount(publicKey: String, completion: @escaping (ApiServiceResult) -> Void) { - sendRequest( - path: ApiCommands.Delegates.getForgedByAccount, - queryItems: [URLQueryItem(name: "generatorPublicKey", value: publicKey)], - method: .get - ) { (serverResponse: ApiServiceResult) in - switch serverResponse { - case .success(let details): - completion(.success(details)) - - case .failure(let error): - completion(.failure(.networkError(error: error))) - } + func getForgedByAccount(publicKey: String) async -> ApiServiceResult { + await request { core, node in + await core.sendRequestJson( + node: node, + path: ApiCommands.Delegates.getForgedByAccount, + method: .get, + parameters: ["generatorPublicKey": publicKey], + encoding: .url + ) } } - func getForgingTime(for delegate: Delegate, completion: @escaping (ApiServiceResult) -> Void) { - getNextForgers { (result) in - switch result { - case .success(let nextForgers): - var forgingTime = -1 - if let fIndex = nextForgers.delegates.firstIndex(of: delegate.publicKey) { - forgingTime = fIndex * 10 - } - completion(.success(forgingTime)) - - case .failure(let error): - completion(.failure(.networkError(error: error))) + func getForgingTime(for delegate: Delegate) async -> ApiServiceResult { + await getNextForgers().map { nextForgers in + var forgingTime = -1 + if let fIndex = nextForgers.delegates.firstIndex(of: delegate.publicKey) { + forgingTime = fIndex * 10 } + return forgingTime } } - private func getDelegatesCount(completion: @escaping (ApiServiceResult) -> Void) { - sendRequest( - path: ApiCommands.Delegates.getDelegatesCount, - method: .get - ) { (serverResponse: ApiServiceResult) in - completion(serverResponse) + private func getDelegatesCount() async -> ApiServiceResult { + await request { core, node in + await core.sendRequestJson( + node: node, + path: ApiCommands.Delegates.getDelegatesCount + ) } } - private func getNextForgers(completion: @escaping (ApiServiceResult) -> Void) { - sendRequest( - path: ApiCommands.Delegates.getNextForgers, - queryItems: [URLQueryItem(name: "limit", value: "\(101)")], - method: .get - ) { (serverResponse: ApiServiceResult) in - completion(serverResponse) + private func getNextForgers() async -> ApiServiceResult { + await request { core, node in + await core.sendRequestJson( + node: node, + path: ApiCommands.Delegates.getNextForgers, + method: .get, + parameters: ["limit": "\(101)"], + encoding: .url + ) } } - func getVotes(for address: String, completion: @escaping (ApiServiceResult<[Delegate]>) -> Void) { - sendRequest( - path: ApiCommands.Delegates.votes, - queryItems: [URLQueryItem(name: "address", value: address)], - method: .get - ) { (serverResponse: ApiServiceResult>) in - switch serverResponse { - case .success(let delegates): - completion(.success(delegates.collection ?? [])) - case .failure(let error): - completion(.failure(.networkError(error: error))) - } + func getVotes(for address: String) async -> ApiServiceResult<[Delegate]> { + let response: ApiServiceResult> + response = await request { core, node in + await core.sendRequestJson( + node: node, + path: ApiCommands.Delegates.votes, + method: .get, + parameters: ["address": address], + encoding: .url + ) } + + return response.map { $0.collection ?? .init() } } func voteForDelegates( from address: String, keypair: Keypair, - votes: [DelegateVote], - completion: @escaping (ApiServiceResult) -> Void - ) { - Task { @MainActor in - sendingMsgTaskId = UIApplication.shared.beginBackgroundTask { [weak self] in - guard let self = self else { return } - UIApplication.shared.endBackgroundTask(self.sendingMsgTaskId) - self.sendingMsgTaskId = UIBackgroundTaskIdentifier.invalid - } - } - + votes: [DelegateVote] + ) async -> ApiServiceResult { // MARK: 0. Prepare var votesOrdered = votes _ = votesOrdered.partition { @@ -180,10 +158,10 @@ extension AdamantApiService { // MARK: 1. Create and sign transaction let transaction = NormalizedTransaction( type: .vote, - amount: 0, + amount: .zero, senderPublicKey: keypair.publicKey, requesterPublicKey: nil, - date: Date(), + date: .now, recipientId: address, asset: TransactionAsset(votes: votesAsset) ) @@ -193,57 +171,29 @@ extension AdamantApiService { senderId: address, keypair: keypair ) else { - completion(.failure(.internalError(message: "Failed to sign transaction", error: nil))) - return + return .failure(.internalError(error: InternalAPIError.signTransactionFailed)) } - sendDelegateVoteTransaction( + return await sendDelegateVoteTransaction( path: ApiCommands.Delegates.votes, transaction: transaction - ) { serverResponse in - switch serverResponse { - case .success(let response): - if response.success { - completion(.success(1)) - } else { - completion(.failure(.serverError(error: response.error ?? ""))) - } - - case .failure(let error): - completion(.failure(.networkError(error: error))) - } - - Task { @MainActor [weak self] in - guard let self = self else { return } - UIApplication.shared.endBackgroundTask(self.sendingMsgTaskId) - self.sendingMsgTaskId = UIBackgroundTaskIdentifier.invalid - } - } + ) } // MARK: - Private methods - private func getBlocks(completion: @escaping (ApiServiceResult<[Block]>) -> Void) { - sendRequest( - path: ApiCommands.Delegates.getBlocks, - queryItems: [ - URLQueryItem(name: "orderBy", value: "height:desc"), - URLQueryItem(name: "limit", value: "\(101)") - ], - method: .get - ) { (serverResponse: ApiServiceResult>) in - switch serverResponse { - case .success(let blocks): - if let blocks = blocks.collection { - completion(.success(blocks)) - } else { - completion(.failure(.serverError(error: "No delegates"))) - } - - case .failure(let error): - completion(.failure(.networkError(error: error))) - } + private func getBlocks() async -> ApiServiceResult<[Block]> { + let response: ApiServiceResult> = await request { core, node in + await core.sendRequestJson( + node: node, + path: ApiCommands.Delegates.getBlocks, + method: .get, + parameters: ["orderBy": "height:desc", "limit": "\(101)"], + encoding: .url + ) } + + return response.flatMap { $0.resolved() } } private func getRoundDelegates(delegates: [String], height: UInt64) -> [String] { diff --git a/Adamant/Services/ApiService/AdamantApi+Keys.swift b/Adamant/Services/ApiService/AdamantApi+Keys.swift index fb6c7ab7f..124aadd8a 100644 --- a/Adamant/Services/ApiService/AdamantApi+Keys.swift +++ b/Adamant/Services/ApiService/AdamantApi+Keys.swift @@ -10,26 +10,17 @@ import Foundation import CommonKit extension AdamantApiService { - func getPublicKey( - byAddress address: String, - completion: @escaping (ApiServiceResult) -> Void - ) { - sendRequest( - path: ApiCommands.Accounts.getPublicKey, - queryItems: [URLQueryItem(name: "address", value: address)] - ) { (serverResponse: ApiServiceResult) in - switch serverResponse { - case .success(let response): - if let publicKey = response.publicKey { - completion(.success(publicKey)) - } else { - let error = AdamantApiService.translateServerError(response.error) - completion(.failure(error)) - } - - case .failure(let error): - completion(.failure(.networkError(error: error))) - } + func getPublicKey(byAddress address: String) async -> ApiServiceResult { + let response: ApiServiceResult = await request { service, node in + await service.sendRequestJson( + node: node, + path: ApiCommands.Accounts.getPublicKey, + method: .get, + parameters: ["address": address], + encoding: .url + ) } + + return response.flatMap { $0.resolved() } } } diff --git a/Adamant/Services/ApiService/AdamantApi+Peers.swift b/Adamant/Services/ApiService/AdamantApi+Peers.swift deleted file mode 100644 index 5d7175a1d..000000000 --- a/Adamant/Services/ApiService/AdamantApi+Peers.swift +++ /dev/null @@ -1,42 +0,0 @@ -// -// AdamantApi+Peers.swift -// Adamant -// -// Created by Anokhov Pavel on 21.06.2018. -// Copyright © 2018 Adamant. All rights reserved. -// - -import Foundation - -extension AdamantApiService.ApiCommands { - static let Peers = ( - root: "/api/peers", - version: "/api/peers/version" - ) -} - -// MARK: - Peers -extension AdamantApiService { - func getNodeVersion(url: URL, completion: @escaping (ApiServiceResult) -> Void) { - // MARK: 1. Prepare - let endpoint: URL - do { - endpoint = try buildUrl(url: url, path: ApiCommands.Peers.version) - } catch { - let err = InternalError.endpointBuildFailed.apiServiceErrorWith(error: error) - completion(.failure(err)) - return - } - - // MARK: 2. Make request - sendRequest(url: endpoint, method: .get) { (serverResponse: ApiServiceResult) in - switch serverResponse { - case .success(let version): - completion(.success(version)) - - case .failure(let error): - completion(.failure(.networkError(error: error))) - } - } - } -} diff --git a/Adamant/Services/ApiService/AdamantApi+States.swift b/Adamant/Services/ApiService/AdamantApi+States.swift index c491519c3..b926763f7 100644 --- a/Adamant/Services/ApiService/AdamantApi+States.swift +++ b/Adamant/Services/ApiService/AdamantApi+States.swift @@ -10,7 +10,7 @@ import Foundation import UIKit import CommonKit -extension AdamantApiService.ApiCommands { +extension ApiCommands { static let States = ( root: "/api/states", get: "/api/states/get", @@ -19,90 +19,21 @@ extension AdamantApiService.ApiCommands { } extension AdamantApiService { - static let KvsFee: Decimal = 0.001 - func store( - key: String, - value: String, - type: StateType, - sender: String, - keypair: Keypair, - completion: @escaping (ApiServiceResult) -> Void - ) { - Task { @MainActor in - sendingMsgTaskId = UIApplication.shared.beginBackgroundTask { [weak self] in - guard let self = self else { return } - UIApplication.shared.endBackgroundTask(self.sendingMsgTaskId) - self.sendingMsgTaskId = UIBackgroundTaskIdentifier.invalid - } - } - - // MARK: Create and sign transaction - let transaction = NormalizedTransaction( - type: .state, - amount: .zero, - senderPublicKey: keypair.publicKey, - requesterPublicKey: nil, - date: Date(), - recipientId: nil, - asset: TransactionAsset(state: StateAsset(key: key, value: value, type: .keyValue)) - ) - - guard let transaction = adamantCore.makeSignedTransaction( - transaction: transaction, - senderId: sender, - keypair: keypair - ) else { - completion(.failure(.internalError(message: "Failed to sign transaction", error: nil))) - return - } - - // MARK: Send - sendTransaction(path: ApiCommands.States.store, transaction: transaction) { serverResponse in - switch serverResponse { - case .success(let response): - if let id = response.transactionId { - completion(.success(id)) - } else { - completion(ApiServiceResult.success(0)) - } - - case .failure(let error): - completion(.failure(.networkError(error: error))) - } - - Task { @MainActor [weak self] in - self?.sendingMsgTaskId = UIApplication.shared.beginBackgroundTask { - guard let self = self else { return } - UIApplication.shared.endBackgroundTask(self.sendingMsgTaskId) - self.sendingMsgTaskId = UIBackgroundTaskIdentifier.invalid - } - } - } - } - func store( key: String, value: String, type: StateType, sender: String, keypair: Keypair - ) async throws -> UInt64 { - Task { @MainActor in - self.sendingMsgTaskId = UIApplication.shared.beginBackgroundTask { - UIApplication.shared.endBackgroundTask(self.sendingMsgTaskId) - self.sendingMsgTaskId = UIBackgroundTaskIdentifier.invalid - } - } - - // MARK: Create and sign transaction + ) async -> ApiServiceResult { let transaction = NormalizedTransaction( type: .state, amount: .zero, senderPublicKey: keypair.publicKey, requesterPublicKey: nil, - date: Date(), + date: .now, recipientId: nil, asset: TransactionAsset(state: StateAsset(key: key, value: value, type: .keyValue)) ) @@ -112,78 +43,38 @@ extension AdamantApiService { senderId: sender, keypair: keypair ) else { - throw ApiServiceError.internalError(message: "Failed to sign transaction", error: nil) + return .failure(.internalError(error: InternalAPIError.signTransactionFailed)) } // MARK: Send - return try await withUnsafeThrowingContinuation { (continuation: UnsafeContinuation) in - sendTransaction(path: ApiCommands.States.store, transaction: transaction) { [weak self] serverResponse in - switch serverResponse { - case .success(let response): - if let id = response.transactionId { - continuation.resume(returning: id) - } else { - continuation.resume(returning: 0) - } - - case .failure(let error): - continuation.resume(throwing: ApiServiceError.networkError(error: error)) - } - - guard let self = self else { return } - Task { @MainActor in - UIApplication.shared.endBackgroundTask(self.sendingMsgTaskId) - self.sendingMsgTaskId = UIBackgroundTaskIdentifier.invalid - } - } - } + return await sendTransaction( + path: ApiCommands.States.store, + transaction: transaction + ) } - func get(key: String, sender: String, completion: @escaping (ApiServiceResult) -> Void) { + func get(key: String, sender: String) async -> ApiServiceResult { // MARK: 1. Prepare - let queryItems = [URLQueryItem(name: "senderId", value: sender), - URLQueryItem(name: "orderBy", value: "timestamp:desc"), - URLQueryItem(name: "key", value: key)] + let parameters = [ + "senderId": sender, + "orderBy": "timestamp:desc", + "key": key + ] - // MARK: 2. Send - sendRequest( - path: ApiCommands.States.get, - queryItems: queryItems - ) { (serverResponse: ApiServiceResult>) in - switch serverResponse { - case .success(let response): - if let collection = response.collection { - if collection.count > 0, let value = collection.first?.asset.state?.value { - completion(.success(value)) - } else { - completion(.success(nil)) - } - } else { - let error = AdamantApiService.translateServerError(response.error) - completion(.failure(error)) - } - - case .failure(let error): - completion(.failure(.networkError(error: error))) - } - } - } - - func get( - key: String, - sender: String - ) async throws -> String? { - return try await withUnsafeThrowingContinuation { - (continuation: UnsafeContinuation) in - get(key: key, sender: sender) { result in - switch result { - case .success(let data): - continuation.resume(returning: data) - case .failure(let error): - continuation.resume(throwing: error) - } - } + let response: ApiServiceResult> + response = await request { [parameters] core, node in + await core.sendRequestJson( + node: node, + path: ApiCommands.States.get, + method: .get, + parameters: parameters, + encoding: .url + ) } + + return response + .flatMap { $0.resolved() } + .map { $0.first?.asset.state?.value } } } diff --git a/Adamant/Services/ApiService/AdamantApi+Status.swift b/Adamant/Services/ApiService/AdamantApi+Status.swift deleted file mode 100644 index e0e3101c1..000000000 --- a/Adamant/Services/ApiService/AdamantApi+Status.swift +++ /dev/null @@ -1,35 +0,0 @@ -// -// AdamantApi+Status.swift -// Adamant -// -// Created by Андрей on 10.05.2022. -// Copyright © 2022 Adamant. All rights reserved. -// - -import Foundation -import Alamofire - -extension AdamantApiService.ApiCommands { - static let status = "/api/node/status" -} - -extension AdamantApiService { - @discardableResult - func getNodeStatus( - url: URL, - completion: @escaping (ApiServiceResult) -> Void - ) -> DataRequest? { - // MARK: 1. Prepare - let endpoint: URL - do { - endpoint = try buildUrl(url: url, path: ApiCommands.status) - } catch { - let err = InternalError.endpointBuildFailed.apiServiceErrorWith(error: error) - completion(.failure(err)) - return nil - } - - // MARK: 2. Make request - return sendRequest(url: endpoint, method: .get, completion: completion) - } -} diff --git a/Adamant/Services/ApiService/AdamantApi+Transactions.swift b/Adamant/Services/ApiService/AdamantApi+Transactions.swift index 50c9289a0..0c53fc2b7 100644 --- a/Adamant/Services/ApiService/AdamantApi+Transactions.swift +++ b/Adamant/Services/ApiService/AdamantApi+Transactions.swift @@ -9,7 +9,7 @@ import Foundation import CommonKit -extension AdamantApiService.ApiCommands { +extension ApiCommands { static let Transactions = ( root: "/api/transactions", getTransaction: "/api/transactions/get", @@ -21,79 +21,58 @@ extension AdamantApiService.ApiCommands { extension AdamantApiService { func sendTransaction( path: String, - transaction: UnregisteredTransaction, - completion: @escaping (ApiServiceResult) -> Void - ) { - sendRequest( - path: path, - method: .post, - body: ["transaction": transaction], - completion: completion - ) + transaction: UnregisteredTransaction + ) async -> ApiServiceResult { + let response: ApiServiceResult = await request { core, node in + await core.sendRequestJson( + node: node, + path: path, + method: .post, + parameters: ["transaction": transaction], + encoding: .json + ) + } + + return response.flatMap { $0.resolved() } } func sendDelegateVoteTransaction( path: String, - transaction: UnregisteredTransaction, - completion: @escaping (ApiServiceResult) -> Void - ) { - sendRequest( - path: path, - method: .post, - body: transaction, - completion: completion - ) - } - - func getTransaction(id: UInt64, completion: @escaping (ApiServiceResult) -> Void) { - sendRequest( - path: ApiCommands.Transactions.getTransaction, - queryItems: [URLQueryItem(name: "id", value: String(id))] - ) { (serverResponse: ApiServiceResult>) in - switch serverResponse { - case .success(let response): - if let model = response.model { - completion(.success(model)) - } else { - let error = AdamantApiService.translateServerError(response.error) - completion(.failure(error)) - } - - case .failure(let error): - completion(.failure(.networkError(error: error))) - } + transaction: UnregisteredTransaction + ) async -> ApiServiceResult { + let response: ApiServiceResult = await request { core, node in + await core.sendRequestJson( + node: node, + path: path, + method: .post, + parameters: transaction, + encoding: .json + ) } + + return response.flatMap { $0.resolved() } } - func getTransaction(id: UInt64) async throws -> Transaction { - try await getTransaction(id: id, withAsset: false) + func getTransaction(id: UInt64) async -> ApiServiceResult { + await getTransaction(id: id, withAsset: false) } - func getTransaction(id: UInt64, withAsset: Bool) async throws -> Transaction { - var queryItems = [ - URLQueryItem(name: "id", value: String(id)), - URLQueryItem(name: "returnAsset", value: withAsset ? "1" : "0") - ] - - return try await withUnsafeThrowingContinuation { (continuation: UnsafeContinuation) in - sendRequest( + func getTransaction(id: UInt64, withAsset: Bool) async -> ApiServiceResult { + let response: ApiServiceResult> + response = await request { core, node in + await core.sendRequestJson( + node: node, path: ApiCommands.Transactions.getTransaction, - queryItems: queryItems - ) { (serverResponse: ApiServiceResult>) in - switch serverResponse { - case .success(let response): - if let model = response.model { - continuation.resume(returning: model) - } else { - let error = AdamantApiService.translateServerError(response.error) - continuation.resume(throwing: error) - } - - case .failure(let error): - continuation.resume(throwing: ApiServiceError.networkError(error: error)) - } - } + method: .get, + parameters: [ + "id": String(id), + "returnAsset": withAsset ? "1" : "0" + ], + encoding: .url + ) } + + return response.flatMap { $0.resolved() } } func getTransactions( @@ -102,8 +81,8 @@ extension AdamantApiService { fromHeight: Int64?, offset: Int?, limit: Int? - ) async throws -> [Transaction] { - try await getTransactions( + ) async -> ApiServiceResult<[Transaction]> { + await getTransactions( forAccount: account, type: type, fromHeight: fromHeight, @@ -120,8 +99,7 @@ extension AdamantApiService { offset: Int?, limit: Int?, orderByTime: Bool? - ) async throws -> [Transaction] { - + ) async -> ApiServiceResult<[Transaction]> { var queryItems = [URLQueryItem(name: "inId", value: account)] if type == .send { @@ -131,9 +109,13 @@ extension AdamantApiService { queryItems.append(URLQueryItem(name: "and:type", value: String(type.rawValue))) } - if let limit = limit { queryItems.append(URLQueryItem(name: "limit", value: String(limit))) } + if let limit = limit { + queryItems.append(URLQueryItem(name: "limit", value: String(limit))) + } - if let offset = offset { queryItems.append(URLQueryItem(name: "offset", value: String(offset))) } + if let offset = offset { + queryItems.append(URLQueryItem(name: "offset", value: String(offset))) + } if let fromHeight = fromHeight, fromHeight > 0 { queryItems.append(URLQueryItem(name: "and:fromHeight", value: String(fromHeight))) @@ -143,24 +125,17 @@ extension AdamantApiService { queryItems.append(URLQueryItem(name: "orderBy", value: "timestamp:desc")) } - return try await withUnsafeThrowingContinuation { (continuation: UnsafeContinuation<[Transaction], Error>) in - sendRequest( + let response: ApiServiceResult> + response = await request { [queryItems] core, node in + await core.sendRequestJson( + node: node, path: ApiCommands.Transactions.root, - queryItems: queryItems - ) { (serverResponse: ApiServiceResult>) in - switch serverResponse { - case .success(let response): - if let collection = response.collection { - continuation.resume(returning: collection) - } else { - let error = AdamantApiService.translateServerError(response.error) - continuation.resume(throwing: error) - } - - case .failure(let error): - continuation.resume(throwing: ApiServiceError.networkError(error: error)) - } - } + method: .get, + parameters: [Bool](), + encoding: .forceQueryItems(queryItems) + ) } + + return response.flatMap { $0.resolved() } } } diff --git a/Adamant/Services/ApiService/AdamantApi+Transfers.swift b/Adamant/Services/ApiService/AdamantApi+Transfers.swift index 2e10d2ca1..470de0ff6 100644 --- a/Adamant/Services/ApiService/AdamantApi+Transfers.swift +++ b/Adamant/Services/ApiService/AdamantApi+Transfers.swift @@ -10,56 +10,18 @@ import Foundation import CommonKit extension AdamantApiService { - func transferFunds(sender: String, recipient: String, amount: Decimal, keypair: Keypair, completion: @escaping (ApiServiceResult) -> Void) { - let normalizedTransaction = NormalizedTransaction( - type: .send, - amount: amount, - senderPublicKey: keypair.publicKey, - requesterPublicKey: nil, - date: lastRequestTimeDelta.map { Date().addingTimeInterval(-$0) } ?? Date(), - recipientId: recipient, - asset: .init() - ) - - guard let transaction = adamantCore.makeSignedTransaction( - transaction: normalizedTransaction, - senderId: sender, - keypair: keypair - ) else { - completion(.failure(InternalError.signTransactionFailed.apiServiceErrorWith(error: nil))) - return - } - - sendTransaction( - path: ApiCommands.Transactions.processTransaction, - transaction: transaction - ) { response in - switch response { - case .success(let result): - if let id = result.transactionId { - completion(.success(id)) - } else { - completion(.failure(.internalError(message: result.error ?? "Unknown Error", error: nil))) - } - - case .failure(let error): - completion(.failure(error)) - } - } - } - func transferFunds( sender: String, recipient: String, amount: Decimal, keypair: Keypair - ) async throws -> UInt64 { + ) async -> ApiServiceResult { let normalizedTransaction = NormalizedTransaction( type: .send, amount: amount, senderPublicKey: keypair.publicKey, requesterPublicKey: nil, - date: lastRequestTimeDelta.map { Date().addingTimeInterval(-$0) } ?? Date(), + date: .now, recipientId: recipient, asset: .init() ) @@ -69,31 +31,12 @@ extension AdamantApiService { senderId: sender, keypair: keypair ) else { - throw InternalError.signTransactionFailed.apiServiceErrorWith(error: nil) + return .failure(.internalError(error: InternalAPIError.signTransactionFailed)) } - return try await withUnsafeThrowingContinuation { (continuation: UnsafeContinuation) in - sendTransaction( - path: ApiCommands.Transactions.processTransaction, - transaction: transaction - ) { response in - switch response { - case .success(let result): - if let id = result.transactionId { - continuation.resume(returning: id) - } else { - continuation.resume( - throwing: ApiServiceError.internalError( - message: result.error ?? "Unknown Error", - error: nil - ) - ) - } - - case .failure(let error): - continuation.resume(throwing: error) - } - } - } + return await sendTransaction( + path: ApiCommands.Transactions.processTransaction, + transaction: transaction + ) } } diff --git a/Adamant/Services/ApiService/AdamantApiCore.swift b/Adamant/Services/ApiService/AdamantApiCore.swift new file mode 100644 index 000000000..f7a44f196 --- /dev/null +++ b/Adamant/Services/ApiService/AdamantApiCore.swift @@ -0,0 +1,49 @@ +// +// AdamantApiCore.swift +// Adamant +// +// Created by Andrew G on 31.10.2023. +// Copyright © 2023 Adamant. All rights reserved. +// + +import Foundation +import Alamofire +import CommonKit + +extension ApiCommands { + static let status = "/api/node/status" + static let version = "/api/peers/version" +} + +final class AdamantApiCore { + let apiCore: APICoreProtocol + + init(apiCore: APICoreProtocol) { + self.apiCore = apiCore + } + + func getNodeStatus(node: Node) async -> ApiServiceResult { + await apiCore.sendRequestJson( + node: node, + path: ApiCommands.status + ) + } +} + +extension AdamantApiCore: BlockchainHealthCheckableService { + func getStatusInfo(node: Node) async -> ApiServiceResult { + let startTimestamp = Date.now.timeIntervalSince1970 + let statusResponse = await getNodeStatus(node: node) + let ping = Date.now.timeIntervalSince1970 - startTimestamp + + return statusResponse.map { statusDto in + .init( + ping: ping, + height: statusDto.network?.height ?? .zero, + wsEnabled: statusDto.wsClient?.enabled ?? false, + wsPort: statusDto.wsClient?.port, + version: statusDto.version?.version + ) + } + } +} diff --git a/Adamant/Services/ApiService/AdamantApiService.swift b/Adamant/Services/ApiService/AdamantApiService.swift index 6c13ff585..035a0e3cb 100644 --- a/Adamant/Services/ApiService/AdamantApiService.swift +++ b/Adamant/Services/ApiService/AdamantApiService.swift @@ -6,469 +6,36 @@ // Copyright © 2018 Adamant. All rights reserved. // -import UIKit -import Alamofire import CommonKit -import Combine +import Foundation -actor AdamantApiService: ApiService { - // MARK: - Shared constants - - enum ApiCommands {} - - enum InternalError: Error { - case endpointBuildFailed - case signTransactionFailed - case parsingFailed - case unknownError - case noNodesAvailable - - func apiServiceErrorWith(error: Error?) -> ApiServiceError { - return .internalError(message: self.localized, error: error) - } - - var localized: String { - switch self { - case .endpointBuildFailed: - return .localized("ApiService.InternalError.EndpointBuildFailed", comment: "Serious internal error: Failed to build endpoint url") - - case .signTransactionFailed: - return .localized("ApiService.InternalError.FailedTransactionSigning", comment: "Serious internal error: Failed to sign transaction") - - case .parsingFailed: - return .localized("ApiService.InternalError.ParsingFailed", comment: "Serious internal error: Error parsing response") - - case .unknownError: - return String.adamant.sharedErrors.unknownError - - case .noNodesAvailable: - return .localized("ApiService.InternalError.NoNodesAvailable", comment: "Serious internal error: No nodes available") - } - } - } - - // MARK: - Dependencies - +final class AdamantApiService { let adamantCore: AdamantCore + let service: BlockchainHealthCheckWrapper - weak var nodesSource: NodesSource? { - didSet { - updateCurrentNodes() - } - } - - // MARK: - Properties - - @MainActor - var sendingMsgTaskId: UIBackgroundTaskIdentifier = UIBackgroundTaskIdentifier.invalid - - private(set) var lastRequestTimeDelta: TimeInterval? - private var subscriptions = Set() - private let timeOutInterval: TimeInterval = 15 - - private(set) var currentNodes: [Node] = [] { - didSet { - guard oldValue != currentNodes else { return } - sendCurrentNodeUpdateNotification() - } - } - - private let defaultResponseDispatchQueue = DispatchQueue( - label: "com.adamant.response-queue", - qos: .userInteractive - ) - - private lazy var manager: Session = { - let configuration = AF.sessionConfiguration - configuration.waitsForConnectivity = true - configuration.timeoutIntervalForRequest = timeOutInterval - configuration.timeoutIntervalForResource = timeOutInterval - let manager = Alamofire.Session.init(configuration: configuration) - return manager - }() - - // MARK: - Init - - init(adamantCore: AdamantCore) { - self.adamantCore = adamantCore - Task { await setupSubscriptions() } - } - - // MARK: - Tools - - func buildUrl(url: URL, path: String, queryItems: [URLQueryItem]? = nil) throws -> URL { - guard var components = URLComponents(url: url, resolvingAgainstBaseURL: false) else { - throw ApiServiceError.internalError(message: "Failed to build URL from \(url)", error: nil) - } - - components.path = path - components.queryItems = queryItems - - return try components.asURL() - } - - func sendRequest( - path: String, - queryItems: [URLQueryItem]? = nil, - method: HTTPMethod = .get, - waitsForConnectivity: Bool = false, - completion: @escaping (ApiServiceResult) -> Void - ) { - sendRequest( - path: path, - queryItems: queryItems, - method: method, - body: Optional.none, - waitsForConnectivity: waitsForConnectivity, - completion: completion - ) - } - - func sendRequest( - path: String, - queryItems: [URLQueryItem]? = nil, - method: HTTPMethod = .get, - body: Body? = nil, - waitsForConnectivity: Bool = false, - completion: @escaping (ApiServiceResult) -> Void + init( + healthCheckWrapper: BlockchainHealthCheckWrapper, + adamantCore: AdamantCore ) { - guard !currentNodes.isEmpty else { - let error = InternalError.endpointBuildFailed.apiServiceErrorWith( - error: InternalError.noNodesAvailable - ) - completion(.failure(error)) - return - } - - var needNodesUpdate = false - - sendSafeRequest( - nodes: currentNodes, - path: path, - queryItems: queryItems, - method: method, - body: body, - waitsForConnectivity: waitsForConnectivity, - onFailure: { node in - node.connectionStatus = .offline - needNodesUpdate = true - }, - completion: { [weak nodesSource] in - completion($0) - guard needNodesUpdate else { return } - nodesSource?.nodesUpdate() - } - ) - - updateCurrentNodes() - } - - @discardableResult - func sendRequest( - url: URLConvertible, - method: HTTPMethod = .get, - waitsForConnectivity: Bool = false, - completion: @escaping (ApiServiceResult) -> Void - ) -> DataRequest { - sendRequest( - url: url, - method: method, - body: Optional.none, - waitsForConnectivity: waitsForConnectivity, - completion: completion - ) - } - - private func createRequest( - url: URLConvertible, - method: HTTPMethod, - parameters: Parameters?, - encoding: ParameterEncoding, - waitsForConnectivity: Bool, - headers: HTTPHeaders? - ) -> DataRequest { - return manager.request( - url, - method: method, - parameters: parameters, - encoding: encoding, - headers: headers - ) - } - - func sendRequest(request: DataRequest) async throws -> Data { - return try await withUnsafeThrowingContinuation { (continuation: UnsafeContinuation) in - request.responseData(queue: defaultResponseDispatchQueue) { response in - switch response.result { - case .success(let data): - continuation.resume(returning: data) - - case .failure(let error): - continuation.resume(throwing: ApiServiceError.init(error: error)) - } - } - } - } - - @discardableResult - func sendRequest( - url: URLConvertible, - method: HTTPMethod = .get, - body: Body? = nil, - waitsForConnectivity: Bool = false, - completion: @escaping (ApiServiceResult) -> Void - ) -> DataRequest { - let request = createRequest( - url: url, - method: method, - parameters: body?.asDictionary, - encoding: JSONEncoding.default, - waitsForConnectivity: waitsForConnectivity, - headers: HTTPHeaders(["Content-Type": "application/json"]) - ) - - Task { - do { - let data = try await sendRequest(request: request) - - do { - let model = try JSONDecoder().decode(Output.self, from: data) - - if let timestampResponse = model as? ServerResponseWithTimestamp { - let nodeDate = AdamantUtilities.decodeAdamant(timestamp: timestampResponse.nodeTimestamp) - lastRequestTimeDelta = Date().timeIntervalSince(nodeDate) - } - - completion(.success(model)) - } catch { - completion(.failure(InternalError.parsingFailed.apiServiceErrorWith(error: error))) - } - } catch let error as ApiServiceError { - completion(.failure(error)) - } catch { - completion(.failure(.init(error: error))) - } - } - - return request - } - - func sendRequest( - url: URLConvertible, - method: HTTPMethod, - parameters: Parameters? - ) async throws -> Output { - try await sendRequest( - url: url, - method: method, - parameters: parameters, - encoding: URLEncoding.default - ) - } - - func sendRequest( - url: URLConvertible, - method: HTTPMethod, - parameters: Parameters?, - encoding: ParameterEncoding - ) async throws -> Output { - let data = try await sendRequest( - url: url, - method: method, - parameters: parameters, - encoding: encoding - ) - - do { - let model = try JSONDecoder().decode(Output.self, from: data) - return model - } catch { - throw InternalError.parsingFailed.apiServiceErrorWith(error: error) - } - } - - func sendRequest( - url: URLConvertible, - method: HTTPMethod, - parameters: Parameters? - ) async throws -> Data { - try await sendRequest( - url: url, - method: method, - parameters: parameters, - encoding: URLEncoding.default - ) - } - - func sendRequest( - url: URLConvertible, - method: HTTPMethod, - parameters: Parameters?, - encoding: ParameterEncoding - ) async throws -> Data { - return try await sendRequest( - url: url, - method: method, - parameters: parameters, - encoding: encoding, - waitsForConnectivity: false - ) - } - - private func sendRequest( - url: URLConvertible, - method: HTTPMethod, - parameters: Parameters?, - encoding: ParameterEncoding, - waitsForConnectivity: Bool - ) async throws -> Data { - let request = createRequest( - url: url, - method: method, - parameters: parameters, - encoding: encoding, - waitsForConnectivity: waitsForConnectivity, - headers: HTTPHeaders(["Content-Type": "application/json"]) - ) - - return try await sendRequest(request: request) + service = healthCheckWrapper + self.adamantCore = adamantCore } - static func translateServerError(_ error: String?) -> ApiServiceError { - guard let error = error else { - return InternalError.unknownError.apiServiceErrorWith(error: nil) - } - - switch error { - case "Account not found": - return .accountNotFound - - default: - return .serverError(error: error) + func request( + _ request: @Sendable (APICoreProtocol, Node) async -> ApiServiceResult + ) async -> ApiServiceResult { + await service.request { admApiCore, node in + await request(admApiCore.apiCore, node) } } - - func setupWeakDeps(nodesSource: NodesSource) { - self.nodesSource = nodesSource - } } -private extension AdamantApiService { - /// On failure this method doesn't call completion, it just goes to next node. Completion called on success or on last node failure. - func sendSafeRequest( - nodes: [Node], - path: String, - queryItems: [URLQueryItem]?, - method: HTTPMethod, - body: Body?, - waitsForConnectivity: Bool = false, - onFailure: @escaping (Node) -> Void, - completion: @escaping (ApiServiceResult) -> Void - ) { - guard let node = nodes.first else { - completion(.failure(.networkError(error: InternalError.unknownError))) - return - } - - let url: URL - do { - url = try buildUrl(node: node, path: path, queryItems: queryItems) - } catch { - let err = InternalError.endpointBuildFailed.apiServiceErrorWith(error: error) - completion(.failure(err)) - return - } - - sendRequest( - url: url, - method: method, - body: body, - waitsForConnectivity: waitsForConnectivity, - completion: makeSafeRequestCompletion( - nodes: nodes, - path: path, - queryItems: queryItems, - method: method, - body: body, - waitsForConnectivity: waitsForConnectivity, - onFailure: onFailure, - completion: completion - ) - ) - } - - func makeSafeRequestCompletion( - nodes: [Node], - path: String, - queryItems: [URLQueryItem]?, - method: HTTPMethod, - body: Body?, - waitsForConnectivity: Bool = false, - onFailure: @escaping (Node) -> Void, - completion: @escaping (ApiServiceResult) -> Void - ) -> (ApiServiceResult) -> Void { - { [weak self] result in - switch result { - case .success: - completion(result) - case let .failure(error): - switch error { - case .networkError: - var nodes = nodes - onFailure(nodes.removeFirst()) - Task { [weak self, nodes] in - await self?.sendSafeRequest( - nodes: nodes, - path: path, - queryItems: queryItems, - method: method, - body: body, - waitsForConnectivity: waitsForConnectivity, - onFailure: onFailure, - completion: completion - ) - } - case .accountNotFound, .internalError, .notLogged, .serverError, .requestCancelled, .commonError: - completion(result) - } - } - } - } - - func updateCurrentNodes() { - currentNodes = nodesSource?.getAllowedNodes(needWS: false) ?? [] +extension AdamantApiService: ApiService { + var preferredNodeIds: [UUID] { + service.preferredNodeIds } - func sendCurrentNodeUpdateNotification() { - NotificationCenter.default.post( - name: Notification.Name.ApiService.currentNodeUpdate, - object: self, - userInfo: nil - ) - } - - func buildUrl(node: Node, path: String, queryItems: [URLQueryItem]? = nil) throws -> URL { - guard let url = node.asURL() else { throw InternalError.endpointBuildFailed } - return try buildUrl(url: url, path: path, queryItems: queryItems) - } - - func setupSubscriptions() { - NotificationCenter.default - .publisher(for: .NodesSource.nodesUpdate, object: nil) - .sink { _ in Task { [weak self] in await self?.updateCurrentNodes() } } - .store(in: &subscriptions) - } -} - -private extension ApiServiceError { - init(error: Error) { - let afError = error as? AFError - - switch afError { - case .explicitlyCancelled: - self = .requestCancelled - default: - self = .networkError(error: error) - } + func healthCheck() { + service.healthCheck() } } diff --git a/Adamant/Services/BlockchainHealthCheckWrapper.swift b/Adamant/Services/BlockchainHealthCheckWrapper.swift new file mode 100644 index 000000000..9c741e017 --- /dev/null +++ b/Adamant/Services/BlockchainHealthCheckWrapper.swift @@ -0,0 +1,145 @@ +// +// BlockchainHealthCheckWrapper.swift +// Adamant +// +// Created by Andrew G on 22.10.2023. +// Copyright © 2023 Adamant. All rights reserved. +// + +import CommonKit +import Foundation + +protocol BlockchainHealthCheckableService { + associatedtype Error: HealthCheckableError + + func getStatusInfo(node: Node) async -> Result +} + +final class BlockchainHealthCheckWrapper< + Service: BlockchainHealthCheckableService +>: HealthCheckWrapper { + private let nodesStorage: NodesStorageProtocol + private let nodeGroup: NodeGroup + + @Atomic private var currentRequests = Set() + + init( + service: Service, + nodesStorage: NodesStorageProtocol, + nodesAdditionalParamsStorage: NodesAdditionalParamsStorageProtocol, + nodeGroup: NodeGroup + ) { + self.nodesStorage = nodesStorage + self.nodeGroup = nodeGroup + + super.init( + service: service, + normalUpdateInterval: nodeGroup.normalUpdateInterval, + crucialUpdateInterval: nodeGroup.crucialUpdateInterval + ) + + nodesStorage + .getNodesPublisher(group: nodeGroup) + .sink { [weak self] in self?.nodes = $0 } + .store(in: &subscriptions) + + nodesAdditionalParamsStorage + .fastestNodeMode(group: nodeGroup) + .sink { [weak self] in self?.fastestNodeMode = $0 } + .store(in: &subscriptions) + } + + override func healthCheck() { + super.healthCheck() + + Task { + updateNodesAvailability() + await withTaskGroup(of: Void.self, returning: Void.self) { group in + nodes.filter { $0.isEnabled }.forEach { node in + group.addTask { [weak self] in + guard let self = self, !currentRequests.contains(node.id) else { return } + await updateNodeStatusInfo(node: node) + } + } + + await group.waitForAll() + } + } + } +} + +private extension BlockchainHealthCheckWrapper { + func updateNodeStatusInfo(node: Node) async { + currentRequests.insert(node.id) + + defer { + currentRequests.remove(node.id) + updateNodesAvailability() + } + + switch await service.getStatusInfo(node: node) { + case let .success(statusInfo): + nodesStorage.updateNodeStatus(id: node.id, statusInfo: statusInfo) + case let .failure(error): + guard !error.isRequestCancelledError else { return } + nodesStorage.updateNodeParams(id: node.id, connectionStatus: .offline) + } + } + + func updateNodesAvailability() { + let workingNodes = nodes.filter { $0.isWorking } + + let actualHeightsRange = getActualNodeHeightsRange( + heights: workingNodes.compactMap { $0.height }, + group: nodeGroup + ) + + workingNodes.forEach { node in + let status: Node.ConnectionStatus? = node.height.map { height in + actualHeightsRange?.contains(height) ?? false + ? .allowed + : .synchronizing + } ?? .none + + nodesStorage.updateNodeParams( + id: node.id, + connectionStatus: status + ) + } + } +} + +private struct NodeHeightsInterval { + let range: ClosedRange + var count: Int +} + +private func getActualNodeHeightsRange(heights: [Int], group: NodeGroup) -> ClosedRange? { + guard heights.count > 2 else { return heights.max().map { $0...$0 } } + + let heights = heights.sorted() + var bestInterval: NodeHeightsInterval? + + for i in heights.indices { + var currentInterval = NodeHeightsInterval( + range: heights[i] - group.nodeHeightEpsilon ... heights[i] + group.nodeHeightEpsilon, + count: 1 + ) + + for j in stride(from: i + 1, to: heights.endIndex, by: 1) { + guard currentInterval.range.contains(heights[j]) else { break } + currentInterval.count += 1 + } + + for j in stride(from: i - 1, through: .zero, by: -1) { + guard currentInterval.range.contains(heights[j]) else { break } + currentInterval.count += 1 + } + + if currentInterval.count >= bestInterval?.count ?? .zero { + bestInterval = currentInterval + } + } + + return bestInterval?.range +} diff --git a/Adamant/Services/DataProviders/AdamantAccountsProvider.swift b/Adamant/Services/DataProviders/AdamantAccountsProvider.swift index 80399e897..a0dda4a56 100644 --- a/Adamant/Services/DataProviders/AdamantAccountsProvider.swift +++ b/Adamant/Services/DataProviders/AdamantAccountsProvider.swift @@ -222,7 +222,7 @@ extension AdamantAccountsProvider { switch validation { case .valid: do { - var account = try await apiService.getAccount(byAddress: address) + var account = try await apiService.getAccount(byAddress: address).get() guard account.publicKey != nil else { account.publicKey = "dummy\(address)" account.isDummy = true diff --git a/Adamant/Services/DataProviders/AdamantChatsProvider+backgroundFetch.swift b/Adamant/Services/DataProviders/AdamantChatsProvider+backgroundFetch.swift index 8515260cf..202e67d36 100644 --- a/Adamant/Services/DataProviders/AdamantChatsProvider+backgroundFetch.swift +++ b/Adamant/Services/DataProviders/AdamantChatsProvider+backgroundFetch.swift @@ -38,7 +38,7 @@ extension AdamantChatsProvider: BackgroundFetchService { address: address, height: lastHeight, offset: nil - ) + ).get() guard transactions.count > 0 else { return .noData } diff --git a/Adamant/Services/DataProviders/AdamantChatsProvider.swift b/Adamant/Services/DataProviders/AdamantChatsProvider.swift index b518b95ee..65dadde61 100644 --- a/Adamant/Services/DataProviders/AdamantChatsProvider.swift +++ b/Adamant/Services/DataProviders/AdamantChatsProvider.swift @@ -415,7 +415,7 @@ extension AdamantChatsProvider { func apiGetChatrooms(address: String, offset: Int?) async throws -> ChatRooms? { do { - let chatrooms = try await apiService.getChatRooms(address: address, offset: offset) + let chatrooms = try await apiService.getChatRooms(address: address, offset: offset).get() return chatrooms } catch let error as ApiServiceError { guard case .networkError = error else { @@ -525,7 +525,7 @@ extension AdamantChatsProvider { addressRecipient: addressRecipient, offset: offset, limit: limit - ) + ).get() return chatrooms } catch let error as ApiServiceError { guard case .networkError = error else { @@ -650,7 +650,7 @@ extension AdamantChatsProvider { case .accountNotFound: err = .accountNotFound(address) - case .serverError, .commonError: + case .serverError, .commonError, .noEndpointsAvailable: err = .serverError(error) case .internalError(let message, _): @@ -1150,7 +1150,7 @@ extension AdamantChatsProvider { } // MARK: 2. Create - let signedTransaction = await apiService.createSendTransaction( + let signedTransaction = try? adamantCore.makeSendMessageTransaction( senderId: senderId, recipientId: recipientId, keypair: keypair, @@ -1161,7 +1161,7 @@ extension AdamantChatsProvider { ) guard let signedTransaction = signedTransaction else { - throw ChatsProviderError.internalError(AdamantError(message: AdamantApiService.InternalError.signTransactionFailed.localized)) + throw ChatsProviderError.internalError(AdamantError(message: InternalAPIError.signTransactionFailed.localizedDescription)) } unconfirmedTransactionsBySignature.append(signedTransaction.signature) @@ -1169,7 +1169,7 @@ extension AdamantChatsProvider { // MARK: 3. Send do { - let id = try await apiService.sendTransaction(transaction: signedTransaction) + let id = try await apiService.sendMessageTransaction(transaction: signedTransaction).get() // Update ID with recieved, add to unconfirmed transactions. transaction.transactionId = String(id) @@ -1197,6 +1197,10 @@ extension AdamantChatsProvider { throw ChatsProviderError.notLogged case .serverError(let e), .commonError(let e): throw ChatsProviderError.serverError(AdamantError(message: e)) + case .noEndpointsAvailable: + throw ChatsProviderError.serverError(AdamantError( + message: error.localizedDescription + )) case .internalError(let message, _): throw ChatsProviderError.internalError(AdamantError(message: message)) case .requestCancelled: @@ -1333,7 +1337,7 @@ extension AdamantChatsProvider { address: senderId, height: height, offset: offset - ) + ).get() if transactions.count == 0 { return @@ -1703,7 +1707,7 @@ extension AdamantChatsProvider { } for chatroom in viewContextChatrooms { - await chatroom.updateLastTransaction() + chatroom.updateLastTransaction() } } } diff --git a/Adamant/Services/DataProviders/AdamantTransfersProvider+backgroundFetch.swift b/Adamant/Services/DataProviders/AdamantTransfersProvider+backgroundFetch.swift index 83f0a8f86..6af87ce7b 100644 --- a/Adamant/Services/DataProviders/AdamantTransfersProvider+backgroundFetch.swift +++ b/Adamant/Services/DataProviders/AdamantTransfersProvider+backgroundFetch.swift @@ -40,7 +40,7 @@ extension AdamantTransfersProvider: BackgroundFetchService { fromHeight: lastHeight, offset: 0, limit: 100 - ) + ).get() let total = transactions.filter({$0.recipientId == address}).count diff --git a/Adamant/Services/DataProviders/AdamantTransfersProvider.swift b/Adamant/Services/DataProviders/AdamantTransfersProvider.swift index e99abedd0..494642ff8 100644 --- a/Adamant/Services/DataProviders/AdamantTransfersProvider.swift +++ b/Adamant/Services/DataProviders/AdamantTransfersProvider.swift @@ -278,7 +278,7 @@ extension AdamantTransfersProvider { case .accountNotFound: err = .accountNotFound(address: address) - case .serverError, .commonError: + case .serverError, .commonError, .noEndpointsAvailable: err = .serverError(error) case .internalError(let message, _): @@ -505,7 +505,7 @@ extension AdamantTransfersProvider { ? .message : .richMessage - let signedTransaction = await apiService.createSendTransaction( + let signedTransaction = try? adamantCore.makeSendMessageTransaction( senderId: loggedAccount.address, recipientId: recipient, keypair: keypair, @@ -517,13 +517,13 @@ extension AdamantTransfersProvider { guard let signedTransaction = signedTransaction else { throw TransfersProviderError.internalError( - message: AdamantApiService.InternalError.signTransactionFailed.localized, + message: InternalAPIError.signTransactionFailed.localizedDescription, error: nil ) } do { - let id = try await apiService.sendTransaction(transaction: signedTransaction) + let id = try await apiService.sendMessageTransaction(transaction: signedTransaction).get() transaction.transactionId = String(id) await chatsProvider?.addUnconfirmed(transactionId: id, managedObjectId: transaction.objectID) @@ -671,7 +671,7 @@ extension AdamantTransfersProvider { recipient: recipient, amount: amount, keypair: keypair - ) + ).get() transaction.transactionId = String(id) @@ -752,7 +752,7 @@ extension AdamantTransfersProvider { } do { - let transaction = try await apiService.getTransaction(id: intId) + let transaction = try await apiService.getTransaction(id: intId).get() guard transfer.confirmations != transaction.confirmations else { return @@ -816,7 +816,7 @@ extension AdamantTransfersProvider { fromHeight: fromHeight, offset: offset, limit: self.apiTransactions - ) + ).get() guard transactions.count > 0 else { return @@ -864,7 +864,7 @@ extension AdamantTransfersProvider { offset: offset, limit: limit, orderByTime: orderByTime - ) + ).get() guard transactions.count > 0 else { return 0 @@ -1076,7 +1076,7 @@ extension AdamantTransfersProvider { } for chatroom in viewContextChatrooms { - await chatroom.updateLastTransaction() + chatroom.updateLastTransaction() } } } diff --git a/Adamant/Services/HealthCheckWrapper.swift b/Adamant/Services/HealthCheckWrapper.swift new file mode 100644 index 000000000..2871774f6 --- /dev/null +++ b/Adamant/Services/HealthCheckWrapper.swift @@ -0,0 +1,149 @@ +// +// HealthCheckWrapper.swift +// Adamant +// +// Created by Andrew G on 22.10.2023. +// Copyright © 2023 Adamant. All rights reserved. +// + +import CommonKit +import Foundation +import Combine + +protocol HealthCheckableError: Error { + var isNetworkError: Bool { get } + var isRequestCancelledError: Bool { get } + + static var noEndpointsError: Self { get } +} + +class HealthCheckWrapper { + @ObservableValue var nodes: [Node] = .init() + + let service: Service + let normalUpdateInterval: TimeInterval + let crucialUpdateInterval: TimeInterval + + @Atomic var fastestNodeMode = true + @Atomic var healthCheckTimerSubscription: AnyCancellable? + @Atomic var subscriptions: Set = .init() + + @ObservableValue private var allowedNodes: [Node] = .init() + + var preferredNodeIds: [UUID] { + fastestNodeMode + ? [allowedNodes.first?.id].compactMap { $0 } + : allowedNodes.map { $0.id } + } + + init( + service: Service, + normalUpdateInterval: TimeInterval, + crucialUpdateInterval: TimeInterval + ) { + self.service = service + self.normalUpdateInterval = normalUpdateInterval + self.crucialUpdateInterval = crucialUpdateInterval + + $nodes + .removeDuplicates { !$0.doesNeedHealthCheck($1) } + .sink { [weak self] _ in self?.healthCheck() } + .store(in: &subscriptions) + + $nodes + .removeDuplicates() + .sink { [weak self] in + self?.allowedNodes = $0.getAllowedNodes( + sortedBySpeedDescending: true, + needWS: false + ) + } + .store(in: &subscriptions) + + $allowedNodes + .map { $0.isEmpty } + .removeDuplicates() + .sink { [weak self] _ in self?.updateHealthCheckTimerSubscription() } + .store(in: &subscriptions) + } + + func request( + _ request: @Sendable (Service, Node) async -> Result + ) async -> Result { + var lastConnectionError = allowedNodes.isEmpty + ? Error.noEndpointsError + : nil + + let nodesList = allowedNodes.isEmpty + ? nodes.filter { $0.isEnabled }.shuffled() + : fastestNodeMode + ? allowedNodes + : allowedNodes.shuffled() + + for node in nodesList { + let response = await request(service, node) + + switch response { + case .success: + return response + case let .failure(error): + guard error.isNetworkError else { return response } + lastConnectionError = error + } + } + + if lastConnectionError != nil { healthCheck() } + return .failure(lastConnectionError ?? Error.noEndpointsError) + } + + func healthCheck() { + updateHealthCheckTimerSubscription() + } +} + +extension Node { + var isWorking: Bool { + switch connectionStatus { + case .allowed, .synchronizing, .none: + return isEnabled + case .offline: + return false + } + } +} + +private extension HealthCheckWrapper { + func updateHealthCheckTimerSubscription() { + healthCheckTimerSubscription = Timer.publish( + every: allowedNodes.isEmpty + ? crucialUpdateInterval + : normalUpdateInterval, + on: .main, + in: .default + ).autoconnect().sink { [weak self] _ in + self?.healthCheck() + } + } +} + +private extension Node { + func doesNeedHealthCheck(_ node: Node) -> Bool { + scheme != node.scheme || + host != node.host || + isEnabled != node.isEnabled || + port != node.port + } +} + +private extension Sequence where Element == Node { + func doesNeedHealthCheck(_ nodes: Nodes) -> Bool where Nodes.Element == Self.Element { + let firstNodes = Dictionary(uniqueKeysWithValues: map { ($0.id, $0) }) + let secondNodes = Dictionary(uniqueKeysWithValues: nodes.map { ($0.id, $0) }) + + guard Set(firstNodes.keys) == Set(secondNodes.keys) else { return true } + + return firstNodes.contains { id, firstNode in + secondNodes[id]?.doesNeedHealthCheck(firstNode) ?? true + } + } +} diff --git a/Adamant/Services/NodesAdditionalParamsStorage.swift b/Adamant/Services/NodesAdditionalParamsStorage.swift new file mode 100644 index 000000000..9dad62d1f --- /dev/null +++ b/Adamant/Services/NodesAdditionalParamsStorage.swift @@ -0,0 +1,62 @@ +// +// NodesAdditionalParamsStorage.swift +// Adamant +// +// Created by Andrew G on 18.11.2023. +// Copyright © 2023 Adamant. All rights reserved. +// + +import Foundation +import CommonKit +import Combine + +final class NodesAdditionalParamsStorage: NodesAdditionalParamsStorageProtocol { + @Atomic private var fastestNodeModeValues: ObservableValue<[NodeGroup: Bool]> + + private let securedStore: SecuredStore + private var subscription: AnyCancellable? + + func isFastestNodeMode(group: NodeGroup) -> Bool { + fastestNodeModeValues.wrappedValue[group] ?? group.defaultFastestNodeMode + } + + func fastestNodeMode(group: NodeGroup) -> AnyObservable { + fastestNodeModeValues + .map { $0[group] ?? group.defaultFastestNodeMode } + .removeDuplicates() + .eraseToAnyPublisher() + } + + func setFastestNodeMode(groups: Set, value: Bool) { + $fastestNodeModeValues.mutate { dict in + groups.forEach { + dict.wrappedValue[$0] = value + } + } + } + + func setFastestNodeMode(group: NodeGroup, value: Bool) { + fastestNodeModeValues.wrappedValue[group] = value + } + + init(securedStore: SecuredStore) { + self.securedStore = securedStore + + _fastestNodeModeValues = .init(wrappedValue: .init( + wrappedValue: securedStore.get( + StoreKey.NodesAdditionalParamsStorage.fastestNodeMode + ) ?? [:] + )) + + subscription = fastestNodeModeValues.removeDuplicates().sink { [weak self] in + guard let self = self, subscription != nil else { return } + saveFastestNodeMode($0) + } + } +} + +private extension NodesAdditionalParamsStorage { + func saveFastestNodeMode(_ dict: [NodeGroup: Bool]) { + securedStore.set(dict, for: StoreKey.NodesAdditionalParamsStorage.fastestNodeMode) + } +} diff --git a/Adamant/Services/NodesStorage.swift b/Adamant/Services/NodesStorage.swift new file mode 100644 index 000000000..c53a2be8a --- /dev/null +++ b/Adamant/Services/NodesStorage.swift @@ -0,0 +1,144 @@ +// +// NodesStorage.swift +// Adamant +// +// Created by Andrew G on 30.10.2023. +// Copyright © 2023 Adamant. All rights reserved. +// + +import CommonKit +import Foundation +import Combine + +final class NodesStorage: NodesStorageProtocol { + @Atomic private var items: ObservableValue<[NodeItem]> + + private var subscription: AnyCancellable? + private let securedStore: SecuredStore + + func getNodesPublisher(group: NodeGroup) -> AnyObservable<[Node]> { + items + .map { $0.filter { $0.group == group }.map { $0.node } } + .removeDuplicates() + .eraseToAnyPublisher() + } + + func addNode(_ node: Node, group: NodeGroup) { + items.wrappedValue.append(.init(group: group, node: node)) + } + + func removeNode(id: UUID) { + $items.mutate { items in + guard let index = items.wrappedValue.getIndex(id: id) else { return } + items.wrappedValue.remove(at: index) + } + } + + func updateNode( + id: UUID, + scheme: CommonKit.Node.URLScheme?, + host: String?, + isEnabled: Bool?, + wsEnabled: Bool?, + port: Int??, + wsPort: Int??, + version: String??, + height: Int??, + ping: TimeInterval??, + connectionStatus: CommonKit.Node.ConnectionStatus?? + ) { + $items.mutate { items in + guard + let index = items.wrappedValue.getIndex(id: id), + var node = items.wrappedValue[safe: index]?.node + else { return } + + scheme.map { node.scheme = $0 } + host.map { node.host = $0 } + wsEnabled.map { node.wsEnabled = $0 } + port.map { node.port = $0 } + wsPort.map { node.wsPort = $0 } + version.map { node.version = $0 } + height.map { node.height = $0 } + ping.map { node.ping = $0 } + connectionStatus.map { node.connectionStatus = $0 } + + if let isEnabled = isEnabled { + node.isEnabled = isEnabled + + if !isEnabled { + node.connectionStatus = nil + } + } + + items.wrappedValue[index].node = node + } + } + + func resetNodes(group: NodeGroup) { + $items.mutate { items in + items.wrappedValue = items.wrappedValue.filter { + $0.group != group + } + + items.wrappedValue += Self.defaultItems(group: group) + } + } + + init(securedStore: SecuredStore) { + self.securedStore = securedStore + + _items = .init(wrappedValue: .init( + wrappedValue: securedStore.get(StoreKey.NodesStorage.nodes) ?? Self.defaultItems + )) + + subscription = items.removeDuplicates().sink { [weak self] in + guard let self = self, subscription != nil else { return } + saveNodes(nodes: $0) + } + } +} + +private extension NodesStorage { + struct NodeItem: Codable, Equatable { + let group: NodeGroup + var node: Node + } + + static func defaultItems(group: NodeGroup) -> [NodeItem] { + switch group { + case .btc: + return BtcWalletService.nodes.map { .init(group: .btc, node: $0) } + case .eth: + return EthWalletService.nodes.map { .init(group: .eth, node: $0) } + case .lskNode: + return LskWalletService.nodes.map { .init(group: .lskNode, node: $0) } + case .lskService: + return LskWalletService.serviceNodes.map { .init(group: .lskService, node: $0) } + case .doge: + return DogeWalletService.nodes.map { .init(group: .doge, node: $0) } + case .dash: + return DashWalletService.nodes.map { .init(group: .dash, node: $0) } + case .adm: + return AdmWalletService.nodes.map { .init(group: .adm, node: $0) } + } + } + + static var defaultItems: [NodeItem] { + NodeGroup.allCases.flatMap { Self.defaultItems(group: $0) } + } + + func saveNodes(nodes: [NodeItem]) { + securedStore.set(nodes, for: StoreKey.NodesStorage.nodes) + } +} + +private extension Array where Element == NodesStorage.NodeItem { + func getNode(id: UUID) -> Node? { + first { $0.node.id == id }?.node + } + + func getIndex(id: UUID) -> Int? { + firstIndex { $0.node.id == id } + } +} diff --git a/Adamant/Services/RichTransactionReplyService/AdamantRichTransactionReplyService.swift b/Adamant/Services/RichTransactionReplyService/AdamantRichTransactionReplyService.swift index 702c1e1f6..9c82550fc 100644 --- a/Adamant/Services/RichTransactionReplyService/AdamantRichTransactionReplyService.swift +++ b/Adamant/Services/RichTransactionReplyService/AdamantRichTransactionReplyService.swift @@ -131,7 +131,7 @@ private extension AdamantRichTransactionReplyService { } func getTransactionFromAPI(by id: UInt64) async throws -> Transaction { - try await apiService.getTransaction(id: id, withAsset: true) + try await apiService.getTransaction(id: id, withAsset: true).get() } func getReplyMessage(from transaction: Transaction) throws -> String { diff --git a/Adamant/Services/SocketService/AdamantSocketService.swift b/Adamant/Services/SocketService/AdamantSocketService.swift index 3d384d63b..5899bda83 100644 --- a/Adamant/Services/SocketService/AdamantSocketService.swift +++ b/Adamant/Services/SocketService/AdamantSocketService.swift @@ -9,16 +9,11 @@ import Foundation import SocketIO import CommonKit +import Combine final class AdamantSocketService: SocketService { - - // MARK: - Dependencies - - weak var nodesSource: NodesSource? { - didSet { - refreshNode() - } - } + private let nodesStorage: NodesStorageProtocol + private let nodesAdditionalParamsStorage: NodesAdditionalParamsStorageProtocol // MARK: - Properties @@ -26,7 +21,7 @@ final class AdamantSocketService: SocketService { didSet { currentUrl = currentNode?.asSocketURL() - guard oldValue !== currentNode else { return } + guard oldValue?.id != currentNode?.id else { return } sendCurrentNodeUpdateNotification() } } @@ -49,20 +44,25 @@ final class AdamantSocketService: SocketService { @Atomic private var socket: SocketIOClient? @Atomic private var currentAddress: String? @Atomic private var currentHandler: ((ApiServiceResult) -> Void)? + @Atomic private var subscriptions = Set() let defaultResponseDispatchQueue = DispatchQueue( label: "com.adamant.response-queue", qos: .utility ) - init() { - NotificationCenter.default.addObserver( - forName: Notification.Name.NodesSource.nodesUpdate, - object: nil, - queue: nil - ) { [weak self] _ in - self?.refreshNode() - } + init( + nodesStorage: NodesStorageProtocol, + nodesAdditionalParamsStorage: NodesAdditionalParamsStorageProtocol + ) { + self.nodesAdditionalParamsStorage = nodesAdditionalParamsStorage + self.nodesStorage = nodesStorage + + nodesStorage + .getNodesPublisher(group: .adm) + .combineLatest(nodesAdditionalParamsStorage.fastestNodeMode(group: .adm)) + .sink { [weak self] in self?.updateCurrentNode(nodes: $0.0, fastestNode: $0.1) } + .store(in: &subscriptions) } // MARK: - Tools @@ -97,10 +97,6 @@ final class AdamantSocketService: SocketService { manager = nil } - private func refreshNode() { - currentNode = nodesSource?.getAllowedNodes(needWS: true).first - } - private func handleTransaction(data: [Any]) { guard let data = data.first, @@ -126,4 +122,19 @@ final class AdamantSocketService: SocketService { userInfo: nil ) } + + private func updateCurrentNode(nodes: [Node], fastestNode: Bool) { + let allowedNodes = nodes.getAllowedNodes( + sortedBySpeedDescending: fastestNode, + needWS: true + ) + + guard !fastestNode else { + currentNode = allowedNodes.first + return + } + + guard currentNode.map({ !allowedNodes.contains($0) }) ?? true else { return } + currentNode = allowedNodes.randomElement() + } } diff --git a/AdamantTests/Core/adamant-core.js b/AdamantTests/Core/adamant-core.js index 9307a0077..fd8b69d08 100644 --- a/AdamantTests/Core/adamant-core.js +++ b/AdamantTests/Core/adamant-core.js @@ -66584,4 +66584,4 @@ exports.decode = decode; //# sourceMappingURL=utf8.js.map /***/ }) -/******/ ]); \ No newline at end of file +/******/ ]); diff --git a/CommonKit/Sources/CommonKit/AdamantDynamicResources.swift b/CommonKit/Sources/CommonKit/AdamantDynamicResources.swift index c538d3afc..152b7b570 100644 --- a/CommonKit/Sources/CommonKit/AdamantDynamicResources.swift +++ b/CommonKit/Sources/CommonKit/AdamantDynamicResources.swift @@ -18,4 +18,4 @@ Node(url: URL(string: "https://node2.blockchain2fa.io")!), ] } -} \ No newline at end of file +} diff --git a/CommonKit/Sources/CommonKit/Assets/Shared.xcassets/Wallets/verse_notification.imageset/Contents.json b/CommonKit/Sources/CommonKit/Assets/Shared.xcassets/Wallets/verse_notification.imageset/Contents.json new file mode 100644 index 000000000..eac049afb --- /dev/null +++ b/CommonKit/Sources/CommonKit/Assets/Shared.xcassets/Wallets/verse_notification.imageset/Contents.json @@ -0,0 +1,23 @@ +{ + "images" : [ + { + "idiom" : "universal", + "scale" : "1x", + "filename" : "verse_notification.png" + }, + { + "idiom" : "universal", + "scale" : "2x", + "filename" : "verse_notification@2x.png" + }, + { + "idiom" : "universal", + "scale" : "3x", + "filename" : "verse_notification@3x.png" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} diff --git a/CommonKit/Sources/CommonKit/Assets/Shared.xcassets/Wallets/verse_notification.imageset/verse_notification.png b/CommonKit/Sources/CommonKit/Assets/Shared.xcassets/Wallets/verse_notification.imageset/verse_notification.png new file mode 100644 index 0000000000000000000000000000000000000000..aa7be0a56b67466f947a1f0a194aa6c243716418 GIT binary patch literal 1074 zcmV-21kL-2P)f664fjIzo=&-gjFdqpiz{t)l39tyvS9ZumOMP&M4zpnb<}Y7F z;UbubUnlS!(o%rUw9gS|J%Z-1@o=}i|WZ=0a zlbL1mgyzUg)Cc$lr?pstl?ua6V}pas(fY%E_E{nB;=OhY=7X)m<{>R8D}#g^Xqx;5 z?UM@r&%2`bQpllI5i_zO*}P84A_BJ2s74~lv;|a)%_UE{TB(w1{U$C@bq@9oO8Ohq z{zH6JS9=%QE1B}^RFX02RQs#m=hg-p!BMCQqSf`pcgLg{^iIe@YP-XNsgX=5_e`~M zO9bYzvXNBUnMsI(7U0C}N5EFje3I~iQDp+@mrZ3ODHv1gJG~EmLZE}2H3V`ISOKu9 zOdzoDUS$JvA-P?_`6sX8lJ-N!w6-xIJA)p|SZENNP0qx%h`y*SmgSO$k5xVC0$i`X zC8rsl(q;aM4`DB1-Q^s>0g35VOk5NDCr+`%)F0cvkV|me3DXn%r~IHo7vNgY+N%L+ zhmZLu-{Oc|ybLrQGK`e^$wX+#Cc=-iE&%+W8Mv&3hoPb6NaHBcT4Y20GH@=n7fP6s z4Gly8^w8qy!Toex>L(J@+l^>10glIEkV$r3%V{F-y!t0Oh}iM|O~9DN2XH?~d>ynT z2#^mZ?|cD%=6yLE=PD1{6NQF`A(y^W>|>C5hBrl`dy%yyawarvJnV~`ST*EMwDho1 zEC~%M@F#^R{qmtbS&N|Z1gz=jb#NqAaw(#c$5-2aHm}ygj8%b4+U)2 z0`>l)X-mS#P;>C!W0O@Mv+zC)_y9MEsblqA>Nw}@OCBG<_M-X4)W9Dz-4EdBUY9Gu zt#_dtsa(~VGe9XYwKfAGn6Dm8)H-(@1E$_XxdJR_0E)1pXm+PBH7DBIIlTK(CpK7IL}pYreL>|K5>cb|WwlG?3D#6U_VI2@?N;!ppqK5O~mz!nvl zp8+^Cpg1st>T@f<`nL53K*nAw5Z3{?^I-KPNTZtj!(iN%uBy*77PlH90jw{<$_O~^ zEerGO1y01Rsf=CJj5BYxVPXgIFbaTKNo*#*85ns6j3*rPj*<-@4CDZ=E(^G)uSdzU zOE(?^hpv;D$OB+J@p!eYyC}HNSq0vLCUDoovUBXacagkAjsWXPmZdD%rv$IC`9otz zuJI#)wln?^zg6op#tYojSGKvx=0fi^+PqqyB|cXvKwAcU{KUP;5@=kTjRPjy#*5Z> zZNQ5xfW}#t1uXo67qRy*+>wUhGDFbK~zcC?oLL*T?WTqP4D({3gttSf+X zZ*zzDH8^IR%UINNd^4w5S6WO|h5)t=9IxfCW8i){Udn7sbC0%QVp6o-&wvdnx_&_W z+E?f_43V!?f{TErWeyb!V6hjvYGYUzMA#Sf}G6Z1mUnZptn>@ty5$<-xrsc#8_?2gP-+0gUWv-YbM{8S9{#>ZW!>zHP8^ zG5SyTM3Ja)aSU1Lj|LAj(@y_Ite$tUc9o9vT?97OJnAym7^n}=fm{NUq0XAAV%Dwo zrK@SX=#U3s#l#%8jr6~>PJyn06N|1d4@yO%lFCw3n+Ko(9;7z!a?xVe{ehm)eJfQN zYu-{HLMkLF>v#YrW;hyN#}?#48-17q)Bnx~J2ALQ_+ki`R#?)9(0ZiJc7%%n4%+50 zY3+7JNoxp9|53CqVge4zy7k7zeYXts0Khg#ebysM0S9e!m?)|y_A@?r?Fh!emB_#w z0kT65+Oq|Hh(7(N03580Bs#C!$%AILxo$@w?<(tY3ZgHE7W5$*wqa!)e6fTED_ZG8 zHQOE;!XrXr#BXv+fpKbRT`c=;M=)}gSizfGs$-_czaQx+HFn?cZT1fZ4S>seK;qu z2CI5jx%&okgYLIIDnh__eTbkPLDSw3ffK3pn!tra%UBaleMse;>2HkV_Cn<1Aseo$ zcD(&@9!S~|bY#G`M0pHcP6Rj`Z4ULAN#!J^UCfS8R326r$U4=y7LRGKjLy-HtJA>I za+lCDmP#I^MD91QcUi}Zm{1bA8EBisjM23NX%nA$Iw=^mj_LB$A*Yp+RG*QQ`)-k`r<(oJr#bwxx{LS<}jR9Jl$;NaaCdB^F|Ek!L#ant>B(D1CV#Vjz{+ zwr)BbQ~$+(PhWos-T2`P^ys~6rtz?DNDKOq(XnIJQF_(POy{s}=&LrzgyMua?w!`F zX1He4I$aR{0(UwuZ0D7qA52vLQ!>}9d0dCdw9bUlsQ2}Vfp({T#)=W+Xa;*4ZXIA| z^Ks(IxMOq*(H8}pIdFS@7r<+03lq@HUFD(M;?GZ_A3MKJBXZtw=x^`0hve^ u_9d7}OE9+w7VyviO!PAr6qe$z00RKm!k6EUxW8Wj0000*$d1(qwYQbD8&qN~8_;M9RnFu+(J6bTYb5R2Xaj54{Ii%5Y# z?ia9t6s&6f{OIlf?3@2e&8LpG=A*O`Kje-F^|B-n!4Z7f0d%+f&moM^+U!3H&b0wn z!MfpxKp9GH{W1ez-~2rUtqid1&%YD2s*)Ip0|Q~!q6KWjqOLw6{UMhOWjr8$+kXbe z9b=ZVnhMNcF#{?i1yNbThe$xI0`zqc=!&5XH%1)pKcfcOF?Oj$X}opiR@W{O!l*3ciwLKL6S@?XkC-f{La*sJxLi8MRLL>tRQY9}lg4wi>Y<(PRty2aHW6#8m1LmhyEls?+V<@Yw zqXM$W%y@I@k0Du284!$Y9ocFI1f{MyzHsaqM6CJPb!{^V07#Wt|7IYAQ2O~S^fMu7 zDcN1KI}Zvn8+%;#{c(U_5pRF2+XCMa*Bs%p>oarsh zk{*E^ni!IWS1QI_!~rD($zTQrB?HM|1_dPp$zTQrB?HM|1_htXXAA(<#z^>)i4{kE~?vGO1;?PoyV8ukn~tB+NfhGd4EWjYtm!Ike#JuE6IY> zb(OWWwUpe}z=BdGOVv>71s55QB@0NIVamx;KJYlQfc4NkrsPF=w6#nwJ$G4RC4DZs@Ulk@h{E+AjTS!?{61CWo^^S@WijPY_*--_ zgK9czUE1YL1H7|20Fc(TL;&Rnyw*x|Mji(=Kfno#p3R#cine6udnk3v zo_defv}?p*EftXCfbPisUi2msL(lh}7L|vRCMkQXZXAnr47>^8PF6tMrgSy-54KQt zwO(>iRagy7Ay`ZI%7tFy1z_1 zuJxO$M6~sIJyimF26_2IdEN&u{M1t32K1o6n=%CkYw5d01d-gR#AX=KDBfOzGPKMW zQ;A5GWmX=pv;;cPGqkbI&Y{#XTtB!Q^?0ZdfW|1Uq4%Q??OFHpJR%9$N@BE@s+hiL zIl0+70@(|AOg|aC3N)TPlqwNDfz0jx{P3|PtF72WkPZ zr}n=BZ&&6Rs00*p4Y-fRedpE(0w&+38z`-S)|-YtTb6x6iKFx~bey%9uLKXK>Zv&1UN+BRxC+v2;7y(_UtaT!$76-EKTL{>B@mUr@=3)fji+TuVS zN~qG!)Z#ir;N%LzN~(M6Gn8>!OJ%VZ9+Ink{yp{<2l7z*D&3?4W(D_H#NXmT9!jN= z^Ok}-8JSmt0CAo8TO1fl0aYT>D|D*dY6vbfx-*Vqgi>h;G%sFNBI zsNJm#5w|!rao=n$?X2ZW*tAZxk@O&Maj2n94kZO^3E4W~V_y6`W{tE2bfR4jx8zF>N^I6o{xzs zUHgpj$=AI2EQou>q&cUXFzJS*pMuC$a&bHZ=0&(?{Q@HiJTRtDul z(5zYJf@q!C5r4VM6Pyd1(`=7Z3=2$e8n6dgoY)xV4J;!UWpGNRO3|EvFt^y0uQwZE>)UCF@#B&cxkYCtiq;-_DjHdnoPJ3dXKxtrI#? z%7~BM+;;U<-JN`G8l>V+di?yGm{cpts|Tgs9y+nBUh4$i7KcNP>uk;|8wTxfg4U)> zMe)&fjZWg>n+~w)9GvKg1snRZnr_yt4>cOnDq%s++nKv#Nzyuo59P7d zoN=t#O6)ie$vWbJ^2=(f41L5d9mG|uBUGM4vyQl+{DMA; zh4L6q6>nzDnUz*1ZrozIVTpFAb#3#3EoZ4^eC@jCNDInyw?5#QQ{<<_O)*)<_@F#a zh=wL1qL!_1O#-tBIf0=EWwC3I*?Q(y5v7Z!2jwxK3e0bj0GfmNE7TezL)i?_Ta&qC z*8Aku~MJO<4Sq_=t5ZxYTp2w8d!TRTGWkk$7_?Q hY<#cq4psjPFaXJ{>Q{ImNW}mE002ovPDHLkV1nt|!r1@- literal 0 HcmV?d00001 diff --git a/CommonKit/Sources/CommonKit/Assets/Shared.xcassets/Wallets/verse_wallet.imageset/Contents.json b/CommonKit/Sources/CommonKit/Assets/Shared.xcassets/Wallets/verse_wallet.imageset/Contents.json new file mode 100644 index 000000000..4dab739fb --- /dev/null +++ b/CommonKit/Sources/CommonKit/Assets/Shared.xcassets/Wallets/verse_wallet.imageset/Contents.json @@ -0,0 +1,23 @@ +{ + "images" : [ + { + "idiom" : "universal", + "scale" : "1x", + "filename" : "verse_wallet.png" + }, + { + "idiom" : "universal", + "scale" : "2x", + "filename" : "verse_wallet@2x.png" + }, + { + "idiom" : "universal", + "scale" : "3x", + "filename" : "verse_wallet@3x.png" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} diff --git a/CommonKit/Sources/CommonKit/Assets/Shared.xcassets/Wallets/verse_wallet.imageset/verse_wallet.png b/CommonKit/Sources/CommonKit/Assets/Shared.xcassets/Wallets/verse_wallet.imageset/verse_wallet.png new file mode 100644 index 0000000000000000000000000000000000000000..aa7be0a56b67466f947a1f0a194aa6c243716418 GIT binary patch literal 1074 zcmV-21kL-2P)f664fjIzo=&-gjFdqpiz{t)l39tyvS9ZumOMP&M4zpnb<}Y7F z;UbubUnlS!(o%rUw9gS|J%Z-1@o=}i|WZ=0a zlbL1mgyzUg)Cc$lr?pstl?ua6V}pas(fY%E_E{nB;=OhY=7X)m<{>R8D}#g^Xqx;5 z?UM@r&%2`bQpllI5i_zO*}P84A_BJ2s74~lv;|a)%_UE{TB(w1{U$C@bq@9oO8Ohq z{zH6JS9=%QE1B}^RFX02RQs#m=hg-p!BMCQqSf`pcgLg{^iIe@YP-XNsgX=5_e`~M zO9bYzvXNBUnMsI(7U0C}N5EFje3I~iQDp+@mrZ3ODHv1gJG~EmLZE}2H3V`ISOKu9 zOdzoDUS$JvA-P?_`6sX8lJ-N!w6-xIJA)p|SZENNP0qx%h`y*SmgSO$k5xVC0$i`X zC8rsl(q;aM4`DB1-Q^s>0g35VOk5NDCr+`%)F0cvkV|me3DXn%r~IHo7vNgY+N%L+ zhmZLu-{Oc|ybLrQGK`e^$wX+#Cc=-iE&%+W8Mv&3hoPb6NaHBcT4Y20GH@=n7fP6s z4Gly8^w8qy!Toex>L(J@+l^>10glIEkV$r3%V{F-y!t0Oh}iM|O~9DN2XH?~d>ynT z2#^mZ?|cD%=6yLE=PD1{6NQF`A(y^W>|>C5hBrl`dy%yyawarvJnV~`ST*EMwDho1 zEC~%M@F#^R{qmtbS&N|Z1gz=jb#NqAaw(#c$5-2aHm}ygj8%b4+U)2 z0`>l)X-mS#P;>C!W0O@Mv+zC)_y9MEsblqA>Nw}@OCBG<_M-X4)W9Dz-4EdBUY9Gu zt#_dtsa(~VGe9XYwKfAGn6Dm8)H-(@1E$_XxdJR_0E)1pXm+PBH7DBIIlTK(CpK7IL}pYreL>|K5>cb|WwlG?3D#6U_VI2@?N;!ppqK5O~mz!nvl zp8+^Cpg1st>T@f<`nL53K*nAw5Z3{?^I-KPNTZtj!(iN%uBy*77PlH90jw{<$_O~^ zEerGO1y01Rsf=CJj5BYxVPXgIFbaTKNo*#*85ns6j3*rPj*<-@4CDZ=E(^G)uSdzU zOE(?^hpv;D$OB+J@p!eYyC}HNSq0vLCUDoovUBXacagkAjsWXPmZdD%rv$IC`9otz zuJI#)wln?^zg6op#tYojSGKvx=0fi^+PqqyB|cXvKwAcU{KUP;5@=kTjRPjy#*5Z> zZNQ5xfW}#t1uXo67qRy*+>wUhGDFbK~zcC?oLL*T?WTqP4D({3gttSf+X zZ*zzDH8^IR%UINNd^4w5S6WO|h5)t=9IxfCW8i){Udn7sbC0%QVp6o-&wvdnx_&_W z+E?f_43V!?f{TErWeyb!V6hjvYGYUzMA#Sf}G6Z1mUnZptn>@ty5$<-xrsc#8_?2gP-+0gUWv-YbM{8S9{#>ZW!>zHP8^ zG5SyTM3Ja)aSU1Lj|LAj(@y_Ite$tUc9o9vT?97OJnAym7^n}=fm{NUq0XAAV%Dwo zrK@SX=#U3s#l#%8jr6~>PJyn06N|1d4@yO%lFCw3n+Ko(9;7z!a?xVe{ehm)eJfQN zYu-{HLMkLF>v#YrW;hyN#}?#48-17q)Bnx~J2ALQ_+ki`R#?)9(0ZiJc7%%n4%+50 zY3+7JNoxp9|53CqVge4zy7k7zeYXts0Khg#ebysM0S9e!m?)|y_A@?r?Fh!emB_#w z0kT65+Oq|Hh(7(N03580Bs#C!$%AILxo$@w?<(tY3ZgHE7W5$*wqa!)e6fTED_ZG8 zHQOE;!XrXr#BXv+fpKbRT`c=;M=)}gSizfGs$-_czaQx+HFn?cZT1fZ4S>seK;qu z2CI5jx%&okgYLIIDnh__eTbkPLDSw3ffK3pn!tra%UBaleMse;>2HkV_Cn<1Aseo$ zcD(&@9!S~|bY#G`M0pHcP6Rj`Z4ULAN#!J^UCfS8R326r$U4=y7LRGKjLy-HtJA>I za+lCDmP#I^MD91QcUi}Zm{1bA8EBisjM23NX%nA$Iw=^mj_LB$A*Yp+RG*QQ`)-k`r<(oJr#bwxx{LS<}jR9Jl$;NaaCdB^F|Ek!L#ant>B(D1CV#Vjz{+ zwr)BbQ~$+(PhWos-T2`P^ys~6rtz?DNDKOq(XnIJQF_(POy{s}=&LrzgyMua?w!`F zX1He4I$aR{0(UwuZ0D7qA52vLQ!>}9d0dCdw9bUlsQ2}Vfp({T#)=W+Xa;*4ZXIA| z^Ks(IxMOq*(H8}pIdFS@7r<+03lq@HUFD(M;?GZ_A3MKJBXZtw=x^`0hve^ u_9d7}OE9+w7VyviO!PAr6qe$z00RKm!k6EUxW8Wj0000*$d1(qwYQbD8&qN~8_;M9RnFu+(J6bTYb5R2Xaj54{Ii%5Y# z?ia9t6s&6f{OIlf?3@2e&8LpG=A*O`Kje-F^|B-n!4Z7f0d%+f&moM^+U!3H&b0wn z!MfpxKp9GH{W1ez-~2rUtqid1&%YD2s*)Ip0|Q~!q6KWjqOLw6{UMhOWjr8$+kXbe z9b=ZVnhMNcF#{?i1yNbThe$xI0`zqc=!&5XH%1)pKcfcOF?Oj$X}opiR@W{O!l*3ciwLKL6S@?XkC-f{La*sJxLi8MRLL>tRQY9}lg4wi>Y<(PRty2aHW6#8m1LmhyEls?+V<@Yw zqXM$W%y@I@k0Du284!$Y9ocFI1f{MyzHsaqM6CJPb!{^V07#Wt|7IYAQ2O~S^fMu7 zDcN1KI}Zvn8+%;#{c(U_5pRF2+XCMa*Bs%p>oarsh zk{*E^ni!IWS1QI_!~rD($zTQrB?HM|1_dPp$zTQrB?HM|1_htXXAA(<#z^>)i4{kE~?vGO1;?PoyV8ukn~tB+NfhGd4EWjYtm!Ike#JuE6IY> zb(OWWwUpe}z=BdGOVv>71s55QB@0NIVamx;KJYlQfc4NkrsPF=w6#nwJ$G4RC4DZs@Ulk@h{E+AjTS!?{61CWo^^S@WijPY_*--_ zgK9czUE1YL1H7|20Fc(TL;&Rnyw*x|Mji(=Kfno#p3R#cine6udnk3v zo_defv}?p*EftXCfbPisUi2msL(lh}7L|vRCMkQXZXAnr47>^8PF6tMrgSy-54KQt zwO(>iRagy7Ay`ZI%7tFy1z_1 zuJxO$M6~sIJyimF26_2IdEN&u{M1t32K1o6n=%CkYw5d01d-gR#AX=KDBfOzGPKMW zQ;A5GWmX=pv;;cPGqkbI&Y{#XTtB!Q^?0ZdfW|1Uq4%Q??OFHpJR%9$N@BE@s+hiL zIl0+70@(|AOg|aC3N)TPlqwNDfz0jx{P3|PtF72WkPZ zr}n=BZ&&6Rs00*p4Y-fRedpE(0w&+38z`-S)|-YtTb6x6iKFx~bey%9uLKXK>Zv&1UN+BRxC+v2;7y(_UtaT!$76-EKTL{>B@mUr@=3)fji+TuVS zN~qG!)Z#ir;N%LzN~(M6Gn8>!OJ%VZ9+Ink{yp{<2l7z*D&3?4W(D_H#NXmT9!jN= z^Ok}-8JSmt0CAo8TO1fl0aYT>D|D*dY6vbfx-*Vqgi>h;G%sFNBI zsNJm#5w|!rao=n$?X2ZW*tAZxk@O&Maj2n94kZO^3E4W~V_y6`W{tE2bfR4jx8zF>N^I6o{xzs zUHgpj$=AI2EQou>q&cUXFzJS*pMuC$a&bHZ=0&(?{Q@HiJTRtDul z(5zYJf@q!C5r4VM6Pyd1(`=7Z3=2$e8n6dgoY)xV4J;!UWpGNRO3|EvFt^y0uQwZE>)UCF@#B&cxkYCtiq;-_DjHdnoPJ3dXKxtrI#? z%7~BM+;;U<-JN`G8l>V+di?yGm{cpts|Tgs9y+nBUh4$i7KcNP>uk;|8wTxfg4U)> zMe)&fjZWg>n+~w)9GvKg1snRZnr_yt4>cOnDq%s++nKv#Nzyuo59P7d zoN=t#O6)ie$vWbJ^2=(f41L5d9mG|uBUGM4vyQl+{DMA; zh4L6q6>nzDnUz*1ZrozIVTpFAb#3#3EoZ4^eC@jCNDInyw?5#QQ{<<_O)*)<_@F#a zh=wL1qL!_1O#-tBIf0=EWwC3I*?Q(y5v7Z!2jwxK3e0bj0GfmNE7TezL)i?_Ta&qC z*8Aku~MJO<4Sq_=t5ZxYTp2w8d!TRTGWkk$7_?Q hY<#cq4psjPFaXJ{>Q{ImNW}mE002ovPDHLkV1nt|!r1@- literal 0 HcmV?d00001 diff --git a/CommonKit/Sources/CommonKit/Assets/Shared.xcassets/Wallets/verse_wallet_row.imageset/Contents.json b/CommonKit/Sources/CommonKit/Assets/Shared.xcassets/Wallets/verse_wallet_row.imageset/Contents.json new file mode 100644 index 000000000..ddea69321 --- /dev/null +++ b/CommonKit/Sources/CommonKit/Assets/Shared.xcassets/Wallets/verse_wallet_row.imageset/Contents.json @@ -0,0 +1,23 @@ +{ + "images" : [ + { + "idiom" : "universal", + "scale" : "1x", + "filename" : "verse_wallet_row.png" + }, + { + "idiom" : "universal", + "scale" : "2x", + "filename" : "verse_wallet_row@2x.png" + }, + { + "idiom" : "universal", + "scale" : "3x", + "filename" : "verse_wallet_row@3x.png" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} diff --git a/CommonKit/Sources/CommonKit/Assets/Shared.xcassets/Wallets/verse_wallet_row.imageset/verse_wallet_row.png b/CommonKit/Sources/CommonKit/Assets/Shared.xcassets/Wallets/verse_wallet_row.imageset/verse_wallet_row.png new file mode 100644 index 0000000000000000000000000000000000000000..aa7be0a56b67466f947a1f0a194aa6c243716418 GIT binary patch literal 1074 zcmV-21kL-2P)f664fjIzo=&-gjFdqpiz{t)l39tyvS9ZumOMP&M4zpnb<}Y7F z;UbubUnlS!(o%rUw9gS|J%Z-1@o=}i|WZ=0a zlbL1mgyzUg)Cc$lr?pstl?ua6V}pas(fY%E_E{nB;=OhY=7X)m<{>R8D}#g^Xqx;5 z?UM@r&%2`bQpllI5i_zO*}P84A_BJ2s74~lv;|a)%_UE{TB(w1{U$C@bq@9oO8Ohq z{zH6JS9=%QE1B}^RFX02RQs#m=hg-p!BMCQqSf`pcgLg{^iIe@YP-XNsgX=5_e`~M zO9bYzvXNBUnMsI(7U0C}N5EFje3I~iQDp+@mrZ3ODHv1gJG~EmLZE}2H3V`ISOKu9 zOdzoDUS$JvA-P?_`6sX8lJ-N!w6-xIJA)p|SZENNP0qx%h`y*SmgSO$k5xVC0$i`X zC8rsl(q;aM4`DB1-Q^s>0g35VOk5NDCr+`%)F0cvkV|me3DXn%r~IHo7vNgY+N%L+ zhmZLu-{Oc|ybLrQGK`e^$wX+#Cc=-iE&%+W8Mv&3hoPb6NaHBcT4Y20GH@=n7fP6s z4Gly8^w8qy!Toex>L(J@+l^>10glIEkV$r3%V{F-y!t0Oh}iM|O~9DN2XH?~d>ynT z2#^mZ?|cD%=6yLE=PD1{6NQF`A(y^W>|>C5hBrl`dy%yyawarvJnV~`ST*EMwDho1 zEC~%M@F#^R{qmtbS&N|Z1gz=jb#NqAaw(#c$5-2aHm}ygj8%b4+U)2 z0`>l)X-mS#P;>C!W0O@Mv+zC)_y9MEsblqA>Nw}@OCBG<_M-X4)W9Dz-4EdBUY9Gu zt#_dtsa(~VGe9XYwKfAGn6Dm8)H-(@1E$_XxdJR_0E)1pXm+PBH7DBIIlTK(CpK7IL}pYreL>|K5>cb|WwlG?3D#6U_VI2@?N;!ppqK5O~mz!nvl zp8+^Cpg1st>T@f<`nL53K*nAw5Z3{?^I-KPNTZtj!(iN%uBy*77PlH90jw{<$_O~^ zEerGO1y01Rsf=CJj5BYxVPXgIFbaTKNo*#*85ns6j3*rPj*<-@4CDZ=E(^G)uSdzU zOE(?^hpv;D$OB+J@p!eYyC}HNSq0vLCUDoovUBXacagkAjsWXPmZdD%rv$IC`9otz zuJI#)wln?^zg6op#tYojSGKvx=0fi^+PqqyB|cXvKwAcU{KUP;5@=kTjRPjy#*5Z> zZNQ5xfW}#t1uXo67qRy*+>wUhGDFbK~zcC?oLL*T?WTqP4D({3gttSf+X zZ*zzDH8^IR%UINNd^4w5S6WO|h5)t=9IxfCW8i){Udn7sbC0%QVp6o-&wvdnx_&_W z+E?f_43V!?f{TErWeyb!V6hjvYGYUzMA#Sf}G6Z1mUnZptn>@ty5$<-xrsc#8_?2gP-+0gUWv-YbM{8S9{#>ZW!>zHP8^ zG5SyTM3Ja)aSU1Lj|LAj(@y_Ite$tUc9o9vT?97OJnAym7^n}=fm{NUq0XAAV%Dwo zrK@SX=#U3s#l#%8jr6~>PJyn06N|1d4@yO%lFCw3n+Ko(9;7z!a?xVe{ehm)eJfQN zYu-{HLMkLF>v#YrW;hyN#}?#48-17q)Bnx~J2ALQ_+ki`R#?)9(0ZiJc7%%n4%+50 zY3+7JNoxp9|53CqVge4zy7k7zeYXts0Khg#ebysM0S9e!m?)|y_A@?r?Fh!emB_#w z0kT65+Oq|Hh(7(N03580Bs#C!$%AILxo$@w?<(tY3ZgHE7W5$*wqa!)e6fTED_ZG8 zHQOE;!XrXr#BXv+fpKbRT`c=;M=)}gSizfGs$-_czaQx+HFn?cZT1fZ4S>seK;qu z2CI5jx%&okgYLIIDnh__eTbkPLDSw3ffK3pn!tra%UBaleMse;>2HkV_Cn<1Aseo$ zcD(&@9!S~|bY#G`M0pHcP6Rj`Z4ULAN#!J^UCfS8R326r$U4=y7LRGKjLy-HtJA>I za+lCDmP#I^MD91QcUi}Zm{1bA8EBisjM23NX%nA$Iw=^mj_LB$A*Yp+RG*QQ`)-k`r<(oJr#bwxx{LS<}jR9Jl$;NaaCdB^F|Ek!L#ant>B(D1CV#Vjz{+ zwr)BbQ~$+(PhWos-T2`P^ys~6rtz?DNDKOq(XnIJQF_(POy{s}=&LrzgyMua?w!`F zX1He4I$aR{0(UwuZ0D7qA52vLQ!>}9d0dCdw9bUlsQ2}Vfp({T#)=W+Xa;*4ZXIA| z^Ks(IxMOq*(H8}pIdFS@7r<+03lq@HUFD(M;?GZ_A3MKJBXZtw=x^`0hve^ u_9d7}OE9+w7VyviO!PAr6qe$z00RKm!k6EUxW8Wj0000*$d1(qwYQbD8&qN~8_;M9RnFu+(J6bTYb5R2Xaj54{Ii%5Y# z?ia9t6s&6f{OIlf?3@2e&8LpG=A*O`Kje-F^|B-n!4Z7f0d%+f&moM^+U!3H&b0wn z!MfpxKp9GH{W1ez-~2rUtqid1&%YD2s*)Ip0|Q~!q6KWjqOLw6{UMhOWjr8$+kXbe z9b=ZVnhMNcF#{?i1yNbThe$xI0`zqc=!&5XH%1)pKcfcOF?Oj$X}opiR@W{O!l*3ciwLKL6S@?XkC-f{La*sJxLi8MRLL>tRQY9}lg4wi>Y<(PRty2aHW6#8m1LmhyEls?+V<@Yw zqXM$W%y@I@k0Du284!$Y9ocFI1f{MyzHsaqM6CJPb!{^V07#Wt|7IYAQ2O~S^fMu7 zDcN1KI}Zvn8+%;#{c(U_5pRF2+XCMa*Bs%p>oarsh zk{*E^ni!IWS1QI_!~rD($zTQrB?HM|1_dPp$zTQrB?HM|1_htXXAA(<#z^>)i4{kE~?vGO1;?PoyV8ukn~tB+NfhGd4EWjYtm!Ike#JuE6IY> zb(OWWwUpe}z=BdGOVv>71s55QB@0NIVamx;KJYlQfc4NkrsPF=w6#nwJ$G4RC4DZs@Ulk@h{E+AjTS!?{61CWo^^S@WijPY_*--_ zgK9czUE1YL1H7|20Fc(TL;&Rnyw*x|Mji(=Kfno#p3R#cine6udnk3v zo_defv}?p*EftXCfbPisUi2msL(lh}7L|vRCMkQXZXAnr47>^8PF6tMrgSy-54KQt zwO(>iRagy7Ay`ZI%7tFy1z_1 zuJxO$M6~sIJyimF26_2IdEN&u{M1t32K1o6n=%CkYw5d01d-gR#AX=KDBfOzGPKMW zQ;A5GWmX=pv;;cPGqkbI&Y{#XTtB!Q^?0ZdfW|1Uq4%Q??OFHpJR%9$N@BE@s+hiL zIl0+70@(|AOg|aC3N)TPlqwNDfz0jx{P3|PtF72WkPZ zr}n=BZ&&6Rs00*p4Y-fRedpE(0w&+38z`-S)|-YtTb6x6iKFx~bey%9uLKXK>Zv&1UN+BRxC+v2;7y(_UtaT!$76-EKTL{>B@mUr@=3)fji+TuVS zN~qG!)Z#ir;N%LzN~(M6Gn8>!OJ%VZ9+Ink{yp{<2l7z*D&3?4W(D_H#NXmT9!jN= z^Ok}-8JSmt0CAo8TO1fl0aYT>D|D*dY6vbfx-*Vqgi>h;G%sFNBI zsNJm#5w|!rao=n$?X2ZW*tAZxk@O&Maj2n94kZO^3E4W~V_y6`W{tE2bfR4jx8zF>N^I6o{xzs zUHgpj$=AI2EQou>q&cUXFzJS*pMuC$a&bHZ=0&(?{Q@HiJTRtDul z(5zYJf@q!C5r4VM6Pyd1(`=7Z3=2$e8n6dgoY)xV4J;!UWpGNRO3|EvFt^y0uQwZE>)UCF@#B&cxkYCtiq;-_DjHdnoPJ3dXKxtrI#? z%7~BM+;;U<-JN`G8l>V+di?yGm{cpts|Tgs9y+nBUh4$i7KcNP>uk;|8wTxfg4U)> zMe)&fjZWg>n+~w)9GvKg1snRZnr_yt4>cOnDq%s++nKv#Nzyuo59P7d zoN=t#O6)ie$vWbJ^2=(f41L5d9mG|uBUGM4vyQl+{DMA; zh4L6q6>nzDnUz*1ZrozIVTpFAb#3#3EoZ4^eC@jCNDInyw?5#QQ{<<_O)*)<_@F#a zh=wL1qL!_1O#-tBIf0=EWwC3I*?Q(y5v7Z!2jwxK3e0bj0GfmNE7TezL)i?_Ta&qC z*8Aku~MJO<4Sq_=t5ZxYTp2w8d!TRTGWkk$7_?Q hY<#cq4psjPFaXJ{>Q{ImNW}mE002ovPDHLkV1nt|!r1@- literal 0 HcmV?d00001 diff --git a/CommonKit/Sources/CommonKit/Helpers/Encodable+Dictionary.swift b/CommonKit/Sources/CommonKit/Helpers/Encodable+Dictionary.swift index e4f26ba0f..201048be4 100644 --- a/CommonKit/Sources/CommonKit/Helpers/Encodable+Dictionary.swift +++ b/CommonKit/Sources/CommonKit/Helpers/Encodable+Dictionary.swift @@ -9,7 +9,7 @@ import Foundation public extension Encodable { - var asDictionary: [String: Any]? { + func asDictionary() -> [String: Any]? { guard let data = try? JSONEncoder().encode(self), let object = try? JSONSerialization.jsonObject(with: data, options: .allowFragments) diff --git a/CommonKit/Sources/CommonKit/Helpers/HashableAction.swift b/CommonKit/Sources/CommonKit/Helpers/HashableAction.swift deleted file mode 100644 index 0d9fd783a..000000000 --- a/CommonKit/Sources/CommonKit/Helpers/HashableAction.swift +++ /dev/null @@ -1,30 +0,0 @@ -// -// HashableAction.swift -// -// -// Created by Andrey Golubenko on 06.12.2022. -// - -import Foundation - -public struct HashableAction { - public let id: Int - public let action: () -> Void - - public init(id: Int, action: @escaping () -> Void) { - self.id = id - self.action = action - } -} - -extension HashableAction: Equatable { - public static func == (lhs: HashableAction, rhs: HashableAction) -> Bool { - lhs.id == rhs.id - } -} - -extension HashableAction: Hashable { - public func hash(into hasher: inout Hasher) { - hasher.combine(id) - } -} diff --git a/CommonKit/Sources/CommonKit/Helpers/IDWrapper.swift b/CommonKit/Sources/CommonKit/Helpers/IDWrapper.swift new file mode 100644 index 000000000..d30a5208f --- /dev/null +++ b/CommonKit/Sources/CommonKit/Helpers/IDWrapper.swift @@ -0,0 +1,31 @@ +// +// IDWrapper.swift +// Adamant +// +// Created by Andrey Golubenko on 17.07.2023. +// Copyright © 2023 Adamant. All rights reserved. +// + +import Foundation + +public struct IDWrapper: Identifiable { + public let id: String + public let value: T + + public init(id: String, value: T) { + self.id = id + self.value = value + } +} + +extension IDWrapper: Equatable { + public static func == (lhs: Self, rhs: Self) -> Bool { + lhs.id == rhs.id + } +} + +extension IDWrapper: Hashable { + public func hash(into hasher: inout Hasher) { + hasher.combine(id) + } +} diff --git a/CommonKit/Sources/CommonKit/Helpers/IdentifiableContainer.swift b/CommonKit/Sources/CommonKit/Helpers/IdentifiableContainer.swift deleted file mode 100644 index 1ef54151a..000000000 --- a/CommonKit/Sources/CommonKit/Helpers/IdentifiableContainer.swift +++ /dev/null @@ -1,21 +0,0 @@ -// -// IdentifiableContainer.swift -// Adamant -// -// Created by Andrey Golubenko on 17.07.2023. -// Copyright © 2023 Adamant. All rights reserved. -// - -import Foundation - -public struct IdentifiableContainer>: Identifiable { - public let value: T - - public var id: String { - value.rawValue - } - - public init(value: T) { - self.value = value - } -} diff --git a/CommonKit/Sources/CommonKit/Helpers/Nodes+Allowance.swift b/CommonKit/Sources/CommonKit/Helpers/Nodes+Allowance.swift index 57c7c2a5b..2ad2769ad 100644 --- a/CommonKit/Sources/CommonKit/Helpers/Nodes+Allowance.swift +++ b/CommonKit/Sources/CommonKit/Helpers/Nodes+Allowance.swift @@ -6,11 +6,11 @@ // Copyright © 2022 Adamant. All rights reserved. // -public extension Collection where Element: Node { +public extension Collection where Element == Node { func getAllowedNodes(sortedBySpeedDescending: Bool, needWS: Bool) -> [Node] { var allowedNodes = filter { $0.connectionStatus == .allowed - && (!needWS || $0.status?.wsEnabled ?? false) + && (!needWS || $0.wsEnabled) } if allowedNodes.isEmpty && !needWS { @@ -19,7 +19,7 @@ public extension Collection where Element: Node { return sortedBySpeedDescending ? allowedNodes.sorted { - $0.status?.ping ?? .greatestFiniteMagnitude < $1.status?.ping ?? .greatestFiniteMagnitude + $0.ping ?? .greatestFiniteMagnitude < $1.ping ?? .greatestFiniteMagnitude } : allowedNodes.shuffled() } diff --git a/CommonKit/Sources/CommonKit/Helpers/Result+Extension.swift b/CommonKit/Sources/CommonKit/Helpers/Result+Extension.swift new file mode 100644 index 000000000..5ff43f7ba --- /dev/null +++ b/CommonKit/Sources/CommonKit/Helpers/Result+Extension.swift @@ -0,0 +1,19 @@ +// +// Result+Extension.swift +// +// +// Created by Andrew G on 13.11.2023. +// + +public extension Result { + func asyncMap( + _ transform: @escaping (Success) async -> NewSuccess + ) async -> Result { + switch self { + case let .success(value): + return await .success(transform(value)) + case let .failure(error): + return .failure(error) + } + } +} diff --git a/CommonKit/Sources/CommonKit/Helpers/URL+Extension.swift b/CommonKit/Sources/CommonKit/Helpers/URL+Extension.swift deleted file mode 100644 index 020eb47dd..000000000 --- a/CommonKit/Sources/CommonKit/Helpers/URL+Extension.swift +++ /dev/null @@ -1,21 +0,0 @@ -// -// URL+Extension.swift -// Adamant -// -// Created by Andrey Golubenko on 17.07.2023. -// Copyright © 2023 Adamant. All rights reserved. -// - -import Foundation - -extension URL: RawRepresentable { - public typealias RawValue = String - - public init?(rawValue: String) { - self.init(string: rawValue) - } - - public var rawValue: String { - absoluteString - } -} diff --git a/CommonKit/Sources/CommonKit/Models/Node.swift b/CommonKit/Sources/CommonKit/Models/Node.swift index 23347edfa..73d467274 100644 --- a/CommonKit/Sources/CommonKit/Models/Node.swift +++ b/CommonKit/Sources/CommonKit/Models/Node.swift @@ -8,167 +8,107 @@ import Foundation -public enum URLScheme: String, Codable { - case http, https - - public static let `default`: URLScheme = .https - - public var defaultPort: Int { - switch self { - case .http: return 36666 - case .https: return 443 - } - } -} - -final public class Node: Equatable { - public struct Status: Equatable, Codable { - public let ping: TimeInterval - public let wsEnabled: Bool - public let height: Int? - public let version: String? - - public init(ping: TimeInterval, wsEnabled: Bool, height: Int?, version: String?) { - self.ping = ping - self.wsEnabled = wsEnabled - self.height = height - self.version = version - } - } - - public enum ConnectionStatus: Equatable, Codable { - case offline - case synchronizing - case allowed - } - - public static func == (lhs: Node, rhs: Node) -> Bool { - lhs.scheme == rhs.scheme - && lhs.host == rhs.host - && lhs.port == rhs.port - && lhs.status == rhs.status - && lhs.isEnabled == rhs.isEnabled - && lhs._connectionStatus == rhs._connectionStatus - } +public struct Node: Equatable, Codable, Identifiable { + public let id: UUID + public var scheme: URLScheme + public var host: String + public var isEnabled: Bool + public var wsEnabled: Bool + public var port: Int? + public var wsPort: Int? + public var version: String? + public var height: Int? + public var ping: TimeInterval? + public var connectionStatus: ConnectionStatus? public init( + id: UUID = .init(), scheme: URLScheme, host: String, + isEnabled: Bool, + wsEnabled: Bool, port: Int? = nil, wsPort: Int? = nil, - status: Status? = nil, - isEnabled: Bool = true, + version: String? = nil, + height: Int? = nil, + ping: TimeInterval? = nil, connectionStatus: ConnectionStatus? = nil ) { + self.id = id self.scheme = scheme self.host = host + self.isEnabled = isEnabled + self.wsEnabled = wsEnabled self.port = port self.wsPort = wsPort - self.status = status - self.isEnabled = isEnabled - self._connectionStatus = connectionStatus + self.version = version + self.height = height + self.ping = ping + self.connectionStatus = connectionStatus } - - public init(url: URL) { - let schemeRaw = url.scheme ?? "https" - self.scheme = URLScheme(rawValue: schemeRaw) ?? .https - self.host = url.host ?? "" - self.port = url.port - self.isEnabled = true +} + +public extension Node { + enum ConnectionStatus: Equatable, Codable { + case offline + case synchronizing + case allowed } - @Atomic public var scheme: URLScheme - @Atomic public var host: String - @Atomic public var port: Int? - @Atomic public var wsPort: Int? - @Atomic public var status: Status? - @Atomic public var isEnabled: Bool - - @Atomic private var _connectionStatus: ConnectionStatus? + enum URLScheme: String, Codable { + case http, https + + public static let `default`: URLScheme = .https + + public var defaultPort: Int { + switch self { + case .http: return 36666 + case .https: return 443 + } + } + } - public var connectionStatus: ConnectionStatus? { - get { isEnabled ? _connectionStatus : nil } - set { _connectionStatus = newValue } + init(url: URL) { + self.init( + scheme: URLScheme(rawValue: url.scheme ?? .empty) ?? .https, + host: url.host ?? .empty, + isEnabled: true, + wsEnabled: false, + port: url.port + ) } - public func asString() -> String { - if let url = asURL(forcePort: scheme != URLScheme.default) { + func asString() -> String { + if let url = asURL(forcePort: scheme != .https) { return url.absoluteString } else { return host } } - /// Builds URL, using specified port, or default scheme's port, if nil - /// - /// - Returns: URL, if no errors were thrown - - public func asSocketURL() -> URL? { - return asURL(forcePort: false, useWsPort: true) + func asSocketURL() -> URL? { + asURL(forcePort: false, useWsPort: true) } - - public func asURL() -> URL? { - return asURL(forcePort: true) + + func asURL() -> URL? { + asURL(forcePort: true) } - - private func asURL(forcePort: Bool, useWsPort: Bool = false) -> URL? { +} + +private extension Node { + func asURL(forcePort: Bool, useWsPort: Bool = false) -> URL? { var components = URLComponents() components.scheme = scheme.rawValue components.host = host - + let usePort = useWsPort ? wsPort : port - + if let port = usePort, scheme == .http { components.port = port } else if forcePort { components.port = usePort ?? scheme.defaultPort } - - return components.url - } -} -extension Node: Encodable { - public func encode(to encoder: Encoder) throws { - var container = encoder.container(keyedBy: CodingKeys.self) - - try container.encode(scheme, forKey: .scheme) - try container.encode(host, forKey: .host) - try container.encode(port, forKey: .port) - try container.encode(wsPort, forKey: .wsPort) - try container.encode(status, forKey: .status) - try container.encode(isEnabled, forKey: .isEnabled) - try container.encode(_connectionStatus, forKey: ._connectionStatus) - } -} - -extension Node: Decodable { - public convenience init(from decoder: Decoder) throws { - let container = try decoder.container(keyedBy: CodingKeys.self) - - self.init( - scheme: try container.decode(URLScheme.self, forKey: .scheme), - host: try container.decode(String.self, forKey: .host), - port: try? container.decode(Optional.self, forKey: .port), - wsPort: try? container.decode(Optional.self, forKey: .wsPort), - status: try? container.decode(Optional.self, forKey: .status), - isEnabled: try container.decode(Bool.self, forKey: .isEnabled), - connectionStatus: try? container.decode( - Optional.self, - forKey: ._connectionStatus - ) - ) - } -} - -private extension Node { - enum CodingKeys: String, CodingKey { - case scheme - case host - case port - case wsPort - case status - case isEnabled - case _connectionStatus + return components.url } } diff --git a/CommonKit/Sources/CommonKit/Models/NodeGroup+Constants.swift b/CommonKit/Sources/CommonKit/Models/NodeGroup+Constants.swift new file mode 100644 index 000000000..8ca65a7c4 --- /dev/null +++ b/CommonKit/Sources/CommonKit/Models/NodeGroup+Constants.swift @@ -0,0 +1,59 @@ +// +// NodeGroup+Constants.swift +// +// +// Created by Andrew G on 18.11.2023. +// + +import Foundation + +public extension NodeGroup { + var nodeHeightEpsilon: Int { + switch self { + case .adm: + return 10 + case .btc: + return 2 + case .eth: + return 5 + case .lskService, .lskNode: + return 5 + case .doge: + return 3 + case .dash: + return 3 + } + } + + var normalUpdateInterval: TimeInterval { + switch self { + case .adm: + return 300000 + case .btc: + return 360000 + case .eth: + return 300000 + case .lskNode: + return 270000 + case .lskService: + return 330000 + case .doge: + return 390000 + case .dash: + return 210000 + } + } + + var crucialUpdateInterval: TimeInterval { + 30 + } + + var defaultFastestNodeMode: Bool { + switch self { + case .adm: + return false + case .eth, .lskNode, .lskService, .doge, .dash, .btc: + return true + } + } +} diff --git a/CommonKit/Sources/CommonKit/Models/NodeGroup.swift b/CommonKit/Sources/CommonKit/Models/NodeGroup.swift new file mode 100644 index 000000000..ad6384a48 --- /dev/null +++ b/CommonKit/Sources/CommonKit/Models/NodeGroup.swift @@ -0,0 +1,16 @@ +// +// NodeGroup.swift +// +// +// Created by Andrew G on 30.10.2023. +// + +public enum NodeGroup: Equatable, Codable, CaseIterable { + case btc + case eth + case lskNode + case lskService + case doge + case dash + case adm +} diff --git a/CommonKit/Sources/CommonKit/Models/ethereumTokensList.swift b/CommonKit/Sources/CommonKit/Models/ethereumTokensList.swift index 0dc898def..2961e7f60 100644 --- a/CommonKit/Sources/CommonKit/Models/ethereumTokensList.swift +++ b/CommonKit/Sources/CommonKit/Models/ethereumTokensList.swift @@ -255,6 +255,18 @@ defaultGasPriceGwei: 30, defaultGasLimit: 58000, warningGasPriceGwei: 70), + ERC20Token(symbol: "VERSE", + name: "Verse", + contractAddress: "0x249cA82617eC3DfB2589c4c17ab7EC9765350a18", + decimals: 18, + naturalUnits: 18, + defaultVisibility: true, + defaultOrdinalLevel: 95, + reliabilityGasPricePercent: 10, + reliabilityGasLimitPercent: 10, + defaultGasPriceGwei: 30, + defaultGasLimit: 58000, + warningGasPriceGwei: 70), ERC20Token(symbol: "WOO", name: "WOO Network", contractAddress: "0x4691937a7508860f876c9c0a2a617e7d9e945d4b", diff --git a/CommonKit/Sources/CommonKit/Services/KeychainStore.swift b/CommonKit/Sources/CommonKit/Services/KeychainStore.swift index 60714d917..bd290b26d 100644 --- a/CommonKit/Sources/CommonKit/Services/KeychainStore.swift +++ b/CommonKit/Sources/CommonKit/Services/KeychainStore.swift @@ -26,12 +26,7 @@ public final class KeychainStore: SecuredStore { let data = raw.data(using: .utf8) else { return nil } - do { - return try JSONDecoder().decode(T.self, from: data) - } catch { - assertionFailure("Failed to decode data. Error: \(error.localizedDescription)") - return nil - } + return try? JSONDecoder().decode(T.self, from: data) } public func set(_ value: T, for key: String) { @@ -40,12 +35,8 @@ public final class KeychainStore: SecuredStore { return } - do { - let data = try JSONEncoder().encode(value) - String(data: data, encoding: .utf8).map { setString($0, for: key) } - } catch { - assertionFailure("Failed to encode data. Error: \(error.localizedDescription)") - } + guard let data = try? JSONEncoder().encode(value) else { return } + String(data: data, encoding: .utf8).map { setString($0, for: key) } } public func remove(_ key: String) { diff --git a/LiskKit/Sources/API/Node/Models/NodeStatusModel.swift b/LiskKit/Sources/API/Node/Models/NodeStatusModel.swift index 69b794450..e9bfdc8cd 100644 --- a/LiskKit/Sources/API/Node/Models/NodeStatusModel.swift +++ b/LiskKit/Sources/API/Node/Models/NodeStatusModel.swift @@ -44,6 +44,8 @@ extension Node { public let version: String public let networkVersion: String + + public let height: Int? } } diff --git a/LiskKit/Sources/Core/APIClient.swift b/LiskKit/Sources/Core/APIClient.swift index 436b1bc60..a54dfe9da 100644 --- a/LiskKit/Sources/Core/APIClient.swift +++ b/LiskKit/Sources/Core/APIClient.swift @@ -8,10 +8,7 @@ import Foundation /// Represents an HTTP response -public enum Response { - case success(response: R) - case error(response: APIError) -} +public typealias Response = Result public enum Result { case success(response: R) diff --git a/NotificationServiceExtension/WalletImages/verse_notificationContent.png b/NotificationServiceExtension/WalletImages/verse_notificationContent.png new file mode 100644 index 0000000000000000000000000000000000000000..be130ddff08efb8b1b97b66295ead3cbb2fb8cf5 GIT binary patch literal 3091 zcmV+u4D9oXP)*$d1(qwYQbD8&qN~8_;M9RnFu+(J6bTYb5R2Xaj54{Ii%5Y# z?ia9t6s&6f{OIlf?3@2e&8LpG=A*O`Kje-F^|B-n!4Z7f0d%+f&moM^+U!3H&b0wn z!MfpxKp9GH{W1ez-~2rUtqid1&%YD2s*)Ip0|Q~!q6KWjqOLw6{UMhOWjr8$+kXbe z9b=ZVnhMNcF#{?i1yNbThe$xI0`zqc=!&5XH%1)pKcfcOF?Oj$X}opiR@W{O!l*3ciwLKL6S@?XkC-f{La*sJxLi8MRLL>tRQY9}lg4wi>Y<(PRty2aHW6#8m1LmhyEls?+V<@Yw zqXM$W%y@I@k0Du284!$Y9ocFI1f{MyzHsaqM6CJPb!{^V07#Wt|7IYAQ2O~S^fMu7 zDcN1KI}Zvn8+%;#{c(U_5pRF2+XCMa*Bs%p>oarsh zk{*E^ni!IWS1QI_!~rD($zTQrB?HM|1_dPp$zTQrB?HM|1_htXXAA(<#z^>)i4{kE~?vGO1;?PoyV8ukn~tB+NfhGd4EWjYtm!Ike#JuE6IY> zb(OWWwUpe}z=BdGOVv>71s55QB@0NIVamx;KJYlQfc4NkrsPF=w6#nwJ$G4RC4DZs@Ulk@h{E+AjTS!?{61CWo^^S@WijPY_*--_ zgK9czUE1YL1H7|20Fc(TL;&Rnyw*x|Mji(=Kfno#p3R#cine6udnk3v zo_defv}?p*EftXCfbPisUi2msL(lh}7L|vRCMkQXZXAnr47>^8PF6tMrgSy-54KQt zwO(>iRagy7Ay`ZI%7tFy1z_1 zuJxO$M6~sIJyimF26_2IdEN&u{M1t32K1o6n=%CkYw5d01d-gR#AX=KDBfOzGPKMW zQ;A5GWmX=pv;;cPGqkbI&Y{#XTtB!Q^?0ZdfW|1Uq4%Q??OFHpJR%9$N@BE@s+hiL zIl0+70@(|AOg|aC3N)TPlqwNDfz0jx{P3|PtF72WkPZ zr}n=BZ&&6Rs00*p4Y-fRedpE(0w&+38z`-S)|-YtTb6x6iKFx~bey%9uLKXK>Zv&1UN+BRxC+v2;7y(_UtaT!$76-EKTL{>B@mUr@=3)fji+TuVS zN~qG!)Z#ir;N%LzN~(M6Gn8>!OJ%VZ9+Ink{yp{<2l7z*D&3?4W(D_H#NXmT9!jN= z^Ok}-8JSmt0CAo8TO1fl0aYT>D|D*dY6vbfx-*Vqgi>h;G%sFNBI zsNJm#5w|!rao=n$?X2ZW*tAZxk@O&Maj2n94kZO^3E4W~V_y6`W{tE2bfR4jx8zF>N^I6o{xzs zUHgpj$=AI2EQou>q&cUXFzJS*pMuC$a&bHZ=0&(?{Q@HiJTRtDul z(5zYJf@q!C5r4VM6Pyd1(`=7Z3=2$e8n6dgoY)xV4J;!UWpGNRO3|EvFt^y0uQwZE>)UCF@#B&cxkYCtiq;-_DjHdnoPJ3dXKxtrI#? z%7~BM+;;U<-JN`G8l>V+di?yGm{cpts|Tgs9y+nBUh4$i7KcNP>uk;|8wTxfg4U)> zMe)&fjZWg>n+~w)9GvKg1snRZnr_yt4>cOnDq%s++nKv#Nzyuo59P7d zoN=t#O6)ie$vWbJ^2=(f41L5d9mG|uBUGM4vyQl+{DMA; zh4L6q6>nzDnUz*1ZrozIVTpFAb#3#3EoZ4^eC@jCNDInyw?5#QQ{<<_O)*)<_@F#a zh=wL1qL!_1O#-tBIf0=EWwC3I*?Q(y5v7Z!2jwxK3e0bj0GfmNE7TezL)i?_Ta&qC z*8Aku~MJO<4Sq_=t5ZxYTp2w8d!TRTGWkk$7_?Q hY<#cq4psjPFaXJ{>Q{ImNW}mE002ovPDHLkV1nt|!r1@- literal 0 HcmV?d00001 diff --git a/PopupKit/Sources/PopupKit/Implementation/Models/AdvancedAlertModel.swift b/PopupKit/Sources/PopupKit/Implementation/Models/AdvancedAlertModel.swift index 0e53db715..920133c58 100644 --- a/PopupKit/Sources/PopupKit/Implementation/Models/AdvancedAlertModel.swift +++ b/PopupKit/Sources/PopupKit/Implementation/Models/AdvancedAlertModel.swift @@ -33,9 +33,9 @@ public struct AdvancedAlertModel: Equatable, Hashable { public extension AdvancedAlertModel { struct Button: Equatable, Hashable { public let title: String - public let action: HashableAction + public let action: IDWrapper - public init(title: String, action: HashableAction) { + public init(title: String, action: IDWrapper) { self.title = title self.action = action } diff --git a/PopupKit/Sources/PopupKit/Implementation/Models/NotificationModel.swift b/PopupKit/Sources/PopupKit/Implementation/Models/NotificationModel.swift index 070670264..ebc5403be 100644 --- a/PopupKit/Sources/PopupKit/Implementation/Models/NotificationModel.swift +++ b/PopupKit/Sources/PopupKit/Implementation/Models/NotificationModel.swift @@ -12,5 +12,5 @@ struct NotificationModel: Equatable, Hashable { let icon: UIImage? let title: String? let description: String? - let tapHandler: HashableAction? + let tapHandler: IDWrapper? } diff --git a/PopupKit/Sources/PopupKit/Implementation/Models/PopupCoordinatorModel.swift b/PopupKit/Sources/PopupKit/Implementation/Models/PopupCoordinatorModel.swift index 9f6c7882e..f561f86db 100644 --- a/PopupKit/Sources/PopupKit/Implementation/Models/PopupCoordinatorModel.swift +++ b/PopupKit/Sources/PopupKit/Implementation/Models/PopupCoordinatorModel.swift @@ -8,8 +8,60 @@ import UIKit final class PopupCoordinatorModel: ObservableObject { - @Published var notification: NotificationModel? - @Published var alert: AlertModel? - @Published var toastMessage: String? - @Published var advancedAlert: AdvancedAlertModel? + @Published var top: NotificationModel? + @Published var middle: Alert? + @Published var bottom: String? + + var notification: NotificationModel? { + get { top } + set { top = newValue } + } + + var toastMessage: String? { + get { bottom } + set { bottom = newValue } + } + + var alert: AlertModel? { + get { + switch middle { + case let .common(model): + return model + case .none, .advanced: + return nil + } + } set { + switch middle { + case .none, .common: + middle = newValue.map { .common($0) } + case .advanced: + break + } + } + } + + var advancedAlert: AdvancedAlertModel? { + get { + switch middle { + case let .advanced(model): + return model + case .none, .common: + return nil + } + } set { + switch middle { + case .none, .advanced: + middle = newValue.map { .advanced($0) } + case .common: + break + } + } + } +} + +extension PopupCoordinatorModel { + enum Alert { + case common(AlertModel) + case advanced(AdvancedAlertModel) + } } diff --git a/PopupKit/Sources/PopupKit/Implementation/Views/AdvancedAlertView.swift b/PopupKit/Sources/PopupKit/Implementation/Views/AdvancedAlertView.swift index 33fa281e2..1e476e82a 100644 --- a/PopupKit/Sources/PopupKit/Implementation/Views/AdvancedAlertView.swift +++ b/PopupKit/Sources/PopupKit/Implementation/Views/AdvancedAlertView.swift @@ -70,7 +70,7 @@ private extension AdvancedAlertView { } var primaryButton: some View { - Button(action: model.primaryButton.action.action) { + Button(action: model.primaryButton.action.value) { Text(model.primaryButton.title) .padding(.vertical, bigSpacing) .expanded(axes: .horizontal) @@ -87,7 +87,7 @@ private extension AdvancedAlertView { } func makeSecondaryButton(model: AdvancedAlertModel.Button) -> some View { - Button(model.title, action: model.action.action) + Button(model.title, action: model.action.value) } } diff --git a/PopupKit/Sources/PopupKit/Implementation/Views/NotificationView.swift b/PopupKit/Sources/PopupKit/Implementation/Views/NotificationView.swift index 889ff59c5..759a6c8e5 100644 --- a/PopupKit/Sources/PopupKit/Implementation/Views/NotificationView.swift +++ b/PopupKit/Sources/PopupKit/Implementation/Views/NotificationView.swift @@ -81,7 +81,7 @@ private extension NotificationView { } func onTap() { - model.tapHandler?.action() + model.tapHandler?.value() dismissAction() } } diff --git a/PopupKit/Sources/PopupKit/PopupManager.swift b/PopupKit/Sources/PopupKit/PopupManager.swift index 918eaa557..57a506cd1 100644 --- a/PopupKit/Sources/PopupKit/PopupManager.swift +++ b/PopupKit/Sources/PopupKit/PopupManager.swift @@ -82,7 +82,7 @@ public extension PopupManager { icon: icon, title: title, description: description, - tapHandler: tapHandler.map { .init(id: .zero, action: $0) } + tapHandler: tapHandler.map { .init(id: .empty, value: $0) } ) if autoDismiss { From 56639d651cf3cb656f8a124eb9a8e2bcb68755e9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=80=9Cjust-software-dev=E2=80=9D?= Date: Sun, 19 Nov 2023 10:54:34 +0600 Subject: [PATCH 59/96] [trello.com/c/YD9Iu6wp] Delegates API fix --- Adamant/ServiceProtocols/ApiService.swift | 2 +- Adamant/Services/ApiService/AdamantApi+Delegates.swift | 2 +- .../Services/ApiService/AdamantApi+Transactions.swift | 9 ++++++--- 3 files changed, 8 insertions(+), 5 deletions(-) diff --git a/Adamant/ServiceProtocols/ApiService.swift b/Adamant/ServiceProtocols/ApiService.swift index 1cea07cbb..f08813759 100644 --- a/Adamant/ServiceProtocols/ApiService.swift +++ b/Adamant/ServiceProtocols/ApiService.swift @@ -130,5 +130,5 @@ protocol ApiService { from address: String, keypair: Keypair, votes: [DelegateVote] - ) async -> ApiServiceResult + ) async -> ApiServiceResult } diff --git a/Adamant/Services/ApiService/AdamantApi+Delegates.swift b/Adamant/Services/ApiService/AdamantApi+Delegates.swift index 357f7cd4a..f4c1e6caf 100644 --- a/Adamant/Services/ApiService/AdamantApi+Delegates.swift +++ b/Adamant/Services/ApiService/AdamantApi+Delegates.swift @@ -143,7 +143,7 @@ extension AdamantApiService { from address: String, keypair: Keypair, votes: [DelegateVote] - ) async -> ApiServiceResult { + ) async -> ApiServiceResult { // MARK: 0. Prepare var votesOrdered = votes _ = votesOrdered.partition { diff --git a/Adamant/Services/ApiService/AdamantApi+Transactions.swift b/Adamant/Services/ApiService/AdamantApi+Transactions.swift index 0c53fc2b7..baeb070de 100644 --- a/Adamant/Services/ApiService/AdamantApi+Transactions.swift +++ b/Adamant/Services/ApiService/AdamantApi+Transactions.swift @@ -39,8 +39,8 @@ extension AdamantApiService { func sendDelegateVoteTransaction( path: String, transaction: UnregisteredTransaction - ) async -> ApiServiceResult { - let response: ApiServiceResult = await request { core, node in + ) async -> ApiServiceResult { + let response: ApiServiceResult = await request { core, node in await core.sendRequestJson( node: node, path: path, @@ -50,7 +50,10 @@ extension AdamantApiService { ) } - return response.flatMap { $0.resolved() } + return response.flatMap { + guard let error = $0.error else { return .success($0.success) } + return .failure(.serverError(error: error)) + } } func getTransaction(id: UInt64) async -> ApiServiceResult { From 05418e01efdb64a40f42744b5820d0fec385f84a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=80=9Cjust-software-dev=E2=80=9D?= Date: Mon, 20 Nov 2023 08:44:11 +0600 Subject: [PATCH 60/96] [trello.com/c/YD9Iu6wp] Added coins node screen --- Adamant.xcodeproj/project.pbxproj | 79 +++++++++++- Adamant/Helpers/Node+Strings.swift | 58 +++++++++ Adamant/Models/NodeWithGroup.swift | 14 +++ .../Account/AccountViewController.swift | 34 +++++- .../ComplexTransferViewController.swift | 2 +- .../CoinsNodesListFactory.swift | 54 +++++++++ .../View/CoinsNodesListView+Row.swift | 73 +++++++++++ .../View/CoinsNodesListView.swift | 57 +++++++++ .../ViewModel/CoinsNodesListMapper.swift | 113 ++++++++++++++++++ .../ViewModel/CoinsNodesListState.swift | 35 ++++++ .../ViewModel/CoinsNodesListStrings.swift | 28 +++++ .../CoinsNodesListViewModel+ApiServices.swift | 42 +++++++ .../ViewModel/CoinsNodesListViewModel.swift | 100 ++++++++++++++++ .../Modules/Login/LoginViewController.swift | 25 +++- .../NodesEditor/NodesListViewController.swift | 59 ++------- .../AdamantScreensFactory.swift | 6 + .../ScreensFactory/ScreensFactory.swift | 1 + .../VisibleWalletsViewController.swift | 2 +- .../Wallets/Adamant/AdmWalletService.swift | 4 +- .../Wallets/Bitcoin/BtcApiService.swift | 10 +- .../Wallets/Bitcoin/BtcWalletService.swift | 4 +- .../Modules/Wallets/Dash/DashApiService.swift | 10 +- .../Wallets/Dash/DashWalletService.swift | 4 +- .../Modules/Wallets/Doge/DogeApiService.swift | 10 +- .../Wallets/Doge/DogeWalletService.swift | 4 +- .../ERC20/ERC20TransferViewController.swift | 2 +- .../Wallets/ERC20/ERC20WalletService.swift | 4 +- .../ERC20/ERC20WalletViewController.swift | 2 +- .../Wallets/Ethereum/EthApiService.swift | 10 +- .../Wallets/Ethereum/EthWalletService.swift | 4 +- .../Wallets/Lisk/LskNodeApiService.swift | 11 +- .../Wallets/Lisk/LskServiceApiService.swift | 10 +- .../Wallets/Lisk/LskWalletService.swift | 4 +- .../Modules/Wallets/WalletApiService.swift | 15 +++ Adamant/Modules/Wallets/WalletService.swift | 2 +- Adamant/ServiceProtocols/ApiService.swift | 5 +- .../NodesStorageProtocol.swift | 2 + .../BlockchainHealthCheckWrapper.swift | 1 + Adamant/Services/HealthCheckWrapper.swift | 2 +- Adamant/Services/NodesStorage.swift | 19 ++- .../SelfRemovableHostingController.swift | 31 +++++ .../Localization/de.lproj/Localizable.strings | 9 ++ .../Localization/en.lproj/Localizable.strings | 9 ++ .../Localization/ru.lproj/Localizable.strings | 13 +- .../CommonKit/Helpers/Nodes+Allowance.swift | 3 +- .../Models/NodeGroup+Constants.swift | 7 +- .../Sources/CommonKit/Models/NodeGroup.swift | 2 +- 47 files changed, 886 insertions(+), 109 deletions(-) create mode 100644 Adamant/Helpers/Node+Strings.swift create mode 100644 Adamant/Models/NodeWithGroup.swift create mode 100644 Adamant/Modules/CoinsNodesList/CoinsNodesListFactory.swift create mode 100644 Adamant/Modules/CoinsNodesList/View/CoinsNodesListView+Row.swift create mode 100644 Adamant/Modules/CoinsNodesList/View/CoinsNodesListView.swift create mode 100644 Adamant/Modules/CoinsNodesList/ViewModel/CoinsNodesListMapper.swift create mode 100644 Adamant/Modules/CoinsNodesList/ViewModel/CoinsNodesListState.swift create mode 100644 Adamant/Modules/CoinsNodesList/ViewModel/CoinsNodesListStrings.swift create mode 100644 Adamant/Modules/CoinsNodesList/ViewModel/CoinsNodesListViewModel+ApiServices.swift create mode 100644 Adamant/Modules/CoinsNodesList/ViewModel/CoinsNodesListViewModel.swift create mode 100644 Adamant/Modules/Wallets/WalletApiService.swift create mode 100644 Adamant/SharedViews/SelfRemovableHostingController.swift diff --git a/Adamant.xcodeproj/project.pbxproj b/Adamant.xcodeproj/project.pbxproj index e3133e29e..1710bd233 100644 --- a/Adamant.xcodeproj/project.pbxproj +++ b/Adamant.xcodeproj/project.pbxproj @@ -229,6 +229,17 @@ 93496BB72A6CAED100DD062F /* Roboto_500_normal.ttf in Resources */ = {isa = PBXBuildFile; fileRef = 93496BAC2A6CAED100DD062F /* Roboto_500_normal.ttf */; }; 93547BCA29E2262D00B0914B /* WelcomeViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 93547BC929E2262D00B0914B /* WelcomeViewController.swift */; }; 935F53D629BE8F7400779492 /* TransactionStatusPublisher.swift in Sources */ = {isa = PBXBuildFile; fileRef = 935F53D529BE8F7400779492 /* TransactionStatusPublisher.swift */; }; + 9366588D2B0AB6BD00BDB2D3 /* CoinsNodesListState.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9366588C2B0AB6BD00BDB2D3 /* CoinsNodesListState.swift */; }; + 9366588F2B0AB97500BDB2D3 /* CoinsNodesListMapper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9366588E2B0AB97500BDB2D3 /* CoinsNodesListMapper.swift */; }; + 936658912B0AB9DC00BDB2D3 /* NodeWithGroup.swift in Sources */ = {isa = PBXBuildFile; fileRef = 936658902B0AB9DC00BDB2D3 /* NodeWithGroup.swift */; }; + 936658932B0AC03700BDB2D3 /* CoinsNodesListStrings.swift in Sources */ = {isa = PBXBuildFile; fileRef = 936658922B0AC03700BDB2D3 /* CoinsNodesListStrings.swift */; }; + 936658952B0AC15300BDB2D3 /* Node+Strings.swift in Sources */ = {isa = PBXBuildFile; fileRef = 936658942B0AC15300BDB2D3 /* Node+Strings.swift */; }; + 936658972B0ACB1500BDB2D3 /* CoinsNodesListViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 936658962B0ACB1500BDB2D3 /* CoinsNodesListViewModel.swift */; }; + 936658992B0AD32600BDB2D3 /* CoinsNodesListViewModel+ApiServices.swift in Sources */ = {isa = PBXBuildFile; fileRef = 936658982B0AD32600BDB2D3 /* CoinsNodesListViewModel+ApiServices.swift */; }; + 9366589B2B0AD3E600BDB2D3 /* WalletApiService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9366589A2B0AD3E600BDB2D3 /* WalletApiService.swift */; }; + 9366589D2B0ADBAF00BDB2D3 /* CoinsNodesListView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9366589C2B0ADBAF00BDB2D3 /* CoinsNodesListView.swift */; }; + 936658A32B0ADE4400BDB2D3 /* CoinsNodesListView+Row.swift in Sources */ = {isa = PBXBuildFile; fileRef = 936658A22B0ADE4400BDB2D3 /* CoinsNodesListView+Row.swift */; }; + 936658A52B0AE67A00BDB2D3 /* CoinsNodesListFactory.swift in Sources */ = {isa = PBXBuildFile; fileRef = 936658A42B0AE67A00BDB2D3 /* CoinsNodesListFactory.swift */; }; 93684A2A29EFA28A00F9EFFE /* FixedTextMessageSizeCalculator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 93684A2929EFA28A00F9EFFE /* FixedTextMessageSizeCalculator.swift */; }; 9371130F2996EDA900F64CF9 /* ChatRefreshMock.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9371130E2996EDA900F64CF9 /* ChatRefreshMock.swift */; }; 9371E561295CD53100438F2C /* ChatLocalization.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9371E560295CD53100438F2C /* ChatLocalization.swift */; }; @@ -274,6 +285,7 @@ 93B28EC52B076E2C007F268B /* DashBlockchainInfoDTO.swift in Sources */ = {isa = PBXBuildFile; fileRef = 93B28EC42B076E2C007F268B /* DashBlockchainInfoDTO.swift */; }; 93B28EC82B076E68007F268B /* DashResponseDTO.swift in Sources */ = {isa = PBXBuildFile; fileRef = 93B28EC72B076E68007F268B /* DashResponseDTO.swift */; }; 93B28ECA2B076E88007F268B /* DashErrorDTO.swift in Sources */ = {isa = PBXBuildFile; fileRef = 93B28EC92B076E88007F268B /* DashErrorDTO.swift */; }; + 93BB4E6C2B0AF9EE00A537F2 /* SelfRemovableHostingController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 93BB4E6B2B0AF9EE00A537F2 /* SelfRemovableHostingController.swift */; }; 93BF4A6629E4859900505CD0 /* DelegatesBottomPanel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 93BF4A6529E4859900505CD0 /* DelegatesBottomPanel.swift */; }; 93BF4A6C29E4B4BF00505CD0 /* DelegatesBottomPanel+Model.swift in Sources */ = {isa = PBXBuildFile; fileRef = 93BF4A6B29E4B4BF00505CD0 /* DelegatesBottomPanel+Model.swift */; }; 93C794442B07725C00408826 /* DashGetAddressBalanceDTO.swift in Sources */ = {isa = PBXBuildFile; fileRef = 93C794432B07725C00408826 /* DashGetAddressBalanceDTO.swift */; }; @@ -310,7 +322,6 @@ 93E8EDCD2AF1BD65003E163C /* AdamantApiCore.swift in Sources */ = {isa = PBXBuildFile; fileRef = 93E8EDCC2AF1BD65003E163C /* AdamantApiCore.swift */; }; 93E8EDCF2AF1CD9F003E163C /* NodeStatusInfo.swift in Sources */ = {isa = PBXBuildFile; fileRef = 93E8EDCE2AF1CD9F003E163C /* NodeStatusInfo.swift */; }; 93E8EDD12AF1DF8E003E163C /* ServerResponse+Resolver.swift in Sources */ = {isa = PBXBuildFile; fileRef = 93E8EDD02AF1DF8E003E163C /* ServerResponse+Resolver.swift */; }; - 93EE9C3329C2666200D9853F /* RichTransactionStatusSubscription.swift in Sources */ = {isa = PBXBuildFile; fileRef = 93EE9C3229C2666200D9853F /* RichTransactionStatusSubscription.swift */; }; 93EE9C3329C2666200D9853F /* TransactionStatusSubscription.swift in Sources */ = {isa = PBXBuildFile; fileRef = 93EE9C3229C2666200D9853F /* TransactionStatusSubscription.swift */; }; 93F391502962F5D400BFD6AE /* SpinnerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 93F3914F2962F5D400BFD6AE /* SpinnerView.swift */; }; 93FA403629401BFC00D20DB6 /* PopupKit in Frameworks */ = {isa = PBXBuildFile; productRef = 93FA403529401BFC00D20DB6 /* PopupKit */; }; @@ -846,6 +857,17 @@ 93496BAC2A6CAED100DD062F /* Roboto_500_normal.ttf */ = {isa = PBXFileReference; lastKnownFileType = file; path = Roboto_500_normal.ttf; sourceTree = ""; }; 93547BC929E2262D00B0914B /* WelcomeViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WelcomeViewController.swift; sourceTree = ""; }; 935F53D529BE8F7400779492 /* TransactionStatusPublisher.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TransactionStatusPublisher.swift; sourceTree = ""; }; + 9366588C2B0AB6BD00BDB2D3 /* CoinsNodesListState.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CoinsNodesListState.swift; sourceTree = ""; }; + 9366588E2B0AB97500BDB2D3 /* CoinsNodesListMapper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CoinsNodesListMapper.swift; sourceTree = ""; }; + 936658902B0AB9DC00BDB2D3 /* NodeWithGroup.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NodeWithGroup.swift; sourceTree = ""; }; + 936658922B0AC03700BDB2D3 /* CoinsNodesListStrings.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CoinsNodesListStrings.swift; sourceTree = ""; }; + 936658942B0AC15300BDB2D3 /* Node+Strings.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Node+Strings.swift"; sourceTree = ""; }; + 936658962B0ACB1500BDB2D3 /* CoinsNodesListViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CoinsNodesListViewModel.swift; sourceTree = ""; }; + 936658982B0AD32600BDB2D3 /* CoinsNodesListViewModel+ApiServices.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "CoinsNodesListViewModel+ApiServices.swift"; sourceTree = ""; }; + 9366589A2B0AD3E600BDB2D3 /* WalletApiService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WalletApiService.swift; sourceTree = ""; }; + 9366589C2B0ADBAF00BDB2D3 /* CoinsNodesListView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CoinsNodesListView.swift; sourceTree = ""; }; + 936658A22B0ADE4400BDB2D3 /* CoinsNodesListView+Row.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "CoinsNodesListView+Row.swift"; sourceTree = ""; }; + 936658A42B0AE67A00BDB2D3 /* CoinsNodesListFactory.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CoinsNodesListFactory.swift; sourceTree = ""; }; 93684A2929EFA28A00F9EFFE /* FixedTextMessageSizeCalculator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FixedTextMessageSizeCalculator.swift; sourceTree = ""; }; 9371130E2996EDA900F64CF9 /* ChatRefreshMock.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChatRefreshMock.swift; sourceTree = ""; }; 9371E560295CD53100438F2C /* ChatLocalization.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChatLocalization.swift; sourceTree = ""; }; @@ -887,6 +909,7 @@ 93B28EC42B076E2C007F268B /* DashBlockchainInfoDTO.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DashBlockchainInfoDTO.swift; sourceTree = ""; }; 93B28EC72B076E68007F268B /* DashResponseDTO.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DashResponseDTO.swift; sourceTree = ""; }; 93B28EC92B076E88007F268B /* DashErrorDTO.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DashErrorDTO.swift; sourceTree = ""; }; + 93BB4E6B2B0AF9EE00A537F2 /* SelfRemovableHostingController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SelfRemovableHostingController.swift; sourceTree = ""; }; 93BF4A6529E4859900505CD0 /* DelegatesBottomPanel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DelegatesBottomPanel.swift; sourceTree = ""; }; 93BF4A6B29E4B4BF00505CD0 /* DelegatesBottomPanel+Model.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "DelegatesBottomPanel+Model.swift"; sourceTree = ""; }; 93C794432B07725C00408826 /* DashGetAddressBalanceDTO.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DashGetAddressBalanceDTO.swift; sourceTree = ""; }; @@ -918,7 +941,6 @@ 93E8EDCC2AF1BD65003E163C /* AdamantApiCore.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AdamantApiCore.swift; sourceTree = ""; }; 93E8EDCE2AF1CD9F003E163C /* NodeStatusInfo.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NodeStatusInfo.swift; sourceTree = ""; }; 93E8EDD02AF1DF8E003E163C /* ServerResponse+Resolver.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "ServerResponse+Resolver.swift"; sourceTree = ""; }; - 93EE9C3229C2666200D9853F /* RichTransactionStatusSubscription.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RichTransactionStatusSubscription.swift; sourceTree = ""; }; 93EE9C3229C2666200D9853F /* TransactionStatusSubscription.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TransactionStatusSubscription.swift; sourceTree = ""; }; 93F3914F2962F5D400BFD6AE /* SpinnerView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SpinnerView.swift; sourceTree = ""; }; 93FC169A2B0197FD0062B507 /* BtcApiService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BtcApiService.swift; sourceTree = ""; }; @@ -1547,6 +1569,37 @@ path = RichTransactionStatusService; sourceTree = ""; }; + 9366588B2B0AB68300BDB2D3 /* CoinsNodesList */ = { + isa = PBXGroup; + children = ( + 936658A12B0ADE3100BDB2D3 /* View */, + 936658A02B0ADE2300BDB2D3 /* ViewModel */, + 936658A42B0AE67A00BDB2D3 /* CoinsNodesListFactory.swift */, + ); + path = CoinsNodesList; + sourceTree = ""; + }; + 936658A02B0ADE2300BDB2D3 /* ViewModel */ = { + isa = PBXGroup; + children = ( + 936658922B0AC03700BDB2D3 /* CoinsNodesListStrings.swift */, + 9366588C2B0AB6BD00BDB2D3 /* CoinsNodesListState.swift */, + 9366588E2B0AB97500BDB2D3 /* CoinsNodesListMapper.swift */, + 936658962B0ACB1500BDB2D3 /* CoinsNodesListViewModel.swift */, + 936658982B0AD32600BDB2D3 /* CoinsNodesListViewModel+ApiServices.swift */, + ); + path = ViewModel; + sourceTree = ""; + }; + 936658A12B0ADE3100BDB2D3 /* View */ = { + isa = PBXGroup; + children = ( + 9366589C2B0ADBAF00BDB2D3 /* CoinsNodesListView.swift */, + 936658A22B0ADE4400BDB2D3 /* CoinsNodesListView+Row.swift */, + ); + path = View; + sourceTree = ""; + }; 937736832B0949C700B35C7A /* NodeCell */ = { isa = PBXGroup; children = ( @@ -1943,6 +1996,7 @@ 9338AE8E2AEF8131001D32DF /* InternalAPIError.swift */, 93E8EDCE2AF1CD9F003E163C /* NodeStatusInfo.swift */, 93B28EBF2B076667007F268B /* APIResponseModel.swift */, + 936658902B0AB9DC00BDB2D3 /* NodeWithGroup.swift */, ); path = Models; sourceTree = ""; @@ -1977,6 +2031,7 @@ 93294B832AAD0C8F00911109 /* Assembler+Extension.swift */, 93E8EDD02AF1DF8E003E163C /* ServerResponse+Resolver.swift */, 93CCAE7F2B06E2D100EA5B94 /* ApiServiceError+Extension.swift */, + 936658942B0AC15300BDB2D3 /* Node+Strings.swift */, ); path = Helpers; sourceTree = ""; @@ -2001,6 +2056,7 @@ E919479920000FFD001362F8 /* Modules */ = { isa = PBXGroup; children = ( + 9366588B2B0AB68300BDB2D3 /* CoinsNodesList */, 3AA50DED2AEBE61C00C58FC8 /* PartnerQR */, 93ADE06D2ACA66AF008ED641 /* TestVibration */, 93294B942AAD31F200911109 /* ScreensFactory */, @@ -2094,6 +2150,7 @@ 6449BA5D235CA0930033B936 /* ERC20 */, E94008712114EACF00CD2D67 /* WalletAccount.swift */, E940086D2114AA2E00CD2D67 /* WalletService.swift */, + 9366589A2B0AD3E600BDB2D3 /* WalletApiService.swift */, E99818932120892F0018C84C /* WalletViewControllerBase.swift */, E9981897212096ED0018C84C /* WalletViewControllerBase.xib */, E9EC342020052ABB00C0E546 /* TransferViewControllerBase.swift */, @@ -2400,6 +2457,7 @@ 4133AED329769EEC00F3D017 /* UpdatingIndicatorView.swift */, 41A1994529D2FCF80031AD75 /* ReplyView.swift */, 41A1994729D325800031AD75 /* SwipeableView.swift */, + 93BB4E6B2B0AF9EE00A537F2 /* SelfRemovableHostingController.swift */, ); path = SharedViews; sourceTree = ""; @@ -2888,6 +2946,7 @@ E908472A2196FEA80095825D /* RichMessageTransaction+CoreDataClass.swift in Sources */, 4E9EE86F28CE793D008359F7 /* SafeDecimalRow.swift in Sources */, 93ADE0732ACA66AF008ED641 /* VibrationSelectionFactory.swift in Sources */, + 9366589B2B0AD3E600BDB2D3 /* WalletApiService.swift in Sources */, 937751AB2A68BB390054BD65 /* ChatTransactionCell.swift in Sources */, 64A223D620F760BB005157CB /* Localization.swift in Sources */, 64E1C82F222E95F6006C4DA7 /* DogeWallet.swift in Sources */, @@ -2898,6 +2957,7 @@ 4186B332294200B4006594A3 /* BtcWalletService+DynamicConstants.swift in Sources */, 93A118512993167500E144CC /* ChatMessageBackgroundColor.swift in Sources */, E9CAE8D22018AA7700345E76 /* AdamantApi+Accounts.swift in Sources */, + 936658A32B0ADE4400BDB2D3 /* CoinsNodesListView+Row.swift in Sources */, 648CE3A6229AD1CD0070A2CC /* DashWalletService+Send.swift in Sources */, E987024920C2B1F700E393F4 /* AdamantChatsProvider+fakeMessages.swift in Sources */, 6403F5E222723F7500D58779 /* DashWallet.swift in Sources */, @@ -2931,6 +2991,7 @@ E9E7CDB72003994E00DFC4DB /* AdamantUtilities+extended.swift in Sources */, E9147B6320505C7500145913 /* QRCodeReader+adamant.swift in Sources */, E90055F720EC200900D0CB2D /* SecurityViewController.swift in Sources */, + 93BB4E6C2B0AF9EE00A537F2 /* SelfRemovableHostingController.swift in Sources */, 644EC35E20F34F1E00F40C73 /* DelegateDetailsViewController.swift in Sources */, E9942B80203C058C00C163AF /* QRGeneratorViewController.swift in Sources */, 4184F1772A33173100D7B8B9 /* ContributeView.swift in Sources */, @@ -3003,6 +3064,7 @@ E9147B612050599000145913 /* LoginViewController+QR.swift in Sources */, 9399F5ED29A85A48006C3E30 /* ChatCacheService.swift in Sources */, 3A9015A92A615893002A2464 /* ChatMessagesListViewModel.swift in Sources */, + 936658952B0AC15300BDB2D3 /* Node+Strings.swift in Sources */, 41A1995429D56E340031AD75 /* ChatMessageReplyCell.swift in Sources */, 93294B872AAD0E0A00911109 /* AdmWallet.swift in Sources */, 6449BA6B235CA0930033B936 /* ERC20TransactionDetailsViewController.swift in Sources */, @@ -3025,6 +3087,7 @@ 4153045929C09902000E4BEA /* AdamantIncreaseFeeService.swift in Sources */, 3AA50DEF2AEBE65D00C58FC8 /* PartnerQRView.swift in Sources */, E90A494B204D9EB8009F6A65 /* AdamantAuthentication.swift in Sources */, + 936658972B0ACB1500BDB2D3 /* CoinsNodesListViewModel.swift in Sources */, E9215973206119FB0000CA5C /* ReachabilityMonitor.swift in Sources */, 9338AE8B2AEF7E37001D32DF /* APIParametersEncoding.swift in Sources */, 3A2F55FC2AC6F885000A3F26 /* CoinStorage.swift in Sources */, @@ -3050,9 +3113,11 @@ A5BBD811262C657300B5C40C /* ByteBackpacker.swift in Sources */, 648BCA6D213D384F00875EB5 /* AvatarService.swift in Sources */, E95F856F2007B61D0070534A /* GetPublicKeyResponse.swift in Sources */, + 936658992B0AD32600BDB2D3 /* CoinsNodesListViewModel+ApiServices.swift in Sources */, 644EC34D20EFA60900F40C73 /* AdamantApi+Delegates.swift in Sources */, E940088F2119A9E800CD2D67 /* BigInt+Decimal.swift in Sources */, E9E7CDC72003F6D200DFC4DB /* TransactionTableViewCell.swift in Sources */, + 936658912B0AB9DC00BDB2D3 /* NodeWithGroup.swift in Sources */, 9338AE862AEF6A97001D32DF /* APICore.swift in Sources */, 938F7D5D2955C8F9001915CA /* ChatLayoutManager.swift in Sources */, 6403F5DE22723C6800D58779 /* DashMainnet.swift in Sources */, @@ -3092,6 +3157,7 @@ 3AA50DF32AEBE67C00C58FC8 /* PartnerQRFactory.swift in Sources */, E9AA8C02212C5BF500F9249F /* AdmWalletService+Send.swift in Sources */, E90847332196FEA80095825D /* TransferTransaction+CoreDataProperties.swift in Sources */, + 9366588D2B0AB6BD00BDB2D3 /* CoinsNodesListState.swift in Sources */, E99818942120892F0018C84C /* WalletViewControllerBase.swift in Sources */, E9B3D39E201F99F40019EB36 /* DataProvider.swift in Sources */, 93BF4A6C29E4B4BF00505CD0 /* DelegatesBottomPanel+Model.swift in Sources */, @@ -3107,7 +3173,7 @@ E9C51EEF20139DC600385EB7 /* TransactionIdResponse.swift in Sources */, A50A41092822F8CE006BDFE1 /* BtcWalletViewController.swift in Sources */, E9722066201F42BB004F2AAD /* CoreDataStack.swift in Sources */, - 93EE9C3329C2666200D9853F /* RichTransactionStatusSubscription.swift in Sources */, + 93EE9C3329C2666200D9853F /* TransactionStatusSubscription.swift in Sources */, 9338AE7F2AEF43DA001D32DF /* NodesStorageProtocol.swift in Sources */, 93EE9C3329C2666200D9853F /* TransactionStatusSubscription.swift in Sources */, E913C8F21FFFA51D001A83F7 /* AppDelegate.swift in Sources */, @@ -3177,11 +3243,11 @@ 3A2F55F92AC6F308000A3F26 /* CoinTransaction+CoreDataClass.swift in Sources */, 93A91FD1297972B7001DB1F8 /* ChatScrollDownButton.swift in Sources */, 41C1698C29E7F34900FEB3CB /* RichTransactionReplyService.swift in Sources */, - 935F53D629BE8F7400779492 /* RichTransactionStatusPublisher.swift in Sources */, - E9A03FDA20DC0B14007653A1 /* NodesSource.swift in Sources */, + 935F53D629BE8F7400779492 /* TransactionStatusPublisher.swift in Sources */, 935F53D629BE8F7400779492 /* TransactionStatusPublisher.swift in Sources */, 9390C5032976B42800270CDF /* ChatDialogManager.swift in Sources */, E926E02E213EAABF005E536B /* TransferViewControllerBase+Alert.swift in Sources */, + 936658A52B0AE67A00BDB2D3 /* CoinsNodesListFactory.swift in Sources */, E9B1AA572121ACC000080A2A /* AdmWalletViewController.swift in Sources */, 93C7944A2B077A1C00408826 /* DashGetUnspentTransactionsDTO.swift in Sources */, E9240BF5215D686500187B09 /* AdmWalletService+RichMessageProvider.swift in Sources */, @@ -3189,7 +3255,9 @@ E9A174B32057EC47003667CD /* BackgroundFetchService.swift in Sources */, 649D6BE821B95DB7009E727B /* LskWalletService+RichMessageProvider.swift in Sources */, E9E7CDBE2003AEFB00DFC4DB /* CellFactory.swift in Sources */, + 9366589D2B0ADBAF00BDB2D3 /* CoinsNodesListView.swift in Sources */, 411743022A39B208008CD98A /* ContributeState.swift in Sources */, + 9366588F2B0AB97500BDB2D3 /* CoinsNodesListMapper.swift in Sources */, 93F391502962F5D400BFD6AE /* SpinnerView.swift in Sources */, 93A18C892AAEAE7700D0AB98 /* WalletFactory.swift in Sources */, E923222621135F9000A7E5AF /* EthAccount.swift in Sources */, @@ -3238,6 +3306,7 @@ 649D6BEC21BD5A53009E727B /* UISuffixTextField.swift in Sources */, E93B0D762028B28E00126346 /* AdamantChatsProvider.swift in Sources */, 3A33F9FA2A7A53DA002B8003 /* EmojiUpdateType.swift in Sources */, + 936658932B0AC03700BDB2D3 /* CoinsNodesListStrings.swift in Sources */, E993302021354B1800CD5200 /* AdmWalletFactory.swift in Sources */, E9332B8921F1FA4400D56E72 /* OnboardFactory.swift in Sources */, 938F7D722955CE72001915CA /* ChatFactory.swift in Sources */, diff --git a/Adamant/Helpers/Node+Strings.swift b/Adamant/Helpers/Node+Strings.swift new file mode 100644 index 000000000..cb6063023 --- /dev/null +++ b/Adamant/Helpers/Node+Strings.swift @@ -0,0 +1,58 @@ +// +// Node+Strings.swift +// Adamant +// +// Created by Andrew G on 20.11.2023. +// Copyright © 2023 Adamant. All rights reserved. +// + +import CommonKit + +extension Node { + func statusString(_ status: Node.ConnectionStatus?) -> String? { + switch status { + case .allowed: + let ping = ping.map { Int($0 * 1000) } + return ping.map { "\(NodeCell.Strings.ping): \($0) \(NodeCell.Strings.milliseconds)" } + case .synchronizing: + return NodeCell.Strings.synchronizing + case .offline: + return NodeCell.Strings.offline + case .none: + return nil + } + } + + var versionString: String? { + version.map { "(\(NodeCell.Strings.version): \($0))" } + } +} + +private extension NodeCell { + enum Strings { + static let ping = String.localized( + "NodesList.NodeCell.Ping", + comment: "NodesList.NodeCell: Node ping" + ) + + static let milliseconds = String.localized( + "NodesList.NodeCell.Milliseconds", + comment: "NodesList.NodeCell: Milliseconds" + ) + + static let synchronizing = String.localized( + "NodesList.NodeCell.Synchronizing", + comment: "NodesList.NodeCell: Node is synchronizing" + ) + + static let offline = String.localized( + "NodesList.NodeCell.Offline", + comment: "NodesList.NodeCell: Node is offline" + ) + + static let version = String.localized( + "NodesList.NodeCell.Version", + comment: "NodesList.NodeCell: Node version" + ) + } +} diff --git a/Adamant/Models/NodeWithGroup.swift b/Adamant/Models/NodeWithGroup.swift new file mode 100644 index 000000000..60813c733 --- /dev/null +++ b/Adamant/Models/NodeWithGroup.swift @@ -0,0 +1,14 @@ +// +// NodeWithGroup.swift +// Adamant +// +// Created by Andrew G on 20.11.2023. +// Copyright © 2023 Adamant. All rights reserved. +// + +import CommonKit + +struct NodeWithGroup: Codable, Equatable { + let group: NodeGroup + var node: Node +} diff --git a/Adamant/Modules/Account/AccountViewController.swift b/Adamant/Modules/Account/AccountViewController.swift index 3ef091cfe..ef866599c 100644 --- a/Adamant/Modules/Account/AccountViewController.swift +++ b/Adamant/Modules/Account/AccountViewController.swift @@ -60,7 +60,7 @@ final class AccountViewController: FormViewController { enum Rows { case balance, sendTokens // Wallet - case security, nodes, theme, currency, about, visibleWallets, contribute, vibration // Application + case security, nodes, coinsNodes, theme, currency, about, visibleWallets, contribute, vibration // Application case voteForDelegates, generateQr, generatePk, logout // Actions case stayIn, biometry, notifications // Security @@ -83,6 +83,7 @@ final class AccountViewController: FormViewController { case .visibleWallets: return "visibleWallets" case .contribute: return "contribute" case .vibration: return "vibration" + case .coinsNodes: return "coinsNodes" } } @@ -105,6 +106,7 @@ final class AccountViewController: FormViewController { case .visibleWallets: return .localized("VisibleWallets.Title", comment: "Visible Wallets page: scene title") case .contribute: return .localized("AccountTab.Row.Contribute", comment: "Account tab: 'Contribute' row") case .vibration: return "Vibrations" + case .coinsNodes: return .adamant.coinsNodesList.title } } @@ -115,6 +117,7 @@ final class AccountViewController: FormViewController { case .theme: return .asset(named: "row_themes.png") case .currency: return .asset(named: "row_currency") case .nodes: return .asset(named: "row_nodes") + case .coinsNodes: return .init(systemName: "server.rack") case .balance: return .asset(named: "row_balance") case .voteForDelegates: return .asset(named: "row_vote-delegates") case .logout: return .asset(named: "row_logout") @@ -323,6 +326,33 @@ final class AccountViewController: FormViewController { appSection.append(nodesRow) + // Coins nodes list + let coinsNodesRow = LabelRow { + $0.title = Rows.coinsNodes.localized + $0.tag = Rows.coinsNodes.tag + $0.cell.imageView?.image = Rows.coinsNodes.image + $0.cell.selectionStyle = .gray + }.cellUpdate { (cell, _) in + cell.accessoryType = .disclosureIndicator + }.onCellSelection { [weak self] (_, _) in + guard let self = self else { return } + let vc = screensFactory.makeCoinsNodesList() + + if let split = splitViewController { + let details = UINavigationController(rootViewController:vc) + split.showDetailViewController(details, sender: self) + } else if let nav = navigationController { + nav.pushViewController(vc, animated: true) + } else { + vc.modalPresentationStyle = .overFullScreen + present(vc, animated: true, completion: nil) + } + + deselectWalletViewControllers() + } + + appSection.append(coinsNodesRow) + // Currency select let currencyRow = ActionSheetRow { $0.title = Rows.currency.localized @@ -999,7 +1029,7 @@ extension AccountViewController: PagingViewControllerDataSource, PagingViewContr if ERC20Token.supportedTokens.contains(where: { token in return token.symbol == service.tokenSymbol }) { - network = service.tokenNetworkSymbol + network = type(of: service).tokenNetworkSymbol } let item = WalletPagingItem( diff --git a/Adamant/Modules/ChatsList/ComplexTransferViewController.swift b/Adamant/Modules/ChatsList/ComplexTransferViewController.swift index dc8df14b9..879eec89f 100644 --- a/Adamant/Modules/ChatsList/ComplexTransferViewController.swift +++ b/Adamant/Modules/ChatsList/ComplexTransferViewController.swift @@ -167,7 +167,7 @@ extension ComplexTransferViewController: PagingViewControllerDataSource { if ERC20Token.supportedTokens.contains(where: { token in return token.symbol == service.tokenSymbol }) { - network = service.tokenNetworkSymbol + network = type(of: service).tokenNetworkSymbol } let item = WalletPagingItem( diff --git a/Adamant/Modules/CoinsNodesList/CoinsNodesListFactory.swift b/Adamant/Modules/CoinsNodesList/CoinsNodesListFactory.swift new file mode 100644 index 000000000..ba7e212f8 --- /dev/null +++ b/Adamant/Modules/CoinsNodesList/CoinsNodesListFactory.swift @@ -0,0 +1,54 @@ +// +// CoinsNodesListFactory.swift +// Adamant +// +// Created by Andrew G on 20.11.2023. +// Copyright © 2023 Adamant. All rights reserved. +// + +import Swinject +import SwiftUI +import CommonKit + +struct CoinsNodesListFactory { + private let assembler: Assembler + + init(parent: Assembler) { + assembler = .init([CoinsNodesListAssembly()], parent: parent) + } + + func makeViewController() -> UIViewController { + SelfRemovableHostingController( + rootView: CoinsNodesListView( + viewModel: assembler.resolve(CoinsNodesListViewModel.self)! + ) + ) + } +} + +private struct CoinsNodesListAssembly: Assembly { + func assemble(container: Container) { + container.register(CoinsNodesListViewModel.self) { + let processedGroups = Set(NodeGroup.allCases).subtracting([.adm]) + + return .init( + mapper: .init(processedGroups: processedGroups), + nodesStorage: $0.resolve(NodesStorageProtocol.self)!, + nodesAdditionalParamsStorage: $0.resolve( + NodesAdditionalParamsStorageProtocol.self + )!, + processedGroups: processedGroups, + apiServices: .init( + btc: $0.resolve(BtcApiService.self)!, + eth: $0.resolve(EthApiService.self)!, + lskNode: $0.resolve(LskNodeApiService.self)!, + lskService: $0.resolve(LskServiceApiService.self)!, + doge: $0.resolve(DogeApiService.self)!, + dash: $0.resolve(DashApiService.self)!, + adm: $0.resolve(ApiService.self)! + ) + ) + }.inObjectScope(.weak) + } +} + diff --git a/Adamant/Modules/CoinsNodesList/View/CoinsNodesListView+Row.swift b/Adamant/Modules/CoinsNodesList/View/CoinsNodesListView+Row.swift new file mode 100644 index 000000000..00973fbb1 --- /dev/null +++ b/Adamant/Modules/CoinsNodesList/View/CoinsNodesListView+Row.swift @@ -0,0 +1,73 @@ +// +// CoinsNodesListView+Row.swift +// Adamant +// +// Created by Andrew G on 20.11.2023. +// Copyright © 2023 Adamant. All rights reserved. +// + +import SwiftUI +import CommonKit + +extension CoinsNodesListView { + struct Row: View { + let model: CoinsNodesListState.Section.Row + let setIsEnabled: (Bool) -> Void + + var body: some View { + HStack(spacing: 10) { + CheckmarkView( + isEnabled: model.isEnabled, + setIsEnabled: setIsEnabled + ) + .frame(squareSize: 24) + .animation(.easeInOut(duration: 0.1), value: model.isEnabled) + + VStack(alignment: .leading, spacing: 4) { + Text(model.title).font(titleFont).lineLimit(1) + + HStack(spacing: 6) { + Text(model.connectionStatus).font(captionFont) + + Text(model.description).font(subtitleFont) + .lineLimit(1) + .frame(height: 10) + } + } + }.padding(2) + } + } +} + +private extension CoinsNodesListView.Row { + struct CheckmarkView: View { + let isEnabled: Bool + let setIsEnabled: (Bool) -> Void + + var body: some View { + ZStack { + if isEnabled { + Image(uiImage: .asset(named: "status_success") ?? .strokedCheckmark) + .resizable() + .scaledToFit() + .transition(.scale) + } + + if !isEnabled { + Circle().strokeBorder( + Color(uiColor: .adamant.secondary), + lineWidth: 1 + ) + } + } + .contentShape(Rectangle()) + .onTapGesture { + setIsEnabled(!isEnabled) + } + } + } +} + +private let titleFont = Font.system(size: 17, weight: .regular) +private let subtitleFont = Font(UIFont.preferredFont(forTextStyle: .caption1)) +private let captionFont = Font.system(size: 12, weight: .regular) diff --git a/Adamant/Modules/CoinsNodesList/View/CoinsNodesListView.swift b/Adamant/Modules/CoinsNodesList/View/CoinsNodesListView.swift new file mode 100644 index 000000000..933d2a5ea --- /dev/null +++ b/Adamant/Modules/CoinsNodesList/View/CoinsNodesListView.swift @@ -0,0 +1,57 @@ +// +// CoinsNodesListView.swift +// Adamant +// +// Created by Andrew G on 20.11.2023. +// Copyright © 2023 Adamant. All rights reserved. +// + +import SwiftUI +import CommonKit + +struct CoinsNodesListView: View { + @StateObject private var viewModel: CoinsNodesListViewModel + + var body: some View { + List { + ForEach(viewModel.state.sections, content: makeSection) + makeFastestNodeModeSection() + } + .listStyle(.insetGrouped) + .withoutListBackground() + .background(Color(.adamant.secondBackgroundColor)) + .navigationTitle(String.adamant.coinsNodesList.title) + } + + init(viewModel: CoinsNodesListViewModel) { + _viewModel = .init(wrappedValue: viewModel) + } +} + +private extension CoinsNodesListView { + func makeSection(_ model: CoinsNodesListState.Section) -> some View { + Section( + header: Text(model.title), + content: { + ForEach(model.rows) { row in + Row( + model: row, + setIsEnabled: { viewModel.setIsEnabled(id: row.id, value: $0) } + ).listRowBackground(Color(uiColor: .adamant.cellColor)) + } + } + ) + } + + func makeFastestNodeModeSection() -> some View { + Section( + content: { + Toggle( + String.adamant.coinsNodesList.preferTheFastestNode, + isOn: $viewModel.state.fastestNodeMode + ).listRowBackground(Color(uiColor: .adamant.cellColor)) + }, + footer: { Text(String.adamant.coinsNodesList.fastestNodeTip) } + ) + } +} diff --git a/Adamant/Modules/CoinsNodesList/ViewModel/CoinsNodesListMapper.swift b/Adamant/Modules/CoinsNodesList/ViewModel/CoinsNodesListMapper.swift new file mode 100644 index 000000000..0ca1c2ebf --- /dev/null +++ b/Adamant/Modules/CoinsNodesList/ViewModel/CoinsNodesListMapper.swift @@ -0,0 +1,113 @@ +// +// CoinsNodesListMapper.swift +// Adamant +// +// Created by Andrew G on 20.11.2023. +// Copyright © 2023 Adamant. All rights reserved. +// + +import CommonKit +import SwiftUI + +struct CoinsNodesListMapper { + let processedGroups: Set + + func map(items: [NodeWithGroup], restNodeIds: [UUID]) -> [CoinsNodesListState.Section] { + var nodesDict = Dictionary() + + items.forEach { item in + guard processedGroups.contains(item.group) else { return } + + if nodesDict[item.group] == nil { + nodesDict[item.group] = [item.node] + } else { + nodesDict[item.group]?.append(item.node) + } + } + + return nodesDict.keys.map { + map( + group: $0, + nodes: nodesDict[$0] ?? .init(), + restNodeIds: restNodeIds + ) + }.sorted { $0.title < $1.title } + } +} + +private extension CoinsNodesListMapper { + func map( + group: NodeGroup, + nodes: [Node], + restNodeIds: [UUID] + ) -> CoinsNodesListState.Section { + .init( + id: group, + title: group.name, + rows: nodes.map { + map(node: $0, restNodeIds: restNodeIds) + } + ) + } + + func map(node: Node, restNodeIds: [UUID]) -> CoinsNodesListState.Section.Row { + let connectionStatus = node.isEnabled + ? node.connectionStatus + : nil + + let connectionStatusString = [ + "●", + restNodeIds.contains(node.id) + ? node.scheme.rawValue + : nil, + ].compactMap { $0 }.joined(separator: " ") + + var connectionStatusAttrString = AttributedString(connectionStatusString) + connectionStatusAttrString.foregroundColor = .init( + uiColor: getIndicatorColor(status: connectionStatus) + ) + + return .init( + id: node.id, + isEnabled: node.isEnabled, + title: node.asString(), + connectionStatus: connectionStatusAttrString, + description: node.statusString(connectionStatus) ?? .empty + ) + } +} + +private extension NodeGroup { + var name: String { + switch self { + case .btc: + return BtcWalletService.tokenNetworkSymbol + case .eth: + return EthWalletService.tokenNetworkSymbol + case .lskNode: + return LskWalletService.tokenNetworkSymbol + case .lskService: + return LskWalletService.tokenNetworkSymbol + + " " + .adamant.coinsNodesList.serviceNode + case .doge: + return DogeWalletService.tokenNetworkSymbol + case .dash: + return DashWalletService.tokenNetworkSymbol + case .adm: + return AdmWalletService.tokenNetworkSymbol + } + } +} + +private func getIndicatorColor(status: Node.ConnectionStatus?) -> UIColor { + switch status { + case .allowed: + return .adamant.good + case .synchronizing: + return .adamant.alert + case .offline: + return .adamant.danger + case .none: + return .adamant.inactive + } +} diff --git a/Adamant/Modules/CoinsNodesList/ViewModel/CoinsNodesListState.swift b/Adamant/Modules/CoinsNodesList/ViewModel/CoinsNodesListState.swift new file mode 100644 index 000000000..f60601052 --- /dev/null +++ b/Adamant/Modules/CoinsNodesList/ViewModel/CoinsNodesListState.swift @@ -0,0 +1,35 @@ +// +// CoinsNodesListState.swift +// Adamant +// +// Created by Andrew G on 20.11.2023. +// Copyright © 2023 Adamant. All rights reserved. +// + +import Foundation +import CommonKit + +struct CoinsNodesListState: Equatable { + var sections: [Section] + var fastestNodeMode: Bool + + static let `default` = Self(sections: .init(), fastestNodeMode: false) +} + +extension CoinsNodesListState { + struct Section: Equatable, Identifiable { + let id: NodeGroup + let title: String + let rows: [Row] + } +} + +extension CoinsNodesListState.Section { + struct Row: Equatable, Identifiable { + let id: UUID + let isEnabled: Bool + let title: String + let connectionStatus: AttributedString + let description: String + } +} diff --git a/Adamant/Modules/CoinsNodesList/ViewModel/CoinsNodesListStrings.swift b/Adamant/Modules/CoinsNodesList/ViewModel/CoinsNodesListStrings.swift new file mode 100644 index 000000000..d65bb3fa0 --- /dev/null +++ b/Adamant/Modules/CoinsNodesList/ViewModel/CoinsNodesListStrings.swift @@ -0,0 +1,28 @@ +// +// CoinsNodesListStrings.swift +// Adamant +// +// Created by Andrew G on 20.11.2023. +// Copyright © 2023 Adamant. All rights reserved. +// + +import CommonKit + +extension String.adamant { + enum coinsNodesList { + static let title = String.localized("CoinsNodesList.Title", comment: .empty) + static let serviceNode = String.localized("CoinsNodesList.ServiceNode", comment: .empty) + static let reset = String.localized("NodesList.ResetButton", comment: .empty) + static let resetAlert = String.localized("NodesList.ResetNodeListAlert", comment: .empty) + + static let preferTheFastestNode = String.localized( + "NodesList.PreferTheFastestNode", + comment: .empty + ) + + static let fastestNodeTip = String.localized( + "NodesList.PreferTheFastestNode.Footer", + comment: .empty + ) + } +} diff --git a/Adamant/Modules/CoinsNodesList/ViewModel/CoinsNodesListViewModel+ApiServices.swift b/Adamant/Modules/CoinsNodesList/ViewModel/CoinsNodesListViewModel+ApiServices.swift new file mode 100644 index 000000000..53bc5f309 --- /dev/null +++ b/Adamant/Modules/CoinsNodesList/ViewModel/CoinsNodesListViewModel+ApiServices.swift @@ -0,0 +1,42 @@ +// +// CoinsNodesListViewModel+Wallets.swift +// Adamant +// +// Created by Andrew G on 20.11.2023. +// Copyright © 2023 Adamant. All rights reserved. +// + +import CommonKit + +extension CoinsNodesListViewModel { + struct ApiServices { + let btc: WalletApiService + let eth: WalletApiService + let lskNode: WalletApiService + let lskService: WalletApiService + let doge: WalletApiService + let dash: WalletApiService + let adm: WalletApiService + } +} + +extension CoinsNodesListViewModel.ApiServices { + func getApiService(group: NodeGroup) -> WalletApiService { + switch group { + case .btc: + return btc + case .eth: + return eth + case .lskNode: + return lskNode + case .lskService: + return lskService + case .doge: + return doge + case .dash: + return dash + case .adm: + return adm + } + } +} diff --git a/Adamant/Modules/CoinsNodesList/ViewModel/CoinsNodesListViewModel.swift b/Adamant/Modules/CoinsNodesList/ViewModel/CoinsNodesListViewModel.swift new file mode 100644 index 000000000..d9f126b7b --- /dev/null +++ b/Adamant/Modules/CoinsNodesList/ViewModel/CoinsNodesListViewModel.swift @@ -0,0 +1,100 @@ +// +// CoinsNodesListViewModel.swift +// Adamant +// +// Created by Andrew G on 20.11.2023. +// Copyright © 2023 Adamant. All rights reserved. +// + +import SwiftUI +import Combine +import CommonKit + +@MainActor +final class CoinsNodesListViewModel: ObservableObject { + @Published var state: CoinsNodesListState = .default + + private let mapper: CoinsNodesListMapper + private let nodesStorage: NodesStorageProtocol + private let nodesAdditionalParamsStorage: NodesAdditionalParamsStorageProtocol + private let processedGroups: Set + private let apiServices: ApiServices + private var subscriptions = Set() + + nonisolated init( + mapper: CoinsNodesListMapper, + nodesStorage: NodesStorageProtocol, + nodesAdditionalParamsStorage: NodesAdditionalParamsStorageProtocol, + processedGroups: Set, + apiServices: ApiServices + ) { + self.mapper = mapper + self.nodesStorage = nodesStorage + self.nodesAdditionalParamsStorage = nodesAdditionalParamsStorage + self.processedGroups = processedGroups + self.apiServices = apiServices + Task { @MainActor in setup() } + } + + func setIsEnabled(id: UUID, value: Bool) { + nodesStorage.updateNodeParams(id: id, isEnabled: value) + } + + func reset() { + processedGroups.forEach { + nodesStorage.resetNodes(group: $0) + } + } +} + +private extension CoinsNodesListViewModel { + func setup() { + state.fastestNodeMode = processedGroups + .map { nodesAdditionalParamsStorage.isFastestNodeMode(group: $0) } + .reduce(into: true) { $0 = $0 && $1 } + + $state + .map(\.fastestNodeMode) + .removeDuplicates() + .sink { [weak self] in self?.saveFastestNodeMode($0) } + .store(in: &subscriptions) + + guard let someGroup = processedGroups.first else { return } + + nodesStorage.nodesWithGroupsPublisher + .combineLatest(nodesAdditionalParamsStorage.fastestNodeMode(group: someGroup)) + .receive(on: DispatchQueue.main) + .sink { [weak self] in self?.updateSections(items: $0.0) } + .store(in: &subscriptions) + + Timer + .publish(every: someGroup.onScreenUpdateInterval, on: .main, in: .default) + .autoconnect() + .sink { [weak self] _ in self?.healthCheck() } + .store(in: &subscriptions) + + healthCheck() + } + + func updateSections(items: [NodeWithGroup]) { + state.sections = mapper.map( + items: items, + restNodeIds: processedGroups.flatMap { + apiServices.getApiService(group: $0).preferredNodeIds + } + ) + } + + func saveFastestNodeMode(_ value: Bool) { + nodesAdditionalParamsStorage.setFastestNodeMode( + groups: processedGroups, + value: value + ) + } + + func healthCheck() { + processedGroups.forEach { + apiServices.getApiService(group: $0).healthCheck() + } + } +} diff --git a/Adamant/Modules/Login/LoginViewController.swift b/Adamant/Modules/Login/LoginViewController.swift index a2dff3ccd..e3dce54b0 100644 --- a/Adamant/Modules/Login/LoginViewController.swift +++ b/Adamant/Modules/Login/LoginViewController.swift @@ -70,6 +70,7 @@ final class LoginViewController: FormViewController { case tapToSaveHint case generateNewPassphraseButton case nodes + case coinsNodes var localized: String { switch self { @@ -98,11 +99,14 @@ final class LoginViewController: FormViewController { return "" case .nodes: - return String.adamant.nodesList.nodesListButton + return .adamant.nodesList.nodesListButton + + case .coinsNodes: + return .adamant.coinsNodesList.title } } - var tag:String { + var tag: String { switch self { case .passphrase: return "pass" case .loginButton: return "login" @@ -113,6 +117,7 @@ final class LoginViewController: FormViewController { case .generateNewPassphraseButton: return "generate" case .tapToSaveHint: return "hint" case .nodes: return "nodes" + case .coinsNodes: return "coinsNodes" } } } @@ -325,6 +330,22 @@ final class LoginViewController: FormViewController { present(nav, animated: true, completion: nil) } + // MARK: Coins nodes list settings + <<< ButtonRow { + $0.title = Rows.coinsNodes.localized + $0.tag = Rows.coinsNodes.tag + }.cellSetup { (cell, _) in + cell.selectionStyle = .gray + }.cellUpdate { (cell, _) in + cell.textLabel?.textColor = UIColor.adamant.primary + }.onCellSelection { [weak self] (_, _) in + guard let self = self else { return } + let vc = screensFactory.makeCoinsNodesList() + let nav = UINavigationController(rootViewController: vc) + nav.modalPresentationStyle = .overFullScreen + present(nav, animated: true, completion: nil) + } + // MARK: tableView position tuning if let row: PasswordRow = form.rowBy(tag: Rows.passphrase.tag) { NotificationCenter.default.addObserver(forName: UITextField.textDidBeginEditingNotification, object: row.cell.textField, queue: nil) { [weak self] _ in diff --git a/Adamant/Modules/NodesEditor/NodesListViewController.swift b/Adamant/Modules/NodesEditor/NodesListViewController.swift index 637a04865..b9f83c9d5 100644 --- a/Adamant/Modules/NodesEditor/NodesListViewController.swift +++ b/Adamant/Modules/NodesEditor/NodesListViewController.swift @@ -13,7 +13,7 @@ import Combine // MARK: - Localization extension String.adamant { - struct nodesList { + enum nodesList { static let title = String.localized("NodesList.Title", comment: "NodesList: scene title") static let nodesListButton = String.localized("NodesList.NodesList", comment: "NodesList: Button label") @@ -21,7 +21,10 @@ extension String.adamant { static let resetAlertTitle = String.localized("NodesList.ResetNodeListAlert", comment: "NodesList: Reset nodes alert title") - private init() {} + static let fastestNodeModeTip = String.localized( + "NodesList.PreferTheFastestNode.Footer", + comment: .empty + ) } } @@ -151,6 +154,7 @@ final class NodesListViewController: FormViewController { +++ Section { $0.tag = Sections.preferTheFastestNode.tag + $0.footer = HeaderFooterView(stringLiteral: .adamant.nodesList.fastestNodeModeTip) } <<< SwitchRow { [nodesAdditionalParamsStorage] in @@ -394,7 +398,7 @@ extension NodesListViewController { private func setHealthCheckTimer() { timerSubsctiption = Timer - .publish(every: nodeGroup.crucialUpdateInterval, on: .main, in: .default) + .publish(every: nodeGroup.onScreenUpdateInterval, on: .main, in: .default) .autoconnect() .sink { [apiService] _ in apiService.healthCheck() } } @@ -442,53 +446,4 @@ extension NodesListViewController { } } -private extension Node { - func statusString(_ status: Node.ConnectionStatus?) -> String? { - switch status { - case .allowed: - let ping = ping.map { Int($0 * 1000) } - return ping.map { "\(NodeCell.Strings.ping): \($0) \(NodeCell.Strings.milliseconds)" } - case .synchronizing: - return NodeCell.Strings.synchronizing - case .offline: - return NodeCell.Strings.offline - case .none: - return nil - } - } - - var versionString: String? { - version.map { "(\(NodeCell.Strings.version): \($0))" } - } -} - -private extension NodeCell { - enum Strings { - static let ping = String.localized( - "NodesList.NodeCell.Ping", - comment: "NodesList.NodeCell: Node ping" - ) - - static let milliseconds = String.localized( - "NodesList.NodeCell.Milliseconds", - comment: "NodesList.NodeCell: Milliseconds" - ) - - static let synchronizing = String.localized( - "NodesList.NodeCell.Synchronizing", - comment: "NodesList.NodeCell: Node is synchronizing" - ) - - static let offline = String.localized( - "NodesList.NodeCell.Offline", - comment: "NodesList.NodeCell: Node is offline" - ) - - static let version = String.localized( - "NodesList.NodeCell.Version", - comment: "NodesList.NodeCell: Node version" - ) - } -} - private let nodeGroup: NodeGroup = .adm diff --git a/Adamant/Modules/ScreensFactory/AdamantScreensFactory.swift b/Adamant/Modules/ScreensFactory/AdamantScreensFactory.swift index 57904b424..cb4287049 100644 --- a/Adamant/Modules/ScreensFactory/AdamantScreensFactory.swift +++ b/Adamant/Modules/ScreensFactory/AdamantScreensFactory.swift @@ -25,6 +25,7 @@ struct AdamantScreensFactory: ScreensFactory { private let accountFactory: AccountFactory private let vibrationSelectionFactory: VibrationSelectionFactory private let partnerQRFactory: PartnerQRFactory + private let coinsNodesListFactory: CoinsNodesListFactory init(assembler: Assembler) { admWalletFactory = .init(assembler: assembler) @@ -40,6 +41,7 @@ struct AdamantScreensFactory: ScreensFactory { accountFactory = .init(assembler: assembler) vibrationSelectionFactory = .init(parent: assembler) partnerQRFactory = .init(parent: assembler) + coinsNodesListFactory = .init(parent: assembler) walletFactoryCompose = AdamantWalletFactoryCompose( lskWalletFactory: .init(assembler: assembler), @@ -175,4 +177,8 @@ struct AdamantScreensFactory: ScreensFactory { func makePartnerQR(partner: CoreDataAccount) -> UIViewController { partnerQRFactory.makeViewController(partner: partner) } + + func makeCoinsNodesList() -> UIViewController { + coinsNodesListFactory.makeViewController() + } } diff --git a/Adamant/Modules/ScreensFactory/ScreensFactory.swift b/Adamant/Modules/ScreensFactory/ScreensFactory.swift index 9eda7de46..f9c6c6c39 100644 --- a/Adamant/Modules/ScreensFactory/ScreensFactory.swift +++ b/Adamant/Modules/ScreensFactory/ScreensFactory.swift @@ -43,6 +43,7 @@ protocol ScreensFactory { func makeNodesList() -> UIViewController func makeNodeEditor() -> NodeEditorViewController + func makeCoinsNodesList() -> UIViewController // MARK: Other diff --git a/Adamant/Modules/Settings/VisibleWallets/VisibleWalletsViewController.swift b/Adamant/Modules/Settings/VisibleWallets/VisibleWalletsViewController.swift index 6665e9a3e..8003142d6 100644 --- a/Adamant/Modules/Settings/VisibleWallets/VisibleWalletsViewController.swift +++ b/Adamant/Modules/Settings/VisibleWallets/VisibleWalletsViewController.swift @@ -229,7 +229,7 @@ extension VisibleWalletsViewController: UITableViewDataSource, UITableViewDelega cell.backgroundColor = UIColor.adamant.cellColor cell.title = wallet.tokenName - cell.caption = !isToken ? "Blockchain" : wallet.tokenNetworkSymbol + cell.caption = !isToken ? "Blockchain" : type(of: wallet).tokenNetworkSymbol cell.subtitle = wallet.tokenSymbol cell.logoImage = wallet.tokenLogo cell.balance = wallet.wallet?.balance diff --git a/Adamant/Modules/Wallets/Adamant/AdmWalletService.swift b/Adamant/Modules/Wallets/Adamant/AdmWalletService.swift index a36effa0c..cd59d988f 100644 --- a/Adamant/Modules/Wallets/Adamant/AdmWalletService.swift +++ b/Adamant/Modules/Wallets/Adamant/AdmWalletService.swift @@ -33,7 +33,7 @@ final class AdmWalletService: NSObject, WalletService { return AdmWalletService.fixedFee } - var tokenNetworkSymbol: String { + static var tokenNetworkSymbol: String { return Self.currencySymbol } @@ -42,7 +42,7 @@ final class AdmWalletService: NSObject, WalletService { } var tokenUnicID: String { - return tokenNetworkSymbol + tokenSymbol + Self.tokenNetworkSymbol + tokenSymbol } var richMessageType: String { diff --git a/Adamant/Modules/Wallets/Bitcoin/BtcApiService.swift b/Adamant/Modules/Wallets/Bitcoin/BtcApiService.swift index bf781fc71..7dd9e441d 100644 --- a/Adamant/Modules/Wallets/Bitcoin/BtcApiService.swift +++ b/Adamant/Modules/Wallets/Bitcoin/BtcApiService.swift @@ -49,13 +49,21 @@ final class BtcApiCore: BlockchainHealthCheckableService { } } -final class BtcApiService { +final class BtcApiService: WalletApiService { let api: BlockchainHealthCheckWrapper + var preferredNodeIds: [UUID] { + api.preferredNodeIds + } + init(api: BlockchainHealthCheckWrapper) { self.api = api } + func healthCheck() { + api.healthCheck() + } + func request( _ request: @Sendable @escaping (APICoreProtocol, Node) async -> ApiServiceResult ) async -> WalletServiceResult { diff --git a/Adamant/Modules/Wallets/Bitcoin/BtcWalletService.swift b/Adamant/Modules/Wallets/Bitcoin/BtcWalletService.swift index 51105ff4e..d6aa87656 100644 --- a/Adamant/Modules/Wallets/Bitcoin/BtcWalletService.swift +++ b/Adamant/Modules/Wallets/Bitcoin/BtcWalletService.swift @@ -71,7 +71,7 @@ final class BtcWalletService: WalletService { type(of: self).currencyLogo } - var tokenNetworkSymbol: String { + static var tokenNetworkSymbol: String { "BTC" } @@ -80,7 +80,7 @@ final class BtcWalletService: WalletService { } var tokenUnicID: String { - return tokenNetworkSymbol + tokenSymbol + Self.tokenNetworkSymbol + tokenSymbol } var richMessageType: String { diff --git a/Adamant/Modules/Wallets/Dash/DashApiService.swift b/Adamant/Modules/Wallets/Dash/DashApiService.swift index 0668e53fa..36bddeade 100644 --- a/Adamant/Modules/Wallets/Dash/DashApiService.swift +++ b/Adamant/Modules/Wallets/Dash/DashApiService.swift @@ -56,13 +56,21 @@ final class DashApiCore: BlockchainHealthCheckableService { } } -final class DashApiService { +final class DashApiService: WalletApiService { let api: BlockchainHealthCheckWrapper + var preferredNodeIds: [UUID] { + api.preferredNodeIds + } + init(api: BlockchainHealthCheckWrapper) { self.api = api } + func healthCheck() { + api.healthCheck() + } + func request( _ request: @Sendable @escaping (APICoreProtocol, Node) async -> ApiServiceResult ) async -> WalletServiceResult { diff --git a/Adamant/Modules/Wallets/Dash/DashWalletService.swift b/Adamant/Modules/Wallets/Dash/DashWalletService.swift index 44d05bf18..f1376e784 100644 --- a/Adamant/Modules/Wallets/Dash/DashWalletService.swift +++ b/Adamant/Modules/Wallets/Dash/DashWalletService.swift @@ -23,7 +23,7 @@ final class DashWalletService: WalletService { return type(of: self).currencyLogo } - var tokenNetworkSymbol: String { + static var tokenNetworkSymbol: String { return "DASH" } @@ -32,7 +32,7 @@ final class DashWalletService: WalletService { } var tokenUnicID: String { - return tokenNetworkSymbol + tokenSymbol + Self.tokenNetworkSymbol + tokenSymbol } var richMessageType: String { diff --git a/Adamant/Modules/Wallets/Doge/DogeApiService.swift b/Adamant/Modules/Wallets/Doge/DogeApiService.swift index 0b914089b..2c96b4ad1 100644 --- a/Adamant/Modules/Wallets/Doge/DogeApiService.swift +++ b/Adamant/Modules/Wallets/Doge/DogeApiService.swift @@ -48,13 +48,21 @@ final class DogeApiCore: BlockchainHealthCheckableService { } } -final class DogeApiService { +final class DogeApiService: WalletApiService { let api: BlockchainHealthCheckWrapper + var preferredNodeIds: [UUID] { + api.preferredNodeIds + } + init(api: BlockchainHealthCheckWrapper) { self.api = api } + func healthCheck() { + api.healthCheck() + } + func request( _ request: @Sendable @escaping (APICoreProtocol, Node) async -> ApiServiceResult ) async -> WalletServiceResult { diff --git a/Adamant/Modules/Wallets/Doge/DogeWalletService.swift b/Adamant/Modules/Wallets/Doge/DogeWalletService.swift index 3b8d70924..ef7c4a58e 100644 --- a/Adamant/Modules/Wallets/Doge/DogeWalletService.swift +++ b/Adamant/Modules/Wallets/Doge/DogeWalletService.swift @@ -71,7 +71,7 @@ final class DogeWalletService: WalletService { return type(of: self).currencyLogo } - var tokenNetworkSymbol: String { + static var tokenNetworkSymbol: String { return "DOGE" } @@ -80,7 +80,7 @@ final class DogeWalletService: WalletService { } var tokenUnicID: String { - return tokenNetworkSymbol + tokenSymbol + Self.tokenNetworkSymbol + tokenSymbol } var transactionFee: Decimal { diff --git a/Adamant/Modules/Wallets/ERC20/ERC20TransferViewController.swift b/Adamant/Modules/Wallets/ERC20/ERC20TransferViewController.swift index 11b6fd141..be309cf36 100644 --- a/Adamant/Modules/Wallets/ERC20/ERC20TransferViewController.swift +++ b/Adamant/Modules/Wallets/ERC20/ERC20TransferViewController.swift @@ -226,7 +226,7 @@ final class ERC20TransferViewController: TransferViewControllerBase { } override func defaultSceneTitle() -> String? { - let networkSymbol = service?.tokenNetworkSymbol ?? "ERC20" + let networkSymbol = ERC20WalletService.tokenNetworkSymbol return String.adamant.wallets.erc20.sendToken(service?.tokenSymbol ?? "ERC20") + " (\(networkSymbol))" } diff --git a/Adamant/Modules/Wallets/ERC20/ERC20WalletService.swift b/Adamant/Modules/Wallets/ERC20/ERC20WalletService.swift index f66730710..ec42b5363 100644 --- a/Adamant/Modules/Wallets/ERC20/ERC20WalletService.swift +++ b/Adamant/Modules/Wallets/ERC20/ERC20WalletService.swift @@ -39,7 +39,7 @@ final class ERC20WalletService: WalletService { return token.logo } - var tokenNetworkSymbol: String { + static var tokenNetworkSymbol: String { return "ERC20" } @@ -52,7 +52,7 @@ final class ERC20WalletService: WalletService { } var tokenUnicID: String { - return tokenNetworkSymbol + tokenSymbol + tokenContract + Self.tokenNetworkSymbol + tokenSymbol + tokenContract } var defaultVisibility: Bool { diff --git a/Adamant/Modules/Wallets/ERC20/ERC20WalletViewController.swift b/Adamant/Modules/Wallets/ERC20/ERC20WalletViewController.swift index 6906fd6a3..019ca4d4b 100644 --- a/Adamant/Modules/Wallets/ERC20/ERC20WalletViewController.swift +++ b/Adamant/Modules/Wallets/ERC20/ERC20WalletViewController.swift @@ -32,7 +32,7 @@ final class ERC20WalletViewController: WalletViewControllerBase { } override func sendRowLocalizedLabel() -> NSAttributedString { - let networkSymbol = service?.tokenNetworkSymbol ?? "ERC20" + let networkSymbol = ERC20WalletService.tokenNetworkSymbol let tokenSymbol = String.adamant.wallets.erc20.sendToken(service?.tokenSymbol ?? "") let currencyFont = UIFont.systemFont(ofSize: 17) let networkFont = currencyFont.withSize(8) diff --git a/Adamant/Modules/Wallets/Ethereum/EthApiService.swift b/Adamant/Modules/Wallets/Ethereum/EthApiService.swift index 64bf2c0c4..2bd7b2567 100644 --- a/Adamant/Modules/Wallets/Ethereum/EthApiService.swift +++ b/Adamant/Modules/Wallets/Ethereum/EthApiService.swift @@ -70,7 +70,7 @@ final class EthApiCore: BlockchainHealthCheckableService { } } -class EthApiService { +class EthApiService: WalletApiService { let api: BlockchainHealthCheckWrapper var keystoreManager: KeystoreManager? { @@ -78,10 +78,18 @@ class EthApiService { set { api.service.keystoreManager = newValue } } + var preferredNodeIds: [UUID] { + api.preferredNodeIds + } + init(api: BlockchainHealthCheckWrapper) { self.api = api } + func healthCheck() { + api.healthCheck() + } + func requestWeb3( _ request: @Sendable @escaping (Web3) async throws -> Output ) async -> WalletServiceResult { diff --git a/Adamant/Modules/Wallets/Ethereum/EthWalletService.swift b/Adamant/Modules/Wallets/Ethereum/EthWalletService.swift index 8b74b9f07..330fa344c 100644 --- a/Adamant/Modules/Wallets/Ethereum/EthWalletService.swift +++ b/Adamant/Modules/Wallets/Ethereum/EthWalletService.swift @@ -77,7 +77,7 @@ final class EthWalletService: WalletService { return type(of: self).currencyLogo } - var tokenNetworkSymbol: String { + static var tokenNetworkSymbol: String { return "ERC20" } @@ -86,7 +86,7 @@ final class EthWalletService: WalletService { } var tokenUnicID: String { - return tokenNetworkSymbol + tokenSymbol + Self.tokenNetworkSymbol + tokenSymbol } var richMessageType: String { diff --git a/Adamant/Modules/Wallets/Lisk/LskNodeApiService.swift b/Adamant/Modules/Wallets/Lisk/LskNodeApiService.swift index 64104fd49..cc0993cc4 100644 --- a/Adamant/Modules/Wallets/Lisk/LskNodeApiService.swift +++ b/Adamant/Modules/Wallets/Lisk/LskNodeApiService.swift @@ -7,14 +7,23 @@ // import LiskKit +import Foundation -final class LskNodeApiService { +final class LskNodeApiService: WalletApiService { let api: BlockchainHealthCheckWrapper + var preferredNodeIds: [UUID] { + api.preferredNodeIds + } + init(api: BlockchainHealthCheckWrapper) { self.api = api } + func healthCheck() { + api.healthCheck() + } + func requestNodeApi( body: @escaping @Sendable ( _ api: LiskKit.Node, diff --git a/Adamant/Modules/Wallets/Lisk/LskServiceApiService.swift b/Adamant/Modules/Wallets/Lisk/LskServiceApiService.swift index 255abad2b..b272d3416 100644 --- a/Adamant/Modules/Wallets/Lisk/LskServiceApiService.swift +++ b/Adamant/Modules/Wallets/Lisk/LskServiceApiService.swift @@ -30,13 +30,21 @@ final class LskServiceApiCore: LskApiCore { } } -final class LskServiceApiService { +final class LskServiceApiService: WalletApiService { let api: BlockchainHealthCheckWrapper + var preferredNodeIds: [UUID] { + api.preferredNodeIds + } + init(api: BlockchainHealthCheckWrapper) { self.api = api } + func healthCheck() { + api.healthCheck() + } + func requestServiceApi( body: @escaping @Sendable ( _ api: LiskKit.Service, diff --git a/Adamant/Modules/Wallets/Lisk/LskWalletService.swift b/Adamant/Modules/Wallets/Lisk/LskWalletService.swift index 8e23d9b4d..ac0b4a596 100644 --- a/Adamant/Modules/Wallets/Lisk/LskWalletService.swift +++ b/Adamant/Modules/Wallets/Lisk/LskWalletService.swift @@ -61,7 +61,7 @@ final class LskWalletService: WalletService { return type(of: self).currencyLogo } - var tokenNetworkSymbol: String { + static var tokenNetworkSymbol: String { return "LSK" } @@ -70,7 +70,7 @@ final class LskWalletService: WalletService { } var tokenUnicID: String { - return tokenNetworkSymbol + tokenSymbol + Self.tokenNetworkSymbol + tokenSymbol } var richMessageType: String { diff --git a/Adamant/Modules/Wallets/WalletApiService.swift b/Adamant/Modules/Wallets/WalletApiService.swift new file mode 100644 index 000000000..097854e0b --- /dev/null +++ b/Adamant/Modules/Wallets/WalletApiService.swift @@ -0,0 +1,15 @@ +// +// WalletApiService.swift +// Adamant +// +// Created by Andrew G on 20.11.2023. +// Copyright © 2023 Adamant. All rights reserved. +// + +import Foundation + +protocol WalletApiService { + var preferredNodeIds: [UUID] { get } + + func healthCheck() +} diff --git a/Adamant/Modules/Wallets/WalletService.swift b/Adamant/Modules/Wallets/WalletService.swift index 9a733a2e4..1110ddfd3 100644 --- a/Adamant/Modules/Wallets/WalletService.swift +++ b/Adamant/Modules/Wallets/WalletService.swift @@ -240,7 +240,7 @@ protocol WalletService: AnyObject { var tokenName: String { get } var tokenLogo: UIImage { get } var tokenUnicID: String { get } - var tokenNetworkSymbol: String { get } + static var tokenNetworkSymbol: String { get } var consistencyMaxTime: Double { get } var tokenContract: String { get } var minBalance: Decimal { get } diff --git a/Adamant/ServiceProtocols/ApiService.swift b/Adamant/ServiceProtocols/ApiService.swift index f08813759..343aab1b3 100644 --- a/Adamant/ServiceProtocols/ApiService.swift +++ b/Adamant/ServiceProtocols/ApiService.swift @@ -10,11 +10,8 @@ import Foundation import Alamofire import CommonKit -protocol ApiService { - var preferredNodeIds: [UUID] { get } - +protocol ApiService: WalletApiService { // MARK: - Accounts - func healthCheck() func getAccount(byPassphrase passphrase: String) async -> ApiServiceResult func getAccount(byPublicKey publicKey: String) async -> ApiServiceResult func getAccount(byAddress address: String) async -> ApiServiceResult diff --git a/Adamant/ServiceProtocols/NodesStorageProtocol.swift b/Adamant/ServiceProtocols/NodesStorageProtocol.swift index e460bf745..365c5b41b 100644 --- a/Adamant/ServiceProtocols/NodesStorageProtocol.swift +++ b/Adamant/ServiceProtocols/NodesStorageProtocol.swift @@ -17,6 +17,8 @@ extension StoreKey { } protocol NodesStorageProtocol { + var nodesWithGroupsPublisher: AnyObservable<[NodeWithGroup]> { get } + func getNodesPublisher(group: NodeGroup) -> AnyObservable<[Node]> func addNode(_ node: Node, group: NodeGroup) func resetNodes(group: NodeGroup) diff --git a/Adamant/Services/BlockchainHealthCheckWrapper.swift b/Adamant/Services/BlockchainHealthCheckWrapper.swift index 9c741e017..7c7c5bbe0 100644 --- a/Adamant/Services/BlockchainHealthCheckWrapper.swift +++ b/Adamant/Services/BlockchainHealthCheckWrapper.swift @@ -80,6 +80,7 @@ private extension BlockchainHealthCheckWrapper { switch await service.getStatusInfo(node: node) { case let .success(statusInfo): nodesStorage.updateNodeStatus(id: node.id, statusInfo: statusInfo) + nodesStorage.updateNodeParams(id: node.id, connectionStatus: .none) case let .failure(error): guard !error.isRequestCancelledError else { return } nodesStorage.updateNodeParams(id: node.id, connectionStatus: .offline) diff --git a/Adamant/Services/HealthCheckWrapper.swift b/Adamant/Services/HealthCheckWrapper.swift index 2871774f6..332783f86 100644 --- a/Adamant/Services/HealthCheckWrapper.swift +++ b/Adamant/Services/HealthCheckWrapper.swift @@ -33,7 +33,7 @@ class HealthCheckWrapper { var preferredNodeIds: [UUID] { fastestNodeMode ? [allowedNodes.first?.id].compactMap { $0 } - : allowedNodes.map { $0.id } + : [] } init( diff --git a/Adamant/Services/NodesStorage.swift b/Adamant/Services/NodesStorage.swift index c53a2be8a..c2566a59c 100644 --- a/Adamant/Services/NodesStorage.swift +++ b/Adamant/Services/NodesStorage.swift @@ -11,7 +11,11 @@ import Foundation import Combine final class NodesStorage: NodesStorageProtocol { - @Atomic private var items: ObservableValue<[NodeItem]> + @Atomic private var items: ObservableValue<[NodeWithGroup]> + + var nodesWithGroupsPublisher: AnyObservable<[NodeWithGroup]> { + items.removeDuplicates().eraseToAnyPublisher() + } private var subscription: AnyCancellable? private let securedStore: SecuredStore @@ -100,12 +104,7 @@ final class NodesStorage: NodesStorageProtocol { } private extension NodesStorage { - struct NodeItem: Codable, Equatable { - let group: NodeGroup - var node: Node - } - - static func defaultItems(group: NodeGroup) -> [NodeItem] { + static func defaultItems(group: NodeGroup) -> [NodeWithGroup] { switch group { case .btc: return BtcWalletService.nodes.map { .init(group: .btc, node: $0) } @@ -124,16 +123,16 @@ private extension NodesStorage { } } - static var defaultItems: [NodeItem] { + static var defaultItems: [NodeWithGroup] { NodeGroup.allCases.flatMap { Self.defaultItems(group: $0) } } - func saveNodes(nodes: [NodeItem]) { + func saveNodes(nodes: [NodeWithGroup]) { securedStore.set(nodes, for: StoreKey.NodesStorage.nodes) } } -private extension Array where Element == NodesStorage.NodeItem { +private extension Array where Element == NodeWithGroup { func getNode(id: UUID) -> Node? { first { $0.node.id == id }?.node } diff --git a/Adamant/SharedViews/SelfRemovableHostingController.swift b/Adamant/SharedViews/SelfRemovableHostingController.swift new file mode 100644 index 000000000..2377bdaa4 --- /dev/null +++ b/Adamant/SharedViews/SelfRemovableHostingController.swift @@ -0,0 +1,31 @@ +// +// SelfRemovableHostingController.swift +// Adamant +// +// Created by Andrew G on 20.11.2023. +// Copyright © 2023 Adamant. All rights reserved. +// + +import SwiftUI + +class SelfRemovableHostingController: UIHostingController { + override func viewDidLoad() { + super.viewDidLoad() + + guard + splitViewController == nil, + let navigationController = navigationController, + navigationController.viewControllers.count > .zero + else { return } + + navigationItem.rightBarButtonItem = UIBarButtonItem( + barButtonSystemItem: .done, + target: self, + action: #selector(close) + ) + } + + @objc private func close() { + dismiss(animated: true) + } +} diff --git a/CommonKit/Sources/CommonKit/Assets/Localization/de.lproj/Localizable.strings b/CommonKit/Sources/CommonKit/Assets/Localization/de.lproj/Localizable.strings index 3fa11009b..a596a2bd0 100644 --- a/CommonKit/Sources/CommonKit/Assets/Localization/de.lproj/Localizable.strings +++ b/CommonKit/Sources/CommonKit/Assets/Localization/de.lproj/Localizable.strings @@ -640,6 +640,9 @@ /* NodesList: 'Prefer the fastest node' switch */ "NodesList.PreferTheFastestNode" = "Prefer the fastest node"; +/* NodesList: 'Prefer the fastest node' switch */ +"NodesList.PreferTheFastestNode.Footer" = "If it's turned off, every HTTP/HTTPS request will go to a random allowed (green) node"; + /* NodesList.NodeCell: Node ping */ "NodesList.NodeCell.Ping" = "Ping"; @@ -658,6 +661,12 @@ /* NodeList: Inform that default nodes was loaded, if user deleted all nodes */ "NodeList.DefaultNodesLoaded" = "Default nodes list was loaded"; +/* CoinsNodesList: Title */ +"CoinsNodesList.Title" = "Coins node list"; + +/* CoinsNodesList: ServiceNode */ +"CoinsNodesList.ServiceNode" = "Service"; + /* NodesEditor: New node scene title */ "NodesEditor.NewNodeTitle" = "New node"; diff --git a/CommonKit/Sources/CommonKit/Assets/Localization/en.lproj/Localizable.strings b/CommonKit/Sources/CommonKit/Assets/Localization/en.lproj/Localizable.strings index 9661a4533..3df4baa48 100644 --- a/CommonKit/Sources/CommonKit/Assets/Localization/en.lproj/Localizable.strings +++ b/CommonKit/Sources/CommonKit/Assets/Localization/en.lproj/Localizable.strings @@ -631,6 +631,9 @@ /* NodesList: 'Prefer the fastest node' switch */ "NodesList.PreferTheFastestNode" = "Prefer the fastest node"; +/* NodesList: 'Prefer the fastest node' switch */ +"NodesList.PreferTheFastestNode.Footer" = "If it's turned off, every HTTP/HTTPS request will go to a random allowed (green) node"; + /* NodesList.NodeCell: Node ping */ "NodesList.NodeCell.Ping" = "Ping"; @@ -649,6 +652,12 @@ /* NodeList: Inform that default nodes was loaded, if user deleted all nodes */ "NodeList.DefaultNodesLoaded" = "Default nodes list was loaded"; +/* CoinsNodesList: Title */ +"CoinsNodesList.Title" = "Coins node list"; + +/* CoinsNodesList: ServiceNode */ +"CoinsNodesList.ServiceNode" = "Service"; + /* NodesEditor: New node scene title */ "NodesEditor.NewNodeTitle" = "New node"; diff --git a/CommonKit/Sources/CommonKit/Assets/Localization/ru.lproj/Localizable.strings b/CommonKit/Sources/CommonKit/Assets/Localization/ru.lproj/Localizable.strings index d2541c1b1..07f20f33d 100644 --- a/CommonKit/Sources/CommonKit/Assets/Localization/ru.lproj/Localizable.strings +++ b/CommonKit/Sources/CommonKit/Assets/Localization/ru.lproj/Localizable.strings @@ -614,10 +614,10 @@ "NewChatScene.NotInitialized.HelpButton" = "Что это значит?"; /* NodesList: Button label */ -"NodesList.NodesList" = "Список нод"; +"NodesList.NodesList" = "Список нод ADM"; /* NodesList: scene title */ -"NodesList.Title" = "Список нод"; +"NodesList.Title" = "Список нод ADM"; /* NodesList: 'Add new node' button lable */ "NodesList.AddNewNode" = "Добавить новую ноду"; @@ -631,6 +631,9 @@ /* NodesList: 'Prefer the fastest node' switch */ "NodesList.PreferTheFastestNode" = "Выбирать самую быструю ноду"; +/* NodesList: 'Prefer the fastest node' switch */ +"NodesList.PreferTheFastestNode.Footer" = "Если выключено, каждый HTTP/HTTPS запрос будет идти на случайную доступную (зелёную) ноду"; + /* NodesList.NodeCell: Node ping */ "NodesList.NodeCell.Ping" = "Пинг"; @@ -646,6 +649,12 @@ /* NodesList.NodeCell: Node version */ "NodesList.NodeCell.Version" = "версия"; +/* CoinsNodesList: Title */ +"CoinsNodesList.Title" = "Список нод блокчейнов"; + +/* CoinsNodesList: ServiceNode */ +"CoinsNodesList.ServiceNode" = "Сервис"; + /* NodeList: Inform that default nodes was loaded, if user deleted all nodes */ "NodeList.DefaultNodesLoaded" = "Был загружен список нод по умолчанию"; diff --git a/CommonKit/Sources/CommonKit/Helpers/Nodes+Allowance.swift b/CommonKit/Sources/CommonKit/Helpers/Nodes+Allowance.swift index 2ad2769ad..21f7b0721 100644 --- a/CommonKit/Sources/CommonKit/Helpers/Nodes+Allowance.swift +++ b/CommonKit/Sources/CommonKit/Helpers/Nodes+Allowance.swift @@ -10,7 +10,8 @@ public extension Collection where Element == Node { func getAllowedNodes(sortedBySpeedDescending: Bool, needWS: Bool) -> [Node] { var allowedNodes = filter { $0.connectionStatus == .allowed - && (!needWS || $0.wsEnabled) + && $0.isEnabled + && (!needWS || $0.wsEnabled) } if allowedNodes.isEmpty && !needWS { diff --git a/CommonKit/Sources/CommonKit/Models/NodeGroup+Constants.swift b/CommonKit/Sources/CommonKit/Models/NodeGroup+Constants.swift index 8ca65a7c4..1fedf72cd 100644 --- a/CommonKit/Sources/CommonKit/Models/NodeGroup+Constants.swift +++ b/CommonKit/Sources/CommonKit/Models/NodeGroup+Constants.swift @@ -8,6 +8,9 @@ import Foundation public extension NodeGroup { + var crucialUpdateInterval: TimeInterval { 30 } + var onScreenUpdateInterval: TimeInterval { 10 } + var nodeHeightEpsilon: Int { switch self { case .adm: @@ -44,10 +47,6 @@ public extension NodeGroup { } } - var crucialUpdateInterval: TimeInterval { - 30 - } - var defaultFastestNodeMode: Bool { switch self { case .adm: diff --git a/CommonKit/Sources/CommonKit/Models/NodeGroup.swift b/CommonKit/Sources/CommonKit/Models/NodeGroup.swift index ad6384a48..d87fb9d80 100644 --- a/CommonKit/Sources/CommonKit/Models/NodeGroup.swift +++ b/CommonKit/Sources/CommonKit/Models/NodeGroup.swift @@ -5,7 +5,7 @@ // Created by Andrew G on 30.10.2023. // -public enum NodeGroup: Equatable, Codable, CaseIterable { +public enum NodeGroup: Codable, CaseIterable, Hashable { case btc case eth case lskNode From e5a23fff7c0f948f60ecdcb3e9d2b5742bf09a3e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=80=9Cjust-software-dev=E2=80=9D?= Date: Mon, 20 Nov 2023 09:07:31 +0600 Subject: [PATCH 61/96] [trello.com/c/YD9Iu6wp] Health check on internet restore --- Adamant/Services/BlockchainHealthCheckWrapper.swift | 2 +- Adamant/Services/HealthCheckWrapper.swift | 10 ++++++++++ 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/Adamant/Services/BlockchainHealthCheckWrapper.swift b/Adamant/Services/BlockchainHealthCheckWrapper.swift index 7c7c5bbe0..935012486 100644 --- a/Adamant/Services/BlockchainHealthCheckWrapper.swift +++ b/Adamant/Services/BlockchainHealthCheckWrapper.swift @@ -80,7 +80,7 @@ private extension BlockchainHealthCheckWrapper { switch await service.getStatusInfo(node: node) { case let .success(statusInfo): nodesStorage.updateNodeStatus(id: node.id, statusInfo: statusInfo) - nodesStorage.updateNodeParams(id: node.id, connectionStatus: .none) + nodesStorage.updateNodeParams(id: node.id, connectionStatus: .some(.none)) case let .failure(error): guard !error.isRequestCancelledError else { return } nodesStorage.updateNodeParams(id: node.id, connectionStatus: .offline) diff --git a/Adamant/Services/HealthCheckWrapper.swift b/Adamant/Services/HealthCheckWrapper.swift index 332783f86..c97eac363 100644 --- a/Adamant/Services/HealthCheckWrapper.swift +++ b/Adamant/Services/HealthCheckWrapper.swift @@ -65,6 +65,16 @@ class HealthCheckWrapper { .removeDuplicates() .sink { [weak self] _ in self?.updateHealthCheckTimerSubscription() } .store(in: &subscriptions) + + NotificationCenter.default + .publisher(for: .AdamantReachabilityMonitor.reachabilityChanged, object: nil) + .compactMap { + $0.userInfo?[AdamantUserInfoKey.ReachabilityMonitor.connection] as? Bool + } + .removeDuplicates() + .filter { $0 == true } + .sink { [weak self] _ in self?.healthCheck() } + .store(in: &subscriptions) } func request( From 0a55650c634265ad29a7268e0a75e2aeb7ab1ebc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=80=9Cjust-software-dev=E2=80=9D?= Date: Mon, 20 Nov 2023 09:26:10 +0600 Subject: [PATCH 62/96] [trello.com/c/YD9Iu6wp] Added reset action --- .../View/CoinsNodesListView.swift | 21 +++++++++++++++++++ .../ViewModel/CoinsNodesListState.swift | 7 ++++++- 2 files changed, 27 insertions(+), 1 deletion(-) diff --git a/Adamant/Modules/CoinsNodesList/View/CoinsNodesListView.swift b/Adamant/Modules/CoinsNodesList/View/CoinsNodesListView.swift index 933d2a5ea..c3e19ffd2 100644 --- a/Adamant/Modules/CoinsNodesList/View/CoinsNodesListView.swift +++ b/Adamant/Modules/CoinsNodesList/View/CoinsNodesListView.swift @@ -16,10 +16,18 @@ struct CoinsNodesListView: View { List { ForEach(viewModel.state.sections, content: makeSection) makeFastestNodeModeSection() + makeResetSection() } .listStyle(.insetGrouped) .withoutListBackground() .background(Color(.adamant.secondBackgroundColor)) + .alert( + String.adamant.coinsNodesList.resetAlert, + isPresented: $viewModel.state.isAlertShown + ) { + Button(String.adamant.alert.cancel, role: .cancel) {} + Button(String.adamant.coinsNodesList.reset) { viewModel.reset() } + } .navigationTitle(String.adamant.coinsNodesList.title) } @@ -54,4 +62,17 @@ private extension CoinsNodesListView { footer: { Text(String.adamant.coinsNodesList.fastestNodeTip) } ) } + + func makeResetSection() -> some View { + Section { + Button(action: showResetAlert) { + Text(String.adamant.coinsNodesList.reset) + .expanded(axes: .horizontal) + }.listRowBackground(Color(uiColor: .adamant.cellColor)) + } + } + + func showResetAlert() { + viewModel.state.isAlertShown = true + } } diff --git a/Adamant/Modules/CoinsNodesList/ViewModel/CoinsNodesListState.swift b/Adamant/Modules/CoinsNodesList/ViewModel/CoinsNodesListState.swift index f60601052..9a868bbf3 100644 --- a/Adamant/Modules/CoinsNodesList/ViewModel/CoinsNodesListState.swift +++ b/Adamant/Modules/CoinsNodesList/ViewModel/CoinsNodesListState.swift @@ -12,8 +12,13 @@ import CommonKit struct CoinsNodesListState: Equatable { var sections: [Section] var fastestNodeMode: Bool + var isAlertShown: Bool - static let `default` = Self(sections: .init(), fastestNodeMode: false) + static let `default` = Self( + sections: .init(), + fastestNodeMode: false, + isAlertShown: false + ) } extension CoinsNodesListState { From 7761d31b086dead7126e9c5473059be9cea54959 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=80=9Cjust-software-dev=E2=80=9D?= Date: Mon, 20 Nov 2023 09:55:52 +0600 Subject: [PATCH 63/96] [trello.com/c/YD9Iu6wp] Popup fix --- .../Models/PopupCoordinatorModel.swift | 60 ++----------------- 1 file changed, 4 insertions(+), 56 deletions(-) diff --git a/PopupKit/Sources/PopupKit/Implementation/Models/PopupCoordinatorModel.swift b/PopupKit/Sources/PopupKit/Implementation/Models/PopupCoordinatorModel.swift index f561f86db..2c7420fd7 100644 --- a/PopupKit/Sources/PopupKit/Implementation/Models/PopupCoordinatorModel.swift +++ b/PopupKit/Sources/PopupKit/Implementation/Models/PopupCoordinatorModel.swift @@ -8,60 +8,8 @@ import UIKit final class PopupCoordinatorModel: ObservableObject { - @Published var top: NotificationModel? - @Published var middle: Alert? - @Published var bottom: String? - - var notification: NotificationModel? { - get { top } - set { top = newValue } - } - - var toastMessage: String? { - get { bottom } - set { bottom = newValue } - } - - var alert: AlertModel? { - get { - switch middle { - case let .common(model): - return model - case .none, .advanced: - return nil - } - } set { - switch middle { - case .none, .common: - middle = newValue.map { .common($0) } - case .advanced: - break - } - } - } - - var advancedAlert: AdvancedAlertModel? { - get { - switch middle { - case let .advanced(model): - return model - case .none, .common: - return nil - } - } set { - switch middle { - case .none, .advanced: - middle = newValue.map { .advanced($0) } - case .common: - break - } - } - } -} - -extension PopupCoordinatorModel { - enum Alert { - case common(AlertModel) - case advanced(AdvancedAlertModel) - } + @Published var notification: NotificationModel? + @Published var alert: AlertModel? + @Published var advancedAlert: AdvancedAlertModel? + @Published var toastMessage: String? } From 118d44e7dffcb64b594012f954f942b4a8755344 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=80=9Cjust-software-dev=E2=80=9D?= Date: Mon, 20 Nov 2023 10:29:04 +0600 Subject: [PATCH 64/96] [trello.com/c/YD9Iu6wp] Lsk + double health check fix --- Adamant/App/DI/AppAssembly.swift | 9 ++------- Adamant/Modules/Wallets/Lisk/LskWalletService.swift | 3 ++- 2 files changed, 4 insertions(+), 8 deletions(-) diff --git a/Adamant/App/DI/AppAssembly.swift b/Adamant/App/DI/AppAssembly.swift index e5db4e952..27bd4893a 100644 --- a/Adamant/App/DI/AppAssembly.swift +++ b/Adamant/App/DI/AppAssembly.swift @@ -171,13 +171,8 @@ struct AppAssembly: Assembly { // MARK: EthApiService container.register(EthApiService.self) { r in - EthApiService(api: .init( - service: .init(apiCore: r.resolve(APICoreProtocol.self)!), - nodesStorage: r.resolve(NodesStorageProtocol.self)!, - nodesAdditionalParamsStorage: r.resolve(NodesAdditionalParamsStorageProtocol.self)!, - nodeGroup: .eth - )) - }.inObjectScope(.container) + r.resolve(ERC20ApiService.self)! + }.inObjectScope(.transient) // MARK: ERC20ApiService container.register(ERC20ApiService.self) { r in diff --git a/Adamant/Modules/Wallets/Lisk/LskWalletService.swift b/Adamant/Modules/Wallets/Lisk/LskWalletService.swift index ac0b4a596..05ef17afa 100644 --- a/Adamant/Modules/Wallets/Lisk/LskWalletService.swift +++ b/Adamant/Modules/Wallets/Lisk/LskWalletService.swift @@ -83,8 +83,9 @@ final class LskWalletService: WalletService { // MARK: - Properties let transferAvailable: Bool = true + let netHash = Constants.Nethash.main + @Atomic private var initialBalanceCheck = false - @Atomic var netHash: String = "" @Atomic private(set) var lskWallet: LskWallet? let defaultDispatchQueue = DispatchQueue( From bf89bf379ae6216d94e3f50a1b771e559e39944f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=80=9Cjust-software-dev=E2=80=9D?= Date: Mon, 20 Nov 2023 10:34:58 +0600 Subject: [PATCH 65/96] [trello.com/c/YD9Iu6wp] Done button fix --- Adamant/SharedViews/SelfRemovableHostingController.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Adamant/SharedViews/SelfRemovableHostingController.swift b/Adamant/SharedViews/SelfRemovableHostingController.swift index 2377bdaa4..01b5e0c97 100644 --- a/Adamant/SharedViews/SelfRemovableHostingController.swift +++ b/Adamant/SharedViews/SelfRemovableHostingController.swift @@ -15,7 +15,7 @@ class SelfRemovableHostingController: UIHostingController { guard splitViewController == nil, let navigationController = navigationController, - navigationController.viewControllers.count > .zero + navigationController.viewControllers.count == 1 else { return } navigationItem.rightBarButtonItem = UIBarButtonItem( From 001e53676e8749c01c3d59e264568b7b059c3917 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=80=9Cjust-software-dev=E2=80=9D?= Date: Mon, 20 Nov 2023 11:29:48 +0600 Subject: [PATCH 66/96] [trello.com/c/YD9Iu6wp] Small fixes --- .../Account/AccountViewController.swift | 41 +++++++++++-------- Adamant/Modules/Wallets/Lisk/LskApiCore.swift | 2 +- .../Localization/de.lproj/Localizable.strings | 4 +- .../Localization/en.lproj/Localizable.strings | 4 +- 4 files changed, 28 insertions(+), 23 deletions(-) diff --git a/Adamant/Modules/Account/AccountViewController.swift b/Adamant/Modules/Account/AccountViewController.swift index ef866599c..98aa903d5 100644 --- a/Adamant/Modules/Account/AccountViewController.swift +++ b/Adamant/Modules/Account/AccountViewController.swift @@ -111,26 +111,31 @@ final class AccountViewController: FormViewController { } var image: UIImage? { + var image: UIImage? switch self { - case .security: return .asset(named: "row_security") - case .about: return .asset(named: "row_about") - case .theme: return .asset(named: "row_themes.png") - case .currency: return .asset(named: "row_currency") - case .nodes: return .asset(named: "row_nodes") - case .coinsNodes: return .init(systemName: "server.rack") - case .balance: return .asset(named: "row_balance") - case .voteForDelegates: return .asset(named: "row_vote-delegates") - case .logout: return .asset(named: "row_logout") - case .sendTokens: return nil - case .generateQr: return .asset(named: "row_QR.png") - case .generatePk: return .asset(named: "privateKey_row") - case .stayIn: return .asset(named: "row_security") - case .biometry: return nil // Determined by localAuth service - case .notifications: return .asset(named: "row_Notifications.png") - case .visibleWallets: return .asset(named: "row_balance") - case .contribute: return .asset(named: "row_contribute") - case .vibration: return .asset(named: "row_contribute") + case .security: image = .asset(named: "row_security") + case .about: image = .asset(named: "row_about") + case .theme: image = .asset(named: "row_themes.png") + case .currency: image = .asset(named: "row_currency") + case .nodes: image = .asset(named: "row_nodes") + case .coinsNodes: image = .init(systemName: "server.rack") + case .balance: image = .asset(named: "row_balance") + case .voteForDelegates: image = .asset(named: "row_vote-delegates") + case .logout: image = .asset(named: "row_logout") + case .sendTokens: image = nil + case .generateQr: image = .asset(named: "row_QR.png") + case .generatePk: image = .asset(named: "privateKey_row") + case .stayIn: image = .asset(named: "row_security") + case .biometry: image = nil // Determined by localAuth service + case .notifications: image = .asset(named: "row_Notifications.png") + case .visibleWallets: image = .asset(named: "row_balance") + case .contribute: image = .asset(named: "row_contribute") + case .vibration: image = .asset(named: "row_contribute") } + + return image? + .imageResized(to: .init(squareSize: 24)) + .withTintColor(.adamant.tableRowIcons) } } diff --git a/Adamant/Modules/Wallets/Lisk/LskApiCore.swift b/Adamant/Modules/Wallets/Lisk/LskApiCore.swift index b1a9d8c5f..b2e7e7c7a 100644 --- a/Adamant/Modules/Wallets/Lisk/LskApiCore.swift +++ b/Adamant/Modules/Wallets/Lisk/LskApiCore.swift @@ -66,6 +66,6 @@ private func mapError(_ error: APIError) -> WalletServiceError { case .noNetwork: return .networkError default: - return .remoteServiceError(message: error.message) + return .remoteServiceError(message: error.message, error: error) } } diff --git a/CommonKit/Sources/CommonKit/Assets/Localization/de.lproj/Localizable.strings b/CommonKit/Sources/CommonKit/Assets/Localization/de.lproj/Localizable.strings index a596a2bd0..9df8cfb09 100644 --- a/CommonKit/Sources/CommonKit/Assets/Localization/de.lproj/Localizable.strings +++ b/CommonKit/Sources/CommonKit/Assets/Localization/de.lproj/Localizable.strings @@ -617,10 +617,10 @@ "NewChatScene.NotInitialized.HelpButton" = "Was bedeutet das?"; /* NodesList: Button label */ -"NodesList.NodesList" = "Node-Liste"; +"NodesList.NodesList" = "ADM Node-Liste"; /* NodesList: scene title */ -"NodesList.Title" = "Liste der benutzten Nodes"; +"NodesList.Title" = "Liste der benutzten ADM Nodes"; /* NodesList: 'Saved' message */ "NodesList.Saved" = "Gespeichert"; diff --git a/CommonKit/Sources/CommonKit/Assets/Localization/en.lproj/Localizable.strings b/CommonKit/Sources/CommonKit/Assets/Localization/en.lproj/Localizable.strings index 3df4baa48..457c6ce86 100644 --- a/CommonKit/Sources/CommonKit/Assets/Localization/en.lproj/Localizable.strings +++ b/CommonKit/Sources/CommonKit/Assets/Localization/en.lproj/Localizable.strings @@ -614,10 +614,10 @@ "NewChatScene.NotInitialized.HelpButton" = "What does it mean?"; /* NodesList: Button label */ -"NodesList.NodesList" = "Node list"; +"NodesList.NodesList" = "ADM Node list"; /* NodesList: scene title */ -"NodesList.Title" = "List of nodes"; +"NodesList.Title" = "List of ADM nodes"; /* NodesList: 'Add new node' button lable */ "NodesList.AddNewNode" = "Add new node"; From 3b3bcf73c526d8a2fe35807812efb0fffcac0459 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=80=9Cjust-software-dev=E2=80=9D?= Date: Mon, 20 Nov 2023 11:58:58 +0600 Subject: [PATCH 67/96] [trello.com/c/YD9Iu6wp] Statuses observing fix --- Adamant/App/DI/AppAssembly.swift | 3 +- ...e+RichMessageProviderWithStatusCheck.swift | 7 +- ...e+RichMessageProviderWithStatusCheck.swift | 7 +- ...e+RichMessageProviderWithStatusCheck.swift | 7 +- ...e+RichMessageProviderWithStatusCheck.swift | 7 +- .../Wallets/ERC20/ERC20WalletService.swift | 145 +++++++----------- .../Wallets/Ethereum/EthApiService.swift | 2 + ...e+RichMessageProviderWithStatusCheck.swift | 4 +- ...e+RichMessageProviderWithStatusCheck.swift | 7 +- .../RichMessageProviderWithStatusCheck.swift | 14 ++ .../AdamantTransactionStatusService.swift | 20 ++- 11 files changed, 96 insertions(+), 127 deletions(-) diff --git a/Adamant/App/DI/AppAssembly.swift b/Adamant/App/DI/AppAssembly.swift index 27bd4893a..e47b09bac 100644 --- a/Adamant/App/DI/AppAssembly.swift +++ b/Adamant/App/DI/AppAssembly.swift @@ -292,7 +292,8 @@ struct AppAssembly: Assembly { return AdamantTransactionStatusService( coreDataStack: r.resolve(CoreDataStack.self)!, - richProviders: Dictionary(uniqueKeysWithValues: richProviders) + richProviders: Dictionary(uniqueKeysWithValues: richProviders), + nodesStorage: r.resolve(NodesStorageProtocol.self)! ) }.inObjectScope(.container) diff --git a/Adamant/Modules/Wallets/Bitcoin/BtcWalletService+RichMessageProviderWithStatusCheck.swift b/Adamant/Modules/Wallets/Bitcoin/BtcWalletService+RichMessageProviderWithStatusCheck.swift index 8b360d635..ffa6bc719 100644 --- a/Adamant/Modules/Wallets/Bitcoin/BtcWalletService+RichMessageProviderWithStatusCheck.swift +++ b/Adamant/Modules/Wallets/Bitcoin/BtcWalletService+RichMessageProviderWithStatusCheck.swift @@ -31,12 +31,7 @@ extension BtcWalletService: RichMessageProviderWithStatusCheck { status: getStatus(transaction: transaction, btcTransaction: btcTransaction) ) } catch { - switch error { - case ApiServiceError.networkError(_): - return .init(sentDate: nil, status: .noNetwork) - default: - return .init(sentDate: nil, status: .pending) - } + return .init(error: error) } } } diff --git a/Adamant/Modules/Wallets/Dash/DashWalletService+RichMessageProviderWithStatusCheck.swift b/Adamant/Modules/Wallets/Dash/DashWalletService+RichMessageProviderWithStatusCheck.swift index d9cdd9701..49a371d19 100644 --- a/Adamant/Modules/Wallets/Dash/DashWalletService+RichMessageProviderWithStatusCheck.swift +++ b/Adamant/Modules/Wallets/Dash/DashWalletService+RichMessageProviderWithStatusCheck.swift @@ -28,12 +28,7 @@ extension DashWalletService: RichMessageProviderWithStatusCheck { do { dashTransaction = try await getTransaction(by: hash) } catch { - switch error { - case ApiServiceError.networkError(_): - return .init(sentDate: nil, status: .noNetwork) - default: - return .init(sentDate: nil, status: .pending) - } + return .init(error: error) } return .init( diff --git a/Adamant/Modules/Wallets/Doge/DogeWalletService+RichMessageProviderWithStatusCheck.swift b/Adamant/Modules/Wallets/Doge/DogeWalletService+RichMessageProviderWithStatusCheck.swift index 44d2c81b3..bf9b84306 100644 --- a/Adamant/Modules/Wallets/Doge/DogeWalletService+RichMessageProviderWithStatusCheck.swift +++ b/Adamant/Modules/Wallets/Doge/DogeWalletService+RichMessageProviderWithStatusCheck.swift @@ -28,12 +28,7 @@ extension DogeWalletService: RichMessageProviderWithStatusCheck { do { dogeTransaction = try await getTransaction(by: hash) } catch { - switch error { - case ApiServiceError.networkError(_): - return .init(sentDate: nil, status: .noNetwork) - default: - return .init(sentDate: nil, status: .pending) - } + return .init(error: error) } return .init( diff --git a/Adamant/Modules/Wallets/ERC20/ERC20WalletService+RichMessageProviderWithStatusCheck.swift b/Adamant/Modules/Wallets/ERC20/ERC20WalletService+RichMessageProviderWithStatusCheck.swift index 0eb036261..89e6cfdb6 100644 --- a/Adamant/Modules/Wallets/ERC20/ERC20WalletService+RichMessageProviderWithStatusCheck.swift +++ b/Adamant/Modules/Wallets/ERC20/ERC20WalletService+RichMessageProviderWithStatusCheck.swift @@ -30,12 +30,7 @@ extension ERC20WalletService: RichMessageProviderWithStatusCheck { do { erc20Transaction = try await getTransaction(by: hash) } catch { - switch error { - case WalletServiceError.networkError: - return .init(sentDate: nil, status: .noNetwork) - default: - return .init(sentDate: nil, status: .pending) - } + return .init(error: error) } return .init( diff --git a/Adamant/Modules/Wallets/ERC20/ERC20WalletService.swift b/Adamant/Modules/Wallets/ERC20/ERC20WalletService.swift index ec42b5363..619876e08 100644 --- a/Adamant/Modules/Wallets/ERC20/ERC20WalletService.swift +++ b/Adamant/Modules/Wallets/ERC20/ERC20WalletService.swift @@ -410,103 +410,70 @@ extension ERC20WalletService { func getTransaction(by hash: String) async throws -> EthTransaction { let sender = wallet?.address let isOutgoing: Bool - let details: Web3Core.TransactionDetails // MARK: 1. Transaction details - do { - details = try await erc20ApiService.requestWeb3 { web3 in - try await web3.eth.transactionDetails(hash) - }.get() - } catch let error as Web3Error { - throw error.asWalletServiceError() - } catch _ as URLError { - throw WalletServiceError.networkError - } catch { - throw WalletServiceError.remoteServiceError(message: "Failed to get transaction") - } + let details: Web3Core.TransactionDetails = try await erc20ApiService.requestWeb3 { + web3 in + try await web3.eth.transactionDetails(hash) + }.get() - // MARK: 2. Transaction receipt - do { - let receipt = try await erc20ApiService.requestWeb3 { web3 in - try await web3.eth.transactionReceipt(hash) - }.get() - - // MARK: 3. Check if transaction is delivered - guard receipt.status == .ok, - let blockNumber = details.blockNumber - else { - let transaction = details.transaction.asEthTransaction( - date: nil, - gasUsed: receipt.gasUsed, - gasPrice: receipt.effectiveGasPrice, - blockNumber: nil, - confirmations: nil, - receiptStatus: receipt.status, - isOutgoing: false - ) - return transaction - } - - // MARK: 4. Block timestamp & confirmations - let currentBlock = try await erc20ApiService.requestWeb3 { web3 in - try await web3.eth.blockNumber() - }.get() - - let block = try await erc20ApiService.requestWeb3 { web3 in - try await web3.eth.block(by: receipt.blockHash) - }.get() - - guard currentBlock >= blockNumber else { - throw WalletServiceError.remoteServiceError( - message: "ERC20 confirmations calculating error" - ) - } - - let confirmations = currentBlock - blockNumber - - let transaction = details.transaction - - if let sender = sender { - isOutgoing = transaction.sender?.address == sender - } else { - isOutgoing = false - } - - let ethTransaction = transaction.asEthTransaction( - date: block.timestamp, + let receipt = try await erc20ApiService.requestWeb3 { web3 in + try await web3.eth.transactionReceipt(hash) + }.get() + + // MARK: 3. Check if transaction is delivered + guard receipt.status == .ok, + let blockNumber = details.blockNumber + else { + let transaction = details.transaction.asEthTransaction( + date: nil, gasUsed: receipt.gasUsed, gasPrice: receipt.effectiveGasPrice, - blockNumber: String(blockNumber), - confirmations: String(confirmations), + blockNumber: nil, + confirmations: nil, receiptStatus: receipt.status, - isOutgoing: isOutgoing, - for: self.token + isOutgoing: false ) - - return ethTransaction - } catch let error as Web3Error { - switch error { - // Transaction not delivered yet - case .inputError, .nodeError: - let transaction = details.transaction.asEthTransaction( - date: nil, - gasUsed: nil, - gasPrice: nil, - blockNumber: nil, - confirmations: nil, - receiptStatus: TransactionReceipt.TXStatus.notYetProcessed, - isOutgoing: false - ) - return transaction - - default: - throw error - } - } catch _ as URLError { - throw WalletServiceError.networkError - } catch { - throw error + return transaction } + + // MARK: 4. Block timestamp & confirmations + let currentBlock = try await erc20ApiService.requestWeb3 { web3 in + try await web3.eth.blockNumber() + }.get() + + let block = try await erc20ApiService.requestWeb3 { web3 in + try await web3.eth.block(by: receipt.blockHash) + }.get() + + guard currentBlock >= blockNumber else { + throw WalletServiceError.remoteServiceError( + message: "ERC20 confirmations calculating error" + ) + } + + let confirmations = currentBlock - blockNumber + + let transaction = details.transaction + + if let sender = sender { + isOutgoing = transaction.sender?.address == sender + } else { + isOutgoing = false + } + + let ethTransaction = transaction.asEthTransaction( + date: block.timestamp, + gasUsed: receipt.gasUsed, + gasPrice: receipt.effectiveGasPrice, + blockNumber: String(blockNumber), + confirmations: String(confirmations), + receiptStatus: receipt.status, + isOutgoing: isOutgoing, + for: self.token + ) + + return ethTransaction } func getBalance(address: String) async throws -> Decimal { diff --git a/Adamant/Modules/Wallets/Ethereum/EthApiService.swift b/Adamant/Modules/Wallets/Ethereum/EthApiService.swift index 2bd7b2567..1a5de623b 100644 --- a/Adamant/Modules/Wallets/Ethereum/EthApiService.swift +++ b/Adamant/Modules/Wallets/Ethereum/EthApiService.swift @@ -120,6 +120,8 @@ private func mapError(_ error: Error) -> WalletServiceError { return error.asWalletServiceError() } else if let error = error as? WalletServiceError { return error + } else if let _ = error as? URLError { + return .networkError } else { return .remoteServiceError(message: error.localizedDescription) } diff --git a/Adamant/Modules/Wallets/Ethereum/EthWalletService+RichMessageProviderWithStatusCheck.swift b/Adamant/Modules/Wallets/Ethereum/EthWalletService+RichMessageProviderWithStatusCheck.swift index d4c3b581d..f0871929b 100644 --- a/Adamant/Modules/Wallets/Ethereum/EthWalletService+RichMessageProviderWithStatusCheck.swift +++ b/Adamant/Modules/Wallets/Ethereum/EthWalletService+RichMessageProviderWithStatusCheck.swift @@ -32,10 +32,8 @@ extension EthWalletService: RichMessageProviderWithStatusCheck { guard let self = self else { throw WalletServiceError.internalError(.unknownError) } return try await getTransactionInfo(hash: hash, web3: web3) }.get() - } catch _ as URLError { - return .init(sentDate: nil, status: .noNetwork) } catch { - return .init(sentDate: nil, status: .pending) + return .init(error: error) } guard diff --git a/Adamant/Modules/Wallets/Lisk/LskWalletService+RichMessageProviderWithStatusCheck.swift b/Adamant/Modules/Wallets/Lisk/LskWalletService+RichMessageProviderWithStatusCheck.swift index ed881e446..89494e673 100644 --- a/Adamant/Modules/Wallets/Lisk/LskWalletService+RichMessageProviderWithStatusCheck.swift +++ b/Adamant/Modules/Wallets/Lisk/LskWalletService+RichMessageProviderWithStatusCheck.swift @@ -29,12 +29,7 @@ extension LskWalletService: RichMessageProviderWithStatusCheck { do { lskTransaction = try await getTransaction(by: hash) } catch { - switch error { - case ApiServiceError.networkError(_): - return .init(sentDate: nil, status: .noNetwork) - default: - return .init(sentDate: nil, status: .pending) - } + return .init(error: error) } lskTransaction.updateConfirmations(value: lastHeight) diff --git a/Adamant/ServiceProtocols/RichMessageProviderWithStatusCheck/RichMessageProviderWithStatusCheck.swift b/Adamant/ServiceProtocols/RichMessageProviderWithStatusCheck/RichMessageProviderWithStatusCheck.swift index d92993c37..a74d99812 100644 --- a/Adamant/ServiceProtocols/RichMessageProviderWithStatusCheck/RichMessageProviderWithStatusCheck.swift +++ b/Adamant/ServiceProtocols/RichMessageProviderWithStatusCheck/RichMessageProviderWithStatusCheck.swift @@ -13,6 +13,20 @@ struct TransactionStatusInfo { let status: TransactionStatus } +extension TransactionStatusInfo { + init(error: Error) { + switch error { + case ApiServiceError.networkError, + ApiServiceError.noEndpointsAvailable, + WalletServiceError.networkError, + WalletServiceError.apiError(.noEndpointsAvailable): + self.init(sentDate: nil, status: .noNetwork) + default: + self.init(sentDate: nil, status: .pending) + } + } +} + protocol RichMessageProviderWithStatusCheck: RichMessageProvider { func statusInfoFor(transaction: CoinTransaction) async -> TransactionStatusInfo } diff --git a/Adamant/Services/RichTransactionStatusService/AdamantTransactionStatusService.swift b/Adamant/Services/RichTransactionStatusService/AdamantTransactionStatusService.swift index 8cd0f1741..9cc3a93a0 100644 --- a/Adamant/Services/RichTransactionStatusService/AdamantTransactionStatusService.swift +++ b/Adamant/Services/RichTransactionStatusService/AdamantTransactionStatusService.swift @@ -13,6 +13,7 @@ import CommonKit actor AdamantTransactionStatusService: NSObject, TransactionStatusService { private let richProviders: [String: RichMessageProviderWithStatusCheck] private let coreDataStack: CoreDataStack + private let nodesStorage: NodesStorageProtocol private lazy var controller = getRichTransactionsController() private var networkSubscription: AnyCancellable? @@ -21,10 +22,12 @@ actor AdamantTransactionStatusService: NSObject, TransactionStatusService { init( coreDataStack: CoreDataStack, - richProviders: [String: RichMessageProviderWithStatusCheck] + richProviders: [String: RichMessageProviderWithStatusCheck], + nodesStorage: NodesStorageProtocol ) { self.coreDataStack = coreDataStack self.richProviders = richProviders + self.nodesStorage = nodesStorage super.init() Task { await setupNetworkSubscription() } } @@ -73,12 +76,20 @@ private extension AdamantTransactionStatusService { networkSubscription = NotificationCenter.default .publisher(for: .AdamantReachabilityMonitor.reachabilityChanged) .compactMap { $0.userInfo?[AdamantUserInfoKey.ReachabilityMonitor.connection] as? Bool } - .removeDuplicates() - .sink { connected in - guard connected else { return } + .combineLatest(makeNodesAvailabilitySubscription()) + .removeDuplicates { $0.0 == $1.0 && $0.1 == $1.1 } + .filter { $0.0 } + .sink { _ in Task { [weak self] in await self?.reloadNoNetworkTransactions() } } } + + func makeNodesAvailabilitySubscription() -> some Observable<[UUID]> { + nodesStorage + .nodesWithGroupsPublisher + .map { $0.compactMap { $0.node.isEnabled ? $0.node.id : nil } } + .removeDuplicates() + } func reloadNoNetworkTransactions() { let transactions = controller.fetchedObjects?.filter { @@ -96,6 +107,7 @@ private extension AdamantTransactionStatusService { transactions?.forEach { transaction in setStatus(for: transaction, status: .noNetwork) + Task { await forceUpdate(transaction: transaction) } add(transaction: transaction) } } From 906c8598323d5e60edd4074230dd87369de6da5e Mon Sep 17 00:00:00 2001 From: StanislavDevIOS Date: Mon, 20 Nov 2023 15:15:14 +0200 Subject: [PATCH 68/96] [trello.com/c/pUGfzAtU] Small node screen fixes --- Adamant/Helpers/Node+Strings.swift | 7 ++++++- Adamant/Models/ApiServiceError.swift | 20 +++++++++++-------- .../CoinsNodesListFactory.swift | 1 - .../View/CoinsNodesListView.swift | 4 +++- .../ViewModel/CoinsNodesListMapper.swift | 2 +- .../Wallets/Doge/DogeWalletService+Send.swift | 2 +- Adamant/Modules/Wallets/WalletService.swift | 4 ++-- .../BlockchainHealthCheckWrapper.swift | 3 ++- Adamant/Services/HealthCheckWrapper.swift | 14 ++++++++----- .../Localization/de.lproj/Localizable.strings | 9 ++++++--- .../Localization/en.lproj/Localizable.strings | 9 ++++++--- .../Localization/ru.lproj/Localizable.strings | 9 ++++++--- .../Sources/CommonKit/Models/NodeGroup.swift | 19 ++++++++++++++++++ 13 files changed, 73 insertions(+), 30 deletions(-) diff --git a/Adamant/Helpers/Node+Strings.swift b/Adamant/Helpers/Node+Strings.swift index cb6063023..42e18bc53 100644 --- a/Adamant/Helpers/Node+Strings.swift +++ b/Adamant/Helpers/Node+Strings.swift @@ -19,7 +19,7 @@ extension Node { case .offline: return NodeCell.Strings.offline case .none: - return nil + return NodeCell.Strings.disabled } } @@ -54,5 +54,10 @@ private extension NodeCell { "NodesList.NodeCell.Version", comment: "NodesList.NodeCell: Node version" ) + + static let disabled = String.localized( + "NodesList.NodeCell.Disabled", + comment: "NodesList.NodeCell: Node is disabled" + ) } } diff --git a/Adamant/Models/ApiServiceError.swift b/Adamant/Models/ApiServiceError.swift index e8d013975..8abc11831 100644 --- a/Adamant/Models/ApiServiceError.swift +++ b/Adamant/Models/ApiServiceError.swift @@ -17,7 +17,7 @@ enum ApiServiceError: LocalizedError, Error { case networkError(error: Error) case requestCancelled case commonError(message: String) - case noEndpointsAvailable + case noEndpointsAvailable(coin: String) var errorDescription: String? { switch self { @@ -43,11 +43,15 @@ enum ApiServiceError: LocalizedError, Error { case let .commonError(message): return String.adamant.sharedErrors.commonError(message) - case .noEndpointsAvailable: - return .localized( - "ApiService.InternalError.NoNodesAvailable", - comment: "Serious internal error: No nodes available" - ) + case let .noEndpointsAvailable(coin): + return + .localizedStringWithFormat( + .localized( + "ApiService.InternalError.NoNodesAvailable", + comment: "Serious internal error: No nodes available" + ), + coin + ).localized } } @@ -131,7 +135,7 @@ extension ApiServiceError: HealthCheckableError { } } - static var noEndpointsError: ApiServiceError { - .noEndpointsAvailable + static func noEndpointsError(coin: String) -> ApiServiceError { + .noEndpointsAvailable(coin: coin) } } diff --git a/Adamant/Modules/CoinsNodesList/CoinsNodesListFactory.swift b/Adamant/Modules/CoinsNodesList/CoinsNodesListFactory.swift index ba7e212f8..1c210234d 100644 --- a/Adamant/Modules/CoinsNodesList/CoinsNodesListFactory.swift +++ b/Adamant/Modules/CoinsNodesList/CoinsNodesListFactory.swift @@ -51,4 +51,3 @@ private struct CoinsNodesListAssembly: Assembly { }.inObjectScope(.weak) } } - diff --git a/Adamant/Modules/CoinsNodesList/View/CoinsNodesListView.swift b/Adamant/Modules/CoinsNodesList/View/CoinsNodesListView.swift index c3e19ffd2..0f20bb046 100644 --- a/Adamant/Modules/CoinsNodesList/View/CoinsNodesListView.swift +++ b/Adamant/Modules/CoinsNodesList/View/CoinsNodesListView.swift @@ -57,7 +57,9 @@ private extension CoinsNodesListView { Toggle( String.adamant.coinsNodesList.preferTheFastestNode, isOn: $viewModel.state.fastestNodeMode - ).listRowBackground(Color(uiColor: .adamant.cellColor)) + ) + .listRowBackground(Color(uiColor: .adamant.cellColor)) + .tint(Color(uiColor: .adamant.active)) }, footer: { Text(String.adamant.coinsNodesList.fastestNodeTip) } ) diff --git a/Adamant/Modules/CoinsNodesList/ViewModel/CoinsNodesListMapper.swift b/Adamant/Modules/CoinsNodesList/ViewModel/CoinsNodesListMapper.swift index 0ca1c2ebf..150b6fa5d 100644 --- a/Adamant/Modules/CoinsNodesList/ViewModel/CoinsNodesListMapper.swift +++ b/Adamant/Modules/CoinsNodesList/ViewModel/CoinsNodesListMapper.swift @@ -59,7 +59,7 @@ private extension CoinsNodesListMapper { "●", restNodeIds.contains(node.id) ? node.scheme.rawValue - : nil, + : nil ].compactMap { $0 }.joined(separator: " ") var connectionStatusAttrString = AttributedString(connectionStatusString) diff --git a/Adamant/Modules/Wallets/Doge/DogeWalletService+Send.swift b/Adamant/Modules/Wallets/Doge/DogeWalletService+Send.swift index 0a5db47d7..a089708b3 100644 --- a/Adamant/Modules/Wallets/Doge/DogeWalletService+Send.swift +++ b/Adamant/Modules/Wallets/Doge/DogeWalletService+Send.swift @@ -57,7 +57,7 @@ extension DogeWalletService: WalletServiceTwoStepSend { ) return transaction } catch { - throw WalletServiceError.notEnoughMoney + throw error } } diff --git a/Adamant/Modules/Wallets/WalletService.swift b/Adamant/Modules/Wallets/WalletService.swift index 1110ddfd3..45ef7f05e 100644 --- a/Adamant/Modules/Wallets/WalletService.swift +++ b/Adamant/Modules/Wallets/WalletService.swift @@ -133,8 +133,8 @@ extension WalletServiceError: HealthCheckableError { } } - static var noEndpointsError: WalletServiceError { - .apiError(.noEndpointsError) + static func noEndpointsError(coin: String) -> WalletServiceError { + .apiError(.noEndpointsError(coin: coin)) } } diff --git a/Adamant/Services/BlockchainHealthCheckWrapper.swift b/Adamant/Services/BlockchainHealthCheckWrapper.swift index 935012486..7aea91e60 100644 --- a/Adamant/Services/BlockchainHealthCheckWrapper.swift +++ b/Adamant/Services/BlockchainHealthCheckWrapper.swift @@ -35,7 +35,8 @@ final class BlockchainHealthCheckWrapper< super.init( service: service, normalUpdateInterval: nodeGroup.normalUpdateInterval, - crucialUpdateInterval: nodeGroup.crucialUpdateInterval + crucialUpdateInterval: nodeGroup.crucialUpdateInterval, + nodeGroup: nodeGroup ) nodesStorage diff --git a/Adamant/Services/HealthCheckWrapper.swift b/Adamant/Services/HealthCheckWrapper.swift index c97eac363..90523f1e7 100644 --- a/Adamant/Services/HealthCheckWrapper.swift +++ b/Adamant/Services/HealthCheckWrapper.swift @@ -14,7 +14,7 @@ protocol HealthCheckableError: Error { var isNetworkError: Bool { get } var isRequestCancelledError: Bool { get } - static var noEndpointsError: Self { get } + static func noEndpointsError(coin: String) -> Self } class HealthCheckWrapper { @@ -24,6 +24,8 @@ class HealthCheckWrapper { let normalUpdateInterval: TimeInterval let crucialUpdateInterval: TimeInterval + @Atomic private var nodeGroup: NodeGroup + @Atomic var fastestNodeMode = true @Atomic var healthCheckTimerSubscription: AnyCancellable? @Atomic var subscriptions: Set = .init() @@ -39,11 +41,13 @@ class HealthCheckWrapper { init( service: Service, normalUpdateInterval: TimeInterval, - crucialUpdateInterval: TimeInterval + crucialUpdateInterval: TimeInterval, + nodeGroup: NodeGroup ) { self.service = service self.normalUpdateInterval = normalUpdateInterval self.crucialUpdateInterval = crucialUpdateInterval + self.nodeGroup = nodeGroup $nodes .removeDuplicates { !$0.doesNeedHealthCheck($1) } @@ -81,8 +85,8 @@ class HealthCheckWrapper { _ request: @Sendable (Service, Node) async -> Result ) async -> Result { var lastConnectionError = allowedNodes.isEmpty - ? Error.noEndpointsError - : nil + ? Error.noEndpointsError(coin: nodeGroup.name) + : nil let nodesList = allowedNodes.isEmpty ? nodes.filter { $0.isEnabled }.shuffled() @@ -103,7 +107,7 @@ class HealthCheckWrapper { } if lastConnectionError != nil { healthCheck() } - return .failure(lastConnectionError ?? Error.noEndpointsError) + return .failure(lastConnectionError ?? Error.noEndpointsError(coin: nodeGroup.name)) } func healthCheck() { diff --git a/CommonKit/Sources/CommonKit/Assets/Localization/de.lproj/Localizable.strings b/CommonKit/Sources/CommonKit/Assets/Localization/de.lproj/Localizable.strings index 9df8cfb09..44bdef6cf 100644 --- a/CommonKit/Sources/CommonKit/Assets/Localization/de.lproj/Localizable.strings +++ b/CommonKit/Sources/CommonKit/Assets/Localization/de.lproj/Localizable.strings @@ -275,7 +275,7 @@ "ApiService.InternalError.ParsingFailed" = "Parsing fehlgeschlagen. Bericht senden"; /* Serious internal error: No nodes available */ -"ApiService.InternalError.NoNodesAvailable" = "No nodes available. Report a bug"; +"ApiService.InternalError.NoNodesAvailable" = "No active %@ nodes. Review the Coin node list"; /* Eureka forms Cancel button */ "Cancel" = "Abbrechen"; @@ -641,7 +641,7 @@ "NodesList.PreferTheFastestNode" = "Prefer the fastest node"; /* NodesList: 'Prefer the fastest node' switch */ -"NodesList.PreferTheFastestNode.Footer" = "If it's turned off, every HTTP/HTTPS request will go to a random allowed (green) node"; +"NodesList.PreferTheFastestNode.Footer" = "Verarbeiten Sie Anfragen schneller, aber es kann zu weniger Datenschutz führen"; /* NodesList.NodeCell: Node ping */ "NodesList.NodeCell.Ping" = "Ping"; @@ -655,6 +655,9 @@ /* NodesList.NodeCell: Node is offline */ "NodesList.NodeCell.Offline" = "Offline"; +/* NodesList.NodeCell: Node is disabled */ +"NodesList.NodeCell.Disabled" = "Deaktiviert"; + /* NodesList.NodeCell: Node version */ "NodesList.NodeCell.Version" = "version"; @@ -662,7 +665,7 @@ "NodeList.DefaultNodesLoaded" = "Default nodes list was loaded"; /* CoinsNodesList: Title */ -"CoinsNodesList.Title" = "Coins node list"; +"CoinsNodesList.Title" = "Liste der Münz- und Serviceknoten"; /* CoinsNodesList: ServiceNode */ "CoinsNodesList.ServiceNode" = "Service"; diff --git a/CommonKit/Sources/CommonKit/Assets/Localization/en.lproj/Localizable.strings b/CommonKit/Sources/CommonKit/Assets/Localization/en.lproj/Localizable.strings index 457c6ce86..1517340b4 100644 --- a/CommonKit/Sources/CommonKit/Assets/Localization/en.lproj/Localizable.strings +++ b/CommonKit/Sources/CommonKit/Assets/Localization/en.lproj/Localizable.strings @@ -272,7 +272,7 @@ "ApiService.InternalError.ParsingFailed" = "Parsing failed. Report a bug"; /* Serious internal error: No nodes available */ -"ApiService.InternalError.NoNodesAvailable" = "No nodes available. Report a bug"; +"ApiService.InternalError.NoNodesAvailable" = "No active %@ nodes. Review the Coin node list"; /* Eureka forms Cancel button */ "Cancel" = "Cancel"; @@ -632,7 +632,7 @@ "NodesList.PreferTheFastestNode" = "Prefer the fastest node"; /* NodesList: 'Prefer the fastest node' switch */ -"NodesList.PreferTheFastestNode.Footer" = "If it's turned off, every HTTP/HTTPS request will go to a random allowed (green) node"; +"NodesList.PreferTheFastestNode.Footer" = "Process requests faster, but it may cause less privacy"; /* NodesList.NodeCell: Node ping */ "NodesList.NodeCell.Ping" = "Ping"; @@ -646,6 +646,9 @@ /* NodesList.NodeCell: Node is offline */ "NodesList.NodeCell.Offline" = "Offline"; +/* NodesList.NodeCell: Node is disabled */ +"NodesList.NodeCell.Disabled" = "Disabled"; + /* NodesList.NodeCell: Node version */ "NodesList.NodeCell.Version" = "version"; @@ -653,7 +656,7 @@ "NodeList.DefaultNodesLoaded" = "Default nodes list was loaded"; /* CoinsNodesList: Title */ -"CoinsNodesList.Title" = "Coins node list"; +"CoinsNodesList.Title" = "Coin and service node list"; /* CoinsNodesList: ServiceNode */ "CoinsNodesList.ServiceNode" = "Service"; diff --git a/CommonKit/Sources/CommonKit/Assets/Localization/ru.lproj/Localizable.strings b/CommonKit/Sources/CommonKit/Assets/Localization/ru.lproj/Localizable.strings index 07f20f33d..d8b3c23f6 100644 --- a/CommonKit/Sources/CommonKit/Assets/Localization/ru.lproj/Localizable.strings +++ b/CommonKit/Sources/CommonKit/Assets/Localization/ru.lproj/Localizable.strings @@ -272,7 +272,7 @@ "ApiService.InternalError.ParsingFailed" = "Не удалось разобрать ответ узла блокчена. Сообщите разработчикам"; /* Serious internal error: No nodes available */ -"ApiService.InternalError.NoNodesAvailable" = "Нет доступных нод. Сообщите разработчикам"; +"ApiService.InternalError.NoNodesAvailable" = "Нет доступных %@ нод. Просмотрите список узлов"; /* Eureka forms Cancel button */ "Cancel" = "Отмена"; @@ -632,7 +632,7 @@ "NodesList.PreferTheFastestNode" = "Выбирать самую быструю ноду"; /* NodesList: 'Prefer the fastest node' switch */ -"NodesList.PreferTheFastestNode.Footer" = "Если выключено, каждый HTTP/HTTPS запрос будет идти на случайную доступную (зелёную) ноду"; +"NodesList.PreferTheFastestNode.Footer" = "Обрабатывайте запросы быстрее, но это может привести к снижению конфиденциальности."; /* NodesList.NodeCell: Node ping */ "NodesList.NodeCell.Ping" = "Пинг"; @@ -650,7 +650,10 @@ "NodesList.NodeCell.Version" = "версия"; /* CoinsNodesList: Title */ -"CoinsNodesList.Title" = "Список нод блокчейнов"; +"CoinsNodesList.Title" = "Список монет и сервисных нод"; + +/* NodesList.NodeCell: Node is disabled */ +"NodesList.NodeCell.Disabled" = "Отключена"; /* CoinsNodesList: ServiceNode */ "CoinsNodesList.ServiceNode" = "Сервис"; diff --git a/CommonKit/Sources/CommonKit/Models/NodeGroup.swift b/CommonKit/Sources/CommonKit/Models/NodeGroup.swift index d87fb9d80..80426c348 100644 --- a/CommonKit/Sources/CommonKit/Models/NodeGroup.swift +++ b/CommonKit/Sources/CommonKit/Models/NodeGroup.swift @@ -14,3 +14,22 @@ public enum NodeGroup: Codable, CaseIterable, Hashable { case dash case adm } + +public extension NodeGroup { + var name: String { + switch self { + case .btc: + return "BTC" + case .eth: + return "ETH" + case .lskNode, .lskService: + return "LSK" + case .doge: + return "DOGE" + case .dash: + return "DASH" + case .adm: + return "ADM" + } + } +} From 2f212ab271bc8c0292df7fb9c07aebacc38ada4f Mon Sep 17 00:00:00 2001 From: StanislavDevIOS Date: Mon, 20 Nov 2023 16:21:14 +0200 Subject: [PATCH 69/96] [trello.com/c/PNNpGdAn] Small features on chat screen --- .../Chat/View/ChatViewController.swift | 33 ++++++++++++++++++- .../View/Managers/ChatDialogManager.swift | 16 ++++++--- .../Chat/ViewModel/ChatViewModel.swift | 9 +++++ .../Chat/ViewModel/Models/ChatDialog.swift | 1 + Adamant/Modules/PartnerQR/PartnerQRView.swift | 8 +++-- .../PartnerQR/PartnerQRViewModel.swift | 5 +++ 6 files changed, 64 insertions(+), 8 deletions(-) diff --git a/Adamant/Modules/Chat/View/ChatViewController.swift b/Adamant/Modules/Chat/View/ChatViewController.swift index beecbf236..07a3226e9 100644 --- a/Adamant/Modules/Chat/View/ChatViewController.swift +++ b/Adamant/Modules/Chat/View/ChatViewController.swift @@ -267,7 +267,12 @@ private extension ChatViewController { viewModel.$isSendingAvailable .removeDuplicates() - .assign(to: \.isEnabled, on: inputBar) + .sink(receiveValue: { [weak self] value in + self?.inputBar.isEnabled = value + if !value { + self?.navigationItem.rightBarButtonItem = nil + } + }) .store(in: &subscriptions) viewModel.$fee @@ -384,6 +389,19 @@ private extension ChatViewController { target: self, action: #selector(showMenu) ) + + let tapGesture = UITapGestureRecognizer( + target: self, + action: #selector(shortTapAction) + ) + + let longPressGesture = UILongPressGestureRecognizer( + target: self, + action: #selector(longTapAction(_:)) + ) + + navigationItem.titleView?.addGestureRecognizer(tapGesture) + navigationItem.titleView?.addGestureRecognizer(longPressGesture) } func configureReplyView() { @@ -416,6 +434,19 @@ private extension ChatViewController { } } +// MARK: Tap on title view + +private extension ChatViewController { + @objc func shortTapAction() { + viewModel.openPartnerQR() + } + + @objc func longTapAction(_ gestureRecognizer: UILongPressGestureRecognizer) { + guard gestureRecognizer.state == .began else { return } + viewModel.renamePartner() + } +} + // MARK: Content updating private extension ChatViewController { diff --git a/Adamant/Modules/Chat/View/Managers/ChatDialogManager.swift b/Adamant/Modules/Chat/View/Managers/ChatDialogManager.swift index 11ad3d8cb..cc4126ef6 100644 --- a/Adamant/Modules/Chat/View/Managers/ChatDialogManager.swift +++ b/Adamant/Modules/Chat/View/Managers/ChatDialogManager.swift @@ -104,6 +104,8 @@ private extension ChatDialogManager { ) case .dismissMenu: dismissMenu() + case .renameAlert: + showRenameAlert() } } @@ -136,7 +138,7 @@ private extension ChatDialogManager { } func showSystemPartnerMenu(sender: UIBarButtonItem) { - guard let address = address, let encodedAddress = encodedAddress else { return } + guard let address = address else { return } let didSelect: ((ShareType) -> Void)? = { [weak self] type in guard case .partnerQR = type, @@ -225,6 +227,13 @@ private extension ChatDialogManager { from: nil ) } + + func showRenameAlert() { + guard let alert = makeRenameAlert() else { return } + dialogService.present(alert, animated: true) { [weak self] in + self?.dialogService.selectAllTextFields(in: alert) + } + } } // MARK: Alert actions @@ -261,10 +270,7 @@ private extension ChatDialogManager { title: .adamant.chat.rename, style: .default ) { [weak self] _ in - guard let alert = self?.makeRenameAlert() else { return } - self?.dialogService.present(alert, animated: true) { - self?.dialogService.selectAllTextFields(in: alert) - } + self?.showRenameAlert() } } diff --git a/Adamant/Modules/Chat/ViewModel/ChatViewModel.swift b/Adamant/Modules/Chat/ViewModel/ChatViewModel.swift index 966258bf0..6cfddcd86 100644 --- a/Adamant/Modules/Chat/ViewModel/ChatViewModel.swift +++ b/Adamant/Modules/Chat/ViewModel/ChatViewModel.swift @@ -618,6 +618,15 @@ extension ChatViewModel { tempOffsets.append(id) } + + func openPartnerQR() { + guard let partner = chatroom?.partner else { return } + didTapPartnerQR.send(partner) + } + + func renamePartner() { + dialog.send(.renameAlert) + } } extension ChatViewModel: NSFetchedResultsControllerDelegate { diff --git a/Adamant/Modules/Chat/ViewModel/Models/ChatDialog.swift b/Adamant/Modules/Chat/ViewModel/Models/ChatDialog.swift index e2954f08d..f65484731 100644 --- a/Adamant/Modules/Chat/ViewModel/Models/ChatDialog.swift +++ b/Adamant/Modules/Chat/ViewModel/Models/ChatDialog.swift @@ -34,4 +34,5 @@ enum ChatDialog { didDismissMenuAction: ChatDialogManager.ContextMenuAction ) case dismissMenu + case renameAlert } diff --git a/Adamant/Modules/PartnerQR/PartnerQRView.swift b/Adamant/Modules/PartnerQR/PartnerQRView.swift index f4d268d9f..706f18929 100644 --- a/Adamant/Modules/PartnerQR/PartnerQRView.swift +++ b/Adamant/Modules/PartnerQR/PartnerQRView.swift @@ -53,8 +53,12 @@ private extension PartnerQRView { HStack { Spacer() - Text(viewModel.title) - .padding() + Button(action: { + viewModel.copyToPasteboard() + }, label: { + Text(viewModel.title) + .padding() + }) Spacer() } } diff --git a/Adamant/Modules/PartnerQR/PartnerQRViewModel.swift b/Adamant/Modules/PartnerQR/PartnerQRViewModel.swift index 1da8120ec..326e1ad81 100644 --- a/Adamant/Modules/PartnerQR/PartnerQRViewModel.swift +++ b/Adamant/Modules/PartnerQR/PartnerQRViewModel.swift @@ -107,6 +107,11 @@ final class PartnerQRViewModel: NSObject, ObservableObject { partnerQRService.setIncludeNameEnabled(includeContactsName) generateQR() } + + func copyToPasteboard() { + UIPasteboard.general.string = title + dialogService.showToastMessage(.adamant.alert.copiedToPasteboardNotification) + } } private extension PartnerQRViewModel { From 268d6a5bd985297bb52b8ef7381a696a849d323f Mon Sep 17 00:00:00 2001 From: StanislavDevIOS Date: Mon, 20 Nov 2023 16:26:31 +0200 Subject: [PATCH 70/96] [trello.com/c/PNNpGdAn] fix: new features only for non read only chats --- Adamant/Modules/Chat/ViewModel/ChatViewModel.swift | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/Adamant/Modules/Chat/ViewModel/ChatViewModel.swift b/Adamant/Modules/Chat/ViewModel/ChatViewModel.swift index 6cfddcd86..b2c122e2e 100644 --- a/Adamant/Modules/Chat/ViewModel/ChatViewModel.swift +++ b/Adamant/Modules/Chat/ViewModel/ChatViewModel.swift @@ -620,11 +620,16 @@ extension ChatViewModel { } func openPartnerQR() { - guard let partner = chatroom?.partner else { return } + guard let partner = chatroom?.partner, + isSendingAvailable + else { return } + didTapPartnerQR.send(partner) } func renamePartner() { + guard isSendingAvailable else { return } + dialog.send(.renameAlert) } } From 8a6507e70f618c2cca150022c5f0f79c04ba8ee5 Mon Sep 17 00:00:00 2001 From: StanislavDevIOS Date: Mon, 20 Nov 2023 16:27:51 +0200 Subject: [PATCH 71/96] [trello.com/c/vxmIWHn2] fix: dont show under line for last row in context menu --- .../Views/Menu/Components/AMenuViewController.swift | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/pckg/AdvancedContextMenuKit/Sources/AdvancedContextMenuKit/Implementation/Views/Menu/Components/AMenuViewController.swift b/pckg/AdvancedContextMenuKit/Sources/AdvancedContextMenuKit/Implementation/Views/Menu/Components/AMenuViewController.swift index 33104233b..95b8b354c 100644 --- a/pckg/AdvancedContextMenuKit/Sources/AdvancedContextMenuKit/Implementation/Views/Menu/Components/AMenuViewController.swift +++ b/pckg/AdvancedContextMenuKit/Sources/AdvancedContextMenuKit/Implementation/Views/Menu/Components/AMenuViewController.swift @@ -234,10 +234,10 @@ extension AMenuViewController: UITableViewDelegate, UITableViewDataSource { let rowPosition: AMenuRowCell.RowPosition - if indexPath.row == 0 { - rowPosition = .top - } else if indexPath.row == menuContent.menuItems.count - 1 { + if indexPath.row == menuContent.menuItems.count - 1 { rowPosition = .bottom + } else if indexPath.row == .zero { + rowPosition = .top } else { rowPosition = .other } From 164f2c1b75f7cdb9ec49b24ec56be86cf7cc5e23 Mon Sep 17 00:00:00 2001 From: StanislavDevIOS Date: Tue, 21 Nov 2023 17:09:23 +0200 Subject: [PATCH 72/96] [trello.com/c/PNNpGdAn] fix: configure header right button if needed --- .../Chat/View/ChatViewController.swift | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/Adamant/Modules/Chat/View/ChatViewController.swift b/Adamant/Modules/Chat/View/ChatViewController.swift index 07a3226e9..56ad90502 100644 --- a/Adamant/Modules/Chat/View/ChatViewController.swift +++ b/Adamant/Modules/Chat/View/ChatViewController.swift @@ -271,6 +271,8 @@ private extension ChatViewController { self?.inputBar.isEnabled = value if !value { self?.navigationItem.rightBarButtonItem = nil + } else { + self?.configureHeaderRightButton() } }) .store(in: &subscriptions) @@ -383,12 +385,8 @@ private extension ChatViewController { func configureHeader() { navigationItem.titleView = updatingIndicatorView navigationItem.largeTitleDisplayMode = .never - navigationItem.rightBarButtonItem = .init( - title: "•••", - style: .plain, - target: self, - action: #selector(showMenu) - ) + + configureHeaderRightButton() let tapGesture = UITapGestureRecognizer( target: self, @@ -404,6 +402,15 @@ private extension ChatViewController { navigationItem.titleView?.addGestureRecognizer(longPressGesture) } + func configureHeaderRightButton() { + navigationItem.rightBarButtonItem = .init( + title: "•••", + style: .plain, + target: self, + action: #selector(showMenu) + ) + } + func configureReplyView() { replyView.snp.makeConstraints { make in make.height.equalTo(40) From eeacf8112bfddbdb93111483eaee859c60776f45 Mon Sep 17 00:00:00 2001 From: StanislavDevIOS Date: Tue, 21 Nov 2023 17:29:45 +0200 Subject: [PATCH 73/96] [trello.com/c/pUGfzAtU] Code refactoring --- Adamant/Helpers/Node+Strings.swift | 8 +++++-- Adamant/Models/NodeWithGroup.swift | 22 +++++++++++++++++ .../ViewModel/CoinsNodesListMapper.swift | 24 +------------------ .../NodesEditor/NodesListViewController.swift | 2 +- .../BlockchainHealthCheckWrapper.swift | 2 -- Adamant/Services/HealthCheckWrapper.swift | 2 +- .../Sources/CommonKit/Models/NodeGroup.swift | 19 --------------- 7 files changed, 31 insertions(+), 48 deletions(-) diff --git a/Adamant/Helpers/Node+Strings.swift b/Adamant/Helpers/Node+Strings.swift index 42e18bc53..2bc9f5b55 100644 --- a/Adamant/Helpers/Node+Strings.swift +++ b/Adamant/Helpers/Node+Strings.swift @@ -9,7 +9,11 @@ import CommonKit extension Node { - func statusString(_ status: Node.ConnectionStatus?) -> String? { + func statusString(_ status: Node.ConnectionStatus?, isEnabled: Bool) -> String? { + guard isEnabled else { + return NodeCell.Strings.disabled + } + switch status { case .allowed: let ping = ping.map { Int($0 * 1000) } @@ -19,7 +23,7 @@ extension Node { case .offline: return NodeCell.Strings.offline case .none: - return NodeCell.Strings.disabled + return nil } } diff --git a/Adamant/Models/NodeWithGroup.swift b/Adamant/Models/NodeWithGroup.swift index 60813c733..a5f57100a 100644 --- a/Adamant/Models/NodeWithGroup.swift +++ b/Adamant/Models/NodeWithGroup.swift @@ -12,3 +12,25 @@ struct NodeWithGroup: Codable, Equatable { let group: NodeGroup var node: Node } + +extension NodeGroup { + var name: String { + switch self { + case .btc: + return BtcWalletService.tokenNetworkSymbol + case .eth: + return EthWalletService.tokenNetworkSymbol + case .lskNode: + return LskWalletService.tokenNetworkSymbol + case .lskService: + return LskWalletService.tokenNetworkSymbol + + " " + .adamant.coinsNodesList.serviceNode + case .doge: + return DogeWalletService.tokenNetworkSymbol + case .dash: + return DashWalletService.tokenNetworkSymbol + case .adm: + return AdmWalletService.tokenNetworkSymbol + } + } +} diff --git a/Adamant/Modules/CoinsNodesList/ViewModel/CoinsNodesListMapper.swift b/Adamant/Modules/CoinsNodesList/ViewModel/CoinsNodesListMapper.swift index 150b6fa5d..da9821cc7 100644 --- a/Adamant/Modules/CoinsNodesList/ViewModel/CoinsNodesListMapper.swift +++ b/Adamant/Modules/CoinsNodesList/ViewModel/CoinsNodesListMapper.swift @@ -72,33 +72,11 @@ private extension CoinsNodesListMapper { isEnabled: node.isEnabled, title: node.asString(), connectionStatus: connectionStatusAttrString, - description: node.statusString(connectionStatus) ?? .empty + description: node.statusString(connectionStatus, isEnabled: node.isEnabled) ?? .empty ) } } -private extension NodeGroup { - var name: String { - switch self { - case .btc: - return BtcWalletService.tokenNetworkSymbol - case .eth: - return EthWalletService.tokenNetworkSymbol - case .lskNode: - return LskWalletService.tokenNetworkSymbol - case .lskService: - return LskWalletService.tokenNetworkSymbol - + " " + .adamant.coinsNodesList.serviceNode - case .doge: - return DogeWalletService.tokenNetworkSymbol - case .dash: - return DashWalletService.tokenNetworkSymbol - case .adm: - return AdmWalletService.tokenNetworkSymbol - } - } -} - private func getIndicatorColor(status: Node.ConnectionStatus?) -> UIColor { switch status { case .allowed: diff --git a/Adamant/Modules/NodesEditor/NodesListViewController.swift b/Adamant/Modules/NodesEditor/NodesListViewController.swift index b9f83c9d5..f1dfa25e4 100644 --- a/Adamant/Modules/NodesEditor/NodesListViewController.swift +++ b/Adamant/Modules/NodesEditor/NodesListViewController.swift @@ -412,7 +412,7 @@ extension NodesListViewController { id: node.id, title: node.asString(), connectionStatus: connectionStatus, - statusString: node.statusString(connectionStatus), + statusString: node.statusString(connectionStatus, isEnabled: node.isEnabled), versionString: node.versionString, isEnabled: node.isEnabled, activities: .init([ diff --git a/Adamant/Services/BlockchainHealthCheckWrapper.swift b/Adamant/Services/BlockchainHealthCheckWrapper.swift index 7aea91e60..5f7a49294 100644 --- a/Adamant/Services/BlockchainHealthCheckWrapper.swift +++ b/Adamant/Services/BlockchainHealthCheckWrapper.swift @@ -19,7 +19,6 @@ final class BlockchainHealthCheckWrapper< Service: BlockchainHealthCheckableService >: HealthCheckWrapper { private let nodesStorage: NodesStorageProtocol - private let nodeGroup: NodeGroup @Atomic private var currentRequests = Set() @@ -30,7 +29,6 @@ final class BlockchainHealthCheckWrapper< nodeGroup: NodeGroup ) { self.nodesStorage = nodesStorage - self.nodeGroup = nodeGroup super.init( service: service, diff --git a/Adamant/Services/HealthCheckWrapper.swift b/Adamant/Services/HealthCheckWrapper.swift index 90523f1e7..4cf288717 100644 --- a/Adamant/Services/HealthCheckWrapper.swift +++ b/Adamant/Services/HealthCheckWrapper.swift @@ -24,7 +24,7 @@ class HealthCheckWrapper { let normalUpdateInterval: TimeInterval let crucialUpdateInterval: TimeInterval - @Atomic private var nodeGroup: NodeGroup + @Atomic var nodeGroup: NodeGroup @Atomic var fastestNodeMode = true @Atomic var healthCheckTimerSubscription: AnyCancellable? diff --git a/CommonKit/Sources/CommonKit/Models/NodeGroup.swift b/CommonKit/Sources/CommonKit/Models/NodeGroup.swift index 80426c348..d87fb9d80 100644 --- a/CommonKit/Sources/CommonKit/Models/NodeGroup.swift +++ b/CommonKit/Sources/CommonKit/Models/NodeGroup.swift @@ -14,22 +14,3 @@ public enum NodeGroup: Codable, CaseIterable, Hashable { case dash case adm } - -public extension NodeGroup { - var name: String { - switch self { - case .btc: - return "BTC" - case .eth: - return "ETH" - case .lskNode, .lskService: - return "LSK" - case .doge: - return "DOGE" - case .dash: - return "DASH" - case .adm: - return "ADM" - } - } -} From 535f8130a86043336e4605659046e1afd90f7904 Mon Sep 17 00:00:00 2001 From: StanislavDevIOS Date: Tue, 21 Nov 2023 18:01:34 +0200 Subject: [PATCH 74/96] [trello.com/c/9yRYiolD] Context menu: scroll down immediately in iOS 17 --- .../Implementation/ContextMenuOverlayView.swift | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/pckg/AdvancedContextMenuKit/Sources/AdvancedContextMenuKit/Implementation/ContextMenuOverlayView.swift b/pckg/AdvancedContextMenuKit/Sources/AdvancedContextMenuKit/Implementation/ContextMenuOverlayView.swift index b89bfa9ac..9b8624de0 100644 --- a/pckg/AdvancedContextMenuKit/Sources/AdvancedContextMenuKit/Implementation/ContextMenuOverlayView.swift +++ b/pckg/AdvancedContextMenuKit/Sources/AdvancedContextMenuKit/Implementation/ContextMenuOverlayView.swift @@ -92,6 +92,11 @@ private extension ContextMenuOverlayView { } func makeOverlayScrollToBottom(_ content: some View) -> some View { + if #available(iOS 17.0, *) { + return content + .defaultScrollAnchor(.bottom) + } + return ScrollViewReader { value in content .onChange(of: viewModel.scrollToEnd) { scrollToBottom in From 4b85d650b9eca8c05db1866659aec81b486ac962 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=80=9Cjust-software-dev=E2=80=9D?= Date: Tue, 21 Nov 2023 23:41:58 +0600 Subject: [PATCH 75/96] [trello.com/c/OKi8zrOy] Fixed crash --- .../NodesEditor/NodesListViewController.swift | 38 +++++++++---------- 1 file changed, 19 insertions(+), 19 deletions(-) diff --git a/Adamant/Modules/NodesEditor/NodesListViewController.swift b/Adamant/Modules/NodesEditor/NodesListViewController.swift index f1dfa25e4..6a84524ed 100644 --- a/Adamant/Modules/NodesEditor/NodesListViewController.swift +++ b/Adamant/Modules/NodesEditor/NodesListViewController.swift @@ -108,31 +108,12 @@ final class NodesListViewController: FormViewController { self.apiService = apiService self.socketService = socketService super.init(nibName: nil, bundle: nil) - setup() } required init?(coder aDecoder: NSCoder) { fatalError("Isn't implemented") } - private func setup() { - nodesStorage.getNodesPublisher(group: nodeGroup) - .combineLatest(nodesAdditionalParamsStorage.fastestNodeMode(group: nodeGroup)) - .receive(on: DispatchQueue.main) - .sink { [weak self] in self?.setNewNodesList($0.0) } - .store(in: &subscriptions) - - NotificationCenter.default - .publisher(for: .SocketService.currentNodeUpdate, object: nil) - .receive(on: DispatchQueue.main) - .map { [weak self] _ in self?.socketService.currentNode?.id } - .removeDuplicates() - .assign(to: _currentSocketsNodeId) - .store(in: &subscriptions) - - currentSocketsNodeId = socketService.currentNode?.id - } - override func viewDidLoad() { super.viewDidLoad() navigationItem.title = String.adamant.nodesList.title @@ -194,6 +175,7 @@ final class NodesListViewController: FormViewController { } setColors() + setupObservers() } override func viewWillAppear(_ animated: Bool) { @@ -209,6 +191,24 @@ final class NodesListViewController: FormViewController { tableView.backgroundColor = .clear } + private func setupObservers() { + nodesStorage.getNodesPublisher(group: nodeGroup) + .combineLatest(nodesAdditionalParamsStorage.fastestNodeMode(group: nodeGroup)) + .receive(on: DispatchQueue.main) + .sink { [weak self] in self?.setNewNodesList($0.0) } + .store(in: &subscriptions) + + NotificationCenter.default + .publisher(for: .SocketService.currentNodeUpdate, object: nil) + .receive(on: DispatchQueue.main) + .map { [weak self] _ in self?.socketService.currentNode?.id } + .removeDuplicates() + .assign(to: _currentSocketsNodeId) + .store(in: &subscriptions) + + currentSocketsNodeId = socketService.currentNode?.id + } + private func setNewNodesList(_ newNodes: [Node]) { nodesList = newNodes currentRestNodesIds = apiService.preferredNodeIds From 51d00dc11275aa6a82455141ad984b5bdd95c596 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=80=9Cjust-software-dev=E2=80=9D?= Date: Wed, 22 Nov 2023 05:17:34 +0600 Subject: [PATCH 76/96] [small-lag/trello.com/c/pUGfzAtU] Some fixes --- Adamant.xcodeproj/project.pbxproj | 16 +-- Adamant/Helpers/Node+Strings.swift | 67 ---------- Adamant/Helpers/Node+UI.swift | 117 ++++++++++++++++++ .../SelfRemovableHostingController.swift | 9 +- .../Account/AccountViewController.swift | 2 +- .../CoinsNodesListFactory.swift | 21 +++- .../ViewModel/CoinsNodesListMapper.swift | 39 ++---- .../Modules/Login/LoginViewController.swift | 2 +- .../NodesEditor/NodeCell/NodeCell+Model.swift | 21 +--- .../NodesEditor/NodeCell/NodeCell.swift | 43 +------ .../NodesEditor/NodesListViewController.swift | 19 ++- .../AdamantScreensFactory.swift | 4 +- .../ScreensFactory/ScreensFactory.swift | 2 +- .../Wallets/Lisk/LskWalletService.swift | 2 +- .../BlockchainHealthCheckWrapper.swift | 41 +++--- Adamant/Services/HealthCheckWrapper.swift | 11 -- .../Localization/en.lproj/Localizable.strings | 2 +- 17 files changed, 199 insertions(+), 219 deletions(-) delete mode 100644 Adamant/Helpers/Node+Strings.swift create mode 100644 Adamant/Helpers/Node+UI.swift rename Adamant/{SharedViews => Helpers}/SelfRemovableHostingController.swift (68%) diff --git a/Adamant.xcodeproj/project.pbxproj b/Adamant.xcodeproj/project.pbxproj index 1710bd233..2b7a02368 100644 --- a/Adamant.xcodeproj/project.pbxproj +++ b/Adamant.xcodeproj/project.pbxproj @@ -233,7 +233,7 @@ 9366588F2B0AB97500BDB2D3 /* CoinsNodesListMapper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9366588E2B0AB97500BDB2D3 /* CoinsNodesListMapper.swift */; }; 936658912B0AB9DC00BDB2D3 /* NodeWithGroup.swift in Sources */ = {isa = PBXBuildFile; fileRef = 936658902B0AB9DC00BDB2D3 /* NodeWithGroup.swift */; }; 936658932B0AC03700BDB2D3 /* CoinsNodesListStrings.swift in Sources */ = {isa = PBXBuildFile; fileRef = 936658922B0AC03700BDB2D3 /* CoinsNodesListStrings.swift */; }; - 936658952B0AC15300BDB2D3 /* Node+Strings.swift in Sources */ = {isa = PBXBuildFile; fileRef = 936658942B0AC15300BDB2D3 /* Node+Strings.swift */; }; + 936658952B0AC15300BDB2D3 /* Node+UI.swift in Sources */ = {isa = PBXBuildFile; fileRef = 936658942B0AC15300BDB2D3 /* Node+UI.swift */; }; 936658972B0ACB1500BDB2D3 /* CoinsNodesListViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 936658962B0ACB1500BDB2D3 /* CoinsNodesListViewModel.swift */; }; 936658992B0AD32600BDB2D3 /* CoinsNodesListViewModel+ApiServices.swift in Sources */ = {isa = PBXBuildFile; fileRef = 936658982B0AD32600BDB2D3 /* CoinsNodesListViewModel+ApiServices.swift */; }; 9366589B2B0AD3E600BDB2D3 /* WalletApiService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9366589A2B0AD3E600BDB2D3 /* WalletApiService.swift */; }; @@ -268,6 +268,7 @@ 9390C5052976B53000270CDF /* ChatDialog.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9390C5042976B53000270CDF /* ChatDialog.swift */; }; 93996A972968209C008D080B /* ChatMessagesCollection.swift in Sources */ = {isa = PBXBuildFile; fileRef = 93996A962968209C008D080B /* ChatMessagesCollection.swift */; }; 9399F5ED29A85A48006C3E30 /* ChatCacheService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9399F5EC29A85A48006C3E30 /* ChatCacheService.swift */; }; + 939FA3422B0D6F0000710EC6 /* SelfRemovableHostingController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 939FA3412B0D6F0000710EC6 /* SelfRemovableHostingController.swift */; }; 93A118512993167500E144CC /* ChatMessageBackgroundColor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 93A118502993167500E144CC /* ChatMessageBackgroundColor.swift */; }; 93A118532993241D00E144CC /* ChatMessagesListFactory.swift in Sources */ = {isa = PBXBuildFile; fileRef = 93A118522993241D00E144CC /* ChatMessagesListFactory.swift */; }; 93A18C862AAEACC100D0AB98 /* AdamantWalletFactoryCompose.swift in Sources */ = {isa = PBXBuildFile; fileRef = 93A18C852AAEACC100D0AB98 /* AdamantWalletFactoryCompose.swift */; }; @@ -285,7 +286,6 @@ 93B28EC52B076E2C007F268B /* DashBlockchainInfoDTO.swift in Sources */ = {isa = PBXBuildFile; fileRef = 93B28EC42B076E2C007F268B /* DashBlockchainInfoDTO.swift */; }; 93B28EC82B076E68007F268B /* DashResponseDTO.swift in Sources */ = {isa = PBXBuildFile; fileRef = 93B28EC72B076E68007F268B /* DashResponseDTO.swift */; }; 93B28ECA2B076E88007F268B /* DashErrorDTO.swift in Sources */ = {isa = PBXBuildFile; fileRef = 93B28EC92B076E88007F268B /* DashErrorDTO.swift */; }; - 93BB4E6C2B0AF9EE00A537F2 /* SelfRemovableHostingController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 93BB4E6B2B0AF9EE00A537F2 /* SelfRemovableHostingController.swift */; }; 93BF4A6629E4859900505CD0 /* DelegatesBottomPanel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 93BF4A6529E4859900505CD0 /* DelegatesBottomPanel.swift */; }; 93BF4A6C29E4B4BF00505CD0 /* DelegatesBottomPanel+Model.swift in Sources */ = {isa = PBXBuildFile; fileRef = 93BF4A6B29E4B4BF00505CD0 /* DelegatesBottomPanel+Model.swift */; }; 93C794442B07725C00408826 /* DashGetAddressBalanceDTO.swift in Sources */ = {isa = PBXBuildFile; fileRef = 93C794432B07725C00408826 /* DashGetAddressBalanceDTO.swift */; }; @@ -861,7 +861,7 @@ 9366588E2B0AB97500BDB2D3 /* CoinsNodesListMapper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CoinsNodesListMapper.swift; sourceTree = ""; }; 936658902B0AB9DC00BDB2D3 /* NodeWithGroup.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NodeWithGroup.swift; sourceTree = ""; }; 936658922B0AC03700BDB2D3 /* CoinsNodesListStrings.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CoinsNodesListStrings.swift; sourceTree = ""; }; - 936658942B0AC15300BDB2D3 /* Node+Strings.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Node+Strings.swift"; sourceTree = ""; }; + 936658942B0AC15300BDB2D3 /* Node+UI.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Node+UI.swift"; sourceTree = ""; }; 936658962B0ACB1500BDB2D3 /* CoinsNodesListViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CoinsNodesListViewModel.swift; sourceTree = ""; }; 936658982B0AD32600BDB2D3 /* CoinsNodesListViewModel+ApiServices.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "CoinsNodesListViewModel+ApiServices.swift"; sourceTree = ""; }; 9366589A2B0AD3E600BDB2D3 /* WalletApiService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WalletApiService.swift; sourceTree = ""; }; @@ -892,6 +892,7 @@ 9390C5042976B53000270CDF /* ChatDialog.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChatDialog.swift; sourceTree = ""; }; 93996A962968209C008D080B /* ChatMessagesCollection.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChatMessagesCollection.swift; sourceTree = ""; }; 9399F5EC29A85A48006C3E30 /* ChatCacheService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChatCacheService.swift; sourceTree = ""; }; + 939FA3412B0D6F0000710EC6 /* SelfRemovableHostingController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SelfRemovableHostingController.swift; sourceTree = ""; }; 93A118502993167500E144CC /* ChatMessageBackgroundColor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChatMessageBackgroundColor.swift; sourceTree = ""; }; 93A118522993241D00E144CC /* ChatMessagesListFactory.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChatMessagesListFactory.swift; sourceTree = ""; }; 93A18C852AAEACC100D0AB98 /* AdamantWalletFactoryCompose.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AdamantWalletFactoryCompose.swift; sourceTree = ""; }; @@ -909,7 +910,6 @@ 93B28EC42B076E2C007F268B /* DashBlockchainInfoDTO.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DashBlockchainInfoDTO.swift; sourceTree = ""; }; 93B28EC72B076E68007F268B /* DashResponseDTO.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DashResponseDTO.swift; sourceTree = ""; }; 93B28EC92B076E88007F268B /* DashErrorDTO.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DashErrorDTO.swift; sourceTree = ""; }; - 93BB4E6B2B0AF9EE00A537F2 /* SelfRemovableHostingController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SelfRemovableHostingController.swift; sourceTree = ""; }; 93BF4A6529E4859900505CD0 /* DelegatesBottomPanel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DelegatesBottomPanel.swift; sourceTree = ""; }; 93BF4A6B29E4B4BF00505CD0 /* DelegatesBottomPanel+Model.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "DelegatesBottomPanel+Model.swift"; sourceTree = ""; }; 93C794432B07725C00408826 /* DashGetAddressBalanceDTO.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DashGetAddressBalanceDTO.swift; sourceTree = ""; }; @@ -2031,7 +2031,8 @@ 93294B832AAD0C8F00911109 /* Assembler+Extension.swift */, 93E8EDD02AF1DF8E003E163C /* ServerResponse+Resolver.swift */, 93CCAE7F2B06E2D100EA5B94 /* ApiServiceError+Extension.swift */, - 936658942B0AC15300BDB2D3 /* Node+Strings.swift */, + 939FA3412B0D6F0000710EC6 /* SelfRemovableHostingController.swift */, + 936658942B0AC15300BDB2D3 /* Node+UI.swift */, ); path = Helpers; sourceTree = ""; @@ -2457,7 +2458,6 @@ 4133AED329769EEC00F3D017 /* UpdatingIndicatorView.swift */, 41A1994529D2FCF80031AD75 /* ReplyView.swift */, 41A1994729D325800031AD75 /* SwipeableView.swift */, - 93BB4E6B2B0AF9EE00A537F2 /* SelfRemovableHostingController.swift */, ); path = SharedViews; sourceTree = ""; @@ -2991,7 +2991,7 @@ E9E7CDB72003994E00DFC4DB /* AdamantUtilities+extended.swift in Sources */, E9147B6320505C7500145913 /* QRCodeReader+adamant.swift in Sources */, E90055F720EC200900D0CB2D /* SecurityViewController.swift in Sources */, - 93BB4E6C2B0AF9EE00A537F2 /* SelfRemovableHostingController.swift in Sources */, + 939FA3422B0D6F0000710EC6 /* SelfRemovableHostingController.swift in Sources */, 644EC35E20F34F1E00F40C73 /* DelegateDetailsViewController.swift in Sources */, E9942B80203C058C00C163AF /* QRGeneratorViewController.swift in Sources */, 4184F1772A33173100D7B8B9 /* ContributeView.swift in Sources */, @@ -3064,7 +3064,7 @@ E9147B612050599000145913 /* LoginViewController+QR.swift in Sources */, 9399F5ED29A85A48006C3E30 /* ChatCacheService.swift in Sources */, 3A9015A92A615893002A2464 /* ChatMessagesListViewModel.swift in Sources */, - 936658952B0AC15300BDB2D3 /* Node+Strings.swift in Sources */, + 936658952B0AC15300BDB2D3 /* Node+UI.swift in Sources */, 41A1995429D56E340031AD75 /* ChatMessageReplyCell.swift in Sources */, 93294B872AAD0E0A00911109 /* AdmWallet.swift in Sources */, 6449BA6B235CA0930033B936 /* ERC20TransactionDetailsViewController.swift in Sources */, diff --git a/Adamant/Helpers/Node+Strings.swift b/Adamant/Helpers/Node+Strings.swift deleted file mode 100644 index 2bc9f5b55..000000000 --- a/Adamant/Helpers/Node+Strings.swift +++ /dev/null @@ -1,67 +0,0 @@ -// -// Node+Strings.swift -// Adamant -// -// Created by Andrew G on 20.11.2023. -// Copyright © 2023 Adamant. All rights reserved. -// - -import CommonKit - -extension Node { - func statusString(_ status: Node.ConnectionStatus?, isEnabled: Bool) -> String? { - guard isEnabled else { - return NodeCell.Strings.disabled - } - - switch status { - case .allowed: - let ping = ping.map { Int($0 * 1000) } - return ping.map { "\(NodeCell.Strings.ping): \($0) \(NodeCell.Strings.milliseconds)" } - case .synchronizing: - return NodeCell.Strings.synchronizing - case .offline: - return NodeCell.Strings.offline - case .none: - return nil - } - } - - var versionString: String? { - version.map { "(\(NodeCell.Strings.version): \($0))" } - } -} - -private extension NodeCell { - enum Strings { - static let ping = String.localized( - "NodesList.NodeCell.Ping", - comment: "NodesList.NodeCell: Node ping" - ) - - static let milliseconds = String.localized( - "NodesList.NodeCell.Milliseconds", - comment: "NodesList.NodeCell: Milliseconds" - ) - - static let synchronizing = String.localized( - "NodesList.NodeCell.Synchronizing", - comment: "NodesList.NodeCell: Node is synchronizing" - ) - - static let offline = String.localized( - "NodesList.NodeCell.Offline", - comment: "NodesList.NodeCell: Node is offline" - ) - - static let version = String.localized( - "NodesList.NodeCell.Version", - comment: "NodesList.NodeCell: Node version" - ) - - static let disabled = String.localized( - "NodesList.NodeCell.Disabled", - comment: "NodesList.NodeCell: Node is disabled" - ) - } -} diff --git a/Adamant/Helpers/Node+UI.swift b/Adamant/Helpers/Node+UI.swift new file mode 100644 index 000000000..f51c55c62 --- /dev/null +++ b/Adamant/Helpers/Node+UI.swift @@ -0,0 +1,117 @@ +// +// Node+UI.swift +// Adamant +// +// Created by Andrew G on 20.11.2023. +// Copyright © 2023 Adamant. All rights reserved. +// + +import CommonKit +import UIKit + +extension Node { + func statusString(showVersion: Bool) -> String? { + guard isEnabled else { return Strings.disabled } + + switch connectionStatus { + case .allowed: + return [ + pingString, + showVersion ? versionString : nil, + heightString + ] + .compactMap { $0 } + .joined(separator: " ") + case .synchronizing: + return [ + Strings.synchronizing, + showVersion ? versionString : nil, + heightString + ] + .compactMap { $0 } + .joined(separator: " ") + case .offline: + return Strings.offline + case .none: + return nil + } + } + + func indicatorString(isRest: Bool, isWs: Bool) -> String { + let connections = [ + isRest ? scheme.rawValue : nil, + isWs ? "ws" : nil + ].compactMap { $0 } + + return [ + "●", + connections.isEmpty + ? nil + : connections.joined(separator: ", ") + ] + .compactMap { $0 } + .joined(separator: " ") + } + + var indicatorColor: UIColor { + guard isEnabled else { return .adamant.inactive } + + switch connectionStatus { + case .allowed: + return .adamant.good + case .synchronizing: + return .adamant.alert + case .offline: + return .adamant.danger + case .none: + return .adamant.inactive + } + } +} + +private extension Node { + enum Strings { + static let ping = String.localized( + "NodesList.NodeCell.Ping", + comment: "NodesList.NodeCell: Node ping" + ) + + static let milliseconds = String.localized( + "NodesList.NodeCell.Milliseconds", + comment: "NodesList.NodeCell: Milliseconds" + ) + + static let synchronizing = String.localized( + "NodesList.NodeCell.Synchronizing", + comment: "NodesList.NodeCell: Node is synchronizing" + ) + + static let offline = String.localized( + "NodesList.NodeCell.Offline", + comment: "NodesList.NodeCell: Node is offline" + ) + + static let version = String.localized( + "NodesList.NodeCell.Version", + comment: "NodesList.NodeCell: Node version" + ) + + static let disabled = String.localized( + "NodesList.NodeCell.Disabled", + comment: "NodesList.NodeCell: Node is disabled" + ) + } + + var versionString: String? { + version.map { "(\(Strings.version): \($0))" } + } + + var pingString: String? { + guard let ping = ping else { return nil } + return "\(Strings.ping): \(Int(ping * 1000)) \(Strings.milliseconds)" + } + + var heightString: String? { + height.map { "▱ \($0)" } + } +} diff --git a/Adamant/SharedViews/SelfRemovableHostingController.swift b/Adamant/Helpers/SelfRemovableHostingController.swift similarity index 68% rename from Adamant/SharedViews/SelfRemovableHostingController.swift rename to Adamant/Helpers/SelfRemovableHostingController.swift index 01b5e0c97..8436a5e2b 100644 --- a/Adamant/SharedViews/SelfRemovableHostingController.swift +++ b/Adamant/Helpers/SelfRemovableHostingController.swift @@ -2,7 +2,7 @@ // SelfRemovableHostingController.swift // Adamant // -// Created by Andrew G on 20.11.2023. +// Created by Andrew G on 22.11.2023. // Copyright © 2023 Adamant. All rights reserved. // @@ -12,12 +12,6 @@ class SelfRemovableHostingController: UIHostingController { override func viewDidLoad() { super.viewDidLoad() - guard - splitViewController == nil, - let navigationController = navigationController, - navigationController.viewControllers.count == 1 - else { return } - navigationItem.rightBarButtonItem = UIBarButtonItem( barButtonSystemItem: .done, target: self, @@ -29,3 +23,4 @@ class SelfRemovableHostingController: UIHostingController { dismiss(animated: true) } } + diff --git a/Adamant/Modules/Account/AccountViewController.swift b/Adamant/Modules/Account/AccountViewController.swift index 98aa903d5..1b3387cea 100644 --- a/Adamant/Modules/Account/AccountViewController.swift +++ b/Adamant/Modules/Account/AccountViewController.swift @@ -341,7 +341,7 @@ final class AccountViewController: FormViewController { cell.accessoryType = .disclosureIndicator }.onCellSelection { [weak self] (_, _) in guard let self = self else { return } - let vc = screensFactory.makeCoinsNodesList() + let vc = screensFactory.makeCoinsNodesList(context: .menu) if let split = splitViewController { let details = UINavigationController(rootViewController:vc) diff --git a/Adamant/Modules/CoinsNodesList/CoinsNodesListFactory.swift b/Adamant/Modules/CoinsNodesList/CoinsNodesListFactory.swift index 1c210234d..2e230fc37 100644 --- a/Adamant/Modules/CoinsNodesList/CoinsNodesListFactory.swift +++ b/Adamant/Modules/CoinsNodesList/CoinsNodesListFactory.swift @@ -10,6 +10,11 @@ import Swinject import SwiftUI import CommonKit +enum CoinsNodesListContext { + case login + case menu +} + struct CoinsNodesListFactory { private let assembler: Assembler @@ -17,12 +22,16 @@ struct CoinsNodesListFactory { assembler = .init([CoinsNodesListAssembly()], parent: parent) } - func makeViewController() -> UIViewController { - SelfRemovableHostingController( - rootView: CoinsNodesListView( - viewModel: assembler.resolve(CoinsNodesListViewModel.self)! - ) - ) + func makeViewController(context: CoinsNodesListContext) -> UIViewController { + let viewModel = assembler.resolve(CoinsNodesListViewModel.self)! + let view = CoinsNodesListView(viewModel: viewModel) + + switch context { + case .login: + return SelfRemovableHostingController(rootView: view) + case .menu: + return UIHostingController(rootView: view) + } } } diff --git a/Adamant/Modules/CoinsNodesList/ViewModel/CoinsNodesListMapper.swift b/Adamant/Modules/CoinsNodesList/ViewModel/CoinsNodesListMapper.swift index da9821cc7..7d813038e 100644 --- a/Adamant/Modules/CoinsNodesList/ViewModel/CoinsNodesListMapper.swift +++ b/Adamant/Modules/CoinsNodesList/ViewModel/CoinsNodesListMapper.swift @@ -13,7 +13,7 @@ struct CoinsNodesListMapper { let processedGroups: Set func map(items: [NodeWithGroup], restNodeIds: [UUID]) -> [CoinsNodesListState.Section] { - var nodesDict = Dictionary() + var nodesDict = [NodeGroup: [Node]]() items.forEach { item in guard processedGroups.contains(item.group) else { return } @@ -51,41 +51,20 @@ private extension CoinsNodesListMapper { } func map(node: Node, restNodeIds: [UUID]) -> CoinsNodesListState.Section.Row { - let connectionStatus = node.isEnabled - ? node.connectionStatus - : nil - - let connectionStatusString = [ - "●", - restNodeIds.contains(node.id) - ? node.scheme.rawValue - : nil - ].compactMap { $0 }.joined(separator: " ") - - var connectionStatusAttrString = AttributedString(connectionStatusString) - connectionStatusAttrString.foregroundColor = .init( - uiColor: getIndicatorColor(status: connectionStatus) + let indicatorString = node.indicatorString( + isRest: restNodeIds.contains(node.id), + isWs: false ) + var indicatorAttrString = AttributedString(stringLiteral: indicatorString) + indicatorAttrString.foregroundColor = .init(uiColor: node.indicatorColor) + return .init( id: node.id, isEnabled: node.isEnabled, title: node.asString(), - connectionStatus: connectionStatusAttrString, - description: node.statusString(connectionStatus, isEnabled: node.isEnabled) ?? .empty + connectionStatus: indicatorAttrString, + description: node.statusString(showVersion: false) ?? .empty ) } } - -private func getIndicatorColor(status: Node.ConnectionStatus?) -> UIColor { - switch status { - case .allowed: - return .adamant.good - case .synchronizing: - return .adamant.alert - case .offline: - return .adamant.danger - case .none: - return .adamant.inactive - } -} diff --git a/Adamant/Modules/Login/LoginViewController.swift b/Adamant/Modules/Login/LoginViewController.swift index e3dce54b0..8453cdcdd 100644 --- a/Adamant/Modules/Login/LoginViewController.swift +++ b/Adamant/Modules/Login/LoginViewController.swift @@ -340,7 +340,7 @@ final class LoginViewController: FormViewController { cell.textLabel?.textColor = UIColor.adamant.primary }.onCellSelection { [weak self] (_, _) in guard let self = self else { return } - let vc = screensFactory.makeCoinsNodesList() + let vc = screensFactory.makeCoinsNodesList(context: .login) let nav = UINavigationController(rootViewController: vc) nav.modalPresentationStyle = .overFullScreen present(nav, animated: true, completion: nil) diff --git a/Adamant/Modules/NodesEditor/NodeCell/NodeCell+Model.swift b/Adamant/Modules/NodesEditor/NodeCell/NodeCell+Model.swift index 0185cef65..28342922b 100644 --- a/Adamant/Modules/NodesEditor/NodeCell/NodeCell+Model.swift +++ b/Adamant/Modules/NodesEditor/NodeCell/NodeCell+Model.swift @@ -6,7 +6,7 @@ // Copyright © 2023 Adamant. All rights reserved. // -import Foundation +import UIKit import CommonKit extension NodeCell { @@ -15,29 +15,20 @@ extension NodeCell { struct Model: Equatable { let id: UUID let title: String - let connectionStatus: Node.ConnectionStatus? - let statusString: String? - let versionString: String? + let indicatorString: String + let indicatorColor: UIColor + let statusString: String let isEnabled: Bool - let activities: Set let nodeUpdateAction: IDWrapper static let `default` = Self( id: .init(), title: .empty, - connectionStatus: nil, + indicatorString: .init(), + indicatorColor: .adamant.inactive, statusString: .empty, - versionString: .empty, isEnabled: false, - activities: .init(), nodeUpdateAction: .init(id: .empty) { _ in } ) } } - -extension NodeCell.Model { - enum NodeActivity: Equatable, Hashable { - case webSockets - case rest(scheme: Node.URLScheme) - } -} diff --git a/Adamant/Modules/NodesEditor/NodeCell/NodeCell.swift b/Adamant/Modules/NodesEditor/NodeCell/NodeCell.swift index fef3fdf2f..c1e426fd4 100644 --- a/Adamant/Modules/NodesEditor/NodeCell/NodeCell.swift +++ b/Adamant/Modules/NodesEditor/NodeCell/NodeCell.swift @@ -37,20 +37,9 @@ final class NodeCell: Cell, CellType { override func update() { checkmarkRowView.setIsChecked(model.isEnabled, animated: true) checkmarkRowView.title = model.title - checkmarkRowView.captionColor = getIndicatorColor(status: model.connectionStatus) - - checkmarkRowView.caption = ["●", makeActivitiesString()] - .compactMap { $0 } - .joined(separator: " ") - - let descriptionStrings = [ - model.statusString, - model.versionString - ] - - checkmarkRowView.subtitle = descriptionStrings - .compactMap { $0 } - .joined(separator: " ") + checkmarkRowView.captionColor = model.indicatorColor + checkmarkRowView.caption = model.indicatorString + checkmarkRowView.subtitle = model.statusString } func subscribe>(_ publisher: P) { @@ -68,23 +57,12 @@ private extension NodeCell { } checkmarkRowView.checkmarkImage = .asset(named: "status_success") - checkmarkRowView.onCheckmarkTap = { [weak self] in self?.onCheckmarkTap()} + checkmarkRowView.onCheckmarkTap = { [weak self] in self?.onCheckmarkTap() } } func onCheckmarkTap() { model.nodeUpdateAction.value(!checkmarkRowView.isChecked) } - - func makeActivitiesString() -> String? { - model.activities.map { activity in - switch activity { - case .webSockets: - return "ws" - case let .rest(scheme): - return scheme.rawValue - } - }.sorted().joined(separator: ", ") - } } final class NodeRow: Row, RowType { @@ -93,16 +71,3 @@ final class NodeRow: Row, RowType { cellProvider = .init() } } - -private func getIndicatorColor(status: Node.ConnectionStatus?) -> UIColor { - switch status { - case .allowed: - return .adamant.good - case .synchronizing: - return .adamant.alert - case .offline: - return .adamant.danger - case .none: - return .adamant.inactive - } -} diff --git a/Adamant/Modules/NodesEditor/NodesListViewController.swift b/Adamant/Modules/NodesEditor/NodesListViewController.swift index f1dfa25e4..25ed8c937 100644 --- a/Adamant/Modules/NodesEditor/NodesListViewController.swift +++ b/Adamant/Modules/NodesEditor/NodesListViewController.swift @@ -137,7 +137,7 @@ final class NodesListViewController: FormViewController { super.viewDidLoad() navigationItem.title = String.adamant.nodesList.title navigationOptions = .Disabled - navigationItem.largeTitleDisplayMode = .always + navigationItem.largeTitleDisplayMode = .never if splitViewController == nil, navigationController?.viewControllers.count == 1 { let done = UIBarButtonItem(barButtonSystemItem: .done, target: self, action: #selector(NodesListViewController.close)) @@ -411,18 +411,13 @@ extension NodesListViewController { return .init( id: node.id, title: node.asString(), - connectionStatus: connectionStatus, - statusString: node.statusString(connectionStatus, isEnabled: node.isEnabled), - versionString: node.versionString, + indicatorString: node.indicatorString( + isRest: currentRestNodesIds.contains(node.id), + isWs: currentSocketsNodeId == node.id + ), + indicatorColor: node.indicatorColor, + statusString: node.statusString(showVersion: true) ?? .empty, isEnabled: node.isEnabled, - activities: .init([ - currentRestNodesIds.contains(node.id) - ? .rest(scheme: node.scheme) - : nil, - currentSocketsNodeId == node.id - ? .webSockets - : nil - ].compactMap { $0 }), nodeUpdateAction: .init(id: node.id.uuidString) { [nodesStorage] isEnabled in nodesStorage.updateNodeParams(id: node.id, isEnabled: isEnabled) } diff --git a/Adamant/Modules/ScreensFactory/AdamantScreensFactory.swift b/Adamant/Modules/ScreensFactory/AdamantScreensFactory.swift index cb4287049..f4e734ee2 100644 --- a/Adamant/Modules/ScreensFactory/AdamantScreensFactory.swift +++ b/Adamant/Modules/ScreensFactory/AdamantScreensFactory.swift @@ -178,7 +178,7 @@ struct AdamantScreensFactory: ScreensFactory { partnerQRFactory.makeViewController(partner: partner) } - func makeCoinsNodesList() -> UIViewController { - coinsNodesListFactory.makeViewController() + func makeCoinsNodesList(context: CoinsNodesListContext) -> UIViewController { + coinsNodesListFactory.makeViewController(context: context) } } diff --git a/Adamant/Modules/ScreensFactory/ScreensFactory.swift b/Adamant/Modules/ScreensFactory/ScreensFactory.swift index f9c6c6c39..628db804a 100644 --- a/Adamant/Modules/ScreensFactory/ScreensFactory.swift +++ b/Adamant/Modules/ScreensFactory/ScreensFactory.swift @@ -43,7 +43,7 @@ protocol ScreensFactory { func makeNodesList() -> UIViewController func makeNodeEditor() -> NodeEditorViewController - func makeCoinsNodesList() -> UIViewController + func makeCoinsNodesList(context: CoinsNodesListContext) -> UIViewController // MARK: Other diff --git a/Adamant/Modules/Wallets/Lisk/LskWalletService.swift b/Adamant/Modules/Wallets/Lisk/LskWalletService.swift index 05ef17afa..a5b5136da 100644 --- a/Adamant/Modules/Wallets/Lisk/LskWalletService.swift +++ b/Adamant/Modules/Wallets/Lisk/LskWalletService.swift @@ -455,7 +455,7 @@ extension LskWalletService { guard case let .remoteServiceError(_, lskError) = error, let lskError = lskError as? APIError, - lskError.code == 404 + [404, 500].contains(lskError.code) else { throw error } return .zero diff --git a/Adamant/Services/BlockchainHealthCheckWrapper.swift b/Adamant/Services/BlockchainHealthCheckWrapper.swift index 5f7a49294..0202d455c 100644 --- a/Adamant/Services/BlockchainHealthCheckWrapper.swift +++ b/Adamant/Services/BlockchainHealthCheckWrapper.swift @@ -19,6 +19,7 @@ final class BlockchainHealthCheckWrapper< Service: BlockchainHealthCheckableService >: HealthCheckWrapper { private let nodesStorage: NodesStorageProtocol + private let updateNodesAvailabilityLock = NSLock() @Atomic private var currentRequests = Set() @@ -70,24 +71,26 @@ final class BlockchainHealthCheckWrapper< private extension BlockchainHealthCheckWrapper { func updateNodeStatusInfo(node: Node) async { currentRequests.insert(node.id) - - defer { - currentRequests.remove(node.id) - updateNodesAvailability() - } + defer { currentRequests.remove(node.id) } switch await service.getStatusInfo(node: node) { case let .success(statusInfo): nodesStorage.updateNodeStatus(id: node.id, statusInfo: statusInfo) - nodesStorage.updateNodeParams(id: node.id, connectionStatus: .some(.none)) + updateNodesAvailability(forceInclude: node.id) case let .failure(error): guard !error.isRequestCancelledError else { return } nodesStorage.updateNodeParams(id: node.id, connectionStatus: .offline) + updateNodesAvailability() } } - func updateNodesAvailability() { - let workingNodes = nodes.filter { $0.isWorking } + func updateNodesAvailability(forceInclude: UUID? = nil) { + updateNodesAvailabilityLock.lock() + defer { updateNodesAvailabilityLock.unlock() } + + let workingNodes = nodes.filter { + $0.isEnabled && ($0.isWorkingStatus || $0.id == forceInclude) + } let actualHeightsRange = getActualNodeHeightsRange( heights: workingNodes.compactMap { $0.height }, @@ -109,29 +112,33 @@ private extension BlockchainHealthCheckWrapper { } } +private extension Node { + var isWorkingStatus: Bool { + switch connectionStatus { + case .allowed, .synchronizing, .none: + return isEnabled + case .offline: + return false + } + } +} + private struct NodeHeightsInterval { let range: ClosedRange var count: Int } private func getActualNodeHeightsRange(heights: [Int], group: NodeGroup) -> ClosedRange? { - guard heights.count > 2 else { return heights.max().map { $0...$0 } } - let heights = heights.sorted() var bestInterval: NodeHeightsInterval? for i in heights.indices { var currentInterval = NodeHeightsInterval( - range: heights[i] - group.nodeHeightEpsilon ... heights[i] + group.nodeHeightEpsilon, + range: heights[i] ... heights[i] + group.nodeHeightEpsilon - 1, count: 1 ) - for j in stride(from: i + 1, to: heights.endIndex, by: 1) { - guard currentInterval.range.contains(heights[j]) else { break } - currentInterval.count += 1 - } - - for j in stride(from: i - 1, through: .zero, by: -1) { + for j in i + 1 ..< heights.endIndex { guard currentInterval.range.contains(heights[j]) else { break } currentInterval.count += 1 } diff --git a/Adamant/Services/HealthCheckWrapper.swift b/Adamant/Services/HealthCheckWrapper.swift index 4cf288717..03c96ad6a 100644 --- a/Adamant/Services/HealthCheckWrapper.swift +++ b/Adamant/Services/HealthCheckWrapper.swift @@ -115,17 +115,6 @@ class HealthCheckWrapper { } } -extension Node { - var isWorking: Bool { - switch connectionStatus { - case .allowed, .synchronizing, .none: - return isEnabled - case .offline: - return false - } - } -} - private extension HealthCheckWrapper { func updateHealthCheckTimerSubscription() { healthCheckTimerSubscription = Timer.publish( diff --git a/CommonKit/Sources/CommonKit/Assets/Localization/en.lproj/Localizable.strings b/CommonKit/Sources/CommonKit/Assets/Localization/en.lproj/Localizable.strings index 1517340b4..9d686ce4d 100644 --- a/CommonKit/Sources/CommonKit/Assets/Localization/en.lproj/Localizable.strings +++ b/CommonKit/Sources/CommonKit/Assets/Localization/en.lproj/Localizable.strings @@ -614,7 +614,7 @@ "NewChatScene.NotInitialized.HelpButton" = "What does it mean?"; /* NodesList: Button label */ -"NodesList.NodesList" = "ADM Node list"; +"NodesList.NodesList" = "ADM node list"; /* NodesList: scene title */ "NodesList.Title" = "List of ADM nodes"; From 577c4c50eb22e42150bf7862ba63364a67997b43 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=80=9Cjust-software-dev=E2=80=9D?= Date: Wed, 22 Nov 2023 06:33:51 +0600 Subject: [PATCH 77/96] [trello.com/c/q6fyv0yn] Dash fix --- .../Wallets/Bitcoin/BtcWalletService.swift | 8 +-- .../Dash/DTO/DashGetRawTransactionDTO.swift | 21 +++++-- .../Modules/Wallets/Dash/DashApiService.swift | 2 +- .../Wallets/Dash/DashWalletService+Send.swift | 2 +- .../Dash/DashWalletService+Transactions.swift | 17 +++--- .../Modules/Wallets/Doge/DogeApiService.swift | 2 +- .../Wallets/Doge/DogeWalletService.swift | 4 +- .../Wallets/ERC20/ERC20WalletService.swift | 2 +- .../Wallets/Ethereum/EthWalletService.swift | 4 +- .../ServiceProtocols/APICoreProtocol.swift | 55 +++++++++++++------ Adamant/Services/APICore.swift | 28 ++++++++++ .../ApiService/AdamantApi+Accounts.swift | 4 +- .../ApiService/AdamantApi+Chats.swift | 6 +- .../ApiService/AdamantApi+Delegates.swift | 12 ++-- .../Services/ApiService/AdamantApi+Keys.swift | 2 +- .../ApiService/AdamantApi+States.swift | 2 +- .../ApiService/AdamantApi+Transactions.swift | 10 ++-- .../Services/ApiService/AdamantApiCore.swift | 2 +- 18 files changed, 124 insertions(+), 59 deletions(-) diff --git a/Adamant/Modules/Wallets/Bitcoin/BtcWalletService.swift b/Adamant/Modules/Wallets/Bitcoin/BtcWalletService.swift index d6aa87656..68e8fc78e 100644 --- a/Adamant/Modules/Wallets/Bitcoin/BtcWalletService.swift +++ b/Adamant/Modules/Wallets/Bitcoin/BtcWalletService.swift @@ -492,7 +492,7 @@ extension BtcWalletService { func getBalance(address: String) async throws -> Decimal { let response: BtcBalanceResponse = try await btcApiService.request { api, node in - await api.sendRequestJson(node: node, path: BtcApiCommands.balance(for: address)) + await api.sendRequestJsonResponse(node: node, path: BtcApiCommands.balance(for: address)) }.get() return response.value / BtcWalletService.multiplier @@ -500,7 +500,7 @@ extension BtcWalletService { func getFeeRate() async throws -> Decimal { let response: [String: Decimal] = try await btcApiService.request { api, node in - await api.sendRequestJson(node: node, path: BtcApiCommands.getFeeRate()) + await api.sendRequestJsonResponse(node: node, path: BtcApiCommands.getFeeRate()) }.get() return response["2"] ?? 1 @@ -639,7 +639,7 @@ extension BtcWalletService { fromTx: String? = nil ) async throws -> [RawBtcTransactionResponse] { return try await btcApiService.request { api, node in - await api.sendRequestJson( + await api.sendRequestJsonResponse( node: node, path: BtcApiCommands.getTransactions( for: address, @@ -655,7 +655,7 @@ extension BtcWalletService { } let rawTransaction: RawBtcTransactionResponse = try await btcApiService.request { api, node in - await api.sendRequestJson( + await api.sendRequestJsonResponse( node: node, path: BtcApiCommands.getTransaction(by: hash) ) diff --git a/Adamant/Modules/Wallets/Dash/DTO/DashGetRawTransactionDTO.swift b/Adamant/Modules/Wallets/Dash/DTO/DashGetRawTransactionDTO.swift index 94048f8e6..dd40573b8 100644 --- a/Adamant/Modules/Wallets/Dash/DTO/DashGetRawTransactionDTO.swift +++ b/Adamant/Modules/Wallets/Dash/DTO/DashGetRawTransactionDTO.swift @@ -8,19 +8,32 @@ import Foundation -struct DashGetRawTransactionDTO: Codable { +struct DashGetRawTransactionDTO: Encodable { let method: String let params: [Parameter] init(hash: String) { self.method = "getrawtransaction" - self.params = [.hash(hash), .bool(true)] + self.params = [.string(hash), .bool(true)] } } extension DashGetRawTransactionDTO { - enum Parameter: Codable { - case hash(String) + enum Parameter { + case string(String) case bool(Bool) } } + +extension DashGetRawTransactionDTO.Parameter: Encodable { + func encode(to encoder: Encoder) throws { + var container = encoder.singleValueContainer() + + switch self { + case let .string(value): + try container.encode(value) + case let .bool(value): + try container.encode(value) + } + } +} diff --git a/Adamant/Modules/Wallets/Dash/DashApiService.swift b/Adamant/Modules/Wallets/Dash/DashApiService.swift index 36bddeade..c5de87166 100644 --- a/Adamant/Modules/Wallets/Dash/DashApiService.swift +++ b/Adamant/Modules/Wallets/Dash/DashApiService.swift @@ -27,7 +27,7 @@ final class DashApiCore: BlockchainHealthCheckableService { let startTimestamp = Date.now.timeIntervalSince1970 let response: WalletServiceResult = await request(node: node) { core, node in - let response: ApiServiceResult> = await core.sendRequestJson( + let response: ApiServiceResult> = await core.sendRequestJsonResponse( node: node, path: .empty, method: .post, diff --git a/Adamant/Modules/Wallets/Dash/DashWalletService+Send.swift b/Adamant/Modules/Wallets/Dash/DashWalletService+Send.swift index 243923b3f..b977a47cb 100644 --- a/Adamant/Modules/Wallets/Dash/DashWalletService+Send.swift +++ b/Adamant/Modules/Wallets/Dash/DashWalletService+Send.swift @@ -71,7 +71,7 @@ extension DashWalletService: WalletServiceTwoStepSend { let txHex = transaction.serialized().hex let response: BTCRPCServerResponce = try await dashApiService.request { core, node in - await core.sendRequestJson( + await core.sendRequestJsonResponse( node: node, path: .empty, method: .post, diff --git a/Adamant/Modules/Wallets/Dash/DashWalletService+Transactions.swift b/Adamant/Modules/Wallets/Dash/DashWalletService+Transactions.swift index b68b5df98..f000178cb 100644 --- a/Adamant/Modules/Wallets/Dash/DashWalletService+Transactions.swift +++ b/Adamant/Modules/Wallets/Dash/DashWalletService+Transactions.swift @@ -38,7 +38,7 @@ extension DashWalletService { func getTransaction(by hash: String) async throws -> BTCRawTransaction { let result: BTCRPCServerResponce = try await dashApiService.request { core, node in - await core.sendRequestJson( + await core.sendRequestJsonResponse( node: node, path: .empty, method: .post, @@ -59,16 +59,17 @@ extension DashWalletService { throw ApiServiceError.notLogged } - let parameters: [DashGetRawTransactionDTO] = hashes.map { .init(hash: $0) } + let parameters: [Any] = hashes.compactMap { + DashGetRawTransactionDTO(hash: $0).asDictionary() + } let result: [BTCRPCServerResponce] = try await dashApiService.request { core, node in - await core.sendRequestJson( + await core.sendRequestJsonResponse( node: node, path: .empty, method: .post, - parameters: parameters, - encoding: .json + jsonParameters: parameters ) }.get() @@ -81,7 +82,7 @@ extension DashWalletService { } let result: BTCRPCServerResponce = try await dashApiService.request { core, node in - await core.sendRequestJson( + await core.sendRequestJsonResponse( node: node, path: .empty, method: .post, @@ -104,7 +105,7 @@ extension DashWalletService { let response: BTCRPCServerResponce<[DashUnspentTransaction]> = try await dashApiService.request { core, node in - await core.sendRequestJson( + await core.sendRequestJsonResponse( node: node, path: .empty, method: .post, @@ -169,7 +170,7 @@ extension DashWalletService { func requestTransactionsIds(for address: String) async throws -> [String] { let response: BTCRPCServerResponce<[String]> = try await dashApiService.request { core, node in - await core.sendRequestJson( + await core.sendRequestJsonResponse( node: node, path: .empty, method: .post, diff --git a/Adamant/Modules/Wallets/Doge/DogeApiService.swift b/Adamant/Modules/Wallets/Doge/DogeApiService.swift index 2c96b4ad1..8f9c77b35 100644 --- a/Adamant/Modules/Wallets/Doge/DogeApiService.swift +++ b/Adamant/Modules/Wallets/Doge/DogeApiService.swift @@ -27,7 +27,7 @@ final class DogeApiCore: BlockchainHealthCheckableService { let startTimestamp = Date.now.timeIntervalSince1970 let response: WalletServiceResult = await request(node: node) { core, node in - await core.sendRequestJson( + await core.sendRequestJsonResponse( node: node, path: DogeApiCommands.getBlocks(), method: .get, diff --git a/Adamant/Modules/Wallets/Doge/DogeWalletService.swift b/Adamant/Modules/Wallets/Doge/DogeWalletService.swift index ef7c4a58e..d98c95f6e 100644 --- a/Adamant/Modules/Wallets/Doge/DogeWalletService.swift +++ b/Adamant/Modules/Wallets/Doge/DogeWalletService.swift @@ -504,7 +504,7 @@ extension DogeWalletService { ] return try await dogeApiService.request { core, node in - await core.sendRequestJson( + await core.sendRequestJsonResponse( node: node, path: DogeApiCommands.getTransactions(for: address), method: .get, @@ -576,7 +576,7 @@ extension DogeWalletService { func getTransaction(by hash: String) async throws -> BTCRawTransaction { try await dogeApiService.request { core, node in - await core.sendRequestJson( + await core.sendRequestJsonResponse( node: node, path: DogeApiCommands.getTransaction(by: hash) ) diff --git a/Adamant/Modules/Wallets/ERC20/ERC20WalletService.swift b/Adamant/Modules/Wallets/ERC20/ERC20WalletService.swift index 619876e08..b85c843a8 100644 --- a/Adamant/Modules/Wallets/ERC20/ERC20WalletService.swift +++ b/Adamant/Modules/Wallets/ERC20/ERC20WalletService.swift @@ -530,7 +530,7 @@ extension ERC20WalletService { ] var transactions: [EthTransactionShort] = try await erc20ApiService.requestApiCore { core, node in - await core.sendRequestJson( + await core.sendRequestJsonResponse( node: node, path: EthWalletService.transactionsListApiSubpath, method: .get, diff --git a/Adamant/Modules/Wallets/Ethereum/EthWalletService.swift b/Adamant/Modules/Wallets/Ethereum/EthWalletService.swift index 330fa344c..bb4be4520 100644 --- a/Adamant/Modules/Wallets/Ethereum/EthWalletService.swift +++ b/Adamant/Modules/Wallets/Ethereum/EthWalletService.swift @@ -689,7 +689,7 @@ extension EthWalletService { let transactionsFrom: [EthTransactionShort] = try await ethApiService.requestApiCore { core, node in - await core.sendRequestJson( + await core.sendRequestJsonResponse( node: node, path: EthWalletService.transactionsListApiSubpath, method: .get, @@ -700,7 +700,7 @@ extension EthWalletService { let transactionsTo: [EthTransactionShort] = try await ethApiService.requestApiCore { core, node in - await core.sendRequestJson( + await core.sendRequestJsonResponse( node: node, path: EthWalletService.transactionsListApiSubpath, method: .get, diff --git a/Adamant/ServiceProtocols/APICoreProtocol.swift b/Adamant/ServiceProtocols/APICoreProtocol.swift index f8e283b3c..6fb495bf2 100644 --- a/Adamant/ServiceProtocols/APICoreProtocol.swift +++ b/Adamant/ServiceProtocols/APICoreProtocol.swift @@ -21,9 +21,19 @@ protocol APICoreProtocol: Actor { parameters: Parameters, encoding: APIParametersEncoding ) async -> APIResponseModel + + /// jsonParameters - arrays and dictionaries are allowed only + func sendRequestBasic( + node: Node, + path: String, + method: HTTPMethod, + jsonParameters: Any + ) async -> APIResponseModel } extension APICoreProtocol { + var emptyParameters: [String: Bool] { [:] } + func sendRequest( node: Node, path: String, @@ -40,37 +50,27 @@ extension APICoreProtocol { ).result } - func sendRequestJson( + func sendRequestJsonResponse( node: Node, path: String, method: HTTPMethod, parameters: Parameters, encoding: APIParametersEncoding ) async -> ApiServiceResult { - switch await sendRequest( + await sendRequest( node: node, path: path, method: method, parameters: parameters, encoding: encoding - ) { - case let .success(data): - do { - let output = try JSONDecoder().decode(JSONOutput.self, from: data) - return .success(output) - } catch { - return .failure(.internalError(error: InternalAPIError.parsingFailed)) - } - case let .failure(error): - return .failure(error) - } + ).flatMap { parseJSON(data: $0) } } - func sendRequestJson( + func sendRequestJsonResponse( node: Node, path: String ) async -> ApiServiceResult { - await sendRequestJson( + await sendRequestJsonResponse( node: node, path: path, method: .get, @@ -91,6 +91,29 @@ extension APICoreProtocol { encoding: .url ) } + + func sendRequestJsonResponse( + node: Node, + path: String, + method: HTTPMethod, + jsonParameters: Any + ) async -> ApiServiceResult { + await sendRequestBasic( + node: node, + path: path, + method: method, + jsonParameters: jsonParameters + ).result.flatMap { parseJSON(data: $0) } + } } -private let emptyParameters: [String: Bool] = [:] +private extension APICoreProtocol { + func parseJSON(data: Data) -> ApiServiceResult { + do { + let output = try JSONDecoder().decode(JSON.self, from: data) + return .success(output) + } catch { + return .failure(.internalError(error: InternalAPIError.parsingFailed)) + } + } +} diff --git a/Adamant/Services/APICore.swift b/Adamant/Services/APICore.swift index b0ed33fb4..f3748aa34 100644 --- a/Adamant/Services/APICore.swift +++ b/Adamant/Services/APICore.swift @@ -50,6 +50,34 @@ actor APICore: APICoreProtocol { ) } } + + func sendRequestBasic( + node: Node, + path: String, + method: HTTPMethod, + jsonParameters: Any + ) async -> APIResponseModel { + do { + let data = try JSONSerialization.data( + withJSONObject: jsonParameters + ) + + var request = try URLRequest( + url: try buildUrl(node: node, path: path), + method: method + ) + + request.httpBody = data + request.headers.update(.contentType("application/json")) + return await sendRequest(request: AF.request(request)) + } catch { + return .init( + result: .failure(.internalError(message: error.localizedDescription, error: error)), + data: nil, + code: nil + ) + } + } } private extension APICore { diff --git a/Adamant/Services/ApiService/AdamantApi+Accounts.swift b/Adamant/Services/ApiService/AdamantApi+Accounts.swift index d999f7f3e..7b8c6bd71 100644 --- a/Adamant/Services/ApiService/AdamantApi+Accounts.swift +++ b/Adamant/Services/ApiService/AdamantApi+Accounts.swift @@ -31,7 +31,7 @@ extension AdamantApiService { /// Get account by publicKey func getAccount(byPublicKey publicKey: String) async -> ApiServiceResult { switch await request({ apiCore, node in - let response: ApiServiceResult> = await apiCore.sendRequestJson( + let response: ApiServiceResult> = await apiCore.sendRequestJsonResponse( node: node, path: ApiCommands.Accounts.root, method: .get, @@ -57,7 +57,7 @@ extension AdamantApiService { await request { apiCore, node in let response: ApiServiceResult< ServerModelResponse - > = await apiCore.sendRequestJson( + > = await apiCore.sendRequestJsonResponse( node: node, path: ApiCommands.Accounts.root, method: .get, diff --git a/Adamant/Services/ApiService/AdamantApi+Chats.swift b/Adamant/Services/ApiService/AdamantApi+Chats.swift index 2c8c60552..1ec2c866e 100644 --- a/Adamant/Services/ApiService/AdamantApi+Chats.swift +++ b/Adamant/Services/ApiService/AdamantApi+Chats.swift @@ -41,7 +41,7 @@ extension AdamantApiService { let response: ApiServiceResult> response = await request { [parameters] service, node in - await service.sendRequestJson( + await service.sendRequestJsonResponse( node: node, path: ApiCommands.Chats.get, method: .get, @@ -73,7 +73,7 @@ extension AdamantApiService { } return await request { [parameters] service, node in - await service.sendRequestJson( + await service.sendRequestJsonResponse( node: node, path: ApiCommands.Chats.getChatRooms + "/\(address)", method: .get, @@ -100,7 +100,7 @@ extension AdamantApiService { } return await request { [parameters] service, node in - await service.sendRequestJson( + await service.sendRequestJsonResponse( node: node, path: ApiCommands.Chats.getChatRooms + "/\(address)/\(addressRecipient)", method: .get, diff --git a/Adamant/Services/ApiService/AdamantApi+Delegates.swift b/Adamant/Services/ApiService/AdamantApi+Delegates.swift index f4c1e6caf..0ce14a3f2 100644 --- a/Adamant/Services/ApiService/AdamantApi+Delegates.swift +++ b/Adamant/Services/ApiService/AdamantApi+Delegates.swift @@ -34,7 +34,7 @@ extension AdamantApiService { ) async -> ApiServiceResult<[Delegate]> { let response: ApiServiceResult> response = await request { core, node in - await core.sendRequestJson( + await core.sendRequestJsonResponse( node: node, path: ApiCommands.Delegates.getDelegates, method: .get, @@ -83,7 +83,7 @@ extension AdamantApiService { func getForgedByAccount(publicKey: String) async -> ApiServiceResult { await request { core, node in - await core.sendRequestJson( + await core.sendRequestJsonResponse( node: node, path: ApiCommands.Delegates.getForgedByAccount, method: .get, @@ -105,7 +105,7 @@ extension AdamantApiService { private func getDelegatesCount() async -> ApiServiceResult { await request { core, node in - await core.sendRequestJson( + await core.sendRequestJsonResponse( node: node, path: ApiCommands.Delegates.getDelegatesCount ) @@ -114,7 +114,7 @@ extension AdamantApiService { private func getNextForgers() async -> ApiServiceResult { await request { core, node in - await core.sendRequestJson( + await core.sendRequestJsonResponse( node: node, path: ApiCommands.Delegates.getNextForgers, method: .get, @@ -127,7 +127,7 @@ extension AdamantApiService { func getVotes(for address: String) async -> ApiServiceResult<[Delegate]> { let response: ApiServiceResult> response = await request { core, node in - await core.sendRequestJson( + await core.sendRequestJsonResponse( node: node, path: ApiCommands.Delegates.votes, method: .get, @@ -184,7 +184,7 @@ extension AdamantApiService { private func getBlocks() async -> ApiServiceResult<[Block]> { let response: ApiServiceResult> = await request { core, node in - await core.sendRequestJson( + await core.sendRequestJsonResponse( node: node, path: ApiCommands.Delegates.getBlocks, method: .get, diff --git a/Adamant/Services/ApiService/AdamantApi+Keys.swift b/Adamant/Services/ApiService/AdamantApi+Keys.swift index 124aadd8a..2226421f4 100644 --- a/Adamant/Services/ApiService/AdamantApi+Keys.swift +++ b/Adamant/Services/ApiService/AdamantApi+Keys.swift @@ -12,7 +12,7 @@ import CommonKit extension AdamantApiService { func getPublicKey(byAddress address: String) async -> ApiServiceResult { let response: ApiServiceResult = await request { service, node in - await service.sendRequestJson( + await service.sendRequestJsonResponse( node: node, path: ApiCommands.Accounts.getPublicKey, method: .get, diff --git a/Adamant/Services/ApiService/AdamantApi+States.swift b/Adamant/Services/ApiService/AdamantApi+States.swift index b926763f7..4800bb41b 100644 --- a/Adamant/Services/ApiService/AdamantApi+States.swift +++ b/Adamant/Services/ApiService/AdamantApi+States.swift @@ -64,7 +64,7 @@ extension AdamantApiService { let response: ApiServiceResult> response = await request { [parameters] core, node in - await core.sendRequestJson( + await core.sendRequestJsonResponse( node: node, path: ApiCommands.States.get, method: .get, diff --git a/Adamant/Services/ApiService/AdamantApi+Transactions.swift b/Adamant/Services/ApiService/AdamantApi+Transactions.swift index baeb070de..f63400128 100644 --- a/Adamant/Services/ApiService/AdamantApi+Transactions.swift +++ b/Adamant/Services/ApiService/AdamantApi+Transactions.swift @@ -24,7 +24,7 @@ extension AdamantApiService { transaction: UnregisteredTransaction ) async -> ApiServiceResult { let response: ApiServiceResult = await request { core, node in - await core.sendRequestJson( + await core.sendRequestJsonResponse( node: node, path: path, method: .post, @@ -41,7 +41,7 @@ extension AdamantApiService { transaction: UnregisteredTransaction ) async -> ApiServiceResult { let response: ApiServiceResult = await request { core, node in - await core.sendRequestJson( + await core.sendRequestJsonResponse( node: node, path: path, method: .post, @@ -63,7 +63,7 @@ extension AdamantApiService { func getTransaction(id: UInt64, withAsset: Bool) async -> ApiServiceResult { let response: ApiServiceResult> response = await request { core, node in - await core.sendRequestJson( + await core.sendRequestJsonResponse( node: node, path: ApiCommands.Transactions.getTransaction, method: .get, @@ -130,11 +130,11 @@ extension AdamantApiService { let response: ApiServiceResult> response = await request { [queryItems] core, node in - await core.sendRequestJson( + await core.sendRequestJsonResponse( node: node, path: ApiCommands.Transactions.root, method: .get, - parameters: [Bool](), + parameters: core.emptyParameters, encoding: .forceQueryItems(queryItems) ) } diff --git a/Adamant/Services/ApiService/AdamantApiCore.swift b/Adamant/Services/ApiService/AdamantApiCore.swift index f7a44f196..1a4861d62 100644 --- a/Adamant/Services/ApiService/AdamantApiCore.swift +++ b/Adamant/Services/ApiService/AdamantApiCore.swift @@ -23,7 +23,7 @@ final class AdamantApiCore { } func getNodeStatus(node: Node) async -> ApiServiceResult { - await apiCore.sendRequestJson( + await apiCore.sendRequestJsonResponse( node: node, path: ApiCommands.status ) From 9c8651766cb0e82d99b0d600c3fd802622900155 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=80=9Cjust-software-dev=E2=80=9D?= Date: Wed, 22 Nov 2023 06:54:59 +0600 Subject: [PATCH 78/96] [trello.com/c/KxaHBH5q] Strings fix --- .../Assets/Localization/ru.lproj/Localizable.strings | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/CommonKit/Sources/CommonKit/Assets/Localization/ru.lproj/Localizable.strings b/CommonKit/Sources/CommonKit/Assets/Localization/ru.lproj/Localizable.strings index d8b3c23f6..262f07c4d 100644 --- a/CommonKit/Sources/CommonKit/Assets/Localization/ru.lproj/Localizable.strings +++ b/CommonKit/Sources/CommonKit/Assets/Localization/ru.lproj/Localizable.strings @@ -71,7 +71,7 @@ "Contribute.Section.RunNodes" = "Запустите узел сети АДАМАНТа"; /* Contribute scene: 'Run nodes' section description. */ -"Contribute.Section.RunNodesDescription" = "Поддержите децентрализацию и повысьте уровень конфиденциальности"; +"Contribute.Section.RunNodesDescription" = "Поддержите децентрализацию и повысьте уровень приватности"; /* Contribute scene: 'Network delegate' section title. */ "Contribute.Section.NetworkDelegate" = "Станьте делегатом сети"; @@ -632,7 +632,7 @@ "NodesList.PreferTheFastestNode" = "Выбирать самую быструю ноду"; /* NodesList: 'Prefer the fastest node' switch */ -"NodesList.PreferTheFastestNode.Footer" = "Обрабатывайте запросы быстрее, но это может привести к снижению конфиденциальности."; +"NodesList.PreferTheFastestNode.Footer" = "Обрабатывайте запросы быстрее, но это может привести к снижению приватности."; /* NodesList.NodeCell: Node ping */ "NodesList.NodeCell.Ping" = "Пинг"; @@ -650,7 +650,7 @@ "NodesList.NodeCell.Version" = "версия"; /* CoinsNodesList: Title */ -"CoinsNodesList.Title" = "Список монет и сервисных нод"; +"CoinsNodesList.Title" = "Ноды сервисов и монет"; /* NodesList.NodeCell: Node is disabled */ "NodesList.NodeCell.Disabled" = "Отключена"; From 8e93d403d565188b9c95327ff81797a19396fe04 Mon Sep 17 00:00:00 2001 From: StanislavDevIOS Date: Thu, 23 Nov 2023 16:10:50 +0200 Subject: [PATCH 79/96] [trello.com/c/PNNpGdAn] fix: dont present chat node name in qr screen --- Adamant/Modules/PartnerQR/PartnerQRViewModel.swift | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Adamant/Modules/PartnerQR/PartnerQRViewModel.swift b/Adamant/Modules/PartnerQR/PartnerQRViewModel.swift index 326e1ad81..acb043846 100644 --- a/Adamant/Modules/PartnerQR/PartnerQRViewModel.swift +++ b/Adamant/Modules/PartnerQR/PartnerQRViewModel.swift @@ -125,7 +125,7 @@ private extension PartnerQRViewModel { return } - let name = partner?.name ?? addressBookService.getName(for: address) + let name = addressBookService.getName(for: partner) if let name = name { partnerName = name @@ -157,7 +157,7 @@ private extension PartnerQRViewModel { var params: [AdamantAddressParam] = [] - let name = partner?.name ?? addressBookService.getName(for: address) + let name = addressBookService.getName(for: partner) if includeContactsName, let name = name { From a4f1fc38b1898b2142811bf255c6fdb3ebea189e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=80=9Cjust-software-dev=E2=80=9D?= Date: Thu, 23 Nov 2023 20:45:56 +0600 Subject: [PATCH 80/96] [trello.com/c/JC9K4Xfl] ETH synchronization --- .../Wallets/ERC20/ERC20WalletService+Send.swift | 2 +- .../Wallets/ERC20/ERC20WalletService.swift | 2 +- .../Modules/Wallets/Ethereum/EthApiService.swift | 15 +++++++++++---- .../Wallets/Ethereum/EthWalletService.swift | 2 +- Adamant/Services/HealthCheckWrapper.swift | 3 +-- 5 files changed, 15 insertions(+), 9 deletions(-) diff --git a/Adamant/Modules/Wallets/ERC20/ERC20WalletService+Send.swift b/Adamant/Modules/Wallets/ERC20/ERC20WalletService+Send.swift index f3d3c1300..270aec647 100644 --- a/Adamant/Modules/Wallets/ERC20/ERC20WalletService+Send.swift +++ b/Adamant/Modules/Wallets/ERC20/ERC20WalletService+Send.swift @@ -25,7 +25,7 @@ extension ERC20WalletService: WalletServiceTwoStepSend { throw WalletServiceError.accountNotFound } - guard let keystoreManager = erc20ApiService.keystoreManager else { + guard let keystoreManager = await erc20ApiService.keystoreManager else { throw WalletServiceError.internalError(message: "Failed to get web3.provider.KeystoreManager", error: nil) } diff --git a/Adamant/Modules/Wallets/ERC20/ERC20WalletService.swift b/Adamant/Modules/Wallets/ERC20/ERC20WalletService.swift index b85c843a8..9ef71c24a 100644 --- a/Adamant/Modules/Wallets/ERC20/ERC20WalletService.swift +++ b/Adamant/Modules/Wallets/ERC20/ERC20WalletService.swift @@ -360,7 +360,7 @@ extension ERC20WalletService: InitiatedWithPassphraseService { throw WalletServiceError.internalError(message: "ETH Wallet: failed to create Keystore", error: error) } - erc20ApiService.keystoreManager = .init([keystore]) + await erc20ApiService.setKeystoreManager(.init([keystore])) guard let ethAddress = keystore.addresses?.first else { throw WalletServiceError.internalError(message: "ETH Wallet: failed to create Keystore", error: nil) diff --git a/Adamant/Modules/Wallets/Ethereum/EthApiService.swift b/Adamant/Modules/Wallets/Ethereum/EthApiService.swift index 1a5de623b..964bd62da 100644 --- a/Adamant/Modules/Wallets/Ethereum/EthApiService.swift +++ b/Adamant/Modules/Wallets/Ethereum/EthApiService.swift @@ -11,9 +11,9 @@ import Foundation import web3swift import Web3Core -final class EthApiCore: BlockchainHealthCheckableService { +actor EthApiCore: BlockchainHealthCheckableService { let apiCore: APICoreProtocol - var keystoreManager: KeystoreManager? + private(set) var keystoreManager: KeystoreManager? func makeWeb3(node: Node) async -> WalletServiceResult { do { @@ -65,6 +65,10 @@ final class EthApiCore: BlockchainHealthCheckableService { } } + func setKeystoreManager(_ keystoreManager: KeystoreManager) { + self.keystoreManager = keystoreManager + } + init(apiCore: APICoreProtocol) { self.apiCore = apiCore } @@ -74,8 +78,7 @@ class EthApiService: WalletApiService { let api: BlockchainHealthCheckWrapper var keystoreManager: KeystoreManager? { - get { api.service.keystoreManager } - set { api.service.keystoreManager = newValue } + get async { await api.service.keystoreManager } } var preferredNodeIds: [UUID] { @@ -111,6 +114,10 @@ class EthApiService: WalletApiService { await core.getStatusInfo(node: node) } } + + func setKeystoreManager(_ keystoreManager: KeystoreManager) async { + await api.service.setKeystoreManager(keystoreManager) + } } private func mapError(_ error: Error) -> WalletServiceError { diff --git a/Adamant/Modules/Wallets/Ethereum/EthWalletService.swift b/Adamant/Modules/Wallets/Ethereum/EthWalletService.swift index bb4be4520..32944779d 100644 --- a/Adamant/Modules/Wallets/Ethereum/EthWalletService.swift +++ b/Adamant/Modules/Wallets/Ethereum/EthWalletService.swift @@ -385,7 +385,7 @@ extension EthWalletService: InitiatedWithPassphraseService { } walletStorage = .init(keystore: store) - ethApiService.keystoreManager = .init([store]) + await ethApiService.setKeystoreManager(.init([store])) } catch { throw WalletServiceError.internalError(message: "ETH Wallet: failed to create Keystore", error: error) } diff --git a/Adamant/Services/HealthCheckWrapper.swift b/Adamant/Services/HealthCheckWrapper.swift index 03c96ad6a..562e1c977 100644 --- a/Adamant/Services/HealthCheckWrapper.swift +++ b/Adamant/Services/HealthCheckWrapper.swift @@ -23,8 +23,7 @@ class HealthCheckWrapper { let service: Service let normalUpdateInterval: TimeInterval let crucialUpdateInterval: TimeInterval - - @Atomic var nodeGroup: NodeGroup + let nodeGroup: NodeGroup @Atomic var fastestNodeMode = true @Atomic var healthCheckTimerSubscription: AnyCancellable? From 58c8bff8e4241a6d088c2b6ee7bb0591171edc46 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=80=9Cjust-software-dev=E2=80=9D?= Date: Thu, 23 Nov 2023 21:21:08 +0600 Subject: [PATCH 81/96] [trello.com/c/q6fyv0yn] Error types fix --- .../Wallets/Adamant/AdmWalletService+Send.swift | 6 +++--- .../Modules/Wallets/Dash/DashWalletService+Send.swift | 6 +++--- .../Wallets/Dash/DashWalletService+Transactions.swift | 10 +++++----- 3 files changed, 11 insertions(+), 11 deletions(-) diff --git a/Adamant/Modules/Wallets/Adamant/AdmWalletService+Send.swift b/Adamant/Modules/Wallets/Adamant/AdmWalletService+Send.swift index dc5c2948b..e8db50d6e 100644 --- a/Adamant/Modules/Wallets/Adamant/AdmWalletService+Send.swift +++ b/Adamant/Modules/Wallets/Adamant/AdmWalletService+Send.swift @@ -31,9 +31,9 @@ extension AdmWalletService: WalletServiceSimpleSend { } catch let error as TransfersProviderError { throw error.asWalletServiceError() } catch { - throw WalletServiceError.internalError( - message: String.adamant.sharedErrors.unknownError, - error: nil + throw WalletServiceError.remoteServiceError( + message: error.localizedDescription, + error: error ) } } diff --git a/Adamant/Modules/Wallets/Dash/DashWalletService+Send.swift b/Adamant/Modules/Wallets/Dash/DashWalletService+Send.swift index b977a47cb..2551c52d5 100644 --- a/Adamant/Modules/Wallets/Dash/DashWalletService+Send.swift +++ b/Adamant/Modules/Wallets/Dash/DashWalletService+Send.swift @@ -24,7 +24,7 @@ extension DashWalletService: WalletServiceTwoStepSend { guard let confirmations = transaction.confirmations, confirmations >= 1 else { - throw WalletServiceError.internalError(message: "WAIT_FOR_COMPLETION", error: nil) + throw WalletServiceError.remoteServiceError(message: "WAIT_FOR_COMPLETION", error: nil) } return try await createTransaction(recipient: recipient, amount: amount) @@ -84,12 +84,12 @@ extension DashWalletService: WalletServiceTwoStepSend { lastTransactionId = transaction.txID } else if let error = response.error?.message { if error.lowercased().contains("16: tx-txlock-conflict") { - throw WalletServiceError.internalError( + throw WalletServiceError.remoteServiceError( message: String.adamant.sharedErrors.walletFrezzed, error: nil ) } else { - throw WalletServiceError.internalError(message: error, error: nil) + throw WalletServiceError.remoteServiceError(message: error, error: nil) } } else { throw WalletServiceError.internalError(message: "DASH Wallet: not valid response", error: nil) diff --git a/Adamant/Modules/Wallets/Dash/DashWalletService+Transactions.swift b/Adamant/Modules/Wallets/Dash/DashWalletService+Transactions.swift index f000178cb..da22af581 100644 --- a/Adamant/Modules/Wallets/Dash/DashWalletService+Transactions.swift +++ b/Adamant/Modules/Wallets/Dash/DashWalletService+Transactions.swift @@ -29,7 +29,7 @@ extension DashWalletService { handleTransactionResponse(id: id, .success(transaction), completion) } catch { let error = error as? WalletServiceError - let errorApi = ApiServiceError.internalError(message: error?.message ?? "", error: error) + let errorApi = ApiServiceError.serverError(error: error?.message ?? .empty) handleTransactionResponse(id: id, .failure(errorApi), completion) } } @@ -78,7 +78,7 @@ extension DashWalletService { func getBlockId(by hash: String?) async throws -> String { guard let hash = hash else { - throw ApiServiceError.internalError(message: "Hash is empty", error: nil) + throw WalletServiceError.internalError(message: "Hash is empty", error: nil) } let result: BTCRPCServerResponce = try await dashApiService.request { core, node in @@ -94,7 +94,7 @@ extension DashWalletService { if let block = result.result { return String(block.height) } else { - throw ApiServiceError.internalError(message: "DASH: Parsing block error", error: nil) + throw WalletServiceError.internalError(message: "DASH: Parsing block error", error: nil) } } @@ -119,7 +119,7 @@ extension DashWalletService { $0.asUnspentTransaction(lockScript: wallet.addressEntity.lockingScript) } } else if let error = response.error?.message { - throw WalletServiceError.internalError(message: error, error: nil) + throw WalletServiceError.remoteServiceError(message: error, error: nil) } throw WalletServiceError.internalError( @@ -183,7 +183,7 @@ extension DashWalletService { return result } - throw ApiServiceError.internalError(message: "DASH Wallet: not a valid response", error: nil) + throw WalletServiceError.internalError(message: "DASH Wallet: not a valid response", error: nil) } } From 575bd569e3b831bfcee29e5b94f177f7bbf68220 Mon Sep 17 00:00:00 2001 From: StanislavDevIOS Date: Thu, 23 Nov 2023 20:45:53 +0200 Subject: [PATCH 82/96] [trello.com/c/EdiYUEfs] fix: multiply "vibrations" row --- Adamant/Modules/Account/AccountViewController.swift | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Adamant/Modules/Account/AccountViewController.swift b/Adamant/Modules/Account/AccountViewController.swift index 1b3387cea..30f23240d 100644 --- a/Adamant/Modules/Account/AccountViewController.swift +++ b/Adamant/Modules/Account/AccountViewController.swift @@ -942,7 +942,8 @@ final class AccountViewController: FormViewController { } private func addVibrationRow() { - guard let appSection = form.sectionBy(tag: Sections.application.tag) + guard let appSection = form.sectionBy(tag: Sections.application.tag), + form.rowBy(tag: Rows.vibration.tag) == nil else { return } let vibrationRow = LabelRow { From 5f72b53d1bfec96b105f16c2176b09a1bcdc5b09 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=80=9Cjust-software-dev=E2=80=9D?= Date: Fri, 24 Nov 2023 13:17:01 +0600 Subject: [PATCH 83/96] [trello.com/c/6m6AqepK] Localization fix --- .../CommonKit/Assets/Localization/de.lproj/Localizable.strings | 2 +- .../CommonKit/Assets/Localization/en.lproj/Localizable.strings | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/CommonKit/Sources/CommonKit/Assets/Localization/de.lproj/Localizable.strings b/CommonKit/Sources/CommonKit/Assets/Localization/de.lproj/Localizable.strings index 44bdef6cf..7f7b85c0b 100644 --- a/CommonKit/Sources/CommonKit/Assets/Localization/de.lproj/Localizable.strings +++ b/CommonKit/Sources/CommonKit/Assets/Localization/de.lproj/Localizable.strings @@ -275,7 +275,7 @@ "ApiService.InternalError.ParsingFailed" = "Parsing fehlgeschlagen. Bericht senden"; /* Serious internal error: No nodes available */ -"ApiService.InternalError.NoNodesAvailable" = "No active %@ nodes. Review the Coin node list"; +"ApiService.InternalError.NoNodesAvailable" = "No active %@ nodes. Review the node list"; /* 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 9d686ce4d..e01391cfb 100644 --- a/CommonKit/Sources/CommonKit/Assets/Localization/en.lproj/Localizable.strings +++ b/CommonKit/Sources/CommonKit/Assets/Localization/en.lproj/Localizable.strings @@ -272,7 +272,7 @@ "ApiService.InternalError.ParsingFailed" = "Parsing failed. Report a bug"; /* Serious internal error: No nodes available */ -"ApiService.InternalError.NoNodesAvailable" = "No active %@ nodes. Review the Coin node list"; +"ApiService.InternalError.NoNodesAvailable" = "No active %@ nodes. Review the node list"; /* Eureka forms Cancel button */ "Cancel" = "Cancel"; From ba68c9559a4dfa1b5666e74d51881c5102f4dbfb Mon Sep 17 00:00:00 2001 From: StanislavDevIOS Date: Fri, 24 Nov 2023 09:51:08 +0200 Subject: [PATCH 84/96] [trello.com/c/FkbZOCIe] fix: remove "clock" status in transaction list --- Adamant/Modules/Wallets/TransactionTableViewCell.swift | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/Adamant/Modules/Wallets/TransactionTableViewCell.swift b/Adamant/Modules/Wallets/TransactionTableViewCell.swift index 4ea352f35..7de93bd6c 100644 --- a/Adamant/Modules/Wallets/TransactionTableViewCell.swift +++ b/Adamant/Modules/Wallets/TransactionTableViewCell.swift @@ -108,14 +108,11 @@ final class TransactionTableViewCell: UITableViewCell { } else { dateLabel.text = nil } - case .notInitiated: - dateLabel.text = TransactionDetailsViewControllerBase.awaitingValueString case .failed: dateLabel.text = TransactionStatus.failed.localized - case .pending: - dateLabel.text = TransactionStatus.pending.localized default: - dateLabel.text = TransactionDetailsViewControllerBase.awaitingValueString + dateLabel.text = TransactionStatus.pending.localized + dateLabel.textColor = TransactionStatus.pending.color } if let partnerName = transaction.partnerName { From f3ad1cbd1bdb1da52bf5ea974a385374a1a48fc5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=80=9Cjust-software-dev=E2=80=9D?= Date: Fri, 24 Nov 2023 14:16:53 +0600 Subject: [PATCH 85/96] [fix/trello.com/c/JC9K4Xfl] Fix eth core --- .../Wallets/Ethereum/EthApiService.swift | 9 ++++++--- .../CommonKit/Helpers/Result+Extension.swift | 19 ------------------- 2 files changed, 6 insertions(+), 22 deletions(-) delete mode 100644 CommonKit/Sources/CommonKit/Helpers/Result+Extension.swift diff --git a/Adamant/Modules/Wallets/Ethereum/EthApiService.swift b/Adamant/Modules/Wallets/Ethereum/EthApiService.swift index 964bd62da..2ae6bf964 100644 --- a/Adamant/Modules/Wallets/Ethereum/EthApiService.swift +++ b/Adamant/Modules/Wallets/Ethereum/EthApiService.swift @@ -28,15 +28,18 @@ actor EthApiCore: BlockchainHealthCheckableService { func performRequest( node: Node, - _ body: @escaping (_ web3: Web3) async throws -> Success + _ body: @escaping @Sendable (_ web3: Web3) async throws -> Success ) async -> WalletServiceResult { - await makeWeb3(node: node).asyncMap { web3 in + switch await makeWeb3(node: node) { + case let .success(web3): do { return .success(try await body(web3)) } catch { return .failure(mapError(error)) } - }.flatMap { $0 } + case let .failure(error): + return .failure(error) + } } func performRequest( diff --git a/CommonKit/Sources/CommonKit/Helpers/Result+Extension.swift b/CommonKit/Sources/CommonKit/Helpers/Result+Extension.swift deleted file mode 100644 index 5ff43f7ba..000000000 --- a/CommonKit/Sources/CommonKit/Helpers/Result+Extension.swift +++ /dev/null @@ -1,19 +0,0 @@ -// -// Result+Extension.swift -// -// -// Created by Andrew G on 13.11.2023. -// - -public extension Result { - func asyncMap( - _ transform: @escaping (Success) async -> NewSuccess - ) async -> Result { - switch self { - case let .success(value): - return await .success(transform(value)) - case let .failure(error): - return .failure(error) - } - } -} From 8388d46b0e08da257659aff3ef604cf2c9752723 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=80=9Cjust-software-dev=E2=80=9D?= Date: Fri, 24 Nov 2023 14:29:26 +0600 Subject: [PATCH 86/96] [fix/trello.com/c/JC9K4Xfl] Additional fix --- Adamant/Services/DataProviders/AdamantChatsProvider.swift | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/Adamant/Services/DataProviders/AdamantChatsProvider.swift b/Adamant/Services/DataProviders/AdamantChatsProvider.swift index 909720e0f..c1315c851 100644 --- a/Adamant/Services/DataProviders/AdamantChatsProvider.swift +++ b/Adamant/Services/DataProviders/AdamantChatsProvider.swift @@ -1823,8 +1823,10 @@ extension AdamantChatsProvider { func markChatAsRead(chatroom: Chatroom) { let privateContext = NSManagedObjectContext(concurrencyType: .privateQueueConcurrencyType) privateContext.parent = self.stack.container.viewContext - chatroom.markAsReaded() - try? privateContext.save() + privateContext.perform { + chatroom.markAsReaded() + try? privateContext.save() + } } private func onConnectionToTheInternetRestored() { From a388770b191c16eeadce13c58278c97af7a90d9e Mon Sep 17 00:00:00 2001 From: StanislavDevIOS Date: Fri, 24 Nov 2023 12:47:02 +0200 Subject: [PATCH 87/96] [trello.com/c/FkbZOCIe] feat: show "registered" status as "pending" --- .../Wallets/Lisk/LskTransactionsViewController.swift | 7 ++++--- Adamant/Modules/Wallets/TransactionTableViewCell.swift | 9 ++++++--- 2 files changed, 10 insertions(+), 6 deletions(-) diff --git a/Adamant/Modules/Wallets/Lisk/LskTransactionsViewController.swift b/Adamant/Modules/Wallets/Lisk/LskTransactionsViewController.swift index 0e5f94515..b9848906a 100644 --- a/Adamant/Modules/Wallets/Lisk/LskTransactionsViewController.swift +++ b/Adamant/Modules/Wallets/Lisk/LskTransactionsViewController.swift @@ -87,7 +87,8 @@ extension Transactions.TransactionModel: TransactionDetails { var transactionStatus: TransactionStatus? { guard let confirmations = confirmations, - let height = height + let height = height, + confirmations > .zero else { return .notInitiated } if confirmations < height { return .registered } @@ -97,10 +98,10 @@ extension Transactions.TransactionModel: TransactionDetails { if conf > 1 { return .success } else { - return .registered + return .pending } } - return .registered + return .notInitiated } var senderAddress: String { diff --git a/Adamant/Modules/Wallets/TransactionTableViewCell.swift b/Adamant/Modules/Wallets/TransactionTableViewCell.swift index 7de93bd6c..ed90ed456 100644 --- a/Adamant/Modules/Wallets/TransactionTableViewCell.swift +++ b/Adamant/Modules/Wallets/TransactionTableViewCell.swift @@ -108,11 +108,14 @@ final class TransactionTableViewCell: UITableViewCell { } else { dateLabel.text = nil } + case .notInitiated: + dateLabel.text = TransactionDetailsViewControllerBase.awaitingValueString case .failed: dateLabel.text = TransactionStatus.failed.localized - default: + case .pending, .registered: dateLabel.text = TransactionStatus.pending.localized - dateLabel.textColor = TransactionStatus.pending.color + default: + dateLabel.text = TransactionDetailsViewControllerBase.awaitingValueString } if let partnerName = transaction.partnerName { @@ -142,7 +145,7 @@ private extension TransactionStatus { switch self { case .failed: return .adamant.danger - case .pending: + case .pending, .registered: return .adamant.alert default: return .adamant.secondary From b5d6e5c4b7d9ff137a2c0141f0295704b8d99fac Mon Sep 17 00:00:00 2001 From: StanislavDevIOS Date: Fri, 24 Nov 2023 13:27:30 +0200 Subject: [PATCH 88/96] [trello.com/c/8xBN2FOa] fix: dont present menu for transfers in readonly chats --- Adamant/Modules/Chat/ViewModel/ChatViewModel.swift | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/Adamant/Modules/Chat/ViewModel/ChatViewModel.swift b/Adamant/Modules/Chat/ViewModel/ChatViewModel.swift index b2c122e2e..913d2e863 100644 --- a/Adamant/Modules/Chat/ViewModel/ChatViewModel.swift +++ b/Adamant/Modules/Chat/ViewModel/ChatViewModel.swift @@ -567,6 +567,11 @@ final class ChatViewModel: NSObject { let tx = chatTransactions.first(where: { $0.txId == arg.messageId }) guard tx?.statusEnum == .delivered else { return } + let amount = tx?.amountValue ?? .zero + if !amount.isZero && !isSendingAvailable { + return + } + let presentReactions = isSendingAvailable && tx?.isFake == false dialog.send( From 0767ba1e6e183d4aef21597819b2d962d5d85cee Mon Sep 17 00:00:00 2001 From: StanislavDevIOS Date: Mon, 27 Nov 2023 13:30:53 +0200 Subject: [PATCH 89/96] [trello.com/c/c1PSCmUv] fix: present fee immediately on open --- Adamant/Modules/Wallets/Ethereum/EthWalletService.swift | 8 +++----- Adamant/Services/AdamantCoinStorageService.swift | 1 + 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/Adamant/Modules/Wallets/Ethereum/EthWalletService.swift b/Adamant/Modules/Wallets/Ethereum/EthWalletService.swift index 32944779d..6b25c1858 100644 --- a/Adamant/Modules/Wallets/Ethereum/EthWalletService.swift +++ b/Adamant/Modules/Wallets/Ethereum/EthWalletService.swift @@ -687,8 +687,7 @@ extension EthWalletService { "contract_to": "eq." ] - let transactionsFrom: [EthTransactionShort] = try await ethApiService.requestApiCore { - core, node in + let transactionsFrom: [EthTransactionShort] = try await ethApiService.requestApiCore { core, node in await core.sendRequestJsonResponse( node: node, path: EthWalletService.transactionsListApiSubpath, @@ -698,8 +697,7 @@ extension EthWalletService { ) }.get() - let transactionsTo: [EthTransactionShort] = try await ethApiService.requestApiCore { - core, node in + let transactionsTo: [EthTransactionShort] = try await ethApiService.requestApiCore { core, node in await core.sendRequestJsonResponse( node: node, path: EthWalletService.transactionsListApiSubpath, @@ -737,7 +735,7 @@ extension EthWalletService { recipientAddress: transaction.to, dateValue: transaction.date, amountValue: transaction.value, - feeValue: nil, + feeValue: transaction.gasUsed * transaction.gasPrice, confirmationsValue: nil, blockValue: nil, isOutgoing: isOutgoing, diff --git a/Adamant/Services/AdamantCoinStorageService.swift b/Adamant/Services/AdamantCoinStorageService.swift index 67768591d..403be4182 100644 --- a/Adamant/Services/AdamantCoinStorageService.swift +++ b/Adamant/Services/AdamantCoinStorageService.swift @@ -70,6 +70,7 @@ final class AdamantCoinStorageService: NSObject, CoinStorageService { coinTransaction.transactionId = transaction.txId coinTransaction.transactionStatus = transaction.transactionStatus coinTransaction.blockchainType = blockchainType + coinTransaction.fee = NSDecimalNumber(decimal: transaction.feeValue ?? 0) coinTransactions.append(coinTransaction) } From 439ca835f5b6636e252f1da8b1714f118a731538 Mon Sep 17 00:00:00 2001 From: StanislavDevIOS Date: Mon, 27 Nov 2023 13:35:21 +0200 Subject: [PATCH 90/96] [trello.com/c/c1PSCmUv] fix: present fee immediately on open (erc20) --- Adamant/Modules/Wallets/ERC20/ERC20WalletService.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Adamant/Modules/Wallets/ERC20/ERC20WalletService.swift b/Adamant/Modules/Wallets/ERC20/ERC20WalletService.swift index 9ef71c24a..aee990a69 100644 --- a/Adamant/Modules/Wallets/ERC20/ERC20WalletService.swift +++ b/Adamant/Modules/Wallets/ERC20/ERC20WalletService.swift @@ -569,7 +569,7 @@ extension ERC20WalletService { recipientAddress: transaction.to, dateValue: transaction.date, amountValue: transaction.contract_value.asDecimal(exponent: exponent), - feeValue: nil, + feeValue: transaction.gasUsed * transaction.gasPrice, confirmationsValue: nil, blockValue: nil, isOutgoing: isOutgoing, From ed9e40ed21a5cdc4a6b71539a60c470dc3ff49fa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=80=9Cjust-software-dev=E2=80=9D?= Date: Mon, 27 Nov 2023 22:15:01 +0600 Subject: [PATCH 91/96] [trello.com/c/KRmMmVRY, trello.com/c/vYdIi1p2] Fixes --- .../Modules/Wallets/Ethereum/EthApiService.swift | 15 +++------------ .../Services/BlockchainHealthCheckWrapper.swift | 8 +++++--- .../DataProviders/AdamantChatsProvider.swift | 6 ++---- 3 files changed, 10 insertions(+), 19 deletions(-) diff --git a/Adamant/Modules/Wallets/Ethereum/EthApiService.swift b/Adamant/Modules/Wallets/Ethereum/EthApiService.swift index 2ae6bf964..867059ec7 100644 --- a/Adamant/Modules/Wallets/Ethereum/EthApiService.swift +++ b/Adamant/Modules/Wallets/Ethereum/EthApiService.swift @@ -11,7 +11,8 @@ import Foundation import web3swift import Web3Core -actor EthApiCore: BlockchainHealthCheckableService { +@MainActor +final class EthApiCore: BlockchainHealthCheckableService { let apiCore: APICoreProtocol private(set) var keystoreManager: KeystoreManager? @@ -42,16 +43,6 @@ actor EthApiCore: BlockchainHealthCheckableService { } } - func performRequest( - _ body: @escaping () async throws -> Success - ) async -> WalletServiceResult { - do { - return .success(try await body()) - } catch { - return .failure(mapError(error)) - } - } - func getStatusInfo(node: Node) async -> WalletServiceResult { await performRequest(node: node) { web3 in let startTimestamp = Date.now.timeIntervalSince1970 @@ -72,7 +63,7 @@ actor EthApiCore: BlockchainHealthCheckableService { self.keystoreManager = keystoreManager } - init(apiCore: APICoreProtocol) { + nonisolated init(apiCore: APICoreProtocol) { self.apiCore = apiCore } } diff --git a/Adamant/Services/BlockchainHealthCheckWrapper.swift b/Adamant/Services/BlockchainHealthCheckWrapper.swift index 0202d455c..e5733b9ec 100644 --- a/Adamant/Services/BlockchainHealthCheckWrapper.swift +++ b/Adamant/Services/BlockchainHealthCheckWrapper.swift @@ -73,9 +73,11 @@ private extension BlockchainHealthCheckWrapper { currentRequests.insert(node.id) defer { currentRequests.remove(node.id) } - switch await service.getStatusInfo(node: node) { - case let .success(statusInfo): - nodesStorage.updateNodeStatus(id: node.id, statusInfo: statusInfo) + let statusInfo = await service.getStatusInfo(node: node) + nodesStorage.updateNodeStatus(id: node.id, statusInfo: try? statusInfo.get()) + + switch statusInfo { + case .success: updateNodesAvailability(forceInclude: node.id) case let .failure(error): guard !error.isRequestCancelledError else { return } diff --git a/Adamant/Services/DataProviders/AdamantChatsProvider.swift b/Adamant/Services/DataProviders/AdamantChatsProvider.swift index c1315c851..a67fa77ce 100644 --- a/Adamant/Services/DataProviders/AdamantChatsProvider.swift +++ b/Adamant/Services/DataProviders/AdamantChatsProvider.swift @@ -1821,11 +1821,9 @@ extension AdamantChatsProvider { } func markChatAsRead(chatroom: Chatroom) { - let privateContext = NSManagedObjectContext(concurrencyType: .privateQueueConcurrencyType) - privateContext.parent = self.stack.container.viewContext - privateContext.perform { + chatroom.managedObjectContext?.perform { chatroom.markAsReaded() - try? privateContext.save() + try? chatroom.managedObjectContext?.save() } } From a2efabb3b8b2ed9e38763c8d28b731c86f30d936 Mon Sep 17 00:00:00 2001 From: StanislavDevIOS Date: Tue, 28 Nov 2023 13:57:27 +0200 Subject: [PATCH 92/96] [trello.com/c/QSPhhLjV] fix: wallets balance freezes when pull refresh --- .../Wallets/Adamant/AdmWalletService.swift | 4 +++- .../Wallets/Bitcoin/BtcWalletService.swift | 18 +++++++++-------- .../Wallets/Dash/DashWalletService.swift | 18 +++++++++-------- .../Wallets/Doge/DogeWalletService.swift | 18 +++++++++-------- .../Wallets/ERC20/ERC20WalletService.swift | 18 +++++++++-------- .../Wallets/Ethereum/EthWalletService.swift | 19 +++++++++--------- .../Wallets/Lisk/LskWalletService.swift | 20 ++++++++++--------- 7 files changed, 64 insertions(+), 51 deletions(-) diff --git a/Adamant/Modules/Wallets/Adamant/AdmWalletService.swift b/Adamant/Modules/Wallets/Adamant/AdmWalletService.swift index cd59d988f..8fddcbcb8 100644 --- a/Adamant/Modules/Wallets/Adamant/AdmWalletService.swift +++ b/Adamant/Modules/Wallets/Adamant/AdmWalletService.swift @@ -142,14 +142,16 @@ final class AdmWalletService: NSObject, WalletService { let isRaised: Bool if let wallet = wallet as? AdmWallet { - wallet.isBalanceInitialized = true isRaised = (wallet.balance < account.balance) && wallet.isBalanceInitialized if wallet.balance != account.balance { wallet.balance = account.balance notify = true + } else if wallet.isBalanceInitialized { + notify = true } else { notify = false } + wallet.isBalanceInitialized = true } else { let wallet = AdmWallet(address: account.address) wallet.isBalanceInitialized = true diff --git a/Adamant/Modules/Wallets/Bitcoin/BtcWalletService.swift b/Adamant/Modules/Wallets/Bitcoin/BtcWalletService.swift index 68e8fc78e..0613545a3 100644 --- a/Adamant/Modules/Wallets/Bitcoin/BtcWalletService.swift +++ b/Adamant/Modules/Wallets/Bitcoin/BtcWalletService.swift @@ -139,7 +139,6 @@ final class BtcWalletService: WalletService { @Atomic private(set) var btcWallet: BtcWallet? @Atomic private(set) var enabled = true @Atomic public var network: Network - @Atomic private var initialBalanceCheck = false static let jsonDecoder = JSONDecoder() @@ -215,7 +214,6 @@ final class BtcWalletService: WalletService { .receive(on: OperationQueue.main) .sink { [weak self] _ in self?.btcWallet = nil - self?.initialBalanceCheck = false if let balanceObserver = self?.balanceObserver { NotificationCenter.default.removeObserver(balanceObserver) self?.balanceObserver = nil @@ -255,22 +253,21 @@ final class BtcWalletService: WalletService { setState(.updating) if let balance = try? await getBalance() { - wallet.isBalanceInitialized = true let notification: Notification.Name? - let isRaised = (wallet.balance < balance) && !initialBalanceCheck + let isRaised = (wallet.balance < balance) && wallet.isBalanceInitialized if wallet.balance != balance { wallet.balance = balance notification = walletUpdatedNotification - initialBalanceCheck = false - } else if initialBalanceCheck { - initialBalanceCheck = false + } else if !wallet.isBalanceInitialized { notification = walletUpdatedNotification } else { notification = nil } + wallet.isBalanceInitialized = true + if isRaised { vibroService.applyVibration(.success) } @@ -413,6 +410,12 @@ extension BtcWalletService: InitiatedWithPassphraseService { let eWallet = try BtcWallet(privateKey: privateKey, addressConverter: addressConverter) self.btcWallet = eWallet + NotificationCenter.default.post( + name: walletUpdatedNotification, + object: self, + userInfo: [AdamantUserInfoKey.WalletService.wallet: eWallet] + ) + if !self.enabled { self.enabled = true NotificationCenter.default.post(name: self.serviceEnabledChanged, object: self) @@ -429,7 +432,6 @@ extension BtcWalletService: InitiatedWithPassphraseService { throw WalletServiceError.accountNotFound } - service.initialBalanceCheck = true service.setState(.upToDate, silent: true) Task { service.update() diff --git a/Adamant/Modules/Wallets/Dash/DashWalletService.swift b/Adamant/Modules/Wallets/Dash/DashWalletService.swift index f1376e784..d31b42cfa 100644 --- a/Adamant/Modules/Wallets/Dash/DashWalletService.swift +++ b/Adamant/Modules/Wallets/Dash/DashWalletService.swift @@ -117,7 +117,6 @@ final class DashWalletService: WalletService { @Atomic private (set) var dashWallet: DashWallet? @Atomic private (set) var enabled = true @Atomic public var network: Network - @Atomic private var initialBalanceCheck = false let defaultDispatchQueue = DispatchQueue(label: "im.adamant.dashWalletService", qos: .userInteractive, attributes: [.concurrent]) @@ -191,7 +190,6 @@ final class DashWalletService: WalletService { .receive(on: OperationQueue.main) .sink { [weak self] _ in self?.dashWallet = nil - self?.initialBalanceCheck = false if let balanceObserver = self?.balanceObserver { NotificationCenter.default.removeObserver(balanceObserver) self?.balanceObserver = nil @@ -231,21 +229,20 @@ final class DashWalletService: WalletService { setState(.updating) if let balance = try? await getBalance() { - wallet.isBalanceInitialized = true let notification: Notification.Name? - let isRaised = (wallet.balance < balance) && !initialBalanceCheck + let isRaised = (wallet.balance < balance) && wallet.isBalanceInitialized if wallet.balance != balance { wallet.balance = balance notification = walletUpdatedNotification - initialBalanceCheck = false - } else if initialBalanceCheck { - initialBalanceCheck = false + } else if !wallet.isBalanceInitialized { notification = walletUpdatedNotification } else { notification = nil } + wallet.isBalanceInitialized = true + if isRaised { vibroService.applyVibration(.success) } @@ -304,6 +301,12 @@ extension DashWalletService: InitiatedWithPassphraseService { self.dashWallet = eWallet + NotificationCenter.default.post( + name: walletUpdatedNotification, + object: self, + userInfo: [AdamantUserInfoKey.WalletService.wallet: eWallet] + ) + if !self.enabled { self.enabled = true NotificationCenter.default.post(name: self.serviceEnabledChanged, object: self) @@ -319,7 +322,6 @@ extension DashWalletService: InitiatedWithPassphraseService { } } - service.initialBalanceCheck = true service.setState(.upToDate, silent: true) Task { service.update() diff --git a/Adamant/Modules/Wallets/Doge/DogeWalletService.swift b/Adamant/Modules/Wallets/Doge/DogeWalletService.swift index d98c95f6e..04234b135 100644 --- a/Adamant/Modules/Wallets/Doge/DogeWalletService.swift +++ b/Adamant/Modules/Wallets/Doge/DogeWalletService.swift @@ -112,7 +112,6 @@ final class DogeWalletService: WalletService { @Atomic private(set) var dogeWallet: DogeWallet? @Atomic private(set) var enabled = true @Atomic public var network: Network - @Atomic private var initialBalanceCheck = false let defaultDispatchQueue = DispatchQueue( label: "im.adamant.dogeWalletService", @@ -189,7 +188,6 @@ final class DogeWalletService: WalletService { .receive(on: OperationQueue.main) .sink { [weak self] _ in self?.dogeWallet = nil - self?.initialBalanceCheck = false if let balanceObserver = self?.balanceObserver { NotificationCenter.default.removeObserver(balanceObserver) self?.balanceObserver = nil @@ -229,21 +227,20 @@ final class DogeWalletService: WalletService { setState(.updating) if let balance = try? await getBalance() { - wallet.isBalanceInitialized = true let notification: Notification.Name? - let isRaised = (wallet.balance < balance) && !initialBalanceCheck + let isRaised = (wallet.balance < balance) && wallet.isBalanceInitialized if wallet.balance != balance { wallet.balance = balance notification = walletUpdatedNotification - initialBalanceCheck = false - } else if initialBalanceCheck { - initialBalanceCheck = false + } else if !wallet.isBalanceInitialized { notification = walletUpdatedNotification } else { notification = nil } + wallet.isBalanceInitialized = true + if isRaised { vibroService.applyVibration(.success) } @@ -293,6 +290,12 @@ extension DogeWalletService: InitiatedWithPassphraseService { let eWallet = try DogeWallet(privateKey: privateKey, addressConverter: addressConverter) self.dogeWallet = eWallet + NotificationCenter.default.post( + name: walletUpdatedNotification, + object: self, + userInfo: [AdamantUserInfoKey.WalletService.wallet: eWallet] + ) + if !self.enabled { self.enabled = true NotificationCenter.default.post(name: self.serviceEnabledChanged, object: self) @@ -308,7 +311,6 @@ extension DogeWalletService: InitiatedWithPassphraseService { } } - service.initialBalanceCheck = true service.setState(.upToDate, silent: true) Task { diff --git a/Adamant/Modules/Wallets/ERC20/ERC20WalletService.swift b/Adamant/Modules/Wallets/ERC20/ERC20WalletService.swift index aee990a69..1c8508abc 100644 --- a/Adamant/Modules/Wallets/ERC20/ERC20WalletService.swift +++ b/Adamant/Modules/Wallets/ERC20/ERC20WalletService.swift @@ -122,7 +122,6 @@ final class ERC20WalletService: WalletService { let token: ERC20Token @Atomic private(set) var enabled = true @Atomic private var subscriptions = Set() - @Atomic private var initialBalanceCheck = false // MARK: - State @Atomic private (set) var state: WalletServiceState = .notInitiated @@ -199,7 +198,6 @@ final class ERC20WalletService: WalletService { .receive(on: OperationQueue.main) .sink { [weak self] _ in self?.ethWallet = nil - self?.initialBalanceCheck = false if let balanceObserver = self?.balanceObserver { NotificationCenter.default.removeObserver(balanceObserver) self?.balanceObserver = nil @@ -239,21 +237,20 @@ final class ERC20WalletService: WalletService { setState(.updating) if let balance = try? await getBalance(forAddress: wallet.ethAddress) { - wallet.isBalanceInitialized = true let notification: Notification.Name? - let isRaised = (wallet.balance < balance) && !initialBalanceCheck + let isRaised = (wallet.balance < balance) && wallet.isBalanceInitialized if wallet.balance != balance { wallet.balance = balance notification = walletUpdatedNotification - initialBalanceCheck = false - } else if initialBalanceCheck { - initialBalanceCheck = false + } else if !wallet.isBalanceInitialized { notification = walletUpdatedNotification } else { notification = nil } + wallet.isBalanceInitialized = true + if isRaised { vibroService.applyVibration(.success) } @@ -375,7 +372,12 @@ extension ERC20WalletService: InitiatedWithPassphraseService { NotificationCenter.default.post(name: serviceEnabledChanged, object: self) } - self.initialBalanceCheck = true + NotificationCenter.default.post( + name: walletUpdatedNotification, + object: self, + userInfo: [AdamantUserInfoKey.WalletService.wallet: eWallet] + ) + self.setState(.upToDate, silent: true) Task { await update() diff --git a/Adamant/Modules/Wallets/Ethereum/EthWalletService.swift b/Adamant/Modules/Wallets/Ethereum/EthWalletService.swift index 6b25c1858..7b75a03ed 100644 --- a/Adamant/Modules/Wallets/Ethereum/EthWalletService.swift +++ b/Adamant/Modules/Wallets/Ethereum/EthWalletService.swift @@ -139,7 +139,6 @@ final class EthWalletService: WalletService { public static let transactionsListApiSubpath = "ethtxs" @Atomic private(set) var enabled = true - @Atomic private var initialBalanceCheck = false @Atomic private var subscriptions = Set() @ObservableValue private(set) var historyTransactions: [TransactionDetails] = [] @@ -214,7 +213,6 @@ final class EthWalletService: WalletService { .receive(on: OperationQueue.main) .sink { [weak self] _ in self?.ethWallet = nil - self?.initialBalanceCheck = false if let balanceObserver = self?.balanceObserver { NotificationCenter.default.removeObserver(balanceObserver) self?.balanceObserver = nil @@ -264,23 +262,21 @@ final class EthWalletService: WalletService { setState(.updating) if let balance = try? await getBalance(forAddress: wallet.ethAddress) { - wallet.isBalanceInitialized = true - let notification: Notification.Name? - let isRaised = (wallet.balance < balance) && !initialBalanceCheck + let isRaised = (wallet.balance < balance) && wallet.isBalanceInitialized if wallet.balance != balance { wallet.balance = balance notification = walletUpdatedNotification - initialBalanceCheck = false - } else if initialBalanceCheck { - initialBalanceCheck = false + } else if !wallet.isBalanceInitialized { notification = walletUpdatedNotification } else { notification = nil } + wallet.isBalanceInitialized = true + if isRaised { vibroService.applyVibration(.success) } @@ -399,6 +395,12 @@ extension EthWalletService: InitiatedWithPassphraseService { // MARK: 3. Update ethWallet = eWallet + NotificationCenter.default.post( + name: walletUpdatedNotification, + object: self, + userInfo: [AdamantUserInfoKey.WalletService.wallet: eWallet] + ) + if !enabled { enabled = true NotificationCenter.default.post(name: serviceEnabledChanged, object: self) @@ -414,7 +416,6 @@ extension EthWalletService: InitiatedWithPassphraseService { } } - service.initialBalanceCheck = true service.setState(.upToDate, silent: true) Task { diff --git a/Adamant/Modules/Wallets/Lisk/LskWalletService.swift b/Adamant/Modules/Wallets/Lisk/LskWalletService.swift index a5b5136da..987116b73 100644 --- a/Adamant/Modules/Wallets/Lisk/LskWalletService.swift +++ b/Adamant/Modules/Wallets/Lisk/LskWalletService.swift @@ -85,7 +85,6 @@ final class LskWalletService: WalletService { let transferAvailable: Bool = true let netHash = Constants.Nethash.main - @Atomic private var initialBalanceCheck = false @Atomic private(set) var lskWallet: LskWallet? let defaultDispatchQueue = DispatchQueue( @@ -161,7 +160,6 @@ final class LskWalletService: WalletService { .receive(on: OperationQueue.main) .sink { [weak self] _ in self?.lskWallet = nil - self?.initialBalanceCheck = false if let balanceObserver = self?.balanceObserver { NotificationCenter.default.removeObserver(balanceObserver) self?.balanceObserver = nil @@ -208,22 +206,21 @@ final class LskWalletService: WalletService { } if let balance = try? await getBalance() { - let isRaised = (wallet.balance < balance) && !initialBalanceCheck - - wallet.isBalanceInitialized = true let notification: Notification.Name? + let isRaised = (wallet.balance < balance) && wallet.isBalanceInitialized + if wallet.balance != balance { wallet.balance = balance notification = walletUpdatedNotification - initialBalanceCheck = false - } else if initialBalanceCheck { - initialBalanceCheck = false + } else if !wallet.isBalanceInitialized { notification = walletUpdatedNotification } else { notification = nil } + wallet.isBalanceInitialized = true + if isRaised { vibroService.applyVibration(.success) } @@ -312,6 +309,12 @@ extension LskWalletService: InitiatedWithPassphraseService { // MARK: 3. Update let wallet = LskWallet(address: address, keyPair: keyPair, nounce: "", isNewApi: true) self.lskWallet = wallet + + NotificationCenter.default.post( + name: walletUpdatedNotification, + object: self, + userInfo: [AdamantUserInfoKey.WalletService.wallet: wallet] + ) } catch { print("\(error)") throw WalletServiceError.accountNotFound @@ -337,7 +340,6 @@ extension LskWalletService: InitiatedWithPassphraseService { } } - service.initialBalanceCheck = true service.setState(.upToDate, silent: true) Task { From 1f2d56287e8d20e2c9ff8728edcb0fbd63d48c55 Mon Sep 17 00:00:00 2001 From: StanislavDevIOS Date: Tue, 28 Nov 2023 14:00:36 +0200 Subject: [PATCH 93/96] [trello.com/c/3uD9Q0eY] update libraries --- .../xcshareddata/swiftpm/Package.resolved | 26 +++++++++---------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/Adamant.xcworkspace/xcshareddata/swiftpm/Package.resolved b/Adamant.xcworkspace/xcshareddata/swiftpm/Package.resolved index 233c8657b..88a2fda6f 100644 --- a/Adamant.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/Adamant.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -105,8 +105,8 @@ "repositoryURL": "https://github.com/google/GoogleDataTransport.git", "state": { "branch": null, - "revision": "98a00258d4518b7521253a70b7f70bb76d2120fe", - "version": "9.2.4" + "revision": "aae45a320fd0d11811820335b1eabc8753902a40", + "version": "9.2.5" } }, { @@ -114,8 +114,8 @@ "repositoryURL": "https://github.com/google/GoogleUtilities.git", "state": { "branch": null, - "revision": "4446686bc3714d49ce043d0f68318f42ed718cb6", - "version": "7.11.4" + "revision": "bc27fad73504f3d4af235de451f02ee22586ebd3", + "version": "7.12.1" } }, { @@ -159,8 +159,8 @@ "repositoryURL": "https://github.com/firebase/leveldb.git", "state": { "branch": null, - "revision": "0706abcc6b0bd9cedfbb015ba840e4a780b5159b", - "version": "1.22.2" + "revision": "9d108e9112aa1d65ce508facf804674546116d9c", + "version": "1.22.3" } }, { @@ -177,8 +177,8 @@ "repositoryURL": "https://github.com/MessageKit/MessageKit.git", "state": { "branch": null, - "revision": "14bfa7eb9f93267c3d7b8cdf58615bba27be672a", - "version": "4.1.1" + "revision": "1993e8e90d4e9a61b8d9bc8ceb733964ce943376", + "version": "4.2.0" } }, { @@ -276,7 +276,7 @@ "repositoryURL": "https://github.com/socketio/socket.io-client-swift", "state": { "branch": "master", - "revision": "a1ed825835a2d8c2555938e96557ccc05e4bebf3", + "revision": "175da8b5156f6b132436f0676cc84c2f6a766b6e", "version": null } }, @@ -285,8 +285,8 @@ "repositoryURL": "https://github.com/daltoniam/Starscream", "state": { "branch": null, - "revision": "df8d82047f6654d8e4b655d1b1525c64e1059d21", - "version": "4.0.4" + "revision": "ac6c0fc9da221873e01bd1a0d4818498a71eef33", + "version": "4.0.6" } }, { @@ -294,8 +294,8 @@ "repositoryURL": "https://github.com/apple/swift-protobuf.git", "state": { "branch": null, - "revision": "ce20dc083ee485524b802669890291c0d8090170", - "version": "1.22.1" + "revision": "07f7f26ded8df9645c072f220378879c4642e063", + "version": "1.25.1" } }, { From dce0b44320cab02c91617e0486f2d9fbb33a1517 Mon Sep 17 00:00:00 2001 From: StanislavDevIOS Date: Tue, 28 Nov 2023 14:03:25 +0200 Subject: [PATCH 94/96] [trello.com/c/3uD9Q0eY] removed our implementation of velocity --- .../Helpers/SwiftUI/DragGesture+Extension.swift | 17 ----------------- 1 file changed, 17 deletions(-) delete mode 100644 CommonKit/Sources/CommonKit/Helpers/SwiftUI/DragGesture+Extension.swift diff --git a/CommonKit/Sources/CommonKit/Helpers/SwiftUI/DragGesture+Extension.swift b/CommonKit/Sources/CommonKit/Helpers/SwiftUI/DragGesture+Extension.swift deleted file mode 100644 index e92ff8006..000000000 --- a/CommonKit/Sources/CommonKit/Helpers/SwiftUI/DragGesture+Extension.swift +++ /dev/null @@ -1,17 +0,0 @@ -// -// File.swift -// -// -// Created by Andrey Golubenko on 24.07.2023. -// - -import SwiftUI - -public extension DragGesture.Value { - var velocity: CGSize { - .init( - width: predictedEndLocation.x - location.x, - height: predictedEndLocation.y - location.y - ) - } -} From 0c26b20253ee0301295e02f05479373ead7fc1bf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=80=9Cjust-software-dev=E2=80=9D?= Date: Thu, 30 Nov 2023 05:05:25 +0600 Subject: [PATCH 95/96] [trello.com/c/H3H8SXeM] Eth crash fix --- Adamant.xcodeproj/project.pbxproj | 4 + .../Modules/Wallets/Ethereum/EthApiCore.swift | 99 +++++++++++++++++++ .../Wallets/Ethereum/EthApiService.swift | 71 ------------- ...e+RichMessageProviderWithStatusCheck.swift | 12 ++- 4 files changed, 110 insertions(+), 76 deletions(-) create mode 100644 Adamant/Modules/Wallets/Ethereum/EthApiCore.swift diff --git a/Adamant.xcodeproj/project.pbxproj b/Adamant.xcodeproj/project.pbxproj index 2b7a02368..6ff5739da 100644 --- a/Adamant.xcodeproj/project.pbxproj +++ b/Adamant.xcodeproj/project.pbxproj @@ -296,6 +296,7 @@ 93C7944E2B077C1F00408826 /* DashSendRawTransactionDTO.swift in Sources */ = {isa = PBXBuildFile; fileRef = 93C7944D2B077C1F00408826 /* DashSendRawTransactionDTO.swift */; }; 93CC8DC7296F00D6003772BF /* ChatTransactionContainerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 93CC8DC6296F00D6003772BF /* ChatTransactionContainerView.swift */; }; 93CC8DC9296F01DE003772BF /* ChatTransactionContainerView+Model.swift in Sources */ = {isa = PBXBuildFile; fileRef = 93CC8DC8296F01DE003772BF /* ChatTransactionContainerView+Model.swift */; }; + 93CC94C12B17EE73004842AC /* EthApiCore.swift in Sources */ = {isa = PBXBuildFile; fileRef = 93CC94C02B17EE73004842AC /* EthApiCore.swift */; }; 93CCAE752B06CC3600EA5B94 /* LskNodeApiService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 93CCAE742B06CC3600EA5B94 /* LskNodeApiService.swift */; }; 93CCAE772B06D6CC00EA5B94 /* LskServiceApiService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 93CCAE762B06D6CC00EA5B94 /* LskServiceApiService.swift */; }; 93CCAE792B06D81D00EA5B94 /* DogeApiService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 93CCAE782B06D81D00EA5B94 /* DogeApiService.swift */; }; @@ -920,6 +921,7 @@ 93C7944D2B077C1F00408826 /* DashSendRawTransactionDTO.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DashSendRawTransactionDTO.swift; sourceTree = ""; }; 93CC8DC6296F00D6003772BF /* ChatTransactionContainerView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChatTransactionContainerView.swift; sourceTree = ""; }; 93CC8DC8296F01DE003772BF /* ChatTransactionContainerView+Model.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "ChatTransactionContainerView+Model.swift"; sourceTree = ""; }; + 93CC94C02B17EE73004842AC /* EthApiCore.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EthApiCore.swift; sourceTree = ""; }; 93CCAE742B06CC3600EA5B94 /* LskNodeApiService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LskNodeApiService.swift; sourceTree = ""; }; 93CCAE762B06D6CC00EA5B94 /* LskServiceApiService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LskServiceApiService.swift; sourceTree = ""; }; 93CCAE782B06D81D00EA5B94 /* DogeApiService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DogeApiService.swift; sourceTree = ""; }; @@ -2175,6 +2177,7 @@ E993302121354BC300CD5200 /* EthWalletFactory.swift */, E940087A2114ED0600CD2D67 /* EthWalletService.swift */, 93FC169C2B019F440062B507 /* EthApiService.swift */, + 93CC94C02B17EE73004842AC /* EthApiCore.swift */, 4186B333294200C5006594A3 /* EthWalletService+DynamicConstants.swift */, E9AA8BF9212C166600F9249F /* EthWalletService+Send.swift */, E9FEECA52143C300007DD7C8 /* EthWalletService+RichMessageProvider.swift */, @@ -3025,6 +3028,7 @@ E9E7CD8B20026B0600DFC4DB /* AccountService.swift in Sources */, 41E3C9CC2A0E20F500AF0985 /* AdamantCoinTools.swift in Sources */, 93ADE0712ACA66AF008ED641 /* VibrationSelectionViewModel.swift in Sources */, + 93CC94C12B17EE73004842AC /* EthApiCore.swift in Sources */, 93FC169D2B019F440062B507 /* EthApiService.swift in Sources */, 9371130F2996EDA900F64CF9 /* ChatRefreshMock.swift in Sources */, 93547BCA29E2262D00B0914B /* WelcomeViewController.swift in Sources */, diff --git a/Adamant/Modules/Wallets/Ethereum/EthApiCore.swift b/Adamant/Modules/Wallets/Ethereum/EthApiCore.swift new file mode 100644 index 000000000..aa59f1e7e --- /dev/null +++ b/Adamant/Modules/Wallets/Ethereum/EthApiCore.swift @@ -0,0 +1,99 @@ +// +// EthApiCore.swift +// Adamant +// +// Created by Andrew G on 30.11.2023. +// Copyright © 2023 Adamant. All rights reserved. +// + +import CommonKit +import Foundation +import web3swift +import Web3Core + +actor EthApiCore { + let apiCore: APICoreProtocol + private(set) var keystoreManager: KeystoreManager? + private var web3Cache: [URL: Web3] = .init() + + func performRequest( + node: Node, + _ body: @escaping @Sendable (_ web3: Web3) async throws -> Success + ) async -> WalletServiceResult { + switch await getWeb3(node: node) { + case let .success(web3): + do { + return .success(try await body(web3)) + } catch { + return .failure(mapError(error)) + } + case let .failure(error): + return .failure(error) + } + } + + func setKeystoreManager(_ keystoreManager: KeystoreManager) { + self.keystoreManager = keystoreManager + web3Cache = .init() + } + + init(apiCore: APICoreProtocol) { + self.apiCore = apiCore + } +} + +extension EthApiCore: BlockchainHealthCheckableService { + func getStatusInfo(node: Node) async -> WalletServiceResult { + await performRequest(node: node) { web3 in + let startTimestamp = Date.now.timeIntervalSince1970 + let height = try await web3.eth.blockNumber() + let ping = Date.now.timeIntervalSince1970 - startTimestamp + + return .init( + ping: ping, + height: Int(height.asDouble()), + wsEnabled: false, + wsPort: nil, + version: nil + ) + } + } +} + +private extension EthApiCore { + func getWeb3(node: Node) async -> WalletServiceResult { + guard let url = node.asURL() else { + return .failure(.internalError(.endpointBuildFailed)) + } + + if let web3 = web3Cache[url] { + return .success(web3) + } + + do { + let web3 = try await Web3.new(url) + web3.addKeystoreManager(keystoreManager) + web3Cache[url] = web3 + return .success(web3) + } catch { + return .failure(.internalError( + message: error.localizedDescription, + error: error + )) + } + } +} + +private func mapError(_ error: Error) -> WalletServiceError { + if let error = error as? Web3Error { + return error.asWalletServiceError() + } else if let error = error as? ApiServiceError { + return error.asWalletServiceError() + } else if let error = error as? WalletServiceError { + return error + } else if let _ = error as? URLError { + return .networkError + } else { + return .remoteServiceError(message: error.localizedDescription) + } +} diff --git a/Adamant/Modules/Wallets/Ethereum/EthApiService.swift b/Adamant/Modules/Wallets/Ethereum/EthApiService.swift index 867059ec7..701bad3d2 100644 --- a/Adamant/Modules/Wallets/Ethereum/EthApiService.swift +++ b/Adamant/Modules/Wallets/Ethereum/EthApiService.swift @@ -11,63 +11,6 @@ import Foundation import web3swift import Web3Core -@MainActor -final class EthApiCore: BlockchainHealthCheckableService { - let apiCore: APICoreProtocol - private(set) var keystoreManager: KeystoreManager? - - func makeWeb3(node: Node) async -> WalletServiceResult { - do { - guard let url = node.asURL() else { throw InternalAPIError.endpointBuildFailed } - let web3 = try await Web3.new(url) - web3.addKeystoreManager(keystoreManager) - return .success(web3) - } catch { - return .failure(.internalError(message: error.localizedDescription, error: error)) - } - } - - func performRequest( - node: Node, - _ body: @escaping @Sendable (_ web3: Web3) async throws -> Success - ) async -> WalletServiceResult { - switch await makeWeb3(node: node) { - case let .success(web3): - do { - return .success(try await body(web3)) - } catch { - return .failure(mapError(error)) - } - case let .failure(error): - return .failure(error) - } - } - - func getStatusInfo(node: Node) async -> WalletServiceResult { - await performRequest(node: node) { web3 in - let startTimestamp = Date.now.timeIntervalSince1970 - let height = try await web3.eth.blockNumber() - let ping = Date.now.timeIntervalSince1970 - startTimestamp - - return .init( - ping: ping, - height: Int(height.asDouble()), - wsEnabled: false, - wsPort: nil, - version: nil - ) - } - } - - func setKeystoreManager(_ keystoreManager: KeystoreManager) { - self.keystoreManager = keystoreManager - } - - nonisolated init(apiCore: APICoreProtocol) { - self.apiCore = apiCore - } -} - class EthApiService: WalletApiService { let api: BlockchainHealthCheckWrapper @@ -113,17 +56,3 @@ class EthApiService: WalletApiService { await api.service.setKeystoreManager(keystoreManager) } } - -private func mapError(_ error: Error) -> WalletServiceError { - if let error = error as? Web3Error { - return error.asWalletServiceError() - } else if let error = error as? ApiServiceError { - return error.asWalletServiceError() - } else if let error = error as? WalletServiceError { - return error - } else if let _ = error as? URLError { - return .networkError - } else { - return .remoteServiceError(message: error.localizedDescription) - } -} diff --git a/Adamant/Modules/Wallets/Ethereum/EthWalletService+RichMessageProviderWithStatusCheck.swift b/Adamant/Modules/Wallets/Ethereum/EthWalletService+RichMessageProviderWithStatusCheck.swift index f0871929b..64c08e907 100644 --- a/Adamant/Modules/Wallets/Ethereum/EthWalletService+RichMessageProviderWithStatusCheck.swift +++ b/Adamant/Modules/Wallets/Ethereum/EthWalletService+RichMessageProviderWithStatusCheck.swift @@ -71,7 +71,7 @@ private extension EthWalletService { func getTransactionInfo(hash: String, web3: Web3) async throws -> EthTransactionInfo { try await withThrowingTaskGroup( of: EthTransactionInfoElement.self, - returning: EthTransactionInfo.self + returning: Atomic.self ) { group in group.addTask(priority: .userInitiated) { .details(try await web3.eth.transactionDetails(hash)) @@ -81,15 +81,17 @@ private extension EthWalletService { .receipt(try await web3.eth.transactionReceipt(hash)) } - return try await group.reduce(into: .init()) { result, value in + return try await group.reduce( + into: .init(wrappedValue: .init()) + ) { result, value in switch value { case let .receipt(receipt): - result.receipt = receipt + result.wrappedValue.receipt = receipt case let .details(details): - result.details = details + result.wrappedValue.details = details } } - } + }.wrappedValue } func getStatus( From 68facd79edf024a46bb4cd37e7270858f1a38435 Mon Sep 17 00:00:00 2001 From: StanislavDevIOS Date: Thu, 30 Nov 2023 09:51:27 +0200 Subject: [PATCH 96/96] [trello.com/c/4Xsjaskd] fix: load old transactions after re-login --- Adamant/Modules/Wallets/Bitcoin/BtcWalletService.swift | 2 ++ Adamant/Modules/Wallets/Dash/DashWalletService.swift | 2 ++ Adamant/Modules/Wallets/Doge/DogeWalletService.swift | 2 ++ Adamant/Modules/Wallets/ERC20/ERC20WalletService.swift | 2 ++ Adamant/Modules/Wallets/Ethereum/EthWalletService.swift | 2 ++ Adamant/Modules/Wallets/Lisk/LskWalletService.swift | 2 ++ 6 files changed, 12 insertions(+) diff --git a/Adamant/Modules/Wallets/Bitcoin/BtcWalletService.swift b/Adamant/Modules/Wallets/Bitcoin/BtcWalletService.swift index 0613545a3..c3e78f06c 100644 --- a/Adamant/Modules/Wallets/Bitcoin/BtcWalletService.swift +++ b/Adamant/Modules/Wallets/Bitcoin/BtcWalletService.swift @@ -219,6 +219,8 @@ final class BtcWalletService: WalletService { self?.balanceObserver = nil } self?.coinStorage.clear() + self?.hasMoreOldTransactions = true + self?.transactions = [] } .store(in: &subscriptions) } diff --git a/Adamant/Modules/Wallets/Dash/DashWalletService.swift b/Adamant/Modules/Wallets/Dash/DashWalletService.swift index d31b42cfa..3e5abd33f 100644 --- a/Adamant/Modules/Wallets/Dash/DashWalletService.swift +++ b/Adamant/Modules/Wallets/Dash/DashWalletService.swift @@ -195,6 +195,8 @@ final class DashWalletService: WalletService { self?.balanceObserver = nil } self?.coinStorage.clear() + self?.hasMoreOldTransactions = true + self?.historyTransactions = [] } .store(in: &subscriptions) } diff --git a/Adamant/Modules/Wallets/Doge/DogeWalletService.swift b/Adamant/Modules/Wallets/Doge/DogeWalletService.swift index 04234b135..a6a4d2a06 100644 --- a/Adamant/Modules/Wallets/Doge/DogeWalletService.swift +++ b/Adamant/Modules/Wallets/Doge/DogeWalletService.swift @@ -193,6 +193,8 @@ final class DogeWalletService: WalletService { self?.balanceObserver = nil } self?.coinStorage.clear() + self?.hasMoreOldTransactions = true + self?.historyTransactions = [] } .store(in: &subscriptions) } diff --git a/Adamant/Modules/Wallets/ERC20/ERC20WalletService.swift b/Adamant/Modules/Wallets/ERC20/ERC20WalletService.swift index 1c8508abc..c5fd96fc0 100644 --- a/Adamant/Modules/Wallets/ERC20/ERC20WalletService.swift +++ b/Adamant/Modules/Wallets/ERC20/ERC20WalletService.swift @@ -203,6 +203,8 @@ final class ERC20WalletService: WalletService { self?.balanceObserver = nil } self?.coinStorage.clear() + self?.hasMoreOldTransactions = true + self?.historyTransactions = [] } .store(in: &subscriptions) } diff --git a/Adamant/Modules/Wallets/Ethereum/EthWalletService.swift b/Adamant/Modules/Wallets/Ethereum/EthWalletService.swift index 7b75a03ed..9adaa8377 100644 --- a/Adamant/Modules/Wallets/Ethereum/EthWalletService.swift +++ b/Adamant/Modules/Wallets/Ethereum/EthWalletService.swift @@ -218,6 +218,8 @@ final class EthWalletService: WalletService { self?.balanceObserver = nil } self?.coinStorage.clear() + self?.hasMoreOldTransactions = true + self?.historyTransactions = [] } .store(in: &subscriptions) } diff --git a/Adamant/Modules/Wallets/Lisk/LskWalletService.swift b/Adamant/Modules/Wallets/Lisk/LskWalletService.swift index 987116b73..48be5c4e9 100644 --- a/Adamant/Modules/Wallets/Lisk/LskWalletService.swift +++ b/Adamant/Modules/Wallets/Lisk/LskWalletService.swift @@ -165,6 +165,8 @@ final class LskWalletService: WalletService { self?.balanceObserver = nil } self?.coinStorage.clear() + self?.hasMoreOldTransactions = true + self?.transactions = [] } .store(in: &subscriptions) }