From c541a1f792dd16d4c412c8a19253596e8c2fed65 Mon Sep 17 00:00:00 2001 From: Iana Silosiev Date: Thu, 11 Jul 2024 19:54:43 +0300 Subject: [PATCH 001/106] [trello.com/c/YzjugYW6] UI: Style buttons for the message list --- .../Modules/ChatsList/ChatListViewController.swift | 5 +++-- .../CommonKit/Helpers/UIHelpers/UIColor+adamant.swift | 11 +++++++++++ 2 files changed, 14 insertions(+), 2 deletions(-) diff --git a/Adamant/Modules/ChatsList/ChatListViewController.swift b/Adamant/Modules/ChatsList/ChatListViewController.swift index 62c14079f..01efc13cd 100644 --- a/Adamant/Modules/ChatsList/ChatListViewController.swift +++ b/Adamant/Modules/ChatsList/ChatListViewController.swift @@ -1066,7 +1066,8 @@ extension ChatListViewController { ) } - block.image = .asset(named: "swipe_block") + block.image = .asset(named: "swipe_block")?.withTintColor(.adamant.warning, renderingMode: .alwaysOriginal) + block.backgroundColor = .adamant.swipeBlockColor return block } @@ -1156,7 +1157,7 @@ extension ChatListViewController { } more.image = .asset(named: "swipe_more") - more.backgroundColor = .adamant.secondary + more.backgroundColor = .adamant.swipeMoreColor return more } diff --git a/CommonKit/Sources/CommonKit/Helpers/UIHelpers/UIColor+adamant.swift b/CommonKit/Sources/CommonKit/Helpers/UIHelpers/UIColor+adamant.swift index 640ae44d5..793ad78e7 100644 --- a/CommonKit/Sources/CommonKit/Helpers/UIHelpers/UIColor+adamant.swift +++ b/CommonKit/Sources/CommonKit/Helpers/UIHelpers/UIColor+adamant.swift @@ -149,6 +149,17 @@ extension UIColor { public static var tableRowIcons: UIColor { let colorWhiteTheme = UIColor(red: 0.45, green: 0.45, blue: 0.45, alpha: 1) let colorDarkTheme = UIColor(red: 0.878, green: 0.878, blue: 0.878, alpha: 1) + + /// Chat list, swipe color + public static var swipeMoreColor: UIColor { + let colorWhiteTheme = #colorLiteral(red: 0.8784313725, green: 0.8784313725, blue: 0.8784313725, alpha: 1) //E0E0E0 + let colorDarkTheme = #colorLiteral(red: 0.3294117647, green: 0.3294117647, blue: 0.3294117647, alpha: 1) //545454 + return returnColorByTheme(colorWhiteTheme: colorWhiteTheme, colorDarkTheme: colorDarkTheme) + } + + public static var swipeBlockColor: UIColor { + let colorWhiteTheme = #colorLiteral(red: 0.9254901961, green: 0.9254901961, blue: 0.9254901961, alpha: 1) //ECECEC + let colorDarkTheme = #colorLiteral(red: 0.2705882353, green: 0.2705882353, blue: 0.2705882353, alpha: 1) //474747 return returnColorByTheme(colorWhiteTheme: colorWhiteTheme, colorDarkTheme: colorDarkTheme) } From 2ef9da0cc835b9a1ba7679be05e90e85d36b5b71 Mon Sep 17 00:00:00 2001 From: Iana Silosiev Date: Sat, 13 Jul 2024 13:42:27 +0300 Subject: [PATCH 002/106] [trello.com/c/6yHWxNBw] colors refactoring --- Adamant.xcodeproj/project.pbxproj | 8 +- Adamant/Helpers/Node+UI.swift | 6 +- .../ChatTransactionContainerView.swift | 2 +- .../Delegates/AdamantDelegateCell.swift | 4 +- .../DelegatesListViewController.swift | 4 +- .../AdmWalletService+DynamicConstants.swift | 4 +- .../BtcWalletService+DynamicConstants.swift | 2 +- ...TransactionDetailsViewControllerBase.swift | 6 +- .../Wallets/TransactionTableViewCell.swift | 4 +- .../TransactionsListViewControllerBase.swift | 4 +- .../Wallets/TransferViewControllerBase.swift | 10 +- Adamant/SharedViews/ReplyView.swift | 2 +- .../Helpers/UIHelpers/UIColor+adamant.swift | 136 +++++++++--------- .../Menu/Components/AMenuViewController.swift | 2 +- 14 files changed, 99 insertions(+), 95 deletions(-) diff --git a/Adamant.xcodeproj/project.pbxproj b/Adamant.xcodeproj/project.pbxproj index 8ca9dce71..3efc0fa3a 100644 --- a/Adamant.xcodeproj/project.pbxproj +++ b/Adamant.xcodeproj/project.pbxproj @@ -2910,7 +2910,7 @@ }; 418BBB14293752F800CAB719 /* Run Script - Load wallets */ = { isa = PBXShellScriptBuildPhase; - buildActionMask = 8; + buildActionMask = 12; files = ( ); inputFileListPaths = ( @@ -2924,7 +2924,7 @@ ); outputPaths = ( ); - runOnlyForDeploymentPostprocessing = 1; + runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; shellScript = "$SCRIPT_INPUT_FILE_0 xcode\n$SCRIPT_INPUT_FILE_1 xcode\n\nrm -r $PWD/scripts\n"; }; @@ -3758,7 +3758,7 @@ "$(inherited)", "@executable_path/Frameworks", ); - MARKETING_VERSION = 3.6.1; + MARKETING_VERSION = 3.6.2; PRODUCT_BUNDLE_IDENTIFIER = "im.adamant.adamant-messenger-dev"; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; @@ -3789,7 +3789,7 @@ "$(inherited)", "@executable_path/Frameworks", ); - MARKETING_VERSION = 3.6.1; + MARKETING_VERSION = 3.6.2; PRODUCT_BUNDLE_IDENTIFIER = "im.adamant.adamant-messenger"; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; diff --git a/Adamant/Helpers/Node+UI.swift b/Adamant/Helpers/Node+UI.swift index 25a06a4c8..556981724 100644 --- a/Adamant/Helpers/Node+UI.swift +++ b/Adamant/Helpers/Node+UI.swift @@ -65,11 +65,11 @@ extension Node { switch connectionStatus { case .allowed: - return .adamant.good + return .adamant.success case .synchronizing: - return .adamant.alert + return .adamant.attention case .offline, .notAllowed: - return .adamant.danger + return .adamant.warning case .none: return .adamant.inactive } diff --git a/Adamant/Modules/Chat/View/Subviews/ChatTransaction/Container/ChatTransactionContainerView.swift b/Adamant/Modules/Chat/View/Subviews/ChatTransaction/Container/ChatTransactionContainerView.swift index 4ffd6ab26..11b3fa05c 100644 --- a/Adamant/Modules/Chat/View/Subviews/ChatTransaction/Container/ChatTransactionContainerView.swift +++ b/Adamant/Modules/Chat/View/Subviews/ChatTransaction/Container/ChatTransactionContainerView.swift @@ -278,7 +278,7 @@ private extension TransactionStatus { case .notInitiated: return .adamant.secondary case .pending, .registered, .noNetwork, .noNetworkFinal: return .adamant.primary case .success: return .adamant.active - case .failed, .inconsistent: return .adamant.alert + case .failed, .inconsistent: return .adamant.attention } } } diff --git a/Adamant/Modules/Delegates/AdamantDelegateCell.swift b/Adamant/Modules/Delegates/AdamantDelegateCell.swift index 4cadf52a5..d4f030be6 100644 --- a/Adamant/Modules/Delegates/AdamantDelegateCell.swift +++ b/Adamant/Modules/Delegates/AdamantDelegateCell.swift @@ -58,8 +58,8 @@ final class AdamantDelegateCell: UITableViewCell { var isUpvoted: Bool = false { didSet { checkmarkRowView.checkmarkImage = isUpvoted ? .asset(named: "Downvote") : .asset(named: "Upvote") - checkmarkRowView.checkmarkImageBorderColor = isUpvoted ? UIColor.adamant.good.cgColor : UIColor.adamant.secondary.cgColor - checkmarkRowView.checkmarkImageTintColor = isUpvoted ? .adamant.danger : .adamant.good + checkmarkRowView.checkmarkImageBorderColor = isUpvoted ? UIColor.adamant.success.cgColor : UIColor.adamant.secondary.cgColor + checkmarkRowView.checkmarkImageTintColor = isUpvoted ? .adamant.warning : .adamant.success } } diff --git a/Adamant/Modules/Delegates/DelegatesListViewController.swift b/Adamant/Modules/Delegates/DelegatesListViewController.swift index 1387ea5f3..4d51b3029 100644 --- a/Adamant/Modules/Delegates/DelegatesListViewController.swift +++ b/Adamant/Modules/Delegates/DelegatesListViewController.swift @@ -409,8 +409,8 @@ private extension DelegatesListViewController { let totalVoted = delegates.reduce(0) { $0 + ($1.delegate.voted ? 1 : 0) } + upvoted - downvoted let votingEnabled = changes.count > 0 && changes.count <= maxVotes && totalVoted <= maxTotalVotes - let newVotesColor = changes.count > maxVotes ? UIColor.adamant.alert : UIColor.adamant.primary - let totalVotesColor = totalVoted > maxTotalVotes ? UIColor.adamant.alert : UIColor.adamant.primary + let newVotesColor = changes.count > maxVotes ? UIColor.adamant.attention : UIColor.adamant.primary + let totalVotesColor = totalVoted > maxTotalVotes ? UIColor.adamant.attention : UIColor.adamant.primary DispatchQueue.onMainAsync { [self] in bottomPanel.model = .init( diff --git a/Adamant/Modules/Wallets/Adamant/AdmWalletService+DynamicConstants.swift b/Adamant/Modules/Wallets/Adamant/AdmWalletService+DynamicConstants.swift index f180c7e52..9cbd3ec83 100644 --- a/Adamant/Modules/Wallets/Adamant/AdmWalletService+DynamicConstants.swift +++ b/Adamant/Modules/Wallets/Adamant/AdmWalletService+DynamicConstants.swift @@ -79,9 +79,9 @@ Node(url: URL(string: "http://184.94.215.92:45555")!), Node(url: URL(string: "https://node1.adamant.business")!, altUrl: URL(string: "http://194.233.75.29:45555")), Node(url: URL(string: "https://node2.blockchain2fa.io")!), Node(url: URL(string: "https://phecda.adm.im")!, altUrl: URL(string: "http://46.250.234.248:36666")), -Node(url: URL(string: "https://tegmine.adm.im")!, altUrl: URL(string: "http://5.104.87.219:36666")), +Node(url: URL(string: "https://tegmine.adm.im")!), Node(url: URL(string: "https://tauri.adm.im")!, altUrl: URL(string: "http://154.26.159.245:36666")), -Node(url: URL(string: "https://dschubba.adm.im")!, altUrl: URL(string: "http://85.239.234.17:36666")), +Node(url: URL(string: "https://dschubba.adm.im")!), ] } diff --git a/Adamant/Modules/Wallets/Bitcoin/BtcWalletService+DynamicConstants.swift b/Adamant/Modules/Wallets/Bitcoin/BtcWalletService+DynamicConstants.swift index 732eb5bf2..130bc3953 100644 --- a/Adamant/Modules/Wallets/Bitcoin/BtcWalletService+DynamicConstants.swift +++ b/Adamant/Modules/Wallets/Bitcoin/BtcWalletService+DynamicConstants.swift @@ -76,7 +76,7 @@ extension BtcWalletService { static var nodes: [Node] { [ 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")), +Node(url: URL(string: "https://btcnode3.adamant.im")!, altUrl: URL(string: "http://195.201.242.108:44099")), ] } diff --git a/Adamant/Modules/Wallets/TransactionDetailsViewControllerBase.swift b/Adamant/Modules/Wallets/TransactionDetailsViewControllerBase.swift index 0bda84cb3..f0e3ef291 100644 --- a/Adamant/Modules/Wallets/TransactionDetailsViewControllerBase.swift +++ b/Adamant/Modules/Wallets/TransactionDetailsViewControllerBase.swift @@ -15,9 +15,9 @@ import CommonKit private extension TransactionStatus { var color: UIColor { switch self { - case .failed: return .adamant.danger - case .notInitiated, .inconsistent, .noNetwork, .noNetworkFinal, .pending, .registered: return .adamant.alert - case .success: return .adamant.good + case .failed: return .adamant.warning + case .notInitiated, .inconsistent, .noNetwork, .noNetworkFinal, .pending, .registered: return .adamant.attention + case .success: return .adamant.success } } diff --git a/Adamant/Modules/Wallets/TransactionTableViewCell.swift b/Adamant/Modules/Wallets/TransactionTableViewCell.swift index 619b4ca6b..3238d4be7 100644 --- a/Adamant/Modules/Wallets/TransactionTableViewCell.swift +++ b/Adamant/Modules/Wallets/TransactionTableViewCell.swift @@ -236,9 +236,9 @@ private extension TransactionStatus { var color: UIColor { switch self { case .failed: - return .adamant.danger + return .adamant.warning case .pending, .registered: - return .adamant.alert + return .adamant.attention default: return .adamant.secondary } diff --git a/Adamant/Modules/Wallets/TransactionsListViewControllerBase.swift b/Adamant/Modules/Wallets/TransactionsListViewControllerBase.swift index 861f2f52e..a61cec1c8 100644 --- a/Adamant/Modules/Wallets/TransactionsListViewControllerBase.swift +++ b/Adamant/Modules/Wallets/TransactionsListViewControllerBase.swift @@ -375,9 +375,9 @@ extension TransactionsListViewControllerBase: UITableViewDelegate { private extension TransactionStatus { var color: UIColor { switch self { - case .failed: return .adamant.danger + case .failed: return .adamant.warning case .notInitiated, .inconsistent, .noNetwork, .noNetworkFinal, .pending, .registered: - return .adamant.alert + return .adamant.attention case .success: return .adamant.secondary } } diff --git a/Adamant/Modules/Wallets/TransferViewControllerBase.swift b/Adamant/Modules/Wallets/TransferViewControllerBase.swift index 6b3fa1496..31a825227 100644 --- a/Adamant/Modules/Wallets/TransferViewControllerBase.swift +++ b/Adamant/Modules/Wallets/TransferViewControllerBase.swift @@ -648,7 +648,7 @@ class TransferViewControllerBase: FormViewController { func markRow(_ row: BaseRowType, valid: Bool) { row.baseCell.textLabel?.textColor = valid ? getBaseColor(for: row.tag) - : UIColor.adamant.alert + : UIColor.adamant.attention } func getBaseColor(for tag: String?) -> UIColor { @@ -669,13 +669,13 @@ class TransferViewControllerBase: FormViewController { } if let label = recipientSection.header?.viewForSection(recipientSection, type: .header), let label = label as? UILabel { - label.textColor = isValid ? UIColor.adamant.primary : UIColor.adamant.alert + label.textColor = isValid ? UIColor.adamant.primary : UIColor.adamant.attention } - recipientRow.cell.textField.textColor = isValid ? UIColor.adamant.primary : UIColor.adamant.alert + recipientRow.cell.textField.textColor = isValid ? UIColor.adamant.primary : UIColor.adamant.attention recipientRow.cell.textField.leftView?.subviews.forEach { view in guard let label = view as? UILabel else { return } - label.textColor = isValid ? UIColor.adamant.primary : UIColor.adamant.alert + label.textColor = isValid ? UIColor.adamant.primary : UIColor.adamant.attention } } @@ -1135,7 +1135,7 @@ extension TransferViewControllerBase { $0.disabled = true $0.title = BaseRows.fee.localized + estimateSymbol $0.cell.titleLabel.textColor = .adamant.active - $0.cell.secondDetailsLabel.textColor = .adamant.alert + $0.cell.secondDetailsLabel.textColor = .adamant.attention $0.value = self?.getCellFeeValue() } diff --git a/Adamant/SharedViews/ReplyView.swift b/Adamant/SharedViews/ReplyView.swift index c2f8cd6a0..b9e2be8e6 100644 --- a/Adamant/SharedViews/ReplyView.swift +++ b/Adamant/SharedViews/ReplyView.swift @@ -52,7 +52,7 @@ final class ReplyView: UIView { private lazy var closeBtn: UIButton = { let btn = UIButton() btn.setImage( - UIImage(systemName: "xmark")?.withTintColor(.adamant.alert), + UIImage(systemName: "xmark")?.withTintColor(.adamant.attention), for: .normal ) btn.addTarget(self, action: #selector(didTapCloseBtn), for: .touchUpInside) diff --git a/CommonKit/Sources/CommonKit/Helpers/UIHelpers/UIColor+adamant.swift b/CommonKit/Sources/CommonKit/Helpers/UIHelpers/UIColor+adamant.swift index 640ae44d5..89809333b 100644 --- a/CommonKit/Sources/CommonKit/Helpers/UIHelpers/UIColor+adamant.swift +++ b/CommonKit/Sources/CommonKit/Helpers/UIHelpers/UIColor+adamant.swift @@ -21,18 +21,15 @@ extension UIColor { } // MARK: Colors from Adamant Guideline - public static let first = UIColor(hex: "#474a5f") - public static let fourth = UIColor(hex: "#eeeeee") - - public static let active = UIColor(red: 0.0901961, green: 0.611765, blue: 0.92549, alpha: 1) - public static let alert = UIColor(hex: "#faa05a") - public static let good = UIColor(hex: "#32d296") - public static let danger = UIColor(hex: "#f0506e") - public static let inactive = UIColor(hex: "#6d6f72") + public static let active = #colorLiteral(red: 0.09019607843, green: 0.6117647059, blue: 0.9215686275, alpha: 1) //#179CEB + public static let attention = #colorLiteral(red: 0.9902971387, green: 0.6896653175, blue: 0.4256819189, alpha: 1) //#faa05a + public static let success = #colorLiteral(red: 0.2102436721, green: 0.8444728255, blue: 0.6537195444, alpha: 1) //#32d296 + public static let warning = #colorLiteral(red: 0.9622407556, green: 0.4130832553, blue: 0.5054324269, alpha: 1) //#f0506e + public static let inactive = #colorLiteral(red: 0.5025414228, green: 0.5106091499, blue: 0.5218499899, alpha: 1) //#6d6f72 public static var background: UIColor { - let colorWhiteTheme = UIColor(hex: "#f2f6fa") - let colorDarkTheme = UIColor(hex: "#1c1c1c") + let colorWhiteTheme = #colorLiteral(red: 0.9590962529, green: 0.9721178412, blue: 0.9845080972, alpha: 1) //f2f6fa + let colorDarkTheme = #colorLiteral(red: 0.1462407112, green: 0.1462407112, blue: 0.1462407112, alpha: 1) //1c1c1c return returnColorByTheme(colorWhiteTheme: colorWhiteTheme, colorDarkTheme: colorDarkTheme) } @@ -40,32 +37,32 @@ extension UIColor { /// Income Arrow View Background Color public static var incomeArrowBackgroundColor: UIColor { - return UIColor(hex: "36C436") + return #colorLiteral(red: 0.2381577492, green: 0.7938874364, blue: 0.2725245357, alpha: 1) //36C436 } /// Outcome Arrow View Background Color public static var outcomeArrowBackgroundColor: UIColor { - return UIColor(hex: "F44444") + return #colorLiteral(red: 0.9752754569, green: 0.3635693789, blue: 0.3339065611, alpha: 1) //F44444 } /// Default background color public static var backgroundColor: UIColor { let colorWhiteTheme = UIColor.white - let colorDarkTheme = UIColor(hex: "#212121") + let colorDarkTheme = #colorLiteral(red: 0.1726317406, green: 0.1726317406, blue: 0.1726317406, alpha: 1) //212121 return returnColorByTheme(colorWhiteTheme: colorWhiteTheme, colorDarkTheme: colorDarkTheme) } /// Second default background color public static var secondBackgroundColor: UIColor { - let colorWhiteTheme = UIColor(hex: "#f2f1f6") - let colorDarkTheme = UIColor(hex: "#212121") + let colorWhiteTheme = #colorLiteral(red: 0.9594989419, green: 0.956831634, blue: 0.9719926715, alpha: 1) //f2f1f6 + let colorDarkTheme = #colorLiteral(red: 0.1725490196, green: 0.1725490196, blue: 0.1725490196, alpha: 1) //2C2C2C return returnColorByTheme(colorWhiteTheme: colorWhiteTheme, colorDarkTheme: colorDarkTheme) } /// Welcome background color public static var welcomeBackgroundColor: UIColor { let colorWhiteTheme = UIColor(patternImage: .asset(named: "stripeBg") ?? .init()) - let colorDarkTheme = UIColor(hex: "#212121") + let colorDarkTheme = #colorLiteral(red: 0.1294117647, green: 0.1294117647, blue: 0.1294117647, alpha: 1) //212121 return returnColorByTheme(colorWhiteTheme: colorWhiteTheme, colorDarkTheme: colorDarkTheme) } /// Default text color @@ -78,91 +75,104 @@ extension UIColor { /// Default cell alert text color public static var cellAlertTextColor: UIColor { let colorWhiteTheme = UIColor.white - let colorDarkTheme = UIColor(hex: "#212121") + let colorDarkTheme = #colorLiteral(red: 0.1294117647, green: 0.1294117647, blue: 0.1294117647, alpha: 1) //212121 return returnColorByTheme(colorWhiteTheme: colorWhiteTheme, colorDarkTheme: colorDarkTheme) } /// Default cell color public static var cellColor: UIColor { let colorWhiteTheme = UIColor.white - let colorDarkTheme = UIColor(hex: "#1c1c1d") + let colorDarkTheme = #colorLiteral(red: 0.1098039216, green: 0.1098039216, blue: 0.1137254902, alpha: 1) //1c1c1d return returnColorByTheme(colorWhiteTheme: colorWhiteTheme, colorDarkTheme: colorDarkTheme) } /// Code block background color public static var codeBlock: UIColor { - let colorWhiteTheme = UIColor(red: 0.29, green: 0.29, blue: 0.29, alpha: 0.1) - let colorDarkTheme = UIColor(hex: "#2a2a2b") + let colorWhiteTheme = UIColor(hex: "#4a4a4a").withAlphaComponent(0.1) + let colorDarkTheme = #colorLiteral(red: 0.1647058824, green: 0.1647058824, blue: 0.168627451, alpha: 1) //2a2a2b return returnColorByTheme(colorWhiteTheme: colorWhiteTheme, colorDarkTheme: colorDarkTheme) } /// Code block text color public static var codeBlockText: UIColor { - let colorWhiteTheme = UIColor(red: 0.32, green: 0.32, blue: 0.32, alpha: 1) + let colorWhiteTheme = #colorLiteral(red: 0.3215686275, green: 0.3215686275, blue: 0.3215686275, alpha: 1) //525252 let colorDarkTheme = UIColor.white.withAlphaComponent(0.8) return returnColorByTheme(colorWhiteTheme: colorWhiteTheme, colorDarkTheme: colorDarkTheme) } /// Reactions background color public static var reactionsBackground: 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 colorWhiteTheme = #colorLiteral(red: 0.9490196078, green: 0.9490196078, blue: 0.9490196078, alpha: 1) //F2F2F2 + let colorDarkTheme = #colorLiteral(red: 0.262745098, green: 0.262745098, blue: 0.262745098, alpha: 1) //434343 return returnColorByTheme(colorWhiteTheme: colorWhiteTheme, colorDarkTheme: colorDarkTheme) } /// More reactions background button color public static var moreReactionsBackground: UIColor { let colorWhiteTheme = UIColor.white - let colorDarkTheme = UIColor(red: 0.22, green: 0.22, blue: 0.22, alpha: 1) + let colorDarkTheme = #colorLiteral(red: 0.2196078431, green: 0.2196078431, blue: 0.2196078431, alpha: 1) //383838 return returnColorByTheme(colorWhiteTheme: colorWhiteTheme, colorDarkTheme: colorDarkTheme) } /// Picked reaction background color public static var pickedReactionBackground: UIColor { - let colorWhiteTheme = UIColor(red: 0.92, green: 0.925, blue: 0.93, alpha: 0.85) - let colorDarkTheme = UIColor(red: 0.329, green: 0.329, blue: 0.329, alpha: 1.0) + let colorWhiteTheme = UIColor(hex: "#EBECED").withAlphaComponent(0.85) + let colorDarkTheme = #colorLiteral(red: 0.3294117647, green: 0.3294117647, blue: 0.3294117647, alpha: 1) //545454 return returnColorByTheme(colorWhiteTheme: colorWhiteTheme, colorDarkTheme: colorDarkTheme) } /// Main dark gray, ~70% gray public static var primary: UIColor { - let colorWhiteTheme = UIColor(red: 0.29, green: 0.29, blue: 0.29, alpha: 1) + let colorWhiteTheme = #colorLiteral(red: 0.2901960784, green: 0.2901960784, blue: 0.2901960784, alpha: 1) //4A4A4A let colorDarkTheme = UIColor.white return returnColorByTheme(colorWhiteTheme: colorWhiteTheme, colorDarkTheme: colorDarkTheme) } /// Secondary color, ~50% gray public static var secondary: UIColor { - let colorWhiteTheme = UIColor(red: 0.478, green: 0.478, blue: 0.478, alpha: 1) - let colorDarkTheme = UIColor(red: 0.878, green: 0.878, blue: 0.878, alpha: 1) + let colorWhiteTheme = #colorLiteral(red: 0.4784313725, green: 0.4784313725, blue: 0.4784313725, alpha: 1) //7A7A7A + let colorDarkTheme = #colorLiteral(red: 0.8784313725, green: 0.8784313725, blue: 0.8784313725, alpha: 1) //E0E0E0 return returnColorByTheme(colorWhiteTheme: colorWhiteTheme, colorDarkTheme: colorDarkTheme) } /// Chat icons color, ~40% gray public static var chatIcons: UIColor { - let colorWhiteTheme = UIColor(red: 0.62, green: 0.62, blue: 0.62, alpha: 1) - let colorDarkTheme = UIColor(red: 0.278, green: 0.278, blue: 0.278, alpha: 1) + let colorWhiteTheme = #colorLiteral(red: 0.6196078431, green: 0.6196078431, blue: 0.6196078431, alpha: 1) //9E9E9E + let colorDarkTheme = #colorLiteral(red: 0.2784313725, green: 0.2784313725, blue: 0.2784313725, alpha: 1) //474747 return returnColorByTheme(colorWhiteTheme: colorWhiteTheme, colorDarkTheme: colorDarkTheme) } /// Table row icons color, ~45% gray public static var tableRowIcons: UIColor { - let colorWhiteTheme = UIColor(red: 0.45, green: 0.45, blue: 0.45, alpha: 1) - let colorDarkTheme = UIColor(red: 0.878, green: 0.878, blue: 0.878, alpha: 1) + let colorWhiteTheme = #colorLiteral(red: 0.4509803922, green: 0.4509803922, blue: 0.4509803922, alpha: 1) //737373 + let colorDarkTheme = #colorLiteral(red: 0.8784313725, green: 0.8784313725, blue: 0.8784313725, alpha: 1) //E0E0E0 + return returnColorByTheme(colorWhiteTheme: colorWhiteTheme, colorDarkTheme: colorDarkTheme) + } + + /// Chat list, swipe color + public static var swipeMoreColor: UIColor { + let colorWhiteTheme = #colorLiteral(red: 0.8784313725, green: 0.8784313725, blue: 0.8784313725, alpha: 1) //E0E0E0 + let colorDarkTheme = #colorLiteral(red: 0.3294117647, green: 0.3294117647, blue: 0.3294117647, alpha: 1) //545454 + return returnColorByTheme(colorWhiteTheme: colorWhiteTheme, colorDarkTheme: colorDarkTheme) + } + + public static var swipeBlockColor: UIColor { + let colorWhiteTheme = #colorLiteral(red: 0.9254901961, green: 0.9254901961, blue: 0.9254901961, alpha: 1) //ECECEC + let colorDarkTheme = #colorLiteral(red: 0.2705882353, green: 0.2705882353, blue: 0.2705882353, alpha: 1) //474747 return returnColorByTheme(colorWhiteTheme: colorWhiteTheme, colorDarkTheme: colorDarkTheme) } /// Switch onTintColor public static var switchColor: UIColor { - let colorWhiteTheme = UIColor(hex: "#179cec") - let colorDarkTheme = UIColor(hex: "#05456b") + let colorWhiteTheme = #colorLiteral(red: 0.09019607843, green: 0.6117647059, blue: 0.9254901961, alpha: 1) //179cec + let colorDarkTheme = #colorLiteral(red: 0.01960784314, green: 0.2705882353, blue: 0.4196078431, alpha: 1) //05456b return returnColorByTheme(colorWhiteTheme: colorWhiteTheme, colorDarkTheme: colorDarkTheme) } /// Secondary color, ~50% gray public static var errorOkButton: UIColor { - let colorWhiteTheme = UIColor(red: 0.478, green: 0.478, blue: 0.478, alpha: 1) - let colorDarkTheme = UIColor(red: 0.31, green: 0.31, blue: 0.31, alpha: 1) + let colorWhiteTheme = #colorLiteral(red: 0.4784313725, green: 0.4784313725, blue: 0.4784313725, alpha: 1) //7A7A7A + let colorDarkTheme = #colorLiteral(red: 0.3098039216, green: 0.3098039216, blue: 0.3098039216, alpha: 1) //4F4F4F return returnColorByTheme(colorWhiteTheme: colorWhiteTheme, colorDarkTheme: colorDarkTheme) } @@ -170,82 +180,76 @@ extension UIColor { /// User chat bubble background, ~4% gray public static var chatRecipientBackground: UIColor { - let colorWhiteTheme = UIColor(red: 0.965, green: 0.973, blue: 0.981, alpha: 1) - let colorDarkTheme = UIColor(red: 0.27, green: 0.27, blue: 0.27, alpha: 1) + let colorWhiteTheme = #colorLiteral(red: 0.9647058824, green: 0.9725490196, blue: 0.9843137255, alpha: 1) //F6F8FB + let colorDarkTheme = #colorLiteral(red: 0.2705882353, green: 0.2705882353, blue: 0.2705882353, alpha: 1) //454545 return returnColorByTheme(colorWhiteTheme: colorWhiteTheme, colorDarkTheme: colorDarkTheme) } public static var pendingChatBackground: UIColor { let colorWhiteTheme = UIColor(white: 0.98, alpha: 1.0) - let colorDarkTheme = UIColor(red: 0.42, green: 0.42, blue: 0.42, alpha: 1) + let colorDarkTheme = #colorLiteral(red: 0.4196078431, green: 0.4196078431, blue: 0.4196078431, alpha: 1) //6B6B6B return returnColorByTheme(colorWhiteTheme: colorWhiteTheme, colorDarkTheme: colorDarkTheme) } public static var failChatBackground: UIColor { let colorWhiteTheme = UIColor(white: 0.8, alpha: 1.0) - let colorDarkTheme = UIColor(red: 0.46, green: 0.46, blue: 0.46, alpha: 1) + let colorDarkTheme = #colorLiteral(red: 0.4588235294, green: 0.4588235294, blue: 0.4588235294, alpha: 1) //757575 return returnColorByTheme(colorWhiteTheme: colorWhiteTheme, colorDarkTheme: colorDarkTheme) } /// Partner chat bubble background, ~8% gray public static var chatSenderBackground: UIColor { - let colorWhiteTheme = UIColor(red: 0.925, green: 0.925, blue: 0.925, alpha: 1) - let colorDarkTheme = UIColor(red: 0.21, green: 0.21, blue: 0.21, alpha: 1) + let colorWhiteTheme = #colorLiteral(red: 0.9254901961, green: 0.9254901961, blue: 0.9254901961, alpha: 1) //ECECEC + let colorDarkTheme = #colorLiteral(red: 0.2117647059, green: 0.2117647059, blue: 0.2117647059, alpha: 1) //363636 return returnColorByTheme(colorWhiteTheme: colorWhiteTheme, colorDarkTheme: colorDarkTheme) } /// Partner chat bubble background, ~8% gray public static var chatInputBarBackground: UIColor { - let colorWhiteTheme = UIColor(red: 247/255, green: 247/255, blue: 247/255, alpha: 1.0) - let colorDarkTheme = UIColor(red: 0.20, green: 0.20, blue: 0.20, alpha: 1) + let colorWhiteTheme = #colorLiteral(red: 0.968627451, green: 0.968627451, blue: 0.968627451, alpha: 1) //F7F7F7 + let colorDarkTheme = #colorLiteral(red: 0.2, green: 0.2, blue: 0.2, alpha: 1) //333333 return returnColorByTheme(colorWhiteTheme: colorWhiteTheme, colorDarkTheme: colorDarkTheme) } /// InputBar field background, ~8% gray public static var chatInputFieldBarBackground: UIColor { let colorWhiteTheme = UIColor.white - let colorDarkTheme = UIColor(hex: "#212121") + let colorDarkTheme = #colorLiteral(red: 0.1294117647, green: 0.1294117647, blue: 0.1294117647, alpha: 1) //212121 return returnColorByTheme(colorWhiteTheme: colorWhiteTheme, colorDarkTheme: colorDarkTheme) } /// Border colors for readOnly mode public static var disableBorderColor: UIColor { - let colorWhiteTheme = UIColor(hex: "#B0B0B0") - let colorDarkTheme = UIColor(hex: "#878787") + let colorWhiteTheme = #colorLiteral(red: 0.6901960784, green: 0.6901960784, blue: 0.6901960784, alpha: 1) //B0B0B0 + let colorDarkTheme = #colorLiteral(red: 0.5294117647, green: 0.5294117647, blue: 0.5294117647, alpha: 1) //878787 return returnColorByTheme(colorWhiteTheme: colorWhiteTheme, colorDarkTheme: colorDarkTheme) } - public static let chatInputBarBorderColor = UIColor(red: 200/255, green: 200/255, blue: 200/255, alpha: 1) + public static let chatInputBarBorderColor = #colorLiteral(red: 0.7843137255, green: 0.7843137255, blue: 0.7843137255, alpha: 1) //C8C8C8 /// Color of input bar placeholder - public static let chatPlaceholderTextColor = UIColor(red: 0.6, green: 0.6, blue: 0.6, alpha: 1) + public static let chatPlaceholderTextColor = #colorLiteral(red: 0.6, green: 0.6, blue: 0.6, alpha: 1) //999999 // MARK: Context Menu public static var contextMenuLineColor: UIColor { - let colorWhiteTheme = UIColor(red: 0.75, green: 0.75, blue: 0.75, alpha: 0.8) - let colorDarkTheme = UIColor(red: 0.50, green: 0.50, blue: 0.50, alpha: 0.8) + let colorWhiteTheme = UIColor(hex: "#BFBFBF").withAlphaComponent(0.8) + let colorDarkTheme = UIColor(hex: "#808080").withAlphaComponent(0.8) return returnColorByTheme(colorWhiteTheme: colorWhiteTheme, colorDarkTheme: colorDarkTheme) } public static var contextMenuSelectColor: UIColor { let colorWhiteTheme = UIColor.black.withAlphaComponent(0.10) - let colorDarkTheme = UIColor(red: 0.214, green: 0.214, blue: 0.214, alpha: 1) + let colorDarkTheme = #colorLiteral(red: 0.2156862745, green: 0.2156862745, blue: 0.2156862745, alpha: 1) //#373737 return returnColorByTheme(colorWhiteTheme: colorWhiteTheme, colorDarkTheme: colorDarkTheme) } 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 colorWhiteTheme = #colorLiteral(red: 0.9490196078, green: 0.9490196078, blue: 0.9490196078, alpha: 1) //#F2F2F2 + let colorDarkTheme = #colorLiteral(red: 0.262745098, green: 0.262745098, blue: 0.262745098, alpha: 1) //#434343 return returnColorByTheme(colorWhiteTheme: colorWhiteTheme, colorDarkTheme: colorDarkTheme) } - - public static var contextMenuTextColor: UIColor { - let colorWhiteTheme = UIColor.black - let colorDarkTheme = UIColor.white - return returnColorByTheme(colorWhiteTheme: colorWhiteTheme, colorDarkTheme: colorDarkTheme) - } - + public static var contextMenuOverlayMacColor: UIColor { let colorWhiteTheme = UIColor.black.withAlphaComponent(0.3) let colorDarkTheme = UIColor.white.withAlphaComponent(0.3) @@ -253,18 +257,18 @@ extension UIColor { } public static var contextMenuDestructive: UIColor { - UIColor(red: 1, green: 0.2196078431, blue: 0.137254902, alpha: 1) + #colorLiteral(red: 1, green: 0.2196078431, blue: 0.137254902, alpha: 1) //#FF3823 } // MARK: Pinpad /// Pinpad highligh button background, 12% gray - public static let pinpadHighlightButton = UIColor(red: 0.88, green: 0.88, blue: 0.88, alpha: 1) + public static let pinpadHighlightButton = #colorLiteral(red: 0.8823529412, green: 0.8823529412, blue: 0.8823529412, alpha: 1) //#E1E1E1 // MARK: Transfers /// Income transfer icon background, light green - public static let transferIncomeIconBackground = UIColor(red: 0.7, green: 0.93, blue: 0.55, alpha: 1) + public static let transferIncomeIconBackground = #colorLiteral(red: 0.7019607843, green: 0.9294117647, blue: 0.5490196078, alpha: 1) //#B3ED8C // Outcome transfer icon background, light red - public static let transferOutcomeIconBackground = UIColor(red: 0.94, green: 0.52, blue: 0.53, alpha: 1) + public static let transferOutcomeIconBackground = #colorLiteral(red: 0.9411764706, green: 0.5215686275, blue: 0.5294117647, alpha: 1) //#F08587 } } 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 95b8b354c..beda64862 100644 --- a/pckg/AdvancedContextMenuKit/Sources/AdvancedContextMenuKit/Implementation/Views/Menu/Components/AMenuViewController.swift +++ b/pckg/AdvancedContextMenuKit/Sources/AdvancedContextMenuKit/Implementation/Views/Menu/Components/AMenuViewController.swift @@ -246,7 +246,7 @@ extension AMenuViewController: UITableViewDelegate, UITableViewDataSource { cell.configure( with: menuItem, - accentColor: .adamant.contextMenuTextColor, + accentColor: .adamant.textColor, backgroundColor: .adamant.contextMenuDefaultBackgroundColor, font: font, rowPosition: rowPosition From 6aae85e5f8f5d7ac3671c1fd76ad2bffe16c774d Mon Sep 17 00:00:00 2001 From: Iana Silosiev Date: Sat, 13 Jul 2024 13:50:13 +0300 Subject: [PATCH 003/106] [trello.com/c/GJ5CEZSz] fix: dates not update --- .../Modules/Chat/View/ChatViewController.swift | 1 + .../Modules/Chat/ViewModel/ChatViewModel.swift | 14 +++++++++++++- .../ChatsList/ChatListViewController.swift | 16 ++++++++++++++++ 3 files changed, 30 insertions(+), 1 deletion(-) diff --git a/Adamant/Modules/Chat/View/ChatViewController.swift b/Adamant/Modules/Chat/View/ChatViewController.swift index def74b149..68b054970 100644 --- a/Adamant/Modules/Chat/View/ChatViewController.swift +++ b/Adamant/Modules/Chat/View/ChatViewController.swift @@ -333,6 +333,7 @@ private extension ChatViewController { .sink { [weak self] in if $0 { self?.updatingIndicatorView.startAnimate() + self?.viewModel.refreshDateHeadersIfNeeded() } else { self?.updatingIndicatorView.stopAnimate() } diff --git a/Adamant/Modules/Chat/ViewModel/ChatViewModel.swift b/Adamant/Modules/Chat/ViewModel/ChatViewModel.swift index 4f81b7fdb..ee61e53b3 100644 --- a/Adamant/Modules/Chat/ViewModel/ChatViewModel.swift +++ b/Adamant/Modules/Chat/ViewModel/ChatViewModel.swift @@ -62,7 +62,8 @@ final class ChatViewModel: NSObject { private let partnerImageSize: CGFloat = 25 private let maxMessageLenght: Int = 10000 private var previousArg: ChatContextMenuArguments? - + private var lastDateHeaderUpdate: Date = Date() + let minIndexForStartLoadNewMessages = 4 let minOffsetForStartLoadNewMessages: CGFloat = 100 var tempOffsets: [String] = [] @@ -612,6 +613,17 @@ final class ChatViewModel: NSObject { return true } + + /// If the user opens the app from the background + /// update messages to refresh the header dates. + func refreshDateHeadersIfNeeded() { + guard !Calendar.current.isDate(Date(), inSameDayAs: lastDateHeaderUpdate) else { + return + } + + lastDateHeaderUpdate = Date() + updateMessages(resetLoadingProperty: false) + } } extension ChatViewModel { diff --git a/Adamant/Modules/ChatsList/ChatListViewController.swift b/Adamant/Modules/ChatsList/ChatListViewController.swift index 62c14079f..fea59ded1 100644 --- a/Adamant/Modules/ChatsList/ChatListViewController.swift +++ b/Adamant/Modules/ChatsList/ChatListViewController.swift @@ -127,6 +127,7 @@ final class ChatListViewController: KeyboardObservingViewController { private var onMessagesLoadedActions = [() -> Void]() private var areMessagesLoaded = false + private var lastDatesUpdate: Date = Date() // MARK: Tasks @@ -321,9 +322,24 @@ final class ChatListViewController: KeyboardObservingViewController { if case .updating = newState { updatingIndicatorView.startAnimate() + refreshDatesIfNeeded() } } + /// If the user opens the app from the background and new chats are not loaded, + /// update specific rows in the tableView to refresh the dates. + private func refreshDatesIfNeeded() { + guard !Calendar.current.isDate(Date(), inSameDayAs: lastDatesUpdate), + !isBusy, + let indexPaths = tableView.indexPathsForVisibleRows + else { + return + } + + lastDatesUpdate = Date() + tableView.reloadRows(at: indexPaths, with: .none) + } + private func updateChats() { guard accountService.account?.address != nil, accountService.keypair?.privateKey != nil From c6ba3f123390297675167390f66be5c37f458f73 Mon Sep 17 00:00:00 2001 From: Iana Silosiev Date: Fri, 19 Jul 2024 12:04:17 +0300 Subject: [PATCH 004/106] [trello.com/c/PsjTT4AX] Added showing 6 quick emojis --- Adamant/Modules/Chat/View/Helpers/ChatReactionsView.swift | 6 +++--- Adamant/Modules/Chat/View/Managers/ChatDialogManager.swift | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/Adamant/Modules/Chat/View/Helpers/ChatReactionsView.swift b/Adamant/Modules/Chat/View/Helpers/ChatReactionsView.swift index ea402d3f5..1e9eeca8a 100644 --- a/Adamant/Modules/Chat/View/Helpers/ChatReactionsView.swift +++ b/Adamant/Modules/Chat/View/Helpers/ChatReactionsView.swift @@ -17,7 +17,7 @@ protocol ChatReactionsViewDelegate: AnyObject { struct ChatReactionsView: View { private let emojis: [String] - private let defaultEmojis = ["😂", "🤔", "😁", "👍", "👌"] + private let defaultEmojis = ["😂", "🤔", "😁", "👍", "👌", "🤝"] private let selectedEmoji: String? private let messageId: String @@ -37,8 +37,8 @@ struct ChatReactionsView: View { var body: some View { HStack(spacing: 10) { ScrollView(.horizontal, showsIndicators: false) { - HStack(spacing: 10) { - ForEach(emojis, id: \.self) { emoji in + HStack(spacing: 5) { + ForEach(emojis.prefix(6), id: \.self) { emoji in ChatReactionButton( emoji: emoji ) diff --git a/Adamant/Modules/Chat/View/Managers/ChatDialogManager.swift b/Adamant/Modules/Chat/View/Managers/ChatDialogManager.swift index cc4126ef6..4ba864c4a 100644 --- a/Adamant/Modules/Chat/View/Managers/ChatDialogManager.swift +++ b/Adamant/Modules/Chat/View/Managers/ChatDialogManager.swift @@ -481,7 +481,7 @@ private extension ChatDialogManager { } func getUpperContentViewSize() -> CGSize { - .init(width: 310, height: 50) + .init(width: 335, height: 50) } func getUpperContentView( From 699c595af4e8ed0b9520b686f4472315cf5fd8dd Mon Sep 17 00:00:00 2001 From: Iana Silosiev Date: Fri, 19 Jul 2024 14:23:29 +0300 Subject: [PATCH 005/106] [trello.com/c/5Lqd9RFP] UI: Fixed Welcome screens on small screens --- Adamant/Modules/Onboard/OnboardPage.swift | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/Adamant/Modules/Onboard/OnboardPage.swift b/Adamant/Modules/Onboard/OnboardPage.swift index 313f66fa6..26691cc65 100644 --- a/Adamant/Modules/Onboard/OnboardPage.swift +++ b/Adamant/Modules/Onboard/OnboardPage.swift @@ -45,6 +45,7 @@ final class OnboardPage: SwiftyOnboardPage { super.init(frame: .zero) setupView() + layoutScreen() } required public init?(coder aDecoder: NSCoder) { @@ -69,6 +70,14 @@ final class OnboardPage: SwiftyOnboardPage { make.height.equalTo(260) } } + + private func layoutScreen() { + if UIScreen.main.bounds.height == 667 { + textView.snp.updateConstraints { make in + make.bottom.equalToSuperview().offset(-120) + } + } + } } // MARK: - UITextViewDelegate From 0b4a30702836760456c3679c3bf90ef385a9b9d8 Mon Sep 17 00:00:00 2001 From: Iana Silosiev Date: Fri, 26 Jul 2024 11:14:10 +0300 Subject: [PATCH 006/106] [trello.com/c/1tMg5Cbp] Feat: Added tx data record --- .../KlyTransactionDetailsViewController.swift | 3 + .../Klayr/KlyTransactionsViewController.swift | 4 + .../Wallets/Models/TransactionDetails.swift | 4 + ...TransactionDetailsViewControllerBase.swift | 43 +++++++++++ .../Localization/de.lproj/Localizable.strings | 3 + .../Localization/en.lproj/Localizable.strings | 3 + .../Localization/ru.lproj/Localizable.strings | 3 + .../Localization/zh.lproj/Localizable.strings | 3 + .../Models/ServiceTransactionModel.swift | 1 + LiskKit/Sources/API/Service/Service.swift | 73 +------------------ .../Models/TransactionModel.swift | 2 + 11 files changed, 71 insertions(+), 71 deletions(-) diff --git a/Adamant/Modules/Wallets/Klayr/KlyTransactionDetailsViewController.swift b/Adamant/Modules/Wallets/Klayr/KlyTransactionDetailsViewController.swift index 872d8a640..e6f49f1fa 100644 --- a/Adamant/Modules/Wallets/Klayr/KlyTransactionDetailsViewController.swift +++ b/Adamant/Modules/Wallets/Klayr/KlyTransactionDetailsViewController.swift @@ -28,6 +28,9 @@ final class KlyTransactionDetailsViewController: TransactionDetailsViewControlle return control }() + override var showTxRecordData: Bool { + true + } // MARK: - Lifecycle override func viewDidLoad() { diff --git a/Adamant/Modules/Wallets/Klayr/KlyTransactionsViewController.swift b/Adamant/Modules/Wallets/Klayr/KlyTransactionsViewController.swift index 5106ab376..4b549e50f 100644 --- a/Adamant/Modules/Wallets/Klayr/KlyTransactionsViewController.swift +++ b/Adamant/Modules/Wallets/Klayr/KlyTransactionsViewController.swift @@ -121,6 +121,10 @@ extension Transactions.TransactionModel: TransactionDetails { var sentDate: Date? { timestamp.map { Date(timeIntervalSince1970: TimeInterval($0)) } } + + var txRecordData: String? { + txData + } } extension LocalTransaction: TransactionDetails { diff --git a/Adamant/Modules/Wallets/Models/TransactionDetails.swift b/Adamant/Modules/Wallets/Models/TransactionDetails.swift index 3384f61bb..d25191f8c 100644 --- a/Adamant/Modules/Wallets/Models/TransactionDetails.swift +++ b/Adamant/Modules/Wallets/Models/TransactionDetails.swift @@ -47,6 +47,8 @@ protocol TransactionDetails { var nonceRaw: String? { get } + var txRecordData: String? { get } + func summary( with url: String?, currentValue: String?, @@ -57,6 +59,8 @@ protocol TransactionDetails { extension TransactionDetails { var feeCurrencySymbol: String? { defaultCurrencySymbol } + var txRecordData: String? { nil } + func summary( with url: String? = nil, currentValue: String? = nil, diff --git a/Adamant/Modules/Wallets/TransactionDetailsViewControllerBase.swift b/Adamant/Modules/Wallets/TransactionDetailsViewControllerBase.swift index 0bda84cb3..050a390f4 100644 --- a/Adamant/Modules/Wallets/TransactionDetailsViewControllerBase.swift +++ b/Adamant/Modules/Wallets/TransactionDetailsViewControllerBase.swift @@ -71,6 +71,7 @@ class TransactionDetailsViewControllerBase: FormViewController { case historyFiat case currentFiat case inconsistentReason + case txRecordData var tag: String { switch self { @@ -89,6 +90,7 @@ class TransactionDetailsViewControllerBase: FormViewController { case .historyFiat: return "hfiat" case .currentFiat: return "cfiat" case .inconsistentReason: return "incReason" + case .txRecordData: return "data" } } @@ -110,6 +112,9 @@ class TransactionDetailsViewControllerBase: FormViewController { case .currentFiat: return .localized("TransactionDetailsScene.Row.CurrentFiat", comment: "Transaction details: current fiat value") case .inconsistentReason: return .localized("TransactionStatus.Inconsistent.Reason.Title", comment: "Transaction status: inconsistent reason title") + case .txRecordData: + return + .localized("TransactionStatus.Inconsistent.RecordData.Title", comment: "Transaction details: Tx data record") } } @@ -160,6 +165,10 @@ class TransactionDetailsViewControllerBase: FormViewController { // MARK: - Properties + var showTxRecordData: Bool { + false + } + var transaction: TransactionDetails? { didSet { if !isFiatSet { @@ -592,6 +601,40 @@ class TransactionDetailsViewControllerBase: FormViewController { detailsSection.append(statusRow) + // MARK: Tx data record + let txRecordData = LabelRow { + $0.disabled = true + $0.tag = Rows.txRecordData.tag + $0.title = Rows.txRecordData.localized + + if let value = transaction?.txRecordData { + $0.value = value + } else { + $0.value = TransactionDetailsViewControllerBase.awaitingValueString + } + + $0.cell.detailTextLabel?.textAlignment = .right + $0.cell.detailTextLabel?.lineBreakMode = .byTruncatingMiddle + }.cellSetup { (cell, _) in + cell.selectionStyle = .gray + cell.textLabel?.textColor = UIColor.adamant.textColor + }.onCellSelection { [weak self] (cell, row) in + if let text = row.value { + self?.shareValue(text, from: cell) + } + }.cellUpdate { [weak self] (cell, row) in + cell.textLabel?.textColor = UIColor.adamant.textColor + if let value = self?.transaction?.txRecordData { + row.value = value + } else { + row.value = TransactionDetailsViewControllerBase.awaitingValueString + } + } + + if showTxRecordData { + detailsSection.append(txRecordData) + } + // MARK: Current Fiat let currentFiatRow = LabelRow { $0.disabled = true diff --git a/CommonKit/Sources/CommonKit/Assets/Localization/de.lproj/Localizable.strings b/CommonKit/Sources/CommonKit/Assets/Localization/de.lproj/Localizable.strings index 4088c7870..83c1fa116 100644 --- a/CommonKit/Sources/CommonKit/Assets/Localization/de.lproj/Localizable.strings +++ b/CommonKit/Sources/CommonKit/Assets/Localization/de.lproj/Localizable.strings @@ -982,6 +982,9 @@ /* Transaction status: inconsistent reason title */ "TransactionStatus.Inconsistent.Reason.Title" = "Inkonsistenter Grund"; +/* Transaction status: inconsistent data title */ +"TransactionStatus.Inconsistent.RecordData.Title" = "Tx-Datensatz"; + /* Transaction details: scene title */ "TransactionDetailsScene.Title" = "Details"; diff --git a/CommonKit/Sources/CommonKit/Assets/Localization/en.lproj/Localizable.strings b/CommonKit/Sources/CommonKit/Assets/Localization/en.lproj/Localizable.strings index 37bca7fef..e5b51ddb7 100644 --- a/CommonKit/Sources/CommonKit/Assets/Localization/en.lproj/Localizable.strings +++ b/CommonKit/Sources/CommonKit/Assets/Localization/en.lproj/Localizable.strings @@ -967,6 +967,9 @@ /* Transaction status: inconsistent reason title */ "TransactionStatus.Inconsistent.Reason.Title" = "Inconsistent reason"; +/* Transaction status: inconsistent data title */ +"TransactionStatus.Inconsistent.RecordData.Title" = "Tx data record"; + /* Transaction details: scene title */ "TransactionDetailsScene.Title" = "Details"; diff --git a/CommonKit/Sources/CommonKit/Assets/Localization/ru.lproj/Localizable.strings b/CommonKit/Sources/CommonKit/Assets/Localization/ru.lproj/Localizable.strings index 066703b31..3a2478eaf 100644 --- a/CommonKit/Sources/CommonKit/Assets/Localization/ru.lproj/Localizable.strings +++ b/CommonKit/Sources/CommonKit/Assets/Localization/ru.lproj/Localizable.strings @@ -961,6 +961,9 @@ /* Transaction status: inconsistent reason title */ "TransactionStatus.Inconsistent.Reason.Title" = "Противоречивая причина"; +/* Transaction status: inconsistent data title */ +"TransactionStatus.Inconsistent.RecordData.Title" = "Данные транзакции"; + /* Transaction details: scene title */ "TransactionDetailsScene.Title" = "Подробности"; diff --git a/CommonKit/Sources/CommonKit/Assets/Localization/zh.lproj/Localizable.strings b/CommonKit/Sources/CommonKit/Assets/Localization/zh.lproj/Localizable.strings index 63fafa539..2863ea7a6 100644 --- a/CommonKit/Sources/CommonKit/Assets/Localization/zh.lproj/Localizable.strings +++ b/CommonKit/Sources/CommonKit/Assets/Localization/zh.lproj/Localizable.strings @@ -964,6 +964,9 @@ /* Transaction status: inconsistent reason title */ "TransactionStatus.Inconsistent.Reason.Title" = "不一致的原因"; +/* Transaction status: inconsistent data title */ +"TransactionStatus.Inconsistent.RecordData.Title" = "Tx数据记录"; + /* Transaction details: scene title */ "TransactionDetailsScene.Title" = "详细信息"; diff --git a/LiskKit/Sources/API/Service/Models/ServiceTransactionModel.swift b/LiskKit/Sources/API/Service/Models/ServiceTransactionModel.swift index a8fa3a812..a06b98862 100644 --- a/LiskKit/Sources/API/Service/Models/ServiceTransactionModel.swift +++ b/LiskKit/Sources/API/Service/Models/ServiceTransactionModel.swift @@ -84,6 +84,7 @@ public struct ServiceTransactionModel: APIModel { public struct Params: APIModel { public let amount: String public let recipientAddress: String + public let data: String? } public let id: String diff --git a/LiskKit/Sources/API/Service/Service.swift b/LiskKit/Sources/API/Service/Service.swift index 8624f755b..27f2d835d 100644 --- a/LiskKit/Sources/API/Service/Service.swift +++ b/LiskKit/Sources/API/Service/Service.swift @@ -75,48 +75,6 @@ extension Service { 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 { - case .success(response: let value): - completionHandler(.success(response: value.data)) - case .error(response: let error): - completionHandler(.error(response: error)) - } - } - return - } - if version == .v2 { - transactionsV2(id: id, block: block, sender: sender, recipient: recipient, senderIdOrRecipientId: senderIdOrRecipientId, limit: limit, offset: offset, sort: sort) { result in - 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, - isOutgoing: $0.senderId.lowercased() == ownerAddress?.lowercased(), - nonce: $0.nonce, - executionStatus: $0.executionStatus - ) - } - completionHandler(.success(response: transaction)) - case .error(response: let error): - completionHandler(.error(response: error)) - } - } - } - if version == .v3 { transactionsV3( id: id, @@ -147,7 +105,8 @@ extension Service { confirmations: $0.confirmations, isOutgoing: $0.senderId.lowercased() == ownerAddress?.lowercased(), nonce: $0.nonce, - executionStatus: $0.executionStatus + executionStatus: $0.executionStatus, + txData: $0.params.data ) } completionHandler(.success(response: transaction)) @@ -158,34 +117,6 @@ extension Service { } } - private func transactionsV1(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 (Response) -> Void) { - var options: RequestOptions = [:] - if let value = id { options["id"] = value } - if let value = block { options["blockId"] = value } - if let value = limit { options["limit"] = value } - if let value = offset { options["offset"] = value } - if let value = sort?.value { options["sort"] = value } - if let value = sender { options["senderId"] = value } - if let value = recipient { options["recipientId"] = value } - if let value = senderIdOrRecipientId { options["senderIdOrRecipientId"] = value } - - client.get(path: "\(Version.v1.rawValue)/transactions", options: options, completionHandler: completionHandler) - } - - private func transactionsV2(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 (Response) -> Void) { - var options: RequestOptions = [:] - if let value = id { options["transactionId"] = value } - if let value = block { options["blockId"] = value } - if let value = limit { options["limit"] = value } - if let value = offset { options["offset"] = value } - if let value = sort?.value { options["sort"] = value } - if let value = sender { options["senderAddress"] = value } - if let value = recipient { options["recipientAddress"] = value } - if let value = senderIdOrRecipientId { options["address"] = value } - - client.get(path: "\(Version.v2.rawValue)/transactions", options: options, completionHandler: completionHandler) - } - private func transactionsV3( id: String? = nil, block: String? = nil, diff --git a/LiskKit/Sources/API/Transactions/Models/TransactionModel.swift b/LiskKit/Sources/API/Transactions/Models/TransactionModel.swift index 4a2d5d4fd..01708120a 100644 --- a/LiskKit/Sources/API/Transactions/Models/TransactionModel.swift +++ b/LiskKit/Sources/API/Transactions/Models/TransactionModel.swift @@ -57,6 +57,8 @@ extension Transactions { public var executionStatus: ExecutionStatus + public var txData: String? + // MARK: - Hashable public static func == (lhs: TransactionModel, rhs: TransactionModel) -> Bool { From 9461376374a0e29dd2cdd1ac6cc03d4c118a313b Mon Sep 17 00:00:00 2001 From: Iana Silosiev Date: Fri, 26 Jul 2024 16:34:59 +0300 Subject: [PATCH 007/106] [trello.com/c/Au8CInUP] UI: Update "Exchange tokens" screen --- .../Adamant/AdmWalletViewController.swift | 9 +++ .../Adamant/BuyAndSellViewController.swift | 69 +++++-------------- .../Localization/de.lproj/Localizable.strings | 8 ++- .../Localization/en.lproj/Localizable.strings | 8 ++- .../Localization/ru.lproj/Localizable.strings | 8 ++- .../Localization/zh.lproj/Localizable.strings | 6 ++ 6 files changed, 53 insertions(+), 55 deletions(-) diff --git a/Adamant/Modules/Wallets/Adamant/AdmWalletViewController.swift b/Adamant/Modules/Wallets/Adamant/AdmWalletViewController.swift index 0c951c9fd..0b3bcadd5 100644 --- a/Adamant/Modules/Wallets/Adamant/AdmWalletViewController.swift +++ b/Adamant/Modules/Wallets/Adamant/AdmWalletViewController.swift @@ -27,6 +27,15 @@ extension String.adamant.wallets { static var exchangeInChatAdmTokens: String { String.localized("AccountTab.Row.ExchangeADMInChat", comment: "Account tab: Exchange ADM in chat") } + + static var exchangesOnCoinMarketCap: String { + String.localized("AccountTab.Row.ExchangesOnCoinMarketCap", comment: "Account tab: Exchanges on CMC") + } + + static var exchangesOnCoinGecko: String { + String.localized("AccountTab.Row.ExchangesOnCoinGecko", comment: "Account tab: Exchanges on CoinGecko") + } + // URLs static func getFreeTokensUrl(for address: String) -> String { return String.localizedStringWithFormat(.localized("AccountTab.FreeTokens.UrlFormat", comment: "Account tab: A full 'Get free tokens' link, with %@ as address"), address) diff --git a/Adamant/Modules/Wallets/Adamant/BuyAndSellViewController.swift b/Adamant/Modules/Wallets/Adamant/BuyAndSellViewController.swift index c3f14868c..20e55d129 100644 --- a/Adamant/Modules/Wallets/Adamant/BuyAndSellViewController.swift +++ b/Adamant/Modules/Wallets/Adamant/BuyAndSellViewController.swift @@ -16,23 +16,15 @@ final class BuyAndSellViewController: FormViewController { enum Rows { case adamantMessage case adamantSite - case azbit - case stakecube - case coinstore - case fameEX - case xeggeX - case nonKYC - + case coinMarketCap + case coinGecko + var tag: String { switch self { case .adamantMessage: return "admChat" case .adamantSite: return "admSite" - case .azbit: return "cDeal" - case .stakecube: return "stakecube" - case .coinstore: return "coinstore" - case .fameEX: return "fameEX" - case .xeggeX: return "xeggeX" - case .nonKYC: return "nonKYC" + case .coinMarketCap: return "coinMarketCap" + case .coinGecko: return "coinGecko" } } @@ -40,12 +32,8 @@ final class BuyAndSellViewController: FormViewController { switch self { case .adamantMessage: return .asset(named: "row_logo") case .adamantSite: return .asset(named: "row_logo") - case .azbit: return .asset(named: "azbit_logo") - case .stakecube: return .asset(named: "row_stakecube") - case .coinstore: return .asset(named: "row_coinstore") - case .fameEX: return .asset(named: "row_fameex") - case .xeggeX: return .asset(named: "row_xeggex") - case .nonKYC: return .asset(named: "row_nonkyc") + case .coinMarketCap: return .asset(named: "row_logo") + case .coinGecko: return .asset(named: "row_logo") } } @@ -53,12 +41,8 @@ final class BuyAndSellViewController: FormViewController { switch self { case .adamantMessage: return String.adamant.wallets.exchangeInChatAdmTokens case .adamantSite: return String.adamant.wallets.buyAdmTokens - case .azbit: return "Azbit" - case .stakecube: return "StakeCube" - case .coinstore: return "Coinstore" - case .fameEX: return "FameEX" - case .xeggeX: return "XeggeX" - case .nonKYC: return "NonKYC" + case .coinMarketCap: return String.adamant.wallets.exchangesOnCoinMarketCap + case .coinGecko: return String.adamant.wallets.exchangesOnCoinGecko } } @@ -66,12 +50,8 @@ final class BuyAndSellViewController: FormViewController { switch self { case .adamantMessage: return "" case .adamantSite: return "https://adamant.im/buy-tokens/" - case .azbit: return "https://azbit.com?referralCode=9YVWYAF" - case .stakecube: return "https://stakecube.net/app/exchange/adm_usdt?layout=pro&team=adm" - case .coinstore: return "https://h5.coinstore.com/h5/signup?invitCode=o951vZ" - case .fameEX: return "https://www.fameex.com/en-US/trade/adm-usdt/commissiondispense?code=MKKAWV" - case .xeggeX: return "https://xeggex.com/market/ADM_USDT?ref=656846d209bbed85b91aba4d" - case .nonKYC: return "https://nonkyc.io/market/ADM_USDT?ref=655b4df9eb13acde84677358" + case .coinMarketCap: return "https://coinmarketcap.com/currencies/adamant-messenger/#Markets" + case .coinGecko: return "https://www.coingecko.com/en/coins/adamant-messenger#markets" } } } @@ -109,29 +89,14 @@ final class BuyAndSellViewController: FormViewController { section.append(admRow) - // MARK: Azbit - let coinRow = buildUrlRow(for: .azbit) - section.append(coinRow) - - // MARK: StakeCube - let stakecubeCoinRow = buildUrlRow(for: .stakecube) - section.append(stakecubeCoinRow) - - // MARK: Coinstore - let coinstoreCoinRow = buildUrlRow(for: .coinstore) - section.append(coinstoreCoinRow) - - // MARK: FameEX - let fameEXCoinRow = buildUrlRow(for: .fameEX) - section.append(fameEXCoinRow) + // MARK: CoinMarketCap + let coinMarketCap = buildUrlRow(for: .coinMarketCap) + section.append(coinMarketCap) - // MARK: XeggeX - let xeggeXCoinRow = buildUrlRow(for: .xeggeX) - section.append(xeggeXCoinRow) + // MARK: CoinGecko + let coinGecko = buildUrlRow(for: .coinGecko) + section.append(coinGecko) - // MARK: NonKYC - let nonKYCCoinRow = buildUrlRow(for: .nonKYC) - section.append(nonKYCCoinRow) form.append(section) setColors() diff --git a/CommonKit/Sources/CommonKit/Assets/Localization/de.lproj/Localizable.strings b/CommonKit/Sources/CommonKit/Assets/Localization/de.lproj/Localizable.strings index 4088c7870..aba72ef26 100644 --- a/CommonKit/Sources/CommonKit/Assets/Localization/de.lproj/Localizable.strings +++ b/CommonKit/Sources/CommonKit/Assets/Localization/de.lproj/Localizable.strings @@ -167,7 +167,7 @@ "AccountTab.Row.Balance" = "Kontostand"; /* Account tab: 'Buy tokens' button */ -"AccountTab.Row.BuyTokens" = "Token tauschen"; +"AccountTab.Row.BuyTokens" = "Coin tauschen"; /* Account tab: 'Logout' button */ "AccountTab.Row.Logout" = "Ausloggen"; @@ -205,6 +205,12 @@ /* Account tab: Exchange ADM tokens in chat */ "AccountTab.Row.ExchangeADMInChat" = "Austauscher im Chat"; +/* Account tab: Exchanges on CMC */ +"AccountTab.Row.ExchangesOnCoinMarketCap" = "Börsen auf CoinMarketCap"; + +/* Account tab: Exchanges on CoinGecko */ +"AccountTab.Row.ExchangesOnCoinGecko" = "Börsen auf CoinGecko"; + /* Account tab: 'Address' row */ "AccountTab.Row.Address" = "Addresse"; diff --git a/CommonKit/Sources/CommonKit/Assets/Localization/en.lproj/Localizable.strings b/CommonKit/Sources/CommonKit/Assets/Localization/en.lproj/Localizable.strings index 37bca7fef..0844c108d 100644 --- a/CommonKit/Sources/CommonKit/Assets/Localization/en.lproj/Localizable.strings +++ b/CommonKit/Sources/CommonKit/Assets/Localization/en.lproj/Localizable.strings @@ -164,7 +164,7 @@ "AccountTab.Row.Balance" = "Balance"; /* Account tab: 'Buy tokens' button */ -"AccountTab.Row.BuyTokens" = "Exchange tokens"; +"AccountTab.Row.BuyTokens" = "Exchange coins"; /* Account tab: 'Logout' button */ "AccountTab.Row.Logout" = "Logout"; @@ -202,6 +202,12 @@ /* Account tab: Exchange ADM tokens in chat */ "AccountTab.Row.ExchangeADMInChat" = "In-chat Exchanger"; +/* Account tab: Exchanges on CMC */ +"AccountTab.Row.ExchangesOnCoinMarketCap" = "Exchanges on CoinMarketCap"; + +/* Account tab: Exchanges on CoinGecko */ +"AccountTab.Row.ExchangesOnCoinGecko" = "Exchanges on CoinGecko"; + /* Account tab: 'Address' row */ "AccountTab.Row.Address" = "Address"; diff --git a/CommonKit/Sources/CommonKit/Assets/Localization/ru.lproj/Localizable.strings b/CommonKit/Sources/CommonKit/Assets/Localization/ru.lproj/Localizable.strings index 066703b31..cd5af3dac 100644 --- a/CommonKit/Sources/CommonKit/Assets/Localization/ru.lproj/Localizable.strings +++ b/CommonKit/Sources/CommonKit/Assets/Localization/ru.lproj/Localizable.strings @@ -155,7 +155,7 @@ "AccountTab.Row.Balance" = "Баланс"; /* Account tab: 'Buy tokens' button */ -"AccountTab.Row.BuyTokens" = "Обменять токены"; +"AccountTab.Row.BuyTokens" = "Купить или продать монеты"; /* Account tab: 'Logout' button */ "AccountTab.Row.Logout" = "Выход"; @@ -193,6 +193,12 @@ /* Account tab: Exchange ADM tokens in chat */ "AccountTab.Row.ExchangeADMInChat" = "Обменник в чате"; +/* Account tab: Exchanges on CMC */ +"AccountTab.Row.ExchangesOnCoinMarketCap" = "Биржи на CoinMarketCap"; + +/* Account tab: Exchanges on CoinGecko */ +"AccountTab.Row.ExchangesOnCoinGecko" = "Биржи на CoinGecko"; + /* Account tab: 'Address' row */ "AccountTab.Row.Address" = "Адрес"; diff --git a/CommonKit/Sources/CommonKit/Assets/Localization/zh.lproj/Localizable.strings b/CommonKit/Sources/CommonKit/Assets/Localization/zh.lproj/Localizable.strings index 63fafa539..6ebfb5706 100644 --- a/CommonKit/Sources/CommonKit/Assets/Localization/zh.lproj/Localizable.strings +++ b/CommonKit/Sources/CommonKit/Assets/Localization/zh.lproj/Localizable.strings @@ -202,6 +202,12 @@ /* Account tab: Exchange ADM tokens in chat */ "AccountTab.Row.ExchangeADMInChat" = "聊天交流"; +/* Account tab: Exchanges on CMC */ +"AccountTab.Row.ExchangesOnCoinMarketCap" = "CoinMarketCap 上的交易所"; + +/* Account tab: Exchanges on CoinGecko */ +"AccountTab.Row.ExchangesOnCoinGecko" = "CoinGecko 上的交易所"; + /* Account tab: 'Address' row */ "AccountTab.Row.Address" = "地址"; From b5bdabf126e9101b5567a2308fb8cc2a06fc6519 Mon Sep 17 00:00:00 2001 From: Iana Silosiev Date: Fri, 26 Jul 2024 18:47:52 +0300 Subject: [PATCH 008/106] [trello.com/c/Kv0L5rYH] Feat: Added read/unread quick action --- .../CoreData/Chatroom+CoreDataClass.swift | 5 +++ .../ChatsList/ChatListViewController.swift | 35 +++++++++++++------ 2 files changed, 29 insertions(+), 11 deletions(-) diff --git a/Adamant/Models/CoreData/Chatroom+CoreDataClass.swift b/Adamant/Models/CoreData/Chatroom+CoreDataClass.swift index e2225d0c4..89b5459c3 100644 --- a/Adamant/Models/CoreData/Chatroom+CoreDataClass.swift +++ b/Adamant/Models/CoreData/Chatroom+CoreDataClass.swift @@ -23,6 +23,11 @@ public class Chatroom: NSManagedObject { lastTransaction?.isUnread = false } + func markAsUnread() { + hasUnreadMessages = true + lastTransaction?.isUnread = true + } + func getFirstUnread() -> ChatTransaction? { if let trs = transactions as? Set { return trs.filter { $0.isUnread }.map { $0 }.first diff --git a/Adamant/Modules/ChatsList/ChatListViewController.swift b/Adamant/Modules/ChatsList/ChatListViewController.swift index 62c14079f..9dfb822f6 100644 --- a/Adamant/Modules/ChatsList/ChatListViewController.swift +++ b/Adamant/Modules/ChatsList/ChatListViewController.swift @@ -1012,12 +1012,6 @@ extension ChatListViewController { let more = makeMooreContextualAction(for: chatroom) actions.append(more) - // Mark as read - if chatroom.hasUnreadMessages || (chatroom.lastTransaction?.isUnread ?? false) { - let markAsRead = makeMarkAsReadContextualAction(for: chatroom) - actions.append(markAsRead) - } - // Block let block = makeBlockContextualAction(for: chatroom) actions.append(block) @@ -1025,6 +1019,22 @@ extension ChatListViewController { return UISwipeActionsConfiguration(actions: actions) } + func tableView( + _ tableView: UITableView, + leadingSwipeActionsConfigurationForRowAt indexPath: IndexPath + ) -> UISwipeActionsConfiguration? { + guard let chatroom = chatsController?.fetchedObjects?[safe: indexPath.row] else { + return nil + } + + var actions: [UIContextualAction] = [] + + let markAsRead = makeMarkAsReadContextualAction(for: chatroom) + actions.append(markAsRead) + + return UISwipeActionsConfiguration(actions: actions) + } + private func blockChat(with address: String, for chatroom: Chatroom?) { Task { chatroom?.isHidden = true @@ -1074,15 +1084,18 @@ extension ChatListViewController { private func makeMarkAsReadContextualAction(for chatroom: Chatroom) -> UIContextualAction { let markAsRead = UIContextualAction( style: .normal, - title: nil + title: "👀" ) { (_, _, completionHandler) in - chatroom.markAsReaded() + if chatroom.hasUnreadMessages || (chatroom.lastTransaction?.isUnread ?? false) { + chatroom.markAsReaded() + } else { + chatroom.markAsUnread() + } try? chatroom.managedObjectContext?.save() completionHandler(true) } - - markAsRead.image = .asset(named: "swipe_mark-as-read") - markAsRead.backgroundColor = UIColor.adamant.primary + + markAsRead.backgroundColor = UIColor.adamant.contextMenuDefaultBackgroundColor return markAsRead } From 6fd21909218a5b6a4be2eea36f48e0347bdf4697 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=80=9Cjust-software-dev=E2=80=9D?= Date: Sat, 27 Jul 2024 02:43:55 -0300 Subject: [PATCH 009/106] [trello.com/c/zUXIwW2y] Fixed a crash on wallets sorting --- Adamant/Services/AdamantVisibleWalletsService.swift | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/Adamant/Services/AdamantVisibleWalletsService.swift b/Adamant/Services/AdamantVisibleWalletsService.swift index 26020be18..45f759909 100644 --- a/Adamant/Services/AdamantVisibleWalletsService.swift +++ b/Adamant/Services/AdamantVisibleWalletsService.swift @@ -222,7 +222,12 @@ final class AdamantVisibleWalletsService: VisibleWalletsService { } let wallet = availableServices.remove(at: index) - availableServices.insert(wallet, at: newIndex) + + if (0 ... availableServices.count).contains(newIndex) { + availableServices.insert(wallet, at: newIndex) + } else { + availableServices.append(wallet) + } } return availableServices From 370fc74eb9a1fa842e78a0d80b2f3a3ef71b484e Mon Sep 17 00:00:00 2001 From: Iana Silosiev Date: Sat, 27 Jul 2024 12:27:43 +0300 Subject: [PATCH 010/106] [trello.com/c/GR5OunFg] fix: transfer cell dynamic width & context menu --- .../Container/ChatTransactionContainerView.swift | 5 +++++ .../Content/ChatTransactionContentView.swift | 6 ++++++ 2 files changed, 11 insertions(+) diff --git a/Adamant/Modules/Chat/View/Subviews/ChatTransaction/Container/ChatTransactionContainerView.swift b/Adamant/Modules/Chat/View/Subviews/ChatTransaction/Container/ChatTransactionContainerView.swift index 4ffd6ab26..17efc280b 100644 --- a/Adamant/Modules/Chat/View/Subviews/ChatTransaction/Container/ChatTransactionContainerView.swift +++ b/Adamant/Modules/Chat/View/Subviews/ChatTransaction/Container/ChatTransactionContainerView.swift @@ -61,6 +61,10 @@ final class ChatTransactionContainerView: UIView, ChatModelView { stack.addArrangedSubview(statusButton) stack.addArrangedSubview(ownReactionLabel) stack.addArrangedSubview(opponentReactionLabel) + + stack.snp.makeConstraints { + $0.width.equalTo(Self.opponentReactionWidth) + } return stack }() @@ -343,6 +347,7 @@ extension ChatTransactionContainerView { view.contentView.model = model.content view.updateStatus(model.status) view.updateLayout() + view.contentView.setFixWidth(width: contentView.frame.width) return view } } diff --git a/Adamant/Modules/Chat/View/Subviews/ChatTransaction/Content/ChatTransactionContentView.swift b/Adamant/Modules/Chat/View/Subviews/ChatTransaction/Content/ChatTransactionContentView.swift index dd7079104..997df69d2 100644 --- a/Adamant/Modules/Chat/View/Subviews/ChatTransaction/Content/ChatTransactionContentView.swift +++ b/Adamant/Modules/Chat/View/Subviews/ChatTransaction/Content/ChatTransactionContentView.swift @@ -125,6 +125,12 @@ final class ChatTransactionContentView: UIView { super.init(coder: coder) configure() } + + func setFixWidth(width: CGFloat) { + snp.remakeConstraints { + $0.width.lessThanOrEqualTo(width) + } + } } extension ChatTransactionContentView.Model { From a281ffdae96bc3dedadcf887ece1d3fa9b73e344 Mon Sep 17 00:00:00 2001 From: Iana Silosiev Date: Mon, 29 Jul 2024 13:04:19 +0300 Subject: [PATCH 011/106] [trello.com/c/Au8CInUP] Updated the icons --- .../Adamant/BuyAndSellViewController.swift | 6 ++-- .../Row/exch_anon.imageset/Contents.json | 26 ++++++++++++++++++ .../Row/exch_anon.imageset/exch_anon@3x-2.png | Bin 0 -> 797 bytes .../Row/exch_anon.imageset/exch_anon@3x-3.png | Bin 0 -> 1216 bytes .../Row/exch_anon.imageset/exch_anon@3x.png | Bin 0 -> 391 bytes .../Row/row_coingecko.imageset/Contents.json | 26 ++++++++++++++++++ .../row_coingecko.imageset/coingecko@3x-2.png | Bin 0 -> 822 bytes .../row_coingecko.imageset/coingecko@3x-3.png | Bin 0 -> 1330 bytes .../row_coingecko.imageset/coingecko@3x.png | Bin 0 -> 376 bytes .../Row/row_coinmarket.imageset/Contents.json | 26 ++++++++++++++++++ .../coinmarket@3x-2.png | Bin 0 -> 1041 bytes .../coinmarket@3x-3.png | Bin 0 -> 1682 bytes .../row_coinmarket.imageset/coinmarket@3x.png | Bin 0 -> 431 bytes 13 files changed, 81 insertions(+), 3 deletions(-) create mode 100644 CommonKit/Sources/CommonKit/Assets/Shared.xcassets/Row/exch_anon.imageset/Contents.json create mode 100644 CommonKit/Sources/CommonKit/Assets/Shared.xcassets/Row/exch_anon.imageset/exch_anon@3x-2.png create mode 100644 CommonKit/Sources/CommonKit/Assets/Shared.xcassets/Row/exch_anon.imageset/exch_anon@3x-3.png create mode 100644 CommonKit/Sources/CommonKit/Assets/Shared.xcassets/Row/exch_anon.imageset/exch_anon@3x.png create mode 100644 CommonKit/Sources/CommonKit/Assets/Shared.xcassets/Row/row_coingecko.imageset/Contents.json create mode 100644 CommonKit/Sources/CommonKit/Assets/Shared.xcassets/Row/row_coingecko.imageset/coingecko@3x-2.png create mode 100644 CommonKit/Sources/CommonKit/Assets/Shared.xcassets/Row/row_coingecko.imageset/coingecko@3x-3.png create mode 100644 CommonKit/Sources/CommonKit/Assets/Shared.xcassets/Row/row_coingecko.imageset/coingecko@3x.png create mode 100644 CommonKit/Sources/CommonKit/Assets/Shared.xcassets/Row/row_coinmarket.imageset/Contents.json create mode 100644 CommonKit/Sources/CommonKit/Assets/Shared.xcassets/Row/row_coinmarket.imageset/coinmarket@3x-2.png create mode 100644 CommonKit/Sources/CommonKit/Assets/Shared.xcassets/Row/row_coinmarket.imageset/coinmarket@3x-3.png create mode 100644 CommonKit/Sources/CommonKit/Assets/Shared.xcassets/Row/row_coinmarket.imageset/coinmarket@3x.png diff --git a/Adamant/Modules/Wallets/Adamant/BuyAndSellViewController.swift b/Adamant/Modules/Wallets/Adamant/BuyAndSellViewController.swift index 20e55d129..41ff0fbc4 100644 --- a/Adamant/Modules/Wallets/Adamant/BuyAndSellViewController.swift +++ b/Adamant/Modules/Wallets/Adamant/BuyAndSellViewController.swift @@ -31,9 +31,9 @@ final class BuyAndSellViewController: FormViewController { var image: UIImage? { switch self { case .adamantMessage: return .asset(named: "row_logo") - case .adamantSite: return .asset(named: "row_logo") - case .coinMarketCap: return .asset(named: "row_logo") - case .coinGecko: return .asset(named: "row_logo") + case .adamantSite: return .asset(named: "exch_anon") + case .coinMarketCap: return .asset(named: "row_coinmarket") + case .coinGecko: return .asset(named: "row_coingecko") } } diff --git a/CommonKit/Sources/CommonKit/Assets/Shared.xcassets/Row/exch_anon.imageset/Contents.json b/CommonKit/Sources/CommonKit/Assets/Shared.xcassets/Row/exch_anon.imageset/Contents.json new file mode 100644 index 000000000..2de93eb52 --- /dev/null +++ b/CommonKit/Sources/CommonKit/Assets/Shared.xcassets/Row/exch_anon.imageset/Contents.json @@ -0,0 +1,26 @@ +{ + "images" : [ + { + "filename" : "exch_anon@3x.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "exch_anon@3x-2.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "exch_anon@3x-3.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + }, + "properties" : { + "template-rendering-intent" : "template" + } +} diff --git a/CommonKit/Sources/CommonKit/Assets/Shared.xcassets/Row/exch_anon.imageset/exch_anon@3x-2.png b/CommonKit/Sources/CommonKit/Assets/Shared.xcassets/Row/exch_anon.imageset/exch_anon@3x-2.png new file mode 100644 index 0000000000000000000000000000000000000000..2603ca29da61e76ea48eec70c3009b2870aab92d GIT binary patch literal 797 zcmeAS@N?(olHy`uVBq!ia0vp^S|H593?x6vT4n;Nr~sc3S0IfQxEJ_f6(?S>tXd`l zs9LTh$S+vJ{Q#>&my#&M>1fM<&(i#OG*TcR&N=lbHGXDS1m%C6VWpnPy!A*y>6s*M;==Pe%7If|ASyUCVKjlL*p72Wziqx@Og&flUx0%W%w``+obJETdHamy!}Ra+*XKDqAD z)Wxm7qAzlsw4QDEy|g7qIVk$n*}>y89) z9McZbGZD_tk%_(JznJ@=(XYJ+mTV4q*J_v-op*iiu1`#^5lJ3ShJw3tECute>kc1s zS`(6(Idf0J-Le$xEQ%<(xmiYY3-}})o1>R-Mat8>4lyFCZ7s3b}gv1?NXQiDeRRr=N+>gOV>ZU2i1+M zYbp|dG&ovXEIA{;+-BClpN2^`0a?fT3jB6m4A83jc6#^J)y|yVKRXX=T*-YapYl&@ UO5KY!??CC+)78&qol`;+0LVQ-f&c&j literal 0 HcmV?d00001 diff --git a/CommonKit/Sources/CommonKit/Assets/Shared.xcassets/Row/exch_anon.imageset/exch_anon@3x-3.png b/CommonKit/Sources/CommonKit/Assets/Shared.xcassets/Row/exch_anon.imageset/exch_anon@3x-3.png new file mode 100644 index 0000000000000000000000000000000000000000..5231fe7eb507632904e4a019cb9181ef7f92730a GIT binary patch literal 1216 zcmeAS@N?(olHy`uVBq!ia0vp^_8`o`3?vg8AG`%pu>n3Iu0R?SxEJ_f3X&lb%{p%f zP@8;7kYBKb&1=K?AHOk(%1q06%BpcwG5X^Dm3tf9Pi*Qb4dnT^Nq5dI1_tKOo-U3d z9>=GKXWwp8;CY&LhlNEX|LgzyXWf}gw^n7(uerjUV{M)Df+Z^VxgKj!tlsofEmuZg!(6H|7NVeHc_MJ-mD{CyO-6qPhXRj{^jD6JK>8P zK5lusSakkJkAP5Ku|tm&wz&MCJ>SDW{^O0KY_;jPSbp^^S3LJ3I@>3|zvAYsXW14) zvv`vF?TyzREO^bLZylPv_wehi7AN(zAj=8-^FBUnT)uJZgLvmT9ZZv*em}_nF>9h^ z%9?e{qW1p0RuG#1VTqOnM%O*2^iK`MEBtW{#G@7A5Jh_>VLD6=aSl zt}MU8&Q%wFnla$xy$Ag4Hgl!!HU99q(zJcz!JUtzyUsVCe8AQ`nRR_7GwY3Q?~Vof zJdox3)bi?y#+eN-*JUd2VeNg@vW8iF|Fd^#*Bbv!a-NX!Oe(7S$!EXRQ;7=fR(*l) zMh)lH)<*4Z3)m9C=iOn-boYDa>A7#NSxeAoc`~5a+#|FpG~&t2&)V6@w0jHH}`(X-G%=+-0r>Qh|%)8y1VPE zZ~WGu7Yk&T-r~1)GQBOjAa}8?M%#wTx7!~oq#pgN{`d0ijJKlP+DTTGla0P|gkRjh z^Fi$%>i`$;J$cH{8{41#QA(}j$k=IrCHB8}-2|O5jUCHZeb}|WO=rE?y`qQ;ySVrA zK6$q;#V(503G+8^+YtNGbA4Lgk4aipN3XvW3%R}V>ay!WNB!@g`j)l#zuye)&N|ci z>r&gUEiilJUcvdV;=TQv*Dw1kq~!h{KXh8&_Cx0G_zTCxKQvX|XDbig{(iq-l7ju0 z`k>Emt9E^#zwQ6R#w$yvp8u<_`}Xtp-IJ?-?|QvI(_ihs?+dGYQ7is$cRu=Jzs-V` zUPANikDU1JRu{D}BTL$Bd6QF;A`>U#DAZZ4Ydw`o_@Y(Lf}8+{|`T1 zUa+!e)_DkrBFkkNTQR$hk$;oBp4dvY6?)+7!ka8?Ue( w_6#uoRaUo8uW@UZtmfzPnTw|$d;jk@b7*>SNhZ%OZ%_f_>FVdQ&MBb@07CjUa{vGU literal 0 HcmV?d00001 diff --git a/CommonKit/Sources/CommonKit/Assets/Shared.xcassets/Row/exch_anon.imageset/exch_anon@3x.png b/CommonKit/Sources/CommonKit/Assets/Shared.xcassets/Row/exch_anon.imageset/exch_anon@3x.png new file mode 100644 index 0000000000000000000000000000000000000000..6f9a5ec6b37f7a7efe5554870177fb942e07a27c GIT binary patch literal 391 zcmeAS@N?(olHy`uVBq!ia0vp^q9Dw{3?%2B3|#`GoC178T!A!J;9lT^RUA{WIcA+8 zP*kiW$S+tTS|eO7cvjk<4TlzAGUNSVW%wf`+t=<$?BQK&BwjNdh*aKwY2s3WInJ&HIkRqW z?Y#Z{YC)hj`?u?IHm(X@7n!!!dbcEOQ%g*1cE88+;raeYod@MJwI|H^_FOpQ+-A;I z+uSQ-r&Ndc2WdLHwB^=MX?pn2Yw5*93%#%Z)V`*Ep6A*1Yq4@hMd$Q>+gUuG<325Z zy3f32E4H+$#hUzGchl;T?OfhwT6W literal 0 HcmV?d00001 diff --git a/CommonKit/Sources/CommonKit/Assets/Shared.xcassets/Row/row_coingecko.imageset/Contents.json b/CommonKit/Sources/CommonKit/Assets/Shared.xcassets/Row/row_coingecko.imageset/Contents.json new file mode 100644 index 000000000..a8925078b --- /dev/null +++ b/CommonKit/Sources/CommonKit/Assets/Shared.xcassets/Row/row_coingecko.imageset/Contents.json @@ -0,0 +1,26 @@ +{ + "images" : [ + { + "filename" : "coingecko@3x.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "coingecko@3x-2.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "coingecko@3x-3.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + }, + "properties" : { + "template-rendering-intent" : "template" + } +} diff --git a/CommonKit/Sources/CommonKit/Assets/Shared.xcassets/Row/row_coingecko.imageset/coingecko@3x-2.png b/CommonKit/Sources/CommonKit/Assets/Shared.xcassets/Row/row_coingecko.imageset/coingecko@3x-2.png new file mode 100644 index 0000000000000000000000000000000000000000..56fc0f72a26d3ae9865254d709324681bea7d18a GIT binary patch literal 822 zcmeAS@N?(olHy`uVBq!ia0vp^S|H593?x6vT4n;Nr~sc3S0IfE+zWg#1xXd@V~|t< z>X9o6@(Y$wR6owW+nqslmH~@QR6xe+gZ4rb%$K$mo!wM>IzdCuIdG_Vg{WwYWHvcbzODuz=b3H4!mCajQ^>gY%>kEfG zm^q( z=XQ^F{wOcr7as0A+*5%Uh$+DWEW6V8g)d!2haS$>tYdh(g8 zQ_t8M2^xtS3$NYH!{q4o#x!J+*>w(S!J@4-LT~TgTl@3z?b}nn+`7ob;%>UHATZ|Q z=`W3LVLp-@B_D}b_FUhkzM;;ibY?*0#W2^o&mQ00aCOtRhq?M?u^VD9m^n-juihtl zXj6-MQOxV!3lFk9oVHr?zzZjP!821oIMyF|QmknyZf_Hy9QgE4 zm+Lfp;UirV9j%A1xOE$FU*%uV_s5`cLX&&XmNOQgc0Wp0_^q^r<@2jWOU;>Hoasvq zDPhq!k?o@o8B&)90g@@meRK2WmuboFyt=akR{0Gb0eU7~v zs9&)p$S+vp%eMC~xilC=Wgm7a?Jsbu)m^Y=>M5g3aX&&VZ?H0^`>-#+yo!N=CCbyq zF~sBe-09bEt=1B7SlLzLa^=S7fBKaI$&co5NE6>R&#Ep***iRnE@;v(VQybl z%$m%TBQ&4udMGoGRL#SecN|QuH!S8)Jdnxi?EU}fce%F6eTTLyZ?bfVuYYoUrts|A zMVC4K1n%{{uT)zQb-+B5N#u(x|0(a6H^S##{b>8`6f=J-OS%i!i<`#hZMLvlsJFJ3 z^XA#CjVt)((J-C)Nb2{4$NYZ<$$p%nzwEVDaJp;QaTA?+ev?$^^zTvBus*Q&8}q?H z4+q|ZDGr-!x|Efk9b`8B(yx+P>+?UNBz83`S6oCipJDDlxr7}_*7lJrzS|qgCjHWX zFilxGT7KD~N6k&1UtHhUoc+7*-lA(P^WskBTnhNJmaDU3+s9;Au`+I+ht-|F2d+M- zyWsM7sbF_aOG{~lPv?5i$Tyvh-(HiR_vnV_)#+B=Ug#5fYp4rmTKGU5Vrpx$<-gnAQtlq!u;r49R{2STlFEd{Zd_J#3 zMx@vORl4Kd(^kqazWE3i=I>hPd?AmGp;_1aUW1kW+2;%sGny1?O%{3YQRqK*?2z;S z1NYAH9$IC4V3tuO)zwKY;7Rl|bTMp|^&SKc$bJ5u4|H|oyw9ZY{ja#Rh_*<84zUaJ$ z>{9Pz-+roTzLeg2GELC6mw98VyWP(Ht=@HRD^^ba$JX63_4?fF8_w4YH0-$f!+X>G z$e8)dGs9wYv=VtvOxq_q{ds0`;j0KC-h=?nfBO>EP0iO{bh)~H7Gp-i%4wpt^*0SU zgbi-#29n>PLlKyYRB__=4;WTl;6#pZdHfL%U^4 zSM?0DjSKsv7T?zC@H0Q~F-w*`qTZ~sXYGz@Ijc2z%WZb;*JSwW%XXv2s5b1*Tird+ zo^pJ;!oYI!+=GkX*!NAVe>1-?WaF>HD?e;4zdiX^bI+d_m2caMtQw_-ejR5qUNY<2 z&y5bZ-#-2`|Iw_2GHMB}XRYkjR!RNe=lxG~TGg}Is&_BV>a8nWzwCZ}H2Ku}Ys+5? z`~Usg@=I2aU)%4@-mE*{`{Qc+=jF_L%=PGsnWV6N+T-gdxbOVC`0rU^!KN3ymRbBJ zYhLKv=>9Y;(REb#-)g?Y>Va&2%s+1X&?7P*7oM+wr24)5HMj39yZ^oU`PQd)#f1gz z{A2!a-{K{#8<~Fg+k{3>W~+7m;qy8Er_#5ZrEB}F6JPq()Z2Oc>J-GDG>pplXa8f~ zk5!F(OupRt{@(ksg7~|yxA`9g+gy(Qw$Skp`!(jOmw5v1|3F2Wr>mdKI;Vst0B`hK A)&Kwi literal 0 HcmV?d00001 diff --git a/CommonKit/Sources/CommonKit/Assets/Shared.xcassets/Row/row_coingecko.imageset/coingecko@3x.png b/CommonKit/Sources/CommonKit/Assets/Shared.xcassets/Row/row_coingecko.imageset/coingecko@3x.png new file mode 100644 index 0000000000000000000000000000000000000000..9dc110a231286c43a5aff072dab5c1e53d60cf43 GIT binary patch literal 376 zcmeAS@N?(olHy`uVBq!ia0vp^q9Dw{3?%2B3|#`GECYN(T!Azua4+z|6hsq|VQtt9 z6cR28@(UK%^$fL8DPSmsPtNCfD`6R9v*F1x^x2*dvTkWYd`R?519#t7;hm<$fT-K4fzTwv~oyyB8 zJbForHzl_%u~42@{cy^Kpf|rK3KuJIZ;^2i{F-EBsMd31R#ooWU+)Zi4SCz2=T>E8 zOzrA&E!5dMt$g_s6DzA@cP+m3ue)h@?Q(LN5|(a%VfkG1tIzJG zopBKp=J}h&-!<`K-=EeOoF3N}+_C$>C4R?a;s4J<1tNc!X1nc)+5ctJo*p~n>k6&;r zP=kC)kYBKv)5%pwWYr}YEcYk))a;nu*QywnsdskKCjMd;j$41uO=^F@z`*R{>Eak- zak@10X44ui9-h^klovg^^!&g5XSEx}$LATV>vM@r2+?9Z#(z6fpr&?jm>%D@`gI43 zH?(LReUkS6xfQ1*|6l$U=W2?(o3^h!%4s(>enI$C>0pBer7E>k#qZ3uU+6rkufZVW z+1$@Q{3bzt=|5VgsqyIQ=1snIEMuv!-{qc6C) zTayQ?dTr0=<^{EV7lp%eev5xO)4T2Z*B>WSBDCAKA1TxH=mf<_f>gL<{{_kz3WdqB}&|TCvtJ& z)aRcoF2tNu)_BRdXHRP5!k|x*_cng;I%#!l=|qJ)zUmMD2JiCBZ+v1Tu~Kj1`}lRo zbq{RtU~}YY&RXZ(zheIaKfT!0s975qv3to(F#2MsIXjslOHpI8N4Jsrl!r%|A4M@3 zOnOq6%Q?4L^mvSb^XC$0@tq6$nUppiwhA?#xVPZeuPQ?&jl1GvnG8Zb!veV`b$+`w4F1|yQr9Zk+IHtLu%5YJ5#2K2^lueTcLa1&(Bu< z6l0rdu0`MHs|IZkH#h`nIr^0L-&q@2cHDLXi$U&GySCqk{<6C~+MSd>)wrGu&}WeP ze&A2l#T=)ZEo;*iyH_t~S#f2f#M7RlgC;z#YL5gbTNo|jIZ!I=boiA@vSGZ2)AfLx z^E9@qtk*V8>-&1HMDSYz8>`J%t$-!&n^u0+lG!xn?dmgS*Ei^^eA7GZ5EF26MtR}N zqFV7YoLuMD+XYyJ&JwEpb2Mb_>BWMfLUU#<`6->5BQmGfd42BT6IZsJd#?VWC|koh z<9fNWR+&=y+&gEOYi9XctuonZf9v)kPHwg0hbCKlim%`3Fb;L{KeISfYUavZUNx6I zSFb~#BzQ6%)mPg`f4n5MIE?q`8PgUk>8sY;`&e7BG9Jzu}mx$GPDm1YwA zgw`DRA7-;p`Puj5FOocu#q5pj`Dp4s^TyZp<=Hk{Z~s^=?Z2+((jWO7tDN*79$I}4 Plvh1n{an^LB{Ts5NHDh1 literal 0 HcmV?d00001 diff --git a/CommonKit/Sources/CommonKit/Assets/Shared.xcassets/Row/row_coinmarket.imageset/coinmarket@3x-3.png b/CommonKit/Sources/CommonKit/Assets/Shared.xcassets/Row/row_coinmarket.imageset/coinmarket@3x-3.png new file mode 100644 index 0000000000000000000000000000000000000000..dfff75dbcf6ab087b677d00f7853c742bf902710 GIT binary patch literal 1682 zcmbW2eK->c0LEu$NeI=K+DDXg!dNl z|9bm(TS;b!=eH2y>E=t^;(y91-t9m8|5rx-OfA`38dJPIeDov_kT8=)C}3p(MC`E~ zYj|(l?oShCo~_Km#(M0~PA90ya{vHPtIG*HU+3qyPEUkA`d%|Uk)W%K6@F9VY5rqR zM&8OW><6aPXF+vqd`8=l>tW4w^w&A5;;D^PPgvx5=;I&mBl^~a3U?li-aRsgM$T40 zM44Uj3_5L*lbxR_S0N=m``|wWOf|tTAViK`&jRFXJW^VLI=;SH^BB~!brYL>zU9Hk z#Q0r|(_OloP#y|5E{Dt#; z?p)z7rPszALW90q=^l>0R!Rc|a({b95^kGMePqB~cQ3OMRA-uM_^m-Fqjtlz(eEl! z;F55&4gDC^Ed9F(ZngtxSQH_ZeKJM{wtSkQib>qGI1X(Y$1F`Po!z_@SE2=3W^QN| z3FlbDjshqT+q!E1Vttfr^~RAGC>~IS$Lics4_UHqm*~HSR|9O|vN#tyCSIdYC&=Wt z=5&PKsJ&382bvu)6VePRI%4H$dbjb;CT!;v1b6*U8(QM!0LehPRuekc3m_Y8Cwdtck`5#3#H!M^vu zZ#1};saJNs{Z=rhGvz3Z8#WbE`**nl?NH$T)_id)p!&eJ3>|Soub2@=F;cB70xctK zgC&CA+w?&~8ow>C!(vjtX8AKYh0&pFr$2me`E!?;v>s>_+2Ig{Q_EHJ1wM?~ethU` zh3g(DS4yJ!O?+uG{3LQPP$3v{7Ga{0Q`!77su=%*ZO7lb%ED?OZX1wdfb$*=JFI&L{yBecHO_{RSnXsCp$F=S{rD5oE-j!;CJRY5zE94!mQXetA6YMy(Bm zb5Pi($dMAw@1A7lyJ`pwD@}UpqVeG-GV*dPgri)k0AI z6cJ+i)aj8q)0|{BUPXOy`FM>pH5dh!AiPR8vfVbJ8eZql`20G1!M|>7F#H3tk^=rr zze%ny974TbK;6FojK9l1B%ui$!)b38CIe61kqPuPkR^~)G?>BQ1iDGoueJ~CUphg` zts2-|&VFZ3y8Y0U9F6f^uJjL!b7F4`@43YLrlhp|=#@~of8`sx9#ZxS-%ldkz^sNp zR47t==k&+o+BZ6sJjuww8wH5>#xK;)Vr5a*#z6J+H($zUlPI5IAW+hs40p&z~Y1!Pv6gOORVX!Mjd%RRjvC?u(4F%&I-Ox$thU) zTuRuF1yH;|dNXh2G81<_KvZ*D6^-rU(lT=Xr=S(uF(Y`bjH!(vyX{6-2^=pTn15B* z5%^TN7~yIRl>R-m6sxcjQd*`7rt>#$4&St{)eL~-;2Vn(qrW5@RbCV&4A#P_=V~r> ztpS0(W#U$4BQLKYtm;5f7KF+PC4TtImNai>4Y;vL>a6+9Kg+J~+elGbSiHnY;+M`{ zy-1h5u+O-wTr9qP6|*}s>irD(3A*_tiZlB}KB5Q=bAqS$z1ld&pD+MT47xSSKP%>L oc8X%Q_26x7aXt|XAGQ5gwbkhbHpBZ5*Zq4IE<}$L9D8!wKlh;sng9R* literal 0 HcmV?d00001 diff --git a/CommonKit/Sources/CommonKit/Assets/Shared.xcassets/Row/row_coinmarket.imageset/coinmarket@3x.png b/CommonKit/Sources/CommonKit/Assets/Shared.xcassets/Row/row_coinmarket.imageset/coinmarket@3x.png new file mode 100644 index 0000000000000000000000000000000000000000..4ff823cef6a645c18cb8aa0626afb1d966b20878 GIT binary patch literal 431 zcmeAS@N?(olHy`uVBq!ia0vp^q9Dw{3?%2B3|#`GTmyVUT-^(NfJ_R(&Bbe*fJTUy z1o;IsWa(QKY1Sx6m$y2Yh~y^wxwAy_%$vAk{$(i!21X`N7sn8Z%dUZ(eJqL`E>FK~ zdpq;LeQ9BztWeWRkpxi&^>r;jS(b7CYc@~`4E1Du$t~tLi%p4deT>}#&yr2soO_dA zHXNE{QFlvAJ?W$C-Z@oM*XHw0We^D5{oQ9ySv+_8%U9CJS5?e4+4ev z#%btG$x`07`)Fe5j=h|h7p+T3TbQxs>(;v~L(l8J+VI2r;$Dl*mFvD=u-|r_C+mZ# R^HNZld%F6$taD0e0szcXlI{Qi literal 0 HcmV?d00001 From 9a5a72cde96f886705ecae6fd8ce3f8c4e78f842 Mon Sep 17 00:00:00 2001 From: Iana Silosiev Date: Mon, 29 Jul 2024 15:03:59 +0300 Subject: [PATCH 012/106] [trello.com/c/WyaTh3T8] Fixed: Unknown crypto icon for dark theme --- .../Chat/ViewModel/ChatMessageFactory.swift | 2 +- .../Wallets/no-token.imageset/Contents.json | 4 ++-- .../Wallets/no-token.imageset/no-token.png | Bin 2302 -> 2365 bytes .../Wallets/no-token.imageset/no-token@2x.png | Bin 5699 -> 0 bytes .../no-token.imageset/no-token@2x_white.png | Bin 0 -> 5823 bytes .../Wallets/no-token.imageset/no-token@3x.png | Bin 9283 -> 0 bytes .../no-token.imageset/no-token@3x_white.png | Bin 0 -> 9346 bytes 7 files changed, 3 insertions(+), 3 deletions(-) delete mode 100644 CommonKit/Sources/CommonKit/Assets/Shared.xcassets/Wallets/no-token.imageset/no-token@2x.png create mode 100644 CommonKit/Sources/CommonKit/Assets/Shared.xcassets/Wallets/no-token.imageset/no-token@2x_white.png delete mode 100644 CommonKit/Sources/CommonKit/Assets/Shared.xcassets/Wallets/no-token.imageset/no-token@3x.png create mode 100644 CommonKit/Sources/CommonKit/Assets/Shared.xcassets/Wallets/no-token.imageset/no-token@3x_white.png diff --git a/Adamant/Modules/Chat/ViewModel/ChatMessageFactory.swift b/Adamant/Modules/Chat/ViewModel/ChatMessageFactory.swift index 22832f057..5970fa4ec 100644 --- a/Adamant/Modules/Chat/ViewModel/ChatMessageFactory.swift +++ b/Adamant/Modules/Chat/ViewModel/ChatMessageFactory.swift @@ -254,7 +254,7 @@ private extension ChatMessageFactory { : transaction.senderAddress let coreService = walletServiceCompose.getWallet(by: transfer.type)?.core - let defaultIcon: UIImage = .asset(named: "no-token") ?? .init() + let defaultIcon: UIImage = .asset(named: "no-token")?.withTintColor(.adamant.primary) ?? .init() return .transaction(.init(value: .init( id: id, diff --git a/CommonKit/Sources/CommonKit/Assets/Shared.xcassets/Wallets/no-token.imageset/Contents.json b/CommonKit/Sources/CommonKit/Assets/Shared.xcassets/Wallets/no-token.imageset/Contents.json index 88cbb81ed..78e8130df 100644 --- a/CommonKit/Sources/CommonKit/Assets/Shared.xcassets/Wallets/no-token.imageset/Contents.json +++ b/CommonKit/Sources/CommonKit/Assets/Shared.xcassets/Wallets/no-token.imageset/Contents.json @@ -6,12 +6,12 @@ "scale" : "1x" }, { - "filename" : "no-token@2x.png", + "filename" : "no-token@2x_white.png", "idiom" : "universal", "scale" : "2x" }, { - "filename" : "no-token@3x.png", + "filename" : "no-token@3x_white.png", "idiom" : "universal", "scale" : "3x" } diff --git a/CommonKit/Sources/CommonKit/Assets/Shared.xcassets/Wallets/no-token.imageset/no-token.png b/CommonKit/Sources/CommonKit/Assets/Shared.xcassets/Wallets/no-token.imageset/no-token.png index ee0a0b29af3a4d14b380046301c0579495bc7a66..a3f02a85dfb68667f37a5e488864423202ec8da3 100644 GIT binary patch delta 2303 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 O0000my%a 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 diff --git a/CommonKit/Sources/CommonKit/Assets/Shared.xcassets/Wallets/no-token.imageset/no-token@2x_white.png b/CommonKit/Sources/CommonKit/Assets/Shared.xcassets/Wallets/no-token.imageset/no-token@2x_white.png new file mode 100644 index 0000000000000000000000000000000000000000..23a0ca1302ca9d14f7edc15269d8e8581ad1eb34 GIT binary patch literal 5823 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 literal 0 HcmV?d00001 diff --git a/CommonKit/Sources/CommonKit/Assets/Shared.xcassets/Wallets/no-token.imageset/no-token@3x.png b/CommonKit/Sources/CommonKit/Assets/Shared.xcassets/Wallets/no-token.imageset/no-token@3x.png deleted file mode 100644 index 014f9a68c49c1aaba56787832b66a1c338094426..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 9283 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 literal 0 HcmV?d00001 From cd29f0c98e5c259c9c1ba92dfc09873a0357ec66 Mon Sep 17 00:00:00 2001 From: Iana Silosiev Date: Mon, 29 Jul 2024 15:30:47 +0300 Subject: [PATCH 013/106] [trello.com/c/Au8CInUP] Updated icons --- .../Row/exch_anon.imageset/exch_anon@3x-2.png | Bin 797 -> 913 bytes .../Row/exch_anon.imageset/exch_anon@3x-3.png | Bin 1216 -> 1336 bytes .../Row/exch_anon.imageset/exch_anon@3x.png | Bin 391 -> 457 bytes .../row_coingecko.imageset/coingecko@3x-2.png | Bin 822 -> 902 bytes .../row_coingecko.imageset/coingecko@3x-3.png | Bin 1330 -> 1379 bytes .../row_coingecko.imageset/coingecko@3x.png | Bin 376 -> 434 bytes .../coinmarket@3x-2.png | Bin 1041 -> 1113 bytes .../coinmarket@3x-3.png | Bin 1682 -> 1771 bytes .../row_coinmarket.imageset/coinmarket@3x.png | Bin 431 -> 505 bytes .../Row/row_faceid.imageset/Contents.json | 6 +++--- .../Row/row_faceid.imageset/face-id@3x-2.png | Bin 0 -> 572 bytes .../Row/row_faceid.imageset/face-id@3x-3.png | Bin 0 -> 736 bytes .../Row/row_faceid.imageset/face-id@3x.png | Bin 0 -> 318 bytes .../Row/row_faceid.imageset/row_faceid.png | Bin 575 -> 0 bytes .../Row/row_faceid.imageset/row_faceid@2x.png | Bin 1011 -> 0 bytes .../Row/row_faceid.imageset/row_faceid@3x.png | Bin 1553 -> 0 bytes 16 files changed, 3 insertions(+), 3 deletions(-) create mode 100644 CommonKit/Sources/CommonKit/Assets/Shared.xcassets/Row/row_faceid.imageset/face-id@3x-2.png create mode 100644 CommonKit/Sources/CommonKit/Assets/Shared.xcassets/Row/row_faceid.imageset/face-id@3x-3.png create mode 100644 CommonKit/Sources/CommonKit/Assets/Shared.xcassets/Row/row_faceid.imageset/face-id@3x.png delete mode 100644 CommonKit/Sources/CommonKit/Assets/Shared.xcassets/Row/row_faceid.imageset/row_faceid.png delete mode 100644 CommonKit/Sources/CommonKit/Assets/Shared.xcassets/Row/row_faceid.imageset/row_faceid@2x.png delete mode 100644 CommonKit/Sources/CommonKit/Assets/Shared.xcassets/Row/row_faceid.imageset/row_faceid@3x.png diff --git a/CommonKit/Sources/CommonKit/Assets/Shared.xcassets/Row/exch_anon.imageset/exch_anon@3x-2.png b/CommonKit/Sources/CommonKit/Assets/Shared.xcassets/Row/exch_anon.imageset/exch_anon@3x-2.png index 2603ca29da61e76ea48eec70c3009b2870aab92d..1787e7909c00ff9e64f29bdcd538ba663f19f74f 100644 GIT binary patch delta 798 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 Z{Q=+Tb^AIpjoJVJ002ovPDHLkV1k4La})po delta 1090 zcmV-I1ikyX3cv}Fjej3>QchDCJnJ!^@%8`}9F}P11t{4eThZUCy?{r^vW#_64F0k$ zoZ0{Y1M^8lK~zY`l~-%qf*=g#YTN__6mRwaf98v6rL}r%pM2BN z8@!byakQwgscM>zvk{{)`9$zLy&0Z8d`hg^G;>TVIT)Oj!{Q4fX^naTD^u) zv``L9h&2M;_i4+W?AAJ+L5^UfPypyz$UUwHUn#g0ICM-KT4d5|`0Q3(r7$nOT%SyO z-N&~MReuc1onD=1MDcEY?0_7~M9@XOyvD9wgI5aE4`$U$ANtoEvI~Hhisvor4vS1! zy=m0+75;!I-}b8k_!s|oC$5(JKV4dJ^G3d;8JXefrQRZTnp|AX&9`ViN!b$r(EDd$ zTr-CI$x452WqRNbNe2u9xR2wW;k)w_!IO<%S7(Qq84+7CTKHQN=SZKFLX zhOm>{hv6V)#r-G!)0=4R6$>k3I(d^a^$1tdzq#OjygN`tOT2C)=Yfaj{32z32xz%K z)PG(7OMZ|nRw%fqs_?q6hAgi(-f~!YK3?x1Om5oJU7}koR!=vEuwCg%uV!xek}7(| z*Xb5i+p*QA*HXn#-^%uCz5h;_D~WzJpRQ$w)}S`xM|cVTc<(=~>*o!x4V;j`?~AD zX-_8qP3St_TB!fGL&fO7JfNvc5}rTA$oocqTA@@@;NAJ%{8Kl9v-}<{i=g#${bBy{ zBQYw&^Ow(zkWv0K|Kak}ps9SC|2)=hS(^Sof7W;`v*ysv*m_x5ExpA}Hy9p9T04I= z|DI%{0_P@_0hqDW2E$2EGx~OZzAk~aY8@%_cbTJ=#_#_7165~JbZHE_O8@`>07*qo IM6N<$f)z$G?*IS* diff --git a/CommonKit/Sources/CommonKit/Assets/Shared.xcassets/Row/exch_anon.imageset/exch_anon@3x.png b/CommonKit/Sources/CommonKit/Assets/Shared.xcassets/Row/exch_anon.imageset/exch_anon@3x.png index 6f9a5ec6b37f7a7efe5554870177fb942e07a27c..86ebf749d4e22508f7f31a0fdeffa445c8b78f09 100644 GIT binary patch delta 355 zcmV-p0i6Da1IYuBBn4VfOjJdYQ5cayDv@9dkzfmd004G-99RGV03LKwPE!~joWl}3 zu@wMcqc$sTtU#3{iwS2>Nm&BAdkenZ!`rSw00031Nkl;BrU6Ie-sDH&l-tdBpiRrL>bcE&4Klvy;f)s-fR;e}VASV`0JI`- zw>o=&LbYKKu5Ypoo9N({o6?__XWbXR{{U3a4-EVNT}1!@002ovPDHLkV1io~ BqtgHY delta 305 zcmV-10nYx(1BU~UBn3iHOjJdYQ5cayDu07qt`PtL02XvoPE#0T9yd@SdKCb6Ml%eE zS1o~UgggS8Re^l90002qNkldc(ZZoi z*ZC{fC(jJ#*VbJgGIGu?`#w11oJW>lmrS0fsI-PAT{Qi!**fAG&?5Fd8{cvfeKRB4 zIWVJU2LGAyKsA4&(R_J|=q~zi7an_WlAh;DqI>%QHjWCP+6;^r00000NkvXXu0mjf D=V^wS diff --git a/CommonKit/Sources/CommonKit/Assets/Shared.xcassets/Row/row_coingecko.imageset/coingecko@3x-2.png b/CommonKit/Sources/CommonKit/Assets/Shared.xcassets/Row/row_coingecko.imageset/coingecko@3x-2.png index 56fc0f72a26d3ae9865254d709324681bea7d18a..4bbe640482ea8f0d251297cbcafc35176077965e 100644 GIT binary patch delta 789 zcmV+w1M2*?28IWaBn4woOjJdYK^2icMUhYme|}n+sQ>@~A#_qsQy54Lrt}>802L$L zYKoNh3<#9ArKku`>4Kkfy4QUWw7xxoMc0hyJMEeL>8gHZhpuGPFXQ zJhG?U@)B$wNsNR9s;65EvyHpZqyuD=e_&Qz&kpTXweJ;Hx|f%AK5BoUi={ni1hXb_ zsFB0%on?Si4&$F;1eNQxg zxV{}UnMKcYjaVC`m~{|mcRRv_V5IvVDuD?UIK(K>Ml&4Bx2wGhWFyh5pCh$we{GpY ziqQ!9Wo*TTwr^1INJU383Ob3o*n7OpCBVE)^~zUmkd6{WyZhPP9%ZTd!R(vu7kEs^ zs1P?yxLfMMnzl7xWIdS4q7{UIGdN zkS#;~IjR(B&pL`qkgQYa6SuI0e+|z}LRnFH8RCmMY^aufU&MfW>|s2lNV-Fo;z60^ zB`0=k&Uu|?fesRhjgDyI>s-d)0;L}qLTsi$pTC3@x@8I)5tO zHYa)Hhk3=cQS3wc`OtZ67lG3d%xFXQ_vl2sbH@cPP&gQhOaGqqx}DE8f0SLB&y8K& z`}~zT_Vc_5nsLAGf&G1cSkvor>PtYcSMjq?QKggIv`Y1VQCQOJOnoo#=Q%Mt9XIS+ z6t4e|LKm4pFjXPdf1t1<4BJ9SR{w=gjp+(Cc&YzHUBi{d)u->)lK?x(XTSdeD~v&@ TW3Z5f00000NkvXXu0mjfXeNLt delta 715 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 diff --git a/CommonKit/Sources/CommonKit/Assets/Shared.xcassets/Row/row_coingecko.imageset/coingecko@3x.png b/CommonKit/Sources/CommonKit/Assets/Shared.xcassets/Row/row_coingecko.imageset/coingecko@3x.png index 9dc110a231286c43a5aff072dab5c1e53d60cf43..f4293f22f7704020d5b0602e69901597404c0345 100644 GIT binary patch delta 333 zcmV-T0kZ!10Mu}0000TbW%=J06R-2sIwFo zk1ZcH!BLrpTcE9QL=6fBd}z91-RAI?&j0`b?MXyIR2b7;kK2O7AP5B!QBV^#YOdY? z|IRgOyV;h<;Q#}`S*uEB7zUHn3V-EyWB}f$vIS}occd9uv=)Cl>h2+M zsIZe!l6Jn1fO9QLZ=}%HtD4-8h?X2r*8R f`i84aXgA%7eNfU^Jq026dlPE!{xNmV!`Z~%85 zgbs>+X9+?BUc^ixKmY&$+et)0R2b7$ky(<%APhts&bc$`f7gXL8>k_zb7 zNm93juJ;{#Ng|Woou^28Xg0zlvV7Al9M`b=#w>Z$WDG81A=w$Wq&OpEm5Pc*aV)i#cc-K@Iy%PPIP{OM z+`}t;C*Q-KDCQLh+wF=%M3h!%KhUS0H`V6eX3Rtu6Ab-o4~mh|j{Jq_2}stU+&=IM z7u-mp|ML=X6#b=ZM!a0V^s>B+K0ox(UKsOT@~A#_qsQx@_bkNo%}02dj! z&OVwidc)qO<|(l3TZKtdW^o5K)(&F3{X_r&1B6LLK~y-)bye4b;~)&Ahhl813$FkF zl_xpb+hlK#yu^4A(r7d$kB`L~4_OK!bKuQE#!yqLAxe49$A6$lqBP8{ ze^<;Hrj9lTIr{z==rv@Tnj%EM79nb?GH=&UsH!4Nj3B}&SO(7!HaU^ZC$t3V3o%-k zHrL=>NCVse;64ef-l4_Ofx)Hy;H1nh1eYZZ8y;q}4C)(dtlS_XTTgM$i@NsxQVTzY zo8Y_|WF=qF-HRFT_VQfy8^>FuTtNgbf95afBoplPRV4LEI&KxMm8HuQ%3SQ=xeAWF z>__Xq`SHRlO_m(U4IRXwjp2gtn6{@v+VX1OY_1cgoe}~7C z<68Pz`a~Nc3-u48qGj5oJLsO|N{rFn$eGIxy2WZ!R03JD>F%s($DzNTyXoNPg zVzQjrd|-HxaEFmd8#F>W5Grsb5B`e{1gGqV9ml zF58Q+XJcPN+{hW=5!Kvb^A!AtMwBWW%4I~kR1hLWx*ncFLh7Co7$o20w09N7{3XI( zKO;Sk9Ouo7d-8M=ryqutB@PIAXotds-zIJ$E->FCe~#m|hLO_Iw*5I<+npy+>e&6> zEUn+a9B-<1E&G=bpr8oIe@$5O__4H?RJ!BaT_#yUaRtVz|3n^Eq-7iJ->8qB-pk z6#MFvx)3+6?^^{)!RAca4mURN1{xx`A zCM)B*|MJ=B{U{xuj>2G-W8zL`UVi@=J&xH?Fl}3_McZ~m|G)1R(zeVtQX%MB=jF${ YKbA&IM=j?USO5S307*qoM6N<$f@eqM*Z=?k delta 933 zcmV;W16usq2$2YoBn4eiOjJdYK^T!fMSr|bQ-uHk03UQxPE!^_$*RO1Cl~-ZzhF#! zxSNiJAy#QF&7!goa|8(5{LYex-~a#vL`g(JR5;6ZRoQ~9Dhv#(vLmA6(&zs_^CsAH z$DT7MFA5ZpR4M_+58GJ~e0{xEE)KSTuEBG#geb-2X7A@Z2^kOl52((3bBltvsei=@ zK9ygfSLGX1FralLeU%s7oj;*Nl8%5dXy%>sOb;|tj%WCUmL?1>EpC(2#%QHYPScEO zLOr1+CY%R-$QYQ~uWK(XSbGpyZ*BO7f=C4?eT?&ipnZ{Xj|FW5|z*crc!G9k8Om?rH`}x57)!|}@dxzFohJmf~y~Z{x*aPc0 zQlqUSC|KNA#B{z-nf$bx+f_en3}jOSR6G{O*&^Fc-sCBHV5V}Secsy0kF&^`j`bs) zX~IKWy|2qcVHn=-6w#rT=ks{bT+SmX=>fdFWr3km@)_Q-@Jh)#+NF^o+<#3c;Qdp& zNpFGVG8m~Yk?&uw$1T9HNCrU+gKDlrkEp+(PA*+#TAHz<2TB}}GW0Pin_~cKAt;kb zi!wKq;l%^uS^zMTuPZfXj`hxT5%ypP1w8dCP^3q)sr4!xvXt$s%y!qXFC_LZ!$4e6$(VO> zsd9Z6%n1t4uRc&XRhklc{KZtQ%cBuh5}cZ(`5S3$6r6oSuWiG~)U?j$C-8D>C_`x1 zcOxoxB6pqK%maLyO@BJ7G`T<8+rkM8CUfC5wTyGu*oZS#LQl-2X&RZSZ4D+wZbeGM z@)!(hK_{y}Tk+BwqgD;Y%r%5M8`V3vk3#=!@PH{DN1535uXk%awcGfs8&9r$()=ITsuDsk;liuV00000NkvXX Hu0mjfDNVSY diff --git a/CommonKit/Sources/CommonKit/Assets/Shared.xcassets/Row/row_coinmarket.imageset/coinmarket@3x-3.png b/CommonKit/Sources/CommonKit/Assets/Shared.xcassets/Row/row_coinmarket.imageset/coinmarket@3x-3.png index dfff75dbcf6ab087b677d00f7853c742bf902710..7a6ce7ea71cc6208a5701c359604a582430411e0 100644 GIT binary patch delta 1668 zcmV-~27CFE4eJe%Bn4eiOjJdYQ5cayMSr|bQ-uHk03LKwPE!_7B{=&o9vJ`^UH9?T zwu-<)pnsLi>vId`-m7Q?P-29x000IbNkl2|a#5CjDbsG#D$@cxf^s{v<8 zCg07RlOItEtM_X5_3?Kpz8urmh#;DM8kSyNpAWK650x3(CcOTdY2?-4@K@AYJX{*-RHU%b%Q`SUL+Q}GHnoU{fQ^IEm=BLx~ZnH z*G+>IqWE{d%y#tC>jgJ{9YZ*zj^8fb4#;jDxR*UY#|}XWt(WQcvJ1jr@A%7$U=v)O zExu>Z;z@FqO9wuaiPkY(P@nI3-h{4 zau!U@`YF@TJ&0r?Gjr&wC;l2iNN3yNhR>z;MK6<)70mTdJlGFP6M491e7KacZ#=34 zx04CFuwg52JhNdhjv_V{KA1$m?|F+PW3&3ID>f3q4gK-yUM_4r;O+Gb0u zF*JR`uQs$Y-noTNT=r4mzoJWE+O;FV>T74BodYF+EaI;mOADo&&rZLSu4I zs_=i&AxyVz6p&FT`nje~A>5`cZ-XX1dn@s~{*Ljho8On!3aNO*sT-qklg zI#Vy=f&T(mP}JoHJ2D=DC49NvY&Vhd(V0{(ulTO`hELyLkAIZhl$`ndwIR479v|^< z_MhUx_QoHvj!cvBC;6`nM|Io(;{WRoy)IF$5nuT}I&(r8VwxEt3{^_3n7Zb#@mB~v z^1*u z41wH*+jACo&3}+H=hBGekm4L5g{r%OQ2pK2^ay7z$*FXQX z+v|EC)qlHwyWoMC8xNH`i94y=L84-8Zq={)3)z$>CKReb@BEG?>AQLL=t1A~BeFT`zshvjS|(q< zNZZm+c-AF#i6iJvf^j&|iBh-bt^|$JmVcKTs=Q-_PS-~r)$l6~Y@YjmgG&`9O zYE~5x&m0oExR)6n)ors}pZAXocZbeAwwJtsJok^{L|Z*%;z{}jg7q=5gm ze{r#8kK(;l&+o=9PL)x@x99pl`(HuJgV>LS%R*_=OsXikw|iV6)F1X&>ErU!?A#M4P$h}`en-B&ix0_qq_iKu=Ixj O0000$d1836Ro z1x3a#)w1J0yjgkOiDajeH*QV}4}(Af{E`3w1-eN@K~zY`jaS)XtRM_!cR&#lH{ARG zKY3Zzw$tAFG^3m(Ca1w!6!4dr#zbQD@;5ORb9b-!FtAKiDqWS?}u1s|KV1b>}8YzU3&AfVd z#P_6Aa4O&7_mekEIz_8lFy^VZjz7(ze;g~4zO{z;<#+r(a)d$B(J*NGiXNqZ4qBa# zQM*M6pl%)lGQRAcXQGDIvSD2IFA+6shN5`@TfTn9PuvFWfUFvh_)Vla*Qtpl3t0CV z-)a*a4>01UeIM#&CHWX^1dAA!C%%wF1=8JS@cQW?C|kMd=6K*wzJhPAdGk1qf1-e* z=lzXup$!-Fj`89l$`D*4ez$`s7SuxA`W6M}&L!M=8*~uh|I~xTAr7@Sz~L7-hqyU;cY9ju z&ZP~tFq27EIVf9|K2-W>fW`qoe?rfllh<6etATf6zdJ&%2bpE8qP0E?!`LfxQty7}A&Xf9t{EPxxCN zXuU&fI0uslR067O2tCncrb-kUzVJ_ZSlRNm8|mFc8Ua8XuJ0V(UsaILqJe&lEvm)eMM@7HaKy5R|KLrOH;5Zg z^&F$~1SsJJNOdr#=fpEU$wMCxJQ+|T^VT2mI3-stKVZV2DYxI?z5aB6G|)gji-9&W zneeiX@|1{p8Sg(P(m@}{1t;|WPgMEh(ntK5drNbv*F2hHxDvl~oCDy2ToO?a1NO)Z-73wEZ zq4|(Z{TT~UX=77y=Wwk=3u$|E<+U_Zln#Io&LSJ>Y6>P4&x7>UJ zRh9bJ77Hk$7gc#28qnD=wcX7JBR5&1*-w+G&i(U{;wn#(f2t&4Ycy2zr&(feN23O1 z#&*B^|8%)JQFWar^!qTd7o^xf%}+pzH1%bw{{Dc->=@N=y!pNVzv|P@+x`W=+gM>i zEA;RByMZaq&;85%=X!D+pDF==nLu_op>BAwk46ph_!h?!KV8oB2RKDxLIt&IzRr)X zVV~rhkh^!Qf5tH>Hpk(q5&PHq@vyv)U59j%o9^qPKqfZ;bWsHs=G*-GB25+mYPW)@ zd80{5xzlY{T=aQ79v`jxjt?hbOcOAu*G6+K?bxrqTf^M%W=bIf@XXQiZvHRabh+d} zB<+ff(u^^!3|beGKeV4G)MtDir*qwP(5)YMX{&a@fA<0c$fV%9|Fpl9n_{Rfo{JZF zu3<&x_M_^z{fo{_k537Y?TNUgha>rY|0^rYH0p5iX@zrt>d8WgmjCacdxbO;Xlmo; z6}B+9ODV4Bw*1rnTVh8agUZXgJ&`~of5-v6VgLXDA#_qsQve${jTWq*MI&gK zF+fayrf?syvsA(<2a{jD42O5P)akEyeJ3d!qCvww%h0ZDby zf2AY=&iD-5jJg~9XFXRU`=UoH5{Pp>QhzjAoN%4vlGK|zSTHo}+@-l|lZ}M{(J_>5 z>YKW3pB0=Jlac+Bg;K`6ey9BP3VB*HchrJ&>nzD!@fIU`+!Ij6rM-z<`Ay15j4 j`ybJY{T{j9{mtzchO;n`W00000NkvXXu0mjfh!C@` delta 342 zcmV-c0jd7^1Fr*+BnU-NOjJcja7>XgLVww#t%3jm02g#pPE!DCFFJB5d>|Wlg+MeE zZDUSH1X&E8k*J^38UO$R0!c(cR2b7mQ3;L&AqYg}^tSDp|37tcjvW$$sT5!p04J`5 z`2?m5{(~?iQB_F+=?fN4ng${cuUtN$Np!NdLycnTfWneEe%dN0V(~@2oO+e5Z+{Mz z01#2T_e`92UkhjH>Kn(Zc%3l~b@muM=YULmoiR=OGHG?JO$=E&fwvX4XNoBX3X&l! zN@H3N&8W{2gY<(scTdSrnKB=nY>k7`M%1xUlq~tns2GG*AGO+zReOOxRU;GBuCCH6 z+rFR$M024Rrsf|6XfJJT*8626OGDwWe`LX4C@hp}BeuK6VO6-j3DcskU}m9cwDq;! osa4M{>ah4b(Y-jcd9L@+KepElYVZ|9r2qf`07*qoM6N<$f|(PND*ylh diff --git a/CommonKit/Sources/CommonKit/Assets/Shared.xcassets/Row/row_faceid.imageset/Contents.json b/CommonKit/Sources/CommonKit/Assets/Shared.xcassets/Row/row_faceid.imageset/Contents.json index 84ca8d215..6ec0c171b 100644 --- a/CommonKit/Sources/CommonKit/Assets/Shared.xcassets/Row/row_faceid.imageset/Contents.json +++ b/CommonKit/Sources/CommonKit/Assets/Shared.xcassets/Row/row_faceid.imageset/Contents.json @@ -1,17 +1,17 @@ { "images" : [ { - "filename" : "row_faceid.png", + "filename" : "face-id@3x.png", "idiom" : "universal", "scale" : "1x" }, { - "filename" : "row_faceid@2x.png", + "filename" : "face-id@3x-2.png", "idiom" : "universal", "scale" : "2x" }, { - "filename" : "row_faceid@3x.png", + "filename" : "face-id@3x-3.png", "idiom" : "universal", "scale" : "3x" } diff --git a/CommonKit/Sources/CommonKit/Assets/Shared.xcassets/Row/row_faceid.imageset/face-id@3x-2.png b/CommonKit/Sources/CommonKit/Assets/Shared.xcassets/Row/row_faceid.imageset/face-id@3x-2.png new file mode 100644 index 0000000000000000000000000000000000000000..fd2024d8b9e0a1841b0e4bf729ca6a142a6ff905 GIT binary patch literal 572 zcmeAS@N?(olHy`uVBq!ia0vp^S|H593?x6vT4n;Nr~sc3S0IfQxEJ_f6(?S>tXd`l zs9LTh$S+tTg)i%eeS|0jkJ2-r3YkT3OzyRP*4sGs7}xot*xeVqPpL65Fcy2dIEGl9 zK0EbwUyA}yYj@G)CgnJ`_3!`B;_fpxdi8EX&Ze86S3jvy(%izTsrun$#YCTZDxV(s zNHM7`m3yg`;#?MRuwd)0IQEe6QsZL%wGUL5HXEBqWO5o@i?U%6zopZ=m%(qg_p=YJ z*`_`vevZYnnitxzs{UtGT@f76Cu6iNeczsrdF2PWCLG>Z@>5K3d!7Bx+Sv!=b3R7h ze6`JWdspk)_c!*Qc_e#OIqJXeDye^MhjLEkUx<~KQh%$=^G%5M}3GQ^m8m qb#1XX*If3Gk8B=os@yu`!$dsKqj@ooe5rgK(i!E zg8YIR%@B^ny&cU-LkA*{$*`pi}Z=KUVVfHWS-ivI1 zK5)wj-50Zc=Xr@mCfvl~!Xu6u?6>Dfu)4`z>gMs9@4@`1#zRjt^?vG2nyQn+%tb>`pp)qK6By!y?o|l#`(9uF0%Q~pOI%+u<(6y>ySMY_&-H`ak_L|K=L*HBzZRvw!cy|F;kRPyV~n z@3eLM@zs*A`xpLO=>Ft|`+~!tm2FH!%qxy{ugXkwovbkD_@|9cPmJe2*!Ft=%xjMt zgHl%6*9L~N1=_dA8Jvtc;FT9@+8Aec>iNAhCaJrNe_wIQU+mHJg#49Vf TxYWZ>f)c5xtDnm{r-UW|31&C` literal 0 HcmV?d00001 diff --git a/CommonKit/Sources/CommonKit/Assets/Shared.xcassets/Row/row_faceid.imageset/face-id@3x.png b/CommonKit/Sources/CommonKit/Assets/Shared.xcassets/Row/row_faceid.imageset/face-id@3x.png new file mode 100644 index 0000000000000000000000000000000000000000..4b8a7faf67c1a6abb088d3058d817d8aefba1e86 GIT binary patch literal 318 zcmeAS@N?(olHy`uVBq!ia0vp^q9Dw{3?%2B3|#`GECYN(T!A!sU{6_qEl{U$NswQ# zM2fJ|Chr(gR>QTS9FuLAcQzE*Z}1lbN-y+uaSV~T+bdS-qS4?q}wPKdeN@07nyLI#b5T+jM&pTbr z8#RATa(un4veWqT!ntKfm(6^a^?K#&*vEo7$-m>byT)hrzKSk>`(ck`l^55or&k{5 zdt}_4;Kb|BcjwODk0&0^?wc7q^Y6Lv7`e?imNUDiscT-pQ?u?V^K~h<%`aj#_kcpd M)78&qol`;+0N1N*9smFU literal 0 HcmV?d00001 diff --git a/CommonKit/Sources/CommonKit/Assets/Shared.xcassets/Row/row_faceid.imageset/row_faceid.png b/CommonKit/Sources/CommonKit/Assets/Shared.xcassets/Row/row_faceid.imageset/row_faceid.png deleted file mode 100644 index fa075c07aa7e6f7f0ee42abdd52d126502805900..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 575 zcmV-F0>J%=P)Px$`AI}UR5%f>l+8;PQ51(?HAO2Ievq123qf!ZE+RqPv=vvPiwH_^p&S2zp#Pyw zE^N`tO`B{bxXKqlK@iG?pyEO*KS&T1zbdTfd7Xjbau?#;51zSm&Yd}P=bUqINiLTw zffq0W^`I)?8$5+e2vv$>0h-|~ncQW33zKDB`e6)CU>?@t7~aDg#Pk>EUNW)4_#K|W z02n-%hizzsV&wZPu;6br!8G{AV&wbFP;XFmel-+*{bdA$RzbPOf6#qmR2cLJOhp3T zj`-3)bIJ}0^#O-nQ&?>{`5ebS+{;W%a6gyWesB|BCw6uy^~EumDcgrFy zfgdbdkBM*uZsYOmM04+(SMwFqU7YUg18|jZ;1u*18l5pzp-!`S`@bT+P`<}~r6aNydsw=`p;vd)-Vg|6qc&Y#Z N002ovPDHLkV1f#T3#Px&tw}^dR9Fesm^*J1K@^3JgMd^(5 zW~HBgQ~E(<77kvVirvZU1m>%h7mI&NYTX(0h(!%t67o${+B|T=4}y8 z=xA0CCjvnx3OoIH_1*{-%1w(K&35s*cQ1n#xmywv#OIsAqT;7eq37i4 zzIaSz|1E=^YIIk`$B!}&>S}Xcj%?L4s9F#@qqTHe#!1~m&q&1CGD6mCQ{`(i>-)Vj zc8DxCK>R&%RzyFO(qeH*%Kk~ZTdW1Pr&V?KSq}JYI4N>O&Wp!IPO>Or1K+TlSX;vm ziC50^2jb~~>7wo(@jyTu28=eh=oe)WPtX_C?TMentKwRNq>MV$Wr2nEV|^te$?zF1 zqob~o_~aP%l^gb1ahG4Y;AJHo&mW>d@mfxvT5;O0}Zw)(bi$-NA{s2+PK*d|$*Mk56002ovPDHLkV1mu()6xI{ diff --git a/CommonKit/Sources/CommonKit/Assets/Shared.xcassets/Row/row_faceid.imageset/row_faceid@3x.png b/CommonKit/Sources/CommonKit/Assets/Shared.xcassets/Row/row_faceid.imageset/row_faceid@3x.png deleted file mode 100644 index 4d99bc8b787d7f77a06daa1ce0d7270593127fbf..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1553 zcmV+s2JZQZP)j{00001b5ch_0Itp) z=>Px)%Sl8*RA>e5n>~nJRUE~mCI~6~-fXa$Ew&L9zeu*QQBf4sPO%S)MGDb?R;IN{ z?4n(gCWUAk6+vZpEW{-dA(a*)tnNlIuA0U~vqn9?$(-fCFZaEfypLJu?Ho9{-~anR z_uhB!eQ!3~w&kOvq61=ATox~h|K{*-KP^acJD3nny%; z;4rT<=TmQf=w8ZITnK)--@MR%6QT!Qg}7g(XT{56p3$QGl|t*)3$b6+e_k1S(1pGv zD7YZ5iCH86^z$jpBW(_eP2#YO(@&lc&d1-Z2FlR4J^ z#t`_8|4g9uIL;o>Lr4F5bD+@Nf2DZr|3!y2IYe&rzKg3h2pd>{5{nfk%YV`I5{Q0$p+Tm)Q-cgHSexrI2n@6kX^urV+oxseYJu zoMozwT{5d{2jaIZCgQj1)K6x9E6;q~{uu`@18qcB*JYOdSE2`iHznTA+6Z*{O%T24 z?tOZiSGXebKHk=S0A1)qCwgbat&1CKlFkQ&XT`h3??mpz!x zzHHx{-fkj()2CpQ9H?XFQ^!*(ud2j9GX}EhO!44k9pbqra^`YngsUPyfYgnTWsr@I z9iQX%6R{F7QWab{9uZ#?5&x$6hgdgWl|crUUt2yDPl#iShvm7?cZUZ@siux)KT3aV z?Q0^w*5Mo@V|8qJ*DCixHMmE-JJGQ6@s+UozsY(-o_KR3jK80e=5@7|;FlpTKT*8n z9FZKEH;b(V;#t-f?9j9Sd@EH4QLiV&|E#=cLYM4%HDG9UC6k)NlG^*-tRa z5vc0}X7qWd5|4?uh(E=Gl5sv-a-YW*e@<2_W@T+bToDh6Rc?I<=IgCDvv(E0Nh|}} z(cZSOi~X`d7y8gSFQWUZzh!-a%#wIT?d&We&+_*oA33iD)TbToqdn}G1z&${ixXy% z(O_A5`I9*(mIe0cz#!cbzYsqapAva2QXrpl>QSF|w2#Q9W7(iMy3hyEyJvepLWV(J zSsYixm&H3p52(XOmG`*FMdxdgXZSafud>wV+RKk0%rfs z%+!5@cC^P1_OM$!)<;5?85u^XE=&6LH{2G3x|r@+qfY6E8SdiLbJ`$;Y8_UNztAlSV9aHaf_r2bqMC0`ZJGZ(IgD zZqq>MuYES%m+fbDt+C*?+)(;E#lLVUN_CZH#)vCGVVB*qaT z?}*qYpa)$BdePmxM#fQL_ThdCTL5%elSA}&tc{V0FA-x?L|J@@An z@_k^H41@n~c0tTuKs*5C7m<3rFE@$9GEP55eo)2)P%n~v^r15e3eJjgnM4DF;wwq3 z^<`pzkPoSH^q?zOxLxEC;F<6y=rCn8=236nMj8qif?u)kRNQ_OqG#a$rHwv!lsF)E z#bxo5nCgT)I#?8E#U_D$`mwN-Klq8i_>KP^F^T^GkZxCLDZtg=00000NkvXXu0mjf Dr`H0a From 4677722562feb90e73f16c3d4e5eecf3acec49cf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=80=9Cjust-software-dev=E2=80=9D?= Date: Tue, 30 Jul 2024 00:58:06 -0300 Subject: [PATCH 014/106] [trello.com/c/PdYjG3m2] Added alt_ip processing --- Adamant.xcodeproj/project.pbxproj | 20 +++ Adamant/Helpers/Node+UI.swift | 2 +- .../NodeWithGroup+NodeWithGroupDTO.swift | 19 +++ .../Models/Keychain/NodeWithGroupDTO.swift | 14 ++ .../Models/Keychain/OldNodeWithGroupDTO.swift | 20 +++ Adamant/Models/NodeWithGroup.swift | 2 +- .../ViewModel/CoinsNodesListViewModel.swift | 2 +- .../NodeEditorViewController.swift | 48 ++++--- .../NodesEditor/NodesListViewController.swift | 2 +- .../Wallets/Bitcoin/BtcApiService.swift | 20 +-- .../Bitcoin/BtcWalletService+Send.swift | 9 +- .../Wallets/Bitcoin/BtcWalletService.swift | 16 +-- .../Modules/Wallets/Dash/DashApiService.swift | 20 +-- .../Wallets/Dash/DashWalletService+Send.swift | 4 +- .../Dash/DashWalletService+Transactions.swift | 20 +-- .../Wallets/Dash/DashWalletService.swift | 4 +- .../Modules/Wallets/Doge/DogeApiService.swift | 22 +-- .../Wallets/Doge/DogeWalletService+Send.swift | 4 +- .../Wallets/Doge/DogeWalletService.swift | 20 +-- .../Wallets/ERC20/ERC20WalletService.swift | 4 +- .../Modules/Wallets/Ethereum/EthApiCore.swift | 12 +- .../Wallets/Ethereum/EthApiService.swift | 14 +- .../Wallets/Ethereum/EthWalletService.swift | 8 +- .../Modules/Wallets/Klayr/KlyApiCore.swift | 16 +-- .../Wallets/Klayr/KlyNodeApiService.swift | 12 +- .../Wallets/Klayr/KlyServiceApiService.swift | 12 +- .../ServiceProtocols/APICoreProtocol.swift | 32 ++--- .../NodesStorageProtocol.swift | 56 +------- Adamant/Services/APICore.swift | 12 +- .../ApiService/AdamantApi+Accounts.swift | 8 +- .../ApiService/AdamantApi+Chats.swift | 12 +- .../ApiService/AdamantApi+Delegates.swift | 24 ++-- .../Services/ApiService/AdamantApi+Keys.swift | 4 +- .../ApiService/AdamantApi+States.swift | 4 +- .../ApiService/AdamantApi+Transactions.swift | 16 +-- .../Services/ApiService/AdamantApiCore.swift | 8 +- .../ApiService/AdamantApiService.swift | 6 +- .../BlockchainHealthCheckWrapper.swift | 107 ++++++++++---- Adamant/Services/HealthCheckWrapper.swift | 11 +- Adamant/Services/NodesStorage.swift | 50 +++---- .../AdamantHealthCheckServiceTests.swift | 2 +- AdamantTests/NodesAllowanceTests.swift | 2 +- .../ExtensionsTools/ExtensionsApi.swift | 5 +- .../CommonKit/Helpers/Node+NodeDTO.swift | 38 +++++ .../CommonKit/Helpers/SafeDecodingArray.swift | 40 ++++++ .../Helpers/SwiftUI/View+Extension.swift | 1 + .../CommonKit/Models/Keychain/NodeDTO.swift | 19 +++ .../Models/Keychain/OldNodeDTO.swift | 93 +++++++++++++ CommonKit/Sources/CommonKit/Models/Node.swift | 131 ++++++------------ .../Models/NodeConnectionStatus.swift | 33 +++++ .../Sources/CommonKit/Models/NodeOrigin.swift | 84 +++++++++++ 51 files changed, 739 insertions(+), 405 deletions(-) create mode 100644 Adamant/Helpers/NodeWithGroup+NodeWithGroupDTO.swift create mode 100644 Adamant/Models/Keychain/NodeWithGroupDTO.swift create mode 100644 Adamant/Models/Keychain/OldNodeWithGroupDTO.swift create mode 100644 CommonKit/Sources/CommonKit/Helpers/Node+NodeDTO.swift create mode 100644 CommonKit/Sources/CommonKit/Helpers/SafeDecodingArray.swift create mode 100644 CommonKit/Sources/CommonKit/Models/Keychain/NodeDTO.swift create mode 100644 CommonKit/Sources/CommonKit/Models/Keychain/OldNodeDTO.swift create mode 100644 CommonKit/Sources/CommonKit/Models/NodeConnectionStatus.swift create mode 100644 CommonKit/Sources/CommonKit/Models/NodeOrigin.swift diff --git a/Adamant.xcodeproj/project.pbxproj b/Adamant.xcodeproj/project.pbxproj index 0f21beb0c..0cad71528 100644 --- a/Adamant.xcodeproj/project.pbxproj +++ b/Adamant.xcodeproj/project.pbxproj @@ -226,6 +226,7 @@ 93294B9A2AAD624100911109 /* WalletFactoryCompose.swift in Sources */ = {isa = PBXBuildFile; fileRef = 93294B992AAD624100911109 /* WalletFactoryCompose.swift */; }; 932B34E92974AA4A002A75BA /* ChatPreservationProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 932B34E82974AA4A002A75BA /* ChatPreservationProtocol.swift */; }; 932F77592989F999006D8801 /* ChatCellManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 932F77582989F999006D8801 /* ChatCellManager.swift */; }; + 9332DBCD2C58C398000EC872 /* OldNodeWithGroupDTO.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9332DBCC2C58C398000EC872 /* OldNodeWithGroupDTO.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 */; }; @@ -322,6 +323,8 @@ 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 */; }; + 93D02C802C564EF90011D819 /* NodeWithGroupDTO.swift in Sources */ = {isa = PBXBuildFile; fileRef = 93D02C7F2C564EF90011D819 /* NodeWithGroupDTO.swift */; }; + 93D02C822C564F710011D819 /* NodeWithGroup+NodeWithGroupDTO.swift in Sources */ = {isa = PBXBuildFile; fileRef = 93D02C812C564F710011D819 /* NodeWithGroup+NodeWithGroupDTO.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 */; }; @@ -873,6 +876,7 @@ 93294B992AAD624100911109 /* WalletFactoryCompose.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WalletFactoryCompose.swift; sourceTree = ""; }; 932B34E82974AA4A002A75BA /* ChatPreservationProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChatPreservationProtocol.swift; sourceTree = ""; }; 932F77582989F999006D8801 /* ChatCellManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChatCellManager.swift; sourceTree = ""; }; + 9332DBCC2C58C398000EC872 /* OldNodeWithGroupDTO.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OldNodeWithGroupDTO.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 = ""; }; @@ -964,6 +968,8 @@ 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 = ""; }; + 93D02C7F2C564EF90011D819 /* NodeWithGroupDTO.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NodeWithGroupDTO.swift; sourceTree = ""; }; + 93D02C812C564F710011D819 /* NodeWithGroup+NodeWithGroupDTO.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "NodeWithGroup+NodeWithGroupDTO.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 = ""; }; @@ -1835,6 +1841,15 @@ path = DTO; sourceTree = ""; }; + 93D02C7E2C564DEE0011D819 /* Keychain */ = { + isa = PBXGroup; + children = ( + 93D02C7F2C564EF90011D819 /* NodeWithGroupDTO.swift */, + 9332DBCC2C58C398000EC872 /* OldNodeWithGroupDTO.swift */, + ); + path = Keychain; + sourceTree = ""; + }; 93E123342A6DFCA6004DF33B /* NotificationsShared */ = { isa = PBXGroup; children = ( @@ -2045,6 +2060,7 @@ E913C9091FFFA95A001A83F7 /* Models */ = { isa = PBXGroup; children = ( + 93D02C7E2C564DEE0011D819 /* Keychain */, E91947B72000326B001362F8 /* ServerResponses */, E95F859220094B8E0070534A /* CoreData */, E91947B320002809001362F8 /* AdamantAccount.swift */, @@ -2117,6 +2133,7 @@ 936658942B0AC15300BDB2D3 /* Node+UI.swift */, 3AF53F8C2B3DCFA300B30312 /* NodeGroup+Constants.swift */, 3AA3880B2B69201B00125684 /* ADM+JsonDecode.swift */, + 93D02C812C564F710011D819 /* NodeWithGroup+NodeWithGroupDTO.swift */, ); path = Helpers; sourceTree = ""; @@ -3059,6 +3076,7 @@ E9E7CD8D20026B6600DFC4DB /* DialogService.swift in Sources */, E9E7CDB72003994E00DFC4DB /* AdamantUtilities+extended.swift in Sources */, E9147B6320505C7500145913 /* QRCodeReader+adamant.swift in Sources */, + 9332DBCD2C58C398000EC872 /* OldNodeWithGroupDTO.swift in Sources */, E90055F720EC200900D0CB2D /* SecurityViewController.swift in Sources */, 939FA3422B0D6F0000710EC6 /* SelfRemovableHostingController.swift in Sources */, 644EC35E20F34F1E00F40C73 /* DelegateDetailsViewController.swift in Sources */, @@ -3132,6 +3150,7 @@ E9E7CD932002740500DFC4DB /* AdamantAccountService.swift in Sources */, 41330F7629F1509400CB587C /* AdamantCellAnimation.swift in Sources */, 64FA53CD20E1300B006783C9 /* EthTransactionsViewController.swift in Sources */, + 93D02C822C564F710011D819 /* NodeWithGroup+NodeWithGroupDTO.swift in Sources */, 6449BA6A235CA0930033B936 /* ERC20Wallet.swift in Sources */, E9147B612050599000145913 /* LoginViewController+QR.swift in Sources */, 9399F5ED29A85A48006C3E30 /* ChatCacheService.swift in Sources */, @@ -3190,6 +3209,7 @@ 648BCA6D213D384F00875EB5 /* AvatarService.swift in Sources */, E95F856F2007B61D0070534A /* GetPublicKeyResponse.swift in Sources */, 3A26D94B2C3D3838003AD832 /* KlyTransactionsViewController.swift in Sources */, + 93D02C802C564EF90011D819 /* NodeWithGroupDTO.swift in Sources */, 936658992B0AD32600BDB2D3 /* CoinsNodesListViewModel+ApiServices.swift in Sources */, 644EC34D20EFA60900F40C73 /* AdamantApi+Delegates.swift in Sources */, E940088F2119A9E800CD2D67 /* BigInt+Decimal.swift in Sources */, diff --git a/Adamant/Helpers/Node+UI.swift b/Adamant/Helpers/Node+UI.swift index 25a06a4c8..151ee35e5 100644 --- a/Adamant/Helpers/Node+UI.swift +++ b/Adamant/Helpers/Node+UI.swift @@ -46,7 +46,7 @@ extension Node { func indicatorString(isRest: Bool, isWs: Bool) -> String { let connections = [ - isRest ? scheme.rawValue : nil, + isRest ? preferredOrigin.scheme.rawValue : nil, isWs ? "ws" : nil ].compactMap { $0 } diff --git a/Adamant/Helpers/NodeWithGroup+NodeWithGroupDTO.swift b/Adamant/Helpers/NodeWithGroup+NodeWithGroupDTO.swift new file mode 100644 index 000000000..88041ad56 --- /dev/null +++ b/Adamant/Helpers/NodeWithGroup+NodeWithGroupDTO.swift @@ -0,0 +1,19 @@ +// +// NodeWithGroup+NodeWithGroupDTO.swift +// Adamant +// +// Created by Andrew G on 28.07.2024. +// Copyright © 2024 Adamant. All rights reserved. +// + +extension NodeWithGroup { + func mapToDto() -> NodeWithGroupDTO { + .init(group: group, node: node.mapToDto()) + } +} + +extension NodeWithGroupDTO { + func mapToModel() -> NodeWithGroup { + .init(group: group, node: node.mapToModel()) + } +} diff --git a/Adamant/Models/Keychain/NodeWithGroupDTO.swift b/Adamant/Models/Keychain/NodeWithGroupDTO.swift new file mode 100644 index 000000000..3d572ab3c --- /dev/null +++ b/Adamant/Models/Keychain/NodeWithGroupDTO.swift @@ -0,0 +1,14 @@ +// +// NodeWithGroupDTO.swift +// Adamant +// +// Created by Andrew G on 28.07.2024. +// Copyright © 2024 Adamant. All rights reserved. +// + +import CommonKit + +struct NodeWithGroupDTO: Codable { + let group: NodeGroup + let node: NodeDTO +} diff --git a/Adamant/Models/Keychain/OldNodeWithGroupDTO.swift b/Adamant/Models/Keychain/OldNodeWithGroupDTO.swift new file mode 100644 index 000000000..3ae581a57 --- /dev/null +++ b/Adamant/Models/Keychain/OldNodeWithGroupDTO.swift @@ -0,0 +1,20 @@ +// +// OldNodeWithGroupDTO.swift +// Adamant +// +// Created by Andrew G on 30.07.2024. +// Copyright © 2024 Adamant. All rights reserved. +// + +import CommonKit + +struct OldNodeWithGroupDTO: Codable { + let group: NodeGroup + let node: OldNodeDTO +} + +extension OldNodeWithGroupDTO { + func mapToModernDto() -> NodeWithGroupDTO { + .init(group: group, node: node.mapToModernDto()) + } +} diff --git a/Adamant/Models/NodeWithGroup.swift b/Adamant/Models/NodeWithGroup.swift index 7574cb556..e06510bec 100644 --- a/Adamant/Models/NodeWithGroup.swift +++ b/Adamant/Models/NodeWithGroup.swift @@ -8,7 +8,7 @@ import CommonKit -struct NodeWithGroup: Codable, Equatable { +struct NodeWithGroup: Equatable { let group: NodeGroup var node: Node } diff --git a/Adamant/Modules/CoinsNodesList/ViewModel/CoinsNodesListViewModel.swift b/Adamant/Modules/CoinsNodesList/ViewModel/CoinsNodesListViewModel.swift index d9f126b7b..91edbfa92 100644 --- a/Adamant/Modules/CoinsNodesList/ViewModel/CoinsNodesListViewModel.swift +++ b/Adamant/Modules/CoinsNodesList/ViewModel/CoinsNodesListViewModel.swift @@ -37,7 +37,7 @@ final class CoinsNodesListViewModel: ObservableObject { } func setIsEnabled(id: UUID, value: Bool) { - nodesStorage.updateNodeParams(id: id, isEnabled: value) + nodesStorage.updateNode(id: id) { $0.isEnabled = value } } func reset() { diff --git a/Adamant/Modules/NodesEditor/NodeEditorViewController.swift b/Adamant/Modules/NodesEditor/NodeEditorViewController.swift index e4c4422be..905bdf538 100644 --- a/Adamant/Modules/NodesEditor/NodeEditorViewController.swift +++ b/Adamant/Modules/NodesEditor/NodeEditorViewController.swift @@ -114,7 +114,7 @@ final class NodeEditorViewController: FormViewController { super.viewDidLoad() if let node = node { - self.navigationItem.title = node.host + self.navigationItem.title = node.mainOrigin.host } else { self.navigationItem.title = String.adamant.nodesEditor.newNodeTitle } @@ -131,7 +131,7 @@ final class NodeEditorViewController: FormViewController { $0.tag = Rows.host.tag $0.placeholder = Rows.host.placeholder - $0.value = node?.host + $0.value = node?.mainOrigin.host } // Port @@ -140,18 +140,18 @@ final class NodeEditorViewController: FormViewController { $0.tag = Rows.port.tag if let node = node { - $0.value = node.port - $0.placeholder = String(node.scheme.defaultPort) + $0.value = node.mainOrigin.port + $0.placeholder = String(node.mainOrigin.scheme.defaultPort) } else { - $0.placeholder = String(Node.URLScheme.default.defaultPort) + $0.placeholder = String(NodeOrigin.URLScheme.default.defaultPort) } } // Scheme - <<< PickerInlineRow { + <<< PickerInlineRow { $0.title = Rows.scheme.localized $0.tag = Rows.scheme.tag - $0.value = node?.scheme ?? Node.URLScheme.default + $0.value = node?.mainOrigin.scheme ?? NodeOrigin.URLScheme.default $0.options = [.https, .http] $0.baseCell.detailTextLabel?.textColor = .adamant.textColor }.onExpandInlineRow { (cell, _, inlineRow) in @@ -161,7 +161,7 @@ final class NodeEditorViewController: FormViewController { if let scheme = row.value { portRow.placeholder = String(scheme.defaultPort) } else { - portRow.placeholder = String(Node.URLScheme.default.defaultPort) + portRow.placeholder = String(NodeOrigin.URLScheme.default.defaultPort) } portRow.updateCell() @@ -223,11 +223,11 @@ extension NodeEditorViewController { } let host = rawUrl.trimmingCharacters(in: .whitespaces) - let scheme: Node.URLScheme + let scheme: NodeOrigin.URLScheme if let row = form.rowBy(tag: Rows.scheme.tag), - let value = row.baseValue as? Node.URLScheme + let value = row.baseValue as? NodeOrigin.URLScheme { scheme = value } else { @@ -243,21 +243,29 @@ extension NodeEditorViewController { let result: NodeEditorResult if let node = node { - nodesStorage.updateNodeParams( - id: node.id, - scheme: scheme, - host: host, - port: port - ) + nodesStorage.updateNode(id: node.id) { node in + node.mainOrigin.scheme = scheme + node.mainOrigin.host = host + node.mainOrigin.port = port + } result = .nodeUpdated } else { - result = .new(node: Node( - scheme: scheme, - host: host, + result = .new(node: .init( + id: .init(), isEnabled: true, wsEnabled: false, - port: port + mainOrigin: .init( + scheme: scheme, + host: host, + port: port + ), + altOrigin: nil, + version: nil, + height: nil, + ping: nil, + connectionStatus: nil, + preferMainOrigin: nil )) } diff --git a/Adamant/Modules/NodesEditor/NodesListViewController.swift b/Adamant/Modules/NodesEditor/NodesListViewController.swift index 69a7cf3a6..ea4599e8b 100644 --- a/Adamant/Modules/NodesEditor/NodesListViewController.swift +++ b/Adamant/Modules/NodesEditor/NodesListViewController.swift @@ -429,7 +429,7 @@ extension NodesListViewController { statusString: node.statusString(showVersion: true) ?? .empty, isEnabled: node.isEnabled, nodeUpdateAction: .init(id: node.id.uuidString) { [nodesStorage] isEnabled in - nodesStorage.updateNodeParams(id: node.id, isEnabled: isEnabled) + nodesStorage.updateNode(id: node.id) { $0.isEnabled = isEnabled } } ) } diff --git a/Adamant/Modules/Wallets/Bitcoin/BtcApiService.swift b/Adamant/Modules/Wallets/Bitcoin/BtcApiService.swift index 6834da542..f41593a22 100644 --- a/Adamant/Modules/Wallets/Bitcoin/BtcApiService.swift +++ b/Adamant/Modules/Wallets/Bitcoin/BtcApiService.swift @@ -17,17 +17,17 @@ final class BtcApiCore: BlockchainHealthCheckableService { } func request( - node: Node, - _ request: @Sendable @escaping (APICoreProtocol, Node) async -> ApiServiceResult + origin: NodeOrigin, + _ request: @Sendable @escaping (APICoreProtocol, NodeOrigin) async -> ApiServiceResult ) async -> WalletServiceResult { - await request(apiCore, node).mapError { $0.asWalletServiceError() } + await request(apiCore, origin).mapError { $0.asWalletServiceError() } } - func getStatusInfo(node: Node) async -> WalletServiceResult { + func getStatusInfo(origin: NodeOrigin) async -> WalletServiceResult { let startTimestamp = Date.now.timeIntervalSince1970 let response = await apiCore.sendRequestRPC( - node: node, + origin: origin, path: BtcApiCommands.getRPC(), requests: [ .init(method: BtcApiCommands.blockchainInfoMethod), @@ -80,16 +80,16 @@ final class BtcApiService: WalletApiService { } func request( - _ request: @Sendable @escaping (APICoreProtocol, Node) async -> ApiServiceResult + _ request: @Sendable @escaping (APICoreProtocol, NodeOrigin) async -> ApiServiceResult ) async -> WalletServiceResult { - await api.request { core, node in - await core.request(node: node, request) + await api.request { core, origin in + await core.request(origin: origin, request) } } func getStatusInfo() async -> WalletServiceResult { - await api.request { core, node in - await core.getStatusInfo(node: node) + await api.request { core, origin in + await core.getStatusInfo(origin: origin) } } } diff --git a/Adamant/Modules/Wallets/Bitcoin/BtcWalletService+Send.swift b/Adamant/Modules/Wallets/Bitcoin/BtcWalletService+Send.swift index 46f0f0f07..484f85583 100644 --- a/Adamant/Modules/Wallets/Bitcoin/BtcWalletService+Send.swift +++ b/Adamant/Modules/Wallets/Bitcoin/BtcWalletService+Send.swift @@ -65,9 +65,9 @@ extension BtcWalletService: WalletServiceTwoStepSend { let txHex = transaction.serialized().hex // MARK: Sending request - let responseData = try await btcApiService.request { core, node in + let responseData = try await btcApiService.request { core, origin in await core.sendRequest( - node: node, + origin: origin, path: BtcApiCommands.sendTransaction(), method: .post, parameters: [String.empty: txHex], @@ -88,9 +88,9 @@ extension BtcWalletService: WalletServiceTwoStepSend { let address = wallet.address let parameters = ["noCache": "1"] - let responseData = try await btcApiService.request { core, node in + let responseData = try await btcApiService.request { core, origin in await core.sendRequest( - node: node, + origin: origin, path: BtcApiCommands.getUnspentTransactions(for: address), method: .get, parameters: parameters, @@ -128,5 +128,4 @@ extension BtcWalletService: WalletServiceTwoStepSend { return utxos } - } diff --git a/Adamant/Modules/Wallets/Bitcoin/BtcWalletService.swift b/Adamant/Modules/Wallets/Bitcoin/BtcWalletService.swift index 2792f8b5b..b54564b31 100644 --- a/Adamant/Modules/Wallets/Bitcoin/BtcWalletService.swift +++ b/Adamant/Modules/Wallets/Bitcoin/BtcWalletService.swift @@ -516,16 +516,16 @@ extension BtcWalletService { } func getBalance(address: String) async throws -> Decimal { - let response: BtcBalanceResponse = try await btcApiService.request { api, node in - await api.sendRequestJsonResponse(node: node, path: BtcApiCommands.balance(for: address)) + let response: BtcBalanceResponse = try await btcApiService.request { api, origin in + await api.sendRequestJsonResponse(origin: origin, path: BtcApiCommands.balance(for: address)) }.get() return response.value / BtcWalletService.multiplier } func getFeeRate() async throws -> Decimal { - let response: [String: Decimal] = try await btcApiService.request { api, node in - await api.sendRequestJsonResponse(node: node, path: BtcApiCommands.getFeeRate()) + let response: [String: Decimal] = try await btcApiService.request { api, origin in + await api.sendRequestJsonResponse(origin: origin, path: BtcApiCommands.getFeeRate()) }.get() return response["2"] ?? 1 @@ -663,9 +663,9 @@ extension BtcWalletService { for address: String, fromTx: String? = nil ) async throws -> [RawBtcTransactionResponse] { - return try await btcApiService.request { api, node in + return try await btcApiService.request { api, origin in await api.sendRequestJsonResponse( - node: node, + origin: origin, path: BtcApiCommands.getTransactions( for: address, fromTx: fromTx @@ -679,9 +679,9 @@ extension BtcWalletService { throw WalletServiceError.notLogged } - let rawTransaction: RawBtcTransactionResponse = try await btcApiService.request { api, node in + let rawTransaction: RawBtcTransactionResponse = try await btcApiService.request { api, origin in await api.sendRequestJsonResponse( - node: node, + origin: origin, path: BtcApiCommands.getTransaction(by: hash) ) }.get() diff --git a/Adamant/Modules/Wallets/Dash/DashApiService.swift b/Adamant/Modules/Wallets/Dash/DashApiService.swift index 051441f0a..9d0312d2e 100644 --- a/Adamant/Modules/Wallets/Dash/DashApiService.swift +++ b/Adamant/Modules/Wallets/Dash/DashApiService.swift @@ -17,17 +17,17 @@ final class DashApiCore: BlockchainHealthCheckableService { } func request( - node: Node, - _ request: @Sendable @escaping (APICoreProtocol, Node) async -> ApiServiceResult + origin: NodeOrigin, + _ request: @Sendable @escaping (APICoreProtocol, NodeOrigin) async -> ApiServiceResult ) async -> WalletServiceResult { - await request(apiCore, node).mapError { $0.asWalletServiceError() } + await request(apiCore, origin).mapError { $0.asWalletServiceError() } } - func getStatusInfo(node: Node) async -> WalletServiceResult { + func getStatusInfo(origin: NodeOrigin) async -> WalletServiceResult { let startTimestamp = Date.now.timeIntervalSince1970 let response = await apiCore.sendRequestRPC( - node: node, + origin: origin, path: .empty, requests: [ .init(method: DashApiComand.networkInfoMethod), @@ -80,16 +80,16 @@ final class DashApiService: WalletApiService { } func request( - _ request: @Sendable @escaping (APICoreProtocol, Node) async -> ApiServiceResult + _ request: @Sendable @escaping (APICoreProtocol, NodeOrigin) async -> ApiServiceResult ) async -> WalletServiceResult { - await api.request { core, node in - await core.request(node: node, request) + await api.request { core, origin in + await core.request(origin: origin, request) } } func getStatusInfo() async -> WalletServiceResult { - await api.request { core, node in - await core.getStatusInfo(node: node) + await api.request { core, origin in + await core.getStatusInfo(origin: origin) } } } diff --git a/Adamant/Modules/Wallets/Dash/DashWalletService+Send.swift b/Adamant/Modules/Wallets/Dash/DashWalletService+Send.swift index e2aa6cf8a..cd5b1a860 100644 --- a/Adamant/Modules/Wallets/Dash/DashWalletService+Send.swift +++ b/Adamant/Modules/Wallets/Dash/DashWalletService+Send.swift @@ -85,9 +85,9 @@ extension DashWalletService: WalletServiceTwoStepSend { func sendTransaction(_ transaction: BitcoinKit.Transaction) async throws { let txHex = transaction.serialized().hex - let response: BTCRPCServerResponce = try await dashApiService.request { core, node in + let response: BTCRPCServerResponce = try await dashApiService.request { core, origin in await core.sendRequestJsonResponse( - node: node, + origin: origin, path: .empty, method: .post, parameters: DashSendRawTransactionDTO(txHex: txHex), diff --git a/Adamant/Modules/Wallets/Dash/DashWalletService+Transactions.swift b/Adamant/Modules/Wallets/Dash/DashWalletService+Transactions.swift index c465fe5f1..22f289e63 100644 --- a/Adamant/Modules/Wallets/Dash/DashWalletService+Transactions.swift +++ b/Adamant/Modules/Wallets/Dash/DashWalletService+Transactions.swift @@ -36,9 +36,9 @@ extension DashWalletService { } func getTransaction(by hash: String) async throws -> BTCRawTransaction { - let result: BTCRawTransaction? = try await dashApiService.request { core, node in + let result: BTCRawTransaction? = try await dashApiService.request { core, origin in let response = await core.sendRequestRPC( - node: node, + origin: origin, path: .empty, request: .init( method: DashApiComand.rawTransactionMethod, @@ -73,9 +73,9 @@ extension DashWalletService { ) } - let result: [BTCRawTransaction] = try await dashApiService.request { core, node in + let result: [BTCRawTransaction] = try await dashApiService.request { core, origin in let response = await core.sendRequestRPC( - node: node, + origin: origin, path: .empty, requests: params ) @@ -102,9 +102,9 @@ extension DashWalletService { throw WalletServiceError.internalError(message: "Hash is empty", error: nil) } - let result: BTCRPCServerResponce = try await dashApiService.request { core, node in + let result: BTCRPCServerResponce = try await dashApiService.request { core, origin in await core.sendRequestJsonResponse( - node: node, + origin: origin, path: .empty, method: .post, parameters: DashGetBlockDTO(hash: hash), @@ -125,9 +125,9 @@ extension DashWalletService { } let response: BTCRPCServerResponce<[DashUnspentTransaction]> = try await dashApiService.request { - core, node in + core, origin in await core.sendRequestJsonResponse( - node: node, + origin: origin, path: .empty, method: .post, parameters: DashGetUnspentTransactionDTO(address: wallet.address), @@ -190,9 +190,9 @@ private extension DashWalletService { extension DashWalletService { func requestTransactionsIds(for address: String) async throws -> [String] { let response: BTCRPCServerResponce<[String]> = try await dashApiService.request { - core, node in + core, origin in await core.sendRequestJsonResponse( - node: node, + origin: origin, path: .empty, method: .post, parameters: DashGetAddressTransactionIds(address: address), diff --git a/Adamant/Modules/Wallets/Dash/DashWalletService.swift b/Adamant/Modules/Wallets/Dash/DashWalletService.swift index 695fa2c32..9b6ae2edc 100644 --- a/Adamant/Modules/Wallets/Dash/DashWalletService.swift +++ b/Adamant/Modules/Wallets/Dash/DashWalletService.swift @@ -396,9 +396,9 @@ extension DashWalletService { } func getBalance(address: String) async throws -> Decimal { - let data: Data = try await dashApiService.request { core, node in + let data: Data = try await dashApiService.request { core, origin in await core.sendRequest( - node: node, + origin: origin, path: .empty, method: .post, parameters: DashGetAddressBalanceDTO(address: address), diff --git a/Adamant/Modules/Wallets/Doge/DogeApiService.swift b/Adamant/Modules/Wallets/Doge/DogeApiService.swift index 5874978e2..7446fb989 100644 --- a/Adamant/Modules/Wallets/Doge/DogeApiService.swift +++ b/Adamant/Modules/Wallets/Doge/DogeApiService.swift @@ -17,18 +17,18 @@ final class DogeApiCore: BlockchainHealthCheckableService { } func request( - node: Node, - _ request: @Sendable @escaping (APICoreProtocol, Node) async -> ApiServiceResult + origin: NodeOrigin, + _ request: @Sendable @escaping (APICoreProtocol, NodeOrigin) async -> ApiServiceResult ) async -> WalletServiceResult { - await request(apiCore, node).mapError { $0.asWalletServiceError() } + await request(apiCore, origin).mapError { $0.asWalletServiceError() } } - func getStatusInfo(node: Node) async -> WalletServiceResult { + func getStatusInfo(origin: NodeOrigin) async -> WalletServiceResult { let startTimestamp = Date.now.timeIntervalSince1970 - let response: WalletServiceResult = await request(node: node) { core, node in + let response: WalletServiceResult = await request(origin: origin) { core, origin in await core.sendRequestJsonResponse( - node: node, + origin: origin, path: DogeApiCommands.getInfo() ) } @@ -61,16 +61,16 @@ final class DogeApiService: WalletApiService { } func request( - _ request: @Sendable @escaping (APICoreProtocol, Node) async -> ApiServiceResult + _ request: @Sendable @escaping (APICoreProtocol, NodeOrigin) async -> ApiServiceResult ) async -> WalletServiceResult { - await api.request { core, node in - await core.request(node: node, request) + await api.request { core, origin in + await core.request(origin: origin, request) } } func getStatusInfo() async -> WalletServiceResult { - await api.request { core, node in - await core.getStatusInfo(node: node) + await api.request { core, origin in + await core.getStatusInfo(origin: origin) } } } diff --git a/Adamant/Modules/Wallets/Doge/DogeWalletService+Send.swift b/Adamant/Modules/Wallets/Doge/DogeWalletService+Send.swift index b3e61438f..4725d556b 100644 --- a/Adamant/Modules/Wallets/Doge/DogeWalletService+Send.swift +++ b/Adamant/Modules/Wallets/Doge/DogeWalletService+Send.swift @@ -69,9 +69,9 @@ extension DogeWalletService: WalletServiceTwoStepSend { func sendTransaction(_ transaction: BitcoinKit.Transaction) async throws { let txHex = transaction.serialized().hex - _ = try await dogeApiService.api.request { core, node in + _ = try await dogeApiService.api.request { core, origin in let response: APIResponseModel = await core.apiCore.sendRequestBasic( - node: node, + origin: origin, path: DogeApiCommands.sendTransaction(), method: .post, parameters: ["rawtx": txHex], diff --git a/Adamant/Modules/Wallets/Doge/DogeWalletService.swift b/Adamant/Modules/Wallets/Doge/DogeWalletService.swift index 062706875..3e574ce5e 100644 --- a/Adamant/Modules/Wallets/Doge/DogeWalletService.swift +++ b/Adamant/Modules/Wallets/Doge/DogeWalletService.swift @@ -383,9 +383,9 @@ extension DogeWalletService { } func getBalance(address: String) async throws -> Decimal { - let data: Data = try await dogeApiService.request { core, node in + let data: Data = try await dogeApiService.request { core, origin in await core.sendRequest( - node: node, + origin: origin, path: DogeApiCommands.balance(for: address) ) }.get() @@ -525,9 +525,9 @@ extension DogeWalletService { "to": to ] - return try await dogeApiService.request { core, node in + return try await dogeApiService.request { core, origin in await core.sendRequestJsonResponse( - node: node, + origin: origin, path: DogeApiCommands.getTransactions(for: address), method: .get, parameters: parameters, @@ -548,9 +548,9 @@ extension DogeWalletService { ] // MARK: Sending request - let data = try await dogeApiService.request { core, node in + let data = try await dogeApiService.request { core, origin in await core.sendRequest( - node: node, + origin: origin, path: DogeApiCommands.getUnspentTransactions(for: address), method: .get, parameters: parameters, @@ -597,17 +597,17 @@ extension DogeWalletService { } func getTransaction(by hash: String) async throws -> BTCRawTransaction { - try await dogeApiService.request { core, node in + try await dogeApiService.request { core, origin in await core.sendRequestJsonResponse( - node: node, + origin: origin, path: DogeApiCommands.getTransaction(by: hash) ) }.get() } func getBlockId(by hash: String) async throws -> String { - let data = try await dogeApiService.request { core, node in - await core.sendRequest(node: node, path: DogeApiCommands.getBlock(by: hash)) + let data = try await dogeApiService.request { core, origin in + await core.sendRequest(origin: origin, path: DogeApiCommands.getBlock(by: hash)) }.get() let json = try? JSONSerialization.jsonObject( diff --git a/Adamant/Modules/Wallets/ERC20/ERC20WalletService.swift b/Adamant/Modules/Wallets/ERC20/ERC20WalletService.swift index 88e73bec2..852d71488 100644 --- a/Adamant/Modules/Wallets/ERC20/ERC20WalletService.swift +++ b/Adamant/Modules/Wallets/ERC20/ERC20WalletService.swift @@ -548,9 +548,9 @@ extension ERC20WalletService { "order": "time.desc" ] - var transactions: [EthTransactionShort] = try await erc20ApiService.requestApiCore { core, node in + var transactions: [EthTransactionShort] = try await erc20ApiService.requestApiCore { core, origin in await core.sendRequestJsonResponse( - node: node, + origin: origin, path: EthWalletService.transactionsListApiSubpath, method: .get, parameters: txQueryParameters, diff --git a/Adamant/Modules/Wallets/Ethereum/EthApiCore.swift b/Adamant/Modules/Wallets/Ethereum/EthApiCore.swift index 833f3160a..0e29ab563 100644 --- a/Adamant/Modules/Wallets/Ethereum/EthApiCore.swift +++ b/Adamant/Modules/Wallets/Ethereum/EthApiCore.swift @@ -17,10 +17,10 @@ actor EthApiCore { private var web3Cache: [URL: Web3] = .init() func performRequest( - node: Node, + origin: NodeOrigin, _ body: @escaping @Sendable (_ web3: Web3) async throws -> Success ) async -> WalletServiceResult { - switch await getWeb3(node: node) { + switch await getWeb3(origin: origin) { case let .success(web3): do { return .success(try await body(web3)) @@ -43,11 +43,11 @@ actor EthApiCore { } extension EthApiCore: BlockchainHealthCheckableService { - func getStatusInfo(node: Node) async -> WalletServiceResult { + func getStatusInfo(origin: NodeOrigin) async -> WalletServiceResult { let startTimestamp = Date.now.timeIntervalSince1970 let response = await apiCore.sendRequestRPC( - node: node, + origin: origin, path: .empty, requests: [ .init(method: EthApiComand.blockNumberMethod), @@ -91,8 +91,8 @@ extension EthApiCore: BlockchainHealthCheckableService { } private extension EthApiCore { - func getWeb3(node: Node) async -> WalletServiceResult { - guard let url = node.asURL() else { + func getWeb3(origin: NodeOrigin) async -> WalletServiceResult { + guard let url = origin.asURL() else { return .failure(.internalError(.endpointBuildFailed)) } diff --git a/Adamant/Modules/Wallets/Ethereum/EthApiService.swift b/Adamant/Modules/Wallets/Ethereum/EthApiService.swift index 701bad3d2..928c4dc7b 100644 --- a/Adamant/Modules/Wallets/Ethereum/EthApiService.swift +++ b/Adamant/Modules/Wallets/Ethereum/EthApiService.swift @@ -33,22 +33,22 @@ class EthApiService: WalletApiService { func requestWeb3( _ request: @Sendable @escaping (Web3) async throws -> Output ) async -> WalletServiceResult { - await api.request { core, node in - await core.performRequest(node: node, request) + await api.request { core, origin in + await core.performRequest(origin: origin, request) } } func requestApiCore( - _ request: @Sendable @escaping (APICoreProtocol, Node) async -> ApiServiceResult + _ request: @Sendable @escaping (APICoreProtocol, NodeOrigin) async -> ApiServiceResult ) async -> WalletServiceResult { - await api.request { core, node in - await request(core.apiCore, node).mapError { $0.asWalletServiceError() } + await api.request { core, origin in + await request(core.apiCore, origin).mapError { $0.asWalletServiceError() } } } func getStatusInfo() async -> WalletServiceResult { - await api.request { core, node in - await core.getStatusInfo(node: node) + await api.request { core, origin in + await core.getStatusInfo(origin: origin) } } diff --git a/Adamant/Modules/Wallets/Ethereum/EthWalletService.swift b/Adamant/Modules/Wallets/Ethereum/EthWalletService.swift index fb14484ae..e9d74d3c4 100644 --- a/Adamant/Modules/Wallets/Ethereum/EthWalletService.swift +++ b/Adamant/Modules/Wallets/Ethereum/EthWalletService.swift @@ -701,9 +701,9 @@ extension EthWalletService { "contract_to": "eq." ] - let transactionsFrom: [EthTransactionShort] = try await ethApiService.requestApiCore { core, node in + let transactionsFrom: [EthTransactionShort] = try await ethApiService.requestApiCore { core, origin in await core.sendRequestJsonResponse( - node: node, + origin: origin, path: EthWalletService.transactionsListApiSubpath, method: .get, parameters: txFromQueryParameters, @@ -711,9 +711,9 @@ extension EthWalletService { ) }.get() - let transactionsTo: [EthTransactionShort] = try await ethApiService.requestApiCore { core, node in + let transactionsTo: [EthTransactionShort] = try await ethApiService.requestApiCore { core, origin in await core.sendRequestJsonResponse( - node: node, + origin: origin, path: EthWalletService.transactionsListApiSubpath, method: .get, parameters: txToQueryParameters, diff --git a/Adamant/Modules/Wallets/Klayr/KlyApiCore.swift b/Adamant/Modules/Wallets/Klayr/KlyApiCore.swift index 769cdc204..faa3e8221 100644 --- a/Adamant/Modules/Wallets/Klayr/KlyApiCore.swift +++ b/Adamant/Modules/Wallets/Klayr/KlyApiCore.swift @@ -11,33 +11,33 @@ import Foundation import LiskKit class KlyApiCore: BlockchainHealthCheckableService { - func makeClient(node: CommonKit.Node) -> APIClient { + func makeClient(origin: NodeOrigin) -> APIClient { .init(options: .init( - nodes: [.init(origin: node.asString())], + nodes: [.init(origin: origin.asString())], nethash: .mainnet, randomNode: false )) } func request( - node: CommonKit.Node, + origin: NodeOrigin, body: @escaping @Sendable ( _ client: APIClient, _ completion: @escaping @Sendable (LiskKit.Result) -> Void ) -> Void ) async -> WalletServiceResult { await withCheckedContinuation { continuation in - body(makeClient(node: node)) { result in + body(makeClient(origin: origin)) { result in continuation.resume(returning: result.asWalletServiceResult()) } } } func request( - node: CommonKit.Node, + origin: NodeOrigin, _ body: @Sendable @escaping (APIClient) async throws -> Output ) async -> WalletServiceResult { - let client = makeClient(node: node) + let client = makeClient(origin: origin) do { return .success(try await body(client)) @@ -46,10 +46,10 @@ class KlyApiCore: BlockchainHealthCheckableService { } } - func getStatusInfo(node: CommonKit.Node) async -> WalletServiceResult { + func getStatusInfo(origin: NodeOrigin) async -> WalletServiceResult { let startTimestamp = Date.now.timeIntervalSince1970 - return await request(node: node) { client in + return await request(origin: origin) { client in try await LiskKit.Node(client: client).info() }.map { model in .init( diff --git a/Adamant/Modules/Wallets/Klayr/KlyNodeApiService.swift b/Adamant/Modules/Wallets/Klayr/KlyNodeApiService.swift index 885bbd5da..5959d6288 100644 --- a/Adamant/Modules/Wallets/Klayr/KlyNodeApiService.swift +++ b/Adamant/Modules/Wallets/Klayr/KlyNodeApiService.swift @@ -52,8 +52,8 @@ final class KlyNodeApiService: WalletApiService { } func getStatusInfo() async -> WalletServiceResult { - await api.request { core, node in - await core.getStatusInfo(node: node) + await api.request { core, origin in + await core.getStatusInfo(origin: origin) } } } @@ -65,16 +65,16 @@ private extension KlyNodeApiService { _ completion: @escaping @Sendable (LiskKit.Result) -> Void ) -> Void ) async -> WalletServiceResult { - await api.request { core, node in - await core.request(node: node, body: body) + await api.request { core, origin in + await core.request(origin: origin, body: body) } } func requestClient( _ body: @Sendable @escaping (APIClient) async throws -> Output ) async -> WalletServiceResult { - await api.request { core, node in - await core.request(node: node, body) + await api.request { core, origin in + await core.request(origin: origin, body) } } } diff --git a/Adamant/Modules/Wallets/Klayr/KlyServiceApiService.swift b/Adamant/Modules/Wallets/Klayr/KlyServiceApiService.swift index b34ded0eb..ebc8e8dff 100644 --- a/Adamant/Modules/Wallets/Klayr/KlyServiceApiService.swift +++ b/Adamant/Modules/Wallets/Klayr/KlyServiceApiService.swift @@ -12,11 +12,11 @@ import CommonKit final class KlyServiceApiCore: KlyApiCore { override func getStatusInfo( - node: CommonKit.Node + origin: NodeOrigin ) async -> WalletServiceResult { let startTimestamp = Date.now.timeIntervalSince1970 - return await request(node: node) { client in + return await request(origin: origin) { client in let service = LiskKit.Service(client: client) return try await (fee: service.fees(), info: service.info()) }.map { model in @@ -73,16 +73,16 @@ private extension KlyServiceApiService { _ completion: @escaping @Sendable (LiskKit.Result) -> Void ) -> Void ) async -> WalletServiceResult { - await api.request { core, node in - await core.request(node: node, body: body) + await api.request { core, origin in + await core.request(origin: origin, body: body) } } func requestClient( _ body: @Sendable @escaping (APIClient) async throws -> Output ) async -> WalletServiceResult { - await api.request { core, node in - await core.request(node: node, body) + await api.request { core, origin in + await core.request(origin: origin, body) } } } diff --git a/Adamant/ServiceProtocols/APICoreProtocol.swift b/Adamant/ServiceProtocols/APICoreProtocol.swift index 4c1ea329a..e9e7bd9e9 100644 --- a/Adamant/ServiceProtocols/APICoreProtocol.swift +++ b/Adamant/ServiceProtocols/APICoreProtocol.swift @@ -15,7 +15,7 @@ enum ApiCommands {} protocol APICoreProtocol: Actor { func sendRequestBasic( - node: Node, + origin: NodeOrigin, path: String, method: HTTPMethod, parameters: Parameters, @@ -24,7 +24,7 @@ protocol APICoreProtocol: Actor { /// jsonParameters - arrays and dictionaries are allowed only func sendRequestBasic( - node: Node, + origin: NodeOrigin, path: String, method: HTTPMethod, jsonParameters: Any @@ -35,14 +35,14 @@ extension APICoreProtocol { var emptyParameters: [String: Bool] { [:] } func sendRequest( - node: Node, + origin: NodeOrigin, path: String, method: HTTPMethod, parameters: Parameters, encoding: APIParametersEncoding ) async -> ApiServiceResult { await sendRequestBasic( - node: node, + origin: origin, path: path, method: method, parameters: parameters, @@ -51,14 +51,14 @@ extension APICoreProtocol { } func sendRequestJsonResponse( - node: Node, + origin: NodeOrigin, path: String, method: HTTPMethod, parameters: Parameters, encoding: APIParametersEncoding ) async -> ApiServiceResult { await sendRequest( - node: node, + origin: origin, path: path, method: method, parameters: parameters, @@ -67,11 +67,11 @@ extension APICoreProtocol { } func sendRequestJsonResponse( - node: Node, + origin: NodeOrigin, path: String ) async -> ApiServiceResult { await sendRequestJsonResponse( - node: node, + origin: origin, path: path, method: .get, parameters: emptyParameters, @@ -80,11 +80,11 @@ extension APICoreProtocol { } func sendRequest( - node: Node, + origin: NodeOrigin, path: String ) async -> ApiServiceResult { await sendRequest( - node: node, + origin: origin, path: path, method: .get, parameters: emptyParameters, @@ -93,13 +93,13 @@ extension APICoreProtocol { } func sendRequestJsonResponse( - node: Node, + origin: NodeOrigin, path: String, method: HTTPMethod, jsonParameters: Any ) async -> ApiServiceResult { await sendRequestBasic( - node: node, + origin: origin, path: path, method: method, jsonParameters: jsonParameters @@ -107,7 +107,7 @@ extension APICoreProtocol { } func sendRequestRPC( - node: Node, + origin: NodeOrigin, path: String, requests: [RpcRequest] ) async -> ApiServiceResult<[RPCResponseModel]> { @@ -116,7 +116,7 @@ extension APICoreProtocol { } return await sendRequestJsonResponse( - node: node, + origin: origin, path: path, method: .post, jsonParameters: parameters @@ -124,12 +124,12 @@ extension APICoreProtocol { } func sendRequestRPC( - node: Node, + origin: NodeOrigin, path: String, request: RpcRequest ) async -> ApiServiceResult { await sendRequestJsonResponse( - node: node, + origin: origin, path: path, method: .post, jsonParameters: request.asDictionary() ?? [:] diff --git a/Adamant/ServiceProtocols/NodesStorageProtocol.swift b/Adamant/ServiceProtocols/NodesStorageProtocol.swift index be9077df9..380ce364c 100644 --- a/Adamant/ServiceProtocols/NodesStorageProtocol.swift +++ b/Adamant/ServiceProtocols/NodesStorageProtocol.swift @@ -24,59 +24,5 @@ protocol NodesStorageProtocol { func resetNodes(group: NodeGroup) func removeNode(id: UUID) func haveActiveNode(in group: NodeGroup) -> Bool - - 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 - ) - } + func updateNode(id: UUID, mutate: (inout Node) -> Void) } diff --git a/Adamant/Services/APICore.swift b/Adamant/Services/APICore.swift index f3748aa34..4c0e74b73 100644 --- a/Adamant/Services/APICore.swift +++ b/Adamant/Services/APICore.swift @@ -26,7 +26,7 @@ actor APICore: APICoreProtocol { }() func sendRequestBasic( - node: Node, + origin: NodeOrigin, path: String, method: HTTPMethod, parameters: Parameters, @@ -34,7 +34,7 @@ actor APICore: APICoreProtocol { ) async -> APIResponseModel { do { let request = session.request( - try buildUrl(node: node, path: path), + try buildUrl(origin: origin, path: path), method: method, parameters: parameters.asDictionary(), encoding: encoding.parametersEncoding, @@ -52,7 +52,7 @@ actor APICore: APICoreProtocol { } func sendRequestBasic( - node: Node, + origin: NodeOrigin, path: String, method: HTTPMethod, jsonParameters: Any @@ -63,7 +63,7 @@ actor APICore: APICoreProtocol { ) var request = try URLRequest( - url: try buildUrl(node: node, path: path), + url: try buildUrl(origin: origin, path: path), method: method ) @@ -93,8 +93,8 @@ private extension APICore { } } - func buildUrl(node: Node, path: String) throws -> URL { - guard let url = node.asURL()?.appendingPathComponent(path, conformingTo: .url) + func buildUrl(origin: NodeOrigin, path: String) throws -> URL { + guard let url = origin.asURL()?.appendingPathComponent(path, conformingTo: .url) else { throw InternalAPIError.endpointBuildFailed } return url } diff --git a/Adamant/Services/ApiService/AdamantApi+Accounts.swift b/Adamant/Services/ApiService/AdamantApi+Accounts.swift index 7b8c6bd71..00c4aabfc 100644 --- a/Adamant/Services/ApiService/AdamantApi+Accounts.swift +++ b/Adamant/Services/ApiService/AdamantApi+Accounts.swift @@ -30,9 +30,9 @@ extension AdamantApiService { /// Get account by publicKey func getAccount(byPublicKey publicKey: String) async -> ApiServiceResult { - switch await request({ apiCore, node in + switch await request({ apiCore, origin in let response: ApiServiceResult> = await apiCore.sendRequestJsonResponse( - node: node, + origin: origin, path: ApiCommands.Accounts.root, method: .get, parameters: ["publicKey": publicKey], @@ -54,11 +54,11 @@ extension AdamantApiService { } func getAccount(byAddress address: String) async -> ApiServiceResult { - await request { apiCore, node in + await request { apiCore, origin in let response: ApiServiceResult< ServerModelResponse > = await apiCore.sendRequestJsonResponse( - node: node, + origin: origin, path: ApiCommands.Accounts.root, method: .get, parameters: ["address": address], diff --git a/Adamant/Services/ApiService/AdamantApi+Chats.swift b/Adamant/Services/ApiService/AdamantApi+Chats.swift index 1ec2c866e..601d74937 100644 --- a/Adamant/Services/ApiService/AdamantApi+Chats.swift +++ b/Adamant/Services/ApiService/AdamantApi+Chats.swift @@ -40,9 +40,9 @@ extension AdamantApiService { } let response: ApiServiceResult> - response = await request { [parameters] service, node in + response = await request { [parameters] service, origin in await service.sendRequestJsonResponse( - node: node, + origin: origin, path: ApiCommands.Chats.get, method: .get, parameters: parameters, @@ -72,9 +72,9 @@ extension AdamantApiService { parameters["offset"] = String(offset) } - return await request { [parameters] service, node in + return await request { [parameters] service, origin in await service.sendRequestJsonResponse( - node: node, + origin: origin, path: ApiCommands.Chats.getChatRooms + "/\(address)", method: .get, parameters: parameters, @@ -99,9 +99,9 @@ extension AdamantApiService { parameters["limit"] = String(limit) } - return await request { [parameters] service, node in + return await request { [parameters] service, origin in await service.sendRequestJsonResponse( - node: node, + origin: origin, path: ApiCommands.Chats.getChatRooms + "/\(address)/\(addressRecipient)", method: .get, parameters: parameters, diff --git a/Adamant/Services/ApiService/AdamantApi+Delegates.swift b/Adamant/Services/ApiService/AdamantApi+Delegates.swift index 0ce14a3f2..fa3fe2f6f 100644 --- a/Adamant/Services/ApiService/AdamantApi+Delegates.swift +++ b/Adamant/Services/ApiService/AdamantApi+Delegates.swift @@ -33,9 +33,9 @@ extension AdamantApiService { currentDelegates: [Delegate] ) async -> ApiServiceResult<[Delegate]> { let response: ApiServiceResult> - response = await request { core, node in + response = await request { core, origin in await core.sendRequestJsonResponse( - node: node, + origin: origin, path: ApiCommands.Delegates.getDelegates, method: .get, parameters: ["limit": String(limit), "offset": String(offset)], @@ -82,9 +82,9 @@ extension AdamantApiService { } func getForgedByAccount(publicKey: String) async -> ApiServiceResult { - await request { core, node in + await request { core, origin in await core.sendRequestJsonResponse( - node: node, + origin: origin, path: ApiCommands.Delegates.getForgedByAccount, method: .get, parameters: ["generatorPublicKey": publicKey], @@ -104,18 +104,18 @@ extension AdamantApiService { } private func getDelegatesCount() async -> ApiServiceResult { - await request { core, node in + await request { core, origin in await core.sendRequestJsonResponse( - node: node, + origin: origin, path: ApiCommands.Delegates.getDelegatesCount ) } } private func getNextForgers() async -> ApiServiceResult { - await request { core, node in + await request { core, origin in await core.sendRequestJsonResponse( - node: node, + origin: origin, path: ApiCommands.Delegates.getNextForgers, method: .get, parameters: ["limit": "\(101)"], @@ -126,9 +126,9 @@ extension AdamantApiService { func getVotes(for address: String) async -> ApiServiceResult<[Delegate]> { let response: ApiServiceResult> - response = await request { core, node in + response = await request { core, origin in await core.sendRequestJsonResponse( - node: node, + origin: origin, path: ApiCommands.Delegates.votes, method: .get, parameters: ["address": address], @@ -183,9 +183,9 @@ extension AdamantApiService { // MARK: - Private methods private func getBlocks() async -> ApiServiceResult<[Block]> { - let response: ApiServiceResult> = await request { core, node in + let response: ApiServiceResult> = await request { core, origin in await core.sendRequestJsonResponse( - node: node, + origin: origin, path: ApiCommands.Delegates.getBlocks, method: .get, parameters: ["orderBy": "height:desc", "limit": "\(101)"], diff --git a/Adamant/Services/ApiService/AdamantApi+Keys.swift b/Adamant/Services/ApiService/AdamantApi+Keys.swift index 2226421f4..2fe178b90 100644 --- a/Adamant/Services/ApiService/AdamantApi+Keys.swift +++ b/Adamant/Services/ApiService/AdamantApi+Keys.swift @@ -11,9 +11,9 @@ import CommonKit extension AdamantApiService { func getPublicKey(byAddress address: String) async -> ApiServiceResult { - let response: ApiServiceResult = await request { service, node in + let response: ApiServiceResult = await request { service, origin in await service.sendRequestJsonResponse( - node: node, + origin: origin, path: ApiCommands.Accounts.getPublicKey, method: .get, parameters: ["address": address], diff --git a/Adamant/Services/ApiService/AdamantApi+States.swift b/Adamant/Services/ApiService/AdamantApi+States.swift index 4800bb41b..08c5c21d2 100644 --- a/Adamant/Services/ApiService/AdamantApi+States.swift +++ b/Adamant/Services/ApiService/AdamantApi+States.swift @@ -63,9 +63,9 @@ extension AdamantApiService { ] let response: ApiServiceResult> - response = await request { [parameters] core, node in + response = await request { [parameters] core, origin in await core.sendRequestJsonResponse( - node: node, + origin: origin, path: ApiCommands.States.get, method: .get, parameters: parameters, diff --git a/Adamant/Services/ApiService/AdamantApi+Transactions.swift b/Adamant/Services/ApiService/AdamantApi+Transactions.swift index f63400128..d33f5cbda 100644 --- a/Adamant/Services/ApiService/AdamantApi+Transactions.swift +++ b/Adamant/Services/ApiService/AdamantApi+Transactions.swift @@ -23,9 +23,9 @@ extension AdamantApiService { path: String, transaction: UnregisteredTransaction ) async -> ApiServiceResult { - let response: ApiServiceResult = await request { core, node in + let response: ApiServiceResult = await request { core, origin in await core.sendRequestJsonResponse( - node: node, + origin: origin, path: path, method: .post, parameters: ["transaction": transaction], @@ -40,9 +40,9 @@ extension AdamantApiService { path: String, transaction: UnregisteredTransaction ) async -> ApiServiceResult { - let response: ApiServiceResult = await request { core, node in + let response: ApiServiceResult = await request { core, origin in await core.sendRequestJsonResponse( - node: node, + origin: origin, path: path, method: .post, parameters: transaction, @@ -62,9 +62,9 @@ extension AdamantApiService { func getTransaction(id: UInt64, withAsset: Bool) async -> ApiServiceResult { let response: ApiServiceResult> - response = await request { core, node in + response = await request { core, origin in await core.sendRequestJsonResponse( - node: node, + origin: origin, path: ApiCommands.Transactions.getTransaction, method: .get, parameters: [ @@ -129,9 +129,9 @@ extension AdamantApiService { } let response: ApiServiceResult> - response = await request { [queryItems] core, node in + response = await request { [queryItems] core, origin in await core.sendRequestJsonResponse( - node: node, + origin: origin, path: ApiCommands.Transactions.root, method: .get, parameters: core.emptyParameters, diff --git a/Adamant/Services/ApiService/AdamantApiCore.swift b/Adamant/Services/ApiService/AdamantApiCore.swift index 1a4861d62..0904f8a65 100644 --- a/Adamant/Services/ApiService/AdamantApiCore.swift +++ b/Adamant/Services/ApiService/AdamantApiCore.swift @@ -22,18 +22,18 @@ final class AdamantApiCore { self.apiCore = apiCore } - func getNodeStatus(node: Node) async -> ApiServiceResult { + func getNodeStatus(origin: NodeOrigin) async -> ApiServiceResult { await apiCore.sendRequestJsonResponse( - node: node, + origin: origin, path: ApiCommands.status ) } } extension AdamantApiCore: BlockchainHealthCheckableService { - func getStatusInfo(node: Node) async -> ApiServiceResult { + func getStatusInfo(origin: NodeOrigin) async -> ApiServiceResult { let startTimestamp = Date.now.timeIntervalSince1970 - let statusResponse = await getNodeStatus(node: node) + let statusResponse = await getNodeStatus(origin: origin) let ping = Date.now.timeIntervalSince1970 - startTimestamp return statusResponse.map { statusDto in diff --git a/Adamant/Services/ApiService/AdamantApiService.swift b/Adamant/Services/ApiService/AdamantApiService.swift index 035a0e3cb..605d10b69 100644 --- a/Adamant/Services/ApiService/AdamantApiService.swift +++ b/Adamant/Services/ApiService/AdamantApiService.swift @@ -22,10 +22,10 @@ final class AdamantApiService { } func request( - _ request: @Sendable (APICoreProtocol, Node) async -> ApiServiceResult + _ request: @Sendable (APICoreProtocol, NodeOrigin) async -> ApiServiceResult ) async -> ApiServiceResult { - await service.request { admApiCore, node in - await request(admApiCore.apiCore, node) + await service.request { admApiCore, origin in + await request(admApiCore.apiCore, origin) } } } diff --git a/Adamant/Services/BlockchainHealthCheckWrapper.swift b/Adamant/Services/BlockchainHealthCheckWrapper.swift index 105eea8d5..7ba1b6897 100644 --- a/Adamant/Services/BlockchainHealthCheckWrapper.swift +++ b/Adamant/Services/BlockchainHealthCheckWrapper.swift @@ -12,7 +12,7 @@ import Foundation protocol BlockchainHealthCheckableService { associatedtype Error: HealthCheckableError - func getStatusInfo(node: Node) async -> Result + func getStatusInfo(origin: NodeOrigin) async -> Result } final class BlockchainHealthCheckWrapper< @@ -57,8 +57,7 @@ final class BlockchainHealthCheckWrapper< 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 self?.updateNodeStatusInfo(node: node) } } @@ -70,27 +69,90 @@ final class BlockchainHealthCheckWrapper< private extension BlockchainHealthCheckWrapper { func updateNodeStatusInfo(node: Node) async { + guard !currentRequests.contains(node.id) else { return } currentRequests.insert(node.id) - defer { currentRequests.remove(node.id) } + var forceInclude: UUID? - let statusInfo = await service.getStatusInfo(node: node) - nodesStorage.updateNodeStatus(id: node.id, statusInfo: try? statusInfo.get()) + defer { + currentRequests.remove(node.id) + updateNodesAvailability(forceInclude: forceInclude) + } - switch statusInfo { - case .success(let info): - if let versionNumber = Node.stringToDouble(info.version), - versionNumber < nodeGroup.minNodeVersion { - nodesStorage.updateNodeParams( - id: node.id, - connectionStatus: .notAllowed(.outdatedApiVersion) - ) + guard node.preferMainOrigin == nil else { + switch await updateNodeStatusInfo( + id: node.id, + origin: node.preferredOrigin, + markAsOfflineIfFailed: true + ) { + case .success: + forceInclude = node.id + case .failure, .none: + break } - updateNodesAvailability(forceInclude: node.id) + return + } + + switch await updateNodeStatusInfo( + id: node.id, + origin: node.mainOrigin, + markAsOfflineIfFailed: false + ) { + case .success: + nodesStorage.updateNode(id: node.id) { $0.preferMainOrigin = true } + forceInclude = node.id + case .failure: + switch await updateNodeStatusInfo( + id: node.id, + origin: node.mainOrigin, + markAsOfflineIfFailed: true + ) { + case .success: + nodesStorage.updateNode(id: node.id) { $0.preferMainOrigin = false } + forceInclude = node.id + case .failure, .none: + break + } + case .none: + break + } + } + + @discardableResult + func updateNodeStatusInfo( + id: UUID, + origin: NodeOrigin, + markAsOfflineIfFailed: Bool + ) async -> Result? { + switch await service.getStatusInfo(origin: origin) { + case let .success(info): + applyStatusInfo(id: id, info: info) + return .success(()) case let .failure(error): - guard !error.isRequestCancelledError else { return } - nodesStorage.updateNodeParams(id: node.id, connectionStatus: .offline) - updateNodesAvailability() + guard !error.isRequestCancelledError else { return nil } + + if markAsOfflineIfFailed { + nodesStorage.updateNode(id: id) { $0.connectionStatus = .offline } + } + + return .failure(error) + } + } + + func applyStatusInfo(id: UUID, info: NodeStatusInfo) { + nodesStorage.updateNode(id: id) { node in + node.wsEnabled = info.wsEnabled + node.updateWsPort(info.wsPort) + node.version = info.version + node.height = info.height + node.ping = info.ping + + guard + let versionNumber = Node.stringToDouble(info.version), + versionNumber < nodeGroup.minNodeVersion + else { return } + + node.connectionStatus = .notAllowed(.outdatedApiVersion) } } @@ -108,12 +170,12 @@ private extension BlockchainHealthCheckWrapper { ) workingNodes.forEach { node in - var status: Node.ConnectionStatus? + var status: NodeConnectionStatus? let actualNodeVersion = Node.stringToDouble(node.version) if let actualNodeVersion = actualNodeVersion, actualNodeVersion < nodeGroup.minNodeVersion { - status = Node.ConnectionStatus.notAllowed(.outdatedApiVersion) + status = .notAllowed(.outdatedApiVersion) } else { status = node.height.map { height in actualHeightsRange?.contains(height) ?? false @@ -122,10 +184,7 @@ private extension BlockchainHealthCheckWrapper { } ?? .none } - nodesStorage.updateNodeParams( - id: node.id, - connectionStatus: status - ) + nodesStorage.updateNode(id: node.id) { $0.connectionStatus = status } } } } diff --git a/Adamant/Services/HealthCheckWrapper.swift b/Adamant/Services/HealthCheckWrapper.swift index e1afb94c5..571fb2d8d 100644 --- a/Adamant/Services/HealthCheckWrapper.swift +++ b/Adamant/Services/HealthCheckWrapper.swift @@ -95,7 +95,7 @@ class HealthCheckWrapper { } func request( - _ request: @Sendable (Service, Node) async -> Result + _ request: @Sendable (Service, NodeOrigin) async -> Result ) async -> Result { var lastConnectionError = allowedNodes.isEmpty ? Error.noEndpointsError(coin: nodeGroup.name) @@ -106,7 +106,7 @@ class HealthCheckWrapper { : allowedNodes.shuffled() for node in nodesList { - let response = await request(service, node) + let response = await request(service, node.preferredOrigin) switch response { case .success: @@ -154,10 +154,9 @@ private extension HealthCheckWrapper { private extension Node { func doesNeedHealthCheck(_ node: Node) -> Bool { - scheme != node.scheme || - host != node.host || - isEnabled != node.isEnabled || - port != node.port + mainOrigin != node.mainOrigin || + altOrigin != node.altOrigin || + isEnabled != node.isEnabled } } diff --git a/Adamant/Services/NodesStorage.swift b/Adamant/Services/NodesStorage.swift index f58c484f4..ce18b8134 100644 --- a/Adamant/Services/NodesStorage.swift +++ b/Adamant/Services/NodesStorage.swift @@ -38,43 +38,17 @@ final class NodesStorage: NodesStorageProtocol { } } - 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?? - ) { + func updateNode(id: UUID, mutate: (inout Node) -> Void) { $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 - } - } + let previousValue = node + mutate(&node) + guard node != previousValue else { return } items.wrappedValue[index].node = node } } @@ -97,10 +71,19 @@ final class NodesStorage: NodesStorageProtocol { init(securedStore: SecuredStore) { self.securedStore = securedStore + var nodesDto: [NodeWithGroupDTO]? = securedStore.get(StoreKey.NodesStorage.nodes) + + if nodesDto == nil { + let oldNodesDto: SafeDecodingArray? = securedStore.get( + StoreKey.NodesStorage.nodes + ) + + nodesDto = oldNodesDto?.values.map { $0.mapToModernDto() } + } - var nodes = securedStore.get(StoreKey.NodesStorage.nodes) ?? Self.defaultItems + var nodes = nodesDto.map { $0.map { $0.mapToModel() } } ?? Self.defaultItems let nodesToAdd = Self.defaultItems.filter { defaultNode in - !nodes.contains { $0.node.host == defaultNode.node.host } + !nodes.contains { $0.node.mainOrigin.host == defaultNode.node.mainOrigin.host } } nodes.append(contentsOf: nodesToAdd) @@ -140,7 +123,8 @@ private extension NodesStorage { } func saveNodes(nodes: [NodeWithGroup]) { - securedStore.set(nodes, for: StoreKey.NodesStorage.nodes) + let nodesDto = nodes.map { $0.mapToDto() } + securedStore.set(nodesDto, for: StoreKey.NodesStorage.nodes) } } diff --git a/AdamantTests/AdamantHealthCheckServiceTests.swift b/AdamantTests/AdamantHealthCheckServiceTests.swift index 6549ca478..cd21bcd65 100644 --- a/AdamantTests/AdamantHealthCheckServiceTests.swift +++ b/AdamantTests/AdamantHealthCheckServiceTests.swift @@ -113,7 +113,7 @@ class AdamantHealthCheckServiceTests: XCTestCase { // MARK: - Helpers - private func makeTestNode(connectionStatus: Node.ConnectionStatus = .synchronizing) -> Node { + private func makeTestNode(connectionStatus: NodeConnectionStatus = .synchronizing) -> Node { let node = Node(scheme: .default, host: "", port: nil) node.connectionStatus = connectionStatus return node diff --git a/AdamantTests/NodesAllowanceTests.swift b/AdamantTests/NodesAllowanceTests.swift index 39d983c73..86c83d21a 100644 --- a/AdamantTests/NodesAllowanceTests.swift +++ b/AdamantTests/NodesAllowanceTests.swift @@ -104,7 +104,7 @@ class NodesAllowanceTests: XCTest { nodes.getAllowedNodes(sortedBySpeedDescending: true, needWS: ws) } - private func makeTestNode(connectionStatus: Node.ConnectionStatus = .synchronizing) -> Node { + private func makeTestNode(connectionStatus: NodeConnectionStatus = .synchronizing) -> Node { let node = Node(scheme: .default, host: "", port: nil) node.connectionStatus = connectionStatus return node diff --git a/CommonKit/Sources/CommonKit/ExtensionsTools/ExtensionsApi.swift b/CommonKit/Sources/CommonKit/ExtensionsTools/ExtensionsApi.swift index 5d8080212..4c07a8ae7 100644 --- a/CommonKit/Sources/CommonKit/ExtensionsTools/ExtensionsApi.swift +++ b/CommonKit/Sources/CommonKit/ExtensionsTools/ExtensionsApi.swift @@ -15,7 +15,10 @@ public final class ExtensionsApi { public let keychainStore: KeychainStore public private(set) lazy var nodes: [Node] = { - let nodes = keychainStore.get(nodesStoreKey) ?? AdamantResources.nodes + let nodesDto: [NodeDTO]? = keychainStore.get(nodesStoreKey) + let nodes = nodesDto.map { $0.map { $0.mapToModel() } } + ?? AdamantResources.nodes + return nodes.filter { $0.isEnabled }.shuffled() }() diff --git a/CommonKit/Sources/CommonKit/Helpers/Node+NodeDTO.swift b/CommonKit/Sources/CommonKit/Helpers/Node+NodeDTO.swift new file mode 100644 index 000000000..62279fbc5 --- /dev/null +++ b/CommonKit/Sources/CommonKit/Helpers/Node+NodeDTO.swift @@ -0,0 +1,38 @@ +// +// Node+NodeDTO.swift +// +// +// Created by Andrew G on 28.07.2024. +// + +public extension Node { + func mapToDto() -> NodeDTO { + .init( + mainOrigin: mainOrigin, + altOrigin: altOrigin, + wsEnabled: wsEnabled, + isEnabled: isEnabled, + version: version, + height: height, + ping: ping, + connectionStatus: connectionStatus + ) + } +} + +public extension NodeDTO { + func mapToModel() -> Node { + .init( + id: .init(), + isEnabled: isEnabled, + wsEnabled: wsEnabled, + mainOrigin: mainOrigin, + altOrigin: altOrigin, + version: version, + height: height, + ping: ping, + connectionStatus: connectionStatus, + preferMainOrigin: nil + ) + } +} diff --git a/CommonKit/Sources/CommonKit/Helpers/SafeDecodingArray.swift b/CommonKit/Sources/CommonKit/Helpers/SafeDecodingArray.swift new file mode 100644 index 000000000..8bb5c328b --- /dev/null +++ b/CommonKit/Sources/CommonKit/Helpers/SafeDecodingArray.swift @@ -0,0 +1,40 @@ +// +// SafeDecodingArray.swift +// +// +// Created by Andrew G on 30.07.2024. +// + +public struct SafeDecodingArray { + public let values: [T] + + init(_ values: [T]) { + self.values = values + } +} + +extension SafeDecodingArray: Encodable { + public func encode(to encoder: Encoder) throws { + var container = encoder.singleValueContainer() + try container.encode(self.values) + } +} + +extension SafeDecodingArray: Decodable { + struct Item { + let value: Value? + } + + public init(from decoder: Decoder) throws { + let container = try decoder.singleValueContainer() + let items = try container.decode([Item].self) + values = items.compactMap { $0.value } + } +} + +extension SafeDecodingArray.Item: Decodable { + public init(from decoder: Decoder) throws { + let container = try decoder.singleValueContainer() + value = try? container.decode(Value.self) + } +} diff --git a/CommonKit/Sources/CommonKit/Helpers/SwiftUI/View+Extension.swift b/CommonKit/Sources/CommonKit/Helpers/SwiftUI/View+Extension.swift index 8ce6b6d67..c183ac767 100644 --- a/CommonKit/Sources/CommonKit/Helpers/SwiftUI/View+Extension.swift +++ b/CommonKit/Sources/CommonKit/Helpers/SwiftUI/View+Extension.swift @@ -39,6 +39,7 @@ public extension View { return resultView } + // TODO: Remove this function (or fix) func fullScreen() -> some View { return frame(width: .infinity, height: .infinity) .ignoresSafeArea() diff --git a/CommonKit/Sources/CommonKit/Models/Keychain/NodeDTO.swift b/CommonKit/Sources/CommonKit/Models/Keychain/NodeDTO.swift new file mode 100644 index 000000000..5371bec64 --- /dev/null +++ b/CommonKit/Sources/CommonKit/Models/Keychain/NodeDTO.swift @@ -0,0 +1,19 @@ +// +// NodeDTO.swift +// +// +// Created by Andrew G on 28.07.2024. +// + +import Foundation + +public struct NodeDTO: Codable { + public let mainOrigin: NodeOrigin + public let altOrigin: NodeOrigin? + public let wsEnabled: Bool + public let isEnabled: Bool + public let version: String? + public let height: Int? + public let ping: TimeInterval? + public let connectionStatus: NodeConnectionStatus? +} diff --git a/CommonKit/Sources/CommonKit/Models/Keychain/OldNodeDTO.swift b/CommonKit/Sources/CommonKit/Models/Keychain/OldNodeDTO.swift new file mode 100644 index 000000000..e5bd875e1 --- /dev/null +++ b/CommonKit/Sources/CommonKit/Models/Keychain/OldNodeDTO.swift @@ -0,0 +1,93 @@ +// +// OldNodeDTO.swift +// +// +// Created by Andrew G on 30.07.2024. +// + +import Foundation + +public struct OldNodeDTO: Codable { + public let id: UUID + public let scheme: URLScheme + public let host: String + public let isEnabled: Bool + public let wsEnabled: Bool + public let port: Int? + public let wsPort: Int? + public let version: String? + public let height: Int? + public let ping: TimeInterval? + public let connectionStatus: ConnectionStatus? +} + +public extension OldNodeDTO { + enum RejectedReason: Codable, Equatable { + case outdatedApiVersion + } + + enum ConnectionStatus: Equatable, Codable { + case offline + case synchronizing + case allowed + case notAllowed(RejectedReason) + } + + enum URLScheme: String, Codable { + case http + case https + } + + func mapToModernDto() -> NodeDTO { + .init( + mainOrigin: .init( + scheme: scheme.map(), + host: host, + port: port, + wsPort: wsPort + ), + altOrigin: nil, + wsEnabled: wsEnabled, + isEnabled: isEnabled, + version: version, + height: height, + ping: ping, + connectionStatus: connectionStatus?.map() + ) + } +} + +private extension OldNodeDTO.URLScheme { + func map() -> NodeOrigin.URLScheme { + switch self { + case .http: + return .http + case .https: + return .https + } + } +} + +private extension OldNodeDTO.ConnectionStatus { + func map() -> NodeConnectionStatus { + switch self { + case .offline: + return .offline + case .synchronizing: + return .synchronizing + case .allowed: + return .allowed + case let .notAllowed(reason): + return .notAllowed(reason.map()) + } + } +} + +private extension OldNodeDTO.RejectedReason { + func map() -> NodeConnectionStatus.RejectedReason { + switch self { + case .outdatedApiVersion: + return .outdatedApiVersion + } + } +} diff --git a/CommonKit/Sources/CommonKit/Models/Node.swift b/CommonKit/Sources/CommonKit/Models/Node.swift index 258648786..182753a54 100644 --- a/CommonKit/Sources/CommonKit/Models/Node.swift +++ b/CommonKit/Sources/CommonKit/Models/Node.swift @@ -8,130 +8,85 @@ import Foundation -public struct Node: Equatable, Codable, Identifiable { +public struct Node: Equatable, Identifiable { public let id: UUID - public var scheme: URLScheme - public var host: String - public var isEnabled: Bool + public var mainOrigin: NodeOrigin + public var altOrigin: NodeOrigin? 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 var connectionStatus: NodeConnectionStatus? + public var preferMainOrigin: Bool? + + public var isEnabled: Bool { + didSet { + guard !isEnabled else { return } + connectionStatus = nil + } + } public init( - id: UUID = .init(), - scheme: URLScheme, - host: String, + id: UUID, isEnabled: Bool, wsEnabled: Bool, - port: Int? = nil, - wsPort: Int? = nil, - version: String? = nil, - height: Int? = nil, - ping: TimeInterval? = nil, - connectionStatus: ConnectionStatus? = nil + mainOrigin: NodeOrigin, + altOrigin: NodeOrigin?, + version: String?, + height: Int?, + ping: TimeInterval?, + connectionStatus: NodeConnectionStatus?, + preferMainOrigin: Bool? ) { self.id = id - self.scheme = scheme - self.host = host + self.mainOrigin = mainOrigin + self.altOrigin = altOrigin self.isEnabled = isEnabled self.wsEnabled = wsEnabled - self.port = port - self.wsPort = wsPort self.version = version self.height = height self.ping = ping self.connectionStatus = connectionStatus + self.preferMainOrigin = preferMainOrigin } } public extension Node { - enum RejectedReason: Codable, Equatable { - case outdatedApiVersion - - public var text: String { - switch self { - case .outdatedApiVersion: - return Strings.outdated - } - } - } - - enum ConnectionStatus: Equatable, Codable { - case offline - case synchronizing - case allowed - case notAllowed(RejectedReason) + var preferredOrigin: NodeOrigin { + preferMainOrigin ?? true + ? mainOrigin + : altOrigin ?? mainOrigin } - - 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 - } - } - } - - init(url: URL, altUrl _: URL? = nil) { + init(url: URL, altUrl: URL? = nil) { self.init( - scheme: URLScheme(rawValue: url.scheme ?? .empty) ?? .https, - host: url.host ?? .empty, + id: .init(), isEnabled: true, wsEnabled: false, - port: url.port + mainOrigin: .init(url: url), + altOrigin: altUrl.map { .init(url: $0) }, + version: nil, + height: nil, + ping: nil, + connectionStatus: nil, + preferMainOrigin: nil ) } func asString() -> String { - if let url = asURL(forcePort: scheme != .https) { - return url.absoluteString - } else { - return host - } + preferredOrigin.asString() } func asSocketURL() -> URL? { - asURL(forcePort: false, useWsPort: true) + preferredOrigin.asSocketURL() } func asURL() -> URL? { - asURL(forcePort: true) + preferredOrigin.asURL() } -} - -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 - } -} - -private extension Node { - enum Strings { - static var outdated: String { - String.localized( - "NodesList.NodeCell.Outdated", - comment: "NodesList.NodeCell: Node is outdated" - ) - } + + mutating func updateWsPort(_ wsPort: Int?) { + mainOrigin.wsPort = wsPort + altOrigin?.wsPort = wsPort } } diff --git a/CommonKit/Sources/CommonKit/Models/NodeConnectionStatus.swift b/CommonKit/Sources/CommonKit/Models/NodeConnectionStatus.swift new file mode 100644 index 000000000..5e0fcad7c --- /dev/null +++ b/CommonKit/Sources/CommonKit/Models/NodeConnectionStatus.swift @@ -0,0 +1,33 @@ +// +// NodeConnectionStatus.swift +// +// +// Created by Andrew G on 28.07.2024. +// + +import Foundation + +public enum NodeConnectionStatus: Equatable, Codable { + case offline + case synchronizing + case allowed + case notAllowed(RejectedReason) +} + +public extension NodeConnectionStatus { + enum RejectedReason: Codable, Equatable { + case outdatedApiVersion + } +} + +public extension NodeConnectionStatus.RejectedReason { + var text: String { + switch self { + case .outdatedApiVersion: + return String.localized( + "NodesList.NodeCell.Outdated", + comment: "NodesList.NodeCell: Node is outdated" + ) + } + } +} diff --git a/CommonKit/Sources/CommonKit/Models/NodeOrigin.swift b/CommonKit/Sources/CommonKit/Models/NodeOrigin.swift new file mode 100644 index 000000000..1174f46d3 --- /dev/null +++ b/CommonKit/Sources/CommonKit/Models/NodeOrigin.swift @@ -0,0 +1,84 @@ +// +// NodeOrigin.swift +// +// +// Created by Andrew G on 27.07.2024. +// + +import Foundation + +public struct NodeOrigin: Codable, Equatable { + public var scheme: URLScheme + public var host: String + public var port: Int? + public var wsPort: Int? + + public init( + scheme: URLScheme, + host: String, + port: Int? = nil, + wsPort: Int? = nil + ) { + self.scheme = scheme + self.host = host + self.port = port + self.wsPort = wsPort + } +} + +public extension NodeOrigin { + 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 + } + } + } + + init(url: URL) { + self.init( + scheme: URLScheme(rawValue: url.scheme ?? .empty) ?? .https, + host: url.host ?? .empty, + port: url.port + ) + } + + func asString() -> String { + if let url = asURL(forcePort: scheme != .https) { + return url.absoluteString + } else { + return host + } + } + + func asSocketURL() -> URL? { + asURL(forcePort: false, useWsPort: true) + } + + func asURL() -> URL? { + asURL(forcePort: true) + } +} + +private extension NodeOrigin { + 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 + } +} From c0e91aa73806259806f984a02b2047f739bdc204 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=80=9Cjust-software-dev=E2=80=9D?= Date: Tue, 30 Jul 2024 04:24:35 -0300 Subject: [PATCH 015/106] [trello.com/c/PdYjG3m2] A few comments --- Adamant/Models/Keychain/OldNodeWithGroupDTO.swift | 1 + CommonKit/Sources/CommonKit/Models/Keychain/OldNodeDTO.swift | 1 + 2 files changed, 2 insertions(+) diff --git a/Adamant/Models/Keychain/OldNodeWithGroupDTO.swift b/Adamant/Models/Keychain/OldNodeWithGroupDTO.swift index 3ae581a57..7728eb39e 100644 --- a/Adamant/Models/Keychain/OldNodeWithGroupDTO.swift +++ b/Adamant/Models/Keychain/OldNodeWithGroupDTO.swift @@ -8,6 +8,7 @@ import CommonKit +// TODO: Remove after a few updates (it's used for migration) struct OldNodeWithGroupDTO: Codable { let group: NodeGroup let node: OldNodeDTO diff --git a/CommonKit/Sources/CommonKit/Models/Keychain/OldNodeDTO.swift b/CommonKit/Sources/CommonKit/Models/Keychain/OldNodeDTO.swift index e5bd875e1..b4583549a 100644 --- a/CommonKit/Sources/CommonKit/Models/Keychain/OldNodeDTO.swift +++ b/CommonKit/Sources/CommonKit/Models/Keychain/OldNodeDTO.swift @@ -7,6 +7,7 @@ import Foundation +// TODO: Remove after a few updates (it's used for migration) public struct OldNodeDTO: Codable { public let id: UUID public let scheme: URLScheme From cbd187632ee14a817f714e6d43252c371c1ea303 Mon Sep 17 00:00:00 2001 From: Iana Silosiev Date: Tue, 30 Jul 2024 17:28:50 +0300 Subject: [PATCH 016/106] [trello.com/c/hxbMeJhs] Added "Stake" option --- .../DelegatesListViewController.swift | 64 +++++++++++++++---- .../Adamant/AdmWalletViewController.swift | 30 ++++++++- .../Localization/de.lproj/Localizable.strings | 6 ++ .../Localization/en.lproj/Localizable.strings | 6 ++ .../Localization/ru.lproj/Localizable.strings | 6 ++ .../Localization/zh.lproj/Localizable.strings | 6 ++ 6 files changed, 106 insertions(+), 12 deletions(-) diff --git a/Adamant/Modules/Delegates/DelegatesListViewController.swift b/Adamant/Modules/Delegates/DelegatesListViewController.swift index 1387ea5f3..589c0b7e5 100644 --- a/Adamant/Modules/Delegates/DelegatesListViewController.swift +++ b/Adamant/Modules/Delegates/DelegatesListViewController.swift @@ -9,6 +9,8 @@ import UIKit import SnapKit import CommonKit +import MarkdownKit +import SafariServices // MARK: - Localization extension String.adamant { @@ -57,8 +59,30 @@ final class DelegatesListViewController: KeyboardObservingViewController { // MARK: - Properties + private var headerTextView: UITextView { + let textView = UITextView() + textView.backgroundColor = .clear + textView.isEditable = false + textView.delegate = self + + let attributedString = NSMutableAttributedString( + attributedString: MarkdownParser(color: .adamant.chatPlaceholderTextColor).parse(self.text) + ) + + textView.attributedText = attributedString + textView.linkTextAttributes = [NSAttributedString.Key.foregroundColor : UIColor.adamant.active] + + textView.sizeToFit() + + return textView + } + + var text: String { + .localized("Delegates.HeaderText") + } + private lazy var tableView: UITableView = { - let tableView = UITableView() + let tableView = UITableView(frame: .zero, style: .grouped) tableView.register(AdamantDelegateCell.self, forCellReuseIdentifier: cellIdentifier) tableView.rowHeight = 50 tableView.backgroundColor = .clear @@ -192,6 +216,19 @@ final class DelegatesListViewController: KeyboardObservingViewController { ) } + private func openURL(_ url: URL) { + let safari = SFSafariViewController(url: url) + safari.preferredControlTintColor = UIColor.adamant.primary + safari.modalPresentationStyle = .overFullScreen + present(safari, animated: true, completion: nil) + } + + private func calculateHeightForHeader() -> CGFloat { + headerTextView.sizeToFit() + let height = headerTextView.contentSize.height + return height + } + private func setupViews() { view.addSubview(tableView) view.addSubview(bottomPanel) @@ -222,19 +259,11 @@ extension DelegatesListViewController: UITableViewDataSource, UITableViewDelegat } func tableView(_: UITableView, viewForHeaderInSection _: Int) -> UIView? { - UIView() - } - - func tableView(_: UITableView, viewForFooterInSection _: Int) -> UIView? { - UIView() + return self.headerTextView } func tableView(_: UITableView, heightForHeaderInSection _: Int) -> CGFloat { - .zero - } - - func tableView(_: UITableView, heightForFooterInSection _: Int) -> CGFloat { - .zero + return UITableView.automaticDimension } func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { @@ -451,3 +480,16 @@ private extension DelegatesListViewController { ) } } + +// MARK: - UITextViewDelegate +extension DelegatesListViewController: UITextViewDelegate { + func textView( + _ textView: UITextView, + shouldInteractWith URL: URL, + in characterRange: NSRange, + interaction: UITextItemInteraction + ) -> Bool { + openURL(URL) + return false + } +} diff --git a/Adamant/Modules/Wallets/Adamant/AdmWalletViewController.swift b/Adamant/Modules/Wallets/Adamant/AdmWalletViewController.swift index 0c951c9fd..ed9ebf9a3 100644 --- a/Adamant/Modules/Wallets/Adamant/AdmWalletViewController.swift +++ b/Adamant/Modules/Wallets/Adamant/AdmWalletViewController.swift @@ -43,10 +43,11 @@ extension String.adamant.wallets { final class AdmWalletViewController: WalletViewControllerBase { // MARK: - Rows & Sections enum Rows { - case buyTokens, freeTokens + case stakeAdm, buyTokens, freeTokens var tag: String { switch self { + case .stakeAdm: return "stakeAdm" case .buyTokens: return "bTkns" case .freeTokens: return "frrTkns" } @@ -54,6 +55,7 @@ final class AdmWalletViewController: WalletViewControllerBase { var localized: String { switch self { + case .stakeAdm: return .localized("AccountTab.Row.StakeAdm", comment: "Stake ADM tokens' row") case .buyTokens: return .localized("AccountTab.Row.BuyTokens", comment: "Account tab: 'Buy tokens' button") case .freeTokens: return .localized("AccountTab.Row.FreeTokens", comment: "Account tab: 'Get free tokens' button") } @@ -61,6 +63,7 @@ final class AdmWalletViewController: WalletViewControllerBase { var image: UIImage? { switch self { + case .stakeAdm: return .asset(named: "row_buy-coins") case .buyTokens: return .asset(named: "row_buy-coins") case .freeTokens: return .asset(named: "row_free-tokens") } @@ -87,6 +90,30 @@ final class AdmWalletViewController: WalletViewControllerBase { // MARK: Rows + let stakeAdmRow = LabelRow { + $0.tag = Rows.stakeAdm.tag + $0.title = Rows.stakeAdm.localized + $0.cell.imageView?.image = Rows.stakeAdm.image + $0.cell.imageView?.tintColor = UIColor.adamant.tableRowIcons + $0.cell.selectionStyle = .gray + $0.cell.backgroundColor = UIColor.adamant.cellColor + }.cellUpdate { (cell, row) in + cell.accessoryType = .disclosureIndicator + + row.title = Rows.stakeAdm.localized + }.onCellSelection { [weak self] (_, row) in + guard let self = self else { return } + let vc = screensFactory.makeDelegatesList() + row.deselect() + + if let split = splitViewController { + let details = UINavigationController(rootViewController:vc) + split.showDetailViewController(details, sender: self) + } else { + navigationController?.pushViewController(vc, animated: true) + } + } + let buyTokensRow = LabelRow { $0.tag = Rows.buyTokens.tag $0.title = Rows.buyTokens.localized @@ -147,6 +174,7 @@ final class AdmWalletViewController: WalletViewControllerBase { } } + section.append(stakeAdmRow) section.append(buyTokensRow) section.append(freeTokensRow) diff --git a/CommonKit/Sources/CommonKit/Assets/Localization/de.lproj/Localizable.strings b/CommonKit/Sources/CommonKit/Assets/Localization/de.lproj/Localizable.strings index 4088c7870..cc26fd687 100644 --- a/CommonKit/Sources/CommonKit/Assets/Localization/de.lproj/Localizable.strings +++ b/CommonKit/Sources/CommonKit/Assets/Localization/de.lproj/Localizable.strings @@ -169,6 +169,9 @@ /* Account tab: 'Buy tokens' button */ "AccountTab.Row.BuyTokens" = "Token tauschen"; +/* Account tab: 'Stake ADM' button */ +"AccountTab.Row.StakeAdm" = "Setzen und verdienen"; + /* Account tab: 'Logout' button */ "AccountTab.Row.Logout" = "Ausloggen"; @@ -463,6 +466,9 @@ /* Delegates page: scene title */ "Delegates.Title" = "Delegierte"; +/* Delegates page: header text */ +"Delegates.HeaderText" = "Wenn Sie ADM einsetzen und Belohnungen erhalten möchten, stimmen Sie für einen aktiven Schmiedepool Ihres Vertrauens. Lesen Sie mehr im [ADAMANT Blog](https://news.adamant.im/hodl-list-of-adamant-pools-join-in-and-get-rewards-491a98610f4b)."; + /* Visible Wallets page: scene title */ "VisibleWallets.Title" = "Wallet-Liste"; diff --git a/CommonKit/Sources/CommonKit/Assets/Localization/en.lproj/Localizable.strings b/CommonKit/Sources/CommonKit/Assets/Localization/en.lproj/Localizable.strings index 37bca7fef..020066816 100644 --- a/CommonKit/Sources/CommonKit/Assets/Localization/en.lproj/Localizable.strings +++ b/CommonKit/Sources/CommonKit/Assets/Localization/en.lproj/Localizable.strings @@ -166,6 +166,9 @@ /* Account tab: 'Buy tokens' button */ "AccountTab.Row.BuyTokens" = "Exchange tokens"; +/* Account tab: 'Stake ADM' button */ +"AccountTab.Row.StakeAdm" = "Stake and earn"; + /* Account tab: 'Logout' button */ "AccountTab.Row.Logout" = "Logout"; @@ -454,6 +457,9 @@ /* Delegates page: scene title */ "Delegates.Title" = "Delegates"; +/* Delegates page: header text */ +"Delegates.HeaderText" = "To stake ADM and get rewards, vote for an active forging pool you trust. Read more in the [ADAMANT blog](https://news.adamant.im/hodl-list-of-adamant-pools-join-in-and-get-rewards-491a98610f4b)."; + /* Visible Wallets page: scene title */ "VisibleWallets.Title" = "Wallet list"; diff --git a/CommonKit/Sources/CommonKit/Assets/Localization/ru.lproj/Localizable.strings b/CommonKit/Sources/CommonKit/Assets/Localization/ru.lproj/Localizable.strings index 066703b31..9ad0bcfc6 100644 --- a/CommonKit/Sources/CommonKit/Assets/Localization/ru.lproj/Localizable.strings +++ b/CommonKit/Sources/CommonKit/Assets/Localization/ru.lproj/Localizable.strings @@ -157,6 +157,9 @@ /* Account tab: 'Buy tokens' button */ "AccountTab.Row.BuyTokens" = "Обменять токены"; +/* Account tab: 'Stake ADM' button */ +"AccountTab.Row.StakeAdm" = "Застейкать и заработать"; + /* Account tab: 'Logout' button */ "AccountTab.Row.Logout" = "Выход"; @@ -454,6 +457,9 @@ /* Delegates page: scene title */ "Delegates.Title" = "Делегаты"; +/* Delegates page: header text */ +"Delegates.HeaderText" = "Чтобы застейкать ADM и получать вознаграждения, проголосуйте за активный форжинг-пул, которому вы доверяете. Подробнее читайте в [блоге АДАМАНТа](https://news.adamant.im/hodl-list-of-adamant-pools-join-in-and-get-rewards-491a98610f4b)."; + /* Visible Wallets page: scene title */ "VisibleWallets.Title" = "Кошельки"; diff --git a/CommonKit/Sources/CommonKit/Assets/Localization/zh.lproj/Localizable.strings b/CommonKit/Sources/CommonKit/Assets/Localization/zh.lproj/Localizable.strings index 63fafa539..02e6c13dc 100644 --- a/CommonKit/Sources/CommonKit/Assets/Localization/zh.lproj/Localizable.strings +++ b/CommonKit/Sources/CommonKit/Assets/Localization/zh.lproj/Localizable.strings @@ -166,6 +166,9 @@ /* Account tab: 'Buy tokens' button */ "AccountTab.Row.BuyTokens" = "交换代币"; +/* Account tab: 'Stake ADM' button */ +"AccountTab.Row.StakeAdm" = "赌注和收入"; + /* Account tab: 'Logout' button */ "AccountTab.Row.Logout" = "注销"; @@ -454,6 +457,9 @@ /* Delegates page: scene title */ "Delegates.Title" = "与会代表"; +/* Delegates page: header text */ +"Delegates.HeaderText" = "要入股 ADM 并获得奖励,请为您信任的活跃锻造池投票。在 [ADAMANT 博客](https://news.adamant.im/hodl-list-of-adamant-pools-join-in-and-get-rewards-491a98610f4b) 阅读更多内容."; + /* Visible Wallets page: scene title */ "VisibleWallets.Title" = "钱包列表"; From e3b7abe58d124d475e6d151f1acef91b758017b6 Mon Sep 17 00:00:00 2001 From: Iana Silosiev Date: Tue, 30 Jul 2024 17:42:58 +0300 Subject: [PATCH 017/106] [trello.com/c/hxbMeJhs] Set the icon --- .../Adamant/AdmWalletViewController.swift | 2 +- .../Row/row_stake.imageset/Contents.json | 26 ++++++++++++++++++ .../Row/row_stake.imageset/Stake@3x-2.png | Bin 0 -> 704 bytes .../Row/row_stake.imageset/Stake@3x-3.png | Bin 0 -> 1020 bytes .../Row/row_stake.imageset/Stake@3x.png | Bin 0 -> 339 bytes 5 files changed, 27 insertions(+), 1 deletion(-) create mode 100644 CommonKit/Sources/CommonKit/Assets/Shared.xcassets/Row/row_stake.imageset/Contents.json create mode 100644 CommonKit/Sources/CommonKit/Assets/Shared.xcassets/Row/row_stake.imageset/Stake@3x-2.png create mode 100644 CommonKit/Sources/CommonKit/Assets/Shared.xcassets/Row/row_stake.imageset/Stake@3x-3.png create mode 100644 CommonKit/Sources/CommonKit/Assets/Shared.xcassets/Row/row_stake.imageset/Stake@3x.png diff --git a/Adamant/Modules/Wallets/Adamant/AdmWalletViewController.swift b/Adamant/Modules/Wallets/Adamant/AdmWalletViewController.swift index ed9ebf9a3..ad14d8e0c 100644 --- a/Adamant/Modules/Wallets/Adamant/AdmWalletViewController.swift +++ b/Adamant/Modules/Wallets/Adamant/AdmWalletViewController.swift @@ -63,7 +63,7 @@ final class AdmWalletViewController: WalletViewControllerBase { var image: UIImage? { switch self { - case .stakeAdm: return .asset(named: "row_buy-coins") + case .stakeAdm: return .asset(named: "row_stake") case .buyTokens: return .asset(named: "row_buy-coins") case .freeTokens: return .asset(named: "row_free-tokens") } diff --git a/CommonKit/Sources/CommonKit/Assets/Shared.xcassets/Row/row_stake.imageset/Contents.json b/CommonKit/Sources/CommonKit/Assets/Shared.xcassets/Row/row_stake.imageset/Contents.json new file mode 100644 index 000000000..822af7cc6 --- /dev/null +++ b/CommonKit/Sources/CommonKit/Assets/Shared.xcassets/Row/row_stake.imageset/Contents.json @@ -0,0 +1,26 @@ +{ + "images" : [ + { + "filename" : "Stake@3x.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "Stake@3x-2.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "Stake@3x-3.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + }, + "properties" : { + "template-rendering-intent" : "template" + } +} diff --git a/CommonKit/Sources/CommonKit/Assets/Shared.xcassets/Row/row_stake.imageset/Stake@3x-2.png b/CommonKit/Sources/CommonKit/Assets/Shared.xcassets/Row/row_stake.imageset/Stake@3x-2.png new file mode 100644 index 0000000000000000000000000000000000000000..3394c50ed61802af765bfbf913387a55d0879556 GIT binary patch literal 704 zcmeAS@N?(olHy`uVBq!ia0vp^S|H593?x6vT4n;Nr~sc3S0IfQxEJ_f6(?S>tXd`l zs9Lrp$S+vzjQr1ubG}G2h$tUvDZi@0>>P8Sdqz;^a=zyVd&0PF)-f0~an@RQ!MY&Vz5?Z8Pq9JLl~BS$6KupY}B>F8bo ze@}R$yXAL<%iHNeaz_0tzB26lcXWoK>G7;(+j*~aKNi?*T6NygZ@1Yt;r7MN^^Uqb zawjMCWe165_Rjra&fI4nxW`j5X#0~rYdi%yeWz3^=I{6!EZOn+LFb)kVT`lRPnG2~ zzBbECSW_k6^z^wy{h^ZrxGMbC{BgHitj5)E(P*{AM&wP)s+?QO5tCiHHH(f_=o~22 zh%IKx@t1uhzc5tu;f}Vwt+UpMWt}Trf6gi^ZoS$yr^Tl-PT%i8)2Y1lpK$8s+pI5a z*DA|qTlOD)cU)>;_O%9?Q%RDWy$hwj=g%sYKgf6L?B?KiNi02IBH2$@PoMPKl=s@? zy4iXwejSd2VHCR9KF^;z`!x9U?h@3bHP`Mjv|gpL2} zt4hBPR&g8b*=oZ4Cq*>6BKx5Fa!Ef`9sMkcX;YR}NlyHn_*O4z>Frm0dRD!0h>b1e cTBY()d_}2~&IgW_2SG{8)78&qol`;+0J``X_W%F@ literal 0 HcmV?d00001 diff --git a/CommonKit/Sources/CommonKit/Assets/Shared.xcassets/Row/row_stake.imageset/Stake@3x-3.png b/CommonKit/Sources/CommonKit/Assets/Shared.xcassets/Row/row_stake.imageset/Stake@3x-3.png new file mode 100644 index 0000000000000000000000000000000000000000..13430cb15a9627fd4a2b07b3271e0428f8053b8e GIT binary patch literal 1020 zcmeAS@N?(olHy`uVBq!ia0vp^_8`o`3?vg8AG`%pegQrqu0R?qa4+z|Dvn=p^W-)E zfGQ+Qg8YId?<=(z{CXwIkfb@u?C9*OZG6i@SZy!4WbhyCm1JOGcJXv^4DmR=HT>nJ z)e1bk3Mu1BVpFe9iW>RmNFo$JDMAiDHgNGOS2JAcU@u0sXzCm2j zYu%5`CHKv)9#`GUWcWkUp3~%%^UvM`4r=}LJ{GxLJor6`nvmiYZ`yK+w6N}X?2fLZn@b<=UD#y z2bV8jVY^qP7MPW@Abz>o2fm9ER}9x{IYwoxrmW7j_Fw5=(bbWWo5H$Ort3zFp~*&x z1u8qrrtK@g_^nAl+hp4+y)_H+^5-brn)TpSQ0ToE(VO-h-pq7`B|9WgG%MJ+5D`0)JJOzqPx6 z-`6qi>FT0YaeWiZ_qXRCpRaO%Wsk%(X@iUDx$ijl_3CG@<&LWpUE!L+R2g5gMq%N* zZ;#`*N!%z6e{}tcs>l3}`H_r065l`fJAF8HOnCRYlSZscyK)XFU(J1(*>nAyZr|bi z7NH#1pFgOK`@O6FNLpM~`9+^iGS?aJ-uri?v(Nv3#w&+J@5M{1`SY1KeHU%}y?T@K zvg;Rp|IGOKu25Za#gz@r-#^;F+dpT2okhLn+UNNXXZ&A#K>h#eqX z2{+kY^;|Y(4aK_?geG^8%-rMIO;R$JKWC*VU@|mHSxgm;RD=dYqM&w^Pr>-sJb!?(RL&ulH{$ zTKRuX!}79M(}kkCw_L4%veNMF75xvf-`C04&ikeR_xkp~^Z#x9|7YR<)TrM(|EtL! z_WayAm0Mv^@$&ofi&>W~{U!3}l#Ba&{Z;SoYil%0^(=exeZKs~9gcNh8Kwv?b>8s# z_ht5TrLRo+yR$lkrFQSDckIlczkiD7hwpW2>-1*%Tw_1DNb?QPANG_->A`Pq_WOde NrKhW(%Q~loCICmK)F%J{ literal 0 HcmV?d00001 diff --git a/CommonKit/Sources/CommonKit/Assets/Shared.xcassets/Row/row_stake.imageset/Stake@3x.png b/CommonKit/Sources/CommonKit/Assets/Shared.xcassets/Row/row_stake.imageset/Stake@3x.png new file mode 100644 index 0000000000000000000000000000000000000000..889ee89a03b9b3a3861dc53306f9e074ff08d282 GIT binary patch literal 339 zcmeAS@N?(olHy`uVBq!ia0vp^q9Dw{3?%2B3|#`G>;rs4T!AztV6$qIK2X1CNswQ# zVoKi>iQUnn+r>Kr7C5pf)K^)QDXz0pz6MmV&eO#)MB;L8V6;$+0*`iXuE2!$d;iyU zzh2dyZ0Dt3V&v;}^QXGbWN&WKU4a>%Zgq`)2Ogcgd~c5Z{Wi5vOYP4Irp?`G&hE`M zeTJ@w!Lug?8+)`0doL-7uRdaWc$VVz06*4NP8ExHR|_^4cZi9cGHm;q6ngF2*9Y4| zOJ+R1uj3Y5_eyB{zQba_`TjhAbZ2|h|ClB3vcHr)2{I` literal 0 HcmV?d00001 From 6c29247344c1c67fae36885503bc6456d12ec840 Mon Sep 17 00:00:00 2001 From: Iana Silosiev Date: Fri, 2 Aug 2024 12:39:56 +0300 Subject: [PATCH 018/106] [trello.com/c/hxbMeJhs] Added padding, changed the font --- .../Delegates/DelegatesListViewController.swift | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/Adamant/Modules/Delegates/DelegatesListViewController.swift b/Adamant/Modules/Delegates/DelegatesListViewController.swift index 589c0b7e5..c4a01aa64 100644 --- a/Adamant/Modules/Delegates/DelegatesListViewController.swift +++ b/Adamant/Modules/Delegates/DelegatesListViewController.swift @@ -66,7 +66,20 @@ final class DelegatesListViewController: KeyboardObservingViewController { textView.delegate = self let attributedString = NSMutableAttributedString( - attributedString: MarkdownParser(color: .adamant.chatPlaceholderTextColor).parse(self.text) + attributedString: MarkdownParser( + font: UIFont.preferredFont(forTextStyle: .subheadline), + color: .adamant.chatPlaceholderTextColor + ).parse(self.text) + ) + + let paragraphStyle = NSMutableParagraphStyle() + paragraphStyle.firstLineHeadIndent = 10 + paragraphStyle.headIndent = 10 + + attributedString.addAttribute( + .paragraphStyle, + value: paragraphStyle, + range: .init(location: .zero, length: attributedString.length) ) textView.attributedText = attributedString From 6a84fe4b9ddfc15a69234e6f33c794b24b490769 Mon Sep 17 00:00:00 2001 From: Iana Silosiev Date: Fri, 2 Aug 2024 16:35:04 +0300 Subject: [PATCH 019/106] [trello.com/c/1tMg5Cbp] Hide tx Record Data if it's empty. --- .../KlyTransactionDetailsViewController.swift | 1 + ...TransactionDetailsViewControllerBase.swift | 85 +++++++++++-------- 2 files changed, 52 insertions(+), 34 deletions(-) diff --git a/Adamant/Modules/Wallets/Klayr/KlyTransactionDetailsViewController.swift b/Adamant/Modules/Wallets/Klayr/KlyTransactionDetailsViewController.swift index e6f49f1fa..14ddb3189 100644 --- a/Adamant/Modules/Wallets/Klayr/KlyTransactionDetailsViewController.swift +++ b/Adamant/Modules/Wallets/Klayr/KlyTransactionDetailsViewController.swift @@ -79,6 +79,7 @@ final class KlyTransactionDetailsViewController: TransactionDetailsViewControlle trs.updateConfirmations(value: lastHeight) transaction = trs updateIncosinstentRowIfNeeded() + updateTxDataRow() tableView.reloadData() refreshControl.endRefreshing() } catch { diff --git a/Adamant/Modules/Wallets/TransactionDetailsViewControllerBase.swift b/Adamant/Modules/Wallets/TransactionDetailsViewControllerBase.swift index 050a390f4..419df06ed 100644 --- a/Adamant/Modules/Wallets/TransactionDetailsViewControllerBase.swift +++ b/Adamant/Modules/Wallets/TransactionDetailsViewControllerBase.swift @@ -601,40 +601,6 @@ class TransactionDetailsViewControllerBase: FormViewController { detailsSection.append(statusRow) - // MARK: Tx data record - let txRecordData = LabelRow { - $0.disabled = true - $0.tag = Rows.txRecordData.tag - $0.title = Rows.txRecordData.localized - - if let value = transaction?.txRecordData { - $0.value = value - } else { - $0.value = TransactionDetailsViewControllerBase.awaitingValueString - } - - $0.cell.detailTextLabel?.textAlignment = .right - $0.cell.detailTextLabel?.lineBreakMode = .byTruncatingMiddle - }.cellSetup { (cell, _) in - cell.selectionStyle = .gray - cell.textLabel?.textColor = UIColor.adamant.textColor - }.onCellSelection { [weak self] (cell, row) in - if let text = row.value { - self?.shareValue(text, from: cell) - } - }.cellUpdate { [weak self] (cell, row) in - cell.textLabel?.textColor = UIColor.adamant.textColor - if let value = self?.transaction?.txRecordData { - row.value = value - } else { - row.value = TransactionDetailsViewControllerBase.awaitingValueString - } - } - - if showTxRecordData { - detailsSection.append(txRecordData) - } - // MARK: Current Fiat let currentFiatRow = LabelRow { $0.disabled = true @@ -690,6 +656,52 @@ class TransactionDetailsViewControllerBase: FormViewController { detailsSection.append(fiatRow) + // MARK: Tx data record + let txRecordData = LabelRow { + $0.disabled = true + $0.tag = Rows.txRecordData.tag + $0.title = Rows.txRecordData.localized + + if let value = transaction?.txRecordData { + $0.value = value + } else { + $0.value = TransactionDetailsViewControllerBase.awaitingValueString + } + + $0.cell.detailTextLabel?.textAlignment = .right + $0.cell.detailTextLabel?.lineBreakMode = .byTruncatingMiddle + + $0.hidden = Condition.function([], { [weak self] _ -> Bool in + guard let value = self?.transaction?.txRecordData else { + return false + } + + if value.isEmpty { + return true + } + return false + }) + + }.cellSetup { (cell, _) in + cell.selectionStyle = .gray + cell.textLabel?.textColor = UIColor.adamant.textColor + }.onCellSelection { [weak self] (cell, row) in + if let text = row.value { + self?.shareValue(text, from: cell) + } + }.cellUpdate { [weak self] (cell, row) in + cell.textLabel?.textColor = UIColor.adamant.textColor + if let value = self?.transaction?.txRecordData { + row.value = value + } else { + row.value = TransactionDetailsViewControllerBase.awaitingValueString + } + } + + if showTxRecordData { + detailsSection.append(txRecordData) + } + // MARK: Comments section if let comment = comment { @@ -858,6 +870,11 @@ class TransactionDetailsViewControllerBase: FormViewController { checkAddressesIfNeeded() } + func updateTxDataRow() { + let row = form.rowBy(tag: Rows.txRecordData.tag) + row?.evaluateHidden() + } + // MARK: - Other private func setColors() { From 07154367b191fae317a3c7573be145c7853355b5 Mon Sep 17 00:00:00 2001 From: Iana Silosiev Date: Fri, 2 Aug 2024 20:16:54 +0300 Subject: [PATCH 020/106] [trello.com/c/Ut31LisJ] Fixed: Ethereum "QR does not contain address" --- Adamant/Helpers/String+adamant.swift | 18 ++++++++++++++---- .../ChatsList/NewChatViewController.swift | 4 +--- .../Adamant/AdmTransferViewController.swift | 5 +++++ Adamant/Utilities/AdamantCoinTools.swift | 19 ++++++++++++++----- Adamant/Utilities/AdamantUriTools.swift | 11 ++++++++++- 5 files changed, 44 insertions(+), 13 deletions(-) diff --git a/Adamant/Helpers/String+adamant.swift b/Adamant/Helpers/String+adamant.swift index a69173cc3..24db3e1ba 100644 --- a/Adamant/Helpers/String+adamant.swift +++ b/Adamant/Helpers/String+adamant.swift @@ -41,6 +41,7 @@ extension String { let address: String? var name: String? var message: String? + var amount: Double? let newUrl = self.replacingOccurrences(of: "//", with: "") @@ -57,6 +58,8 @@ extension String { name = label case .message(let urlMessage): message = urlMessage + case .amount(let value): + amount = value } } } @@ -71,6 +74,8 @@ extension String { name = label case .message(let urlMessage): message = urlMessage + case .amount(let value): + amount = value } } } @@ -88,10 +93,15 @@ extension String { } if let address = address { - return AdamantAddress(address: address, name: name, amount: nil, message: message) - } else { - return nil - } + return AdamantAddress( + address: address, + name: name, + amount: amount, + message: message + ) + } + + return nil } func addPrefixIfNeeded(prefix: String) -> String { diff --git a/Adamant/Modules/ChatsList/NewChatViewController.swift b/Adamant/Modules/ChatsList/NewChatViewController.swift index f77c4be7c..c09d71772 100644 --- a/Adamant/Modules/ChatsList/NewChatViewController.swift +++ b/Adamant/Modules/ChatsList/NewChatViewController.swift @@ -333,11 +333,9 @@ final class NewChatViewController: FormViewController { case .address(address: let addr, params: let params): if let params = params?.first { switch params { - case .address: - break case .label(label: let label): startNewChat(with: addr, name: label, message: nil) - case .message: + case .address, .message, .amount: break } } else { diff --git a/Adamant/Modules/Wallets/Adamant/AdmTransferViewController.swift b/Adamant/Modules/Wallets/Adamant/AdmTransferViewController.swift index fd1019f10..4748e14d8 100644 --- a/Adamant/Modules/Wallets/Adamant/AdmTransferViewController.swift +++ b/Adamant/Modules/Wallets/Adamant/AdmTransferViewController.swift @@ -294,6 +294,11 @@ final class AdmTransferViewController: TransferViewControllerBase { return true } else if let admAddress = address.getLegacyAdamantAddress() { recipientAddress = admAddress.address + if let row: SafeDecimalRow = form.rowBy(tag: BaseRows.amount.tag) { + row.value = admAddress.amount + row.updateCell() + reloadFormData() + } return true } diff --git a/Adamant/Utilities/AdamantCoinTools.swift b/Adamant/Utilities/AdamantCoinTools.swift index 62b5c43da..3084f1d97 100644 --- a/Adamant/Utilities/AdamantCoinTools.swift +++ b/Adamant/Utilities/AdamantCoinTools.swift @@ -38,18 +38,27 @@ final class AdamantCoinTools { let url = URLComponents(string: uri) guard !uri.isEmpty, - let url = url, - let raw = url.string + let url = url else { return nil } - guard let prefix = uri.split(separator: ":").first, - prefix.caseInsensitiveCompare(qqPrefix) == .orderedSame + let array = uri.split(separator: ":") + + guard array.count > 1, + let prefix = array.first else { - return QQAddressInformation(address: raw, params: nil) + return parseAdress(url: url) + } + + guard prefix.caseInsensitiveCompare(qqPrefix) == .orderedSame else { + return nil } + return parseAdress(url: url) + } + + private class func parseAdress(url: URLComponents) -> QQAddressInformation { let addressRaw = url.path let params = url.queryItems?.compactMap { diff --git a/Adamant/Utilities/AdamantUriTools.swift b/Adamant/Utilities/AdamantUriTools.swift index 0d3e995da..a0dd83626 100644 --- a/Adamant/Utilities/AdamantUriTools.swift +++ b/Adamant/Utilities/AdamantUriTools.swift @@ -19,6 +19,7 @@ enum AdamantAddressParam { case address(String) case label(String) case message(String) + case amount(Double) init?(raw: String) { let keyValue = raw.split(separator: "=") @@ -33,7 +34,9 @@ enum AdamantAddressParam { self = AdamantAddressParam.label(keyValue[1].replacingOccurrences(of: "+", with: " ").replacingOccurrences(of: "%20", with: " ")) case "message": self = AdamantAddressParam.message(keyValue[1].replacingOccurrences(of: "+", with: " ").replacingOccurrences(of: "%20", with: " ")) - + case "amount": + guard let amount = Double(keyValue[1]) else { return nil } + self = AdamantAddressParam.amount(amount) default: return nil } @@ -47,6 +50,8 @@ enum AdamantAddressParam { return "label=\(value.replacingOccurrences(of: " ", with: "+"))" case .message(let value): return "message=\(value.replacingOccurrences(of: " ", with: "+"))" + case .amount(let value): + return "amount=\(String(value).replacingOccurrences(of: " ", with: "+"))" } } } @@ -73,6 +78,8 @@ final class AdamantUriTools { components.queryItems?.append(.init(name: "label", value: value)) case .message(let value): components.queryItems?.append(.init(name: "message", value: value)) + case .amount(let value): + components.queryItems?.append(.init(name: "amount", value: String(value))) } } @@ -93,6 +100,8 @@ final class AdamantUriTools { components.queryItems?.append(.init(name: "label", value: value)) case .message(let value): components.queryItems?.append(.init(name: "message", value: value)) + case .amount(let value): + components.queryItems?.append(.init(name: "message", value: String(value))) } } From 4572d688db3889658cc6846631bf4c6ef4610f74 Mon Sep 17 00:00:00 2001 From: IanaaDvlp <156532902+IanaaDvlp@users.noreply.github.com> Date: Mon, 5 Aug 2024 11:07:04 +0300 Subject: [PATCH 021/106] Update Adamant/Utilities/AdamantUriTools.swift Co-authored-by: adamant-al <33592982+adamant-al@users.noreply.github.com> --- Adamant/Utilities/AdamantUriTools.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Adamant/Utilities/AdamantUriTools.swift b/Adamant/Utilities/AdamantUriTools.swift index a0dd83626..3208d785a 100644 --- a/Adamant/Utilities/AdamantUriTools.swift +++ b/Adamant/Utilities/AdamantUriTools.swift @@ -101,7 +101,7 @@ final class AdamantUriTools { case .message(let value): components.queryItems?.append(.init(name: "message", value: value)) case .amount(let value): - components.queryItems?.append(.init(name: "message", value: String(value))) + components.queryItems?.append(.init(name: "amount", value: String(value))) } } From 327cf92d9a91fa4f5b609bf6003a2c5ce945845d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=80=9Cjust-software-dev=E2=80=9D?= Date: Thu, 1 Aug 2024 08:45:21 -0300 Subject: [PATCH 022/106] [trello.com/c/dvqSTasv] Nodes merging algorithm + refactoring --- Adamant.xcodeproj/project.pbxproj | 36 ++-- Adamant/App/DI/AppAssembly.swift | 10 +- .../HealthCheckWrapper+Extension.swift | 17 ++ Adamant/Helpers/NodeGroup+Constants.swift | 29 +++ .../NodeWithGroup+NodeWithGroupDTO.swift | 19 -- Adamant/Models/ApiServiceError.swift | 9 - .../Models/Keychain/NodeWithGroupDTO.swift | 14 -- .../Models/Keychain/OldNodeWithGroupDTO.swift | 21 -- Adamant/Models/NodeWithGroup.swift | 45 ----- .../ComplexTransferViewController.swift | 47 +++-- .../CoinsNodesListFactory.swift | 17 +- .../View/CoinsNodesListView.swift | 12 +- .../ViewModel/CoinsNodesListMapper.swift | 37 ++-- .../ViewModel/CoinsNodesListState.swift | 1 + .../ViewModel/CoinsNodesListViewModel.swift | 18 +- .../NodeEditorViewController.swift | 5 +- .../NodesEditor/NodesListViewController.swift | 12 +- .../Modules/PartnerQR/PartnerQRFactory.swift | 22 +-- Adamant/Modules/PartnerQR/PartnerQRView.swift | 4 + .../Contribute/ContributeFactory.swift | 15 +- .../Settings/Contribute/ContributeView.swift | 4 +- .../VibrationSelectionFactory.swift | 16 +- .../VibrationSelectionView.swift | 4 +- .../Wallets/Adamant/AdmWalletFactory.swift | 5 +- .../AdmWalletService+DynamicConstants.swift | 32 ++-- .../Wallets/Adamant/AdmWalletService.swift | 4 + .../Wallets/Bitcoin/BtcApiService.swift | 8 +- .../Wallets/Bitcoin/BtcWalletFactory.swift | 5 +- .../BtcWalletService+DynamicConstants.swift | 4 +- .../Wallets/Bitcoin/BtcWalletService.swift | 4 + .../Modules/Wallets/Dash/DashApiService.swift | 8 +- .../Wallets/Dash/DashWalletFactory.swift | 5 +- .../DashWalletService+DynamicConstants.swift | 4 +- .../Wallets/Dash/DashWalletService.swift | 4 + .../Modules/Wallets/Doge/DogeApiService.swift | 8 +- .../Wallets/Doge/DogeWalletFactory.swift | 5 +- .../DogeWalletService+DynamicConstants.swift | 4 +- .../Wallets/Doge/DogeWalletService.swift | 4 + .../Wallets/ERC20/ERC20WalletFactory.swift | 5 +- .../Wallets/ERC20/ERC20WalletService.swift | 4 + .../Wallets/Ethereum/EthApiService.swift | 8 +- .../Wallets/Ethereum/EthWalletFactory.swift | 5 +- .../EthWalletService+DynamicConstants.swift | 4 +- .../Wallets/Ethereum/EthWalletService.swift | 4 + .../KLYWalletService+DynamicConstants.swift | 8 +- .../Wallets/Klayr/KlyNodeApiService.swift | 8 +- .../Wallets/Klayr/KlyServiceApiService.swift | 8 +- .../Wallets/Klayr/KlyWalletFactory.swift | 5 +- .../WalletService/KlyWalletService.swift | 4 + .../Wallets/TransferViewControllerBase.swift | 17 +- .../Modules/Wallets/WalletApiService.swift | 3 +- .../WalletsService/WalletCoreProtocol.swift | 10 +- .../ServiceProtocols/APICoreProtocol.swift | 1 - .../NodesMergingService.swift | 16 ++ .../NodesStorageProtocol.swift | 7 +- Adamant/Services/APICore.swift | 25 ++- .../Services/AdamantNodesMergingService.swift | 88 +++++++++ .../ApiService/AdamantApiService.swift | 8 +- .../BlockchainHealthCheckWrapper.swift | 12 +- Adamant/Services/HealthCheckWrapper.swift | 21 +- Adamant/Services/NodesStorage.swift | 180 +++++++++++------- .../AdamantTransactionStatusService.swift | 4 +- CommonKit/Scripts/CoinsScript.rb | 6 +- .../CommonKit/AdamantDynamicResources.swift | 32 ++-- .../ExtensionsTools/ExtensionsApi.swift | 2 +- ...deDTO.swift => Node+NodeKeychainDTO.swift} | 10 +- .../CommonKit/Helpers/SafeDecodingArray.swift | 11 +- .../Helpers/SafeDecodingDictionary.swift | 61 ++++++ .../{NodeDTO.swift => NodeKeychainDTO.swift} | 5 +- .../Models/Keychain/OldNodeDTO.swift | 94 --------- .../Models/Keychain/OldNodeKeychainDTO.swift | 134 +++++++++++++ CommonKit/Sources/CommonKit/Models/Node.swift | 28 +-- .../Sources/CommonKit/Models/NodeType.swift | 11 ++ 73 files changed, 823 insertions(+), 544 deletions(-) create mode 100644 Adamant/Helpers/HealthCheckWrapper+Extension.swift delete mode 100644 Adamant/Helpers/NodeWithGroup+NodeWithGroupDTO.swift delete mode 100644 Adamant/Models/Keychain/NodeWithGroupDTO.swift delete mode 100644 Adamant/Models/Keychain/OldNodeWithGroupDTO.swift delete mode 100644 Adamant/Models/NodeWithGroup.swift create mode 100644 Adamant/ServiceProtocols/NodesMergingService.swift create mode 100644 Adamant/Services/AdamantNodesMergingService.swift rename CommonKit/Sources/CommonKit/Helpers/{Node+NodeDTO.swift => Node+NodeKeychainDTO.swift} (77%) create mode 100644 CommonKit/Sources/CommonKit/Helpers/SafeDecodingDictionary.swift rename CommonKit/Sources/CommonKit/Models/Keychain/{NodeDTO.swift => NodeKeychainDTO.swift} (78%) delete mode 100644 CommonKit/Sources/CommonKit/Models/Keychain/OldNodeDTO.swift create mode 100644 CommonKit/Sources/CommonKit/Models/Keychain/OldNodeKeychainDTO.swift create mode 100644 CommonKit/Sources/CommonKit/Models/NodeType.swift diff --git a/Adamant.xcodeproj/project.pbxproj b/Adamant.xcodeproj/project.pbxproj index 0cad71528..fe32d3c18 100644 --- a/Adamant.xcodeproj/project.pbxproj +++ b/Adamant.xcodeproj/project.pbxproj @@ -226,7 +226,6 @@ 93294B9A2AAD624100911109 /* WalletFactoryCompose.swift in Sources */ = {isa = PBXBuildFile; fileRef = 93294B992AAD624100911109 /* WalletFactoryCompose.swift */; }; 932B34E92974AA4A002A75BA /* ChatPreservationProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 932B34E82974AA4A002A75BA /* ChatPreservationProtocol.swift */; }; 932F77592989F999006D8801 /* ChatCellManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 932F77582989F999006D8801 /* ChatCellManager.swift */; }; - 9332DBCD2C58C398000EC872 /* OldNodeWithGroupDTO.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9332DBCC2C58C398000EC872 /* OldNodeWithGroupDTO.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 */; }; @@ -250,11 +249,13 @@ 93496BB52A6CAED100DD062F /* Roboto_300_normal.ttf in Resources */ = {isa = PBXBuildFile; fileRef = 93496BAA2A6CAED100DD062F /* Roboto_300_normal.ttf */; }; 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 */; }; + 934A19172C5CD00500EA6E65 /* NodesMergingService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 934A19162C5CD00500EA6E65 /* NodesMergingService.swift */; }; + 934A19192C5CD0BF00EA6E65 /* AdamantNodesMergingService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 934A19182C5CD0BF00EA6E65 /* AdamantNodesMergingService.swift */; }; 93547BCA29E2262D00B0914B /* WelcomeViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 93547BC929E2262D00B0914B /* WelcomeViewController.swift */; }; + 9356589E2C60D29300E7D38A /* HealthCheckWrapper+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9356589D2C60D29300E7D38A /* HealthCheckWrapper+Extension.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+UI.swift in Sources */ = {isa = PBXBuildFile; fileRef = 936658942B0AC15300BDB2D3 /* Node+UI.swift */; }; 936658972B0ACB1500BDB2D3 /* CoinsNodesListViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 936658962B0ACB1500BDB2D3 /* CoinsNodesListViewModel.swift */; }; @@ -323,8 +324,6 @@ 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 */; }; - 93D02C802C564EF90011D819 /* NodeWithGroupDTO.swift in Sources */ = {isa = PBXBuildFile; fileRef = 93D02C7F2C564EF90011D819 /* NodeWithGroupDTO.swift */; }; - 93D02C822C564F710011D819 /* NodeWithGroup+NodeWithGroupDTO.swift in Sources */ = {isa = PBXBuildFile; fileRef = 93D02C812C564F710011D819 /* NodeWithGroup+NodeWithGroupDTO.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 */; }; @@ -876,7 +875,6 @@ 93294B992AAD624100911109 /* WalletFactoryCompose.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WalletFactoryCompose.swift; sourceTree = ""; }; 932B34E82974AA4A002A75BA /* ChatPreservationProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChatPreservationProtocol.swift; sourceTree = ""; }; 932F77582989F999006D8801 /* ChatCellManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChatCellManager.swift; sourceTree = ""; }; - 9332DBCC2C58C398000EC872 /* OldNodeWithGroupDTO.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OldNodeWithGroupDTO.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 = ""; }; @@ -899,11 +897,13 @@ 93496BAA2A6CAED100DD062F /* Roboto_300_normal.ttf */ = {isa = PBXFileReference; lastKnownFileType = file; path = Roboto_300_normal.ttf; sourceTree = ""; }; 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 = ""; }; + 934A19162C5CD00500EA6E65 /* NodesMergingService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NodesMergingService.swift; sourceTree = ""; }; + 934A19182C5CD0BF00EA6E65 /* AdamantNodesMergingService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AdamantNodesMergingService.swift; sourceTree = ""; }; 93547BC929E2262D00B0914B /* WelcomeViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WelcomeViewController.swift; sourceTree = ""; }; + 9356589D2C60D29300E7D38A /* HealthCheckWrapper+Extension.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "HealthCheckWrapper+Extension.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+UI.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Node+UI.swift"; sourceTree = ""; }; 936658962B0ACB1500BDB2D3 /* CoinsNodesListViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CoinsNodesListViewModel.swift; sourceTree = ""; }; @@ -968,8 +968,6 @@ 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 = ""; }; - 93D02C7F2C564EF90011D819 /* NodeWithGroupDTO.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NodeWithGroupDTO.swift; sourceTree = ""; }; - 93D02C812C564F710011D819 /* NodeWithGroup+NodeWithGroupDTO.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "NodeWithGroup+NodeWithGroupDTO.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 = ""; }; @@ -1841,15 +1839,6 @@ path = DTO; sourceTree = ""; }; - 93D02C7E2C564DEE0011D819 /* Keychain */ = { - isa = PBXGroup; - children = ( - 93D02C7F2C564EF90011D819 /* NodeWithGroupDTO.swift */, - 9332DBCC2C58C398000EC872 /* OldNodeWithGroupDTO.swift */, - ); - path = Keychain; - sourceTree = ""; - }; 93E123342A6DFCA6004DF33B /* NotificationsShared */ = { isa = PBXGroup; children = ( @@ -2016,6 +2005,7 @@ 3A96E37B2AED27F8001F5A52 /* PartnerQRService.swift */, 3AF08D602B4EB3C400EB82B1 /* LanguageStorageProtocol.swift */, 265AA1612B74E6B900CF98B0 /* ChatPreservation.swift */, + 934A19162C5CD00500EA6E65 /* NodesMergingService.swift */, ); path = ServiceProtocols; sourceTree = ""; @@ -2053,6 +2043,7 @@ 3A2F55FD2AC6F90E000A3F26 /* AdamantCoinStorageService.swift */, 3A96E3792AED27D7001F5A52 /* AdamantPartnerQRService.swift */, 3AF08D5E2B4EB3A200EB82B1 /* LanguageService.swift */, + 934A19182C5CD0BF00EA6E65 /* AdamantNodesMergingService.swift */, ); path = Services; sourceTree = ""; @@ -2060,7 +2051,6 @@ E913C9091FFFA95A001A83F7 /* Models */ = { isa = PBXGroup; children = ( - 93D02C7E2C564DEE0011D819 /* Keychain */, E91947B72000326B001362F8 /* ServerResponses */, E95F859220094B8E0070534A /* CoreData */, E91947B320002809001362F8 /* AdamantAccount.swift */, @@ -2094,7 +2084,6 @@ 93B28EBF2B076667007F268B /* APIResponseModel.swift */, 3AA388022B67F47600125684 /* RPCResponseModel.swift */, 3AA3880D2B6A356900125684 /* RpcRequestModel.swift */, - 936658902B0AB9DC00BDB2D3 /* NodeWithGroup.swift */, ); path = Models; sourceTree = ""; @@ -2102,6 +2091,7 @@ E913C9101FFFAA4B001A83F7 /* Helpers */ = { isa = PBXGroup; children = ( + 9356589D2C60D29300E7D38A /* HealthCheckWrapper+Extension.swift */, 93775E452A674FA9009061AC /* Markdown+Adamant.swift */, E91947B12000246A001362F8 /* AdamantError.swift */, E9061B96207501E40011F104 /* AdamantUserInfoKey.swift */, @@ -2133,7 +2123,6 @@ 936658942B0AC15300BDB2D3 /* Node+UI.swift */, 3AF53F8C2B3DCFA300B30312 /* NodeGroup+Constants.swift */, 3AA3880B2B69201B00125684 /* ADM+JsonDecode.swift */, - 93D02C812C564F710011D819 /* NodeWithGroup+NodeWithGroupDTO.swift */, ); path = Helpers; sourceTree = ""; @@ -3029,6 +3018,7 @@ 93ADE0732ACA66AF008ED641 /* VibrationSelectionFactory.swift in Sources */, 9366589B2B0AD3E600BDB2D3 /* WalletApiService.swift in Sources */, 3AFE7E522B1F6B3400718739 /* WalletServiceProtocol.swift in Sources */, + 934A19192C5CD0BF00EA6E65 /* AdamantNodesMergingService.swift in Sources */, 937751AB2A68BB390054BD65 /* ChatTransactionCell.swift in Sources */, 64A223D620F760BB005157CB /* Localization.swift in Sources */, 64E1C82F222E95F6006C4DA7 /* DogeWallet.swift in Sources */, @@ -3076,7 +3066,6 @@ E9E7CD8D20026B6600DFC4DB /* DialogService.swift in Sources */, E9E7CDB72003994E00DFC4DB /* AdamantUtilities+extended.swift in Sources */, E9147B6320505C7500145913 /* QRCodeReader+adamant.swift in Sources */, - 9332DBCD2C58C398000EC872 /* OldNodeWithGroupDTO.swift in Sources */, E90055F720EC200900D0CB2D /* SecurityViewController.swift in Sources */, 939FA3422B0D6F0000710EC6 /* SelfRemovableHostingController.swift in Sources */, 644EC35E20F34F1E00F40C73 /* DelegateDetailsViewController.swift in Sources */, @@ -3150,11 +3139,11 @@ E9E7CD932002740500DFC4DB /* AdamantAccountService.swift in Sources */, 41330F7629F1509400CB587C /* AdamantCellAnimation.swift in Sources */, 64FA53CD20E1300B006783C9 /* EthTransactionsViewController.swift in Sources */, - 93D02C822C564F710011D819 /* NodeWithGroup+NodeWithGroupDTO.swift in Sources */, 6449BA6A235CA0930033B936 /* ERC20Wallet.swift in Sources */, E9147B612050599000145913 /* LoginViewController+QR.swift in Sources */, 9399F5ED29A85A48006C3E30 /* ChatCacheService.swift in Sources */, 3A9015A92A615893002A2464 /* ChatMessagesListViewModel.swift in Sources */, + 934A19172C5CD00500EA6E65 /* NodesMergingService.swift in Sources */, 936658952B0AC15300BDB2D3 /* Node+UI.swift in Sources */, 26A976012B7E852E0095C367 /* ChatSelectTextViewFactory.swift in Sources */, 41A1995429D56E340031AD75 /* ChatMessageReplyCell.swift in Sources */, @@ -3209,12 +3198,10 @@ 648BCA6D213D384F00875EB5 /* AvatarService.swift in Sources */, E95F856F2007B61D0070534A /* GetPublicKeyResponse.swift in Sources */, 3A26D94B2C3D3838003AD832 /* KlyTransactionsViewController.swift in Sources */, - 93D02C802C564EF90011D819 /* NodeWithGroupDTO.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 */, @@ -3362,6 +3349,7 @@ E923222621135F9000A7E5AF /* EthAccount.swift in Sources */, E9061B97207501E40011F104 /* AdamantUserInfoKey.swift in Sources */, E9CAE8D62018AC5300345E76 /* AdamantApi+Transactions.swift in Sources */, + 9356589E2C60D29300E7D38A /* HealthCheckWrapper+Extension.swift in Sources */, E93D7ABE2052CEE1005D19DC /* NotificationsService.swift in Sources */, E940087D2114EDEE00CD2D67 /* EthWallet.swift in Sources */, A5E0422B282AB18B0076CD13 /* BtcUnspentTransactionResponse.swift in Sources */, diff --git a/Adamant/App/DI/AppAssembly.swift b/Adamant/App/DI/AppAssembly.swift index b116c8375..dc9766090 100644 --- a/Adamant/App/DI/AppAssembly.swift +++ b/Adamant/App/DI/AppAssembly.swift @@ -94,7 +94,10 @@ struct AppAssembly: Assembly { // MARK: NodesStorage container.register(NodesStorageProtocol.self) { r in - NodesStorage(securedStore: r.resolve(SecuredStore.self)!) + NodesStorage( + securedStore: r.resolve(SecuredStore.self)!, + nodesMergingService: r.resolve(NodesMergingService.self)! + ) }.inObjectScope(.container) // MARK: NodesAdditionalParamsStorage @@ -356,5 +359,10 @@ struct AppAssembly: Assembly { } } } + + // MARK: NodesMergingService + container.register(NodesMergingService.self) { r in + AdamantNodesMergingService() + }.inObjectScope(.transient) } } diff --git a/Adamant/Helpers/HealthCheckWrapper+Extension.swift b/Adamant/Helpers/HealthCheckWrapper+Extension.swift new file mode 100644 index 000000000..a97512153 --- /dev/null +++ b/Adamant/Helpers/HealthCheckWrapper+Extension.swift @@ -0,0 +1,17 @@ +// +// HealthCheckWrapper+Extension.swift +// Adamant +// +// Created by Andrew G on 04.08.2024. +// Copyright © 2024 Adamant. All rights reserved. +// + +import Foundation + +extension HealthCheckWrapper { + var chosenFastestNodeId: UUID? { + fastestNodeMode + ? sortedAllowedNodes.first?.id + : nil + } +} diff --git a/Adamant/Helpers/NodeGroup+Constants.swift b/Adamant/Helpers/NodeGroup+Constants.swift index bcac6516b..801b264f8 100644 --- a/Adamant/Helpers/NodeGroup+Constants.swift +++ b/Adamant/Helpers/NodeGroup+Constants.swift @@ -110,4 +110,33 @@ public extension NodeGroup { return versionNumber } + + var name: String { + switch self { + case .btc: + return BtcWalletService.tokenNetworkSymbol + case .eth: + return EthWalletService.tokenNetworkSymbol + case .klyNode: + return KlyWalletService.tokenNetworkSymbol + case .klyService: + return KlyWalletService.tokenNetworkSymbol + + " " + .adamant.coinsNodesList.serviceNode + case .doge: + return DogeWalletService.tokenNetworkSymbol + case .dash: + return DashWalletService.tokenNetworkSymbol + case .adm: + return AdmWalletService.tokenNetworkSymbol + } + } + + var includeVersionTitle: Bool { + switch self { + case .btc, .klyNode, .klyService, .doge, .adm: + return true + case .eth, .dash: + return false + } + } } diff --git a/Adamant/Helpers/NodeWithGroup+NodeWithGroupDTO.swift b/Adamant/Helpers/NodeWithGroup+NodeWithGroupDTO.swift deleted file mode 100644 index 88041ad56..000000000 --- a/Adamant/Helpers/NodeWithGroup+NodeWithGroupDTO.swift +++ /dev/null @@ -1,19 +0,0 @@ -// -// NodeWithGroup+NodeWithGroupDTO.swift -// Adamant -// -// Created by Andrew G on 28.07.2024. -// Copyright © 2024 Adamant. All rights reserved. -// - -extension NodeWithGroup { - func mapToDto() -> NodeWithGroupDTO { - .init(group: group, node: node.mapToDto()) - } -} - -extension NodeWithGroupDTO { - func mapToModel() -> NodeWithGroup { - .init(group: group, node: node.mapToModel()) - } -} diff --git a/Adamant/Models/ApiServiceError.swift b/Adamant/Models/ApiServiceError.swift index 8abc11831..c61b5597d 100644 --- a/Adamant/Models/ApiServiceError.swift +++ b/Adamant/Models/ApiServiceError.swift @@ -126,15 +126,6 @@ extension ApiServiceError: HealthCheckableError { } } - var isRequestCancelledError: Bool { - switch self { - case .requestCancelled: - return true - default: - return false - } - } - static func noEndpointsError(coin: String) -> ApiServiceError { .noEndpointsAvailable(coin: coin) } diff --git a/Adamant/Models/Keychain/NodeWithGroupDTO.swift b/Adamant/Models/Keychain/NodeWithGroupDTO.swift deleted file mode 100644 index 3d572ab3c..000000000 --- a/Adamant/Models/Keychain/NodeWithGroupDTO.swift +++ /dev/null @@ -1,14 +0,0 @@ -// -// NodeWithGroupDTO.swift -// Adamant -// -// Created by Andrew G on 28.07.2024. -// Copyright © 2024 Adamant. All rights reserved. -// - -import CommonKit - -struct NodeWithGroupDTO: Codable { - let group: NodeGroup - let node: NodeDTO -} diff --git a/Adamant/Models/Keychain/OldNodeWithGroupDTO.swift b/Adamant/Models/Keychain/OldNodeWithGroupDTO.swift deleted file mode 100644 index 7728eb39e..000000000 --- a/Adamant/Models/Keychain/OldNodeWithGroupDTO.swift +++ /dev/null @@ -1,21 +0,0 @@ -// -// OldNodeWithGroupDTO.swift -// Adamant -// -// Created by Andrew G on 30.07.2024. -// Copyright © 2024 Adamant. All rights reserved. -// - -import CommonKit - -// TODO: Remove after a few updates (it's used for migration) -struct OldNodeWithGroupDTO: Codable { - let group: NodeGroup - let node: OldNodeDTO -} - -extension OldNodeWithGroupDTO { - func mapToModernDto() -> NodeWithGroupDTO { - .init(group: group, node: node.mapToModernDto()) - } -} diff --git a/Adamant/Models/NodeWithGroup.swift b/Adamant/Models/NodeWithGroup.swift deleted file mode 100644 index e06510bec..000000000 --- a/Adamant/Models/NodeWithGroup.swift +++ /dev/null @@ -1,45 +0,0 @@ -// -// NodeWithGroup.swift -// Adamant -// -// Created by Andrew G on 20.11.2023. -// Copyright © 2023 Adamant. All rights reserved. -// - -import CommonKit - -struct NodeWithGroup: 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 .klyNode: - return KlyWalletService.tokenNetworkSymbol - case .klyService: - return KlyWalletService.tokenNetworkSymbol - + " " + .adamant.coinsNodesList.serviceNode - case .doge: - return DogeWalletService.tokenNetworkSymbol - case .dash: - return DashWalletService.tokenNetworkSymbol - case .adm: - return AdmWalletService.tokenNetworkSymbol - } - } - - var includeVersionTitle: Bool { - switch self { - case .btc, .klyNode, .klyService, .doge, .adm: - return true - case .eth, .dash: - return false - } - } -} diff --git a/Adamant/Modules/ChatsList/ComplexTransferViewController.swift b/Adamant/Modules/ChatsList/ComplexTransferViewController.swift index 1eda62f01..f55add2b5 100644 --- a/Adamant/Modules/ChatsList/ComplexTransferViewController.swift +++ b/Adamant/Modules/ChatsList/ComplexTransferViewController.swift @@ -119,54 +119,51 @@ extension ComplexTransferViewController: PagingViewControllerDataSource { @MainActor func pagingViewController(_ pagingViewController: PagingViewController, viewControllerAt index: Int) -> UIViewController { let service = services[index] - + let admService = services.first { $0.core.nodeGroups.contains(.adm) } let vc = screensFactory.makeTransferVC(service: service) - guard let v = vc as? TransferViewControllerBase else { return vc } - - v.delegate = self + vc.delegate = self guard let address = partner?.address else { return vc } let name = partner?.chatroom?.getName(addressBookService: addressBookService) - v.replyToMessageId = replyToMessageId - v.admReportRecipient = address - v.recipientIsReadonly = true - v.commentsEnabled = service.core.commentsEnabledForRichMessages && partner?.isDummy != true - v.showProgressView(animated: false) + vc.replyToMessageId = replyToMessageId + vc.admReportRecipient = address + vc.recipientIsReadonly = true + vc.commentsEnabled = service.core.commentsEnabledForRichMessages && partner?.isDummy != true + vc.showProgressView(animated: false) Task { - let groupsWithoutActiveNode = service.core.nodeGroups.filter { - !nodesStorage.haveActiveNode(in: $0) - } - - if let group = groupsWithoutActiveNode.first { - v.showAlertView( + guard service.core.hasActiveNode else { + vc.showAlertView( title: nil, - message: ApiServiceError.noEndpointsAvailable(coin: group.name).errorDescription ?? String.adamant.sharedErrors.unknownError, + message: ApiServiceError.noEndpointsAvailable( + coin: service.core.tokenName + ).errorDescription ?? .adamant.sharedErrors.unknownError, animated: true ) return } - if !nodesStorage.haveActiveNode(in: .adm) { - v.showAlertView( + guard admService?.core.hasActiveNode ?? false else { + vc.showAlertView( title: nil, - message: String.adamant.sharedErrors.admNodeErrorMessage(service.core.tokenSymbol), + message: .adamant.sharedErrors.admNodeErrorMessage(service.core.tokenSymbol), animated: true ) return } + do { let walletAddress = try await service.core .getWalletAddress( byAdamantAddress: address ) - v.recipientAddress = walletAddress - v.recipientName = name - v.hideProgress(animated: true) + vc.recipientAddress = walletAddress + vc.recipientName = name + vc.hideProgress(animated: true) if ERC20Token.supportedTokens.contains( where: { token in @@ -177,16 +174,16 @@ extension ComplexTransferViewController: PagingViewControllerDataSource { by: EthWalletService.richMessageType )?.core - v.rootCoinBalance = ethWallet?.wallet?.balance + vc.rootCoinBalance = ethWallet?.wallet?.balance } } catch let error as WalletServiceError { - v.showAlertView( + vc.showAlertView( title: nil, message: error.message, animated: true ) } catch { - v.showAlertView( + vc.showAlertView( title: nil, message: String.adamant.sharedErrors.unknownError, animated: true diff --git a/Adamant/Modules/CoinsNodesList/CoinsNodesListFactory.swift b/Adamant/Modules/CoinsNodesList/CoinsNodesListFactory.swift index 081c56256..2d35ba864 100644 --- a/Adamant/Modules/CoinsNodesList/CoinsNodesListFactory.swift +++ b/Adamant/Modules/CoinsNodesList/CoinsNodesListFactory.swift @@ -16,14 +16,17 @@ enum CoinsNodesListContext { } struct CoinsNodesListFactory { - private let assembler: Assembler + private let parent: Assembler + private let assemblies = [CoinsNodesListAssembly()] init(parent: Assembler) { - assembler = .init([CoinsNodesListAssembly()], parent: parent) + self.parent = parent } + @MainActor func makeViewController(context: CoinsNodesListContext) -> UIViewController { - let viewModel = assembler.resolve(CoinsNodesListViewModel.self)! + let assembler = Assembler(assemblies, parent: parent) + let viewModel = { assembler.resolver.resolve(CoinsNodesListViewModel.self)! } let view = CoinsNodesListView(viewModel: viewModel) switch context { @@ -38,7 +41,11 @@ struct CoinsNodesListFactory { private struct CoinsNodesListAssembly: Assembly { func assemble(container: Container) { container.register(CoinsNodesListViewModel.self) { - let processedGroups = Set(NodeGroup.allCases).subtracting([.adm]) + let processedGroups = NodeGroup.allCases.compactMap { + $0 == .adm + ? nil + : $0 + } return .init( mapper: .init(processedGroups: processedGroups), @@ -57,6 +64,6 @@ private struct CoinsNodesListAssembly: Assembly { adm: $0.resolve(ApiService.self)! ) ) - }.inObjectScope(.weak) + }.inObjectScope(.transient) } } diff --git a/Adamant/Modules/CoinsNodesList/View/CoinsNodesListView.swift b/Adamant/Modules/CoinsNodesList/View/CoinsNodesListView.swift index 0f20bb046..ac0ade64e 100644 --- a/Adamant/Modules/CoinsNodesList/View/CoinsNodesListView.swift +++ b/Adamant/Modules/CoinsNodesList/View/CoinsNodesListView.swift @@ -31,8 +31,8 @@ struct CoinsNodesListView: View { .navigationTitle(String.adamant.coinsNodesList.title) } - init(viewModel: CoinsNodesListViewModel) { - _viewModel = .init(wrappedValue: viewModel) + init(viewModel: @escaping () -> CoinsNodesListViewModel) { + _viewModel = .init(wrappedValue: viewModel()) } } @@ -44,7 +44,13 @@ private extension CoinsNodesListView { ForEach(model.rows) { row in Row( model: row, - setIsEnabled: { viewModel.setIsEnabled(id: row.id, value: $0) } + setIsEnabled: { + viewModel.setIsEnabled( + id: row.id, + group: row.group, + value: $0 + ) + } ).listRowBackground(Color(uiColor: .adamant.cellColor)) } } diff --git a/Adamant/Modules/CoinsNodesList/ViewModel/CoinsNodesListMapper.swift b/Adamant/Modules/CoinsNodesList/ViewModel/CoinsNodesListMapper.swift index 7bc790405..5bdc02a4a 100644 --- a/Adamant/Modules/CoinsNodesList/ViewModel/CoinsNodesListMapper.swift +++ b/Adamant/Modules/CoinsNodesList/ViewModel/CoinsNodesListMapper.swift @@ -10,25 +10,13 @@ import CommonKit import SwiftUI struct CoinsNodesListMapper { - let processedGroups: Set + let processedGroups: [NodeGroup] - func map(items: [NodeWithGroup], restNodeIds: [UUID]) -> [CoinsNodesListState.Section] { - var nodesDict = [NodeGroup: [Node]]() - - 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 { + func map(items: [NodeGroup: [Node]], restNodeIds: [UUID]) -> [CoinsNodesListState.Section] { + processedGroups.map { map( group: $0, - nodes: nodesDict[$0] ?? .init(), + nodes: items[$0] ?? .init(), restNodeIds: restNodeIds ) }.sorted { $0.title < $1.title } @@ -45,26 +33,29 @@ private extension CoinsNodesListMapper { id: group, title: group.name, rows: nodes.map { - map(node: $0, restNodeIds: restNodeIds, includeVersionTitle: group.includeVersionTitle) + map( + node: $0, + group: group, + isRest: restNodeIds.contains($0.id), + includeVersionTitle: group.includeVersionTitle + ) } ) } func map( node: Node, - restNodeIds: [UUID], + group: NodeGroup, + isRest: Bool, includeVersionTitle: Bool ) -> CoinsNodesListState.Section.Row { - let indicatorString = node.indicatorString( - isRest: restNodeIds.contains(node.id), - isWs: false - ) - + let indicatorString = node.indicatorString(isRest: isRest, isWs: false) var indicatorAttrString = AttributedString(stringLiteral: indicatorString) indicatorAttrString.foregroundColor = .init(uiColor: node.indicatorColor) return .init( id: node.id, + group: group, isEnabled: node.isEnabled, title: node.asString(), connectionStatus: indicatorAttrString, diff --git a/Adamant/Modules/CoinsNodesList/ViewModel/CoinsNodesListState.swift b/Adamant/Modules/CoinsNodesList/ViewModel/CoinsNodesListState.swift index 9a868bbf3..640753860 100644 --- a/Adamant/Modules/CoinsNodesList/ViewModel/CoinsNodesListState.swift +++ b/Adamant/Modules/CoinsNodesList/ViewModel/CoinsNodesListState.swift @@ -32,6 +32,7 @@ extension CoinsNodesListState { extension CoinsNodesListState.Section { struct Row: Equatable, Identifiable { let id: UUID + let group: NodeGroup let isEnabled: Bool let title: String let connectionStatus: AttributedString diff --git a/Adamant/Modules/CoinsNodesList/ViewModel/CoinsNodesListViewModel.swift b/Adamant/Modules/CoinsNodesList/ViewModel/CoinsNodesListViewModel.swift index 91edbfa92..d689ca0c6 100644 --- a/Adamant/Modules/CoinsNodesList/ViewModel/CoinsNodesListViewModel.swift +++ b/Adamant/Modules/CoinsNodesList/ViewModel/CoinsNodesListViewModel.swift @@ -17,7 +17,7 @@ final class CoinsNodesListViewModel: ObservableObject { private let mapper: CoinsNodesListMapper private let nodesStorage: NodesStorageProtocol private let nodesAdditionalParamsStorage: NodesAdditionalParamsStorageProtocol - private let processedGroups: Set + private let processedGroups: [NodeGroup] private let apiServices: ApiServices private var subscriptions = Set() @@ -25,7 +25,7 @@ final class CoinsNodesListViewModel: ObservableObject { mapper: CoinsNodesListMapper, nodesStorage: NodesStorageProtocol, nodesAdditionalParamsStorage: NodesAdditionalParamsStorageProtocol, - processedGroups: Set, + processedGroups: [NodeGroup], apiServices: ApiServices ) { self.mapper = mapper @@ -36,8 +36,8 @@ final class CoinsNodesListViewModel: ObservableObject { Task { @MainActor in setup() } } - func setIsEnabled(id: UUID, value: Bool) { - nodesStorage.updateNode(id: id) { $0.isEnabled = value } + func setIsEnabled(id: UUID, group: NodeGroup, value: Bool) { + nodesStorage.updateNode(id: id, group: group) { $0.isEnabled = value } } func reset() { @@ -61,7 +61,7 @@ private extension CoinsNodesListViewModel { guard let someGroup = processedGroups.first else { return } - nodesStorage.nodesWithGroupsPublisher + nodesStorage.nodesPublisher .combineLatest(nodesAdditionalParamsStorage.fastestNodeMode(group: someGroup)) .receive(on: DispatchQueue.main) .sink { [weak self] in self?.updateSections(items: $0.0) } @@ -76,18 +76,18 @@ private extension CoinsNodesListViewModel { healthCheck() } - func updateSections(items: [NodeWithGroup]) { + func updateSections(items: [NodeGroup: [Node]]) { state.sections = mapper.map( items: items, - restNodeIds: processedGroups.flatMap { - apiServices.getApiService(group: $0).preferredNodeIds + restNodeIds: processedGroups.compactMap { + apiServices.getApiService(group: $0).chosenFastestNodeId } ) } func saveFastestNodeMode(_ value: Bool) { nodesAdditionalParamsStorage.setFastestNodeMode( - groups: processedGroups, + groups: .init(processedGroups), value: value ) } diff --git a/Adamant/Modules/NodesEditor/NodeEditorViewController.swift b/Adamant/Modules/NodesEditor/NodeEditorViewController.swift index 905bdf538..e4d7e3a6d 100644 --- a/Adamant/Modules/NodesEditor/NodeEditorViewController.swift +++ b/Adamant/Modules/NodesEditor/NodeEditorViewController.swift @@ -243,7 +243,7 @@ extension NodeEditorViewController { let result: NodeEditorResult if let node = node { - nodesStorage.updateNode(id: node.id) { node in + nodesStorage.updateNode(id: node.id, group: .adm) { node in node.mainOrigin.scheme = scheme node.mainOrigin.host = host node.mainOrigin.port = port @@ -265,7 +265,8 @@ extension NodeEditorViewController { height: nil, ping: nil, connectionStatus: nil, - preferMainOrigin: nil + preferMainOrigin: nil, + type: .custom )) } diff --git a/Adamant/Modules/NodesEditor/NodesListViewController.swift b/Adamant/Modules/NodesEditor/NodesListViewController.swift index ea4599e8b..166ddc274 100644 --- a/Adamant/Modules/NodesEditor/NodesListViewController.swift +++ b/Adamant/Modules/NodesEditor/NodesListViewController.swift @@ -93,7 +93,7 @@ final class NodesListViewController: FormViewController { @ObservableValue private var nodesList = [Node]() @ObservableValue private var currentSocketsNodeId: UUID? - @ObservableValue private var currentRestNodesIds = [UUID]() + @ObservableValue private var chosenFastestNodeId: UUID? private var nodesHaveBeenDisplayed = false private var timerSubsctiption: AnyCancellable? @@ -221,7 +221,7 @@ final class NodesListViewController: FormViewController { private func setNewNodesList(_ newNodes: [Node]) { nodesList = newNodes - currentRestNodesIds = apiService.preferredNodeIds + chosenFastestNodeId = apiService.chosenFastestNodeId if !nodesHaveBeenDisplayed { UIView.performWithoutAnimation { @@ -250,7 +250,7 @@ extension NodesListViewController { guard let index = getNodeIndex(nodeId: nodeId) else { return } getNodesSection()?.remove(at: index) - nodesStorage.removeNode(id: nodeId) + nodesStorage.removeNode(id: nodeId, group: .adm) } func getNodeIndex(nodeId: UUID) -> Int? { @@ -422,14 +422,14 @@ extension NodesListViewController { id: node.id, title: node.asString(), indicatorString: node.indicatorString( - isRest: currentRestNodesIds.contains(node.id), + isRest: chosenFastestNodeId == node.id, isWs: currentSocketsNodeId == node.id ), indicatorColor: node.indicatorColor, statusString: node.statusString(showVersion: true) ?? .empty, isEnabled: node.isEnabled, nodeUpdateAction: .init(id: node.id.uuidString) { [nodesStorage] isEnabled in - nodesStorage.updateNode(id: node.id) { $0.isEnabled = isEnabled } + nodesStorage.updateNode(id: node.id, group: .adm) { $0.isEnabled = isEnabled } } ) } @@ -437,7 +437,7 @@ extension NodesListViewController { private func makeNodeCellPublisher(nodeId: UUID) -> some Observable { $nodesList.combineLatest( $currentSocketsNodeId, - $currentRestNodesIds + $chosenFastestNodeId ).compactMap { [weak self] tuple in let nodes = tuple.0 diff --git a/Adamant/Modules/PartnerQR/PartnerQRFactory.swift b/Adamant/Modules/PartnerQR/PartnerQRFactory.swift index 27801872d..a7805eed2 100644 --- a/Adamant/Modules/PartnerQR/PartnerQRFactory.swift +++ b/Adamant/Modules/PartnerQR/PartnerQRFactory.swift @@ -11,24 +11,24 @@ import SwiftUI import CommonKit struct PartnerQRFactory { - private let assembler: Assembler + private let parent: Assembler + private let assemblies = [PartnerQRAssembly()] init(parent: Assembler) { - assembler = .init([PartnerQRAssembly()], parent: parent) + self.parent = parent } @MainActor func makeViewController(partner: CoreDataAccount) -> UIViewController { - let viewModel = assembler.resolve(PartnerQRViewModel.self)! - viewModel.setup(partner: partner) + let assembler = Assembler(assemblies, parent: parent) - let view = PartnerQRView( - viewModel: viewModel - ) + let viewModel = { + let viewModel = assembler.resolver.resolve(PartnerQRViewModel.self)! + viewModel.setup(partner: partner) + return viewModel + } - return UIHostingController( - rootView: view - ) + return UIHostingController(rootView: PartnerQRView(viewModel: viewModel)) } } @@ -47,6 +47,6 @@ private struct PartnerQRAssembly: Assembly { avatarService: $0.resolve(AvatarService.self)!, partnerQRService: $0.resolve(PartnerQRService.self)! ) - }.inObjectScope(.weak) + }.inObjectScope(.transient) } } diff --git a/Adamant/Modules/PartnerQR/PartnerQRView.swift b/Adamant/Modules/PartnerQR/PartnerQRView.swift index 2d8117d0d..f872bf394 100644 --- a/Adamant/Modules/PartnerQR/PartnerQRView.swift +++ b/Adamant/Modules/PartnerQR/PartnerQRView.swift @@ -26,6 +26,10 @@ struct PartnerQRView: View { } } } + + init(viewModel: @escaping () -> PartnerQRViewModel) { + _viewModel = .init(wrappedValue: viewModel()) + } } private extension PartnerQRView { diff --git a/Adamant/Modules/Settings/Contribute/ContributeFactory.swift b/Adamant/Modules/Settings/Contribute/ContributeFactory.swift index a8c13a4ec..cdda294ca 100644 --- a/Adamant/Modules/Settings/Contribute/ContributeFactory.swift +++ b/Adamant/Modules/Settings/Contribute/ContributeFactory.swift @@ -10,18 +10,17 @@ import Swinject import SwiftUI struct ContributeFactory { - private let assembler: Assembler + private let parent: Assembler + private let assemblies = [ContributeAssembly()] init(parent: Assembler) { - assembler = .init([ContributeAssembly()], parent: parent) + self.parent = parent } func makeViewController() -> UIViewController { - UIHostingController( - rootView: ContributeView( - viewModel: assembler.resolve(ContributeViewModel.self)! - ) - ) + let assembler = Assembler(assemblies, parent: parent) + let viewModel = { assembler.resolver.resolve(ContributeViewModel.self)! } + return UIHostingController(rootView: ContributeView(viewModel: viewModel)) } } @@ -31,6 +30,6 @@ private struct ContributeAssembly: Assembly { ContributeViewModel( crashliticsService: $0.resolve(CrashlyticsService.self)! ) - }.inObjectScope(.weak) + }.inObjectScope(.transient) } } diff --git a/Adamant/Modules/Settings/Contribute/ContributeView.swift b/Adamant/Modules/Settings/Contribute/ContributeView.swift index cc252b3de..99f4719d8 100644 --- a/Adamant/Modules/Settings/Contribute/ContributeView.swift +++ b/Adamant/Modules/Settings/Contribute/ContributeView.swift @@ -39,8 +39,8 @@ struct ContributeView: View { } } - init(viewModel: ContributeViewModel) { - _viewModel = .init(wrappedValue: viewModel) + init(viewModel: @escaping () -> ContributeViewModel) { + _viewModel = .init(wrappedValue: viewModel()) } } diff --git a/Adamant/Modules/TestVibration/VibrationSelectionFactory.swift b/Adamant/Modules/TestVibration/VibrationSelectionFactory.swift index 3c3e87b05..d85712417 100644 --- a/Adamant/Modules/TestVibration/VibrationSelectionFactory.swift +++ b/Adamant/Modules/TestVibration/VibrationSelectionFactory.swift @@ -10,18 +10,18 @@ import Swinject import SwiftUI struct VibrationSelectionFactory { - private let assembler: Assembler + private let parent: Assembler + private let assemblies = [VibrationSelectionAssembly()] init(parent: Assembler) { - assembler = .init([VibrationSelectionAssembly()], parent: parent) + self.parent = parent } + @MainActor func makeViewController() -> UIViewController { - UIHostingController( - rootView: VibrationSelectionView( - viewModel: assembler.resolve(VibrationSelectionViewModel.self)! - ) - ) + let assembler = Assembler(assemblies, parent: parent) + let viewModel = { assembler.resolver.resolve(VibrationSelectionViewModel.self)! } + return UIHostingController(rootView: VibrationSelectionView(viewModel: viewModel)) } } @@ -31,6 +31,6 @@ private struct VibrationSelectionAssembly: Assembly { VibrationSelectionViewModel( vibroService: $0.resolve(VibroService.self)! ) - }.inObjectScope(.weak) + }.inObjectScope(.transient) } } diff --git a/Adamant/Modules/TestVibration/VibrationSelectionView.swift b/Adamant/Modules/TestVibration/VibrationSelectionView.swift index 11b065079..7452705f0 100644 --- a/Adamant/Modules/TestVibration/VibrationSelectionView.swift +++ b/Adamant/Modules/TestVibration/VibrationSelectionView.swift @@ -12,8 +12,8 @@ import CommonKit struct VibrationSelectionView: View { @StateObject var viewModel: VibrationSelectionViewModel - init(viewModel: VibrationSelectionViewModel) { - _viewModel = .init(wrappedValue: viewModel) + init(viewModel: @escaping () -> VibrationSelectionViewModel) { + _viewModel = .init(wrappedValue: viewModel()) } var body: some View { diff --git a/Adamant/Modules/Wallets/Adamant/AdmWalletFactory.swift b/Adamant/Modules/Wallets/Adamant/AdmWalletFactory.swift index ec164fc28..5bc7e5f03 100644 --- a/Adamant/Modules/Wallets/Adamant/AdmWalletFactory.swift +++ b/Adamant/Modules/Wallets/Adamant/AdmWalletFactory.swift @@ -52,7 +52,10 @@ struct AdmWalletFactory: WalletFactory { vibroService: assembler.resolve(VibroService.self)!, walletService: service, reachabilityMonitor: assembler.resolve(ReachabilityMonitor.self)!, - nodesStorage: assembler.resolve(NodesStorageProtocol.self)! + nodesStorage: assembler.resolve(NodesStorageProtocol.self)!, + isActiveAdmNode: { [admApi = assembler.resolve(ApiService.self)!] in + admApi.hasActiveNode + } ) } diff --git a/Adamant/Modules/Wallets/Adamant/AdmWalletService+DynamicConstants.swift b/Adamant/Modules/Wallets/Adamant/AdmWalletService+DynamicConstants.swift index 9cbd3ec83..ef16ecc4b 100644 --- a/Adamant/Modules/Wallets/Adamant/AdmWalletService+DynamicConstants.swift +++ b/Adamant/Modules/Wallets/Adamant/AdmWalletService+DynamicConstants.swift @@ -67,27 +67,27 @@ extension AdmWalletService { 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")!, 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")!, 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")!), -Node(url: URL(string: "https://node1.adamant.business")!, altUrl: URL(string: "http://194.233.75.29:45555")), -Node(url: URL(string: "https://node2.blockchain2fa.io")!), -Node(url: URL(string: "https://phecda.adm.im")!, altUrl: URL(string: "http://46.250.234.248:36666")), -Node(url: URL(string: "https://tegmine.adm.im")!), -Node(url: URL(string: "https://tauri.adm.im")!, altUrl: URL(string: "http://154.26.159.245:36666")), -Node(url: URL(string: "https://dschubba.adm.im")!), + Node.makeDefaultNode(url: URL(string: "https://clown.adamant.im")!), +Node.makeDefaultNode(url: URL(string: "https://lake.adamant.im")!), +Node.makeDefaultNode(url: URL(string: "https://endless.adamant.im")!, altUrl: URL(string: "http://149.102.157.15:36666")), +Node.makeDefaultNode(url: URL(string: "https://bid.adamant.im")!), +Node.makeDefaultNode(url: URL(string: "https://unusual.adamant.im")!), +Node.makeDefaultNode(url: URL(string: "https://debate.adamant.im")!, altUrl: URL(string: "http://95.216.161.113:36666")), +Node.makeDefaultNode(url: URL(string: "http://78.47.205.206:36666")!), +Node.makeDefaultNode(url: URL(string: "http://5.161.53.74:36666")!), +Node.makeDefaultNode(url: URL(string: "http://184.94.215.92:45555")!), +Node.makeDefaultNode(url: URL(string: "https://node1.adamant.business")!, altUrl: URL(string: "http://194.233.75.29:45555")), +Node.makeDefaultNode(url: URL(string: "https://node2.blockchain2fa.io")!), +Node.makeDefaultNode(url: URL(string: "https://phecda.adm.im")!, altUrl: URL(string: "http://46.250.234.248:36666")), +Node.makeDefaultNode(url: URL(string: "https://tegmine.adm.im")!), +Node.makeDefaultNode(url: URL(string: "https://tauri.adm.im")!, altUrl: URL(string: "http://154.26.159.245:36666")), +Node.makeDefaultNode(url: URL(string: "https://dschubba.adm.im")!), ] } static var serviceNodes: [Node] { [ - Node(url: URL(string: "https://info.adamant.im")!), + Node.makeDefaultNode(url: URL(string: "https://info.adamant.im")!), ] } } diff --git a/Adamant/Modules/Wallets/Adamant/AdmWalletService.swift b/Adamant/Modules/Wallets/Adamant/AdmWalletService.swift index 04784d370..027e80795 100644 --- a/Adamant/Modules/Wallets/Adamant/AdmWalletService.swift +++ b/Adamant/Modules/Wallets/Adamant/AdmWalletService.swift @@ -90,6 +90,10 @@ final class AdmWalletService: NSObject, WalletCoreProtocol { $hasMoreOldTransactions.eraseToAnyPublisher() } + var hasActiveNode: Bool { + apiService.hasActiveNode + } + private(set) lazy var coinStorage: CoinStorageService = AdamantCoinStorageService( coinId: tokenUnicID, coreDataStack: coreDataStack, diff --git a/Adamant/Modules/Wallets/Bitcoin/BtcApiService.swift b/Adamant/Modules/Wallets/Bitcoin/BtcApiService.swift index f41593a22..4ad28bc2c 100644 --- a/Adamant/Modules/Wallets/Bitcoin/BtcApiService.swift +++ b/Adamant/Modules/Wallets/Bitcoin/BtcApiService.swift @@ -67,8 +67,12 @@ final class BtcApiCore: BlockchainHealthCheckableService { final class BtcApiService: WalletApiService { let api: BlockchainHealthCheckWrapper - var preferredNodeIds: [UUID] { - api.preferredNodeIds + var chosenFastestNodeId: UUID? { + api.chosenFastestNodeId + } + + var hasActiveNode: Bool { + !api.sortedAllowedNodes.isEmpty } init(api: BlockchainHealthCheckWrapper) { diff --git a/Adamant/Modules/Wallets/Bitcoin/BtcWalletFactory.swift b/Adamant/Modules/Wallets/Bitcoin/BtcWalletFactory.swift index f4e82a85f..74251a96b 100644 --- a/Adamant/Modules/Wallets/Bitcoin/BtcWalletFactory.swift +++ b/Adamant/Modules/Wallets/Bitcoin/BtcWalletFactory.swift @@ -49,7 +49,10 @@ struct BtcWalletFactory: WalletFactory { vibroService: assembler.resolve(VibroService.self)!, walletService: service, reachabilityMonitor: assembler.resolve(ReachabilityMonitor.self)!, - nodesStorage: assembler.resolve(NodesStorageProtocol.self)! + nodesStorage: assembler.resolve(NodesStorageProtocol.self)!, + isActiveAdmNode: { [admApi = assembler.resolve(ApiService.self)!] in + admApi.hasActiveNode + } ) } diff --git a/Adamant/Modules/Wallets/Bitcoin/BtcWalletService+DynamicConstants.swift b/Adamant/Modules/Wallets/Bitcoin/BtcWalletService+DynamicConstants.swift index 130bc3953..977b8abd5 100644 --- a/Adamant/Modules/Wallets/Bitcoin/BtcWalletService+DynamicConstants.swift +++ b/Adamant/Modules/Wallets/Bitcoin/BtcWalletService+DynamicConstants.swift @@ -75,8 +75,8 @@ extension BtcWalletService { static var nodes: [Node] { [ - Node(url: URL(string: "https://btcnode1.adamant.im")!, altUrl: URL(string: "http://176.9.38.204:44099")), -Node(url: URL(string: "https://btcnode3.adamant.im")!, altUrl: URL(string: "http://195.201.242.108:44099")), + Node.makeDefaultNode(url: URL(string: "https://btcnode1.adamant.im")!, altUrl: URL(string: "http://176.9.38.204:44099")), +Node.makeDefaultNode(url: URL(string: "https://btcnode3.adamant.im")!, altUrl: URL(string: "http://195.201.242.108:44099")), ] } diff --git a/Adamant/Modules/Wallets/Bitcoin/BtcWalletService.swift b/Adamant/Modules/Wallets/Bitcoin/BtcWalletService.swift index b54564b31..0e26b9c78 100644 --- a/Adamant/Modules/Wallets/Bitcoin/BtcWalletService.swift +++ b/Adamant/Modules/Wallets/Bitcoin/BtcWalletService.swift @@ -175,6 +175,10 @@ final class BtcWalletService: WalletCoreProtocol { $hasMoreOldTransactions.eraseToAnyPublisher() } + var hasActiveNode: Bool { + apiService.hasActiveNode + } + private(set) lazy var coinStorage: CoinStorageService = AdamantCoinStorageService( coinId: tokenUnicID, coreDataStack: coreDataStack, diff --git a/Adamant/Modules/Wallets/Dash/DashApiService.swift b/Adamant/Modules/Wallets/Dash/DashApiService.swift index 9d0312d2e..2f1a4933a 100644 --- a/Adamant/Modules/Wallets/Dash/DashApiService.swift +++ b/Adamant/Modules/Wallets/Dash/DashApiService.swift @@ -67,8 +67,12 @@ final class DashApiCore: BlockchainHealthCheckableService { final class DashApiService: WalletApiService { let api: BlockchainHealthCheckWrapper - var preferredNodeIds: [UUID] { - api.preferredNodeIds + var chosenFastestNodeId: UUID? { + api.chosenFastestNodeId + } + + var hasActiveNode: Bool { + !api.sortedAllowedNodes.isEmpty } init(api: BlockchainHealthCheckWrapper) { diff --git a/Adamant/Modules/Wallets/Dash/DashWalletFactory.swift b/Adamant/Modules/Wallets/Dash/DashWalletFactory.swift index e468dc05a..95e54b2f6 100644 --- a/Adamant/Modules/Wallets/Dash/DashWalletFactory.swift +++ b/Adamant/Modules/Wallets/Dash/DashWalletFactory.swift @@ -48,7 +48,10 @@ struct DashWalletFactory: WalletFactory { vibroService: assembler.resolve(VibroService.self)!, walletService: service, reachabilityMonitor: assembler.resolve(ReachabilityMonitor.self)!, - nodesStorage: assembler.resolve(NodesStorageProtocol.self)! + nodesStorage: assembler.resolve(NodesStorageProtocol.self)!, + isActiveAdmNode: { [admApi = assembler.resolve(ApiService.self)!] in + admApi.hasActiveNode + } ) } diff --git a/Adamant/Modules/Wallets/Dash/DashWalletService+DynamicConstants.swift b/Adamant/Modules/Wallets/Dash/DashWalletService+DynamicConstants.swift index 0b6668985..ddd4c6316 100644 --- a/Adamant/Modules/Wallets/Dash/DashWalletService+DynamicConstants.swift +++ b/Adamant/Modules/Wallets/Dash/DashWalletService+DynamicConstants.swift @@ -75,8 +75,8 @@ extension DashWalletService { static var nodes: [Node] { [ - Node(url: URL(string: "https://dashnode1.adamant.im")!, altUrl: URL(string: "http://45.85.147.224:44099")), -Node(url: URL(string: "https://dashnode2.adamant.im")!, altUrl: URL(string: "http://207.180.210.95:44099")), + Node.makeDefaultNode(url: URL(string: "https://dashnode1.adamant.im")!, altUrl: URL(string: "http://45.85.147.224:44099")), +Node.makeDefaultNode(url: URL(string: "https://dashnode2.adamant.im")!, altUrl: URL(string: "http://207.180.210.95:44099")), ] } diff --git a/Adamant/Modules/Wallets/Dash/DashWalletService.swift b/Adamant/Modules/Wallets/Dash/DashWalletService.swift index 9b6ae2edc..493b0828a 100644 --- a/Adamant/Modules/Wallets/Dash/DashWalletService.swift +++ b/Adamant/Modules/Wallets/Dash/DashWalletService.swift @@ -114,6 +114,10 @@ final class DashWalletService: WalletCoreProtocol { } } + var hasActiveNode: Bool { + apiService.hasActiveNode + } + // MARK: - Notifications let walletUpdatedNotification = Notification.Name("adamant.dashWallet.walletUpdated") let serviceEnabledChanged = Notification.Name("adamant.dashWallet.enabledChanged") diff --git a/Adamant/Modules/Wallets/Doge/DogeApiService.swift b/Adamant/Modules/Wallets/Doge/DogeApiService.swift index 7446fb989..52ea9878e 100644 --- a/Adamant/Modules/Wallets/Doge/DogeApiService.swift +++ b/Adamant/Modules/Wallets/Doge/DogeApiService.swift @@ -48,8 +48,12 @@ final class DogeApiCore: BlockchainHealthCheckableService { final class DogeApiService: WalletApiService { let api: BlockchainHealthCheckWrapper - var preferredNodeIds: [UUID] { - api.preferredNodeIds + var chosenFastestNodeId: UUID? { + api.chosenFastestNodeId + } + + var hasActiveNode: Bool { + !api.sortedAllowedNodes.isEmpty } init(api: BlockchainHealthCheckWrapper) { diff --git a/Adamant/Modules/Wallets/Doge/DogeWalletFactory.swift b/Adamant/Modules/Wallets/Doge/DogeWalletFactory.swift index b2e700f46..d118446fa 100644 --- a/Adamant/Modules/Wallets/Doge/DogeWalletFactory.swift +++ b/Adamant/Modules/Wallets/Doge/DogeWalletFactory.swift @@ -48,7 +48,10 @@ struct DogeWalletFactory: WalletFactory { vibroService: assembler.resolve(VibroService.self)!, walletService: service, reachabilityMonitor: assembler.resolve(ReachabilityMonitor.self)!, - nodesStorage: assembler.resolve(NodesStorageProtocol.self)! + nodesStorage: assembler.resolve(NodesStorageProtocol.self)!, + isActiveAdmNode: { [admApi = assembler.resolve(ApiService.self)!] in + admApi.hasActiveNode + } ) } diff --git a/Adamant/Modules/Wallets/Doge/DogeWalletService+DynamicConstants.swift b/Adamant/Modules/Wallets/Doge/DogeWalletService+DynamicConstants.swift index 69c4c7900..fd2449ad2 100644 --- a/Adamant/Modules/Wallets/Doge/DogeWalletService+DynamicConstants.swift +++ b/Adamant/Modules/Wallets/Doge/DogeWalletService+DynamicConstants.swift @@ -75,8 +75,8 @@ extension DogeWalletService { static var nodes: [Node] { [ - Node(url: URL(string: "https://dogenode1.adamant.im")!, altUrl: URL(string: "http://5.9.99.62:44099")), -Node(url: URL(string: "https://dogenode2.adamant.im")!, altUrl: URL(string: "http://176.9.32.126:44098")), + Node.makeDefaultNode(url: URL(string: "https://dogenode1.adamant.im")!, altUrl: URL(string: "http://5.9.99.62:44099")), +Node.makeDefaultNode(url: URL(string: "https://dogenode2.adamant.im")!, altUrl: URL(string: "http://176.9.32.126:44098")), ] } diff --git a/Adamant/Modules/Wallets/Doge/DogeWalletService.swift b/Adamant/Modules/Wallets/Doge/DogeWalletService.swift index 3e574ce5e..ebcae6a9a 100644 --- a/Adamant/Modules/Wallets/Doge/DogeWalletService.swift +++ b/Adamant/Modules/Wallets/Doge/DogeWalletService.swift @@ -143,6 +143,10 @@ final class DogeWalletService: WalletCoreProtocol { $hasMoreOldTransactions.eraseToAnyPublisher() } + var hasActiveNode: Bool { + apiService.hasActiveNode + } + private(set) lazy var coinStorage: CoinStorageService = AdamantCoinStorageService( coinId: tokenUnicID, coreDataStack: coreDataStack, diff --git a/Adamant/Modules/Wallets/ERC20/ERC20WalletFactory.swift b/Adamant/Modules/Wallets/ERC20/ERC20WalletFactory.swift index 7b0842dec..2e6874e97 100644 --- a/Adamant/Modules/Wallets/ERC20/ERC20WalletFactory.swift +++ b/Adamant/Modules/Wallets/ERC20/ERC20WalletFactory.swift @@ -48,7 +48,10 @@ struct ERC20WalletFactory: WalletFactory { vibroService: assembler.resolve(VibroService.self)!, walletService: service, reachabilityMonitor: assembler.resolve(ReachabilityMonitor.self)!, - nodesStorage: assembler.resolve(NodesStorageProtocol.self)! + nodesStorage: assembler.resolve(NodesStorageProtocol.self)!, + isActiveAdmNode: { [admApi = assembler.resolve(ApiService.self)!] in + admApi.hasActiveNode + } ) } diff --git a/Adamant/Modules/Wallets/ERC20/ERC20WalletService.swift b/Adamant/Modules/Wallets/ERC20/ERC20WalletService.swift index 852d71488..dbb72c433 100644 --- a/Adamant/Modules/Wallets/ERC20/ERC20WalletService.swift +++ b/Adamant/Modules/Wallets/ERC20/ERC20WalletService.swift @@ -166,6 +166,10 @@ final class ERC20WalletService: WalletCoreProtocol { $hasMoreOldTransactions.eraseToAnyPublisher() } + var hasActiveNode: Bool { + apiService.hasActiveNode + } + private(set) lazy var coinStorage: CoinStorageService = AdamantCoinStorageService( coinId: tokenUnicID, coreDataStack: coreDataStack, diff --git a/Adamant/Modules/Wallets/Ethereum/EthApiService.swift b/Adamant/Modules/Wallets/Ethereum/EthApiService.swift index 928c4dc7b..bd4e490ba 100644 --- a/Adamant/Modules/Wallets/Ethereum/EthApiService.swift +++ b/Adamant/Modules/Wallets/Ethereum/EthApiService.swift @@ -18,8 +18,12 @@ class EthApiService: WalletApiService { get async { await api.service.keystoreManager } } - var preferredNodeIds: [UUID] { - api.preferredNodeIds + var chosenFastestNodeId: UUID? { + api.chosenFastestNodeId + } + + var hasActiveNode: Bool { + !api.sortedAllowedNodes.isEmpty } init(api: BlockchainHealthCheckWrapper) { diff --git a/Adamant/Modules/Wallets/Ethereum/EthWalletFactory.swift b/Adamant/Modules/Wallets/Ethereum/EthWalletFactory.swift index 7bd4efbcf..a9b4c6c5a 100644 --- a/Adamant/Modules/Wallets/Ethereum/EthWalletFactory.swift +++ b/Adamant/Modules/Wallets/Ethereum/EthWalletFactory.swift @@ -48,7 +48,10 @@ struct EthWalletFactory: WalletFactory { vibroService: assembler.resolve(VibroService.self)!, walletService: service, reachabilityMonitor: assembler.resolve(ReachabilityMonitor.self)!, - nodesStorage: assembler.resolve(NodesStorageProtocol.self)! + nodesStorage: assembler.resolve(NodesStorageProtocol.self)!, + isActiveAdmNode: { [admApi = assembler.resolve(ApiService.self)!] in + admApi.hasActiveNode + } ) } diff --git a/Adamant/Modules/Wallets/Ethereum/EthWalletService+DynamicConstants.swift b/Adamant/Modules/Wallets/Ethereum/EthWalletService+DynamicConstants.swift index 113a0f217..f67891869 100644 --- a/Adamant/Modules/Wallets/Ethereum/EthWalletService+DynamicConstants.swift +++ b/Adamant/Modules/Wallets/Ethereum/EthWalletService+DynamicConstants.swift @@ -95,8 +95,8 @@ extension EthWalletService { static var nodes: [Node] { [ - Node(url: URL(string: "https://ethnode2.adamant.im")!, altUrl: URL(string: "http://95.216.114.252:44099")), -Node(url: URL(string: "https://ethnode3.adamant.im")!, altUrl: URL(string: "http://46.4.37.157:44099")), + Node.makeDefaultNode(url: URL(string: "https://ethnode2.adamant.im")!, altUrl: URL(string: "http://95.216.114.252:44099")), +Node.makeDefaultNode(url: URL(string: "https://ethnode3.adamant.im")!, altUrl: URL(string: "http://46.4.37.157:44099")), ] } diff --git a/Adamant/Modules/Wallets/Ethereum/EthWalletService.swift b/Adamant/Modules/Wallets/Ethereum/EthWalletService.swift index e9d74d3c4..4fa134dcf 100644 --- a/Adamant/Modules/Wallets/Ethereum/EthWalletService.swift +++ b/Adamant/Modules/Wallets/Ethereum/EthWalletService.swift @@ -157,6 +157,10 @@ final class EthWalletService: WalletCoreProtocol { $hasMoreOldTransactions.eraseToAnyPublisher() } + var hasActiveNode: Bool { + apiService.hasActiveNode + } + private(set) lazy var coinStorage: CoinStorageService = AdamantCoinStorageService( coinId: tokenUnicID, coreDataStack: coreDataStack, diff --git a/Adamant/Modules/Wallets/Klayr/KLYWalletService+DynamicConstants.swift b/Adamant/Modules/Wallets/Klayr/KLYWalletService+DynamicConstants.swift index b58ec6f05..5a9469fc8 100644 --- a/Adamant/Modules/Wallets/Klayr/KLYWalletService+DynamicConstants.swift +++ b/Adamant/Modules/Wallets/Klayr/KLYWalletService+DynamicConstants.swift @@ -75,15 +75,15 @@ extension KlyWalletService { static var nodes: [Node] { [ - Node(url: URL(string: "https://klynode1.adamant.im")!, altUrl: URL(string: "http://195.26.255.137:44099")), -Node(url: URL(string: "https://klynode2.adamant.im")!, altUrl: URL(string: "http://109.176.199.130:44099")), + Node.makeDefaultNode(url: URL(string: "https://klynode1.adamant.im")!, altUrl: URL(string: "http://195.26.255.137:44099")), +Node.makeDefaultNode(url: URL(string: "https://klynode2.adamant.im")!, altUrl: URL(string: "http://109.176.199.130:44099")), ] } static var serviceNodes: [Node] { [ - Node(url: URL(string: "https://klyservice1.adamant.im")!), -Node(url: URL(string: "https://klyservice2.adamant.im")!), + Node.makeDefaultNode(url: URL(string: "https://klyservice1.adamant.im")!), +Node.makeDefaultNode(url: URL(string: "https://klyservice2.adamant.im")!), ] } } diff --git a/Adamant/Modules/Wallets/Klayr/KlyNodeApiService.swift b/Adamant/Modules/Wallets/Klayr/KlyNodeApiService.swift index 5959d6288..1a29e2970 100644 --- a/Adamant/Modules/Wallets/Klayr/KlyNodeApiService.swift +++ b/Adamant/Modules/Wallets/Klayr/KlyNodeApiService.swift @@ -12,8 +12,12 @@ import Foundation final class KlyNodeApiService: WalletApiService { let api: BlockchainHealthCheckWrapper - var preferredNodeIds: [UUID] { - api.preferredNodeIds + var chosenFastestNodeId: UUID? { + api.chosenFastestNodeId + } + + var hasActiveNode: Bool { + !api.sortedAllowedNodes.isEmpty } init(api: BlockchainHealthCheckWrapper) { diff --git a/Adamant/Modules/Wallets/Klayr/KlyServiceApiService.swift b/Adamant/Modules/Wallets/Klayr/KlyServiceApiService.swift index ebc8e8dff..6300884a8 100644 --- a/Adamant/Modules/Wallets/Klayr/KlyServiceApiService.swift +++ b/Adamant/Modules/Wallets/Klayr/KlyServiceApiService.swift @@ -34,8 +34,12 @@ final class KlyServiceApiCore: KlyApiCore { final class KlyServiceApiService: WalletApiService { let api: BlockchainHealthCheckWrapper - var preferredNodeIds: [UUID] { - api.preferredNodeIds + var chosenFastestNodeId: UUID? { + api.chosenFastestNodeId + } + + var hasActiveNode: Bool { + !api.sortedAllowedNodes.isEmpty } init(api: BlockchainHealthCheckWrapper) { diff --git a/Adamant/Modules/Wallets/Klayr/KlyWalletFactory.swift b/Adamant/Modules/Wallets/Klayr/KlyWalletFactory.swift index cb5f85e33..ce8e5d4de 100644 --- a/Adamant/Modules/Wallets/Klayr/KlyWalletFactory.swift +++ b/Adamant/Modules/Wallets/Klayr/KlyWalletFactory.swift @@ -49,7 +49,10 @@ struct KlyWalletFactory: WalletFactory { vibroService: assembler.resolve(VibroService.self)!, walletService: service, reachabilityMonitor: assembler.resolve(ReachabilityMonitor.self)!, - nodesStorage: assembler.resolve(NodesStorageProtocol.self)! + nodesStorage: assembler.resolve(NodesStorageProtocol.self)!, + isActiveAdmNode: { [admApi = assembler.resolve(ApiService.self)!] in + admApi.hasActiveNode + } ) } diff --git a/Adamant/Modules/Wallets/Klayr/WalletService/KlyWalletService.swift b/Adamant/Modules/Wallets/Klayr/WalletService/KlyWalletService.swift index 82ccbcbca..8075ffe95 100644 --- a/Adamant/Modules/Wallets/Klayr/WalletService/KlyWalletService.swift +++ b/Adamant/Modules/Wallets/Klayr/WalletService/KlyWalletService.swift @@ -32,6 +32,10 @@ final class KlyWalletService: WalletCoreProtocol { static let currencyLogo = UIImage.asset(named: "klayr_wallet") ?? .init() static let kvsAddress = "kly:address" static let defaultFee: BigUInt = 141000 + + var hasActiveNode: Bool { + apiService.hasActiveNode + } @Atomic var transactionFeeRaw: BigUInt = BigUInt(integerLiteral: 141000) diff --git a/Adamant/Modules/Wallets/TransferViewControllerBase.swift b/Adamant/Modules/Wallets/TransferViewControllerBase.swift index 6c5065f5b..250ab6c3e 100644 --- a/Adamant/Modules/Wallets/TransferViewControllerBase.swift +++ b/Adamant/Modules/Wallets/TransferViewControllerBase.swift @@ -188,6 +188,7 @@ class TransferViewControllerBase: FormViewController { let walletCore: WalletCoreProtocol let reachabilityMonitor: ReachabilityMonitor let nodesStorage: NodesStorageProtocol + let isActiveAdmNode: () -> Bool // MARK: - Properties @@ -319,7 +320,8 @@ class TransferViewControllerBase: FormViewController { vibroService: VibroService, walletService: WalletService, reachabilityMonitor: ReachabilityMonitor, - nodesStorage: NodesStorageProtocol + nodesStorage: NodesStorageProtocol, + isActiveAdmNode: @escaping () -> Bool ) { self.accountService = accountService self.accountsProvider = accountsProvider @@ -333,7 +335,7 @@ class TransferViewControllerBase: FormViewController { self.walletCore = walletService.core self.reachabilityMonitor = reachabilityMonitor self.nodesStorage = nodesStorage - + self.isActiveAdmNode = isActiveAdmNode super.init(nibName: nil, bundle: nil) } @@ -800,8 +802,7 @@ class TransferViewControllerBase: FormViewController { return } - if admReportRecipient != nil, - !nodesStorage.haveActiveNode(in: .adm) { + guard isActiveAdmNode() || admReportRecipient == nil else { dialogService.showWarning( withMessage: ApiServiceError.noEndpointsAvailable( coin: NodeGroup.adm.name @@ -810,14 +811,10 @@ class TransferViewControllerBase: FormViewController { return } - let groupsWithoutActiveNode = walletCore.nodeGroups.filter { - !nodesStorage.haveActiveNode(in: $0) - } - - if let group = groupsWithoutActiveNode.first { + guard walletCore.hasActiveNode else { dialogService.showWarning( withMessage: ApiServiceError.noEndpointsAvailable( - coin: group.name + coin: walletCore.tokenName ).localizedDescription ) return diff --git a/Adamant/Modules/Wallets/WalletApiService.swift b/Adamant/Modules/Wallets/WalletApiService.swift index 097854e0b..cc3af7d60 100644 --- a/Adamant/Modules/Wallets/WalletApiService.swift +++ b/Adamant/Modules/Wallets/WalletApiService.swift @@ -9,7 +9,8 @@ import Foundation protocol WalletApiService { - var preferredNodeIds: [UUID] { get } + var chosenFastestNodeId: UUID? { get } + var hasActiveNode: Bool { get } func healthCheck() } diff --git a/Adamant/Modules/Wallets/WalletsService/WalletCoreProtocol.swift b/Adamant/Modules/Wallets/WalletsService/WalletCoreProtocol.swift index a204a8342..37becdc8c 100644 --- a/Adamant/Modules/Wallets/WalletsService/WalletCoreProtocol.swift +++ b/Adamant/Modules/Wallets/WalletsService/WalletCoreProtocol.swift @@ -124,15 +124,6 @@ extension WalletServiceError: HealthCheckableError { } } - var isRequestCancelledError: Bool { - switch self { - case .requestCancelled: - return true - default: - return false - } - } - static func noEndpointsError(coin: String) -> WalletServiceError { .apiError(.noEndpointsError(coin: coin)) } @@ -290,6 +281,7 @@ protocol WalletCoreProtocol: AnyObject { var enabled: Bool { get } // MARK: Logic + var hasActiveNode: Bool { get } func update() // MARK: Tools diff --git a/Adamant/ServiceProtocols/APICoreProtocol.swift b/Adamant/ServiceProtocols/APICoreProtocol.swift index e9e7bd9e9..aac00af71 100644 --- a/Adamant/ServiceProtocols/APICoreProtocol.swift +++ b/Adamant/ServiceProtocols/APICoreProtocol.swift @@ -9,7 +9,6 @@ import Foundation import Alamofire import CommonKit -import UIKit enum ApiCommands {} diff --git a/Adamant/ServiceProtocols/NodesMergingService.swift b/Adamant/ServiceProtocols/NodesMergingService.swift new file mode 100644 index 000000000..91bbe09ed --- /dev/null +++ b/Adamant/ServiceProtocols/NodesMergingService.swift @@ -0,0 +1,16 @@ +// +// NodesMergingService.swift +// Adamant +// +// Created by Andrew G on 02.08.2024. +// Copyright © 2024 Adamant. All rights reserved. +// + +import CommonKit + +protocol NodesMergingService { + func merge( + savedNodes: [NodeGroup: [Node]], + defaultNodes: [NodeGroup: [Node]] + ) -> [NodeGroup: [Node]] +} diff --git a/Adamant/ServiceProtocols/NodesStorageProtocol.swift b/Adamant/ServiceProtocols/NodesStorageProtocol.swift index 380ce364c..b7d4c79e8 100644 --- a/Adamant/ServiceProtocols/NodesStorageProtocol.swift +++ b/Adamant/ServiceProtocols/NodesStorageProtocol.swift @@ -17,12 +17,11 @@ extension StoreKey { } protocol NodesStorageProtocol { - var nodesWithGroupsPublisher: AnyObservable<[NodeWithGroup]> { get } + var nodesPublisher: AnyObservable<[NodeGroup: [Node]]> { get } func getNodesPublisher(group: NodeGroup) -> AnyObservable<[Node]> func addNode(_ node: Node, group: NodeGroup) func resetNodes(group: NodeGroup) - func removeNode(id: UUID) - func haveActiveNode(in group: NodeGroup) -> Bool - func updateNode(id: UUID, mutate: (inout Node) -> Void) + func removeNode(id: UUID, group: NodeGroup) + func updateNode(id: UUID, group: NodeGroup, mutate: (inout Node) -> Void) } diff --git a/Adamant/Services/APICore.swift b/Adamant/Services/APICore.swift index 4c0e74b73..b1a6389c6 100644 --- a/Adamant/Services/APICore.swift +++ b/Adamant/Services/APICore.swift @@ -69,7 +69,7 @@ actor APICore: APICoreProtocol { request.httpBody = data request.headers.update(.contentType("application/json")) - return await sendRequest(request: AF.request(request)) + return await sendRequest(request: session.request(request)) } catch { return .init( result: .failure(.internalError(message: error.localizedDescription, error: error)), @@ -82,15 +82,20 @@ actor APICore: APICoreProtocol { 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 - )) - } - } + await withTaskCancellationHandler( + operation: { + 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 + )) + } + } + }, + onCancel: { request.cancel() } + ) } func buildUrl(origin: NodeOrigin, path: String) throws -> URL { diff --git a/Adamant/Services/AdamantNodesMergingService.swift b/Adamant/Services/AdamantNodesMergingService.swift new file mode 100644 index 000000000..ac71ebd46 --- /dev/null +++ b/Adamant/Services/AdamantNodesMergingService.swift @@ -0,0 +1,88 @@ +// +// AdamantNodesMergingService.swift +// Adamant +// +// Created by Andrew G on 02.08.2024. +// Copyright © 2024 Adamant. All rights reserved. +// + +import CommonKit + +struct AdamantNodesMergingService: NodesMergingService { + func merge( + savedNodes: [NodeGroup: [Node]], + defaultNodes: [NodeGroup: [Node]] + ) -> [NodeGroup: [Node]] { + var resultNodes = savedNodes + + defaultNodes.keys.forEach { group in + guard resultNodes[group] == nil else { return } + resultNodes[group] = .init() + } + + resultNodes.forEach { group, nodes in + guard let defaultNodes = defaultNodes[group] else { return } + resultNodes[group] = merge(savedNodes: nodes, defaultNodes: defaultNodes) + } + + return resultNodes + } +} + +private extension AdamantNodesMergingService { + func merge(savedNodes: [Node], defaultNodes: [Node]) -> [Node] { + var resultNodes = savedNodes + var defaultNodes = defaultNodes + var removedNodesIndexes: [Int] = .init() + + resultNodes.enumerated().forEach { index, node in + switch node.type { + case .default: + let defaultNodeIndex = defaultNodes.firstIndex { $0.isSame(node) } + + if let defaultNodeIndex = defaultNodeIndex { + resultNodes[index].merge(defaultNodes[defaultNodeIndex]) + defaultNodes.remove(at: defaultNodeIndex) + } else { + removedNodesIndexes.append(index) + } + case .custom: + break + } + } + + removedNodesIndexes.reversed().forEach { + resultNodes.remove(at: $0) + } + + resultNodes.append(contentsOf: defaultNodes) + return resultNodes + } +} + +private extension Node { + mutating func merge(_ node: Node) { + mainOrigin.merge(node.mainOrigin) + + guard let mergedAltOrigin = node.altOrigin else { + altOrigin = nil + return + } + + guard altOrigin != nil else { + altOrigin = mergedAltOrigin + return + } + + altOrigin?.merge(mergedAltOrigin) + } +} + +private extension NodeOrigin { + mutating func merge(_ origin: NodeOrigin) { + scheme = origin.scheme + host = origin.host + port = origin.port + origin.wsPort.map { wsPort = $0 } + } +} diff --git a/Adamant/Services/ApiService/AdamantApiService.swift b/Adamant/Services/ApiService/AdamantApiService.swift index 605d10b69..87d8ad978 100644 --- a/Adamant/Services/ApiService/AdamantApiService.swift +++ b/Adamant/Services/ApiService/AdamantApiService.swift @@ -31,11 +31,15 @@ final class AdamantApiService { } extension AdamantApiService: ApiService { - var preferredNodeIds: [UUID] { - service.preferredNodeIds + var chosenFastestNodeId: UUID? { + service.chosenFastestNodeId } func healthCheck() { service.healthCheck() } + + var hasActiveNode: Bool { + !service.sortedAllowedNodes.isEmpty + } } diff --git a/Adamant/Services/BlockchainHealthCheckWrapper.swift b/Adamant/Services/BlockchainHealthCheckWrapper.swift index 7ba1b6897..89c952a8a 100644 --- a/Adamant/Services/BlockchainHealthCheckWrapper.swift +++ b/Adamant/Services/BlockchainHealthCheckWrapper.swift @@ -99,7 +99,7 @@ private extension BlockchainHealthCheckWrapper { markAsOfflineIfFailed: false ) { case .success: - nodesStorage.updateNode(id: node.id) { $0.preferMainOrigin = true } + nodesStorage.updateNode(id: node.id, group: nodeGroup) { $0.preferMainOrigin = true } forceInclude = node.id case .failure: switch await updateNodeStatusInfo( @@ -108,7 +108,7 @@ private extension BlockchainHealthCheckWrapper { markAsOfflineIfFailed: true ) { case .success: - nodesStorage.updateNode(id: node.id) { $0.preferMainOrigin = false } + nodesStorage.updateNode(id: node.id, group: nodeGroup) { $0.preferMainOrigin = false } forceInclude = node.id case .failure, .none: break @@ -129,10 +129,8 @@ private extension BlockchainHealthCheckWrapper { applyStatusInfo(id: id, info: info) return .success(()) case let .failure(error): - guard !error.isRequestCancelledError else { return nil } - if markAsOfflineIfFailed { - nodesStorage.updateNode(id: id) { $0.connectionStatus = .offline } + nodesStorage.updateNode(id: id, group: nodeGroup) { $0.connectionStatus = .offline } } return .failure(error) @@ -140,7 +138,7 @@ private extension BlockchainHealthCheckWrapper { } func applyStatusInfo(id: UUID, info: NodeStatusInfo) { - nodesStorage.updateNode(id: id) { node in + nodesStorage.updateNode(id: id, group: nodeGroup) { node in node.wsEnabled = info.wsEnabled node.updateWsPort(info.wsPort) node.version = info.version @@ -184,7 +182,7 @@ private extension BlockchainHealthCheckWrapper { } ?? .none } - nodesStorage.updateNode(id: node.id) { $0.connectionStatus = status } + nodesStorage.updateNode(id: node.id, group: nodeGroup) { $0.connectionStatus = status } } } } diff --git a/Adamant/Services/HealthCheckWrapper.swift b/Adamant/Services/HealthCheckWrapper.swift index 571fb2d8d..5b64b3d04 100644 --- a/Adamant/Services/HealthCheckWrapper.swift +++ b/Adamant/Services/HealthCheckWrapper.swift @@ -13,7 +13,6 @@ import UIKit protocol HealthCheckableError: Error { var isNetworkError: Bool { get } - var isRequestCancelledError: Bool { get } static func noEndpointsError(coin: String) -> Self } @@ -33,11 +32,11 @@ class HealthCheckWrapper { @Atomic private var previousAppState: UIApplication.State? @Atomic private var lastUpdateTime = Date() - @ObservableValue private var allowedNodes: [Node] = .init() + @ObservableValue private(set) var sortedAllowedNodes: [Node] = .init() var preferredNodeIds: [UUID] { fastestNodeMode - ? [allowedNodes.first?.id].compactMap { $0 } + ? [sortedAllowedNodes.first?.id].compactMap { $0 } : [] } @@ -60,14 +59,14 @@ class HealthCheckWrapper { $nodes .removeDuplicates() .sink { [weak self] in - self?.allowedNodes = $0.getAllowedNodes( + self?.sortedAllowedNodes = $0.getAllowedNodes( sortedBySpeedDescending: true, needWS: false ) } .store(in: &subscriptions) - $allowedNodes + $sortedAllowedNodes .map { $0.isEmpty } .removeDuplicates() .sink { [weak self] _ in self?.updateHealthCheckTimerSubscription() } @@ -97,13 +96,13 @@ class HealthCheckWrapper { func request( _ request: @Sendable (Service, NodeOrigin) async -> Result ) async -> Result { - var lastConnectionError = allowedNodes.isEmpty - ? Error.noEndpointsError(coin: nodeGroup.name) - : nil + var lastConnectionError = sortedAllowedNodes.isEmpty + ? Error.noEndpointsError(coin: nodeGroup.name) + : nil let nodesList = fastestNodeMode - ? allowedNodes - : allowedNodes.shuffled() + ? sortedAllowedNodes + : sortedAllowedNodes.shuffled() for node in nodesList { let response = await request(service, node.preferredOrigin) @@ -129,7 +128,7 @@ class HealthCheckWrapper { private extension HealthCheckWrapper { func updateHealthCheckTimerSubscription() { healthCheckTimerSubscription = Timer.publish( - every: allowedNodes.isEmpty + every: sortedAllowedNodes.isEmpty ? crucialUpdateInterval : normalUpdateInterval, on: .main, diff --git a/Adamant/Services/NodesStorage.swift b/Adamant/Services/NodesStorage.swift index ce18b8134..07d9abb4b 100644 --- a/Adamant/Services/NodesStorage.swift +++ b/Adamant/Services/NodesStorage.swift @@ -11,129 +11,169 @@ import Foundation import Combine final class NodesStorage: NodesStorageProtocol { - @Atomic private var items: ObservableValue<[NodeWithGroup]> + @Atomic private var items: ObservableValue<[NodeGroup: [Node]]> = .init(wrappedValue: .init()) - var nodesWithGroupsPublisher: AnyObservable<[NodeWithGroup]> { - items.removeDuplicates().eraseToAnyPublisher() + var nodesPublisher: AnyObservable<[NodeGroup: [Node]]> { + items + .map { $0.mapValues { $0.filter { !$0.isHidden } } } + .removeDuplicates() + .eraseToAnyPublisher() } private var subscription: AnyCancellable? private let securedStore: SecuredStore + private let nodesMergingService: NodesMergingService func getNodesPublisher(group: NodeGroup) -> AnyObservable<[Node]> { - items - .map { $0.filter { $0.group == group }.map { $0.node } } + nodesPublisher + .map { $0[group] ?? .init() } .removeDuplicates() .eraseToAnyPublisher() } func addNode(_ node: Node, group: NodeGroup) { - items.wrappedValue.append(.init(group: group, node: node)) + $items.mutate { items in + if items.wrappedValue[group] == nil { + items.wrappedValue[group] = [node] + } else { + items.wrappedValue[group]?.append(node) + } + } } - func removeNode(id: UUID) { + func removeNode(id: UUID, group: NodeGroup) { $items.mutate { items in - guard let index = items.wrappedValue.getIndex(id: id) else { return } - items.wrappedValue.remove(at: index) + guard + let index = items.wrappedValue[group]?.firstIndex(where: { $0.id == id }) + else { return } + + switch items.wrappedValue[group]?[safe: index]?.type { + case .default: + items.wrappedValue[group]?[index].type = .default(isHidden: true) + case .custom: + items.wrappedValue[group]?.remove(at: index) + case .none: + break + } } } - func updateNode(id: UUID, mutate: (inout Node) -> Void) { + func updateNode(id: UUID, group: NodeGroup, mutate: (inout Node) -> Void) { $items.mutate { items in guard - let index = items.wrappedValue.getIndex(id: id), - var node = items.wrappedValue[safe: index]?.node + let index = items.wrappedValue[group]?.firstIndex(where: { $0.id == id }), + var node = items.wrappedValue[group]?[safe: index] else { return } let previousValue = node mutate(&node) - guard node != previousValue else { return } - items.wrappedValue[index].node = node - } - } - - func resetNodes(group: NodeGroup) { - $items.mutate { items in - items.wrappedValue = items.wrappedValue.filter { - $0.group != group + if !node.isEnabled { + node.connectionStatus = nil + } + + switch node.type { + case .default: + guard !previousValue.isSame(node) else { break } + node.type = .custom + case .custom: + break } - items.wrappedValue += Self.defaultItems(group: group) + guard node != previousValue else { return } + items.wrappedValue[group]?[index] = node } } - func haveActiveNode(in group: CommonKit.NodeGroup) -> Bool { - let nodes = items.wrappedValue.filter { $0.group == group }.map { $0.node } - let node = nodes.first(where: { $0.connectionStatus == .allowed && $0.isEnabled }) - return node != nil + func resetNodes(group: NodeGroup) { + items.wrappedValue[group] = Self.defaultItems(group: group) } - init(securedStore: SecuredStore) { + init(securedStore: SecuredStore, nodesMergingService: NodesMergingService) { self.securedStore = securedStore - var nodesDto: [NodeWithGroupDTO]? = securedStore.get(StoreKey.NodesStorage.nodes) - - if nodesDto == nil { - let oldNodesDto: SafeDecodingArray? = securedStore.get( - StoreKey.NodesStorage.nodes - ) - - nodesDto = oldNodesDto?.values.map { $0.mapToModernDto() } - } - - var nodes = nodesDto.map { $0.map { $0.mapToModel() } } ?? Self.defaultItems - let nodesToAdd = Self.defaultItems.filter { defaultNode in - !nodes.contains { $0.node.mainOrigin.host == defaultNode.node.mainOrigin.host } - } - nodes.append(contentsOf: nodesToAdd) - - _items = .init(wrappedValue: .init( - wrappedValue: nodes - )) - - subscription = items.removeDuplicates().sink { [weak self] in - guard let self = self, subscription != nil else { return } - saveNodes(nodes: $0) - } + self.nodesMergingService = nodesMergingService + setupNodes() } } private extension NodesStorage { - static func defaultItems(group: NodeGroup) -> [NodeWithGroup] { + static func defaultItems(group: NodeGroup) -> [Node] { switch group { case .btc: - return BtcWalletService.nodes.map { .init(group: .btc, node: $0) } + return BtcWalletService.nodes case .eth: - return EthWalletService.nodes.map { .init(group: .eth, node: $0) } + return EthWalletService.nodes case .klyNode: - return KlyWalletService.nodes.map { .init(group: .klyNode, node: $0) } + return KlyWalletService.nodes case .klyService: - return KlyWalletService.serviceNodes.map { .init(group: .klyService, node: $0) } + return KlyWalletService.serviceNodes case .doge: - return DogeWalletService.nodes.map { .init(group: .doge, node: $0) } + return DogeWalletService.nodes case .dash: - return DashWalletService.nodes.map { .init(group: .dash, node: $0) } + return DashWalletService.nodes case .adm: - return AdmWalletService.nodes.map { .init(group: .adm, node: $0) } + return AdmWalletService.nodes } } - static var defaultItems: [NodeWithGroup] { - NodeGroup.allCases.flatMap { Self.defaultItems(group: $0) } + static var defaultItems: [NodeGroup: [Node]] { + .init( + uniqueKeysWithValues: NodeGroup.allCases.map { + ($0, defaultItems(group: $0)) + } + ) } - func saveNodes(nodes: [NodeWithGroup]) { - let nodesDto = nodes.map { $0.mapToDto() } + func saveNodes(nodes: [NodeGroup: [Node]]) { + let nodesDto = nodes.mapValues { $0.map { $0.mapToDto() } } securedStore.set(nodesDto, for: StoreKey.NodesStorage.nodes) } -} - -private extension Array where Element == NodeWithGroup { - func getNode(id: UUID) -> Node? { - first { $0.node.id == id }?.node + + func setupNodes() { + let dto: SafeDecodingDictionary< + NodeGroup, + SafeDecodingArray + >? = securedStore.get(StoreKey.NodesStorage.nodes) + + let savedNodes = dto?.values.mapValues { $0.map { $0.mapToModel() } } + ?? migrateOldNodesData() + ?? .init() + + items.wrappedValue = nodesMergingService.merge( + savedNodes: savedNodes, + defaultNodes: Self.defaultItems + ) + + subscription = items.removeDuplicates().sink { [weak self] in + guard let self = self else { return } + saveNodes(nodes: $0) + } } - func getIndex(id: UUID) -> Int? { - firstIndex { $0.node.id == id } + func migrateOldNodesData() -> [NodeGroup: [Node]]? { + let dto: SafeDecodingArray? = securedStore.get(StoreKey.NodesStorage.nodes) + guard let dto = dto else { return nil } + var result: [NodeGroup: [Node]] = [:] + + dto.forEach { + if result[$0.group] == nil { + result[$0.group] = [] + } + + result[$0.group]?.append($0.node.mapToModernDto().mapToModel()) + } + + return result + } +} + +private extension Node { + var isHidden: Bool { + switch type { + case let .default(isHidden): + return isHidden + case .custom: + return false + } } } diff --git a/Adamant/Services/RichTransactionStatusService/AdamantTransactionStatusService.swift b/Adamant/Services/RichTransactionStatusService/AdamantTransactionStatusService.swift index 58e8aba73..0e3a04af9 100644 --- a/Adamant/Services/RichTransactionStatusService/AdamantTransactionStatusService.swift +++ b/Adamant/Services/RichTransactionStatusService/AdamantTransactionStatusService.swift @@ -86,8 +86,8 @@ private extension AdamantTransactionStatusService { func makeNodesAvailabilitySubscription() -> some Observable<[UUID]> { nodesStorage - .nodesWithGroupsPublisher - .map { $0.compactMap { $0.node.isEnabled ? $0.node.id : nil } } + .nodesPublisher + .map { $0.values.flatMap { $0.compactMap { $0.isEnabled ? $0.id : nil } } } .removeDuplicates() } diff --git a/CommonKit/Scripts/CoinsScript.rb b/CommonKit/Scripts/CoinsScript.rb index d8c4b4539..27f2caec8 100755 --- a/CommonKit/Scripts/CoinsScript.rb +++ b/CommonKit/Scripts/CoinsScript.rb @@ -90,9 +90,9 @@ def writeToSwiftFile(name, json) url = node["url"] altUrl = node["alt_ip"] if altUrl == nil - nodes += "Node(url: URL(string: \"#{url}\")!),\n" + nodes += "Node.makeDefaultNode(url: URL(string: \"#{url}\")!),\n" else - nodes += "Node(url: URL(string: \"#{url}\")!, altUrl: URL(string: \"#{altUrl}\")),\n" + nodes += "Node.makeDefaultNode(url: URL(string: \"#{url}\")!, altUrl: URL(string: \"#{altUrl}\")),\n" end end end @@ -106,7 +106,7 @@ def writeToSwiftFile(name, json) if serviceNodesArray != nil serviceNodesArray.each do |node| url = node["url"] - serviceNodes += "Node(url: URL(string: \"#{url}\")!),\n" + serviceNodes += "Node.makeDefaultNode(url: URL(string: \"#{url}\")!),\n" end end end diff --git a/CommonKit/Sources/CommonKit/AdamantDynamicResources.swift b/CommonKit/Sources/CommonKit/AdamantDynamicResources.swift index 5b46f2aa1..763eae145 100644 --- a/CommonKit/Sources/CommonKit/AdamantDynamicResources.swift +++ b/CommonKit/Sources/CommonKit/AdamantDynamicResources.swift @@ -4,22 +4,22 @@ public extension AdamantResources { // MARK: Nodes 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")!, 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")!, 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")!), -Node(url: URL(string: "https://node1.adamant.business")!, altUrl: URL(string: "http://194.233.75.29:45555")), -Node(url: URL(string: "https://node2.blockchain2fa.io")!), -Node(url: URL(string: "https://phecda.adm.im")!, altUrl: URL(string: "http://46.250.234.248:36666")), -Node(url: URL(string: "https://tegmine.adm.im")!), -Node(url: URL(string: "https://tauri.adm.im")!, altUrl: URL(string: "http://154.26.159.245:36666")), -Node(url: URL(string: "https://dschubba.adm.im")!), + Node.makeDefaultNode(url: URL(string: "https://clown.adamant.im")!), + Node.makeDefaultNode(url: URL(string: "https://lake.adamant.im")!), +Node.makeDefaultNode(url: URL(string: "https://endless.adamant.im")!, altUrl: URL(string: "http://149.102.157.15:36666")), +Node.makeDefaultNode(url: URL(string: "https://bid.adamant.im")!), +Node.makeDefaultNode(url: URL(string: "https://unusual.adamant.im")!), +Node.makeDefaultNode(url: URL(string: "https://debate.adamant.im")!, altUrl: URL(string: "http://95.216.161.113:36666")), +Node.makeDefaultNode(url: URL(string: "http://78.47.205.206:36666")!), +Node.makeDefaultNode(url: URL(string: "http://5.161.53.74:36666")!), +Node.makeDefaultNode(url: URL(string: "http://184.94.215.92:45555")!), +Node.makeDefaultNode(url: URL(string: "https://node1.adamant.business")!, altUrl: URL(string: "http://194.233.75.29:45555")), +Node.makeDefaultNode(url: URL(string: "https://node2.blockchain2fa.io")!), +Node.makeDefaultNode(url: URL(string: "https://phecda.adm.im")!, altUrl: URL(string: "http://46.250.234.248:36666")), +Node.makeDefaultNode(url: URL(string: "https://tegmine.adm.im")!), +Node.makeDefaultNode(url: URL(string: "https://tauri.adm.im")!, altUrl: URL(string: "http://154.26.159.245:36666")), +Node.makeDefaultNode(url: URL(string: "https://dschubba.adm.im")!), ] } -} \ No newline at end of file +} diff --git a/CommonKit/Sources/CommonKit/ExtensionsTools/ExtensionsApi.swift b/CommonKit/Sources/CommonKit/ExtensionsTools/ExtensionsApi.swift index 4c07a8ae7..dac73decd 100644 --- a/CommonKit/Sources/CommonKit/ExtensionsTools/ExtensionsApi.swift +++ b/CommonKit/Sources/CommonKit/ExtensionsTools/ExtensionsApi.swift @@ -15,7 +15,7 @@ public final class ExtensionsApi { public let keychainStore: KeychainStore public private(set) lazy var nodes: [Node] = { - let nodesDto: [NodeDTO]? = keychainStore.get(nodesStoreKey) + let nodesDto: [NodeKeychainDTO]? = keychainStore.get(nodesStoreKey) let nodes = nodesDto.map { $0.map { $0.mapToModel() } } ?? AdamantResources.nodes diff --git a/CommonKit/Sources/CommonKit/Helpers/Node+NodeDTO.swift b/CommonKit/Sources/CommonKit/Helpers/Node+NodeKeychainDTO.swift similarity index 77% rename from CommonKit/Sources/CommonKit/Helpers/Node+NodeDTO.swift rename to CommonKit/Sources/CommonKit/Helpers/Node+NodeKeychainDTO.swift index 62279fbc5..214f3fa83 100644 --- a/CommonKit/Sources/CommonKit/Helpers/Node+NodeDTO.swift +++ b/CommonKit/Sources/CommonKit/Helpers/Node+NodeKeychainDTO.swift @@ -6,7 +6,7 @@ // public extension Node { - func mapToDto() -> NodeDTO { + func mapToDto() -> NodeKeychainDTO { .init( mainOrigin: mainOrigin, altOrigin: altOrigin, @@ -15,12 +15,13 @@ public extension Node { version: version, height: height, ping: ping, - connectionStatus: connectionStatus + connectionStatus: connectionStatus, + type: type ) } } -public extension NodeDTO { +public extension NodeKeychainDTO { func mapToModel() -> Node { .init( id: .init(), @@ -32,7 +33,8 @@ public extension NodeDTO { height: height, ping: ping, connectionStatus: connectionStatus, - preferMainOrigin: nil + preferMainOrigin: nil, + type: type ) } } diff --git a/CommonKit/Sources/CommonKit/Helpers/SafeDecodingArray.swift b/CommonKit/Sources/CommonKit/Helpers/SafeDecodingArray.swift index 8bb5c328b..877cbd026 100644 --- a/CommonKit/Sources/CommonKit/Helpers/SafeDecodingArray.swift +++ b/CommonKit/Sources/CommonKit/Helpers/SafeDecodingArray.swift @@ -13,10 +13,19 @@ public struct SafeDecodingArray { } } +extension SafeDecodingArray: Sequence { + public typealias Element = T + public typealias Iterator = IndexingIterator> + + public func makeIterator() -> Iterator { + values.makeIterator() + } +} + extension SafeDecodingArray: Encodable { public func encode(to encoder: Encoder) throws { var container = encoder.singleValueContainer() - try container.encode(self.values) + try container.encode(values) } } diff --git a/CommonKit/Sources/CommonKit/Helpers/SafeDecodingDictionary.swift b/CommonKit/Sources/CommonKit/Helpers/SafeDecodingDictionary.swift new file mode 100644 index 000000000..4b7dcf028 --- /dev/null +++ b/CommonKit/Sources/CommonKit/Helpers/SafeDecodingDictionary.swift @@ -0,0 +1,61 @@ +// +// SafeDecodingDictionary.swift +// +// +// Created by Andrew G on 02.08.2024. +// + +public struct SafeDecodingDictionary { + public let values: [Key: Value] + + init(_ values: [Key: Value]) { + self.values = values + } +} + +extension SafeDecodingDictionary: Encodable { + public func encode(to encoder: Encoder) throws { + var container = encoder.singleValueContainer() + try container.encode(values) + } +} + +extension SafeDecodingDictionary: Decodable { + struct KeyItem: Hashable { + let value: T? + } + + struct ValueItem { + let value: T? + } + + public init(from decoder: Decoder) throws { + let container = try decoder.singleValueContainer() + let items = try container.decode([KeyItem: ValueItem].self) + + let keysAndValues: [(Key, Value)] = items.compactMap { keyItem, valueItem in + guard + let key = keyItem.value, + let value = valueItem.value + else { return nil } + + return (key, value) + } + + values = .init(uniqueKeysWithValues: keysAndValues) + } +} + +extension SafeDecodingDictionary.KeyItem: Decodable { + public init(from decoder: Decoder) throws { + let container = try decoder.singleValueContainer() + value = try? container.decode(T.self) + } +} + +extension SafeDecodingDictionary.ValueItem: Decodable { + public init(from decoder: Decoder) throws { + let container = try decoder.singleValueContainer() + value = try? container.decode(T.self) + } +} diff --git a/CommonKit/Sources/CommonKit/Models/Keychain/NodeDTO.swift b/CommonKit/Sources/CommonKit/Models/Keychain/NodeKeychainDTO.swift similarity index 78% rename from CommonKit/Sources/CommonKit/Models/Keychain/NodeDTO.swift rename to CommonKit/Sources/CommonKit/Models/Keychain/NodeKeychainDTO.swift index 5371bec64..d5cde63c0 100644 --- a/CommonKit/Sources/CommonKit/Models/Keychain/NodeDTO.swift +++ b/CommonKit/Sources/CommonKit/Models/Keychain/NodeKeychainDTO.swift @@ -1,5 +1,5 @@ // -// NodeDTO.swift +// NodeKeychainDTO.swift // // // Created by Andrew G on 28.07.2024. @@ -7,7 +7,7 @@ import Foundation -public struct NodeDTO: Codable { +public struct NodeKeychainDTO: Codable { public let mainOrigin: NodeOrigin public let altOrigin: NodeOrigin? public let wsEnabled: Bool @@ -16,4 +16,5 @@ public struct NodeDTO: Codable { public let height: Int? public let ping: TimeInterval? public let connectionStatus: NodeConnectionStatus? + public let type: NodeType } diff --git a/CommonKit/Sources/CommonKit/Models/Keychain/OldNodeDTO.swift b/CommonKit/Sources/CommonKit/Models/Keychain/OldNodeDTO.swift deleted file mode 100644 index b4583549a..000000000 --- a/CommonKit/Sources/CommonKit/Models/Keychain/OldNodeDTO.swift +++ /dev/null @@ -1,94 +0,0 @@ -// -// OldNodeDTO.swift -// -// -// Created by Andrew G on 30.07.2024. -// - -import Foundation - -// TODO: Remove after a few updates (it's used for migration) -public struct OldNodeDTO: Codable { - public let id: UUID - public let scheme: URLScheme - public let host: String - public let isEnabled: Bool - public let wsEnabled: Bool - public let port: Int? - public let wsPort: Int? - public let version: String? - public let height: Int? - public let ping: TimeInterval? - public let connectionStatus: ConnectionStatus? -} - -public extension OldNodeDTO { - enum RejectedReason: Codable, Equatable { - case outdatedApiVersion - } - - enum ConnectionStatus: Equatable, Codable { - case offline - case synchronizing - case allowed - case notAllowed(RejectedReason) - } - - enum URLScheme: String, Codable { - case http - case https - } - - func mapToModernDto() -> NodeDTO { - .init( - mainOrigin: .init( - scheme: scheme.map(), - host: host, - port: port, - wsPort: wsPort - ), - altOrigin: nil, - wsEnabled: wsEnabled, - isEnabled: isEnabled, - version: version, - height: height, - ping: ping, - connectionStatus: connectionStatus?.map() - ) - } -} - -private extension OldNodeDTO.URLScheme { - func map() -> NodeOrigin.URLScheme { - switch self { - case .http: - return .http - case .https: - return .https - } - } -} - -private extension OldNodeDTO.ConnectionStatus { - func map() -> NodeConnectionStatus { - switch self { - case .offline: - return .offline - case .synchronizing: - return .synchronizing - case .allowed: - return .allowed - case let .notAllowed(reason): - return .notAllowed(reason.map()) - } - } -} - -private extension OldNodeDTO.RejectedReason { - func map() -> NodeConnectionStatus.RejectedReason { - switch self { - case .outdatedApiVersion: - return .outdatedApiVersion - } - } -} diff --git a/CommonKit/Sources/CommonKit/Models/Keychain/OldNodeKeychainDTO.swift b/CommonKit/Sources/CommonKit/Models/Keychain/OldNodeKeychainDTO.swift new file mode 100644 index 000000000..dc1950455 --- /dev/null +++ b/CommonKit/Sources/CommonKit/Models/Keychain/OldNodeKeychainDTO.swift @@ -0,0 +1,134 @@ +// +// OldNodeKeychainDTO.swift +// +// +// Created by Andrew G on 01.08.2024. +// + +import Foundation + +// TODO: delete after a few updates. It's used for migration. +public struct OldNodeKeychainDTO: Codable { + public let group: NodeGroup + public let node: NodeData +} + +public extension OldNodeKeychainDTO { + struct NodeData: Codable { + public let id: UUID + public let scheme: URLScheme + public let host: String + public let isEnabled: Bool + public let wsEnabled: Bool + public let port: Int? + public let wsPort: Int? + public let version: String? + public let height: Int? + public let ping: TimeInterval? + public let connectionStatus: ConnectionStatus? + } +} + +public extension OldNodeKeychainDTO.NodeData { + enum RejectedReason: Codable, Equatable { + case outdatedApiVersion + } + + enum ConnectionStatus: Equatable, Codable { + case offline + case synchronizing + case allowed + case notAllowed(RejectedReason) + } + + enum URLScheme: String, Codable { + case http + case https + } + + func mapToModernDto() -> NodeKeychainDTO { + .init( + mainOrigin: .init( + scheme: scheme.map(), + host: host, + port: port, + wsPort: wsPort + ), + altOrigin: nil, + wsEnabled: wsEnabled, + isEnabled: isEnabled, + version: version, + height: height, + ping: ping, + connectionStatus: connectionStatus?.map(), + type: oldDefaultHosts.contains(host) + ? .default(isHidden: false) + : .custom + ) + } +} + +private extension OldNodeKeychainDTO.NodeData.URLScheme { + func map() -> NodeOrigin.URLScheme { + switch self { + case .http: + return .http + case .https: + return .https + } + } +} + +private extension OldNodeKeychainDTO.NodeData.ConnectionStatus { + func map() -> NodeConnectionStatus { + switch self { + case .offline: + return .offline + case .synchronizing: + return .synchronizing + case .allowed: + return .allowed + case let .notAllowed(reason): + return .notAllowed(reason.map()) + } + } +} + +private extension OldNodeKeychainDTO.NodeData.RejectedReason { + func map() -> NodeConnectionStatus.RejectedReason { + switch self { + case .outdatedApiVersion: + return .outdatedApiVersion + } + } +} + +private let oldDefaultHosts: [String] = [ + "btcnode1.adamant.im", + "btcnode3.adamant.im", + "ethnode2.adamant.im", + "ethnode3.adamant.im", + "klyservice1.adamant.im", + "klyservice2.adamant.im", + "dogenode1.adamant.im", + "dogenode2.adamant.im", + "dashnode1.adamant.im", + "dashnode2.adamant.im", + "clown.adamant.im", + "lake.adamant.im", + "endless.adamant.im", + "bid.adamant.im", + "unusual.adamant.im", + "debate.adamant.im", + "78.47.205.206", + "5.161.53.74", + "184.94.215.92", + "node1.adamant.business", + "node2.blockchain2fa.io", + "phecda.adm.im", + "tegmine.adm.im", + "tauri.adm.im", + "dschubba.adm.im", + "klynode1.adamant.im", + "klynode2.adamant.im" +] diff --git a/CommonKit/Sources/CommonKit/Models/Node.swift b/CommonKit/Sources/CommonKit/Models/Node.swift index 182753a54..a2b6398a8 100644 --- a/CommonKit/Sources/CommonKit/Models/Node.swift +++ b/CommonKit/Sources/CommonKit/Models/Node.swift @@ -5,6 +5,8 @@ // Created by Anokhov Pavel on 20.06.2018. // Copyright © 2018 Adamant. All rights reserved. // +// Check AdamantNodesMergingService when the structure is changed +// import Foundation @@ -18,13 +20,8 @@ public struct Node: Equatable, Identifiable { public var ping: TimeInterval? public var connectionStatus: NodeConnectionStatus? public var preferMainOrigin: Bool? - - public var isEnabled: Bool { - didSet { - guard !isEnabled else { return } - connectionStatus = nil - } - } + public var isEnabled: Bool + public var type: NodeType public init( id: UUID, @@ -36,7 +33,8 @@ public struct Node: Equatable, Identifiable { height: Int?, ping: TimeInterval?, connectionStatus: NodeConnectionStatus?, - preferMainOrigin: Bool? + preferMainOrigin: Bool?, + type: NodeType ) { self.id = id self.mainOrigin = mainOrigin @@ -48,6 +46,7 @@ public struct Node: Equatable, Identifiable { self.ping = ping self.connectionStatus = connectionStatus self.preferMainOrigin = preferMainOrigin + self.type = type } } @@ -57,9 +56,9 @@ public extension Node { ? mainOrigin : altOrigin ?? mainOrigin } - - init(url: URL, altUrl: URL? = nil) { - self.init( + + static func makeDefaultNode(url: URL, altUrl: URL? = nil) -> Self { + .init( id: .init(), isEnabled: true, wsEnabled: false, @@ -69,7 +68,8 @@ public extension Node { height: nil, ping: nil, connectionStatus: nil, - preferMainOrigin: nil + preferMainOrigin: nil, + type: .default(isHidden: false) ) } @@ -85,6 +85,10 @@ public extension Node { preferredOrigin.asURL() } + func isSame(_ node: Node) -> Bool { + mainOrigin.host == node.mainOrigin.host + } + mutating func updateWsPort(_ wsPort: Int?) { mainOrigin.wsPort = wsPort altOrigin?.wsPort = wsPort diff --git a/CommonKit/Sources/CommonKit/Models/NodeType.swift b/CommonKit/Sources/CommonKit/Models/NodeType.swift new file mode 100644 index 000000000..c809b33a1 --- /dev/null +++ b/CommonKit/Sources/CommonKit/Models/NodeType.swift @@ -0,0 +1,11 @@ +// +// NodeType.swift +// +// +// Created by Andrew G on 01.08.2024. +// + +public enum NodeType: Codable, Equatable { + case custom + case `default`(isHidden: Bool) +} From 4ac2d273cdf9c2573f99dd881c67a73c49ef4d31 Mon Sep 17 00:00:00 2001 From: StanislavDevIOS Date: Mon, 5 Aug 2024 16:07:37 +0300 Subject: [PATCH 023/106] [trello.com/c/XBkkMhlC] Feat: Upgrade keychain security --- Adamant/App/AppDelegate.swift | 5 +- .../Helpers/AdamantSecureStorage.swift | 96 ++++++++++ .../Helpers/SecureStorageProtocol.swift | 15 ++ .../CommonKit/Services/KeychainStore.swift | 165 +++++++++++++----- 4 files changed, 236 insertions(+), 45 deletions(-) create mode 100644 CommonKit/Sources/CommonKit/Helpers/AdamantSecureStorage.swift create mode 100644 CommonKit/Sources/CommonKit/Helpers/SecureStorageProtocol.swift diff --git a/Adamant/App/AppDelegate.swift b/Adamant/App/AppDelegate.swift index 64b21f67a..f195015d4 100644 --- a/Adamant/App/AppDelegate.swift +++ b/Adamant/App/AppDelegate.swift @@ -61,10 +61,7 @@ class AppDelegate: UIResponder, UIApplicationDelegate { // MARK: - Lifecycle - func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { - // MARK: 0. Migrate keychain if needed - KeychainStore.migrateIfNeeded() - + func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { // MARK: 1. Initiating Swinject container = AppContainer() screensFactory = AdamantScreensFactory(assembler: container.assembler) diff --git a/CommonKit/Sources/CommonKit/Helpers/AdamantSecureStorage.swift b/CommonKit/Sources/CommonKit/Helpers/AdamantSecureStorage.swift new file mode 100644 index 000000000..e04df3237 --- /dev/null +++ b/CommonKit/Sources/CommonKit/Helpers/AdamantSecureStorage.swift @@ -0,0 +1,96 @@ +// +// AdamantSecureStorage.swift +// +// +// Created by Stanislav Jelezoglo on 02.08.2024. +// + +import Foundation + +final class AdamantSecureStorage: SecureStorageProtocol { + let tag = "com.adamant.keys.id".data(using: .utf8)! + + func getPrivateKey() -> SecKey? { + guard let existingKey = loadPrivateKey() else { + return createAndStorePrivateKey() + } + + return existingKey + } + + func getPublicKey(privateKey: SecKey) -> SecKey? { + SecKeyCopyPublicKey(privateKey) + } + + func encrypt(data: Data, publicKey: SecKey) -> Data? { + guard let encryptedData = SecKeyCreateEncryptedData( + publicKey, + .eciesEncryptionCofactorX963SHA256AESGCM, + data as CFData, + nil + ) else { + return nil + } + + return encryptedData as Data + } + + func decrypt(data: Data, privateKey: SecKey) -> Data? { + guard let decryptedData = SecKeyCreateDecryptedData( + privateKey, + .eciesEncryptionCofactorX963SHA256AESGCM, + data as CFData, + nil + ) else { + return nil + } + + return decryptedData as Data + } +} + +private extension AdamantSecureStorage { + func loadPrivateKey() -> SecKey? { + let query: [String: Any] = [ + kSecClass as String: kSecClassKey, + kSecAttrApplicationTag as String: tag, + kSecAttrKeyType as String: kSecAttrKeyTypeEC, + kSecReturnRef as String: true + ] + + var item: CFTypeRef? + let status = SecItemCopyMatching(query as CFDictionary, &item) + + if status == errSecSuccess { + return (item as! SecKey) + } + + return nil + } + + func createAndStorePrivateKey() -> SecKey? { + guard let access = SecAccessControlCreateWithFlags( + kCFAllocatorDefault, + kSecAttrAccessibleAfterFirstUnlock, + .privateKeyUsage, + nil + ) else { return nil } + + let attributes: [String: Any] = [ + kSecAttrKeyType as String: kSecAttrKeyTypeEC, + kSecAttrKeySizeInBits as String: 256, + kSecAttrTokenID as String: kSecAttrTokenIDSecureEnclave, + kSecPrivateKeyAttrs as String: [ + kSecAttrIsPermanent as String: true, + kSecAttrApplicationTag as String: tag, + kSecAttrAccessControl as String: access + ] + ] + + guard let privateKey = SecKeyCreateRandomKey(attributes as CFDictionary, nil) else { + return nil + } + + return privateKey + } +} diff --git a/CommonKit/Sources/CommonKit/Helpers/SecureStorageProtocol.swift b/CommonKit/Sources/CommonKit/Helpers/SecureStorageProtocol.swift new file mode 100644 index 000000000..17138cc76 --- /dev/null +++ b/CommonKit/Sources/CommonKit/Helpers/SecureStorageProtocol.swift @@ -0,0 +1,15 @@ +// +// SecureStorageProtocol.swift +// +// +// Created by Stanislav Jelezoglo on 05.08.2024. +// + +import Foundation + +protocol SecureStorageProtocol { + func getPrivateKey() -> SecKey? + func getPublicKey(privateKey: SecKey) -> SecKey? + func encrypt(data: Data, publicKey: SecKey) -> Data? + func decrypt(data: Data, privateKey: SecKey) -> Data? +} diff --git a/CommonKit/Sources/CommonKit/Services/KeychainStore.swift b/CommonKit/Sources/CommonKit/Services/KeychainStore.swift index bd290b26d..c244b3f59 100644 --- a/CommonKit/Sources/CommonKit/Services/KeychainStore.swift +++ b/CommonKit/Sources/CommonKit/Services/KeychainStore.swift @@ -14,7 +14,18 @@ public final class KeychainStore: SecuredStore { // MARK: - Properties private static let keychain = Keychain(service: "\(AdamantSecret.appIdentifierPrefix).im.adamant.messenger") - public init() {} + private let secureStorage: SecureStorageProtocol = AdamantSecureStorage() + private let keychainStoreIdAlias = "com.adamant.messenger.id" + private var keychainPassword: String? + + private let oldKeychainService = "im.adamant" + private let migrationKey = "migrated" + private let migrationValue = "2" + + public init() { + configure() + migrateIfNeeded() + } // MARK: - SecuredStore @@ -47,35 +58,89 @@ public final class KeychainStore: SecuredStore { try? KeychainStore.keychain.removeAll() NotificationCenter.default.post(name: Notification.Name.SecuredStore.securedStorePurged, object: self) } +} + +private extension KeychainStore { + func configure() { + guard let privateKey = secureStorage.getPrivateKey(), + let publicKey = secureStorage.getPublicKey(privateKey: privateKey) + else { return } + + if let savedKey = getData(for: keychainStoreIdAlias) { + let decryptedData = secureStorage.decrypt( + data: savedKey, + privateKey: privateKey + ) ?? Data() + + keychainPassword = String(data: decryptedData, encoding: .utf8) + return + } + + let randomID = String.random(length: 32) + + guard let data = randomID.data(using: .utf8), + let encryptedData = secureStorage.encrypt(data: data, publicKey: publicKey) + else { return } + + keychainPassword = randomID + setData(encryptedData, for: keychainStoreIdAlias) + } - // MARK: - Tools - - private func getString(_ key: String) -> String? { - guard let encryptedValue = KeychainStore.keychain[key], - let decryptedValue = KeychainStore.decrypt(string: encryptedValue, password: AdamantSecret.keychainValuePassword) else { - return nil + func getString(_ key: String) -> String? { + guard let keychainPassword = keychainPassword, + let value = KeychainStore.keychain[key] else { + return nil } - + + let decryptedValue = decrypt( + string: value, + password: keychainPassword + ) + return decryptedValue } - - private func setString(_ value: String, for key: String) { - guard let encryptedValue = KeychainStore.encrypt(string: value, password: AdamantSecret.keychainValuePassword) else { + + func setString(_ value: String, for key: String) { + guard let keychainPassword = keychainPassword, + let encryptedValue = encrypt( + string: value, + password: keychainPassword + ) + else { return } - + try? KeychainStore.keychain.set(encryptedValue, key: key) } - private static func encrypt(string: String, password: String, encoding: String.Encoding = .utf8) -> String? { + func getData(for key: String) -> Data? { + try? KeychainStore.keychain.getData(key) + } + + func setData(_ value: Data, for key: String) { + try? KeychainStore.keychain.set(value, key: key) + } + + func encrypt( + string: String, + password: String, + encoding: String.Encoding = .utf8 + ) -> String? { guard let data = string.data(using: encoding) else { return nil } - return RNCryptor.encrypt(data: data, withPassword: password).base64EncodedString() + return RNCryptor.encrypt( + data: data, + withPassword: password + ).base64EncodedString() } - private static func decrypt(string: String, password: String, encoding: String.Encoding = .utf8) -> String? { + func decrypt( + string: String, + password: String, + encoding: String.Encoding = .utf8 + ) -> String? { if let encryptedData = Data(base64Encoded: string), let data = try? RNCryptor.decrypt(data: encryptedData, withPassword: password), let string = String(data: data, encoding: encoding) { @@ -84,41 +149,59 @@ public final class KeychainStore: SecuredStore { return nil } - +} + +private extension KeychainStore { // MARK: - Migration /* * Long time ago, we didn't use shared keychain. Now we do. We need to move all items from old keychain to new. And drop old one. */ - private static let oldKeychainService = "im.adamant" - private static let migrationKey = "migrated" - private static let migrationValue = "1" - public static func migrateIfNeeded() { - // Check flag - if let migrated = KeychainStore.keychain[migrationKey], migrated == migrationValue { - return - } + func migrateIfNeeded() { + let migrated = KeychainStore.keychain[migrationKey] - // Get old keychain - let oldKeychain = Keychain(service: KeychainStore.oldKeychainService) - for key in oldKeychain.allKeys() { - // Get value, decode with old pass - guard let oldEncryptedValue = oldKeychain[key], let value = decrypt(string: oldEncryptedValue, password: AdamantSecret.oldKeychainPass) else { - continue - } - - // Encode value and key with new pass - guard let encryptedValue = encrypt(string: value, password: AdamantSecret.keychainValuePassword) else { - continue - } - - try? KeychainStore.keychain.set(encryptedValue, key: key) - } + guard let keychainPassword = keychainPassword, + migrated != migrationValue + else { return } + + let oldKeychain = Keychain(service: oldKeychainService) + + migrate( + keychain: oldKeychain, + oldPassword: AdamantSecret.oldKeychainPass, + newPassword: keychainPassword + ) + + migrate( + keychain: KeychainStore.keychain, + oldPassword: AdamantSecret.keychainValuePassword, + newPassword: keychainPassword + ) - // Set flag try? KeychainStore.keychain.set(migrationValue, key: migrationKey) - // Drop old keychain try? oldKeychain.removeAll() } + + func migrate( + keychain: Keychain, + oldPassword: String, + newPassword: String + ) { + for key in keychain.allKeys() { + guard key != keychainStoreIdAlias, + let oldEncryptedValue = keychain[key], + let value = decrypt( + string: oldEncryptedValue, + password: oldPassword + ), + let encryptedValue = encrypt( + string: value, + password: newPassword + ) + else { continue } + + try? KeychainStore.keychain.set(encryptedValue, key: key) + } + } } From f0aa092842d82944f98ff7393be394954e3f4d64 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=80=9Cjust-software-dev=E2=80=9D?= Date: Mon, 5 Aug 2024 12:25:30 -0300 Subject: [PATCH 024/106] [feature/trello.com/c/dvqSTasv] Small fix --- Adamant/Modules/CoinsNodesList/CoinsNodesListFactory.swift | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/Adamant/Modules/CoinsNodesList/CoinsNodesListFactory.swift b/Adamant/Modules/CoinsNodesList/CoinsNodesListFactory.swift index 2d35ba864..3c4724dab 100644 --- a/Adamant/Modules/CoinsNodesList/CoinsNodesListFactory.swift +++ b/Adamant/Modules/CoinsNodesList/CoinsNodesListFactory.swift @@ -41,11 +41,7 @@ struct CoinsNodesListFactory { private struct CoinsNodesListAssembly: Assembly { func assemble(container: Container) { container.register(CoinsNodesListViewModel.self) { - let processedGroups = NodeGroup.allCases.compactMap { - $0 == .adm - ? nil - : $0 - } + let processedGroups = NodeGroup.allCases.filter { $0 != .adm } return .init( mapper: .init(processedGroups: processedGroups), From fd0d8d3daa8b8d3c8c1184047d153610d997b1ef Mon Sep 17 00:00:00 2001 From: StanislavDevIOS Date: Mon, 5 Aug 2024 19:36:37 +0300 Subject: [PATCH 025/106] [trello.com/c/XBkkMhlC] Feat: code improvements --- Adamant/App/DI/AppAssembly.swift | 4 +- .../ExtensionsTools/ExtensionsApi.swift | 4 +- .../Helpers/AdamantSecureStorage.swift | 50 +++++++------------ .../Helpers/SecureStorageProtocol.swift | 2 +- .../CommonKit/Services/KeychainStore.swift | 16 ++++-- .../NotificationViewController.swift | 33 ++++++------ .../NotificationService.swift | 5 +- .../NotificationViewController.swift | 13 +++-- 8 files changed, 65 insertions(+), 62 deletions(-) diff --git a/Adamant/App/DI/AppAssembly.swift b/Adamant/App/DI/AppAssembly.swift index b116c8375..2aa1e8300 100644 --- a/Adamant/App/DI/AppAssembly.swift +++ b/Adamant/App/DI/AppAssembly.swift @@ -20,7 +20,9 @@ struct AppAssembly: Assembly { container.register(CellFactory.self) { _ in AdamantCellFactory() }.inObjectScope(.container) // MARK: Secured Store - container.register(SecuredStore.self) { _ in KeychainStore() }.inObjectScope(.container) + container.register(SecuredStore.self) { _ in + KeychainStore(secureStorage: AdamantSecureStorage()) + }.inObjectScope(.container) // MARK: LocalAuthentication container.register(LocalAuthentication.self) { _ in AdamantAuthentication() }.inObjectScope(.container) diff --git a/CommonKit/Sources/CommonKit/ExtensionsTools/ExtensionsApi.swift b/CommonKit/Sources/CommonKit/ExtensionsTools/ExtensionsApi.swift index 5d8080212..12ddaee92 100644 --- a/CommonKit/Sources/CommonKit/ExtensionsTools/ExtensionsApi.swift +++ b/CommonKit/Sources/CommonKit/ExtensionsTools/ExtensionsApi.swift @@ -12,7 +12,7 @@ public final class ExtensionsApi { // MARK: Properties private let addressBookKey = "contact_list" private let nodesStoreKey = "nodesSource.nodes" - public let keychainStore: KeychainStore + public let keychainStore: SecuredStore public private(set) lazy var nodes: [Node] = { let nodes = keychainStore.get(nodesStoreKey) ?? AdamantResources.nodes @@ -26,7 +26,7 @@ public final class ExtensionsApi { } // MARK: Cotr - public init(keychainStore: KeychainStore) { + public init(keychainStore: SecuredStore) { self.keychainStore = keychainStore } diff --git a/CommonKit/Sources/CommonKit/Helpers/AdamantSecureStorage.swift b/CommonKit/Sources/CommonKit/Helpers/AdamantSecureStorage.swift index e04df3237..c622f3a7c 100644 --- a/CommonKit/Sources/CommonKit/Helpers/AdamantSecureStorage.swift +++ b/CommonKit/Sources/CommonKit/Helpers/AdamantSecureStorage.swift @@ -7,45 +7,35 @@ import Foundation -final class AdamantSecureStorage: SecureStorageProtocol { - let tag = "com.adamant.keys.id".data(using: .utf8)! +public struct AdamantSecureStorage: SecureStorageProtocol { + private let tag = "com.adamant.keys.id".data(using: .utf8)! - func getPrivateKey() -> SecKey? { - guard let existingKey = loadPrivateKey() else { - return createAndStorePrivateKey() - } - - return existingKey + public init() { } + + public func getPrivateKey() -> SecKey? { + loadPrivateKey() ?? createAndStorePrivateKey() } - func getPublicKey(privateKey: SecKey) -> SecKey? { + public func getPublicKey(privateKey: SecKey) -> SecKey? { SecKeyCopyPublicKey(privateKey) } - func encrypt(data: Data, publicKey: SecKey) -> Data? { - guard let encryptedData = SecKeyCreateEncryptedData( + public func encrypt(data: Data, publicKey: SecKey) -> Data? { + SecKeyCreateEncryptedData( publicKey, .eciesEncryptionCofactorX963SHA256AESGCM, data as CFData, nil - ) else { - return nil - } - - return encryptedData as Data + ).map { $0 as Data } } - func decrypt(data: Data, privateKey: SecKey) -> Data? { - guard let decryptedData = SecKeyCreateDecryptedData( + public func decrypt(data: Data, privateKey: SecKey) -> Data? { + SecKeyCreateDecryptedData( privateKey, .eciesEncryptionCofactorX963SHA256AESGCM, data as CFData, nil - ) else { - return nil - } - - return decryptedData as Data + ).map { $0 as Data } } } @@ -61,11 +51,9 @@ private extension AdamantSecureStorage { var item: CFTypeRef? let status = SecItemCopyMatching(query as CFDictionary, &item) - if status == errSecSuccess { - return (item as! SecKey) - } - - return nil + return status == errSecSuccess + ? (item as! SecKey) + : nil } func createAndStorePrivateKey() -> SecKey? { @@ -87,10 +75,6 @@ private extension AdamantSecureStorage { ] ] - guard let privateKey = SecKeyCreateRandomKey(attributes as CFDictionary, nil) else { - return nil - } - - return privateKey + return SecKeyCreateRandomKey(attributes as CFDictionary, nil) } } diff --git a/CommonKit/Sources/CommonKit/Helpers/SecureStorageProtocol.swift b/CommonKit/Sources/CommonKit/Helpers/SecureStorageProtocol.swift index 17138cc76..d71eb3d9f 100644 --- a/CommonKit/Sources/CommonKit/Helpers/SecureStorageProtocol.swift +++ b/CommonKit/Sources/CommonKit/Helpers/SecureStorageProtocol.swift @@ -7,7 +7,7 @@ import Foundation -protocol SecureStorageProtocol { +public protocol SecureStorageProtocol { func getPrivateKey() -> SecKey? func getPublicKey(privateKey: SecKey) -> SecKey? func encrypt(data: Data, publicKey: SecKey) -> Data? diff --git a/CommonKit/Sources/CommonKit/Services/KeychainStore.swift b/CommonKit/Sources/CommonKit/Services/KeychainStore.swift index c244b3f59..ccc3b65ed 100644 --- a/CommonKit/Sources/CommonKit/Services/KeychainStore.swift +++ b/CommonKit/Sources/CommonKit/Services/KeychainStore.swift @@ -9,12 +9,14 @@ import Foundation import KeychainAccess import RNCryptor +import CryptoKit public final class KeychainStore: SecuredStore { // MARK: - Properties private static let keychain = Keychain(service: "\(AdamantSecret.appIdentifierPrefix).im.adamant.messenger") - private let secureStorage: SecureStorageProtocol = AdamantSecureStorage() + private let secureStorage: SecureStorageProtocol + private let keychainStoreIdAlias = "com.adamant.messenger.id" private var keychainPassword: String? @@ -22,7 +24,9 @@ public final class KeychainStore: SecuredStore { private let migrationKey = "migrated" private let migrationValue = "2" - public init() { + public init(secureStorage: SecureStorageProtocol) { + self.secureStorage = secureStorage + configure() migrateIfNeeded() } @@ -76,13 +80,15 @@ private extension KeychainStore { return } - let randomID = String.random(length: 32) + let keychainRandomKey = SymmetricKey(size: .bits256) + .withUnsafeBytes { Data($0) } + .base64EncodedString() - guard let data = randomID.data(using: .utf8), + guard let data = keychainRandomKey.data(using: .utf8), let encryptedData = secureStorage.encrypt(data: data, publicKey: publicKey) else { return } - keychainPassword = randomID + keychainPassword = keychainRandomKey setData(encryptedData, for: keychainStoreIdAlias) } diff --git a/MessageNotificationContentExtension/NotificationViewController.swift b/MessageNotificationContentExtension/NotificationViewController.swift index 47d1ae2e5..cf8899809 100644 --- a/MessageNotificationContentExtension/NotificationViewController.swift +++ b/MessageNotificationContentExtension/NotificationViewController.swift @@ -18,6 +18,10 @@ class NotificationViewController: UIViewController, UNNotificationContentExtensi private let passphraseStoreKey = "accountService.passphrase" private let sizeWithoutMessageLabel: CGFloat = 123.0 + private lazy var securedStore: SecuredStore = { + KeychainStore(secureStorage: AdamantSecureStorage()) + }() + // MARK: - IBOutlets @IBOutlet weak var senderAvatarImageView: UIImageView! @@ -42,7 +46,6 @@ class NotificationViewController: UIViewController, UNNotificationContentExtensi func didReceive(_ notification: UNNotification) { // MARK: 0. Necessary services let avatarService = AdamantAvatarService() - var keychainStore: KeychainStore? var extensionApi: ExtensionsApi? var nativeCore: NativeAdamantCore? var keypair: Keypair? @@ -57,11 +60,9 @@ class NotificationViewController: UIViewController, UNNotificationContentExtensi return } - let store = KeychainStore() - let api = ExtensionsApi(keychainStore: store) + let api = ExtensionsApi(keychainStore: securedStore) trs = api.getTransaction(by: id) - keychainStore = store extensionApi = api } @@ -76,16 +77,18 @@ class NotificationViewController: UIViewController, UNNotificationContentExtensi if let raw = notification.request.content.userInfo[AdamantNotificationUserInfoKeys.decodedMessage] as? String { message = raw } else { - let keychainStore = keychainStore ?? KeychainStore() nativeCore = NativeAdamantCore() - guard let passphrase: String = keychainStore.get(passphraseStoreKey), - let keys = nativeCore!.createKeypairFor(passphrase: passphrase), - let chat = transaction.asset.chat, - let raw = nativeCore!.decodeMessage(rawMessage: chat.message, - rawNonce: chat.ownMessage, - senderPublicKey: transaction.senderPublicKey, - privateKey: keys.privateKey) else { + guard let passphrase: String = securedStore.get(passphraseStoreKey), + let keys = nativeCore!.createKeypairFor(passphrase: passphrase), + let chat = transaction.asset.chat, + let raw = nativeCore!.decodeMessage( + rawMessage: chat.message, + rawNonce: chat.ownMessage, + senderPublicKey: transaction.senderPublicKey, + privateKey: keys.privateKey + ) + else { showError() return } @@ -107,14 +110,14 @@ class NotificationViewController: UIViewController, UNNotificationContentExtensi } // No name, no flag - something broke. Check sender name, if we have a recipient address else if let recipient = notification.request.content.userInfo[AdamantNotificationUserInfoKeys.pushRecipient] as? String { - let keychain = keychainStore ?? KeychainStore() let core = nativeCore ?? NativeAdamantCore() - let api: ExtensionsApi = extensionApi ?? ExtensionsApi(keychainStore: keychain) + let api: ExtensionsApi = extensionApi ?? ExtensionsApi(keychainStore: securedStore) let key: Keypair? if let keypair = keypair { key = keypair - } else if let passphrase: String = keychain.get(passphraseStoreKey), let keypair = core.createKeypairFor(passphrase: passphrase) { + } else if let passphrase: String = securedStore.get(passphraseStoreKey), + let keypair = core.createKeypairFor(passphrase: passphrase) { key = keypair } else { key = nil diff --git a/NotificationServiceExtension/NotificationService.swift b/NotificationServiceExtension/NotificationService.swift index 684af775d..0a53f1baa 100644 --- a/NotificationServiceExtension/NotificationService.swift +++ b/NotificationServiceExtension/NotificationService.swift @@ -18,6 +18,10 @@ class NotificationService: UNNotificationServiceExtension { return AdamantProvider() }() + private lazy var securedStore: SecuredStore = { + KeychainStore(secureStorage: AdamantSecureStorage()) + }() + /// Lazy constructors private lazy var richMessageProviders: [String: TransferNotificationContentProvider] = { var providers: [String: TransferNotificationContentProvider] = [ @@ -59,7 +63,6 @@ class NotificationService: UNNotificationServiceExtension { } // MARK: 1. Getting services - let securedStore = KeychainStore() let core = NativeAdamantCore() let api = ExtensionsApi(keychainStore: securedStore) diff --git a/TransferNotificationContentExtension/NotificationViewController.swift b/TransferNotificationContentExtension/NotificationViewController.swift index 952281d2f..183b6a4cb 100644 --- a/TransferNotificationContentExtension/NotificationViewController.swift +++ b/TransferNotificationContentExtension/NotificationViewController.swift @@ -22,6 +22,10 @@ class NotificationViewController: UIViewController, UNNotificationContentExtensi return AdamantProvider() }() + private lazy var securedStore: SecuredStore = { + KeychainStore(secureStorage: AdamantSecureStorage()) + }() + /// Lazy contstructors private lazy var richMessageProviders: [String: TransferNotificationContentProvider] = { var providers: [String: TransferNotificationContentProvider] = [ @@ -119,12 +123,13 @@ class NotificationViewController: UIViewController, UNNotificationContentExtensi func didReceive(_ notification: UNNotification) { // MARK: 0. Services - let keychain = KeychainStore() let core = NativeAdamantCore() let avatarService = AdamantAvatarService() var api: ExtensionsApi? - guard let passphrase: String = keychain.get(passphraseStoreKey), let keypair = core.createKeypairFor(passphrase: passphrase) else { + guard let passphrase: String = securedStore.get(passphraseStoreKey), + let keypair = core.createKeypairFor(passphrase: passphrase) + else { showError() return } @@ -139,7 +144,7 @@ class NotificationViewController: UIViewController, UNNotificationContentExtensi return } - api = ExtensionsApi(keychainStore: keychain) + api = ExtensionsApi(keychainStore: securedStore) trs = api!.getTransaction(by: id) } @@ -233,7 +238,7 @@ class NotificationViewController: UIViewController, UNNotificationContentExtensi else if let flag = notification.request.content.userInfo[AdamantNotificationUserInfoKeys.partnerNoDislpayNameKey] as? String, flag == AdamantNotificationUserInfoKeys.partnerNoDisplayNameValue { senderName = nil } else { - let _api = api ?? ExtensionsApi(keychainStore: keychain) + let _api = api ?? ExtensionsApi(keychainStore: securedStore) checkName(of: transaction.senderId, for: transaction.recipientId, api: _api, core: core, keypair: keypair) senderName = nil } From be9c257961b9ecbcc67fedc74f3e6172c3cffa0b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=80=9Cjust-software-dev=E2=80=9D?= Date: Mon, 5 Aug 2024 15:19:07 -0300 Subject: [PATCH 026/106] [feature/trello.com/c/dvqSTasv] Small fix --- Adamant/Services/HealthCheckWrapper.swift | 6 ------ 1 file changed, 6 deletions(-) diff --git a/Adamant/Services/HealthCheckWrapper.swift b/Adamant/Services/HealthCheckWrapper.swift index 5b64b3d04..bbdf8ab6e 100644 --- a/Adamant/Services/HealthCheckWrapper.swift +++ b/Adamant/Services/HealthCheckWrapper.swift @@ -34,12 +34,6 @@ class HealthCheckWrapper { @ObservableValue private(set) var sortedAllowedNodes: [Node] = .init() - var preferredNodeIds: [UUID] { - fastestNodeMode - ? [sortedAllowedNodes.first?.id].compactMap { $0 } - : [] - } - init( service: Service, normalUpdateInterval: TimeInterval, From d08afe473bbb3d6a6f6140d2ae3bc3fcfb21cc82 Mon Sep 17 00:00:00 2001 From: StanislavDevIOS Date: Tue, 6 Aug 2024 17:47:03 +0300 Subject: [PATCH 027/106] [trello.com/c/XBkkMhlC] Feat: improved save data --- .../CommonKit/Services/KeychainStore.swift | 116 ++++++++---------- 1 file changed, 53 insertions(+), 63 deletions(-) diff --git a/CommonKit/Sources/CommonKit/Services/KeychainStore.swift b/CommonKit/Sources/CommonKit/Services/KeychainStore.swift index ccc3b65ed..5d781c5eb 100644 --- a/CommonKit/Sources/CommonKit/Services/KeychainStore.swift +++ b/CommonKit/Sources/CommonKit/Services/KeychainStore.swift @@ -34,24 +34,24 @@ public final class KeychainStore: SecuredStore { // MARK: - SecuredStore public func get(_ key: String) -> T? { - guard !(T.self == String.self) else { return getString(key) as? T } + guard let data = getValue(key) else { return nil } - guard - let raw = getString(key), - let data = raw.data(using: .utf8) - else { return nil } + guard !(T.self == String.self) else { + return String(data: data, encoding: .utf8) as? T + } return try? JSONDecoder().decode(T.self, from: data) } public func set(_ value: T, for key: String) { - if let string = value as? String { - setString(string, for: key) + if let string = value as? String, + let data = string.data(using: .utf8) { + setValue(data, for: key) return } guard let data = try? JSONEncoder().encode(value) else { return } - String(data: data, encoding: .utf8).map { setString($0, for: key) } + setValue(data, for: key) } public func remove(_ key: String) { @@ -74,49 +74,47 @@ private extension KeychainStore { let decryptedData = secureStorage.decrypt( data: savedKey, privateKey: privateKey - ) ?? Data() + ) - keychainPassword = String(data: decryptedData, encoding: .utf8) + keychainPassword = decryptedData?.base64EncodedString() return } - let keychainRandomKey = SymmetricKey(size: .bits256) + let keychainRandomKeyData = SymmetricKey(size: .bits256) .withUnsafeBytes { Data($0) } - .base64EncodedString() + let keychainRandomKey = keychainRandomKeyData.base64EncodedString() - guard let data = keychainRandomKey.data(using: .utf8), - let encryptedData = secureStorage.encrypt(data: data, publicKey: publicKey) - else { return } + guard let encryptedData = secureStorage.encrypt( + data: keychainRandomKeyData, + publicKey: publicKey + ) else { return } keychainPassword = keychainRandomKey setData(encryptedData, for: keychainStoreIdAlias) } - func getString(_ key: String) -> String? { + func getValue(_ key: String) -> Data? { guard let keychainPassword = keychainPassword, - let value = KeychainStore.keychain[key] else { - return nil - } + let data = getData(for: key) + else { return nil} - let decryptedValue = decrypt( - string: value, + return decrypt( + data: data, password: keychainPassword ) - - return decryptedValue } - func setString(_ value: String, for key: String) { - guard let keychainPassword = keychainPassword, - let encryptedValue = encrypt( - string: value, - password: keychainPassword - ) - else { + func setValue(_ value: Data, for key: String) { + guard let keychainPassword = keychainPassword else { return } - try? KeychainStore.keychain.set(encryptedValue, key: key) + let encryptedValue = encrypt( + data: value, + password: keychainPassword + ) + + setData(encryptedValue, for: key) } func getData(for key: String) -> Data? { @@ -128,32 +126,30 @@ private extension KeychainStore { } func encrypt( - string: String, - password: String, - encoding: String.Encoding = .utf8 - ) -> String? { - guard let data = string.data(using: encoding) else { - return nil - } - - return RNCryptor.encrypt( + data: Data, + password: String + ) -> Data { + RNCryptor.encrypt( data: data, withPassword: password - ).base64EncodedString() + ) } func decrypt( + data: Data, + password: String + ) -> Data? { + try? RNCryptor.decrypt(data: data, withPassword: password) + } + + func decryptOld( string: String, - password: String, - encoding: String.Encoding = .utf8 - ) -> String? { - if let encryptedData = Data(base64Encoded: string), - let data = try? RNCryptor.decrypt(data: encryptedData, withPassword: password), - let string = String(data: data, encoding: encoding) { - return string + password: String + ) -> Data? { + guard let encryptedData = Data(base64Encoded: string) else { + return nil } - - return nil + return try? RNCryptor.decrypt(data: encryptedData, withPassword: password) } } @@ -167,7 +163,7 @@ private extension KeychainStore { func migrateIfNeeded() { let migrated = KeychainStore.keychain[migrationKey] - guard let keychainPassword = keychainPassword, + guard keychainPassword != nil, migrated != migrationValue else { return } @@ -175,14 +171,12 @@ private extension KeychainStore { migrate( keychain: oldKeychain, - oldPassword: AdamantSecret.oldKeychainPass, - newPassword: keychainPassword + oldPassword: AdamantSecret.oldKeychainPass ) migrate( keychain: KeychainStore.keychain, - oldPassword: AdamantSecret.keychainValuePassword, - newPassword: keychainPassword + oldPassword: AdamantSecret.keychainValuePassword ) try? KeychainStore.keychain.set(migrationValue, key: migrationKey) @@ -191,23 +185,19 @@ private extension KeychainStore { func migrate( keychain: Keychain, - oldPassword: String, - newPassword: String + oldPassword: String ) { for key in keychain.allKeys() { guard key != keychainStoreIdAlias, let oldEncryptedValue = keychain[key], - let value = decrypt( + let value = decryptOld( string: oldEncryptedValue, password: oldPassword - ), - let encryptedValue = encrypt( - string: value, - password: newPassword ) else { continue } - try? KeychainStore.keychain.set(encryptedValue, key: key) + try? KeychainStore.keychain.remove(key) + setValue(value, for: key) } } } From f7e4bfb097871fca5a1b0c6d8fd5a7d559940477 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=80=9Cjust-software-dev=E2=80=9D?= Date: Fri, 9 Aug 2024 00:06:19 -0300 Subject: [PATCH 028/106] [trello.com/c/hX21rGqP] Use the same services to access nodes in the app and the push notification extension --- Adamant.xcodeproj/project.pbxproj | 257 ++---------------- Adamant/App/DI/AppAssembly.swift | 36 ++- .../Helpers/ApiServiceError+Extension.swift | 38 ++- .../HealthCheckWrapper+Extension.swift | 17 -- Adamant/Helpers/Localization.swift | 12 +- Adamant/Helpers/Node+UI.swift | 10 - Adamant/Helpers/NodeGroup+Constants.swift | 15 +- Adamant/Helpers/String+localized.swift | 70 ----- Adamant/Models/APIResponseModel.swift | 15 - Adamant/Models/Delegate.swift | 157 ----------- Adamant/Models/NodeStatusInfo.swift | 17 -- .../Modules/ChatsList/ChatListFactory.swift | 1 + .../ComplexTransferViewController.swift | 2 +- .../Modules/Delegates/DelegatesFactory.swift | 1 + Adamant/Modules/Login/LoginFactory.swift | 1 + .../Modules/Login/LoginViewController.swift | 2 +- .../Wallets/Adamant/AdmWalletFactory.swift | 1 + .../Dash/DashWalletService+Transactions.swift | 2 +- .../Wallets/Klayr/KlyNodeApiService.swift | 1 + .../Wallets/TransferViewControllerBase.swift | 4 +- .../WalletsService/WalletCoreProtocol.swift | 4 +- .../ReachabilityMonitor.swift | 2 + .../RichMessageProviderWithStatusCheck.swift | 1 + Adamant/Services/AdamantReachability.swift | 6 +- .../DataProviders/DefaultNodesProvider.swift | 38 +++ .../DataProviders/InMemoryCoreDataStack.swift | 1 + Adamant/Services/NativeCore+AdamantCore.swift | 119 -------- CommonKit/Package.swift | 13 +- .../CommonKit/Core/NativeAdamantCore.swift | 97 ++++++- .../ExtensionsTools/ExtensionsApi.swift | 150 ++-------- .../ExtensionsApiFactory.swift | 43 +++ .../CommonKit}/Helpers/ADM+JsonDecode.swift | 14 +- .../Helpers}/AdamantCore+Extensions.swift | 5 +- .../Helpers/ApiServiceError+AFError.swift | 21 ++ .../Sources/CommonKit/Helpers/Atomic.swift | 2 +- .../CommonKit/Helpers}/ByteBackpacker.swift | 0 .../NodeGroup+Constants.swift | 0 .../Helpers/ServerResponse+Resolver.swift | 10 +- .../Localization/AdamantLocalized.swift | 82 ++++++ .../Models/APIParametersEncoding.swift | 4 +- .../CommonKit/Models/APIResponseModel.swift | 21 ++ .../CommonKit}/Models/AdamantAccount.swift | 53 +++- .../CommonKit/Models}/AdamantError.swift | 4 +- .../CommonKit}/Models/ApiServiceError.swift | 66 ++--- .../CommonKit}/Models/ApiServiceResult.swift | 2 +- .../Models/BlockchainHealthCheckParams.swift | 33 +++ .../Models/BodyStringEncoding.swift | 4 +- .../Models/ForceQueryItemsEncoding.swift | 10 +- .../CommonKit}/Models/InternalAPIError.swift | 7 +- .../CommonKit/Models/{ => Node}/Node.swift | 8 + .../{ => Node}/NodeConnectionStatus.swift | 0 .../Models/{ => Node}/NodeGroup.swift | 0 .../Models/{ => Node}/NodeOrigin.swift | 0 .../Models/Node/NodeStatusInfo.swift | 31 +++ .../Models/{ => Node}/NodeType.swift | 0 .../Models/{ => ServerDTOs}/ChatAsset.swift | 2 +- .../Models/{ => ServerDTOs}/ChatRooms.swift | 0 .../{ => ServerDTOs}/ChatRoomsChats.swift | 0 .../Models/{ => ServerDTOs}/ChatType.swift | 2 +- .../{ => ServerDTOs}/ContactDescription.swift | 0 .../Models/ServerDTOs/Delegate.swift | 184 +++++++++++++ .../ServerDTOs}/GetPublicKeyResponse.swift | 7 +- .../Models/ServerDTOs}/NodeStatus.swift | 44 +-- .../ServerDTOs}/NormalizedTransaction.swift | 56 ++-- .../Models/ServerDTOs}/RPCResponseModel.swift | 10 +- .../Models/{ => ServerDTOs}/RichMessage.swift | 0 .../Models/ServerDTOs}/RpcRequestModel.swift | 20 +- .../{ => ServerDTOs}/ServerResponse.swift | 0 .../Models/{ => ServerDTOs}/StateAsset.swift | 2 +- .../Models/{ => ServerDTOs}/StateType.swift | 2 +- .../Models/{ => ServerDTOs}/Transaction.swift | 2 +- .../{ => ServerDTOs}/TransactionAsset.swift | 2 +- .../ServerDTOs}/TransactionIdResponse.swift | 7 +- .../{ => ServerDTOs}/TransactionType.swift | 2 +- .../ServerDTOs}/UnregisteredTransaction.swift | 47 +++- .../Models/{ => ServerDTOs}/VotesAsset.swift | 2 +- .../Protocols}/APICoreProtocol.swift | 7 +- .../CommonKit/Protocols}/AdamantCore.swift | 6 +- .../CommonKit/Protocols}/ApiService.swift | 3 +- ...NodesAdditionalParamsStorageProtocol.swift | 8 +- .../Protocols}/NodesMergingService.swift | 4 +- .../Protocols}/NodesStorageProtocol.swift | 7 +- .../Protocols}/WalletApiService.swift | 2 +- .../Sources/CommonKit}/Services/APICore.swift | 9 +- .../Services/AdamantNodesMergingService.swift | 8 +- .../ApiService/AdamantApi+Accounts.swift | 9 +- .../ApiService/AdamantApi+Chats.swift | 11 +- .../ApiService/AdamantApi+Delegates.swift | 17 +- .../Services/ApiService/AdamantApi+Keys.swift | 3 +- .../ApiService/AdamantApi+States.swift | 9 +- .../ApiService/AdamantApi+Transactions.swift | 15 +- .../ApiService/AdamantApi+Transfers.swift | 5 +- .../Services/ApiService/AdamantApiCore.swift | 17 +- .../ApiService/AdamantApiService.swift | 17 +- .../BlockchainHealthCheckWrapper.swift | 64 +++-- .../HealthCheck}/HealthCheckWrapper.swift | 89 +++--- .../NodesAdditionalParamsStorage.swift | 13 +- .../CommonKit}/Services/NodesStorage.swift | 54 ++-- .../NotificationViewController.swift | 34 +-- .../NotificationService.swift | 2 +- .../NotificationViewController.swift | 8 +- 101 files changed, 1123 insertions(+), 1199 deletions(-) delete mode 100644 Adamant/Helpers/HealthCheckWrapper+Extension.swift delete mode 100644 Adamant/Models/APIResponseModel.swift delete mode 100644 Adamant/Models/Delegate.swift delete mode 100644 Adamant/Models/NodeStatusInfo.swift create mode 100644 Adamant/Services/DataProviders/DefaultNodesProvider.swift delete mode 100644 Adamant/Services/NativeCore+AdamantCore.swift create mode 100644 CommonKit/Sources/CommonKit/ExtensionsTools/ExtensionsApiFactory.swift rename {Adamant => CommonKit/Sources/CommonKit}/Helpers/ADM+JsonDecode.swift (93%) rename {Adamant/ServiceProtocols/AdamantCore => CommonKit/Sources/CommonKit/Helpers}/AdamantCore+Extensions.swift (98%) create mode 100644 CommonKit/Sources/CommonKit/Helpers/ApiServiceError+AFError.swift rename {Adamant/Utilities => CommonKit/Sources/CommonKit/Helpers}/ByteBackpacker.swift (100%) rename CommonKit/Sources/CommonKit/{Models => Helpers}/NodeGroup+Constants.swift (100%) rename {Adamant => CommonKit/Sources/CommonKit}/Helpers/ServerResponse+Resolver.swift (89%) rename {Adamant => CommonKit/Sources/CommonKit}/Models/APIParametersEncoding.swift (86%) create mode 100644 CommonKit/Sources/CommonKit/Models/APIResponseModel.swift rename {Adamant => CommonKit/Sources/CommonKit}/Models/AdamantAccount.swift (64%) rename {Adamant/Helpers => CommonKit/Sources/CommonKit/Models}/AdamantError.swift (76%) rename {Adamant => CommonKit/Sources/CommonKit}/Models/ApiServiceError.swift (57%) rename {Adamant => CommonKit/Sources/CommonKit}/Models/ApiServiceResult.swift (65%) create mode 100644 CommonKit/Sources/CommonKit/Models/BlockchainHealthCheckParams.swift rename {Adamant => CommonKit/Sources/CommonKit}/Models/BodyStringEncoding.swift (92%) rename {Adamant => CommonKit/Sources/CommonKit}/Models/ForceQueryItemsEncoding.swift (76%) rename {Adamant => CommonKit/Sources/CommonKit}/Models/InternalAPIError.swift (87%) rename CommonKit/Sources/CommonKit/Models/{ => Node}/Node.swift (89%) rename CommonKit/Sources/CommonKit/Models/{ => Node}/NodeConnectionStatus.swift (100%) rename CommonKit/Sources/CommonKit/Models/{ => Node}/NodeGroup.swift (100%) rename CommonKit/Sources/CommonKit/Models/{ => Node}/NodeOrigin.swift (100%) create mode 100644 CommonKit/Sources/CommonKit/Models/Node/NodeStatusInfo.swift rename CommonKit/Sources/CommonKit/Models/{ => Node}/NodeType.swift (100%) rename CommonKit/Sources/CommonKit/Models/{ => ServerDTOs}/ChatAsset.swift (93%) rename CommonKit/Sources/CommonKit/Models/{ => ServerDTOs}/ChatRooms.swift (100%) rename CommonKit/Sources/CommonKit/Models/{ => ServerDTOs}/ChatRoomsChats.swift (100%) rename CommonKit/Sources/CommonKit/Models/{ => ServerDTOs}/ChatType.swift (97%) rename CommonKit/Sources/CommonKit/Models/{ => ServerDTOs}/ContactDescription.swift (100%) create mode 100644 CommonKit/Sources/CommonKit/Models/ServerDTOs/Delegate.swift rename {Adamant/Models/ServerResponses => CommonKit/Sources/CommonKit/Models/ServerDTOs}/GetPublicKeyResponse.swift (84%) rename {Adamant/Models => CommonKit/Sources/CommonKit/Models/ServerDTOs}/NodeStatus.swift (52%) rename {Adamant/Models => CommonKit/Sources/CommonKit/Models/ServerDTOs}/NormalizedTransaction.swift (62%) rename {Adamant/Models => CommonKit/Sources/CommonKit/Models/ServerDTOs}/RPCResponseModel.swift (72%) rename CommonKit/Sources/CommonKit/Models/{ => ServerDTOs}/RichMessage.swift (100%) rename {Adamant/Models => CommonKit/Sources/CommonKit/Models/ServerDTOs}/RpcRequestModel.swift (66%) rename CommonKit/Sources/CommonKit/Models/{ => ServerDTOs}/ServerResponse.swift (100%) rename CommonKit/Sources/CommonKit/Models/{ => ServerDTOs}/StateAsset.swift (89%) rename CommonKit/Sources/CommonKit/Models/{ => ServerDTOs}/StateType.swift (94%) rename CommonKit/Sources/CommonKit/Models/{ => ServerDTOs}/Transaction.swift (99%) rename CommonKit/Sources/CommonKit/Models/{ => ServerDTOs}/TransactionAsset.swift (87%) rename {Adamant/Models/ServerResponses => CommonKit/Sources/CommonKit/Models/ServerDTOs}/TransactionIdResponse.swift (85%) rename CommonKit/Sources/CommonKit/Models/{ => ServerDTOs}/TransactionType.swift (97%) rename {Adamant/Models => CommonKit/Sources/CommonKit/Models/ServerDTOs}/UnregisteredTransaction.swift (64%) rename CommonKit/Sources/CommonKit/Models/{ => ServerDTOs}/VotesAsset.swift (93%) rename {Adamant/ServiceProtocols => CommonKit/Sources/CommonKit/Protocols}/APICoreProtocol.swift (97%) rename {Adamant/ServiceProtocols/AdamantCore => CommonKit/Sources/CommonKit/Protocols}/AdamantCore.swift (90%) rename {Adamant/ServiceProtocols => CommonKit/Sources/CommonKit/Protocols}/ApiService.swift (98%) rename {Adamant/ServiceProtocols => CommonKit/Sources/CommonKit/Protocols}/NodesAdditionalParamsStorageProtocol.swift (73%) rename {Adamant/ServiceProtocols => CommonKit/Sources/CommonKit/Protocols}/NodesMergingService.swift (84%) rename {Adamant/ServiceProtocols => CommonKit/Sources/CommonKit/Protocols}/NodesStorageProtocol.swift (82%) rename {Adamant/Modules/Wallets => CommonKit/Sources/CommonKit/Protocols}/WalletApiService.swift (88%) rename {Adamant => CommonKit/Sources/CommonKit}/Services/APICore.swift (95%) rename {Adamant => CommonKit/Sources/CommonKit}/Services/AdamantNodesMergingService.swift (95%) rename {Adamant => CommonKit/Sources/CommonKit}/Services/ApiService/AdamantApi+Accounts.swift (85%) rename {Adamant => CommonKit/Sources/CommonKit}/Services/ApiService/AdamantApi+Chats.swift (94%) rename {Adamant => CommonKit/Sources/CommonKit}/Services/ApiService/AdamantApi+Delegates.swift (92%) rename {Adamant => CommonKit/Sources/CommonKit}/Services/ApiService/AdamantApi+Keys.swift (86%) rename {Adamant => CommonKit/Sources/CommonKit}/Services/ApiService/AdamantApi+States.swift (91%) rename {Adamant => CommonKit/Sources/CommonKit}/Services/ApiService/AdamantApi+Transactions.swift (92%) rename {Adamant => CommonKit/Sources/CommonKit}/Services/ApiService/AdamantApi+Transfers.swift (89%) rename {Adamant => CommonKit/Sources/CommonKit}/Services/ApiService/AdamantApiCore.swift (74%) rename {Adamant => CommonKit/Sources/CommonKit}/Services/ApiService/AdamantApiService.swift (72%) rename {Adamant/Services => CommonKit/Sources/CommonKit/Services/HealthCheck}/BlockchainHealthCheckWrapper.swift (76%) rename {Adamant/Services => CommonKit/Sources/CommonKit/Services/HealthCheck}/HealthCheckWrapper.swift (68%) rename {Adamant => CommonKit/Sources/CommonKit}/Services/NodesAdditionalParamsStorage.swift (78%) rename {Adamant => CommonKit/Sources/CommonKit}/Services/NodesStorage.swift (74%) diff --git a/Adamant.xcodeproj/project.pbxproj b/Adamant.xcodeproj/project.pbxproj index fe32d3c18..b86b94655 100644 --- a/Adamant.xcodeproj/project.pbxproj +++ b/Adamant.xcodeproj/project.pbxproj @@ -49,12 +49,9 @@ 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 */; }; - 3AA388032B67F47600125684 /* RPCResponseModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3AA388022B67F47600125684 /* RPCResponseModel.swift */; }; 3AA388052B67F4DD00125684 /* BtcBlockchainInfoDTO.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3AA388042B67F4DD00125684 /* BtcBlockchainInfoDTO.swift */; }; 3AA388072B67F53F00125684 /* BtcNetworkInfoDTO.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3AA388062B67F53F00125684 /* BtcNetworkInfoDTO.swift */; }; 3AA3880A2B69173500125684 /* DashNetworkInfoDTO.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3AA388092B69173500125684 /* DashNetworkInfoDTO.swift */; }; - 3AA3880C2B69201B00125684 /* ADM+JsonDecode.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3AA3880B2B69201B00125684 /* ADM+JsonDecode.swift */; }; - 3AA3880E2B6A356900125684 /* RpcRequestModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3AA3880D2B6A356900125684 /* RpcRequestModel.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 */; }; @@ -125,7 +122,6 @@ 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 */; }; - 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 */; }; 55D1D84F287B78F200F94A4E /* SnapKit in Frameworks */ = {isa = PBXBuildFile; productRef = 55D1D84E287B78F200F94A4E /* SnapKit */; }; @@ -152,8 +148,6 @@ 6449BA6F235CA0930033B936 /* ERC20WalletService+Send.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6449BA65235CA0930033B936 /* ERC20WalletService+Send.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 /* 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 */; }; @@ -206,8 +200,6 @@ 9304F8BE292F88F900173F18 /* ANSPayload.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9304F8BD292F88F900173F18 /* ANSPayload.swift */; }; 9304F8C2292F895C00173F18 /* PushNotificationsTokenService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9304F8C1292F895C00173F18 /* PushNotificationsTokenService.swift */; }; 9304F8C4292F8A3100173F18 /* AdamantPushNotificationsTokenService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9304F8C3292F8A3100173F18 /* AdamantPushNotificationsTokenService.swift */; }; - 9304F8C6292F971600173F18 /* ApiServiceResult.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9304F8C5292F971600173F18 /* ApiServiceResult.swift */; }; - 9304F8C8292F972600173F18 /* ApiServiceError.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9304F8C7292F972600173F18 /* ApiServiceError.swift */; }; 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 */; }; @@ -226,13 +218,6 @@ 93294B9A2AAD624100911109 /* WalletFactoryCompose.swift in Sources */ = {isa = PBXBuildFile; fileRef = 93294B992AAD624100911109 /* WalletFactoryCompose.swift */; }; 932B34E92974AA4A002A75BA /* ChatPreservationProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 932B34E82974AA4A002A75BA /* ChatPreservationProtocol.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 */; }; @@ -249,10 +234,7 @@ 93496BB52A6CAED100DD062F /* Roboto_300_normal.ttf in Resources */ = {isa = PBXBuildFile; fileRef = 93496BAA2A6CAED100DD062F /* Roboto_300_normal.ttf */; }; 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 */; }; - 934A19172C5CD00500EA6E65 /* NodesMergingService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 934A19162C5CD00500EA6E65 /* NodesMergingService.swift */; }; - 934A19192C5CD0BF00EA6E65 /* AdamantNodesMergingService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 934A19182C5CD0BF00EA6E65 /* AdamantNodesMergingService.swift */; }; 93547BCA29E2262D00B0914B /* WelcomeViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 93547BC929E2262D00B0914B /* WelcomeViewController.swift */; }; - 9356589E2C60D29300E7D38A /* HealthCheckWrapper+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9356589D2C60D29300E7D38A /* HealthCheckWrapper+Extension.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 */; }; @@ -260,13 +242,16 @@ 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 */; }; 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 */; }; + 93760BD72C656CF8002507C3 /* DefaultNodesProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 93760BD62C656CF8002507C3 /* DefaultNodesProvider.swift */; }; + 93760BDF2C65A284002507C3 /* WordList.swift in Sources */ = {isa = PBXBuildFile; fileRef = 93760BDE2C65A284002507C3 /* WordList.swift */; }; + 93760BE12C65A2F3002507C3 /* Mnemonic+extended.swift in Sources */ = {isa = PBXBuildFile; fileRef = 93760BE02C65A2F3002507C3 /* Mnemonic+extended.swift */; }; + 93760BE22C65A424002507C3 /* english.txt in Resources */ = {isa = PBXBuildFile; fileRef = 93760BDD2C65A1FA002507C3 /* english.txt */; }; 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 */; }; @@ -277,8 +262,6 @@ 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 */; }; @@ -299,13 +282,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 */; }; - 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 */; }; @@ -337,13 +316,6 @@ 93E1234C2A6DFF62004DF33B /* NotificationStrings.swift in Sources */ = {isa = PBXBuildFile; fileRef = 93E1234A2A6DFEF7004DF33B /* NotificationStrings.swift */; }; 93E1234D2A6DFF62004DF33B /* NotificationStrings.swift in Sources */ = {isa = PBXBuildFile; fileRef = 93E1234A2A6DFEF7004DF33B /* NotificationStrings.swift */; }; 93E1234E2A6DFF62004DF33B /* NotificationStrings.swift in Sources */ = {isa = PBXBuildFile; fileRef = 93E1234A2A6DFEF7004DF33B /* NotificationStrings.swift */; }; - 93E5D4DB293000BE00439298 /* UnregisteredTransaction.swift in Sources */ = {isa = PBXBuildFile; fileRef = 93E5D4DA293000BE00439298 /* UnregisteredTransaction.swift */; }; - 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 /* 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 */; }; @@ -359,7 +331,6 @@ A50A41142822FC35006BDFE1 /* BtcTransferViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = A50A41102822FC35006BDFE1 /* BtcTransferViewController.swift */; }; A50AEB04262C815200B37C22 /* EFQRCode in Frameworks */ = {isa = PBXBuildFile; productRef = A50AEB03262C815200B37C22 /* EFQRCode */; }; A50AEB0C262C81E300B37C22 /* QRCodeReader in Frameworks */ = {isa = PBXBuildFile; productRef = A50AEB0B262C81E300B37C22 /* QRCodeReader */; }; - A50AEB14262C837900B37C22 /* Alamofire in Frameworks */ = {isa = PBXBuildFile; productRef = A50AEB13262C837900B37C22 /* Alamofire */; }; A5241B70262DEDE1009FA43E /* Clibsodium in Frameworks */ = {isa = PBXBuildFile; productRef = A5241B6F262DEDE1009FA43E /* Clibsodium */; }; A5241B77262DEDEF009FA43E /* Clibsodium in Frameworks */ = {isa = PBXBuildFile; productRef = A5241B76262DEDEF009FA43E /* Clibsodium */; }; A5241B7E262DEDFE009FA43E /* Clibsodium in Frameworks */ = {isa = PBXBuildFile; productRef = A5241B7D262DEDFE009FA43E /* Clibsodium */; }; @@ -373,7 +344,6 @@ A578BDE52623051C00090141 /* DashWalletService+Transactions.swift in Sources */ = {isa = PBXBuildFile; fileRef = A578BDE42623051C00090141 /* DashWalletService+Transactions.swift */; }; A5AC8DFF262E0B030053A7E2 /* SipHash in Frameworks */ = {isa = PBXBuildFile; productRef = A5AC8DFE262E0B030053A7E2 /* SipHash */; }; A5AC8E00262E0B030053A7E2 /* SipHash in Embed Frameworks */ = {isa = PBXBuildFile; productRef = A5AC8DFE262E0B030053A7E2 /* SipHash */; settings = {ATTRIBUTES = (CodeSignOnCopy, ); }; }; - A5BBD811262C657300B5C40C /* ByteBackpacker.swift in Sources */ = {isa = PBXBuildFile; fileRef = A5BBD810262C657300B5C40C /* ByteBackpacker.swift */; }; A5C99E0E262C9E3A00F7B1B7 /* Reachability in Frameworks */ = {isa = PBXBuildFile; productRef = A5C99E0D262C9E3A00F7B1B7 /* Reachability */; }; A5D87BA3262CA01D00DC28F0 /* ProcedureKit in Frameworks */ = {isa = PBXBuildFile; productRef = A5D87BA2262CA01D00DC28F0 /* ProcedureKit */; }; A5DBBABD262C7221004AC028 /* Clibsodium in Frameworks */ = {isa = PBXBuildFile; productRef = A5DBBABC262C7221004AC028 /* Clibsodium */; }; @@ -429,15 +399,10 @@ E90EA5C321BA8BF400A2CE25 /* DelegateDetailsViewController.xib in Resources */ = {isa = PBXBuildFile; fileRef = E90EA5C221BA8BF400A2CE25 /* DelegateDetailsViewController.xib */; }; E913C8F21FFFA51D001A83F7 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = E913C8F11FFFA51D001A83F7 /* AppDelegate.swift */; }; E913C8F91FFFA51D001A83F7 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = E913C8F81FFFA51D001A83F7 /* Assets.xcassets */; }; - E913C9081FFFA943001A83F7 /* AdamantCore.swift in Sources */ = {isa = PBXBuildFile; fileRef = E913C9071FFFA943001A83F7 /* AdamantCore.swift */; }; E9147B5F20500E9300145913 /* MyLittlePinpad+adamant.swift in Sources */ = {isa = PBXBuildFile; fileRef = E9147B5E20500E9300145913 /* MyLittlePinpad+adamant.swift */; }; E9147B612050599000145913 /* LoginViewController+QR.swift in Sources */ = {isa = PBXBuildFile; fileRef = E9147B602050599000145913 /* LoginViewController+QR.swift */; }; E9147B6320505C7500145913 /* QRCodeReader+adamant.swift in Sources */ = {isa = PBXBuildFile; fileRef = E9147B6220505C7500145913 /* QRCodeReader+adamant.swift */; }; E9147B6F205088DE00145913 /* LoginViewController+Pinpad.swift in Sources */ = {isa = PBXBuildFile; fileRef = E9147B6E205088DE00145913 /* LoginViewController+Pinpad.swift */; }; - E91947AC20001A9A001362F8 /* ApiService.swift in Sources */ = {isa = PBXBuildFile; fileRef = E91947AB20001A9A001362F8 /* ApiService.swift */; }; - 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 /* 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 */; }; @@ -494,7 +459,6 @@ E957E132229B10F80019732A /* NotificationViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = E957E131229B10F80019732A /* NotificationViewController.swift */; }; E957E135229B10F80019732A /* MainInterface.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = E957E133229B10F80019732A /* MainInterface.storyboard */; }; E957E139229B10F80019732A /* TransferNotificationContentExtension.appex in Embed App Extensions */ = {isa = PBXBuildFile; fileRef = E957E12D229B10F80019732A /* TransferNotificationContentExtension.appex */; platformFilter = ios; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; }; - 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 /* ChatListFactory.swift in Sources */ = {isa = PBXBuildFile; fileRef = E95F857E2008C8D60070534A /* ChatListFactory.swift */; }; @@ -504,19 +468,14 @@ E95F85C0200A51BB0070534A /* Account.json in Resources */ = {isa = PBXBuildFile; fileRef = E95F85BF200A51BB0070534A /* Account.json */; }; E95F85C7200A9B070070534A /* ChatTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = E95F85C5200A9B070070534A /* ChatTableViewCell.swift */; }; E95F85C8200A9B070070534A /* ChatTableViewCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = E95F85C6200A9B070070534A /* ChatTableViewCell.xib */; }; - E965A53020B594120041A3EA /* AdamantApi+States.swift in Sources */ = {isa = PBXBuildFile; fileRef = E965A52F20B594120041A3EA /* AdamantApi+States.swift */; }; E96BBE3121F70F5E009AA738 /* ReadonlyTextView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E96BBE3021F70F5E009AA738 /* ReadonlyTextView.swift */; }; E96BBE3321F71290009AA738 /* BuyAndSellViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = E96BBE3221F71290009AA738 /* BuyAndSellViewController.swift */; }; - E96D64B62295BED700CA5587 /* NormalizedTransaction.swift in Sources */ = {isa = PBXBuildFile; fileRef = E95F85682006AB9D0070534A /* NormalizedTransaction.swift */; }; E96D64BE2295C06400CA5587 /* JSAdamantCore.swift in Sources */ = {isa = PBXBuildFile; fileRef = E9220E0221983155009C9642 /* JSAdamantCore.swift */; }; E96D64BF2295C06400CA5587 /* adamant-core.js in Resources */ = {isa = PBXBuildFile; fileRef = E9220E0121983155009C9642 /* adamant-core.js */; }; E96D64C02295C06400CA5587 /* JSModels.swift in Sources */ = {isa = PBXBuildFile; fileRef = E95F856A200789450070534A /* JSModels.swift */; }; E96D64C12295C06400CA5587 /* JSAdamantCoreTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = E95F85762007E8EC0070534A /* JSAdamantCoreTests.swift */; }; E96D64C22295C06400CA5587 /* NativeCoreTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = E9220E07219879B9009C9642 /* NativeCoreTests.swift */; }; - E96D64C62295C3ED00CA5587 /* Mnemonic+extended.swift in Sources */ = {isa = PBXBuildFile; fileRef = 649E9A142111B3C200686B01 /* Mnemonic+extended.swift */; }; E96D64C82295C44400CA5587 /* Data+utilites.swift in Sources */ = {isa = PBXBuildFile; fileRef = E96D64C72295C44400CA5587 /* Data+utilites.swift */; }; - E96D64CA2295C4A800CA5587 /* WordList.swift in Sources */ = {isa = PBXBuildFile; fileRef = E96D64C92295C4A800CA5587 /* WordList.swift */; }; - E96D64CE2295C7F500CA5587 /* english.txt in Resources */ = {isa = PBXBuildFile; fileRef = E96D648C229570ED00CA5587 /* english.txt */; }; E96D64CF2295C82B00CA5587 /* Chat.json in Resources */ = {isa = PBXBuildFile; fileRef = E95F85C3200A540B0070534A /* Chat.json */; }; E96D64D02295C82B00CA5587 /* NormalizedTransaction.json in Resources */ = {isa = PBXBuildFile; fileRef = E95F85C1200A53E90070534A /* NormalizedTransaction.json */; }; E96D64D12295C82B00CA5587 /* TransactionChat.json in Resources */ = {isa = PBXBuildFile; fileRef = E95F85BD200A503A0070534A /* TransactionChat.json */; }; @@ -529,7 +488,6 @@ E9722066201F42BB004F2AAD /* CoreDataStack.swift in Sources */ = {isa = PBXBuildFile; fileRef = E9722065201F42BB004F2AAD /* CoreDataStack.swift */; }; E9722068201F42CC004F2AAD /* InMemoryCoreDataStack.swift in Sources */ = {isa = PBXBuildFile; fileRef = E9722067201F42CC004F2AAD /* InMemoryCoreDataStack.swift */; }; E972206B201F44CA004F2AAD /* TransfersProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = E972206A201F44CA004F2AAD /* TransfersProvider.swift */; }; - E9771D9E22997A6F0099AAC7 /* NativeCore+AdamantCore.swift in Sources */ = {isa = PBXBuildFile; fileRef = E9771D9D22997A6F0099AAC7 /* NativeCore+AdamantCore.swift */; }; E9771DA722997F310099AAC7 /* ServerResponseWithTimestamp.swift in Sources */ = {isa = PBXBuildFile; fileRef = E9771DA622997F310099AAC7 /* ServerResponseWithTimestamp.swift */; }; E983AE2120E655C500497E1A /* AccountHeaderView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E983AE2020E655C500497E1A /* AccountHeaderView.swift */; }; E983AE2A20E65F3200497E1A /* AccountViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = E983AE2820E65F3200497E1A /* AccountViewController.swift */; }; @@ -569,13 +527,7 @@ E9B4E1A8210F079E007E77FC /* DoubleDetailsTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = E9B4E1A7210F079E007E77FC /* DoubleDetailsTableViewCell.swift */; }; E9B4E1AA210F1803007E77FC /* DoubleDetailsTableViewCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = E9B4E1A9210F08BE007E77FC /* DoubleDetailsTableViewCell.xib */; }; E9C51ECF200E2D1100385EB7 /* FeeTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = E9C51ECE200E2D1100385EB7 /* FeeTests.swift */; }; - E9C51EEF20139DC600385EB7 /* TransactionIdResponse.swift in Sources */ = {isa = PBXBuildFile; fileRef = E9C51EEE20139DC600385EB7 /* TransactionIdResponse.swift */; }; E9C51EF12013F18000385EB7 /* NewChatViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = E9C51EF02013F18000385EB7 /* NewChatViewController.swift */; }; - E9CAE8D22018AA7700345E76 /* AdamantApi+Accounts.swift in Sources */ = {isa = PBXBuildFile; fileRef = E9CAE8D12018AA7700345E76 /* AdamantApi+Accounts.swift */; }; - E9CAE8D42018AC1800345E76 /* AdamantApi+Keys.swift in Sources */ = {isa = PBXBuildFile; fileRef = E9CAE8D32018AC1800345E76 /* AdamantApi+Keys.swift */; }; - E9CAE8D62018AC5300345E76 /* AdamantApi+Transactions.swift in Sources */ = {isa = PBXBuildFile; fileRef = E9CAE8D52018AC5300345E76 /* AdamantApi+Transactions.swift */; }; - E9CAE8D82018ACA700345E76 /* AdamantApi+Transfers.swift in Sources */ = {isa = PBXBuildFile; fileRef = E9CAE8D72018ACA700345E76 /* AdamantApi+Transfers.swift */; }; - E9CAE8DA2018ACD300345E76 /* AdamantApi+Chats.swift in Sources */ = {isa = PBXBuildFile; fileRef = E9CAE8D92018ACD300345E76 /* AdamantApi+Chats.swift */; }; E9D1BE1C211DABE100E86B72 /* WalletPagingItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = E9D1BE1B211DABE100E86B72 /* WalletPagingItem.swift */; }; E9DFB71C21624C9200CF8C7C /* AdmTransactionDetailsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = E9DFB71B21624C9200CF8C7C /* AdmTransactionDetailsViewController.swift */; }; E9E7CD8B20026B0600DFC4DB /* AccountService.swift in Sources */ = {isa = PBXBuildFile; fileRef = E9E7CD8A20026B0600DFC4DB /* AccountService.swift */; }; @@ -701,12 +653,9 @@ 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 = ""; }; - 3AA388022B67F47600125684 /* RPCResponseModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RPCResponseModel.swift; sourceTree = ""; }; 3AA388042B67F4DD00125684 /* BtcBlockchainInfoDTO.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BtcBlockchainInfoDTO.swift; sourceTree = ""; }; 3AA388062B67F53F00125684 /* BtcNetworkInfoDTO.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BtcNetworkInfoDTO.swift; sourceTree = ""; }; 3AA388092B69173500125684 /* DashNetworkInfoDTO.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DashNetworkInfoDTO.swift; sourceTree = ""; }; - 3AA3880B2B69201B00125684 /* ADM+JsonDecode.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "ADM+JsonDecode.swift"; sourceTree = ""; }; - 3AA3880D2B6A356900125684 /* RpcRequestModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RpcRequestModel.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 = ""; }; @@ -775,7 +724,6 @@ 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 = ""; }; - 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 = ""; }; 55E69E162868D7920025D82E /* CheckmarkView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CheckmarkView.swift; sourceTree = ""; }; @@ -798,8 +746,6 @@ 6449BA65235CA0930033B936 /* ERC20WalletService+Send.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "ERC20WalletService+Send.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 /* 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 = ""; }; @@ -833,7 +779,6 @@ 649D6BEB21BD5A53009E727B /* UISuffixTextField.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UISuffixTextField.swift; sourceTree = ""; }; 649D6BEF21BFF481009E727B /* AdamantChatsProvider+search.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "AdamantChatsProvider+search.swift"; sourceTree = ""; }; 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 = ""; }; 64B5736C2201E196005DC968 /* BtcTransactionsViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BtcTransactionsViewController.swift; sourceTree = ""; }; 64B5736E2209B892005DC968 /* BtcTransactionDetailsViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BtcTransactionDetailsViewController.swift; sourceTree = ""; }; @@ -855,8 +800,6 @@ 9304F8BD292F88F900173F18 /* ANSPayload.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ANSPayload.swift; sourceTree = ""; }; 9304F8C1292F895C00173F18 /* PushNotificationsTokenService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PushNotificationsTokenService.swift; sourceTree = ""; }; 9304F8C3292F8A3100173F18 /* AdamantPushNotificationsTokenService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AdamantPushNotificationsTokenService.swift; sourceTree = ""; }; - 9304F8C5292F971600173F18 /* ApiServiceResult.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ApiServiceResult.swift; sourceTree = ""; }; - 9304F8C7292F972600173F18 /* ApiServiceError.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ApiServiceError.swift; sourceTree = ""; }; 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 = ""; }; @@ -875,13 +818,6 @@ 93294B992AAD624100911109 /* WalletFactoryCompose.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WalletFactoryCompose.swift; sourceTree = ""; }; 932B34E82974AA4A002A75BA /* ChatPreservationProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChatPreservationProtocol.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 = ""; }; @@ -897,10 +833,7 @@ 93496BAA2A6CAED100DD062F /* Roboto_300_normal.ttf */ = {isa = PBXFileReference; lastKnownFileType = file; path = Roboto_300_normal.ttf; sourceTree = ""; }; 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 = ""; }; - 934A19162C5CD00500EA6E65 /* NodesMergingService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NodesMergingService.swift; sourceTree = ""; }; - 934A19182C5CD0BF00EA6E65 /* AdamantNodesMergingService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AdamantNodesMergingService.swift; sourceTree = ""; }; 93547BC929E2262D00B0914B /* WelcomeViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WelcomeViewController.swift; sourceTree = ""; }; - 9356589D2C60D29300E7D38A /* HealthCheckWrapper+Extension.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "HealthCheckWrapper+Extension.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 = ""; }; @@ -908,13 +841,16 @@ 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 = ""; }; 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 = ""; }; + 93760BD62C656CF8002507C3 /* DefaultNodesProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DefaultNodesProvider.swift; sourceTree = ""; }; + 93760BDD2C65A1FA002507C3 /* english.txt */ = {isa = PBXFileReference; lastKnownFileType = text; path = english.txt; sourceTree = ""; }; + 93760BDE2C65A284002507C3 /* WordList.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = WordList.swift; sourceTree = ""; }; + 93760BE02C65A2F3002507C3 /* Mnemonic+extended.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Mnemonic+extended.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 = ""; }; @@ -922,8 +858,6 @@ 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 = ""; }; @@ -943,13 +877,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 = ""; }; - 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 = ""; }; @@ -978,11 +908,6 @@ 93E123422A6DFE27004DF33B /* de */ = {isa = PBXFileReference; lastKnownFileType = text.plist.stringsdict; name = de; path = de.lproj/Localizable.stringsdict; sourceTree = ""; }; 93E123432A6DFE2E004DF33B /* ru */ = {isa = PBXFileReference; lastKnownFileType = text.plist.stringsdict; name = ru; path = ru.lproj/Localizable.stringsdict; sourceTree = ""; }; 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 /* 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 = ""; }; @@ -996,7 +921,6 @@ A50A410F2822FC35006BDFE1 /* BtcWalletService+Send.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "BtcWalletService+Send.swift"; sourceTree = ""; }; A50A41102822FC35006BDFE1 /* BtcTransferViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BtcTransferViewController.swift; sourceTree = ""; }; A578BDE42623051C00090141 /* DashWalletService+Transactions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "DashWalletService+Transactions.swift"; sourceTree = ""; }; - A5BBD810262C657300B5C40C /* ByteBackpacker.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ByteBackpacker.swift; sourceTree = ""; }; A5E04226282A8BDC0076CD13 /* BtcBalanceResponse.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BtcBalanceResponse.swift; sourceTree = ""; }; A5E04228282A998C0076CD13 /* BtcTransactionResponse.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BtcTransactionResponse.swift; sourceTree = ""; }; A5E0422A282AB18B0076CD13 /* BtcUnspentTransactionResponse.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BtcUnspentTransactionResponse.swift; sourceTree = ""; }; @@ -1042,15 +966,10 @@ E913C8F11FFFA51D001A83F7 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; E913C8F81FFFA51D001A83F7 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; E913C8FD1FFFA51E001A83F7 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; - E913C9071FFFA943001A83F7 /* AdamantCore.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AdamantCore.swift; sourceTree = ""; }; E9147B5E20500E9300145913 /* MyLittlePinpad+adamant.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "MyLittlePinpad+adamant.swift"; sourceTree = ""; }; E9147B602050599000145913 /* LoginViewController+QR.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "LoginViewController+QR.swift"; sourceTree = ""; }; E9147B6220505C7500145913 /* QRCodeReader+adamant.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "QRCodeReader+adamant.swift"; sourceTree = ""; }; E9147B6E205088DE00145913 /* LoginViewController+Pinpad.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "LoginViewController+Pinpad.swift"; sourceTree = ""; }; - E91947AB20001A9A001362F8 /* ApiService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ApiService.swift; sourceTree = ""; }; - 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 /* 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 = ""; }; @@ -1112,9 +1031,7 @@ E957E134229B10F80019732A /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/MainInterface.storyboard; sourceTree = ""; }; E957E136229B10F80019732A /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; E957E13D229B118E0019732A /* Release.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = Release.entitlements; sourceTree = ""; }; - E95F85682006AB9D0070534A /* NormalizedTransaction.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NormalizedTransaction.swift; sourceTree = ""; }; E95F856A200789450070534A /* JSModels.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = JSModels.swift; sourceTree = ""; }; - E95F856E2007B61D0070534A /* GetPublicKeyResponse.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GetPublicKeyResponse.swift; sourceTree = ""; }; 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 = ""; }; @@ -1129,12 +1046,9 @@ E95F85C3200A540B0070534A /* Chat.json */ = {isa = PBXFileReference; lastKnownFileType = text.json; path = Chat.json; sourceTree = ""; }; E95F85C5200A9B070070534A /* ChatTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChatTableViewCell.swift; sourceTree = ""; }; E95F85C6200A9B070070534A /* ChatTableViewCell.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = ChatTableViewCell.xib; sourceTree = ""; }; - E965A52F20B594120041A3EA /* AdamantApi+States.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "AdamantApi+States.swift"; sourceTree = ""; }; E96BBE3021F70F5E009AA738 /* ReadonlyTextView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReadonlyTextView.swift; sourceTree = ""; }; E96BBE3221F71290009AA738 /* BuyAndSellViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BuyAndSellViewController.swift; sourceTree = ""; }; - E96D648C229570ED00CA5587 /* english.txt */ = {isa = PBXFileReference; lastKnownFileType = text; path = english.txt; sourceTree = ""; }; E96D64C72295C44400CA5587 /* Data+utilites.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Data+utilites.swift"; sourceTree = ""; }; - E96D64C92295C4A800CA5587 /* WordList.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WordList.swift; sourceTree = ""; }; E96D64DB2295CD4700CA5587 /* NotificationServiceExtension.appex */ = {isa = PBXFileReference; explicitFileType = "wrapper.app-extension"; includeInIndex = 0; path = NotificationServiceExtension.appex; sourceTree = BUILT_PRODUCTS_DIR; }; E96D64DD2295CD4700CA5587 /* NotificationService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationService.swift; sourceTree = ""; }; E96D64DF2295CD4700CA5587 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; @@ -1145,7 +1059,6 @@ E9722067201F42CC004F2AAD /* InMemoryCoreDataStack.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = InMemoryCoreDataStack.swift; sourceTree = ""; }; E972206A201F44CA004F2AAD /* TransfersProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TransfersProvider.swift; sourceTree = ""; }; E9771D7D22995C870099AAC7 /* Debug.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = Debug.entitlements; sourceTree = ""; }; - E9771D9D22997A6F0099AAC7 /* NativeCore+AdamantCore.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "NativeCore+AdamantCore.swift"; sourceTree = ""; }; E9771DA622997F310099AAC7 /* ServerResponseWithTimestamp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ServerResponseWithTimestamp.swift; sourceTree = ""; }; E983AE2020E655C500497E1A /* AccountHeaderView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccountHeaderView.swift; sourceTree = ""; }; E983AE2820E65F3200497E1A /* AccountViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccountViewController.swift; sourceTree = ""; }; @@ -1189,13 +1102,7 @@ E9B994C122BFD723004CD645 /* Release.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = Release.entitlements; sourceTree = ""; }; E9B994C222BFD73F004CD645 /* Release.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = Release.entitlements; sourceTree = ""; }; E9C51ECE200E2D1100385EB7 /* FeeTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FeeTests.swift; sourceTree = ""; }; - E9C51EEE20139DC600385EB7 /* TransactionIdResponse.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TransactionIdResponse.swift; sourceTree = ""; }; E9C51EF02013F18000385EB7 /* NewChatViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NewChatViewController.swift; sourceTree = ""; }; - E9CAE8D12018AA7700345E76 /* AdamantApi+Accounts.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "AdamantApi+Accounts.swift"; sourceTree = ""; }; - E9CAE8D32018AC1800345E76 /* AdamantApi+Keys.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "AdamantApi+Keys.swift"; sourceTree = ""; }; - E9CAE8D52018AC5300345E76 /* AdamantApi+Transactions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "AdamantApi+Transactions.swift"; sourceTree = ""; }; - E9CAE8D72018ACA700345E76 /* AdamantApi+Transfers.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "AdamantApi+Transfers.swift"; sourceTree = ""; }; - E9CAE8D92018ACD300345E76 /* AdamantApi+Chats.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "AdamantApi+Chats.swift"; sourceTree = ""; }; E9D1BE1B211DABE100E86B72 /* WalletPagingItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WalletPagingItem.swift; sourceTree = ""; }; E9DFB71B21624C9200CF8C7C /* AdmTransactionDetailsViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AdmTransactionDetailsViewController.swift; sourceTree = ""; }; E9E7CD8A20026B0600DFC4DB /* AccountService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccountService.swift; sourceTree = ""; }; @@ -1255,7 +1162,6 @@ 9342F6C22A6A35E300A9B39F /* CommonKit in Frameworks */, A5F0A04B262C9CA90009672A /* Swinject in Frameworks */, A5DBBABD262C7221004AC028 /* Clibsodium in Frameworks */, - A50AEB14262C837900B37C22 /* Alamofire in Frameworks */, 938F7D582955C1DA001915CA /* MessageKit in Frameworks */, A530B0D82842110D003F0210 /* (null) in Frameworks */, A5DBBADC262C729B004AC028 /* CryptoSwift in Frameworks */, @@ -1677,6 +1583,14 @@ path = View; sourceTree = ""; }; + 93760BDC2C65A1FA002507C3 /* Mnemonic */ = { + isa = PBXGroup; + children = ( + 93760BDD2C65A1FA002507C3 /* english.txt */, + ); + path = Mnemonic; + sourceTree = ""; + }; 937736832B0949C700B35C7A /* NodeCell */ = { isa = PBXGroup; children = ( @@ -1860,8 +1774,6 @@ 93E5D4DE2930027F00439298 /* AdamantCore */ = { isa = PBXGroup; children = ( - E913C9071FFFA943001A83F7 /* AdamantCore.swift */, - 93E5D4DF2930029300439298 /* AdamantCore+Extensions.swift */, ); path = AdamantCore; sourceTree = ""; @@ -1978,8 +1890,6 @@ E9B3D398201F90320019EB36 /* DataProviders */, E9E7CD8A20026B0600DFC4DB /* AccountService.swift */, 6455E9F021075D3600B2E94C /* AddressBookService.swift */, - E91947AB20001A9A001362F8 /* ApiService.swift */, - 9338AE832AEF5EFA001D32DF /* APICoreProtocol.swift */, 3AA2D5F6280EADE3000ED971 /* SocketService.swift */, 4164A9D628F17D4000EEF16D /* ChatTransactionService.swift */, 648BCA6C213D384F00875EB5 /* AvatarService.swift */, @@ -1999,13 +1909,10 @@ 3A7BD00D2AA9BCE80045AAB0 /* VibroService.swift */, 41C1698B29E7F34900FEB3CB /* RichTransactionReplyService.swift */, 3A4193902A580C85006A6B22 /* RichTransactionReactService.swift */, - 9338AE7E2AEF43DA001D32DF /* NodesStorageProtocol.swift */, - 93ADC17C2B083C3B00F2DF77 /* NodesAdditionalParamsStorageProtocol.swift */, 3A2F55FB2AC6F885000A3F26 /* CoinStorage.swift */, 3A96E37B2AED27F8001F5A52 /* PartnerQRService.swift */, 3AF08D602B4EB3C400EB82B1 /* LanguageStorageProtocol.swift */, 265AA1612B74E6B900CF98B0 /* ChatPreservation.swift */, - 934A19162C5CD00500EA6E65 /* NodesMergingService.swift */, ); path = ServiceProtocols; sourceTree = ""; @@ -2017,7 +1924,6 @@ 41C1698A29E7F2EE00FEB3CB /* RichTransactionReplyService */, 935F53D429BE8F4800779492 /* RichTransactionStatusService */, 3AA2D5F8280EAF49000ED971 /* SocketService */, - E9CAE8D02018AA5000345E76 /* ApiService */, E9B3D39F201FA2090019EB36 /* DataProviders */, E9E7CD922002740500DFC4DB /* AdamantAccountService.swift */, 6455E9F221075D8000B2E94C /* AdamantAddressBookService.swift */, @@ -2033,17 +1939,10 @@ 3A7BD00F2AA9BD030045AAB0 /* AdamantVibroService.swift */, E921597420611A6A0000CA5C /* AdamantReachability.swift */, E950273F202E257E002C1098 /* RepeaterService.swift */, - E9771D9D22997A6F0099AAC7 /* NativeCore+AdamantCore.swift */, 9304F8C3292F8A3100173F18 /* AdamantPushNotificationsTokenService.swift */, - 9338AE852AEF6A97001D32DF /* APICore.swift */, - 938A46A32AE6103E00FC03DB /* HealthCheckWrapper.swift */, - 938A46A52AE6106300FC03DB /* BlockchainHealthCheckWrapper.swift */, - 9338AE802AEF4B8E001D32DF /* NodesStorage.swift */, - 93ADC17E2B083D7A00F2DF77 /* NodesAdditionalParamsStorage.swift */, 3A2F55FD2AC6F90E000A3F26 /* AdamantCoinStorageService.swift */, 3A96E3792AED27D7001F5A52 /* AdamantPartnerQRService.swift */, 3AF08D5E2B4EB3A200EB82B1 /* LanguageService.swift */, - 934A19182C5CD0BF00EA6E65 /* AdamantNodesMergingService.swift */, ); path = Services; sourceTree = ""; @@ -2053,9 +1952,7 @@ children = ( E91947B72000326B001362F8 /* ServerResponses */, E95F859220094B8E0070534A /* CoreData */, - E91947B320002809001362F8 /* AdamantAccount.swift */, E9393FA92055D03300EE6F30 /* AdamantMessage.swift */, - 644EC34E20EFA77A00F40C73 /* Delegate.swift */, 648CE39F22999C890070A2CC /* BaseBtcTransaction.swift */, 648CE3A122999CE70070A2CC /* BTCRawTransaction.swift */, 648DD7A12237D9A000B811FD /* DogeTransaction.swift */, @@ -2064,26 +1961,13 @@ E940086A2114A70600CD2D67 /* LskAccount.swift */, E9204B5120C9762300F3B9AB /* MessageStatus.swift */, E9A03FD320DBC824007653A1 /* NodeVersion.swift */, - E95F85682006AB9D0070534A /* NormalizedTransaction.swift */, - 93E5D4DA293000BE00439298 /* UnregisteredTransaction.swift */, - 5558A437282AB9390024DDD6 /* NodeStatus.swift */, E9FCA1E5218334C00005E83D /* SimpleTransactionDetails.swift */, E971591921681D6900A5F904 /* TransactionStatus.swift */, 648C696E22915A12006645F5 /* DashTransaction.swift */, 9304F8BD292F88F900173F18 /* ANSPayload.swift */, - 9304F8C5292F971600173F18 /* ApiServiceResult.swift */, - 9304F8C7292F972600173F18 /* ApiServiceError.swift */, 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 */, - 3AA388022B67F47600125684 /* RPCResponseModel.swift */, - 3AA3880D2B6A356900125684 /* RpcRequestModel.swift */, ); path = Models; sourceTree = ""; @@ -2091,9 +1975,7 @@ E913C9101FFFAA4B001A83F7 /* Helpers */ = { isa = PBXGroup; children = ( - 9356589D2C60D29300E7D38A /* HealthCheckWrapper+Extension.swift */, 93775E452A674FA9009061AC /* Markdown+Adamant.swift */, - E91947B12000246A001362F8 /* AdamantError.swift */, E9061B96207501E40011F104 /* AdamantUserInfoKey.swift */, E94008862114F05B00CD2D67 /* AddressValidationResult.swift */, E940088E2119A9E800CD2D67 /* BigInt+Decimal.swift */, @@ -2117,12 +1999,10 @@ 41A1994129D2D3920031AD75 /* SwipePanGestureRecognizer.swift */, 4193AE1529FBEFBF002F21BE /* NSAttributedText+Adamant.swift */, 93294B832AAD0C8F00911109 /* Assembler+Extension.swift */, - 93E8EDD02AF1DF8E003E163C /* ServerResponse+Resolver.swift */, 93CCAE7F2B06E2D100EA5B94 /* ApiServiceError+Extension.swift */, 939FA3412B0D6F0000710EC6 /* SelfRemovableHostingController.swift */, 936658942B0AC15300BDB2D3 /* Node+UI.swift */, 3AF53F8C2B3DCFA300B30312 /* NodeGroup+Constants.swift */, - 3AA3880B2B69201B00125684 /* ADM+JsonDecode.swift */, ); path = Helpers; sourceTree = ""; @@ -2130,8 +2010,8 @@ E913C9111FFFAB05001A83F7 /* Assets */ = { isa = PBXGroup; children = ( + 93760BDC2C65A1FA002507C3 /* Mnemonic */, 93496BA12A6CAED100DD062F /* Fonts */, - E96D64CD2295C54600CA5587 /* Mnemonic */, E913C8F81FFFA51D001A83F7 /* Assets.xcassets */, E9A174B820587B83003667CD /* notification.mp3 */, 4198D57A28C8B7DA009337F2 /* so-proud-notification.mp3 */, @@ -2184,8 +2064,6 @@ isa = PBXGroup; children = ( E933475A225539390083839E /* DogeGetTransactionsResponse.swift */, - E95F856E2007B61D0070534A /* GetPublicKeyResponse.swift */, - E9C51EEE20139DC600385EB7 /* TransactionIdResponse.swift */, E9771DA622997F310099AAC7 /* ServerResponseWithTimestamp.swift */, 648C697022915CB8006645F5 /* BTCRPCServerResponce.swift */, ); @@ -2240,7 +2118,6 @@ 6403F5DC22723C2800D58779 /* Dash */, 6449BA5D235CA0930033B936 /* ERC20 */, E94008712114EACF00CD2D67 /* WalletAccount.swift */, - 9366589A2B0AD3E600BDB2D3 /* WalletApiService.swift */, E99818932120892F0018C84C /* WalletViewControllerBase.swift */, E9981897212096ED0018C84C /* WalletViewControllerBase.xib */, E9EC342020052ABB00C0E546 /* TransferViewControllerBase.swift */, @@ -2297,13 +2174,12 @@ E950651F20404997008352E5 /* Utilities */ = { isa = PBXGroup; children = ( - A5BBD810262C657300B5C40C /* ByteBackpacker.swift */, + 93760BDE2C65A284002507C3 /* WordList.swift */, E9942B83203CBFCE00C163AF /* AdamantQRTools.swift */, E950652220404C84008352E5 /* AdamantUriTools.swift */, + 93760BE02C65A2F3002507C3 /* Mnemonic+extended.swift */, 41E3C9CB2A0E20F500AF0985 /* AdamantCoinTools.swift */, E9E7CDB62003994E00DFC4DB /* AdamantUtilities+extended.swift */, - 649E9A142111B3C200686B01 /* Mnemonic+extended.swift */, - E96D64C92295C4A800CA5587 /* WordList.swift */, ); path = Utilities; sourceTree = ""; @@ -2405,14 +2281,6 @@ path = Core; sourceTree = ""; }; - E96D64CD2295C54600CA5587 /* Mnemonic */ = { - isa = PBXGroup; - children = ( - E96D648C229570ED00CA5587 /* english.txt */, - ); - path = Mnemonic; - sourceTree = ""; - }; E96D64DC2295CD4700CA5587 /* NotificationServiceExtension */ = { isa = PBXGroup; children = ( @@ -2468,26 +2336,11 @@ E9A174B42057EDCE003667CD /* AdamantTransfersProvider+backgroundFetch.swift */, 4164A9D828F17DA700EEF16D /* AdamantChatTransactionService.swift */, E9722067201F42CC004F2AAD /* InMemoryCoreDataStack.swift */, + 93760BD62C656CF8002507C3 /* DefaultNodesProvider.swift */, ); path = DataProviders; sourceTree = ""; }; - E9CAE8D02018AA5000345E76 /* ApiService */ = { - isa = PBXGroup; - children = ( - 93E8EDCC2AF1BD65003E163C /* AdamantApiCore.swift */, - E91947AF20002393001362F8 /* AdamantApiService.swift */, - E9CAE8D12018AA7700345E76 /* AdamantApi+Accounts.swift */, - E9CAE8D92018ACD300345E76 /* AdamantApi+Chats.swift */, - E9CAE8D32018AC1800345E76 /* AdamantApi+Keys.swift */, - E9CAE8D52018AC5300345E76 /* AdamantApi+Transactions.swift */, - E9CAE8D72018ACA700345E76 /* AdamantApi+Transfers.swift */, - E965A52F20B594120041A3EA /* AdamantApi+States.swift */, - 644EC34C20EFA60900F40C73 /* AdamantApi+Delegates.swift */, - ); - path = ApiService; - sourceTree = ""; - }; E9E7CDA52002AE1C00DFC4DB /* Account */ = { isa = PBXGroup; children = ( @@ -2611,7 +2464,6 @@ A5DBBAEF262C72EF004AC028 /* LiskKit */, A50AEB03262C815200B37C22 /* EFQRCode */, A50AEB0B262C81E300B37C22 /* QRCodeReader */, - A50AEB13262C837900B37C22 /* Alamofire */, A5F92993262C855B00C3E60A /* MarkdownKit */, A57282C9262C94CD00C96FA8 /* DateToolsSwift */, A544F0D3262C9878001F1A6D /* Eureka */, @@ -2778,7 +2630,6 @@ A5DBBADA262C729B004AC028 /* XCRemoteSwiftPackageReference "CryptoSwift" */, A50AEB02262C815200B37C22 /* XCRemoteSwiftPackageReference "EFQRCode" */, A50AEB0A262C81E300B37C22 /* XCRemoteSwiftPackageReference "QRCodeReader.swift" */, - A50AEB12262C837900B37C22 /* XCRemoteSwiftPackageReference "Alamofire" */, A5F92992262C855A00C3E60A /* XCRemoteSwiftPackageReference "MarkdownKit" */, A57282C8262C94CD00C96FA8 /* XCRemoteSwiftPackageReference "DateTools" */, A544F0D2262C9878001F1A6D /* XCRemoteSwiftPackageReference "Eureka" */, @@ -2851,13 +2702,13 @@ E94E7B0C205D5E4A0042B639 /* TransactionsListViewControllerBase.xib in Resources */, E9484B7A227CA93B008E10F0 /* BalanceTableViewCell.xib in Resources */, 6406D74A21C7F06000196713 /* SearchResultsViewController.xib in Resources */, - E96D64CE2295C7F500CA5587 /* english.txt in Resources */, E9B4E1AA210F1803007E77FC /* DoubleDetailsTableViewCell.xib in Resources */, 6458548C211B3AB1004C5909 /* WelcomeViewController.xib in Resources */, 93496BAF2A6CAED100DD062F /* Exo+2_100_normal.ttf in Resources */, 645938952378395E00A2BE7C /* EulaViewController.xib in Resources */, 93496BA02A6CAE9300DD062F /* LogoFullHeader.xib in Resources */, E9A174B920587B84003667CD /* notification.mp3 in Resources */, + 93760BE22C65A424002507C3 /* english.txt in Resources */, 645FEB35213E72C100D6BA2D /* OnboardViewController.xib in Resources */, E948E0482024F02700975D6B /* VersionFooter.xib in Resources */, E9FAE5E3203ED1AE008D3A6B /* ShareQrViewController.xib in Resources */, @@ -3011,14 +2862,11 @@ 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 */, 93ADE0732ACA66AF008ED641 /* VibrationSelectionFactory.swift in Sources */, - 9366589B2B0AD3E600BDB2D3 /* WalletApiService.swift in Sources */, 3AFE7E522B1F6B3400718739 /* WalletServiceProtocol.swift in Sources */, - 934A19192C5CD0BF00EA6E65 /* AdamantNodesMergingService.swift in Sources */, 937751AB2A68BB390054BD65 /* ChatTransactionCell.swift in Sources */, 64A223D620F760BB005157CB /* Localization.swift in Sources */, 64E1C82F222E95F6006C4DA7 /* DogeWallet.swift in Sources */, @@ -3030,7 +2878,7 @@ 3AFE7E432B19E4D900718739 /* WalletServiceCompose.swift in Sources */, 3A26D93D2C3C1CC3003AD832 /* KlyNodeApiService.swift in Sources */, 93A118512993167500E144CC /* ChatMessageBackgroundColor.swift in Sources */, - E9CAE8D22018AA7700345E76 /* AdamantApi+Accounts.swift in Sources */, + 93760BD72C656CF8002507C3 /* DefaultNodesProvider.swift in Sources */, 3A26D93B2C3C1C97003AD832 /* KlyApiCore.swift in Sources */, 936658A32B0ADE4400BDB2D3 /* CoinsNodesListView+Row.swift in Sources */, 648CE3A6229AD1CD0070A2CC /* DashWalletService+Send.swift in Sources */, @@ -3053,11 +2901,9 @@ E94008872114F05B00CD2D67 /* AddressValidationResult.swift in Sources */, E9E7CD8F20026CD300DFC4DB /* AdamantDialogService.swift in Sources */, E993301E212EF39700CD5200 /* EthTransferViewController.swift in Sources */, - 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 */, 93FC16A12B01DE120062B507 /* ERC20ApiService.swift in Sources */, 93294B9A2AAD624100911109 /* WalletFactoryCompose.swift in Sources */, @@ -3081,8 +2927,6 @@ E933475B225539390083839E /* DogeGetTransactionsResponse.swift in Sources */, 9340078029AC341100A20622 /* ChatAction.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 */, @@ -3091,8 +2935,6 @@ 9304F8BE292F88F900173F18 /* ANSPayload.swift in Sources */, 41CA598C29A0D84F002BFDE4 /* TaskManager.swift in Sources */, E9E7CD9120026FA100DFC4DB /* AppAssembly.swift in Sources */, - E96D64CA2295C4A800CA5587 /* WordList.swift in Sources */, - 3AA3880C2B69201B00125684 /* ADM+JsonDecode.swift in Sources */, 64BD2B7520E2814B00E2CD36 /* EthTransaction.swift in Sources */, 93B28EC22B076D31007F268B /* DashApiService.swift in Sources */, E908472B2196FEA80095825D /* RichMessageTransaction+CoreDataProperties.swift in Sources */, @@ -3115,7 +2957,6 @@ 64F085D920E2D7600006DE68 /* AdmTransactionsViewController.swift in Sources */, 9322E87B2970431200B8357C /* ChatMessageFactory.swift in Sources */, 648DD7AA2239150E00B811FD /* DogeWalletService+RichMessageProviderWithStatusCheck.swift in Sources */, - 9304F8C8292F972600173F18 /* ApiServiceError.swift in Sources */, E9D1BE1C211DABE100E86B72 /* WalletPagingItem.swift in Sources */, E940086E2114AA2E00CD2D67 /* WalletCoreProtocol.swift in Sources */, 645FEB34213E72C100D6BA2D /* OnboardViewController.swift in Sources */, @@ -3125,7 +2966,6 @@ 3A96E37C2AED27F8001F5A52 /* PartnerQRService.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 */, @@ -3143,7 +2983,6 @@ E9147B612050599000145913 /* LoginViewController+QR.swift in Sources */, 9399F5ED29A85A48006C3E30 /* ChatCacheService.swift in Sources */, 3A9015A92A615893002A2464 /* ChatMessagesListViewModel.swift in Sources */, - 934A19172C5CD00500EA6E65 /* NodesMergingService.swift in Sources */, 936658952B0AC15300BDB2D3 /* Node+UI.swift in Sources */, 26A976012B7E852E0095C367 /* ChatSelectTextViewFactory.swift in Sources */, 41A1995429D56E340031AD75 /* ChatMessageReplyCell.swift in Sources */, @@ -3172,9 +3011,7 @@ 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 */, - E91947B420002809001362F8 /* AdamantAccount.swift in Sources */, 3AA50DF12AEBE66A00C58FC8 /* PartnerQRViewModel.swift in Sources */, 6449BA6C235CA0930033B936 /* ERC20WalletViewController.swift in Sources */, E9E7CDC22003F5A400DFC4DB /* TransactionsListViewControllerBase.swift in Sources */, @@ -3182,7 +3019,6 @@ 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 */, E908472C2196FEA80095825D /* CoreDataAccount+CoreDataClass.swift in Sources */, @@ -3190,24 +3026,18 @@ E9393FAA2055D03300EE6F30 /* AdamantMessage.swift in Sources */, 938F7D692955C9EC001915CA /* ChatViewModel.swift in Sources */, E90A494D204DA932009F6A65 /* LocalAuthentication.swift in Sources */, - E96D64C62295C3ED00CA5587 /* Mnemonic+extended.swift in Sources */, 3A26D9352C3C1BE2003AD832 /* KlyWalletService.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 */, 3A26D94B2C3D3838003AD832 /* KlyTransactionsViewController.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 */, - 9338AE862AEF6A97001D32DF /* APICore.swift in Sources */, 938F7D5D2955C8F9001915CA /* ChatLayoutManager.swift in Sources */, 6403F5DE22723C6800D58779 /* DashMainnet.swift in Sources */, E940088B2114F63000CD2D67 /* NSRegularExpression+adamant.swift in Sources */, 932B34E92974AA4A002A75BA /* ChatPreservationProtocol.swift in Sources */, - 3AA388032B67F47600125684 /* RPCResponseModel.swift in Sources */, E9484B7F2285C016008E10F0 /* PKGeneratorViewController.swift in Sources */, E9B3D3A9202082450019EB36 /* AdamantTransfersProvider.swift in Sources */, 6449BA71235CA0930033B936 /* ERC20WalletService+RichMessageProvider.swift in Sources */, @@ -3222,7 +3052,6 @@ 3AF08D5F2B4EB3A200EB82B1 /* LanguageService.swift in Sources */, E9AA8BFA212C166600F9249F /* EthWalletService+Send.swift in Sources */, 411743042A39B257008CD98A /* ContributeViewModel.swift in Sources */, - 93E5D4DB293000BE00439298 /* UnregisteredTransaction.swift in Sources */, 411DB8332A14D01F006AB158 /* ChatKeyboardManager.swift in Sources */, 6449BA68235CA0930033B936 /* ERC20WalletService.swift in Sources */, 3A9365A92C41332F0073D9A7 /* KLYWalletService+DynamicConstants.swift in Sources */, @@ -3231,15 +3060,12 @@ 9345769528FD0C34004E6C7A /* UIViewController+email.swift in Sources */, 64E1C831222E9617006C4DA7 /* DogeWalletService.swift in Sources */, 269E13522B594B2D008D1CA7 /* AccountFooterView.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 */, - 3AA3880E2B6A356900125684 /* RpcRequestModel.swift in Sources */, - 93E8EDCD2AF1BD65003E163C /* AdamantApiCore.swift in Sources */, E9942B87203D9E5100C163AF /* EurekaQRRow.swift in Sources */, 3AA50DF32AEBE67C00C58FC8 /* PartnerQRFactory.swift in Sources */, E9AA8C02212C5BF500F9249F /* AdmWalletService+Send.swift in Sources */, @@ -3256,12 +3082,9 @@ 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 */, A50A41092822F8CE006BDFE1 /* BtcWalletViewController.swift in Sources */, E9722066201F42BB004F2AAD /* CoreDataStack.swift in Sources */, 93EE9C3329C2666200D9853F /* TransactionStatusSubscription.swift in Sources */, - 9338AE7F2AEF43DA001D32DF /* NodesStorageProtocol.swift in Sources */, 93EE9C3329C2666200D9853F /* TransactionStatusSubscription.swift in Sources */, E913C8F21FFFA51D001A83F7 /* AppDelegate.swift in Sources */, 648CE3A222999CE70070A2CC /* BTCRawTransaction.swift in Sources */, @@ -3273,10 +3096,9 @@ 64E1C82D222E95E2006C4DA7 /* DogeWalletFactory.swift in Sources */, E90055F920ECD86800D0CB2D /* SecurityViewController+StayIn.swift in Sources */, E90847322196FEA80095825D /* TransferTransaction+CoreDataClass.swift in Sources */, - 9304F8C6292F971600173F18 /* ApiServiceResult.swift in Sources */, + 93760BDF2C65A284002507C3 /* WordList.swift in Sources */, 93996A972968209C008D080B /* ChatMessagesCollection.swift in Sources */, 645AE06621E67D3300AD3623 /* UITextField+adamant.swift in Sources */, - 93E5D4E02930029300439298 /* AdamantCore+Extensions.swift in Sources */, 41047B72294B5F210039E956 /* VisibleWalletsTableViewCell.swift in Sources */, E90847392196FEF50095825D /* BaseTransaction+TransactionDetails.swift in Sources */, 649D6BF021BFF481009E727B /* AdamantChatsProvider+search.swift in Sources */, @@ -3289,22 +3111,17 @@ E940087B2114ED0600CD2D67 /* EthWalletService.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 */, 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 */, - E91947AC20001A9A001362F8 /* ApiService.swift in Sources */, 4164A9D928F17DA700EEF16D /* AdamantChatTransactionService.swift in Sources */, E993302221354BC300CD5200 /* EthWalletFactory.swift in Sources */, 418FDE502A25CA340055E3CD /* ChatMenuManager.swift in Sources */, E90055F520EBF5DA00D0CB2D /* AboutViewController.swift in Sources */, - E965A53020B594120041A3EA /* AdamantApi+States.swift in Sources */, E908473D219713300095825D /* NotificationsViewController.swift in Sources */, E908472E2196FEA80095825D /* BaseTransaction+CoreDataClass.swift in Sources */, 64D059FF20D3116B003AD655 /* NodesListViewController.swift in Sources */, @@ -3314,13 +3131,10 @@ E90847312196FEA80095825D /* ChatTransaction+CoreDataProperties.swift in Sources */, 3A2F55FA2AC6F308000A3F26 /* CoinTransaction+CoreDataProperties.swift in Sources */, E99330262136B0E500CD5200 /* TransferViewControllerBase+QR.swift in Sources */, - 93ADC17F2B083D7A00F2DF77 /* NodesAdditionalParamsStorage.swift in Sources */, E9B1AA5B21283E0F00080A2A /* AdmTransferViewController.swift in Sources */, 3A26D94D2C3D387B003AD832 /* KlyTransactionDetailsViewController.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 */, E9B3D3A1201FA26B0019EB36 /* AdamantAccountsProvider.swift in Sources */, @@ -3348,8 +3162,6 @@ 93A18C892AAEAE7700D0AB98 /* WalletFactory.swift in Sources */, E923222621135F9000A7E5AF /* EthAccount.swift in Sources */, E9061B97207501E40011F104 /* AdamantUserInfoKey.swift in Sources */, - E9CAE8D62018AC5300345E76 /* AdamantApi+Transactions.swift in Sources */, - 9356589E2C60D29300E7D38A /* HealthCheckWrapper+Extension.swift in Sources */, E93D7ABE2052CEE1005D19DC /* NotificationsService.swift in Sources */, E940087D2114EDEE00CD2D67 /* EthWallet.swift in Sources */, A5E0422B282AB18B0076CD13 /* BtcUnspentTransactionResponse.swift in Sources */, @@ -3367,7 +3179,6 @@ 4186B334294200C5006594A3 /* EthWalletService+DynamicConstants.swift in Sources */, 3A26D93F2C3C1CED003AD832 /* KlyServiceApiService.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 */, 938F7D5B2955C8DA001915CA /* ChatDisplayManager.swift in Sources */, @@ -3400,7 +3211,6 @@ 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 */, @@ -3412,17 +3222,13 @@ 4164A9D728F17D4000EEF16D /* ChatTransactionService.swift in Sources */, E90A4943204C5ED6009F6A65 /* EurekaPassphraseRow.swift in Sources */, 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 */, - E9771D9E22997A6F0099AAC7 /* NativeCore+AdamantCore.swift in Sources */, 416380E12A51765F00F90E6D /* ChatReactionsView.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 */, 3A26D9452C3D336A003AD832 /* KlyWalletService+RichMessageProvider.swift in Sources */, @@ -3438,7 +3244,7 @@ 64E1C833222EA0F0006C4DA7 /* DogeWalletViewController.swift in Sources */, E93EB09F20DA3FA4001F9601 /* NodesEditorFactory.swift in Sources */, 93294B8F2AAD2C6B00911109 /* SwiftyOnboard.swift in Sources */, - 93ADC17B2B08283500F2DF77 /* ForceQueryItemsEncoding.swift in Sources */, + 93760BE12C65A2F3002507C3 /* Mnemonic+extended.swift in Sources */, 41BCB310295C6082004B12AB /* VisibleWalletsResetTableViewCell.swift in Sources */, 93CCAE7B2B06D9B500EA5B94 /* DogeBlocksDTO.swift in Sources */, 3AF08D612B4EB3C400EB82B1 /* LanguageStorageProtocol.swift in Sources */, @@ -3464,7 +3270,6 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( - 93E5D4DD293000BE00439298 /* UnregisteredTransaction.swift in Sources */, E957E132229B10F80019732A /* NotificationViewController.swift in Sources */, 93E1234D2A6DFF62004DF33B /* NotificationStrings.swift in Sources */, ); @@ -3475,7 +3280,6 @@ buildActionMask = 2147483647; files = ( E96D64DE2295CD4700CA5587 /* NotificationService.swift in Sources */, - 93E5D4DC293000BE00439298 /* UnregisteredTransaction.swift in Sources */, 93E1234C2A6DFF62004DF33B /* NotificationStrings.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; @@ -4117,14 +3921,6 @@ minimumVersion = 10.1.1; }; }; - A50AEB12262C837900B37C22 /* XCRemoteSwiftPackageReference "Alamofire" */ = { - isa = XCRemoteSwiftPackageReference; - repositoryURL = "https://github.com/Alamofire/Alamofire.git"; - requirement = { - kind = upToNextMinorVersion; - minimumVersion = 5.4.2; - }; - }; A544F0D2262C9878001F1A6D /* XCRemoteSwiftPackageReference "Eureka" */ = { isa = XCRemoteSwiftPackageReference; repositoryURL = "https://github.com/xmartlabs/Eureka.git"; @@ -4282,11 +4078,6 @@ package = A50AEB0A262C81E300B37C22 /* XCRemoteSwiftPackageReference "QRCodeReader.swift" */; productName = QRCodeReader; }; - A50AEB13262C837900B37C22 /* Alamofire */ = { - isa = XCSwiftPackageProductDependency; - package = A50AEB12262C837900B37C22 /* XCRemoteSwiftPackageReference "Alamofire" */; - productName = Alamofire; - }; A5241B6F262DEDE1009FA43E /* Clibsodium */ = { isa = XCSwiftPackageProductDependency; package = A5DBBABB262C7221004AC028 /* XCRemoteSwiftPackageReference "swift-sodium" */; diff --git a/Adamant/App/DI/AppAssembly.swift b/Adamant/App/DI/AppAssembly.swift index dc9766090..94c69bbc8 100644 --- a/Adamant/App/DI/AppAssembly.swift +++ b/Adamant/App/DI/AppAssembly.swift @@ -96,7 +96,8 @@ struct AppAssembly: Assembly { container.register(NodesStorageProtocol.self) { r in NodesStorage( securedStore: r.resolve(SecuredStore.self)!, - nodesMergingService: r.resolve(NodesMergingService.self)! + nodesMergingService: r.resolve(NodesMergingService.self)!, + defaultNodes: r.resolve(DefaultNodesProvider.self)!.nodes ) }.inObjectScope(.container) @@ -117,7 +118,9 @@ struct AppAssembly: Assembly { service: .init(apiCore: r.resolve(APICoreProtocol.self)!), nodesStorage: r.resolve(NodesStorageProtocol.self)!, nodesAdditionalParamsStorage: r.resolve(NodesAdditionalParamsStorageProtocol.self)!, - nodeGroup: .adm + isActive: true, + params: NodeGroup.adm.blockchainHealthCheckParams, + connection: r.resolve(ReachabilityMonitor.self)!.connectionPublisher ), adamantCore: r.resolve(AdamantCore.self)! ) @@ -129,7 +132,9 @@ struct AppAssembly: Assembly { service: .init(apiCore: r.resolve(APICoreProtocol.self)!), nodesStorage: r.resolve(NodesStorageProtocol.self)!, nodesAdditionalParamsStorage: r.resolve(NodesAdditionalParamsStorageProtocol.self)!, - nodeGroup: .btc + isActive: true, + params: NodeGroup.btc.blockchainHealthCheckParams, + connection: r.resolve(ReachabilityMonitor.self)!.connectionPublisher )) }.inObjectScope(.container) @@ -139,7 +144,9 @@ struct AppAssembly: Assembly { service: .init(apiCore: r.resolve(APICoreProtocol.self)!), nodesStorage: r.resolve(NodesStorageProtocol.self)!, nodesAdditionalParamsStorage: r.resolve(NodesAdditionalParamsStorageProtocol.self)!, - nodeGroup: .doge + isActive: true, + params: NodeGroup.doge.blockchainHealthCheckParams, + connection: r.resolve(ReachabilityMonitor.self)!.connectionPublisher )) }.inObjectScope(.container) @@ -149,7 +156,9 @@ struct AppAssembly: Assembly { service: .init(apiCore: r.resolve(APICoreProtocol.self)!), nodesStorage: r.resolve(NodesStorageProtocol.self)!, nodesAdditionalParamsStorage: r.resolve(NodesAdditionalParamsStorageProtocol.self)!, - nodeGroup: .dash + isActive: true, + params: NodeGroup.dash.blockchainHealthCheckParams, + connection: r.resolve(ReachabilityMonitor.self)!.connectionPublisher )) }.inObjectScope(.container) @@ -159,7 +168,9 @@ struct AppAssembly: Assembly { service: .init(), nodesStorage: r.resolve(NodesStorageProtocol.self)!, nodesAdditionalParamsStorage: r.resolve(NodesAdditionalParamsStorageProtocol.self)!, - nodeGroup: .klyNode + isActive: true, + params: NodeGroup.klyNode.blockchainHealthCheckParams, + connection: r.resolve(ReachabilityMonitor.self)!.connectionPublisher )) }.inObjectScope(.container) @@ -169,7 +180,9 @@ struct AppAssembly: Assembly { service: .init(), nodesStorage: r.resolve(NodesStorageProtocol.self)!, nodesAdditionalParamsStorage: r.resolve(NodesAdditionalParamsStorageProtocol.self)!, - nodeGroup: .klyService + isActive: true, + params: NodeGroup.klyService.blockchainHealthCheckParams, + connection: r.resolve(ReachabilityMonitor.self)!.connectionPublisher )) }.inObjectScope(.container) @@ -184,7 +197,9 @@ struct AppAssembly: Assembly { service: .init(apiCore: r.resolve(APICoreProtocol.self)!), nodesStorage: r.resolve(NodesStorageProtocol.self)!, nodesAdditionalParamsStorage: r.resolve(NodesAdditionalParamsStorageProtocol.self)!, - nodeGroup: .eth + isActive: true, + params: NodeGroup.eth.blockchainHealthCheckParams, + connection: r.resolve(ReachabilityMonitor.self)!.connectionPublisher )) }.inObjectScope(.container) @@ -364,5 +379,10 @@ struct AppAssembly: Assembly { container.register(NodesMergingService.self) { r in AdamantNodesMergingService() }.inObjectScope(.transient) + + // MARK: DefaultNodesProvider + container.register(DefaultNodesProvider.self) { r in + DefaultNodesProvider() + }.inObjectScope(.transient) } } diff --git a/Adamant/Helpers/ApiServiceError+Extension.swift b/Adamant/Helpers/ApiServiceError+Extension.swift index b07097854..a1610bea2 100644 --- a/Adamant/Helpers/ApiServiceError+Extension.swift +++ b/Adamant/Helpers/ApiServiceError+Extension.swift @@ -7,16 +7,36 @@ // import Alamofire +import CommonKit -extension ApiServiceError { - init(error: Error) { - let afError = error as? AFError - - switch afError { - case .explicitlyCancelled: - self = .requestCancelled - default: - self = .networkError(error: error) +extension ApiServiceError: RichError { + var message: String { + localizedDescription + } + + var level: ErrorLevel { + switch self { + case .accountNotFound, .notLogged, .networkError, .requestCancelled, .noEndpointsAvailable: + return .warning + + case .serverError, .commonError: + return .error + + case .internalError: + return .internalError + } + } + + var internalError: Error? { + switch self { + case .accountNotFound, .notLogged, .serverError, .requestCancelled, .commonError, .noEndpointsAvailable: + return nil + + case .internalError(_, let error): + return error + + case .networkError(let error): + return error } } } diff --git a/Adamant/Helpers/HealthCheckWrapper+Extension.swift b/Adamant/Helpers/HealthCheckWrapper+Extension.swift deleted file mode 100644 index a97512153..000000000 --- a/Adamant/Helpers/HealthCheckWrapper+Extension.swift +++ /dev/null @@ -1,17 +0,0 @@ -// -// HealthCheckWrapper+Extension.swift -// Adamant -// -// Created by Andrew G on 04.08.2024. -// Copyright © 2024 Adamant. All rights reserved. -// - -import Foundation - -extension HealthCheckWrapper { - var chosenFastestNodeId: UUID? { - fastestNodeMode - ? sortedAllowedNodes.first?.id - : nil - } -} diff --git a/Adamant/Helpers/Localization.swift b/Adamant/Helpers/Localization.swift index a27958ba4..c5c6fde4d 100644 --- a/Adamant/Helpers/Localization.swift +++ b/Adamant/Helpers/Localization.swift @@ -6,19 +6,9 @@ // Copyright © 2018 Adamant. All rights reserved. // -import Foundation +import CommonKit import UIKit -protocol Localizable { - var localized: String { get } -} - -extension String: Localizable { - var localized: String { - return .localized(self, comment: "") - } -} - protocol XIBLocalizable { var xibLocKey: String? { get set } } diff --git a/Adamant/Helpers/Node+UI.swift b/Adamant/Helpers/Node+UI.swift index 151ee35e5..dca5de942 100644 --- a/Adamant/Helpers/Node+UI.swift +++ b/Adamant/Helpers/Node+UI.swift @@ -149,13 +149,3 @@ private extension Node { return version.map { "(v\($0))" } } } - -extension Node { - static func stringToDouble(_ value: String?) -> Double? { - guard let minNodeVersion = value?.replacingOccurrences(of: ".", with: ""), - let versionNumber = Double(minNodeVersion) - else { return nil } - - return versionNumber - } -} diff --git a/Adamant/Helpers/NodeGroup+Constants.swift b/Adamant/Helpers/NodeGroup+Constants.swift index 801b264f8..207afa79f 100644 --- a/Adamant/Helpers/NodeGroup+Constants.swift +++ b/Adamant/Helpers/NodeGroup+Constants.swift @@ -8,7 +8,7 @@ import Foundation import CommonKit -public extension NodeGroup { +extension NodeGroup { var onScreenUpdateInterval: TimeInterval { switch self { case .adm: @@ -104,7 +104,7 @@ public extension NodeGroup { minNodeVersion = DashWalletService.minNodeVersion } - guard let versionNumber = Node.stringToDouble(minNodeVersion) else { + guard let versionNumber = Node.versionToDouble(minNodeVersion) else { return .zero } @@ -139,4 +139,15 @@ public extension NodeGroup { return false } } + + var blockchainHealthCheckParams: BlockchainHealthCheckParams { + .init( + group: self, + name: name, + normalUpdateInterval: normalUpdateInterval, + crucialUpdateInterval: crucialUpdateInterval, + minNodeVersion: minNodeVersion, + nodeHeightEpsilon: nodeHeightEpsilon + ) + } } diff --git a/Adamant/Helpers/String+localized.swift b/Adamant/Helpers/String+localized.swift index 9d0b2a613..f35bb4aa6 100644 --- a/Adamant/Helpers/String+localized.swift +++ b/Adamant/Helpers/String+localized.swift @@ -10,12 +10,6 @@ import Foundation import CommonKit extension String.adamant { - enum shared { - static var productName: String { - String.localized("ADAMANT", comment: "Product name") - } - } - enum alert { // MARK: Buttons static var cancel: String { @@ -75,70 +69,6 @@ extension String.adamant { } } - enum sharedErrors { - static var userNotLogged: String { - String.localized("Error.UserNotLogged", comment: "Shared error: User not logged") - } - static var networkError: String { - String.localized("Error.NoNetwork", comment: "Shared error: Network problems. In most cases - no connection") - } - static var requestCancelled: String { - String.localized("Error.RequestCancelled", comment: "Shared error: Request cancelled") - } - static func commonError(_ text: String) -> String { - String.localizedStringWithFormat( - .localized( - "Error.BaseErrorFormat", - comment: "Shared error: Base format, %@" - ), - text - ) - } - - static func accountNotFound(_ account: String) -> String { - String.localizedStringWithFormat(.localized("Error.AccountNotFoundFormat", comment: "Shared error: Account not found error. Using %@ for address."), account) - } - - static var accountNotInitiated: String { - String.localized("Error.AccountNotInitiated", comment: "Shared error: Account not initiated") - } - - static var unknownError: String { - String.localized("Error.UnknownError", comment: "Shared unknown error") - } - static func admNodeErrorMessage(_ coin: String) -> String { - String.localizedStringWithFormat(.localized("ApiService.InternalError.NoAdmNodesAvailable", comment: "No active ADM nodes to fetch the partner's %@ address"), coin) - } - - static var notEnoughMoney: String { - String.localized("WalletServices.SharedErrors.notEnoughMoney", comment: "Wallet Services: Shared error, user do not have enought money.") - } - - static var dustError: String { - String.localized("TransferScene.Dust.Error", comment: "Tranfser: Dust error.") - } - - static var transactionUnavailable: String { - String.localized("WalletServices.SharedErrors.transactionUnavailable", comment: "Wallet Services: Transaction unavailable") - } - - static var inconsistentTransaction: String { - String.localized("WalletServices.SharedErrors.inconsistentTransaction", comment: "Wallet Services: Cannot verify transaction") - } - - static var walletFrezzed: String { - String.localized("WalletServices.SharedErrors.walletFrezzed", comment: "Wallet Services: Wait until other transactions approved") - } - - static func internalError(message: String) -> String { - String.localizedStringWithFormat(.localized("Error.InternalErrorFormat", comment: "Shared error: Internal error format, %@ for message"), message) - } - - static func remoteServerError(message: String) -> String { - String.localizedStringWithFormat(.localized("Error.RemoteServerErrorFormat", comment: "Shared error: Remote error format, %@ for message"), message) - } - } - enum reply { static var shortUnknownMessageError: String { String.localized("Reply.ShortUnknownMessageError", comment: "Short unknown message error") diff --git a/Adamant/Models/APIResponseModel.swift b/Adamant/Models/APIResponseModel.swift deleted file mode 100644 index 2fe7c288b..000000000 --- a/Adamant/Models/APIResponseModel.swift +++ /dev/null @@ -1,15 +0,0 @@ -// -// 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/Delegate.swift b/Adamant/Models/Delegate.swift deleted file mode 100644 index 4c1869d7d..000000000 --- a/Adamant/Models/Delegate.swift +++ /dev/null @@ -1,157 +0,0 @@ -// -// Delegate.swift -// Adamant -// -// Created by Anton Boyarkin on 06/07/2018. -// Copyright © 2018 Adamant. All rights reserved. -// - -import Foundation -import CommonKit - -final class Delegate: Decodable { - let username: String - let address: String - let publicKey: String - let voteObsolete: String - let voteFair: String - let producedblocks: Int - let missedblocks: Int - let rate: Int - let rank: Int - let approval: Double - let productivity: Double - - var voted: Bool = false - - enum CodingKeys: String, CodingKey { - case username - case address - case publicKey - case voteObsolete = "vote" - case voteFair = "votesWeight" - case producedblocks - case missedblocks - case rate - case rank - case approval - case productivity - } -} - -extension Delegate: WrappableModel { - static let ModelKey = "delegate" -} - -extension Delegate: WrappableCollection { - static let CollectionKey = "delegates" -} - -struct DelegateForgeDetails: Decodable { - let nodeTimestamp: Date - let fees: Decimal - let rewards: Decimal - let forged: Decimal - - enum CodingKeys: String, CodingKey { - case nodeTimestamp - case fees - case rewards - case forged - } - - init(from decoder: Decoder) throws { - let container = try decoder.container(keyedBy: CodingKeys.self) - - let feesStr = try container.decode(String.self, forKey: .fees) - let fees = Decimal(string: feesStr) ?? 0 - self.fees = fees.shiftedFromAdamant() - - let rewardsStr = try container.decode(String.self, forKey: .forged) - let rewards = Decimal(string: rewardsStr) ?? 0 - self.rewards = rewards.shiftedFromAdamant() - - let forgedStr = try container.decode(String.self, forKey: .forged) - let forged = Decimal(string: forgedStr) ?? 0 - self.forged = forged.shiftedFromAdamant() - - let timestamp = try container.decode(UInt64.self, forKey: .nodeTimestamp) - self.nodeTimestamp = AdamantUtilities.decodeAdamant(timestamp: TimeInterval(timestamp)) - } -} - -struct DelegatesCountResult: Decodable { - let nodeTimestamp: UInt64 - let count: UInt -} - -struct NextForgersResult: Decodable { - let nodeTimestamp: Date - let currentBlock: UInt64 - let currentBlockSlot: UInt64 - let currentSlot: UInt64 - let delegates: [String] - - enum CodingKeys: String, CodingKey { - case nodeTimestamp - case currentBlock - case currentBlockSlot - case currentSlot - case delegates - } - - init(from decoder: Decoder) throws { - let container = try decoder.container(keyedBy: CodingKeys.self) - - self.currentBlock = try container.decode(UInt64.self, forKey: .currentBlock) - self.currentBlockSlot = try container.decode(UInt64.self, forKey: .currentBlockSlot) - self.currentSlot = try container.decode(UInt64.self, forKey: .currentSlot) - self.delegates = try container.decode([String].self, forKey: .delegates) - - let timestamp = try container.decode(UInt64.self, forKey: .nodeTimestamp) - self.nodeTimestamp = AdamantUtilities.decodeAdamant(timestamp: TimeInterval(timestamp)) - } -} - -struct Block: Decodable { - let id: String - let version: UInt - let timestamp: UInt64 - let height: UInt64 - let previousBlock:String - let numberOfTransactions: UInt - let totalAmount: UInt - let totalFee: UInt - let reward: UInt - let payloadLength: UInt - let payloadHash: String - let generatorPublicKey: String - let generatorId: String - let blockSignature: String - let confirmations: UInt - let totalForged: String -} - -extension Block: WrappableModel { - static let ModelKey = "block" -} - -extension Block: WrappableCollection { - static let CollectionKey = "blocks" -} - -/* -{ - "username": "permit", - "address": "U8339394976025567725", - "publicKey": "01c5079a2234f69feca1b00daf4ddbd8904e13dfb67ce47c21f26377468706fa", - "producedblocks": 11153, - "missedblocks": 3, - "vote": "13373617430543", - "votesWeight": "13373617430543", - "rate": 1, - "rank": 1, - "approval": 0.95, - "productivity": 99.97 -} -*/ diff --git a/Adamant/Models/NodeStatusInfo.swift b/Adamant/Models/NodeStatusInfo.swift deleted file mode 100644 index 0046b8af4..000000000 --- a/Adamant/Models/NodeStatusInfo.swift +++ /dev/null @@ -1,17 +0,0 @@ -// -// 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/ChatsList/ChatListFactory.swift b/Adamant/Modules/ChatsList/ChatListFactory.swift index ad84877f5..b31502e58 100644 --- a/Adamant/Modules/ChatsList/ChatListFactory.swift +++ b/Adamant/Modules/ChatsList/ChatListFactory.swift @@ -8,6 +8,7 @@ import UIKit import Swinject +import CommonKit struct ChatListFactory { let assembler: Assembler diff --git a/Adamant/Modules/ChatsList/ComplexTransferViewController.swift b/Adamant/Modules/ChatsList/ComplexTransferViewController.swift index f55add2b5..b5a75371d 100644 --- a/Adamant/Modules/ChatsList/ComplexTransferViewController.swift +++ b/Adamant/Modules/ChatsList/ComplexTransferViewController.swift @@ -139,7 +139,7 @@ extension ComplexTransferViewController: PagingViewControllerDataSource { vc.showAlertView( title: nil, message: ApiServiceError.noEndpointsAvailable( - coin: service.core.tokenName + nodeGroupName: service.core.tokenName ).errorDescription ?? .adamant.sharedErrors.unknownError, animated: true ) diff --git a/Adamant/Modules/Delegates/DelegatesFactory.swift b/Adamant/Modules/Delegates/DelegatesFactory.swift index f079897ab..85fa4405a 100644 --- a/Adamant/Modules/Delegates/DelegatesFactory.swift +++ b/Adamant/Modules/Delegates/DelegatesFactory.swift @@ -8,6 +8,7 @@ import UIKit import Swinject +import CommonKit struct DelegatesFactory { let assembler: Assembler diff --git a/Adamant/Modules/Login/LoginFactory.swift b/Adamant/Modules/Login/LoginFactory.swift index dd1fb2c78..83c401f12 100644 --- a/Adamant/Modules/Login/LoginFactory.swift +++ b/Adamant/Modules/Login/LoginFactory.swift @@ -8,6 +8,7 @@ import UIKit import Swinject +import CommonKit struct LoginFactory { let assembler: Assembler diff --git a/Adamant/Modules/Login/LoginViewController.swift b/Adamant/Modules/Login/LoginViewController.swift index 0392fa815..c973cf187 100644 --- a/Adamant/Modules/Login/LoginViewController.swift +++ b/Adamant/Modules/Login/LoginViewController.swift @@ -419,7 +419,7 @@ extension LoginViewController { } func generateNewPassphrase() { - let passphrase = adamantCore.generateNewPassphrase() + let passphrase = (try? Mnemonic.generate().joined(separator: " ")) ?? .empty hideNewPassphrase = false diff --git a/Adamant/Modules/Wallets/Adamant/AdmWalletFactory.swift b/Adamant/Modules/Wallets/Adamant/AdmWalletFactory.swift index 5bc7e5f03..cb27ed35e 100644 --- a/Adamant/Modules/Wallets/Adamant/AdmWalletFactory.swift +++ b/Adamant/Modules/Wallets/Adamant/AdmWalletFactory.swift @@ -8,6 +8,7 @@ import Swinject import UIKit +import CommonKit struct AdmWalletFactory: WalletFactory { typealias Service = WalletService diff --git a/Adamant/Modules/Wallets/Dash/DashWalletService+Transactions.swift b/Adamant/Modules/Wallets/Dash/DashWalletService+Transactions.swift index 22f289e63..d5e178318 100644 --- a/Adamant/Modules/Wallets/Dash/DashWalletService+Transactions.swift +++ b/Adamant/Modules/Wallets/Dash/DashWalletService+Transactions.swift @@ -7,7 +7,7 @@ // import Foundation -import Alamofire +import CommonKit import BitcoinKit struct DashTransactionsPointer { diff --git a/Adamant/Modules/Wallets/Klayr/KlyNodeApiService.swift b/Adamant/Modules/Wallets/Klayr/KlyNodeApiService.swift index 1a29e2970..2eb046d34 100644 --- a/Adamant/Modules/Wallets/Klayr/KlyNodeApiService.swift +++ b/Adamant/Modules/Wallets/Klayr/KlyNodeApiService.swift @@ -8,6 +8,7 @@ import LiskKit import Foundation +import CommonKit final class KlyNodeApiService: WalletApiService { let api: BlockchainHealthCheckWrapper diff --git a/Adamant/Modules/Wallets/TransferViewControllerBase.swift b/Adamant/Modules/Wallets/TransferViewControllerBase.swift index 250ab6c3e..70e36d300 100644 --- a/Adamant/Modules/Wallets/TransferViewControllerBase.swift +++ b/Adamant/Modules/Wallets/TransferViewControllerBase.swift @@ -805,7 +805,7 @@ class TransferViewControllerBase: FormViewController { guard isActiveAdmNode() || admReportRecipient == nil else { dialogService.showWarning( withMessage: ApiServiceError.noEndpointsAvailable( - coin: NodeGroup.adm.name + nodeGroupName: NodeGroup.adm.name ).localizedDescription ) return @@ -814,7 +814,7 @@ class TransferViewControllerBase: FormViewController { guard walletCore.hasActiveNode else { dialogService.showWarning( withMessage: ApiServiceError.noEndpointsAvailable( - coin: walletCore.tokenName + nodeGroupName: walletCore.tokenName ).localizedDescription ) return diff --git a/Adamant/Modules/Wallets/WalletsService/WalletCoreProtocol.swift b/Adamant/Modules/Wallets/WalletsService/WalletCoreProtocol.swift index 37becdc8c..253d65558 100644 --- a/Adamant/Modules/Wallets/WalletsService/WalletCoreProtocol.swift +++ b/Adamant/Modules/Wallets/WalletsService/WalletCoreProtocol.swift @@ -124,8 +124,8 @@ extension WalletServiceError: HealthCheckableError { } } - static func noEndpointsError(coin: String) -> WalletServiceError { - .apiError(.noEndpointsError(coin: coin)) + static func noEndpointsError(nodeGroupName: String) -> WalletServiceError { + .apiError(.noEndpointsError(nodeGroupName: nodeGroupName)) } } diff --git a/Adamant/ServiceProtocols/ReachabilityMonitor.swift b/Adamant/ServiceProtocols/ReachabilityMonitor.swift index a58880744..0becf2da0 100644 --- a/Adamant/ServiceProtocols/ReachabilityMonitor.swift +++ b/Adamant/ServiceProtocols/ReachabilityMonitor.swift @@ -7,6 +7,7 @@ // import Foundation +import CommonKit extension Notification.Name { struct AdamantReachabilityMonitor { @@ -26,6 +27,7 @@ extension AdamantUserInfoKey { } protocol ReachabilityMonitor { + var connectionPublisher: AnyObservable { get } var connection: Bool { get } func start() diff --git a/Adamant/ServiceProtocols/RichMessageProviderWithStatusCheck/RichMessageProviderWithStatusCheck.swift b/Adamant/ServiceProtocols/RichMessageProviderWithStatusCheck/RichMessageProviderWithStatusCheck.swift index 399298992..94c5ce365 100644 --- a/Adamant/ServiceProtocols/RichMessageProviderWithStatusCheck/RichMessageProviderWithStatusCheck.swift +++ b/Adamant/ServiceProtocols/RichMessageProviderWithStatusCheck/RichMessageProviderWithStatusCheck.swift @@ -7,6 +7,7 @@ // import Foundation +import CommonKit struct TransactionStatusInfo { let sentDate: Date? diff --git a/Adamant/Services/AdamantReachability.swift b/Adamant/Services/AdamantReachability.swift index aa85a0eef..02079b275 100644 --- a/Adamant/Services/AdamantReachability.swift +++ b/Adamant/Services/AdamantReachability.swift @@ -13,9 +13,13 @@ import CommonKit // MARK: - AdamantReachability wrapper final class AdamantReachability: ReachabilityMonitor { + @ObservableValue private(set) var connection = true + private let monitor = NWPathMonitor() - @Atomic private(set) var connection = true + var connectionPublisher: AnyObservable { + $connection.eraseToAnyPublisher() + } func start() { monitor.pathUpdateHandler = { [weak self] _ in diff --git a/Adamant/Services/DataProviders/DefaultNodesProvider.swift b/Adamant/Services/DataProviders/DefaultNodesProvider.swift new file mode 100644 index 000000000..378a2e126 --- /dev/null +++ b/Adamant/Services/DataProviders/DefaultNodesProvider.swift @@ -0,0 +1,38 @@ +// +// DefaultNodesProvider.swift +// Adamant +// +// Created by Andrew G on 08.08.2024. +// Copyright © 2024 Adamant. All rights reserved. +// + +import CommonKit + +struct DefaultNodesProvider { + var nodes: [NodeGroup: [Node]] { + .init(uniqueKeysWithValues: NodeGroup.allCases.map { + ($0, defaultItems(group: $0)) + }) + } +} + +private extension DefaultNodesProvider { + func defaultItems(group: NodeGroup) -> [Node] { + switch group { + case .btc: + return BtcWalletService.nodes + case .eth: + return EthWalletService.nodes + case .klyNode: + return KlyWalletService.nodes + case .klyService: + return KlyWalletService.serviceNodes + case .doge: + return DogeWalletService.nodes + case .dash: + return DashWalletService.nodes + case .adm: + return AdmWalletService.nodes + } + } +} diff --git a/Adamant/Services/DataProviders/InMemoryCoreDataStack.swift b/Adamant/Services/DataProviders/InMemoryCoreDataStack.swift index 44ef0bb4c..2b199ec11 100644 --- a/Adamant/Services/DataProviders/InMemoryCoreDataStack.swift +++ b/Adamant/Services/DataProviders/InMemoryCoreDataStack.swift @@ -8,6 +8,7 @@ import Foundation import CoreData +import CommonKit final class InMemoryCoreDataStack: CoreDataStack { let container: NSPersistentContainer diff --git a/Adamant/Services/NativeCore+AdamantCore.swift b/Adamant/Services/NativeCore+AdamantCore.swift deleted file mode 100644 index 1d34430ca..000000000 --- a/Adamant/Services/NativeCore+AdamantCore.swift +++ /dev/null @@ -1,119 +0,0 @@ -// -// NativeCore+AdamantCore.swift -// Adamant -// -// Created by Anokhov Pavel on 25/05/2019. -// Copyright © 2019 Adamant. All rights reserved. -// - -import Foundation -import CryptoSwift -import CommonKit - -extension NativeAdamantCore: AdamantCore { - // MARK: - Passphrases - - func generateNewPassphrase() -> String { - if let passphrase = try? Mnemonic.generate().joined(separator: " ") { - return passphrase - } - return "" - } - - // MARK: - Signing transactions - - func sign(transaction: SignableTransaction, senderId: String, keypair: Keypair) -> String? { - let privateKey = keypair.privateKey.hexBytes() - let hash = transaction.bytes.sha256() - - guard let signature = Crypto.sign.signature(message: hash, secretKey: privateKey) else { - print("FAIL to sign of transaction") - return nil - } - - return signature.hexString() - } -} - -// MARK: - Bytes -fileprivate extension SignableTransaction { - - var bytes: [UInt8] { - return - typeBytes + - timestampBytes + - senderPublicKeyBytes + - requesterPublicKeyBytes + - recipientIdBytes + - amountBytes + - assetBytes + - signatureBytes + - signSignatureBytes - } - - var typeBytes: [UInt8] { - return [UInt8(type.rawValue)] - } - - var timestampBytes: [UInt8] { - return ByteBackpacker.pack(UInt32(timestamp), byteOrder: .littleEndian) - } - - var senderPublicKeyBytes: [UInt8] { - return senderPublicKey.hexBytes() - } - - var requesterPublicKeyBytes: [UInt8] { - return 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] { - return [] - } - - var signSignatureBytes: [UInt8] { - return [] - } - - 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/CommonKit/Package.swift b/CommonKit/Package.swift index 54fb7ce47..b2d1515aa 100644 --- a/CommonKit/Package.swift +++ b/CommonKit/Package.swift @@ -45,7 +45,12 @@ let package = Package( .package( url: "https://github.com/RNCryptor/RNCryptor.git", .upToNextMinor(from: "5.1.0") - ) + ), + .package( + url: "https://github.com/Alamofire/Alamofire.git", + .upToNextMinor(from: "5.4.2") + ), + .package(path: "../BitcoinKit") ], targets: [ // Targets are the basic building blocks of a package, defining a module or a test suite. @@ -59,12 +64,14 @@ let package = Package( "SnapKit", "MarkdownKit", "KeychainAccess", - "RNCryptor" + "RNCryptor", + "Alamofire", + "BitcoinKit" ] ), .testTarget( name: "CommonKitTests", dependencies: ["CommonKit"] - ), + ) ] ) diff --git a/CommonKit/Sources/CommonKit/Core/NativeAdamantCore.swift b/CommonKit/Sources/CommonKit/Core/NativeAdamantCore.swift index ba37eb7ca..49fd1a674 100644 --- a/CommonKit/Sources/CommonKit/Core/NativeAdamantCore.swift +++ b/CommonKit/Sources/CommonKit/Core/NativeAdamantCore.swift @@ -14,7 +14,7 @@ import CryptoSwift * Decoding and Encoding for messages and values */ -public final class NativeAdamantCore { +public final class NativeAdamantCore: AdamantCore { // MARK: - Messages public func encodeMessage(_ message: String, recipientPublicKey publicKey: String, privateKey privateKeyHex: String) -> (message: String, nonce: String)? { @@ -67,6 +67,18 @@ public final class NativeAdamantCore { return decrepted.utf8String } + public func sign(transaction: SignableTransaction, senderId: String, keypair: Keypair) -> String? { + let privateKey = keypair.privateKey.hexBytes() + let hash = transaction.bytes.sha256() + + guard let signature = Crypto.sign.signature(message: hash, secretKey: privateKey) else { + print("FAIL to sign of transaction") + return nil + } + + return signature.hexString() + } + // MARK: - Values public func encodeValue(_ value: [String: Any], privateKey privateKeyHex: String) -> (message: String, nonce: String)? { @@ -170,3 +182,86 @@ public extension String { }) } } + +// MARK: - Bytes +private extension SignableTransaction { + + var bytes: [UInt8] { + return + typeBytes + + timestampBytes + + senderPublicKeyBytes + + requesterPublicKeyBytes + + recipientIdBytes + + amountBytes + + assetBytes + + signatureBytes + + signSignatureBytes + } + + var typeBytes: [UInt8] { + return [UInt8(type.rawValue)] + } + + var timestampBytes: [UInt8] { + return ByteBackpacker.pack(UInt32(timestamp), byteOrder: .littleEndian) + } + + var senderPublicKeyBytes: [UInt8] { + return senderPublicKey.hexBytes() + } + + var requesterPublicKeyBytes: [UInt8] { + return 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] { + return [] + } + + var signSignatureBytes: [UInt8] { + return [] + } + + 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/CommonKit/Sources/CommonKit/ExtensionsTools/ExtensionsApi.swift b/CommonKit/Sources/CommonKit/ExtensionsTools/ExtensionsApi.swift index dac73decd..547134d54 100644 --- a/CommonKit/Sources/CommonKit/ExtensionsTools/ExtensionsApi.swift +++ b/CommonKit/Sources/CommonKit/ExtensionsTools/ExtensionsApi.swift @@ -11,100 +11,19 @@ import Foundation public final class ExtensionsApi { // MARK: Properties private let addressBookKey = "contact_list" - private let nodesStoreKey = "nodesSource.nodes" - public let keychainStore: KeychainStore - - public private(set) lazy var nodes: [Node] = { - let nodesDto: [NodeKeychainDTO]? = keychainStore.get(nodesStoreKey) - let nodes = nodesDto.map { $0.map { $0.mapToModel() } } - ?? AdamantResources.nodes - - return nodes.filter { $0.isEnabled }.shuffled() - }() - - private var currentNode: Node? - - private func selectNewNode() { - currentNode = nodes.popLast() - } + private let apiService: ApiService // MARK: Cotr - public init(keychainStore: KeychainStore) { - self.keychainStore = keychainStore + public init(apiService: ApiService) { + self.apiService = apiService } // MARK: - API // MARK: Transactions public func getTransaction(by id: UInt64) -> Transaction? { - // MARK: 1. Getting Transaction - var response: ServerModelResponse? - var nodeUrl: URL! = nil - if currentNode == nil { - selectNewNode() - } - - repeat { - guard let node = currentNode, let url = node.asURL() else { - selectNewNode() - continue - } - nodeUrl = url - - do { - guard var components = URLComponents(url: url, resolvingAgainstBaseURL: false) else { - selectNewNode() - continue - } - - components.path = "/api/transactions/get" - components.queryItems = [URLQueryItem(name: "id", value: "\(id)"), - URLQueryItem(name: "returnAsset", value: "1")] - - if let url = components.url { - let data = try Data(contentsOf: url) - response = try JSONDecoder().decode(ServerModelResponse.self, from: data) - } else { - selectNewNode() - continue - } - } catch { - selectNewNode() - continue - } - } while response == nil && nodes.count > 0 // Try until we have a transaction, or we run out of nodes - - guard let transaction = response?.model else { - return nil - } - - // MARK: 2. Working on transaction - - // For old nodes - if /api/transaction/get doesn't return chat asset - get it from /api/chats/ - if transaction.type == .chatMessage, transaction.asset.chat == nil { - do { - guard var components = URLComponents(url: nodeUrl, resolvingAgainstBaseURL: false) else { - return nil - } - - components.path = "/api/chats/get" - components.queryItems = [URLQueryItem(name: "recipientId", value: transaction.recipientId), - URLQueryItem(name: "orderBy", value: "timestamp:asc"), - URLQueryItem(name: "fromHeight", value: "\(transaction.height - 1)") - ] - - if let url = components.url { - let data = try Data(contentsOf: url) - let collection = try JSONDecoder().decode(ServerCollectionResponse.self, from: data) - return collection.collection?.first { $0.id == id } - } else { - return nil - } - } catch { - return nil - } - } else { - return transaction + syncRequest { [apiService] in + try? await apiService.getTransaction(id: id).get() } } @@ -115,46 +34,18 @@ public final class ExtensionsApi { core: NativeAdamantCore, keypair: Keypair ) -> [String:ContactDescription]? { - var response: ServerCollectionResponse? - - // Getting transaction - repeat { - guard let node = currentNode, let url = node.asURL() else { - selectNewNode() - continue - } - - do { - guard var components = URLComponents(url: url, resolvingAgainstBaseURL: false) else { - selectNewNode() - continue - } - - components.path = "/api/states/get" - components.queryItems = [URLQueryItem(name: "senderId", value: address), - URLQueryItem(name: "orderBy", value: "timestamp:desc"), - URLQueryItem(name: "key", value: addressBookKey)] - - if let url = components.url { - let data = try Data(contentsOf: url) - response = try JSONDecoder().decode(ServerCollectionResponse.self, from: data) - } else { - selectNewNode() - continue - } - } catch { - selectNewNode() - continue - } - } while response == nil && nodes.count > 0 // Try until we have a transaction, or we run out of nodes + let addressBookString = syncRequest { [apiService, addressBookKey] in + try? await apiService.get(key: addressBookKey, sender: address).get() + } // Working with transaction - guard let collection = response?.collection, - let object = collection.first?.asset.state?.value.toDictionary(), + guard + let object = addressBookString?.toDictionary(), let message = object["message"] as? String, - let nonce = object["nonce"] as? String else { - return nil + let nonce = object["nonce"] as? String + else { + return nil } // Decoding @@ -182,4 +73,19 @@ public final class ExtensionsApi { return nil } } + + private func syncRequest( + _ request: @Sendable @escaping () async -> T? + ) -> T? { + let result = Atomic(wrappedValue: nil) + let semaphore = DispatchSemaphore(value: .zero) + + Task.detached { + result.wrappedValue = await request() + semaphore.signal() + } + + semaphore.wait() + return result.wrappedValue + } } diff --git a/CommonKit/Sources/CommonKit/ExtensionsTools/ExtensionsApiFactory.swift b/CommonKit/Sources/CommonKit/ExtensionsTools/ExtensionsApiFactory.swift new file mode 100644 index 000000000..8ea9a6c5f --- /dev/null +++ b/CommonKit/Sources/CommonKit/ExtensionsTools/ExtensionsApiFactory.swift @@ -0,0 +1,43 @@ +// +// ExtensionsApiFactory.swift +// +// +// Created by Andrew G on 08.08.2024. +// + +public struct ExtensionsApiFactory { + public let core: AdamantCore + public let securedStore: SecuredStore + + public init(core: AdamantCore, securedStore: SecuredStore) { + self.core = core + self.securedStore = securedStore + } + + public func make() -> ExtensionsApi { + .init(apiService: AdamantApiService( + healthCheckWrapper: .init( + service: AdamantApiCore(apiCore: APICore()), + nodesStorage: NodesStorage( + securedStore: securedStore, + nodesMergingService: AdamantNodesMergingService(), + defaultNodes: .init() + ), + nodesAdditionalParamsStorage: NodesAdditionalParamsStorage( + securedStore: securedStore + ), + isActive: false, + params: .init( + group: .adm, + name: "ADM", + normalUpdateInterval: .infinity, + crucialUpdateInterval: .infinity, + minNodeVersion: .zero, + nodeHeightEpsilon: .zero + ), + connection: nil + ), + adamantCore: core + )) + } +} diff --git a/Adamant/Helpers/ADM+JsonDecode.swift b/CommonKit/Sources/CommonKit/Helpers/ADM+JsonDecode.swift similarity index 93% rename from Adamant/Helpers/ADM+JsonDecode.swift rename to CommonKit/Sources/CommonKit/Helpers/ADM+JsonDecode.swift index 778fe1667..cb85f5854 100644 --- a/Adamant/Helpers/ADM+JsonDecode.swift +++ b/CommonKit/Sources/CommonKit/Helpers/ADM+JsonDecode.swift @@ -8,22 +8,22 @@ import Foundation -struct JSONCodingKeys: CodingKey { - var stringValue: String +public struct JSONCodingKeys: CodingKey { + public var stringValue: String - init?(stringValue: String) { + public init?(stringValue: String) { self.stringValue = stringValue } - var intValue: Int? + public var intValue: Int? - init?(intValue: Int) { + public init?(intValue: Int) { self.init(stringValue: "\(intValue)") self.intValue = intValue } } -extension KeyedDecodingContainer { +public extension KeyedDecodingContainer { func decode(forKey key: K) throws -> Data { if let stringValue = try? decode(String.self, forKey: key) { return Data(stringValue.utf8) @@ -82,7 +82,7 @@ extension KeyedDecodingContainer { } } -extension UnkeyedDecodingContainer { +public extension UnkeyedDecodingContainer { mutating func decode(_ type: Array.Type) throws -> [Any] { var array: [Any] = [] while isAtEnd == false { diff --git a/Adamant/ServiceProtocols/AdamantCore/AdamantCore+Extensions.swift b/CommonKit/Sources/CommonKit/Helpers/AdamantCore+Extensions.swift similarity index 98% rename from Adamant/ServiceProtocols/AdamantCore/AdamantCore+Extensions.swift rename to CommonKit/Sources/CommonKit/Helpers/AdamantCore+Extensions.swift index 2cebbc0be..006a642c4 100644 --- a/Adamant/ServiceProtocols/AdamantCore/AdamantCore+Extensions.swift +++ b/CommonKit/Sources/CommonKit/Helpers/AdamantCore+Extensions.swift @@ -7,10 +7,9 @@ // import Foundation -import CommonKit import BigInt -extension AdamantCore { +public extension AdamantCore { func makeSignedTransaction( transaction: SignableTransaction, senderId: String, @@ -97,7 +96,7 @@ extension AdamantCore { // MARK: - Bytes -extension UnregisteredTransaction { +public extension UnregisteredTransaction { func generateId() -> String? { let hash = bytes.sha256() diff --git a/CommonKit/Sources/CommonKit/Helpers/ApiServiceError+AFError.swift b/CommonKit/Sources/CommonKit/Helpers/ApiServiceError+AFError.swift new file mode 100644 index 000000000..8d6c1a6ac --- /dev/null +++ b/CommonKit/Sources/CommonKit/Helpers/ApiServiceError+AFError.swift @@ -0,0 +1,21 @@ +// +// ApiServiceError+AFError.swift +// +// +// Created by Andrew G on 08.08.2024. +// + +import Alamofire + +public extension ApiServiceError { + init(error: Error) { + let afError = error as? AFError + + switch afError { + case .explicitlyCancelled: + self = .requestCancelled + default: + self = .networkError(error: error) + } + } +} diff --git a/CommonKit/Sources/CommonKit/Helpers/Atomic.swift b/CommonKit/Sources/CommonKit/Helpers/Atomic.swift index 2ebcfc977..6c6d85ca9 100644 --- a/CommonKit/Sources/CommonKit/Helpers/Atomic.swift +++ b/CommonKit/Sources/CommonKit/Helpers/Atomic.swift @@ -16,7 +16,7 @@ import Foundation /// results or crashes. /// In order to ensure you've acquired the lock for a certain amount of time use the `mutate` method. @propertyWrapper -public final class Atomic { +public final class Atomic: @unchecked Sendable { private var value: Value private let lock = NSLock() diff --git a/Adamant/Utilities/ByteBackpacker.swift b/CommonKit/Sources/CommonKit/Helpers/ByteBackpacker.swift similarity index 100% rename from Adamant/Utilities/ByteBackpacker.swift rename to CommonKit/Sources/CommonKit/Helpers/ByteBackpacker.swift diff --git a/CommonKit/Sources/CommonKit/Models/NodeGroup+Constants.swift b/CommonKit/Sources/CommonKit/Helpers/NodeGroup+Constants.swift similarity index 100% rename from CommonKit/Sources/CommonKit/Models/NodeGroup+Constants.swift rename to CommonKit/Sources/CommonKit/Helpers/NodeGroup+Constants.swift diff --git a/Adamant/Helpers/ServerResponse+Resolver.swift b/CommonKit/Sources/CommonKit/Helpers/ServerResponse+Resolver.swift similarity index 89% rename from Adamant/Helpers/ServerResponse+Resolver.swift rename to CommonKit/Sources/CommonKit/Helpers/ServerResponse+Resolver.swift index fef3485a3..c59a2d339 100644 --- a/Adamant/Helpers/ServerResponse+Resolver.swift +++ b/CommonKit/Sources/CommonKit/Helpers/ServerResponse+Resolver.swift @@ -6,9 +6,7 @@ // Copyright © 2023 Adamant. All rights reserved. // -import CommonKit - -extension ServerModelResponse { +public extension ServerModelResponse { func resolved() -> ApiServiceResult { if let model = model { return .success(model) @@ -18,7 +16,7 @@ extension ServerModelResponse { } } -extension ServerCollectionResponse { +public extension ServerCollectionResponse { func resolved() -> ApiServiceResult<[T]> { if let collection = collection { return .success(collection) @@ -28,7 +26,7 @@ extension ServerCollectionResponse { } } -extension TransactionIdResponse { +public extension TransactionIdResponse { func resolved() -> ApiServiceResult { if let ransactionId = transactionId { return .success(ransactionId) @@ -38,7 +36,7 @@ extension TransactionIdResponse { } } -extension GetPublicKeyResponse { +public extension GetPublicKeyResponse { func resolved() -> ApiServiceResult { if let publicKey = publicKey { return .success(publicKey) diff --git a/CommonKit/Sources/CommonKit/Localization/AdamantLocalized.swift b/CommonKit/Sources/CommonKit/Localization/AdamantLocalized.swift index e2e9bdf77..4d5c4010e 100644 --- a/CommonKit/Sources/CommonKit/Localization/AdamantLocalized.swift +++ b/CommonKit/Sources/CommonKit/Localization/AdamantLocalized.swift @@ -8,6 +8,16 @@ import Foundation +public protocol Localizable { + var localized: String { get } +} + +extension String: Localizable { + public var localized: String { + .localized(self, comment: "") + } +} + public extension String { enum adamant {} @@ -24,3 +34,75 @@ public extension String { return NSLocalizedString(key, bundle: bundle, comment: comment) } } + +public extension String.adamant { + enum shared { + public static var productName: String { + String.localized("ADAMANT", comment: "Product name") + } + } + + enum sharedErrors { + public static var userNotLogged: String { + String.localized("Error.UserNotLogged", comment: "Shared error: User not logged") + } + public static var networkError: String { + String.localized("Error.NoNetwork", comment: "Shared error: Network problems. In most cases - no connection") + } + public static var requestCancelled: String { + String.localized("Error.RequestCancelled", comment: "Shared error: Request cancelled") + } + public static func commonError(_ text: String) -> String { + String.localizedStringWithFormat( + .localized( + "Error.BaseErrorFormat", + comment: "Shared error: Base format, %@" + ), + text + ) + } + + public static func accountNotFound(_ account: String) -> String { + String.localizedStringWithFormat(.localized("Error.AccountNotFoundFormat", comment: "Shared error: Account not found error. Using %@ for address."), account) + } + + public static var accountNotInitiated: String { + String.localized("Error.AccountNotInitiated", comment: "Shared error: Account not initiated") + } + + public static var unknownError: String { + String.localized("Error.UnknownError", comment: "Shared unknown error") + } + public static func admNodeErrorMessage(_ coin: String) -> String { + String.localizedStringWithFormat(.localized("ApiService.InternalError.NoAdmNodesAvailable", comment: "No active ADM nodes to fetch the partner's %@ address"), coin) + } + + public static var notEnoughMoney: String { + String.localized("WalletServices.SharedErrors.notEnoughMoney", comment: "Wallet Services: Shared error, user do not have enought money.") + } + + public static var dustError: String { + String.localized("TransferScene.Dust.Error", comment: "Tranfser: Dust error.") + } + + public static var transactionUnavailable: String { + String.localized("WalletServices.SharedErrors.transactionUnavailable", comment: "Wallet Services: Transaction unavailable") + } + + public static var inconsistentTransaction: String { + String.localized("WalletServices.SharedErrors.inconsistentTransaction", comment: "Wallet Services: Cannot verify transaction") + } + + public static var walletFrezzed: String { + String.localized("WalletServices.SharedErrors.walletFrezzed", comment: "Wallet Services: Wait until other transactions approved") + } + + public static func internalError(message: String) -> String { + String.localizedStringWithFormat(.localized("Error.InternalErrorFormat", comment: "Shared error: Internal error format, %@ for message"), message) + } + + public static func remoteServerError(message: String) -> String { + String.localizedStringWithFormat(.localized("Error.RemoteServerErrorFormat", comment: "Shared error: Remote error format, %@ for message"), message) + } + } +} diff --git a/Adamant/Models/APIParametersEncoding.swift b/CommonKit/Sources/CommonKit/Models/APIParametersEncoding.swift similarity index 86% rename from Adamant/Models/APIParametersEncoding.swift rename to CommonKit/Sources/CommonKit/Models/APIParametersEncoding.swift index 545507ebd..e2e85b2b5 100644 --- a/Adamant/Models/APIParametersEncoding.swift +++ b/CommonKit/Sources/CommonKit/Models/APIParametersEncoding.swift @@ -9,13 +9,13 @@ import Alamofire import Foundation -enum APIParametersEncoding { +public enum APIParametersEncoding { case url case json case bodyString case forceQueryItems([URLQueryItem]) - var parametersEncoding: ParameterEncoding { + public var parametersEncoding: ParameterEncoding { switch self { case .url: return URLEncoding.default diff --git a/CommonKit/Sources/CommonKit/Models/APIResponseModel.swift b/CommonKit/Sources/CommonKit/Models/APIResponseModel.swift new file mode 100644 index 000000000..4663f55dd --- /dev/null +++ b/CommonKit/Sources/CommonKit/Models/APIResponseModel.swift @@ -0,0 +1,21 @@ +// +// APIResponseModel.swift +// Adamant +// +// Created by Andrew G on 17.11.2023. +// Copyright © 2023 Adamant. All rights reserved. +// + +import Foundation + +public struct APIResponseModel { + public let result: ApiServiceResult + public let data: Data? + public let code: Int? + + public init(result: ApiServiceResult, data: Data?, code: Int?) { + self.result = result + self.data = data + self.code = code + } +} diff --git a/Adamant/Models/AdamantAccount.swift b/CommonKit/Sources/CommonKit/Models/AdamantAccount.swift similarity index 64% rename from Adamant/Models/AdamantAccount.swift rename to CommonKit/Sources/CommonKit/Models/AdamantAccount.swift index 7cacd8c17..d2425351d 100644 --- a/Adamant/Models/AdamantAccount.swift +++ b/CommonKit/Sources/CommonKit/Models/AdamantAccount.swift @@ -7,19 +7,42 @@ // import Foundation -import CommonKit -struct AdamantAccount { - let address: String - var unconfirmedBalance: Decimal - var balance: Decimal - var publicKey: String? - let unconfirmedSignature: Int - let secondSignature: Int - let secondPublicKey: String? - let multisignatures: [String]? - let uMultisignatures: [String]? - var isDummy: Bool +public struct AdamantAccount { + public let address: String + public var unconfirmedBalance: Decimal + public var balance: Decimal + public var publicKey: String? + public let unconfirmedSignature: Int + public let secondSignature: Int + public let secondPublicKey: String? + public let multisignatures: [String]? + public let uMultisignatures: [String]? + public var isDummy: Bool + + public init( + address: String, + unconfirmedBalance: Decimal, + balance: Decimal, + publicKey: String?, + unconfirmedSignature: Int, + secondSignature: Int, + secondPublicKey: String?, + multisignatures: [String]?, + uMultisignatures: [String]?, + isDummy: Bool + ) { + self.address = address + self.unconfirmedBalance = unconfirmedBalance + self.balance = balance + self.publicKey = publicKey + self.unconfirmedSignature = unconfirmedSignature + self.secondSignature = secondSignature + self.secondPublicKey = secondPublicKey + self.multisignatures = multisignatures + self.uMultisignatures = uMultisignatures + self.isDummy = isDummy + } } extension AdamantAccount: Decodable { @@ -35,7 +58,7 @@ extension AdamantAccount: Decodable { case uMultisignatures = "u_multisignatures" } - init(from decoder: Decoder) throws { + public init(from decoder: Decoder) throws { let container = try decoder.container(keyedBy: CodingKeys.self) self.address = try container.decode(String.self, forKey: .address) @@ -55,9 +78,9 @@ extension AdamantAccount: Decodable { } extension AdamantAccount: WrappableModel { - static let ModelKey = "account" + public static let ModelKey = "account" - static func makeEmptyAccount(publicKey: String) -> Self { + public static func makeEmptyAccount(publicKey: String) -> Self { .init( address: AdamantUtilities.generateAddress(publicKey: publicKey), unconfirmedBalance: .zero, diff --git a/Adamant/Helpers/AdamantError.swift b/CommonKit/Sources/CommonKit/Models/AdamantError.swift similarity index 76% rename from Adamant/Helpers/AdamantError.swift rename to CommonKit/Sources/CommonKit/Models/AdamantError.swift index 3dade0f6b..e38deb67a 100644 --- a/Adamant/Helpers/AdamantError.swift +++ b/CommonKit/Sources/CommonKit/Models/AdamantError.swift @@ -8,10 +8,10 @@ import Foundation -struct AdamantError: LocalizedError { +public struct AdamantError: LocalizedError { public let errorDescription: String? - init(message: String) { + public init(message: String) { self.errorDescription = message } } diff --git a/Adamant/Models/ApiServiceError.swift b/CommonKit/Sources/CommonKit/Models/ApiServiceError.swift similarity index 57% rename from Adamant/Models/ApiServiceError.swift rename to CommonKit/Sources/CommonKit/Models/ApiServiceError.swift index c61b5597d..6ae71c696 100644 --- a/Adamant/Models/ApiServiceError.swift +++ b/CommonKit/Sources/CommonKit/Models/ApiServiceError.swift @@ -7,9 +7,8 @@ // import Foundation -import CommonKit -enum ApiServiceError: LocalizedError, Error { +public enum ApiServiceError: LocalizedError { case notLogged case accountNotFound case serverError(error: String) @@ -17,9 +16,9 @@ enum ApiServiceError: LocalizedError, Error { case networkError(error: Error) case requestCancelled case commonError(message: String) - case noEndpointsAvailable(coin: String) + case noEndpointsAvailable(nodeGroupName: String) - var errorDescription: String? { + public var errorDescription: String? { switch self { case .notLogged: return String.adamant.sharedErrors.userNotLogged @@ -43,57 +42,24 @@ enum ApiServiceError: LocalizedError, Error { case let .commonError(message): return String.adamant.sharedErrors.commonError(message) - case let .noEndpointsAvailable(coin): - return - .localizedStringWithFormat( - .localized( - "ApiService.InternalError.NoNodesAvailable", - comment: "Serious internal error: No nodes available" - ), - coin - ).localized + case let .noEndpointsAvailable(nodeGroupName): + return .localizedStringWithFormat( + .localized( + "ApiService.InternalError.NoNodesAvailable", + comment: "Serious internal error: No nodes available" + ), + nodeGroupName + ).localized } } - static func internalError(error: InternalAPIError) -> Self { + public static func internalError(error: InternalAPIError) -> Self { .internalError(message: error.localizedDescription, error: error) } } -extension ApiServiceError: RichError { - var message: String { - localizedDescription - } - - var level: ErrorLevel { - switch self { - case .accountNotFound, .notLogged, .networkError, .requestCancelled, .noEndpointsAvailable: - return .warning - - case .serverError, .commonError: - return .error - - case .internalError: - return .internalError - } - } - - var internalError: Error? { - switch self { - case .accountNotFound, .notLogged, .serverError, .requestCancelled, .commonError, .noEndpointsAvailable: - return nil - - case .internalError(_, let error): - return error - - case .networkError(let error): - return error - } - } -} - extension ApiServiceError: Equatable { - static func == (lhs: ApiServiceError, rhs: ApiServiceError) -> Bool { + public static func == (lhs: ApiServiceError, rhs: ApiServiceError) -> Bool { switch (lhs, rhs) { case (.notLogged, .notLogged): return true @@ -117,7 +83,7 @@ extension ApiServiceError: Equatable { } extension ApiServiceError: HealthCheckableError { - var isNetworkError: Bool { + public var isNetworkError: Bool { switch self { case .networkError: return true @@ -126,7 +92,7 @@ extension ApiServiceError: HealthCheckableError { } } - static func noEndpointsError(coin: String) -> ApiServiceError { - .noEndpointsAvailable(coin: coin) + public static func noEndpointsError(nodeGroupName: String) -> ApiServiceError { + .noEndpointsAvailable(nodeGroupName: nodeGroupName) } } diff --git a/Adamant/Models/ApiServiceResult.swift b/CommonKit/Sources/CommonKit/Models/ApiServiceResult.swift similarity index 65% rename from Adamant/Models/ApiServiceResult.swift rename to CommonKit/Sources/CommonKit/Models/ApiServiceResult.swift index 71c88ccd3..10a8d40f3 100644 --- a/Adamant/Models/ApiServiceResult.swift +++ b/CommonKit/Sources/CommonKit/Models/ApiServiceResult.swift @@ -6,4 +6,4 @@ // Copyright © 2022 Adamant. All rights reserved. // -typealias ApiServiceResult = Result +public typealias ApiServiceResult = Result diff --git a/CommonKit/Sources/CommonKit/Models/BlockchainHealthCheckParams.swift b/CommonKit/Sources/CommonKit/Models/BlockchainHealthCheckParams.swift new file mode 100644 index 000000000..9f5c74438 --- /dev/null +++ b/CommonKit/Sources/CommonKit/Models/BlockchainHealthCheckParams.swift @@ -0,0 +1,33 @@ +// +// BlockchainHealthCheckParams.swift +// +// +// Created by Andrew G on 08.08.2024. +// + +import Foundation + +public struct BlockchainHealthCheckParams { + public let group: NodeGroup + public let name: String + public let normalUpdateInterval: TimeInterval + public let crucialUpdateInterval: TimeInterval + public let minNodeVersion: Double + public let nodeHeightEpsilon: Int + + public init( + group: NodeGroup, + name: String, + normalUpdateInterval: TimeInterval, + crucialUpdateInterval: TimeInterval, + minNodeVersion: Double, + nodeHeightEpsilon: Int + ) { + self.group = group + self.name = name + self.normalUpdateInterval = normalUpdateInterval + self.crucialUpdateInterval = crucialUpdateInterval + self.minNodeVersion = minNodeVersion + self.nodeHeightEpsilon = nodeHeightEpsilon + } +} diff --git a/Adamant/Models/BodyStringEncoding.swift b/CommonKit/Sources/CommonKit/Models/BodyStringEncoding.swift similarity index 92% rename from Adamant/Models/BodyStringEncoding.swift rename to CommonKit/Sources/CommonKit/Models/BodyStringEncoding.swift index 1b7242d61..ece7a7b30 100644 --- a/Adamant/Models/BodyStringEncoding.swift +++ b/CommonKit/Sources/CommonKit/Models/BodyStringEncoding.swift @@ -9,8 +9,8 @@ import Alamofire import Foundation -struct BodyStringEncoding: ParameterEncoding { - func encode( +public struct BodyStringEncoding: ParameterEncoding { + public func encode( _ urlRequest: URLRequestConvertible, with parameters: Parameters? ) throws -> URLRequest { diff --git a/Adamant/Models/ForceQueryItemsEncoding.swift b/CommonKit/Sources/CommonKit/Models/ForceQueryItemsEncoding.swift similarity index 76% rename from Adamant/Models/ForceQueryItemsEncoding.swift rename to CommonKit/Sources/CommonKit/Models/ForceQueryItemsEncoding.swift index 44ae8296b..ccc664acb 100644 --- a/Adamant/Models/ForceQueryItemsEncoding.swift +++ b/CommonKit/Sources/CommonKit/Models/ForceQueryItemsEncoding.swift @@ -9,10 +9,10 @@ import Alamofire import Foundation -struct ForceQueryItemsEncoding: ParameterEncoding { - let queryItems: [URLQueryItem] +public struct ForceQueryItemsEncoding: ParameterEncoding { + public let queryItems: [URLQueryItem] - func encode( + public func encode( _ urlRequest: URLRequestConvertible, with parameters: Parameters? ) throws -> URLRequest { @@ -29,4 +29,8 @@ struct ForceQueryItemsEncoding: ParameterEncoding { urlRequest.url = urlComponents.url return urlRequest } + + public init(queryItems: [URLQueryItem]) { + self.queryItems = queryItems + } } diff --git a/Adamant/Models/InternalAPIError.swift b/CommonKit/Sources/CommonKit/Models/InternalAPIError.swift similarity index 87% rename from Adamant/Models/InternalAPIError.swift rename to CommonKit/Sources/CommonKit/Models/InternalAPIError.swift index c28ad55af..02fc8f75c 100644 --- a/Adamant/Models/InternalAPIError.swift +++ b/CommonKit/Sources/CommonKit/Models/InternalAPIError.swift @@ -6,20 +6,19 @@ // Copyright © 2023 Adamant. All rights reserved. // -import CommonKit import Foundation -enum InternalAPIError: LocalizedError { +public enum InternalAPIError: LocalizedError { case endpointBuildFailed case signTransactionFailed case parsingFailed case unknownError - func apiServiceErrorWith(error: Error) -> ApiServiceError { + public func apiServiceErrorWith(error: Error) -> ApiServiceError { .internalError(message: localizedDescription, error: error) } - var errorDescription: String? { + public var errorDescription: String? { switch self { case .endpointBuildFailed: return .localized( diff --git a/CommonKit/Sources/CommonKit/Models/Node.swift b/CommonKit/Sources/CommonKit/Models/Node/Node.swift similarity index 89% rename from CommonKit/Sources/CommonKit/Models/Node.swift rename to CommonKit/Sources/CommonKit/Models/Node/Node.swift index a2b6398a8..71f96786f 100644 --- a/CommonKit/Sources/CommonKit/Models/Node.swift +++ b/CommonKit/Sources/CommonKit/Models/Node/Node.swift @@ -93,4 +93,12 @@ public extension Node { mainOrigin.wsPort = wsPort altOrigin?.wsPort = wsPort } + + static func versionToDouble(_ value: String?) -> Double? { + guard let minNodeVersion = value?.replacingOccurrences(of: ".", with: ""), + let versionNumber = Double(minNodeVersion) + else { return nil } + + return versionNumber + } } diff --git a/CommonKit/Sources/CommonKit/Models/NodeConnectionStatus.swift b/CommonKit/Sources/CommonKit/Models/Node/NodeConnectionStatus.swift similarity index 100% rename from CommonKit/Sources/CommonKit/Models/NodeConnectionStatus.swift rename to CommonKit/Sources/CommonKit/Models/Node/NodeConnectionStatus.swift diff --git a/CommonKit/Sources/CommonKit/Models/NodeGroup.swift b/CommonKit/Sources/CommonKit/Models/Node/NodeGroup.swift similarity index 100% rename from CommonKit/Sources/CommonKit/Models/NodeGroup.swift rename to CommonKit/Sources/CommonKit/Models/Node/NodeGroup.swift diff --git a/CommonKit/Sources/CommonKit/Models/NodeOrigin.swift b/CommonKit/Sources/CommonKit/Models/Node/NodeOrigin.swift similarity index 100% rename from CommonKit/Sources/CommonKit/Models/NodeOrigin.swift rename to CommonKit/Sources/CommonKit/Models/Node/NodeOrigin.swift diff --git a/CommonKit/Sources/CommonKit/Models/Node/NodeStatusInfo.swift b/CommonKit/Sources/CommonKit/Models/Node/NodeStatusInfo.swift new file mode 100644 index 000000000..d53cfc959 --- /dev/null +++ b/CommonKit/Sources/CommonKit/Models/Node/NodeStatusInfo.swift @@ -0,0 +1,31 @@ +// +// NodeStatusInfo.swift +// Adamant +// +// Created by Andrew G on 01.11.2023. +// Copyright © 2023 Adamant. All rights reserved. +// + +import Foundation + +public struct NodeStatusInfo: Equatable { + public let ping: TimeInterval + public let height: Int + public let wsEnabled: Bool + public let wsPort: Int? + public let version: String? + + public init( + ping: TimeInterval, + height: Int, + wsEnabled: Bool, + wsPort: Int?, + version: String? + ) { + self.ping = ping + self.height = height + self.wsEnabled = wsEnabled + self.wsPort = wsPort + self.version = version + } +} diff --git a/CommonKit/Sources/CommonKit/Models/NodeType.swift b/CommonKit/Sources/CommonKit/Models/Node/NodeType.swift similarity index 100% rename from CommonKit/Sources/CommonKit/Models/NodeType.swift rename to CommonKit/Sources/CommonKit/Models/Node/NodeType.swift diff --git a/CommonKit/Sources/CommonKit/Models/ChatAsset.swift b/CommonKit/Sources/CommonKit/Models/ServerDTOs/ChatAsset.swift similarity index 93% rename from CommonKit/Sources/CommonKit/Models/ChatAsset.swift rename to CommonKit/Sources/CommonKit/Models/ServerDTOs/ChatAsset.swift index 578f22c89..990131292 100644 --- a/CommonKit/Sources/CommonKit/Models/ChatAsset.swift +++ b/CommonKit/Sources/CommonKit/Models/ServerDTOs/ChatAsset.swift @@ -8,7 +8,7 @@ import Foundation -public struct ChatAsset: Codable, Hashable { +public struct ChatAsset: Codable, Hashable, Sendable { public enum CodingKeys: String, CodingKey { case message, ownMessage = "own_message", type } diff --git a/CommonKit/Sources/CommonKit/Models/ChatRooms.swift b/CommonKit/Sources/CommonKit/Models/ServerDTOs/ChatRooms.swift similarity index 100% rename from CommonKit/Sources/CommonKit/Models/ChatRooms.swift rename to CommonKit/Sources/CommonKit/Models/ServerDTOs/ChatRooms.swift diff --git a/CommonKit/Sources/CommonKit/Models/ChatRoomsChats.swift b/CommonKit/Sources/CommonKit/Models/ServerDTOs/ChatRoomsChats.swift similarity index 100% rename from CommonKit/Sources/CommonKit/Models/ChatRoomsChats.swift rename to CommonKit/Sources/CommonKit/Models/ServerDTOs/ChatRoomsChats.swift diff --git a/CommonKit/Sources/CommonKit/Models/ChatType.swift b/CommonKit/Sources/CommonKit/Models/ServerDTOs/ChatType.swift similarity index 97% rename from CommonKit/Sources/CommonKit/Models/ChatType.swift rename to CommonKit/Sources/CommonKit/Models/ServerDTOs/ChatType.swift index 392674225..410abddf7 100644 --- a/CommonKit/Sources/CommonKit/Models/ChatType.swift +++ b/CommonKit/Sources/CommonKit/Models/ServerDTOs/ChatType.swift @@ -12,7 +12,7 @@ import Foundation /// - message: new and main message type, with 0.001 transaction fee /// - richMessage: json with additional data /// - signal: hidden system message for/from services -public enum ChatType: Hashable { +public enum ChatType: Hashable, Sendable { case unknown(raw: Int) case messageOld // 0 case message // 1 diff --git a/CommonKit/Sources/CommonKit/Models/ContactDescription.swift b/CommonKit/Sources/CommonKit/Models/ServerDTOs/ContactDescription.swift similarity index 100% rename from CommonKit/Sources/CommonKit/Models/ContactDescription.swift rename to CommonKit/Sources/CommonKit/Models/ServerDTOs/ContactDescription.swift diff --git a/CommonKit/Sources/CommonKit/Models/ServerDTOs/Delegate.swift b/CommonKit/Sources/CommonKit/Models/ServerDTOs/Delegate.swift new file mode 100644 index 000000000..dd859b943 --- /dev/null +++ b/CommonKit/Sources/CommonKit/Models/ServerDTOs/Delegate.swift @@ -0,0 +1,184 @@ +// +// Delegate.swift +// Adamant +// +// Created by Anton Boyarkin on 06/07/2018. +// Copyright © 2018 Adamant. All rights reserved. +// + +import Foundation + +public final class Delegate: Decodable { + public let username: String + public let address: String + public let publicKey: String + public let voteObsolete: String + public let voteFair: String + public let producedblocks: Int + public let missedblocks: Int + public let rate: Int + public let rank: Int + public let approval: Double + public let productivity: Double + + public var voted: Bool = false + + public enum CodingKeys: String, CodingKey { + case username + case address + case publicKey + case voteObsolete = "vote" + case voteFair = "votesWeight" + case producedblocks + case missedblocks + case rate + case rank + case approval + case productivity + } + + public init( + username: String, + address: String, + publicKey: String, + voteObsolete: String, + voteFair: String, + producedblocks: Int, + missedblocks: Int, + rate: Int, + rank: Int, + approval: Double, + productivity: Double, + voted: Bool + ) { + self.username = username + self.address = address + self.publicKey = publicKey + self.voteObsolete = voteObsolete + self.voteFair = voteFair + self.producedblocks = producedblocks + self.missedblocks = missedblocks + self.rate = rate + self.rank = rank + self.approval = approval + self.productivity = productivity + self.voted = voted + } +} + +extension Delegate: WrappableModel { + public static let ModelKey = "delegate" +} + +extension Delegate: WrappableCollection { + public static let CollectionKey = "delegates" +} + +public struct DelegateForgeDetails: Decodable { + public let nodeTimestamp: Date + public let fees: Decimal + public let rewards: Decimal + public let forged: Decimal + + public enum CodingKeys: String, CodingKey { + case nodeTimestamp + case fees + case rewards + case forged + } + + public init(from decoder: Decoder) throws { + let container = try decoder.container(keyedBy: CodingKeys.self) + + let feesStr = try container.decode(String.self, forKey: .fees) + let fees = Decimal(string: feesStr) ?? 0 + self.fees = fees.shiftedFromAdamant() + + let rewardsStr = try container.decode(String.self, forKey: .forged) + let rewards = Decimal(string: rewardsStr) ?? 0 + self.rewards = rewards.shiftedFromAdamant() + + let forgedStr = try container.decode(String.self, forKey: .forged) + let forged = Decimal(string: forgedStr) ?? 0 + self.forged = forged.shiftedFromAdamant() + + let timestamp = try container.decode(UInt64.self, forKey: .nodeTimestamp) + self.nodeTimestamp = AdamantUtilities.decodeAdamant(timestamp: TimeInterval(timestamp)) + } +} + +public struct DelegatesCountResult: Decodable { + public let nodeTimestamp: UInt64 + public let count: UInt +} + +public struct NextForgersResult: Decodable { + public let nodeTimestamp: Date + public let currentBlock: UInt64 + public let currentBlockSlot: UInt64 + public let currentSlot: UInt64 + public let delegates: [String] + + public enum CodingKeys: String, CodingKey { + case nodeTimestamp + case currentBlock + case currentBlockSlot + case currentSlot + case delegates + } + + public init(from decoder: Decoder) throws { + let container = try decoder.container(keyedBy: CodingKeys.self) + + self.currentBlock = try container.decode(UInt64.self, forKey: .currentBlock) + self.currentBlockSlot = try container.decode(UInt64.self, forKey: .currentBlockSlot) + self.currentSlot = try container.decode(UInt64.self, forKey: .currentSlot) + self.delegates = try container.decode([String].self, forKey: .delegates) + + let timestamp = try container.decode(UInt64.self, forKey: .nodeTimestamp) + self.nodeTimestamp = AdamantUtilities.decodeAdamant(timestamp: TimeInterval(timestamp)) + } +} + +public struct Block: Decodable { + public let id: String + public let version: UInt + public let timestamp: UInt64 + public let height: UInt64 + public let previousBlock:String + public let numberOfTransactions: UInt + public let totalAmount: UInt + public let totalFee: UInt + public let reward: UInt + public let payloadLength: UInt + public let payloadHash: String + public let generatorPublicKey: String + public let generatorId: String + public let blockSignature: String + public let confirmations: UInt + public let totalForged: String +} + +extension Block: WrappableModel { + public static let ModelKey = "block" +} + +extension Block: WrappableCollection { + public static let CollectionKey = "blocks" +} + +/* +{ + "username": "permit", + "address": "U8339394976025567725", + "publicKey": "01c5079a2234f69feca1b00daf4ddbd8904e13dfb67ce47c21f26377468706fa", + "producedblocks": 11153, + "missedblocks": 3, + "vote": "13373617430543", + "votesWeight": "13373617430543", + "rate": 1, + "rank": 1, + "approval": 0.95, + "productivity": 99.97 +} +*/ diff --git a/Adamant/Models/ServerResponses/GetPublicKeyResponse.swift b/CommonKit/Sources/CommonKit/Models/ServerDTOs/GetPublicKeyResponse.swift similarity index 84% rename from Adamant/Models/ServerResponses/GetPublicKeyResponse.swift rename to CommonKit/Sources/CommonKit/Models/ServerDTOs/GetPublicKeyResponse.swift index f86821ca3..711b1599e 100644 --- a/Adamant/Models/ServerResponses/GetPublicKeyResponse.swift +++ b/CommonKit/Sources/CommonKit/Models/ServerDTOs/GetPublicKeyResponse.swift @@ -7,12 +7,11 @@ // import Foundation -import CommonKit -final class GetPublicKeyResponse: ServerResponse { - let publicKey: String? +public final class GetPublicKeyResponse: ServerResponse { + public let publicKey: String? - required init(from decoder: Decoder) throws { + public required init(from decoder: Decoder) throws { let container = try decoder.container(keyedBy: CodingKeys.self) let success = try container.decode(Bool.self, forKey: .success) let error = try? container.decode(String.self, forKey: .error) diff --git a/Adamant/Models/NodeStatus.swift b/CommonKit/Sources/CommonKit/Models/ServerDTOs/NodeStatus.swift similarity index 52% rename from Adamant/Models/NodeStatus.swift rename to CommonKit/Sources/CommonKit/Models/ServerDTOs/NodeStatus.swift index 9920bb7bd..7c26b3cfc 100644 --- a/Adamant/Models/NodeStatus.swift +++ b/CommonKit/Sources/CommonKit/Models/ServerDTOs/NodeStatus.swift @@ -8,34 +8,34 @@ import Foundation -struct NodeStatus: Codable { - struct Network: Codable { - let broadhash: String? - let epoch: String? - let height: Int? - let fee: Int? - let milestone: Int? - let nethash: String? - let reward: Int? - let supply: Int? +public struct NodeStatus: Codable { + public struct Network: Codable { + public let broadhash: String? + public let epoch: String? + public let height: Int? + public let fee: Int? + public let milestone: Int? + public let nethash: String? + public let reward: Int? + public let supply: Int? } - struct Version: Codable { - let build: String? - let commit: String? - let version: String? + public struct Version: Codable { + public let build: String? + public let commit: String? + public let version: String? } - struct WsClient: Codable { - let enabled: Bool? - let port: Int? + public struct WsClient: Codable { + public let enabled: Bool? + public let port: Int? } - let success: Bool - let nodeTimestamp: TimeInterval - let network: Network? - let version: Version? - let wsClient: WsClient? + public let success: Bool + public let nodeTimestamp: TimeInterval + public let network: Network? + public let version: Version? + public let wsClient: WsClient? } /* JSON diff --git a/Adamant/Models/NormalizedTransaction.swift b/CommonKit/Sources/CommonKit/Models/ServerDTOs/NormalizedTransaction.swift similarity index 62% rename from Adamant/Models/NormalizedTransaction.swift rename to CommonKit/Sources/CommonKit/Models/ServerDTOs/NormalizedTransaction.swift index 04ccfbc29..5bd7331e6 100644 --- a/Adamant/Models/NormalizedTransaction.swift +++ b/CommonKit/Sources/CommonKit/Models/ServerDTOs/NormalizedTransaction.swift @@ -7,25 +7,45 @@ // import Foundation -import CommonKit -struct NormalizedTransaction: SignableTransaction { - let type: TransactionType - let amount: Decimal - let senderPublicKey: String - let requesterPublicKey: String? - let timestamp: UInt64 - let recipientId: String? - let asset: TransactionAsset +public struct NormalizedTransaction: SignableTransaction { + public let type: TransactionType + public let amount: Decimal + public let senderPublicKey: String + public let requesterPublicKey: String? + public let timestamp: UInt64 + public let recipientId: String? + public let asset: TransactionAsset - var date: Date { - return AdamantUtilities.decodeAdamant(timestamp: TimeInterval(timestamp)) + init( + type: TransactionType, + amount: Decimal, + senderPublicKey: String, + requesterPublicKey: String?, + timestamp: UInt64, + recipientId: String?, + asset: TransactionAsset + ) { + self.type = type + self.amount = amount + self.senderPublicKey = senderPublicKey + self.requesterPublicKey = requesterPublicKey + self.timestamp = timestamp + self.recipientId = recipientId + self.asset = asset } } -// Convinient init -extension NormalizedTransaction { - init(type: TransactionType, amount: Decimal, senderPublicKey: String, requesterPublicKey: String?, date: Date, recipientId: String?, asset: TransactionAsset) { +public extension NormalizedTransaction { + init( + type: TransactionType, + amount: Decimal, + senderPublicKey: String, + requesterPublicKey: String?, + date: Date, + recipientId: String?, + asset: TransactionAsset + ) { self.type = type self.amount = amount self.senderPublicKey = senderPublicKey @@ -34,6 +54,10 @@ extension NormalizedTransaction { self.recipientId = recipientId self.asset = asset } + + var date: Date { + return AdamantUtilities.decodeAdamant(timestamp: TimeInterval(timestamp)) + } } extension NormalizedTransaction: Decodable { @@ -47,7 +71,7 @@ extension NormalizedTransaction: Decodable { case asset } - init(from decoder: Decoder) throws { + public init(from decoder: Decoder) throws { let container = try decoder.container(keyedBy: CodingKeys.self) self.type = try container.decode(TransactionType.self, forKey: .type) @@ -63,7 +87,7 @@ extension NormalizedTransaction: Decodable { } extension NormalizedTransaction: WrappableModel { - static let ModelKey = "transaction" + public static let ModelKey = "transaction" } // MARK: - JSON diff --git a/Adamant/Models/RPCResponseModel.swift b/CommonKit/Sources/CommonKit/Models/ServerDTOs/RPCResponseModel.swift similarity index 72% rename from Adamant/Models/RPCResponseModel.swift rename to CommonKit/Sources/CommonKit/Models/ServerDTOs/RPCResponseModel.swift index 4baf1c05c..b3ccd7e2f 100644 --- a/Adamant/Models/RPCResponseModel.swift +++ b/CommonKit/Sources/CommonKit/Models/ServerDTOs/RPCResponseModel.swift @@ -8,22 +8,22 @@ import Foundation -struct RPCResponseModel: Codable { - let id: String - let result: Data +public struct RPCResponseModel: Codable { + public let id: String + public let result: Data private enum CodingKeys: String, CodingKey { case id case result } - init(from decoder: Decoder) throws { + public init(from decoder: Decoder) throws { let container = try decoder.container(keyedBy: CodingKeys.self) id = try container.decode(String.self, forKey: .id) result = try container.decode(forKey: .result) } - func serialize() -> Response? { + public func serialize() -> Response? { try? JSONDecoder().decode(Response.self, from: result) } } diff --git a/CommonKit/Sources/CommonKit/Models/RichMessage.swift b/CommonKit/Sources/CommonKit/Models/ServerDTOs/RichMessage.swift similarity index 100% rename from CommonKit/Sources/CommonKit/Models/RichMessage.swift rename to CommonKit/Sources/CommonKit/Models/ServerDTOs/RichMessage.swift diff --git a/Adamant/Models/RpcRequestModel.swift b/CommonKit/Sources/CommonKit/Models/ServerDTOs/RpcRequestModel.swift similarity index 66% rename from Adamant/Models/RpcRequestModel.swift rename to CommonKit/Sources/CommonKit/Models/ServerDTOs/RpcRequestModel.swift index d0b4d18c8..c0fd9cc3d 100644 --- a/Adamant/Models/RpcRequestModel.swift +++ b/CommonKit/Sources/CommonKit/Models/ServerDTOs/RpcRequestModel.swift @@ -8,32 +8,32 @@ import Foundation -struct RpcRequest: Encodable { - let method: String - let id: String - let params: [Parameter] - let jsonrpc: String = "2.0" +public struct RpcRequest: Encodable { + public let method: String + public let id: String + public let params: [Parameter] + public let jsonrpc: String = "2.0" - init(method: String, id: String, params: [Parameter]) { + public init(method: String, id: String, params: [Parameter]) { self.method = method self.id = id self.params = params } - init(method: String, params: [Parameter]) { + public init(method: String, params: [Parameter]) { self.method = method self.id = method self.params = params } - init(method: String) { + public init(method: String) { self.method = method self.id = method self.params = [] } } -extension RpcRequest { +public extension RpcRequest { enum Parameter { case string(String) case bool(Bool) @@ -41,7 +41,7 @@ extension RpcRequest { } extension RpcRequest.Parameter: Encodable { - func encode(to encoder: Encoder) throws { + public func encode(to encoder: Encoder) throws { var container = encoder.singleValueContainer() switch self { diff --git a/CommonKit/Sources/CommonKit/Models/ServerResponse.swift b/CommonKit/Sources/CommonKit/Models/ServerDTOs/ServerResponse.swift similarity index 100% rename from CommonKit/Sources/CommonKit/Models/ServerResponse.swift rename to CommonKit/Sources/CommonKit/Models/ServerDTOs/ServerResponse.swift diff --git a/CommonKit/Sources/CommonKit/Models/StateAsset.swift b/CommonKit/Sources/CommonKit/Models/ServerDTOs/StateAsset.swift similarity index 89% rename from CommonKit/Sources/CommonKit/Models/StateAsset.swift rename to CommonKit/Sources/CommonKit/Models/ServerDTOs/StateAsset.swift index 098049634..aeb9b5131 100644 --- a/CommonKit/Sources/CommonKit/Models/StateAsset.swift +++ b/CommonKit/Sources/CommonKit/Models/ServerDTOs/StateAsset.swift @@ -8,7 +8,7 @@ import Foundation -public struct StateAsset: Codable, Hashable { +public struct StateAsset: Codable, Hashable, Sendable { public let key: String public let value: String public let type: StateType diff --git a/CommonKit/Sources/CommonKit/Models/StateType.swift b/CommonKit/Sources/CommonKit/Models/ServerDTOs/StateType.swift similarity index 94% rename from CommonKit/Sources/CommonKit/Models/StateType.swift rename to CommonKit/Sources/CommonKit/Models/ServerDTOs/StateType.swift index 104462171..fd8d46470 100644 --- a/CommonKit/Sources/CommonKit/Models/StateType.swift +++ b/CommonKit/Sources/CommonKit/Models/ServerDTOs/StateType.swift @@ -8,7 +8,7 @@ import Foundation -public enum StateType: Equatable, Hashable { +public enum StateType: Equatable, Hashable, Sendable { case unknown(raw: Int) case keyValue // 0 diff --git a/CommonKit/Sources/CommonKit/Models/Transaction.swift b/CommonKit/Sources/CommonKit/Models/ServerDTOs/Transaction.swift similarity index 99% rename from CommonKit/Sources/CommonKit/Models/Transaction.swift rename to CommonKit/Sources/CommonKit/Models/ServerDTOs/Transaction.swift index f584a1f77..2a19cc909 100644 --- a/CommonKit/Sources/CommonKit/Models/Transaction.swift +++ b/CommonKit/Sources/CommonKit/Models/ServerDTOs/Transaction.swift @@ -8,7 +8,7 @@ import Foundation -public struct Transaction { +public struct Transaction: Sendable { public let id: UInt64 public let height: Int64 public let blockId: String diff --git a/CommonKit/Sources/CommonKit/Models/TransactionAsset.swift b/CommonKit/Sources/CommonKit/Models/ServerDTOs/TransactionAsset.swift similarity index 87% rename from CommonKit/Sources/CommonKit/Models/TransactionAsset.swift rename to CommonKit/Sources/CommonKit/Models/ServerDTOs/TransactionAsset.swift index 8b54df965..45e155158 100644 --- a/CommonKit/Sources/CommonKit/Models/TransactionAsset.swift +++ b/CommonKit/Sources/CommonKit/Models/ServerDTOs/TransactionAsset.swift @@ -8,7 +8,7 @@ import Foundation -public struct TransactionAsset: Codable, Hashable { +public struct TransactionAsset: Codable, Hashable, Sendable { public let chat: ChatAsset? public let state: StateAsset? public let votes: VotesAsset? diff --git a/Adamant/Models/ServerResponses/TransactionIdResponse.swift b/CommonKit/Sources/CommonKit/Models/ServerDTOs/TransactionIdResponse.swift similarity index 85% rename from Adamant/Models/ServerResponses/TransactionIdResponse.swift rename to CommonKit/Sources/CommonKit/Models/ServerDTOs/TransactionIdResponse.swift index 158a09337..49ca1d0ca 100644 --- a/Adamant/Models/ServerResponses/TransactionIdResponse.swift +++ b/CommonKit/Sources/CommonKit/Models/ServerDTOs/TransactionIdResponse.swift @@ -7,12 +7,11 @@ // import Foundation -import CommonKit -final class TransactionIdResponse: ServerResponse { - let transactionId: UInt64? +public final class TransactionIdResponse: ServerResponse { + public let transactionId: UInt64? - required init(from decoder: Decoder) throws { + public required init(from decoder: Decoder) throws { let container = try decoder.container(keyedBy: CodingKeys.self) let success = try container.decode(Bool.self, forKey: .success) let error = try? container.decode(String.self, forKey: .error) diff --git a/CommonKit/Sources/CommonKit/Models/TransactionType.swift b/CommonKit/Sources/CommonKit/Models/ServerDTOs/TransactionType.swift similarity index 97% rename from CommonKit/Sources/CommonKit/Models/TransactionType.swift rename to CommonKit/Sources/CommonKit/Models/ServerDTOs/TransactionType.swift index 74b69e3f9..87eb8b369 100644 --- a/CommonKit/Sources/CommonKit/Models/TransactionType.swift +++ b/CommonKit/Sources/CommonKit/Models/ServerDTOs/TransactionType.swift @@ -8,7 +8,7 @@ import Foundation -public enum TransactionType: Hashable { +public enum TransactionType: Hashable, Sendable { case unknown(raw: Int) case send // 0 case signature // 1 diff --git a/Adamant/Models/UnregisteredTransaction.swift b/CommonKit/Sources/CommonKit/Models/ServerDTOs/UnregisteredTransaction.swift similarity index 64% rename from Adamant/Models/UnregisteredTransaction.swift rename to CommonKit/Sources/CommonKit/Models/ServerDTOs/UnregisteredTransaction.swift index e23543153..32bcb1e2a 100644 --- a/Adamant/Models/UnregisteredTransaction.swift +++ b/CommonKit/Sources/CommonKit/Models/ServerDTOs/UnregisteredTransaction.swift @@ -7,19 +7,40 @@ // import Foundation -import CommonKit import BigInt -struct UnregisteredTransaction: Hashable { - let type: TransactionType - let timestamp: UInt64 - let senderPublicKey: String - let senderId: String - let recipientId: String? - let amount: Decimal - let signature: String - let asset: TransactionAsset - let requesterPublicKey: String? +public struct UnregisteredTransaction: Hashable { + public let type: TransactionType + public let timestamp: UInt64 + public let senderPublicKey: String + public let senderId: String + public let recipientId: String? + public let amount: Decimal + public let signature: String + public let asset: TransactionAsset + public let requesterPublicKey: String? + + public init( + type: TransactionType, + timestamp: UInt64, + senderPublicKey: String, + senderId: String, + recipientId: String?, + amount: Decimal, + signature: String, + asset: TransactionAsset, + requesterPublicKey: String? + ) { + self.type = type + self.timestamp = timestamp + self.senderPublicKey = senderPublicKey + self.senderId = senderId + self.recipientId = recipientId + self.amount = amount + self.signature = signature + self.asset = asset + self.requesterPublicKey = requesterPublicKey + } } extension UnregisteredTransaction: Codable { @@ -34,7 +55,7 @@ extension UnregisteredTransaction: Codable { case asset } - init(from decoder: Decoder) throws { + public init(from decoder: Decoder) throws { let container = try decoder.container(keyedBy: CodingKeys.self) self.type = try container.decode(TransactionType.self, forKey: .type) @@ -50,7 +71,7 @@ extension UnregisteredTransaction: Codable { self.requesterPublicKey = "" } - func encode(to encoder: Encoder) throws { + public func encode(to encoder: Encoder) throws { var container = encoder.container(keyedBy: CodingKeys.self) try container.encode(type, forKey: .type) // TransactionType diff --git a/CommonKit/Sources/CommonKit/Models/VotesAsset.swift b/CommonKit/Sources/CommonKit/Models/ServerDTOs/VotesAsset.swift similarity index 93% rename from CommonKit/Sources/CommonKit/Models/VotesAsset.swift rename to CommonKit/Sources/CommonKit/Models/ServerDTOs/VotesAsset.swift index ff311145b..b31b8d4bd 100644 --- a/CommonKit/Sources/CommonKit/Models/VotesAsset.swift +++ b/CommonKit/Sources/CommonKit/Models/ServerDTOs/VotesAsset.swift @@ -8,7 +8,7 @@ import Foundation -public struct VotesAsset: Hashable { +public struct VotesAsset: Hashable, Sendable { public let votes: [String] public init(votes: [String]) { diff --git a/Adamant/ServiceProtocols/APICoreProtocol.swift b/CommonKit/Sources/CommonKit/Protocols/APICoreProtocol.swift similarity index 97% rename from Adamant/ServiceProtocols/APICoreProtocol.swift rename to CommonKit/Sources/CommonKit/Protocols/APICoreProtocol.swift index aac00af71..78c2bea6e 100644 --- a/Adamant/ServiceProtocols/APICoreProtocol.swift +++ b/CommonKit/Sources/CommonKit/Protocols/APICoreProtocol.swift @@ -8,11 +8,10 @@ import Foundation import Alamofire -import CommonKit -enum ApiCommands {} +public enum ApiCommands {} -protocol APICoreProtocol: Actor { +public protocol APICoreProtocol: Actor { func sendRequestBasic( origin: NodeOrigin, path: String, @@ -30,7 +29,7 @@ protocol APICoreProtocol: Actor { ) async -> APIResponseModel } -extension APICoreProtocol { +public extension APICoreProtocol { var emptyParameters: [String: Bool] { [:] } func sendRequest( diff --git a/Adamant/ServiceProtocols/AdamantCore/AdamantCore.swift b/CommonKit/Sources/CommonKit/Protocols/AdamantCore.swift similarity index 90% rename from Adamant/ServiceProtocols/AdamantCore/AdamantCore.swift rename to CommonKit/Sources/CommonKit/Protocols/AdamantCore.swift index 0be586ec4..d743e04d9 100644 --- a/Adamant/ServiceProtocols/AdamantCore/AdamantCore.swift +++ b/CommonKit/Sources/CommonKit/Protocols/AdamantCore.swift @@ -7,13 +7,11 @@ // import Foundation -import CommonKit -protocol AdamantCore: AnyObject { +public protocol AdamantCore: AnyObject { // MARK: - Keys func createHashFor(passphrase: String) -> String? func createKeypairFor(passphrase: String) -> Keypair? - func generateNewPassphrase() -> String // MARK: - Signing transactions func sign(transaction: SignableTransaction, senderId: String, keypair: Keypair) -> String? @@ -25,7 +23,7 @@ protocol AdamantCore: AnyObject { func decodeValue(rawMessage: String, rawNonce: String, privateKey: String) -> String? } -protocol SignableTransaction { +public protocol SignableTransaction { var type: TransactionType { get } var amount: Decimal { get } var senderPublicKey: String { get } diff --git a/Adamant/ServiceProtocols/ApiService.swift b/CommonKit/Sources/CommonKit/Protocols/ApiService.swift similarity index 98% rename from Adamant/ServiceProtocols/ApiService.swift rename to CommonKit/Sources/CommonKit/Protocols/ApiService.swift index 343aab1b3..5976e4c6d 100644 --- a/Adamant/ServiceProtocols/ApiService.swift +++ b/CommonKit/Sources/CommonKit/Protocols/ApiService.swift @@ -8,9 +8,8 @@ import Foundation import Alamofire -import CommonKit -protocol ApiService: WalletApiService { +public protocol ApiService: WalletApiService { // MARK: - Accounts func getAccount(byPassphrase passphrase: String) async -> ApiServiceResult func getAccount(byPublicKey publicKey: String) async -> ApiServiceResult diff --git a/Adamant/ServiceProtocols/NodesAdditionalParamsStorageProtocol.swift b/CommonKit/Sources/CommonKit/Protocols/NodesAdditionalParamsStorageProtocol.swift similarity index 73% rename from Adamant/ServiceProtocols/NodesAdditionalParamsStorageProtocol.swift rename to CommonKit/Sources/CommonKit/Protocols/NodesAdditionalParamsStorageProtocol.swift index 16a5e4df8..1209229ff 100644 --- a/Adamant/ServiceProtocols/NodesAdditionalParamsStorageProtocol.swift +++ b/CommonKit/Sources/CommonKit/Protocols/NodesAdditionalParamsStorageProtocol.swift @@ -6,16 +6,14 @@ // Copyright © 2023 Adamant. All rights reserved. // -import CommonKit - // MARK: - SecuredStore keys -extension StoreKey { +public extension StoreKey { enum NodesAdditionalParamsStorage { - static let fastestNodeMode = "nodesAdditionalParamsStorage.fastestNodeMode" + public static let fastestNodeMode = "nodesAdditionalParamsStorage.fastestNodeMode" } } -protocol NodesAdditionalParamsStorageProtocol { +public protocol NodesAdditionalParamsStorageProtocol { func isFastestNodeMode(group: NodeGroup) -> Bool func fastestNodeMode(group: NodeGroup) -> AnyObservable func setFastestNodeMode(groups: Set, value: Bool) diff --git a/Adamant/ServiceProtocols/NodesMergingService.swift b/CommonKit/Sources/CommonKit/Protocols/NodesMergingService.swift similarity index 84% rename from Adamant/ServiceProtocols/NodesMergingService.swift rename to CommonKit/Sources/CommonKit/Protocols/NodesMergingService.swift index 91bbe09ed..63bd6ca22 100644 --- a/Adamant/ServiceProtocols/NodesMergingService.swift +++ b/CommonKit/Sources/CommonKit/Protocols/NodesMergingService.swift @@ -6,9 +6,7 @@ // Copyright © 2024 Adamant. All rights reserved. // -import CommonKit - -protocol NodesMergingService { +public protocol NodesMergingService { func merge( savedNodes: [NodeGroup: [Node]], defaultNodes: [NodeGroup: [Node]] diff --git a/Adamant/ServiceProtocols/NodesStorageProtocol.swift b/CommonKit/Sources/CommonKit/Protocols/NodesStorageProtocol.swift similarity index 82% rename from Adamant/ServiceProtocols/NodesStorageProtocol.swift rename to CommonKit/Sources/CommonKit/Protocols/NodesStorageProtocol.swift index b7d4c79e8..d47b2489d 100644 --- a/Adamant/ServiceProtocols/NodesStorageProtocol.swift +++ b/CommonKit/Sources/CommonKit/Protocols/NodesStorageProtocol.swift @@ -6,17 +6,16 @@ // Copyright © 2023 Adamant. All rights reserved. // -import CommonKit import Foundation // MARK: - SecuredStore keys -extension StoreKey { +public extension StoreKey { enum NodesStorage { - static let nodes = "nodesStorage.nodes" + public static let nodes = "nodesStorage.nodes" } } -protocol NodesStorageProtocol { +public protocol NodesStorageProtocol { var nodesPublisher: AnyObservable<[NodeGroup: [Node]]> { get } func getNodesPublisher(group: NodeGroup) -> AnyObservable<[Node]> diff --git a/Adamant/Modules/Wallets/WalletApiService.swift b/CommonKit/Sources/CommonKit/Protocols/WalletApiService.swift similarity index 88% rename from Adamant/Modules/Wallets/WalletApiService.swift rename to CommonKit/Sources/CommonKit/Protocols/WalletApiService.swift index cc3af7d60..21afb4d25 100644 --- a/Adamant/Modules/Wallets/WalletApiService.swift +++ b/CommonKit/Sources/CommonKit/Protocols/WalletApiService.swift @@ -8,7 +8,7 @@ import Foundation -protocol WalletApiService { +public protocol WalletApiService { var chosenFastestNodeId: UUID? { get } var hasActiveNode: Bool { get } diff --git a/Adamant/Services/APICore.swift b/CommonKit/Sources/CommonKit/Services/APICore.swift similarity index 95% rename from Adamant/Services/APICore.swift rename to CommonKit/Sources/CommonKit/Services/APICore.swift index b1a6389c6..22b104f0d 100644 --- a/Adamant/Services/APICore.swift +++ b/CommonKit/Sources/CommonKit/Services/APICore.swift @@ -8,9 +8,8 @@ import Foundation import Alamofire -import CommonKit -actor APICore: APICoreProtocol { +public actor APICore: APICoreProtocol { private let responseQueue = DispatchQueue( label: "com.adamant.response-queue", qos: .userInteractive @@ -25,7 +24,7 @@ actor APICore: APICoreProtocol { return Alamofire.Session.init(configuration: configuration) }() - func sendRequestBasic( + public func sendRequestBasic( origin: NodeOrigin, path: String, method: HTTPMethod, @@ -51,7 +50,7 @@ actor APICore: APICoreProtocol { } } - func sendRequestBasic( + public func sendRequestBasic( origin: NodeOrigin, path: String, method: HTTPMethod, @@ -78,6 +77,8 @@ actor APICore: APICoreProtocol { ) } } + + public init() {} } private extension APICore { diff --git a/Adamant/Services/AdamantNodesMergingService.swift b/CommonKit/Sources/CommonKit/Services/AdamantNodesMergingService.swift similarity index 95% rename from Adamant/Services/AdamantNodesMergingService.swift rename to CommonKit/Sources/CommonKit/Services/AdamantNodesMergingService.swift index ac71ebd46..7edf223ba 100644 --- a/Adamant/Services/AdamantNodesMergingService.swift +++ b/CommonKit/Sources/CommonKit/Services/AdamantNodesMergingService.swift @@ -6,10 +6,8 @@ // Copyright © 2024 Adamant. All rights reserved. // -import CommonKit - -struct AdamantNodesMergingService: NodesMergingService { - func merge( +public struct AdamantNodesMergingService: NodesMergingService { + public func merge( savedNodes: [NodeGroup: [Node]], defaultNodes: [NodeGroup: [Node]] ) -> [NodeGroup: [Node]] { @@ -27,6 +25,8 @@ struct AdamantNodesMergingService: NodesMergingService { return resultNodes } + + public init() {} } private extension AdamantNodesMergingService { diff --git a/Adamant/Services/ApiService/AdamantApi+Accounts.swift b/CommonKit/Sources/CommonKit/Services/ApiService/AdamantApi+Accounts.swift similarity index 85% rename from Adamant/Services/ApiService/AdamantApi+Accounts.swift rename to CommonKit/Sources/CommonKit/Services/ApiService/AdamantApi+Accounts.swift index 00c4aabfc..7d40a12e9 100644 --- a/Adamant/Services/ApiService/AdamantApi+Accounts.swift +++ b/CommonKit/Sources/CommonKit/Services/ApiService/AdamantApi+Accounts.swift @@ -7,9 +7,8 @@ // import Foundation -import CommonKit -extension ApiCommands { +public extension ApiCommands { static let Accounts = ( root: "/api/accounts", getPublicKey: "/api/accounts/getPublicKey", @@ -20,7 +19,7 @@ extension ApiCommands { // MARK: - Accounts extension AdamantApiService { /// Get account by passphrase. - func getAccount(byPassphrase passphrase: String) async -> ApiServiceResult { + public func getAccount(byPassphrase passphrase: String) async -> ApiServiceResult { guard let keypair = adamantCore.createKeypairFor(passphrase: passphrase) else { return .failure(.accountNotFound) } @@ -29,7 +28,7 @@ extension AdamantApiService { } /// Get account by publicKey - func getAccount(byPublicKey publicKey: String) async -> ApiServiceResult { + public func getAccount(byPublicKey publicKey: String) async -> ApiServiceResult { switch await request({ apiCore, origin in let response: ApiServiceResult> = await apiCore.sendRequestJsonResponse( origin: origin, @@ -53,7 +52,7 @@ extension AdamantApiService { } } - func getAccount(byAddress address: String) async -> ApiServiceResult { + public func getAccount(byAddress address: String) async -> ApiServiceResult { await request { apiCore, origin in let response: ApiServiceResult< ServerModelResponse diff --git a/Adamant/Services/ApiService/AdamantApi+Chats.swift b/CommonKit/Sources/CommonKit/Services/ApiService/AdamantApi+Chats.swift similarity index 94% rename from Adamant/Services/ApiService/AdamantApi+Chats.swift rename to CommonKit/Sources/CommonKit/Services/ApiService/AdamantApi+Chats.swift index 601d74937..ea5b4b289 100644 --- a/Adamant/Services/ApiService/AdamantApi+Chats.swift +++ b/CommonKit/Sources/CommonKit/Services/ApiService/AdamantApi+Chats.swift @@ -8,9 +8,8 @@ import Foundation import UIKit -import CommonKit -extension ApiCommands { +public extension ApiCommands { static let Chats = ( root: "/api/chats", get: "/api/chats/get", @@ -21,7 +20,7 @@ extension ApiCommands { } extension AdamantApiService { - func getMessageTransactions( + public func getMessageTransactions( address: String, height: Int64?, offset: Int? @@ -53,7 +52,7 @@ extension AdamantApiService { return response.flatMap { $0.resolved() } } - func sendMessageTransaction( + public func sendMessageTransaction( transaction: UnregisteredTransaction ) async -> ApiServiceResult { await sendTransaction( @@ -62,7 +61,7 @@ extension AdamantApiService { ) } - func getChatRooms( + public func getChatRooms( address: String, offset: Int? ) async -> ApiServiceResult { @@ -83,7 +82,7 @@ extension AdamantApiService { } } - func getChatMessages( + public func getChatMessages( address: String, addressRecipient: String, offset: Int?, diff --git a/Adamant/Services/ApiService/AdamantApi+Delegates.swift b/CommonKit/Sources/CommonKit/Services/ApiService/AdamantApi+Delegates.swift similarity index 92% rename from Adamant/Services/ApiService/AdamantApi+Delegates.swift rename to CommonKit/Sources/CommonKit/Services/ApiService/AdamantApi+Delegates.swift index fa3fe2f6f..a466aef71 100644 --- a/Adamant/Services/ApiService/AdamantApi+Delegates.swift +++ b/CommonKit/Sources/CommonKit/Services/ApiService/AdamantApi+Delegates.swift @@ -8,9 +8,8 @@ import Foundation import UIKit -import CommonKit -extension ApiCommands { +public extension ApiCommands { static let Delegates = ( root: "/api/delegates", getDelegates: "/api/delegates", @@ -23,11 +22,11 @@ extension ApiCommands { } extension AdamantApiService { - func getDelegates(limit: Int) async -> ApiServiceResult<[Delegate]> { + public func getDelegates(limit: Int) async -> ApiServiceResult<[Delegate]> { await getDelegates(limit: limit, offset: .zero, currentDelegates: [Delegate]()) } - func getDelegates( + public func getDelegates( limit: Int, offset: Int, currentDelegates: [Delegate] @@ -58,7 +57,7 @@ extension AdamantApiService { } } - func getDelegatesWithVotes(for address: String, limit: Int) async -> ApiServiceResult<[Delegate]> { + public func getDelegatesWithVotes(for address: String, limit: Int) async -> ApiServiceResult<[Delegate]> { let response = await getVotes(for: address) switch response { @@ -81,7 +80,7 @@ extension AdamantApiService { } } - func getForgedByAccount(publicKey: String) async -> ApiServiceResult { + public func getForgedByAccount(publicKey: String) async -> ApiServiceResult { await request { core, origin in await core.sendRequestJsonResponse( origin: origin, @@ -93,7 +92,7 @@ extension AdamantApiService { } } - func getForgingTime(for delegate: Delegate) async -> ApiServiceResult { + public func getForgingTime(for delegate: Delegate) async -> ApiServiceResult { await getNextForgers().map { nextForgers in var forgingTime = -1 if let fIndex = nextForgers.delegates.firstIndex(of: delegate.publicKey) { @@ -124,7 +123,7 @@ extension AdamantApiService { } } - func getVotes(for address: String) async -> ApiServiceResult<[Delegate]> { + public func getVotes(for address: String) async -> ApiServiceResult<[Delegate]> { let response: ApiServiceResult> response = await request { core, origin in await core.sendRequestJsonResponse( @@ -139,7 +138,7 @@ extension AdamantApiService { return response.map { $0.collection ?? .init() } } - func voteForDelegates( + public func voteForDelegates( from address: String, keypair: Keypair, votes: [DelegateVote] diff --git a/Adamant/Services/ApiService/AdamantApi+Keys.swift b/CommonKit/Sources/CommonKit/Services/ApiService/AdamantApi+Keys.swift similarity index 86% rename from Adamant/Services/ApiService/AdamantApi+Keys.swift rename to CommonKit/Sources/CommonKit/Services/ApiService/AdamantApi+Keys.swift index 2fe178b90..2e6f2ca6f 100644 --- a/Adamant/Services/ApiService/AdamantApi+Keys.swift +++ b/CommonKit/Sources/CommonKit/Services/ApiService/AdamantApi+Keys.swift @@ -7,10 +7,9 @@ // import Foundation -import CommonKit extension AdamantApiService { - func getPublicKey(byAddress address: String) async -> ApiServiceResult { + public func getPublicKey(byAddress address: String) async -> ApiServiceResult { let response: ApiServiceResult = await request { service, origin in await service.sendRequestJsonResponse( origin: origin, diff --git a/Adamant/Services/ApiService/AdamantApi+States.swift b/CommonKit/Sources/CommonKit/Services/ApiService/AdamantApi+States.swift similarity index 91% rename from Adamant/Services/ApiService/AdamantApi+States.swift rename to CommonKit/Sources/CommonKit/Services/ApiService/AdamantApi+States.swift index 08c5c21d2..705d51a1d 100644 --- a/Adamant/Services/ApiService/AdamantApi+States.swift +++ b/CommonKit/Sources/CommonKit/Services/ApiService/AdamantApi+States.swift @@ -8,9 +8,8 @@ import Foundation import UIKit -import CommonKit -extension ApiCommands { +public extension ApiCommands { static let States = ( root: "/api/states", get: "/api/states/get", @@ -19,9 +18,9 @@ extension ApiCommands { } extension AdamantApiService { - static let KvsFee: Decimal = 0.001 + public static let KvsFee: Decimal = 0.001 - func store( + public func store( key: String, value: String, type: StateType, @@ -54,7 +53,7 @@ extension AdamantApiService { ) } - func get(key: String, sender: String) async -> ApiServiceResult { + public func get(key: String, sender: String) async -> ApiServiceResult { // MARK: 1. Prepare let parameters = [ "senderId": sender, diff --git a/Adamant/Services/ApiService/AdamantApi+Transactions.swift b/CommonKit/Sources/CommonKit/Services/ApiService/AdamantApi+Transactions.swift similarity index 92% rename from Adamant/Services/ApiService/AdamantApi+Transactions.swift rename to CommonKit/Sources/CommonKit/Services/ApiService/AdamantApi+Transactions.swift index d33f5cbda..9abd6da8c 100644 --- a/Adamant/Services/ApiService/AdamantApi+Transactions.swift +++ b/CommonKit/Sources/CommonKit/Services/ApiService/AdamantApi+Transactions.swift @@ -7,9 +7,8 @@ // import Foundation -import CommonKit -extension ApiCommands { +public extension ApiCommands { static let Transactions = ( root: "/api/transactions", getTransaction: "/api/transactions/get", @@ -19,7 +18,7 @@ extension ApiCommands { } extension AdamantApiService { - func sendTransaction( + public func sendTransaction( path: String, transaction: UnregisteredTransaction ) async -> ApiServiceResult { @@ -36,7 +35,7 @@ extension AdamantApiService { return response.flatMap { $0.resolved() } } - func sendDelegateVoteTransaction( + public func sendDelegateVoteTransaction( path: String, transaction: UnregisteredTransaction ) async -> ApiServiceResult { @@ -56,11 +55,11 @@ extension AdamantApiService { } } - func getTransaction(id: UInt64) async -> ApiServiceResult { + public func getTransaction(id: UInt64) async -> ApiServiceResult { await getTransaction(id: id, withAsset: false) } - func getTransaction(id: UInt64, withAsset: Bool) async -> ApiServiceResult { + public func getTransaction(id: UInt64, withAsset: Bool) async -> ApiServiceResult { let response: ApiServiceResult> response = await request { core, origin in await core.sendRequestJsonResponse( @@ -78,7 +77,7 @@ extension AdamantApiService { return response.flatMap { $0.resolved() } } - func getTransactions( + public func getTransactions( forAccount account: String, type: TransactionType, fromHeight: Int64?, @@ -95,7 +94,7 @@ extension AdamantApiService { ) } - func getTransactions( + public func getTransactions( forAccount account: String, type: TransactionType, fromHeight: Int64?, diff --git a/Adamant/Services/ApiService/AdamantApi+Transfers.swift b/CommonKit/Sources/CommonKit/Services/ApiService/AdamantApi+Transfers.swift similarity index 89% rename from Adamant/Services/ApiService/AdamantApi+Transfers.swift rename to CommonKit/Sources/CommonKit/Services/ApiService/AdamantApi+Transfers.swift index e2c9cf7b3..700f0ed3c 100644 --- a/Adamant/Services/ApiService/AdamantApi+Transfers.swift +++ b/CommonKit/Sources/CommonKit/Services/ApiService/AdamantApi+Transfers.swift @@ -7,19 +7,18 @@ // import Foundation -import CommonKit import CryptoSwift import BigInt extension AdamantApiService { - func transferFunds(transaction: UnregisteredTransaction) async -> ApiServiceResult { + public func transferFunds(transaction: UnregisteredTransaction) async -> ApiServiceResult { return await sendTransaction( path: ApiCommands.Transactions.processTransaction, transaction: transaction ) } - func transferFunds( + public func transferFunds( sender: String, recipient: String, amount: Decimal, diff --git a/Adamant/Services/ApiService/AdamantApiCore.swift b/CommonKit/Sources/CommonKit/Services/ApiService/AdamantApiCore.swift similarity index 74% rename from Adamant/Services/ApiService/AdamantApiCore.swift rename to CommonKit/Sources/CommonKit/Services/ApiService/AdamantApiCore.swift index 0904f8a65..f1f9bffcb 100644 --- a/Adamant/Services/ApiService/AdamantApiCore.swift +++ b/CommonKit/Sources/CommonKit/Services/ApiService/AdamantApiCore.swift @@ -8,21 +8,22 @@ import Foundation import Alamofire -import CommonKit -extension ApiCommands { +public extension ApiCommands { static let status = "/api/node/status" static let version = "/api/peers/version" } -final class AdamantApiCore { - let apiCore: APICoreProtocol +public final class AdamantApiCore { + public let apiCore: APICoreProtocol - init(apiCore: APICoreProtocol) { + public init(apiCore: APICoreProtocol) { self.apiCore = apiCore } - func getNodeStatus(origin: NodeOrigin) async -> ApiServiceResult { + public func getNodeStatus( + origin: NodeOrigin + ) async -> ApiServiceResult { await apiCore.sendRequestJsonResponse( origin: origin, path: ApiCommands.status @@ -31,7 +32,9 @@ final class AdamantApiCore { } extension AdamantApiCore: BlockchainHealthCheckableService { - func getStatusInfo(origin: NodeOrigin) async -> ApiServiceResult { + public func getStatusInfo( + origin: NodeOrigin + ) async -> ApiServiceResult { let startTimestamp = Date.now.timeIntervalSince1970 let statusResponse = await getNodeStatus(origin: origin) let ping = Date.now.timeIntervalSince1970 - startTimestamp diff --git a/Adamant/Services/ApiService/AdamantApiService.swift b/CommonKit/Sources/CommonKit/Services/ApiService/AdamantApiService.swift similarity index 72% rename from Adamant/Services/ApiService/AdamantApiService.swift rename to CommonKit/Sources/CommonKit/Services/ApiService/AdamantApiService.swift index 87d8ad978..fd7574f53 100644 --- a/Adamant/Services/ApiService/AdamantApiService.swift +++ b/CommonKit/Sources/CommonKit/Services/ApiService/AdamantApiService.swift @@ -6,14 +6,13 @@ // Copyright © 2018 Adamant. All rights reserved. // -import CommonKit import Foundation -final class AdamantApiService { - let adamantCore: AdamantCore - let service: BlockchainHealthCheckWrapper +public final class AdamantApiService { + public let adamantCore: AdamantCore + public let service: BlockchainHealthCheckWrapper - init( + public init( healthCheckWrapper: BlockchainHealthCheckWrapper, adamantCore: AdamantCore ) { @@ -21,7 +20,7 @@ final class AdamantApiService { self.adamantCore = adamantCore } - func request( + public func request( _ request: @Sendable (APICoreProtocol, NodeOrigin) async -> ApiServiceResult ) async -> ApiServiceResult { await service.request { admApiCore, origin in @@ -31,15 +30,15 @@ final class AdamantApiService { } extension AdamantApiService: ApiService { - var chosenFastestNodeId: UUID? { + public var chosenFastestNodeId: UUID? { service.chosenFastestNodeId } - func healthCheck() { + public func healthCheck() { service.healthCheck() } - var hasActiveNode: Bool { + public var hasActiveNode: Bool { !service.sortedAllowedNodes.isEmpty } } diff --git a/Adamant/Services/BlockchainHealthCheckWrapper.swift b/CommonKit/Sources/CommonKit/Services/HealthCheck/BlockchainHealthCheckWrapper.swift similarity index 76% rename from Adamant/Services/BlockchainHealthCheckWrapper.swift rename to CommonKit/Sources/CommonKit/Services/HealthCheck/BlockchainHealthCheckWrapper.swift index 89c952a8a..6059acb78 100644 --- a/Adamant/Services/BlockchainHealthCheckWrapper.swift +++ b/CommonKit/Sources/CommonKit/Services/HealthCheck/BlockchainHealthCheckWrapper.swift @@ -6,50 +6,55 @@ // Copyright © 2023 Adamant. All rights reserved. // -import CommonKit import Foundation -protocol BlockchainHealthCheckableService { +public protocol BlockchainHealthCheckableService { associatedtype Error: HealthCheckableError func getStatusInfo(origin: NodeOrigin) async -> Result } -final class BlockchainHealthCheckWrapper< +public final class BlockchainHealthCheckWrapper< Service: BlockchainHealthCheckableService >: HealthCheckWrapper { private let nodesStorage: NodesStorageProtocol private let updateNodesAvailabilityLock = NSLock() + private let params: BlockchainHealthCheckParams @Atomic private var currentRequests = Set() - init( + public init( service: Service, nodesStorage: NodesStorageProtocol, nodesAdditionalParamsStorage: NodesAdditionalParamsStorageProtocol, - nodeGroup: NodeGroup + isActive: Bool, + params: BlockchainHealthCheckParams, + connection: AnyObservable? ) { self.nodesStorage = nodesStorage + self.params = params super.init( service: service, - normalUpdateInterval: nodeGroup.normalUpdateInterval, - crucialUpdateInterval: nodeGroup.crucialUpdateInterval, - nodeGroup: nodeGroup + isActive: isActive, + name: params.name, + normalUpdateInterval: params.normalUpdateInterval, + crucialUpdateInterval: params.crucialUpdateInterval, + connection: connection ) nodesStorage - .getNodesPublisher(group: nodeGroup) + .getNodesPublisher(group: params.group) .sink { [weak self] in self?.nodes = $0 } .store(in: &subscriptions) nodesAdditionalParamsStorage - .fastestNodeMode(group: nodeGroup) + .fastestNodeMode(group: params.group) .sink { [weak self] in self?.fastestNodeMode = $0 } .store(in: &subscriptions) } - override func healthCheck() { + public override func healthCheck() { super.healthCheck() Task { @@ -99,7 +104,7 @@ private extension BlockchainHealthCheckWrapper { markAsOfflineIfFailed: false ) { case .success: - nodesStorage.updateNode(id: node.id, group: nodeGroup) { $0.preferMainOrigin = true } + nodesStorage.updateNode(id: node.id, group: params.group) { $0.preferMainOrigin = true } forceInclude = node.id case .failure: switch await updateNodeStatusInfo( @@ -108,7 +113,7 @@ private extension BlockchainHealthCheckWrapper { markAsOfflineIfFailed: true ) { case .success: - nodesStorage.updateNode(id: node.id, group: nodeGroup) { $0.preferMainOrigin = false } + nodesStorage.updateNode(id: node.id, group: params.group) { $0.preferMainOrigin = false } forceInclude = node.id case .failure, .none: break @@ -130,7 +135,7 @@ private extension BlockchainHealthCheckWrapper { return .success(()) case let .failure(error): if markAsOfflineIfFailed { - nodesStorage.updateNode(id: id, group: nodeGroup) { $0.connectionStatus = .offline } + updateNode(id: id) { $0.connectionStatus = .offline } } return .failure(error) @@ -138,7 +143,7 @@ private extension BlockchainHealthCheckWrapper { } func applyStatusInfo(id: UUID, info: NodeStatusInfo) { - nodesStorage.updateNode(id: id, group: nodeGroup) { node in + updateNode(id: id) { node in node.wsEnabled = info.wsEnabled node.updateWsPort(info.wsPort) node.version = info.version @@ -146,8 +151,8 @@ private extension BlockchainHealthCheckWrapper { node.ping = info.ping guard - let versionNumber = Node.stringToDouble(info.version), - versionNumber < nodeGroup.minNodeVersion + let versionNumber = Node.versionToDouble(info.version), + versionNumber < params.minNodeVersion else { return } node.connectionStatus = .notAllowed(.outdatedApiVersion) @@ -164,15 +169,16 @@ private extension BlockchainHealthCheckWrapper { let actualHeightsRange = getActualNodeHeightsRange( heights: workingNodes.compactMap { $0.height }, - group: nodeGroup + group: params.group, + nodeHeightEpsilon: params.nodeHeightEpsilon ) workingNodes.forEach { node in var status: NodeConnectionStatus? - let actualNodeVersion = Node.stringToDouble(node.version) + let actualNodeVersion = Node.versionToDouble(node.version) if let actualNodeVersion = actualNodeVersion, - actualNodeVersion < nodeGroup.minNodeVersion { + actualNodeVersion < params.minNodeVersion { status = .notAllowed(.outdatedApiVersion) } else { status = node.height.map { height in @@ -182,9 +188,17 @@ private extension BlockchainHealthCheckWrapper { } ?? .none } - nodesStorage.updateNode(id: node.id, group: nodeGroup) { $0.connectionStatus = status } + updateNode(id: node.id) { $0.connectionStatus = status } } } + + func updateNode(id: UUID, mutate: (inout Node) -> Void) { + nodesStorage.updateNode( + id: id, + group: params.group, + mutate: mutate + ) + } } private extension Node { @@ -203,13 +217,17 @@ private struct NodeHeightsInterval { var count: Int } -private func getActualNodeHeightsRange(heights: [Int], group: NodeGroup) -> ClosedRange? { +private func getActualNodeHeightsRange( + heights: [Int], + group: NodeGroup, + nodeHeightEpsilon: Int +) -> ClosedRange? { let heights = heights.sorted() var bestInterval: NodeHeightsInterval? for i in heights.indices { var currentInterval = NodeHeightsInterval( - range: heights[i] ... heights[i] + group.nodeHeightEpsilon - 1, + range: heights[i] ... heights[i] + nodeHeightEpsilon - 1, count: 1 ) diff --git a/Adamant/Services/HealthCheckWrapper.swift b/CommonKit/Sources/CommonKit/Services/HealthCheck/HealthCheckWrapper.swift similarity index 68% rename from Adamant/Services/HealthCheckWrapper.swift rename to CommonKit/Sources/CommonKit/Services/HealthCheck/HealthCheckWrapper.swift index bbdf8ab6e..7792fd402 100644 --- a/Adamant/Services/HealthCheckWrapper.swift +++ b/CommonKit/Sources/CommonKit/Services/HealthCheck/HealthCheckWrapper.swift @@ -6,44 +6,54 @@ // Copyright © 2023 Adamant. All rights reserved. // -import CommonKit import Foundation import Combine import UIKit -protocol HealthCheckableError: Error { +public protocol HealthCheckableError: Error { var isNetworkError: Bool { get } - static func noEndpointsError(coin: String) -> Self + static func noEndpointsError(nodeGroupName: String) -> Self } -class HealthCheckWrapper { - @ObservableValue var nodes: [Node] = .init() +open class HealthCheckWrapper { + @ObservableValue public var nodes: [Node] = .init() - let service: Service - let normalUpdateInterval: TimeInterval - let crucialUpdateInterval: TimeInterval - let nodeGroup: NodeGroup + public let service: Service + public let isActive: Bool + public let name: String - @Atomic var fastestNodeMode = true - @Atomic var healthCheckTimerSubscription: AnyCancellable? - @Atomic var subscriptions: Set = .init() + private let normalUpdateInterval: TimeInterval + private let crucialUpdateInterval: TimeInterval + + @Atomic public var fastestNodeMode = true + @Atomic public var healthCheckTimerSubscription: AnyCancellable? + @Atomic public var subscriptions: Set = .init() @Atomic private var previousAppState: UIApplication.State? - @Atomic private var lastUpdateTime = Date() + @Atomic private var lastUpdateTime: Date? + + @ObservableValue public private(set) var sortedAllowedNodes: [Node] = .init() - @ObservableValue private(set) var sortedAllowedNodes: [Node] = .init() + public var chosenFastestNodeId: UUID? { + fastestNodeMode + ? sortedAllowedNodes.first?.id + : nil + } - init( + public init( service: Service, + isActive: Bool, + name: String, normalUpdateInterval: TimeInterval, crucialUpdateInterval: TimeInterval, - nodeGroup: NodeGroup + connection: AnyObservable? ) { self.service = service + self.isActive = isActive + self.name = name self.normalUpdateInterval = normalUpdateInterval self.crucialUpdateInterval = crucialUpdateInterval - self.nodeGroup = nodeGroup $nodes .removeDuplicates { !$0.doesNeedHealthCheck($1) } @@ -60,18 +70,7 @@ class HealthCheckWrapper { } .store(in: &subscriptions) - $sortedAllowedNodes - .map { $0.isEmpty } - .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() + connection? .filter { $0 == true } .sink { [weak self] _ in self?.healthCheck() } .store(in: &subscriptions) @@ -87,11 +86,11 @@ class HealthCheckWrapper { .store(in: &subscriptions) } - func request( + public func request( _ request: @Sendable (Service, NodeOrigin) async -> Result ) async -> Result { var lastConnectionError = sortedAllowedNodes.isEmpty - ? Error.noEndpointsError(coin: nodeGroup.name) + ? Error.noEndpointsError(nodeGroupName: name) : nil let nodesList = fastestNodeMode @@ -111,10 +110,12 @@ class HealthCheckWrapper { } if lastConnectionError != nil { healthCheck() } - return .failure(lastConnectionError ?? Error.noEndpointsError(coin: nodeGroup.name)) + return .failure(lastConnectionError ?? .noEndpointsError(nodeGroupName: name)) } - func healthCheck() { + open func healthCheck() { + guard isActive else { return } + lastUpdateTime = .now updateHealthCheckTimerSubscription() } } @@ -129,19 +130,27 @@ private extension HealthCheckWrapper { in: .default ).autoconnect().sink { [weak self] _ in self?.healthCheck() - self?.lastUpdateTime = Date() } } func didBecomeActiveAction() { defer { previousAppState = .active } - guard previousAppState == .background, - Date() > lastUpdateTime.addingTimeInterval(normalUpdateInterval / 3) - else { return } - - healthCheck() - lastUpdateTime = Date() + switch previousAppState { + case .background: + guard + isActive, + let timeToUpdate = lastUpdateTime? + .addingTimeInterval(normalUpdateInterval / 3), + Date.now > timeToUpdate + else { break } + + healthCheck() + case .inactive, .active, .none: + break + @unknown default: + break + } } } diff --git a/Adamant/Services/NodesAdditionalParamsStorage.swift b/CommonKit/Sources/CommonKit/Services/NodesAdditionalParamsStorage.swift similarity index 78% rename from Adamant/Services/NodesAdditionalParamsStorage.swift rename to CommonKit/Sources/CommonKit/Services/NodesAdditionalParamsStorage.swift index 9dad62d1f..6da72bbeb 100644 --- a/Adamant/Services/NodesAdditionalParamsStorage.swift +++ b/CommonKit/Sources/CommonKit/Services/NodesAdditionalParamsStorage.swift @@ -7,27 +7,26 @@ // import Foundation -import CommonKit import Combine -final class NodesAdditionalParamsStorage: NodesAdditionalParamsStorageProtocol { +public final class NodesAdditionalParamsStorage: NodesAdditionalParamsStorageProtocol { @Atomic private var fastestNodeModeValues: ObservableValue<[NodeGroup: Bool]> private let securedStore: SecuredStore private var subscription: AnyCancellable? - func isFastestNodeMode(group: NodeGroup) -> Bool { + public func isFastestNodeMode(group: NodeGroup) -> Bool { fastestNodeModeValues.wrappedValue[group] ?? group.defaultFastestNodeMode } - func fastestNodeMode(group: NodeGroup) -> AnyObservable { + public func fastestNodeMode(group: NodeGroup) -> AnyObservable { fastestNodeModeValues .map { $0[group] ?? group.defaultFastestNodeMode } .removeDuplicates() .eraseToAnyPublisher() } - func setFastestNodeMode(groups: Set, value: Bool) { + public func setFastestNodeMode(groups: Set, value: Bool) { $fastestNodeModeValues.mutate { dict in groups.forEach { dict.wrappedValue[$0] = value @@ -35,11 +34,11 @@ final class NodesAdditionalParamsStorage: NodesAdditionalParamsStorageProtocol { } } - func setFastestNodeMode(group: NodeGroup, value: Bool) { + public func setFastestNodeMode(group: NodeGroup, value: Bool) { fastestNodeModeValues.wrappedValue[group] = value } - init(securedStore: SecuredStore) { + public init(securedStore: SecuredStore) { self.securedStore = securedStore _fastestNodeModeValues = .init(wrappedValue: .init( diff --git a/Adamant/Services/NodesStorage.swift b/CommonKit/Sources/CommonKit/Services/NodesStorage.swift similarity index 74% rename from Adamant/Services/NodesStorage.swift rename to CommonKit/Sources/CommonKit/Services/NodesStorage.swift index 07d9abb4b..4ff8a4198 100644 --- a/Adamant/Services/NodesStorage.swift +++ b/CommonKit/Sources/CommonKit/Services/NodesStorage.swift @@ -6,14 +6,13 @@ // Copyright © 2023 Adamant. All rights reserved. // -import CommonKit import Foundation import Combine -final class NodesStorage: NodesStorageProtocol { +public final class NodesStorage: NodesStorageProtocol { @Atomic private var items: ObservableValue<[NodeGroup: [Node]]> = .init(wrappedValue: .init()) - var nodesPublisher: AnyObservable<[NodeGroup: [Node]]> { + public var nodesPublisher: AnyObservable<[NodeGroup: [Node]]> { items .map { $0.mapValues { $0.filter { !$0.isHidden } } } .removeDuplicates() @@ -23,15 +22,16 @@ final class NodesStorage: NodesStorageProtocol { private var subscription: AnyCancellable? private let securedStore: SecuredStore private let nodesMergingService: NodesMergingService + private let defaultNodes: [NodeGroup: [Node]] - func getNodesPublisher(group: NodeGroup) -> AnyObservable<[Node]> { + public func getNodesPublisher(group: NodeGroup) -> AnyObservable<[Node]> { nodesPublisher .map { $0[group] ?? .init() } .removeDuplicates() .eraseToAnyPublisher() } - func addNode(_ node: Node, group: NodeGroup) { + public func addNode(_ node: Node, group: NodeGroup) { $items.mutate { items in if items.wrappedValue[group] == nil { items.wrappedValue[group] = [node] @@ -41,7 +41,7 @@ final class NodesStorage: NodesStorageProtocol { } } - func removeNode(id: UUID, group: NodeGroup) { + public func removeNode(id: UUID, group: NodeGroup) { $items.mutate { items in guard let index = items.wrappedValue[group]?.firstIndex(where: { $0.id == id }) @@ -58,7 +58,7 @@ final class NodesStorage: NodesStorageProtocol { } } - func updateNode(id: UUID, group: NodeGroup, mutate: (inout Node) -> Void) { + public func updateNode(id: UUID, group: NodeGroup, mutate: (inout Node) -> Void) { $items.mutate { items in guard let index = items.wrappedValue[group]?.firstIndex(where: { $0.id == id }), @@ -85,45 +85,23 @@ final class NodesStorage: NodesStorageProtocol { } } - func resetNodes(group: NodeGroup) { - items.wrappedValue[group] = Self.defaultItems(group: group) + public func resetNodes(group: NodeGroup) { + items.wrappedValue[group] = defaultNodes[group] ?? .init() } - init(securedStore: SecuredStore, nodesMergingService: NodesMergingService) { + public init( + securedStore: SecuredStore, + nodesMergingService: NodesMergingService, + defaultNodes: [NodeGroup: [Node]] + ) { self.securedStore = securedStore self.nodesMergingService = nodesMergingService + self.defaultNodes = defaultNodes setupNodes() } } private extension NodesStorage { - static func defaultItems(group: NodeGroup) -> [Node] { - switch group { - case .btc: - return BtcWalletService.nodes - case .eth: - return EthWalletService.nodes - case .klyNode: - return KlyWalletService.nodes - case .klyService: - return KlyWalletService.serviceNodes - case .doge: - return DogeWalletService.nodes - case .dash: - return DashWalletService.nodes - case .adm: - return AdmWalletService.nodes - } - } - - static var defaultItems: [NodeGroup: [Node]] { - .init( - uniqueKeysWithValues: NodeGroup.allCases.map { - ($0, defaultItems(group: $0)) - } - ) - } - func saveNodes(nodes: [NodeGroup: [Node]]) { let nodesDto = nodes.mapValues { $0.map { $0.mapToDto() } } securedStore.set(nodesDto, for: StoreKey.NodesStorage.nodes) @@ -141,7 +119,7 @@ private extension NodesStorage { items.wrappedValue = nodesMergingService.merge( savedNodes: savedNodes, - defaultNodes: Self.defaultItems + defaultNodes: defaultNodes ) subscription = items.removeDuplicates().sink { [weak self] in diff --git a/MessageNotificationContentExtension/NotificationViewController.swift b/MessageNotificationContentExtension/NotificationViewController.swift index 47d1ae2e5..f361ad9cf 100644 --- a/MessageNotificationContentExtension/NotificationViewController.swift +++ b/MessageNotificationContentExtension/NotificationViewController.swift @@ -42,9 +42,14 @@ class NotificationViewController: UIViewController, UNNotificationContentExtensi func didReceive(_ notification: UNNotification) { // MARK: 0. Necessary services let avatarService = AdamantAvatarService() - var keychainStore: KeychainStore? - var extensionApi: ExtensionsApi? - var nativeCore: NativeAdamantCore? + let keychainStore = KeychainStore() + let nativeCore = NativeAdamantCore() + + let extensionApi = ExtensionsApiFactory( + core: nativeCore, + securedStore: keychainStore + ).make() + var keypair: Keypair? // MARK: 1. Get the transaction @@ -56,13 +61,8 @@ class NotificationViewController: UIViewController, UNNotificationContentExtensi showError() return } - - let store = KeychainStore() - let api = ExtensionsApi(keychainStore: store) - trs = api.getTransaction(by: id) - - keychainStore = store - extensionApi = api + + trs = extensionApi.getTransaction(by: id) } guard let transaction = trs else { @@ -76,13 +76,10 @@ class NotificationViewController: UIViewController, UNNotificationContentExtensi if let raw = notification.request.content.userInfo[AdamantNotificationUserInfoKeys.decodedMessage] as? String { message = raw } else { - let keychainStore = keychainStore ?? KeychainStore() - nativeCore = NativeAdamantCore() - guard let passphrase: String = keychainStore.get(passphraseStoreKey), - let keys = nativeCore!.createKeypairFor(passphrase: passphrase), + let keys = nativeCore.createKeypairFor(passphrase: passphrase), let chat = transaction.asset.chat, - let raw = nativeCore!.decodeMessage(rawMessage: chat.message, + let raw = nativeCore.decodeMessage(rawMessage: chat.message, rawNonce: chat.ownMessage, senderPublicKey: transaction.senderPublicKey, privateKey: keys.privateKey) else { @@ -107,14 +104,11 @@ class NotificationViewController: UIViewController, UNNotificationContentExtensi } // No name, no flag - something broke. Check sender name, if we have a recipient address else if let recipient = notification.request.content.userInfo[AdamantNotificationUserInfoKeys.pushRecipient] as? String { - let keychain = keychainStore ?? KeychainStore() - let core = nativeCore ?? NativeAdamantCore() - let api: ExtensionsApi = extensionApi ?? ExtensionsApi(keychainStore: keychain) let key: Keypair? if let keypair = keypair { key = keypair - } else if let passphrase: String = keychain.get(passphraseStoreKey), let keypair = core.createKeypairFor(passphrase: passphrase) { + } else if let passphrase: String = keychainStore.get(passphraseStoreKey), let keypair = nativeCore.createKeypairFor(passphrase: passphrase) { key = keypair } else { key = nil @@ -122,7 +116,7 @@ class NotificationViewController: UIViewController, UNNotificationContentExtensi let id = transaction.senderId if let key = key { - checkName(of: id, for: recipient, api: api, core: core, keypair: key) + checkName(of: id, for: recipient, api: extensionApi, core: nativeCore, keypair: key) } senderName = nil diff --git a/NotificationServiceExtension/NotificationService.swift b/NotificationServiceExtension/NotificationService.swift index 684af775d..55dd0d703 100644 --- a/NotificationServiceExtension/NotificationService.swift +++ b/NotificationServiceExtension/NotificationService.swift @@ -61,7 +61,7 @@ class NotificationService: UNNotificationServiceExtension { // MARK: 1. Getting services let securedStore = KeychainStore() let core = NativeAdamantCore() - let api = ExtensionsApi(keychainStore: securedStore) + let api = ExtensionsApiFactory(core: core, securedStore: securedStore).make() if let sound: String = securedStore.get(StoreKey.notificationsService.notificationsSound) { if sound.isEmpty { diff --git a/TransferNotificationContentExtension/NotificationViewController.swift b/TransferNotificationContentExtension/NotificationViewController.swift index 952281d2f..f1034a1bc 100644 --- a/TransferNotificationContentExtension/NotificationViewController.swift +++ b/TransferNotificationContentExtension/NotificationViewController.swift @@ -122,7 +122,7 @@ class NotificationViewController: UIViewController, UNNotificationContentExtensi let keychain = KeychainStore() let core = NativeAdamantCore() let avatarService = AdamantAvatarService() - var api: ExtensionsApi? + let api = ExtensionsApiFactory(core: core, securedStore: keychain).make() guard let passphrase: String = keychain.get(passphraseStoreKey), let keypair = core.createKeypairFor(passphrase: passphrase) else { showError() @@ -139,8 +139,7 @@ class NotificationViewController: UIViewController, UNNotificationContentExtensi return } - api = ExtensionsApi(keychainStore: keychain) - trs = api!.getTransaction(by: id) + trs = api.getTransaction(by: id) } guard let transaction = trs else { @@ -233,8 +232,7 @@ class NotificationViewController: UIViewController, UNNotificationContentExtensi else if let flag = notification.request.content.userInfo[AdamantNotificationUserInfoKeys.partnerNoDislpayNameKey] as? String, flag == AdamantNotificationUserInfoKeys.partnerNoDisplayNameValue { senderName = nil } else { - let _api = api ?? ExtensionsApi(keychainStore: keychain) - checkName(of: transaction.senderId, for: transaction.recipientId, api: _api, core: core, keypair: keypair) + checkName(of: transaction.senderId, for: transaction.recipientId, api: api, core: core, keypair: keypair) senderName = nil } From a5f3792eb49bb729a7a6462a5163605cb635e9a5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=80=9Cjust-software-dev=E2=80=9D?= Date: Fri, 9 Aug 2024 00:13:48 -0300 Subject: [PATCH 029/106] [trello.com/c/Or0mq52c] Force unwrap crash fix --- Adamant/Modules/NodesEditor/NodeCell/NodeCell.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Adamant/Modules/NodesEditor/NodeCell/NodeCell.swift b/Adamant/Modules/NodesEditor/NodeCell/NodeCell.swift index c1e426fd4..1a0338615 100644 --- a/Adamant/Modules/NodesEditor/NodeCell/NodeCell.swift +++ b/Adamant/Modules/NodesEditor/NodeCell/NodeCell.swift @@ -19,7 +19,7 @@ final class NodeCell: Cell, CellType { private var model: Model = .default { didSet { guard model != oldValue else { return } - baseRow.baseValue = model + baseRow?.baseValue = model update() } } From 8ccb2aa3a792f2fed80fc3df2428c21cfa32a7e5 Mon Sep 17 00:00:00 2001 From: StanislavDevIOS Date: Fri, 16 Aug 2024 18:31:57 +0300 Subject: [PATCH 030/106] [trello.com/c/CUreX9oF] fix: message freeze on clock status --- .../Services/DataProviders/AdamantChatsProvider.swift | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/Adamant/Services/DataProviders/AdamantChatsProvider.swift b/Adamant/Services/DataProviders/AdamantChatsProvider.swift index 575269f31..d0fe56405 100644 --- a/Adamant/Services/DataProviders/AdamantChatsProvider.swift +++ b/Adamant/Services/DataProviders/AdamantChatsProvider.swift @@ -1787,17 +1787,18 @@ extension AdamantChatsProvider { transaction.blockId = blockId transaction.confirmations = confirmations - if blockId.isEmpty { - transaction.statusEnum = .delivered - } else { + if !blockId.isEmpty { self.unconfirmedTransactions.removeValue(forKey: id) } if let lastHeight = receivedLastHeight, lastHeight < height { self.receivedLastHeight = height - transaction.statusEnum = .delivered + } + + if height != .zero { transaction.isConfirmed = true } + transaction.statusEnum = .delivered } func blockChat(with address: String) { From 30e5a27c71dc36b3228e07b3c565f2618703649d Mon Sep 17 00:00:00 2001 From: Arkadii Seniiants Date: Mon, 19 Aug 2024 13:45:03 +0300 Subject: [PATCH 031/106] fixed bug: coin carousel scroll drop --- Adamant/Modules/Wallets/Adamant/AdmWalletService.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Adamant/Modules/Wallets/Adamant/AdmWalletService.swift b/Adamant/Modules/Wallets/Adamant/AdmWalletService.swift index 04784d370..9b2bdbd8a 100644 --- a/Adamant/Modules/Wallets/Adamant/AdmWalletService.swift +++ b/Adamant/Modules/Wallets/Adamant/AdmWalletService.swift @@ -149,7 +149,7 @@ final class AdmWalletService: NSObject, WalletCoreProtocol { if wallet.balance != account.balance { wallet.balance = account.balance notify = true - } else if wallet.isBalanceInitialized { + } else if !wallet.isBalanceInitialized { notify = true } else { notify = false From 85d20976c8711f0359439933b387612ff225b5b4 Mon Sep 17 00:00:00 2001 From: Iana Silosiev Date: Tue, 20 Aug 2024 13:38:09 +0300 Subject: [PATCH 032/106] [trello.com/c/44KDaoe5] Rebuilt Notification screen --- Adamant.xcodeproj/project.pbxproj | 50 ++++- Adamant/Assets/note.mp3 | Bin 0 -> 72585 bytes .../AdamantScreensFactory.swift | 4 +- .../NotificationSoundsPickerView.swift | 27 +++ .../NotificationSoundsViewController.swift | 33 ++- .../Notifications/NotificationsFactory.swift | 40 ++++ .../Notifications/NotificationsView.swift | 200 ++++++++++++++++++ .../NotificationsViewModel.swift | 173 +++++++++++++++ .../NotificationsViewController.swift | 4 +- .../Services/AdamantNotificationService.swift | 58 +++-- .../Localization/de.lproj/Localizable.strings | 3 + .../Localization/ru.lproj/Localizable.strings | 3 + .../Sources/CommonKit/Core/SecuredStore.swift | 1 + .../NotificationService.swift | 33 ++- 14 files changed, 597 insertions(+), 32 deletions(-) create mode 100644 Adamant/Assets/note.mp3 create mode 100644 Adamant/Modules/Settings/Notifications/Helpers/NotificationSoundsPickerView.swift rename Adamant/Modules/Settings/{ => Notifications}/NotificationSoundsViewController.swift (76%) create mode 100644 Adamant/Modules/Settings/Notifications/NotificationsFactory.swift create mode 100644 Adamant/Modules/Settings/Notifications/NotificationsView.swift create mode 100644 Adamant/Modules/Settings/Notifications/NotificationsViewModel.swift diff --git a/Adamant.xcodeproj/project.pbxproj b/Adamant.xcodeproj/project.pbxproj index 0f21beb0c..a009c6b98 100644 --- a/Adamant.xcodeproj/project.pbxproj +++ b/Adamant.xcodeproj/project.pbxproj @@ -7,7 +7,18 @@ objects = { /* Begin PBXBuildFile section */ + 2621AB372C60E74A00046D7A /* NotificationsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2621AB362C60E74A00046D7A /* NotificationsView.swift */; }; + 2621AB392C60E7AE00046D7A /* NotificationsViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2621AB382C60E7AE00046D7A /* NotificationsViewModel.swift */; }; + 2621AB3B2C613C8100046D7A /* NotificationsFactory.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2621AB3A2C613C8100046D7A /* NotificationsFactory.swift */; }; + 2657A0C92C7078750021E7E6 /* NotificationSoundsPickerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2657A0C82C7078750021E7E6 /* NotificationSoundsPickerView.swift */; }; + 2657A0CA2C707D780021E7E6 /* notification.mp3 in Resources */ = {isa = PBXBuildFile; fileRef = E9A174B820587B83003667CD /* notification.mp3 */; }; + 2657A0CB2C707D7B0021E7E6 /* so-proud-notification.mp3 in Resources */ = {isa = PBXBuildFile; fileRef = 4198D57A28C8B7DA009337F2 /* so-proud-notification.mp3 */; }; + 2657A0CC2C707D7E0021E7E6 /* relax-message-tone.mp3 in Resources */ = {isa = PBXBuildFile; fileRef = 4198D57C28C8B7F9009337F2 /* relax-message-tone.mp3 */; }; + 2657A0CD2C707D800021E7E6 /* short-success.mp3 in Resources */ = {isa = PBXBuildFile; fileRef = 4198D57E28C8B834009337F2 /* short-success.mp3 */; }; + 2657A0CE2C707D830021E7E6 /* default.mp3 in Resources */ = {isa = PBXBuildFile; fileRef = 4198D58028C8B8D1009337F2 /* default.mp3 */; }; 265AA1622B74E6B900CF98B0 /* ChatPreservation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 265AA1612B74E6B900CF98B0 /* ChatPreservation.swift */; }; + 269B83102C74A2FF002AA1D7 /* note.mp3 in Resources */ = {isa = PBXBuildFile; fileRef = 269B830F2C74A2FF002AA1D7 /* note.mp3 */; }; + 269B83112C74A34F002AA1D7 /* note.mp3 in Resources */ = {isa = PBXBuildFile; fileRef = 269B830F2C74A2FF002AA1D7 /* note.mp3 */; }; 269E13522B594B2D008D1CA7 /* AccountFooterView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 269E13512B594B2D008D1CA7 /* AccountFooterView.swift */; }; 26A975FF2B7E843E0095C367 /* SelectTextView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 26A975FE2B7E843E0095C367 /* SelectTextView.swift */; }; 26A976012B7E852E0095C367 /* ChatSelectTextViewFactory.swift in Sources */ = {isa = PBXBuildFile; fileRef = 26A976002B7E852E0095C367 /* ChatSelectTextViewFactory.swift */; }; @@ -656,7 +667,12 @@ /* End PBXCopyFilesBuildPhase section */ /* Begin PBXFileReference section */ + 2621AB362C60E74A00046D7A /* NotificationsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationsView.swift; sourceTree = ""; }; + 2621AB382C60E7AE00046D7A /* NotificationsViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationsViewModel.swift; sourceTree = ""; }; + 2621AB3A2C613C8100046D7A /* NotificationsFactory.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationsFactory.swift; sourceTree = ""; }; + 2657A0C82C7078750021E7E6 /* NotificationSoundsPickerView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationSoundsPickerView.swift; sourceTree = ""; }; 265AA1612B74E6B900CF98B0 /* ChatPreservation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChatPreservation.swift; sourceTree = ""; }; + 269B830F2C74A2FF002AA1D7 /* note.mp3 */ = {isa = PBXFileReference; lastKnownFileType = audio.mp3; path = note.mp3; sourceTree = ""; }; 269E13512B594B2D008D1CA7 /* AccountFooterView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccountFooterView.swift; sourceTree = ""; }; 26A975FE2B7E843E0095C367 /* SelectTextView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SelectTextView.swift; sourceTree = ""; }; 26A976002B7E852E0095C367 /* ChatSelectTextViewFactory.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChatSelectTextViewFactory.swift; sourceTree = ""; }; @@ -1314,6 +1330,26 @@ path = Pods; sourceTree = ""; }; + 2621AB352C60E52900046D7A /* Notifications */ = { + isa = PBXGroup; + children = ( + 2657A0C72C7078630021E7E6 /* Helpers */, + 2621AB362C60E74A00046D7A /* NotificationsView.swift */, + 2621AB382C60E7AE00046D7A /* NotificationsViewModel.swift */, + 2621AB3A2C613C8100046D7A /* NotificationsFactory.swift */, + 417BA7F328BF894F00DF94C5 /* NotificationSoundsViewController.swift */, + ); + path = Notifications; + sourceTree = ""; + }; + 2657A0C72C7078630021E7E6 /* Helpers */ = { + isa = PBXGroup; + children = ( + 2657A0C82C7078750021E7E6 /* NotificationSoundsPickerView.swift */, + ); + path = Helpers; + sourceTree = ""; + }; 3A20D9392AE7F305005475A6 /* Models */ = { isa = PBXGroup; children = ( @@ -2132,6 +2168,7 @@ 4198D57C28C8B7F9009337F2 /* relax-message-tone.mp3 */, 4198D57E28C8B834009337F2 /* short-success.mp3 */, 4198D58028C8B8D1009337F2 /* default.mp3 */, + 269B830F2C74A2FF002AA1D7 /* note.mp3 */, E9256F752039A9A200DE86E9 /* LaunchScreen.storyboard */, 4184F16D2A33023A00D7B8B9 /* GoogleService-Info.plist */, ); @@ -2422,6 +2459,7 @@ E982F69820235AF000566AC7 /* Settings */ = { isa = PBXGroup; children = ( + 2621AB352C60E52900046D7A /* Notifications */, 411742FE2A39B1B1008CD98A /* Contribute */, 4197B9C72952FAA2004CAF64 /* VisibleWallets */, E948E03A20235E2300975D6B /* SettingsFactory.swift */, @@ -2433,7 +2471,6 @@ E9484B7E2285C016008E10F0 /* PKGeneratorViewController.swift */, E9484B7C2285BAD8008E10F0 /* PrivateKeyGenerator.swift */, E908473C219713300095825D /* NotificationsViewController.swift */, - 417BA7F328BF894F00DF94C5 /* NotificationSoundsViewController.swift */, ); path = Settings; sourceTree = ""; @@ -2837,6 +2874,7 @@ E94E7B01205D3F090042B639 /* ChatListViewController.xib in Resources */, E941CCDB20E786D800C96220 /* AccountHeader.xib in Resources */, 93496BB72A6CAED100DD062F /* Roboto_500_normal.ttf in Resources */, + 269B83102C74A2FF002AA1D7 /* note.mp3 in Resources */, 4198D58128C8B8D1009337F2 /* default.mp3 in Resources */, 93E123382A6DFD15004DF33B /* Localizable.strings in Resources */, 93496BB52A6CAED100DD062F /* Roboto_300_normal.ttf in Resources */, @@ -2878,14 +2916,20 @@ isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( + 2657A0CD2C707D800021E7E6 /* short-success.mp3 in Resources */, 4154413B2923AED000824478 /* bitcoin_notificationContent.png in Resources */, + 2657A0CC2C707D7E0021E7E6 /* relax-message-tone.mp3 in Resources */, E957E107229AF7CB0019732A /* adamant_notificationContent.png in Resources */, E957E108229AF7CB0019732A /* doge_notificationContent.png in Resources */, 93E123442A6DFECB004DF33B /* Localizable.strings in Resources */, E957E10A229AF7CB0019732A /* ethereum_notificationContent.png in Resources */, + 2657A0CA2C707D780021E7E6 /* notification.mp3 in Resources */, 93E123452A6DFECB004DF33B /* Localizable.stringsdict in Resources */, + 2657A0CB2C707D7B0021E7E6 /* so-proud-notification.mp3 in Resources */, E957E109229AF7CB0019732A /* lisk_notificationContent.png in Resources */, + 269B83112C74A34F002AA1D7 /* note.mp3 in Resources */, 412C0ED929124A3400DE2C5E /* dash_notificationContent.png in Resources */, + 2657A0CE2C707D830021E7E6 /* default.mp3 in Resources */, 3A26D9522C3E7F1E003AD832 /* klayr_notificationContent.png in Resources */, ); runOnlyForDeploymentPostprocessing = 0; @@ -3025,10 +3069,12 @@ 93A118512993167500E144CC /* ChatMessageBackgroundColor.swift in Sources */, E9CAE8D22018AA7700345E76 /* AdamantApi+Accounts.swift in Sources */, 3A26D93B2C3C1C97003AD832 /* KlyApiCore.swift in Sources */, + 2621AB372C60E74A00046D7A /* NotificationsView.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 */, + 2657A0C92C7078750021E7E6 /* NotificationSoundsPickerView.swift in Sources */, 26A975FF2B7E843E0095C367 /* SelectTextView.swift in Sources */, 93294B822AAD0BB400911109 /* BtcWalletFactory.swift in Sources */, 648DD7A42237DB9E00B811FD /* DogeWalletService+Send.swift in Sources */, @@ -3185,6 +3231,7 @@ E96D64C62295C3ED00CA5587 /* Mnemonic+extended.swift in Sources */, 3A26D9352C3C1BE2003AD832 /* KlyWalletService.swift in Sources */, 41047B70294B5EE10039E956 /* VisibleWalletsViewController.swift in Sources */, + 2621AB392C60E7AE00046D7A /* NotificationsViewModel.swift in Sources */, 93B28EC82B076E68007F268B /* DashResponseDTO.swift in Sources */, A5BBD811262C657300B5C40C /* ByteBackpacker.swift in Sources */, 648BCA6D213D384F00875EB5 /* AvatarService.swift in Sources */, @@ -3328,6 +3375,7 @@ 9390C5032976B42800270CDF /* ChatDialogManager.swift in Sources */, E926E02E213EAABF005E536B /* TransferViewControllerBase+Alert.swift in Sources */, 936658A52B0AE67A00BDB2D3 /* CoinsNodesListFactory.swift in Sources */, + 2621AB3B2C613C8100046D7A /* NotificationsFactory.swift in Sources */, E9B1AA572121ACC000080A2A /* AdmWalletViewController.swift in Sources */, 93C7944A2B077A1C00408826 /* DashGetUnspentTransactionsDTO.swift in Sources */, E9240BF5215D686500187B09 /* AdmWalletService+RichMessageProvider.swift in Sources */, diff --git a/Adamant/Assets/note.mp3 b/Adamant/Assets/note.mp3 new file mode 100644 index 0000000000000000000000000000000000000000..2f51d1c0cd083464ace7804f08d93a9997ee251d GIT binary patch literal 72585 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 literal 0 HcmV?d00001 diff --git a/Adamant/Modules/ScreensFactory/AdamantScreensFactory.swift b/Adamant/Modules/ScreensFactory/AdamantScreensFactory.swift index 794ca2a6d..4fe000a6c 100644 --- a/Adamant/Modules/ScreensFactory/AdamantScreensFactory.swift +++ b/Adamant/Modules/ScreensFactory/AdamantScreensFactory.swift @@ -27,6 +27,7 @@ struct AdamantScreensFactory: ScreensFactory { private let partnerQRFactory: PartnerQRFactory private let coinsNodesListFactory: CoinsNodesListFactory private let chatSelectTextFactory: ChatSelectTextViewFactory + private let notificasionsFactory: NotificationsFactory init(assembler: Assembler) { admWalletFactory = .init(assembler: assembler) @@ -44,6 +45,7 @@ struct AdamantScreensFactory: ScreensFactory { partnerQRFactory = .init(parent: assembler) coinsNodesListFactory = .init(parent: assembler) chatSelectTextFactory = .init() + notificasionsFactory = .init(parent: assembler) walletFactoryCompose = AdamantWalletFactoryCompose( klyWalletFactory: .init(assembler: assembler), @@ -157,7 +159,7 @@ struct AdamantScreensFactory: ScreensFactory { } func makeNotifications() -> UIViewController { - settingsFactory.makeNotificationsVC() + notificasionsFactory.makeViewController() } func makeVisibleWallets() -> UIViewController { diff --git a/Adamant/Modules/Settings/Notifications/Helpers/NotificationSoundsPickerView.swift b/Adamant/Modules/Settings/Notifications/Helpers/NotificationSoundsPickerView.swift new file mode 100644 index 000000000..afd61bac1 --- /dev/null +++ b/Adamant/Modules/Settings/Notifications/Helpers/NotificationSoundsPickerView.swift @@ -0,0 +1,27 @@ +// +// NotificationSoundsPickerView.swift +// Adamant +// +// Created by Yana Silosieva on 17.08.2024. +// Copyright © 2024 Adamant. All rights reserved. +// + +import SwiftUI + +struct NotificationSoundsPickerView: UIViewControllerRepresentable { + private let notificationService: NotificationsService + private let notificationTarget: NotificationTarget + + init(notificationService: NotificationsService, target: NotificationTarget) { + self.notificationService = notificationService + self.notificationTarget = target + } + + func makeUIViewController(context: Context) -> UINavigationController { + let vc = NotificationSoundsViewController(notificationsService: notificationService, target: notificationTarget) + let nav = UINavigationController(rootViewController: vc) + return nav + } + + func updateUIViewController(_ uiViewController: UINavigationController, context: Context) { } +} diff --git a/Adamant/Modules/Settings/NotificationSoundsViewController.swift b/Adamant/Modules/Settings/Notifications/NotificationSoundsViewController.swift similarity index 76% rename from Adamant/Modules/Settings/NotificationSoundsViewController.swift rename to Adamant/Modules/Settings/Notifications/NotificationSoundsViewController.swift index 4d5f1e2b8..9be16ad80 100644 --- a/Adamant/Modules/Settings/NotificationSoundsViewController.swift +++ b/Adamant/Modules/Settings/Notifications/NotificationSoundsViewController.swift @@ -30,16 +30,33 @@ final class NotificationSoundsViewController: FormViewController { } } - var notificationsService: NotificationsService! - private var selectSound:NotificationSound = .inputDefault + private let notificationsService: NotificationsService + private let notificationTarget: NotificationTarget + + private var selectSound: NotificationSound = .inputDefault private var section = SelectableSection>() - var audioPlayer: AVAudioPlayer! + private var audioPlayer: AVAudioPlayer? + + init(notificationsService: NotificationsService, target: NotificationTarget) { + self.notificationsService = notificationsService + self.notificationTarget = target + super.init(nibName: nil, bundle: nil) + } + + required init?(coder aDecoder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } override func viewDidLoad() { super.viewDidLoad() self.title = .localized("Notifications.Sounds.Name", comment: "Notifications: Select Sounds") - selectSound = notificationsService.notificationsSound + switch notificationTarget { + case .baseMessage: + selectSound = notificationsService.notificationsSound + case .reaction: + selectSound = notificationsService.notificationsReactionSound + } section = SelectableSection>(Sections.alerts.localized, selectionType: .singleSelection(enableDeselection: false)) @@ -92,7 +109,7 @@ final class NotificationSoundsViewController: FormViewController { } private func setNotificationSound(_ sound: NotificationSound) { - notificationsService.setNotificationSound(sound) + notificationsService.setNotificationSound(sound, for: notificationTarget) } private func playSound(_ sound: NotificationSound) { @@ -112,9 +129,9 @@ final class NotificationSoundsViewController: FormViewController { do { try AVAudioSession.sharedInstance().setCategory(.playback) try AVAudioSession.sharedInstance().setActive(true) - self.audioPlayer = try AVAudioPlayer(contentsOf: url, fileTypeHint: AVFileType.mp3.rawValue) - audioPlayer.volume = 1.0 - audioPlayer.play() + audioPlayer = try AVAudioPlayer(contentsOf: url, fileTypeHint: AVFileType.mp3.rawValue) + audioPlayer?.volume = 1.0 + audioPlayer?.play() } catch let error as NSError { print("error: \(error.localizedDescription)") } diff --git a/Adamant/Modules/Settings/Notifications/NotificationsFactory.swift b/Adamant/Modules/Settings/Notifications/NotificationsFactory.swift new file mode 100644 index 000000000..8f2064322 --- /dev/null +++ b/Adamant/Modules/Settings/Notifications/NotificationsFactory.swift @@ -0,0 +1,40 @@ +// +// NotificationsFactory.swift +// Adamant +// +// Created by Yana Silosieva on 05.08.2024. +// Copyright © 2024 Adamant. All rights reserved. +// + +import Swinject +import SwiftUI + +struct NotificationsFactory { + private let assembler: Assembler + + init(parent: Assembler) { + assembler = .init([NotificationsAssembly()], parent: parent) + } + + @MainActor + func makeViewController() -> UIViewController { + let viewModel = assembler.resolve(NotificationsViewModel.self)! + + let view = NotificationsView(viewModel: viewModel) + + return UIHostingController( + rootView: view + ) + } +} + +private struct NotificationsAssembly: Assembly { + func assemble(container: Container) { + container.register(NotificationsViewModel.self) { r in + NotificationsViewModel( + dialogService: r.resolve(DialogService.self)!, + notificationsService: r.resolve(NotificationsService.self)! + ) + }.inObjectScope(.weak) + } +} diff --git a/Adamant/Modules/Settings/Notifications/NotificationsView.swift b/Adamant/Modules/Settings/Notifications/NotificationsView.swift new file mode 100644 index 000000000..b6aa907ac --- /dev/null +++ b/Adamant/Modules/Settings/Notifications/NotificationsView.swift @@ -0,0 +1,200 @@ +// +// NotificationsView.swift +// Adamant +// +// Created by Yana Silosieva on 05.08.2024. +// Copyright © 2024 Adamant. All rights reserved. +// + +import SwiftUI +import CommonKit + +struct NotificationsView: View { + @ObservedObject var viewModel: NotificationsViewModel + + var body: some View { + GeometryReader { geometry in + Form { + notificationsSection() + messageSoundSection() + messageReactionsSection() + inAppNotificationsSection() + settingsSection() + moreDetailsSection() + messageReactionsSection() + } + .navigationBarTitleDisplayMode(.inline) + .toolbar { + ToolbarItem(placement: .principal) { + toolbar(maxWidth: geometry.size.width) + } + } + .sheet(isPresented: $viewModel.presentSoundsPicker, content: { + NotificationSoundsPickerView(notificationService: viewModel.notificationsService, target: .baseMessage) + }) + .sheet(isPresented: $viewModel.presentReactionSoundsPicker, content: { + NotificationSoundsPickerView(notificationService: viewModel.notificationsService, target: .reaction) + }) + .fullScreenCover(isPresented: $viewModel.openSafariURL) { + SafariWebView(url: viewModel.safariURL).ignoresSafeArea() + } + } + } +} + +private extension NotificationsView { + func toolbar(maxWidth: CGFloat) -> some View { + HStack { + Text(viewModel.notificationsTitle) + .font(.headline) + .minimumScaleFactor(0.7) + .lineLimit(1) + } + .frame(maxWidth: maxWidth - toolbarSpace, alignment: .center) + } + + func notificationsSection() -> some View { + Section { + Button(action: { + viewModel.showAlert() + }, label: { + HStack { + Text(viewModel.notificationsTitle) + Spacer() + Text(viewModel.notificationsMode.localized) + .foregroundColor(.gray) + NavigationLink(destination: { EmptyView() }, label: { EmptyView() }).fixedSize() + } + }) + } header: { + Text(viewModel.notificationsTitle) + } + } + + func messageSoundSection() -> some View { + Section { + Button(action: { + viewModel.presentNotificationSoundsPicker() + }, label: { + HStack { + Text(soundTitle) + Spacer() + Text(viewModel.notificationSound.localized) + .foregroundColor(.gray) + NavigationLink(destination: { EmptyView() }, label: { EmptyView() }).fixedSize() + } + }) + } header: { + Text(soundHeader) + } + } + + func messageReactionsSection() -> some View { + Section { + Button(action: { + viewModel.presentReactionNotificationSoundsPicker() + }, label: { + HStack { + Text(soundTitle) + Spacer() + Text(viewModel.notificationReactionSound.localized) + .foregroundColor(.gray) + NavigationLink(destination: { EmptyView() }, label: { EmptyView() }).fixedSize() + } + }) + } header: { + Text(reactionsHeader) + } + } + + func inAppNotificationsSection() -> some View { + Section { + Toggle(isOn: $viewModel.inAppSounds) { + Text("Sounds") + } + .tint(.init(uiColor: .adamant.active)) + + Toggle(isOn: $viewModel.inAppVibrate) { + Text("Vibrate") + } + .tint(.init(uiColor: .adamant.active)) + + Toggle(isOn: $viewModel.inAppToasts) { + Text("Toasts") + } + .tint(.init(uiColor: .adamant.active)) + } header: { + Text(inAppNotifications) + } + } + + func settingsSection() -> some View { + Section { + Button(action: { + viewModel.openAppSettings() + }, label: { + HStack { + Text(settingsHeader) + Spacer() + NavigationLink(destination: { EmptyView() }, label: { EmptyView() }).fixedSize() + } + }) + } header: { + Text(settingsHeader) + } + } + + func moreDetailsSection() -> some View { + Section { + if let attributedString = viewModel.parseMarkdown(descriptionText) { + Text(AttributedString(attributedString)) + } + + Button(action: { + viewModel.presentSafariURL() + }, label: { + HStack { + Image(uiImage: viewModel.githubRowImage) + + Text(visitGithub) + + Spacer() + + NavigationLink(destination: { EmptyView() }, label: { EmptyView() }).fixedSize() + } + .padding() + }) + } + } +} + + +private let toolbarSpace: CGFloat = 150 + +private var soundHeader: String { + .localized("SecurityPage.Section.Messages") +} + +private var soundTitle: String { + .localized("Notifications.Sound.Name") +} + +private var settingsHeader: String { + .localized("Notifications.Settings.System") +} + +private var descriptionText: String { + .localized("SecurityPage.Row.Notifications.ModesDescription") +} + +private var visitGithub: String { + .localized("SecurityPage.Row.VisitGithub") +} + +private var reactionsHeader: String { + "Reactions" +} + +private var inAppNotifications: String { + "In-app notifications" +} diff --git a/Adamant/Modules/Settings/Notifications/NotificationsViewModel.swift b/Adamant/Modules/Settings/Notifications/NotificationsViewModel.swift new file mode 100644 index 000000000..e305bf500 --- /dev/null +++ b/Adamant/Modules/Settings/Notifications/NotificationsViewModel.swift @@ -0,0 +1,173 @@ +// +// NotificationsViewModel.swift +// Adamant +// +// Created by Yana Silosieva on 05.08.2024. +// Copyright © 2024 Adamant. All rights reserved. +// + +import SwiftUI +import CommonKit +import SafariServices +import MarkdownKit +import Combine + +@MainActor +final class NotificationsViewModel: ObservableObject { + + @Published var notificationsMode: NotificationsMode = .disabled + @Published var notificationSound: NotificationSound = .inputDefault + @Published var notificationReactionSound: NotificationSound = .none + @Published var githubRowImage: UIImage = .asset(named: "row_github") ?? UIImage() + @Published var notificationsTitle: String = .localized("SecurityPage.Row.Notifications") + @Published var presentSoundsPicker: Bool = false + @Published var presentReactionSoundsPicker: Bool = false + @Published var openSafariURL: Bool = false + @Published var inAppSounds: Bool = false + @Published var inAppVibrate: Bool = true + @Published var inAppToasts: Bool = true + + var safariURL = URL(string: "https://github.com/Adamant-im")! + + private let dialogService: DialogService + let notificationsService: NotificationsService + private var subscriptions = Set() + + nonisolated init(dialogService: DialogService, notificationsService: NotificationsService) { + self.dialogService = dialogService + self.notificationsService = notificationsService + + Task { + await addObservers() + await configure() + } + } + + func configure() { + notificationsMode = notificationsService.notificationsMode + notificationSound = notificationsService.notificationsSound + notificationReactionSound = notificationsService.notificationsReactionSound + } + + func presentNotificationSoundsPicker() { + presentSoundsPicker = true + } + + func presentReactionNotificationSoundsPicker() { + presentReactionSoundsPicker = true + } + + func presentSafariURL() { + openSafariURL = true + } + + func showAlert() { + dialogService.showAlert( + title: notificationsTitle, + message: nil, + style: .actionSheet, + actions: [ + makeAction( + title: NotificationsMode.disabled.localized, + action: { [weak self] _ in + self?.setNotificationMode(.disabled) + } + ), + makeAction( + title: NotificationsMode.backgroundFetch.localized, + action: { [weak self] _ in + self?.setNotificationMode(.backgroundFetch) + } + ), + makeAction( + title: NotificationsMode.push.localized, + action: { [weak self] _ in + self?.setNotificationMode(.push) + } + ), + makeCancelAction() + ], + from: nil + ) + } + + private func presentNotificationsDeniedError() { + dialogService.showAlert( + title: nil, + message: NotificationStrings.notificationsDisabled, + style: .alert, + actions: [ + makeAction( + title: .adamant.alert.settings, + action: { _ in + self.openAppSettings() + }), + makeAction( + title: String.adamant.alert.cancel, + action: nil + ) + ], + from: nil + ) + } + + func setNotificationMode(_ mode: NotificationsMode) { + guard mode != notificationsService.notificationsMode else { + return + } + + notificationsMode = mode + notificationsService.setNotificationsMode(mode) { [weak self] result in + DispatchQueue.onMainAsync { + switch result { + case .success: + return + case .failure(let error): + switch error { + case .notEnoughMoney, .notStayedLoggedIn: + self?.dialogService.showRichError(error: error) + case .denied: + self?.presentNotificationsDeniedError() + } + } + } + } + } + + func openAppSettings() { + if let settingsURL = URL(string: UIApplication.openSettingsURLString) { + if UIApplication.shared.canOpenURL(settingsURL) { + UIApplication.shared.open(settingsURL) + } + } + } + + func parseMarkdown(_ text: String) -> NSAttributedString? { + let parser = MarkdownParser(font: UIFont.systemFont(ofSize: UIFont.systemFontSize), color: UIColor.adamant.textColor) + parser.link.color = UIColor.adamant.secondary + return parser.parse(text) + } +} + +private extension NotificationsViewModel { + func addObservers() { + NotificationCenter.default + .publisher(for: .AdamantNotificationService.notificationsSoundChanged) + .sink { [weak self] _ in self?.configure() } + .store(in: &subscriptions) + } +} + +private extension NotificationsViewModel { + func makeAction(title: String, action: ((UIAlertAction) -> Void)?) -> UIAlertAction { + .init( + title: title, + style: .default, + handler: action + ) + } + + func makeCancelAction() -> UIAlertAction { + .init(title: .adamant.alert.cancel, style: .cancel, handler: nil) + } +} diff --git a/Adamant/Modules/Settings/NotificationsViewController.swift b/Adamant/Modules/Settings/NotificationsViewController.swift index 66d1b2e58..2500a0c47 100644 --- a/Adamant/Modules/Settings/NotificationsViewController.swift +++ b/Adamant/Modules/Settings/NotificationsViewController.swift @@ -127,8 +127,8 @@ final class NotificationsViewController: FormViewController { }.onCellSelection { [weak self] _, row in guard let self = self else { return } row.deselect() - let soundsVC = NotificationSoundsViewController() - soundsVC.notificationsService = self.notificationsService + let soundsVC = NotificationSoundsViewController(notificationsService: notificationsService, target: .baseMessage) +// soundsVC.notificationsService = self.notificationsService let navigationController = UINavigationController(rootViewController: soundsVC) self.present(navigationController, animated: true) } diff --git a/Adamant/Services/AdamantNotificationService.swift b/Adamant/Services/AdamantNotificationService.swift index 2f8095326..9c5b41434 100644 --- a/Adamant/Services/AdamantNotificationService.swift +++ b/Adamant/Services/AdamantNotificationService.swift @@ -26,6 +26,20 @@ extension NotificationsMode { } } +enum NotificationTarget: CaseIterable { + case baseMessage + case reaction + + var storeId: String { + switch self { + case .baseMessage: + return StoreKey.notificationsService.notificationsSound + case .reaction: + return StoreKey.notificationsService.notificationsReactionSound + } + } +} + @MainActor final class AdamantNotificationsService: NotificationsService { // MARK: Dependencies @@ -33,9 +47,13 @@ final class AdamantNotificationsService: NotificationsService { weak var accountService: AccountService? // MARK: Properties + private let defaultNotificationsSound: NotificationSound = .inputDefault + private let defaultNotificationsReactionSound: NotificationSound = .none + private(set) var notificationsMode: NotificationsMode = .disabled private(set) var customBadgeNumber = 0 private(set) var notificationsSound: NotificationSound = .inputDefault + private(set) var notificationsReactionSound: NotificationSound = .none private var isBackgroundSession = false private var backgroundNotifications = 0 private var subscriptions = Set() @@ -79,11 +97,11 @@ final class AdamantNotificationsService: NotificationsService { setNotificationsMode(.disabled, completion: nil) } - if let raw: String = securedStore.get(StoreKey.notificationsService.notificationsSound), - let sound = NotificationSound(fileName: raw) { - setNotificationSound(sound) - } else { - setNotificationsMode(.disabled, completion: nil) + NotificationTarget.allCases.forEach { target in + if let raw: String = securedStore.get(target.storeId), + let sound = NotificationSound(fileName: raw) { + setNotificationSound(sound, for: target) + } } preservedBadgeNumber = nil @@ -91,7 +109,8 @@ final class AdamantNotificationsService: NotificationsService { private func onUserLoggedOut() { setNotificationsMode(.disabled, completion: nil) - setNotificationSound(.inputDefault) + setNotificationSound(defaultNotificationsSound, for: .baseMessage) + setNotificationSound(defaultNotificationsReactionSound, for: .reaction) securedStore.remove(StoreKey.notificationsService.notificationsMode) securedStore.remove(StoreKey.notificationsService.notificationsSound) preservedBadgeNumber = nil @@ -109,12 +128,27 @@ final class AdamantNotificationsService: NotificationsService { // MARK: - Notifications Sound { extension AdamantNotificationsService { - func setNotificationSound(_ sound: NotificationSound) { - notificationsSound = sound - securedStore.set(sound.fileName, for: StoreKey.notificationsService.notificationsSound) - NotificationCenter.default.post(name: Notification.Name.AdamantNotificationService.notificationsSoundChanged, - object: self, - userInfo: nil) + func setNotificationSound( + _ sound: NotificationSound, + for target: NotificationTarget + ) { + switch target { + case .baseMessage: + notificationsSound = sound + case .reaction: + notificationsReactionSound = sound + } + + securedStore.set( + sound.fileName, + for: target.storeId + ) + + NotificationCenter.default.post( + name: .AdamantNotificationService.notificationsSoundChanged, + object: self, + userInfo: nil + ) } } diff --git a/CommonKit/Sources/CommonKit/Assets/Localization/de.lproj/Localizable.strings b/CommonKit/Sources/CommonKit/Assets/Localization/de.lproj/Localizable.strings index 4088c7870..6867a4bd8 100644 --- a/CommonKit/Sources/CommonKit/Assets/Localization/de.lproj/Localizable.strings +++ b/CommonKit/Sources/CommonKit/Assets/Localization/de.lproj/Localizable.strings @@ -775,6 +775,9 @@ /* Notifications: Select Sounds */ "Notifications.Sounds.Name" = "Geräusche"; +/* Notifications: Select Alert Tones */ +"Notifications.Alert.Tones" = "ALERT-TÖNE"; + /* Notifications: Select Alert Save */ "Notifications.Alert.Save" = "Speichern"; diff --git a/CommonKit/Sources/CommonKit/Assets/Localization/ru.lproj/Localizable.strings b/CommonKit/Sources/CommonKit/Assets/Localization/ru.lproj/Localizable.strings index 066703b31..16ff4d936 100644 --- a/CommonKit/Sources/CommonKit/Assets/Localization/ru.lproj/Localizable.strings +++ b/CommonKit/Sources/CommonKit/Assets/Localization/ru.lproj/Localizable.strings @@ -760,6 +760,9 @@ /* Notifications: Select Sounds */ "Notifications.Sounds.Name" = "Звуки"; +/* Notifications: Select Alert Tones */ +"Notifications.Alert.Tones" = "ЗВУКИ УВЕДОМЛЕНИЙ"; + /* Notifications: Select Alert Save */ "Notifications.Alert.Save" = "Сохранить"; diff --git a/CommonKit/Sources/CommonKit/Core/SecuredStore.swift b/CommonKit/Sources/CommonKit/Core/SecuredStore.swift index 8a7a3d920..d4d260c87 100644 --- a/CommonKit/Sources/CommonKit/Core/SecuredStore.swift +++ b/CommonKit/Sources/CommonKit/Core/SecuredStore.swift @@ -25,6 +25,7 @@ public extension StoreKey { public static let notificationsMode = "notifications.mode" public static let customBadgeNumber = "notifications.number" public static let notificationsSound = "notifications.sound" + public static let notificationsReactionSound = "notifications.reaction.sound" } enum visibleWallets { diff --git a/NotificationServiceExtension/NotificationService.swift b/NotificationServiceExtension/NotificationService.swift index 684af775d..140299e01 100644 --- a/NotificationServiceExtension/NotificationService.swift +++ b/NotificationServiceExtension/NotificationService.swift @@ -63,14 +63,6 @@ class NotificationService: UNNotificationServiceExtension { let core = NativeAdamantCore() let api = ExtensionsApi(keychainStore: securedStore) - if let sound: String = securedStore.get(StoreKey.notificationsService.notificationsSound) { - if sound.isEmpty { - bestAttemptContent.sound = nil - } else { - bestAttemptContent.sound = UNNotificationSound(named: UNNotificationSoundName(sound)) - } - } - // No passphrase - no point of trying to get and decode guard let passphrase: String = securedStore.get(passphraseStoreKey), @@ -116,6 +108,8 @@ class NotificationService: UNNotificationServiceExtension { var shouldIgnoreNotification = false + var isReaction = false + // MARK: 5. Content switch transaction.type { // MARK: Messages @@ -238,6 +232,8 @@ class NotificationService: UNNotificationServiceExtension { attachments: nil, categoryIdentifier: AdamantNotificationCategories.message ) + + isReaction = true } guard let content = content else { @@ -268,6 +264,11 @@ class NotificationService: UNNotificationServiceExtension { return } + bestAttemptContent.sound = getSound( + securedStore: securedStore, + isReaction: isReaction + ) + // MARK: 6. Other configurations bestAttemptContent.threadIdentifier = partnerAddress @@ -288,6 +289,22 @@ class NotificationService: UNNotificationServiceExtension { } } + private func getSound(securedStore: KeychainStore, isReaction: Bool) -> UNNotificationSound? { + guard isReaction else { + let sound: String = securedStore.get(StoreKey.notificationsService.notificationsSound) ?? .empty + + return !sound.isEmpty + ? UNNotificationSound(named: UNNotificationSoundName(sound)) + : nil + } + + let sound: String = securedStore.get(StoreKey.notificationsService.notificationsReactionSound) ?? .empty + + return !sound.isEmpty + ? UNNotificationSound(named: UNNotificationSoundName(sound)) + : nil + } + private func handleAdamantTransfer( notificationContent: UNMutableNotificationContent, partnerAddress address: String, From 1babb5fec3dfec5e727d7ef572bc29d9d74bd273 Mon Sep 17 00:00:00 2001 From: Iana Silosiev Date: Tue, 20 Aug 2024 14:56:19 +0300 Subject: [PATCH 033/106] [rello.com/c/44KDaoe5] Add more default sounds --- Adamant.xcodeproj/project.pbxproj | 72 ++++++++++++++++++ Adamant/Assets/antic.mp3 | Bin 0 -> 36640 bytes Adamant/Assets/cheers.mp3 | Bin 0 -> 39775 bytes Adamant/Assets/chord.mp3 | Bin 0 -> 83660 bytes Adamant/Assets/droplet.mp3 | Bin 0 -> 14070 bytes Adamant/Assets/handoff.mp3 | Bin 0 -> 74256 bytes Adamant/Assets/milestone.mp3 | Bin 0 -> 74256 bytes Adamant/Assets/passage.mp3 | Bin 0 -> 42283 bytes Adamant/Assets/portal.mp3 | Bin 0 -> 51060 bytes Adamant/Assets/rattle.mp3 | Bin 0 -> 37267 bytes Adamant/Assets/rebound.mp3 | Bin 0 -> 45417 bytes Adamant/Assets/slide.mp3 | Bin 0 -> 30371 bytes Adamant/Assets/welcome.mp3 | Bin 0 -> 67360 bytes .../NotificationSoundsViewController.swift | 2 +- .../Notifications/NotificationsView.swift | 1 - .../NotificationsService.swift | 71 ++++++++++++++++- 16 files changed, 143 insertions(+), 3 deletions(-) create mode 100644 Adamant/Assets/antic.mp3 create mode 100644 Adamant/Assets/cheers.mp3 create mode 100644 Adamant/Assets/chord.mp3 create mode 100644 Adamant/Assets/droplet.mp3 create mode 100644 Adamant/Assets/handoff.mp3 create mode 100644 Adamant/Assets/milestone.mp3 create mode 100644 Adamant/Assets/passage.mp3 create mode 100644 Adamant/Assets/portal.mp3 create mode 100644 Adamant/Assets/rattle.mp3 create mode 100644 Adamant/Assets/rebound.mp3 create mode 100644 Adamant/Assets/slide.mp3 create mode 100644 Adamant/Assets/welcome.mp3 diff --git a/Adamant.xcodeproj/project.pbxproj b/Adamant.xcodeproj/project.pbxproj index a009c6b98..5632eaa46 100644 --- a/Adamant.xcodeproj/project.pbxproj +++ b/Adamant.xcodeproj/project.pbxproj @@ -19,6 +19,30 @@ 265AA1622B74E6B900CF98B0 /* ChatPreservation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 265AA1612B74E6B900CF98B0 /* ChatPreservation.swift */; }; 269B83102C74A2FF002AA1D7 /* note.mp3 in Resources */ = {isa = PBXBuildFile; fileRef = 269B830F2C74A2FF002AA1D7 /* note.mp3 */; }; 269B83112C74A34F002AA1D7 /* note.mp3 in Resources */ = {isa = PBXBuildFile; fileRef = 269B830F2C74A2FF002AA1D7 /* note.mp3 */; }; + 269B831E2C74B4EC002AA1D7 /* handoff.mp3 in Resources */ = {isa = PBXBuildFile; fileRef = 269B83122C74B4EA002AA1D7 /* handoff.mp3 */; }; + 269B831F2C74B4EC002AA1D7 /* handoff.mp3 in Resources */ = {isa = PBXBuildFile; fileRef = 269B83122C74B4EA002AA1D7 /* handoff.mp3 */; }; + 269B83202C74B4EC002AA1D7 /* portal.mp3 in Resources */ = {isa = PBXBuildFile; fileRef = 269B83132C74B4EA002AA1D7 /* portal.mp3 */; }; + 269B83212C74B4EC002AA1D7 /* portal.mp3 in Resources */ = {isa = PBXBuildFile; fileRef = 269B83132C74B4EA002AA1D7 /* portal.mp3 */; }; + 269B83222C74B4EC002AA1D7 /* antic.mp3 in Resources */ = {isa = PBXBuildFile; fileRef = 269B83142C74B4EB002AA1D7 /* antic.mp3 */; }; + 269B83232C74B4EC002AA1D7 /* antic.mp3 in Resources */ = {isa = PBXBuildFile; fileRef = 269B83142C74B4EB002AA1D7 /* antic.mp3 */; }; + 269B83242C74B4EC002AA1D7 /* droplet.mp3 in Resources */ = {isa = PBXBuildFile; fileRef = 269B83152C74B4EB002AA1D7 /* droplet.mp3 */; }; + 269B83252C74B4EC002AA1D7 /* droplet.mp3 in Resources */ = {isa = PBXBuildFile; fileRef = 269B83152C74B4EB002AA1D7 /* droplet.mp3 */; }; + 269B83262C74B4EC002AA1D7 /* passage.mp3 in Resources */ = {isa = PBXBuildFile; fileRef = 269B83162C74B4EB002AA1D7 /* passage.mp3 */; }; + 269B83272C74B4EC002AA1D7 /* passage.mp3 in Resources */ = {isa = PBXBuildFile; fileRef = 269B83162C74B4EB002AA1D7 /* passage.mp3 */; }; + 269B83282C74B4EC002AA1D7 /* chord.mp3 in Resources */ = {isa = PBXBuildFile; fileRef = 269B83172C74B4EB002AA1D7 /* chord.mp3 */; }; + 269B83292C74B4EC002AA1D7 /* chord.mp3 in Resources */ = {isa = PBXBuildFile; fileRef = 269B83172C74B4EB002AA1D7 /* chord.mp3 */; }; + 269B832A2C74B4EC002AA1D7 /* rattle.mp3 in Resources */ = {isa = PBXBuildFile; fileRef = 269B83182C74B4EB002AA1D7 /* rattle.mp3 */; }; + 269B832B2C74B4EC002AA1D7 /* rattle.mp3 in Resources */ = {isa = PBXBuildFile; fileRef = 269B83182C74B4EB002AA1D7 /* rattle.mp3 */; }; + 269B832C2C74B4EC002AA1D7 /* rebound.mp3 in Resources */ = {isa = PBXBuildFile; fileRef = 269B83192C74B4EB002AA1D7 /* rebound.mp3 */; }; + 269B832D2C74B4EC002AA1D7 /* rebound.mp3 in Resources */ = {isa = PBXBuildFile; fileRef = 269B83192C74B4EB002AA1D7 /* rebound.mp3 */; }; + 269B832E2C74B4EC002AA1D7 /* milestone.mp3 in Resources */ = {isa = PBXBuildFile; fileRef = 269B831A2C74B4EC002AA1D7 /* milestone.mp3 */; }; + 269B832F2C74B4EC002AA1D7 /* milestone.mp3 in Resources */ = {isa = PBXBuildFile; fileRef = 269B831A2C74B4EC002AA1D7 /* milestone.mp3 */; }; + 269B83302C74B4EC002AA1D7 /* cheers.mp3 in Resources */ = {isa = PBXBuildFile; fileRef = 269B831B2C74B4EC002AA1D7 /* cheers.mp3 */; }; + 269B83312C74B4EC002AA1D7 /* cheers.mp3 in Resources */ = {isa = PBXBuildFile; fileRef = 269B831B2C74B4EC002AA1D7 /* cheers.mp3 */; }; + 269B83322C74B4EC002AA1D7 /* slide.mp3 in Resources */ = {isa = PBXBuildFile; fileRef = 269B831C2C74B4EC002AA1D7 /* slide.mp3 */; }; + 269B83332C74B4EC002AA1D7 /* slide.mp3 in Resources */ = {isa = PBXBuildFile; fileRef = 269B831C2C74B4EC002AA1D7 /* slide.mp3 */; }; + 269B83342C74B4EC002AA1D7 /* welcome.mp3 in Resources */ = {isa = PBXBuildFile; fileRef = 269B831D2C74B4EC002AA1D7 /* welcome.mp3 */; }; + 269B83352C74B4EC002AA1D7 /* welcome.mp3 in Resources */ = {isa = PBXBuildFile; fileRef = 269B831D2C74B4EC002AA1D7 /* welcome.mp3 */; }; 269E13522B594B2D008D1CA7 /* AccountFooterView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 269E13512B594B2D008D1CA7 /* AccountFooterView.swift */; }; 26A975FF2B7E843E0095C367 /* SelectTextView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 26A975FE2B7E843E0095C367 /* SelectTextView.swift */; }; 26A976012B7E852E0095C367 /* ChatSelectTextViewFactory.swift in Sources */ = {isa = PBXBuildFile; fileRef = 26A976002B7E852E0095C367 /* ChatSelectTextViewFactory.swift */; }; @@ -673,6 +697,18 @@ 2657A0C82C7078750021E7E6 /* NotificationSoundsPickerView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationSoundsPickerView.swift; sourceTree = ""; }; 265AA1612B74E6B900CF98B0 /* ChatPreservation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChatPreservation.swift; sourceTree = ""; }; 269B830F2C74A2FF002AA1D7 /* note.mp3 */ = {isa = PBXFileReference; lastKnownFileType = audio.mp3; path = note.mp3; sourceTree = ""; }; + 269B83122C74B4EA002AA1D7 /* handoff.mp3 */ = {isa = PBXFileReference; lastKnownFileType = audio.mp3; path = handoff.mp3; sourceTree = ""; }; + 269B83132C74B4EA002AA1D7 /* portal.mp3 */ = {isa = PBXFileReference; lastKnownFileType = audio.mp3; path = portal.mp3; sourceTree = ""; }; + 269B83142C74B4EB002AA1D7 /* antic.mp3 */ = {isa = PBXFileReference; lastKnownFileType = audio.mp3; path = antic.mp3; sourceTree = ""; }; + 269B83152C74B4EB002AA1D7 /* droplet.mp3 */ = {isa = PBXFileReference; lastKnownFileType = audio.mp3; path = droplet.mp3; sourceTree = ""; }; + 269B83162C74B4EB002AA1D7 /* passage.mp3 */ = {isa = PBXFileReference; lastKnownFileType = audio.mp3; path = passage.mp3; sourceTree = ""; }; + 269B83172C74B4EB002AA1D7 /* chord.mp3 */ = {isa = PBXFileReference; lastKnownFileType = audio.mp3; path = chord.mp3; sourceTree = ""; }; + 269B83182C74B4EB002AA1D7 /* rattle.mp3 */ = {isa = PBXFileReference; lastKnownFileType = audio.mp3; path = rattle.mp3; sourceTree = ""; }; + 269B83192C74B4EB002AA1D7 /* rebound.mp3 */ = {isa = PBXFileReference; lastKnownFileType = audio.mp3; path = rebound.mp3; sourceTree = ""; }; + 269B831A2C74B4EC002AA1D7 /* milestone.mp3 */ = {isa = PBXFileReference; lastKnownFileType = audio.mp3; path = milestone.mp3; sourceTree = ""; }; + 269B831B2C74B4EC002AA1D7 /* cheers.mp3 */ = {isa = PBXFileReference; lastKnownFileType = audio.mp3; path = cheers.mp3; sourceTree = ""; }; + 269B831C2C74B4EC002AA1D7 /* slide.mp3 */ = {isa = PBXFileReference; lastKnownFileType = audio.mp3; path = slide.mp3; sourceTree = ""; }; + 269B831D2C74B4EC002AA1D7 /* welcome.mp3 */ = {isa = PBXFileReference; lastKnownFileType = audio.mp3; path = welcome.mp3; sourceTree = ""; }; 269E13512B594B2D008D1CA7 /* AccountFooterView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccountFooterView.swift; sourceTree = ""; }; 26A975FE2B7E843E0095C367 /* SelectTextView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SelectTextView.swift; sourceTree = ""; }; 26A976002B7E852E0095C367 /* ChatSelectTextViewFactory.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChatSelectTextViewFactory.swift; sourceTree = ""; }; @@ -2169,6 +2205,18 @@ 4198D57E28C8B834009337F2 /* short-success.mp3 */, 4198D58028C8B8D1009337F2 /* default.mp3 */, 269B830F2C74A2FF002AA1D7 /* note.mp3 */, + 269B83142C74B4EB002AA1D7 /* antic.mp3 */, + 269B831B2C74B4EC002AA1D7 /* cheers.mp3 */, + 269B83172C74B4EB002AA1D7 /* chord.mp3 */, + 269B83152C74B4EB002AA1D7 /* droplet.mp3 */, + 269B83122C74B4EA002AA1D7 /* handoff.mp3 */, + 269B831A2C74B4EC002AA1D7 /* milestone.mp3 */, + 269B83162C74B4EB002AA1D7 /* passage.mp3 */, + 269B83132C74B4EA002AA1D7 /* portal.mp3 */, + 269B83182C74B4EB002AA1D7 /* rattle.mp3 */, + 269B83192C74B4EB002AA1D7 /* rebound.mp3 */, + 269B831C2C74B4EC002AA1D7 /* slide.mp3 */, + 269B831D2C74B4EC002AA1D7 /* welcome.mp3 */, E9256F752039A9A200DE86E9 /* LaunchScreen.storyboard */, 4184F16D2A33023A00D7B8B9 /* GoogleService-Info.plist */, ); @@ -2859,35 +2907,47 @@ E983AE2D20E6720D00497E1A /* AccountFooter.xib in Resources */, E95F85C8200A9B070070534A /* ChatTableViewCell.xib in Resources */, E913C8F91FFFA51D001A83F7 /* Assets.xcassets in Resources */, + 269B83282C74B4EC002AA1D7 /* chord.mp3 in Resources */, 93496BB42A6CAED100DD062F /* Roboto_400_italic.ttf in Resources */, E9942B89203D9ECA00C163AF /* QrCell.xib in Resources */, 93496BB22A6CAED100DD062F /* Exo+2_400_italic.ttf in Resources */, 93496BAD2A6CAED100DD062F /* Roboto_700_normal.ttf in Resources */, E921597D2065031D0000CA5C /* ButtonsStripe.xib in Resources */, + 269B83342C74B4EC002AA1D7 /* welcome.mp3 in Resources */, 93496BAE2A6CAED100DD062F /* Exo+2_700_normal.ttf in Resources */, 93E1232F2A6DF8EF004DF33B /* InfoPlist.strings in Resources */, + 269B83262C74B4EC002AA1D7 /* passage.mp3 in Resources */, + 269B832A2C74B4EC002AA1D7 /* rattle.mp3 in Resources */, E90A4945204C6204009F6A65 /* PassphraseCell.xib in Resources */, E941CCDF20E7B70200C96220 /* WalletCollectionViewCell.xib in Resources */, 93496BB32A6CAED100DD062F /* Exo+2_500_normal.ttf in Resources */, E926E034213EC454005E536B /* FullscreenAlertView.xib in Resources */, E90EA5C321BA8BF400A2CE25 /* DelegateDetailsViewController.xib in Resources */, + 269B83242C74B4EC002AA1D7 /* droplet.mp3 in Resources */, E94E7B01205D3F090042B639 /* ChatListViewController.xib in Resources */, E941CCDB20E786D800C96220 /* AccountHeader.xib in Resources */, + 269B83322C74B4EC002AA1D7 /* slide.mp3 in Resources */, 93496BB72A6CAED100DD062F /* Roboto_500_normal.ttf in Resources */, 269B83102C74A2FF002AA1D7 /* note.mp3 in Resources */, 4198D58128C8B8D1009337F2 /* default.mp3 in Resources */, 93E123382A6DFD15004DF33B /* Localizable.strings in Resources */, 93496BB52A6CAED100DD062F /* Roboto_300_normal.ttf in Resources */, + 269B83202C74B4EC002AA1D7 /* portal.mp3 in Resources */, 4184F16E2A33023A00D7B8B9 /* GoogleService-Info.plist in Resources */, 93496BB62A6CAED100DD062F /* Roboto_400_normal.ttf in Resources */, E94E7B0C205D5E4A0042B639 /* TransactionsListViewControllerBase.xib in Resources */, E9484B7A227CA93B008E10F0 /* BalanceTableViewCell.xib in Resources */, 6406D74A21C7F06000196713 /* SearchResultsViewController.xib in Resources */, + 269B832E2C74B4EC002AA1D7 /* milestone.mp3 in Resources */, E96D64CE2295C7F500CA5587 /* english.txt in Resources */, E9B4E1AA210F1803007E77FC /* DoubleDetailsTableViewCell.xib in Resources */, + 269B832C2C74B4EC002AA1D7 /* rebound.mp3 in Resources */, 6458548C211B3AB1004C5909 /* WelcomeViewController.xib in Resources */, 93496BAF2A6CAED100DD062F /* Exo+2_100_normal.ttf in Resources */, + 269B83302C74B4EC002AA1D7 /* cheers.mp3 in Resources */, + 269B831E2C74B4EC002AA1D7 /* handoff.mp3 in Resources */, 645938952378395E00A2BE7C /* EulaViewController.xib in Resources */, + 269B83222C74B4EC002AA1D7 /* antic.mp3 in Resources */, 93496BA02A6CAE9300DD062F /* LogoFullHeader.xib in Resources */, E9A174B920587B84003667CD /* notification.mp3 in Resources */, 645FEB35213E72C100D6BA2D /* OnboardViewController.xib in Resources */, @@ -2917,20 +2977,32 @@ buildActionMask = 2147483647; files = ( 2657A0CD2C707D800021E7E6 /* short-success.mp3 in Resources */, + 269B832D2C74B4EC002AA1D7 /* rebound.mp3 in Resources */, 4154413B2923AED000824478 /* bitcoin_notificationContent.png in Resources */, + 269B83232C74B4EC002AA1D7 /* antic.mp3 in Resources */, 2657A0CC2C707D7E0021E7E6 /* relax-message-tone.mp3 in Resources */, E957E107229AF7CB0019732A /* adamant_notificationContent.png in Resources */, E957E108229AF7CB0019732A /* doge_notificationContent.png in Resources */, 93E123442A6DFECB004DF33B /* Localizable.strings in Resources */, + 269B83252C74B4EC002AA1D7 /* droplet.mp3 in Resources */, E957E10A229AF7CB0019732A /* ethereum_notificationContent.png in Resources */, 2657A0CA2C707D780021E7E6 /* notification.mp3 in Resources */, + 269B832F2C74B4EC002AA1D7 /* milestone.mp3 in Resources */, 93E123452A6DFECB004DF33B /* Localizable.stringsdict in Resources */, 2657A0CB2C707D7B0021E7E6 /* so-proud-notification.mp3 in Resources */, + 269B83332C74B4EC002AA1D7 /* slide.mp3 in Resources */, + 269B83352C74B4EC002AA1D7 /* welcome.mp3 in Resources */, + 269B83212C74B4EC002AA1D7 /* portal.mp3 in Resources */, + 269B83312C74B4EC002AA1D7 /* cheers.mp3 in Resources */, E957E109229AF7CB0019732A /* lisk_notificationContent.png in Resources */, 269B83112C74A34F002AA1D7 /* note.mp3 in Resources */, + 269B83292C74B4EC002AA1D7 /* chord.mp3 in Resources */, 412C0ED929124A3400DE2C5E /* dash_notificationContent.png in Resources */, 2657A0CE2C707D830021E7E6 /* default.mp3 in Resources */, + 269B83272C74B4EC002AA1D7 /* passage.mp3 in Resources */, 3A26D9522C3E7F1E003AD832 /* klayr_notificationContent.png in Resources */, + 269B832B2C74B4EC002AA1D7 /* rattle.mp3 in Resources */, + 269B831F2C74B4EC002AA1D7 /* handoff.mp3 in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; diff --git a/Adamant/Assets/antic.mp3 b/Adamant/Assets/antic.mp3 new file mode 100644 index 0000000000000000000000000000000000000000..641f5fd44f070c50af5e6747a3556e91f1d87d70 GIT binary patch literal 36640 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 f9SK=>H;jni8Y0 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 literal 0 HcmV?d00001 diff --git a/Adamant/Assets/droplet.mp3 b/Adamant/Assets/droplet.mp3 new file mode 100644 index 0000000000000000000000000000000000000000..27a630e10037bc11883695c15e6d9bb378aa1528 GIT binary patch literal 14070 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 literal 0 HcmV?d00001 diff --git a/Adamant/Assets/handoff.mp3 b/Adamant/Assets/handoff.mp3 new file mode 100644 index 0000000000000000000000000000000000000000..c6850918f65f7a6843e1775619e95b691251f901 GIT binary patch literal 74256 zcmeFYcTf}G-{`*yNeB=^LNgRGR6`FP6(#f*Iw&AuLJ37cdQ%|@9YQY#1f@$=P*92` zRB0+GDkv%znxLY99}yuJzt8i1e)Id|&i&&)b7$_%eNQs`-aWgUlQXYwcN2EM z$@^SzC^<5~E6Ddhu8sKD#Q_oLf`daf)&G5F;{KTdPEjF_)^>&f7XP2}ABv`t#(y3$ zR{yW2{WH|@+S-Qyb>M$f0h}BiP4@pO-+o7kw@^?$mx z2%((YZ`{8P5Cwp++W-vC&C4esbU;*GLQ3YayrQz2rnc@;Jp&^XGYcy_2d9%Rr_YeR zefM$NH%M*uM||zwQ4mBd|}L55Tt$ z00H|wh64aE763r|pW6N1eN~eR01%>qwTZ@GQl0->eF*HUjLOoByYY zbN*i-$0f{qsh|PRK%m6c^UR;A3LI(8A-&)(w?-KAASO-%YL$7RhCVlSL8!CdlPGPk zs{Gs(Q$YkK7R#)As6bUWK~QTH#93)_NK(>aixerJq59r4VNe5V#`OqpyAuuKx#dJoMAZ0A|R*XSf~pm;=?n8T4FZ?O|x`O9V<( zCo2tE7O|M(+U|DNy}bLdfm%AsUqnn67F&fk4e~;ZnVOl#J+D7!&9q;<(3Nao9~)jT zPI(9w$rtvl!BHQJYI1^-t>wd~N{3;2BO#AWFF-0O+$RI%7wMTn!Al;-VULDuFZcog zDjcZ9!*Lf&B&UZ>ro;!VA30xm;rK{deA0$Ekvx*bz~F{67`O{F?;dshMQ$BA|Nl2~ zQr0=ODgo$;$QnnJH^D1&q7X33(Xdi7S5&mHeUvC3DcPeWLe=O7;~$32)nyNV4F4T( zX+G$shGCe>7H$4pivA_VN{-K#Jl~SvQvooR?>n1a2SWTJ6H!{c*4`6jBCmo1V@;h{ zI+(rYW36K`(Q=5Gs*9+%&OU0uixg#83lL{okyr#n6!DN2ie-=q$zFB{RFu_(%K*2J z_`M_9kDrx|95ir9N>4YF{c!4l-H?ca0?t2OGL2eAbr?SxVslx+#E;=ZKuHU`ujC%& zSHkzVYA;#?QcqL*MJO;kmJBT;J)l`41u&ps<+Fs(Ch^JwnQGSbD#B}C{z4xmloGzQ z5($@z_DtsSw;Tz&Em{ed=YgMDyg!#Uc*)Q#73|(@$!(rzFJ+v;FSGxZ%QxZIG|%7P zl`(&c%h%$^<=5uaG~i?e1qFo#rBb*HDJr*jPCsBL08q*5d9!zZSD#S>>TnHtBcEzK z4EVBR)zQXM=9JqRJ+B{*_=qq>$&&4t>TBOiLlFQ$08v0xQAMY_X8M;qfRyEw2}Vr4 zRRf;|pL)@ycrP=AjOK{{gm|$eJT?6quH7kGqYskakmYhfCdDaUC3c;Z2g*QHZQxl* zL34#;$(l$Asskg4H#Zo-%w#o*GssDn2yuI1mWlUjw`ys78k|Of`;UOmUE``^6_u;t zsBkBP6u6S^+ZuI5HVK6E(8ZKrxzb$b*fBQ#es zT;Ri7C$j}!SAG8%IW~2~-n50MM>BlE&!iyYX!;>&`HnLKKxlP%z&fd*wsb(XT;FYT zSG=*nO7>@+z|}_MBS*BqJc!;psiT-UlQb$;bMfFvcqTbxU}U{G_p~bY;MgfCg~4}s zcE{6Z-Mm?<;TUQ9`6?F|mV(@jNcKzo9*gn`>RK2Ogw;6e$qc&(w>Rv2Q`dDMcG-VTtR9G7qRs+jrL=%VuC+oRTKdfGqksp>y1=N;h3LE4c_?|#F zOyA^vqxRB6gU@629{JfpgV~D>10@RsKR1dWtndB`W+%S;^)7TFcHq~iBglp2;PIHb zv~cnE-$kafmln_&zAnM7x9(h=W4H_;j=ajev-BZ2 zxZ>9B=x^g&772E1N03I{N=@urY2g|FkXP@;bZ#en$sHs}s{>hg?4HvQ8ja=qa(5EyBY~UL^*tiO_Th;-3XcY@-pYq*E(?h`V>~{9!c|f4`lA_0FfIAYu)Z8-( zt#v(->WUqmYKeC| zt-~M2ZL^oyj}zjSZVbPR3I;B}VJkd8BK3nk*(|E^T+ zUW%>6U_rzdI-R4<4o7N(kk&C_VHbQY&RTT_-1otVUg~}?AC*Q&N<@~(W^4Efr#?^m zu`4U9q_23b{`AVosQl;XOSk0%zq+kyjdFaq`l)uUy&|iR(`|X~cR|ET&}YZmVyjjwZ+_7O5|1d0ibsu13#VpE2w!F` zj`h4db5S|y(b*~E@tQ;m`+>*acumnIx5?F*V}_ldn+g>JbCemAK#TQ5pQ!f)rv?=c zj1(V+t|!8GQe+v$H0Gz#Br)Ox1>H~6YHT>OF31IM&P62X#0V zHoQt%sVy26cFp{(yILKm?R(S8VnqZuB{OldC>)1;RMpXT( zue3>?Lt(Uu;A(XIm2Ba+Jtwv5Gvdnx-yAsG$f!7c;)Ra4!&|Xa2E~xPW`oPZ9Zc8& zkk_ZgLsSLDPG+Cu{gwouUDS~HiyR*ua^ArF@?6|}c6v7>#{9T_v@1CPa$9GObzE%$U{hJnmS^RJb~-g&b!0; z)53s1;S4iegpBO<616|F!x-w-G#)&RGMyQ}d|+4#o1&dV=&RP#tFrByuFkvAifZg{ zAFQ0^4S&4F%Y7%Xb!4wV)K_%1wX^xdm&)GBC20rEIko~liPPQ%_v@Gbif8&Do2+Zk;NXot`C>khM2%@ta){dVw)3y{Qk*?|uI%3Yp!F zQF;*7kWdQ@bw65&-`KjkcX5&3`=tAQ@L9N^ti9Apk&+ zpTiVlAWD9Ev;H*AhHL?+mz5c+nLB*zR+g`2O|AusUM!C>q>R^-2}3Pz(*9#s!j3eW zTt@YzgN;L$j9Yu52in)s%WuP9Ueo+a=EQKw#-7*%zBTK`kFOmlP2w}2w!K4Uzu)Qk z+}HQJ_VD48cN*^!XD{EjZQY(}?RK5o4pv|Rpr{VBreW|r+y#k8n5p$f;FHOmC_hAC zlW!Tt&Ov?v2jReTY8?^e2ZhbW{Y4G~F|M;D_I8s$ls0w8pEo~&j*`Wz`11DKAMhQz za`H?cM0TqSt%h77m{~w(&;i&)v<5N4pQaYdU;uuGS*2 z?2jG*DP$fsX^!EE-nSc|JU{$r3jy>b4L^X$llEF0%hz(qwiBR#;&+)sdRFecrO1=V zRI^pg3JmcggVZ;_b-d&$r;yx95rY6d8NX}Lm17sG+jde32wgT#4vQE=uF)(-FXmn3 zcDz^ZCUdV}M)ufpiM-7hE{F2_m+x6G7kpF26@9zce=gy?6J{fH1G$mk(3h>xk^R~9 zjnyd)Q9P6JA$#LvIWdo0jbPkwf}X4@v4IutmHin^ylW}K3>3o`0j;Mp)GB|9erUdy zl0Gi_?4E42kJ^EsnX*##RPyV*iDURLruFY$6apA{hO=nunc3Nf=S@%7swq`w{7R9n z8g88hZH|_6*B>>l+Pze4`m*}aV@oe_Ki_yPdL+c_!L+_n%Y++sFf|=o5{L~CcPSvx z6Ul8VB-Txmzq@xM;KjJ&UyAY?QNkmbnmjc>FeROVIV5D3y{v34LQ8rJtNak%V|bEh zrF#B!UQ>C)bG5M#^+#*2Hq9hsK&;iM_@OwipNBLI&I_BpPTo1^_3PyLszH{y{TW71BYsNpSarzvgE|1abakn0u}VzZ}1FK3Pho`;2??@3FX^Z(LsS@G%3 zQt9_u$I12g(U@@Z%53G}S>uH*$zI17K{hdPSpL%ta>%gj&o6!^S7_RzuMFHu9_yFr zcjX_KG4o&14r};wu%`1`?)mbi@YmNO#ALUedp>tx-0)<$PoLKIP-c1*Rp*Dww&#HxYGqF@D_qQN zG6OhGfsn>!Y2YRb1*4DB2_cwQ(*{htn;4~}INA&kQER1kHZS421qotB5eAgM=DO5cYoDG6^=UuaCF zsh9rN65b+C-|%=&w6te5VTztd_`8xQye=-U9{lz`|0mm{Jj%Y+!ZxLIA>`uK{99)I zTwjm*Sdb6G5|hrZkRtjM-xnEC6HP;U_`H?Ktbp* z8mmn*gw_Kz;sFR5qr*Hc(MV(qOe>zAi=*zCQ&2Ya-A(+2STIMT<0!@)Hm zfI|wd!G(ot#G!8nyW?9yj^H*E43d@?2u_jD>aU@JHYq7DD+`Z|mhBS0#NAr_fD??~ z(t#W1i>v|Q#mt1a+mp&Fd1v*s&RAJ40alWEl z&C8nyU9*?-LYRlBlDrR^S4jhD~#fqYmZxQKGvG|^bOHDCV!)M)}wJw zU!Gp?uy|+VYMTD(uETmJI5E_rqPQjSz!x(k`HY0fa|`KLYc-cp+#FgeI?6dT+y6G6L@mP6Cm*@MpN@@e&wlQdnO89;TI6MZmUE zs&{mRz?ATqN365gM{r=0%iPpQ4zASUyHh&8S(M}9&t>Ze+kt$M@5eP5XTNq)Gg1-U z7BsO`2;-7?h>L&zkk~e zg$`n2{BmScQZ>Yg*S!zIyxV~SX8J1C7uK#uiJU!{;1p2acIDddZS@b|KS!Eeyz$ht zC2zoHV^cS%1gQ6`<;|kvxAnf6tFPV1RxY&xBB|Elc>6&)QY{fqu+XQwS%7R zaC?+d&vVNmZRI0!`H`|<`-`DoxhKMBY0l`y1RnZ4->gjyYbRrl zfWdI(4~yVOH@vR)O6FTan|#l+UzXQdlb+XN^v{=VA|8val=w3{5m;CoE$d=ytf=3>bV`G)y3N5!C=6W{ag8WHd8_on{rUVG3nGQVvYtSSRH z&|yC%VJp>E0l`TOM!1Y;*-o3Zn?dFHV*kV9u%rX#f}S26*?E><*)T5#^T%1ybpRT6 ze{i>=SMAJ`6P*n&?rAlCV7WIM14_*&Vk%z7+`1w|k|bk>4WxMx!3QsnOz4IxNDPIb zZg&Fb#XJa(m|Jp4B@&c!e$%HTaBILmh z$1U|w>cSQRh zYSNjQ#op4{s}v^S0f;F#7$725)C=67{P~EmkdYQI!byX1TpJ)g@Bla#83D+^BEU*q zMmz*pBZv>o`&wVWIqpM%?)$L*@y~bUW?h<2`?h3096wW@>y_x1QVr!IqAx5gkVku1Yn8{EJXr?m`~M9V_h+p2nGDmws=gV}4-kbOMb7l#U-Jjm1JJsf=+;ibLje7UZ? zJ-$x;F`>A1H7=q%!KwT64IMv3=r{I9_T0wT?+q^N1a&#{CbzHZVm#~6p?zW;wX8zW1nV=Ti{%PxU>O6f!85S=et&@F z?ph6e(fw3~v5Gc7d|XJ>a1&pR$-W&rF;o3MIOKi{dNyrdB&ddKG@at%CDCeO;LP`0 zC-~W1Z|e4=Xyn8<$P^OugWni-G|AqfL#0JuM+LpHlv?MPus85j<53w1z_K_qfnYoz ziw7?X5b&m~6nqM`M}2rmLaUf2flux`fLH93$8#nT@!Wl0c)`AV_$!Dfcozc;UE%B? z4IJUGm6K$cC>%8^{4Ega4B1u)Q$8#3sgplA(FkN;=;Lvlw|kPGCkrHqmh|u)R+d8d zrIN)wqfnOLJAB}`(tPV5poFiON2?z3j4r`0>qWj;mg7X!Si2e@z+PN+^kZavjqWxg zF2)`kDiRP*BE9+D>-PMlRhQ{yg-d^eirn6tUR+501V;c6T^2{g(^D5s45}_!5Zd0vy^zt-Ivo>|2$Y9=kD?@ace!R{7+e{dmw?=Jg8#; zptoPWzll2McIfA3g4J#VIH3~MMLzvr{v{iodcF;vTwhw-RWr= zH-nFM-@=S)90!B*@F<_yNFOY^%5?Fu)FJVwzNTC+0z2tf$o!y4k{LLUq@Q|{Bn!$R zVUxgQR}nc<8l57^400-!=IC?GBRSmWpbOtCXmNOk32mN@QH4y>tMLQ1E1K81Pp;ma zwffHMM>)m2Q0{Oh9}eR}j`}<+f1Yhec=5%^?Zwesl%Jgu$d5m_*%J|8cZ$EGmz1&k zr+H2Ecz%3ngq+vkTOWy(Z3cMV&J~jyKu*MmKZ$LELm=s4B;c7+ZKn?ap&fdt_j2m1 z0Vi4LN`0ZGl#X*Zc&yy)v)?uU{8j*L1jbKHZqc5o_dQH!#2NRG_WITTvjVcr)Vx)h z5AZNAVuU%4BRHH9!Q*^r64ek8tEBklG0`TpEu>w&O|ew!cE6tldTnboH*T|-esCr%pm@qB zq`>0=zp{@9?d@1X{n|If==k0TyQcervnCa^l{XQo(vVYcc{xjlIxzeN+PENyJyK(& z?-H-D`C~S9Bln(ORi>ZUSa^j}`>&p^E%^t}j>nY}RSxU!+J^k$`MC1r)%hD%jo#yO zp`m%aKr+FFWk*V{C#wni95oEsUq?&1=j7AR`G=H4&m{TL=%jn#3DPNQCLw@(Www=!}jU(D3JEJQ&&o!12)6SWEe~obyR^$XygGR zNz<8*I@3(v82m6){f76Wz!SYyzwTNTjn^I-l;S%+E?#`5{$|ViSN;)|Kl-BC&+mB0 zEj(!st}BB|x!!OgyG%K?HWM!kKGnIdr2D8^-rg>Py=xAKdILO~O>sLa{gLJL_WP-{ zWPW+ag48+!+CLUSDuLF5UbK4aIM?o2SC@?a_J$cPRc9ot*;t^bG=)rW=3?(8!>d&b z!UJlz*vX!L??gqkij{M}fbBx(!4lvka2`mM8?Z}ZwnNB0J)(!iAm17O)=b$Hc^VoD zHX7*@bR=fV{OM=tO-z3@?SOO1f`w=xX@@2?nTuy0jpU_>wi}B`7mdz2|Ne5!VcY4z z*`r0%PQ^+?D-mlSX36W1c*kySZWe8QLc<~~C6AOl-C*h^f2y@31pof272o{1w^N19 z9s#X$9=hhND8NX+>$_&pFovU5Pj@7pWp1cdyM;WBt#-e?*7&0SN>#c-O_oxrNt~kL z4z*UZ?nYpLoYqa~jHj}+Pj0KuokOt4hpaM#4+4KTrX%%kt-q6FoXe!k4=wcQ0bcWKX?cO=LQF;2W`cx;+f zEGCs1ZUV9CQw#h`RkP7DXzpP+dWNe{e1?C{S3&!VC zRc|7Us;9%J7X|I?Ou6P2Qc?caauEE%MP90?sXOL1Oc{%Q_%q>bUut!|*UF-~y1Dw> zbPz%&fHOILYB_H75Wt!zNB*#z{y?;Aa}Vh0d7QcA;`??<#m#R|K51h2n9Xrt3CGfz zi6hw=zlOhkLkWjDR-_X?(;vI^rh2Ey9}cf_L1xA{HumRrc26_7BG0h5f119zeuKSh zJNie2JxXhU{BUa{RYID;1`)K?Cr%!shuoz2DY0CV8rkoCl-!20)}_{5z|eyQ#mmE z9((cH{K88a+Oq`RjXNFI+GnDEX4{(kbyrwjs@LeMry=;$E`LENb1x2Yh;X|{w|sYu z)5bnUGu!&pkF%#9{BHVvuKR5jxYT!`+Q1ynjThHZbL>@faa90Wr0a3XuKz`DlW%Cs z)1YuiAe8b6M~?_PA(XcP;adyt>&TT%nNwqahx<1cfU$@BQ*p*oxyqVqabn*wmy7T= z{gT(7I%66SmO}GjU8k-oR+TbKz8eKzx#uo>cPA=ciAPhJmq*DfgVUQJQz9I}pVwD( zmkw9f|C9Z4aFZM}z1cpUu{o(o982PM_v zISl^67x!P$;42tbD>itGm!l5hWeEwFU1XK65(^PgTUXxHn+xXWD}y1}yusy?8; z?naThLTK{}RzCt$Z~N$VW4C|TI^l`%b_W=m7 zycaI;?6_-%Vbz?O`j1Zf%z@FVX!|~a8@CM8@Uz3o50s97(^?K1*}my7a`1V;oc`qX z?Z~F1N=k|aC-t8BKkmu->i2c;JknD1eI_%0!>W1*L_NR+0csfdyc<)HT8xn<{bCyQ zzZ7xPdnOu^^cHi9EXUNr_A%WJu3+$$<`@xzo5;u79?T-(E#X8D0HNt76ke|M0oW0# zYh-L>IzWvg3PT}0FM7>5Kro<50?X4Q^oG*Y+St^1pl8`iC^Qd=uS#_QUoG{^wsG*VyfN z&T&vW0w=WRCx=BY=vWEeQc6@ZK5@d3Q!rLgkM4AA44F6_!_*6zpEq%S?)A@G-*;pmT3?ERT8;s&I$pBW zmqo}V$4Ve&je*d~qrMq13M-Q1=fxU`ukI7A=iBodVHK`Kfa!(i^r&=H(@8~2$7vQGwbM072n-Fm<( zSnHjeUycFp_Nq;I^Df7WEQdE^342NXg}1uqOQB}vE_Qa(yoZo&0Y?3GLdO&`(xkW! zh`S6wywUi8{rTAM%*XH8d++wN#$#T3C>yeLl8ej*fil6r6`E|u&x4+QVmpfAAt}p* z7 z8lK`;*DqEb7bIScxS>K#2E1z+GgTE8S-N7<@rKr!0F@I4YT#J}vqV!NBi0m|8eq{F6Gnlf4#FUz{HdJj@)Qsi zxyVW4HdR};fMEzAPNcRE6|l4W7zIXflB|5K9KTb#pmRlE52Zg)Z&!$#}EJAA9Z{yQ!+bqvLcUk?nq! zo&R9-I{UqIMEpst&(o8bP#E$MQ&56*Y}u#pYF#(Z(38C0rtOr)$z9~+gCO=O!X{t* zZkFtq`K@9$dM8edr$#v*D{N^~CSmF5zc(~M5^peOsw+2)nk-F~aiA@!{F0bXG` zjUx7Xe@&s8j|!lvEg)B#Fc3zYd^V%$z=({1H-{!%16b!ULfQQ@tF~ftCo2PkKQryACWUAXvs)k! zRbe1VQ7(dMT4qV5F)ze`G@q~f`Xm&HDjT2{tu7Q2f~tAX=SNlwt8s}Hjeh7TuIrRv z9hR5>(T&N!c=mSpl!+NnGr#6{S4m8dWVmczu#;%^koSip(%#|7^NBcqwJNo%>i?*Jd+VGna;Qc@tjF9^EE(g#iUd2%6~ z_#Gh#X=yR$OK;vFVIzj0*2bq6kVtc0+j^UVEC2{?NV!BnfwVV<<0{XO`4RhqwT4>O zT!<@z+J6R+?uM{D-{`p{{flq9tOfFQY$2X+O6@wmGG&Z0O7(_P8p0FUuMY`dcu)&| z=k*%Gmu*X~J!b!Dwbq|ZWFLPp1x{&7t4SPWwsTnE@Zx$>T%0&FwYiN^Edx`#9EuOr|C7l6!D8`DfGRNvs2y$shP6WQqOyosPzs~{gf*{i z)eb{5k4BUVb(@f9y+;U!nlm^7Bx5l@1QA4%V+iGOPjK$nc(aT|E>V7ylj6r8R%Yho zs|-1KRJ0yW&k1tHL8+Hqqol0nUYs15sT`qKt~r#Jsrx56R>hDSM}{-vsC-tw+5bI)v*gsoRx+lwh?Z&`|z z5K!}P@hj2`*ZUCC9`}0-|`b$MgDux zgjxeagpG#@U*Q(BpDO{Ylg?5tkKhugLsViwRU!>0nhyYLo{k!U{?GPj&oxdtW^3@Nodt%B)RtWf zxQ3|aSW#|NN?%h8l2fE(nM^~|k_KyT+J#XEO{Ks*fLSURL553(?tIC|g>uQz0ELhH zaP~*t*y;(Q+R?g6RD$%uN>0UnS2XDmd)!EMzaFdo#+{CYgzoc8A$LC>&Wo3p{c~?K zROQZx(p0U_;%AyWzNUt$Bz6)Xce|+u)Qw--e`I2|(uF-EU3usL%~({k1oue_`eC)M zDGZuazui-&dji8l_$fug@|=P|#5EHnfuoiYyfThm?+6mhI&iwftWSAcIg5~5eu?X- z(2!&DP4PRH)g@AtM_W*l@YS~}_B(#2J9<_O3u*z_fJ)^krIvBz(;_(oXj1&NWQMv( z@%hEl_PhEPNng3u=$g>&l=?$7Iu?qiUcrPF&)qi2sz*z*-AYF#levpHvSgUCk_=ub zoItHdYDyuIa82s;XGd1GP?9HC1xgf9YI+=yDf6Y0>z~*w_1N;Snsc1eo*H>U!gl;O zQ?bczH4T-l45N>0@r@BhQMmre{AhWZHK&szvT_OvL7yIrs|a&tyU^r}L%?r#_S4kM z^i+dB4X!Mh(eTcGB27U4MQ)GpC(Y4SW>-k!{Q#_7hW1YkGzju73lf~ z0*b(K&UQ)Gzn4l;x>d}KQxbXQU@6s7H4rawTZ;KP@aym;O^>yxcOP%MX@LF|z{O|h zTwXn#@GvRX2oJcjI~H1CeCUnXPuxQHQBgn&z$Al=02zn?(8cKuy1*R)CLQ2_P3zy1 z7|V({=5(r%t5SjkfdD*!ejy#9v2Y`K_v1-|l`x}w$F;$LK!FgGL<(&nId&Oi z0Jy9FBA62aOB0;&hZ3vqdI~Lp@3wBFZWsh{tEnWe^&ZO`Y3pwl+vU$Rm^u<y;RGs%g=k@L}{Hc!JqW{2|&u_@e`_)$Js*@w-N0N7ibp z!VYn1_TjNdiup1x(K096LLWf}@`-|ix6(;Oxpb)k|DV@g*EMBWtns^Gm|qijZN4W! zuMSP)@@;=u_ z14Ao6=^qV9cJ>PpQ2Iyas&E^=D!3=0zRaaN>&nN5Wx4CoSN4Z+yS_M}KGo@nzo~O_ zl5AcRF2!Nij8qx>7dbX#E7a3qKa>mg8yvyMApFI%m|ekpf)C^Q2xWK%hclj|?-~Ar;kh^PBFBDaUm901-$9*mJp59S&?@AV=Rz_0^JIVVC3;uq4{GQgy%i&@C*N+JJ z+BHH0{^8V3J{b=6f2?%f{;ad#t>+1L0SDylm|;kbsgdIvRXNsX5S)LGj1lh%6?77O#W?s_!*-Q?GQZ!(1yL2H*Y#LB=F<82n499*UAq<;2)fga{vCLEqS>BUJ}d#o!b<-dI&RF;wg@uZ!$3 z*F19gg6>^Yf}I!@+eHVG@jNUejSY_4#ON4oM5e5TZiaei(r2~dkTy^vzveAM&GS+8 zcezxi`Sl5mh8w-&TBA*f@=VwbyNB=o`Pm!CllAcW$p>a*Cmip=rBPmrqJy?cNMO%| z`+|CqrZ7Ppp7gO&4YdEHvUlBFeOFAMq0-X0VJ{zmp0`}rgFrAcN?!A7MSFey8-I~o z%YCk|tN%A)rKB_%gh_Q2Q^)@BeR@Lc5hWQ+l(s*>Y6TGet|-NGjN9CwKK_OZqs5eD zX(!=aG`sJf<>BIw{-~IrG5-Gbrtq7U8`;di*lew`U%5`N zmtrIEPX)*EA}l>A$S2l&ptdguSnuBgfFU;y+}l=;kv~6y(1%bS@xG5pah9=+Ig5<0 zWX17EPT1t#9lXb<)W_t6t8k{{z|;c3PtMgKBUFj`^svOH-6STj0$1!Lq6vc8aq;85 z#}`|1>c)5gJQYYHtw zdC7Cx=1Rkr!=;kv3q$7{Vw=*Qkq03*%$A1u5|u-#JmoI>5tdfn=RR;AW>48LR+ajI z&b;0UM2c44TDp!3CtqqxDi(ejX6Vz$@_lV8cdoD4A97|iQ{2Z9N2fYn>RZR4%vP4m zl_*|c^ALlLHuHDSH5E-sj=w4;S-9Aqy|;bwoa>hW)$5Muew>M69DMb0Oh6Z)0;wP- zJQYMm)PrXBCoHMr^{`mIVT2uP@ucT1xmMB}3h%N;DlC+bf?B4Sa<&N#Lt-sqC!`Eb z59?}~a$%7a4i{J|NS9}^hXZk9YmeK~x-o;+hAtz=HFqiz1GAJG1XuhgM$g?RTD%QV z`XO%uA{w209_Jn(b41{tgEP84ymfXqL1utgzGa!$qfct+wNlQ=t(!Pnb;0Rg4RQBg zgx{sWZ@+d5zr7i`-gV>G9{;rj+a$?zZ1!Roo85A@Q>99W7{E3w{>ePu>g&M{=Et;OCLp_(dxvO{W@_#sS-^CM=23lqC zh-#pp5eE}62Ax2O{6h*0%Ue> z&n{xvKXaQ>D9dcQJYArpp2u@^xx6IZO^XBE3#tGOeQuZ>dn4m)QgAV{#O_GpLn(je z?|2o3LXib|;ZwH$Avg1+J$7Ls%FJ3U~oN%FW^7GGW=XMA(01VGZ zJ-8nM!|^IqF#agv5Izi;P=m7+wdhIZ$Br3Dt0{p`;!o4>;-yUA;W4x?cpW3OItasw zf6;#fMkkQOsIN$}fE^)ps!*ePqP+d-2M(JyEszg1mC6aHT`E?X_D9J2LrtX1*s)Sp z?LoIrJu14<*>bJeK1svuEBTopoq`6(3yAg^J#||uGtz{#3ZD(HR;T;d@s78W3tvtr zo6FbgaJ?>e$dPn4@hmT}Jt}B*ucY;vOKqM)hq9>UiCMy1>Fe>qT7Qw-W(-@{>vL>d zO}fiq!h_es#a+vPpIcEiNX&bmajhPuFLq_kVOXJymm9*i3IJ zeH8q1V7t}sW+M6*?%90bt!l~VlHcUo@q7+v3Mb>ZuI$%RRPJZ6;y^h|420S*rto1o zf+tz=5Jn#lU)RH+%WacTA^$#Y$eIgc1BR1~0D{m09D@gwg#8D-5qg3(-;4T@BI5K2 zI0S6r>`F*hi@Anwow4F<@5tK-+a65V!E_Aw3!(<5}D50L_f|Ik4QNqAA;Uw=g7t27&Hp|}! zxr}kaG`v>Q+2xKEJtgkq-Fjgd?L(R&$rqj%f#Ceq-w=*>d{sYvhwS>BYfUueM)y50 z|1(}MyqACb+HA`jta`kehl7{^OOOdBs9?A%otgZsB#aD}%{1?y6A9^E7j5TQ!}!0q zW$KZJm`_%z?7eSL zfSwA$e$D)ZqB{`GC78Af?DJ(CZ_2)gzvB4JHgXQHwfRn992AyvanaYiq}28&b}99w za*@f06rwc!8#{F8|)oSd&H! zA_6R3Eu#PI$8xmz97#|PzAXb>;>@4aD&BCg<;1j*!qwBI zx5QpFnX|w(6Y}PmRQ07!gA)GuD^c<6dAFsa%>6h`6fXjB;$`=%WUQ!IC@ht{0c-{a zCS8g21c`e|(A7z?$uCF{hPdSc1Ddv2cMhR7E_n~#y-E@5$+!f;tW$lMM?lU4z$6Ml z3kxVUG|2lS0CZb@$|)xIesYo-f_NnGW5ei6OU6`vNN%lG;ciYiEacOv;;}CwEjG^9 zVQ?NDGlk0&UEZX%=U3CpgDVBIs#y)PsYS<{l*a-iRs5H3u74Qgv&GH&;97s5eDjU5 zMlSCdwsOKE#vzZ($F^=YK&?9F5HM~CB>(P2SSSrX`IOI-g9ZO=)rMwqf46uUupMB& z_fj%X$@AbDFwO=aXqckxR2_5rhj{1x^tB8q@<@L07e3XVF-3k@RE8eEDS%V%B246THr+?9)t&JJCh)05M* zfk@J*c#{U7)$=?KVZ4gwhcpR&8Fx>KI~Hjetqey*J3KIJpyzmYqf(FVOsx3f%z)!> zd3FEPm^n5zj151^J25ivbTRww^{>}*_sRKJESIx2Vs$@hM#`W{`kjmG0JHf}#|FdYw+8+~} zFxk|6OrZGb1kH}wRURBc;FKHl7y4r7fk3!DJnqweddIkG8@E-k9b( z)J~1S7a0DgfYKn#!(I27Q3 zB!f6$zknJPf~&^4i_FpN#1p~aChQF%9mcl3UWBp;6j-bs#n^D2BH?dT55-EQa$sPK z9Ckcr=6NPt${IeVnV9}XA~^}8rwaxKvMhXb6eVZ}qUw|~q@z6Ns&7ITK&bW?LTGb!@w!j>}6E4GLJPnm3}ehGz%@;=D;v7=(ZciX^RDPzl*l zP9)hZ-TbEM&o|$X7>keA4zxeV7nq67*&0`LNnBU9%zuHsVLOhMz6C#u?+z6+>8m?p zyJ>f$_4$46x?I69HN(%4|8T9ftOLM0i+|s>9t5|s%Ak-wHPpy(Qz`s$fZ#go1(e7N z02=z7;4&;#h&yZbe~@(DK}~#JbW=$n^Z=oU9;%@VNC~|KrAk#p6%ds!h(bayp|{Ya zBOoe85iA(0G!bc1ETDjh4FwcY$rpd$&g^6|lgS@%&)$3Az2}?%gf&N99<>#ZC zs~`uLP*N#XkDTI-0Auw3_=89j$^l3DQ5lvstmP>cF7Y~78Zp6BX}`UgYP8w3Ezz@g zv5Gl+E#H@)bi+eW4$#?YLtW#UbEw7d`-2BpOW#^Z~!Pm5i8d@%Wpj3K?2N3Q}aYuqsQH899V!YL`bBepa#t(IamZW=c5yPdeHId3P=gKH5=3g!K!V;QcgQlLV1Gx$#&N`5>TU6Dl zBt^8YDcYV}Gj!J1F->&-vYt13^?jWBe!Y*=RdL3F{H@8crl$X*9Q)80f4VR;v66hc zjl!+;1lB44&AURVt5(_4ZEd&t1r9(2DS?zM525tY6pamix7qo)1FhCX!Vnktr{%gSaFbBwKkeIztkz#1ccFLDcZ4>gwh9aBn?F z?so}w{JhDgc=Ml=jR@TYNTYiLO3^$%kZrIJd=~nSa|E8~C>_vQ)x;6b(JI^tuMnF7 zkxwTAYYKH>4GncsRmVZhs zR|LjO?wN>*1!hHEJa}k53}UK}K)LMx;4};z%7v9>!5PC@31f|Rk7HzSP5qQbxhkZv z*r6NI(P8F@5QsTw-!jGMdsvE$r7j#cCAa}Wz!K>S8JKM&%QS^Z?ug7_rCbT}DTrT8 z4S2(2>q5ikJ<*tIBjhQkW$zQHo?EI4XYXYfnjgW?Vm>sh4DSr*Cknae8j~#DM+LFM zTxlS#3=Vmm19(Qo!3*)lrl&$~h~BCLJuh0|w0EE_BMdD%$$DBrY5sha`zm*mf zCF=*O*=T*cNTB~I_Z2#g#k5}pGBx>`H{gjL1m8CVO2P`8fHWWlryW_zUs4btsAtD& zG8u$ETZjyPY~(#;?23LIU3q<;HEI01&@u9?`?;vtst^2JYZ>r63sC40i8y=id5lG{ zk>ZiL3;w3c_2lm7Gi)v6BfMH;_f5xVRkgg`gWRQOjg2J2Gqs8%)~03hE*|2|n#Cp^ zm6_E_A?N|)V1zSu6(?n$M~>##h3^RvNB{|s(*Wbxp(>`SYe34+fL2d|8eY{5M7&N6{;z?WRlge-04W*4gPev>_Now?b znqoFjHb4Y0shX*VBGUx02#gAW2jfMG7W_RBjE?`|brak~$^%!B27w^1l1D9A&F8MfEbIr*i>IH%ez@(|%|_Y0G@^mZw~7dG-tro5oK?bp#?6k_=k zSRH-Lze-BX;(u+)*WoS|xn?qcMOz(KF<(w5pL$7e5TBH0ExVbsNA&oZK-+kNSjCYhgpfUTwFjgZvv++eI_u2Ig zaog%lWo33N*Ez+kv2yCT+Z}ZH*Qi{*Cw%pt?hPD0`W%OE^}d4zqIN*US~D3FL|^3h zlfrB`=j*j|42G=)-|5Mkl!s<*4u{#rBCFH%X9wGB?qDKc>^)=b-Ti}j%v@0c>mhRqx;~VkF3bhO%5rtPHtr&!;ZEnYd%?o&`pvit<~2 zG5OU3Px^>_VUEtfP4M$}`ohv&IxSGC7eFLRImzgaF_I z<_OXRL`6D+nz#p`eT5y#o7?Eqd$dqI=LhZi5Lkz+P8eCy2)WP3@YPc)XBjV26_`L4;F*|?a zcRCJG7Di>jq`)-_NtR7*YU!qM8*7|!-W_?qy_6xyi_9{N=vh=TTZiriW7cbjo(q z?^Evc(2eC0BzBlUPtp)BBqh-?bU5K-jY3`j(`y#5RIvgO|HkVdS%=b3AGt6e@#kBP z-#PC0fgyY54|i+jN8WE<_Yaqfdi`E1es;`1wm17luuvq81uKn#5tL|fk|qWPP-xt% zau@~hWU2b!J7vv~O_~hp4~Cy?Oe0EEF+pyMO2YZ9OS*c)5^*tevTnqBBqTj1k?tHzm!xSMQv^IaGhTLjCL@CMyf0>&xo>|S9bON6V4eTqrMS^q(f5?!z|^a`_&KBktDgQwfz zlM(>_EbL$~su7#HS@>&Q%;j@o*O!9HiEA&udut3-jZ~ZzIB_{d$;gh3DMjVYIq#8c zuQNn%GUoRW-46!yYBR!D6Os6z6;2v{=0&vlcb%8 zOMz0R11fAIeY;G4-*YZgx+k34FAI!3E#<`x(*b;+GvG;20lCsOL062Bxzp!w)PBfK z0!KK7>xCkl$!(wzvs@mikg(F;tw+*V6Xzq`xM`4f?tt!*s*($r73R#ma($o{D1<3| zbc|FPcg?S3Mp3fhxRhsl?ie>163})BEuYi`GyTwkRY>l*lCifks>c_cUWv}_S2DkT z{?^w>!+X6S8RcJU&L5VCFg{|XRF*|Km|_-!I71KEP68RB zq(xX|PlsZU%6ONo-2J)?%C9XySY{G2KZ9kCvP|Fo(32|qrc<;m@k7m)%lE;)P=ZF9 zofNh-B5q*iJf_~*zF|C*=2d6pdxt!=NrS~G^01Y92Jm1-Ox4YN)VF2%XW`i$mbd@p z8XOn?N0s=wBU+&ELl;D>x=4AhnHk2?q!&__PDxWuY0Gm<&FzYm7e02j@|P{LSS;ot2$2{$6pn;T zmM`R%h1!<}l!*N`+Zt{GK>aJ#q7#5HQ=?o}=9W=`n z;L-X?{FZ&ZV1P~l?zlpvv#ykSIJn@0VOrDcipAGJsiywWA)=|P+WPpYnA)K8llD&k z*mvx|xA^oY&N62^AQDzm$WS4hlC|F!L;l30RaS#N@A8%5`SbFTtoThjAmF3|kjcDw z2v`cVauM$=;3IpBpy?r+$U?Hkivm(CD?Mz9NJ^6-W=1+M1tX8)1tCJ%aSJFrBWcjk zv)k35R&@WED|)CLjJI0n8S|bCb~9rgJ-mD?O3S=!R9!b_(NK2%AnucXTk@#&PQ1yxEs{fJ}ZOA)uPjdCZtKb6h0Q~8Z+0EsE zo_inI0=4?DJ5r1r{h*$;g4f>BP1m;EBfGkz3M z-0R5sZ~MXMK~HrO-|TZG`%x^HUG8?yk*BbzwB^U!@vp-xE*Q&P&1xTRElhHf)^Wj| zZ5h4U^Cs+}ff$OKDcXrAN?Lw;=(T5ep1;Mo;#-u3(u4QCw+et@a3bK!tb$G9bs#R* z0-#<%3es&>z(0RETy@i_6FkBC4m<+efnUMaf$0zkFu`NOZTHv&affT*yJPm?kGRqU z>jlTK=d3Io8c7D%+>~oaNzr%wlLpTS97LTlft$8g^O(V88Z&T$H71UhFT!YnACnxw zMLn#YJZ2X%%cmtydDHxBm%L7W&=btIZl6+_kw<>ORli@q7<1*1_FnP7yt>R;^wg;r z^4rGF?cJu4Pl^g(yYT_%+Eb%wP6@$@`pq|$j}P8#9`f$T$+(WTL?caSs=GEsX3N%$ zNAmeh7TjOy`LUEWtxYZ8!=`DDh=IIk`g4i3!_Qbd-6}+y?pt{Vow#9s*WAEBNo`o~ z)YrT7J_`f7X0_g`;r*H~y@DQXA-dD=CHLACV3=JJc~BCa#O%M_hSc}PA-DTK3!c#{L=5Vvp{dMdbFM;`&3+hu`U21Gzqxf)@^(0}nUk3QT|rRzuZ&BE6s7O(t+D z$HXU*g6Ec;vp}YR!dog5;KY< z#9ue&^9omSl}cQezQ~qtIUu?2WolDy(h)7vtWNl_6mQY=jx;Gm6FuIbDCynymGCz0%`n^U_sY-X3|N75j`{#%cRKHl1emD%}e?C() zd+a^V0itm1!>)~BPOpXI)(bzAN196~Umdk5wHse26h<(PZeQR9NFXk-XsyQBuPJ^*gF)xZV9^_OO zcD%R{9uZrmQSA+-)2^~Mp8c%<>GLt3#`?yh=R_01*Cq6OGNte2XVTGLyzG)h`_0q0 zGb2)dNuR57X~eyVtAbw()qLQVDS0kWVZirx#3;&gUU7+^{qKw4ZbL2qAh*LYUugF* z@sABDG-yOJG$D;&-t^$e=F>b^Dt72q+k*M`Ukx9uqK}}1-;;=p-TYfF9_<$h5~``s zC6c^XT%~z93UO85lHw7a1LIyFA*PLY^j=+?~ngx3_Yf(&L_f}MH>Yhmd0SEB@)@7;H+64 zuIZH#g3bQD{2>S2jHAaS&w;<t~FzLv-3H!8Cy^5OnXd>m`oh>{#BowgzU7v*-TCuvN`{q`YU z*-8UKrRtsbVt|XAwv<#33BjlJ3kSQb798d}ip-)7I(tYyX&oRbJ@2H9gr7>PAYiT*OYyzj*YM zoEk}9R%}zTkwsfy7P8-!0+1Zc;TT7Mt!a?IP=JpAbI-tCe>;>$lUIX37n0BXJD2ZC zt3()!q?(mXJh)n?Q1gCk1K!YpXN7&xSns2L)W0MCy*qN6dp!SYIp43S`Q!F_iN9?~mol2TBNJ2j z%#8;J z9-EJ2mu^ovW>|dzP|CtU=%d;}9az=G(wye2dn&cVvgwcMHJCq4x&8qo(G88N=nnzn=Y4VuX9 zu;6KhVB*VWxeh0+H#}pHywnUbO33far`VcLA#J3(a=SlyNNlFnFSD8pjK7&@TeGin zV<7!)S%&u%Q`3*WE;j#S#XvvSe*ER0NB&_T+nXO7j~Rde8&c~}#&M<0N)$&v+SDg- z0HNVK4Nt3;bY(FbRBxPpmzib_5gpd%@B4J8%rE zB$6D!YDH0F?aAcwSWJ#WEDp;H>7?PR2jG=&=h--m|HieB$ginfDS8p(EG=NG93}JJ z@~=|1a7u-?Z{+D!VefPt2ad2f}(43 z1#VyzP)ijawh=au>?*%Gq~7e|J5-b8)%E!wH6U_)-Ybj6HVU-P7XBZS}YL#j?YBK8`l|eq>X@5c=@db zy>)2|f4I)La1CGhm@1yos@s;4Kcc*l8B8*&V=G*H->!tp)+F`?iH@xhProike znOybG`H62|Pb#Hfb$t;#oO^4;@8n$eWIf44aJRL~ID9~X*=84^vjRjqlF4WS%ab{v z_T&&mAjO!!FAe*FJFi8|h+K^jB;Nv%3DU??vKq{aqJU6MGBr|7xXfZi4gk9mVC(I> z6d^zX=Q01Vdv-m}^t$-Tt(`|F>g>ZCJ`2k!H+ZXv96g_!9#B87Qhr`F-JS0gqQ6eF z%Sz~DYvajRH{i2}`ZBn=t3|u)*QHCY6+LNZ@oT;+_q!}?YxVbKjlWx?RVMRI+OBqZ zm$%A{ON=GeHOKD=mW3Sig=j2(**ozp<^hWWOihAJcF5j-Uk-dkGv<+<-XrXKJZeX653H^Bhwha=x~&)`?c)CA zqJ?+#)Hz;dA-@mOS=Fok0TQ^KrmVg6-;MZ>!FkA|H%&iXh9Q(5nvCV%njA`*5-uq$ zjp3DVf7Be7llknXDOb!$&;=qTS?IlO1oR)|-lOKmPa6tg%g?*7;<=7NeaU5BuASpY@X2Qc(tz=_@txc0q-d>VtvP-o%L;C>dc z6TKWVNJpaZ3dmLRBr-5{!O$%+1&;jCejmdvX-BcRpSJX?``L}(l12f_vA0^>)EQD# z&s-CS4>x!#@3%PwDhIG6^Z(8QTRoRPn<&P(_pW>(2J2(Efv(LA8M8Fw`>gD5*yihF z*jcbIe*0~RWP$RG*x=P29gVG+ptZ8b;~Ol&%mq1oCS3OE>rbsf{%65wevVC{{KE0a zEP-TVX$hj8zmFFV>iodv?GyHsgL-GJ_KLux#=4(;oWHN-ZqX+H@0HP#~69r>zFCuV zWm5-DgBo0px&M6B`*Y2|yndTA#%jY_q?w5Oe3xVSsoAKFXXy1aZa#F5mjGW#o848{;B0IjM;v|Y9quk27#7v2hkL8~j{Lb%DCWX`Zk$Ur%mgjP+Iws^ z&o4=aC8{pAJ#B(V@741k&FMXADKiTmZ|l$cl{_1}Zcs6|K{CBRr=r5AeZuomoLH~w znQS#3MNjY3QSP3_QmHCAH~&HIJA1M8sowHKx9=WMZS!y|ZNa;y+ngV=jL6bhBc^O) zMlI}N9$qx<$b0O#Gr zjkN$oFu;^-PVrGeSaBj~`zRGch?!syJx#S&>ZcCaccECYu0xd9_&87=UXuP7Z(z)d zXRwdqOO0}M7M}|HF^50=9Apy!CcfIZk}5y}vvQnyE*ZSxKIJ}eHmlP@`I?e+qoY_u zxVM(okg1Zsz45dJPnkJuE|Pt(#8xuRD%T*T{5&;D(Dh}eMqRu8gzb$Iq8?2v*meI| z#`SFJ#L91l*rWN{w=i@zwRa6PoGgM z85)r`ZYRjw;r5yOg)^ry3tach=Pm}s5DKwr`@~#nDyl6*zY@-5a`tUtqoZU~BL zvc|SNOpQyUrw0X?EIF6X<=r}eM)k#CclMlx+fwBvb0YCiD7%Zt6!%D!=wj@ev7w8-n`<5X!Nn{LS zG%>*hlthf-lTZMkp28jrIt-GKj}b1cp(hsefD<=ct1BWVMqD*I_-D=!Hb0uoXL2`K zY}bs9Zy;~x*%e|m9lqxc#8LadGddC#TvhM(F4hh0&sfS#Sb^Iy!hVaM-TeYC-~4yW zO7J=QNq-ueCEqY}^uQp}Za+y=l^?0vt%ye!`v{u8cwte?TnNc>dMv?(Z%aCA;~ zQ{*e%@4wV42r$n90hH(#02a+MN_h=dO+uiM#n~5oT0@XJNyni`@->!Ga<5P|z@G?A z@OJWOXY3$?QQ^P>bO5*l+6F)-02oYQW6IxkKUT}w3R`)b9K9K{o^W-qrt4jD(kA*d z_=Kr^;*#+Lc(US2W19bTqi|E!)zinV*xm;{q~mY zZ~eT3PS*4m)aeC3T()NmO|d80IK2JT)O3$&F7&>5_A5gHRF((XiIQ$pxKZYWJNB>- z@MJY;k*|+cVi!9V@<@B~uB9IUO%h?G~#eBfqp6Yx@{lnE3BGdu0 zC9YF{42 zsQvU0Ynm;}>f?kqiYGjOUc>dNP+dv>h`?ZNm{5e;B+v7@w!^lO%LaHu%bl~9vmG_^ zx?k6ieSL5)_ePviuh^X(7W1<^avBX&A|*K)*N>E6Tvt_J$cw-Eu8?vg_TdD=h7edb91>HnzwgZ_$3-M;xnb6r7pga4vj+4X9p!E6i``G)c0{`2mun*DFz z9X-ij%gTYu1QE#mT?nYqx~cDqvCsYEY!HfH*K(*R%$K}#+hreMyhS2}+@^ZihR z%y)My)#KspOa4h2YAqAuB6r3Q&Pf{mEsRi>tFM^5yw;5KjSG`37FVm%bur;Z-1Tyn zJXd?-``OgIvx3l^akKT!8gYA53kd_?YdZR+g=#@iTKvIJz`PK6S$pGf*eKzh$SlwWRc?K6|t~`EFdiz@l z-v89|*Of`Gm-Z`7X3M~_g^!F6>tRQ0(b>r29_(!R9F@W z*!?)V+XDIf9HH6>eg~#tM;SqfkTE`Z!j2EdXNoaKfW?mWNe zyi#mdpGRKK$j!xhNk2_ms%gmPQj=F(Sw69Ca;?BGv%Ktn{$GVMk1LZPl4NZwSX?6MFqV(N#%M1zMr+AW)au@RK3}N#rGUHT_ zJ1kcGXm(0DqInuv`;;To&8wA)vZ7|fB6GAN!(XIS0r1iEY(dW~$+ed;m)T0DdULzS$Q75*pR&(^fwZkW4J#`})1IVw6U|be{M>BjiPP6rKk0mB^>?jY zNp|$pr#4fVYgDGQy@BNbZP(B)UbF|bUp#XL*exPXG-OI?#d#UH3U{3js#Dh*NuA8Q z^mN~SKwVv3x0LbM>6wisVecdwMig!*Uc@{}x<^SBfN>M6^r@E61fYwsJ|uiH|7F#% z2gEs;ypOEysy1IGw`|p(6HoCII$XAB#MO%UA4!u5ZF?(PH+V$kq{)_$y^q=}nBC_= zLD8{eS-hzeI;z=X<~Mo@1L`Ihwtuq?i8HV$I*92U&GeL`BdJh3{s|VcG?f!?Pm~}( zL3MVUvzzy+Qe~$iC%D_3CInYTQ7AhX6qGJ75uys6P_4!iSwcV%pb`dXoSru>d{+%9 z7x5Q1#zgREls*0U>-GR1vP}BW5EZAE z;l_Cxw)QmiD!3nfo;zlEmT^Wn2heYMD%~Y}|_6mHFxOd9*@*-&& zb2;5t)iJX}7jFhey0g@3U#fU1cvVO1FJCCi`-YYU=j72(Hj~_SHW?W#{~))?`2%UE zS8!ls>iK@3D^w~%Ao;%?t36eYWHijakXOh!w5WRU@zvhDAcJ0Xx)Z^x)hKV!=9K2t zP-s_*2fy?{Z>MPPcCS7@HxU1zIcth$TM+dgp)@s8V{^^rz}ZjjOl7m0;rgnca$;r4 z1=15o&p*#r(ejELehlI}=!AHJep{t+cOu*NcJ92(|eZeE67FfCS@$oEA!Tu{?li%#q3jdWJH z{&_OROOrBaYZM0OHElj@?|di{z4-Td=mcLQE6?WTSGD1{4}aZu5oV>OYBOv?-|LL1Bt+WW)yd|ElkW$wm zA<=xJE+I7^1(lN}t&V~`SKKsWg)7Hq6)qZyc6NE@EOF}_oQ>~1^VjhDluc!7)nTo{ zr+TDMZn0Xg{Ccz|jIobL(N%yX`ak}@Khr+wG1HtL5Bk(Uz>1mdc3*iF&PMLv2O;QD za00!I=L+2%7EE7+Rt#$^Y78JDpXk}3YADQ=4ut@4>4O~{pcB?1OPp^W-^v?{dbl{Q ze7ppH1a&t^Q2q5A$7x4hW7p$6!pRN|msgr+p4`fkUpzN0RG9s=|;xDBXQwrJI-VEAJXyT4P zbz%JM#HIE;lwX}eX>(50Pl~nRTGguwJHO7|7i{8)3x>Zr-#Wb3s|^l49;+jqoSP$#`BuBwnfyiw{Gr;PLb+ zJU7`9Afpp+tlN4Nc&YF~_4d1$Xzbc~PX)@2HqMVRr2r^cr9<1br=187I9SwQk08S8HX?K@xhM&J@kmtTn0 z%jf^`4h~RWKfVYV%25$s@J%j1FEO zyYlySZj3}AVFjSU=sXxUtTc_AV2_bmxlOY%dMKE$dQ9kZLI}padWVK1jnmX9&oDwO zk1!DO8QRv{QCc`~rV>Lp0g;FS=5R1M0aoFcXz^b4E|xXpmg#2J-HpblqFbt@-O^k) zCA1tuFZxcIn?rT6 zDAMW6iO*TwXW6UMy9CorIFc7*ig`|QzZkig-GHnp{qLe#EXPY&8;xK17)9pNo5~p5 zdhr$qBfE|UPjG8A!R?f>#Y5>Twx&27T>2_GPrHsgeI)9N5zDm8b$;>C1pL(1CXITn z$Yj5XN2GxO!-i2kKi)k-yGp?Rl5iSFxaL2EQ9(Yy3aAt0#5`c58mbOSLJT0fMRtxT zn>u>pnBK_Q8<1{MS?I!rVGv0v1+*{Ss7TmsM#|l?pY)Ee+8>`WHVp^c*pj$T8+;e};#qB~Rrsvo1vnTg&4{e|2A@8pn#$2uo(iMvc0Kx%1 zuoQrUY9At7@%ea`Gkx$o6~zS0I)cXIYOl8q}FcsJ}uUKlK0wBDO=V!B0Up%ZlX1MYbHgY zM!YChxbm?x>dyGZr=f!DpnW-6z|y- z$0TXk@x3$czdWuXeroCI(l7amXBC)#LGBYrF^;Im%gn2;%@`o0KSS^ldHZ79Pf9@k zO6f%PN-Qs|TAjh54WgKbX`S7#j8y)GFFKZOT?&3C3K8T$2Y9K~S#->ri%3yrl6~^G zJR){>-@$6LMN*TB3*`NV9eS>p8yCkOvzIiIEUSXj^w)rQJ)p98QspN^PL;S>@?cS$!OH`8{>Qz2b z3_p>^`RwDK?G|T0N&>qso2Trn7J2JM?X!1WR|S>Xww6|{KTb@{v!`3;o2eT&wmF1Q zN~>aHq}e32RdA;DeD)8w%$}H?C|e7jC|qn<3Vq*dVks!4V|P+0z|jhko+jyWW_jd} zjjp)~4DopP1mmZD@YgLr$!CmX%zD<4dH=nF{0&EflPA zmJGoiFFbZ6=`(lBaBAPpM)A!T=+*d7H|@BSetm`&*(P}oW{#OQ^-RAG68|C)Enj}8 z>r!_%*+2RjV}^m?T_3-5UYXC$C3JNC&`=aG2azC+pnb?N1P^P3S|ZJn)k3m--7fIJ zPWX*a6TT9^iFo{x<5EoiQYz2{l^E|LS>AgVq$kja z+zhwa(u(MCJJu;k3u`bzjt^h0YWQnbi_rKjuf#+$*&)uUcBJ%WoG%*Fw@jN`kc{{mZhh(ih<^( z7jL%N%H7-5J~mfi1ws_|H%H_R7G#@!S8mz#2;$6Gh=EWWW2SzoRZLy zCc(!@ofP|5$o-q9CV@fZ6*gJGk~;}#puxQsFpBxPn1b493@hm_O>jk@rc9Qnaa0@7 zxTcNL7bFf@f2>``7&>mS!HGGXW22>|3f~3q z|0+L;&R;LlI$AE)?uRZVkB`eNG~6)>sqnRkKa><;)F0^Iw@uG}Kgn?2^U7WWbW9wQ z!}y?29Z}n6lL&W^Yt8V~pX<^w`jJu{nb;&2bmynB+s0-c8VbP6$xAynM;Cnm^nFr6!6a@&Aui&-%xbX=I zg=zy!Z#6j}FYzUP1$c`-eY`6~1TSsmif5sKRDFj^@$F0^i2z-M2Z*|Y2A1A9hLJ(j zyZ2;zJ#0OFfy@4PdcPiVIgN;y*KWdOe-bT~5G}Z5ImGwJcg?$|Wi$ zQeSD>?l(84+jL#*e!y2_1vv-xxAw{KttUUn?d?ZtZ@j$jwOihKJbXsj=SNLigCj`q z+nDJgolYWSYl&kDTxVBpn`3omA%4N$|!-m;w z-ZQ>39!5`|is$70IFR$!oCZbs2Uc6g7?UIjLDuc+Ji>!1eiV9GfG4T&!m8}Y$>zsn zFK>TO<}sDWQ<>>CBPC!)M-Ee^#JIl5l@{N3mEqkV`tg#QQnEtCW6`Ov`%1!h>U(|( zw$rMH^&qd+F01qd%h59x!zNEgVjdla}t%Y;$HCg5Dg6DY13DwuwP z3T-nZy42(oW!t2W1GLE@qj8{yf~~(M!Geb7-cF`ICe-j={3~+zff5aTb@BAK=AJZf z&7*-jq@dFtwdvZPR<0p5YjG#j!p-f+!`2o>kAcmg?pvimfPSa}QhVavn4_*Pu(lzm z(yS{h5ES9+e3%ap=dRt-PV9_XLJBRl8h zYOr?-4g63$zibxIe?Q!4s<(cV_~^;%N55Ma-dFlb#gyseeH!mdx5i@t3%o3d11~;8$FmT+xPdql z6dymYqt8{__OGzX0L;I>nv1j zfgA`V4SoLo%_R4+gl3+5+3YgNi?pv2{Z0Ix`T5v-Jzr>;BNPp{tT+7@iu(rQ&rU;% zAs!EkppoT&&cAN>f0TnK5+4-&_HjCG)-4+<73Ag>wSBSvHz*vtg6w3)O1ZAmXIGRs z;~Bf*7w&n;)VAdlVdrn2x5ESOJz^fs9%1v*!NSO-O z>h2Jeo7Vs0T^cX|IDs62O-LfB0;Ue0LJhFIl$*f?#&jMZJgSZa_})P*d8MEzygCrT zF$1Q<3wR4%W>`ZY?N~JmT{{U-+NJ@_Q{V_3m8U-hO*en74u8sNlO|n&7cqS4udih3 z>|S4(vYI~f*4niJco_WbfueQLL-WsHOG^XpcJ#l`e?2E!- z+Z#vqJpDZax?VsnYpSdJR}HB7ehJGZ@d}eEczs>o#MD z`4B}7cIPz{Ua2R7yxDi+Y!$ED( zM(_yhFm#)@5k)&XY%Xa#oat&itV9nWS@N6n4jH4_9a-#jH1H=JCGfF$UoZs3#f}D` z8po3@^X{nKQ)@XOTIU}PZPAols{D~^)ZQVd*h>wg}apK|7dPaiA)7vm_)jF@O3k{IxVpZF1U(?d+hA=!X&#K-NXjNnT*kTcU;+os2NFkSG;aF{8E+u00 zV(Pt_xnEc280DS+u3XxL;;TgM-e-+$#t&qfTS<7481r4~hX?tVWjkg{{kwyV zgHHpL;&A_Bln5%Rab$3AC}abU6G6>G4}H0r@jwAqi;!X^=i(e<=tnOoTK!y4;N7nv+%)I~7;;peFse-;J1pHka=m^qe$MtL5;;|v2!p5!T^ z4DKR4Hm=oAuB6KRX|1M7L`5S8*I=-oX{hAkc=H!0%-|)TvkHV+Don(GXHR((I`4HyR^aBu>=*>8xeSYssaNuD>C2)pf zLpam?)4d(xQEU0{1GPXcCOe8gVg$2={FvGFM?E3=FS3VCakLm{um-(o=b2osHIx=n zFuf3I^-)6iFsHOI&f5Ik@=$U}>i*{&z~h_2j(g7*yZn6;w=k+U&csS_R2S>~J0H0J z_lw)$1%sv?Sfl}q^9>$7frwaHJH!n>ewq;mK>%;PnaLnU6d!#quWO+~y1oh6!f*K_n4Mn+MRJO^gE2xua^Kz|G${ z2<<*jKr{2!>uVUwj#7Y1RkA?o0a7?RKoS&+9)YqVU0D`N^k7R8w2&vhO@S~MpU%O6 zd4?IL_B}X_Urd5wjb(^ISiDGSX|fPwQ`gwkLZwFu(eazTZTvc9x9_D7bpdpZI@SHFiN^&{MFW^DB?WrE^?b#Rd;#&&thixBS1?grqW``{A(Q2mLufWn%P%N zSnZreor=$WO@0Gh7mjk$*5KFe26R_RY{0E^ZnOFa!1v^HD;9B9F-r@Ja`w+$)*iNm z)~*QzF&@S0c&F@kKa=LEzW7FE#4mNx{=2N4elx_M_KZJT4`HRoCn4@Op?-C)d-Q0K zD>eP=UIlRmfW@5-r^!AYZb`*i2%1Yo+E>#P)vwAMT$7RS96{Twae~;r7IzD+`!4M+ zrS0QCcq|p|znbXln5S5sR95bu{Nb<^knu~Gv8X)+yRKrVKU(~K-O8|ooR%erMJkLW zSPEk^tfG`pD#x}A0dor^rvqgGEM6Mqi;o2VHlqjwA;1feVEYSFE0TXZ*?2D6cf9Qk z0K!IqG#k>pvY(A7o*Pi6khit^empa-8*z*8D}{Yqww@vS+8akb`PypHRgn#b*n+4vbRbcn?#*s1Yv~ zm#%V6u<^wt81&hS z)vQCFWMt+@T{hE=9BHU`nK2>5LpW=#RuToSqEa_XN)md&IstP%{pGTM>RvRrI8^V3 zr5A|G9rj+jEqnBMJQl?QA>Kg!z}KJ$CN}V~ey9OGDj|F-K`s6+P|SLTzt)zB4+x z#Mvd8M<-L}k7u;&geQ24P-Nwt*#-nnmcE8VFZSrc{pOGUxE_tZvn-B4p)w9n<07A? zBeTVko86Lk^u|sT9%$9-2PU;M=_Ji#cvL#YHmFp41ovFNL#&Poe@yezrcMhoy3D^C>)f6zK4uUXrT{e? z2`8G?p?w$aKn0ta{m7M{Aj5bsEsV{ z{Jz)Sr{AZ@M%Is~j4i#9uTCs*M_IWjEVH&$q(-#rEP3_&f;9H$1)m2P_|mP-(rBhS z>rx|!=sb0!a`p3*{_~0jZD>5RJ2iu@_KYsAAX`h_!*;fZt@6U|L-mjH(6% znUUpk=;qEYg4qC35a!$1js*VZc(`=ZX4Fie+o+;w`ixEOYsN>Orih)Z9|F88!SvcO z9opHG3GpQY9GaJuIlnPw7NFeX5G#}-D~oRl5>WefxrV5+pV*u?alf&kzUI0_))aQ|qcoQ!tvBnXgB!r+Bm&-YawRL}i#)R^4! z`|3*a1vFpm)Y9tHV*l^4mB7+#+|)_`89<4am{DIwx}k(DGj(hW@; zep>tTz?)cmfK%w$pt)!TC4wvyE>36@aY&rm79xUx(FYowWrY&XO7B%DJ3iZBs?m{Y zy_(?_d!F{43D2BH#U}&ot)8EzuK4_INKbd|7y-U}%>OqW1nONmW^EfDmG3EW__VTBnG z{Da&+)wg8p7X**okY0v&PD2tst-%hLrV)ZUYP6La{S>PO#r2h@e$M}qTHD^NdsukC zdU<^{&O{(>y zpGN;@A58t{UCD{~>>tq+2PD%@=BeX?MnC)Ow%hP_E>S3Z0Rp)4$+}HelG+n+YJs@holNJZz&d$C;XN(9=;DM#6uluwx=cYv{%U_5*aprBV#5dBG2i z0U6lTjhLrBTpSt3%`Tnz{%9wZ7{Q({x+d3*RE)|!L3oq1)>oT7clzR{5%NT&wnK<- z@|&C~_`TFSzdJ}p-=;`f|G18)Zcpw&&6y18^TnQD@=;p}dDNzeIEs+nRr6|69OO3L ztyQy_Fg@!2oXZ%p%BcRpScXc;SFn5ZyqoY9i}5H0Kp#(gdm6%jyWV%WwRmX&O%KEn zqsGoO72Z&RpN|f!Pm5@=4F%^=uxaTJ=nKzqn|V%{F(6pr!4xFI`%Df5c&e_t(}1ot z;tY8MZJmo+&u|*nE&Cl}ofY!tjJ)}%D1IYwJw> z<50UcGDAtTWaJS0L9G1gg$YzvmwR|^`>ix)B6(3-n3)z`v$Hb~)a=C3}6S0IL>@eXs5@^>xa?z50LJr7E;eHFGP(Inv(@gJP?C9?Y$2 zBUjALjM-Iq1{m$R_`}H$B;p0&!(Q{=^J<^#h<}LYOGPy^XY1rE#V-+?Uy5msH7Z=^ z7>%Dc-TY#o(%&pq{}QI13W*~il_P6IR=S)(%%;(wqL8*=KaD*rsUEW%WWcM0%_*4hPL5@n{%{Rf<{ zA7MihWX{gN-GKr*dLgw9Y!G@bxtKyb9~$Ed61@& zx_DlVNbYPZ%Y|TOF+Hn-zLlD2fY-TH_gT(FLx;z{U*TZfk$#O;^lHL@&>`Ujrou^e zQbVJPZ zs!Aos#3g&1KL{*-qo$Gm3DaQ1wT(z=gkSj`vi|}|~=BWcoWy)COOfzp|l?SZ$AW89Ybe#Z^ z`IzYpSy9w3Y5(_Gt+`)*3+nzowLGdSM<2*Q)z9V!EcMcps8M1 zw@_u-j+eO`3??Y{Nb@Fn%PxsWUiMs2UQ-~QFJK^Flny#dJ*7X1PqDUEn({^R00hVY z0d4|}dk<$50+$8qJ*kMf$ceeC+}!Ag;Z{45J#%nbE)(tKld_qJw6{w6Uw1luIC4Q| ztpc&}b>=;$grp4Ss429)j17Na&lX9?lxJvARulA#L}@0wjPDeL-oBB7-G|i(*;|*J zMyyXuCxkzG1TOLN(Z=TaL8})q8o43gzF}FJGYzu7DzB;DtHid?JcNYIZM}(7S)1&) z+fTbIv-D-1ukh=G^Hy6Q`^dF#HL$x+ZG@(eZ8(f_-_7=!@JiGk9G*5+`D8s^7Nlee z*r;K63t8Iu+D*zygxfKHphg`kNp_@rODGWEM#*0gX8TL6nboL6&>ZmGe^pNVH0z4T zygxC{z^pp$)-9$<$&5k!2X*imh)#%8;{Bpw)2WTc&%pDE!`<^Y~8MxcS18BnAs4LkS2GgDPi*M3X64?cCFp z;?1XrVA^9yZG4B*V90a)#|^yMK{HW`M{&xfHmLh`7`w|EUNd5(Y+9mLTc5Mrq|e z-LMkLRf+Su7fb4E5e$oQ`YkrkLrjRV z8AX)%9dGjmR#`{;*gp+leCStM#-tIgos=NJz-Z{_Hj-vRw@@pKO3hnpV^zuDPfn$2 zUZ*!U3ASh!b+hp8N+UI^RB9nKrkMCUk!Lw?Y%lHA=3~t7cDM4WHxSQ-bRhPoj`#GI zI3#|hvEpyhn>oIx9R&5nF|t;uyWBK$dX(}55-jDX~kQC;)2i1k^ z>tC4XxJio(@iarcIkb=2eaiIRxw>l82}$10)LRKoc4`GTfgg;oEQ;&BrDf*W^f<$6 zen(^|)V0QP*1lKl$QjSo2Nk>x2%-%Nx&quD z>whpftuQ31yoH=al=kH<!ikI$z_+- z%3*hjwrRP&DxC_4~iCcj%G7cC5lYcI?E{@C&T`kM2oce0y=Q~kYltL}r$_F~PwsK^W1W(OBh zUeU`==G)LqJ_Y(w>8X(`38J47oyBbyf1Fy~%|19Pqro40CG2h3wz`C&)%|MrZ%ymW z`4&kuQ(|i{ z+WG6VQn;6G(kMq#lrqOtNY2tVk@*KjB9=W4+^VD|f*WyxfC6DeLxBbl z42y9}3ku0|pc>KW(+X139F+nnd~F)#!lPgnlO4%KPk*9|%~pd8{cquQn7OQEu1ug7 zxnaM10P-CQuy4A5a_?GrAV$D7{&r$55IbEo(ADvs6h`~JWc;`L%1buAeJ{VqMp@Th zU5QJVg=Xg`Vk$=<2k zlT_85o(N&}l)=ep_}jSOaau{O?DC}_SLXJ5Z3M?C(U9(?UZx`rVKl)D^y~V+5CaDs z6nZ0UD+_l`e(G6`WXoXL6Cs0aq0=P5%K;P}Vqs2f^b|4(OjWZ$$jt6{3pWIYXpYS< zKCq^aYcr`h7EEC2IRsCe1h8 zF@kzNd=&U1*LS_{5^-%OkG5&1lUNoEy*c5P6D%rZ*TqF^ zKXV1uHM{Ox@m_Yn&X<;aEVH%cSFPoxC#faG1GoUp-3ojUQ9p>FGN7gu(j1l?=a9GK zP0(n7BB~Mw%^yP%)3IoF549{<4J|ML05BISBBo*ILbZGd>i~f^O90r&l8xVj*j2zH z<(#tOE@dcNyfNTNW+i=RYaAP397u_#iO3kO!bh-Jm6lcvLIzQ2m+y;EnA4??cEWa+ zWS>scjA0V*9wZY>@xjy$|HZ(IJ*yN(s{BT;MwTI{C-WiHQ-rTj8f))aKV(DuX#4D0? zEapS=6ugA=cC47=)hapHT|b9(qgpTGjgli*9HwQpYn|`xJ%o3?ViVqzaI|)*J z<=xlUyoPIcoc0hJp&4<+gZZh3q+wSR`$cZWcM4joMP)^(%h3WE6~)x_D?_ce^~p&I zSwq!_*NN%YKuY~8=E%}!3o29-Kov^X+=+sk0kya`6wZJQ1Kc%q3uKi(SC_E|X(o3! ze?(Wi3dY)<5e4WZ&>vuwrOoomWyg5~@^A|e^Kz4V9L`^S{UFaN8pEp`ngl6^ClkR@%*J6!Slbr;FEtES(_t4w#^h`JRX*VLluxq;aox=@yGU_zxE zRMrGUj!FXy04bpl5c(9sUT?<LnvNHQu7)|_^Ewr?%4xB5VExwkEbUXP7894p9IcmQI;M!mpnOrDa4&x0GkNm-T&V)*!51ppwru=)u- z&wBF84L7_m!^0tNx!lEBt7q=Ma0m=p{K;4-7T%@C#EN!8Ba zPdPv6Qk{ER_n4Kw7K-6 zPb+S2F%UKPj|4B#YpXg`#=NTWayM6XK)=L9+BDM0A~V}8noVn&htDOfR5Q$hRT&vB zjTT;S3(lKTqea+S!&|MoUkFiIFUYb>tgr?}OTDl>*i-kr`4c`J6(fGP$Mh$!9RQ`k zqiObQ&C58y(mB*7l|p?4cds!AFbi&`Qc@Xs669Vm#`$z9?JR(j2<;{`m^fJ1KR=HB zdysS|%tC+j@WBfmBkHEcrT3eMvxzW+)1krH#o6%KJzG(o z!EMcI7F3%?lswu}icE`;ssz2bZi6Th8a98zcEBS^INfaGCx3jr++$okY@r%fL&>hD zcHi;@79aku9K0pOle%)elUAun(4do4k&!T^-OeB?8-V4|Hvb<-a)@Q@c6dQ|Tjbo* z^+`#zpbXCL=aLZu^Ja~ez}k##%cUWQ@7&Hy)SqRpt1@`*UVJC|=H~Hww0h`#-_V#@ z{{SAr$|5vgo$mKwju|4P0W78bMuCY3O6b2|Ww&8&q(bO4wloE&2`cU?pm~reVR1wRSXg27HnI=g< z44_yL3zmzc`s^C`btr%qruy_5;pW@tu)zzWYp`>j)%XQ_TWWz*q$m%U#g}H{J9;&F zyg`jALPdG$frfmwav^JN*vO~sM7_`V)51z8citvj@*i#5r1{=+@G}?6F4VO=G%I)Y zR!BdbOA%zAVmbRU84eXAF1@=xcWuzJl)3||V>uiim--;azeE~xl6)+zh?7poGxO#pG}zFw(uP-x z$y2|*&(eA)H8|`v3SP(X06mK!G|#qwU+PYy+3!(Y@Y^g`Iq3z4A_)J_;L9=OkE!6WLM{9keDoJN3N{*3{H>NJ~vyW^=t%8@%tw+hOw=!#Tcgcg{brnsQ zuWGWAjitIb?G=dLTuW@)S8K{kXy{LWz$MV@jKjEr^93XtKfW3nm0YvEi;-ARK<}3q zt{RIDwaGddlo-jsS9S5)BKu}3~DMnaR@& zLB-YF-sHrS6>G%%(rKWQiR@7cK!bP`&v^pWP(Ub;*`51ts59z`|^;HrL-ZSy+5~Up^A@^ zdIFIH-uNIYKviv{+8+xn%QIbYnorE&O@$OrP9mu29u2PaO=v&;WA`g{6|qk8dEwPV zS|6RA94;gCXKIq-X|%^aCvY#avxG4QMhi)>9$iiSt}n^ctSj#397!%f2f#(5(TadT z0+{7v%v_8UxAaR>0i%apW@V&wnVB+^!KAHN@;VrQI}#!M-Dwih^pU1r``CY^8Iiug ztA3eos;;*5sAfnZ{MCKW*Q8JdS!W`av@SkZcRY$mhD$(BwtMoYY*e8~2G9z&OBzNL z>;(&@_W;f%fIx?ttEspcAO=d9f?{YUBFMI22t)U2}uqp3!@$WN6Xm@;}`|C z1fX92C*s#hzz1Sn3^kO`6ZW^{&W9!HxpUuq(KKJ#_5Z2q%3d;FF^{~OXT7@YIUiyY zO66^n%v4|SL?3U3?3R4$d+|Z{YmB5MQ`U#r-wY4tqPW&oqc!S;V&z|%PZ!XC%lkmK zVSN@PyCWBR`~a(qcv#|{wRSg|S1XBzH7kRCtC74heM5m>u{Kit_ zC%Hle#8u2jQJiu>iK7O9BC6rx>{tx3=B-nqms?+vE%a4_ ziEw{xt=9H{_?$mdJ#+6K%1zi!Rrs*FaF($^p+Ah}sc#1k8U0dZH$}^EaeP4&Zg#ET zixDl%FBFk+u!=H|AJ|-YpsfFvA=!jKH9*jFD0le24tSuIql}w9E*!{wscTVk8^_~jmC)t zd4-^={wLHC$pi6^Iq{SfFlPNrCmj+_nrs#}fqni5nF6GiUAhJ<|yj z&&9y?lcz0ch=RK>l_HJzC)y&8ybz@(>Hz|-hwfi~PA2m@y>Xd- ztRpl})>~x1Ye&LFYQ7yWo-oqC`&8fgv5o_OFvyd{Z0d~uM zdp*UYe@Ia@-Afs{XC=`h``wnKiiNw3N7EvNB|}PL+Fc_mDkqnfZ03XLD&1W+Lrc5h zoUf`THj3N7bSEyh88Wp)EjscX2W>d4%h*+7Y&7kjmxi5Sk3XxN=6F8j1|uVA+$z{N?3gQ>8NctMxaxWN`HyhH1P`6% z%l|(7Xe0lwLGoI%`lj%8M$WF6Vel8aX~tI>^kepX^2U)J*t^8k6;ysH;XUdqw6fD1 z4^rux5y+V5-ntxS&%Z1QQWue6-pdo*b%o>?i^-t>$k9BJ0`s7IVs=0>7jtsTJ? z4k5gT6ZN6Wk^*8*H?_B7t5*$oYjb%(6`plUCKk$`0DyJ2+-?G_i?u}9g0~ExgD?wM zXr_+EX0Ww0athjirAX)LZpOC`vV!lp^F&efMdi?l?$!Gm^v?cg%9CdA9qYo(eeGa& zP;40*M47?#*RZIlgdq7xr2){j&O+ib5KfE~0wj}E`nw?+lifb!0~DA>d8&AjlaM{| zpgvN95{0@WrOe$8=3)g-B!SK4Bse6DY3HNbY#N55bHj~Fl}n&(?!5 z=Q6|RVUv}$9zU|P@Dj93L#`&Ayic2~}-)s4h z<%Lh7CtZUy2^yIFCK`Ho$eC*Nb@h=Gf9AIW43B9p&4TJb-Ex1Y5kPVgfb-T`JPL{z zB1Vwn8wc=`)8iY{tCgwc8D@Y&{n>c5!(p zX{;lLH**S#ZC)+U)5u%;aJd;n5~a2)`UR4^+eCEJnP30eoragfRG z1L~2fqv9*$hoQkuM#wy z26|47lO@5yMVX&vLS@;3aC(673wcZ!&&=!wBNe+AeQ0E7?uxgeoR$X1Y)X{@LRg7P z8(td$sCs{B3La1Mx;>!17+dS2oIua&c7|@}*C$;}euEBM3KuP<5{nC^uiMiIZAtCy zY*v3*ex!z)aG>9{r6)J^cN3l){H*8Lf&M}6fb^TOUBAq_d$3c>%57s6 zfPV2jF^xz8`idX{Y*?YCufi!K)G7WY908Fr=Zm#EXdY4i&EfDI1y#NFSf`hAw6E^^-94?I|KAe=2!TvqB^USC#G!FRIU?;>iqCLG*d3cQY$;$R2 zUj+eAP#A!6VJU~hNlEL8P&V#yrrxx0T{4gW%g>im#oL^sLFwCRTjZ<(vnEtD75Bp} zIR0$y(K87?jA;xrC`i@(tLkfBF@uI}TQ|~EjM|IquCcO$*54V{SRtk}?=jl7A5Ok& zDGB-QwQl^nx}YF_B=_Ju!LqHH(-Q1TIf%!B*L-D$h-FNfx`a*6=yoANFTF+^Rch9) z%8*9Lb#C3&!q6NOWp6dVYPZT*aO}Th^!mo51OVXFY|~50%rro-x-vG2f~XVl>O$>s-BoBSFo_b1~yolH-eI|6_GZnMcED z(}3pj9g#^IY>`lG5P{eb;#NQHc6TT#UhQK8Ldde2p1+CH*Z1j)DT7Jz$BEj!Bq0r& zFXKQdfW`Ah4BUT05VPhy5|IBVGFuXEo3rmALp5@4aE(8lp{?}Mh#6b=(x^Ek+`;-Fd?fAf) zamxg&W@+SzKAOm*>@6c5#d!Noh*CQ2d#D9+d)8VzWE;R>#(#AE1kW7m$ycJik7fT^zDV;M>sJC z7fw|he9kBQH2%)0g(jECr3E?q!&UkYPw+9ttu6m(Owj`;T%!+C`Q5rMHD<5ikO{Go~wddh~f;%aJ5v#gl@r&<95#nk*L1FoPBNqb>8fFGw5uV zbP3(LHmU$@r769Mb{BlEf>z`S-JZs*sb>n$k@eAeBO0qGc?Flo$F%Fl7bWXCx@6jV zCP=M&z!-A|dJBUsX}MiiML=Pm9ZIfL#eJiK}GW;;2y&cpELBIei4o7eB&89EcNgQcqptd-T< zv@EX8lw=udARSGx5YjxN%}AIe*bShD`&4G1JjZBA0-MK=2Yfi zVAPcFXW3~dV6YdyBg-`<%blxreLY?+Ym{LB8m|%nL63kX434+_I1f9abwKx4qcrki83+fTmS`*DcF@ZjPd?7jUc?hR!p(B3Vjd03&;gSFYXpxN?M zfB8t|W1vs#uAqGZ;zo2=J!@Ztafx4<{Ecb%>n%it*c^n=*KQa+y(Dz-divW@z=ndw zv;f>p0_;szt&J2SAB1p9Q;~JVFHiuJ1d2hzY~>s18?%Bh<0U#qVwmJ2lpzYN6S(glzudn-gv+1Flas!Enasd@rJ-ukc}}RMRTbwaI)#8)Xzy5-m=dAT3&ouIBw6WG9K0Ek}he@Oy9r9|} z`({3g@`~!^e>-19pAy{u>`Y>uubQ z4@~9W=Wjxi2mIPN7Xv8VV?N%5UCpxhzhp@^b`*a+I_Ho_y2-3z7fklP!%yhOW62|r zwv#=Su@Xm0qO0%vaqltqX#P zbla@Y+0@Go`$b%8UFqYwara+Z&?a8ybai$|Xb;J9uj`2N)p*E%P?1(*d&kYdr3#l) zHq85Fm{sUsXF29KwyGtVx}8?W(wJ<1GwGc7R@95rBXVdl2L>ZfPLP$fnBd3#v~joL zmT_51<4O&DOVHXAvF3eHQx~fnjUU>26BFL3DZ0Q9*$6Ov4@u$|J$ddPKJVy`)DDK= z(>OvWIjeHrkqrhM&Er<S$-Uiha)J2zncqOz|AIEchYvlfq1a{<}wI#cOh}Q)pVi`sP)vrSMvvnvVsTqtB>|-Wu0o8i}Z>Eobho<>d`%A9%;i%xj$QCJ+4)%4MwztmK&oFZ61eb zN$Pf6_#dU51WUj0@o-YNe0|T`hW}_KxqR|D)-+e@{np}J$%z6&f!&0`p?vYye$C|6 z44r}cM@c+e?(N){?V%KUsNevCM_C^W7_*(OiFk)zViq0-Znvr{7vH0Z znvDy-aQBuso;B6UUf&GLT^4#Ik|yZOLDXcYd?2&Dp8H|Y@q7fN8LXA%C^5|cK8b`$RY zzWlgrWV9j0V2+B8--MfNXpFW2^KvAjz3e^IGLnsxnT8TGXihH_AIqU-Oh6(KuF|O= zy%_}$l@*sjW~0onj7-z^HEDLu(jIg^w5?ZCV=sZy5q3Wb_y@W3tZ$vSRqjJnhx?Sz zm8W+*GegWVHSGJqf8vh@stSH^k19~#OaPznI4WBiQsoxK4+&Sn0|Du7 zCFw9HvIW5h%5q$kmc$T32&yR_V+R5ZK&BA@ZC|5t6!6zW0j|}FkGl2z-CYhoqk_Ws z1tW|_@{K;OxP(7(qxg0y2YMd-d+ECkld#qI;zih{X6>XGcYvv%y_;E#v=~>OS-SR; z(Xjgf>GR0!o{1<0OkMPgBI-A0nJn&nGM>Y=jp>JPENHeDHYsHUe1z5|aJ2^Dd5)~c z$nj-?3Vn)AZ-}^V+O- zdno5QSjC43-Opng=AfD zL{kdC%?Z?=In@_xPSVrXS+(R)M?dujgJvaU(Q=dSclH&1ad=$M&!VevV z8#K+&R}Fh!3#?m8*sVXLXtaU07>UY>&2sa6xw-jvOF^9fgWLg1*WXn+Z~7h_nKt%= zlb3})Ddt)-P#|6<57`kwf%^pq&{h(8I3OGA&8IG8teA{|T^ABDl|On3i zVEcw9dtD&j*9EhiS*%F27go-V8(cLR9Ww}}e3d0#JG7W}oJlCz+5-NX~uXS1&u*d~U@7mt!rP|#-lb4im zoHq$L%DdJ~2%$9(kl@-OHn?v!kpDB7k*%s0JZzFO&njv>r;}?Ve@VviSbd^IEU$3z zqxe7t+YZtAMmFDM(q^S+t5aoWQeMv{D#Co$x+~^Vg;6%NV@r87rxaJH}1B{!}vnkavZtqXk8#2G0&xs^ZLt7 z?z!kuC9yr_e|C!H=5+nLW=B)#1OO0{ob2Md-^w`YBm5m1J9E|UY}@wvn+C7SpG%6A zJa85mz5CyK_XjDWgnZ0gtqei**e^&>NurMo*AZX2{>U&B!}_>}SzUIro+|YJIJye9 zDBCVbvvhZN!_pzS z+9Fiq0R@X=?TIuVwt4IIQ4gxHZk^Z9S4w~cU99xlR`$3-#z8dzglL#?q6e1r57a!w zh%$jBKf?z)hN3#vI~sLDka1$2xONgVk0_2TV;TAMmA-D}Jjq#7-2C}LFly#2PBb6w zK*ae$S~}?+5Kol|*IyK$mJn@^EhKZsLY$Q;UqT2J1)!G>JKIKhhfz*9QPCG%fShR| z)!H&%Es0c8blPK>;Oe-iMDSJ=3V4(p;-_AZcIRDBpC-}UQ28I^p2LlyW|E07Z#fqY z$Vw-95dQvd$k+$jmmbH#J67_7z*x7ei{AIMA1%m37lLE_LUxC`ega%F%waE zs+v|0W?$sMMHj77rJt2_xb3Tt9_xbIij3cWe*TM$B=ob2$p(1>shS$+7!5_u&OSs% zVg${ObKO==&z>-dm5dUbgi0KlG6)|l80!ORj0Gt$Te!ryy4gMAQI}mUExZA2sT%krtU#$ zf6*^UN*ZY~EzIH;0Dt;~GfI@kI-4|BO)OK1KQu9)m{P!lo}1fkk4Us`Pma^La3B#s z7`8nr;Z&sMA|Rd3kKbn+)t5ta1KJGKFhKI+yCL#Djwnc^Hg+#6VQ|MWr7&g@CheN; zk}%0G6hNs|4H6V>a-ULIUXW{H2jz8gu>Ck_K2k3*dW&ub977Mlg}`hNsnHdau<>R! zW@EWZO(@70De2>!*=^znMdkVU1g-LAit$CciIv&Gu;%^$7Pn~2URP-fO8p{aXudpz zGo=b~X;_|BtRyfzEpgpVU#;bvt(ppQxycw^@y`sG~dek9fFa2VqlG)!IE zJ))35hk_rD{Mzsw@&V-oMQxGE_BY%QQ*~~ETJkdp-_I>2d4V6@=XxEp@1qf|HGE6% z0m}xGp1yd~`TmY}uXJw@E&SbPM5DJun9Nx+;FIAL{Dt15x$^LT$H{3CRtp#--ehPR z>L@>iGdT^|6z|f@RV=fo1cUC(|=g3<3-pc0TSHak{xoGS`@Nw$3a&E7i zfXP(J&CHftM~f@;_TGWS#3cjyYfnJdC5jv#^B13#HyyU1=)aYV6#l!G0a?F4b4*@Y^9ZdcP8?<>+@)GaeH+k6ZR= zoJ5A4_(D^pNH5J8Kmf)F%C#wQJzAeUjZBCrXbcv~mo&>r0rZMn2vj6SrF`T3+)`!? zB-ps#;e05ik^E4qQeU8snI*(rhag6xFe$DJLIscLR^{kPSR;r+t6xO16{fJv>W zh>|B~lc#;iaa}xqLidJQo({du5U6a(a!qZ7=vE8w7dmzdYg6`rBzG8oUt}iT_Q|$c zS3?2`6&0Jh!TE=P_wiIMueCy2Nwcdnx689d6Z8I?{2EQ$+c!!CPpd1B&Ipy506TrM zDaH4jNV{6r7MXY7JkWs4nT+b65pKRl4wMS~k$(&?5r{0+g2^X!L&+LDJ+6u|YD6Vj zZ4!HQEEvy=#d4m@r$KR9txOA-02Z^Pt?l|9HzGxt=z)|baV?ma1z&`T_hJA8d6(Qq z1Qi31F$F5^S6R(C@2t=ghZ#d*9UWsUFInMLDUTZ~C=uSLDeGTDb~+9v8PD&t_ch=u z5U3^~-{ADDj3%2ys*%Dfd8;la1WJhdXsR=&OI$S>%C?gidP7oaU~chNa9Ce0#krgl z(@ZmSW&Pe?083}TGf@o+VAt| zVaj`$Hr_>bWJ!&tkSM4~cde+Mg|UL1Hne)9()f62;SY_gvE$z>BotY~Mul(}dqYvU zy=DxyEwdHJTM2c?F_snc5wzW<4B0QFmBez7ilQ;>_|p$W8%m0mWt7^FG0G#U+cT$D zEnj4qeH%lSrDR$^p6VL=lE)CP;%NxQSw!hRo_MY=VSX^CRWCWe5To(n%BdTqbo`ac7s`A3K4l z_kIV<5VAsu;=8y=U+C<5kk{Rri6mwElvN{pYAROGRwv1dubv4XMIlZ4@7mDO`0Sn( zH!cNN9Aawdbl=pWzke@{&cGy(MwT$5^0OHPLO!@Qk2MO&0>493SC2O%2YpBD;ab2Md!);?JtwIlgfiNZgb#k?Xmb zn2Aakmh6&zL+)P)oS11qhLn5dY;JzM==&00!=lN{E}=Blc#y}~THJu290kFrS;oV9^Vk5Lbn!XH|N5YN78G`bL3&hN0Yg(07gTE8!%n8M_Q9#=`mskYd zs9v}7F433gLo&Ph#a54}r1|YS6PVsIk5u=H)hXi@rqoU(%WELI!G)!Uv$&g3T7z+eFG~0mT?_Zj# zP-njk4kaf33sXMWaY#e!4r?-EHG02}7W^qt%&~(kkKm_%4P>@?L4?HH#Ezd-cQFDx zhQCCTV}jkS2GUHHuke8eEwJ9OeDU|+TD|!Gebq>?nnkV8u)y^`28)raqGtWc zWW5d9tXErBZf8Nnx8fMew3wsw{K4vawW6L<46B5t%8%PdT3B9zk&}tkH|zBKHg6_d z1JuGO7fXyqOBogH+ln_Fmmw@@g+0#W#@`_zyl4>urJxZkGxuxM(^)k_)_9%GA4o0P zf1CR`iR;~)+oh8%3$~-946iTr-@97YO~l@9w!-&VUFpymnUT;UQM^uo|KKE4ErNnD z&XFM&&IAY{4sft|@y`J%YO2CE;+@w1tM_`_ z2cTQ@ednZYMJQkt!!?(9gbeCD0e9HAX$GBhP zQ*g0o9mgOlJ2vH>!^=}HW~lgOux8+O(@|h?RFp2J?za`^OtMR1R8_boMBBDd-y#5c z1rvWSg?;SFZiF3v59$~|lq`MV`iG1yt`Sa#jA=jOnc8Z6>RvxvQM*;%tqgWZjHac999-y8jM)*QCbL4&$0ji4cEC^3S}VrkFKu|^+^;m&R3792 zB8<7pv@TJ4yu&EK3S}qG1ZWM}Y^Cw4uEc>uqb;YhjA0#*O<5#Co>}nQ)P(3MZP|BR z_nk%^vv>4)E$tbSfv^6A%ox6##zlfuBipUo$u=S@2aXdqK5R(#g-wLX~kUZ zxQaN?KO%o74(G}T+gDLEdDS+WLedXaaq&_a&Eh7aSe9lBFS4ZsD6WNwqW8d0> zDit#+zg+&EkhRq{JJk8yRuv)@-0m9C`bJ~GDMt2;rWVX^d+d|&C&l4;u-b-kxM8+N zgFmZB`3Y!Jy!@rCDjp+(FO`rFd?zOCMH^SCY?U@W+M#e4AYqFB?fS{o66zn%G%VE~ zrpJxXR>Ejuw!^kf-Js(dFdS!2tLi^@ zV##;B1uk!AHM7cn+{tR_Lz)=xTTZ(%Lqk$kQ>On+!AghWsGG(g zg>wZq9YD1{C?RA{y#hi9u}L?R_be#+V^>K?1QW#+!N!UU$XHo^Z2rU=onR&@0Qaij1A z93!$2K+7%+YYLwD4i?$o*{|Ckyr0q?G+qBVP44HtyCXy5M@83iup~li2?x;RU}MN(FV~fye|7)7F$MC_z$H3@{;uv zRZYHbI8Cbip&LxELy{OB>|5YdW9sNEj)#y@YxrQymJ}P)NuwHRGMzIS%ZP-8%vMK? zft0rdIp}zc>y+fI%v@i1rvbIkYF^B&1TezZZbngFN+U!Bj16NV(gK-hlgYr8i%dy; z=Uuonm3)2_)4UhuxVLYFoqgQPoxP7i=);YN``8gfXg1B(fRSefZJDI+JV-M|+BCvA z^g;BL5igM>fE-*=6osL1r7=HJG};CiJh2k@{5@8fQv!qzicEd(ixvk5tE5wjSyDcD zn_@Yi#Vo{;D20DwnNA=rtF7N#Tt)`Qg;HbFDA$q!;vu-{2n!!bxGohDaRalG6)25$ zU0T=yLXS%-7l}@AKAW75r30jk*YUv^qiuHQB2h|Al@5x6b0Yu-# zq`&j7S^s&%QM&eW5l^wz)_W4X%tFoNL*|=KWEb>a<48EF=ZHmu|3aUS9#N>?ssAL~ z!$ojpDb-rsw)&aLF*d%Nm8$hN>8~LpDiCQsD}m&tRLVvRLj`-t&c1T0GE~8gFpLe5 zlZl3gCo_S8geMZEBBU_dGc0v*nm1$t(yU%e91!C)I9jKYF(hZ<3MR0Zh<+p;el<)x z8ZFTWG*rEbI}utj(Wa~ib+NA3FocQIpK1{cx=6~war+&Q}3mbpUZC}z_%gl;D zBNgRL2MJ8CFqK6Mz ztC)=~%CW8gwOQD!nW?Bu-Ts$AP?meU@7t$G_U3WTv!%I@4TmZZr>*-H9!d<0yqlXF zfHY+%cfalspT?ZaccUwAeUdUBlBDZ#lQ%xF3X_twfh}=YgI^e(h8|=~|3U5^$#KYB zs_jKSIB<9Ez9?3zGuM~3+%e3%_UjfZ-s7hY&4k~z6b<#UJn0>6giC%P4V!P{GD{SV`a>)zB0T}=BmK0Q)tDX10Z7Epma8!@UqVGy4GZnIP$vhZBM~yO zA@K?vuIgv7QXVyl4|mC37z>)P0y$ZMejf=jnBrTA)% zZei^0l7_HkkNRYagC(mO4I-OpLkoiM(ls~FueCZ;UGu_Y=WdE5Q_e@ILRJ5=gZci< ze|~7S9NG|%g#vnfu2*zdj6fgl4+7et>)V@mubYDTavs+mp85_N`AkqheeW- z>|1qFNQBk~Tgl8GW**}-k+t}Pqgr;&RqA#y#V4fScZZgfYU5|)C9@e(B2+Ey(#Q6u z-Xo$0nChr$6Bkcdg#AZElA8yvZo72AQ5Ac#`x!h`8Z}QcnqlFN2$|aZ4|1olm~4|* z8kWI8Q}x&@n-Nb6n9T)&M=Wv{55wAvkwi$`r~-Jd6l5yH^Pm>yrgT@fk|D*2h~9i! zuPBI*OQ`qDa(umIaD!TTy!^(#;hLrLQ?Xli=S-69zT7{|{D;cy>dUlStX|>5n z2Du3aV74J_w&Ndl0QTG3F*3GMVraAB3wPVvq5OGISEDvUNBrq4oMGH+`^2~#9YIb% zXiPn#OR5LaieN=?S!SfmAQ$qPi8SzH@q6pwB@)%x;$O^HU~2L=M_aGE`MkYU*ClwS zG41^qOShtp1SgH;4&VAVQ~96*934FpmJ3;nrIj-`vXZvu5w84zH9ItS(TVC7y*A7l zUz8_=Dr%4LQP6JSC_$WFs~m>SYdh4pSL}lO)Y(s$Zpz1=VMfv`N1j#(+Ui^Ls%wPe zmg#t3jI#Eo+G=G@1)MGVhVDg_stz*MGrU zKtnRC&^<|Q^Iycqo&{kgFOklMx(n*V0X9eeU*I(fexwaWXlmK~EIwKXcMVt|YQb|H4 z{Cu1W&X$X-*vWeW?45BzU26gn_I-Ehe~AC87!3ABklK|VE)ZwY1Kllq%3pt`~M(!hP38t zCezkuTWnD$el0!XNkiv&F@JpwtEgv?$IanHJ;l^c!x!-0tQ!BgAkLRDBRT7coh9ty z1lII&;2L(?=5ubsH3aGAsB+J`U4?!BVkuBokWCokd(@i1gUGwnTK>$J81P~x36k|=GR@<>@6aOFQt4N^RpvFGta%5huKlV@S;z_K|SC7IfC15+OBX= zo&ZSlqF-&Y1(zIrWd>_-eQ>aHX^~+Bq6b&uTh^4r-FyUUO;krTa#(wgw4V!kaINQnT)kT^53e;me zTu)j|ZTTva*Opl_eY?)`tHHNdXfYddo6stV6AFoCr${?_M6d>w{(5D5CA>*aib`_+B20p-5;1e=A^21yL2bCs*(RhPHJt`G=~ZGKNjoyd~B zo9UqXJALTGmQko&9pd+&1QekBZxn`$@Fm)zM|?prh`uV(y{0D z1{h~VLO@t12tvg`64DjZvq2jF1;F?*O22*$_H-L`ODC&hClz(pN=B3L(pOj| zbtJ^5d;$P7u=&9buZ-hvc?XXivx)Pc9IVuu1yWMR+GF@0(oy2f1?s z$5hi^)K9_9)^!yB4Oa5+j+ZYi2NUM9F`!7|p3qo(#n$T=c}Q&Hp=Hyg(}?HTtrRXE zEN&3=5}XGW+g~Brp{hI%bNsQ%PuU)$^)qoHH(7&K_T$&r?=_P?uwBfy^2}6)zL!u> ze4LWI)Tz$^;|vVU_MC(fr!^(R;6G~77)GPBD{Z4uP}F=4@}9JcE_rgQ2`M>=DP^rA zQBsSEZKXl3WfRFj_WoGl!#3zl7{VImo~_*q(3!e6oo1Ztc#l zG2?nce)xn^NEd~mx`#IWbd_yj>9hjElBt?Vit1l}85UN8?!#r=xu)JxV$tf&vtWmI z=gGdx>(R(b=4rn1_I!_oT;E>u)}-d3%CccVr@C8_Wk(M{)M`WCZIR~0poGUuoR(tP9{f8=oa zRu5wiuU!r5*GZk~6h2I$em0h*G+UV;Mpv&#rcs)~2~0?3NQ@*vaZDBpC{S1wi-q;Y zicaavnWQulys!`H#)PWyUY1|>)#cr6CuugW$>yhG>;)Jm1V94OD`9eWO zuqGVh{-HoqBb#n|r@}%^S3OaJ`yF@gl4@xfic-Nim)PX0@ zoiy9H&Lju^gVKuAEFFSPMrPmy%_lprz(r#+tMn-+KCpj+#5RNRG9OC{)K{JVgWS(3 zqly>#@G@APr%#Sv1}jyr>p8*8Th38{VJJH3w8~JB#QjF%^YRf7s--E>gQvnrg@Kvv zF1YXh!hV>E=AMCF{mzJ{5psx}&&u)$KN&`PFx5lvF?Hsoa0V|3Waa*Hf0!F10qqK5 z>@`v@H!aYTZ!O>ukPBW$bLV-MtCTYt@Jh1G!+)b=n#lGTc%}ST;CUz$&0wqsUSo&1 zRtiwaK#pZ0dm7=4WkFY1B;>b6Vyql4Moy}bmr#0pd!fa|Ul3kzA2;|U6;<6pZsLS3 ziDjWIXR*QNn`oeaubE*uxRT^jPK=(wW~^5;zi!6$xpB4j9s0lQZ0>RTvuOU`*QGZV z@a~3mXTdKLjB_YT%>L{xQtWGCOd`Wht<1m!kUmF3wjowrwSVgLun#E@HV(Opip84z zK`DhMQ^*w&o3n6dh(HCwa}Z|C_I$YcuNhH}6?puC2`<<+GQ#Syvzvzv$6(t#+jcSf z{7Tz_OPO*1x88!#Rw6qlfJg&1W^x4;JujwKT2<%DY*HPDr}67X9pp-tBfwmbDdl;i zM3uWML1z8ceWtlhHpfTtc{p>3$D86&9tqY)7-=8iq<9%M!V@)1PXZJ?1+jBu%<_-{ zZD@~;Yf8&jG%$m0b$AfQ{uJ!@EB44pL8zK$SxF~xp`nEUpXkwHvUqef7WiQ3t>{cU@MkbOjtt?k3q3!zRzV{j z%hp$FjSlj{78S3wS47hLUv@S}{l6zdYNMl=EY`^WnDKMJ9qOTfD%3n=i{p*XYr5bL`P1*t&hj{@1O5L zK>1;0Gq{}>HVSS2=s9Z5>gqR1;nxgb_JR%t^Jx11ooI^jpT*R$2TJ_$ro4ZjchUzL z82)s8N=Pf=TFA{ktZ=!tzlbA_s24zgE|<$;8tCTxa1J((~xXcW7%_7DuU)}n!9}-leqC< zYdBJ?Oy}IAEm4=Za)FKUhYmdchdFa8El^SH>nqcr<=)RNZqI)XT<6dW*-(N=Q8?Th z0-k@Wz4Lc;;h5%+cDFp+?t3+4w|mjGa>-dG`EENg+fax=Y2sU=o)Q&4=Bxy>f$08# zgERSu)7*SD1Lgz~e7%f>Kv&^bDhf=-+h4PbgiR^W-+u=iXeI&VY2{H>kQt9{0S)X7 zOlG#D7^FxJrEEvMu-0YdRGhpB%kR`1I(li_Ii$1TWA$3bt|1qBMZG%m_xDBALI}rl zw|q2DiZy)1&fP|oAoY5$9*;iu@g38xIjYx!T{2x~D%rKrQxU$>=lvRM3^WykP|MGN zm}usuA0q$0soD(}=ld{I=P4p*uO> z%i#8WYG7jhsaKzW81FGQA40(j|5kGuPi3P0K{l4 z+_3x=M9oaG&2MHGly#p;kgYr^8VXVwXB-|_A4OecEX@=#V8lXdZS4eJjag*%2aQ(Y z5gErbC$gzAWr539j%+D&M6eG0oiQV)nqvZDzD>t*hIXRP%90n45#2P1h$w0C662yL z?9lTk<*;>g8Pxtf-j}>!1s;#srO64OW*U~5dm36kvU^AjddFNNL@>`Qq+qov^-abt zoxyq4vxi4H>$bskpF{d^Ha+9NeKRQB&iW_t{1hXA=0$`%83Dlsj3jPkz4h%MD|+8i zDDC;v{A0izu-75v{O49zh5p$VIhS|z7muNzMV;okKV@1)_qY3x&HIj77Gk@sov?g= zN|ihk=$bn|IY#whX?XITW@-OD%}#pKU@e79V*~JKgefBO{_yY=fk*M1t82aJjK6C0C?8QU>b`yJ|@hcCTM2Y*pxagSK}Le{#=a+#SsI zjAN0k1Yq65Vm?W znjiFxDH3erKEeE|Oh7+#LDVUSsENPN?uC+FdU--fv(Jnsbe-1&yOxykqQ~aB<_g9G z{%@E27JmQTN?N!+)XRZKeD@^;(&WhXqUi;4F%8z?xH)QJ^TSmF-@pG5{bx$w=c=Ke z6KzKbmmdN}%O%*PrEm2L`1s?9vis}mwCPlmGOg%67__Ouq$XORL zpV{F2vcIr8M%%O@w3x$ZF4phmuz)}501&LKnh-GuLIA%zK`iY?eEcuN>$-9o^pj+A(#* zc7v3-3ZnL4HDVCb@U4ctz%V*oVH6z@z={gY6^UkLi8b3?%-4HYMdtD|CA)cql%6}& zr)-vk`JQW?ECIkCqTjltXU0I^8x)#faemoFzVYMRz}_IgmA<-NGXm~sVdGWa%j@}r zIciL@l0GX-7PNW_-PnMQ?4NgA4_zN5H0j7IzBj6pBr$tCr>v|iShFlE*BGC|yZY@% z;$3WR!s^%C*rr>%v?-2u2Ww9Q_#Wl5gC}5EFYCEzX&XGSv zjCT;MJt}iew^2iuTYee}B|#HpS=svvT3;$JvZE@^6dMY0B$1a)xlLV*`JzdpaU#;? zU6q0u=~GP^7EVttUBze*QtmKF_TT-57D9B`N?X%J1|2YQ2_oFbKUdFwi}<^wfPp&O zaXN%SU9u;Kqs?484kv@)?`(K8C!!*_frthYyYbiALZ{jZXe%o`P7*h~u~X%e^mXJ0 zr?h2e#Rzc%VI`9aO&RHdYAr6xxM)bJftzbkzEarn%`4|d38)`lHM-o*09#NZ z9hXDO1ws`rDW_5Q&Q+fS@J;)q5=Q@q4l$@lR4lqLGgp!+8 z5$cZHMF*nRCjTa8vvNb5IFBE#6VG28kJpf?7Ia@uHV5Upj;Sxco5&?wUd5$UND|(u z7knNPOXI>b)Y}^zoE<1fx_LC`mJmLlDO@6Nd{ zBYWhUrAm1UYe>9To9T^1D+b7EujG#G@w9d$UCFw(A=_D6B9{Uu2Cz|6UYlziqb!wWPZ@{hlBbkHw@iVd+v-q(0>^4Gku(>^*VAr^?D;a_cuW0ABSv{4G?qpg(3{7s2Qcd(^-jCvsJaPu*~=q7w6fB3j!-QL4~*OSxf*e==bt zU;MTN4b!d#nDzoAb*4^jN6ol&*e}X&mITi*oI@e?YJX?w2>gY_Qm5MYb={6vB7z4PXA^SDRI%%= zNjF>*hTdo~N7@t6Tf}3oOfHS6C1$i>_&j5L{5?bzVW6wNBCmo{i4y+THl$;mYskVL z>PU?55IM+L4ibHo_lCW;5t%QKRL|5!kH^z%Do|f@S@x+Z%xN(Y&{REn^Lz51CxLiL zM6HG$ja~AzF=cfE8Cb2;#!}X6Tx#)E%7WL@Htj-@$v9%L^VTP8lI_LudKtzPe?l@B zug;IXA)0ebgYAf7ROSGw+JUQ1lbOr9*nki>%lTSdiFx61Zv-EslZAyGNSe33}+m9T7+7?JJV^CW{%>lf{2QBkOad4VR*=KQCR)( zUHy=p%%az}lC}m$#@HLg*I&|oW2Y+|?gmp|>d0Y>u3ji{cH$B-5SK5+I8{;0=gSDe zW8~lMzyFYBSXdpg2HLo~|4kntMx7NibwT=_(^3V8!y6peB-S^xjC3;i1mD9SS4&S) z2(%6~k@p^A%#AoswEXeR)ifaS*$r3MZWtt>5oA0k1Ly}Qv%9at!qp7I!tpjp$dBtJ z;^>Uj|F4|>g@mze``=ow*r+xL@&Y+>&8P>6cht+Vjdf-Q?tT)@JQC~mAB@9z9gQx% z{_07HWIa?esl~E?YLnmCj=t>A_G4cBO!qDDIo=}Vl~AS!tZW$&OkSIOva6H$`hraf z8-_)pM!df&jUIiwL(3nGT*xdmjn@Z5uli@s$1F!b@!6Zfk5` zlS6qH-1Ns<<0|-Gw{I1hX7$RQK&Ix`7Aq5&0`J~cap5z_vLBR_I0}PEaQ=2fKIh_#^|X>m(7676s_MeO4h1^M%D&{+S=>y2Q&2TVw~ znNIrft&6VKVfJ05)#RwcCpg%N1pb3=DfCmaYga#WZtxGH0t*$gMPKz886Ugl?l@7~ zA+GcZo$!18?Te)FFZAPLqb5Q`R37?1<({FW(m2|Qal3q$8XMum*}rYdb<^Y#nH@3X zoT!v2Y&mN~xwQg0wqv7$_9m_oG-iq;1otDj7t?+06j_Emi0K}0>$vNkgp-DgY?)O+ zNc+;3oJ1iT?nbqD`csQazJ8Oyh%ab|d`U#H=qoUrTXe3p+4o_abJ`Nv!j$maJ%yJ{ zn=YzhtS#UrTu;#1D=ihf%UOR_Hz8L!Pz>tt@Rp;DC6geA8?ydK-BsGttB}V{q<%)q zNUYB1KbCs}1S;9~B)$k%7dPWghZn($=XyDRb_|0wSno>XB95qPl3~z}mEXXcdih`S zcZdFOm@6;tV~UZpeF=LLSYEeWbkUuy#^d=ss^+5bel)9AQa9b$(a*X{;kZHJU7Mnr zE!oP%q%6#gUfCxZD|0YNn!a$|5Pu@<5U&`pKky9m7wWPQczS+DQ_p?#b*Pw@l$bIG zJ1?%Y2!kA<(-Mo1WT}KwR%|B=kLFmQQ}>ria^kYkKO+|_PbZ9nCi#pBRDzbHK9)>S zd3cSCOtE{iQ7`^nVzCfo$rd2i$sC;{-gZr28!5pJJBiGd2;y8AK6D2BwU-VS<6Mi= zJ*;yIzaF%s$UrA;(j68@q)p*BT9Bicrr`@}pN`U%FL_&w>7Ot#rLM+!V{?tmUmMV# zF3DL|uvC9Co6`7oYDVu56sf11Q2XpIhQGm#MR_yjPQ;|6R8CHu@Ha25I+omS{4Md} zPAU>35|RH%oUKxx>5AJy_T)V9NS&BXGzpsLC4UMPn>0`&9C;Q?R{CMRXZC4Ksv;oD zRPMD7>{SLy4F=u18SBu!!eo;@5s4%k*GCs7AwdT$Ws<2D32$1Y+u^LXDky!#pd`#r zA-7PW-yVpCqb`^@p-`qfoy2Zm@HSwZ%)^3E#!$y zd^FxGAl2a~ybU_xNmDKLX;;enUCk+&$x}C^%SpYDs3X*!t*!QAso5k+$Qt)Q#z0qp z>*pk1St_N^k!2-i&`{sUmV|k$$aK-&vmP@Z+Qs?JHR8h?9+UT?BF5wu#%fK%jYnz# z*FCY$`2Qexoh>y&_nJ!i@Noc{%K-Ze$VFGdnmUwy8fLhkBDhbv=E zM=3hH8sqwM#R`#-iu-U3v=cp4$_mtn%(web!GE#sp^~KiLk}T2Jeg82-4{)CVLAWG z_l4PYxt2ammDFtb;NUO@Kd@D#ccq~@uuL{rXUQqtO>#b>U=FDO&YL%x#N_{6!3t=% zo(H)h3YyBo{4YtYwg3N+!ciyqjc%$@&<8CB)u@uVR)AxaEJ;1hoEZ|{)CFbPalm~` zRA=uxbzv2alA7BovpxO|_wmZB!2IZ?p4-z0U8b4SE^$9!Nehzc%8hv9oHMzc)$=i` z+A2*cN8@HT>g{w)MV(9QS6gd!{Lc4N=O~%bg{QNxK zd7_`va3a^4C`CW!y041qExSLjU&St?6)ex`tbOt>3IWir*MhdpsS8e`T>i8an%5i^ zz0T0ciT&b3u|FHhH52I5B_781!-5abSY+=CHm1S5BBOKpjGyx zsLV5!8^QCQS9-$2!MlsHJ)Rzq4id$#wgDXNNMTkAG=WbnjJUR%240sqHSh4;&ZG^} zAJRUeTkc5=LdhtkxC4~x@>-AON6@d& z!Ns*8bYbE*s~ElRRnb_^$x5y`iEa6yWRYefkGw4rUv3<(uShwczWo&AZF&wBc%EH| zR|K@@{|C84q@Ouvy*w{02h~trw5fC^0{bItmM;zIy`;-Y zAxelC%cOH?{{DXFL;0S?zPmGB!O?={U_i9*N5oJ2z^}pyT3XGmLN()#|CovnUF>`p zeyd(s2F+RgC4NUI_m|1A622n3h5`gI2(goiEb`!Eg~JTgGFTiZ(K!t@SK6(drnY_@-k@vPsv3f~0RO{?i{LP}9>{y>tu~1Q>Z7HIHyF%3 zTypp}M#+Gij6ftI=0dq_AIB%)!H2AooxNZ=?&AF>_vJX#`)ckiPXG7N4uOKf9#pW; z9+~$SrZjX`68*76oQU96RQ9+|qPVFj)7*x{ufohU#n#0wSp4um$ZEUA?vu~@P^=ZK zCyb;WhRBGL-OA)>GK(o)BSt3H+%-MtJzFm9FASXZ-6ExgLIOiUZuPr|_JgUSmEXCz0t;J;z@Dgk2`AJM$bXhIP^2_>u zQx;^In5R&(+(9&Eg;p%M9VpVTT%jT3=VI?Z*G|l=p-Nk3IcM(9;=40_qzAWIEC+zg z6G`O@TbDACxaJPmQD41ssMk3Zd9D zI3|#fFug!d>G?f+h}A399^2yTnTcp$E)nABpc`Co)?sS?B4|J5QVaubA0`dYXd0n= z^f3dm3yMCdQKNEBSifpzuA^2$K7!R(-%r@2%!IZlIY}(m;kd95 z7P%sCYX~C-8(nqem)S`3ElZM3xUm$DkH;sFiPtBGpXh)ywBJ@BtFbk6#uFlR9mXMcd!*v?mY4n-5C70E z6g+j+ME)KJO+t^Mvn4oVLM?Uru!F3HtJ0hp-oC6zm?;9H;b^H3QiDwXvvE%wW5;n= zysGe90IG`f?0uJRo-mDO3RXJ(m6MPtxwgfugs*UX<4pvEW&|>p)AzR@6r~9BphyNY zp@iSys3WHSW4Z6)Dv_42G!VhXt~07@1e}y)f<8C%&={Cl)h?Jh>Gz%CL#|TNw_~VR zZQXkLB80ewCN1iMny0q2Z{p|Nn2v|?SF~C|9)NU}o%W`781$|#zxDf2U!?g)pgE+y z`O|DR;-eN#X3rLM!7Z#+d@S;WnwHHv_WUJ7=fRWDR$yk53|Sai-VZoq7)eo_FX99- z@s)o$54L|#DY?hN7q^o0k;t`UD9BHRf!L_*KIykXI+i(+02bB@BeO}bRl(k3%nENw zAGcdglu)k`E*jAzeKt2@z~m~G*h=G4XU$9O-LAk@ODXr7Bwy}6voRJU0Yy8Ug9woK z`4xBL!`@PGkhNYS7&R+Td(myB+4->|Qfb_)ybb9dMRsCb)>;NTW2*i0tI)_s6INh8 z)BA^IL@x<=2PGI8Oe*kUgP+6hA!1=Y#Hxq&hn>%_S#AExQOUVVEGqIv<8&NerB(s>-&ak0l2{wbbX~BiPduvO4sSHl@)w^?ctsrHo zev6y>?tpLu3l&+a9LT2E4VW5fT57>pX)^Ubu)d)eQ80tQV-$3?*sJ;EgCI5)+gS!* zd+xRjun|bt3scf$2+=^6-^{K2j7cW(`HnU^;Vd&VoKM(2Da~{ z55~+ApjN49%TW1XqIfd|8+HE5f<+`ZjybrxxOdL)>}Lf2LYANmHqe;5s2DX=tAECk z=>PVGC9`OiwFD*O*v6JSvWl~%lxPU*;Bz~4$N$bwU!4Uc@Zl_ruBu3^x+uQ?VdahV zwR&=IVV^;T zz;Wylt>WnD{5~@l3m~1l5@b4o5!`(AI9r9(8ci!|RjumADxj&uc6Kk}4QCM62Y~)g zcd#V+J*_4f+AQYJ=6_={Yw7|0(>2T%!?oJMW;_g!oT`neEL!`xYi~$x09Y}N=LE1K z0p-EMv1{UkZs@2pN`qdLHb~l$t^3VZGw-c|*2|eo45{+o${uMGb{h^rh{(+M-9L&T zm}u8>hBk9EzlzB@7sQhq$}#~j`3uyGiBBL)&|S9rnJlhCv>u#PzRx~x4*x;MA^*O! zhVb5N#~WneUin^fq4h9CS%q7Dr25#4QBWOf6HVB9Pi5tZP`lpA^^luq-bqv~5u~o6 znidV!IgJze3xda8w^)?<=H!l=9 zN+kZb==o3w4cnDM0rryEsEa$EEIoVeKFQdQ0d~==h@}A|<$1l%Z8PvhkUFCxM}W^< zEMI~F*Av9pXd%ODs+L_{XLm8&)Uj>d{8Twx{v5hR zqSb~gie54QLGCv0=cT1&Mo-9<`?sn=R8C?FOTY7!qhpvEzcq{~E2uTS>p{DOJJZQJ zv{1nH>#IiX+nCqqtO=_af*Y|>o|BaE5US*(XkMMh@46%L;&Em#V&HD5^J|Sj5jhmU zK?<(V%(W`(q?h0Z?!3ncD42~A4iU&TBTG21-NLnT^680TX_+3OtzEk?!?qOsUwc;- z76sRC2N)!Tp~o5N?sO0sm2Qw2q>&tI2x$Rnhem4XP`X39yHk__6jTHxhZ2MjL72n$ zU!2?j{yb-2@2mZ+=iTewd%bIMX3o7Fk)s=vf#qj_yFW}eMTHF|*S>CWmuS!$9T#sR z^A?z$?gskoHcwt`%oh0F3LF>4%ZltWrzCdHi?e*=S#mQm5)FD+jP=byXj|jjy$KD? zs~Tgk4rPpLQU=}lj$g1gw^UNa{C?trIIeyW!2q>9E-3Lx*yVJ+oAO8*H*BsLzbs}n zV`A14AhErdeky*+yf#Q^C!}?u5lhK53y|5%&JGJc1_JCtl&G2q!=Qzcg`47b40@49 zbac?3FIG4mOAgaEIX>gWUE2LL)axUbhnSu9QLXxRzfWhtW|#C5?GXP|X})O^8lk|n z!S#^|_fp%Y$OHN$P9@uVL(d#Sv?TT0Z1})dtpzkMzxUHwlmMHJ7lw8vUMWd@@ z{VaV)-!qu3fv<5S;ZLLiQcHxNO*WnL!3gxTE=)ZUvSnXdw?@Crb zCO?eh%?n2L3HR?VWGjKor(&xo!Tt?ixS7q}DdCgGtgUP#ezTUBiHn;j=h|3Z{aZho zg(<~KRO=4<{dT(H*Gli|;F5RKtTGgjo_22-I4>pA45g<|2!wEpDV{0-fWQGjw+(Ho zDpxp#H3yLc;KvitQiDali=}jYeJa^i;DPy0VL~L+x^`*5j`#*VF8yLQL z&3TVPk4j7En+;*R;V!|h(qfms4I@(T*H|kOB+RM!rthU|Q8i{B(WrJrv1ADJvaq@* zG9;SicY<1bm%$`(Kpi4G57{8heDZxZNfjcLb4=?v#3kohffkTZSt3$_guFobhLEh@ zuon>UeV!_9o*P^uCB5&fO=Jmt_+~v(i2go99n?fCrrlgxa}dbW+4G9Na?Rrqe>6%Z z#*f^;o+pmC?w(V2{dLK@IK#}2X9X&K;#g+#cw79ujhXQDRh4VmfJM>e2ri8fqEm#9 zpAdXnNv@b|`vdn+aoNz4u$iGqkG(KgphTcr*;rh*m=6jVG z7(;%@5y(C&)~n9S`Y~ICSF@^;jk*3lVGv20sT9xqYr7_%o(E!imE8uirc%3KhKT7N z2qz@pl^UWIZ_W27A}(JAcjDn8Z)2U?T@B6WiX9g@Vv;5NPI@9GqKu6%#c$fD`T z^IMu#!TJwC2azhms|#&QvV323;GGjt`vxhFgR0@)%b^LacJ z+2Yks#LYOlu&vS)sq$h8P1j=Ow_}G|d7cX$N>eOy!zDJ>t=zIm5bj0bA5xZ(-h|_Y z*&{?i_<9(x!nE+TJE6Wgsq?EFaqG817cTKCaViS&#^M~;SkgnKoL+9054kNppQJba zb2j(f^BBb)n?H{nW)KASVk`Tk)9B*%8?gGnWhj`&p1u#Iv#$>4hn_@zF52NU3w=O& zLtc#+)7?ZtY~igycJKTC+Jl1f9K)iFmx~02rP)$^m}ePE`k^tX2o3%|FO26{1Zl?Y z(0mwgPNMxL<*}^o69%fnHN~=@eUp~pw5sO02+oHcq*2FS+U@#Cm+%7-R9h~4p==JO z;G8YxNp8~cGa}3>0Uy8WFtT)*@PI}@qX1EJRdNhHRR0tEHwm6S~6$9 z)@x{JB}BTye>YyH;xG&xtKyMx{Gg;b%CI!bb^pq)TM*23$}y>|-Fv7#mK=)BJeegHeJDC@Pv-UK5`x=pU|Z@ z6oPVXT~v>CpjAGI6UZRqXX9DX6GwIex?geM!YAkQ5O){rJ z@|MxJV5{zy%eB8}8Q>DQ_@?f|EFd)>#J{M|Ic9|c*7lveCPpZj$xTSUtiSRH&Ex(x z4TF&doV-(>CN+6Chj0<0b#E51JrUM$M`9etwC`QBbyPMCIV59L?}}Y)-H}wuVo3<- z;!&0S$C3Ng?m%mQ&gWA?9m}UvGD7k>oNjHx`yc$uE~&qm1{5Wi5H$S1FD(Z6=grZZu*B$-_t>A-1$jNx!Xx+yrDOmmw`j&YI#b-nG+LI3U!n1 zsZ?EJHrs|aR5TfPD5R&F+qlJ!nj%`XX2`BK-m11vu2(=UOoU;3=K;Cv1LABKiL}z$ z#-TzcyeAD~)GlNl(lr+NC0OeXpQBU(sSUNAe2~@%8>|h_Kvet@r|xy9wJw_jXKvC3 ztJSgdbID*oH}AF+jccX#eWj$BxRw~Xkpm5pcG|kesa;af8v4#(blltSFqfNUm z=v%T2737Q(SqkNd2*)HsSXYeSDB_1icR=8)8?Gl8%AjSQc8I#0arhB|yu;=PYB6>| zueNr2q~SzBdNH=$Sia|>eI;G`NIY*%k`#C@dH8S2$(i&wLKD4?@#UJ508_18CL?2< ztHSg(^=_h**k=o7ezOR(FOpw`iG!WKI~My`vvAtmd(57hi%)>=u@TTHRXtYmX6K1` zmvKr4^;K^XT=FC(H+x($`ciB%Ab9zh+H8%>vQZ9tbcNBHGOYR zl6)H6f!YB%Z-s}wXO;h0dyx-sBEHz{#Du23!2UYMuHo@Nx_%61%QlD0OG!GFJWI_~ za^FpGdekW<8+yl*pAa4(KQ3T==hFFyc;i9rc|y6JNb`%tx7V3ntBbO(uY{+xZa&Fr zhl0Ia?8)nWnqpp^2{$gVroRmeu~l8YESEb8$h?Wh1JlESK-f;VHWE#cmg*=9deL76 zIa!f7hYZv`&7%t1*4lBy8cntKgQVt-p9Tt-|<< z`)z9%5Y_gITA9_>qa}0IN!h_-W_ILY*f(mPCm(sY3|&P`w0q^ zZy28Yy?dZ@^;p|ab$Ql#E|9)sG~|5q7Y;cCQu@2hY#2mDzS-6q5^sgy{SxUKn)0wu z?L!IlBehPPtHG{ea#<0bRFw3)&5WM1x!RXJa93~ZExZK^LqVMW~4xNjc80jBKt0qMz7_-J`3UpPdL!~nyPsQw(vR#I6#PTn+jy* z6^w-aw9eH5y;xUx_^iiVkTD$MZnO^b-7Js$lbdD07rEjiGuM(p8&Ym zMEA?P=z!OJ_CU8)uC$R=9xKqk+@_w4nH-N9kD{&O{L(qO1}mzQM3_uhP3tyIP|o%} z)G~}@p%+Qd#^0<;;afAGzRHT`UbT9(rGqW4Wbl)2IDz2N&Z6GGqnCnzOcvyM4cPzIQ}gtj89 z&9tl`^yhVJYj%~$v=+@L#R8>lGn7pIcm8;6-#bHzlYX!I8QD^i?)u^0;ov8K`?U*$ zzPK5IDtLN6uAM@a$&W0Zij}zm%(>=zNGO=Jx_ns#RDZSH+#;96Pw!&@!(SJ<;8*Sl zeGCK0gYpa2?;GtVu3x1?gnW(LYw(?Co}aG;Cy_eVMjMvgeGXLFqK$29)=vM#zggb< zi!U)h3W>^GJ*C`T|)Hg-6$yfZ!-2DQ(b(ySciB>2vr>c0hq` z!ponOJ8aNkudTX1x`BX(Rr!x^*-BrtFMo>t<0_{Y_Hn#fMx<-eH=n7q>##SqH&E`w zzQRF_%C`@=XQ*dwZ2~jU!%+OeWD)*GgYL+nQp_XyV?Z4L_nb5>H3N?9Kg2O#;l|!v zk8_m0U*z+ze`7HGAnRHwxmd>H^ACMzQ%~xrX$}UKp1TqVxM$TD71s zDwimPs*VT~bu;5L#HJoaSTAMLwhoKngy`%8vK}l{o7lE!;9Wu^z4(kj=iRrvB^NnQ zzB1kIHfnXE0J-f?c1wsnw19D-0JHz2{zG&r364 z7jb0*J8BA3A(~r@Hr2Yc$}wvev+YZj^2L@`1~=W+*w91&8vKoxTPWVy1pm~HPI14r zmH)b9hUe_bwr%GSwSH=Gc@+m|x(?gd{M`8O<%ot{vXA@*^uK9Ak7Kg~=7{&uuNG7z zaz}sX5i%?mZo#pXCc6pf^K-#9i@u6k{q~s%lABIe`go+g668+c1Z!bg$+$=Abqb+c z#7zS=iv48D40irQ^_^Y%5bGKu1L~yoD@|ycl&Ul-?$+_U8#V^9f+jc5 z-di1o%65ZB;n1tJp(Ya6@al2{lggPSWn@O^O28=1^n}T2{qObLD)%0_62{Q=94>g< zJ{aQ4=Y*>2Ae(-_Yn;&&_9C|*jO-WJ^}mZh&c)KKlZ5vtrrehpW#LlxhZZ}hj*_x+ z?GOUV&c}5I3-)_Ax{}oCn&#G9=juyUeXz9IXMPX87?!H6Dp6`)2;Li`%jDgIfW5t5 zg68>avuTt`yzu3NGZ>NfpZ(>xJ9brn<(2dQYT{!=Z`PyRP7MF|SKsje*MB_$Kq@BX z+_M-*UZidT5RhQ=)--Ui6{n8Z(6%NEqa9Fv&4p4)i7-=9)0mKfaF_VR32Bb^!)Q$_ z(l9CW`ZApQw%VH80(r4(+_c^vn+vAx8c2Ofx>O&tR2igK<#_tzpJ?=J5@L0%N%}04 zvfeshqpqwzG9Ua1v?{G3X3kM${$O^{%-zsQIAzcb#Z7B#qy{S1_c2LN8A>g=?fKXk zl+aJ(G|AAy|K6*HRKNF}x^pQck}amx)VVm_h_Z z6+{%Upeh4II^UmxcJcMD1h+aA}mJ~5FHugmk{b7 z5%gc5jsMSwL*t_(BV%;5{(I&*&dgAk#3*NbvMGQi{Ezlu9bGMh|LSO3n*N_6&I~Pr zj=A~&Iq?6m1GqRlTX23UKW8B%zocMYLv<~Ebxi_+Gxpz&{j2l8yZ^UGo2cMuj*xR2 zAQ1pze*ka@MNkvUlN6)~>*u>1j%EpfDY9tpx`x)v9ap=$`)}U9J2-rAVrq77VfoRMXD`+^Hn-n>*!lA9 z$FD#C7QFQ7c07^2kx6uAas_Xxh_CNV_|5MiVzfbbNOats+*Zx-%=lZ{q`(O6= z{(td{{@*|ju^ud$iaE;DFUA@>djE^e=VrC_<%9mOn~GhdEs`8=^H99aH_sNahOm?3 z5U5{OYjseI2Tt(CfL*bykU)aPzQn9;V#NE01*&qJ6`fK=J@emI65=H&Nh=;Ecr?E6lcXEow5nF{C22R@h^2D`Dx-Y@1I{`|XD zNj3VQ^Mh|l>kVn8Eu*`ie-SMXPWcti#l(=HVpO*HVE21nLGA? zMgy{Z6_uM&&D^i*q;+c)+Xd(ZOhz0`_#UiQ^z^Oh1575Og_&RfAtxsk?A}LsXIAc; z88^1Oq_7_c%ERM_Ks?F!NhsqntLePFduA?(;Sr0}K^h!`Dr%B*2yRilI2+ToKiehN zmI!eU&o=lj6rfK)2S}ihh1GR~&5}SKRz!wCt5?KxNnu*6dh{gaVk_aIAQCBXbi~9Y z`iz2c)Uh~Bob))ZKUf$IMjXw{7g?0Pmwzj~E9*enkz4#O@9sY6{W$f@sboxY7-h)5 zw3z>7SD#NiU8NDCxZSt*9mHFZtmOL`?1}qprELyJ{Ct|pzPFl&Lx6>c8KGc=;z*=s0O9jM}i+((|^mnzwlDZyI( zXRqU3@&t{TC?U+#sONM*nGy(vf??od=5!zglw)g#%QriKlouz~lfFNy|H-FDN=BpH zBvxe}5mxmC(LxEM*oQn#@>EMhLAZg#7&3j~hqP>giC;G5vIN0g84PI>hW2bfu22Ed zD6MIfam;=~ks6X}t-4{M^!`PsAK8FXBrF9eo}%8*b)n!<#f_u)5AE(@z!)tah9b1` z9g70eV9CLuI0SVgmjLOI&LR=t6pom{9j@= z+urnjlTl^!JtdiR>uvQf&)@bJzWLzvZsgJ%juy%`ZZaRT-yHrc!hL~BG!W*4Luf>f z!vpZGg0%_$Bca9yN5*F$9n!I$6r-el4dEGr*udbCpVTq`9Zin}RKzieIbFdtf*Uf} zOY;LLJ2a^D*z0Y|2E;XQ<&cGioOqz8>ZXKhli@3pEqGkoG)YDE=EqmW*vhAK{Z@hv z(E9!7zlv&>?dQ#^B?l6RnQO5~L{5>w1iNzT^_#qzDLQ#>apY!kazFfa z0NiqA<69pQV6$sE6Wgn}IsY~5DjdPe09mt&;DJNqe58@DR_6DX&T<2M%7A*{DP$cqf?fx!qKBcUq4GRh2yIE@fY$~h+JdqVgs1^< zDu{+h@d2JRETo9)(sA@=w9qK(+70hYS^8Z|fC3mp+ds|IR^4xsJ0722``cBnaVALk z+JRl=ATg`Nb(lWloYF+!nPtYAMqZ^Y>xvNHVSt!M;D*#z}NGcSpyM;vs|9l5h*cr&JcXzTK~Z3e#zUw;_XE%-W=_hCkupF5J+2y$nbgLFMZXW^7vV1)lic6J+%iVI{F0mDEU z%L73I4Wfrk#AnPQ%t1VSP6i2JhRlb#?^t)R0PTqJS0Vt2dRY4>wjKmPG*YQYrm9>( zl2r`zC+96ap<+A`T(o|!dIbkLb0sQlzvG1DL?hus*bV)Z%^HCf8{9{&xFMBR+{&8* z!)gajcq4M=4>xubzUlixES6z6P2ULcfRYoVydlk zU5|R{zN0Zq(0PN#DqYnX_w4>v`SPbtv)?4=dZO|T ztplfiB$o#`SA>mK|9Q4l5}}AwExLZ*UZrc4_3lO2c^cIk6rhb`@tFfO;EG?CZtz`d z377|j6+?hv!sO+5ZH|v9EGeW=P%4cIr3<14Y*0m@EUL*|VAti_4qx(>9=dlgNtk1r zC&;y#m0r3#d=m$a?7Fr48FCg9lKnme4Mcfr#wxZt3-h%FP zEF72UuYPYwqPkY({sjOFuVu#Ey;J6${Pemz`RSVPXN#rCW_aT0qlQaDzrCu?SX_P_ zdi9Tf{>O8_T}K>jng-Z^)_)&gUCf!NZLR4+RC|OkW!cv#gazA+ckgpmka(~yPV`L6 z4gZD!v?YVY7e8$wQ&NPF9llYsi&tHsUF-3j|VZBsCNbO#Sie7H_Hz zRfOzz%dU$kyFM|lx|DcxDe~-P)o)=xu5Pyj6i~x}Ft83`al$ULGz5lm3>Dj1 zl*&*AI2S3v6d>>{(^KCPogM(I;-2}R7sMg)I0%v;0FJ#$Ttt1+;JSaCmCd{HrOYnv zNssBpk5U0IuRmYTJfu7ka_Ww5baYMh(f)MJR8gZo_Tit$*H1jI$Zt7w_p0Gm{T83k zd56<=8BgOY4c^y#T31_Oq_MkFi0sE6VhSrrT3H)1~Wf(L=kT(T5eUqhEBq{TEu1W&a^33qYrx%rA40%Ty9qOMVtuC3P5L)6v6=h3W&vbYXXnu9)gL6UQ8g8Qc*E^AkBKSBCC{W{sJbg<^F`~9nH`7KH)I|a)JOL}TK0{sV- z#+r3M%|sGR6c9|42Oioy1#aDNp!_U_3=5WQXpj5~WbjdtNSGs50u6YP8pqK7F#erJ48%exwe&?%9pq>9nP!v_QvF?2Ys(f5FDE-0U~0z z0ICVClb2E46g!(0$4H^BrJ?}_H&t%Kgnq;9a`!8YAk{y-fkl9rP_z818=1^(K}hFE zvf6z`N?$#tw&iG{WY>HC;H;mK#(`l^zICmsvZ9aXz4xvPdbs!VuF^o^M@*aC`}J`B zp0}sZn12$@R3R-XiB0m&2I_xdw|!FUUHLRW7xEn)a%0c{u;#}-aEt2F61o*nw9)v; z=g)j>ug>AR9EMe2AqG`A2=X)|zT*koK%#h%F)zx_ZSo2DLf_nHdlq2z5{n?*;j!;8vSjqis z3cC`d*%C|j>i2>GyrHvYJ;*+!`wO#c8R||1EA)RUC<+L zGr$EZ1R?=D07#z5&42)?{wyj9QG@_GS3xKc8BXFN?#ok3Gb9Nxg+NT+$2bYffy9?3 zUq2mF9;r^4Q(QOh>a+%&oM+OnBCXX<+}6-}`DSKuaPw03RqOJw;|n%2rQ;t7`Hqtt zlT}IknKvjRf~9wIWypDJ`WHGza=eW1RrJ!62Uk9xzH$=r<=My7cV|z&+5gt^R6$U6 zuvJv`n=|C4bJ6FHkOPJgG@47Ayru_0hKmR>iW5$}M3R`|alP-Jb-Ot1{;Xe?--CUX z8CEptlzFI|mt;UaaA;boA%VOL#;JxyURHVN8Cb{5n+aNYEPU>WMbFlw5j%^36ZKItU$1z=7%kv^gGd z2FLIdi~uGT^$$6*LKxd|Fm1&=CQ#8(EmG~gn2i;JKU83_3bWoi-PxrIaHo+4zyJmc z4538{BCz3ukE;odpasx0WM9ls{* zn)D{{eRjdUr-oy1#|EPy7iaHm`|ck4{anAR)mwGD<#G2$yS`gS#q-l0e>#dz8X&l{ z#GNB31&m%tc;0(_ZlmmT8BX5hV}`}w!-6^I*y9D5@S`A$2{fqwilT&{|H~p)NI4zE z)8-AUL(;B@*-f1XXYzz$`CM#88#OAwfQ-ZKvVnQ5hQ?13RR87-tPe*YDSaAY}RD-Pkk3gx#_k1$vsyosPr-wd1}%(&86G zUbUY^+tcG`Q>s1NyOXxPe76b^>R<2txU=iC^=zW1V^t_EMB~ygTi?lVzR!_EM_{MZ zu1*qQ02%=zvw}h9tY;wl;21YyaRf>obOVjB@&OXdAE2|G04Xd;9w0+0{RjvVU>dUw zK=^;iNfp8aImmt2i8-!_`5CRM>b_+K=id@AsVtWdoxydn-~fds41}n*&X8#I>OZ(wBsHfh$AoQX09p) zE-chW|KK%NJH?oDwo`v`Vd^cJ9s9HF-5@Sh`RI~ou6N~`3v(N^p*gjkuC+qz4#KLI zO<_am$>K=#1F3E(589tj#8*7~xU?Sb-oIp}JoW)g?LJ z@T=vH6c?3_<{P%EI~6t}Ni8^nyFD7asL1Trxoi{lL~TI)?*a8CXw%O(J?W}fJHGur zcWb{^BxUvT-){>GANCT)LMMM;J@VxOo&c2xR-sy0BQ8)P!7b;u&OXQi!cpo^f&kH; zAVqb+07YQn2Q>i*gX94*kfZW|3rxomO~r@+EY=}#;OI3W7uJ5DeX}shqK$_eVg=(O zm3Ics@M~Dps48Eep1BzFWQBxwKkU?nDh-|X6!Fx}>_byj;jRHUDmB zr!mCU+%uI06*f%k@^ck6kR*(U)TK{}h+c=KGmFjcsi35Au_lS3r=Cj={AuIF7e;c= zv6<$;Spa5E6}1J~#8C!QXggN%*m9tbN46SDjj`LV>Ea=*=>1nO! zQWr96ZnRZO7moKTTePejH=DA2T^7`g2(x(?-^zXSPTv;|oRe;E#m%Nhi?`p9#wmqS z1QAF8jFm_0|c*E%(_a38@!m-AI=a3$C4q_0<2;Sb80e{t89@){4XmfGRZ;i zu@Cv^hV|okC3UZK8!-P?>|mO3K44AUTou z*L-XELRxZ=(R^fG3*J)KI3@csUQ#o@zH6p=Vq`X+Yw_@byzR1pZ-$j;d=9?3yj#rC zVaovfw*Xa$3K-0RAB)Hd(19kuBj6YQ*!gLc_R;X^3o+M|KK<_P6sjARJ|&=l4-ebs ziEK=sn;cDQl@JQNY(8vfBA(>h{rc+kkqb%}f9V(3)O+qb_uaeyYs|TNr_6}>XU`Z= zCIN#XA~-%t8b*Se$?$9PmT0Uy%W!3$!AKI1FnF+n3>X!j354(hP!Hpu zGex8k!b9-TWr<$wX~K7{jMvS4-WI;9mQv?%JEAeh`ZsNDKSf2QK9_ziPI~M|5^cQv zFy!jWJH?EP7B+itdAG#Tf~VK>9@%K5ru@-=?2Nx;{MBSC@Zh}zZs*wSZ+UF?tFdtq z6;QrAK@DIz!%|5SkwzY>2A=@yPy1Z^wNCcpt|yx_5YmpX1b_H+=SDV&VDpI?CieJ* zY)SbmBDgxaXtiUL&fS$b?I&B4$Hb3TeC<8=>YDt+R+A6((1Ba0&Mub`;KV}&44_F6 zBBp~N5|q_+hwu}rij*=MUR8h_foK9DxEojk3^2f*2ZZ5(jep2t3gOOK5)9?x?xOpfO>#hw#Mg7xjBe)QUfm-93l`_ z*`TeZe*ALfwKv}?HSR0ZD-&C{6t$W=E*z`0)xMj2nS|jnu(&dT#+Ay<^%}HPS72+r zd}d#fB@;E)*57-){~HJqSh$!l2hI_*&x+^W@Y%rDmgfDW*c;*e!xxrn!s31I?fc7S|GCO$Z=l}naxT_u&jEP= zWX)|`;}6)-EZ|B_7eQGfBDGE-584^r_Mvh)k2~{6&mmxn>L4-S4iTEi&CU2XZq9K9 zStB&U4P`BhEb5a3621ZO#S2Dy1?5{aA0tk(**j%Yf(O@iZgdQR7hDQv0t5jd06>fULr%C5R{hVH^C7!#7}`hf&lQ`u zf{T0rKndkI<^CPOtq2WPWA!6(H)0_)C=11(mo>IGXe^lLimc~}gO6`vc_~>sj;JifSXu#}RO zL0jERF*qzOY_cBcakeOS{rCBVy88(q?{GP;cWI00{u#Y>m;IBSUTwf;Pfdak4RPCr z0Yr7d)9sqsJf-c8)3MegqQ=N_)L&U=@h4sBj2;;nt-;>AQO7Z|1;1L2Q+mI`GpwdZ zryZ>RqzUUY-c30#9qps06Nn`X3NlU}|Fg)D1QRd{BrfpjQPueAnD@p%5s&hgGYy7V zvU!>`fQ8{hp+E+Mctk1_zwN=P!YS*A14i<-Pv2Gs#1~o6;dT(k9f0gx!~(}aXslJE zhe!6_9S!jv4a<M2Zs4$_<9{*Z3%qmbZ$RN+ zuYt0&C%%Q>2!9i`bUj1(_WOeeb513`e>B|x50b8Nj@5n;%mo&_@#|oct;ZIwYx-Wjmg4mQ?aC zR998Ah~SSDJ96jLG2a;H7E$iSf5<6Ox%7i(KSX1J*k;2&(>j1iqp$v*Nh&B%x~Z>| z30Ntl-@jC$-f-pVAGxybu^$~~oS*ucYvR`s_V2il*!`-9Gy*Y4@+ki*sCwgXg5bDSe?TTeJol9BY5UX8$GQ03bqLk~f1z!J@!` zSP5TXeB_duuzcZds^;6}hT5qvs5@v!0T5z|SjKKU>1eAdOxXrySnQfXC(OCyOXw{XqTzsM#{ufQfd#bW2?Tr5$Y%69DUYg7;x zaJw(XMEgGgr%oGyF!Bt{$Z>{aXIfKlHfFp+M@H*7#!v^- zS#u^kck6m^z6&agfq|txHZoz>l1*XRCCe$IPBv;)xRy%0#*Mms8(i^_>XCg$zK+Hw zx#vOSwAai^&ZpQZkIj*6Fxae7kYZVx6_ zN@6i&cpJD=B4UX6ZWCx&7W3I%#r<~nnQ`1lu&gqypf<7!fxk{stU7a zEw1~;wFc+`FjbU;oVjck)Zu{Dfu=LelUA*^F{5pcKF=hgW4BD$lCQozt0F|IooNUi zwtL`!6FF&~ZushP{E8-wu6VY_RB3kaZm}tLeK+#SSy6q9jrK2>#K?e8SwRS6{=OwF^^eeh2}numyv; zw}RJ|)b9#KjE3M1$t6*O)CZU;GMA1 zdw=r>QthkV(e}a`dHcn@Pr5g*JkdDacjd;_TYtX(@O5P%4q!kl@l-05A_xwKrI$%`vF(OK~h!=6r$Qnio}F_gr#JLkbP5WH5lY0m764XaVE znDchZ&E)oX{dM}3`EE+BTTeRoH7|cXnPr{A)xjIjlb$#y|B4?TyYN*XbMj+GLRV<` zxwYtr`*dsiFLA4GvDtkW|0*K@Wi;=zZoez3CL4Nasv;7S(GGi!CI{nDP16cZ>M*=& zE%)m38;_?t57p){WG%9R-As|(jVw+!I<@k zZx!D^jGs97DoJy4c{Z)%g}0YegHGQGK-Ih${UhS~qDn{4^OsK~14{rv7yu`90Dmwb z&SMXj^Bn2I`hE(P3$7U*-p~idN`uG66m;wPrsw$% zWl?R;Tv!TsjnwP-+rv%?8^C7)vsl4D-@!UQ3hj@XosW0H&>XxUmoSaM2~KQ_|F!-N zOw-F1l{Vr?y!+$Nm>MpvkPOWQM$P$#-rH&l&W_*SM(zF7Y#XatyX=}^@B65faL@TL z5)0e`$>%|c<_2&YP)D)N&X9cB6d0HTKfea{_l5_9{dNLlx5Z%do7O^!LrkM~k60}1 z5`$v7U;f>MNRc@ruWo;4U6Tggl%;Hf9cj)-!#@3P^AFy`$-D1afL?KwWWjzWLqRTvr9frB*hu@cL^xR`uhr>8I*{ zk9LWV<5|^?vmHjGUV-?U@uJ6w0^eo)^I!X{O#Bx?q3tePUf_P{M*;{&BlmgxLecKXsN7Cj3B`P5&>Z%4R6ftAW#H@K^W53M6-y+D)Jc* zpiW?lhOl(j4nP1UzCwF7Ud{FajaA#mr9`vt)xzICziU&;T;BiY0R}oK?MncE@F%bpb3JKogs-WuH zw|Bp>SuI!#c^FXxg`l8~7=h^%Bby}gp-#EktD{`3p`r*oHicoeLK5cIZUFZ-8Yz&i(@)XKc4t>|7~ zr|bK`V(&+{-f@wP;7>1JP699{c%z4U5LHqAghvlaz9YLKYG7|QHZ*iQK|QeUK=2{e za?pdx--z>q`LcZ3I{6b|*3G9D^7^iqo;9Uz5Bz$6U0GKp{QXCyLz>fSRAOuxlKqUWFIyx7tYt5AMss1!b)*vTZOr%oaz_`~c6pVOB zK|m>ZXsAH7D1U~D5QN2L93o6G(LR(gpruO03!%h7f{SHv4-=~NC?ol~hhIm}ojydn zSD5#`?B_{!=s3!IN)JMr2??=IY;* zUo#GK?ibaH0Ct?B!xaor`YAHA!9r#={!6GPQKFl_;`aVS-|hHElP3NYl8|PWWYza$yU4=DSv>p0J4hn(g@ZqoSC=sq9PHqg^hg zV#98SKDz5(I2rWxQM7xchj_!Z=<^p(>@TJ;@4%k1-?%(@F@*rUp*+%brHkfQE2pL9^XEkSO&h2$E+H~vdj+!pi zJAVmzXd!^iCx=LXM8N|VZ_&WU1{#dTzMG^lhKUUblpxd$$flc;xseC6T@?-GHvp;6 z^QI@mj1>UEKS2upeqETf&3n~P?kiWi`ObYsH@)Z8pPQD3%{d;K#D7V=$Zry7p!&m# zdnDdgVz^Lu?8f+;&N@`c3N8TS6q7uQ%|E zCSQQukqC_SvRFwJQ&EhVdr~*-K53)>IU{cXkgcle)5uHDLHJYci%>LrK5k0!cED*> z7*08Q^of7T)Fs;h^g)}O_Uqcm*r_Z*$|2Be2QMxHv@ zxrsuNllFuEkn_TgZ$~StyV~o+`4c(@j}^+dc8DwC5`<3RvkdAn;f$AHLnY>t6H z(d1o)PPHf)p9oT7Zg;HQ08z&UY(f$a35T#m{f4zTssiKrD?sdJ!Fuv6>N<(zOd0=3Xm4`ubnL$~je^3$G zBV%7kyg~qOJT#r;2qv*wbWx(L@54p7!A?{klW2`qI_I^OPOWXJHKCfyrmb>H0s#=2 zJUmBUZBWH<(tn`TO=0e!Mu32W3s9BwwPbrYcyG0DWgik&*(hTz9sLn;^N5iGVz86C zugoXQjr_yhB{ZrWXYD`rfUhhuBuQsuVOnnAEbNG*_o=~*7ypoZ%MTB597ublMhP~z zu86lW76oyV4~aa3;pqCU+u};P|IlKy$KTnrKYLJHOL$Qhj~!G^_L zSI4%(@0YoRKbP*h$vwbk_Yn32vIy;bXjj&bCF9Ww?VU|qHBaxO6Ap|$$F4laNk(24 zD2)ZjaHVq7LPYZ+;%U^@)LJ|a#o0YXFgMYj2$z%26X6p$I7;x?r%8Nh6fvrm7eRm? zRFrt6M8g6)eAXM0YKCVfh^Z9MgLx3ZvBTEQ$x`EW0Yd|F6{_~5*Joqky-`)|y;T`! z%b>?Kd9p!vIM2z?0+8p6j@atl7FX@#Mb49C3SzU% z?*HM!Sq-p13B@2GG!r2VEl=Z|u_XC%YyDX+l+3EOlJKxFyac;XBfMqK6^SuOHUaH= zl7^_{uDLakz$W`RarBUCrB zuim3|8lrF_zWNT&{0btx-0t3CeTeZNV6Yvz#1vf{VLztYFeNkhG;I8p^KGb z=TwAvi-dMTc`zY>#q|J6g@}S7aMLs{qCaJmAcsK=rp8tj;_+}8SFi*$zig027&3XZ zlWqXjVPv2&IAM$+4=(^4C8%w7#i#Qj|#kk-c(Yl;ySAR@(cW2E*DcO*S+m&dqc=m~K z_kSvT&nDc^tu;j?7w;9R`^kpBr@uzOsqRjV)aLV1w`LL@m%BfXKYVcFL;ze=S@LnT zkoZ$gEJ<-!O16F^m7264zNlyzu*&)YJFfir7$IqE;cm4{^7aFvW16mCzRg6O{a$5S z_n|Jl>hB$;5=0(|D#a3pV*?p*p~0 z^YjQSVFty?g@PcWm9t#is3OJ)9|bF$Zpjohp}#(ke4Gg-(w*U;8uu9?8goA^DhKS$ zecyrpJA&XZ=$D?Z0B9~T*Ho&nRg6Jd)OqqB$j*1$eUNxqTH|qHDar<6-T`Fv)FoHq z1K~}qRI}fG81tOHpM-T!R>6OYg5MS_>o^=v`{T6}obf$2-p*KzS06YM%QIgsxHd7< z@SXkXzb@iDfzK~;&QM4x7BCaq;5t^l0BWc6;PmENske}jq1lamfB^&)rIsvZ{c3yl zRYXuuGST(rDNVvEBwq)beK{z&V$ZfFQ?LJ5l@$3#g|os>lgp#Y%meVBtxHE_-@dq+ z!cz@N_ekiv&Ko@9Ob`a6fqfWmm=eu;0yFLRV=2`bQxmVrz%#|A58cS%JieR+G3J1L zpo>O@VYq0aso3IFYDf!WE)O9weL9_FD}>?#!>EZ=LV~PjNPuzoPtB)LK@c~+&Bd^t zNF`w*Cj$6yo>7#T&TaVK2|cRh^ef|OP(+SL*!`dkNSZQLk%<`zc9zcTpP$f@!3&Au zG$ggMV}}{1KW!+7o^Zi`>VK?m5Tc>eG8fgrtE!{$PfIPQp!-HD7jM0!f&GA4kHtW3 z`NRAYs>UzV;vhJA9x5XQLJ4?FaskM&n+5z*%pEd!b9+w;s=;p(Jw&fY&~L9Iq&aVQXgu%If4g2}8iQ1svj z(ccToT-ad&1J z$656F%dYkaBw-ab3X(^sO=sC_org{%;##^~iY$9baHtFqLkb`vsN(UlYSk8pTJP3u zOI=^t-FfJL)ht}S6+%PvNn#Ijtr?Is%q1#KcFy{oHHsZq;In{QHWnB-SkKdjhEqM> z(MFfpqSZszWnzTFe@aqTHLyD?K}`oT6F8esv@kUV2=RMlaUEXqTyhmc1`6SwFnmr= zQR|OEbc|lJd0>{+aMmXMWkVhUK4ubcWfzSP+LTsBAiX3k?`@vh1YoKQr4300B)sFFG{zt3t5G}O}CPNP~#sK45OHIxJ z#`3vpGPHX2IVO)-Xp?RZUR}ikSe#L3YeCO6K>A9w1Qu4J;2=jBDsRqUkm?ctxnOo( zJ4-)&_IIx#*pU?cw=i2)(_PufepVc3U{3u$|G~H_n$31eunX1yVY;FW09?|tIZQA` z5t+dSKIt!?0{^bi7R;;gded(qcpU=YFZ<3<9s62_NYV!!y@)mSC%DIIEe^u;=ie1PqI zCpzE`5kYJSrLb%=VXXTrzm_be+N!dY_c`!1=6OMgJXgvqpSpCPcr!E@vu#65h4TP% z;XVphn+{1I3*f zz!)jZKsX?gHDA%k=ZZ5};M!djkoyuE!^r!xx>Wfv{y*MEAi?W8T5kxEyvkx7F?Z`db^XiWUN^i1cX<`Lm@wcDf{XFXWc+O(>2+gpEy*qcHv zp0)HkPG&|*8StdK0L4@%P&RcDFiDTX{AkMHmrd)9TuX-w4Q9SG^h-^Jtx!uqo>X&4 zD1Ej`j~7C-L}0NK>C`4Pe=0KbZgRF3Du)Zp#VKK>qA|+R#dz{HRoLND& z{EhG@4f-j1LrjHMe+A{@;n@Z6A?cg32vFK5>jKMI#Lh`_0>w`tE^Z~Ra(_{UfrHL4 zH>`bF()qr-eFBbM6MIj3x4>Y%^r!S62*;+CbEh(|Z!7aker5%VOLKdK(q%yz4lD1| zOmec<=zaq2VYJMlpATZMu~+Q3;@0L?s%QK4g2 zshHRAZ1#}V5^2v(?yl}h!LF{9B1|zOu;L6n7UQ#zu>ipsD-?rxFiagn4VF;jZ}`rN z;Ibdj1NB2wl4W@((>@g-2xv>o&rHi)dDbH+KMwNj0YJPgPj$XRPp4~8(V#O`!v}nM z<1isn*RMTwpU}xMzJLzcnFb&AI$+OLAG@(7oxNb>7#QoJDB84KRXG3p&43*CMsC?L zle=c_?7y?eM&GrbyZ+<+I{S~7_`Way@#VhrElar%Nd9)(2~Qu#o(MXMan$-AoftlN zsTe!@tc&pXllGn=1Au43Ud!_wXYe*1%us|vgCp_5LExrjxge(9nPUZ@W>56Z8yZEm zAck`~%SC|z0drh;6e+7rN@7ep*i$2JMf;4ABW8%C9h;z9+i+H*zTO#Zp{jp%+4IcZ z-GK@z!ZTe07a)(~>=Mi5>=M(48?chST_y#^AKg2l)IE~~H=p<`;4>tQ5FY!HJk*69 z+p!*G8Vn8U40S#oDV7?V%gUQF5h{RVHLlsC(Lz81f z5Wq`0_@utBRO9PF|A_~5#G;QpwO4cFGcGFmyq;V=?ybJIG(vdFiaYtKT*5f_Wnm06 z#NiwV%=I4zS-KFPA?mAp`|)oANfUK0qlAbdcT6%Fgm?|6fD&CHg+2D@Z1pAEU2d)JP?rR`Rvv|F(q*S+H}s;YwPOEpI7QY&3B+0fyvV5xe= z`CsP$ zxsQvoSFSrZziFWSvvQvcm%B-~)BaSG!!y4BLr$z%>nH!0&_U0lHH#Tp!f)Ezs(K>#CXCW?s%)iLhweGvtH7EIm&Bk)f z9ts6sqkaZxG;?qgOK1se zYVPUt=`PW&HwknfP2*f6Y$l=Gi+~U&j4NnDjimTcZmE2vt9e&MNdia~Q9KLlNn;W* zTm+J!x*?Ut!bz9_ct9hkLe9fr9~N{Ea8ve~qlRuBX$(2X*orr2XhLf&2geNIL))O2*M8^ zxxHRE_kJm2r>dixAnO)CstMr}p{J8=`?9nLjwQ$!L@sENuq{?0%i6jDh@vCM*%K&$ zNWoIaC@2nj`DqvZDzWdv*0}WoMn$KCxzn(L!(u4E73bVl7H}#GuS! z`~XD?*~t%6guzRL4~n+|jH9{?2~jX8#+olUIWN-9D})U7PY#32QlkdFX}!!yYhMp8 ztYi$z43!Ep0oTGm@U{yX=;sBPJ%!IyWmzkcH_aq6)Uj~?=PLe4qv?BehoaB4f5?3- zTz*Zueg03_j(?y5Ha4SEJW=N}|JP6(r$Skana+N4ynR;df@&2!BDW<&tXPw{aDFVi z*vPI1SGLKXwkYy$*u3@+!na1pi{^GWQ#moivGt)dDIL|SW=eFvMm#F;9d1CDXz}>N z4W8R%(iC3~Bt-mnkSFT2`E=llS4TV$T2=Ku^_7CGvU`)m&rNrw+aWQ3UIur40*!$B z{pLF*OaW?`VnzFbI3YmO7{Y50cJ1EXnu#;}1hbMMXu$ zje8roXM*C!J<`;~t!3uQY<&Xv&Ml4#N2X@ws;sQQJ&$d0GCdQvir`xfxV)Mw`muL@z?NfxDAZuUg=at+m?T>_9?F1dvNb3R#sS078#|OZ zfmI-_0SYJyfanw=9zfJ^!v+9DP7To+*t#@Sh?%RpZ;4rc_N<+U+>cw zJ!GULhajjYZI4kqS;i=o6jSitrEJFLQ78l^H9PH*75ZqoZf|s(Lmw(5XD4gb&?S1i z|A9?X*J388{E_v(Vhd_XXzh1B9(BoMuO&{O@O$I6twHJGHke_tY*Fzb7fBGXo@@(3 z%Ym};{-UJQs_iOT6vP|AlP!WQ+9KE|-6e@VQznqS#(BdY^3s)p--sb^`XuLkTz0Rx zB0|!|0o6TAE&{d)H`5)D5Vc?&H5(M_LT*L0@`Pq$c&LHti{I=2n0GeVhsy@GezGbs z-86jy%OAa}A}BFvY%!RiJFZWI93x7HCV5RaFpMS^${o|_5 z!r+Te3Tyat$iARVjvgXi#Uc{-iZ>M(9w#jZ*M5Ah<(%e>5pfqD(+a-YX;Q3& z7#Gsp6q6TDGl#%KMB=rN0+=bk3F`ypC4dKEVH9gDk`azo zqjY0i$7FOL_ikXVM$NEo$%;4$`&@tu7s@2qbU2LVKGeYt!}}JIi(JXcm?iIAr8jkIlfJhK56hII^0m_x zQVV~Crb4*Dbe4uB-19(Aj!}kNj}|)A-vCD`J2eQ$~o4B zK2}+N(pnEQFc13oLs46;ZN&u(GTUGT_4>A$-`QTi z#_#S5shIdfLuY#he@S=nyTt>!9Wa|-kyHpMO^82bTL6r@34t}JQEklP(&JzXHAYB3 z3@5@1oQvmoLfVr`)DipwV?vxf4Y~3TIG4QPN+fjBT|2i@+D~PKDmy-a0oRa3{j~y6 zd){7+w?krSL03M8yJmO}=2?7U9h`k}y<^Y6?8ReN#y588;yZPirSr|;+WDKuEH1GE zmRNtuAmH<30s~uM9q59JAy<b{n-&d3!E?$)gj zN@EB;nrQN4JIWQ2gTpKBUNV$<-rA!kR|%!V$3GmuH|kxq>oIiF_l;%%YlpSV`pywm5~c^K z^u)PTT*Pk9V%G?`x1hsEerl_TVf%swcvJd7sj|b#Zn*8h$!d~A7sXbAS@rb z6+^~r1iBn0C%M7speKY$&h5@ZsQCSa2HAEamLqJ`6iLKy|LYNoHJsSUz zTfF??lfk3nzdbG?9ENgdP8_Zp*WO@ntXV0g7%ICimrM`QHVsdoY`ur^7ug0gMa}o= zfdzW?M=sE9^R;V2s+5NV!}H0%bWOdq6k;OK)$(>LX^gN1_M(3$)1tk6U@;aP+94BP zVbf|;%$M#~0|-eVzK>ADus1+skja?<-c+|#>8Ca)zmW9dhH)k;x7`E+5`NIux4iS! zeSgWc@uE}}l8~OO&~Kt%!D>@c5P%4Vykek0x+W+oP_9D@z6Akchq zW5&ATTxIoS@xXecgvh`x?I|?$9_ZsWBQDu6e@~Rw-k=*yOi%WBca91%}NI3cs|4Buiham4e4{?$c?fU#Os8j1ObCHUkRLX zf-ATb*@_?GtP~lJgrWurok&jLR^nIgj*!+0<6jvFgz_sLqs-bO*)k>+*%oNLJ>v7e zs_9{md}~3?fN}*U7U^4-pD&gpz>z6bSfw#26?klpEtexcxIFWS^c3>OBCbf42AmH% z=IW^f=8rltysJb`ds}LF-^=6slS6&cJJC{kqX(5&-KD1?#@j|-#b^(X)nA|TYW`jphL*hM6Xo(b<2p!^q&ng zVRm36O92C96h)k@0ZJs_&Mt^pOjQa*A;g1gveFJRShEu4}~c!i)H^O2+}N0k|YA*1ByU5H2OjWC_WOn>y`?NPB*$w z-toBohoCZTW)(P`P7~lRxEy`Mj9;U1`f6QBBY&aat)fS46@8P%nI#;mVRGS5$>`f# z+b>>PWc_L$SA5F$U%JMA>wbqlb>!fog6Z&i?1mykcxEaMk?$dl32n0$#}?b(?i000 z_m7-UcAbAFroB;qc+%|;n*PqLz!IFApCTZOKh4`s5PJPSPvCQCyVuv4e%t%ATf4g# zg(>%x*N^W{kvS;X5D}6PWnv&N@A>Wsc}UR!>M05IBB{i1`*C9a}-OPdn27_XO zC}K~B6;d&HT?CdEWY~tmb;)6oP^=2K0Q+2*HXxtX@F*_~mJY$c=@#X%2ldlBJ^hVy zT9{(({f;8|*@YqfLu>&)JhA`dRenLP!d|@VzaTewxxdZvamv2^rgzG(uv1+2!b{pe z_!DjpyYMb$bzfH@qtByFnJ-m2+e@DL>KxSUq6MHKcmy%&o(w`L6vfrbe6|$gk9hj) zCF|3SNg=kD{Mk51rBB2;({NmDtkab7kwk&t&-^iRO|cF!viz)4>2i;*_-XU+5qd%5 zH`?r=Bkraib|<8a?N>#kuRaZUw9`l8^0Ixo?D05E;nKll2>}ZL&;c+qSQw`Tu#P>4 z?iD;(H@ZSf5%kAm->O3}QNP*Ep>1euJ`Akl!A(K{x82A|pt zh?rhT;{92Fv1U+BUZ5@d{8Ozwao#(AWqzGPa#`WyYZD7r8~)>H`h?wYKHw>S806 zUU}oY&#UrvpOde|TY`O;is*#a}(6DDlS9_4&5plZ@NVyO9tZz z6o6dQL;)=>#^{2X$g-E^H<`$+I{TG`G82JnyK=jg67$xRsFkCVRSg|RLEGe}3;$K1 z6+$bOC1fgVyXEjvYOiJ`+91oUg2aEwt@6)?kh+WC`X=~Wc3H%0*rTuLf9H?YWcuFDS-L-wXi5c$Acb<@}7mq3NS?sdbfsU@W>jv>NN*)Mu z$sI#HuwA(C8(7%nj%DmGqU4(Ky8c|s9f75Tu_BCjgbNXp|hin1=JDm7zK+cnbmbYWQTuRp?m?(v7?e0`PWO z%xptn-9*=wCy~ch5mc~}zfIYI$HQ4rD0lK$7e5}no<8@DTxp@17TP?3a%~ZB7kr0k?oN+RIrH2WOvfl=BdfVKv_6cCFCw62-yn2 zC~nkTOraD5FxXT0a`H#v-At*y|CAMQjamXCRcAvNe>Kvi1?3F2qzx~4M1qdYb4c>ginK5&S}u$W+>+e7>7o@xVHN-?NGBSUb~}&u+)QP_ zn>BVVG~RsTq{=s8QP#Nhg;&{Jd@=_&55GQ~d6FB|ibByEIIn0c(8e`s4N_Hob`3>b zXb^EmxCX-R!EG>ZPoQFE>wIT^o?YZ5^;Pig4cRIZZpdU=`j5!Ofe3g2ml0nK)_Csi z{`pMK=ain&XM2B+JnuT|njZ$ba^T&v-U>7T5uDawENm5o6->r7kz`*wAYK=~9=p){ zE%(brNp9ux00NK6aSR;Q>+^dPSS%rLmAp6uhpPlpvr_Y6`l{&fNn}?t*N?D8)zX0h z6HW2_XBU#i@=^wn@CpsnsLufEIcE8+o~9}NI%`DgxY*=9`*8grnL&f~6!Y@Uj#J#laR(p&+~~Ozxs(+WRHU?TuZ;Wr zl&3H!@p{$W?u~!QedYU5?Pw-3Z@d}Cac1*O*kMUc?X^?es)P$jGS|4Gn*ApG4gDLu z7s|TL{=$Af{9-XxH_sp3IG6Es15NgHjX0{r5vCj*C=S<+h$UprtU_Q^j{0971uC^< z^Q_rv&k}3Q&sWltZ`u^Ai9kPHM~q0|DMnn)S`6{0cY~(Fm_w2>uBEGwekxsi@S>-7 zPG(jtM#KG}6(ugiPDO1+AoWak>5AD2ZRyVeov)O*n?7vwLk}g6T?PISm%I*0hD^MKX&TMc@gIK~1 zEg)QU42{{E1O?@QlL1{YmpYxntLrdwG=X-cW{MB$qVH5<;j?C$N6xq@pwRV5Pn5%F zKfu*a`DkpU7uk;hRYK|6uGM?xaHVzR?UbJ`pt}>mn_@X4` z+J!FBBQpP6KlaDROYAe7W*2~f3x~=^K)C}Y#Zp1mmfmQnNU%*!8G)_=SLz&}c^uhl z7}!+?uUK0}{ILw=`04YW>S(K!zN1vCuAA3SevtHV5>-ofbEWOIy}{Rigq80}k3M^} zp}nYj>LHz7!nOsa12F&$+y!rdy1>(gBK(z#lgtMsGQV15TSd}UV{V7>EMgl}($H&I za=Qx3R09Fx1z8ZtgyU8~!`pK7F9q^uZ7CWB4J0&W3&0R3P)Iv`gm}Oiq#(#*`^18Ay&Y(53llHLzuf&Qp@e@$JI+EWrl4OySM#1d$5sIAF^Bk5p4xue)<5Ld z@(0!&jsHbC|5VZMY=x$1sP4|Gtw^S2{^D?Veb=ky&@o4nBX6eg%6xZG)w=+Mj_?1L zZbnL&#ezO(DgL-*yM_sx9Q~CupbC=!2RRX&enx&h9&1XY-f-#L-@Crov`YUS-$=kVfQLYU z1g>IGrhp~fZ(`blWL_tjn-VWi<4r-fJeB9%fk40r#4;!fvc!z)X;olu60y8kLqyV; zC`Yb@)L4Zaq6Em5hvtI7RE*(-Lbi>b@G6QU9f0%n*>b2+nsQxuY9IOW0KER8IY7%T z4>5V?h8z>;J00V}(MFsWDipBQXMK5iU%@8*7fB5TB3xcAB($jxs)`tXH2i@~PO0_Vw`j#MEQOgf&r>V z#1Esrf?=V-KHk)<4Kb?jABih1ACloha_phzJ$)dZZRV;3&>-w;k5mx-KALYMfR+ia zN9);6(dSmpW402JT1JK)|p)(Y11r2{9~*mC{KH6%lAFj>JW=HxY+shHzAHS%klxm-nr9x z2uKvCK1jI-t{~~cM^br)8t#Wbk~yI-WrLpJNdsiURUdoCFmycC-NUZ%r-aydMk|Q4 zS-GDQ4(-TxFLpZ7giMPoEG^&j{VAj#_@Pb1W_3j!EF(Ju0O(YZKqsRci7+lom|yhE z4dc5o+``KX6j6G}x_#vXNCa&~7j5TQLyik;MNMFMs{Z0hq}U#|@k>1^B1U_rv4E`+ z_Bh_x99!>l@7A{&y`y3^W#m6u6ml{LXApIUn;xTCEyKgzl>gWv?A!j&OW%L`_Y;r2 z+_zzk4epn1R>YVHi-g1$kI;%d0++`ow?@^F0D&xqm4PnvU#~GB3NK4oRA{GwmbGhN zVFB1QIEMWjc#@C;7xhe>OEiMPz+4grf08jAR4u`CM(HkN$A<|i&b3?J`+bs43elh< z*SROZMuTtHebKy?`T}}9@hpQW8Qf8Ad&A0(?~60*_Wj+4@w=DsxwompOQ|Kh@^v}! zqxp%4ijUSaw8%sl3_|`d@U!5c4YfIF#t!B~?>l}VYi;=c0tg|=!d8#OYG*Lws zs)8-h-RKiyTP^@;IxsMkN>~vXl9%h zk$duazvpy@|ALO(%d1%da>wVfcN6TF3&Pay80dEu#H?o;s$F2f0R)7R%Uo>F3uEUw z1#O(v>S1IGNe`rHlnk5}%*nEDIB8dg?3!{vtA)&!o#A@cb+XMIn;4AVu{Ef*X-&yPNz;C zyV@ZmLo6;15z-~2mE^XDHe`6@pt zQ=o@|7O`YC{syk>t`Lf0gSdQk@Cw2j7f~PB0B+b_`iI;b{vD_b1M)~bhB|@<&dVPY zw6oslj~edrQdyvW5i>lSYiOSsLlRy-Gl7SLJyE5@O39=ApcG=|gBn<^s^6IOP|71C zzQAVARrjN1OJk>IoWq5(a8((Yx9%oiuGB8>$vxikH2;Elczy0f{MLU{9&LYf$5N)M zyMrd@!|hN=t7mH+*RH$&R&`AZtZ%$|=id2stUS0EOb@_@!{qs74RDi>de?Lvb!0q* zJejw2M(~z? z7l-giEo#BMhkCNO!*1Pb0X}L+Gx;uPiE=_ZX3Ggn4BJWJQ(2 zro6HD^_M>~yA&YEkPb?FxVd1MF{X@(h?Tw=TBKzr3V=};DcHU(zmeqJ0%VdkWf7~r zUjPi3+~VHF$*6fIc(N-k@<$Ds??uE@veeV%T(DRO!vbBg91awis_2urnw`jcCv|_Z zR#MA&lTP~-4c$}MZC7TAKeBo{c;F4!H+K5Bv3|Kc;Ox;2nYZ60*RJ7C*=dRWb^FS{ ztIj0zV}`pcR1T{W1#C3C2I?zO?~4O;(`@Ygye1gg64RC}#|x}!`!+k(__x+TuvmCz zX7D58c)Cmj5>3M_qs}Hgyd133aOD_hZO%i!Yw=4|(2~yeHx)+SrUDs4O0UzEsLsTY zhf(_emkT@&-*El>C?KHl?`@4EYra22h|L^;B$*$`B@-y^WJgeSP-5*tj7kn7ND%!l zghrW)0B5HL)n%+imgooKYdw`n=9~yIUIZr>f*Y2r3P5;z(Vmv2rwW+xUOc=9Dm==zrMo;aJ|+_~}BGcK0Kb(5VD{QvVui(7D| zF3Blt!!K@Z%9Gv$M5L-=>5=9X1P;}rH-yqzQql(e^|Z3-p#GvxDA>5ygCOb8TRhw|SxTk|Xu$bgH zJIk=jtp<#!E(n?g73m^J;)TK8?>RP1Wj<5w;;pwJz5d-bHVD$_$PA>cn%3YX8KA<&f;QR{*}Qly0fepyLHuoqx}$#KMFV7Y?lTEExoK>q zChAK{kJJxUaJP1o&6B>G*0Dq}&f)}r11F(3^X)4|u40Hvc@09IWq18?ept%A+NVFO zn|_6}HV23j#`Avu#~l{_nEylWQ-1m(^9L#H^dam+IOB}9lMm?F*Hf`mo2Cc?hNI;C!EDJd(KWS)`%xNZ2)=0IzS{d0b>20(!GQgg-r^+?VSDG1c(WJ!{4m5< zhtGP3`dlWiwHj{Y+zjqmd(8H}V;!7FCckn#Q-y@M)%=j}iOVNR?;YB}jx~hx9%Jo^ z7(QvMZ#n2ua{tll2og=OmIJuP>^4jYW6$2WYvhscUeiYEi95V1%fr%=e|R*NyiW$C zXGmL|Qm;U2xjykB7YlWdga+W9_sfs0sU-#|Ra|+X)li@J_TzBqY1W|H&wpx3fblEV=1xTuE_MidfAjFQMUqmBaGQD-5{G~iHH z^D6<_%6;_dnz|FTXnFZCL#GT*CR`j}!40$0P(izVdHnF=LeJS)JLL{u}@n z%^lUjeRLdk_s&DD<)8d>Wl%uUKwsCMFSB7K^;0^%<$NW5#{R>>s-c9KK)|sD(j%h$ zYcsnv-4gM5Rf{~%_8^mjoNOEFqivi#Y0C;;_$F}1QV#1WaO6ixpn_ZZ<5dt+Q9d-6 zGbKQflfak5`IRVERA`XWH#U~vR#Y&3kdEkN?(%)$kh7G|bBO38gp9v^Qfxiv;dZ{+ zG?>xmEATq8(HQJ?$qfFZz9&MlSA{f8!k-HPO zhQ|L_8gPqTK~y6VpNexrz^%@><|FbW$yGRaJsmSlyuP$luOO9RI9C@0a0m#&5vkmK zrCG9*1|&(9-v;=8#l{jnD%_}01^t!o;*n{V)0zHo!@#b>FYj9^Lm+eKCX*x8qIODI z-Y_?3#6*d=J#{*!#hceU!C#fN_D6WMD1W4yoU_YfV2c5Vg$m^*B6}Wf#c| zEoe;D{#sw`$#b&tphHO~A!%D%NkCuG4@X^Ea;V)gXAN~iljh_j6?B?(R0Yyys*Q+z zhtq`1R*VBw!lUv8HXYl>?-6}|2^9sj9?s!Pah4kpbyd17lv1Zzu46HHulMEc%fb3T z&(zN5yl*;lH<+7kh~uq==1KQS?)WKw3MH0$Zikiv-n-uHkA9N@pp?iMb`M$!fbw!6 zw{Dkb%OE)&D=#=sbaDK)bR)Q)pq_GBvWu|;pa%;ppPa>9sP5ANZ99mm*EE7NRmO<9)g#he}dZ@<}VUOMKoZ}t5K z(ORJkGAgH#t;P4w=7R>sgS?BlT28oy2TjATo;^fGoDBam2DVQ1ZGlz3Yy<* z*^x>_g)_FIrg6I>&yA~?!7qMyF1;lio+^Y{!#83{E-p#By#-D~Q6mgO59PW$NEC~a zSHET|j_sv19~$Q(KeE@Co%nxt@VQog5MmF}1OrKiHvZL{TfN z_U5>a;AtzX8=AUJ$6v{Rt$e2I8|itcJ=s@I_L>Rg+F=QM2^RE(v(~rQH=MRz#*I4+ zN5q)MkJA@A0@1&!cRqDQ7wY2*fce_yONcMfbR(8>!njYlyv`S}Po{lj^R#UV@9TLl zqspx6u+Fvdt$rSTRC>M z<@=_k2YLlO@LwMq&6ZU}e{ny2c`6xxEKX)vBRg?WUJU(=zAB)4(}-GXkYH2MMB1Gn z)r!~hD)Kcx^wRmp-l-HX;+(46te#zLmEU2%VG7w8x{6@<1x+P?9F`voYW|cCP*+a; zSE8gTS088#`Osq{#0ivqwg%fT6^M~lkb*kdAP;0C0%h3@C*g@k+GphoHEf}G!)AFy zh;!3#_|3o5!S zGEU@yM5;5Dx%DyyFW8_?T7jjfMC!5cLarD$U#j~uOnm*v;AF&_hho6`5Mt8PdN2R9 zx=7J8czmVwG0aW$yhgI4T8Wh^CdP1~=TgVXmM+=E_TG**x8}*ew<{b5`t&lxN>m(- zSqBYA0z13fccw(e9{fXYh5tp0TXzaOeQ>UA#GO(y75l=L%f+r!dujsu`|>j_OV!fs z@2$g`Hxzj+u!032sqQA+*@^?lL^YSte3C9d;$dwMgt9kovrS0_0;_d6h3AgRk$%=J z{a09n5*HLx*L71c3ZLnBuYF6^iXtMOT5DNepW>AHE(-s45U`Nflj^(U{iNe_&74KT zIgq94l3W@juhdi#1~Q`1)X*G|2h_C+v~SE@^VT~t*|w|}GED+IR+IAziM9_%Ye8w9 z&>`&hvcGA%N9$*I4A=m{2#e#1A@t^cy_5k1vHv`G)*tU zX|KpmiVs#Ft7GIf=i^s+-;S~AbR#qRK*7oQte(Md&ArY{^VgFR=VJY;`sq;H#Ahu~QgKHpV0ULldQsIh#9>ctbk-F)Im6~R5nf22vo z6W2>~i7T86cT^NK!huQ3*de6P(9F)+mjtiH2QVAv;fKA%KC*+ z%j%_`{8Oq|lxNL1g09E)5oUNAc4xAcOogV8Ixi~+pY`RdYQ3uBg>Ck0a{uU|Q7h$n zEJ-z|=Z_OgQ0X6XpDxb^JDNb=o+JAY{Q^bx)cg3Ge&*j=C&b7s656uqRqrK+k2p{A z*s=Bw*4@VEeP>%^$64!K4?l$O>zm#Z@CpTC4VOp7u)8%&Li$p|LZ@q9wx9lxIo0Pg zLlYO4KCL9z_5y;EIC;n(pF&$yLZ#^m-Enp@Q=T5aTQPj}?r4S#w`ZiIK>+LFREY7f zkQ58>m#Sh+(Trd*8lb3B$N(pJF0H^%d=k2su_R9WLvg*F3_@d5xkj~%NX(0eY-C@D zM@6Pri?$$Q#J`+FGL0D_C3E+pqdN(=2O8okE?d7hP!uYgz3Q(y_hBkH_AcZW;#Nq> zuy(eI+YB)68gV(n_eQ8rQ=$tBw_%3CLBKFyG%b zqXGytXF4ZcEwBV;{hkR61_(+vi;^J;CkD95zhmYoscq>Rt<8aezOtfu|86?JQ&Rg! zL8N5VqK?)!)AL#WbGNrWWk=N0V^i*Uq0V1T#{>l)_M7za>Pz=%e4s#_oyzIUBn;{; z*G3)jJhJihyVJM&Z5One=yi98rHH!v1g?^ye(UlVr^OcR2cEOcJeu6v5uEr7J+Bt? zq>}6V3$7#b@Z@n~-I#vw&@-EWA*Bi%5$iuU9&LzlCz;8DPAv|8pr)mXLG0ZcCL|D) z^Jc!rN%vf`9^@XBA9u2qiu3MTLvDAYrK2D9Ez ztfyN%ROSu6x!3Q2F8fgk`xvsyHI?nk@?^bttxqIi;N&n+gUFC+6cp1y53&)<<}AJy z7F>a+jJfb=77YE9%WYiV$#Omnd958&X!ntE-r7lE*YpQ}#Bh(#jjPPB^<5y{){(8U z96*5{nbp&VbcZxmAuTg00)o^YP|$35FU74yKmm(#R-&)fND##mp1KJu|1_@m?>0Nt zG%lEP@!}Hr=N)u+(GBGA$o0ox!XE~FC&Ec`@WQcOH}wEY8x_r+$=GCUYWK`xLi~=02`842Tfx>l2)9sgwXi4@x>FFQLu)h^5#vtF z6=8SJxsw4P-e+O#0vFEPUw~)k*fx;kxTcQem{=Ps^{Lkb&vvOvp9+1ic$LTEx0!Xi zyl}%dPccf-O5N7Z%jk_-dcW{@g)D^kq%|8|zV4bukrmd{(c|rY<1LF8;oHTvV%?nU-g2m64x1e1g^|&D%DVm9oTV8${tC{) zug&G9>Usy!uP=uX~f;%*kV5C>#O#8 z%!Vzp+=6f-XL&}aur?khj+U1DzVc`Ika2l}jj2|53^=!H49vhYGP%Zh8MZwWrejAV zfp06tq&fG^Iqz7+_IlTdsmNILDwa+YRCg+dCij9d6JT81W*26k8* zb);c45eFTZa_kkLSOR&n@;(B`67^Ij>z9l(>gVfVEXB-nrMe%@2`I9Bg(S`I7K~D+}@BTcWN}q0OoCQ`5)-ty7-H7A)(6y0}Y!`drUH zEG-IU4sA8A`~M4aU-)5GE+&xqbL6A7zl=_(YKejHpa1QvXXWUiw(LyBjhXbWxr)r^ zFI3(buy*&vqo%!G$QFBJLSmg$IEjc&f<;-^W>nTfxwFmn(NN9u?xQ%K3+~X7KJiBm z&Ac(BARFnsc?j#!un)n16kW!{CNf`VT^Tg;8Et61bwl7FOv>j{({B+>QR8Eq4*+!+ z3uOpm;S@MlfB^x4@qv;Vwh;vzYIFU_3YvI;NC_O}uea8p8DTa^8 zlvdyu1d38}pYQR2y~HeX6(Gba0`U9_ zXyWOmZ{3i8$bHL)nX!@E4FN$47Fc3Ezn(9lHp>ENDl#c-U^a9zbVEKf*4 z3-j<|q&T^<$afc{(Ik`HapOcpa<@Q+Aof6mj_tPgbl|sa+f$4Txe6n;4so5iCVjhe z$GI6YFg=PrDO6+kbIPpit(0eDz|y25e-6jT&qnKV(Q}n{FnrC*#kf z6ILp^ert#s7&qOb2}pdU;R=(6(PICFY}v-%J#3SLL8?X|w74}MWc3a-JfwMGWx?_} zXGGL!F`dDUOPLH}kE{!KZbb6qELcEhih%Kz+(>`h{l76Gu7huI4;@?{51jrZ1hM}e za4{}URd6o#?hVbdzQAXF#Vi)0YuVRQXHxF<{f1TZK*9dVk+CXUMbgk?xJ&zw zekF7%r*=nzXWfc6|Inwh1`!Y4zmE4>D%EysA8h1S9{9_h?mh5*?{(&r9!`poAwz3X zLF$gIkno0BE6mFln8=Y+mfm9UvJl_Y3^He0^4CKO>4Tu$uQna(*)gw9*D&?MM$TPO z2~gBDcQNabV{L2Bw79WuutV(G)4RLI#Eb5YHswXa^ImN0gBbW26ragJqsI7q$-T)9 zPQiTM&x~w&{TRd-T-&5bFmxz)Te5r@@*Y3+s+1!B8$_tVEn>7$fta8VM2ojNb<$Kv zk2fxSqEdX8Zyb#Eaz@-puZd0f9XKr7+%C)(vaa4!vZS!u|R z4wI}}Sx{3=JY#jVPJk4bnV>YjI&xlMD5D4^H#}E%=nVbR`p38zu8NxDrtaTM%5pGq zf9XCiQDiNZ^am^XiSy#p-;iO5OX<#%mp`{}s6D4LR7|(=XsSx>O+F@pa!*u z5i*(whOH@A7);B!K8ib(CwQ|IlsW-&DAXT2@W@#5+OUwwxTYfFbEt!pn*>R&ANP0* zx2kZf?f7x!g!L^yi{aC*>acRxksETH7DEqT-=Ys3Mqa3-=UisfqPDp@VmWxzPwpO{y{33>R41dgfvc3x|&jbEW z*y(oLkT{w7zK~CFt9#N1bxQ0(udD2B{)v*Wmpf-rP-5~Fk-*o76Ls~@FRZq{;xC%z zdDEx0>Veh|`{#t@SVc>@GK^o%#WnR_jjKTlgiMg%XU?pfh1Q0Q+>iis)`1=X0Fo9E zDp_8ZV`<_G_WgsDOOQd_rx+ne`cM5@qP$eiY3v4yGwP=n0HOwEU7cdV}laIaK1r|iONOcu05+bWE&9?s*Kvd?}wnz}X9 zRlELN^ayD_x3>>!uk2U6KD*1Kwobg4+;lP`HTT&8cIS`Y9l2xDJ2%;*`$+NL^Y1Dm zOKoQ_vJS}iBuA###}<#-mPEo!c1;5%GLId4HYye)apBP*q2k|Uz*#)HtN(!0F zHDhdCyHF=$`I}_V@vCYe{NwBGqqDw%WrNfDG?2o~y?^PU6lxB_RTKOuTj^8ywdt-u zVIIlS*5gH%mf;UVTu!kJ>-_a%BjTikUBVSK`1&doi4iN1KJ&a%XOY}!0!jyItB;Gf=HGNQ zCWhSCC-d9#40|Uo+CY_K)12Vf!Gjg!v!`6{6c+^@ z@tAbEGCIAUwANI4R=eDwUEV3Zu5chbf!n0l*|-zJiz|75Hvn-~eT`07Tu&}z{XOhm zGL-+0;qbppA2#{Rv`G)E=FUw7aW!&81T~|X>>5erIw4nv&e6e_m0f(=NY9n`A~EFo z@6ah>uQxUh+b}bu8VnKN#+BMY0p$3)k)IB~@ae>v>B5ChDxlg`R3C8}njTRrD^hCP zoE$8+qbd1RZEBlaO*1B*JKs?}(tf4o1YZL&1kXQQqhP+XPQcR1B8(_!eO+X^Qq!sHx7oykZ>RRJf=p89D?)^kt60r9yhY>2%#n0j<`m zMPvA~V%;bD2nbxnN^dR{WL17qh}&0Fue39zZ4=|?Yjg;{sxneP$=EHuz4Z63OvY~S z9;EuP>J%jA!hBLzqgO|OQRnE5x{9HR#{>~U{Abh4YZWq%)itDvvWUV90>ycK>HfOp zqKlU=y!)HRRCNFg6@`kRhSLoVpi4BHE7z`z{+GC;828j*m|BYtTNLK`(YdF zs5@D-i%bKhkdPqX6e=;K0gRHg9?69hp@JGJa<$+JT7fX_jbL<>>1%TV%gnc?LbsG5 zyh@>#!V!HAt1ETj=8@pD__K=cmk}#LZ?bPCB;MxvQf40D(5r!dX;9j$GVdiM8Yb)H zAa*-BJ4#8YF(d8nWS76kP~_5NLor`#8czI#jB?QR{;`i{LjOn7S@<=?^#j*t=w1*8Qe6%_Tw-}^s&KlgQCan4E1Q|L1_ zzIZ=;8O)5|5|kC+rN{8Eu%2fm-0Ac^PJ-PPx#$Jr@Wc3M>_*?;DK z*NeY?K#b_UvRa6CriwFP8;OatBX0C#5AYGUa3-SAekSuPgb3=6EaYELp@oo#P5E@B z%Daqd#_73Qe;&=*-;pC9n&WH@mzP_10>{c`FH@K|H4(@4H0SgG#s`06{UG1`Vs70H zu0OO-_4vv$xyJ&s235&*sN(QOYG8WOb9lr17epRd@MZbuXxmZEZ#YDc!V(oU5Ufw* ztio5&iDX1Gyr+}5-JKV>rm_^P0+rE&Xf#O@>$n$)u-n(w2sUshqiHvJ_)1q`bQ_xv z+Fmadg@HOLU*5-F>FUlcqU!%Egj?ZCi`p>Oxm{efidp|{3It;irU>F6<*U9BVxssw z;N8M+E|)%DgNNCAFZrO%OZ(>N)2E^=rKw)Y0|UY}(gPPK8wI9ck)x3%iZ@OCN&tnh z)M?B)kDgZk?`B+KUSnTyKTU%L z0#9RNk+N8H<_vrdcM3#4GtxcidC&3%iV8-XmwnjVB|HFZC6E`Cu!gW4Z8v7)QZX^< zTpE~I1>N-01lE9JbL^4hu^<{sHZ5BmxU*&)j~*VGIJ3iDTteRDkh1a@TsA%5G4R!8 zgUO%iRJH=n7f_c5EnO7r^agC}0F3YXPFtdeV zCI2<5A5#5~Xhb#Y@6#gff5=^st}+_PGHf`&ZNRP1 z{MzjX&ab>efAj4LHjMh63`Oe7-mBY}wO0X%iTw(FIiyzdw2vDSaHT}oOcB$n`CTcV z*h)&U<<;655;e@0dWNdVT7!7R<1+Ws0O*m!TKan}Y%+6Yp>zE)?enX` zRRfmRSeQ_9bT>9FF-CIyq&4kFWx9uj@3g)D)z_8b&~LZcXVg7oNZ+Yu;52VF&*B3C zam&j59yOF3?`C#S*e}E=p^ORK z5@u9Y0dbI0P_$*8>S!cxSo1*hq`DvTYPQzcK`OLQBSehC+=QOR2{*tth#lLqY6Rmi z`de}}tulz6N7+vgjTt8L@Yw`2RQ-QcD}FxM-A?G9vEt{k7Wz9?0z8zY{x2{*zH!c) z0U!XvxZeskMS5S;C~cDysU}~g^_hYB&pQ=PjsU||k}=n~`9a)|KSI;fM=e{lff)3E6vn%Ks8 zHqq|1Xis|%8HF4Ss$`5hm%UD>uz;#Sp1mD)hEZ~A%6ggj7H<#foZFP8DS6K~@r)t? zH1W~|H18Jn!u^`XStjzy_4e6s@6pXK`Np<1-5SU^&UoUav$K|Ye5{h9H>^4HIMy`h z%MmU9Yb|NyPA-_`R#Azf|B(BZnP{%ti?tCBH#s<>b{no@ppx2qoCFU&E8DKFB*@y) zZ~g-sde_8ycef5k9|$d-8zSskYOcR zmX@^ewAUeB7?K58b&R2w7oJ@F6}N+We>KwNEFla>1-0-YKW`p}r7)MO_Iq>(Ws9=d zHAo5D)wmSE|AH}uEzLZ9|l#4%N<{X;k??dj)P}(CP_A=LDq_-_Aw{=sG*|@#Lm%8A|VOtWTr?*s-G5F(|bp-L^ z`a>dxnP_W2c%eH!nuy%d(6mS=iaU^>c`=TZ8ycg2Ki%i}->bk=DQe;zHBQUetsI_; ztJ4u&-0$F|nqid$88Eq#?3VZO`#$*DQx2tjnQRqXric}*EeZh+2^s(iE!H^3pq?Es z4ivZof%ko(zqSxDjAPda8jAf;>!2ai5lz2VqroA4f?rR*K}k#!->CdmYLy*G#J|B{ zsP+aDNP+uGWT#>WjpXIT4|QRvxVcy?Prqwp(R;-*!`(jM9TrkBclDj^^s5J~)Pu^P zQ^{<@IuDZnY)nw`_v{hD+2Q8Khy9(x2cD*ubiwIi*Ckx?yf+ikdozzW-ZuS`gxS4E z*W7fy_doum>u;pyk_{T<{X_0632uOa^kc{BU@(S!;p9+7AFO%g>wT zu(R_1k2#qS%KYojk9tD;K^zDRMkPU6zKBrS$fvd)ktdyB=s@QZHvCKEN^#gsfMHQG z#HbQHCTo%k-~P35szW!l*3D0$^rCIeBQ|yN3E0ewoIkb>N&o z%fYz@+i@OZl%x#BdHk0h$gJcOK?!rb%9UiP^`r^4V~IpA@&x)EwOu30zg=|-RU&h; z-`_YpWZMT+ct2_j%T5h*RHeRYwA>nP?xt;{WY~ta`M&Jn{Mx;``?_Jj8eC_nGm)Z( z6h|ulwBZ*@8x1$`THsQ}#%FL|sW_a-PgVc;R#83n3n=l(T`!O`w$RlA_Yn z9c2m{{l8vR)l<>5=7dnF4kK}*fc;)jBBnajdDFFtdfc(*&b}817av;&i2o#-A|7Ng zD$tm={P-DstP3uLylTQDNFO10R{S1*~yEO+O1_-zNFlPSnd@)%%f0;kW3-(Q`!xxVM0lr-A`w1-6ECjIF zxscojXQ`SlS|TTq-0XRkQ(O@Pv!FtPc%XI?k(6dG5!T+x3ofXx`BH*4h@my+%z7X)GK-#bm(XFc(G=*jf_NU>FKbrB77Cy zp&k+oZwZQV74U9=7$s41VY;uG7q0Da%yQ!K^XaRSt^vPL$?~QYHJO2rn+eqxHQaG1 zkdg&Hg^XIV8*BhrT)9fE-9O=9j4q}nOtX!{Q&fU^%oQx>lINTF|R+RNe6Eesd5nDgw ztDiS1Q*?%U2~-%Y!)GG#YCGXRF%!pOZI~Z!>VHN3R4V`_op2BlZ+>J@FGlJv=O!+EF5+!2+6!0(s?&_`O zCl5!}Kxpz5mJ-+y#ie|IfRcSB%;X^v+PXW+b*&riN8R%7(F`9YN^074KOxu$f6^ zS$C=-+=R^ELScRFA9A;uiJgzgZG;=zOo9+J@Gs~?cMf5q_QEOX8!$Qs+-hd-Rl zss3)Se99wdkY2+oC}M@Gnd@--@Z3(z8K}Tc>_TlGWmouD1WP<%q)iB=^BTv_ zddfOeibuvqjDvwR$FHx5&L^*5b?0|8m)a#Ad6HWnkve*H8N#ZUk)(dP3eA1YxR3vm zc&C$~?v{~UBIch_vk9|DnTA}+zK))hT!3y>z z4V@DIP$a&d7zL;@Ven=_@^n-qCuC-q?W=mN3=P!l^tz=PgbiRh7YVSvF&aYGGt_g_ zwen^{oj-9SPyjw52)E1Rj&Ra7#nnEO37Gi8%4Hy+Dv5wjT_1DEnd3~miPEybFLf)j z;Ox1}80}9ur5v4rypoGR1)-Fa;$C8W{JNUD;~;E?5y?;K8{)BPVKsl z2c#~9IZb8t4?@}FVi+(D8gkhDB~406xcRDbBF}t{>h?y$+q3s$gG-3NYR8KYTm=L+ zp_Ml+`7qhqivKGR9vtim(PpQiNQ?Jowy`OP&B$d8QAV{f`CuFKI$V%AKhHUi|HkM; zKc>|%Eq5-wq}Gu5zrsqo8VHy9_m{JtJfd?|5u_V{9zBkNwRG9+w2JXAvdihmrOCIb z{up?;PU`LOtJ=jn@_|H5?_*3pqWDm2=Jxp>!J_qsLV*8pJ@zvaKKY+&bgASIJUs#l z)2Y3boFMv8CQbYWk&Wiyx7LEuTCu!gsUuS^k}Bz9ZgaWm>0SC(C)+Z4Yc5!Q+bJww zr{rOI2S1@A0>s!oflvZQaNtZVxLINVy1%Zbv}LCRO6aV-TGT;S6wY}pAzBdq?g^OE{42j@?41)VeTI%QKrm`f4%&WZe%>-^P*EtOKIS>K;*d*4j4|Ae=Mz8c3+U8n zOA$Nuk7Si!%@M4&OpWxSWe_yBEEKk8X!l}O(S+}z*lK&M|FDPONo=@SU|{Sg-G68B54k6 zrSHkgd#QLyD6HrWfh_HuxO`*9HL=^))*7)esFSOlKHel3rQtq1Gv5i#N(A)ugD;^Rjt<4u=mvwJh`y0-Cj_~0zVQcEMvqx;S$ z_=cvC%r->R01EW=oWWUg7W{8|%O}Ro#vmPGC;S~BLp6^{bdSeAP6zmtvtI5q?m@EBFb z2oVHOP~-{9n9vvzf4GJSkzA|3*MPVaI?|81(yCPdQ8FGf1d6b-yZ7)8GuA}r`$31k zG{N7VFNU;?pC3Z#bebl4aVrr~2_p7;zKC2-QEX1NZt%VfxvCRPii|Xy+*-%RRM1*o zD3fOxXI#TX#8^D0nmAKdHoV%o!41T))%! zki<2ymrNGO<}e0aIuH#qAXd=;yW5e??2rg~(7Vq$`=Fb~3>jCSQI=ute@4r{b7;{j zMV#V&QbM$^Ec49M5FXw}^rJ)O=*9@Lk7IM5wN4hE^mruQUO`^QkTaI3WPBRd9zd`$M(VMc* z>SE(mL9dhz(tu3Ba;d~+Y6;P2R*g0pUG7ql{{3J3HRlQ9r$J{9VUFH8^d}nHh&W){ zLa58ujxZ}_SAF1Lp=9{apNqK5GtlfIj`4)9_IWPp2Hu_Oj4Y2q5a4@sy z<(O598_A~XsP}rDA4G1aTW;)Ukh33ISb0fn%o zMf3BO$0@%b0tMRw%Wk%VAqu9_C=~sWBQ&RW&6e&3cD3Y!EFc@Bvce!VfG?LQK6Wp& z(hv*II4)O54v&rPWxqU1TZq->E+c5^Axi0Ncq(pPtZ8Zy^X@S}=-Tm1#f>ZXY`SxS zN9POYf5@F>;-VgrTMvhuC9Y8`vJ0ARDL>BTlC`|K>?j}7y7_jsmy)IoH9V&FytqT) znA^|W5%>%Sy}H(KfL^z-?YO3nTzWCSkzDQ59iFoDbH+1b)vurU2}@OH+dE5-J$kbAV-69C|55L=K4 zSe`aVJ4*C~Eo(?qj3~{5anKx$vCCyc%K?4hAW)+uY7Sn8GKWADfdGD@o>%)T|=SKz%Fevc5egZ>C4W^&bqYsCJ4SQku72PW?praKBxjM9o?2lMIxV8S;bcRud@vMtB?B87LdTDbYiSWS=-fyv>E(H{Bm#N{Wc!2O=)Bx;g0T>imtv12{eWNT4 zr0@$FEZOaTQ4i5C808qkJ(U>bm z8TMnWHp)qI6v+v;c|xpD$T@oGU{}CRX~s}M=y>!8ti+4YrN>|2`u(0Lkb_NvV!pKE z>aOq~a#xv&vhaVeoS|28;ek7Q9qrb?QE+n1>v~(IN`h|u-$vBNb-dJUCP|rf96S)4 zkWqfBAN~`%YbGK6BAimhw|i0}^RL-Vs>o#v_>h<6jpUZ?Rbf0oD-bN+iKgZSne#D7)ui z`p`n!0lz<&m(t{%3U+=nIhwX`=sO2X0;RUrf*@-=RZftBLIro3Y6x?(Nw+cf!nimpE#W*tT0>=RH@Czf$m7s%a z9^H*9NBA5r5(SCL?vKmmDli#T30ZAsg zU5?O!+f+NFRV@dPR5XG;fgoayZQzBv2K{mc2A)&i+#7sjAF4d8kgZ@#XkX0G~6e)gQfLzB1SLX|)mdd5UYbA#;b$LF=vSaY6JM%geYY7H^-Iwy9yR-_7;3E_{-j zU9TCQPS=8cWlU|AM_N<;(wxkmP|HbdS-eBkh=I=ayDg@DZEJJi>qmFjpj&etE0$(> z8^P{po>NLts~xs^=3R*08vX;~Vh>L|Lu@VIo7!zX=zk~3rnOuzBoBgoB%-UP%Q2pStFV-8mdO;%Q4 z%qE867q|;j8G}Q(wZFt&zN2HD~{<>q1~ZuRR!~>pr!FU@obC> zs|+IUu7Mn1xC$oVHV zcvctW7A>3x%gsNn06n`7T32@=E3wd7u8+@EYGYWnfR>0Evw}F7vWn=+7|E&;XndKI zfbxd~xpF#x7p;~9+|X@gd`^uy&%fH%pRA0-cl$E^sZ^fWJ*QzF1>4n2b%T8Gp25mU zWB1M~`p_mwhhMDjbbo_D{Yy#t04EPV>Gqhl#X4)ZV4N7hRDwV2M-Gd8qRSRL))Ftx z$45@}Ec2$%KrXHa21ibFXFUVM$g5SaNcO?Xnzf%|wm88+UI_l;%Fo0z9rTQsiVO7l zJ?XH$=qbordy2W0R}UYwY8wqadzH9#x~*PzMTVHF`p{_>13dCN?(dw+oJ zqqC{PCZbgtpB&&oK@OInB#|;&`%g>bfbu3+G0{YFj5!z-YI4J$QdB)s&C-O{OC7cJ zUuXYCz{89YQs%>O(90Qc@Rl>71Th(3MSc;!n^oMZ4-sNFw^C7&i#U6(DvWMd5o(g# zKf_-Kido7rX`6VjUerp%CW|?W)l=}t0NhH~ zZ%iv>oUO zhc99}THg(P`x{Nw%N5cb5ik>oFtnNoH`2s#7W)DtCdi7{eR;!Om<${9i$A_dl|-zyRIn9$6zj_v&VDu;jeg@w z-TLI1eKC7bt&C$c{F-uKRcZN~tJ3}R=n^7nejhAu~{H%i7bN}*yE1u zF$?5~&>Qv1icavRs1uB0Wr;-?l49`j(oN~uq}*ubGrz9a@$jLI7(Qbz!Dx0e;-+3} zUu|yNKI`$;Ccsd{>kvQ|0>yqp2a53C(x89v0^^FW^$RHms8(00DERh2I)y7ZnmG$r zLKZrAlgF$B`ksp#kFD!NOaHOx;FJXdBo4xePeX8K@Di>Xft(?3h^=2$h4_G zy&W~b8i;H$o$$4@9&(f^Hkw8>tv*_E2l)Qov-&9$>b^uOHpx?LPcCTW^VoX^yDVbH z@1ka(JiM$nWKGAd9cenJ_c$h|Zn|sI)T*uu816SxXG+IR7Ul2UT>mWYy5$b8np~-K z3v5x72(NY%OU1yFVH=}lpdesj9HGEnd`esVQs4y(60dhsw9Ji*b`A6 zIbR??%^G8=KS%LBlXf{ztj^Bw>zP^aQ*_bOmI38daSFd{46n5={&^_pdxx}s`NQ-(Iv~YO9m5IdrulGigcUs*6Fey~f43B-gO@Ja~ z+|(_h%Q&oJP)H^Z=U_4y$L5W~?Cy9ljpLZtg)(qi-C}FpfOQ;sCY48!C;Yfp{)gNl z@$I;UZ0foL+^&A&A94)YntT5aNh0fB#XOGXs>jH@-9QIv!K)u#uew3@oCt7fpDd!< zkr3=Fxgwyp^OWYgwOdefJ=9o%*KgScZ*LEyz~N+Kp4Cv^pKNy^R@=0$RqV{8W0fe9 zUz?)NrS?RNxPxYWc1%O{jq`hw8cH-Fz$oO!)KA%Ab`o!Nk)cAtkXv#3adpLbb5&3v z*Rq>6hf7wl4Nf#Z05BAd$A=UUZL9FxL~=-AL@z|qu@=8iG-t!+P<7z+UVfJXncI6C ztwda9{IQ_P$wvo^x{8j;9_iI37Eiq@W}3G2(!(fHUHk1T#1~GT91@k*95+5iEJsWgPAO5?1yekBO@Q|c2*)iK1NavomhR`dNSn2!HL2@Kx z9qmFa%5lLQK~J(qMWD*h2eSvju}lCwhNig8=_#M2aJhou*fN+)@CjV8|EGny(uO~sQoQ6qOq#mUu<2%W#&w)nQXMuNl`ca0{RBwOX%?kEX{#sX!keFHRaprG}hGWIXAcg?MI@&{Egy@=ZThe9omVgR)SLqNyuRQy7(-{b;!l_riO3N$g&I=Prq$QR5Pr zm{f5+GJmQ-jiHDPm{!mbd-^%=-Xk!(^XJ~F>|91SKlic4?@Da|fG8k;F2y=}8Y1z9 zoLz9MkGk=2T8A_FW1x=3_9%LNYk#!w_mbjm(3G=lkWYYiZU3Hk>18S3YKUoNWp7E_ z-^g|#A1}g>f&|uZpL0UX1nB^qVSJ&RV;1cad4XgHJ|G_K004CikRU%LE_ZclP!Ulg z$+@!K3EhD&d#Qa9{gAyuc`Mm84gkX-g>AN=44Y>U4JWEe7*M;7XBLtf3Q9Br8|-o5 zi{=fKrYuGrY_9ggaxsJrxT%i!e#-xp?<{5oUd3~w;N@-D=jP$X8s&4+{J38O+17u) zOKFGmkFDjBuzMW{r{Y6=0zzAr^;VNG?HwnTlj4^AG9|MT-_J$$TEc26{vmgs`91Uh zSFDaHPxkFbGAv`{{)e0az3otZ2BEt0xDv$XtkX+TY-i$xu)F%&wKmj@g)O zF{xa>#tynZvb+2bc-ou&$<@OK^kk;r$B%P(KM=QTiLh=OBs?#0Q()v6!+4fsZU`rU zU}I8X0!$%RmP@7v(tuqd4>h-$*l;WWrP`hhE_cBI2UUU{ti^Eb`2RH7=Y~ zW6JlDqIpBxnSewOV;Kpc+KwBm-wje&mYJF{x$#gtjvb4Fbss;-UFqWC^f{jp6YgZF z5(9*Rme`YCj;bIt9$Smt<1U>jBk4=qojdNSROuUcHCt2-2Y(nF2vektr6ej?wN)4l zX>(`=Ez0|AL%q02zj9qw;dprtvaJJ+rHwhtnhDMw@$59mGqs<*N*QHafoKz0LFtlRalxZcTz%PgBXxBJcQQhIR z=jFmlNJ0Q_!fb*HR_s`wWpcmsQ44{Kj)bBXqrZ-@iY3Z<7!`&t79l^79(81`=qbpz z-1}o)y0_P@7MbA{$j?KGHG$YmeJK{C`I5vqI+IiXIzjFQPim3aA8%Ood&x{ER>SED zW$Wk4P7#$7$yY2+9$uGlR;?;&%cHj@j1F?~<{9F8c~4F#!wEfi|5sQ^6LpMbu>Q?R zlJRiuzrd`r;g4JW$J-^IS3^nZJQs!Las&>_>DEPhJQg_~OeyhS35pYPzXaV$Z6u9g zLS9w7Dr+-hz2itc&x}6OE|6qeZWh#13W>AU8KDP#SF4lSs&Isx7}s8yImx%@=@NY; z=IAkQdCprk?;!FTd{`Ti)zFrk17T}=7g&##-4mQCiCX_|dR(7X`D~80PYEZQ(kJnoAQSI`78$(}pscvDpxHevADj$&H}rg6Yo0%rQ#2 zHQu~7$4?*Zn&M{8BQ+G|Z7H>rT^}vK#il7@U&gyV9bUXm&eYe_olMay-Kz~UWNM-( z(WjwhXH0tv&eYm8F5yh(ry|N@WbL)#1tAnM$e~y#^TW}uv1nKvPDu{C<SJG33oUnI)*w4R( z4gsP8WKA_>Kx|^n?4%16qZyV(lC&k=b8Ob^%X(T&9O<|<45FuQxn=)tJ~?j0i=mxv zQnI>oAvJJE_uRnL4@Hj6Bt|a3f_tak%QZX({1Bp?w5o#+V7(O)%6Z}5_0_p1PZtg5 zCav4>p4O#?m}JY}UAQ*p9Qwue6HU)igOm1F)>RfR=Fk=O5H;DLYPD*Uy0HMp=}rHJ zms-E@A3CRlco)2r!Z5u?_AS5gb;=v+m9@-Ow8@Ryz6q?crZeQ6T>6LHE`sWnv25Iy zy`N4T50|{7xz$IPqi>JMg+Ce^KK|vDR~k=Z&x4!FRaS^~0!6tCE_I>FJ011F^vFP8 zEfB^M;#+zKm1^)*L~nA$30sD^e?tf1rsRd3K?#RTdp#L@JjyZ`_;QOs=!kzW9Q8NMRmb2d0aGUJq7)Vz zIWssAgIWqInafuNCQkd6*3?txQ%ZlD!5Q|OyG^F$VAYdf+3%LKLq1(O1zK6|1oWnT zN?>#ipfA-gZsA*TQrr6-Jw=}F6VXtp!%NM1u>2Ljv|`iA@Xh>7Tc~2OG0^9}F`%Jz zRb-&&cy*wlYOm3)#{IS0awF+S%QcXV^qfbt447kI=-eqgq%J6XAKg@;ucd?zsbVc zMH2e1yxDfr&i+KE=Z8G|`{(ITC}BJ`z_1PyknX&YqW{t|@_LCVmuXncb z@T@CEJ@#NWVmC;%mPOt?z_3J!y@iBh0C?oXy%#)9eQUbBUhZq*w0`vuxvzMu_7<`o zz2R`vJ#%V#c9>=J|2kGJ--d0x@5DTL4`y{soA2Jor2iann=;agHs)cj)o$WCCiuOW z8Y7a(va`e8DKT`e8xurqhl~w4aOS%-*uI}xf1d;(4i`N?vi;6QiPu5Rxhzv4E;C=` z^6(~`IYGRg`jBvP9ytZ343O6IEHE)X*_jC*Vc9P1Se}tAXjHI0BH`x0+V8vR75il&Z_YMF|akBL)IfIA1UeHWRR#wVi$QIKL9-7WvsX;>F3 zqib+c=kq$nTZjd%Ly-?`s%nT2g2_o`Ow^3rC%66lY-F5>^Oh|CG}KTzGz6ybD$#kl z^=a(df|7C9erg8VkV(<+6w#@r62G7d&bOvb=j-0Pix9}Xvcab+uG~m5h^EInH*c6% z;n|U!YW8T6AF&=OUW(KIZ#;*9O?AI1jz@xe=6z9?Agf@K`DfznIoG~1HXbeF-@UNT zN`;^-CNZBcr}*U26vafa>oAkmA6ucuKZSOkJ50J2OwI@Ar(ODM?|X{Lsnx> zMJq(wiZ_tPHTmc;OPp4};JuMzwD{pa_6W_btblJ{K@boBd{-*4iePD^s<%l|(+;eX z-}d#1+WrqxQ>l>t1M!pW4otKX5(!(GFMwE1vkKHZ2euegpS+ai6!1m%?j7ZES=LR# zZba`)UX}4ltrarnI><>*XCzvv;S#Y`Ck1lN7uhQF6I%qr>Z~n86#tj{a70?*Z!P;+ zSmE~C2pl_=3hH3L3sUD~CA$`oT0Ak&u$lW}1__!WTX(7m)je&1jw{euEvlDME&RZ$ z+8gtO^tpu~F6qYwZp;(bt?un(MrL7{=XYloR3nr0Q2P1XWBMYFq<8nPe$ufPeH&Js zeb;$Ru5aYLLnAe@In>1`DuuFVQ0d(4n*STe3kQi@5k$Mk06?_5FMFD#YG^gevtytJ zv=1aov1N)VIEQ6nwP!Wvh)#Qxb=plsqElfbw6-F>@^{1vIIB3Vc*pLc?ipT|81WOr zMXEgd(z4Q&=)pTAHdz9Dw3kAhD&kxWUyA{pa}%n{T3W6w%&etxsL_(I$!XG15o&=c zDi;1^k`rE)AJc%b<8EOe8sM!qw(mYc_=deZ+Emo5)igdxm3YEr&(Yu}>_c)*RfU#n zFtaIRt9<5&fzs7l7BLks`Lx<&;P#5SBDWn6PQOfwn@<@4$mtCIy)wI?Jozq(j%4fi zmqhu#hSquU($`yT!LujK5?xCQ5wI@~ur0AVu&P}~eI)6@IvSFSj9bH@OU9r8V5{KXcL*A(h^?{v z#EsIXb&|RxPu1<_1X6KxIrMEkWI=IFaul5b2)Dr$j3+PmBY4$C}-7ULG z4Yu4j`Qzfl82qBjs&dwiK2(hd9wh<DIdG$ipLaxWR+-l!2*@v3pNpf$?sQq_8qO)m;B zK=oT_jm(-)1!0@Aei*2^ zm7>^G^vjDsws;Q?<5SI<681x%@#WEp4FitV9JHc)u?{D4)lD{AJY4PfVa0+>CAEe! zWaLyCXbl&$+!I|z<|>Cp|f{?(Jmq4C{YvF2BRR~=m18S8mL5MMsCRR zxdT9I#$+Dozx}q@_LcbnWx1)xgR{r->un2qVL56q5<@nUkEVB*xpdEbc@m{bx<{qJW=wuXTpJB+PmBP z!!%q(;1^z6- zmp>W6YP0wu7sdOU)xs`xu(dA8YT5H7l)%)Kai_s8V*~ZMg@%MEKO&LyUtvAMOBAw{ z`S+KD?@LL@W5F!TolfV2_lIJdZHE$xc|IDujbcRU^@o|*BM=#zp)+r8q!)vg2pqe8 zUH_VyE1&yZ36FXGWLW(57foFL{69CkcXK=`GERqWxAWp+OcAM>*hE4@P-tbWMERBX z7z2rOLbzQvPUf3YgH%J&>M<3@Ca zOk8X`8+&7O^>(FQhj{S=wX!*fKPv2iBRe4ead;==z4fd0m*>-fGPbQxo+Rw8-_kMT z>H};d{j?(94Iysy$xpK2O1$+__C%w_O?jq_N#v;N>1EDTMxBsIUQ>lMYGRC`9saUT zKPi4N$E(EbLLz2>5rFYE5I^mO(-&U_gKvy;)4s|lggqS z%8-uLmH1DvD9GU-TL6)*!`9-#c@JPbX`9vXkKqz6S(bO*R(hQ)=&5UZCz9f;6a(oG z#oTOSCx?WmuR3~Y^2Hp?`c)p?%@urFYqM=!{+b2nlO_irFV3|XD;9*L%ohIy337pX zfOhZzhs?cj)U@O4WoHb4k0->j+s!8Fsnl-s#~i45qjbuI+yzvp{*P;7ff!K@{;w2w zwwrjp!B0_wz{xX>ou9T#yrUQEVsqQ?7K<0HU(J(wguUFVhWF@7p3S+f=$!U>n)E+i z()2Dk8vaO}C~;bCQfZ>WXtKU;d2&afWyMJHtLi-;Z|5)Av20D?ce9j<>?wwDJxwym z@3f*XDsVB3#uA-C#Kc(nrfs7V>y*T5&ALea4 zbnSlM8%AR?u;UhQ$dk23)YRQirKK))(B~#32@cQIaWt%D-My&c?I10;)d<)BA(vVZ zXd#o@9S*noDtTeg4x@E_ObBWDLPK9V+`chqdhy@zrDeB$j)!TpRNkDGF5n7UZ2iply&z=Dr>dM zsZ_9UHP6Vfu_Y%5^I7x;aN`>q9Dgs<;iBPvQ%zq^{pJwk`mWW2WCV3UyDL&h`^nwz zop76Kyp8^0gIU{pZl*g}SuN99qDz|DOBS9|J>DBS*qCMTJD4t(*Y2bkf-;PXl)`V@I?s zAt5!}%4Po`SW~*KR6N-GlVB0~ixjRTMzZWZZwFxJK^A(eypV>`)))RqIsl{hTOC#~ z(?u&#lK*4^S44*hd&y`BgfEK{PuU9dY%BUH_szbF0LO46&`^7+dEF5_dGr863!j<4pT^qosO~GBH&L#a-C1NCHE14f5;t1 ztEXD@Hhv6;+wy8sKNeP_a%T*I0kFMF-FDRtzBCV8pUq@b#&FytX#_U z+F0?QwJ={83p8_Jf2O#NDK1e_E$de#(`c(U65A0!?yj|F z)Mv~`#ECQaS1?RsrR^j(xj?dsR9U5XC-AqQ8r5G7!shms2()UQ8tD`SYG+j14;mvw z+xNeUYN4ZmRnfhyX#R#LqFcxVw@2$Xn{1)|-=@#fZO8n#fB?X6T?3x~ z_kjTM#aJNW){?@n%0Ngz1d`6rm%ir6s$r}}PPen(bo+~w|7$$hstM8RT_duXIa~M5 zfyd8@RNDR1Mq}NEm+MpedCbo-|8Xt9+vPe3r=4_%iuFF9pj`N2+yvo221sy3Bp;TU8pgrCw_d6-M~3l z|G#y)e~Ax1#@!j;f;_#{z{^%KAcV2yC9hv(oKF1~(k(uKKo<~*N!qm|W}frmU9F%>xzqVURmJ;vV(E!p250D7*39({xnIXb zrT1i%*mEoetq2_!uXe|Z*NJ)~UyUkl#O^V9YRJePBEm%aE0CwfF;%yW2&b{h)&@(6 z6PXA?O0-d2QOUCBLT=#u(wdXlsy8lpbxWrb+3>Fc2Vb?P$E!V!+3kh8X|lNTiTCGP zYB0Cy$;32aSq10XUzk#|A8Vis3SxQwf3Rc{rUzLB;=G<6i{i?OK4avfzDkQ-a>!E1 zKgZyjj%H%!)L7;zy?#TI(QU@Jcl1eud>`_)lAcRv&$p1{BhqgZeQv@*3p1S^SGA!z z$$69?%FYY|n8*F!-y~H=BzvE1y;jcxA08KDanJf0`@l-UN57#=6S|TX5Vci)@L)*4 z49RmC66Q}_uFXtWz77h-Wz|m)rwYbhxNCoX6uN({F5KNEv@WiEF`I6PUx=F+InWPS zy5i)_Zo_r9#XJPWS$tr=YyU~yx}cDvu&kSU>Ho#$q~Vj9qTL()k?wR8sj<_NrzUO4 z`sN_lI`}&4{d0xqyV7MTYP4yN_+$&#X%C4cG4#Y3!)V1e#x!jYC{vJzsHR#fHOj1& zfuX)>Xr71`&uKt3^<-X!)KJ;>0CRMMNX!za;HN<)(v>(tPR7Iu2XqmrA;siFvk)66 zGaf~~KWR4hJqZ~*8<#^q3nI4jtk%12`GU{25n1L@3*@aSZQ#594?%YRB^Is)baIkP z^33)6ugE>%J;015abJp@`{ct5anP(d>r4LaI0Z;?3DR8>*Iw#?G0px|jq{FJ!W84Z z7UAq<17c47BskNLkdck`A0-5}fy~{KQ5XRA$IGCHI!LKoWJ_W=n=V` z0KJ#28YRJZog#(OLI*ZtFl{k%LLi#9L?6Sv zEM?D^%87L*iNG>`WgIR5(KAJ1%Ssxm67seSN}^UieycuQO?kmC^_F+A(i_GF+v7DM z23?cA){9>HP<(;sEM)O|F@t_$x3q5nUoq>4kUox)o28b3pn9wj!x-Dhj zfknyO1{k7{t1TEtojmkSQ2`%^7F`#QjIZvc`%kSg5bw_RgsHL0 zmay5nI;CfS!!?y|_tkxmfA0uN#ItAW;U*1PnoR`mr%UXCQm_h2VDpR|0yq#V5^7d8 zUrQ!Un?&Ex34boLoCTh|8r_^>q6{><<%rj&6y3y8@|Iy--@BDwq4roZgSf`gg@Rs5 ztuEC~H5SY-@nXiU6$s%*X=v!i12_zmZls;SRcTHj zTsi-Dm%E6IsW4A>m|tvkggoG>4G z8HtbJ{RkXwuI3z1GHDpX)6OYh^ZoHq6aCy;I6C&DxKh?pUahZg5vet&cx=<87=`oa zNUWNYTP)S#a5cXGr{Q2W)5f`(f#OC29leA7Pc0^xUj5&4R5{S_bUpw8_8tKl z>$#bh-@qxmQ>Fcttb~PBt4e()&OO|M95SE?v+Nm;uwv$Ri49~n99&XkPX9WPM`yD6 zcf65___MZ##95m-gFBZ~E=dfmQ%EF7L1=i6Td^{Mg>w!H<>2xPv3;Um&4y*O*#6EN z!Qu8J&Wt#m3rr)Xb?5H43?Bh$W34cQ6s$BY<)crzx2}RL%f`S=#-3&;@eL7J=*f&m zUNo%2{5x*SVquATdC>Hv=CD&-406Iev7dD*@BSUIA{lh}LH)XvEJKZSZRk$7I|e9s z&Pra1*Pm~@4sw|7c8wia*xKDCE_Av`Ke$#4?W(1j#LEOz1w~IrjV&$POjUG^62RK! zvgq>jF$@s8btO_rIaq{LL)yj3UQ|hZG~UQm0e~DcLhAXfZ%tEN(xA)!$tHWUtq)-I z{hh%`W#<+LSZJOSRUb8Ee;_N784@ieTl$s=&JkaZAQXr?Pt5xyF*Blbbg0EX6xLUi zG(kh<;4+*mKRP&Dqt5x|L0B8tU5|gZ0!MJz(NeO)!_7aGWH5Y!s@fFHJPjJCdT}yx zi89?|`dFPAmG~#BUWw3K-!GWr_l-?$L6*IxIXzV5>2FlBzco($tGS_ylZaw*qcjc_ ziroMEF73e4wM=?(dm*0c95u$gtYx%!&ZqqQ#|o7^j7v8JB~7SL^AVI6~$0+U2JRIFjcSk^J*cS$qxhAv-cJwVJn8n;!{8dEl0b~&ql_dCvW+&-uGhrM{A zMejx094xoU3jO+(Xll>VdlV{g`U_u^u1+(3nHMoJw1)!OVsTo4lTo!`G^LyjF&;sA zz$*!>nlpK{NTq&)GRt5YD~&`u*q&3Hk)>cV*+MIaZ&fUHuFWE07R{;ht~;OMAI|mR z+rCt3!O^OA+33h*t6H;U78HAJ!lL%2-l(qOnL&_zps~6MF(lA{o|J<+dqkylPLlza zRYr5XOViH8~LztY$!0`V5bqrPn(=j$576FMyrnBHD%HGCW~SQz-s17p-TbXD{Pv+}cCnXmq=l=|b!I+rH7|gx1w(V5h(Z11 zM!^PrGz1+2mCUs9H<9O1*L$qd90zt~vl3}`Pa3Y7!>dBYA3Iwc$C(B37=S`)a_~8! z=_;`;DzOR&poj>DC%?ThUp>RSgQIVy%PLizyswhNROc3xNIkH42=zLBX2=clv(2FG zlCnqqs@#q2{jcSOc}T#ikMkM3*Cye&nS&!?BC&^R!|rZcB*%owoH#4Xk1iQfn%@b;e8}x^ud8 za!Wsb?#oBnE)epGeJh8lwegZjmY5#En!<2T;X3MCLHYhO9#z|o=$ztKkfx#~-Iyc~ zYFOgH8mR_XSo=V?$ozK5QgqZ?H<>O@ zc%0^eY0Bd7ZdSF(M_!w=h!+#>5#jRrS&JfrPqs=dhObg-Y5vZfCQgczOyTDZf=nJ^ zo+dM{ybbq`!e46>*-z=vZnu$m6j@@vO-je%wn)H7-8vASFh;T#xPCu)nrLDK3o-*FGSwZp}X!Yz}u&C{HW`kv3$EXBO7U*Tyv+^C6ynl013fYTnE7NX4DQ&9AJ~i^)jvk*cPKfe0o2l=bN=Rpzy}Pxek(O`8*59k6>&jib5zCyB zbUhScP1JwL{Y)}&Gm%R87Q|$p@Zm~k2t@VA^@N}=O~JpZc~zVefDXWhTxIR z!{^*tPui6I<>}vVUU2+0QV<)4#`gt~Taq6x3`$cCC;i=lhHm^Jba|h0Kvel|(KbaQ zo#;|6!d_J9c?Z@CP_h<}6mh6hX8LY_p_DV$-K$z?oy&K;f$IsX8QhL@{2N;+@S7ad zWtmHd)&hmdI9wveQ!%%s)m?Bs!>WQgoX8EdT(3s*NoPiBmhHkvc#{pjzI&{Vez*8@ zTiz2k{E_;ko6cc;2AWMClod@rYd|fc&&^nv0!Aa=BE`qw@U+Io7T#Bm9b#5sfdzMT{x0_9K#@kYj1_vZRK9ec| zc3~W9WvNHWK7j5wEVQQ)%phE$q0;5W?N`PH>f;B(rs&s2tE6n@spqqFt>-bLjif zpT)`_!KlE(`@~f-tW(ENRgKIezrR^obYC1rFpfSiRPx4_PS=cR<7)J^cqdVybR?;c zq0|kA?^+k!I42$}yASJT@A_8C>87PS+{U-F8+i+BMe?4$(qN^^0nCy~EV~3pQ4ud6 zv+w(gE#>~-k>nyQ`j<&>TZ;9ixtjRhOD{*G?|OuHw=?jT_x12y67^^)0mV>dsejDB z-|?*%wzzf68icssEsazVd3_)-ZD}zrHCkE{7Z=2vqc#KIo7TW0+L&!fbS;7ZvK%cG z5LEVrr7k?m$EpV?Alp$K>56L__$B$hum2^nM&YM=F(>E~!?QaI6s8t1FVU&iG7~|k zCWG9EMO!Na6I)rgrajUU14;0usqxG)P{U)Xh76iAXTB7`N?4U()c7wuv{lSkwA3q0 zhCX^+n*I(fuCd~E#Fd)^dOWbm0xe2sp$osVWxgxR9CT;+rQ}X2AZyjly)yZ4W2%Th z>+9mmK~ibx6svWf?p5=J5@MteXHw>KP1@K=$w&TT9_>>svv^yQj>5v-d=^H-Wc|r~ z#Q0wyF7uOb*=|?cT}p8*GdmR<^RcYyUcW`yUj-6tEtQ$7WhIZkcj$x-y-zyPxt4Pi z3HQi8#S8KYkbYac?!XF)*VH`w`2EqsI3T~Vh<7GA;qcG#@dM|JWA21Z!BwkUq3Vjb zy^nz0eFvrfkD(4lwaLxkQU8?)gOPR9?F%U{L$|D{*pP)^h8?>UgRtKlAiYK0EK5r= z8@154PjvR{bF!hi@Wq@|j2tu~R$+a3pF7Xl+G{Rt@TVUG z5&9J^#@eL>k=XgDEx1+&TUo@=pI4}k@s%f>o4C(sd zG-#|srSyS$H83(k4q6emcwkZ-iw_2+5{mqdTrrp;on0U|bJ(LDdyE^S(LAa(SA`DC zbpV^UCnQXdnE;roKCEA!Dng*p9A(YdBB`)VGKw-VGe1|*o{p*&=Z?uuj&B)&*&N0$ zA{7-QONOUSSoWCY&Mp|EBJf^LFKZBi$u*Xyjc^U#UdWCe7|3+|f*GL@;>TZpGfxaW zijmHukrrZ%jX)hyW*=AX_C{W3PKb&sv9Hum!2x(2P++TVHk)t8&Q9Z7>2IrW0Jah3{h$WIa#Fx6|MB0Fjxc(}UTPI`6o_8Ofr}J_SBnD} zXYbZzQ>ty*bhBZ>!xvIaM-RdjeI@lUwzx!o--mf%4g!@`gBA0vYg|h<@e7!{a<(qB zG+@@gJHS0CP6CR8Z>9!DoYrosh9vh;I8z`SBEcycxi7Yj4K%$$uhAA%Af=dJ=%$<_3cZ&5DSqE-Q}p zQ^c73PokQaAo~vgqgRbC<6&R#c`sKECOrz8CB!+!B!+F>V?kIDd03yuYs|GaLeZg? zen%^2&owH)a5TFBhKL<)JegqDxOXipA!#@54@nV*M} zTpEl;)ya|YEVZ@165y=~Y1kmoKpF9*uoRf;=uFsR%n3W*H2dna(Q;bE8(GV=vRwSR z@%o9DC`&RoOohcB(2q^M}g)`$t7;zH<*5w(|u0(6BnKHFW zi3eZvWWrQ$!$&YOLa<8Q#Pp?ho!fIme_C6z3{4 z2Jlr*DU|waw#bkfIBNQ|dl3<;sc&r#-*ez~bsWK&>;`?xZ09^ZGyXS4k@z6~av$iZ z6I9jvD+7Jm`U^?1N6&;yMG#x<_Ai*uXCYBo<1l(*YehhzCUu+T)^u~WuI)(R04-4h za@$qTLu^~#-pk7IY91X}-{lV9x^}2yiK{PFSK9`6D3$pi28@X<>s&@18%5m~k}^y@ zo$jCbgqwsfo~Vn9(qD+mP+vg4F^YhB@MxlK_iTs#agI9neUd9usmaS(p7hnVUbt2* zXek)EY^j-*XC|MX)dM-~$_>WOT4>uKXsy=6J1NiAx5GTWCl5zxrEb%PJ6fv2WZhp*OLdS0dm zV&xZ8;)WQa;>9e&!_ZKF42Ix)qxT1dM7~{}xrORX7h=TaY4snf|NbG`%slbolNWmJ zlonk)p|J8rB@O>=(x}T&?M63xZ}{9?oL=Jk5R;67%-@UDQmlm&TQ5wKh5It*xRKK8 z?H_DR92M^JB8-B;{^D!-f*?gy${4Vy)v)6go#UbdVgaV)x#uRuVKpIgHZ91&9dN=x zsO62d&9ph!9^&u3CC)?a+Ail9$WkRH%xjK9F=tGFlB`6!(E5p=*vcsIc-?W@6ET7i zXJ6`+N}vco(3%EV>tdUE`rS`MK+!dDhrR6`b@g#9t(+pFjPUTyQyK3PZ#C&aSvu_a z*qyFMXxnZ}&VebNPGlXX?!5f9GTlV1<=nOx8?wfjK}DTR(zqS9A|Qx~Nu;6lxPWC$ zTgX?Kr8`tkEx_Q*9rsn(L#nklJ%=^VP~@KNciYw6mAh(5+NjE&G$w6@@GZ?UV;yQ@ zDOVNEGTeD~5$aW4;7jxwB1e|c^;T|m4|dhIDfE!4rdV?>&7A`v75tI22OyOxP1+&? zj$rj~!@9JGy$_B}O{mJoFipoeG2V)yNMf7BZ2Ojl8%0-Xt%_@$s;Z+_fh&K3&qPhH zw;BYKbxgQ0c1SWq-37R{g>?TS{xl${eTNI$eOHDeZH;{=V)sEdyY9(}oC1g>oE(;n z@6DnnZ*_9o>jLyfhW88kMkR9v*-Lxn;2`F$zXAf}$;3_25L*?QZq+8JYnB8-{g&hM&n1wQj&iyQq^I#jPzYbW*q86 zsenHv0TyQ@1ZNE7o)4$DOxPyhgpbFtm9P;nVcDZYn|mKqQ*FI9=0w0 z<-V}kqT!0-5^}RWB-xZPLU2&3s|WV)Z}M^-A_;1-*C4q0R#F`Uhr5a~(!o{74%vBh zZ>ZzgV@`>vBiD@ET|K8@(3ircHl><>eIbY76$A)L||3DCD3ko?9)oz*~znmI= zLKB)vhb+FSg@0r)7Xv~xgIwLKw5u+3>Kie0FQ8eHe`f@lQb$q6NxJu0O`22K=*XrW zlVat8ACzVy>RZw1xQTfCht`gWH695V@_cQQ+*Lo{4j$2#!S!aIBe;Wq?BNol#86Fh z%Dq@^YZly!xc=JC92hL^Jh7l$TQOU#&rqgJR`vd5=QYa9aT~ZGr*eEen`d}5Y0kw=58RPFkhSDYnwXE<`C6^`DqDYG|~tHRN^7Jolxr!{G_WAVQP-^(zOCYzno+&`I8m{}Lv9vE1mcw4fpfva?N zc@~9*&1u)_l{QHW)+xOvvXvpM789l<2&Y9~xD1OX0Ixf>)ze8=w=6>h_7X-k=?-Pr1okNv83&xZ}| z9TGBksV%BRY*G@bVcY9tazf2|a6I_?SKsM#EL5p24mt(Oix%jt)cBUbj8C9(YIt2N z>Si)kmp_%=`=HEu$#kVmgnATF!u;jOye&!j9g_#w->)Ka$ocHJbLG@=(eTWozjSip zA?M}lfF((h8`9s<{S(3=t?Y;gHkE423n@F{+3Ne^bRh6!n(Va6iH+!TbdxdPwrUr$ zm(}u)uD#1%+dF&D7W>iX;AWIpITtI&kp&d|G(4LWH7J?CVNP;qnpdm%1Wi}xLQGH` zTkybk&iE?RnI?p^c1j*)XIMkp;EyZTylhOxMJN)laqCh2HWhYWmKT<0zaZ!)pV3dyvcNNS|L2dAY7Ic(Uy@2N|rcg&!`G!ChsJs|lhV2vLSs zp`KX_pvPvdz-A}vd2*f@lh=B$K$=FfVir4BD`N0{TL^LujTjr90Xsoa4`w1YCsp&d zu6zQrWtKEyJ?0>B+Vdb=E;epTfhYq3!9}1023^}(wE=Hnl2pCDZ{R69=LTP*#E-in|qKrfEQ)t zFz111kp5)@L&GhV?jLLFwt2$I?U{klj@{r40i#@ezO_!}*-02O8f1|>jP!V8PRO_lC@0+#QZ`pNbQtX5_3g;dyO zjG_?j6aEF>P*!t#)u8e61@j)I2;{vhww}Q!Pb!RgF)&TFq$as5ILw;S$yomsLrQ_6 z&`{_*u8=n_a;RcP1)pN6b?s55VfET30?ZmA0U@^+CCTEOimW0n-_tCq> z`2a##&~xsEQ?K?OznchuVea#WLa>BHxyvre9y!ia^R=>*eKNB5xJmcnSi5!aDu}fO z)}7f!T*~GPzkdS$z0SIEd*?AL zW~xt58Cv94OqI1O7Jj;5It^}Z<9|c*wz3BA;5qgHR32te7rg$Bnd_%`0FZFjoB7_c zCPWc8i)9FX@`ln`eG*gXt!1Dx2JXPTgIh zZ^MykfRtVWs?lF70}N-trIWu8VuGrQPI$V2L}ijH)I1pU7@*R?Nt-ejcbt)Hj)zlbERYT$WX3^J+#`xE}Fy@A~N;xO?B)vmh7C# zn?AB8Z%IT-z~Tkh45cGL>knn!fT{v)Fj#ExT#sxCK7&AUw=!A4d9O3%6{J8$8pTnR zNi~GDO$95VB-%;5MH2{YVv@CckLQl-)j(t!eTwx~mlB6*FDTArIA*}W0OK|(=8$}Q zw*?p#W^?K%T;>w*DIZDvj=aj>7#4 zM(7^mvoNYv?7SKF0mg0GrbnC^$xv3yi@Z|;Hpy%4#yKP`zn`ot5KT$NdjwkSAPY9`CbgsJvfNzxe z8%4$4{>k0EI%Z|;1-v0@nyA!Qj3D}vy*1oiawRB;)J-i$$Uxo_L(oc|Zo1N0+1(Sg z=}XxSYkMpBI-*rs?ckwW;hl|P_H}5QaJteSLVytmggT?Cs^{HVj{Q+bfnv>_=gSo? zC%cUZnrN>ojJ1qwOp5P>!8@JBH_OXb0J@eia|*_3VJpkMo$rsDf1YVicBspFmMw-=bbRb2N}uIBgK>=}eRNJS;IIYqMx_Wxfrw*z#=z*WRYw`GE=lKJn^HZKsQRE^bVRNIvLPu!n zsa0R*6d%u`j-hDmlinR&E|PeW+Ouk7X)=`og13_KP{N@<-f9WQdjb532E&41ZM@-^ zC*a5J1|wBs64VV_%rv&yKDT#3G$|6VqdpNjo7;Ehy+7XF7nQwPO#walY@cYnbryg< z|KdGEr@Hv_H|})d$QR}fuORaa5LDgHB5hH$Dcva29RKO9%VLFsoBg#j^xf8F46E&I zglRG=vW&1~F+K_o!*lL-ku?)l7}h{}DyCK46*jujJ?7s96LVz9z-yvV1-Q3#6P`YG zi6+q+WJJvc+0#-{4|4Zac7XT%`3Bd_5OW%T>g2|lo>;!f#M#r*jCM3X+Vff%or8~- zv~GWBWHE1dr4I{%ymNSA!83P0UTbH|t_AfM%!hPxGtku9%BFAZMgGk$9fksdEih=?)mrtGj!ywr&-zv?ia#PD<8y8W1Bi zi3~Fl1g-!SU?dBM?L8@&G*$aY%C<7746=izL;{49j ztfCDE9sXq(l?S=}I{_lpj8j=c&e5X;_QsjkqFkhEN#0wCPkflfyrg1OvSdxo8WGy5 zO`97IU`vkXk-A#gy`FCwEZ_fb%ap^wEyFG1yY>?q7NH_e0!NF1#Oo3&>v|8!mr2F& zGe~%2Q^%p2SXWZc^&c(e4Js}k;>k^tsZpEaZ`l6vrqPANl6Cdr^szF%9bNkQR@u}& zQ@A-64gCZ2GuM;DLi0w)!&AoEkd~#Lw!_TlGbA)5qE+06P0(@^WvpWR?@qMk_+s@# z)|=9D3j>4aazX|6^@;8b8x!NrPZJ%fB$RzK6sU)VPb?}Pg?M|4!0h838_ZiA zpTU4d^~46AD3;_eWkw}3)-^LCEyIPHLi}cnr`S6Oo_v6*T5)H2QSPN!$eJ+?K!vqG z-ntMQg!%@Lz|nZi71$>+-%<>i!#MeR3<#wHbNxChF=%V!ggG$RH7J^46J2RC3oMU2 zA1iRBfAaXR$Q_5NL>Yfl^LSB$>&9_q?3-wU9FJb!)$oQ!Mm;(zSPR7{fZ~LUBlS-# zRi|o`QhB{^81l^9G~=#@E290Fy5~4L2j4@+`xj}guelxfE)crX0usESjY!Sp3=Ti2e3;t@G?}Ke`1itC$5%Xg{r=ss9j(NXH+3%(lZCdyEANCpXPw$=NmHy zjvUU+KRnrE-cfceyCq3&hapmR2uyf}71Z!&*c^$eK>S~by?mJMzn**h(@ZXUUg=Id zwGSw%StprHVEu>OQJ9~Sq1=DS+15L3y$q~$?Dl*7=ZE1fjm3I0RItyv*vBhNlfoh) zNhrMYlDL#3r3Gz$RGO|EkD5XXlh~ZfVNeI+cO$8;0`!HR3}y6_(4hHlqUd#S{@jv2 zcU^S`G%*338nb^@Y`sBRvB*_Mvx+(J+?PAuS2_L7TS$}Dn}#^ui@^{}j^dA|bM*nYEqB~NRShi!v=sJuP4{`h4Zn)l_lX5 z^sPwyd_ z&t_k)Yrv&|6Tf0cI`ycyS@&J2KK581V~%3DswVXl8^HYSRC(e$uw$0xv0GC#oM#rX zqG=|lB$5H~8HIw}*j7%ouk5)=!VC`G2;U37WtZG)l@%KUs#G*crW zOKY|%*rDqMIpv)w)Sm`&aZ-ZK?{LPJ9VaYEf%dnJ$7~T14d2OU1XEiK&GZI@`-1v; zUDMC`O^nrBT#h<;9r*a5v4w^!)iZWD4{iu|Sq#sMB>%Y@C)%Hd1G@LSZ-e5V?&-Vy z;#0TQ+%TzYdHABXx~r?yNb^bsO0H8Rz}N*MBMo2;_cjidnZ@wKZYoR3-rG69TH@C6 zT}hO)z$krd;apnPcA(UMsY&7(qq~y70Sm=wyJ~UA{VSME#Z>tpkd80?R_#SSU1ibj?cChPV18* zF-}Q1P^^o|BSUB%=t#be_U2tPz7xNOaC)OsaIVM0pf5Co(_kW?Z!k-*Qk5<>R`nJV zt1OeFL6B%8Ny;6Lhbb){hc(#M53~M`R7UX~A072;Vsgx09r7YFTe}}I?`5(TQXvjj zfgTSf=HQcLMBQ$r=nu|2Uc!@aur4KMG|c55EBqLGYrHyaq7#zvwVwne%Cq)~4fS z5r!s|i2|uBY-be`osR2KgNOnnZXRL`GvFl1YT`6sXKh+mFfgy{!1XKaXQZfH?tSk! zd+i$yj+2{PfHxb-S%2GN$0~OeOd;`&0XskR`#Uu1Wf`kE6#TIT0Czh`)SKB0s4X!YTY<*R6ceoN@>)_P(siH1iq6Hb zv>6p`Q*jjA=amC}Y%K>~V3nkem;1UnjdKN;>Kn!=v;<`p0RLdElzN-_4Cq~Fp);8D zn?E8EVcn}b@!+1j{%b^PsvC+rqB`dT7NnWGtrKmWUxwFo?mo**JT&r{k0k!^EjDfC zI6Nt|1GEUW?+eoY=(BN2gE=VqNjs}xy;Y%&JlXd4t+9WcRz*TW!eoM`5Syjqw^^jQ zJe04turXq79^W0ZXk@?Xb$vsQaMsAlHdk;{!cszUZ8SzFg+d463r#=8@TPO8OjZ8K z*&9YrHNqd8md(Q_9c3mlXY2e-n-iUy>GS*Pdg$@*Ml=n{eDx`>glcF{ z`DlP?igb$%YdEDjY{0>Iafp)N*&aNVQEE}_pvgiAbTgn?w464Uoce=5RA}bOm1yMX z`A;IVj^!-6Dc=;Y0%9pK;Y`*elutvncKGb?4SL6^X1&ea;lm?E5tD9&0l#6U=AOte)HG_u zTWy!N7z_VNL;2sXFU{MZN zjoxzAtW)(Y;I%_!*0(u{Pq4v2M%lylr#Bx`T;lf^2QL4;WxrVj9g~bdIR(b}uCgTxuFX6}B~=vi1cnaR;7Brjn?rkHTFVBelr(ifJ`Hi94B%Y0(4uqyd;!Cf(7!AhD0Mcu4SGJ3G(2_G* zbKZm)>a52o*=TD^x5>XpF-iuyo3o>6YL4{g=3B`)tu zMKYqSq%JP>4#k|~+=+r?HUejBi}O+3cxs-Ny}VGBPV^;NQDGi3=xJvWHWmAy9g5MP z|Nf1L1h^viMV+xETRW->&F6*JHR)7e9QgUKIS97SxylF!)(ualP1Kv?r{FyGIi6~+ ztZT<+CbLqNWfn{??J6j%9ry zAA3~4$^)pCzq4fviU{sT=@N!v6>-?nM24$hRVr8N9}Wg}CZ?!V~DBZwHL9C>ECS5uJoT&#Vo-X8ky?ayLJ;Mxj%(;?QN-Ht| z3uwBa7JiC&9}Xt5jjhWlNr91(!~c$X-{^ zOY%f6`rgOKFeIjE{j41$@;sas`iDoY&!akkO>pE|vHgHkYa%<^5J$wEg&a6|F) z?A5=2{B|6l7E;=Nf9R2KL!19~aQzzh&pg-HwL^NvNj z%z>v{*<8=#ut-i8-Y5ytV^AN6R3VUN$0D7UosX}E=|kWnvtCD`s!nPGh@djD@urPOjc z1SlQtM9k0u`@pacautB0gN=?Lar!K-+%=+2X?6WwR;8T}F8CAcB`Xm{!VZ5az2*xz z!=8eLw^1XKT1rbp^edTDN_rBGmufzXUS~C>eYm1V${vuZh*!BXTw=`ux z$nExtJD<3ld{~=Hxev{&6M|@|28!tL#Pb-t_EjEw2Vd_sl9buI*Ua>|2d7bRCvl3V zAJ^zmENOR8R8+oCckC=sWY;RiGAR>DvR2KTn-ZJprqXM+ZJK;>-){ zpVssu7h`+;a?`Wuf5<(CJun-0MScuQ4?X$NjEPOAdu;!pk(;34&s98to)gE}q*LX> zCnz^H=emwC7nb-_TP}#9JfG7&vEUb+czytOTzMPRe)W`>yN-{oym_!PruFB_N3RM@ zBuY6QAV7IlEc{8@q?nP$V&+5j=y?#kha!8grb96zas5>Ie&wIv-_4&+UxJ^kT4eWz z9t)8?#?AImth%gyYluWzaUGCOa)^+O$W!a5J_dQ4ak7pGaNP~%k=!lnbVzx9D2-&A zG)sTLZz6VejtoNKohB*5l-kP1i^iUq&5Ya25#T{9SE^;tZ-%r%lZ z*2X9O{M5Pwly5-Z1f&OMM{S}!Cjhpn4o%3s>-AjYTcd4%c-nK$v$QtN-J@pN*~}07 z=Tx@mWk+@MdphX45dv9c-9(5SDcQY{*poq#_lu?cSpcGIUkl(DR`{{AZD?=~@MG)P zLdx(KNhubXhzcprW$7J}MoI9G5Wv``Z=;&EH=9Pgy*n8%F!N?(m;mg5D6XEBpP!L* z?5Z&)9qVsLvr^^KVs4v)ic_oQ!fW;3SpKTpC=HMUh%kFK6OvWFtg+W-n6C{*Jy z^^4GI9=e0lKXjuu@MwDYo~aZ8wU_@vXmREF@DH(R*3@0a^&iw6Eio~wJoZd2qrI-BNwpUUm7HIg6#E7PA8Hlqq8QlYI#Z?Ie)^I@eAGkRB;ImLX-n(4 zv;KSiTva{84lL0asKmewT9#w%O7kP+aC*1)i;x=&DILn8UsbBjyrHc)gxf`Sxfji! zMTDn`cIyD(^p!yHiPD~|>;{OweBXbq!#1<_p`D5w=@`SD3q^b>2&yo)^K1=-!vo{g z0qZOXGKpvKe4$vQ{!zxJGy~+;0Pcx5!LHbA6F#BN;4OTsI%vgIWdbMQ(u>lEXT6ra zTDaiY*%_Qj0}P0o8hj$msbDuZmlL@@~HhI$W$qv1Qx_Bc}HG^e<0dwOh3g? zHDCx@R{cXi{FS0z>Ibw<4L`#sPJBC;Rwk44Z?GhJG;_2&Wy`?)H==J;%(;Hqw%xqQd*xsb(>?JPhkgirl1=Hg? zO#p#pncMvu@bPPG$(47k6FTA0x3aW3A`{0r66!Y+kIF5$L6deLVHIiPkpp-t04w&V z9Zd#`hYfE%z8VFw+X1DNoApey_%hulu$9le39bg&hX;fo`QC@a75w*7^s)1E>P?++wADyrZ0 zn_D`2UJEQ=>GuUPKdk7S`+vTL<~|QgEFj5|MK)@8tI}szIdY;5`FH0%#6fe*ug%%*eouV?DKJTYi%$2S)_! zo`^T5%NRjQ8!@$0+hJDX17@yKpQ$lU_2bfr;yc|P$qieVoa>NW8v=v3|7Jle2x3{O zPu9ztZ9i1%oyP`KnA*^>s+Q}~IaI0catxlnEQG$e znVXcJ_F1MeLyHiTkAs@+S++6n39o?2*lxQ=b_y&b| zh-h&l*Y4pH(c9`LPwa(l=C!G9+~@7_?2oVrL#RThfwn(oDoe# zgzUfui`gja2pjpV)^2u4`0u$br9CTMHrzN?XhE`kJ*+k(iYs~>$5P4}VeO^QIA(M& zNo{fWa5G@X8K0#L!Q)S-z5EZk^DvNP7--=VNqeJK)H?AMlw2Jy2dR}goi-Q7RW z^B|oFyhh~*jVm|_H25~<{jEf!()p-U2Mueus{t*+ziqzYjMfclUnIUf>qDMz6*~OT zXuD0_A2f2J)BG7Ym1&Jsf2-f(Mh-ZZm^q5Jf(OA1I>GxKzmT(QJYgmrvSjy+Qgm%C zBC?KX;T>27Zgo!obUu5PzKL&k@`1PTv&%tukNy-^OpCY0jTqfn75U{nW1R7{Lt5iT z@5#7)Ezr#G+7Ef33A_B6E2iUgtEolFY2Gm zSh+5Li_TcF>C(HiInpR@=i|nv3x!=U2Z%2zA|XGeTZFFN{Sh;KA2QkNFy@#}dgW;$ zMvQ)eCXA~1E0Ch7g`nU@`@)X4Aq~mc2RVcqGQ?#uAbVXWnP|u8Z7@j=j$g`4h;7$o zcpRIpkG2gotu?q<0`a#ux;#yx$lcfN>48x|>ZO^fuMw&=;|#7uc3lD^qiqB0=*#)L z$zgkf#lF>Xi*sX53+qqOt9>h#G?ZC`A2UhsGONO&;!=E}X_lBo|AQXB-FXF9Q`IVd zxnL?KBXHwP{(rn20NCu;krz!|M(Y@0pxP+k%zvzzH2b@@)V&H?lWBX%TBh{V`|Lac z0EpUi|CK~ZnvsS4iXewM;a8~{HFx?N*niL@QMshLg)Y_&60o7-Kvq$Rj3ph}ELK5a zNcV9y2j3-6B@e!No%x#!a4T1qp{iy@*jLUL^?a&aHQ zE$ltiHU3MzmMGGEtABh_g2y_Y1)=pnG@WuaE~sx$#9(}En=P5FNCfSG)@{5LmZE^Dm<&8=?g;U);+v` zay(I7_!tbYZYEnOD3|A;Pf2aEo(VT=uFK0|rI^guo**XH%rrwh<#7sQ@u7DfJ&lID z8$i>ILzM~#8=$A(TPQ=ZMh7)K0vk3PhbRiSaN65w zER8p>Rj?!k7*sVXZ58Xkh`FaGaEf&+jo@2kI<8FUxK7r4A!NX=zS_TReSWD6Omm=# zc^4UlQMtY>)I8&Q+<|rxGih%t#_CyN>4xqJBDzc6C%T(&An<-#YJ7< zg&A-r%Sea4z$uO=wVsGL=ufFpr(sQsLgB_>c913!k8yN&ft#AaV0b)UG{3XA$U6C& z^A$93l!KhvD?m5^Fb)%%Cc~{wx@XDr?nB?ovQIn`G~6c%Vn)OL<6TOxel$#0e(cvX zxvBG6acw=Uu7<*xK)r8hIdZLDr*_IyhgeybCOKhM0 z^_O0DjUGJy@${9&s&v*HC!j=nomY5-dVx?lIuRk=^+U**@>=yh!QhG`)2d1_lMXBh zvM)<~ZqC4HKhUw;$=b5x!h(Np$1!OYRd(qJdT!**$s50pU$rqCNNO$rSL~GkyMX?r zC)=x)Uq|R=>&VBKn1=q|cZ*#W#USd0_q-g3!Ue}bo7?65h?7N2$5llIX||+Ng|FL` zTauMPMez0<<1UlJbyI>u&nsrOW*se18X#TuQe7@@`);ggUAI*`YA(RsijK(-+(x*p zurL`k0vf2cR+9likGO-z@}`|byN=+NbLi6cIv6M^G#~?t=nY2HIlq}iDart6S90ux zLev2r^Z#OlR!Y4Nbg9OswSZ$AYF58KCUPPKy zrGpd&QS5T?eE;vfou_+W?ihEDHO882&b9ZLd+*=iu zg4d0JYs3J8uM1#`(?kRR<758k^YlIQg}4R;dfxB_F#mO-&s@$oeEbMOo}Ql_&&Ob8)v3d%Ba=Vh=M%$e=Ka{8DNsEL7am{SUr=RQcO`BvS)4RC+tfDSh*r9lcBw7X(z7@V<&t58@X#>}&K5K) zOHZPs;wZA2ZLfkR5hM3_2W^5!=>QibegwZ&w)R^#{60rl*sCDvHxX!1N`)iwQtZ~^ zg|xtXaI1PMUQ5?$b7nkJnl>R=3ylT&j}(|_h>h66m8xs<*;Hy5(PA$BZI)`g$&z_(00teqRn{%fQEWyn%pQC zGe4wf-0KzS*U<$Vo=2V6p)L%2$C_YZ`L=Q`CyvklX4R4p{lh3>iY#{s-7)5;P5X^N zS#nudNb9kjuI+q^^(Bt^)Xy#oR4FNG+q%eZJ)cBDqzrZ2Nukv%Yw`K9<9$b_$*XBb z%EMK1b1X@dTWNadR%Lz_?zbKg9fsd;{#{dOIUKhA)uwSGH>BNtZ--P&)k7HWg4>4h zqpYK3>QdjLGU9s@#aMT*=P+e&?nO$q_S4FTW64TY zn%Xwd>DzNLu1|)GM&#-bx6#_xtPJ9zujvz6$8JG;Ats~kU>)LymND$ImJl2ViH6Cm zC>@F+PJcDJoRX-2o3qQ?4c5=lK~!A7-x2t#?k96YAbI;->d;FUED80DY8C@RdXFZs zxzMySADNJe@iDAGC!(p)if##cn;``hwRKg=Vde_$ipzX>4~ZRG z??;2b$9uUEFl{6-9tvmb%m@?2%y+X3yR)P0)k_7LOKbdaUu9gADnC<_IkkYofUYvq>B2yBeCbp8rAWiCN*vS9C-Xj(EI%=A_=%m~yTSPw^R>P`sLgwB}Ap@aiGQ#7-Y zmeJbC$|Z~eAR+qQ+kZrNx?5IlVHeSePpC3cFi)TXLwHY&o~`9~pMU!}@^Ad*NJ=Cr z`l)o2>D)7tw7}>PKMR2J>`-*2aL076(dgYMrl$i@W425kr-)bLxfY0qB<+RYd~3x8 z;>zr$_AR{7J}TD>PohB7x`Mpzm#I>}ahSJc7HMSbg5aJnbxvZIR$BiISOlT7aQ&jY z9DiP2Nx}Xw2pEgjl%B-I!SFO|(WbHiTA8r2avG1;HNoP~*q8tdHN42KW6NnilH>lQ zbJP(U6vo+usxKIWl&0s=bHagVm!T>OtjXpI{T&)b(f2gbh1!FsfcF5%|Bqqp! zakahvQ|Arqg(#g-QCP{H1t*-5fkIEAHWdf${B05QH~1hHF!(a+j;(5TT@n{yAg1vc zyvN=~>N~t)ag_u2qip?o;pcOQ8!YElMG`hZ1j-=;FUBtdSWgQLd8R?Pe~o!0E_w0R z%7nHi#z-UukUw`xp_D$x>W`w+`YY}PD&EA^P&?dGu?8N;&UR4gfQ~};Ecri?5O|@? zhG6=KoD$8~C{x)ccm%OAG4_q?ekw8;x1f*%(Ak&eM>QS-Jw`RN40#7e2FU7#{M>z>z;)i&srKLu2&W%4xsS6SGc78ld!t!@4FPVFGb zFu}IVvD4=m>fo9BQYd)?YG;9tHEo@!QsHmjwz_#c(elz<7Jcu^0fT-2U;o3a9Ow#m z#q>j>I(Nb(VsNmZLf`4jGGUVpq6vSJ`lo)vA4jY4OhQC?b_IhKplM_b%ZSAB(583Z zN^9XEes|)Kc;=U?U6j7ouznxq2Vzfj-)N9(s7JC|7Ay$b`%)#d2ZHS{_>nwVVqFCg z4@KIMpgsX5YE6+E2{2el^0yWhIS^WCnI~A;W-S+bpW6L!dZTLpX~`JAUEOkF*$#&Z zL*qm-SEU%;4e8uZN^Vr~_R59zB60>I1%O%`Cp#^Jrh7RBa&4aTmPiCt)H>&gb<6oiXJU?(VY^~iI(%ZL5Bv> zb8CFA+=T;HqE1?e3XCELkHK{+qR~fn24*O`xMF|xQoZUAjLd%@Ws&H?HcZlS(OMV0 z-`|Q+&TZ)~T-lciT);K>SI)j_FxgXVeysAdJox(F_U`kP8>f9(y3huK`rE!bn6xLo zW9uF}U~u*tTq&hyO1eJ*rW|)EtWuA)?ODa$tmc9mDL#)^YGK%w_wTgn1QY&BXtO{p+$tr!14arAf2wroigy zM8{o$EChvvny@kQ;Vg{5rBkwvp7}yDXi-qwPLL`qdHja%+RsRGsMNULSO@vN2BRu2 z3e}&$AZ!Y_xpC1@V}M8mG7W=KKp>ekY2Ov=QLA;s5FOn#(}XqFbacyT%0T4%7+gX9 z@chW-+wLWDbPEREUpU4F!WN3VpU;mRXSQ9gt3Np>^v9^^{-Htb@y|Dn1b&CIU-)7@DLC< z{fjS_Y6GGA{P1vqg*xZ#HwJ!{P0v|ZhHPN1!0oZ3XTH*@WPrE$PE(JspG3ZEi$i~l zhrl#+Xf&w0RP2}@U);W!W)!0hc~=uzk+dz`Pjj7Bh|LCF8oQ=MN!e{&#KbKH730hH@Lg^KUZa6Xx6|BX8 z)^^I_pa~abMqocH=#fs`_-N9jT#4?q!0DYxwHDGe?cTFqR#(C*P^5CubT83#j8zxE@OA@A6hr!dqPj@>Ref9(XmJEvSjO-Wdq6;-id2I&>5@lkFQ^T zD1hhbK)3In&LRr**w>)Wp~bK##h1MrF@1Ci&WwC8;gv!nkv2}GGYYRlSI%CDmVj(; zay^rz&}55?{*^1G*3Gj5-Fbg9x;y^1D1d+Nc2*X!+uwCoHWtcOqDxp zgJ)t&c&GG)3;UiGOEfCO+3!F6kx(OQQkrNt>*=9)#bD7}Q0wNaMV;<_f$$TpO~X8? zc17v#V_dsTkyja1-jhlfCg3WQIA9lN3O6kEi?K^$eRY=Pz%w87C}0&dxU}UFU|a=N z2^YEvcQo27)Rb_KY%V6Rgp&eTft*1-x=t>@mj0dKn8$LS^<}$bg4e^Od@%{`edZwQ z^pe|{NzP0D6*UjpQud2$7%=-1OW{?RA6*W0H<$i06xeBA^>#lgwM?qPgtA88yfdqI zQBOOF?zcP|=#lKK!~{#Pvtm6k^$blA^Z{mzHX01jtLcwL^t8TVCW})EUQqdm91mR_ z+(s{Yf%w^n%5EjXLx!zLQ$W@yeyAL=o_$bSDM1;$ZiLW1j?Ny=?>Ugpva0V7Dg=R6 z%Pw``J#~~mDt-*o^)$txnA@5cULUt#Z3s0HIwZ9S_I&n2B^!hvNpWE27j&5#ZaUjN zdv@3Lqk`8%g&LXD+i0zpw<^CdQdc6RqFR04C3Q^nsfGk9PGLu;#&4@n8C)r7`FsCS z*25$&%@4Vs?m8g@A*46B?(C*eh@srDfV>KZ8#@MJiF1|zF(sgbO}7%EiUw1qed#9I z(fJO^wF&MQ1mwSPV{Xvwv_i?Kx;^ zVenu!_SBnfS>*lt{{2n2!l*oq8{L%p(5~VK79n~>0_HYt4_n$fj6cd5hOWOj@kO7y zKbZ$8=x5PQ>$SXA$f_!8=yG)aEr42;mF+saghl4=*QG>XMtZn9#JU;d zTZus|aVRIx8iJ`;CTu#*r^By6YfRlMC-X27%P2noj8-7fYnHWTHn$QWrm9EyQ~m9?~goi)LEA$KY;2 z)-PxcW8NSG*#Wp*Bv1k?RKlEt)_b5HvGosYcuv91bfOU>sQT{#5?av|3glI zDXxjw2RTE|xx`cYrkOBGIv&NOHba|oUqoR#34OBh3k%f@<=MU>Xz%zMcnS@86q?ih zEg(xFN}3C1?E1WF@d8PcC35MOzg-Z1h`>Mqnhj+7omY$AU?L{Nv7DYq{$6wZ#!=^c z6E_SJX)eDGNtN;WF5tF@Pu&$r{Gr}(hcYb__|$F#xmo(VD^ag;)=N#-+y3p#8tRto zFJ>;p&bCC{j90(*snlWw(_$@7d6-@Z0QzE_BBF4dS6a;9BZK|lkxRBJAI)~CR1<#5|6pyflPU}v;-K5V zpcFsX@^)6QCZzlzyq@=->(MY~jH$%!E(RmQ-sI5Nh+C#P<~ZuYu<@JM-=G9XuMEnd zydsZC7$k)k@F(rCpdbLFUl&eS3Nmo2xYpTwjcl)=*|hf80}iKlPOGKNi$3peKC zc`<6GjRR8Ua7*3d0m4?#_OiTW%50ERDr@Ldm2}<;s6c-fv1Iy<(rG?T3OK33aa{fq z>i4pwY$5G5zD`>ke48@DchT5%wB5^yb0{`C0n zAFQ_BLQ!oYHI?YtEm6fNnDTj1JVcKh9!E`z1~4?7kEp44e{w`jI=!?eFZeG79cIF` zH%2lAlDww)Y*)oLBU??=N`KTYw4RR7_{jRC)%g6nC3r=V2m6(SU-?;4F9QB+GAh=r|#D2z9pieVTZ@T$_=_YdAcK+7-x%h)N+-B8A6G{SzaQC3(w~!XH1#I2tZp_-tIk^{L5iIpt?lO$ze9 z8xJqPc~H|8rGwO5>ZSDF-%}R278n?$a6rl$Un~Hs}|z>|gKx(He%(_Bhb$h}e{f`S2Gd1~$WYQS#2oCEjL z$fK`u=5=?ipkW?Y$svywD3h#6pigspVR&hYGS^GZdPqQYvMNo%64M!ZSi9&@ni`jcpU?jR$@FjVUaOiMR(tPo`|6} zRIJ?Bw`&`6QCIiScTo3RX^Z?$xilH1iwRBTKy&4v6Alk&ms+ax9Q2bP;@M`Kb`Y1W zn9Pu|h&zy<&sfnb*#A1wirL_(BOfhasB3?}+seOb_%$?W#_GUJYN;1Q{qvdnKjidN zV}_0NlWTEWPU|~!H{GOIei-t}V*CxSXKpAgQ7UuBH^2Vecy!uHs{t?oV3Pkm&L6*} z6125~5z~Z8qZ=d`E}R|=P2of&=bPxfC~W79PJ~YB1>~rY@2lt#A%mHEy^o}d=iW)H zymKN*_gC~seV?X}E=~nRfd}snPX8>gPz(4bKkFv3!|kb^rT}&_Tj%e7VJsONv)}Vu znJfSzb?(4A3o$%!k6U;-kn0;7;GGmtk*+g+gIPvziK_vmZUxy@st+=lCHg7dzt01; z^$0M~5A&a2K#o?8T<2Cab>I2=mKA|2x}b#|Nd2CWw1rALbX%1i{=zO_O)a9tkT;a8 zpX9+uV!5a}bMN|uEA7JMi7vNsl5O&@M>XO03bU=^Iv@`k2#2P;!USYftBF4@@Eq(I zZM43|A`wtH`rF6#&eeop@70$|WZ4FK^w{Y}4Kq=9pU?Vue_uA^7rHnkj7JH2jr8$Q zB%&WMjNcZ6zu!5XhK|(Bom2yyR$RS8A(lm8*Z| z8hoM7WxUI;BZ-RVMDNsF@=+E4kPAtI zu2~tdl=C&(Zw&qpjtXUA6X$!4nH-8s*?c)#>SOWeo#amh#cM`04I@Wy1z*AAh-@s( zemHOSK{@%M6TG%)_ zqIHvZIBeAIZ~Oj!*u$Uix8KK)Wv<4kp9I%*HT51-Bw?yBBV|?% zx#UV?$oD&iVi^P$%ax)Vh`J*aTbs+tP*^k}qeek#rF4`tKlNWb;C?eJV(#H+i&v9 z?Jb>hl)lYlw7@s}ac7jWA^Gc4U=7x)iKqX3ZqGUPUk?}BWI>=8U!xcN`*ttq6%?rn z_yxK?wSVAu=vMYHKy~ZxP2{gCve(ehXSJqt1~hP(=IF3>ID^s95^+s!M-MD*n zZ|`+K=1x!D`8#%>CF^$XdlRBP8zCN#xh+bxz#`fVXJ>CRa3#9uG1rtr*A#tR4}XF@ zf4BMSN*9JK0aq2$2)I?^kY8psU4>6*@9#32rM_cl%^ZLK{(~G>@MET)H6E9>A-8|X zl_f#@>;@!jc>>(~Xv!iyC6EuO`LkyMT4%De;B!2nRP6bEGdH~EXy(!FT(sN#jJs?? za1z|@iesMc(X7N%meG(9m*5ZO`)^N{89$wvUO)fsrEfmeiG^ctE}rE7%3`SP{mHGx zu=cYx=T0!(m}6|(m1#x_?nqBI=Ni4o(-*f+6IoPb(20k4n+pVG0Zff!myt&O(RC0^l9sCYL`cO|U}ft1zR&N_}XFPNH3Cxbi1o|ksP@K8uU zZKMi8i`KLKO1~hTrrIre?NCwbh_qWVv_$#9_N!_FJ@&W#@2oXWWtbJbaC+tvCK?yIok`pmJpAWpvCvMm;j`c7A2qO8bK|7W(v?BYWXZ}WHE zoqj^bu6t#`r}>CcD3#rh#VZG?+h&e;&F(&Z;U3adSuc`l`|ZKE124m>IpIF()8UA- zyyEF;cH{W$zy9xorF-wI;MR#fr)IBnKm9ZPU*zw5QIVY2hIKSxQWLmAHAdr(A^;$u zfL9i%$E}|PW>qAyBwWTa$F{G(avl}MVV<~_h+*AQdk9EiNOw7R~ zEau#Z@Q>SBI`lE-2`F*7wVY(p2LDoz8<+keH$$THjOo7|fd_|ehjong0un`e82gWB zso)2@cNGwzlqsJ{!=dB2M2?H6xbqVgr>FT+GPe*n|5DHp0AQ^-=4b1|UN!6y(vDZy zrw^od?pC=e&B7k(hItBZ5)}B2c}OM1hRDQNP}L*&P++iT?4dGB82lkJ?Hq+E+TtE~ z>k2=rw7`v}E1tSvN0O3$cAx$rYq|@uqR7vP0^%`aFm}P_#!4p)lopzT9EB=?V@A0& zz=_6gICe~e6aW(AMVaeJ(PJh;q0(|j!&{x5_1GOLm==*znD#Q#+`N7@Q;gJU zKnFqr%|~eZOZK#uBMS2R+Dhc`nOwSG1-Fxf^a@HMdBg-t^>J1I0!$$ZxMH^J1tgMxln|;r(fO z`Oixw9Dy&5wc*cC-f#Q=NE-iGTU@>)wjdc4FIu&3nYWNp?fBl9?}^DyfB9zPk~=nM zd-%8e>6-rO2?GEy2P*3XXbXZ6o7}emV8BulWJ1=qT6T53D}23HfeG_Pvmi$z($_DFr2 zr4AbNiQEYk!vl_PK0YFk8B?05zGu)5voV_`Zr%`{?v@Zx=K1to(n-DUhMPb`y_6(15D}?az-mfW=T--PhH1h{RBC zx>Wk?jiLOPtSh>6^KT6InndGvugq8)-uX1+D7jd+TP4!S_u%^Uuw#DV?ME{z30 zDvGQjjmyIwF9e>DQZQHrH4W?qdMG}N_)JaAnkGg~Q6ZF~p;5Ns|LmB)ZH^I|U_2Vr zt6CXB)3W2c@+{QC%4X1&b`Y~$-q%0GztzecU;Wg_IyabZ(sd9sw@C5%8WfRu!}oav z?~UjOhVK~p-q)O~c>3{D{=pylSK=dY`GYp{jRUN{Cdae-@4nTLX-l@0$1Q@ud7Yh| zy}d`2QLTx-&}ZLQfB`HkH6g0LWW=SCf+yPEgMQZS$$9m?KRF`$rB-xqu!U~fi5txY zhXM7LAN~-#%LX}F87iTJEr!%86OXP(`2~g3U4qS4JUP8gjTSsBvj70&j9VJi8fgj` zDZwe~ED+|Dt_Oy=hT)|W0(DA&AoNi&09)#zMM#_|_~>(xaj}3Lf96{Qi2{n9=*eH% zk-EAIQnTZKGhfzvn1!a5QjIGkAz712e8^%oE+eu@^VY0>hzRQq+3c%x2TyXkoBTG3 zQb%v(_kG6u&BlJWKdUclNZE|UcpI)x;;8Z@`nnqez`Q=2`~ebaA~NC z1k-5bG#s7BbX9t^T{vn3o`$~7CW-rp9096}u)4H2FBjmTZ69{iM2fX4SI)OSnASLT zb8K0-GJ4b?Ek+A?%5NTpQWr<;Umhm-bS)RjCATX}{0xU$YqfaFybXUfPU=gnv^y49 zyqJp<62ABI;>VvPDiK~rdN`j*O1nX-?1!-cI(RQHSr-nslY;NjPd9VX6mce@ko ztay)0-v;0`fHu`^?O2I~BjmGaPOT-%FB}q5sLma(`q1e0X?#Fg3DunpqVA(DcG0g{TYz-LGcKy>cdiGo|~3-eJaKM^p7sMX$UiZQ0a`Z2}6A>7rJc`IQ_}5fHP;-XL{Y%2e-;|b@3u``pIe1pJgKe zxk#q4jWR$FduxA@hjV|A&k3ES~!k84|?pTRPc5Jh?-1EM?;Re&5e3L32zBq;sJ1QA?T=TlrF$ zQ%+Ms_%pp;*nr4b*2ec<`Sb2x3v-!194<8XbG`+pS+&c_Vih&Nx$Xt#TzgsSHAl0n z^KvB)|f4u0%YZpjnSwW2wieCD^5K0H~kp!|Iq^mqJkhob?eyi_GQQ^l;%EGf(;<{KIoC z&Dn#}aK=bnrx+Rzbdq`YHBz}npg|9M5aO{k0fx=W@m1Eono=)g-W9%Njw_b0${0H? z&rp3No%kx?V4M83&1f%?N{gV$W9}&!Su${-HE8P0ct06QBXlg1P-5&6Wc;aPAV1L} zKy^*U_NFH754#*fox`L`&mLRgx+fFr?c^(^t)ywBgjvf^4FDlV_2On{mXwrHKxVxA z7ll%M@qTGN&;D4ONn}=kMEUh6M5pk2Y~Wqnx0Typo)+6!mD+e-JQ&rY{Ce)H^0Cio z$xH6)0E0wmXswov`QO$SH?yZ+I{>(cX2Kd14~c}+<#7uecGc@}UQAVvOrxPl=eH%L z#nOT`Pf)06+KN;fSXP_F&`x~xduEL(h>p)PMNl@CK z#A>npL3^?Jqm?Owb{w>t)}(#Z!0a_A;l7|PV?(fYFzyc9tI)S*5j9w&kl8;ET{WvV zHx)Cb&Kr-1=suNv{lbxdEoamAhedQn@EjxB4yC&GB@F=433+y*F(eiwl^kzR+L8+9 zv!+{)H6s^O)H)e^cY>gi8Mbd}Uf3Tp&Q2vAdsL-a{`g?0?$>U=tjgGfoMH=~p3^T% zmSWau7=4LHP}R3PE!45*l2}>cb?x42thA9;rT`6=QII4?i$b#)RsfnP-e_!`hA;&M zYKz4%qES!)WZ?dyanY&YLwVX!^7{S;tK1dY3Oc0CR^vYO`Q$IDU!*PnSR0_~!O_Mk*h9pp$+l{Kq#0{l@kbIBFfNRDSi%v8D@`dMn zS-C6?xbcGs+{EjT+<0$!H0YVy1sLdiVS#;X{C$bH;o&~XGLB&F5RBq^znc3A)tC&H zOs@4697lc$@6otAoJO9xXAk4g*n}vOQu2TwGg|0jZZOJI14&plD#SB23LPmuasrDg zq{<^{zywH#%+VSi&u3UdJIUa*78fj@ z{t8KG|NKFhQXu!}DC8-3*y21p_ATC^Xj9W#gv`vvESMkpElX#SDr(TdlTaMwI>~L{TnnMrF5$i; ze>r1Hbxu27!U&VN67qffX;*#mosasQuVGn>O~K6pe?}S+BZEVIMXe-WIbgg&1qd9JeI-; zNER5pFvc`9DB1)W`}DPBj8y8?xK3YzKl$^+`rv!~>BG&I`+J0j3_PKruwL!{DnQ)| zOhGRE937dEZ!7pjgdjm9D|k!8)b122M=mdRyW8h7e8X)}m}9-EdnQwEO&2T$NNpNf z3{I?1l@;H7Nm|Y7 zG=CeQSdV?C=m>ry_A&&ul;bky_zW|2Yc!bTWzNtn0WqmS3VZavPC90}0Dk*KZE}#` zBtx{MxFKlTh$0lJuV~|0D3-myvNv--iT=Lcn33Do{T$n}@mW)G;v<36+_IN@f7`~n z3}NpjK_n;%fVhF81UM7pzMrVMlJxw=YGFJ%n8A-kgffFD!G|rf>>{Is-qpsKB;ul> z*u}$Gdm~j9O6Kf67c-rRi;GRNz0JSB-bOa1YrX)Ro7_FMb`B+c7bu6pP~IBX`@7`C z0&WS2W}-*&cZB&KY{aUa)J_G7p`9MpJ>|^reoiJnbh2*D1yr1(rM5inpPKgwxN{wtU~fggI7jb$j8OfAXOfSxfjL zI}0m+%-6(2VS~_<1d;WaST;sMI0WMzp4D{`ajCq*eR#OHWx(P?TYCP6qKVz*AKiBY z&P|ni=-|=^Rbvm8LZ1A4$Ze#?zOfj{TrfJcj%zat77XGJvtc*$NglewutB>df`0tc z|Gup2{KebIx4$^vy!gtQ&u6(v<^RZ6DE$K`Gl5%GgSL=J=_Nd8zdoAbsql(Fo>L8S zRt-s&II&~y2Ud{k123fJ-b0`oY0I;z2Fh&nQ}QgxUV>m9u1Up`>z^hkzWu%BK?Wdj zdRD(8EiH?azopX9i>EdKtCI_ye+wk6<_sspE+_YT)w0CtfiZYdxE?dw#8Zx6j|RAd zS7dRcAu&c|CUA(g1=6il$Jjx6?1r~&!X#L6D6f6kQ@c!p!_5bwJ>4HLlG*G*m(Ba? z1n4WyS1g3IKcCnM&~7a0vDnbf#`Xu`<91yonR3&DRn1s!3TAH6FF19dAZD*3%7jKt z;H74l0v?`+BZgf2_Jo{A>$eVL8=m-Z9&p9d5H&i?R2~T(UXOmnGeP>qX7azuOP(?>yO_ekFQN4v>XW12IH8 z);RA?cD#CC+%gdE9%bPM6q*p|>_vkF=kU#ta*(F44#>@J`??UojG$9$k@{^QB5%1# zZ=Ny}MA$P@!YbY+^=3 z_#Q*VU&d3ktS9M=Wy5Q%i!b_+Dt`|v>?Lg@n&12k&?JiBoC13E5w&4FvaO-5c)RRY zrqM*D@L&IGxC$nC*Tc+B#22+SPE6{TeBk>i@vOjdM8kXjMk0fvIah1x>Y!+z3ohm%P1%byn<|ZF zE^)dvsoztjXaA{xAFx(~;0)Tht{4>j9k)}TorAO7&vgi^}oR@Y;alF)q+{ts}lwTL@phDy}v73xcG}nhK3m;@l zyT#>x`W4Gj5yBVDk;AT8d5vCU;xAy{lu_`wF^e}w_uL>;x-%}?^6(R6gk^Y@UWv-F1Sq*DPM%+GOQ`5`G%Xw#!g#-))@0@r1} zfXp02>i4wk+?HAe9@{;AxURTLa;o3=|59QKGtHf| zRIZnAsEXBAQ`|tmjRk*Pu=6abddQk*TI|s{K=s-*W&z?-V;|-;>DX76eqJRZW%iM> z0J4tJNOqEFwUg%j&4-OO+-p5_Pg~}z)xDRl6qAVKLct3SFHi(29Y1@5Z zg7Yfe`Ab{MXKF#&D9F~;{~-5uP_Hj}f%wI_B((6x5|~AqQ`q-{tmY6iQS+mRdE3D3 z&1pJlfEA)Q+F2BZ6e~^sS@!7&?-@~w?i{L)Ba$d7CmNqSY9Vi3fRAOFn=GYnmwtU* ze=Rpeb!TDr$->8u)6Xm)S9N6)UFEI$&bK*hr08{7h7pnVv`*bW0n}Xhk|F1Oz=5Sv zvAvv747}Y3DIzYICMq*+-7+IFb<@pStAjG-So?FqA#t3yPX?OiR+`mjYr5aFTsWdh zeegmHlByXak`*HFXV5pw0hcnmHPuWUecS|{+~FZxk_LNOIms7I&^_Mgw82Y33~+f( z@6JIsGEL`g7}SbiZ1=SnA993q63aNYM>c!5k03}Ky~Pu!eFV&2a9Zg;uzB!2)pRq) z)p9lv8MKbsU$<+@x*61Z*zhp?h%gSOsrq|Qk@WGv?DVur^?3Vo$?R}7ss9(8DXO;X z%Xv7_9`k0klA&G8WI)W>gfRx)`8!}VJ!-IX15HN6n)scAGl5W@F<>|d5T*n7vumVI zlLO!1mUx_$Y0SiErv{ zP$lI0L1E%gzdNBMV*Nkl{?dH$)izemGYU;g*uh7DeNidANG4a=u3s4h&5tV-2UibY zR}VqdL#i%Xye1=UF|sI_RAii;lTIGGR{dh1B4c#<-5sN4p7KcE?QmIWqo?uiL0_SR zEuG3i*Hm6NpW*eaQ`O^>vaFU`qffJ+hTFafnEm{AbH-e5od^5F1}jOo&gegY`DGg@ zwAOh31yYEgISq3GuFU{?fkPr7pouK`xXjo%Oth8yHS9znCm2)&qlf5pcSY&=IB=Rc zBMs@nt%-_xrCobR>u3)1@;rtL469LnG#u-E?sE)UkbVfP^=38df>GF8eP_E@g1M!)pL9d%B0SeMeN-EjEt$SFqx0Y_w$Y1~NzcnMY z$JNZ)U~IJb$<*P;qp-}qYo|xyzxTqj-uGlig5o>D6bpKP^3-|M^_m~ANXC>&I?hP6 zGs-EZDpN@n1qS^{%?i8BEFpMjiVEN~uM%w+q*{~qvX<#GHx)=9T3rui%V}h)z4WvS6juMeFVUbCTKiylj&*iEG)Mgwun?ic@AUv^Z)S;+a&Ko@Y^& zSN{DOXGcn8w9){D6J{7)2+OwxA4Q8@M=60Fl6O?sr+{lsuKYLHBM7!{-Z}`^?^hMcizfVhfu$Dx(2al~ z4Z`iXba3Spb77>);S?nnZp$Iv{mS2zW%0!->jAfA_mb+t;!VTdU!!VEOx>F*Dx`YjQ=6`E%k)ktT%cw93K*+ zc9l)^QcQo)z5cYIIDOo;aLT7N3LTULdH8c}=8Tn$m~LKqA(i(6YzuAjVHl_I}V z*agO{Z*=hW5AVwYTUpGA*dDBhqHu0neAHl{k;9Sw+?}DzGl5T|Vx+EjelaJ%Jw5#Z z@H=xNUSq8IS@=pL31b>(|}3}B03)Zc<6-GHhD zEmYPuH5DbS_PHVD9Hh)~`OBCQ<=tmd9Y3=kJo+&Iu%LaGFC;%T9SRpBX>_4X$oe); zqv-r@R1oDNeARm3>1!8jcpN^yU&_)2{o%3>w4f^rlUM#YTk)Lxn;&&56CfEje!ZAyit>sUEVxT3XQs3+3^Aq$!*V?frWs{Z(8ZYnPi^MG?q@1bdFmeBbV^{v zjd5Pz{j7LGil(-g zuhiOWZc^cd4{|XgC5o_?F}{(;<%Dh*&3hT@waS`-DiLWL2GIA)J5Nwnu#N;At&o%b zZPmX|t+Jn=r#Jh&?*CwLeP_#5ci8~XM7L^vZf@dwta2DblSSW=+VoCZNU>s(@BJ!6 z{Wq?B4PPYI+wj!Sp5JlJ?_jO0xwO#QzWevdu}7WhLdf0GgZ1V=s#}Zl7v^U1z&hqS z`}#RIIL1LmNCO1H%(4rtgg_G#-+}VP!h+)CQ%F9pSerGM$W#bRK<0&yiSK-?!^2%? z)6JgMXWui78MLc6N9IkC$016M20ge@CTC{)OpU`FRb!eb8y%~lnAtDN*D5scC3I6x z(l|3edsD(x#0v&9IB`j|Bm)Ula{8O{9d!yE_JLHHgrbWfBq*UMsK^8jSS;Ywjb@vS zB(krFg->a^MnT6ba)n7W0XujZv68hJ0d7uXJiacms!+<_bz&|7@HFE|DK}V?2J`8- zy|~m{B*|IV&+m31k=CKeg~R9sO=xP+HE^+iSMNH)3Sz!+1jJo}n2JtDPmPfEAm^yT zG>MO_zqZ>ymLc2zL+&%vth04b^rCZshxRkS2v2p6{cntCFV@6drq#YBEYVtGtn&%e z&CksAF}ioZUu|U0E^DYTAdI;Cw`PDIcFm`V4MhnYMS&9ZP*e~aa!zR&os)Fq5|1n( zS+si2@wGO;$22xUykz7PyEj`=v#Bu`u}~rA!p3xlyna0q~wz}}@^ zham1^r~!=mIf_j*i#Lr}f;rL47vNkn zSL95>OYJDr+KULDV(a~Ad07yGh;E}wo%q>kpm`VATIu_h{rE!`akAto=5O8uis1!s z{B+xs&>)*!``yoKFLl{)u1wCdjaJyVWq}dHHmZu4Xd5U8rkX_?(bs}y6<(+H^I~LL z2h~?%3mJ~7(EAuVpox>RY6cOs%&?9V#TJO>!{$66q3PJ28n-ziq9|g9P7|_=pOc%B zE*s95dw&Ia3uoo^?^g zcg!L~Cf!vY!iVOfis4>fbY-D(3a2C%CaV#}??#^c?7r z^q*m?QbY{bocI~1Bf-8^lEmVUhHCE*@U)ko=YVC+HZFT3fquV!+*wyEl*Zu6&xg-rJHSiwZXM= zHFJqOC(RD0)5Mf<+#}A1T>a4slIH#N`0xu-pnfUA&zK_C8e~hBimB)2&C5O74AJ(WvD-sp`TCwubXMkKUMS` zr)D=l_z6FNgdGs7N?w=DKTs@0 zVHzd`WKDID&SCewBoE^5KDuuq#q&na@mdFg@*yJH;>pum+FglhjYcK)wvXRVPnX!7 zA>i%-yxBO}gfN?(0$({-`!&EN%WP<8D47M08gPt@f_o&R2SezxIPsM4vWSD>ZFtri zeld62XYj1mFg0f+gJiIQ_sa4!ws$&w2j>J0zAT&PcjU_cX#~Ix7o|uS`Ej8GID$Dm zQ5>?BZR`1_k7)X{&zh%o$61}UM4UP1I2un3j`;r}x13wbY4s1eFf*Zsgcx}-{u6yD zB2pXD|n0i(ihgC1~FKp5CCe?7P`%4wtGm5qh6_KSXb zry#+Y#O6t_sOrspK@F!sT+k%xMHDi^C<*DQ3_Ls7sj9OiCnXIGn$RyAr(=GfkaOz^ z9~)!Tddft-;xO&Gg!owNY9{P;El^v4u`P@2^RzBS?ug1aJrOq_t9T`~na!P?WP40LT{+X7 zn$T!w0G)A`efz{U^W4Qa!UUT!Mp{w3#xUA=r0Tf(w&+L4oVkd=u2)T>0bfAp@pvOo zv7pbDT_ZpLjGxZw{vMT2ETp4Z!md2Id3riWTu6u|ilwj+84+Hd>D&zgSul)eY4Wu& z_<^^_Tzpuq$n`ReETxUJTv;q7_2HSFjuBXh?(H=^8@m8fXQQYppg_#xju<%{N(am# zyaM45a^}&!nv9oh;TfX1QHfrPta-!ytZG?|=zqw~=9UJZA@_O|Z-G|KYQ-b2gXfIe)o|C|nWkKAk^3J@LR$kr+a~V!JvP3Rk{j zg!^jEn4!=xxtoBtOsPpK8epSqATE5MnYFRnCgX!;>xV7hDFCc?W~WVD6wSaj7)PKK_R%i|aSm;<-yphF)r2h(cE9jj zy4a{b;too@JOB+!W(Oo9k*YoiTf`|D;(_g1|M!{u%TO9%EVv9RmVx0Y$GR9Bq>o_U zD2FVBmPMJ`2`JmtP2*;+VhC`sE*JfiFW_IWYT%U_=Ey1IlqLCFS9R}h{BFf2>x1{c zeu*Sg?L))y0XKb)FKo9I#t#RHvAAg7BJIiBrMaB43T^w7V{XyRkj}{!PQm2S`VePI z)s4{0uD|t;a<625zqu>79`qs*TQSD?`dLuS-fC+I0K@`@L1r0HHRl47bSRd0U;~1t zVT_E48=Z5n?w~{Ly4#0IxA7=)hYtz-5uF!u)B5?0 z{L|;Bvwv*?YKb`TZelz@Fe~wVcy4_ODR+upEx{1vl=z2UjigLJo$d#-O2`8_?G~2{ zN`9j&n}(tJ`v(Dveiy_HWmFZz#gagH$$!Z8vV1o%#e+{qh?XFVcZ{SuKZ7hJ^s@v_ zK672?Bc_GJ@aWNu8qw%3qvl>4F?@&!LmS0V0XCB6b074Yh}T@$h*HW=MBv>L!Y-jA zEv2ty?PmR^)3T*JDYI36D^idwj6=tBcMCoba&K$UrxX1JDgQ7V<9mygm1A&Z=JJpi zub-K`B>Z)&TczGhcDe#HUp3KJD&Muv^5d~?U>PQ|JD zxXtHJ7$!PzMxrgoOTJLj(uAvI1BO8>?3#yu=@qn+ThD}CF{s#^6=XRHJ^i?ZWfNf+>dVFgBWxz+wlrG0jX>OdUSvJ!XOl;eyplsaeYNpO4JNpfRF-!8mdACg; zwa{LU;Pom>0XlkqcXmZ)!@0_`EV?$E$RZ6-Z-MB&*|p-$fdVq z>gpkfuk3^0r^#%~)Xb}Vj=3w@djBy!407bTtas^;~q9p;r)3Vt~`aF+tFPUD;Wpy)B_pIN#vt+3(xoYzkto9$=y3%mj_C}*PP^QZI zCN)FlRBwv7M>ixz?ev1@-*uGvap|3!7y*DM-q3_{5Nrbo@On7W(QmpwLp8Xmp37FT zh=op7Z;h?S4a^@bM;MO7+vB~Vx^=<{|B!o(_~vP%16~=PvG0drVqPQpbg&>qs6={3 zE|K&J)4~g5;<|?xWXgMZZs3@d2sMwvg!!V!T_k$%cdDr;AS-mJ>&7k&*X5)iU3a!L zW)o~zcYIdU{PX0xgX`<9u{+$JT)bv7`@RE#2);GO%c4U z!Z%%n^=&7Fv^-CZmrH~>4;mXsE^9o$TzOaISABi$Qya&Z(|Z%kKi)p}i#V1Kbwdf! zZJ+?%m=$qwhIv=Om@lKBJ2(wK~M>{rp#4C$(Q)TN2$vws6pYQcsO5#ynwDfUj>V zYSBfSowL^Pm9mjK2*(7XlDI)_SUJuqj2O>iLR#vF)-irl%6mt%l60T4R@7q-VEs5>U7Abqha#EWuRsNf+%KkEP1o9@OScy5 zNOHO`seUH(P?#~##lV3|YYCg66rL!tv=8Z7r5PTIUA}T3;Edly$7O3QUx>O=?AacT z+y3?u(D4jNXW{4ZZT_~xj0+t;x}fplV$U&C?84!?A(DvY0Lqx z^I_-tfhOVeNZ!J;0x6#XJX38u`&B5>zVO%vJ6IX-d?;>35TVKxj$d_;pH99!}M%EFDQw%(P^4BZ?F7jrT}mf1KiN;=vl{ zIbR4;%CA1WV4IFb!S`hckaca)0ARTH54q;tZ}P^pq~-Vs%YKyQnO%@GnE;Lk#|F!qZC4^6FlE~LLJC1=-oJA( zw7oJoo%6d)*z!Y<#w($gb@b0wzBI)v{n1l+GXtRK@VlPP5-^Y?;>nxXM=SYT%aR=_(fRq8 z5N`hJ4O=Rl&H6)^%msZ0sYH|H!vyLM^FWGOpDTT<%W|ew>GLenX^-(rLX6G!5B!`rHgSd==3n(pza!#7kq=BE0u=Rf9<1MqmlxTMFVlX>nW^LPfpvC z8OqzmeOrL1zY=KD(^`eEwB^YE+~3tZXW2)pg2F|Oo=S%u44N(ys`o}Q zo4|gf##=pH*c*vv!Z@;e)3Z6?(%JENW|S!?oivQTGhtB1$cmctFTp964pR5x$cQA7RWtow2>GGmXgtiJvVgDS$SoXlX>mz>(tR@#@tA5m$D~w!9hyPiS zXzgnH>9Trf>xv|jAjcCW75My?>(q~Gt(E_&;0%Pz-(%C*xtQ3@Tvj<{^` z!=EXv^B!2U7F$$pHykkRMQ@EtyFmD7mmA4_z_s+BhPGc~YvP^kippHkmcLP=IAJM5)kQKibSg`Yh2r#v!aUk`0!q&v36R^b_Nx zvlWaYUNd)2HP0)=AMr=S-01|Km)R)SlSI7Gg!ZJ*O{_c)C^jllTQ9YL#`a9rw< zd`0D01gMY^BL|adyExcjnLN$fAae0NDK$ap*&Y_>b^otPhVz7{Y&=f|IpGg5?nTMO660{6s zT}r^OS(0tfsmH@uO8{sgkPQqNz|09MBdFeya>X%Nq=4k9l*DHAI+Z0%%ov2iPl$oTz!OBWA ztkL6_0`mT;#q;{MxBIV@?wy|gce>=rUoc*ZrE=FW2sdNRxRYX^oT~m}5$4U8#moaY zm;?z5L!ef`C6=PB786y;Zhmbh@Blc;p39oE1EgnQ#?wd(1d_sQ8i*VbBw|@Xg4X#= zy;wDbrFpWCOMcQYWFt8nkQXbl!a$n2GEIJp)=AKk1W|YOatW*tW>Qvd(~Bl_?V1Au zuf=e}ttDM_6J6gTl~7O5{|j>Sh*udVy-6$a*7|*mmN7CiJiZDbMEEXHjn^}Mh!M3f zvg*7*7g)h`qoPdx?v1+0ZhZx>t-})k73b%nnQMEpD!|`-Q4TPh3+Z7DWwY``@STBvAOO)>I zEb9@k{xN4=TJ5%Db*2NHXgeu-{OOjYmn7t$e%ZtF2tEYbMPuTn3ndlKDM!mFaOw_V z+`wQ+oBdhG`4Ir4sTy{~TSd=_tjuO1j{xG{nY=GC04rR$sS9<=OSlBK>?X3trb6!H zmZ4K>q-wXmmRl74eMkKON1Z`^Ze<+obCJ#bQTUZ`4$=OTM9w$fKgutDKkG*fy)FGdOh)d*C+Wc0;g zJ55K<3COW1(xjBlFhn}Z?k_koXl$!1@vWfiDzln)UTN)l>T&wFQ-#syg>d$6t=J_m z@pGp?LZ-XT;c&Z_p%f%bXmahKZH}qfSnXIayN`G7EH4aOuE10MOP=J7RkO~Ep3jgAh4J;NZUrnVfXHywET9QwU{kKgEm|Yt-N1c63 zMEc#gFF}m5xkXtXIG$M}cj9_)ZZ=#JBsH#HycGCYwJ=}!KVgs7`n>*9^*yp8rRf-K zT)w&(jn&+knSYM|Gw|iV|J6t~5#RJpd(W!+miD3z=opYWZv_^Bh$PTDM9$Mba;jeS z%@uyew*SEXWcs=N?p4`}XF`=L`)#~sceAF0GCQ{8zeT+YaT~EF?-gPPi%LCi*HgWv0C029apGDmY zRz2f$=|+vBR*j~qrLt=TzHtYgLDlWM_qsv^EjY7Ta(HbJ9)Gj0m(`!C9e+2fEEF`) zgP~s=Lqgkv(`HH?^O1$qd^@)8OPR1IULw1dcwU{eNwz@b)*0)#jK zh8X5<_7+=e3xd)40YLu%B6(a%c*;okoxHMOs(?rcmqc+yf;B;ghAf^c5D=^Swa>>G zu?%G9&zt)oh`TI%mzCVNJ04w@5#C*Fa(5~1h)vOfh0R`v$lCg<=T1mMUI4!d*+IXF z4^@NZlibt4!q&<$yyfzSFQrB=G_B$HO4Rpv#dlD7HMFy#_>B!5a?FK8yVpB~Dn2L_Ya113y4Nn^+qAtpBeO{BK&{p73f9x=b<;N7LBIrjGkKRrhx8Za;>zjwl!-9DMeP{!<2VKVZ z>vC<9)5>#ZHNow`j6s8oWsnIpQuZ*BC29nycPjb zSA@rUUS9+=zHEJy(a=?<{omQaZOPp^d#^2F0~t2xqjD5_jQTZ2z_=r3J)EV0N?Fp( ziALV6xHy;#mDsx*_~;oRB{4Cz*5$krz9rzG;9e)pim7OiJhXbFJEYm(yUH`+u;0V#4*3b4vsls6@x_M^LKeWt?Kq96g+U!N1)(P!6D*L0 zf#oFbx0A$nB$SCfr^9O6^crF=U&Fj$X*)<)m zRh(P!n!oc?w(N@5KjbzL#|!_WTwIvn`=&8h9`fqXp6+>FSsqgonomV#Y6&H=kZjdydk=QdWJ zs4pbzb~Ka>N8MS_xHT7ExSwyQg=|VZe73J>E3TT$U*`5idye~{sy`)$A#lU&@prA8 zOj|eqOlkkj6b#~9Htj&pZZ$khBtcDZ$S)U!hj_W0jn-;+-onuxMK##dG+ky~wQ2*CK%<*g0|li$NUYL0)3<^5Edrlc&g-DU6cf>i>X#r*O_CTZoN!3|I|0=;LDjhtEHLpMD$a`u{^d zJ?b8-_UV5QUu_E;sM_CIC->77hZ0i=<8$|b= zubNcdzfzJ^C01(4A7Q@atQ*>v?vBlt3iPZ)V&5>aV`pm>z3et_99w9<5KS^YJjFhz zn+iwq_0cmVlI>9%$!ZCCD3lS%Duu|FfG3YUxG*+`ks{03@{9DA=vWJHDdL6%Fbo_7 z*-H}leM{UQFCS$jT))++PjzlI;MVBwTN)?#a@<@wGf`lM#{h0-lIm&tPdV)6ulv-R z(G9rjBF{f$?{hp;Z1fUuyNj?^4>{TaDtSr0u_gU7J*mUy52YM@yL$zy&+Epf3aue zUl@bDn)_Ih*XrBE^l5_XUwoYYU##qH3El&b%(}asx9tdd?%9kV)Gvp;UrYRl+zRZw zvQy8$603<91s!7{#s~BVd|ErANn}!WCQvQB-T@hG4Bn|dGGgWB{jy7iPS>p)m#n|t zvXBdDleV1I@NQ<#)3ZEI&9v(cFaK%}y0KcSQ_}xxw17<%hLoxj?5{h2%FFtaFNfTB z=9c)9Xj9G!fZE=6m{ge2Tz@oKL#&=g7CLY|hZvF)Qj@t4htg&*C`o0u!5(WWR)vvh zA~{~zL85L&{0NQta&wovvH>{r)$&jXvK z@4JU{=9p!*tW^sa#*wtI&SFKdEk^(zoI!8Eq9y-};bfx?Qa*~5nAbCSA{6)E8iz{M zJR6%@a>I$-}UfsxfUSdm0>aJyc`=7{i0_<@J8eEPS9EdR6P{ogv%ukk9lq zSIJ@q%=`>{9bC4#}w729lwr&-2=UoU2 z`1AaW)1NnY#q5$Fe3Vm&!jM;4fpego;zZ>hY&5S`>T zZKDUU1eQ@B++l2yv`s!6hiUqWnP0wdN^XGVL+g{9*B|&j^Vu`PUdWTAx6s1Pi~x>* zIL3~4-TWE%;aSD!^SP%3RmCV|VjO8{>{bsG?F}>QZ6o&9(w#`k2fE$FxF%Fj#zU6vD_armwiEWUV!F373 ze}3ga}9s|ZC?qsh-$$R9g6$qGMgrqw=_QO z6GCXX^u22zj}DsOP1CTwwU`_s?~7P^tgUR4ZQeY_5L6?@fRV_Ho56zFOl9EX{aKpX zQj1MIfvvb5)z2o>W8AjE?4KtgCJDni{hribf<2PCDoo^7;O{6-pVrV544LH!mM@62 zx79NzfYokr_Vai?z%f^2-sh-_1YuEPTnbsyq86>Rk5GbSqH?%l4NtxZ|Gr~Vr$68> zURffnx1ES*wZ7~^`2|eY+kGo{^%ZkTU3|FMqx%C_?1bW&fF`8|hA&Ii%Lj^&t|Uom z%=FxgGUZ_FMiU0Y)^`Rvb>wH|YP`(Bf^qAO?9Q=8aSnGkBJB1D!wl=>-hb;`@W1!c z;GOMWi5g4)(dXmg%U-|&6S-MSYrEpv2(9>MnU`<9o>=TsS-cSA@Ok8YcUkfLZ;z86 zPi5wMrs@!HF`F|HKsBh7d5ivRdMtcM!w+XDg5!WNX7eFoVM2)3w8rmM#$1FMAH`AI zbyFO*mC>x2F2t4%Qa{}IMXl{(&KMIcH(2^6*?0ao$OxCtQ3wNxE7<98&>tSEK|$Uk zc@~%$IGClX@`A%5SS`6R-SJ$WPGO^1tDqVwUm__L0K@@WS`=j+&gQ%j)qZdftQ!ZE z0upX9J!-qXhb+8yPK=}`GX!HYw7SL&NP^7358~7%PPiPl%Uf=+d=vHjd!6eaax;i; zPt7hQtxlsIgvF=+HF%jR{z6>Z*74xQ?dPMh|`dsa9fw_X@O8N4D86oV?g`93^JnxC6* zx<~Fehui7zYg-yLL!!D2lr_b`>%y6}UNFQ{ncgp`$cxpF;}N42M)(LK!Dc6GliGq9 zI~RB;X1+7C_IXfxv9=_(RR@y8KMKWMBI!(5mLg}V$EM zFPWC8sPE3^`N6Ka>V3+&lzW-f12s1WntPROO0O?Rzep@M+q8QzvdltsiCO-Bv`S{n zM?IS+1-3+2Gi#aP;K$3P#!JlR!Mo`#8>#QcnB6_y?V@u&eNzzj`@{9|^p60=r-A|> z;5A_pP*1GblctD=5P8Q`bX24_ z#5J3bP(&lE7(ShZnQp!^4ZzScO>qcOn_dZq`Y@}*nemh)dZld{erm?Rg=HWc&6pDL zl?9vEo!jMg@m+$Gt7he1g*i`nc*WhBIF^@81+~7J_iO7Pdb`x<{--2iUGNXNX~egD zv$H4{Z)q#;a1i4!$2*Pwc2;85u@xW!=Q1o$Md#C&^yn3;g3sH{7+LQ-4!(~SEy}GvNTD4Jd~kA`sP*G_{n1HmBa2FPi4FZ*(+ zy!x!wVIBD?k%acC%eORd=eRA*6kN1dhpzja+^Xxc(*Sdv}2hQ#E!OLnIqXIbD@qEHk!!m)sWv}U(5J#ANMtG3K83!p&Eo&K6|-a`XMWbX+4g2g_seL0>i9{ zk_+cMA`!C>vvc=aV*&LGX(HZHb9o8?hVlNaS&CjDB%%d5Q~ z@mbRAK8y|25qU)@mG>FuD1GCNpDX5^+QcDKL$$)tK)7?gPG=;td4b_%@9Ay1~uPt2aY_myc3Qb`WN8ZZ=!HKDO(|JoZ%T2)jroR9aqdIGn-6v-3fXziHJ4cYrNOe2=u~1G_&-JYg zyxsD|PP__FVqI(ebOS-lx>C@oeKCXZ-bs7@-JR1Po~Ng2+FQi$H3_tAbn9(`XMIfm zE!&Zwn*z>16U2Bb1d{~2pzi!$!qsDqH-70zOC5%VM=f5*aH^^X6?ad^T@7i85jgFV zoQ+l1n%w(z1fBFpg^|i|OCB;_88UwI)sC0jV^W3v2@$5UVv4=jJiTOwnBN;9ABe%w z0}(b`bmP&%eo^PV`h^E{O=RN)z)55Vq;6#(F9)wd(T5VH?*i6bbqKb`H30}wna}XO z(Xe%@KGT=1XP}lHE)fPykl=QHvKDinau@;{?!!};HFGYZ6xw_X3;4f_b?cU?R?_MS z#wzK>VN3`wuNmgsnJz~&XY(MvVrc2~DnMRC6coi99P|hV2@)?rb&)=%M$S{oYqwxD z(epn1(dIO)qGlPVF2-Y9oM9fg3gm>}#m=?e4Xbf|$}J;!4A+Cv_^{gL3sY7vzv?ePsfYaAMF+w9G( zdKW*{;a;&<&E%3G_x_Y9G<>`RV_w6xX_J|H&OvO{tAH5AE5h8Y63MHFFA# zkDLl_Ri|2&(Ard9mtIW2rkBGmX&zP;2pSmCQE4QIn(RI)bS|?TPf~M+ANAI1P2W!g zmG2bhoO|sU)iZgyl{-fDN4i_{w#OZbm#4)wf3qES`W`Wl*A5v@xkF4n;LS6Z+Hl3e z=0SgNX#irV1m;)|EC)g9p)3hV@|{vogY!_wAw~Fj0l3lpzN9e@r|Ka{FP%@R_O}t8 z{Pxjs5cxj2Jvz`7@a4(r-eZ643TVYcN7iT z*>&}du_LU#>k61zHg*uYgC4JwJS>e8CXFNx+fZ(#9NjK^LH7U2KxU*s@YPfw6Zghs z(e8Z0YdJoWu;(sY%P2L;_Cs-z(ME8cf~#eC!S@lhIeX1|tAEJNA-<)aA-8mu7->MC zcQsecSx4?0!uKQX0^$y7rKQS1D4nG1@ZsDICoc^R3wrMKH3M z<0ux}xVEKjx3hbP6*B6b=SI+mT@6W@dl&Cr%2k-}SQ?JWrDKRBFNR^C z`N|JwGlf>Pyi4u8r{$q?=)3UO;ru~g6^ptVZ?c4+wI3il%uTXJf^AIWbGc}Hd&_wGs4xP-Hv)#y}1mi!w&zyia?ww~9$ z{D~CAmk&w{%B&rH-n%MZn6T56x9LZRXA61Ww)s1(s?kfN@pUm~Q{%rsWfk{d=}RRD zWqP%xb;~mNkE^sby0mxJ&5$D`LC>vA=a26T#;osPSMqzlJZak>+zMS9s&ir6GkL{x zTLHBe${%dc!&1jX7NC4pukN^%6%C(SkuHSmYVJ0cU(?8Q$yGoX7z)XhU$uR?%GYbB zF+oWX@$pz1l6r0?9USGT!_vgk(C*T>ofr`zduo;5vPIct8o7&HFDFSKR=)#2@ZC+@ zh)XPF{bbZZ78Fd^qvh#Z)qn!>`16G5axCoJAmnqLu>#&N&Rbv;Kr|U#T^4Nuu5 zb5F%E81ru{%AS$aqPNi^Wi<74wo=v)J=CnASJ5Rr-(EbF=d;J9+M-EER(l@GnM(Dd&qR3SD3e&`9DixmT>hy~u#4W1&uQ^$mu=! z_$c**eEb%g)4M!^=>^I>da|YUiE72cXli{|Q9Wj?$#dqW$NVK~Sk1>Pox|r*VzP$y zGxvL@vpm^&^u8S^b!Z%{_561~IfJbB)pW*dV$${bJ-I)jI{`E>cfmLruk_e;LXm3w zV>GQL_-rKycH9!+nQv)iJSkl8);}zek6DnwE{SD97tGs!D?!G9rINe z6JSpVs?tp%h!PS(sv<_P4Ivo+J_}u%*Wnb0?u^^Y{uv+Bb@@glHwH&W3$^C8@*h@nU?BgN6bAhN?YRNJa)0ruX9 ztzM`jNb36U^wCE7aep=Iu;w<2=0=a|KDL!#hF@$N9v8y!0Oyj*;TGn_Fwu`a$2WGR~c}uLMhm_>q`%>P#s-`nz3J&92#a5%fMy4`yI?sN= zc&E4Dk-j`X{SnE=D+5ASJ;ul(Hkb;<>15WcV?!xlGn@*Lz7!->Cxu+VOGtu|<;I_7 z&C5@=QwTzy!~nlo)nYEI*x@Z5W$h4QAl7n?7lDgu83sE6 zRciyX^Oo6E1o6^W>5!_$eb_Izc8Wzma{Rifukkq1?eE*4%Qi4JxaAPvI~LD(H|kY= z-x0a`p^SK{_dULPHj*PTK~gM}ctEs-`=QlsP1H$Bl`^Kh!+b}QtiHUxYNA4ImTP~j zGy|@vxgg55?v;BfiC2snP~t@nxz{sgedJ%SJa!r$mT>>0_(XGNYV}Ftn-|#0OI!>l zi&7hbW)rS%0sHGY1JELCDV=w9wTUtsW^4s?@fAacZ+Z_$LLSdn|3hvCv6W=_>`a$K znnM>3!i55OI)uLd`(st-rP2+guN!iSO2)d2y^@7qeSEg6WCN+U0Wd!AY#i(AL>}G} z#RmM|9ILU)IO*bjdin8b(&^ggRrlV$L}39fxV)3njA7r|3}}v6q7WU=FgL55H$n)| zvh<{~^KKEInUDt2^ZNKn*&d`$%D$oS@N&)TqsLX%Y~>fqB6}qCS9BY*~Dzx$F3A9*~yVy4BKXI zE=SU5H6~YU1J#S?rsr#_@-A;OCjz|IXvG_;B`#%;eY~U83g}Yslm-+HU-C$V@y6Ny zM)f(j>QrViVt&~4amb|p6E+bS%jx?X*5{IoFGgPcx!F0|f8E!`zdD`2tK7qf3TDn! z%$-kC&F+-R_S>8I?3`mEIU#A4YaZMSMr{@N;)_6_ENf_%Ci^%a2O;S_1`eDiw5RNu z5@A}*xGjiyvbgB-XlR%t+%%7)j-uAPDmZbrM z>o9dUIBqHWe%-vz%C-33<>$h2TBaItz`%-?GC%Gu0W8%Uvf-kTL67xFi5J^h==HYC zJyZ=DzE7#Cmji9rZk5Rf5lr2m>B+7>@{+3B80_f?N@_gaZB%B=n9Y`s9X^qF{1K0jF2E&St$U`_ubSZW9suHo+RGQ2WFxqzCzPrd9zPt*H*kkbsDSg!ZrB1Tc8%gSFz{)i1^YKIoAIk zz?1<_X5$oA92^c~Hu~7ncP~AkF?GV**FZybS}d$H!|3)<(a1BUF6zau(?4~tH&3bI z`dL?0UK8$d*`>S*0C8<;G|c!xZMGbPLxLVy)hhwkh`I_sY5=5vTGbq#)JTWiaGku@}-cYhahERxB>@ zUvE@4?|>{^8Rmul$TzsyomvDrFv!f0^0ljnP$g{PQV~)Ag4{M@A>By*Od}z>3|(A1 zyH`=u=r4%C9hzJTG5t}Q6_S1Mtgk%FD+govxOxOHxRmStqb2EU$AcnlO1Nj%adKXw z&$;k?@n5Z;`Ry+*1vcTn{7MY|uh+rXI<5Wb1HWQ_dCswZ!4q~~f9xBDiha~*o}<}0 zTEb$)hfsz9WR(@Fi}yEG;lE(@)IFjOVXF_W38* zD=OZrOPdHQ%;9P{245;m^0O<7ztM8zw2{KtV0ySV7})!8ojkLQ7;7AQ5>TjH+gD+K z#EZPGQveebK|&ncemDX0$TyN^2Dd)T88mZ4hsUVD`@g5tYU39_OOBE82jniq+Etpt}_ZY3+fzVTK~ky?T~dWRut^NV9Sc-&C6jSBFvyROf&bU;$(6 zMBM`%M-LE2e!)@8B;y^_;kj_nSJtsf#M8w0K6lES%W$mfoZ9Y1aG4Wm)?y&XKF#+A zk|#q@u=&wWpvd$@jS_41L(%22S4Td^gL}*{q|Ewla^;TYKjhvbN`ef#&N?56W_1+S zNT>vl%%6QkFlFrgY-8-wDg*$7 zXGX;~cc_=-hs=1UN}kDQGY>$c_~(=zN~6>v`6X9o|LK*!*E(yLNRkd3S5GMChmdK zeUGeO#pYAJS2yE>rKjzz`V7aH^w{%i**}de-LrTI?L!O1XzfOK3aqujl{R@e#WzS+ ziB!4cPXxy3t{ea{h+sgVLp&@Ye&Sh|EJs@iN_6a9-u{Q&XT$=tb|>HKKD58z!t@o< zaun*%9wJgCJ%Z08WuW*62EAUXbl%E%uY$1pxMaQQ#W$`^{DVkIe_7d{t8Lq-CxKbo z+9wayqfd5=Qf>I?s?$Vx%MbJDXUdAi4A}@rtck3y642n`xl`K^*0^_Q7z*vU$S7*Y zY6q`TCRo7Up8l;QoA;6>#P)aykNbR&@+&K;18!PhyZN*4bMvyLSJ9D9PdD`M-O~Zn z(`Cg=s%5}uKRryT&w9kIK#>@P95K>4GqRoSD(OW9UKiX;RSla}af{iwvt1yn&5SH} zE2SC7dW(-VCHqF!R*xm>yWhB*_qB66M*lfMYUE3P`oLSKoXI;{1nIN;w+K|pI^d?$5)koDI4@1 z5)wUBdp)--YTsq^M7H#OKYN|4$^W!>UQtcGQIroILhle!NoYwZh7fv@5_&*-6D0I5 zMY>8ap?9Q83!#dkDn$jP6Ob+-C|zkPRjdsESu^uK&ok%kuKRM=w|?uav+lRg-Y=V1 zHF=A!IobxkP*U6xbSzey6So75_UBZKe-5uAOPl-3{2)~DF z&x0H_ZxsR@Sy{6%&vprQgntk|2I@cvA}mIqjm^OV1mHRo*w&SlW5C{AWR2bkrmx52 zBgj<|(yFNunYkAOK~b4{k^(8CZsiQup*NnBVb3a0oC*WyMjCM~ioJqMpFK@gViWtp zENvQAzf5$Y$>|W|H2qrKn7xbz>6KPRK}ozf3*Vra*w`yCv$I6rKL@EJ0aN|LD_gSz zqsn}XhLZS6@Fp66g&ca5;WS9)4SNQkUoOpi;zM6y<3Xki372?s&5BQCt4dfKd43jF zN;p;*vNGYYaYtCI*l$jIXD;0!n~2UGOAG?tR|MMaI(Qm^&E3pJRt!WYu5LQvybcn@ zi}RxSQ~}Wi@1t~mKu${IX)Z{=XYQ`14L0oc79!mK>|@JKc0wgi&*b~g@vukJLJtK~ zr9zn{trps?vFO+yFZOwa7K0T1)QBXVqm%HKE{cUIho6iV!Os01yHlLpmpee=)Vq_| zW#U5E933{N#w7*%7Yz(#u-r)Z0M$IN&Wz%O8w?4q#b%oN4QT6Vg(|~YdG&9;P-DVP z4J^p@;(|3Q0_b0+RKyCjGH`-5xpIE%+$932N*t>XTt-Bw*s$4rUHM4za=2y=iWaS? zTDO?2q>G5;fDQ`kc*Xnl}9PgVTZ@A_tIx4pZ~sRr{G z-B@`}?@=Umfl(q^9hDR8N-Q^Mlc`_|8Stphb1zNM9>0$EXZ!O72tSvOi%7IL1h2c-ur00G{UXlg3Jq=OMvf+Z9Ikd)#rUq-AloZP3DH$SO|AY(Kj=7@eN zZiq02uPG-q2i-%9YJ>q6<&ARG(scP^xZ>N2xrz;+8o2(v=;CH)3BMyaX4YA|TD_R) z*UmExoT)Cc(?i74m8XDfn`>?9!$c`tgkCD3<@VrtH3F*Hxm#&0mTgGj(JyDPzTOTO zPO$lEW;8Pn`Jc9}yG-X3T355&XeBm5r1t>~ZkYr*z2mcO?7&mj_f!zJ9X)jI2m3Fk zJpTp0y9*`datj>N7|nQ<3e~8iLT#6%rElBC)Z*kQ(sq)x&DzI}X~E3QwhregTI&#x zoXJQh*ShAY5!Ebdf29ibIEaIn!~P4ppr$pvj~16&w)NO8*;6A6m1SLVf|C6$y&=y& z3s8y0v+$y5IzRt11we`_6(2+P8;pAelO)GP9b@3gsOLLX9=4w-ugw3PC-vfuSe*8aXkiN1kAfFo@eband=HqcUNsx^GlQ*!6KY zH`aL`_vfdntSqk{hXS;q4rD#`5>A&?*yGlbo?SQ?9DWlRDAXAq6trAN9o*U>Q4>Qt z+Zyx&6%4QZ!{gG( zd!HLy@WLcuSKAlAhJ#P2I!_ai#%xOl=KG$NEX=@mBe|T~NyI1H5cGj9WD?h?{jYYD z?PQg`^bXmka&>pd%Y`u3Em4W;R+}clUqy{f2+xL9J_-PKMlEiQ23THDIrKjCY>FNb zX0jJQS>8?MoZ(%sCiX;Dc)?~$RJNI_0k~~U(h*Ay#qilP)3o|K!V^{S;g9gnJp&fx zn|h@k(_qxAjXI&7Qn8yV5pI!uPjhTMj}?&<(D-*?Y2tg z`pERqdt>PVG#aB^q*&y^EqID^MXe#gv@jWCQSN;w^g7yAaI%7#Dw!_`c+`l~IKvSq+J!iQH4lpSQ4z%Nd zJL49htiyC@4KpqBX+=I4$C7W7bHm2>WfFDe6-O6IK5~V$G~u>goGu-1n4uscu1m5ln&Nd zQIkwRvm9{byr_EE^x$*!l6M;2*ZR55PLQ3rOzCXsMrw7T*cYmQDa6yV)Q9OT)kmQ0 z-u>uhR|)V%p5ooCejG%-`5w43qJL~9p3S&lnTpIoB*9vmcbam=WVqt74U05vE22Xo z{rgyyOY)xlf3w^o^SP{n(v>I}Vd;JW@P2E{(~kPew7%bG$p5LY6(|P}e=@>oR3vzt z_uAr3_$JtucbIOF{px}b2{%@1iex;A*&yC4rS~`E^0M1M5qkT^5ayY)b<24)s+T-K zaL&+y#Z<^UN$-#}=C(gaXblf8G8X$jdYz`zPO0A43;(_5_6yG^x}Qr zzx`gtDwhNR%}0ThV3gsNvJ}rHEDWD!iHQ!Sve?#JxBPCtBjf=estLtB*AN>&*}0p@JKct9XtfvON{2H6vjddfu>(fPzoG{0^BRk+3b20`tgSLX8u;1e+s)z$jqZA z;u$poLkw#FA-9Q8S`aPxgtOGAJ;YRmn zOVEIh_snTOoS#R^KJHjuOi zmV{CAi(&E6`2HPEK!G5X4j2Pa=PxBH+BMTs@f}%&NeidGnHJ;63G;OxY(CQ|2(6bQE zc25g^#H=UIi2vW~iT@<$jTP%?S}gz{+SQsB25fjvE!@2VdwFp3LHW8;_TOxD!&`W+yuDjLniY`-uHA|>tD0)-n;C1BtOJ_<| zc1RlOGn0yP@AlaVn?!UShXvhR9$GDGUMlns3!q%6iWZQ%?G6|_P~*mP9K!kEa|~u0 zwdAaU_TqBSoQhq zqW?Im?S~Jl;#q5d?V8DAKL6Lg9zGq9sl3iOuW~e}E{y+H#cSV;TnBI8hIKppi;7#kN z#Ak!6nFOU@%+@FbM+(GVuS#Xsx0dX@ikFU2)9KF;)qHZcoO1%NsH#$Yq;1zpov`S- z7{)Z`ZANWF9m__{SdV<$q>`drYa;yE8VuUJ;ku()?#;>_czI35?rWUGrTrEhdBMTT znlPRvTzLJn3t@<-bO#C~Trc@dnFPQZk!+_!2XZ@hrUZY>P%aY9N?&edf`J7}dkgU} ze+E=M)9AD3+G@iZ$^UL8VaLG+*MsU`S&YYAw4ny=Raq(qUVWK>M{$LDUIh^m6W1FE zr+mE&%axU?rnwWWd0iT*r77(oe-C^yt>rjx0MX*8>FRSAx|#{cWX0X!p!rVmn>+aK z(m9;X9rn{6`w<^f@cKOM#&+V%-gg>8;-)2K{-^WtOM}QF$Hxt9d4&Fx8WaNy&tN|NP#l=f@7tIxyEi|_7qNx!{(p%djkAFj&Tm_ z)A;~wpIV~k=WAkQ+6h25p7|E#bTS&Pm{em{x6K^G5Ar^xUp6U1ORY2%9euaxUUmh_ zeNDb}^8FAoIU3bt-mEhI)%M6y7!=k_zAI2JUprZu7Dkn4L46#4>tR<%pzku6 zC|y;0nc8r?3?}4S z|I+O*@47?J0u0+$OBCUK=k#0D>YWz!txHv1jQp~uWl)gWt6N%T5~Wbw3%k`I!{?YT ztFmDPL!1SoQ-`$Kns|Exaa$5L(C6-S({_^2n=*!MWZ@J6c~5`ZomrPNxaA!!h9f^l zRj(bkcB~2$;I?{%{u$OW{&I%+UQ2Pt#wN4ESpAo~BB!WRt@#f$SR|F=pt@GfZm41k2WtT^6M-P z+^p#o6T3Zh1DTP}<=foqBalE65BcY7%xQieBk#8{KRVSOCi;tU{Bli2%!@i}$c?4I znZp6~6Hl!rDNN5qiYit%DnTeo#I^8Hu;^0rS9lw=b~g@PxAa}zej#r6?vKZKsfS5{ zBlHqN?ojuL+Q{qBAdROE`SI4EQZVhJSaPniDr+3?K?v6onJoPn>OMWIQQCHMNe>f_ z{Hab}hKV*g(eDX|bks|*-tn_{G_WYcEd6~w>e+BpjxV=rH#KyrZeO&}r|1%%%+d%w^U=@AoNOOj zn_UcFtlSYYc>}$xcgU#;eC!L8alQQI@KL|Fk98eAIX$EmqG`nYLUVJ_GtqjG-1%gV zDSg$=4w6<%{b6a_sx+lva8)9xp=4?KMe+ASPs__qN1cHAKd<`VD9`^c^iO~DD54;x z`Pn~0aD{8CRx#i0-O{^dmrLz%$s8#-HF6_|*H=khqd@j*cmdB&2_XFe&=`=yA>fU6 zUjpc8UFA-?W*vRQRx&=S6!Se7j^y%`;dZ^hP{sQhe%v-IzCG8$CNrR%B;n5Pm}PU1 z-;@T{GeV)S_8Au)SCK9vkr6DE(Fa>>pQiHM6w(Fa12bW8z2i~mxfTQ{E-CJj*FA&X>R2(Uh;&omVZV2FJuU530f_LP zrv#{eVYnbRk7%U%%laj^}6AWdelG4Jx zcp`PME}`kP zaSN)nE<3>3Wmh&OC=0KS6d6(zz|U4{Jiz~X0pLu(TZZQFR91avL`eP9cy90SM1T}a z4H1tjR}O+kqe?2LK>)dZM(+M8p2jd`3`U$~L^u11IH9ia#===@9&0u(?vMSu>PPh< zK|;u1E=~U-w+)CdHd2c9bqcsc6Uv6|zh`}A&trJi<(>+D+Kv_M3|2VJER0s;;rBG6 zWOUXc*BHv>6-Oy8NoU&@#H7x7Syk!3j`4piMS*)7NyXS?^jHTtra}Q8RYAeuV zNlu)v-Q}>%p?iN9>D|vPm(4BHV!7C`5)=9DOho(dzR&RCugeGTJ#UNO-kWSYfJ>@U z(9tFoQR@gZWlh=w5&YY^c{J**BdP{XXui63MgL_R4LRS`GdG9d9vD`_;K@ z6=+1Eo+Q;22zjB%gUuG%Q{ksy5Qe0bG4P_tex$m z*tFf!HT$QSH>~R&8cF2^@!`1m3}#0)6dN5>LSi+K}fu54?R!AXcu8~0`72yDf!47;ZWJz>xI`@^z|(=}*!sRN*OwZ{GbVYO zrB`F3ANzg&+B$;*TtNpZ)>VQxS>ln1twnPRRCzI zL8-?(pB3u3)XHECmK3$oH^U2%Y~omu`M}! zTL&te%qkz;hewczG!qpy!4vYeNjPG*!ks@By!dnCSjMCTj_c?AwxH^wo zA3ILFn60J#hukiXS_Gqza)q36f{rKR?LBS>iQ~idjL|Bga=W->xuF?BazXJIVfMP` zCyPTL_l_sJ7Wb8!Hc=0ODGzmxsyRj|V%DoP`=8>w^w9|g9x>xXYb(?&-u^=6bEbBA z-dr(N?TlRhzAufmNgK5_9`T=Sx70MIAL+lIUS8a>QXar54>G-ZIlo7%ZXOv zkMA!;<<6LwKXMHy8;f~cu}o7xT{M*79MB&d;1tmx=hhx|7JIX9&^o}?Z%AqcgUM3Q z_($Q;bc9`Gv9cB>e}7#(e*&A_%&Nex=HKZ!M4_`uN2?)F*G@j5v)w>h$e9WDsLo6p zRUb{h`fp4r3~WiG-KP+sfQeM*FmV0bF5m`d!SN)?1g&IrCb7wRYn^?=As2*Ot}{kX zd*`sCUard1!M9*~>RMbT;_}t&wVQ`QcfIi4KF8_%qi-}67tAY-$YelHx}qRwHUS!r zg3!z_AUIrx@SY>f+q6?}P|a2soqH-Z8G8r{61mH*(>ge+K^ON`zq6j6wbCy2r1uk7 zHn7p|rNngIVMB362dd?Da-=-okkkK{bKH}Btk_@m%tv~uWQ^I&z)-WOk|G(H+zLo+ zGFhMdx_d6#$Utxfz6!=f0pa)ob$77Mf?bR=Ot4!a-g&LtxD76SDC}Hs8oMdf1R_UJHbC&$<6dyL2Dau*LI=vC#Z!7K66D$u8-xYXaCGCLOLFygs|b*OTF zo^Dtt!NQRJfhZe;9w#fdNLvU_9Vg6_t;(7}OK9T?w;)29pc0-my)d-5@`#09!qi!S zCG}MPWA7^0@P)G?K5gYl7<|K44i{2yfM|9|~Icmn?eW(s0u literal 0 HcmV?d00001 diff --git a/Adamant/Assets/portal.mp3 b/Adamant/Assets/portal.mp3 new file mode 100644 index 0000000000000000000000000000000000000000..0f061ccac966a51ea2a0c9610d6f42154b7691af GIT binary patch literal 51060 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 z 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≶ibfPkxE1Cp$IBPN3x4=_08Xlfn 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%0D8BdICU!yxH@w`S5i&cm3TVfKX_M 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 literal 0 HcmV?d00001 diff --git a/Adamant/Assets/rebound.mp3 b/Adamant/Assets/rebound.mp3 new file mode 100644 index 0000000000000000000000000000000000000000..33518d4f82218c1d917ae6e526159a8cc1c66b83 GIT binary patch literal 45417 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 VO+$jbtQ;DB@tXg8{`aiF{{U`G8Q1^- literal 0 HcmV?d00001 diff --git a/Adamant/Assets/slide.mp3 b/Adamant/Assets/slide.mp3 new file mode 100644 index 0000000000000000000000000000000000000000..141220c3e591cdf4988c25aa0c59daf8da2b4c9a GIT binary patch literal 30371 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∨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 Kar=Mq<^KTTqAd#m literal 0 HcmV?d00001 diff --git a/Adamant/Assets/welcome.mp3 b/Adamant/Assets/welcome.mp3 new file mode 100644 index 0000000000000000000000000000000000000000..bb7cd7279897377d5df572d05b8ba33b6bbc711f GIT binary patch literal 67360 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 literal 0 HcmV?d00001 diff --git a/Adamant/Modules/Settings/Notifications/NotificationSoundsViewController.swift b/Adamant/Modules/Settings/Notifications/NotificationSoundsViewController.swift index 9be16ad80..ce45ec60c 100644 --- a/Adamant/Modules/Settings/Notifications/NotificationSoundsViewController.swift +++ b/Adamant/Modules/Settings/Notifications/NotificationSoundsViewController.swift @@ -60,7 +60,7 @@ final class NotificationSoundsViewController: FormViewController { section = SelectableSection>(Sections.alerts.localized, selectionType: .singleSelection(enableDeselection: false)) - let sounds: [NotificationSound] = [.none, .noteDefault, .inputDefault, .proud, .relax, .success] + let sounds: [NotificationSound] = [.none, .noteDefault, .inputDefault, .proud, .relax, .success, .note, .antic, .cheers, .chord, .droplet, .handoff, .milestone, .passage, .portal, .rattle, .rebound, .slide, .welcome] for sound in sounds { section <<< ListCheckRow { listRow in listRow.title = sound.localized diff --git a/Adamant/Modules/Settings/Notifications/NotificationsView.swift b/Adamant/Modules/Settings/Notifications/NotificationsView.swift index b6aa907ac..a30361fef 100644 --- a/Adamant/Modules/Settings/Notifications/NotificationsView.swift +++ b/Adamant/Modules/Settings/Notifications/NotificationsView.swift @@ -21,7 +21,6 @@ struct NotificationsView: View { inAppNotificationsSection() settingsSection() moreDetailsSection() - messageReactionsSection() } .navigationBarTitleDisplayMode(.inline) .toolbar { diff --git a/Adamant/ServiceProtocols/NotificationsService.swift b/Adamant/ServiceProtocols/NotificationsService.swift index b02c913ff..28c36be32 100644 --- a/Adamant/ServiceProtocols/NotificationsService.swift +++ b/Adamant/ServiceProtocols/NotificationsService.swift @@ -35,6 +35,19 @@ enum NotificationSound: String { case proud case relax case success + case note + case antic + case cheers + case chord + case droplet + case handoff + case milestone + case passage + case portal + case rattle + case rebound + case slide + case welcome var tag: String { switch self { @@ -44,6 +57,19 @@ enum NotificationSound: String { case .proud: return "pr" case .relax: return "rl" case .success: return "sh" + case .note: return "note" + case .antic: return "antic" + case .cheers: return "chrs" + case .chord: return "chord" + case .droplet: return "droplet" + case .handoff: return "hnoff" + case .milestone: return "mlst" + case .passage: return "psg" + case .portal: return "portal" + case .rattle: return "rattle" + case .rebound: return "rbnd" + case .slide: return "slide" + case .welcome: return "welcome" } } @@ -55,6 +81,19 @@ enum NotificationSound: String { case .proud: return "so-proud-notification.mp3" case .relax: return "relax-message-tone.mp3" case .success: return "short-success.mp3" + case .note: return "note.mp3" + case .antic: return "antic.mp3" + case .cheers: return "cheers.mp3" + case .chord: return "chord.mp3" + case .droplet: return "droplet.mp3" + case .handoff: return "handoff.mp3" + case .milestone: return "milestone.mp3" + case .passage: return "passage.mp3" + case .portal: return "portal.mp3" + case .rattle: return "rattle.mp3" + case .rebound: return "rebound.mp3" + case .slide: return "slide.mp3" + case .welcome: return "welcome.mp3" } } @@ -66,6 +105,19 @@ enum NotificationSound: String { case .proud: return "Proud" case .relax: return "Relax" case .success: return "Success" + case .note: return "Note" + case .antic: return "Antic" + case .cheers: return "Cheers" + case .chord: return "Chord" + case .droplet: return "Droplet" + case .handoff: return "Handoff" + case .milestone: return "Milestone" + case .passage: return "Passage" + case .portal: return "Portal" + case .rattle: return "Rattle" + case .rebound: return "Rebound" + case .slide: return "Slide" + case .welcome: return "Welcome" } } @@ -76,6 +128,19 @@ enum NotificationSound: String { case "so-proud-notification.mp3": self = .proud case "relax-message-tone.mp3": self = .relax case "short-success.mp3": self = .success + case "note.mp3": self = .note + case "antic.mp3": self = .antic + case "cheers.mp3": self = .cheers + case "chord.mp3": self = .chord + case "droplet.mp3": self = .droplet + case "handoff.mp3": self = .handoff + case "milestone.mp3": self = .milestone + case "passage.mp3": self = .passage + case "portal.mp3": self = .portal + case "rattle.mp3": self = .rattle + case "rebound.mp3": self = .rebound + case "slide.mp3": self = .slide + case "welcome.mp3": self = .welcome case "": self = .none default: self = .inputDefault } @@ -177,8 +242,12 @@ extension NotificationsServiceError: RichError { protocol NotificationsService: AnyObject { var notificationsMode: NotificationsMode { get } var notificationsSound: NotificationSound { get } + var notificationsReactionSound: NotificationSound { get } - func setNotificationSound(_ sound: NotificationSound) + func setNotificationSound( + _ sound: NotificationSound, + for target: NotificationTarget + ) func setNotificationsMode(_ mode: NotificationsMode, completion: ((NotificationsServiceResult) -> Void)?) func showNotification(title: String, body: String, type: AdamantNotificationType) From fdefee6afe850208e69b0333d0f8f3f5936badde Mon Sep 17 00:00:00 2001 From: Iana Silosiev Date: Tue, 20 Aug 2024 15:54:17 +0300 Subject: [PATCH 034/106] [trello.com/c/44KDaoe5] add localization --- .../Notifications/NotificationsView.swift | 27 +++++++++++++------ .../NotificationsViewModel.swift | 11 ++++++-- .../Localization/de.lproj/Localizable.strings | 13 +++++++++ .../Localization/en.lproj/Localizable.strings | 12 +++++++++ .../Localization/ru.lproj/Localizable.strings | 12 +++++++++ .../Localization/zh.lproj/Localizable.strings | 12 +++++++++ 6 files changed, 77 insertions(+), 10 deletions(-) diff --git a/Adamant/Modules/Settings/Notifications/NotificationsView.swift b/Adamant/Modules/Settings/Notifications/NotificationsView.swift index a30361fef..57a56e75a 100644 --- a/Adamant/Modules/Settings/Notifications/NotificationsView.swift +++ b/Adamant/Modules/Settings/Notifications/NotificationsView.swift @@ -84,7 +84,7 @@ private extension NotificationsView { } }) } header: { - Text(soundHeader) + Text(messagesHeader) } } @@ -109,17 +109,17 @@ private extension NotificationsView { func inAppNotificationsSection() -> some View { Section { Toggle(isOn: $viewModel.inAppSounds) { - Text("Sounds") + Text(soundsTitle) } .tint(.init(uiColor: .adamant.active)) Toggle(isOn: $viewModel.inAppVibrate) { - Text("Vibrate") + Text(vibrateTitle) } .tint(.init(uiColor: .adamant.active)) Toggle(isOn: $viewModel.inAppToasts) { - Text("Toasts") + Text(toastsTitle) } .tint(.init(uiColor: .adamant.active)) } header: { @@ -167,10 +167,9 @@ private extension NotificationsView { } } - private let toolbarSpace: CGFloat = 150 -private var soundHeader: String { +private var messagesHeader: String { .localized("SecurityPage.Section.Messages") } @@ -191,9 +190,21 @@ private var visitGithub: String { } private var reactionsHeader: String { - "Reactions" + .localized("Notifications.Reactions.Header") } private var inAppNotifications: String { - "In-app notifications" + .localized("Notifications.InAppNotifications.Header") +} + +private var soundsTitle: String { + .localized("Notifications.Sounds.Name") +} + +private var vibrateTitle: String { + .localized("Notifications.Vibrate.Title") +} + +private var toastsTitle: String { + .localized("Notifications.Toasts.Title") } diff --git a/Adamant/Modules/Settings/Notifications/NotificationsViewModel.swift b/Adamant/Modules/Settings/Notifications/NotificationsViewModel.swift index e305bf500..2940f04ae 100644 --- a/Adamant/Modules/Settings/Notifications/NotificationsViewModel.swift +++ b/Adamant/Modules/Settings/Notifications/NotificationsViewModel.swift @@ -143,7 +143,10 @@ final class NotificationsViewModel: ObservableObject { } func parseMarkdown(_ text: String) -> NSAttributedString? { - let parser = MarkdownParser(font: UIFont.systemFont(ofSize: UIFont.systemFontSize), color: UIColor.adamant.textColor) + let parser = MarkdownParser( + font: UIFont.systemFont(ofSize: UIFont.systemFontSize), + color: UIColor.adamant.textColor + ) parser.link.color = UIColor.adamant.secondary return parser.parse(text) } @@ -168,6 +171,10 @@ private extension NotificationsViewModel { } func makeCancelAction() -> UIAlertAction { - .init(title: .adamant.alert.cancel, style: .cancel, handler: nil) + .init( + title: .adamant.alert.cancel, + style: .cancel, + handler: nil + ) } } diff --git a/CommonKit/Sources/CommonKit/Assets/Localization/de.lproj/Localizable.strings b/CommonKit/Sources/CommonKit/Assets/Localization/de.lproj/Localizable.strings index 6867a4bd8..b9656abde 100644 --- a/CommonKit/Sources/CommonKit/Assets/Localization/de.lproj/Localizable.strings +++ b/CommonKit/Sources/CommonKit/Assets/Localization/de.lproj/Localizable.strings @@ -775,6 +775,19 @@ /* Notifications: Select Sounds */ "Notifications.Sounds.Name" = "Geräusche"; +/* Notifications: Vibrate title */ +"Notifications.Vibrate.Title" = "Vibrieren Sie"; + +/* Notifications: Toasts title */ +"Notifications.Toasts.Title" = "Toasts"; + +/* Notifications: Reactions header */ +"Notifications.Reactions.Header" = "Reaktionen"; + +/* Notifications: In-App notifications header */ +"Notifications.InAppNotifications.Header" = "In-App-Benachrichtigungen"; + + /* Notifications: Select Alert Tones */ "Notifications.Alert.Tones" = "ALERT-TÖNE"; diff --git a/CommonKit/Sources/CommonKit/Assets/Localization/en.lproj/Localizable.strings b/CommonKit/Sources/CommonKit/Assets/Localization/en.lproj/Localizable.strings index 37bca7fef..d9ba40e10 100644 --- a/CommonKit/Sources/CommonKit/Assets/Localization/en.lproj/Localizable.strings +++ b/CommonKit/Sources/CommonKit/Assets/Localization/en.lproj/Localizable.strings @@ -760,6 +760,18 @@ /* Notifications: Select Sounds */ "Notifications.Sounds.Name" = "Sounds"; +/* Notifications: Vibrate title */ +"Notifications.Vibrate.Title" = "Vibrate"; + +/* Notifications: Toasts title */ +"Notifications.Toasts.Title" = "Toasts"; + +/* Notifications: Reactions header */ +"Notifications.Reactions.Header" = "Reactions"; + +/* Notifications: In-App notifications header */ +"Notifications.InAppNotifications.Header" = "In-App Notifications"; + /* Notifications: Select Alert Tones */ "Notifications.Alert.Tones" = "ALERT TONES"; diff --git a/CommonKit/Sources/CommonKit/Assets/Localization/ru.lproj/Localizable.strings b/CommonKit/Sources/CommonKit/Assets/Localization/ru.lproj/Localizable.strings index 16ff4d936..19e7b9d68 100644 --- a/CommonKit/Sources/CommonKit/Assets/Localization/ru.lproj/Localizable.strings +++ b/CommonKit/Sources/CommonKit/Assets/Localization/ru.lproj/Localizable.strings @@ -760,6 +760,18 @@ /* Notifications: Select Sounds */ "Notifications.Sounds.Name" = "Звуки"; +/* Notifications: Vibrate title */ +"Notifications.Vibrate.Title" = "Вибрация"; + +/* Notifications: Toasts title */ +"Notifications.Toasts.Title" = "Превью"; + +/* Notifications: Reactions header */ +"Notifications.Reactions.Header" = "Реакции"; + +/* Notifications: In-App notifications header */ +"Notifications.InAppNotifications.Header" = "В приложении"; + /* Notifications: Select Alert Tones */ "Notifications.Alert.Tones" = "ЗВУКИ УВЕДОМЛЕНИЙ"; diff --git a/CommonKit/Sources/CommonKit/Assets/Localization/zh.lproj/Localizable.strings b/CommonKit/Sources/CommonKit/Assets/Localization/zh.lproj/Localizable.strings index 63fafa539..00e455e38 100644 --- a/CommonKit/Sources/CommonKit/Assets/Localization/zh.lproj/Localizable.strings +++ b/CommonKit/Sources/CommonKit/Assets/Localization/zh.lproj/Localizable.strings @@ -760,6 +760,18 @@ /* Notifications: Select Sounds */ "Notifications.Sounds.Name" = "声音"; +/* Notifications: Vibrate title */ +"Notifications.Vibrate.Title" = "振动"; + +/* Notifications: Toasts title */ +"Notifications.Toasts.Title" = "祝酒词"; + +/* Notifications: Reactions header */ +"Notifications.Reactions.Header" = "反应"; + +/* Notifications: In-App notifications header */ +"Notifications.InAppNotifications.Header" = "应用程序内通知"; + /* Notifications: Select Alert Tones */ "Notifications.Alert.Tones" = "警报音"; From 0840961d57d9adcbd8baa91afba79ad278ad2c31 Mon Sep 17 00:00:00 2001 From: just-software-dev Date: Wed, 21 Aug 2024 22:46:44 -0300 Subject: [PATCH 035/106] [trello.com/c/zUXIwW2y] Fix --- Adamant/Services/AdamantVisibleWalletsService.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Adamant/Services/AdamantVisibleWalletsService.swift b/Adamant/Services/AdamantVisibleWalletsService.swift index 45f759909..566b825c4 100644 --- a/Adamant/Services/AdamantVisibleWalletsService.swift +++ b/Adamant/Services/AdamantVisibleWalletsService.swift @@ -223,7 +223,7 @@ final class AdamantVisibleWalletsService: VisibleWalletsService { let wallet = availableServices.remove(at: index) - if (0 ... availableServices.count).contains(newIndex) { + if availableServices.indices.contains(newIndex) { availableServices.insert(wallet, at: newIndex) } else { availableServices.append(wallet) From f0fb0e7fa2bf23a64a6fde98e69f492b29ab106a Mon Sep 17 00:00:00 2001 From: StanislavDevIOS Date: Thu, 22 Aug 2024 17:09:39 +0300 Subject: [PATCH 036/106] [trello.com/c/ceLMXBa4] fix: multi-line message with an image --- .../FixedTextMessageSizeCalculator.swift | 12 +++++- .../Content/ChatMediaContnentView.swift | 40 +++++++------------ .../MediaContainerView.swift | 9 +---- 3 files changed, 27 insertions(+), 34 deletions(-) diff --git a/Adamant/Modules/Chat/View/Managers/FixedTextMessageSizeCalculator.swift b/Adamant/Modules/Chat/View/Managers/FixedTextMessageSizeCalculator.swift index b79e334fe..9d8c6013a 100644 --- a/Adamant/Modules/Chat/View/Managers/FixedTextMessageSizeCalculator.swift +++ b/Adamant/Modules/Chat/View/Managers/FixedTextMessageSizeCalculator.swift @@ -81,9 +81,16 @@ } if case let .file(model) = getMessages()[indexPath.section].fullModel.content { - let contentViewHeight: CGFloat = model.value.height() + let messageSize = labelSize( + for: model.value.content.comment, + considering: model.value.content.width() + - commenthorizontalInsets * 2 + ) + + let contentViewHeight = model.value.height() messageContainerSize.width = maxWidth messageContainerSize.height = contentViewHeight + + messageSize.height } return messageContainerSize @@ -131,6 +138,8 @@ for attributedText: NSAttributedString, considering maxWidth: CGFloat ) -> CGSize { + guard !attributedText.string.isEmpty else { return .zero } + let textContainer = NSTextContainer( size: CGSize(width: maxWidth, height: .greatestFiniteMagnitude) ) @@ -180,3 +189,4 @@ /// Additional width to fix incorrect size calculating private let additionalWidth: CGFloat = 5 private let additionalHeight: CGFloat = 5 +private let commenthorizontalInsets: CGFloat = 12 diff --git a/Adamant/Modules/Chat/View/Subviews/ChatMedia/Content/ChatMediaContnentView.swift b/Adamant/Modules/Chat/View/Subviews/ChatMedia/Content/ChatMediaContnentView.swift index 8cb0ffcaf..13ad28650 100644 --- a/Adamant/Modules/Chat/View/Subviews/ChatMedia/Content/ChatMediaContnentView.swift +++ b/Adamant/Modules/Chat/View/Subviews/ChatMedia/Content/ChatMediaContnentView.swift @@ -247,7 +247,18 @@ private extension ChatMediaContentView { } } +extension ChatMediaContentView.FileModel { + func width() -> CGFloat { + guard !isMacOS else { return defaultStackWidth } + return UIScreen.main.bounds.width - screenSpace + } +} + extension ChatMediaContentView.Model { + func width() -> CGFloat { + fileModel.width() + } + func height() -> CGFloat { let replyViewDynamicHeight: CGFloat = isReply ? replyViewHeight : .zero @@ -258,41 +269,18 @@ extension ChatMediaContentView.Model { } if !comment.string.isEmpty { - spaceCount += 2 + spaceCount += 3 } - let stackWidth = MediaContainerView.stackWidth - return fileModel.height() + spaceCount * verticalInsets - + labelSize(for: comment, considering: stackWidth - horizontalInsets * 2).height + replyViewDynamicHeight } - - func labelSize( - for attributedText: NSAttributedString, - considering maxWidth: CGFloat - ) -> CGSize { - guard !attributedText.string.isEmpty else { return .zero } - - let textContainer = NSTextContainer( - size: CGSize(width: maxWidth, height: .greatestFiniteMagnitude) - ) - let layoutManager = NSLayoutManager() - - layoutManager.addTextContainer(textContainer) - - let textStorage = NSTextStorage(attributedString: attributedText) - textStorage.addLayoutManager(layoutManager) - - let rect = layoutManager.usedRect(for: textContainer) - - return .init(width: rect.width, height: rect.height + additionalHeight) - } } private let verticalInsets: CGFloat = 8 private let horizontalInsets: CGFloat = 12 private let replyViewHeight: CGFloat = 25 -private let additionalHeight: CGFloat = 2 private let imageSize: CGFloat = 70 +private let defaultStackWidth: CGFloat = 280 +private let screenSpace: CGFloat = 110 diff --git a/Adamant/Modules/Chat/View/Subviews/ChatMedia/Content/Views/MediaContainerView/MediaContainerView.swift b/Adamant/Modules/Chat/View/Subviews/ChatMedia/Content/Views/MediaContainerView/MediaContainerView.swift index acdeb9cbf..f99b71a67 100644 --- a/Adamant/Modules/Chat/View/Subviews/ChatMedia/Content/Views/MediaContainerView/MediaContainerView.swift +++ b/Adamant/Modules/Chat/View/Subviews/ChatMedia/Content/Views/MediaContainerView/MediaContainerView.swift @@ -55,11 +55,6 @@ final class MediaContainerView: UIView { var actionHandler: (ChatAction) -> Void = { _ in } - static var stackWidth: CGFloat { - guard !isMacOS else { return defaultStackWidth } - return UIScreen.main.bounds.width - screenSpace - } - // MARK: - Init override init(frame: CGRect) { @@ -80,7 +75,7 @@ private extension MediaContainerView { addSubview(filesStack) filesStack.snp.makeConstraints { $0.directionalEdges.equalToSuperview() - $0.width.equalTo(Self.stackWidth) + $0.width.equalTo(model.width()) } addSubview(previewDownloadNotAllowedLabel) @@ -171,7 +166,7 @@ private extension MediaContainerView { isHorizontal: Bool, fileList: [ChatFile] ) { - let filesStackWidth = Self.stackWidth + let filesStackWidth = model.width() let minimumWidth = calculateMinimumWidth(availableWidth: filesStackWidth) let maximumWidth = calculateMaximumWidth(availableWidth: filesStackWidth) From 96596e037a2520f77bce13e3093019ff852fd71c Mon Sep 17 00:00:00 2001 From: StanislavDevIOS Date: Thu, 22 Aug 2024 17:23:32 +0300 Subject: [PATCH 037/106] [trello.com/c/ceLMXBa4] fix: mac os files design --- .../Subviews/ChatMedia/Content/ChatMediaContnentView.swift | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/Adamant/Modules/Chat/View/Subviews/ChatMedia/Content/ChatMediaContnentView.swift b/Adamant/Modules/Chat/View/Subviews/ChatMedia/Content/ChatMediaContnentView.swift index 13ad28650..a25aaf33c 100644 --- a/Adamant/Modules/Chat/View/Subviews/ChatMedia/Content/ChatMediaContnentView.swift +++ b/Adamant/Modules/Chat/View/Subviews/ChatMedia/Content/ChatMediaContnentView.swift @@ -249,7 +249,9 @@ private extension ChatMediaContentView { extension ChatMediaContentView.FileModel { func width() -> CGFloat { - guard !isMacOS else { return defaultStackWidth } + guard UIDevice.current.userInterfaceIdiom == .phone else { + return defaultStackWidth + } return UIScreen.main.bounds.width - screenSpace } } From e61871b246af92d6ce5ac14b131c840f9ea5f920 Mon Sep 17 00:00:00 2001 From: just-software-dev Date: Thu, 22 Aug 2024 21:37:46 -0300 Subject: [PATCH 038/106] [trello.com/c/pUGfzAtU] Naming fix --- Adamant.xcodeproj/project.pbxproj | 16 +++++----- Adamant/App/DI/AppAssembly.swift | 32 +++++++++---------- Adamant/Modules/Chat/ChatFactory.swift | 6 ++-- .../Chat/ViewModel/ChatViewModel.swift | 12 +++---- .../CoinsNodesListFactory.swift | 4 +-- .../ViewModel/CoinsNodesListViewModel.swift | 10 +++--- .../DelegateDetailsViewController.swift | 2 +- .../Modules/Delegates/DelegatesFactory.swift | 4 +-- .../DelegatesListViewController.swift | 4 +-- Adamant/Modules/Login/LoginFactory.swift | 2 +- .../Modules/Login/LoginViewController.swift | 4 +-- .../NodeEditorViewController.swift | 2 +- .../NodesEditor/NodesEditorFactory.swift | 4 +-- .../NodesEditor/NodesListViewController.swift | 4 +-- .../Wallets/Adamant/AdmWalletFactory.swift | 2 +- .../Wallets/Adamant/AdmWalletService.swift | 4 +-- .../Wallets/Bitcoin/BtcApiService.swift | 2 +- .../Wallets/Bitcoin/BtcWalletFactory.swift | 2 +- .../Wallets/Bitcoin/BtcWalletService.swift | 4 +-- .../Modules/Wallets/Dash/DashApiService.swift | 2 +- .../Wallets/Dash/DashWalletFactory.swift | 2 +- .../Wallets/Dash/DashWalletService.swift | 4 +-- .../Modules/Wallets/Doge/DogeApiService.swift | 2 +- .../Wallets/Doge/DogeWalletFactory.swift | 2 +- .../Wallets/Doge/DogeWalletService.swift | 4 +-- .../Wallets/ERC20/ERC20WalletFactory.swift | 2 +- .../Wallets/ERC20/ERC20WalletService.swift | 4 +-- .../Wallets/Ethereum/EthApiService.swift | 2 +- .../Wallets/Ethereum/EthWalletFactory.swift | 2 +- .../Wallets/Ethereum/EthWalletService.swift | 4 +-- .../Wallets/Klayr/KlyNodeApiService.swift | 2 +- .../Wallets/Klayr/KlyServiceApiService.swift | 2 +- .../Wallets/Klayr/KlyWalletFactory.swift | 2 +- .../WalletService/KlyWalletService.swift | 4 +-- .../Wallets/TransferViewControllerBase.swift | 8 ++--- ....swift => ApiServiceComposeProtocol.swift} | 4 +-- .../FileApiServiceProtocol.swift | 2 +- Adamant/Services/AdamantAccountService.swift | 4 +-- .../Services/AdamantAddressBookService.swift | 4 +-- ...AdamantPushNotificationsTokenService.swift | 4 +-- ...eCompose.swift => ApiServiceCompose.swift} | 24 +++++++------- .../AdamantAccountsProvider.swift | 4 +-- .../DataProviders/AdamantChatsProvider.swift | 4 +-- .../AdamantTransfersProvider.swift | 4 +-- .../AdamantRichTransactionReactService.swift | 4 +-- .../AdamantRichTransactionReplyService.swift | 4 +-- .../ExtensionsTools/ExtensionsApi.swift | 4 +-- .../ExtensionsApiFactory.swift | 2 +- ....swift => AdamantApiServiceProtocol.swift} | 2 +- ...Service.swift => ApiServiceProtocol.swift} | 4 +-- ...wift => NodesMergingServiceProtocol.swift} | 4 +-- .../ApiService/AdamantApiService.swift | 2 +- ...ervice.swift => NodesMergingService.swift} | 4 +-- .../CommonKit/Services/NodesStorage.swift | 4 +-- 54 files changed, 128 insertions(+), 128 deletions(-) rename Adamant/ServiceProtocols/{WalletApiServiceComposeProtocol.swift => ApiServiceComposeProtocol.swift} (77%) rename Adamant/Services/{WalletApiServiceCompose.swift => ApiServiceCompose.swift} (66%) rename CommonKit/Sources/CommonKit/Protocols/{ApiService.swift => AdamantApiServiceProtocol.swift} (98%) rename CommonKit/Sources/CommonKit/Protocols/{WalletApiService.swift => ApiServiceProtocol.swift} (78%) rename CommonKit/Sources/CommonKit/Protocols/{NodesMergingService.swift => NodesMergingServiceProtocol.swift} (74%) rename CommonKit/Sources/CommonKit/Services/{AdamantNodesMergingService.swift => NodesMergingService.swift} (95%) diff --git a/Adamant.xcodeproj/project.pbxproj b/Adamant.xcodeproj/project.pbxproj index 729bc92e4..7b17e9f3c 100644 --- a/Adamant.xcodeproj/project.pbxproj +++ b/Adamant.xcodeproj/project.pbxproj @@ -253,8 +253,8 @@ 932B34E92974AA4A002A75BA /* ChatPreservationProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 932B34E82974AA4A002A75BA /* ChatPreservationProtocol.swift */; }; 932F77592989F999006D8801 /* ChatCellManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 932F77582989F999006D8801 /* ChatCellManager.swift */; }; 9332C39D2C76BE7500164B80 /* FileApiServiceResult.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9332C39C2C76BE7500164B80 /* FileApiServiceResult.swift */; }; - 9332C3A32C76C45A00164B80 /* WalletApiServiceComposeProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9332C3A22C76C45A00164B80 /* WalletApiServiceComposeProtocol.swift */; }; - 9332C3A52C76C4EC00164B80 /* WalletApiServiceCompose.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9332C3A42C76C4EC00164B80 /* WalletApiServiceCompose.swift */; }; + 9332C3A32C76C45A00164B80 /* ApiServiceComposeProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9332C3A22C76C45A00164B80 /* ApiServiceComposeProtocol.swift */; }; + 9332C3A52C76C4EC00164B80 /* ApiServiceCompose.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9332C3A42C76C4EC00164B80 /* ApiServiceCompose.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 */; }; @@ -887,8 +887,8 @@ 932B34E82974AA4A002A75BA /* ChatPreservationProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChatPreservationProtocol.swift; sourceTree = ""; }; 932F77582989F999006D8801 /* ChatCellManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChatCellManager.swift; sourceTree = ""; }; 9332C39C2C76BE7500164B80 /* FileApiServiceResult.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FileApiServiceResult.swift; sourceTree = ""; }; - 9332C3A22C76C45A00164B80 /* WalletApiServiceComposeProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WalletApiServiceComposeProtocol.swift; sourceTree = ""; }; - 9332C3A42C76C4EC00164B80 /* WalletApiServiceCompose.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WalletApiServiceCompose.swift; sourceTree = ""; }; + 9332C3A22C76C45A00164B80 /* ApiServiceComposeProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ApiServiceComposeProtocol.swift; sourceTree = ""; }; + 9332C3A42C76C4EC00164B80 /* ApiServiceCompose.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ApiServiceCompose.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 = ""; }; @@ -2084,7 +2084,7 @@ 3AE0A4322BC6A9EB00BF7125 /* FileApiServiceProtocol.swift */, 3AE0A4362BC6AA6000BF7125 /* FilesNetworkManagerProtocol.swift */, 3AF9DF0A2BFE306C009A43A8 /* ChatFileProtocol.swift */, - 9332C3A22C76C45A00164B80 /* WalletApiServiceComposeProtocol.swift */, + 9332C3A22C76C45A00164B80 /* ApiServiceComposeProtocol.swift */, ); path = ServiceProtocols; sourceTree = ""; @@ -2117,7 +2117,7 @@ 3A96E3792AED27D7001F5A52 /* AdamantPartnerQRService.swift */, 3AF08D5E2B4EB3A200EB82B1 /* LanguageService.swift */, 3ACD307D2BBD86B700ABF671 /* FilesStorageProprietiesService.swift */, - 9332C3A42C76C4EC00164B80 /* WalletApiServiceCompose.swift */, + 9332C3A42C76C4EC00164B80 /* ApiServiceCompose.swift */, ); path = Services; sourceTree = ""; @@ -3040,7 +3040,7 @@ files = ( 93CCAE7E2B06DA6C00EA5B94 /* DogeBlockDTO.swift in Sources */, 6448C291235CA6E100F3F15B /* ERC20WalletService+RichMessageProviderWithStatusCheck.swift in Sources */, - 9332C3A32C76C45A00164B80 /* WalletApiServiceComposeProtocol.swift in Sources */, + 9332C3A32C76C45A00164B80 /* ApiServiceComposeProtocol.swift in Sources */, E9256F5F2034C21100DE86E9 /* String+localized.swift in Sources */, 6414C18E217DF43100373FA6 /* String+adamant.swift in Sources */, 93A118532993241D00E144CC /* ChatMessagesListFactory.swift in Sources */, @@ -3470,7 +3470,7 @@ 648DD7A22237D9A000B811FD /* DogeTransaction.swift in Sources */, E90847372196FEA80095825D /* Chatroom+CoreDataProperties.swift in Sources */, 648CE3A8229AD1E20070A2CC /* DashWalletService+RichMessageProvider.swift in Sources */, - 9332C3A52C76C4EC00164B80 /* WalletApiServiceCompose.swift in Sources */, + 9332C3A52C76C4EC00164B80 /* ApiServiceCompose.swift in Sources */, E90055FB20ECE78A00D0CB2D /* SecurityViewController+notifications.swift in Sources */, 938F7D662955C966001915CA /* ChatInputBar.swift in Sources */, A50A41122822FC35006BDFE1 /* BtcWalletService+RichMessageProvider.swift in Sources */, diff --git a/Adamant/App/DI/AppAssembly.swift b/Adamant/App/DI/AppAssembly.swift index 6a310c53a..367d3114e 100644 --- a/Adamant/App/DI/AppAssembly.swift +++ b/Adamant/App/DI/AppAssembly.swift @@ -95,7 +95,7 @@ struct AppAssembly: Assembly { container.register(PushNotificationsTokenService.self) { r in AdamantPushNotificationsTokenService( securedStore: r.resolve(SecuredStore.self)!, - apiService: r.resolve(ApiService.self)!, + apiService: r.resolve(AdamantApiServiceProtocol.self)!, adamantCore: r.resolve(AdamantCore.self)!, accountService: r.resolve(AccountService.self)! ) @@ -105,7 +105,7 @@ struct AppAssembly: Assembly { container.register(NodesStorageProtocol.self) { r in NodesStorage( securedStore: r.resolve(SecuredStore.self)!, - nodesMergingService: r.resolve(NodesMergingService.self)!, + nodesMergingService: r.resolve(NodesMergingServiceProtocol.self)!, defaultNodes: r.resolve(DefaultNodesProvider.self)!.nodes ) }.inObjectScope(.container) @@ -121,7 +121,7 @@ struct AppAssembly: Assembly { }.inObjectScope(.container) // MARK: ApiService - container.register(ApiService.self) { r in + container.register(AdamantApiServiceProtocol.self) { r in AdamantApiService( healthCheckWrapper: .init( service: .init(apiCore: r.resolve(APICoreProtocol.self)!), @@ -240,7 +240,7 @@ struct AppAssembly: Assembly { // MARK: AccountService container.register(AccountService.self) { r in AdamantAccountService( - apiService: r.resolve(ApiService.self)!, + apiService: r.resolve(AdamantApiServiceProtocol.self)!, adamantCore: r.resolve(AdamantCore.self)!, dialogService: r.resolve(DialogService.self)!, securedStore: r.resolve(SecuredStore.self)!, @@ -259,7 +259,7 @@ struct AppAssembly: Assembly { // MARK: AddressBookServeice container.register(AddressBookService.self) { r in AdamantAddressBookService( - apiService: r.resolve(ApiService.self)!, + apiService: r.resolve(AdamantApiServiceProtocol.self)!, adamantCore: r.resolve(AdamantCore.self)!, accountService: r.resolve(AccountService.self)!, dialogService: r.resolve(DialogService.self)! @@ -289,7 +289,7 @@ struct AppAssembly: Assembly { container.register(AccountsProvider.self) { r in AdamantAccountsProvider( stack: r.resolve(CoreDataStack.self)!, - apiService: r.resolve(ApiService.self)!, + apiService: r.resolve(AdamantApiServiceProtocol.self)!, addressBookService: r.resolve(AddressBookService.self)! ) }.inObjectScope(.container) @@ -297,7 +297,7 @@ struct AppAssembly: Assembly { // MARK: Transfers container.register(TransfersProvider.self) { r in AdamantTransfersProvider( - apiService: r.resolve(ApiService.self)!, + apiService: r.resolve(AdamantApiServiceProtocol.self)!, stack: r.resolve(CoreDataStack.self)!, adamantCore: r.resolve(AdamantCore.self)!, accountService: r.resolve(AccountService.self)!, @@ -330,7 +330,7 @@ struct AppAssembly: Assembly { container.register(ChatsProvider.self) { r in AdamantChatsProvider( accountService: r.resolve(AccountService.self)!, - apiService: r.resolve(ApiService.self)!, + apiService: r.resolve(AdamantApiServiceProtocol.self)!, socketService: r.resolve(SocketService.self)!, stack: r.resolve(CoreDataStack.self)!, adamantCore: r.resolve(AdamantCore.self)!, @@ -362,7 +362,7 @@ struct AppAssembly: Assembly { container.register(RichTransactionReplyService.self) { r in AdamantRichTransactionReplyService( coreDataStack: r.resolve(CoreDataStack.self)!, - apiService: r.resolve(ApiService.self)!, + apiService: r.resolve(AdamantApiServiceProtocol.self)!, adamantCore: r.resolve(AdamantCore.self)!, accountService: r.resolve(AccountService.self)!, walletServiceCompose: r.resolve(WalletServiceCompose.self)! @@ -373,7 +373,7 @@ struct AppAssembly: Assembly { container.register(RichTransactionReactService.self) { r in AdamantRichTransactionReactService( coreDataStack: r.resolve(CoreDataStack.self)!, - apiService: r.resolve(ApiService.self)!, + apiService: r.resolve(AdamantApiServiceProtocol.self)!, adamantCore: r.resolve(AdamantCore.self)!, accountService: r.resolve(AccountService.self)! ) @@ -420,26 +420,26 @@ struct AppAssembly: Assembly { } // MARK: Wallet Service Compose - container.register(WalletApiServiceComposeProtocol.self) { - WalletApiServiceCompose( + container.register(ApiServiceComposeProtocol.self) { + ApiServiceCompose( btc: $0.resolve(BtcApiService.self)!, eth: $0.resolve(EthApiService.self)!, klyNode: $0.resolve(KlyNodeApiService.self)!, klyService: $0.resolve(KlyServiceApiService.self)!, doge: $0.resolve(DogeApiService.self)!, dash: $0.resolve(DashApiService.self)!, - adm: $0.resolve(ApiService.self)!, + adm: $0.resolve(AdamantApiServiceProtocol.self)!, ipfs: $0.resolve(IPFSApiService.self)! ) }.inObjectScope(.transient) // MARK: NodesMergingService - container.register(NodesMergingService.self) { r in - AdamantNodesMergingService() + container.register(NodesMergingServiceProtocol.self) { _ in + NodesMergingService() }.inObjectScope(.transient) // MARK: DefaultNodesProvider - container.register(DefaultNodesProvider.self) { r in + container.register(DefaultNodesProvider.self) { _ in DefaultNodesProvider() }.inObjectScope(.transient) } diff --git a/Adamant/Modules/Chat/ChatFactory.swift b/Adamant/Modules/Chat/ChatFactory.swift index ff1224682..e07d30c44 100644 --- a/Adamant/Modules/Chat/ChatFactory.swift +++ b/Adamant/Modules/Chat/ChatFactory.swift @@ -33,7 +33,7 @@ struct ChatFactory { let filesStorage: FilesStorageProtocol let chatFileService: ChatFileProtocol let filesStorageProprieties: FilesStorageProprietiesProtocol - let walletApiServiceCompose: WalletApiServiceComposeProtocol + let apiServiceCompose: ApiServiceComposeProtocol let reachabilityMonitor: ReachabilityMonitor let filesPickerKit: FilesPickerProtocol @@ -53,7 +53,7 @@ struct ChatFactory { filesStorage = assembler.resolve(FilesStorageProtocol.self)! chatFileService = assembler.resolve(ChatFileProtocol.self)! filesStorageProprieties = assembler.resolve(FilesStorageProprietiesProtocol.self)! - walletApiServiceCompose = assembler.resolve(WalletApiServiceComposeProtocol.self)! + apiServiceCompose = assembler.resolve(ApiServiceComposeProtocol.self)! reachabilityMonitor = assembler.resolve(ReachabilityMonitor.self)! filesPickerKit = assembler.resolve(FilesPickerProtocol.self)! } @@ -132,7 +132,7 @@ private extension ChatFactory { filesStorage: filesStorage, chatFileService: chatFileService, filesStorageProprieties: filesStorageProprieties, - walletApiServiceCompose: walletApiServiceCompose, + apiServiceCompose: apiServiceCompose, reachabilityMonitor: reachabilityMonitor, filesPicker: filesPickerKit ) diff --git a/Adamant/Modules/Chat/ViewModel/ChatViewModel.swift b/Adamant/Modules/Chat/ViewModel/ChatViewModel.swift index b43f598cf..917f8f7e8 100644 --- a/Adamant/Modules/Chat/ViewModel/ChatViewModel.swift +++ b/Adamant/Modules/Chat/ViewModel/ChatViewModel.swift @@ -37,7 +37,7 @@ final class ChatViewModel: NSObject { private let filesStorage: FilesStorageProtocol private let chatFileService: ChatFileProtocol private let filesStorageProprieties: FilesStorageProprietiesProtocol - private let walletApiServiceCompose: WalletApiServiceComposeProtocol + private let apiServiceCompose: ApiServiceComposeProtocol private let reachabilityMonitor: ReachabilityMonitor private let filesPicker: FilesPickerProtocol @@ -162,7 +162,7 @@ final class ChatViewModel: NSObject { filesStorage: FilesStorageProtocol, chatFileService: ChatFileProtocol, filesStorageProprieties: FilesStorageProprietiesProtocol, - walletApiServiceCompose: WalletApiServiceComposeProtocol, + apiServiceCompose: ApiServiceComposeProtocol, reachabilityMonitor: ReachabilityMonitor, filesPicker: FilesPickerProtocol ) { @@ -184,7 +184,7 @@ final class ChatViewModel: NSObject { self.filesStorage = filesStorage self.chatFileService = chatFileService self.filesStorageProprieties = filesStorageProprieties - self.walletApiServiceCompose = walletApiServiceCompose + self.apiServiceCompose = apiServiceCompose self.reachabilityMonitor = reachabilityMonitor self.filesPicker = filesPicker @@ -288,7 +288,7 @@ final class ChatViewModel: NSObject { return } - guard walletApiServiceCompose.hasActiveNode(group: .adm) else { + guard apiServiceCompose.hasActiveNode(group: .adm) else { dialog.send(.alert(ApiServiceError.noEndpointsAvailable( nodeGroupName: NodeGroup.adm.name ).localizedDescription)) @@ -703,7 +703,7 @@ final class ChatViewModel: NSObject { return false } - guard walletApiServiceCompose.hasActiveNode(group: .adm) else { + guard apiServiceCompose.hasActiveNode(group: .adm) else { dialog.send(.alert(ApiServiceError.noEndpointsAvailable( nodeGroupName: NodeGroup.adm.name ).localizedDescription)) @@ -1026,7 +1026,7 @@ extension ChatViewModel: NSFetchedResultsControllerDelegate { private extension ChatViewModel { func sendFiles(with text: String) async throws { - guard walletApiServiceCompose.hasActiveNode(group: .ipfs) else { + guard apiServiceCompose.hasActiveNode(group: .ipfs) else { dialog.send(.alert(ApiServiceError.noEndpointsAvailable( nodeGroupName: NodeGroup.ipfs.name ).localizedDescription)) diff --git a/Adamant/Modules/CoinsNodesList/CoinsNodesListFactory.swift b/Adamant/Modules/CoinsNodesList/CoinsNodesListFactory.swift index bee5243a6..3fac3e1da 100644 --- a/Adamant/Modules/CoinsNodesList/CoinsNodesListFactory.swift +++ b/Adamant/Modules/CoinsNodesList/CoinsNodesListFactory.swift @@ -50,8 +50,8 @@ private struct CoinsNodesListAssembly: Assembly { NodesAdditionalParamsStorageProtocol.self )!, processedGroups: processedGroups, - walletApiServiceCompose: $0.resolve( - WalletApiServiceComposeProtocol.self + apiServiceCompose: $0.resolve( + ApiServiceComposeProtocol.self )! ) }.inObjectScope(.transient) diff --git a/Adamant/Modules/CoinsNodesList/ViewModel/CoinsNodesListViewModel.swift b/Adamant/Modules/CoinsNodesList/ViewModel/CoinsNodesListViewModel.swift index fdc7f010d..4f57b6d94 100644 --- a/Adamant/Modules/CoinsNodesList/ViewModel/CoinsNodesListViewModel.swift +++ b/Adamant/Modules/CoinsNodesList/ViewModel/CoinsNodesListViewModel.swift @@ -18,7 +18,7 @@ final class CoinsNodesListViewModel: ObservableObject { private let nodesStorage: NodesStorageProtocol private let nodesAdditionalParamsStorage: NodesAdditionalParamsStorageProtocol private let processedGroups: [NodeGroup] - private let walletApiServiceCompose: WalletApiServiceComposeProtocol + private let apiServiceCompose: ApiServiceComposeProtocol private var subscriptions = Set() nonisolated init( @@ -26,13 +26,13 @@ final class CoinsNodesListViewModel: ObservableObject { nodesStorage: NodesStorageProtocol, nodesAdditionalParamsStorage: NodesAdditionalParamsStorageProtocol, processedGroups: [NodeGroup], - walletApiServiceCompose: WalletApiServiceComposeProtocol + apiServiceCompose: ApiServiceComposeProtocol ) { self.mapper = mapper self.nodesStorage = nodesStorage self.nodesAdditionalParamsStorage = nodesAdditionalParamsStorage self.processedGroups = processedGroups - self.walletApiServiceCompose = walletApiServiceCompose + self.apiServiceCompose = apiServiceCompose Task { @MainActor in setup() } } @@ -80,7 +80,7 @@ private extension CoinsNodesListViewModel { state.sections = mapper.map( items: items, restNodeIds: processedGroups.compactMap { - walletApiServiceCompose.chosenFastestNodeId(group: $0) + apiServiceCompose.chosenFastestNodeId(group: $0) } ) } @@ -94,7 +94,7 @@ private extension CoinsNodesListViewModel { func healthCheck() { processedGroups.forEach { - walletApiServiceCompose.healthCheck(group: $0) + apiServiceCompose.healthCheck(group: $0) } } } diff --git a/Adamant/Modules/Delegates/DelegateDetailsViewController.swift b/Adamant/Modules/Delegates/DelegateDetailsViewController.swift index 29d8b01eb..2511c1c98 100644 --- a/Adamant/Modules/Delegates/DelegateDetailsViewController.swift +++ b/Adamant/Modules/Delegates/DelegateDetailsViewController.swift @@ -73,7 +73,7 @@ final class DelegateDetailsViewController: UIViewController { } // MARK: - Dependencies - var apiService: ApiService! + var apiService: AdamantApiServiceProtocol! var accountService: AccountService! var dialogService: DialogService! diff --git a/Adamant/Modules/Delegates/DelegatesFactory.swift b/Adamant/Modules/Delegates/DelegatesFactory.swift index 85fa4405a..8b4929dff 100644 --- a/Adamant/Modules/Delegates/DelegatesFactory.swift +++ b/Adamant/Modules/Delegates/DelegatesFactory.swift @@ -15,7 +15,7 @@ struct DelegatesFactory { func makeDelegatesListVC(screensFactory: ScreensFactory) -> UIViewController { DelegatesListViewController( - apiService: assembler.resolve(ApiService.self)!, + apiService: assembler.resolve(AdamantApiServiceProtocol.self)!, accountService: assembler.resolve(AccountService.self)!, dialogService: assembler.resolve(DialogService.self)!, screensFactory: screensFactory @@ -24,7 +24,7 @@ struct DelegatesFactory { func makeDelegateDetails() -> DelegateDetailsViewController { let c = DelegateDetailsViewController(nibName: "DelegateDetailsViewController", bundle: nil) - c.apiService = assembler.resolve(ApiService.self) + c.apiService = assembler.resolve(AdamantApiServiceProtocol.self) c.accountService = assembler.resolve(AccountService.self) c.dialogService = assembler.resolve(DialogService.self) return c diff --git a/Adamant/Modules/Delegates/DelegatesListViewController.swift b/Adamant/Modules/Delegates/DelegatesListViewController.swift index 1387ea5f3..e07808a22 100644 --- a/Adamant/Modules/Delegates/DelegatesListViewController.swift +++ b/Adamant/Modules/Delegates/DelegatesListViewController.swift @@ -42,7 +42,7 @@ final class DelegatesListViewController: KeyboardObservingViewController { // MARK: - Dependencies - private let apiService: ApiService + private let apiService: AdamantApiServiceProtocol private let accountService: AccountService private let dialogService: DialogService private let screensFactory: ScreensFactory @@ -99,7 +99,7 @@ final class DelegatesListViewController: KeyboardObservingViewController { // MARK: - Lifecycle init( - apiService: ApiService, + apiService: AdamantApiServiceProtocol, accountService: AccountService, dialogService: DialogService, screensFactory: ScreensFactory diff --git a/Adamant/Modules/Login/LoginFactory.swift b/Adamant/Modules/Login/LoginFactory.swift index 83c401f12..5cc26a48e 100644 --- a/Adamant/Modules/Login/LoginFactory.swift +++ b/Adamant/Modules/Login/LoginFactory.swift @@ -20,7 +20,7 @@ struct LoginFactory { dialogService: assembler.resolve(DialogService.self)!, localAuth: assembler.resolve(LocalAuthentication.self)!, screensFactory: screenFactory, - apiService: assembler.resolve(ApiService.self)! + apiService: assembler.resolve(AdamantApiServiceProtocol.self)! ) } } diff --git a/Adamant/Modules/Login/LoginViewController.swift b/Adamant/Modules/Login/LoginViewController.swift index c973cf187..acb6ab91a 100644 --- a/Adamant/Modules/Login/LoginViewController.swift +++ b/Adamant/Modules/Login/LoginViewController.swift @@ -139,7 +139,7 @@ final class LoginViewController: FormViewController { let adamantCore: AdamantCore let localAuth: LocalAuthentication let screensFactory: ScreensFactory - let apiService: ApiService + let apiService: AdamantApiServiceProtocol let dialogService: DialogService // MARK: Properties @@ -158,7 +158,7 @@ final class LoginViewController: FormViewController { dialogService: DialogService, localAuth: LocalAuthentication, screensFactory: ScreensFactory, - apiService: ApiService + apiService: AdamantApiServiceProtocol ) { self.accountService = accountService self.adamantCore = adamantCore diff --git a/Adamant/Modules/NodesEditor/NodeEditorViewController.swift b/Adamant/Modules/NodesEditor/NodeEditorViewController.swift index e4d7e3a6d..0d34e410b 100644 --- a/Adamant/Modules/NodesEditor/NodeEditorViewController.swift +++ b/Adamant/Modules/NodesEditor/NodeEditorViewController.swift @@ -93,7 +93,7 @@ final class NodeEditorViewController: FormViewController { // MARK: - Dependencies var dialogService: DialogService! - var apiService: ApiService! + var apiService: AdamantApiServiceProtocol! var nodesStorage: NodesStorageProtocol! // MARK: - Properties diff --git a/Adamant/Modules/NodesEditor/NodesEditorFactory.swift b/Adamant/Modules/NodesEditor/NodesEditorFactory.swift index 8d6dffdfd..97fcd6f3e 100644 --- a/Adamant/Modules/NodesEditor/NodesEditorFactory.swift +++ b/Adamant/Modules/NodesEditor/NodesEditorFactory.swift @@ -20,7 +20,7 @@ struct NodesEditorFactory { screensFactory: screensFactory, nodesStorage: assembler.resolve(NodesStorageProtocol.self)!, nodesAdditionalParamsStorage: assembler.resolve(NodesAdditionalParamsStorageProtocol.self)!, - apiService: assembler.resolve(ApiService.self)!, + apiService: assembler.resolve(AdamantApiServiceProtocol.self)!, socketService: assembler.resolve(SocketService.self)! ) } @@ -28,7 +28,7 @@ struct NodesEditorFactory { func makeNodeEditorVC() -> NodeEditorViewController { let c = NodeEditorViewController() c.dialogService = assembler.resolve(DialogService.self) - c.apiService = assembler.resolve(ApiService.self) + c.apiService = assembler.resolve(AdamantApiServiceProtocol.self) c.nodesStorage = assembler.resolve(NodesStorageProtocol.self) return c } diff --git a/Adamant/Modules/NodesEditor/NodesListViewController.swift b/Adamant/Modules/NodesEditor/NodesListViewController.swift index 166ddc274..b90cf05fe 100644 --- a/Adamant/Modules/NodesEditor/NodesListViewController.swift +++ b/Adamant/Modules/NodesEditor/NodesListViewController.swift @@ -86,7 +86,7 @@ final class NodesListViewController: FormViewController { private let screensFactory: ScreensFactory private let nodesStorage: NodesStorageProtocol private let nodesAdditionalParamsStorage: NodesAdditionalParamsStorageProtocol - private let apiService: ApiService + private let apiService: AdamantApiServiceProtocol private let socketService: SocketService // Properties @@ -107,7 +107,7 @@ final class NodesListViewController: FormViewController { screensFactory: ScreensFactory, nodesStorage: NodesStorageProtocol, nodesAdditionalParamsStorage: NodesAdditionalParamsStorageProtocol, - apiService: ApiService, + apiService: AdamantApiServiceProtocol, socketService: SocketService ) { self.dialogService = dialogService diff --git a/Adamant/Modules/Wallets/Adamant/AdmWalletFactory.swift b/Adamant/Modules/Wallets/Adamant/AdmWalletFactory.swift index 9c476925b..e65c4c951 100644 --- a/Adamant/Modules/Wallets/Adamant/AdmWalletFactory.swift +++ b/Adamant/Modules/Wallets/Adamant/AdmWalletFactory.swift @@ -54,7 +54,7 @@ struct AdmWalletFactory: WalletFactory { walletService: service, reachabilityMonitor: assembler.resolve(ReachabilityMonitor.self)!, nodesStorage: assembler.resolve(NodesStorageProtocol.self)!, - walletApiServiceCompose: assembler.resolve(WalletApiServiceComposeProtocol.self)! + apiServiceCompose: assembler.resolve(ApiServiceComposeProtocol.self)! ) } diff --git a/Adamant/Modules/Wallets/Adamant/AdmWalletService.swift b/Adamant/Modules/Wallets/Adamant/AdmWalletService.swift index 027e80795..e5d17ac7b 100644 --- a/Adamant/Modules/Wallets/Adamant/AdmWalletService.swift +++ b/Adamant/Modules/Wallets/Adamant/AdmWalletService.swift @@ -58,7 +58,7 @@ final class AdmWalletService: NSObject, WalletCoreProtocol { // MARK: - Dependencies weak var accountService: AccountService? - var apiService: ApiService! + var apiService: AdamantApiServiceProtocol! var transfersProvider: TransfersProvider! var coreDataStack: CoreDataStack! var vibroService: VibroService! @@ -234,7 +234,7 @@ extension AdmWalletService: SwinjectDependentService { @MainActor func injectDependencies(from container: Container) { accountService = container.resolve(AccountService.self) - apiService = container.resolve(ApiService.self) + apiService = container.resolve(AdamantApiServiceProtocol.self) transfersProvider = container.resolve(TransfersProvider.self) coreDataStack = container.resolve(CoreDataStack.self) vibroService = container.resolve(VibroService.self) diff --git a/Adamant/Modules/Wallets/Bitcoin/BtcApiService.swift b/Adamant/Modules/Wallets/Bitcoin/BtcApiService.swift index 4ad28bc2c..f0b2631ce 100644 --- a/Adamant/Modules/Wallets/Bitcoin/BtcApiService.swift +++ b/Adamant/Modules/Wallets/Bitcoin/BtcApiService.swift @@ -64,7 +64,7 @@ final class BtcApiCore: BlockchainHealthCheckableService { } } -final class BtcApiService: WalletApiService { +final class BtcApiService: ApiServiceProtocol { let api: BlockchainHealthCheckWrapper var chosenFastestNodeId: UUID? { diff --git a/Adamant/Modules/Wallets/Bitcoin/BtcWalletFactory.swift b/Adamant/Modules/Wallets/Bitcoin/BtcWalletFactory.swift index be67d9ea6..98dc91faa 100644 --- a/Adamant/Modules/Wallets/Bitcoin/BtcWalletFactory.swift +++ b/Adamant/Modules/Wallets/Bitcoin/BtcWalletFactory.swift @@ -50,7 +50,7 @@ struct BtcWalletFactory: WalletFactory { walletService: service, reachabilityMonitor: assembler.resolve(ReachabilityMonitor.self)!, nodesStorage: assembler.resolve(NodesStorageProtocol.self)!, - walletApiServiceCompose: assembler.resolve(WalletApiServiceComposeProtocol.self)! + apiServiceCompose: assembler.resolve(ApiServiceComposeProtocol.self)! ) } diff --git a/Adamant/Modules/Wallets/Bitcoin/BtcWalletService.swift b/Adamant/Modules/Wallets/Bitcoin/BtcWalletService.swift index 0e26b9c78..9757f28eb 100644 --- a/Adamant/Modules/Wallets/Bitcoin/BtcWalletService.swift +++ b/Adamant/Modules/Wallets/Bitcoin/BtcWalletService.swift @@ -118,7 +118,7 @@ final class BtcWalletService: WalletCoreProtocol { static let richMessageType = "btc_transaction" // MARK: - Dependencies - var apiService: ApiService! + var apiService: AdamantApiServiceProtocol! var btcApiService: BtcApiService! var accountService: AccountService! var dialogService: DialogService! @@ -497,7 +497,7 @@ extension BtcWalletService: SwinjectDependentService { @MainActor func injectDependencies(from container: Container) { accountService = container.resolve(AccountService.self) - apiService = container.resolve(ApiService.self) + apiService = container.resolve(AdamantApiServiceProtocol.self) dialogService = container.resolve(DialogService.self) increaseFeeService = container.resolve(IncreaseFeeService.self) addressConverter = container.resolve(AddressConverterFactory.self)?.make(network: network) diff --git a/Adamant/Modules/Wallets/Dash/DashApiService.swift b/Adamant/Modules/Wallets/Dash/DashApiService.swift index 2f1a4933a..0f084f491 100644 --- a/Adamant/Modules/Wallets/Dash/DashApiService.swift +++ b/Adamant/Modules/Wallets/Dash/DashApiService.swift @@ -64,7 +64,7 @@ final class DashApiCore: BlockchainHealthCheckableService { } } -final class DashApiService: WalletApiService { +final class DashApiService: ApiServiceProtocol { let api: BlockchainHealthCheckWrapper var chosenFastestNodeId: UUID? { diff --git a/Adamant/Modules/Wallets/Dash/DashWalletFactory.swift b/Adamant/Modules/Wallets/Dash/DashWalletFactory.swift index 30c54da80..9250d2d50 100644 --- a/Adamant/Modules/Wallets/Dash/DashWalletFactory.swift +++ b/Adamant/Modules/Wallets/Dash/DashWalletFactory.swift @@ -49,7 +49,7 @@ struct DashWalletFactory: WalletFactory { walletService: service, reachabilityMonitor: assembler.resolve(ReachabilityMonitor.self)!, nodesStorage: assembler.resolve(NodesStorageProtocol.self)!, - walletApiServiceCompose: assembler.resolve(WalletApiServiceComposeProtocol.self)! + apiServiceCompose: assembler.resolve(ApiServiceComposeProtocol.self)! ) } diff --git a/Adamant/Modules/Wallets/Dash/DashWalletService.swift b/Adamant/Modules/Wallets/Dash/DashWalletService.swift index 493b0828a..4d92d1b9a 100644 --- a/Adamant/Modules/Wallets/Dash/DashWalletService.swift +++ b/Adamant/Modules/Wallets/Dash/DashWalletService.swift @@ -59,7 +59,7 @@ final class DashWalletService: WalletCoreProtocol { static let richMessageType = "dash_transaction" // MARK: - Dependencies - var apiService: ApiService! + var apiService: AdamantApiServiceProtocol! var dashApiService: DashApiService! var accountService: AccountService! var securedStore: SecuredStore! @@ -376,7 +376,7 @@ extension DashWalletService: SwinjectDependentService { @MainActor func injectDependencies(from container: Container) { accountService = container.resolve(AccountService.self) - apiService = container.resolve(ApiService.self) + apiService = container.resolve(AdamantApiServiceProtocol.self) securedStore = container.resolve(SecuredStore.self) dialogService = container.resolve(DialogService.self) addressConverter = container.resolve(AddressConverterFactory.self)? diff --git a/Adamant/Modules/Wallets/Doge/DogeApiService.swift b/Adamant/Modules/Wallets/Doge/DogeApiService.swift index 52ea9878e..04a194da5 100644 --- a/Adamant/Modules/Wallets/Doge/DogeApiService.swift +++ b/Adamant/Modules/Wallets/Doge/DogeApiService.swift @@ -45,7 +45,7 @@ final class DogeApiCore: BlockchainHealthCheckableService { } } -final class DogeApiService: WalletApiService { +final class DogeApiService: ApiServiceProtocol { let api: BlockchainHealthCheckWrapper var chosenFastestNodeId: UUID? { diff --git a/Adamant/Modules/Wallets/Doge/DogeWalletFactory.swift b/Adamant/Modules/Wallets/Doge/DogeWalletFactory.swift index 1e3acfbab..2efea1681 100644 --- a/Adamant/Modules/Wallets/Doge/DogeWalletFactory.swift +++ b/Adamant/Modules/Wallets/Doge/DogeWalletFactory.swift @@ -49,7 +49,7 @@ struct DogeWalletFactory: WalletFactory { walletService: service, reachabilityMonitor: assembler.resolve(ReachabilityMonitor.self)!, nodesStorage: assembler.resolve(NodesStorageProtocol.self)!, - walletApiServiceCompose: assembler.resolve(WalletApiServiceComposeProtocol.self)! + apiServiceCompose: assembler.resolve(ApiServiceComposeProtocol.self)! ) } diff --git a/Adamant/Modules/Wallets/Doge/DogeWalletService.swift b/Adamant/Modules/Wallets/Doge/DogeWalletService.swift index ebcae6a9a..36e9f34fc 100644 --- a/Adamant/Modules/Wallets/Doge/DogeWalletService.swift +++ b/Adamant/Modules/Wallets/Doge/DogeWalletService.swift @@ -54,7 +54,7 @@ final class DogeWalletService: WalletCoreProtocol { static let richMessageType = "doge_transaction" // MARK: - Dependencies - var apiService: ApiService! + var apiService: AdamantApiServiceProtocol! var dogeApiService: DogeApiService! var accountService: AccountService! var dialogService: DialogService! @@ -363,7 +363,7 @@ extension DogeWalletService { extension DogeWalletService: SwinjectDependentService { func injectDependencies(from container: Container) { accountService = container.resolve(AccountService.self) - apiService = container.resolve(ApiService.self) + apiService = container.resolve(AdamantApiServiceProtocol.self) dialogService = container.resolve(DialogService.self) addressConverter = container.resolve(AddressConverterFactory.self)? .make(network: network) diff --git a/Adamant/Modules/Wallets/ERC20/ERC20WalletFactory.swift b/Adamant/Modules/Wallets/ERC20/ERC20WalletFactory.swift index be0fc977b..d085df46d 100644 --- a/Adamant/Modules/Wallets/ERC20/ERC20WalletFactory.swift +++ b/Adamant/Modules/Wallets/ERC20/ERC20WalletFactory.swift @@ -49,7 +49,7 @@ struct ERC20WalletFactory: WalletFactory { walletService: service, reachabilityMonitor: assembler.resolve(ReachabilityMonitor.self)!, nodesStorage: assembler.resolve(NodesStorageProtocol.self)!, - walletApiServiceCompose: assembler.resolve(WalletApiServiceComposeProtocol.self)! + apiServiceCompose: assembler.resolve(ApiServiceComposeProtocol.self)! ) } diff --git a/Adamant/Modules/Wallets/ERC20/ERC20WalletService.swift b/Adamant/Modules/Wallets/ERC20/ERC20WalletService.swift index dbb72c433..ae6f150f3 100644 --- a/Adamant/Modules/Wallets/ERC20/ERC20WalletService.swift +++ b/Adamant/Modules/Wallets/ERC20/ERC20WalletService.swift @@ -106,7 +106,7 @@ final class ERC20WalletService: WalletCoreProtocol { // MARK: - Dependencies weak var accountService: AccountService? - var apiService: ApiService! + var apiService: AdamantApiServiceProtocol! var erc20ApiService: ERC20ApiService! var dialogService: DialogService! var increaseFeeService: IncreaseFeeService! @@ -411,7 +411,7 @@ extension ERC20WalletService: SwinjectDependentService { @MainActor func injectDependencies(from container: Container) { accountService = container.resolve(AccountService.self) - apiService = container.resolve(ApiService.self) + apiService = container.resolve(AdamantApiServiceProtocol.self) dialogService = container.resolve(DialogService.self) increaseFeeService = container.resolve(IncreaseFeeService.self) erc20ApiService = container.resolve(ERC20ApiService.self) diff --git a/Adamant/Modules/Wallets/Ethereum/EthApiService.swift b/Adamant/Modules/Wallets/Ethereum/EthApiService.swift index bd4e490ba..e30e81b8a 100644 --- a/Adamant/Modules/Wallets/Ethereum/EthApiService.swift +++ b/Adamant/Modules/Wallets/Ethereum/EthApiService.swift @@ -11,7 +11,7 @@ import Foundation import web3swift import Web3Core -class EthApiService: WalletApiService { +class EthApiService: ApiServiceProtocol { let api: BlockchainHealthCheckWrapper var keystoreManager: KeystoreManager? { diff --git a/Adamant/Modules/Wallets/Ethereum/EthWalletFactory.swift b/Adamant/Modules/Wallets/Ethereum/EthWalletFactory.swift index 2400b6b31..38bef2edb 100644 --- a/Adamant/Modules/Wallets/Ethereum/EthWalletFactory.swift +++ b/Adamant/Modules/Wallets/Ethereum/EthWalletFactory.swift @@ -49,7 +49,7 @@ struct EthWalletFactory: WalletFactory { walletService: service, reachabilityMonitor: assembler.resolve(ReachabilityMonitor.self)!, nodesStorage: assembler.resolve(NodesStorageProtocol.self)!, - walletApiServiceCompose: assembler.resolve(WalletApiServiceComposeProtocol.self)! + apiServiceCompose: assembler.resolve(ApiServiceComposeProtocol.self)! ) } diff --git a/Adamant/Modules/Wallets/Ethereum/EthWalletService.swift b/Adamant/Modules/Wallets/Ethereum/EthWalletService.swift index 4fa134dcf..687e28c91 100644 --- a/Adamant/Modules/Wallets/Ethereum/EthWalletService.swift +++ b/Adamant/Modules/Wallets/Ethereum/EthWalletService.swift @@ -123,7 +123,7 @@ final class EthWalletService: WalletCoreProtocol { // MARK: - Dependencies weak var accountService: AccountService? - var apiService: ApiService! + var apiService: AdamantApiServiceProtocol! var ethApiService: EthApiService! var dialogService: DialogService! var increaseFeeService: IncreaseFeeService! @@ -503,7 +503,7 @@ extension EthWalletService: SwinjectDependentService { @MainActor func injectDependencies(from container: Container) { accountService = container.resolve(AccountService.self) - apiService = container.resolve(ApiService.self) + apiService = container.resolve(AdamantApiServiceProtocol.self) dialogService = container.resolve(DialogService.self) increaseFeeService = container.resolve(IncreaseFeeService.self) ethApiService = container.resolve(EthApiService.self) diff --git a/Adamant/Modules/Wallets/Klayr/KlyNodeApiService.swift b/Adamant/Modules/Wallets/Klayr/KlyNodeApiService.swift index 2eb046d34..f8263c3b0 100644 --- a/Adamant/Modules/Wallets/Klayr/KlyNodeApiService.swift +++ b/Adamant/Modules/Wallets/Klayr/KlyNodeApiService.swift @@ -10,7 +10,7 @@ import LiskKit import Foundation import CommonKit -final class KlyNodeApiService: WalletApiService { +final class KlyNodeApiService: ApiServiceProtocol { let api: BlockchainHealthCheckWrapper var chosenFastestNodeId: UUID? { diff --git a/Adamant/Modules/Wallets/Klayr/KlyServiceApiService.swift b/Adamant/Modules/Wallets/Klayr/KlyServiceApiService.swift index 6300884a8..b0495eb4e 100644 --- a/Adamant/Modules/Wallets/Klayr/KlyServiceApiService.swift +++ b/Adamant/Modules/Wallets/Klayr/KlyServiceApiService.swift @@ -31,7 +31,7 @@ final class KlyServiceApiCore: KlyApiCore { } } -final class KlyServiceApiService: WalletApiService { +final class KlyServiceApiService: ApiServiceProtocol { let api: BlockchainHealthCheckWrapper var chosenFastestNodeId: UUID? { diff --git a/Adamant/Modules/Wallets/Klayr/KlyWalletFactory.swift b/Adamant/Modules/Wallets/Klayr/KlyWalletFactory.swift index f17a228f9..c1f091c07 100644 --- a/Adamant/Modules/Wallets/Klayr/KlyWalletFactory.swift +++ b/Adamant/Modules/Wallets/Klayr/KlyWalletFactory.swift @@ -50,7 +50,7 @@ struct KlyWalletFactory: WalletFactory { walletService: service, reachabilityMonitor: assembler.resolve(ReachabilityMonitor.self)!, nodesStorage: assembler.resolve(NodesStorageProtocol.self)!, - walletApiServiceCompose: assembler.resolve(WalletApiServiceComposeProtocol.self)! + apiServiceCompose: assembler.resolve(ApiServiceComposeProtocol.self)! ) } diff --git a/Adamant/Modules/Wallets/Klayr/WalletService/KlyWalletService.swift b/Adamant/Modules/Wallets/Klayr/WalletService/KlyWalletService.swift index 8075ffe95..2365c7227 100644 --- a/Adamant/Modules/Wallets/Klayr/WalletService/KlyWalletService.swift +++ b/Adamant/Modules/Wallets/Klayr/WalletService/KlyWalletService.swift @@ -18,7 +18,7 @@ final class KlyWalletService: WalletCoreProtocol { // MARK: Dependencies - var apiService: ApiService! + var apiService: AdamantApiServiceProtocol! var klyNodeApiService: KlyNodeApiService! var klyServiceApiService: KlyServiceApiService! var accountService: AccountService! @@ -157,7 +157,7 @@ extension KlyWalletService: SwinjectDependentService { @MainActor func injectDependencies(from container: Container) { accountService = container.resolve(AccountService.self) - apiService = container.resolve(ApiService.self) + apiService = container.resolve(AdamantApiServiceProtocol.self) dialogService = container.resolve(DialogService.self) klyServiceApiService = container.resolve(KlyServiceApiService.self) klyNodeApiService = container.resolve(KlyNodeApiService.self) diff --git a/Adamant/Modules/Wallets/TransferViewControllerBase.swift b/Adamant/Modules/Wallets/TransferViewControllerBase.swift index e4632490d..8360991e4 100644 --- a/Adamant/Modules/Wallets/TransferViewControllerBase.swift +++ b/Adamant/Modules/Wallets/TransferViewControllerBase.swift @@ -188,7 +188,7 @@ class TransferViewControllerBase: FormViewController { let walletCore: WalletCoreProtocol let reachabilityMonitor: ReachabilityMonitor let nodesStorage: NodesStorageProtocol - let walletApiServiceCompose: WalletApiServiceComposeProtocol + let apiServiceCompose: ApiServiceComposeProtocol // MARK: - Properties @@ -321,7 +321,7 @@ class TransferViewControllerBase: FormViewController { walletService: WalletService, reachabilityMonitor: ReachabilityMonitor, nodesStorage: NodesStorageProtocol, - walletApiServiceCompose: WalletApiServiceComposeProtocol + apiServiceCompose: ApiServiceComposeProtocol ) { self.accountService = accountService self.accountsProvider = accountsProvider @@ -335,7 +335,7 @@ class TransferViewControllerBase: FormViewController { self.walletCore = walletService.core self.reachabilityMonitor = reachabilityMonitor self.nodesStorage = nodesStorage - self.walletApiServiceCompose = walletApiServiceCompose + self.apiServiceCompose = apiServiceCompose super.init(nibName: nil, bundle: nil) } @@ -803,7 +803,7 @@ class TransferViewControllerBase: FormViewController { } guard - walletApiServiceCompose.hasActiveNode(group: .adm) || admReportRecipient == nil + apiServiceCompose.hasActiveNode(group: .adm) || admReportRecipient == nil else { dialogService.showWarning( withMessage: ApiServiceError.noEndpointsAvailable( diff --git a/Adamant/ServiceProtocols/WalletApiServiceComposeProtocol.swift b/Adamant/ServiceProtocols/ApiServiceComposeProtocol.swift similarity index 77% rename from Adamant/ServiceProtocols/WalletApiServiceComposeProtocol.swift rename to Adamant/ServiceProtocols/ApiServiceComposeProtocol.swift index b223c09b8..16e0a5b17 100644 --- a/Adamant/ServiceProtocols/WalletApiServiceComposeProtocol.swift +++ b/Adamant/ServiceProtocols/ApiServiceComposeProtocol.swift @@ -1,5 +1,5 @@ // -// WalletApiServiceComposeProtocol.swift +// ApiServiceComposeProtocol.swift // Adamant // // Created by Andrew G on 21.08.2024. @@ -9,7 +9,7 @@ import Foundation import CommonKit -protocol WalletApiServiceComposeProtocol { +protocol ApiServiceComposeProtocol { func chosenFastestNodeId(group: NodeGroup) -> UUID? func hasActiveNode(group: NodeGroup) -> Bool func healthCheck(group: NodeGroup) diff --git a/Adamant/ServiceProtocols/FileApiServiceProtocol.swift b/Adamant/ServiceProtocols/FileApiServiceProtocol.swift index 688c36918..08ef445d2 100644 --- a/Adamant/ServiceProtocols/FileApiServiceProtocol.swift +++ b/Adamant/ServiceProtocols/FileApiServiceProtocol.swift @@ -9,7 +9,7 @@ import Foundation import CommonKit -protocol FileApiServiceProtocol: WalletApiService { +protocol FileApiServiceProtocol: ApiServiceProtocol { func uploadFile( data: Data, uploadProgress: @escaping ((Progress) -> Void) diff --git a/Adamant/Services/AdamantAccountService.swift b/Adamant/Services/AdamantAccountService.swift index 94e427c25..7b9da2533 100644 --- a/Adamant/Services/AdamantAccountService.swift +++ b/Adamant/Services/AdamantAccountService.swift @@ -15,7 +15,7 @@ final class AdamantAccountService: AccountService { // MARK: Dependencies - private let apiService: ApiService + private let apiService: AdamantApiServiceProtocol private let adamantCore: AdamantCore private let dialogService: DialogService private let securedStore: SecuredStore @@ -38,7 +38,7 @@ final class AdamantAccountService: AccountService { @Atomic private var subscriptions = Set() init( - apiService: ApiService, + apiService: AdamantApiServiceProtocol, adamantCore: AdamantCore, dialogService: DialogService, securedStore: SecuredStore, diff --git a/Adamant/Services/AdamantAddressBookService.swift b/Adamant/Services/AdamantAddressBookService.swift index 8d6993bbc..37bd1dbe8 100644 --- a/Adamant/Services/AdamantAddressBookService.swift +++ b/Adamant/Services/AdamantAddressBookService.swift @@ -18,7 +18,7 @@ final class AdamantAddressBookService: AddressBookService { // MARK: - Dependencies - private let apiService: ApiService + private let apiService: AdamantApiServiceProtocol private let adamantCore: AdamantCore private let accountService: AccountService private let dialogService: DialogService @@ -38,7 +38,7 @@ final class AdamantAddressBookService: AddressBookService { // MARK: - Lifecycle nonisolated init( - apiService: ApiService, + apiService: AdamantApiServiceProtocol, adamantCore: AdamantCore, accountService: AccountService, dialogService: DialogService diff --git a/Adamant/Services/AdamantPushNotificationsTokenService.swift b/Adamant/Services/AdamantPushNotificationsTokenService.swift index 00131f3aa..241da8baa 100644 --- a/Adamant/Services/AdamantPushNotificationsTokenService.swift +++ b/Adamant/Services/AdamantPushNotificationsTokenService.swift @@ -11,7 +11,7 @@ import CommonKit final class AdamantPushNotificationsTokenService: PushNotificationsTokenService { private let securedStore: SecuredStore - private let apiService: ApiService + private let apiService: AdamantApiServiceProtocol private let adamantCore: AdamantCore private let accountService: AccountService @@ -21,7 +21,7 @@ final class AdamantPushNotificationsTokenService: PushNotificationsTokenService init( securedStore: SecuredStore, - apiService: ApiService, + apiService: AdamantApiServiceProtocol, adamantCore: AdamantCore, accountService: AccountService ) { diff --git a/Adamant/Services/WalletApiServiceCompose.swift b/Adamant/Services/ApiServiceCompose.swift similarity index 66% rename from Adamant/Services/WalletApiServiceCompose.swift rename to Adamant/Services/ApiServiceCompose.swift index bd020dff0..0ca3ea3ab 100644 --- a/Adamant/Services/WalletApiServiceCompose.swift +++ b/Adamant/Services/ApiServiceCompose.swift @@ -1,5 +1,5 @@ // -// WalletApiServiceCompose.swift +// ApiServiceCompose.swift // Adamant // // Created by Andrew G on 21.08.2024. @@ -9,15 +9,15 @@ import Foundation import CommonKit -struct WalletApiServiceCompose: WalletApiServiceComposeProtocol { - let btc: WalletApiService - let eth: WalletApiService - let klyNode: WalletApiService - let klyService: WalletApiService - let doge: WalletApiService - let dash: WalletApiService - let adm: WalletApiService - let ipfs: WalletApiService +struct ApiServiceCompose: ApiServiceComposeProtocol { + let btc: ApiServiceProtocol + let eth: ApiServiceProtocol + let klyNode: ApiServiceProtocol + let klyService: ApiServiceProtocol + let doge: ApiServiceProtocol + let dash: ApiServiceProtocol + let adm: ApiServiceProtocol + let ipfs: ApiServiceProtocol func chosenFastestNodeId(group: NodeGroup) -> UUID? { getApiService(group: group).chosenFastestNodeId @@ -32,8 +32,8 @@ struct WalletApiServiceCompose: WalletApiServiceComposeProtocol { } } -private extension WalletApiServiceCompose { - func getApiService(group: NodeGroup) -> WalletApiService { +private extension ApiServiceCompose { + func getApiService(group: NodeGroup) -> ApiServiceProtocol { switch group { case .btc: return btc diff --git a/Adamant/Services/DataProviders/AdamantAccountsProvider.swift b/Adamant/Services/DataProviders/AdamantAccountsProvider.swift index 04babdba3..61c5a7a31 100644 --- a/Adamant/Services/DataProviders/AdamantAccountsProvider.swift +++ b/Adamant/Services/DataProviders/AdamantAccountsProvider.swift @@ -42,7 +42,7 @@ final class AdamantAccountsProvider: AccountsProvider { // MARK: Dependencies @MainActor private let stack: CoreDataStack - private let apiService: ApiService + private let apiService: AdamantApiServiceProtocol private let addressBookService: AddressBookService // MARK: Properties @@ -52,7 +52,7 @@ final class AdamantAccountsProvider: AccountsProvider { // MARK: Lifecycle nonisolated init( stack: CoreDataStack, - apiService: ApiService, + apiService: AdamantApiServiceProtocol, addressBookService: AddressBookService ) { self.stack = stack diff --git a/Adamant/Services/DataProviders/AdamantChatsProvider.swift b/Adamant/Services/DataProviders/AdamantChatsProvider.swift index be62fd24f..77f3c1044 100644 --- a/Adamant/Services/DataProviders/AdamantChatsProvider.swift +++ b/Adamant/Services/DataProviders/AdamantChatsProvider.swift @@ -24,7 +24,7 @@ actor AdamantChatsProvider: ChatsProvider { let accountService: AccountService let accountsProvider: AccountsProvider let securedStore: SecuredStore - let apiService: ApiService + let apiService: AdamantApiServiceProtocol let stack: CoreDataStack // MARK: Properties @@ -73,7 +73,7 @@ actor AdamantChatsProvider: ChatsProvider { // MARK: Lifecycle init( accountService: AccountService, - apiService: ApiService, + apiService: AdamantApiServiceProtocol, socketService: SocketService, stack: CoreDataStack, adamantCore: AdamantCore, diff --git a/Adamant/Services/DataProviders/AdamantTransfersProvider.swift b/Adamant/Services/DataProviders/AdamantTransfersProvider.swift index d2a923b68..7763b73a5 100644 --- a/Adamant/Services/DataProviders/AdamantTransfersProvider.swift +++ b/Adamant/Services/DataProviders/AdamantTransfersProvider.swift @@ -16,7 +16,7 @@ actor AdamantTransfersProvider: TransfersProvider { static let transferFee: Decimal = Decimal(sign: .plus, exponent: -1, significand: 5) // MARK: Dependencies - let apiService: ApiService + let apiService: AdamantApiServiceProtocol private let stack: CoreDataStack private let adamantCore: AdamantCore private let accountService: AccountService @@ -61,7 +61,7 @@ actor AdamantTransfersProvider: TransfersProvider { // MARK: Lifecycle init( - apiService: ApiService, + apiService: AdamantApiServiceProtocol, stack: CoreDataStack, adamantCore: AdamantCore, accountService: AccountService, diff --git a/Adamant/Services/RichTransactionReactService/AdamantRichTransactionReactService.swift b/Adamant/Services/RichTransactionReactService/AdamantRichTransactionReactService.swift index a26d0ee53..fcbaf64e8 100644 --- a/Adamant/Services/RichTransactionReactService/AdamantRichTransactionReactService.swift +++ b/Adamant/Services/RichTransactionReactService/AdamantRichTransactionReactService.swift @@ -12,7 +12,7 @@ import CommonKit actor AdamantRichTransactionReactService: NSObject, RichTransactionReactService { private let coreDataStack: CoreDataStack - private let apiService: ApiService + private let apiService: AdamantApiServiceProtocol private let adamantCore: AdamantCore private let accountService: AccountService @@ -25,7 +25,7 @@ actor AdamantRichTransactionReactService: NSObject, RichTransactionReactService init( coreDataStack: CoreDataStack, - apiService: ApiService, + apiService: AdamantApiServiceProtocol, adamantCore: AdamantCore, accountService: AccountService ) { diff --git a/Adamant/Services/RichTransactionReplyService/AdamantRichTransactionReplyService.swift b/Adamant/Services/RichTransactionReplyService/AdamantRichTransactionReplyService.swift index 6e8ce8798..0769b02be 100644 --- a/Adamant/Services/RichTransactionReplyService/AdamantRichTransactionReplyService.swift +++ b/Adamant/Services/RichTransactionReplyService/AdamantRichTransactionReplyService.swift @@ -12,7 +12,7 @@ import CommonKit actor AdamantRichTransactionReplyService: NSObject, RichTransactionReplyService { private let coreDataStack: CoreDataStack - private let apiService: ApiService + private let apiService: AdamantApiServiceProtocol private let adamantCore: AdamantCore private let accountService: AccountService private let walletServiceCompose: WalletServiceCompose @@ -23,7 +23,7 @@ actor AdamantRichTransactionReplyService: NSObject, RichTransactionReplyService init( coreDataStack: CoreDataStack, - apiService: ApiService, + apiService: AdamantApiServiceProtocol, adamantCore: AdamantCore, accountService: AccountService, walletServiceCompose: WalletServiceCompose diff --git a/CommonKit/Sources/CommonKit/ExtensionsTools/ExtensionsApi.swift b/CommonKit/Sources/CommonKit/ExtensionsTools/ExtensionsApi.swift index 547134d54..9ffc97860 100644 --- a/CommonKit/Sources/CommonKit/ExtensionsTools/ExtensionsApi.swift +++ b/CommonKit/Sources/CommonKit/ExtensionsTools/ExtensionsApi.swift @@ -11,10 +11,10 @@ import Foundation public final class ExtensionsApi { // MARK: Properties private let addressBookKey = "contact_list" - private let apiService: ApiService + private let apiService: AdamantApiServiceProtocol // MARK: Cotr - public init(apiService: ApiService) { + public init(apiService: AdamantApiServiceProtocol) { self.apiService = apiService } diff --git a/CommonKit/Sources/CommonKit/ExtensionsTools/ExtensionsApiFactory.swift b/CommonKit/Sources/CommonKit/ExtensionsTools/ExtensionsApiFactory.swift index 8ea9a6c5f..3e47eb767 100644 --- a/CommonKit/Sources/CommonKit/ExtensionsTools/ExtensionsApiFactory.swift +++ b/CommonKit/Sources/CommonKit/ExtensionsTools/ExtensionsApiFactory.swift @@ -20,7 +20,7 @@ public struct ExtensionsApiFactory { service: AdamantApiCore(apiCore: APICore()), nodesStorage: NodesStorage( securedStore: securedStore, - nodesMergingService: AdamantNodesMergingService(), + nodesMergingService: NodesMergingService(), defaultNodes: .init() ), nodesAdditionalParamsStorage: NodesAdditionalParamsStorage( diff --git a/CommonKit/Sources/CommonKit/Protocols/ApiService.swift b/CommonKit/Sources/CommonKit/Protocols/AdamantApiServiceProtocol.swift similarity index 98% rename from CommonKit/Sources/CommonKit/Protocols/ApiService.swift rename to CommonKit/Sources/CommonKit/Protocols/AdamantApiServiceProtocol.swift index 5976e4c6d..93a44aa1d 100644 --- a/CommonKit/Sources/CommonKit/Protocols/ApiService.swift +++ b/CommonKit/Sources/CommonKit/Protocols/AdamantApiServiceProtocol.swift @@ -9,7 +9,7 @@ import Foundation import Alamofire -public protocol ApiService: WalletApiService { +public protocol AdamantApiServiceProtocol: ApiServiceProtocol { // MARK: - Accounts func getAccount(byPassphrase passphrase: String) async -> ApiServiceResult func getAccount(byPublicKey publicKey: String) async -> ApiServiceResult diff --git a/CommonKit/Sources/CommonKit/Protocols/WalletApiService.swift b/CommonKit/Sources/CommonKit/Protocols/ApiServiceProtocol.swift similarity index 78% rename from CommonKit/Sources/CommonKit/Protocols/WalletApiService.swift rename to CommonKit/Sources/CommonKit/Protocols/ApiServiceProtocol.swift index 21afb4d25..3ed1fb9e4 100644 --- a/CommonKit/Sources/CommonKit/Protocols/WalletApiService.swift +++ b/CommonKit/Sources/CommonKit/Protocols/ApiServiceProtocol.swift @@ -1,5 +1,5 @@ // -// WalletApiService.swift +// ApiServiceProtocol.swift // Adamant // // Created by Andrew G on 20.11.2023. @@ -8,7 +8,7 @@ import Foundation -public protocol WalletApiService { +public protocol ApiServiceProtocol { var chosenFastestNodeId: UUID? { get } var hasActiveNode: Bool { get } diff --git a/CommonKit/Sources/CommonKit/Protocols/NodesMergingService.swift b/CommonKit/Sources/CommonKit/Protocols/NodesMergingServiceProtocol.swift similarity index 74% rename from CommonKit/Sources/CommonKit/Protocols/NodesMergingService.swift rename to CommonKit/Sources/CommonKit/Protocols/NodesMergingServiceProtocol.swift index 63bd6ca22..67b37f70a 100644 --- a/CommonKit/Sources/CommonKit/Protocols/NodesMergingService.swift +++ b/CommonKit/Sources/CommonKit/Protocols/NodesMergingServiceProtocol.swift @@ -1,12 +1,12 @@ // -// NodesMergingService.swift +// NodesMergingServiceProtocol.swift // Adamant // // Created by Andrew G on 02.08.2024. // Copyright © 2024 Adamant. All rights reserved. // -public protocol NodesMergingService { +public protocol NodesMergingServiceProtocol { func merge( savedNodes: [NodeGroup: [Node]], defaultNodes: [NodeGroup: [Node]] diff --git a/CommonKit/Sources/CommonKit/Services/ApiService/AdamantApiService.swift b/CommonKit/Sources/CommonKit/Services/ApiService/AdamantApiService.swift index fd7574f53..624b5a48b 100644 --- a/CommonKit/Sources/CommonKit/Services/ApiService/AdamantApiService.swift +++ b/CommonKit/Sources/CommonKit/Services/ApiService/AdamantApiService.swift @@ -29,7 +29,7 @@ public final class AdamantApiService { } } -extension AdamantApiService: ApiService { +extension AdamantApiService: AdamantApiServiceProtocol { public var chosenFastestNodeId: UUID? { service.chosenFastestNodeId } diff --git a/CommonKit/Sources/CommonKit/Services/AdamantNodesMergingService.swift b/CommonKit/Sources/CommonKit/Services/NodesMergingService.swift similarity index 95% rename from CommonKit/Sources/CommonKit/Services/AdamantNodesMergingService.swift rename to CommonKit/Sources/CommonKit/Services/NodesMergingService.swift index 7edf223ba..7d28d58e6 100644 --- a/CommonKit/Sources/CommonKit/Services/AdamantNodesMergingService.swift +++ b/CommonKit/Sources/CommonKit/Services/NodesMergingService.swift @@ -6,7 +6,7 @@ // Copyright © 2024 Adamant. All rights reserved. // -public struct AdamantNodesMergingService: NodesMergingService { +public struct NodesMergingService: NodesMergingServiceProtocol { public func merge( savedNodes: [NodeGroup: [Node]], defaultNodes: [NodeGroup: [Node]] @@ -29,7 +29,7 @@ public struct AdamantNodesMergingService: NodesMergingService { public init() {} } -private extension AdamantNodesMergingService { +private extension NodesMergingService { func merge(savedNodes: [Node], defaultNodes: [Node]) -> [Node] { var resultNodes = savedNodes var defaultNodes = defaultNodes diff --git a/CommonKit/Sources/CommonKit/Services/NodesStorage.swift b/CommonKit/Sources/CommonKit/Services/NodesStorage.swift index 4ff8a4198..7ef9a3e48 100644 --- a/CommonKit/Sources/CommonKit/Services/NodesStorage.swift +++ b/CommonKit/Sources/CommonKit/Services/NodesStorage.swift @@ -21,7 +21,7 @@ public final class NodesStorage: NodesStorageProtocol { private var subscription: AnyCancellable? private let securedStore: SecuredStore - private let nodesMergingService: NodesMergingService + private let nodesMergingService: NodesMergingServiceProtocol private let defaultNodes: [NodeGroup: [Node]] public func getNodesPublisher(group: NodeGroup) -> AnyObservable<[Node]> { @@ -91,7 +91,7 @@ public final class NodesStorage: NodesStorageProtocol { public init( securedStore: SecuredStore, - nodesMergingService: NodesMergingService, + nodesMergingService: NodesMergingServiceProtocol, defaultNodes: [NodeGroup: [Node]] ) { self.securedStore = securedStore From 9255ca1170db861d671f7e0b87337352727cbf3d Mon Sep 17 00:00:00 2001 From: just-software-dev Date: Fri, 23 Aug 2024 00:29:51 -0300 Subject: [PATCH 039/106] [trello.com/c/ySzOI0KG] Version comparasion fix --- Adamant/Helpers/NodeGroup+Constants.swift | 9 ++-- .../ExtensionsApiFactory.swift | 2 +- .../Sources/CommonKit/Helpers/Version.swift | 54 +++++++++++++++++++ .../Models/BlockchainHealthCheckParams.swift | 4 +- .../Sources/CommonKit/Models/Node/Node.swift | 8 --- .../BlockchainHealthCheckWrapper.swift | 15 ++++-- 6 files changed, 72 insertions(+), 20 deletions(-) create mode 100644 CommonKit/Sources/CommonKit/Helpers/Version.swift diff --git a/Adamant/Helpers/NodeGroup+Constants.swift b/Adamant/Helpers/NodeGroup+Constants.swift index 26f25b7bd..a0e02e6fa 100644 --- a/Adamant/Helpers/NodeGroup+Constants.swift +++ b/Adamant/Helpers/NodeGroup+Constants.swift @@ -93,8 +93,9 @@ extension NodeGroup { } } - var minNodeVersion: Double { - let minNodeVersion: String? = switch self { + // swiftlint:disable switch_case_alignment + var minNodeVersion: Version? { + guard let version = switch self { case .adm: AdmWalletService.minNodeVersion case .btc: @@ -111,9 +112,9 @@ extension NodeGroup { DashWalletService.minNodeVersion case .ipfs: nil - } + } else { return nil } - return Node.versionToDouble(minNodeVersion) ?? .zero + return .init(version) } var name: String { diff --git a/CommonKit/Sources/CommonKit/ExtensionsTools/ExtensionsApiFactory.swift b/CommonKit/Sources/CommonKit/ExtensionsTools/ExtensionsApiFactory.swift index 3e47eb767..d29083070 100644 --- a/CommonKit/Sources/CommonKit/ExtensionsTools/ExtensionsApiFactory.swift +++ b/CommonKit/Sources/CommonKit/ExtensionsTools/ExtensionsApiFactory.swift @@ -32,7 +32,7 @@ public struct ExtensionsApiFactory { name: "ADM", normalUpdateInterval: .infinity, crucialUpdateInterval: .infinity, - minNodeVersion: .zero, + minNodeVersion: nil, nodeHeightEpsilon: .zero ), connection: nil diff --git a/CommonKit/Sources/CommonKit/Helpers/Version.swift b/CommonKit/Sources/CommonKit/Helpers/Version.swift new file mode 100644 index 000000000..bc8cef5c7 --- /dev/null +++ b/CommonKit/Sources/CommonKit/Helpers/Version.swift @@ -0,0 +1,54 @@ +// +// Version.swift +// +// +// Created by Andrew G on 22.08.2024. +// + +public struct Version { + public let versions: [Int] + + public init(_ versions: [Int]) { + self.versions = versions + } +} + +public extension Version { + var string: String { + versions.map { String($0) }.joined(separator: ".") + } + + init?(_ string: String) { + let versions = string.split(separator: ".").compactMap { Int($0) } + guard !versions.isEmpty else { return nil } + self.versions = versions + } +} + +extension Version: Comparable { + public static func < (lhs: Self, rhs: Self) -> Bool { + for i in .zero ..< max(lhs.versions.endIndex, rhs.versions.endIndex) { + let left = lhs.versions[safe: i] ?? .zero + let right = rhs.versions[safe: i] ?? .zero + + if left < right { + return true + } else if left > right { + return false + } + } + + return false + } +} + +extension Version: Equatable { + public static func == (lhs: Self, rhs: Self) -> Bool { + for i in .zero ..< max(lhs.versions.endIndex, rhs.versions.endIndex) { + guard lhs.versions[safe: i] ?? .zero == rhs.versions[safe: i] ?? .zero + else { return false } + } + + return true + } +} diff --git a/CommonKit/Sources/CommonKit/Models/BlockchainHealthCheckParams.swift b/CommonKit/Sources/CommonKit/Models/BlockchainHealthCheckParams.swift index 9f5c74438..4ddb9d7d0 100644 --- a/CommonKit/Sources/CommonKit/Models/BlockchainHealthCheckParams.swift +++ b/CommonKit/Sources/CommonKit/Models/BlockchainHealthCheckParams.swift @@ -12,7 +12,7 @@ public struct BlockchainHealthCheckParams { public let name: String public let normalUpdateInterval: TimeInterval public let crucialUpdateInterval: TimeInterval - public let minNodeVersion: Double + public let minNodeVersion: Version? public let nodeHeightEpsilon: Int public init( @@ -20,7 +20,7 @@ public struct BlockchainHealthCheckParams { name: String, normalUpdateInterval: TimeInterval, crucialUpdateInterval: TimeInterval, - minNodeVersion: Double, + minNodeVersion: Version?, nodeHeightEpsilon: Int ) { self.group = group diff --git a/CommonKit/Sources/CommonKit/Models/Node/Node.swift b/CommonKit/Sources/CommonKit/Models/Node/Node.swift index 71f96786f..a2b6398a8 100644 --- a/CommonKit/Sources/CommonKit/Models/Node/Node.swift +++ b/CommonKit/Sources/CommonKit/Models/Node/Node.swift @@ -93,12 +93,4 @@ public extension Node { mainOrigin.wsPort = wsPort altOrigin?.wsPort = wsPort } - - static func versionToDouble(_ value: String?) -> Double? { - guard let minNodeVersion = value?.replacingOccurrences(of: ".", with: ""), - let versionNumber = Double(minNodeVersion) - else { return nil } - - return versionNumber - } } diff --git a/CommonKit/Sources/CommonKit/Services/HealthCheck/BlockchainHealthCheckWrapper.swift b/CommonKit/Sources/CommonKit/Services/HealthCheck/BlockchainHealthCheckWrapper.swift index 6059acb78..7934415cc 100644 --- a/CommonKit/Sources/CommonKit/Services/HealthCheck/BlockchainHealthCheckWrapper.swift +++ b/CommonKit/Sources/CommonKit/Services/HealthCheck/BlockchainHealthCheckWrapper.swift @@ -151,8 +151,10 @@ private extension BlockchainHealthCheckWrapper { node.ping = info.ping guard - let versionNumber = Node.versionToDouble(info.version), - versionNumber < params.minNodeVersion + let versionString = info.version, + let version = Version(versionString), + let minNodeVersion = params.minNodeVersion, + version < minNodeVersion else { return } node.connectionStatus = .notAllowed(.outdatedApiVersion) @@ -175,10 +177,13 @@ private extension BlockchainHealthCheckWrapper { workingNodes.forEach { node in var status: NodeConnectionStatus? - let actualNodeVersion = Node.versionToDouble(node.version) - if let actualNodeVersion = actualNodeVersion, - actualNodeVersion < params.minNodeVersion { + if + let versionString = node.version, + let version = Version(versionString), + let minNodeVersion = params.minNodeVersion, + version < minNodeVersion + { status = .notAllowed(.outdatedApiVersion) } else { status = node.height.map { height in From ab21870993651486ab18f32127bf771a26193f78 Mon Sep 17 00:00:00 2001 From: just-software-dev Date: Fri, 23 Aug 2024 02:50:12 -0300 Subject: [PATCH 040/106] [trello.com/c/sf4UCx9f] Info service architecture --- Adamant.xcodeproj/project.pbxproj | 94 ++++++++++++++++++- Adamant/App/AppDelegate.swift | 4 +- Adamant/App/DI/AppAssembly.swift | 8 +- Adamant/Helpers/Node+UI.swift | 2 +- Adamant/Helpers/NodeGroup+Constants.swift | 14 ++- Adamant/Modules/Account/AccountFactory.swift | 2 +- .../Account/AccountViewController.swift | 4 +- .../DTO/InfoServiceHistoryItemDTO.swift | 13 +++ .../Models/DTO/InfoServiceResponseDTO.swift | 13 +++ .../Models/DTO/InfoServiceStatusDTO.swift | 33 +++++++ .../Models/InfoServiceApiCommands.swift | 15 +++ .../Models/InfoServiceApiError.swift | 50 ++++++++++ .../Models/InfoServiceApiResult.swift | 9 ++ .../Models/InfoServiceHistoryItem.swift | 15 +++ .../Models/InfoServiceStatus.swift | 15 +++ .../Protocols}/CurrencyInfoService.swift | 4 +- .../InfoServiceApiServiceProtocol.swift | 13 +++ .../Protocols/InfoServiceMapperProtocol.swift | 27 ++++++ .../Services/InfoServiceApiCore.swift | 49 ++++++++++ .../Services/InfoServiceApiService.swift | 46 +++++++++ .../Services/InfoServiceMapper.swift | 67 +++++++++++++ .../AdmTransactionDetailsViewController.swift | 2 +- .../Wallets/Adamant/AdmWalletFactory.swift | 6 +- .../Wallets/Bitcoin/BtcApiService.swift | 2 +- .../Wallets/Bitcoin/BtcWalletFactory.swift | 6 +- .../Modules/Wallets/Dash/DashApiService.swift | 2 +- .../Wallets/Dash/DashWalletFactory.swift | 6 +- .../Modules/Wallets/Doge/DogeApiService.swift | 2 +- .../Wallets/Doge/DogeWalletFactory.swift | 6 +- .../Wallets/ERC20/ERC20WalletFactory.swift | 6 +- .../Modules/Wallets/Ethereum/EthApiCore.swift | 2 +- .../Wallets/Ethereum/EthWalletFactory.swift | 6 +- .../Modules/Wallets/Klayr/KlyApiCore.swift | 2 +- .../Wallets/Klayr/KlyServiceApiService.swift | 2 +- .../Wallets/Klayr/KlyWalletFactory.swift | 6 +- ...TransactionDetailsViewControllerBase.swift | 4 +- .../Wallets/TransferViewControllerBase.swift | 4 +- .../Wallets/WalletViewControllerBase.swift | 4 +- Adamant/Services/AdamantAccountService.swift | 2 +- .../Services/AdamantCurrencyInfoService.swift | 15 ++- Adamant/Services/ApiServiceCompose.swift | 2 + .../DataProviders/DefaultNodesProvider.swift | 2 + .../Sources/CommonKit/AdamantResources.swift | 2 +- .../Helpers/Node+NodeKeychainDTO.swift | 4 +- .../Helpers/NodeGroup+Constants.swift | 2 +- .../Sources/CommonKit/Helpers/Version.swift | 2 + .../Sources/CommonKit/Models/Node/Node.swift | 4 +- .../CommonKit/Models/Node/NodeGroup.swift | 1 + .../Models/Node/NodeStatusInfo.swift | 4 +- .../Services/ApiService/AdamantApiCore.swift | 2 +- .../BlockchainHealthCheckWrapper.swift | 6 +- 51 files changed, 547 insertions(+), 66 deletions(-) create mode 100644 Adamant/Modules/InfoService/Models/DTO/InfoServiceHistoryItemDTO.swift create mode 100644 Adamant/Modules/InfoService/Models/DTO/InfoServiceResponseDTO.swift create mode 100644 Adamant/Modules/InfoService/Models/DTO/InfoServiceStatusDTO.swift create mode 100644 Adamant/Modules/InfoService/Models/InfoServiceApiCommands.swift create mode 100644 Adamant/Modules/InfoService/Models/InfoServiceApiError.swift create mode 100644 Adamant/Modules/InfoService/Models/InfoServiceApiResult.swift create mode 100644 Adamant/Modules/InfoService/Models/InfoServiceHistoryItem.swift create mode 100644 Adamant/Modules/InfoService/Models/InfoServiceStatus.swift rename Adamant/{ServiceProtocols => Modules/InfoService/Protocols}/CurrencyInfoService.swift (95%) create mode 100644 Adamant/Modules/InfoService/Protocols/InfoServiceApiServiceProtocol.swift create mode 100644 Adamant/Modules/InfoService/Protocols/InfoServiceMapperProtocol.swift create mode 100644 Adamant/Modules/InfoService/Services/InfoServiceApiCore.swift create mode 100644 Adamant/Modules/InfoService/Services/InfoServiceApiService.swift create mode 100644 Adamant/Modules/InfoService/Services/InfoServiceMapper.swift diff --git a/Adamant.xcodeproj/project.pbxproj b/Adamant.xcodeproj/project.pbxproj index 7b17e9f3c..51b4245b0 100644 --- a/Adamant.xcodeproj/project.pbxproj +++ b/Adamant.xcodeproj/project.pbxproj @@ -271,6 +271,19 @@ 93496BB52A6CAED100DD062F /* Roboto_300_normal.ttf in Resources */ = {isa = PBXBuildFile; fileRef = 93496BAA2A6CAED100DD062F /* Roboto_300_normal.ttf */; }; 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 */; }; + 934FD9A42C783D2E00336841 /* InfoServiceStatusDTO.swift in Sources */ = {isa = PBXBuildFile; fileRef = 934FD9A32C783D2E00336841 /* InfoServiceStatusDTO.swift */; }; + 934FD9A62C783DB700336841 /* InfoServiceStatus.swift in Sources */ = {isa = PBXBuildFile; fileRef = 934FD9A52C783DB700336841 /* InfoServiceStatus.swift */; }; + 934FD9A82C783E0C00336841 /* InfoServiceMapper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 934FD9A72C783E0C00336841 /* InfoServiceMapper.swift */; }; + 934FD9AA2C7842C800336841 /* InfoServiceResponseDTO.swift in Sources */ = {isa = PBXBuildFile; fileRef = 934FD9A92C7842C800336841 /* InfoServiceResponseDTO.swift */; }; + 934FD9AC2C78443600336841 /* InfoServiceHistoryItemDTO.swift in Sources */ = {isa = PBXBuildFile; fileRef = 934FD9AB2C78443600336841 /* InfoServiceHistoryItemDTO.swift */; }; + 934FD9AE2C7846BA00336841 /* InfoServiceHistoryItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 934FD9AD2C7846BA00336841 /* InfoServiceHistoryItem.swift */; }; + 934FD9B02C78481500336841 /* InfoServiceApiError.swift in Sources */ = {isa = PBXBuildFile; fileRef = 934FD9AF2C78481500336841 /* InfoServiceApiError.swift */; }; + 934FD9B22C7849C800336841 /* InfoServiceApiResult.swift in Sources */ = {isa = PBXBuildFile; fileRef = 934FD9B12C7849C800336841 /* InfoServiceApiResult.swift */; }; + 934FD9B42C78514E00336841 /* InfoServiceApiCore.swift in Sources */ = {isa = PBXBuildFile; fileRef = 934FD9B32C78514E00336841 /* InfoServiceApiCore.swift */; }; + 934FD9B62C78519600336841 /* InfoServiceApiCommands.swift in Sources */ = {isa = PBXBuildFile; fileRef = 934FD9B52C78519600336841 /* InfoServiceApiCommands.swift */; }; + 934FD9B82C7854AF00336841 /* InfoServiceMapperProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 934FD9B72C7854AF00336841 /* InfoServiceMapperProtocol.swift */; }; + 934FD9BA2C78565400336841 /* InfoServiceApiService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 934FD9B92C78565400336841 /* InfoServiceApiService.swift */; }; + 934FD9BC2C78567300336841 /* InfoServiceApiServiceProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 934FD9BB2C78567300336841 /* InfoServiceApiServiceProtocol.swift */; }; 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 */; }; @@ -904,6 +917,19 @@ 93496BAA2A6CAED100DD062F /* Roboto_300_normal.ttf */ = {isa = PBXFileReference; lastKnownFileType = file; path = Roboto_300_normal.ttf; sourceTree = ""; }; 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 = ""; }; + 934FD9A32C783D2E00336841 /* InfoServiceStatusDTO.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InfoServiceStatusDTO.swift; sourceTree = ""; }; + 934FD9A52C783DB700336841 /* InfoServiceStatus.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InfoServiceStatus.swift; sourceTree = ""; }; + 934FD9A72C783E0C00336841 /* InfoServiceMapper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InfoServiceMapper.swift; sourceTree = ""; }; + 934FD9A92C7842C800336841 /* InfoServiceResponseDTO.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InfoServiceResponseDTO.swift; sourceTree = ""; }; + 934FD9AB2C78443600336841 /* InfoServiceHistoryItemDTO.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InfoServiceHistoryItemDTO.swift; sourceTree = ""; }; + 934FD9AD2C7846BA00336841 /* InfoServiceHistoryItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InfoServiceHistoryItem.swift; sourceTree = ""; }; + 934FD9AF2C78481500336841 /* InfoServiceApiError.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InfoServiceApiError.swift; sourceTree = ""; }; + 934FD9B12C7849C800336841 /* InfoServiceApiResult.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InfoServiceApiResult.swift; sourceTree = ""; }; + 934FD9B32C78514E00336841 /* InfoServiceApiCore.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InfoServiceApiCore.swift; sourceTree = ""; }; + 934FD9B52C78519600336841 /* InfoServiceApiCommands.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InfoServiceApiCommands.swift; sourceTree = ""; }; + 934FD9B72C7854AF00336841 /* InfoServiceMapperProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InfoServiceMapperProtocol.swift; sourceTree = ""; }; + 934FD9B92C78565400336841 /* InfoServiceApiService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InfoServiceApiService.swift; sourceTree = ""; }; + 934FD9BB2C78567300336841 /* InfoServiceApiServiceProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InfoServiceApiServiceProtocol.swift; 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 = ""; }; @@ -1715,6 +1741,59 @@ path = Fonts; sourceTree = ""; }; + 934FD99E2C783C9500336841 /* InfoService */ = { + isa = PBXGroup; + children = ( + 934FD99F2C783C9E00336841 /* Models */, + 934FD9A12C783CAC00336841 /* Protocols */, + 934FD9A02C783CA700336841 /* Services */, + ); + path = InfoService; + sourceTree = ""; + }; + 934FD99F2C783C9E00336841 /* Models */ = { + isa = PBXGroup; + children = ( + 934FD9A22C783D1E00336841 /* DTO */, + 934FD9A52C783DB700336841 /* InfoServiceStatus.swift */, + 934FD9AD2C7846BA00336841 /* InfoServiceHistoryItem.swift */, + 934FD9AF2C78481500336841 /* InfoServiceApiError.swift */, + 934FD9B12C7849C800336841 /* InfoServiceApiResult.swift */, + 934FD9B52C78519600336841 /* InfoServiceApiCommands.swift */, + ); + path = Models; + sourceTree = ""; + }; + 934FD9A02C783CA700336841 /* Services */ = { + isa = PBXGroup; + children = ( + 934FD9A72C783E0C00336841 /* InfoServiceMapper.swift */, + 934FD9B32C78514E00336841 /* InfoServiceApiCore.swift */, + 934FD9B92C78565400336841 /* InfoServiceApiService.swift */, + ); + path = Services; + sourceTree = ""; + }; + 934FD9A12C783CAC00336841 /* Protocols */ = { + isa = PBXGroup; + children = ( + 934FD9B72C7854AF00336841 /* InfoServiceMapperProtocol.swift */, + 64EAB37322463E020018D9B2 /* CurrencyInfoService.swift */, + 934FD9BB2C78567300336841 /* InfoServiceApiServiceProtocol.swift */, + ); + path = Protocols; + sourceTree = ""; + }; + 934FD9A22C783D1E00336841 /* DTO */ = { + isa = PBXGroup; + children = ( + 934FD9A32C783D2E00336841 /* InfoServiceStatusDTO.swift */, + 934FD9A92C7842C800336841 /* InfoServiceResponseDTO.swift */, + 934FD9AB2C78443600336841 /* InfoServiceHistoryItemDTO.swift */, + ); + path = DTO; + sourceTree = ""; + }; 935F53D429BE8F4800779492 /* RichTransactionStatusService */ = { isa = PBXGroup; children = ( @@ -2062,7 +2141,6 @@ 648BCA6C213D384F00875EB5 /* AvatarService.swift */, E9A174B22057EC47003667CD /* BackgroundFetchService.swift */, E9E7CDBD2003AEFB00DFC4DB /* CellFactory.swift */, - 64EAB37322463E020018D9B2 /* CurrencyInfoService.swift */, E9E7CD8C20026B6600DFC4DB /* DialogService.swift */, E90A494C204DA932009F6A65 /* LocalAuthentication.swift */, E93D7ABD2052CEE1005D19DC /* NotificationsService.swift */, @@ -2204,6 +2282,7 @@ E919479920000FFD001362F8 /* Modules */ = { isa = PBXGroup; children = ( + 934FD99E2C783C9500336841 /* InfoService */, 3A2478AF2BB45DE2009D89E9 /* StorageUsage */, 9366588B2B0AB68300BDB2D3 /* CoinsNodesList */, 3AA50DED2AEBE61C00C58FC8 /* PartnerQR */, @@ -3065,6 +3144,7 @@ 648CE3A6229AD1CD0070A2CC /* DashWalletService+Send.swift in Sources */, 3AF9DF0B2BFE306C009A43A8 /* ChatFileProtocol.swift in Sources */, E987024920C2B1F700E393F4 /* AdamantChatsProvider+fakeMessages.swift in Sources */, + 934FD9B02C78481500336841 /* InfoServiceApiError.swift in Sources */, 6403F5E222723F7500D58779 /* DashWallet.swift in Sources */, 26A975FF2B7E843E0095C367 /* SelectTextView.swift in Sources */, 93294B822AAD0BB400911109 /* BtcWalletFactory.swift in Sources */, @@ -3085,6 +3165,7 @@ E9E7CD8F20026CD300DFC4DB /* AdamantDialogService.swift in Sources */, E993301E212EF39700CD5200 /* EthTransferViewController.swift in Sources */, 648CE3A42299A94D0070A2CC /* DashTransactionDetailsViewController.swift in Sources */, + 934FD9AC2C78443600336841 /* InfoServiceHistoryItemDTO.swift in Sources */, E90847362196FEA80095825D /* Chatroom+CoreDataClass.swift in Sources */, 3A4068342ACD7C18007E87BD /* CoinTransaction+TransactionDetails.swift in Sources */, E921597B206503000000CA5C /* ButtonsStripeView.swift in Sources */, @@ -3160,6 +3241,7 @@ E95F85C7200A9B070070534A /* ChatTableViewCell.swift in Sources */, A50A41082822F8CE006BDFE1 /* BtcWalletService.swift in Sources */, 937736822B0949C500B35C7A /* NodeCell+Model.swift in Sources */, + 934FD9AA2C7842C800336841 /* InfoServiceResponseDTO.swift in Sources */, 6455E9F121075D3600B2E94C /* AddressBookService.swift in Sources */, 93C794482B0778C700408826 /* DashGetBlockDTO.swift in Sources */, 6449BA6D235CA0930033B936 /* ERC20TransactionsViewController.swift in Sources */, @@ -3186,6 +3268,7 @@ 41CE153A297FF98200CC9254 /* Web3Swift+Adamant.swift in Sources */, 93CC8DC9296F01DE003772BF /* ChatTransactionContainerView+Model.swift in Sources */, E9147B6F205088DE00145913 /* LoginViewController+Pinpad.swift in Sources */, + 934FD9B62C78519600336841 /* InfoServiceApiCommands.swift in Sources */, E9FAE5E2203ED1AE008D3A6B /* ShareQrViewController.swift in Sources */, E983AE2120E655C500497E1A /* AccountHeaderView.swift in Sources */, E971591C2168209800A5F904 /* EthWalletService+RichMessageProviderWithStatusCheck.swift in Sources */, @@ -3226,6 +3309,7 @@ 648BCA6D213D384F00875EB5 /* AvatarService.swift in Sources */, 3A26D94B2C3D3838003AD832 /* KlyTransactionsViewController.swift in Sources */, E940088F2119A9E800CD2D67 /* BigInt+Decimal.swift in Sources */, + 934FD9B82C7854AF00336841 /* InfoServiceMapperProtocol.swift in Sources */, E9E7CDC72003F6D200DFC4DB /* TransactionTableViewCell.swift in Sources */, 938F7D5D2955C8F9001915CA /* ChatLayoutManager.swift in Sources */, 6403F5DE22723C6800D58779 /* DashMainnet.swift in Sources */, @@ -3333,6 +3417,7 @@ E91E5BF220DAF05500B06B3C /* NodeCell.swift in Sources */, 4133AF242A1CE1A3001A0A1E /* UITableView+Adamant.swift in Sources */, 41A1995629D56EAA0031AD75 /* ChatMessageReplyCell+Model.swift in Sources */, + 934FD9BC2C78567300336841 /* InfoServiceApiServiceProtocol.swift in Sources */, E90847312196FEA80095825D /* ChatTransaction+CoreDataProperties.swift in Sources */, 3A2F55FA2AC6F308000A3F26 /* CoinTransaction+CoreDataProperties.swift in Sources */, E99330262136B0E500CD5200 /* TransferViewControllerBase+QR.swift in Sources */, @@ -3371,6 +3456,7 @@ E923222621135F9000A7E5AF /* EthAccount.swift in Sources */, 3A7FD6F52C076D86002AF7D9 /* FileMessageStatus.swift in Sources */, E9061B97207501E40011F104 /* AdamantUserInfoKey.swift in Sources */, + 934FD9A42C783D2E00336841 /* InfoServiceStatusDTO.swift in Sources */, 3A299C6D2B838F8F00B54C61 /* ChatMediaContainerView+Model.swift in Sources */, E93D7ABE2052CEE1005D19DC /* NotificationsService.swift in Sources */, E940087D2114EDEE00CD2D67 /* EthWallet.swift in Sources */, @@ -3395,6 +3481,7 @@ E9722068201F42CC004F2AAD /* InMemoryCoreDataStack.swift in Sources */, 4133AED429769EEC00F3D017 /* UpdatingIndicatorView.swift in Sources */, 551F66E628959A5300DE5D69 /* LoadingView.swift in Sources */, + 934FD9A62C783DB700336841 /* InfoServiceStatus.swift in Sources */, 93B28EC52B076E2C007F268B /* DashBlockchainInfoDTO.swift in Sources */, E98FC34420F920BD00032D65 /* UIFont+adamant.swift in Sources */, 644EC35720EFAAB700F40C73 /* DelegatesListViewController.swift in Sources */, @@ -3404,6 +3491,7 @@ E9B4E1A8210F079E007E77FC /* DoubleDetailsTableViewCell.swift in Sources */, E9502740202E257E002C1098 /* RepeaterService.swift in Sources */, E93D7AC02052CF63005D19DC /* AdamantNotificationService.swift in Sources */, + 934FD9B42C78514E00336841 /* InfoServiceApiCore.swift in Sources */, 3AF53F8D2B3DCFA300B30312 /* NodeGroup+Constants.swift in Sources */, 411743002A39B1D2008CD98A /* ContributeFactory.swift in Sources */, A5E04224282A830B0076CD13 /* BtcTransactionsViewController.swift in Sources */, @@ -3412,6 +3500,7 @@ 649D6BEC21BD5A53009E727B /* UISuffixTextField.swift in Sources */, E93B0D762028B28E00126346 /* AdamantChatsProvider.swift in Sources */, 3A33F9FA2A7A53DA002B8003 /* EmojiUpdateType.swift in Sources */, + 934FD9A82C783E0C00336841 /* InfoServiceMapper.swift in Sources */, 936658932B0AC03700BDB2D3 /* CoinsNodesListStrings.swift in Sources */, 3A5DF1792C4698EC0005369D /* EdgeInsetLabel.swift in Sources */, 3AF8D9E92C73ADFA007A7CBC /* IPFSNodeStatus.swift in Sources */, @@ -3448,6 +3537,7 @@ E9484B79227C617E008E10F0 /* BalanceTableViewCell.swift in Sources */, E90847352196FEA80095825D /* MessageTransaction+CoreDataProperties.swift in Sources */, E9771DA722997F310099AAC7 /* ServerResponseWithTimestamp.swift in Sources */, + 934FD9AE2C7846BA00336841 /* InfoServiceHistoryItem.swift in Sources */, E9A03FD420DBC824007653A1 /* NodeVersion.swift in Sources */, 648CE3AA229AD1F90070A2CC /* DashWalletService+RichMessageProviderWithStatusCheck.swift in Sources */, 937751AD2A68BCE10054BD65 /* MessageCellWrapper.swift in Sources */, @@ -3455,6 +3545,7 @@ E90847342196FEA80095825D /* MessageTransaction+CoreDataClass.swift in Sources */, E9960B3521F5154300C840A8 /* DummyAccount+CoreDataClass.swift in Sources */, 64E1C833222EA0F0006C4DA7 /* DogeWalletViewController.swift in Sources */, + 934FD9B22C7849C800336841 /* InfoServiceApiResult.swift in Sources */, E93EB09F20DA3FA4001F9601 /* NodesEditorFactory.swift in Sources */, 93294B8F2AAD2C6B00911109 /* SwiftyOnboard.swift in Sources */, 93760BE12C65A2F3002507C3 /* Mnemonic+extended.swift in Sources */, @@ -3464,6 +3555,7 @@ E9E7CDB12002B97B00DFC4DB /* AccountFactory.swift in Sources */, E9AA8BF82129F13000F9249F /* ComplexTransferViewController.swift in Sources */, E9A174B52057EDCE003667CD /* AdamantTransfersProvider+backgroundFetch.swift in Sources */, + 934FD9BA2C78565400336841 /* InfoServiceApiService.swift in Sources */, 9382F61329DEC0A3005E6216 /* ChatModelView.swift in Sources */, E9147B5F20500E9300145913 /* MyLittlePinpad+adamant.swift in Sources */, E9981896212095CA0018C84C /* EthWalletViewController.swift in Sources */, diff --git a/Adamant/App/AppDelegate.swift b/Adamant/App/AppDelegate.swift index ad9b80d34..cc3e53d5b 100644 --- a/Adamant/App/AppDelegate.swift +++ b/Adamant/App/AppDelegate.swift @@ -263,11 +263,11 @@ class AppDelegate: UIResponder, UIApplicationDelegate { dialogService.showError(withMessage: "Failed to register AddressBookService autoupdate. Please, report a bug", supportEmail: true, error: nil) } - if let currencyInfoService = container.resolve(CurrencyInfoService.self) { + if let currencyInfoService = container.resolve(InfoServiceProtocol.self) { currencyInfoService.update() // Initial update repeater.registerForegroundCall(label: "currencyInfoService", interval: 60, queue: .global(qos: .utility), callback: currencyInfoService.update) } else { - dialogService.showError(withMessage: "Failed to register CurrencyInfoService autoupdate. Please, report a bug", supportEmail: true, error: nil) + dialogService.showError(withMessage: "Failed to register InfoServiceProtocol autoupdate. Please, report a bug", supportEmail: true, error: nil) } // MARK: 7. Logout reset diff --git a/Adamant/App/DI/AppAssembly.swift b/Adamant/App/DI/AppAssembly.swift index 367d3114e..52d199945 100644 --- a/Adamant/App/DI/AppAssembly.swift +++ b/Adamant/App/DI/AppAssembly.swift @@ -251,7 +251,7 @@ struct AppAssembly: Assembly { guard let service = c as? AdamantAccountService else { return } service.notificationsService = r.resolve(NotificationsService.self)! service.pushNotificationsTokenService = r.resolve(PushNotificationsTokenService.self)! - service.currencyInfoService = r.resolve(CurrencyInfoService.self)! + service.currencyInfoService = r.resolve(InfoServiceProtocol.self)! service.visibleWalletService = r.resolve(VisibleWalletsService.self)! } } @@ -266,9 +266,9 @@ struct AppAssembly: Assembly { ) }.inObjectScope(.container) - // MARK: CurrencyInfoService - container.register(CurrencyInfoService.self) { r in - AdamantCurrencyInfoService( + // MARK: InfoServiceProtocol + container.register(InfoServiceProtocol.self) { r in + AdamantInfoServiceProtocol( securedStore: r.resolve(SecuredStore.self)!, walletServiceCompose: r.resolve(WalletServiceCompose.self)! ) diff --git a/Adamant/Helpers/Node+UI.swift b/Adamant/Helpers/Node+UI.swift index dca5de942..c7689f1f1 100644 --- a/Adamant/Helpers/Node+UI.swift +++ b/Adamant/Helpers/Node+UI.swift @@ -35,7 +35,7 @@ extension Node { case .notAllowed(let reason): return [ reason.text, - version + version?.string ] .compactMap { $0 } .joined(separator: " ") diff --git a/Adamant/Helpers/NodeGroup+Constants.swift b/Adamant/Helpers/NodeGroup+Constants.swift index a0e02e6fa..325eb67b7 100644 --- a/Adamant/Helpers/NodeGroup+Constants.swift +++ b/Adamant/Helpers/NodeGroup+Constants.swift @@ -27,6 +27,8 @@ extension NodeGroup { return DashWalletService.healthCheckParameters.onScreenUpdateInterval case .ipfs: return IPFSApiService.healthCheckParameters.onScreenUpdateInterval + case .infoService: + return 1000 } } @@ -48,6 +50,8 @@ extension NodeGroup { return DashWalletService.healthCheckParameters.crucialUpdateInterval case .ipfs: return IPFSApiService.healthCheckParameters.crucialUpdateInterval + case .infoService: + return 1000 } } @@ -69,6 +73,8 @@ extension NodeGroup { return DashWalletService.healthCheckParameters.threshold case .ipfs: return IPFSApiService.healthCheckParameters.threshold + case .infoService: + return 1000 } } @@ -90,6 +96,8 @@ extension NodeGroup { return DashWalletService.healthCheckParameters.normalUpdateInterval case .ipfs: return IPFSApiService.healthCheckParameters.normalUpdateInterval + case .infoService: + return 1000 } } @@ -110,7 +118,7 @@ extension NodeGroup { DogeWalletService.minNodeVersion case .dash: DashWalletService.minNodeVersion - case .ipfs: + case .ipfs, .infoService: nil } else { return nil } @@ -136,12 +144,14 @@ extension NodeGroup { return AdmWalletService.tokenNetworkSymbol case .ipfs: return IPFSApiService.symbol + case .infoService: + return "INFO SERVICE" } } var includeVersionTitle: Bool { switch self { - case .btc, .klyNode, .klyService, .doge, .adm: + case .btc, .klyNode, .klyService, .doge, .adm, .infoService: return true case .eth, .dash, .ipfs: return false diff --git a/Adamant/Modules/Account/AccountFactory.swift b/Adamant/Modules/Account/AccountFactory.swift index cf356923d..8c5ac2a39 100644 --- a/Adamant/Modules/Account/AccountFactory.swift +++ b/Adamant/Modules/Account/AccountFactory.swift @@ -22,7 +22,7 @@ struct AccountFactory { transfersProvider: assembler.resolve(TransfersProvider.self)!, localAuth: assembler.resolve(LocalAuthentication.self)!, avatarService: assembler.resolve(AvatarService.self)!, - currencyInfoService: assembler.resolve(CurrencyInfoService.self)!, + currencyInfoService: assembler.resolve(InfoServiceProtocol.self)!, languageService: assembler.resolve(LanguageStorageProtocol.self)!, walletServiceCompose: assembler.resolve(WalletServiceCompose.self)! ) diff --git a/Adamant/Modules/Account/AccountViewController.swift b/Adamant/Modules/Account/AccountViewController.swift index 0ff1df2f8..7c6829313 100644 --- a/Adamant/Modules/Account/AccountViewController.swift +++ b/Adamant/Modules/Account/AccountViewController.swift @@ -151,7 +151,7 @@ final class AccountViewController: FormViewController { private let notificationsService: NotificationsService private let transfersProvider: TransfersProvider private let avatarService: AvatarService - private let currencyInfoService: CurrencyInfoService + private let currencyInfoService: InfoServiceProtocol private let languageService: LanguageStorageProtocol private let walletServiceCompose: WalletServiceCompose @@ -208,7 +208,7 @@ final class AccountViewController: FormViewController { transfersProvider: TransfersProvider, localAuth: LocalAuthentication, avatarService: AvatarService, - currencyInfoService: CurrencyInfoService, + currencyInfoService: InfoServiceProtocol, languageService: LanguageStorageProtocol, walletServiceCompose: WalletServiceCompose ) { diff --git a/Adamant/Modules/InfoService/Models/DTO/InfoServiceHistoryItemDTO.swift b/Adamant/Modules/InfoService/Models/DTO/InfoServiceHistoryItemDTO.swift new file mode 100644 index 000000000..192d55860 --- /dev/null +++ b/Adamant/Modules/InfoService/Models/DTO/InfoServiceHistoryItemDTO.swift @@ -0,0 +1,13 @@ +// +// InfoServiceHistoryItemDTO.swift +// Adamant +// +// Created by Andrew G on 23.08.2024. +// Copyright © 2024 Adamant. All rights reserved. +// + +struct InfoServiceHistoryItemDTO: Codable { + let _id: String + let date: Int + let tickers: [String: Double]? +} diff --git a/Adamant/Modules/InfoService/Models/DTO/InfoServiceResponseDTO.swift b/Adamant/Modules/InfoService/Models/DTO/InfoServiceResponseDTO.swift new file mode 100644 index 000000000..5b56cb37f --- /dev/null +++ b/Adamant/Modules/InfoService/Models/DTO/InfoServiceResponseDTO.swift @@ -0,0 +1,13 @@ +// +// InfoServiceResponseDTO.swift +// Adamant +// +// Created by Andrew G on 23.08.2024. +// Copyright © 2024 Adamant. All rights reserved. +// + +struct InfoServiceResponseDTO: Codable { + let success: Bool + let date: Int + let result: Body? +} diff --git a/Adamant/Modules/InfoService/Models/DTO/InfoServiceStatusDTO.swift b/Adamant/Modules/InfoService/Models/DTO/InfoServiceStatusDTO.swift new file mode 100644 index 000000000..8c71d2c2e --- /dev/null +++ b/Adamant/Modules/InfoService/Models/DTO/InfoServiceStatusDTO.swift @@ -0,0 +1,33 @@ +// +// InfoServiceStatusDTO.swift +// Adamant +// +// Created by Andrew G on 23.08.2024. +// Copyright © 2024 Adamant. All rights reserved. +// + +struct InfoServiceStatusDTO: Codable { + let success: Bool + let date: Int + let ready: Bool + let updating: Bool + let next_update: Int + let last_updated: Int? + let version: String +} + +//{ +// success: boolean, +// // Unix timestamp of the server time in ms +// date: number, +// // Whether Currencyinfo has fetched its first rates +// ready: boolean, +// // Whether Currencyinfo is updating the rates right now +// updating: boolean, +// // Unix timestamp in ms of the next update or the current one when updating +// next_update: number, +// // Unix timestamp of the last update in ms or `null` before the first rates have been fetched +// last_updated: number | null, +// // Currencyinfo version in `x.y.z` format +// version: string +//} diff --git a/Adamant/Modules/InfoService/Models/InfoServiceApiCommands.swift b/Adamant/Modules/InfoService/Models/InfoServiceApiCommands.swift new file mode 100644 index 000000000..8de0abb4d --- /dev/null +++ b/Adamant/Modules/InfoService/Models/InfoServiceApiCommands.swift @@ -0,0 +1,15 @@ +// +// InfoServiceApiCommands.swift +// Adamant +// +// Created by Andrew G on 23.08.2024. +// Copyright © 2024 Adamant. All rights reserved. +// + +import Foundation + +enum InfoServiceApiCommands { + static let status = "/status" + static let get = "/get" + static let getHistory = "/getHistory" +} diff --git a/Adamant/Modules/InfoService/Models/InfoServiceApiError.swift b/Adamant/Modules/InfoService/Models/InfoServiceApiError.swift new file mode 100644 index 000000000..10c1fb073 --- /dev/null +++ b/Adamant/Modules/InfoService/Models/InfoServiceApiError.swift @@ -0,0 +1,50 @@ +// +// InfoServiceApiError.swift +// Adamant +// +// Created by Andrew G on 23.08.2024. +// Copyright © 2024 Adamant. All rights reserved. +// + +import Foundation +import CommonKit + +enum InfoServiceApiError: Error { + case unknown + case parsingError + case apiError(ApiServiceError) +} + +extension InfoServiceApiError: RichError { + var message: String { + switch self { + case .unknown: + .adamant.sharedErrors.unknownError + case .parsingError: + .localized( + "ApiService.InternalError.ParsingFailed", + comment: "Serious internal error: Error parsing response" + ) + case let .apiError(error): + error.message + } + } + + var internalError: Error? { + switch self { + case .unknown, .parsingError: + nil + case let .apiError(error): + error + } + } + + var level: ErrorLevel { + switch self { + case .unknown, .parsingError: + .error + case let .apiError(error): + error.level + } + } +} diff --git a/Adamant/Modules/InfoService/Models/InfoServiceApiResult.swift b/Adamant/Modules/InfoService/Models/InfoServiceApiResult.swift new file mode 100644 index 000000000..2fbe537d0 --- /dev/null +++ b/Adamant/Modules/InfoService/Models/InfoServiceApiResult.swift @@ -0,0 +1,9 @@ +// +// InfoServiceApiResult.swift +// Adamant +// +// Created by Andrew G on 23.08.2024. +// Copyright © 2024 Adamant. All rights reserved. +// + +typealias InfoServiceApiResult = Result diff --git a/Adamant/Modules/InfoService/Models/InfoServiceHistoryItem.swift b/Adamant/Modules/InfoService/Models/InfoServiceHistoryItem.swift new file mode 100644 index 000000000..4349f1808 --- /dev/null +++ b/Adamant/Modules/InfoService/Models/InfoServiceHistoryItem.swift @@ -0,0 +1,15 @@ +// +// InfoServiceHistoryItem.swift +// Adamant +// +// Created by Andrew G on 23.08.2024. +// Copyright © 2024 Adamant. All rights reserved. +// + +import Foundation + +struct InfoServiceHistoryItem { + let date: Date + let ticker: String + let price: Double +} diff --git a/Adamant/Modules/InfoService/Models/InfoServiceStatus.swift b/Adamant/Modules/InfoService/Models/InfoServiceStatus.swift new file mode 100644 index 000000000..0c67ad745 --- /dev/null +++ b/Adamant/Modules/InfoService/Models/InfoServiceStatus.swift @@ -0,0 +1,15 @@ +// +// InfoServiceStatus.swift +// Adamant +// +// Created by Andrew G on 23.08.2024. +// Copyright © 2024 Adamant. All rights reserved. +// + +import Foundation +import CommonKit + +struct InfoServiceStatus { + let lastUpdated: Date + let version: Version +} diff --git a/Adamant/ServiceProtocols/CurrencyInfoService.swift b/Adamant/Modules/InfoService/Protocols/CurrencyInfoService.swift similarity index 95% rename from Adamant/ServiceProtocols/CurrencyInfoService.swift rename to Adamant/Modules/InfoService/Protocols/CurrencyInfoService.swift index 7a8547491..a31a4c55d 100644 --- a/Adamant/ServiceProtocols/CurrencyInfoService.swift +++ b/Adamant/Modules/InfoService/Protocols/CurrencyInfoService.swift @@ -1,5 +1,5 @@ // -// CurrencyInfoService.swift +// InfoServiceProtocol.swift // Adamant // // Created by Anton Boyarkin on 23/03/2019. @@ -37,7 +37,7 @@ enum Currency: String { } // MARK: - protocol -protocol CurrencyInfoService: AnyObject { +protocol InfoServiceProtocol: AnyObject { var currentCurrency: Currency { get set } // Check rates for list of coins diff --git a/Adamant/Modules/InfoService/Protocols/InfoServiceApiServiceProtocol.swift b/Adamant/Modules/InfoService/Protocols/InfoServiceApiServiceProtocol.swift new file mode 100644 index 000000000..feb083ac3 --- /dev/null +++ b/Adamant/Modules/InfoService/Protocols/InfoServiceApiServiceProtocol.swift @@ -0,0 +1,13 @@ +// +// InfoServiceApiServiceProtocol.swift +// Adamant +// +// Created by Andrew G on 23.08.2024. +// Copyright © 2024 Adamant. All rights reserved. +// + +import CommonKit + +protocol InfoServiceApiServiceProtocol: ApiServiceProtocol { + +} diff --git a/Adamant/Modules/InfoService/Protocols/InfoServiceMapperProtocol.swift b/Adamant/Modules/InfoService/Protocols/InfoServiceMapperProtocol.swift new file mode 100644 index 000000000..ea66cfa5c --- /dev/null +++ b/Adamant/Modules/InfoService/Protocols/InfoServiceMapperProtocol.swift @@ -0,0 +1,27 @@ +// +// InfoServiceMapperProtocol.swift +// Adamant +// +// Created by Andrew G on 23.08.2024. +// Copyright © 2024 Adamant. All rights reserved. +// + +import Foundation +import CommonKit + +protocol InfoServiceMapperProtocol { + func mapToModel(_ dto: InfoServiceStatusDTO) -> InfoServiceStatus + + func mapToModel( + _ dto: InfoServiceResponseDTO<[String: Double]> + ) -> InfoServiceApiResult<[String: Double]> + + func mapToModel( + _ dto: InfoServiceResponseDTO<[InfoServiceHistoryItemDTO]> + ) -> InfoServiceApiResult + + func mapToNodeStatusInfo( + ping: TimeInterval, + status: InfoServiceStatus + ) -> NodeStatusInfo +} diff --git a/Adamant/Modules/InfoService/Services/InfoServiceApiCore.swift b/Adamant/Modules/InfoService/Services/InfoServiceApiCore.swift new file mode 100644 index 000000000..0f539a6c8 --- /dev/null +++ b/Adamant/Modules/InfoService/Services/InfoServiceApiCore.swift @@ -0,0 +1,49 @@ +// +// InfoServiceApiCore.swift +// Adamant +// +// Created by Andrew G on 23.08.2024. +// Copyright © 2024 Adamant. All rights reserved. +// + +import Foundation +import CommonKit + +final class InfoServiceApiCore { + private let apiCore: APICoreProtocol + private let mapper: InfoServiceMapperProtocol + + init( + apiCore: APICoreProtocol, + mapper: InfoServiceMapperProtocol + ) { + self.apiCore = apiCore + self.mapper = mapper + } + + func getNodeStatus( + origin: NodeOrigin + ) async -> ApiServiceResult { + await apiCore.sendRequestJsonResponse( + origin: origin, + path: InfoServiceApiCommands.status + ) + } +} + +extension InfoServiceApiCore: BlockchainHealthCheckableService { + func getStatusInfo( + origin: NodeOrigin + ) async -> ApiServiceResult { + let startTimestamp = Date.now.timeIntervalSince1970 + let statusResponse = await getNodeStatus(origin: origin) + let ping = Date.now.timeIntervalSince1970 - startTimestamp + + return statusResponse.map { statusDto in + mapper.mapToNodeStatusInfo( + ping: ping, + status: mapper.mapToModel(statusDto) + ) + } + } +} diff --git a/Adamant/Modules/InfoService/Services/InfoServiceApiService.swift b/Adamant/Modules/InfoService/Services/InfoServiceApiService.swift new file mode 100644 index 000000000..5611d9126 --- /dev/null +++ b/Adamant/Modules/InfoService/Services/InfoServiceApiService.swift @@ -0,0 +1,46 @@ +// +// InfoServiceApiService.swift +// Adamant +// +// Created by Andrew G on 23.08.2024. +// Copyright © 2024 Adamant. All rights reserved. +// + +import Foundation +import CommonKit + +public final class InfoServiceApiService { + public let adamantCore: AdamantCore + public let service: BlockchainHealthCheckWrapper + + public init( + healthCheckWrapper: BlockchainHealthCheckWrapper, + adamantCore: AdamantCore + ) { + service = healthCheckWrapper + self.adamantCore = adamantCore + } + + public func request( + _ request: @Sendable (APICoreProtocol, NodeOrigin) async -> ApiServiceResult + ) async -> ApiServiceResult { + await service.request { admApiCore, origin in + await request(admApiCore.apiCore, origin) + } + } +} + +extension InfoServiceApiService: ApiServiceProtocol { + public var chosenFastestNodeId: UUID? { + service.chosenFastestNodeId + } + + public func healthCheck() { + service.healthCheck() + } + + public var hasActiveNode: Bool { + !service.sortedAllowedNodes.isEmpty + } +} + diff --git a/Adamant/Modules/InfoService/Services/InfoServiceMapper.swift b/Adamant/Modules/InfoService/Services/InfoServiceMapper.swift new file mode 100644 index 000000000..996d17541 --- /dev/null +++ b/Adamant/Modules/InfoService/Services/InfoServiceMapper.swift @@ -0,0 +1,67 @@ +// +// InfoServiceMapper.swift +// Adamant +// +// Created by Andrew G on 23.08.2024. +// Copyright © 2024 Adamant. All rights reserved. +// + +import Foundation +import CommonKit + +struct InfoServiceMapper: InfoServiceMapperProtocol { + func mapToModel(_ dto: InfoServiceStatusDTO) -> InfoServiceStatus { + .init( + lastUpdated: dto.last_updated.map { + Date(timeIntervalSince1970: .init(milliseconds: $0)) + } ?? .adamantNullDate, + version: .init(dto.version) ?? .zero + ) + } + + func mapToModel( + _ dto: InfoServiceResponseDTO<[String: Double]> + ) -> InfoServiceApiResult<[String: Double]> { + mapResponseDTO(dto) + } + + func mapToModel( + _ dto: InfoServiceResponseDTO<[InfoServiceHistoryItemDTO]> + ) -> InfoServiceApiResult { + mapResponseDTO(dto).flatMap { + guard + let item = $0.first, + let ticker = item.tickers?.first?.key, + let price = item.tickers?.first?.value + else { return .failure(.parsingError) } + + return .success(.init( + date: .init(timeIntervalSince1970: .init(milliseconds: item.date)), + ticker: ticker, + price: price + )) + } + } + + func mapToNodeStatusInfo( + ping: TimeInterval, + status: InfoServiceStatus + ) -> NodeStatusInfo { + .init( + ping: ping, + height: Int(status.lastUpdated.timeIntervalSince1970), + wsEnabled: false, + wsPort: nil, + version: status.version + ) + } +} + +private extension InfoServiceMapper { + func mapResponseDTO( + _ dto: InfoServiceResponseDTO + ) -> InfoServiceApiResult { + guard dto.success else { return .failure(.unknown) } + return dto.result.map { .success($0) } ?? .failure(.parsingError) + } +} diff --git a/Adamant/Modules/Wallets/Adamant/AdmTransactionDetailsViewController.swift b/Adamant/Modules/Wallets/Adamant/AdmTransactionDetailsViewController.swift index 257a1e20c..2d642c0f6 100644 --- a/Adamant/Modules/Wallets/Adamant/AdmTransactionDetailsViewController.swift +++ b/Adamant/Modules/Wallets/Adamant/AdmTransactionDetailsViewController.swift @@ -48,7 +48,7 @@ final class AdmTransactionDetailsViewController: TransactionDetailsViewControlle transfersProvider: TransfersProvider, screensFactory: ScreensFactory, dialogService: DialogService, - currencyInfo: CurrencyInfoService, + currencyInfo: InfoServiceProtocol, addressBookService: AddressBookService, languageService: LanguageStorageProtocol ) { diff --git a/Adamant/Modules/Wallets/Adamant/AdmWalletFactory.swift b/Adamant/Modules/Wallets/Adamant/AdmWalletFactory.swift index e65c4c951..819da85cf 100644 --- a/Adamant/Modules/Wallets/Adamant/AdmWalletFactory.swift +++ b/Adamant/Modules/Wallets/Adamant/AdmWalletFactory.swift @@ -19,7 +19,7 @@ struct AdmWalletFactory: WalletFactory { func makeWalletVC(service: Service, screensFactory: ScreensFactory) -> WalletViewController { AdmWalletViewController( dialogService: assembler.resolve(DialogService.self)!, - currencyInfoService: assembler.resolve(CurrencyInfoService.self)!, + currencyInfoService: assembler.resolve(InfoServiceProtocol.self)!, accountService: assembler.resolve(AccountService.self)!, screensFactory: screensFactory, walletServiceCompose: assembler.resolve(WalletServiceCompose.self)!, @@ -48,7 +48,7 @@ struct AdmWalletFactory: WalletFactory { accountsProvider: assembler.resolve(AccountsProvider.self)!, dialogService: assembler.resolve(DialogService.self)!, screensFactory: screensFactory, - currencyInfoService: assembler.resolve(CurrencyInfoService.self)!, + currencyInfoService: assembler.resolve(InfoServiceProtocol.self)!, increaseFeeService: assembler.resolve(IncreaseFeeService.self)!, vibroService: assembler.resolve(VibroService.self)!, walletService: service, @@ -92,7 +92,7 @@ private extension AdmWalletFactory { transfersProvider: assembler.resolve(TransfersProvider.self)!, screensFactory: screensFactory, dialogService: assembler.resolve(DialogService.self)!, - currencyInfo: assembler.resolve(CurrencyInfoService.self)!, + currencyInfo: assembler.resolve(InfoServiceProtocol.self)!, addressBookService: assembler.resolve(AddressBookService.self)!, languageService: assembler.resolve(LanguageStorageProtocol.self)! ) diff --git a/Adamant/Modules/Wallets/Bitcoin/BtcApiService.swift b/Adamant/Modules/Wallets/Bitcoin/BtcApiService.swift index f0b2631ce..95404df2e 100644 --- a/Adamant/Modules/Wallets/Bitcoin/BtcApiService.swift +++ b/Adamant/Modules/Wallets/Bitcoin/BtcApiService.swift @@ -59,7 +59,7 @@ final class BtcApiCore: BlockchainHealthCheckableService { height: blockchainInfo.blocks, wsEnabled: false, wsPort: nil, - version: String(networkInfo.version) + version: .init(String(networkInfo.version)) )) } } diff --git a/Adamant/Modules/Wallets/Bitcoin/BtcWalletFactory.swift b/Adamant/Modules/Wallets/Bitcoin/BtcWalletFactory.swift index 98dc91faa..984a603cd 100644 --- a/Adamant/Modules/Wallets/Bitcoin/BtcWalletFactory.swift +++ b/Adamant/Modules/Wallets/Bitcoin/BtcWalletFactory.swift @@ -19,7 +19,7 @@ struct BtcWalletFactory: WalletFactory { func makeWalletVC(service: Service, screensFactory: ScreensFactory) -> WalletViewController { BtcWalletViewController( dialogService: assembler.resolve(DialogService.self)!, - currencyInfoService: assembler.resolve(CurrencyInfoService.self)!, + currencyInfoService: assembler.resolve(InfoServiceProtocol.self)!, accountService: assembler.resolve(AccountService.self)!, screensFactory: screensFactory, walletServiceCompose: assembler.resolve(WalletServiceCompose.self)!, @@ -44,7 +44,7 @@ struct BtcWalletFactory: WalletFactory { accountsProvider: assembler.resolve(AccountsProvider.self)!, dialogService: assembler.resolve(DialogService.self)!, screensFactory: screensFactory, - currencyInfoService: assembler.resolve(CurrencyInfoService.self)!, + currencyInfoService: assembler.resolve(InfoServiceProtocol.self)!, increaseFeeService: assembler.resolve(IncreaseFeeService.self)!, vibroService: assembler.resolve(VibroService.self)!, walletService: service, @@ -130,7 +130,7 @@ private extension BtcWalletFactory { func makeTransactionDetailsVC(service: Service) -> BtcTransactionDetailsViewController { BtcTransactionDetailsViewController( dialogService: assembler.resolve(DialogService.self)!, - currencyInfo: assembler.resolve(CurrencyInfoService.self)!, + currencyInfo: assembler.resolve(InfoServiceProtocol.self)!, addressBookService: assembler.resolve(AddressBookService.self)!, accountService: assembler.resolve(AccountService.self)!, walletService: service, diff --git a/Adamant/Modules/Wallets/Dash/DashApiService.swift b/Adamant/Modules/Wallets/Dash/DashApiService.swift index 0f084f491..59ce6fd80 100644 --- a/Adamant/Modules/Wallets/Dash/DashApiService.swift +++ b/Adamant/Modules/Wallets/Dash/DashApiService.swift @@ -59,7 +59,7 @@ final class DashApiCore: BlockchainHealthCheckableService { height: blockchainInfo.blocks, wsEnabled: false, wsPort: nil, - version: networkInfo.buildversion + version: .init(networkInfo.buildversion) )) } } diff --git a/Adamant/Modules/Wallets/Dash/DashWalletFactory.swift b/Adamant/Modules/Wallets/Dash/DashWalletFactory.swift index 9250d2d50..f41aa7930 100644 --- a/Adamant/Modules/Wallets/Dash/DashWalletFactory.swift +++ b/Adamant/Modules/Wallets/Dash/DashWalletFactory.swift @@ -19,7 +19,7 @@ struct DashWalletFactory: WalletFactory { func makeWalletVC(service: Service, screensFactory: ScreensFactory) -> WalletViewController { DashWalletViewController( dialogService: assembler.resolve(DialogService.self)!, - currencyInfoService: assembler.resolve(CurrencyInfoService.self)!, + currencyInfoService: assembler.resolve(InfoServiceProtocol.self)!, accountService: assembler.resolve(AccountService.self)!, screensFactory: screensFactory, walletServiceCompose: assembler.resolve(WalletServiceCompose.self)!, @@ -43,7 +43,7 @@ struct DashWalletFactory: WalletFactory { accountsProvider: assembler.resolve(AccountsProvider.self)!, dialogService: assembler.resolve(DialogService.self)!, screensFactory: screensFactory, - currencyInfoService: assembler.resolve(CurrencyInfoService.self)!, + currencyInfoService: assembler.resolve(InfoServiceProtocol.self)!, increaseFeeService: assembler.resolve(IncreaseFeeService.self)!, vibroService: assembler.resolve(VibroService.self)!, walletService: service, @@ -139,7 +139,7 @@ private extension DashWalletFactory { func makeTransactionDetailsVC(service: Service) -> DashTransactionDetailsViewController { DashTransactionDetailsViewController( dialogService: assembler.resolve(DialogService.self)!, - currencyInfo: assembler.resolve(CurrencyInfoService.self)!, + currencyInfo: assembler.resolve(InfoServiceProtocol.self)!, addressBookService: assembler.resolve(AddressBookService.self)!, accountService: assembler.resolve(AccountService.self)!, walletService: service, diff --git a/Adamant/Modules/Wallets/Doge/DogeApiService.swift b/Adamant/Modules/Wallets/Doge/DogeApiService.swift index 04a194da5..2f1c8baed 100644 --- a/Adamant/Modules/Wallets/Doge/DogeApiService.swift +++ b/Adamant/Modules/Wallets/Doge/DogeApiService.swift @@ -39,7 +39,7 @@ final class DogeApiCore: BlockchainHealthCheckableService { height: data.info.blocks, wsEnabled: false, wsPort: nil, - version: "\(data.info.version)" + version: .init("\(data.info.version)") ) } } diff --git a/Adamant/Modules/Wallets/Doge/DogeWalletFactory.swift b/Adamant/Modules/Wallets/Doge/DogeWalletFactory.swift index 2efea1681..18f378589 100644 --- a/Adamant/Modules/Wallets/Doge/DogeWalletFactory.swift +++ b/Adamant/Modules/Wallets/Doge/DogeWalletFactory.swift @@ -19,7 +19,7 @@ struct DogeWalletFactory: WalletFactory { func makeWalletVC(service: Service, screensFactory: ScreensFactory) -> WalletViewController { DogeWalletViewController( dialogService: assembler.resolve(DialogService.self)!, - currencyInfoService: assembler.resolve(CurrencyInfoService.self)!, + currencyInfoService: assembler.resolve(InfoServiceProtocol.self)!, accountService: assembler.resolve(AccountService.self)!, screensFactory: screensFactory, walletServiceCompose: assembler.resolve(WalletServiceCompose.self)!, @@ -43,7 +43,7 @@ struct DogeWalletFactory: WalletFactory { accountsProvider: assembler.resolve(AccountsProvider.self)!, dialogService: assembler.resolve(DialogService.self)!, screensFactory: screensFactory, - currencyInfoService: assembler.resolve(CurrencyInfoService.self)!, + currencyInfoService: assembler.resolve(InfoServiceProtocol.self)!, increaseFeeService: assembler.resolve(IncreaseFeeService.self)!, vibroService: assembler.resolve(VibroService.self)!, walletService: service, @@ -129,7 +129,7 @@ private extension DogeWalletFactory { func makeTransactionDetailsVC(service: Service) -> DogeTransactionDetailsViewController { DogeTransactionDetailsViewController( dialogService: assembler.resolve(DialogService.self)!, - currencyInfo: assembler.resolve(CurrencyInfoService.self)!, + currencyInfo: assembler.resolve(InfoServiceProtocol.self)!, addressBookService: assembler.resolve(AddressBookService.self)!, accountService: assembler.resolve(AccountService.self)!, walletService: service, diff --git a/Adamant/Modules/Wallets/ERC20/ERC20WalletFactory.swift b/Adamant/Modules/Wallets/ERC20/ERC20WalletFactory.swift index d085df46d..c99459f7a 100644 --- a/Adamant/Modules/Wallets/ERC20/ERC20WalletFactory.swift +++ b/Adamant/Modules/Wallets/ERC20/ERC20WalletFactory.swift @@ -19,7 +19,7 @@ struct ERC20WalletFactory: WalletFactory { func makeWalletVC(service: Service, screensFactory: ScreensFactory) -> WalletViewController { ERC20WalletViewController( dialogService: assembler.resolve(DialogService.self)!, - currencyInfoService: assembler.resolve(CurrencyInfoService.self)!, + currencyInfoService: assembler.resolve(InfoServiceProtocol.self)!, accountService: assembler.resolve(AccountService.self)!, screensFactory: screensFactory, walletServiceCompose: assembler.resolve(WalletServiceCompose.self)!, @@ -43,7 +43,7 @@ struct ERC20WalletFactory: WalletFactory { accountsProvider: assembler.resolve(AccountsProvider.self)!, dialogService: assembler.resolve(DialogService.self)!, screensFactory: screensFactory, - currencyInfoService: assembler.resolve(CurrencyInfoService.self)!, + currencyInfoService: assembler.resolve(InfoServiceProtocol.self)!, increaseFeeService: assembler.resolve(IncreaseFeeService.self)!, vibroService: assembler.resolve(VibroService.self)!, walletService: service, @@ -131,7 +131,7 @@ private extension ERC20WalletFactory { func makeTransactionDetailsVC(service: Service) -> ERC20TransactionDetailsViewController { ERC20TransactionDetailsViewController( dialogService: assembler.resolve(DialogService.self)!, - currencyInfo: assembler.resolve(CurrencyInfoService.self)!, + currencyInfo: assembler.resolve(InfoServiceProtocol.self)!, addressBookService: assembler.resolve(AddressBookService.self)!, accountService: assembler.resolve(AccountService.self)!, walletService: service, diff --git a/Adamant/Modules/Wallets/Ethereum/EthApiCore.swift b/Adamant/Modules/Wallets/Ethereum/EthApiCore.swift index 0e29ab563..30f444c79 100644 --- a/Adamant/Modules/Wallets/Ethereum/EthApiCore.swift +++ b/Adamant/Modules/Wallets/Ethereum/EthApiCore.swift @@ -85,7 +85,7 @@ extension EthApiCore: BlockchainHealthCheckableService { height: Int(height), wsEnabled: false, wsPort: nil, - version: extractVersion(from: clientVersion) + version: extractVersion(from: clientVersion).flatMap { .init($0) } )) } } diff --git a/Adamant/Modules/Wallets/Ethereum/EthWalletFactory.swift b/Adamant/Modules/Wallets/Ethereum/EthWalletFactory.swift index 38bef2edb..c1b6a9fc6 100644 --- a/Adamant/Modules/Wallets/Ethereum/EthWalletFactory.swift +++ b/Adamant/Modules/Wallets/Ethereum/EthWalletFactory.swift @@ -19,7 +19,7 @@ struct EthWalletFactory: WalletFactory { func makeWalletVC(service: Service, screensFactory: ScreensFactory) -> WalletViewController { EthWalletViewController( dialogService: assembler.resolve(DialogService.self)!, - currencyInfoService: assembler.resolve(CurrencyInfoService.self)!, + currencyInfoService: assembler.resolve(InfoServiceProtocol.self)!, accountService: assembler.resolve(AccountService.self)!, screensFactory: screensFactory, walletServiceCompose: assembler.resolve(WalletServiceCompose.self)!, @@ -43,7 +43,7 @@ struct EthWalletFactory: WalletFactory { accountsProvider: assembler.resolve(AccountsProvider.self)!, dialogService: assembler.resolve(DialogService.self)!, screensFactory: screensFactory, - currencyInfoService: assembler.resolve(CurrencyInfoService.self)!, + currencyInfoService: assembler.resolve(InfoServiceProtocol.self)!, increaseFeeService: assembler.resolve(IncreaseFeeService.self)!, vibroService: assembler.resolve(VibroService.self)!, walletService: service, @@ -129,7 +129,7 @@ private extension EthWalletFactory { func makeTransactionDetailsVC(service: Service) -> EthTransactionDetailsViewController { EthTransactionDetailsViewController( dialogService: assembler.resolve(DialogService.self)!, - currencyInfo: assembler.resolve(CurrencyInfoService.self)!, + currencyInfo: assembler.resolve(InfoServiceProtocol.self)!, addressBookService: assembler.resolve(AddressBookService.self)!, accountService: assembler.resolve(AccountService.self)!, walletService: service, diff --git a/Adamant/Modules/Wallets/Klayr/KlyApiCore.swift b/Adamant/Modules/Wallets/Klayr/KlyApiCore.swift index faa3e8221..bdf69dc95 100644 --- a/Adamant/Modules/Wallets/Klayr/KlyApiCore.swift +++ b/Adamant/Modules/Wallets/Klayr/KlyApiCore.swift @@ -57,7 +57,7 @@ class KlyApiCore: BlockchainHealthCheckableService { height: model.height ?? .zero, wsEnabled: false, wsPort: nil, - version: model.version + version: .init(model.version) ) } } diff --git a/Adamant/Modules/Wallets/Klayr/KlyServiceApiService.swift b/Adamant/Modules/Wallets/Klayr/KlyServiceApiService.swift index b0495eb4e..5d93aa1e1 100644 --- a/Adamant/Modules/Wallets/Klayr/KlyServiceApiService.swift +++ b/Adamant/Modules/Wallets/Klayr/KlyServiceApiService.swift @@ -25,7 +25,7 @@ final class KlyServiceApiCore: KlyApiCore { height: .init(model.fee.meta.lastBlockHeight), wsEnabled: false, wsPort: nil, - version: model.info.version + version: .init(model.info.version) ) } } diff --git a/Adamant/Modules/Wallets/Klayr/KlyWalletFactory.swift b/Adamant/Modules/Wallets/Klayr/KlyWalletFactory.swift index c1f091c07..29c695aae 100644 --- a/Adamant/Modules/Wallets/Klayr/KlyWalletFactory.swift +++ b/Adamant/Modules/Wallets/Klayr/KlyWalletFactory.swift @@ -20,7 +20,7 @@ struct KlyWalletFactory: WalletFactory { func makeWalletVC(service: Service, screensFactory: ScreensFactory) -> WalletViewController { KlyWalletViewController( dialogService: assembler.resolve(DialogService.self)!, - currencyInfoService: assembler.resolve(CurrencyInfoService.self)!, + currencyInfoService: assembler.resolve(InfoServiceProtocol.self)!, accountService: assembler.resolve(AccountService.self)!, screensFactory: screensFactory, walletServiceCompose: assembler.resolve(WalletServiceCompose.self)!, @@ -44,7 +44,7 @@ struct KlyWalletFactory: WalletFactory { accountsProvider: assembler.resolve(AccountsProvider.self)!, dialogService: assembler.resolve(DialogService.self)!, screensFactory: screensFactory, - currencyInfoService: assembler.resolve(CurrencyInfoService.self)!, + currencyInfoService: assembler.resolve(InfoServiceProtocol.self)!, increaseFeeService: assembler.resolve(IncreaseFeeService.self)!, vibroService: assembler.resolve(VibroService.self)!, walletService: service, @@ -130,7 +130,7 @@ private extension KlyWalletFactory { func makeTransactionDetailsVC(service: Service) -> KlyTransactionDetailsViewController { KlyTransactionDetailsViewController( dialogService: assembler.resolve(DialogService.self)!, - currencyInfo: assembler.resolve(CurrencyInfoService.self)!, + currencyInfo: assembler.resolve(InfoServiceProtocol.self)!, addressBookService: assembler.resolve(AddressBookService.self)!, accountService: assembler.resolve(AccountService.self)!, walletService: service, diff --git a/Adamant/Modules/Wallets/TransactionDetailsViewControllerBase.swift b/Adamant/Modules/Wallets/TransactionDetailsViewControllerBase.swift index 0bda84cb3..11e209efe 100644 --- a/Adamant/Modules/Wallets/TransactionDetailsViewControllerBase.swift +++ b/Adamant/Modules/Wallets/TransactionDetailsViewControllerBase.swift @@ -152,7 +152,7 @@ class TransactionDetailsViewControllerBase: FormViewController { // MARK: - Dependencies let dialogService: DialogService - let currencyInfo: CurrencyInfoService + let currencyInfo: InfoServiceProtocol let addressBookService: AddressBookService let accountService: AccountService let walletService: WalletService? @@ -244,7 +244,7 @@ class TransactionDetailsViewControllerBase: FormViewController { init( dialogService: DialogService, - currencyInfo: CurrencyInfoService, + currencyInfo: InfoServiceProtocol, addressBookService: AddressBookService, accountService: AccountService, walletService: WalletService?, diff --git a/Adamant/Modules/Wallets/TransferViewControllerBase.swift b/Adamant/Modules/Wallets/TransferViewControllerBase.swift index 8360991e4..f35c03f9e 100644 --- a/Adamant/Modules/Wallets/TransferViewControllerBase.swift +++ b/Adamant/Modules/Wallets/TransferViewControllerBase.swift @@ -180,7 +180,7 @@ class TransferViewControllerBase: FormViewController { let accountsProvider: AccountsProvider let dialogService: DialogService let screensFactory: ScreensFactory - let currencyInfoService: CurrencyInfoService + let currencyInfoService: InfoServiceProtocol var increaseFeeService: IncreaseFeeService var chatsProvider: ChatsProvider let vibroService: VibroService @@ -315,7 +315,7 @@ class TransferViewControllerBase: FormViewController { accountsProvider: AccountsProvider, dialogService: DialogService, screensFactory: ScreensFactory, - currencyInfoService: CurrencyInfoService, + currencyInfoService: InfoServiceProtocol, increaseFeeService: IncreaseFeeService, vibroService: VibroService, walletService: WalletService, diff --git a/Adamant/Modules/Wallets/WalletViewControllerBase.swift b/Adamant/Modules/Wallets/WalletViewControllerBase.swift index 3209d6fec..3b254b0a9 100644 --- a/Adamant/Modules/Wallets/WalletViewControllerBase.swift +++ b/Adamant/Modules/Wallets/WalletViewControllerBase.swift @@ -48,7 +48,7 @@ class WalletViewControllerBase: FormViewController, WalletViewController { // MARK: - Dependencies - private let currencyInfoService: CurrencyInfoService + private let currencyInfoService: InfoServiceProtocol private let accountService: AccountService private let walletServiceCompose: WalletServiceCompose @@ -84,7 +84,7 @@ class WalletViewControllerBase: FormViewController, WalletViewController { init( dialogService: DialogService, - currencyInfoService: CurrencyInfoService, + currencyInfoService: InfoServiceProtocol, accountService: AccountService, screensFactory: ScreensFactory, walletServiceCompose: WalletServiceCompose, diff --git a/Adamant/Services/AdamantAccountService.swift b/Adamant/Services/AdamantAccountService.swift index 7b9da2533..be846f2d8 100644 --- a/Adamant/Services/AdamantAccountService.swift +++ b/Adamant/Services/AdamantAccountService.swift @@ -22,7 +22,7 @@ final class AdamantAccountService: AccountService { private let walletServiceCompose: WalletServiceCompose weak var notificationsService: NotificationsService? - weak var currencyInfoService: CurrencyInfoService? + weak var currencyInfoService: InfoServiceProtocol? weak var pushNotificationsTokenService: PushNotificationsTokenService? weak var visibleWalletService: VisibleWalletsService? diff --git a/Adamant/Services/AdamantCurrencyInfoService.swift b/Adamant/Services/AdamantCurrencyInfoService.swift index 87a32fdb2..f70603bfa 100644 --- a/Adamant/Services/AdamantCurrencyInfoService.swift +++ b/Adamant/Services/AdamantCurrencyInfoService.swift @@ -1,5 +1,5 @@ // -// AdamantCurrencyInfoService.swift +// AdamantInfoServiceProtocol.swift // Adamant // // Created by Anton Boyarkin on 23/03/2019. @@ -18,7 +18,7 @@ extension StoreKey { } // MARK: - Service -final class AdamantCurrencyInfoService: CurrencyInfoService { +final class AdamantInfoServiceProtocol: InfoServiceProtocol { // MARK: - API private lazy var infoServiceUrl: URL = { return URL(string: AdamantResources.coinsInfoSrvice)! @@ -115,6 +115,7 @@ final class AdamantCurrencyInfoService: CurrencyInfoService { } private func loadRates(for coins: [String], completion: @escaping (ApiServiceResult<[String: Decimal]>) -> Void) { + print("--debug loadRates start") guard let url = url(for: .get, with: [URLQueryItem(name: "coin", value: coins.joined(separator: ","))]) else { completion(.failure(.internalError(message: "Failed to build URL", error: nil))) return @@ -128,6 +129,7 @@ final class AdamantCurrencyInfoService: CurrencyInfoService { switch response.result { case .success(let data): do { + print("--debug try (success)") let model: CoinInfoServiceResponseGet = try JSONDecoder().decode(CoinInfoServiceResponseGet.self, from: data) if let result = model.result { let nonOptionalResult = result.compactMapValues { $0 } @@ -136,16 +138,19 @@ final class AdamantCurrencyInfoService: CurrencyInfoService { completion(.failure(.serverError(error: "Coin info API result: Parsing error"))) } } catch { + print("--debug loadRates catch error") completion(.failure(.serverError(error: "Coin info API result: Parsing error"))) } case .failure(let error): + print("--debug loadRates failure") completion(.failure(.networkError(error: error))) } } } func getHistory(for coin: String, timestamp: Date, completion: @escaping (ApiServiceResult<[String:Decimal]?>) -> Void) { + print("--debug getHistory start") guard let url = url(for: .history, with: [URLQueryItem(name: "timestamp", value: String(format: "%.0f", timestamp.timeIntervalSince1970)), URLQueryItem(name: "coin", value: coin)]) else { completion(.failure(.internalError(message: "Failed to build URL", error: nil))) return @@ -159,18 +164,22 @@ final class AdamantCurrencyInfoService: CurrencyInfoService { switch response.result { case .success(let data): do { + print("--debug getHistory try (success)") let model: CoinInfoServiceResponseHistory = try JSONDecoder().decode(CoinInfoServiceResponseHistory.self, from: data) - guard let result = model.result?.first, abs(timestamp.timeIntervalSince(result.date)) < AdamantCurrencyInfoService.historyThreshold else { // Разница в датах не должна превышать суток + guard let result = model.result?.first, abs(timestamp.timeIntervalSince(result.date)) < AdamantInfoServiceProtocol.historyThreshold else { // Разница в датах не должна превышать суток + print("--debug Разница в датах не должна превышать суток") completion(.success(nil)) return } completion(.success(result.tickers)) } catch { + print("--debug getHistory catch error") completion(.failure(.serverError(error: "Coin info API result: Parsing error"))) } case .failure(let error): + print("--debug getHistory failure") completion(.failure(.networkError(error: error))) } } diff --git a/Adamant/Services/ApiServiceCompose.swift b/Adamant/Services/ApiServiceCompose.swift index 0ca3ea3ab..5b55c542d 100644 --- a/Adamant/Services/ApiServiceCompose.swift +++ b/Adamant/Services/ApiServiceCompose.swift @@ -51,6 +51,8 @@ private extension ApiServiceCompose { return adm case .ipfs: return ipfs + case .infoService: + return ipfs } } } diff --git a/Adamant/Services/DataProviders/DefaultNodesProvider.swift b/Adamant/Services/DataProviders/DefaultNodesProvider.swift index ea203d8ef..56a3694dd 100644 --- a/Adamant/Services/DataProviders/DefaultNodesProvider.swift +++ b/Adamant/Services/DataProviders/DefaultNodesProvider.swift @@ -35,6 +35,8 @@ private extension DefaultNodesProvider { return AdmWalletService.nodes case .ipfs: return IPFSApiService.nodes + case .infoService: + return IPFSApiService.nodes } } } diff --git a/CommonKit/Sources/CommonKit/AdamantResources.swift b/CommonKit/Sources/CommonKit/AdamantResources.swift index 45b87f7d1..239e56562 100644 --- a/CommonKit/Sources/CommonKit/AdamantResources.swift +++ b/CommonKit/Sources/CommonKit/AdamantResources.swift @@ -9,7 +9,7 @@ import Foundation public enum AdamantResources { - public static let coinsInfoSrvice = "https://info.adamant.im" + public static let coinsInfoSrvice = "https://info2.adm.im" // MARK: ADAMANT Addresses public static let supportEmail = "business@adamant.im" diff --git a/CommonKit/Sources/CommonKit/Helpers/Node+NodeKeychainDTO.swift b/CommonKit/Sources/CommonKit/Helpers/Node+NodeKeychainDTO.swift index 214f3fa83..5a9b98261 100644 --- a/CommonKit/Sources/CommonKit/Helpers/Node+NodeKeychainDTO.swift +++ b/CommonKit/Sources/CommonKit/Helpers/Node+NodeKeychainDTO.swift @@ -12,7 +12,7 @@ public extension Node { altOrigin: altOrigin, wsEnabled: wsEnabled, isEnabled: isEnabled, - version: version, + version: version?.string, height: height, ping: ping, connectionStatus: connectionStatus, @@ -29,7 +29,7 @@ public extension NodeKeychainDTO { wsEnabled: wsEnabled, mainOrigin: mainOrigin, altOrigin: altOrigin, - version: version, + version: version.flatMap { .init($0) }, height: height, ping: ping, connectionStatus: connectionStatus, diff --git a/CommonKit/Sources/CommonKit/Helpers/NodeGroup+Constants.swift b/CommonKit/Sources/CommonKit/Helpers/NodeGroup+Constants.swift index 739e258fb..d4d9bdb73 100644 --- a/CommonKit/Sources/CommonKit/Helpers/NodeGroup+Constants.swift +++ b/CommonKit/Sources/CommonKit/Helpers/NodeGroup+Constants.swift @@ -12,7 +12,7 @@ public extension NodeGroup { switch self { case .adm: return false - case .eth, .doge, .dash, .btc, .klyNode, .klyService, .ipfs: + case .eth, .doge, .dash, .btc, .klyNode, .klyService, .ipfs, .infoService: return true } } diff --git a/CommonKit/Sources/CommonKit/Helpers/Version.swift b/CommonKit/Sources/CommonKit/Helpers/Version.swift index bc8cef5c7..b7a420ab3 100644 --- a/CommonKit/Sources/CommonKit/Helpers/Version.swift +++ b/CommonKit/Sources/CommonKit/Helpers/Version.swift @@ -14,6 +14,8 @@ public struct Version { } public extension Version { + static var zero: Self { .init([.zero]) } + var string: String { versions.map { String($0) }.joined(separator: ".") } diff --git a/CommonKit/Sources/CommonKit/Models/Node/Node.swift b/CommonKit/Sources/CommonKit/Models/Node/Node.swift index a2b6398a8..7ccf18384 100644 --- a/CommonKit/Sources/CommonKit/Models/Node/Node.swift +++ b/CommonKit/Sources/CommonKit/Models/Node/Node.swift @@ -15,7 +15,7 @@ public struct Node: Equatable, Identifiable { public var mainOrigin: NodeOrigin public var altOrigin: NodeOrigin? public var wsEnabled: Bool - public var version: String? + public var version: Version? public var height: Int? public var ping: TimeInterval? public var connectionStatus: NodeConnectionStatus? @@ -29,7 +29,7 @@ public struct Node: Equatable, Identifiable { wsEnabled: Bool, mainOrigin: NodeOrigin, altOrigin: NodeOrigin?, - version: String?, + version: Version?, height: Int?, ping: TimeInterval?, connectionStatus: NodeConnectionStatus?, diff --git a/CommonKit/Sources/CommonKit/Models/Node/NodeGroup.swift b/CommonKit/Sources/CommonKit/Models/Node/NodeGroup.swift index 8d3fe5dae..6435e6e90 100644 --- a/CommonKit/Sources/CommonKit/Models/Node/NodeGroup.swift +++ b/CommonKit/Sources/CommonKit/Models/Node/NodeGroup.swift @@ -14,4 +14,5 @@ public enum NodeGroup: Codable, CaseIterable, Hashable { case dash case adm case ipfs + case infoService } diff --git a/CommonKit/Sources/CommonKit/Models/Node/NodeStatusInfo.swift b/CommonKit/Sources/CommonKit/Models/Node/NodeStatusInfo.swift index d53cfc959..a661d5009 100644 --- a/CommonKit/Sources/CommonKit/Models/Node/NodeStatusInfo.swift +++ b/CommonKit/Sources/CommonKit/Models/Node/NodeStatusInfo.swift @@ -13,14 +13,14 @@ public struct NodeStatusInfo: Equatable { public let height: Int public let wsEnabled: Bool public let wsPort: Int? - public let version: String? + public let version: Version? public init( ping: TimeInterval, height: Int, wsEnabled: Bool, wsPort: Int?, - version: String? + version: Version? ) { self.ping = ping self.height = height diff --git a/CommonKit/Sources/CommonKit/Services/ApiService/AdamantApiCore.swift b/CommonKit/Sources/CommonKit/Services/ApiService/AdamantApiCore.swift index f1f9bffcb..1ada27df5 100644 --- a/CommonKit/Sources/CommonKit/Services/ApiService/AdamantApiCore.swift +++ b/CommonKit/Sources/CommonKit/Services/ApiService/AdamantApiCore.swift @@ -45,7 +45,7 @@ extension AdamantApiCore: BlockchainHealthCheckableService { height: statusDto.network?.height ?? .zero, wsEnabled: statusDto.wsClient?.enabled ?? false, wsPort: statusDto.wsClient?.port, - version: statusDto.version?.version + version: statusDto.version?.version.flatMap { .init($0) } ) } } diff --git a/CommonKit/Sources/CommonKit/Services/HealthCheck/BlockchainHealthCheckWrapper.swift b/CommonKit/Sources/CommonKit/Services/HealthCheck/BlockchainHealthCheckWrapper.swift index 7934415cc..c8f2569ee 100644 --- a/CommonKit/Sources/CommonKit/Services/HealthCheck/BlockchainHealthCheckWrapper.swift +++ b/CommonKit/Sources/CommonKit/Services/HealthCheck/BlockchainHealthCheckWrapper.swift @@ -151,8 +151,7 @@ private extension BlockchainHealthCheckWrapper { node.ping = info.ping guard - let versionString = info.version, - let version = Version(versionString), + let version = info.version, let minNodeVersion = params.minNodeVersion, version < minNodeVersion else { return } @@ -179,8 +178,7 @@ private extension BlockchainHealthCheckWrapper { var status: NodeConnectionStatus? if - let versionString = node.version, - let version = Version(versionString), + let version = node.version, let minNodeVersion = params.minNodeVersion, version < minNodeVersion { From 75b928f2356555cc4dd063dd04099f4515332fd0 Mon Sep 17 00:00:00 2001 From: just-software-dev Date: Sun, 25 Aug 2024 00:32:04 -0300 Subject: [PATCH 041/106] [trello.com/c/sf4UCx9f] Info Service Health Check --- Adamant.xcodeproj/project.pbxproj | 46 ++- Adamant/App/DI/AppAssembly.swift | 12 +- Adamant/App/DI/AppContainer.swift | 5 +- .../InfoService/InfoServiceAssembly.swift | 39 +++ .../DTO/InfoServiceHistoryItemDTO.swift | 2 +- .../DTO/InfoServiceHistoryRequestDTO.swift | 14 + .../DTO/InfoServiceRatesRequestDTO.swift | 13 + .../Models/DTO/InfoServiceStatusDTO.swift | 16 - .../Models/InfoServiceHistoryItem.swift | 3 +- .../Models/InfoServiceTicker.swift | 15 + .../InfoServiceApiServiceProtocol.swift | 8 + .../Protocols/InfoServiceMapperProtocol.swift | 13 +- ...ervice.swift => InfoServiceProtocol.swift} | 12 +- .../InfoServiceApiService+Extension.swift | 61 ++++ .../ApiService/InfoServiceApiService.swift | 25 ++ .../InfoService/Services/InfoService.swift | 95 ++++++ .../Services/InfoServiceApiCore.swift | 16 +- .../Services/InfoServiceApiService.swift | 46 --- .../Services/InfoServiceMapper.swift | 51 ++- ...TransactionDetailsViewControllerBase.swift | 26 +- Adamant/Services/AdamantAccountService.swift | 6 +- .../Services/AdamantCurrencyInfoService.swift | 292 ------------------ .../CommonKit/Helpers/ConcurrencyQueue.swift | 47 +++ 23 files changed, 447 insertions(+), 416 deletions(-) create mode 100644 Adamant/Modules/InfoService/InfoServiceAssembly.swift create mode 100644 Adamant/Modules/InfoService/Models/DTO/InfoServiceHistoryRequestDTO.swift create mode 100644 Adamant/Modules/InfoService/Models/DTO/InfoServiceRatesRequestDTO.swift create mode 100644 Adamant/Modules/InfoService/Models/InfoServiceTicker.swift rename Adamant/Modules/InfoService/Protocols/{CurrencyInfoService.swift => InfoServiceProtocol.swift} (88%) create mode 100644 Adamant/Modules/InfoService/Services/ApiService/InfoServiceApiService+Extension.swift create mode 100644 Adamant/Modules/InfoService/Services/ApiService/InfoServiceApiService.swift create mode 100644 Adamant/Modules/InfoService/Services/InfoService.swift delete mode 100644 Adamant/Modules/InfoService/Services/InfoServiceApiService.swift delete mode 100644 Adamant/Services/AdamantCurrencyInfoService.swift create mode 100644 CommonKit/Sources/CommonKit/Helpers/ConcurrencyQueue.swift diff --git a/Adamant.xcodeproj/project.pbxproj b/Adamant.xcodeproj/project.pbxproj index 51b4245b0..b60a63a50 100644 --- a/Adamant.xcodeproj/project.pbxproj +++ b/Adamant.xcodeproj/project.pbxproj @@ -225,8 +225,7 @@ 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 */; }; - 64EAB37422463E020018D9B2 /* CurrencyInfoService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 64EAB37322463E020018D9B2 /* CurrencyInfoService.swift */; }; - 64EAB37622463F680018D9B2 /* AdamantCurrencyInfoService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 64EAB37522463F680018D9B2 /* AdamantCurrencyInfoService.swift */; }; + 64EAB37422463E020018D9B2 /* InfoServiceProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 64EAB37322463E020018D9B2 /* InfoServiceProtocol.swift */; }; 64F085D920E2D7600006DE68 /* AdmTransactionsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 64F085D820E2D7600006DE68 /* AdmTransactionsViewController.swift */; }; 64FA53CD20E1300B006783C9 /* EthTransactionsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 64FA53CC20E1300A006783C9 /* EthTransactionsViewController.swift */; }; 64FA53D120E24942006783C9 /* TransactionDetailsViewControllerBase.swift in Sources */ = {isa = PBXBuildFile; fileRef = 64FA53D020E24941006783C9 /* TransactionDetailsViewControllerBase.swift */; }; @@ -234,6 +233,12 @@ 9304F8BE292F88F900173F18 /* ANSPayload.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9304F8BD292F88F900173F18 /* ANSPayload.swift */; }; 9304F8C2292F895C00173F18 /* PushNotificationsTokenService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9304F8C1292F895C00173F18 /* PushNotificationsTokenService.swift */; }; 9304F8C4292F8A3100173F18 /* AdamantPushNotificationsTokenService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9304F8C3292F8A3100173F18 /* AdamantPushNotificationsTokenService.swift */; }; + 931224A92C7AA0E4009E0ED0 /* InfoServiceApiService+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 931224A82C7AA0E4009E0ED0 /* InfoServiceApiService+Extension.swift */; }; + 931224AB2C7AA212009E0ED0 /* InfoServiceRatesRequestDTO.swift in Sources */ = {isa = PBXBuildFile; fileRef = 931224AA2C7AA212009E0ED0 /* InfoServiceRatesRequestDTO.swift */; }; + 931224AD2C7AA67B009E0ED0 /* InfoServiceHistoryRequestDTO.swift in Sources */ = {isa = PBXBuildFile; fileRef = 931224AC2C7AA67B009E0ED0 /* InfoServiceHistoryRequestDTO.swift */; }; + 931224AF2C7AA88E009E0ED0 /* InfoService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 931224AE2C7AA88E009E0ED0 /* InfoService.swift */; }; + 931224B12C7ACFE6009E0ED0 /* InfoServiceAssembly.swift in Sources */ = {isa = PBXBuildFile; fileRef = 931224B02C7ACFE6009E0ED0 /* InfoServiceAssembly.swift */; }; + 931224B32C7AD5DD009E0ED0 /* InfoServiceTicker.swift in Sources */ = {isa = PBXBuildFile; fileRef = 931224B22C7AD5DD009E0ED0 /* InfoServiceTicker.swift */; }; 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 */; }; @@ -871,8 +876,7 @@ 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 = ""; }; - 64EAB37322463E020018D9B2 /* CurrencyInfoService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CurrencyInfoService.swift; sourceTree = ""; }; - 64EAB37522463F680018D9B2 /* AdamantCurrencyInfoService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AdamantCurrencyInfoService.swift; sourceTree = ""; }; + 64EAB37322463E020018D9B2 /* InfoServiceProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InfoServiceProtocol.swift; sourceTree = ""; }; 64F085D820E2D7600006DE68 /* AdmTransactionsViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AdmTransactionsViewController.swift; sourceTree = ""; }; 64FA53CC20E1300A006783C9 /* EthTransactionsViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EthTransactionsViewController.swift; sourceTree = ""; }; 64FA53D020E24941006783C9 /* TransactionDetailsViewControllerBase.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TransactionDetailsViewControllerBase.swift; sourceTree = ""; }; @@ -881,6 +885,12 @@ 9304F8BD292F88F900173F18 /* ANSPayload.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ANSPayload.swift; sourceTree = ""; }; 9304F8C1292F895C00173F18 /* PushNotificationsTokenService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PushNotificationsTokenService.swift; sourceTree = ""; }; 9304F8C3292F8A3100173F18 /* AdamantPushNotificationsTokenService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AdamantPushNotificationsTokenService.swift; sourceTree = ""; }; + 931224A82C7AA0E4009E0ED0 /* InfoServiceApiService+Extension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "InfoServiceApiService+Extension.swift"; sourceTree = ""; }; + 931224AA2C7AA212009E0ED0 /* InfoServiceRatesRequestDTO.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InfoServiceRatesRequestDTO.swift; sourceTree = ""; }; + 931224AC2C7AA67B009E0ED0 /* InfoServiceHistoryRequestDTO.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InfoServiceHistoryRequestDTO.swift; sourceTree = ""; }; + 931224AE2C7AA88E009E0ED0 /* InfoService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InfoService.swift; sourceTree = ""; }; + 931224B02C7ACFE6009E0ED0 /* InfoServiceAssembly.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InfoServiceAssembly.swift; sourceTree = ""; }; + 931224B22C7AD5DD009E0ED0 /* InfoServiceTicker.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InfoServiceTicker.swift; sourceTree = ""; }; 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 = ""; }; @@ -1646,6 +1656,15 @@ path = Doge; sourceTree = ""; }; + 931224A72C7A9F9C009E0ED0 /* ApiService */ = { + isa = PBXGroup; + children = ( + 934FD9B92C78565400336841 /* InfoServiceApiService.swift */, + 931224A82C7AA0E4009E0ED0 /* InfoServiceApiService+Extension.swift */, + ); + path = ApiService; + sourceTree = ""; + }; 9322E87C2970435C00B8357C /* Models */ = { isa = PBXGroup; children = ( @@ -1747,6 +1766,7 @@ 934FD99F2C783C9E00336841 /* Models */, 934FD9A12C783CAC00336841 /* Protocols */, 934FD9A02C783CA700336841 /* Services */, + 931224B02C7ACFE6009E0ED0 /* InfoServiceAssembly.swift */, ); path = InfoService; sourceTree = ""; @@ -1760,6 +1780,7 @@ 934FD9AF2C78481500336841 /* InfoServiceApiError.swift */, 934FD9B12C7849C800336841 /* InfoServiceApiResult.swift */, 934FD9B52C78519600336841 /* InfoServiceApiCommands.swift */, + 931224B22C7AD5DD009E0ED0 /* InfoServiceTicker.swift */, ); path = Models; sourceTree = ""; @@ -1767,9 +1788,10 @@ 934FD9A02C783CA700336841 /* Services */ = { isa = PBXGroup; children = ( + 931224A72C7A9F9C009E0ED0 /* ApiService */, 934FD9A72C783E0C00336841 /* InfoServiceMapper.swift */, 934FD9B32C78514E00336841 /* InfoServiceApiCore.swift */, - 934FD9B92C78565400336841 /* InfoServiceApiService.swift */, + 931224AE2C7AA88E009E0ED0 /* InfoService.swift */, ); path = Services; sourceTree = ""; @@ -1777,8 +1799,8 @@ 934FD9A12C783CAC00336841 /* Protocols */ = { isa = PBXGroup; children = ( + 64EAB37322463E020018D9B2 /* InfoServiceProtocol.swift */, 934FD9B72C7854AF00336841 /* InfoServiceMapperProtocol.swift */, - 64EAB37322463E020018D9B2 /* CurrencyInfoService.swift */, 934FD9BB2C78567300336841 /* InfoServiceApiServiceProtocol.swift */, ); path = Protocols; @@ -1790,6 +1812,8 @@ 934FD9A32C783D2E00336841 /* InfoServiceStatusDTO.swift */, 934FD9A92C7842C800336841 /* InfoServiceResponseDTO.swift */, 934FD9AB2C78443600336841 /* InfoServiceHistoryItemDTO.swift */, + 931224AA2C7AA212009E0ED0 /* InfoServiceRatesRequestDTO.swift */, + 931224AC2C7AA67B009E0ED0 /* InfoServiceHistoryRequestDTO.swift */, ); path = DTO; sourceTree = ""; @@ -2180,7 +2204,6 @@ 6455E9F221075D8000B2E94C /* AdamantAddressBookService.swift */, E90A494A204D9EB8009F6A65 /* AdamantAuthentication.swift */, E9E7CDBF2003AF6D00DFC4DB /* AdamantCellFactory.swift */, - 64EAB37522463F680018D9B2 /* AdamantCurrencyInfoService.swift */, E9E7CD8E20026CD300DFC4DB /* AdamantDialogService.swift */, E93D7ABF2052CF63005D19DC /* AdamantNotificationService.swift */, 41047B75294C62710039E956 /* AdamantVisibleWalletsService.swift */, @@ -3238,10 +3261,12 @@ 3A96E37C2AED27F8001F5A52 /* PartnerQRService.swift in Sources */, E950652320404C84008352E5 /* AdamantUriTools.swift in Sources */, 3A41938F2A580C57006A6B22 /* AdamantRichTransactionReactService.swift in Sources */, + 931224A92C7AA0E4009E0ED0 /* InfoServiceApiService+Extension.swift in Sources */, E95F85C7200A9B070070534A /* ChatTableViewCell.swift in Sources */, A50A41082822F8CE006BDFE1 /* BtcWalletService.swift in Sources */, 937736822B0949C500B35C7A /* NodeCell+Model.swift in Sources */, 934FD9AA2C7842C800336841 /* InfoServiceResponseDTO.swift in Sources */, + 931224AD2C7AA67B009E0ED0 /* InfoServiceHistoryRequestDTO.swift in Sources */, 6455E9F121075D3600B2E94C /* AddressBookService.swift in Sources */, 93C794482B0778C700408826 /* DashGetBlockDTO.swift in Sources */, 6449BA6D235CA0930033B936 /* ERC20TransactionsViewController.swift in Sources */, @@ -3322,7 +3347,7 @@ 938F7D642955C94F001915CA /* ChatViewController.swift in Sources */, E9A174B72057F1B3003667CD /* AdamantChatsProvider+backgroundFetch.swift in Sources */, E96BBE3321F71290009AA738 /* BuyAndSellViewController.swift in Sources */, - 64EAB37422463E020018D9B2 /* CurrencyInfoService.swift in Sources */, + 64EAB37422463E020018D9B2 /* InfoServiceProtocol.swift in Sources */, 93CC8DC7296F00D6003772BF /* ChatTransactionContainerView.swift in Sources */, E9E7CDB32002B9FB00DFC4DB /* LoginFactory.swift in Sources */, E941CCDE20E7B70200C96220 /* WalletCollectionViewCell.swift in Sources */, @@ -3404,8 +3429,10 @@ 3AE0A42E2BC6A96B00BF7125 /* IPFS+Constants.swift in Sources */, E9FEECA62143C300007DD7C8 /* EthWalletService+RichMessageProvider.swift in Sources */, 6403F5E622723FDA00D58779 /* DashWalletViewController.swift in Sources */, + 931224AF2C7AA88E009E0ED0 /* InfoService.swift in Sources */, 93C7944E2B077C1F00408826 /* DashSendRawTransactionDTO.swift in Sources */, 4193AE1629FBEFBF002F21BE /* NSAttributedText+Adamant.swift in Sources */, + 931224AB2C7AA212009E0ED0 /* InfoServiceRatesRequestDTO.swift in Sources */, 41A1994829D325800031AD75 /* SwipeableView.swift in Sources */, 4164A9D928F17DA700EEF16D /* AdamantChatTransactionService.swift in Sources */, E993302221354BC300CD5200 /* EthWalletFactory.swift in Sources */, @@ -3466,6 +3493,7 @@ 93294B962AAD320B00911109 /* ScreensFactory.swift in Sources */, 3A9015A72A614A62002A2464 /* AdamantEmojiService.swift in Sources */, 93ADE0722ACA66AF008ED641 /* VibrationSelectionView.swift in Sources */, + 931224B12C7ACFE6009E0ED0 /* InfoServiceAssembly.swift in Sources */, 648C696F22915A12006645F5 /* DashTransaction.swift in Sources */, 3AF53F8F2B3EE0DA00B30312 /* DogeNodeInfo.swift in Sources */, 3A41939A2A5D554A006A6B22 /* Reaction.swift in Sources */, @@ -3475,7 +3503,6 @@ 4186B334294200C5006594A3 /* EthWalletService+DynamicConstants.swift in Sources */, 3A26D93F2C3C1CED003AD832 /* KlyServiceApiService.swift in Sources */, 93CCAE802B06E2D100EA5B94 /* ApiServiceError+Extension.swift in Sources */, - 64EAB37622463F680018D9B2 /* AdamantCurrencyInfoService.swift in Sources */, 93294B842AAD0C8F00911109 /* Assembler+Extension.swift in Sources */, 938F7D5B2955C8DA001915CA /* ChatDisplayManager.swift in Sources */, E9722068201F42CC004F2AAD /* InMemoryCoreDataStack.swift in Sources */, @@ -3526,6 +3553,7 @@ E90847302196FEA80095825D /* ChatTransaction+CoreDataClass.swift in Sources */, 3A96E37A2AED27D7001F5A52 /* AdamantPartnerQRService.swift in Sources */, E9EC342120052ABB00C0E546 /* TransferViewControllerBase.swift in Sources */, + 931224B32C7AD5DD009E0ED0 /* InfoServiceTicker.swift in Sources */, 9304F8C4292F8A3100173F18 /* AdamantPushNotificationsTokenService.swift in Sources */, 9300F94629D0149100FEDDB8 /* RichMessageProviderWithStatusCheck.swift in Sources */, 416380E12A51765F00F90E6D /* ChatReactionsView.swift in Sources */, diff --git a/Adamant/App/DI/AppAssembly.swift b/Adamant/App/DI/AppAssembly.swift index 52d199945..7fce2180b 100644 --- a/Adamant/App/DI/AppAssembly.swift +++ b/Adamant/App/DI/AppAssembly.swift @@ -244,14 +244,14 @@ struct AppAssembly: Assembly { adamantCore: r.resolve(AdamantCore.self)!, dialogService: r.resolve(DialogService.self)!, securedStore: r.resolve(SecuredStore.self)!, - walletServiceCompose: r.resolve(WalletServiceCompose.self)! + walletServiceCompose: r.resolve(WalletServiceCompose.self)!, + currencyInfoService: r.resolve(InfoServiceProtocol.self)! ) }.inObjectScope(.container).initCompleted { (r, c) in Task { @MainActor in guard let service = c as? AdamantAccountService else { return } service.notificationsService = r.resolve(NotificationsService.self)! service.pushNotificationsTokenService = r.resolve(PushNotificationsTokenService.self)! - service.currencyInfoService = r.resolve(InfoServiceProtocol.self)! service.visibleWalletService = r.resolve(VisibleWalletsService.self)! } } @@ -266,14 +266,6 @@ struct AppAssembly: Assembly { ) }.inObjectScope(.container) - // MARK: InfoServiceProtocol - container.register(InfoServiceProtocol.self) { r in - AdamantInfoServiceProtocol( - securedStore: r.resolve(SecuredStore.self)!, - walletServiceCompose: r.resolve(WalletServiceCompose.self)! - ) - }.inObjectScope(.container) - // MARK: LanguageStorageProtocol container.register(LanguageStorageProtocol.self) { _ in LanguageStorageService() diff --git a/Adamant/App/DI/AppContainer.swift b/Adamant/App/DI/AppContainer.swift index 3f8fa7602..d119dbb69 100644 --- a/Adamant/App/DI/AppContainer.swift +++ b/Adamant/App/DI/AppContainer.swift @@ -9,7 +9,10 @@ import Swinject struct AppContainer { - let assembler = Assembler([AppAssembly()]) + let assembler = Assembler([ + AppAssembly(), + InfoServiceAssembly() + ]) func resolve(_ type: T.Type) -> T? { assembler.resolve(T.self) diff --git a/Adamant/Modules/InfoService/InfoServiceAssembly.swift b/Adamant/Modules/InfoService/InfoServiceAssembly.swift new file mode 100644 index 000000000..8d9059440 --- /dev/null +++ b/Adamant/Modules/InfoService/InfoServiceAssembly.swift @@ -0,0 +1,39 @@ +// +// InfoServiceAssembly.swift +// Adamant +// +// Created by Andrew G on 24.08.2024. +// Copyright © 2024 Adamant. All rights reserved. +// + +import Swinject +import CommonKit + +struct InfoServiceAssembly: Assembly { + func assemble(container: Container) { + container.register(InfoServiceProtocol.self) { r in + InfoService( + securedStore: r.resolve(SecuredStore.self)!, + walletServiceCompose: r.resolve(WalletServiceCompose.self)!, + api: r.resolve(InfoServiceApiServiceProtocol.self)! + ) + }.inObjectScope(.container) + + container.register(InfoServiceApiServiceProtocol.self) { r in + InfoServiceApiService(core: .init( + service: .init( + apiCore: r.resolve(APICoreProtocol.self)!, + mapper: r.resolve(InfoServiceMapperProtocol.self)!), + nodesStorage: r.resolve(NodesStorageProtocol.self)!, + nodesAdditionalParamsStorage: r.resolve(NodesAdditionalParamsStorageProtocol.self)!, + isActive: true, + params: NodeGroup.infoService.blockchainHealthCheckParams, + connection: r.resolve(ReachabilityMonitor.self)!.connectionPublisher + )) + }.inObjectScope(.transient) + + container.register(InfoServiceMapperProtocol.self) { _ in + InfoServiceMapper() + }.inObjectScope(.transient) + } +} diff --git a/Adamant/Modules/InfoService/Models/DTO/InfoServiceHistoryItemDTO.swift b/Adamant/Modules/InfoService/Models/DTO/InfoServiceHistoryItemDTO.swift index 192d55860..f7b2adbe0 100644 --- a/Adamant/Modules/InfoService/Models/DTO/InfoServiceHistoryItemDTO.swift +++ b/Adamant/Modules/InfoService/Models/DTO/InfoServiceHistoryItemDTO.swift @@ -9,5 +9,5 @@ struct InfoServiceHistoryItemDTO: Codable { let _id: String let date: Int - let tickers: [String: Double]? + let tickers: [String: String]? } diff --git a/Adamant/Modules/InfoService/Models/DTO/InfoServiceHistoryRequestDTO.swift b/Adamant/Modules/InfoService/Models/DTO/InfoServiceHistoryRequestDTO.swift new file mode 100644 index 000000000..59037c8ef --- /dev/null +++ b/Adamant/Modules/InfoService/Models/DTO/InfoServiceHistoryRequestDTO.swift @@ -0,0 +1,14 @@ +// +// InfoServiceHistoryRequestDTO.swift +// Adamant +// +// Created by Andrew G on 24.08.2024. +// Copyright © 2024 Adamant. All rights reserved. +// + +import Foundation + +struct InfoServiceHistoryRequestDTO: Codable { + let timestamp: String + let coin: String +} diff --git a/Adamant/Modules/InfoService/Models/DTO/InfoServiceRatesRequestDTO.swift b/Adamant/Modules/InfoService/Models/DTO/InfoServiceRatesRequestDTO.swift new file mode 100644 index 000000000..21a31fc95 --- /dev/null +++ b/Adamant/Modules/InfoService/Models/DTO/InfoServiceRatesRequestDTO.swift @@ -0,0 +1,13 @@ +// +// InfoServiceRatesRequestDTO.swift +// Adamant +// +// Created by Andrew G on 24.08.2024. +// Copyright © 2024 Adamant. All rights reserved. +// + +import Foundation + +struct InfoServiceRatesRequestDTO: Codable { + let coin: String +} diff --git a/Adamant/Modules/InfoService/Models/DTO/InfoServiceStatusDTO.swift b/Adamant/Modules/InfoService/Models/DTO/InfoServiceStatusDTO.swift index 8c71d2c2e..821db64af 100644 --- a/Adamant/Modules/InfoService/Models/DTO/InfoServiceStatusDTO.swift +++ b/Adamant/Modules/InfoService/Models/DTO/InfoServiceStatusDTO.swift @@ -15,19 +15,3 @@ struct InfoServiceStatusDTO: Codable { let last_updated: Int? let version: String } - -//{ -// success: boolean, -// // Unix timestamp of the server time in ms -// date: number, -// // Whether Currencyinfo has fetched its first rates -// ready: boolean, -// // Whether Currencyinfo is updating the rates right now -// updating: boolean, -// // Unix timestamp in ms of the next update or the current one when updating -// next_update: number, -// // Unix timestamp of the last update in ms or `null` before the first rates have been fetched -// last_updated: number | null, -// // Currencyinfo version in `x.y.z` format -// version: string -//} diff --git a/Adamant/Modules/InfoService/Models/InfoServiceHistoryItem.swift b/Adamant/Modules/InfoService/Models/InfoServiceHistoryItem.swift index 4349f1808..73c4bc8d9 100644 --- a/Adamant/Modules/InfoService/Models/InfoServiceHistoryItem.swift +++ b/Adamant/Modules/InfoService/Models/InfoServiceHistoryItem.swift @@ -10,6 +10,5 @@ import Foundation struct InfoServiceHistoryItem { let date: Date - let ticker: String - let price: Double + let tickers: [InfoServiceTicker: Decimal] } diff --git a/Adamant/Modules/InfoService/Models/InfoServiceTicker.swift b/Adamant/Modules/InfoService/Models/InfoServiceTicker.swift new file mode 100644 index 000000000..ef5d05425 --- /dev/null +++ b/Adamant/Modules/InfoService/Models/InfoServiceTicker.swift @@ -0,0 +1,15 @@ +// +// InfoServiceTicker.swift +// Adamant +// +// Created by Andrew G on 25.08.2024. +// Copyright © 2024 Adamant. All rights reserved. +// + +import Foundation +import CommonKit + +struct InfoServiceTicker: Hashable { + let crypto: String + let fiat: String +} diff --git a/Adamant/Modules/InfoService/Protocols/InfoServiceApiServiceProtocol.swift b/Adamant/Modules/InfoService/Protocols/InfoServiceApiServiceProtocol.swift index feb083ac3..7e74c1d0e 100644 --- a/Adamant/Modules/InfoService/Protocols/InfoServiceApiServiceProtocol.swift +++ b/Adamant/Modules/InfoService/Protocols/InfoServiceApiServiceProtocol.swift @@ -6,8 +6,16 @@ // Copyright © 2024 Adamant. All rights reserved. // +import Foundation import CommonKit protocol InfoServiceApiServiceProtocol: ApiServiceProtocol { + func loadRates( + coins: [String] + ) async -> InfoServiceApiResult<[InfoServiceTicker: Decimal]> + func getHistory( + coin: String, + date: Date + ) async -> InfoServiceApiResult } diff --git a/Adamant/Modules/InfoService/Protocols/InfoServiceMapperProtocol.swift b/Adamant/Modules/InfoService/Protocols/InfoServiceMapperProtocol.swift index ea66cfa5c..e1164a51e 100644 --- a/Adamant/Modules/InfoService/Protocols/InfoServiceMapperProtocol.swift +++ b/Adamant/Modules/InfoService/Protocols/InfoServiceMapperProtocol.swift @@ -12,9 +12,9 @@ import CommonKit protocol InfoServiceMapperProtocol { func mapToModel(_ dto: InfoServiceStatusDTO) -> InfoServiceStatus - func mapToModel( - _ dto: InfoServiceResponseDTO<[String: Double]> - ) -> InfoServiceApiResult<[String: Double]> + func mapRatesToModel( + _ dto: InfoServiceResponseDTO<[String: String]> + ) -> InfoServiceApiResult<[InfoServiceTicker: Decimal]> func mapToModel( _ dto: InfoServiceResponseDTO<[InfoServiceHistoryItemDTO]> @@ -24,4 +24,11 @@ protocol InfoServiceMapperProtocol { ping: TimeInterval, status: InfoServiceStatus ) -> NodeStatusInfo + + func mapToRatesRequestDTO(_ coins: [String]) -> InfoServiceRatesRequestDTO + + func mapToHistoryRequestDTO( + date: Date, + coin: String + ) -> InfoServiceHistoryRequestDTO } diff --git a/Adamant/Modules/InfoService/Protocols/CurrencyInfoService.swift b/Adamant/Modules/InfoService/Protocols/InfoServiceProtocol.swift similarity index 88% rename from Adamant/Modules/InfoService/Protocols/CurrencyInfoService.swift rename to Adamant/Modules/InfoService/Protocols/InfoServiceProtocol.swift index a31a4c55d..f2e8e0664 100644 --- a/Adamant/Modules/InfoService/Protocols/CurrencyInfoService.swift +++ b/Adamant/Modules/InfoService/Protocols/InfoServiceProtocol.swift @@ -15,6 +15,12 @@ extension Notification.Name { } } +extension StoreKey { + struct CoinInfo { + static let selectedCurrency = "coinInfo.selectedCurrency" + } +} + // MARK: - Currencies enum Currency: String { case RUB = "RUB" @@ -46,12 +52,10 @@ protocol InfoServiceProtocol: AnyObject { // Get rate for pair Crypto / Fiat currencies func getRate(for coin: String) -> Decimal? - func getHistory(for coin: String, timestamp: Date, completion: @escaping (ApiServiceResult<[String:Decimal]?>) -> Void) - func getHistory( for coin: String, - timestamp: Date - ) async throws -> [String: Decimal] + date: Date + ) async -> InfoServiceApiResult<[InfoServiceTicker: Decimal]> } // MARK: - AdamantBalanceFormat fiat formatter diff --git a/Adamant/Modules/InfoService/Services/ApiService/InfoServiceApiService+Extension.swift b/Adamant/Modules/InfoService/Services/ApiService/InfoServiceApiService+Extension.swift new file mode 100644 index 000000000..c0969c4de --- /dev/null +++ b/Adamant/Modules/InfoService/Services/ApiService/InfoServiceApiService+Extension.swift @@ -0,0 +1,61 @@ +// +// InfoServiceApiService+Extension.swift +// Adamant +// +// Created by Andrew G on 24.08.2024. +// Copyright © 2024 Adamant. All rights reserved. +// + +import Foundation +import CommonKit + +extension InfoServiceApiService: ApiServiceProtocol { + var chosenFastestNodeId: UUID? { + core.chosenFastestNodeId + } + + func healthCheck() { + core.healthCheck() + } + + var hasActiveNode: Bool { + !core.sortedAllowedNodes.isEmpty + } +} + +extension InfoServiceApiService: InfoServiceApiServiceProtocol { + func loadRates( + coins: [String] + ) async -> InfoServiceApiResult<[InfoServiceTicker: Decimal]> { + await request { core, origin in + await core.sendRequestJsonResponse( + origin: origin, + path: InfoServiceApiCommands.get, + method: .get, + parameters: mapper.mapToRatesRequestDTO(coins), + encoding: .url + ) + }.flatMap { mapper.mapRatesToModel($0) } + } + + func getHistory( + coin: String, + date: Date + ) async -> InfoServiceApiResult { + await request { core, origin in + await core.sendRequestJsonResponse( + origin: origin, + path: InfoServiceApiCommands.getHistory, + method: .get, + parameters: mapper.mapToHistoryRequestDTO(date: date, coin: coin), + encoding: .url + ) + }.flatMap { mapper.mapToModel($0) } + } +} + +private extension InfoServiceApiService { + var mapper: InfoServiceMapperProtocol { + core.service.mapper + } +} diff --git a/Adamant/Modules/InfoService/Services/ApiService/InfoServiceApiService.swift b/Adamant/Modules/InfoService/Services/ApiService/InfoServiceApiService.swift new file mode 100644 index 000000000..c83110d65 --- /dev/null +++ b/Adamant/Modules/InfoService/Services/ApiService/InfoServiceApiService.swift @@ -0,0 +1,25 @@ +// +// InfoServiceApiService.swift +// Adamant +// +// Created by Andrew G on 23.08.2024. +// Copyright © 2024 Adamant. All rights reserved. +// + +import Foundation +import CommonKit + +struct InfoServiceApiService { + let core: BlockchainHealthCheckWrapper + + func request( + _ request: @Sendable ( + APICoreProtocol, + NodeOrigin + ) async -> ApiServiceResult + ) async -> InfoServiceApiResult { + await core.request { core, origin in + await request(core.apiCore, origin) + }.mapError { .apiError($0) } + } +} diff --git a/Adamant/Modules/InfoService/Services/InfoService.swift b/Adamant/Modules/InfoService/Services/InfoService.swift new file mode 100644 index 000000000..40c6efccb --- /dev/null +++ b/Adamant/Modules/InfoService/Services/InfoService.swift @@ -0,0 +1,95 @@ +// +// InfoService.swift +// Adamant +// +// Created by Andrew G on 24.08.2024. +// Copyright © 2024 Adamant. All rights reserved. +// + +import Combine +import CommonKit +import UIKit + +final class InfoService: InfoServiceProtocol { + private let securedStore: SecuredStore + private let api: InfoServiceApiServiceProtocol + private let rateCoins: [String] + private let queue = ConcurrencyQueue() + + @Atomic private var rates = [InfoServiceTicker: Decimal]() + @Atomic private var currentCurrencyValue: Currency = .default + @Atomic private var subscriptions = Set() + + var currentCurrency: Currency { + get { currentCurrencyValue } + set { updateCurrency(newValue) } + } + + init( + securedStore: SecuredStore, + walletServiceCompose: WalletServiceCompose, + api: InfoServiceApiServiceProtocol + ) { + self.securedStore = securedStore + self.api = api + rateCoins = walletServiceCompose.getWallets().map { $0.core.tokenSymbol } + setupCurrency() + setupObservers() + } + + func update() { + queue.syncAdd { [weak self, rateCoins] in + (try? await self?.api.loadRates(coins: rateCoins).get()).map { + self?.rates = $0 + self?.sendRatesChangedNotification() + } + } + } + + func getRate(for coin: String) -> Decimal? { + rates[.init(crypto: coin, fiat: currentCurrency.rawValue)] + } + + func getHistory( + for coin: String, + date: Date + ) async -> InfoServiceApiResult<[InfoServiceTicker: Decimal]> { + await api.getHistory(coin: coin, date: date).map { $0.tickers } + } +} + +private extension InfoService { + func sendRatesChangedNotification() { + NotificationCenter.default.post( + name: .AdamantCurrencyInfoService.currencyRatesUpdated, + object: nil + ) + } + + func updateCurrency(_ newValue: Currency) { + $currentCurrencyValue.mutate { value in + guard newValue != value else { return } + value = newValue + securedStore.set(value.rawValue, for: StoreKey.CoinInfo.selectedCurrency) + sendRatesChangedNotification() + } + } + + func setupObservers() { + NotificationCenter.default + .publisher(for: UIApplication.didBecomeActiveNotification) + .sink { [weak self] _ in self?.update() } + .store(in: &subscriptions) + } + + func setupCurrency() { + if + let id: String = securedStore.get(StoreKey.CoinInfo.selectedCurrency), + let currency = Currency(rawValue: id) + { + currentCurrency = currency + } else { + currentCurrency = .default + } + } +} diff --git a/Adamant/Modules/InfoService/Services/InfoServiceApiCore.swift b/Adamant/Modules/InfoService/Services/InfoServiceApiCore.swift index 0f539a6c8..754724e78 100644 --- a/Adamant/Modules/InfoService/Services/InfoServiceApiCore.swift +++ b/Adamant/Modules/InfoService/Services/InfoServiceApiCore.swift @@ -9,18 +9,10 @@ import Foundation import CommonKit -final class InfoServiceApiCore { - private let apiCore: APICoreProtocol - private let mapper: InfoServiceMapperProtocol - - init( - apiCore: APICoreProtocol, - mapper: InfoServiceMapperProtocol - ) { - self.apiCore = apiCore - self.mapper = mapper - } - +struct InfoServiceApiCore { + let apiCore: APICoreProtocol + let mapper: InfoServiceMapperProtocol + func getNodeStatus( origin: NodeOrigin ) async -> ApiServiceResult { diff --git a/Adamant/Modules/InfoService/Services/InfoServiceApiService.swift b/Adamant/Modules/InfoService/Services/InfoServiceApiService.swift deleted file mode 100644 index 5611d9126..000000000 --- a/Adamant/Modules/InfoService/Services/InfoServiceApiService.swift +++ /dev/null @@ -1,46 +0,0 @@ -// -// InfoServiceApiService.swift -// Adamant -// -// Created by Andrew G on 23.08.2024. -// Copyright © 2024 Adamant. All rights reserved. -// - -import Foundation -import CommonKit - -public final class InfoServiceApiService { - public let adamantCore: AdamantCore - public let service: BlockchainHealthCheckWrapper - - public init( - healthCheckWrapper: BlockchainHealthCheckWrapper, - adamantCore: AdamantCore - ) { - service = healthCheckWrapper - self.adamantCore = adamantCore - } - - public func request( - _ request: @Sendable (APICoreProtocol, NodeOrigin) async -> ApiServiceResult - ) async -> ApiServiceResult { - await service.request { admApiCore, origin in - await request(admApiCore.apiCore, origin) - } - } -} - -extension InfoServiceApiService: ApiServiceProtocol { - public var chosenFastestNodeId: UUID? { - service.chosenFastestNodeId - } - - public func healthCheck() { - service.healthCheck() - } - - public var hasActiveNode: Bool { - !service.sortedAllowedNodes.isEmpty - } -} - diff --git a/Adamant/Modules/InfoService/Services/InfoServiceMapper.swift b/Adamant/Modules/InfoService/Services/InfoServiceMapper.swift index 996d17541..820fc2569 100644 --- a/Adamant/Modules/InfoService/Services/InfoServiceMapper.swift +++ b/Adamant/Modules/InfoService/Services/InfoServiceMapper.swift @@ -19,10 +19,10 @@ struct InfoServiceMapper: InfoServiceMapperProtocol { ) } - func mapToModel( - _ dto: InfoServiceResponseDTO<[String: Double]> - ) -> InfoServiceApiResult<[String: Double]> { - mapResponseDTO(dto) + func mapRatesToModel( + _ dto: InfoServiceResponseDTO<[String: String]> + ) -> InfoServiceApiResult<[InfoServiceTicker: Decimal]> { + mapResponseDTO(dto).map { mapToTickers($0) } } func mapToModel( @@ -31,14 +31,12 @@ struct InfoServiceMapper: InfoServiceMapperProtocol { mapResponseDTO(dto).flatMap { guard let item = $0.first, - let ticker = item.tickers?.first?.key, - let price = item.tickers?.first?.value + let tickers = item.tickers else { return .failure(.parsingError) } return .success(.init( date: .init(timeIntervalSince1970: .init(milliseconds: item.date)), - ticker: ticker, - price: price + tickers: mapToTickers(tickers) )) } } @@ -55,9 +53,46 @@ struct InfoServiceMapper: InfoServiceMapperProtocol { version: status.version ) } + + func mapToRatesRequestDTO(_ coins: [String]) -> InfoServiceRatesRequestDTO { + .init(coin: coins.joined(separator: ",")) + } + + func mapToHistoryRequestDTO( + date: Date, + coin: String + ) -> InfoServiceHistoryRequestDTO { + .init( + timestamp: .init(format: "%.0f", date.timeIntervalSince1970), + coin: coin + ) + } } private extension InfoServiceMapper { + func mapToTickers(_ rawTickers: [String: String]) -> [InfoServiceTicker: Decimal] { + .init(uniqueKeysWithValues: rawTickers.compactMap { key, value in + guard + let ticker = mapToTicker(key), + let price = Decimal(string: value) + else { return nil } + + return (ticker, price) + }) + } + + func mapToTicker(_ string: String) -> InfoServiceTicker? { + let list: [String] = string.split(separator: "/").map { .init($0) } + + guard + list.count == 2, + let crypto = list.first, + let fiat = list.last + else { return nil } + + return .init(crypto: crypto, fiat: fiat) + } + func mapResponseDTO( _ dto: InfoServiceResponseDTO ) -> InfoServiceApiResult { diff --git a/Adamant/Modules/Wallets/TransactionDetailsViewControllerBase.swift b/Adamant/Modules/Wallets/TransactionDetailsViewControllerBase.swift index 11e209efe..87ec055e6 100644 --- a/Adamant/Modules/Wallets/TransactionDetailsViewControllerBase.swift +++ b/Adamant/Modules/Wallets/TransactionDetailsViewControllerBase.swift @@ -772,14 +772,17 @@ class TransactionDetailsViewControllerBase: FormViewController { Task { var tickers = try await currencyInfo.getHistory( for: currencySymbol, - timestamp: date - ) + date: date + ).get() isFiatSet = true - guard var ticker = tickers["\(currencySymbol)/\(currentFiat)"] else { - return - } + guard + var ticker = tickers[.init( + crypto: currencySymbol, + fiat: currentFiat + )] + else { return } let totalFiat = amount * ticker valueAtTimeTxn = fiatFormatter.string(from: totalFiat) @@ -790,12 +793,15 @@ class TransactionDetailsViewControllerBase: FormViewController { feeCurrencySymbol != currencySymbol { tickers = try await currencyInfo.getHistory( for: feeCurrencySymbol, - timestamp: date - ) + date: date + ).get() - guard let feeTicker = tickers["\(feeCurrencySymbol)/\(currentFiat)"] else { - return - } + guard + let feeTicker = tickers[.init( + crypto: feeCurrencySymbol, + fiat: currentFiat + )] + else { return } ticker = feeTicker } diff --git a/Adamant/Services/AdamantAccountService.swift b/Adamant/Services/AdamantAccountService.swift index be846f2d8..ec16b222a 100644 --- a/Adamant/Services/AdamantAccountService.swift +++ b/Adamant/Services/AdamantAccountService.swift @@ -20,9 +20,9 @@ final class AdamantAccountService: AccountService { private let dialogService: DialogService private let securedStore: SecuredStore private let walletServiceCompose: WalletServiceCompose + private let currencyInfoService: InfoServiceProtocol weak var notificationsService: NotificationsService? - weak var currencyInfoService: InfoServiceProtocol? weak var pushNotificationsTokenService: PushNotificationsTokenService? weak var visibleWalletService: VisibleWalletsService? @@ -42,13 +42,15 @@ final class AdamantAccountService: AccountService { adamantCore: AdamantCore, dialogService: DialogService, securedStore: SecuredStore, - walletServiceCompose: WalletServiceCompose + walletServiceCompose: WalletServiceCompose, + currencyInfoService: InfoServiceProtocol ) { self.apiService = apiService self.adamantCore = adamantCore self.dialogService = dialogService self.securedStore = securedStore self.walletServiceCompose = walletServiceCompose + self.currencyInfoService = currencyInfoService NotificationCenter.default.addObserver(forName: .AdamantAccountService.forceUpdateBalance, object: nil, queue: OperationQueue.main) { [weak self] _ in self?.update() diff --git a/Adamant/Services/AdamantCurrencyInfoService.swift b/Adamant/Services/AdamantCurrencyInfoService.swift deleted file mode 100644 index f70603bfa..000000000 --- a/Adamant/Services/AdamantCurrencyInfoService.swift +++ /dev/null @@ -1,292 +0,0 @@ -// -// AdamantInfoServiceProtocol.swift -// Adamant -// -// Created by Anton Boyarkin on 23/03/2019. -// Copyright © 2019 Adamant. All rights reserved. -// - -import Foundation -import Alamofire -import UIKit -import CommonKit - -extension StoreKey { - struct CoinInfo { - static let selectedCurrency = "coinInfo.selectedCurrency" - } -} - -// MARK: - Service -final class AdamantInfoServiceProtocol: InfoServiceProtocol { - // MARK: - API - private lazy var infoServiceUrl: URL = { - return URL(string: AdamantResources.coinsInfoSrvice)! - }() - - private enum InfoServiceApiCommands: String { - case get = "/get" - case history = "/getHistory" - } - - private func url(for command: InfoServiceApiCommands, with queryItems: [URLQueryItem]? = nil) -> URL? { - guard var components = URLComponents(url: infoServiceUrl, resolvingAgainstBaseURL: false) else { - fatalError("Failed to build InfoService url") - } - - components.path = command.rawValue - components.queryItems = queryItems - - return try? components.asURL() - } - - // MARK: - Properties - private static let historyThreshold = Double(exactly: 60*60*24)! - - private var rateCoins: [String]? - private var rates = [String: Decimal]() - - private let defaultResponseDispatchQueue = DispatchQueue(label: "com.adamant.info-coins-response-queue", qos: .utility, attributes: [.concurrent]) - - public var currentCurrency: Currency = Currency.default { - didSet { - securedStore.set(currentCurrency.rawValue, for: StoreKey.CoinInfo.selectedCurrency) - NotificationCenter.default.post(name: Notification.Name.AdamantCurrencyInfoService.currencyRatesUpdated, object: nil) - } - } - - private var observerActive: NSObjectProtocol? - - // MARK: - Dependencies - - private let securedStore: SecuredStore - private let walletServiceCompose: WalletServiceCompose - - // MARK: - Init - - init(securedStore: SecuredStore, walletServiceCompose: WalletServiceCompose) { - self.securedStore = securedStore - self.walletServiceCompose = walletServiceCompose - rateCoins = walletServiceCompose.getWallets().map { $0.core.tokenSymbol } - addObservers() - setupSecuredStore() - } - - deinit { - removeObservers() - } - - // MARK: - Observers - func addObservers() { - observerActive = NotificationCenter.default.addObserver(forName: UIApplication.didBecomeActiveNotification, object: nil, queue: OperationQueue.main) { [weak self] _ in - self?.update() - } - } - - func removeObservers() { - if let observerActive = observerActive { - NotificationCenter.default.removeObserver(observerActive) - } - } - - // MARK: - Info service - func update() { - guard let coins = rateCoins else { - return - } - - loadRates(for: coins) { [weak self] result in - switch result { - case .success(let rates): - self?.rates = rates - NotificationCenter.default.post(name: Notification.Name.AdamantCurrencyInfoService.currencyRatesUpdated, object: nil) - - case .failure(let error): - print("Fail to load rates from server with error: \(error.localizedDescription)") - } - } - } - - func getRate(for coin: String) -> Decimal? { - let currency = currentCurrency.rawValue - let pair = "\(coin)/\(currency)" - - return rates[pair] - } - - private func loadRates(for coins: [String], completion: @escaping (ApiServiceResult<[String: Decimal]>) -> Void) { - print("--debug loadRates start") - guard let url = url(for: .get, with: [URLQueryItem(name: "coin", value: coins.joined(separator: ","))]) else { - completion(.failure(.internalError(message: "Failed to build URL", error: nil))) - return - } - - let headers: HTTPHeaders = [ - "Content-Type": "application/json" - ] - - AF.request(url, method: .get, headers: headers).responseData(queue: defaultResponseDispatchQueue) { response in - switch response.result { - case .success(let data): - do { - print("--debug try (success)") - let model: CoinInfoServiceResponseGet = try JSONDecoder().decode(CoinInfoServiceResponseGet.self, from: data) - if let result = model.result { - let nonOptionalResult = result.compactMapValues { $0 } - completion(.success(nonOptionalResult)) - } else { - completion(.failure(.serverError(error: "Coin info API result: Parsing error"))) - } - } catch { - print("--debug loadRates catch error") - completion(.failure(.serverError(error: "Coin info API result: Parsing error"))) - } - - case .failure(let error): - print("--debug loadRates failure") - completion(.failure(.networkError(error: error))) - } - } - } - - func getHistory(for coin: String, timestamp: Date, completion: @escaping (ApiServiceResult<[String:Decimal]?>) -> Void) { - print("--debug getHistory start") - guard let url = url(for: .history, with: [URLQueryItem(name: "timestamp", value: String(format: "%.0f", timestamp.timeIntervalSince1970)), URLQueryItem(name: "coin", value: coin)]) else { - completion(.failure(.internalError(message: "Failed to build URL", error: nil))) - return - } - - let headers: HTTPHeaders = [ - "Content-Type": "application/json" - ] - - AF.request(url, method: .get, headers: headers).responseData(queue: defaultResponseDispatchQueue) { response in - switch response.result { - case .success(let data): - do { - print("--debug getHistory try (success)") - let model: CoinInfoServiceResponseHistory = try JSONDecoder().decode(CoinInfoServiceResponseHistory.self, from: data) - guard let result = model.result?.first, abs(timestamp.timeIntervalSince(result.date)) < AdamantInfoServiceProtocol.historyThreshold else { // Разница в датах не должна превышать суток - print("--debug Разница в датах не должна превышать суток") - completion(.success(nil)) - return - } - - completion(.success(result.tickers)) - } catch { - print("--debug getHistory catch error") - completion(.failure(.serverError(error: "Coin info API result: Parsing error"))) - } - - case .failure(let error): - print("--debug getHistory failure") - completion(.failure(.networkError(error: error))) - } - } - } - - func getHistory( - for coin: String, - timestamp: Date - ) async throws -> [String: Decimal] { - try await withUnsafeThrowingContinuation { continuation in - getHistory( - for: coin, - timestamp: timestamp) { completion in - switch completion { - case .success(let result): - continuation.resume(returning: result ?? [:]) - case .failure(let error): - continuation.resume(throwing: error) - } - } - } - } - - private func setupSecuredStore() { - if - let id: String = securedStore.get(StoreKey.CoinInfo.selectedCurrency), - let currency = Currency(rawValue: id) - { - currentCurrency = currency - } else { - currentCurrency = Currency.default - } - } -} - -// MARK: - Server responses -struct CoinInfoServiceResponseGet: Decodable { - enum CodingKeys: String, CodingKey { - case success - case date - case result - } - - let success: Bool - let date: Date - let result: [String: Decimal?]? - - init(from decoder: Decoder) throws { - let container = try decoder.container(keyedBy: CodingKeys.self) - - self.success = try container.decode(Bool.self, forKey: .success) - self.result = try? container.decode([String: Decimal?].self, forKey: .result) - - if let timeInterval = try? container.decode(TimeInterval.self, forKey: .date) { - self.date = Date(timeIntervalSince1970: timeInterval) - } else { - self.date = Date() - } - } -} - -struct CoinInfoServiceResponseHistory: Decodable { - enum CodingKeys: String, CodingKey { - case success - case date - case result - } - - let success: Bool - let date: Date - let result: [CoinInfoServiceHistoryResult]? - - init(from decoder: Decoder) throws { - let container = try decoder.container(keyedBy: CodingKeys.self) - - self.success = try container.decode(Bool.self, forKey: .success) - self.result = try? container.decode([CoinInfoServiceHistoryResult].self, forKey: .result) - - if let timeInterval = try? container.decode(TimeInterval.self, forKey: .date) { - self.date = Date(timeIntervalSince1970: timeInterval) - } else { - self.date = Date() - } - } -} - -struct CoinInfoServiceHistoryResult: Decodable { - enum CodingKeys: String, CodingKey { - case id = "_id" - case date - case tickers - } - - let id: String - let tickers: [String: Decimal]? - let date: Date - - init(from decoder: Decoder) throws { - let container = try decoder.container(keyedBy: CodingKeys.self) - - self.id = try container.decode(String.self, forKey: .id) - self.tickers = try? container.decode([String: Decimal].self, forKey: .tickers) - - if let timeInterval = try? container.decode(TimeInterval.self, forKey: .date) { - self.date = Date(timeIntervalSince1970: timeInterval / 1000) // ms, just because - } else { - self.date = Date() - } - } -} diff --git a/CommonKit/Sources/CommonKit/Helpers/ConcurrencyQueue.swift b/CommonKit/Sources/CommonKit/Helpers/ConcurrencyQueue.swift new file mode 100644 index 000000000..3090d7429 --- /dev/null +++ b/CommonKit/Sources/CommonKit/Helpers/ConcurrencyQueue.swift @@ -0,0 +1,47 @@ +// +// ConcurrencyQueue.swift +// +// +// Created by Andrew G on 24.08.2024. +// + +import Foundation + +public actor ConcurrencyQueue { + private var isPerforming = false + private var actions = [@Sendable () async -> Void]() + + public func add(_ action: @Sendable @escaping () async -> Void) { + actions.append(action) + guard !isPerforming else { return } + isPerforming = true + Task { await perform() } + } + + public init() {} +} + +public extension ConcurrencyQueue { + nonisolated func syncAdd(_ action: @Sendable @escaping () async -> Void) { + let semaphore = DispatchSemaphore(value: .zero) + + Task { + await add(action) + semaphore.signal() + } + + semaphore.wait() + } +} + +private extension ConcurrencyQueue { + func perform() async { + while !actions.isEmpty { + // Now it's O(n). It's possible to achieve O(1). But we will not do it. + // "Premature optimization is the root of all evil." ~ Donald Knuth + await actions.removeFirst()() + } + + isPerforming = false + } +} From 57fc8c1fe5d85785791af56eca71df5c718d83a9 Mon Sep 17 00:00:00 2001 From: Iana Date: Mon, 26 Aug 2024 13:05:00 +0300 Subject: [PATCH 042/106] =?UTF-8?q?[trello.com/c/44KDaoe5]=20Add=20?= =?UTF-8?q?=E2=80=9CIn-app=20notifications=E2=80=9D=20section?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Adamant/App/DI/AppAssembly.swift | 5 ++- .../NotificationSoundsFactory.swift | 9 +++++ .../NotificationSoundsView.swift | 9 +++++ .../NotificationSoundsViewModel.swift | 9 +++++ .../Notifications/NotificationsView.swift | 9 +++++ .../NotificationsViewModel.swift | 15 ++++++++ .../NotificationsService.swift | 7 ++++ Adamant/Services/AdamantDialogService.swift | 35 ++++++++++++++++- .../Services/AdamantNotificationService.swift | 38 +++++++++++++++++++ .../Sources/CommonKit/Core/SecuredStore.swift | 3 ++ 10 files changed, 137 insertions(+), 2 deletions(-) create mode 100644 Adamant/Modules/Settings/Notifications/NotificationSoundsFactory.swift create mode 100644 Adamant/Modules/Settings/Notifications/NotificationSoundsView.swift create mode 100644 Adamant/Modules/Settings/Notifications/NotificationSoundsViewModel.swift diff --git a/Adamant/App/DI/AppAssembly.swift b/Adamant/App/DI/AppAssembly.swift index b116c8375..e7f24dcff 100644 --- a/Adamant/App/DI/AppAssembly.swift +++ b/Adamant/App/DI/AppAssembly.swift @@ -34,7 +34,10 @@ struct AppAssembly: Assembly { // MARK: - Services with dependencies // MARK: DialogService container.register(DialogService.self) { r in - AdamantDialogService(vibroService: r.resolve(VibroService.self)!) + AdamantDialogService( + vibroService: r.resolve(VibroService.self)!, + notificationsService: r.resolve(NotificationsService.self)! + ) }.inObjectScope(.container) // MARK: Notifications diff --git a/Adamant/Modules/Settings/Notifications/NotificationSoundsFactory.swift b/Adamant/Modules/Settings/Notifications/NotificationSoundsFactory.swift new file mode 100644 index 000000000..0739528f8 --- /dev/null +++ b/Adamant/Modules/Settings/Notifications/NotificationSoundsFactory.swift @@ -0,0 +1,9 @@ +// +// NotificationSoundsFactory.swift +// Adamant +// +// Created by Yana Silosieva on 20.08.2024. +// Copyright © 2024 Adamant. All rights reserved. +// + +import Foundation diff --git a/Adamant/Modules/Settings/Notifications/NotificationSoundsView.swift b/Adamant/Modules/Settings/Notifications/NotificationSoundsView.swift new file mode 100644 index 000000000..51a3f84a6 --- /dev/null +++ b/Adamant/Modules/Settings/Notifications/NotificationSoundsView.swift @@ -0,0 +1,9 @@ +// +// NotificationSoundsView.swift +// Adamant +// +// Created by Yana Silosieva on 20.08.2024. +// Copyright © 2024 Adamant. All rights reserved. +// + +import Foundation diff --git a/Adamant/Modules/Settings/Notifications/NotificationSoundsViewModel.swift b/Adamant/Modules/Settings/Notifications/NotificationSoundsViewModel.swift new file mode 100644 index 000000000..fff0d9a5b --- /dev/null +++ b/Adamant/Modules/Settings/Notifications/NotificationSoundsViewModel.swift @@ -0,0 +1,9 @@ +// +// NotificationSoundsViewModel.swift +// Adamant +// +// Created by Yana Silosieva on 20.08.2024. +// Copyright © 2024 Adamant. All rights reserved. +// + +import Foundation diff --git a/Adamant/Modules/Settings/Notifications/NotificationsView.swift b/Adamant/Modules/Settings/Notifications/NotificationsView.swift index 57a56e75a..035f5e23b 100644 --- a/Adamant/Modules/Settings/Notifications/NotificationsView.swift +++ b/Adamant/Modules/Settings/Notifications/NotificationsView.swift @@ -112,16 +112,25 @@ private extension NotificationsView { Text(soundsTitle) } .tint(.init(uiColor: .adamant.active)) + .onChange(of: viewModel.inAppSounds) { value in + viewModel.applyInAppSounds(value: value) + } Toggle(isOn: $viewModel.inAppVibrate) { Text(vibrateTitle) } .tint(.init(uiColor: .adamant.active)) + .onChange(of: viewModel.inAppVibrate) { value in + viewModel.applyInAppVibrate(value: value) + } Toggle(isOn: $viewModel.inAppToasts) { Text(toastsTitle) } .tint(.init(uiColor: .adamant.active)) + .onChange(of: viewModel.inAppToasts) { value in + viewModel.applyInAppToasts(value: value) + } } header: { Text(inAppNotifications) } diff --git a/Adamant/Modules/Settings/Notifications/NotificationsViewModel.swift b/Adamant/Modules/Settings/Notifications/NotificationsViewModel.swift index 2940f04ae..68eef7556 100644 --- a/Adamant/Modules/Settings/Notifications/NotificationsViewModel.swift +++ b/Adamant/Modules/Settings/Notifications/NotificationsViewModel.swift @@ -47,6 +47,9 @@ final class NotificationsViewModel: ObservableObject { notificationsMode = notificationsService.notificationsMode notificationSound = notificationsService.notificationsSound notificationReactionSound = notificationsService.notificationsReactionSound + inAppSounds = notificationsService.inAppSound + inAppVibrate = notificationsService.inAppVibrate + inAppToasts = notificationsService.inAppToasts } func presentNotificationSoundsPicker() { @@ -61,6 +64,18 @@ final class NotificationsViewModel: ObservableObject { openSafariURL = true } + func applyInAppSounds(value: Bool) { + notificationsService.setInAppSound(value) + } + + func applyInAppVibrate(value: Bool) { + notificationsService.setInAppVibrate(value) + } + + func applyInAppToasts(value: Bool) { + notificationsService.setInAppToasts(value) + } + func showAlert() { dialogService.showAlert( title: notificationsTitle, diff --git a/Adamant/ServiceProtocols/NotificationsService.swift b/Adamant/ServiceProtocols/NotificationsService.swift index 28c36be32..e7d09c8b8 100644 --- a/Adamant/ServiceProtocols/NotificationsService.swift +++ b/Adamant/ServiceProtocols/NotificationsService.swift @@ -243,6 +243,13 @@ protocol NotificationsService: AnyObject { var notificationsMode: NotificationsMode { get } var notificationsSound: NotificationSound { get } var notificationsReactionSound: NotificationSound { get } + var inAppSound: Bool { get } + var inAppVibrate: Bool { get } + var inAppToasts: Bool { get } + + func setInAppSound(_ value: Bool) + func setInAppVibrate(_ value: Bool) + func setInAppToasts(_ value: Bool) func setNotificationSound( _ sound: NotificationSound, diff --git a/Adamant/Services/AdamantDialogService.swift b/Adamant/Services/AdamantDialogService.swift index 1da3dfe68..fb5840477 100644 --- a/Adamant/Services/AdamantDialogService.swift +++ b/Adamant/Services/AdamantDialogService.swift @@ -11,19 +11,26 @@ import MessageUI import PopupKit import SafariServices import CommonKit +import AVFoundation @MainActor final class AdamantDialogService: DialogService { // MARK: Dependencies + private let notificationsService: NotificationsService private let vibroService: VibroService private let popupManager = PopupManager() private let mailDelegate = MailDelegate() private weak var window: UIWindow? + private var audioPlayer: AVAudioPlayer? - nonisolated init(vibroService: VibroService) { + nonisolated init( + vibroService: VibroService, + notificationsService: NotificationsService + ) { self.vibroService = vibroService + self.notificationsService = notificationsService } func setup(window: UIWindow) { @@ -207,6 +214,16 @@ extension AdamantDialogService { // MARK: - Notifications extension AdamantDialogService { func showNotification(title: String?, message: String?, image: UIImage?, tapHandler: (() -> Void)?) { + if notificationsService.inAppVibrate { + vibroService.applyVibration(.medium) + } + + if notificationsService.inAppSound { + playSound(by: notificationsService.notificationsSound.fileName) + } + + guard notificationsService.inAppToasts else { return } + popupManager.showNotification( icon: image, title: title, @@ -219,6 +236,22 @@ extension AdamantDialogService { func dismissNotification() { popupManager.dismissNotification() } + + private func playSound(by fileName: String) { + guard let url = Bundle.main.url(forResource: fileName.replacingOccurrences(of: ".mp3", with: ""), withExtension: "mp3") else { + return + } + + do { + try AVAudioSession.sharedInstance().setCategory(.playback) + try AVAudioSession.sharedInstance().setActive(true) + audioPlayer = try AVAudioPlayer(contentsOf: url, fileTypeHint: AVFileType.mp3.rawValue) + audioPlayer?.volume = 1.0 + audioPlayer?.play() + } catch let error as NSError { + print("error: \(error.localizedDescription)") + } + } } // MAKR: - Activity controllers diff --git a/Adamant/Services/AdamantNotificationService.swift b/Adamant/Services/AdamantNotificationService.swift index 9c5b41434..70861714d 100644 --- a/Adamant/Services/AdamantNotificationService.swift +++ b/Adamant/Services/AdamantNotificationService.swift @@ -49,11 +49,18 @@ final class AdamantNotificationsService: NotificationsService { // MARK: Properties private let defaultNotificationsSound: NotificationSound = .inputDefault private let defaultNotificationsReactionSound: NotificationSound = .none + private let defaultInAppSound: Bool = false + private let defaultInAppVibrate: Bool = true + private let defaultInAppToasts: Bool = true private(set) var notificationsMode: NotificationsMode = .disabled private(set) var customBadgeNumber = 0 private(set) var notificationsSound: NotificationSound = .inputDefault private(set) var notificationsReactionSound: NotificationSound = .none + private(set) var inAppSound: Bool = false + private(set) var inAppVibrate: Bool = true + private(set) var inAppToasts: Bool = true + private var isBackgroundSession = false private var backgroundNotifications = 0 private var subscriptions = Set() @@ -104,6 +111,10 @@ final class AdamantNotificationsService: NotificationsService { } } + inAppSound = getValue(for: StoreKey.notificationsService.inAppSounds) ?? defaultInAppSound + inAppVibrate = getValue(for: StoreKey.notificationsService.inAppVibrate) ?? defaultInAppVibrate + inAppToasts = getValue(for: StoreKey.notificationsService.inAppToasts) ?? defaultInAppToasts + preservedBadgeNumber = nil } @@ -113,6 +124,10 @@ final class AdamantNotificationsService: NotificationsService { setNotificationSound(defaultNotificationsReactionSound, for: .reaction) securedStore.remove(StoreKey.notificationsService.notificationsMode) securedStore.remove(StoreKey.notificationsService.notificationsSound) + securedStore.remove(StoreKey.notificationsService.notificationsReactionSound) + securedStore.remove(StoreKey.notificationsService.inAppSounds) + securedStore.remove(StoreKey.notificationsService.inAppVibrate) + securedStore.remove(StoreKey.notificationsService.inAppToasts) preservedBadgeNumber = nil } @@ -124,6 +139,21 @@ final class AdamantNotificationsService: NotificationsService { setBadge(number: nil, force: true) } } + + func setInAppSound(_ value: Bool) { + setValue(for: StoreKey.notificationsService.inAppSounds, value: value) + inAppSound = value + } + + func setInAppVibrate(_ value: Bool) { + setValue(for: StoreKey.notificationsService.inAppVibrate, value: value) + inAppVibrate = value + } + + func setInAppToasts(_ value: Bool) { + setValue(for: StoreKey.notificationsService.inAppToasts, value: value) + inAppToasts = value + } } // MARK: - Notifications Sound { @@ -317,6 +347,14 @@ extension AdamantNotificationsService { UNUserNotificationCenter.current().removeAllDeliveredNotifications() UIApplication.shared.applicationIconBadgeNumber = customBadgeNumber } + + func setValue(for key: String, value: Bool) { + securedStore.set(value, for: key) + } + + func getValue(for key: String) -> T? { + securedStore.get(key) + } } // MARK: - Background batch notifications diff --git a/CommonKit/Sources/CommonKit/Core/SecuredStore.swift b/CommonKit/Sources/CommonKit/Core/SecuredStore.swift index d4d260c87..0dba3f8a1 100644 --- a/CommonKit/Sources/CommonKit/Core/SecuredStore.swift +++ b/CommonKit/Sources/CommonKit/Core/SecuredStore.swift @@ -26,6 +26,9 @@ public extension StoreKey { public static let customBadgeNumber = "notifications.number" public static let notificationsSound = "notifications.sound" public static let notificationsReactionSound = "notifications.reaction.sound" + public static let inAppSounds = "notifications.inAppSounds" + public static let inAppVibrate = "notifications.inAppVibrate" + public static let inAppToasts = "notifications.inAppToasts" } enum visibleWallets { From 45d72f39eb5e6102f450b701a722916e0c595936 Mon Sep 17 00:00:00 2001 From: Iana Date: Mon, 26 Aug 2024 13:13:36 +0300 Subject: [PATCH 043/106] [trello.com/c/44KDaoe5] Rebuild Sounds screen --- Adamant.xcodeproj/project.pbxproj | 12 ++ .../AdamantScreensFactory.swift | 10 +- .../ScreensFactory/ScreensFactory.swift | 2 + .../NotificationSoundsFactory.swift | 31 ++++- .../NotificationSoundsView.swift | 123 +++++++++++++++++- .../NotificationSoundsViewModel.swift | 78 ++++++++++- .../Notifications/NotificationsFactory.swift | 7 +- .../Notifications/NotificationsView.swift | 11 +- 8 files changed, 265 insertions(+), 9 deletions(-) diff --git a/Adamant.xcodeproj/project.pbxproj b/Adamant.xcodeproj/project.pbxproj index 5632eaa46..e59ba04ca 100644 --- a/Adamant.xcodeproj/project.pbxproj +++ b/Adamant.xcodeproj/project.pbxproj @@ -43,6 +43,9 @@ 269B83332C74B4EC002AA1D7 /* slide.mp3 in Resources */ = {isa = PBXBuildFile; fileRef = 269B831C2C74B4EC002AA1D7 /* slide.mp3 */; }; 269B83342C74B4EC002AA1D7 /* welcome.mp3 in Resources */ = {isa = PBXBuildFile; fileRef = 269B831D2C74B4EC002AA1D7 /* welcome.mp3 */; }; 269B83352C74B4EC002AA1D7 /* welcome.mp3 in Resources */ = {isa = PBXBuildFile; fileRef = 269B831D2C74B4EC002AA1D7 /* welcome.mp3 */; }; + 269B83372C74D1F9002AA1D7 /* NotificationSoundsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 269B83362C74D1F9002AA1D7 /* NotificationSoundsView.swift */; }; + 269B833A2C74D4AA002AA1D7 /* NotificationSoundsViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 269B83392C74D4AA002AA1D7 /* NotificationSoundsViewModel.swift */; }; + 269B833D2C74E661002AA1D7 /* NotificationSoundsFactory.swift in Sources */ = {isa = PBXBuildFile; fileRef = 269B833C2C74E661002AA1D7 /* NotificationSoundsFactory.swift */; }; 269E13522B594B2D008D1CA7 /* AccountFooterView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 269E13512B594B2D008D1CA7 /* AccountFooterView.swift */; }; 26A975FF2B7E843E0095C367 /* SelectTextView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 26A975FE2B7E843E0095C367 /* SelectTextView.swift */; }; 26A976012B7E852E0095C367 /* ChatSelectTextViewFactory.swift in Sources */ = {isa = PBXBuildFile; fileRef = 26A976002B7E852E0095C367 /* ChatSelectTextViewFactory.swift */; }; @@ -709,6 +712,9 @@ 269B831B2C74B4EC002AA1D7 /* cheers.mp3 */ = {isa = PBXFileReference; lastKnownFileType = audio.mp3; path = cheers.mp3; sourceTree = ""; }; 269B831C2C74B4EC002AA1D7 /* slide.mp3 */ = {isa = PBXFileReference; lastKnownFileType = audio.mp3; path = slide.mp3; sourceTree = ""; }; 269B831D2C74B4EC002AA1D7 /* welcome.mp3 */ = {isa = PBXFileReference; lastKnownFileType = audio.mp3; path = welcome.mp3; sourceTree = ""; }; + 269B83362C74D1F9002AA1D7 /* NotificationSoundsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationSoundsView.swift; sourceTree = ""; }; + 269B83392C74D4AA002AA1D7 /* NotificationSoundsViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationSoundsViewModel.swift; sourceTree = ""; }; + 269B833C2C74E661002AA1D7 /* NotificationSoundsFactory.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationSoundsFactory.swift; sourceTree = ""; }; 269E13512B594B2D008D1CA7 /* AccountFooterView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccountFooterView.swift; sourceTree = ""; }; 26A975FE2B7E843E0095C367 /* SelectTextView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SelectTextView.swift; sourceTree = ""; }; 26A976002B7E852E0095C367 /* ChatSelectTextViewFactory.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChatSelectTextViewFactory.swift; sourceTree = ""; }; @@ -1374,6 +1380,9 @@ 2621AB382C60E7AE00046D7A /* NotificationsViewModel.swift */, 2621AB3A2C613C8100046D7A /* NotificationsFactory.swift */, 417BA7F328BF894F00DF94C5 /* NotificationSoundsViewController.swift */, + 269B83362C74D1F9002AA1D7 /* NotificationSoundsView.swift */, + 269B83392C74D4AA002AA1D7 /* NotificationSoundsViewModel.swift */, + 269B833C2C74E661002AA1D7 /* NotificationSoundsFactory.swift */, ); path = Notifications; sourceTree = ""; @@ -3196,6 +3205,7 @@ 938A46A42AE6103E00FC03DB /* HealthCheckWrapper.swift in Sources */, 557AC308287B1365004699D7 /* CheckmarkRowView.swift in Sources */, 9390C5052976B53000270CDF /* ChatDialog.swift in Sources */, + 269B833D2C74E661002AA1D7 /* NotificationSoundsFactory.swift in Sources */, 6455E9F321075D8000B2E94C /* AdamantAddressBookService.swift in Sources */, 3A26D9472C3D37B5003AD832 /* KlyWalletViewController.swift in Sources */, 9324C75E297170600022D7EA /* TransactionStatusService.swift in Sources */, @@ -3207,6 +3217,7 @@ 64BD2B7520E2814B00E2CD36 /* EthTransaction.swift in Sources */, 93B28EC22B076D31007F268B /* DashApiService.swift in Sources */, E908472B2196FEA80095825D /* RichMessageTransaction+CoreDataProperties.swift in Sources */, + 269B83372C74D1F9002AA1D7 /* NotificationSoundsView.swift in Sources */, A578BDE52623051C00090141 /* DashWalletService+Transactions.swift in Sources */, 93C794442B07725C00408826 /* DashGetAddressBalanceDTO.swift in Sources */, 6449BA69235CA0930033B936 /* ERC20TransferViewController.swift in Sources */, @@ -3531,6 +3542,7 @@ 9304F8C4292F8A3100173F18 /* AdamantPushNotificationsTokenService.swift in Sources */, 9300F94629D0149100FEDDB8 /* RichMessageProviderWithStatusCheck.swift in Sources */, E9771D9E22997A6F0099AAC7 /* NativeCore+AdamantCore.swift in Sources */, + 269B833A2C74D4AA002AA1D7 /* NotificationSoundsViewModel.swift in Sources */, 416380E12A51765F00F90E6D /* ChatReactionsView.swift in Sources */, 938A46A62AE6106300FC03DB /* BlockchainHealthCheckWrapper.swift in Sources */, E921534E20EE1E8700C0843F /* EurekaAlertLabelRow.swift in Sources */, diff --git a/Adamant/Modules/ScreensFactory/AdamantScreensFactory.swift b/Adamant/Modules/ScreensFactory/AdamantScreensFactory.swift index 4fe000a6c..cf69df37a 100644 --- a/Adamant/Modules/ScreensFactory/AdamantScreensFactory.swift +++ b/Adamant/Modules/ScreensFactory/AdamantScreensFactory.swift @@ -8,9 +8,11 @@ import UIKit import Swinject +import SwiftUI @MainActor struct AdamantScreensFactory: ScreensFactory { + private let walletFactoryCompose: WalletFactoryCompose private let admWalletFactory: AdmWalletFactory private let chatListFactory: ChatListFactory @@ -28,6 +30,7 @@ struct AdamantScreensFactory: ScreensFactory { private let coinsNodesListFactory: CoinsNodesListFactory private let chatSelectTextFactory: ChatSelectTextViewFactory private let notificasionsFactory: NotificationsFactory + private let notificationSounds: NotificationSoundsFactory init(assembler: Assembler) { admWalletFactory = .init(assembler: assembler) @@ -46,6 +49,7 @@ struct AdamantScreensFactory: ScreensFactory { coinsNodesListFactory = .init(parent: assembler) chatSelectTextFactory = .init() notificasionsFactory = .init(parent: assembler) + notificationSounds = .init(parent: assembler) walletFactoryCompose = AdamantWalletFactoryCompose( klyWalletFactory: .init(assembler: assembler), @@ -159,7 +163,11 @@ struct AdamantScreensFactory: ScreensFactory { } func makeNotifications() -> UIViewController { - notificasionsFactory.makeViewController() + notificasionsFactory.makeViewController(screensFactory: self) + } + + func makeNotificationSounds(target: NotificationTarget) -> NotificationSoundsView { + notificationSounds.makeView(target: target) } func makeVisibleWallets() -> UIViewController { diff --git a/Adamant/Modules/ScreensFactory/ScreensFactory.swift b/Adamant/Modules/ScreensFactory/ScreensFactory.swift index d0490b382..eeeb303db 100644 --- a/Adamant/Modules/ScreensFactory/ScreensFactory.swift +++ b/Adamant/Modules/ScreensFactory/ScreensFactory.swift @@ -7,6 +7,7 @@ // import UIKit +import SwiftUI @MainActor protocol ScreensFactory { @@ -62,4 +63,5 @@ protocol ScreensFactory { func makeLogin() -> LoginViewController func makeVibrationSelection() -> UIViewController func makePartnerQR(partner: CoreDataAccount) -> UIViewController + func makeNotificationSounds(target: NotificationTarget) -> NotificationSoundsView } diff --git a/Adamant/Modules/Settings/Notifications/NotificationSoundsFactory.swift b/Adamant/Modules/Settings/Notifications/NotificationSoundsFactory.swift index 0739528f8..6ffc0be5d 100644 --- a/Adamant/Modules/Settings/Notifications/NotificationSoundsFactory.swift +++ b/Adamant/Modules/Settings/Notifications/NotificationSoundsFactory.swift @@ -6,4 +6,33 @@ // Copyright © 2024 Adamant. All rights reserved. // -import Foundation +import Swinject +import SwiftUI + +struct NotificationSoundsFactory { + private let assembler: Assembler + + init(parent: Assembler) { + assembler = .init([NotificationSoundAssembly()], parent: parent) + } + + @MainActor + func makeView(target: NotificationTarget) -> NotificationSoundsView { + let viewModel = assembler.resolve(NotificationSoundsViewModel.self)! + viewModel.setup(notificationTarget: target) + let view = NotificationSoundsView(viewModel: viewModel) + + return view + } +} + +private struct NotificationSoundAssembly: Assembly { + func assemble(container: Container) { + container.register(NotificationSoundsViewModel.self) { r in + NotificationSoundsViewModel( + notificationsService: r.resolve(NotificationsService.self)!, + target: .baseMessage + ) + } + } +} diff --git a/Adamant/Modules/Settings/Notifications/NotificationSoundsView.swift b/Adamant/Modules/Settings/Notifications/NotificationSoundsView.swift index 51a3f84a6..5bacc2bab 100644 --- a/Adamant/Modules/Settings/Notifications/NotificationSoundsView.swift +++ b/Adamant/Modules/Settings/Notifications/NotificationSoundsView.swift @@ -6,4 +6,125 @@ // Copyright © 2024 Adamant. All rights reserved. // -import Foundation +import SwiftUI +import CommonKit + +struct NotificationSoundsView: View { + @ObservedObject var viewModel: NotificationSoundsViewModel + + @Environment(\.dismiss) var dismiss + + var body: some View { + GeometryReader { _ in + Form { + Section { + listSounds() + } header: { + Text(alertHeader) + } + } + .navigationBarTitleDisplayMode(.inline) + .toolbar { + ToolbarItem(placement: .topBarLeading) { + Button(action: { + dismiss() + }, label: { + HStack { + Text(cancelTitle) + } + }) + } + + ToolbarItem(placement: .principal) { + Text(toolbarTitle) + .font(.headline) + .minimumScaleFactor(0.7) + .lineLimit(1) + .frame(maxWidth: .infinity, alignment: .center) + } + + ToolbarItem(placement: .topBarTrailing) { + Button(action: { + viewModel.save() + }, label: { + HStack { + Text(saveTitle) + } + }) + } + } + .onReceive(viewModel.dismissAction) { + dismiss() + } + } + } +} + +private extension NotificationSoundsView { + func toolbar() -> some View { + HStack { + Button(action: { + dismiss() + }, label: { + HStack { + Text(cancelTitle) + } + }) + + Spacer() + + Text(toolbarTitle) + .font(.headline) + .minimumScaleFactor(0.7) + .lineLimit(1) + .frame(maxWidth: .infinity, alignment: .center) + + Spacer() + + Button(action: { + viewModel.save() + }, label: { + HStack { + Text(saveTitle) + } + }) + } + } + + func listSounds() -> some View { + List { + ForEach(viewModel.sounds, id: \.self) { sound in + Button( + action: { viewModel.selectSound(sound) }, + label: { + HStack { + Text(sound.localized) + Spacer() + if viewModel.selectedSound == sound { + Image(systemName: "checkmark") + .foregroundColor(.black) + .frame(width: 30, height: 30) + } + } + } + ) + } + } + } +} + +private var toolbarTitle: String { + .localized("Notifications.Sounds.Name") +} + +private var cancelTitle: String { + .localized("Notifications.Alert.Cancel") +} + +private var saveTitle: String { + .localized("Notifications.Alert.Save") +} + +private var alertHeader: String { + .localized("Notifications.Alert.Tones") +} diff --git a/Adamant/Modules/Settings/Notifications/NotificationSoundsViewModel.swift b/Adamant/Modules/Settings/Notifications/NotificationSoundsViewModel.swift index fff0d9a5b..d6d924b74 100644 --- a/Adamant/Modules/Settings/Notifications/NotificationSoundsViewModel.swift +++ b/Adamant/Modules/Settings/Notifications/NotificationSoundsViewModel.swift @@ -6,4 +6,80 @@ // Copyright © 2024 Adamant. All rights reserved. // -import Foundation +import SwiftUI +import CommonKit +import Combine +import AVFoundation + +@MainActor +final class NotificationSoundsViewModel: ObservableObject { + private let notificationsService: NotificationsService + private var notificationTarget: NotificationTarget + + private(set) var dismissAction = PassthroughSubject() + @Published var isPresented: Bool = false + @Published var selectedSound: NotificationSound = .inputDefault + @Published var sounds: [NotificationSound] = [.none, .noteDefault, .inputDefault, .proud, .relax, .success, .note, .antic, .cheers, .chord, .droplet, .handoff, .milestone, .passage, .portal, .rattle, .rebound, .slide, .welcome] + + private var audioPlayer: AVAudioPlayer? + + nonisolated init( + notificationsService: NotificationsService, + target: NotificationTarget + ) { + self.notificationsService = notificationsService + self.notificationTarget = target + + Task { @MainActor in + switch notificationTarget { + case .baseMessage: + self.selectedSound = notificationsService.notificationsSound + case .reaction: + self.selectedSound = notificationsService.notificationsReactionSound + } + } + } + + func setup(notificationTarget: NotificationTarget) { + self.notificationTarget = notificationTarget + } + + func save() { + setNotificationSound(selectedSound) + dismissAction.send() + } + + func setNotificationSound(_ sound: NotificationSound) { + notificationsService.setNotificationSound(sound, for: notificationTarget) + } + + func selectSound(_ sound: NotificationSound) { + selectedSound = sound + playSound(sound) + } + + func playSound(_ sound: NotificationSound) { + switch sound { + case .none: + break + default: + playSound(by: sound.fileName) + } + } + + private func playSound(by fileName: String) { + guard let url = Bundle.main.url(forResource: fileName.replacingOccurrences(of: ".mp3", with: ""), withExtension: "mp3") else { + return + } + + do { + try AVAudioSession.sharedInstance().setCategory(.playback) + try AVAudioSession.sharedInstance().setActive(true) + audioPlayer = try AVAudioPlayer(contentsOf: url, fileTypeHint: AVFileType.mp3.rawValue) + audioPlayer?.volume = 1.0 + audioPlayer?.play() + } catch let error as NSError { + print("error: \(error.localizedDescription)") + } + } +} diff --git a/Adamant/Modules/Settings/Notifications/NotificationsFactory.swift b/Adamant/Modules/Settings/Notifications/NotificationsFactory.swift index 8f2064322..e84405452 100644 --- a/Adamant/Modules/Settings/Notifications/NotificationsFactory.swift +++ b/Adamant/Modules/Settings/Notifications/NotificationsFactory.swift @@ -17,10 +17,13 @@ struct NotificationsFactory { } @MainActor - func makeViewController() -> UIViewController { + func makeViewController(screensFactory: ScreensFactory) -> UIViewController { let viewModel = assembler.resolve(NotificationsViewModel.self)! - let view = NotificationsView(viewModel: viewModel) + let view = NotificationsView( + viewModel: viewModel, + screensFactory: screensFactory + ) return UIHostingController( rootView: view diff --git a/Adamant/Modules/Settings/Notifications/NotificationsView.swift b/Adamant/Modules/Settings/Notifications/NotificationsView.swift index 035f5e23b..b3ef406a9 100644 --- a/Adamant/Modules/Settings/Notifications/NotificationsView.swift +++ b/Adamant/Modules/Settings/Notifications/NotificationsView.swift @@ -11,7 +11,8 @@ import CommonKit struct NotificationsView: View { @ObservedObject var viewModel: NotificationsViewModel - + var screensFactory: ScreensFactory + var body: some View { GeometryReader { geometry in Form { @@ -29,10 +30,14 @@ struct NotificationsView: View { } } .sheet(isPresented: $viewModel.presentSoundsPicker, content: { - NotificationSoundsPickerView(notificationService: viewModel.notificationsService, target: .baseMessage) + NavigationView(content: { + screensFactory.makeNotificationSounds(target: .baseMessage) + }) }) .sheet(isPresented: $viewModel.presentReactionSoundsPicker, content: { - NotificationSoundsPickerView(notificationService: viewModel.notificationsService, target: .reaction) + NavigationView(content: { + screensFactory.makeNotificationSounds(target: .reaction) + }) }) .fullScreenCover(isPresented: $viewModel.openSafariURL) { SafariWebView(url: viewModel.safariURL).ignoresSafeArea() From eaeaf78aaecff8ad64663c42754ab5471689cbd5 Mon Sep 17 00:00:00 2001 From: StanislavDevIOS Date: Wed, 28 Aug 2024 16:21:32 +0300 Subject: [PATCH 044/106] [dev/trello.com/c/pOZzbfOy] fix: wallets scroll drop --- .../Account/AccountViewController.swift | 90 ++++++++++++------- .../Account/WalletCollectionViewCell.swift | 29 +++++- .../Modules/Account/WalletPagingItem.swift | 51 +++++++---- .../ComplexTransferViewController.swift | 18 ++-- .../Modules/Wallets/Adamant/AdmWallet.swift | 4 +- .../Wallets/Adamant/AdmWalletService.swift | 8 +- .../Modules/Wallets/Bitcoin/BtcWallet.swift | 8 +- .../Wallets/Bitcoin/BtcWalletService.swift | 6 +- Adamant/Modules/Wallets/Dash/DashWallet.swift | 8 +- .../Wallets/Dash/DashWalletService.swift | 1 + Adamant/Modules/Wallets/Doge/DogeWallet.swift | 10 ++- .../Wallets/Doge/DogeWalletService.swift | 6 +- .../Modules/Wallets/ERC20/ERC20Wallet.swift | 9 +- .../Wallets/ERC20/ERC20WalletService.swift | 7 +- .../Modules/Wallets/Ethereum/EthWallet.swift | 9 +- .../Wallets/Ethereum/EthWalletService.swift | 12 ++- Adamant/Modules/Wallets/Klayr/KlyWallet.swift | 3 + .../WalletService/KlyWalletService.swift | 1 + Adamant/Modules/Wallets/WalletAccount.swift | 1 + 19 files changed, 204 insertions(+), 77 deletions(-) diff --git a/Adamant/Modules/Account/AccountViewController.swift b/Adamant/Modules/Account/AccountViewController.swift index 0ff1df2f8..d1ee89851 100644 --- a/Adamant/Modules/Account/AccountViewController.swift +++ b/Adamant/Modules/Account/AccountViewController.swift @@ -169,7 +169,12 @@ final class AccountViewController: FormViewController { private var initiated = false - private var walletViewControllers = [WalletViewController]() + private var walletViewControllers: [WalletViewController] = [] { + didSet { + makeWalletModels() + } + } + private var notificationsSet: Set = [] // MARK: StayIn @@ -197,6 +202,8 @@ final class AccountViewController: FormViewController { return refreshControl }() + private var walletModels: [String: WalletItemModel] = [:] + // MARK: - Init init( @@ -276,12 +283,11 @@ final class AccountViewController: FormViewController { let footerView = AccountFooterView(frame: CGRect(x: .zero, y: .zero, width: self.view.frame.width, height: 100)) tableView.tableFooterView = footerView - // MARK: Wallet pages setupWalletsVC() pagingViewController = PagingViewController() - pagingViewController.register(UINib(nibName: "WalletCollectionViewCell", bundle: nil), for: WalletPagingItem.self) + pagingViewController.register(UINib(nibName: "WalletCollectionViewCell", bundle: nil), for: WalletItemModel.self) pagingViewController.menuItemSize = .fixed(width: 110, height: 110) pagingViewController.indicatorColor = UIColor.adamant.primary pagingViewController.indicatorOptions = .visible(height: 2, zIndex: Int.max, spacing: UIEdgeInsets.zero, insets: UIEdgeInsets.zero) @@ -302,8 +308,18 @@ final class AccountViewController: FormViewController { pagingViewController.borderColor = UIColor.clear - let callback: ((Notification) -> Void) = { [weak self] _ in - self?.pagingViewController.reloadData() + let callback: ((Notification) -> Void) = { [weak self] data in + guard let account = data.userInfo?[AdamantUserInfoKey.WalletService.wallet] as? WalletAccount + else { + return + } + + var model = self?.walletModels[account.unicId]?.model + model?.balance = account.balance + model?.isBalanceInitialized = account.isBalanceInitialized + model?.notifications = account.notifications + + self?.walletModels[account.unicId]?.model = model ?? .default } for walletService in walletServiceCompose.getWallets() { @@ -1094,35 +1110,10 @@ extension AccountViewController: PagingViewControllerDataSource, PagingViewContr func pagingViewController(_: PagingViewController, pagingItemAt index: Int) -> PagingItem { guard let service = walletViewControllers[index].service?.core else { - return WalletPagingItem( - index: index, - currencySymbol: "", - currencyImage: .asset(named: "adamant_wallet") ?? .init(), - isBalanceInitialized: false) - } - - var network = "" - if ERC20Token.supportedTokens.contains(where: { token in - return token.symbol == service.tokenSymbol - }) { - network = type(of: service).tokenNetworkSymbol - } - - let item = WalletPagingItem( - index: index, - currencySymbol: service.tokenSymbol, - currencyImage: service.tokenLogo, - isBalanceInitialized: service.wallet?.isBalanceInitialized, - currencyNetwork: network) - - if let wallet = service.wallet { - item.balance = wallet.balance - item.notifications = wallet.notifications - } else { - item.balance = nil + return WalletItemModel(model: .default) } - return item + return walletModels[service.tokenUnicID] ?? WalletItemModel(model: .default) } func pagingViewController(_ pagingViewController: PagingViewController, didScrollToItem pagingItem: PagingItem, startingViewController: UIViewController?, destinationViewController: UIViewController, transitionSuccessful: Bool) { @@ -1179,3 +1170,38 @@ extension AccountViewController: WalletViewControllerDelegate { } } } + +private extension AccountViewController { + func makeWalletModels() { + for (index, wallet) in walletViewControllers.enumerated() { + guard let service = wallet.service?.core else { + continue + } + + var network = "" + if ERC20Token.supportedTokens.contains(where: { token in + return token.symbol == service.tokenSymbol + }) { + network = type(of: service).tokenNetworkSymbol + } + + var item = WalletItem( + index: index, + currencySymbol: service.tokenSymbol, + currencyImage: service.tokenLogo, + isBalanceInitialized: service.wallet?.isBalanceInitialized, + currencyNetwork: network + ) + + if let wallet = service.wallet { + item.balance = wallet.balance + item.notifications = wallet.notifications + } else { + item.balance = nil + } + + let model = WalletItemModel(model: item) + walletModels[service.tokenUnicID] = model + } + } +} diff --git a/Adamant/Modules/Account/WalletCollectionViewCell.swift b/Adamant/Modules/Account/WalletCollectionViewCell.swift index 5505a01b4..c96b44244 100644 --- a/Adamant/Modules/Account/WalletCollectionViewCell.swift +++ b/Adamant/Modules/Account/WalletCollectionViewCell.swift @@ -10,6 +10,7 @@ import UIKit import FreakingSimpleRoundImageView import Parchment import CommonKit +import Combine class WalletCollectionViewCell: PagingCell { @IBOutlet weak var currencyImageView: UIImageView! @@ -17,11 +18,35 @@ class WalletCollectionViewCell: PagingCell { @IBOutlet weak var currencySymbolLabel: UILabel! @IBOutlet weak var accessoryContainerView: AccessoryContainerView! - override func setPagingItem(_ pagingItem: PagingItem, selected: Bool, options: PagingOptions) { - guard let item = pagingItem as? WalletPagingItem else { + private var cancellables = Set() + + override func prepareForReuse() { + cancellables.removeAll() + } + + override func setPagingItem( + _ pagingItem: PagingItem, + selected: Bool, + options: PagingOptions + ) { + guard let item = pagingItem as? WalletItemModel else { return } + update(item: item.model) + + cancellables.removeAll() + item.$model + .removeDuplicates() + .sink { [weak self] item in + self?.update(item: item) + } + .store(in: &cancellables) + } +} + +private extension WalletCollectionViewCell { + func update(item: WalletItem) { currencyImageView.image = item.currencyImage if item.currencyNetwork.isEmpty { currencySymbolLabel.text = item.currencySymbol diff --git a/Adamant/Modules/Account/WalletPagingItem.swift b/Adamant/Modules/Account/WalletPagingItem.swift index 0106fabbc..c77101c67 100644 --- a/Adamant/Modules/Account/WalletPagingItem.swift +++ b/Adamant/Modules/Account/WalletPagingItem.swift @@ -8,14 +8,15 @@ import UIKit import Parchment +import CommonKit +import Combine -class WalletPagingItem: PagingItem, Hashable, Comparable { - let index: Int - let currencySymbol: String - let currencyImage: UIImage - let currencyNetwork: String - let isBalanceInitialized: Bool - +struct WalletItem: Equatable { + var index: Int + var currencySymbol: String + var currencyImage: UIImage + var currencyNetwork: String + var isBalanceInitialized: Bool var balance: Decimal? var notifications: Int = 0 @@ -24,31 +25,45 @@ class WalletPagingItem: PagingItem, Hashable, Comparable { currencySymbol symbol: String, currencyImage image: UIImage, isBalanceInitialized: Bool?, - currencyNetwork network: String = "" + currencyNetwork network: String = .empty, + balance: Decimal? = nil ) { self.index = index self.isBalanceInitialized = isBalanceInitialized ?? false self.currencySymbol = symbol self.currencyImage = image self.currencyNetwork = network + self.balance = balance } - // MARK: Hashable, Comparable - var hashValue: Int { - return index.hashValue &+ currencySymbol.hashValue + static let `default` = Self( + index: .zero, + currencySymbol: .empty, + currencyImage: .init(), + isBalanceInitialized: nil + ) +} + +final class WalletItemModel: ObservableObject, PagingItem, Hashable, Comparable { + @Published var model: WalletItem = .default + + init(model: WalletItem) { + self.model = model } + // MARK: Hashable, Comparable + func hash(into hasher: inout Hasher) { - hasher.combine(index) - hasher.combine(currencySymbol) + hasher.combine(model.index) + hasher.combine(model.currencySymbol) } - static func < (lhs: WalletPagingItem, rhs: WalletPagingItem) -> Bool { - return lhs.index < rhs.index + static func < (lhs: WalletItemModel, rhs: WalletItemModel) -> Bool { + lhs.model.index < rhs.model.index } - static func == (lhs: WalletPagingItem, rhs: WalletPagingItem) -> Bool { - return lhs.index == rhs.index && - lhs.currencySymbol == rhs.currencySymbol + static func == (lhs: WalletItemModel, rhs: WalletItemModel) -> Bool { + lhs.model.index == rhs.model.index && + lhs.model.currencySymbol == rhs.model.currencySymbol } } diff --git a/Adamant/Modules/ChatsList/ComplexTransferViewController.swift b/Adamant/Modules/ChatsList/ComplexTransferViewController.swift index 1eda62f01..0dd5e28db 100644 --- a/Adamant/Modules/ChatsList/ComplexTransferViewController.swift +++ b/Adamant/Modules/ChatsList/ComplexTransferViewController.swift @@ -68,7 +68,7 @@ final class ComplexTransferViewController: UIViewController { // MARK: PagingViewController pagingViewController = PagingViewController() - pagingViewController.register(UINib(nibName: "WalletCollectionViewCell", bundle: nil), for: WalletPagingItem.self) + pagingViewController.register(UINib(nibName: "WalletCollectionViewCell", bundle: nil), for: WalletItemModel.self) pagingViewController.menuItemSize = .fixed(width: 110, height: 114) pagingViewController.indicatorColor = UIColor.adamant.primary pagingViewController.indicatorOptions = .visible(height: 2, zIndex: Int.max, spacing: UIEdgeInsets.zero, insets: UIEdgeInsets.zero) @@ -201,11 +201,7 @@ extension ComplexTransferViewController: PagingViewControllerDataSource { let service = services[index].core guard let wallet = service.wallet else { - return WalletPagingItem( - index: index, - currencySymbol: "", - currencyImage: .asset(named: "adamant_wallet") ?? .init(), - isBalanceInitialized: false) + return WalletItemModel(model: .default) } var network = "" @@ -215,16 +211,16 @@ extension ComplexTransferViewController: PagingViewControllerDataSource { network = type(of: service).tokenNetworkSymbol } - let item = WalletPagingItem( + let item = WalletItem( index: index, currencySymbol: service.tokenSymbol, currencyImage: service.tokenLogo, isBalanceInitialized: wallet.isBalanceInitialized, - currencyNetwork: network) - - item.balance = wallet.balance + currencyNetwork: network, + balance: wallet.balance + ) - return item + return WalletItemModel(model: item) } } diff --git a/Adamant/Modules/Wallets/Adamant/AdmWallet.swift b/Adamant/Modules/Wallets/Adamant/AdmWallet.swift index 804a31084..980334f83 100644 --- a/Adamant/Modules/Wallets/Adamant/AdmWallet.swift +++ b/Adamant/Modules/Wallets/Adamant/AdmWallet.swift @@ -9,6 +9,7 @@ import Foundation final class AdmWallet: WalletAccount { + var unicId: String let address: String var balance: Decimal = 0 var notifications: Int = 0 @@ -16,7 +17,8 @@ final class AdmWallet: WalletAccount { var minAmount: Decimal = 0 var isBalanceInitialized: Bool = false - init(address: String) { + init(unicId: String, address: String) { + self.unicId = unicId self.address = address } } diff --git a/Adamant/Modules/Wallets/Adamant/AdmWalletService.swift b/Adamant/Modules/Wallets/Adamant/AdmWalletService.swift index 04784d370..c02b8fb98 100644 --- a/Adamant/Modules/Wallets/Adamant/AdmWalletService.swift +++ b/Adamant/Modules/Wallets/Adamant/AdmWalletService.swift @@ -156,7 +156,7 @@ final class AdmWalletService: NSObject, WalletCoreProtocol { } wallet.isBalanceInitialized = true } else { - let wallet = AdmWallet(address: account.address) + let wallet = AdmWallet(unicId: tokenUnicID, address: account.address) wallet.isBalanceInitialized = true wallet.balance = account.balance @@ -184,7 +184,11 @@ final class AdmWalletService: NSObject, WalletCoreProtocol { } private func postUpdateNotification(with wallet: WalletAccount) { - NotificationCenter.default.post(name: walletUpdatedNotification, object: self, userInfo: [AdamantUserInfoKey.WalletService.wallet: wallet]) + NotificationCenter.default.post( + name: walletUpdatedNotification, + object: self, + userInfo: [AdamantUserInfoKey.WalletService.wallet: wallet] + ) } func getWalletAddress(byAdamantAddress address: String) async throws -> String { diff --git a/Adamant/Modules/Wallets/Bitcoin/BtcWallet.swift b/Adamant/Modules/Wallets/Bitcoin/BtcWallet.swift index 09314e21d..1dffa72fc 100644 --- a/Adamant/Modules/Wallets/Bitcoin/BtcWallet.swift +++ b/Adamant/Modules/Wallets/Bitcoin/BtcWallet.swift @@ -10,6 +10,7 @@ import Foundation import BitcoinKit final class BtcWallet: WalletAccount { + var unicId: String let addressEntity: Address let privateKey: PrivateKey let publicKey: PublicKey @@ -21,7 +22,12 @@ final class BtcWallet: WalletAccount { var address: String { addressEntity.stringValue } - init(privateKey: PrivateKey, addressConverter: AddressConverter) throws { + init( + unicId: String, + privateKey: PrivateKey, + addressConverter: AddressConverter + ) throws { + self.unicId = unicId self.privateKey = privateKey self.publicKey = privateKey.publicKey() self.addressEntity = try addressConverter.convert(publicKey: publicKey, type: .p2pkh) diff --git a/Adamant/Modules/Wallets/Bitcoin/BtcWalletService.swift b/Adamant/Modules/Wallets/Bitcoin/BtcWalletService.swift index 2792f8b5b..acc49aa81 100644 --- a/Adamant/Modules/Wallets/Bitcoin/BtcWalletService.swift +++ b/Adamant/Modules/Wallets/Bitcoin/BtcWalletService.swift @@ -429,7 +429,11 @@ extension BtcWalletService { let privateKeyData = passphrase.data(using: .utf8)!.sha256() let privateKey = PrivateKey(data: privateKeyData, network: self.network, isPublicKeyCompressed: true) - let eWallet = try BtcWallet(privateKey: privateKey, addressConverter: addressConverter) + let eWallet = try BtcWallet( + unicId: tokenUnicID, + privateKey: privateKey, + addressConverter: addressConverter + ) self.btcWallet = eWallet NotificationCenter.default.post( diff --git a/Adamant/Modules/Wallets/Dash/DashWallet.swift b/Adamant/Modules/Wallets/Dash/DashWallet.swift index 77876b482..c442ad02a 100644 --- a/Adamant/Modules/Wallets/Dash/DashWallet.swift +++ b/Adamant/Modules/Wallets/Dash/DashWallet.swift @@ -10,6 +10,7 @@ import Foundation import BitcoinKit final class DashWallet: WalletAccount { + var unicId: String let addressEntity: Address let privateKey: PrivateKey let publicKey: PublicKey @@ -21,7 +22,12 @@ final class DashWallet: WalletAccount { var address: String { addressEntity.stringValue } - init(privateKey: PrivateKey, addressConverter: AddressConverter) throws { + init( + unicId: String, + privateKey: PrivateKey, + addressConverter: AddressConverter + ) throws { + self.unicId = unicId self.privateKey = privateKey self.publicKey = privateKey.publicKey() diff --git a/Adamant/Modules/Wallets/Dash/DashWalletService.swift b/Adamant/Modules/Wallets/Dash/DashWalletService.swift index 695fa2c32..a93e434fc 100644 --- a/Adamant/Modules/Wallets/Dash/DashWalletService.swift +++ b/Adamant/Modules/Wallets/Dash/DashWalletService.swift @@ -308,6 +308,7 @@ extension DashWalletService { let privateKey = PrivateKey(data: privateKeyData, network: self.network, isPublicKeyCompressed: true) let eWallet = try DashWallet( + unicId: tokenUnicID, privateKey: privateKey, addressConverter: addressConverter ) diff --git a/Adamant/Modules/Wallets/Doge/DogeWallet.swift b/Adamant/Modules/Wallets/Doge/DogeWallet.swift index 20451d1f5..c70b4a1ff 100644 --- a/Adamant/Modules/Wallets/Doge/DogeWallet.swift +++ b/Adamant/Modules/Wallets/Doge/DogeWallet.swift @@ -10,6 +10,7 @@ import Foundation import BitcoinKit final class DogeWallet: WalletAccount { + var unicId: String let addressEntity: Address let privateKey: PrivateKey let publicKey: PublicKey @@ -21,18 +22,25 @@ final class DogeWallet: WalletAccount { var address: String { addressEntity.stringValue } - init(privateKey: PrivateKey, addressConverter: AddressConverter) throws { + init( + unicId: String, + privateKey: PrivateKey, + addressConverter: AddressConverter + ) throws { + self.unicId = unicId self.privateKey = privateKey self.publicKey = privateKey.publicKey() self.addressEntity = try addressConverter.convert(publicKey: publicKey, type: .p2pkh) } init( + unicId: String, privateKey: PrivateKey, balance: Decimal, notifications: Int, addressConverter: AddressConverter ) throws { + self.unicId = unicId self.privateKey = privateKey self.balance = balance self.notifications = notifications diff --git a/Adamant/Modules/Wallets/Doge/DogeWalletService.swift b/Adamant/Modules/Wallets/Doge/DogeWalletService.swift index 062706875..dd6c10b2f 100644 --- a/Adamant/Modules/Wallets/Doge/DogeWalletService.swift +++ b/Adamant/Modules/Wallets/Doge/DogeWalletService.swift @@ -299,7 +299,11 @@ extension DogeWalletService { let privateKeyData = passphrase.data(using: .utf8)!.sha256() let privateKey = PrivateKey(data: privateKeyData, network: self.network, isPublicKeyCompressed: true) - let eWallet = try DogeWallet(privateKey: privateKey, addressConverter: addressConverter) + let eWallet = try DogeWallet( + unicId: tokenUnicID, + privateKey: privateKey, + addressConverter: addressConverter + ) self.dogeWallet = eWallet NotificationCenter.default.post( diff --git a/Adamant/Modules/Wallets/ERC20/ERC20Wallet.swift b/Adamant/Modules/Wallets/ERC20/ERC20Wallet.swift index d0eed70f5..8413a62f2 100644 --- a/Adamant/Modules/Wallets/ERC20/ERC20Wallet.swift +++ b/Adamant/Modules/Wallets/ERC20/ERC20Wallet.swift @@ -11,6 +11,7 @@ import web3swift import Web3Core final class ERC20Wallet: WalletAccount { + var unicId: String let address: String let ethAddress: EthereumAddress let keystore: BIP32Keystore @@ -21,7 +22,13 @@ final class ERC20Wallet: WalletAccount { var minAmount: Decimal = 0 var isBalanceInitialized: Bool = false - init(address: String, ethAddress: EthereumAddress, keystore: BIP32Keystore) { + init( + unicId: String, + address: String, + ethAddress: EthereumAddress, + keystore: BIP32Keystore + ) { + self.unicId = unicId self.address = address self.ethAddress = ethAddress self.keystore = keystore diff --git a/Adamant/Modules/Wallets/ERC20/ERC20WalletService.swift b/Adamant/Modules/Wallets/ERC20/ERC20WalletService.swift index 88e73bec2..819470da1 100644 --- a/Adamant/Modules/Wallets/ERC20/ERC20WalletService.swift +++ b/Adamant/Modules/Wallets/ERC20/ERC20WalletService.swift @@ -375,7 +375,12 @@ extension ERC20WalletService { } // MARK: 3. Update - let eWallet = EthWallet(address: ethAddress.address, ethAddress: ethAddress, keystore: keystore) + let eWallet = EthWallet( + unicId: tokenUnicID, + address: ethAddress.address, + ethAddress: ethAddress, + keystore: keystore + ) ethWallet = eWallet if !enabled { diff --git a/Adamant/Modules/Wallets/Ethereum/EthWallet.swift b/Adamant/Modules/Wallets/Ethereum/EthWallet.swift index d279d07fc..0f4ac11b9 100644 --- a/Adamant/Modules/Wallets/Ethereum/EthWallet.swift +++ b/Adamant/Modules/Wallets/Ethereum/EthWallet.swift @@ -11,6 +11,7 @@ import web3swift import Web3Core final class EthWallet: WalletAccount { + var unicId: String let address: String let ethAddress: EthereumAddress let keystore: BIP32Keystore @@ -21,7 +22,13 @@ final class EthWallet: WalletAccount { var minAmount: Decimal = 0 var isBalanceInitialized: Bool = false - init(address: String, ethAddress: EthereumAddress, keystore: BIP32Keystore) { + init( + unicId: String, + address: String, + ethAddress: EthereumAddress, + keystore: BIP32Keystore + ) { + self.unicId = unicId self.address = address self.ethAddress = ethAddress self.keystore = keystore diff --git a/Adamant/Modules/Wallets/Ethereum/EthWalletService.swift b/Adamant/Modules/Wallets/Ethereum/EthWalletService.swift index fb14484ae..af1ef2542 100644 --- a/Adamant/Modules/Wallets/Ethereum/EthWalletService.swift +++ b/Adamant/Modules/Wallets/Ethereum/EthWalletService.swift @@ -18,13 +18,19 @@ import CommonKit struct EthWalletStorage { let keystore: BIP32Keystore - + let unicId: String + func getWallet() -> EthWallet? { guard let ethAddress = keystore.addresses?.first else { return nil } - return EthWallet(address: ethAddress.address, ethAddress: ethAddress, keystore: keystore) + return EthWallet( + unicId: unicId, + address: ethAddress.address, + ethAddress: ethAddress, + keystore: keystore + ) } } @@ -387,7 +393,7 @@ extension EthWalletService { throw WalletServiceError.internalError(message: "ETH Wallet: failed to create Keystore", error: nil) } - walletStorage = .init(keystore: store) + walletStorage = .init(keystore: store, unicId: tokenUnicID) await ethApiService.setKeystoreManager(.init([store])) } catch { throw WalletServiceError.internalError(message: "ETH Wallet: failed to create Keystore", error: error) diff --git a/Adamant/Modules/Wallets/Klayr/KlyWallet.swift b/Adamant/Modules/Wallets/Klayr/KlyWallet.swift index 939e2255b..90b565938 100644 --- a/Adamant/Modules/Wallets/Klayr/KlyWallet.swift +++ b/Adamant/Modules/Wallets/Klayr/KlyWallet.swift @@ -11,6 +11,7 @@ import CommonKit import LiskKit final class KlyWallet: WalletAccount { + var unicId: String let legacyAddress: String let kly32Address: String let keyPair: KeyPair @@ -33,11 +34,13 @@ final class KlyWallet: WalletAccount { } init( + unicId: String, address: String, keyPair: KeyPair, nonce: UInt64, isNewApi: Bool ) { + self.unicId = unicId self.legacyAddress = address self.keyPair = keyPair self.isNewApi = isNewApi diff --git a/Adamant/Modules/Wallets/Klayr/WalletService/KlyWalletService.swift b/Adamant/Modules/Wallets/Klayr/WalletService/KlyWalletService.swift index 82ccbcbca..299809507 100644 --- a/Adamant/Modules/Wallets/Klayr/WalletService/KlyWalletService.swift +++ b/Adamant/Modules/Wallets/Klayr/WalletService/KlyWalletService.swift @@ -395,6 +395,7 @@ private extension KlyWalletService { let address = LiskKit.Crypto.address(fromPublicKey: keyPair.publicKeyString) let wallet = KlyWallet( + unicId: tokenUnicID, address: address, keyPair: keyPair, nonce: .zero, diff --git a/Adamant/Modules/Wallets/WalletAccount.swift b/Adamant/Modules/Wallets/WalletAccount.swift index 923537513..4c1de3708 100644 --- a/Adamant/Modules/Wallets/WalletAccount.swift +++ b/Adamant/Modules/Wallets/WalletAccount.swift @@ -11,6 +11,7 @@ import Foundation // MARK: - Wallet Account protocol WalletAccount { // MARK: Account + var unicId: String { get } var address: String { get } var balance: Decimal { get } var isBalanceInitialized: Bool { get } From c915287df78536189bf5df104273d9cf3edc63e8 Mon Sep 17 00:00:00 2001 From: just-software-dev Date: Thu, 29 Aug 2024 00:04:01 -0300 Subject: [PATCH 045/106] [trello.com/c/sf4UCx9f] Fixes --- Adamant/Modules/InfoService/InfoServiceAssembly.swift | 7 +++++++ .../Models/DTO/InfoServiceHistoryItemDTO.swift | 4 +++- .../Protocols/InfoServiceMapperProtocol.swift | 2 +- .../InfoService/Services/InfoServiceMapper.swift | 11 +++-------- .../Services/DataProviders/DefaultNodesProvider.swift | 2 +- CommonKit/Sources/CommonKit/AdamantResources.swift | 2 +- 6 files changed, 16 insertions(+), 12 deletions(-) diff --git a/Adamant/Modules/InfoService/InfoServiceAssembly.swift b/Adamant/Modules/InfoService/InfoServiceAssembly.swift index 8d9059440..b2c655ea0 100644 --- a/Adamant/Modules/InfoService/InfoServiceAssembly.swift +++ b/Adamant/Modules/InfoService/InfoServiceAssembly.swift @@ -35,5 +35,12 @@ struct InfoServiceAssembly: Assembly { container.register(InfoServiceMapperProtocol.self) { _ in InfoServiceMapper() }.inObjectScope(.transient) + + container.register(InfoServiceApiCore.self) { r in + InfoServiceApiCore( + apiCore: r.resolve(APICoreProtocol.self)!, + mapper: r.resolve(InfoServiceMapperProtocol.self)! + ) + }.inObjectScope(.transient) } } diff --git a/Adamant/Modules/InfoService/Models/DTO/InfoServiceHistoryItemDTO.swift b/Adamant/Modules/InfoService/Models/DTO/InfoServiceHistoryItemDTO.swift index f7b2adbe0..e95231bfc 100644 --- a/Adamant/Modules/InfoService/Models/DTO/InfoServiceHistoryItemDTO.swift +++ b/Adamant/Modules/InfoService/Models/DTO/InfoServiceHistoryItemDTO.swift @@ -6,8 +6,10 @@ // Copyright © 2024 Adamant. All rights reserved. // +import Foundation + struct InfoServiceHistoryItemDTO: Codable { let _id: String let date: Int - let tickers: [String: String]? + let tickers: [String: Decimal]? } diff --git a/Adamant/Modules/InfoService/Protocols/InfoServiceMapperProtocol.swift b/Adamant/Modules/InfoService/Protocols/InfoServiceMapperProtocol.swift index e1164a51e..4798e951a 100644 --- a/Adamant/Modules/InfoService/Protocols/InfoServiceMapperProtocol.swift +++ b/Adamant/Modules/InfoService/Protocols/InfoServiceMapperProtocol.swift @@ -13,7 +13,7 @@ protocol InfoServiceMapperProtocol { func mapToModel(_ dto: InfoServiceStatusDTO) -> InfoServiceStatus func mapRatesToModel( - _ dto: InfoServiceResponseDTO<[String: String]> + _ dto: InfoServiceResponseDTO<[String: Decimal]> ) -> InfoServiceApiResult<[InfoServiceTicker: Decimal]> func mapToModel( diff --git a/Adamant/Modules/InfoService/Services/InfoServiceMapper.swift b/Adamant/Modules/InfoService/Services/InfoServiceMapper.swift index 820fc2569..2c800fd79 100644 --- a/Adamant/Modules/InfoService/Services/InfoServiceMapper.swift +++ b/Adamant/Modules/InfoService/Services/InfoServiceMapper.swift @@ -20,7 +20,7 @@ struct InfoServiceMapper: InfoServiceMapperProtocol { } func mapRatesToModel( - _ dto: InfoServiceResponseDTO<[String: String]> + _ dto: InfoServiceResponseDTO<[String: Decimal]> ) -> InfoServiceApiResult<[InfoServiceTicker: Decimal]> { mapResponseDTO(dto).map { mapToTickers($0) } } @@ -70,14 +70,9 @@ struct InfoServiceMapper: InfoServiceMapperProtocol { } private extension InfoServiceMapper { - func mapToTickers(_ rawTickers: [String: String]) -> [InfoServiceTicker: Decimal] { + func mapToTickers(_ rawTickers: [String: Decimal]) -> [InfoServiceTicker: Decimal] { .init(uniqueKeysWithValues: rawTickers.compactMap { key, value in - guard - let ticker = mapToTicker(key), - let price = Decimal(string: value) - else { return nil } - - return (ticker, price) + mapToTicker(key).map { ($0, value) } }) } diff --git a/Adamant/Services/DataProviders/DefaultNodesProvider.swift b/Adamant/Services/DataProviders/DefaultNodesProvider.swift index 56a3694dd..60329ef71 100644 --- a/Adamant/Services/DataProviders/DefaultNodesProvider.swift +++ b/Adamant/Services/DataProviders/DefaultNodesProvider.swift @@ -36,7 +36,7 @@ private extension DefaultNodesProvider { case .ipfs: return IPFSApiService.nodes case .infoService: - return IPFSApiService.nodes + return [.makeDefaultNode(url: .init(string: AdamantResources.coinsInfoService)!)] } } } diff --git a/CommonKit/Sources/CommonKit/AdamantResources.swift b/CommonKit/Sources/CommonKit/AdamantResources.swift index 239e56562..aab5ae711 100644 --- a/CommonKit/Sources/CommonKit/AdamantResources.swift +++ b/CommonKit/Sources/CommonKit/AdamantResources.swift @@ -9,7 +9,7 @@ import Foundation public enum AdamantResources { - public static let coinsInfoSrvice = "https://info2.adm.im" + public static let coinsInfoService = "https://info2.adm.im" // MARK: ADAMANT Addresses public static let supportEmail = "business@adamant.im" From 63c2ff2274dd3d5bead2e29e1bcd311657420a34 Mon Sep 17 00:00:00 2001 From: just-software-dev Date: Thu, 29 Aug 2024 02:55:01 -0300 Subject: [PATCH 046/106] [trello.com/c/ySzOI0KG] Version UI fixes --- Adamant/Helpers/Node+UI.swift | 20 ++++++++----------- Adamant/Helpers/NodeGroup+Constants.swift | 9 --------- .../ViewModel/CoinsNodesListMapper.swift | 15 +++----------- .../Wallets/Bitcoin/BtcApiService.swift | 2 +- .../Modules/Wallets/Dash/DashApiService.swift | 2 +- .../Modules/Wallets/Doge/DogeApiService.swift | 2 +- .../Modules/Wallets/Ethereum/EthApiCore.swift | 2 +- .../Modules/Wallets/Klayr/KlyApiCore.swift | 2 +- .../Wallets/Klayr/KlyServiceApiService.swift | 2 +- .../Helpers/Node+NodeKeychainDTO.swift | 4 ++-- .../Sources/CommonKit/Helpers/Version.swift | 6 +++++- .../Sources/CommonKit/Models/Node/Node.swift | 4 ++-- .../Models/Node/NodeStatusInfo.swift | 4 ++-- .../Services/ApiService/AdamantApiCore.swift | 2 +- .../BlockchainHealthCheckWrapper.swift | 6 ++---- 15 files changed, 31 insertions(+), 51 deletions(-) diff --git a/Adamant/Helpers/Node+UI.swift b/Adamant/Helpers/Node+UI.swift index dca5de942..095713cb7 100644 --- a/Adamant/Helpers/Node+UI.swift +++ b/Adamant/Helpers/Node+UI.swift @@ -10,14 +10,14 @@ import CommonKit import UIKit extension Node { - func statusString(showVersion: Bool, includeVersionTitle: Bool = true) -> String? { + func statusString(showVersion: Bool) -> String? { guard isEnabled else { return Strings.disabled } switch connectionStatus { case .allowed: return [ pingString, - showVersion ? versionString(includeVersionTitle: includeVersionTitle) : nil, + showVersion ? versionString : nil, heightString ] .compactMap { $0 } @@ -25,7 +25,7 @@ extension Node { case .synchronizing: return [ Strings.synchronizing, - showVersion ? versionString(includeVersionTitle: includeVersionTitle) : nil, + showVersion ? versionString : nil, heightString ] .compactMap { $0 } @@ -35,7 +35,7 @@ extension Node { case .notAllowed(let reason): return [ reason.text, - version + version?.string ] .compactMap { $0 } .joined(separator: " ") @@ -130,6 +130,10 @@ private extension Node { height.map { " ❐ \(getFormattedHeight(from: $0))" } } + var versionString: String? { + version.map { "(v\($0.string))" } + } + var numberFormatter: NumberFormatter { let numberFormatter = NumberFormatter() numberFormatter.numberStyle = .decimal @@ -140,12 +144,4 @@ private extension Node { func getFormattedHeight(from height: Int) -> String { numberFormatter.string(from: Decimal(height)) ?? String(height) } - - func versionString(includeVersionTitle: Bool) -> String? { - guard includeVersionTitle else { - return version.map { "(\($0))" } - } - - return version.map { "(v\($0))" } - } } diff --git a/Adamant/Helpers/NodeGroup+Constants.swift b/Adamant/Helpers/NodeGroup+Constants.swift index a0e02e6fa..12763ced3 100644 --- a/Adamant/Helpers/NodeGroup+Constants.swift +++ b/Adamant/Helpers/NodeGroup+Constants.swift @@ -139,15 +139,6 @@ extension NodeGroup { } } - var includeVersionTitle: Bool { - switch self { - case .btc, .klyNode, .klyService, .doge, .adm: - return true - case .eth, .dash, .ipfs: - return false - } - } - var blockchainHealthCheckParams: BlockchainHealthCheckParams { .init( group: self, diff --git a/Adamant/Modules/CoinsNodesList/ViewModel/CoinsNodesListMapper.swift b/Adamant/Modules/CoinsNodesList/ViewModel/CoinsNodesListMapper.swift index 5bdc02a4a..b2e9a1ad2 100644 --- a/Adamant/Modules/CoinsNodesList/ViewModel/CoinsNodesListMapper.swift +++ b/Adamant/Modules/CoinsNodesList/ViewModel/CoinsNodesListMapper.swift @@ -33,12 +33,7 @@ private extension CoinsNodesListMapper { id: group, title: group.name, rows: nodes.map { - map( - node: $0, - group: group, - isRest: restNodeIds.contains($0.id), - includeVersionTitle: group.includeVersionTitle - ) + map(node: $0, group: group, isRest: restNodeIds.contains($0.id)) } ) } @@ -46,8 +41,7 @@ private extension CoinsNodesListMapper { func map( node: Node, group: NodeGroup, - isRest: Bool, - includeVersionTitle: Bool + isRest: Bool ) -> CoinsNodesListState.Section.Row { let indicatorString = node.indicatorString(isRest: isRest, isWs: false) var indicatorAttrString = AttributedString(stringLiteral: indicatorString) @@ -59,10 +53,7 @@ private extension CoinsNodesListMapper { isEnabled: node.isEnabled, title: node.asString(), connectionStatus: indicatorAttrString, - description: node.statusString( - showVersion: true, - includeVersionTitle: includeVersionTitle - ) ?? .empty + description: node.statusString(showVersion: true) ?? .empty ) } } diff --git a/Adamant/Modules/Wallets/Bitcoin/BtcApiService.swift b/Adamant/Modules/Wallets/Bitcoin/BtcApiService.swift index f0b2631ce..2a82c0431 100644 --- a/Adamant/Modules/Wallets/Bitcoin/BtcApiService.swift +++ b/Adamant/Modules/Wallets/Bitcoin/BtcApiService.swift @@ -59,7 +59,7 @@ final class BtcApiCore: BlockchainHealthCheckableService { height: blockchainInfo.blocks, wsEnabled: false, wsPort: nil, - version: String(networkInfo.version) + version: .init([networkInfo.version]) )) } } diff --git a/Adamant/Modules/Wallets/Dash/DashApiService.swift b/Adamant/Modules/Wallets/Dash/DashApiService.swift index 0f084f491..59ce6fd80 100644 --- a/Adamant/Modules/Wallets/Dash/DashApiService.swift +++ b/Adamant/Modules/Wallets/Dash/DashApiService.swift @@ -59,7 +59,7 @@ final class DashApiCore: BlockchainHealthCheckableService { height: blockchainInfo.blocks, wsEnabled: false, wsPort: nil, - version: networkInfo.buildversion + version: .init(networkInfo.buildversion) )) } } diff --git a/Adamant/Modules/Wallets/Doge/DogeApiService.swift b/Adamant/Modules/Wallets/Doge/DogeApiService.swift index 04a194da5..5ed75818c 100644 --- a/Adamant/Modules/Wallets/Doge/DogeApiService.swift +++ b/Adamant/Modules/Wallets/Doge/DogeApiService.swift @@ -39,7 +39,7 @@ final class DogeApiCore: BlockchainHealthCheckableService { height: data.info.blocks, wsEnabled: false, wsPort: nil, - version: "\(data.info.version)" + version: .init([data.info.version]) ) } } diff --git a/Adamant/Modules/Wallets/Ethereum/EthApiCore.swift b/Adamant/Modules/Wallets/Ethereum/EthApiCore.swift index 0e29ab563..30f444c79 100644 --- a/Adamant/Modules/Wallets/Ethereum/EthApiCore.swift +++ b/Adamant/Modules/Wallets/Ethereum/EthApiCore.swift @@ -85,7 +85,7 @@ extension EthApiCore: BlockchainHealthCheckableService { height: Int(height), wsEnabled: false, wsPort: nil, - version: extractVersion(from: clientVersion) + version: extractVersion(from: clientVersion).flatMap { .init($0) } )) } } diff --git a/Adamant/Modules/Wallets/Klayr/KlyApiCore.swift b/Adamant/Modules/Wallets/Klayr/KlyApiCore.swift index faa3e8221..bdf69dc95 100644 --- a/Adamant/Modules/Wallets/Klayr/KlyApiCore.swift +++ b/Adamant/Modules/Wallets/Klayr/KlyApiCore.swift @@ -57,7 +57,7 @@ class KlyApiCore: BlockchainHealthCheckableService { height: model.height ?? .zero, wsEnabled: false, wsPort: nil, - version: model.version + version: .init(model.version) ) } } diff --git a/Adamant/Modules/Wallets/Klayr/KlyServiceApiService.swift b/Adamant/Modules/Wallets/Klayr/KlyServiceApiService.swift index b0495eb4e..5d93aa1e1 100644 --- a/Adamant/Modules/Wallets/Klayr/KlyServiceApiService.swift +++ b/Adamant/Modules/Wallets/Klayr/KlyServiceApiService.swift @@ -25,7 +25,7 @@ final class KlyServiceApiCore: KlyApiCore { height: .init(model.fee.meta.lastBlockHeight), wsEnabled: false, wsPort: nil, - version: model.info.version + version: .init(model.info.version) ) } } diff --git a/CommonKit/Sources/CommonKit/Helpers/Node+NodeKeychainDTO.swift b/CommonKit/Sources/CommonKit/Helpers/Node+NodeKeychainDTO.swift index 214f3fa83..5a9b98261 100644 --- a/CommonKit/Sources/CommonKit/Helpers/Node+NodeKeychainDTO.swift +++ b/CommonKit/Sources/CommonKit/Helpers/Node+NodeKeychainDTO.swift @@ -12,7 +12,7 @@ public extension Node { altOrigin: altOrigin, wsEnabled: wsEnabled, isEnabled: isEnabled, - version: version, + version: version?.string, height: height, ping: ping, connectionStatus: connectionStatus, @@ -29,7 +29,7 @@ public extension NodeKeychainDTO { wsEnabled: wsEnabled, mainOrigin: mainOrigin, altOrigin: altOrigin, - version: version, + version: version.flatMap { .init($0) }, height: height, ping: ping, connectionStatus: connectionStatus, diff --git a/CommonKit/Sources/CommonKit/Helpers/Version.swift b/CommonKit/Sources/CommonKit/Helpers/Version.swift index bc8cef5c7..39a55e973 100644 --- a/CommonKit/Sources/CommonKit/Helpers/Version.swift +++ b/CommonKit/Sources/CommonKit/Helpers/Version.swift @@ -19,7 +19,11 @@ public extension Version { } init?(_ string: String) { - let versions = string.split(separator: ".").compactMap { Int($0) } + let versions = string + .filter { $0.isNumber || $0 == "." } + .split(separator: ".") + .compactMap { Int($0) } + guard !versions.isEmpty else { return nil } self.versions = versions } diff --git a/CommonKit/Sources/CommonKit/Models/Node/Node.swift b/CommonKit/Sources/CommonKit/Models/Node/Node.swift index a2b6398a8..7ccf18384 100644 --- a/CommonKit/Sources/CommonKit/Models/Node/Node.swift +++ b/CommonKit/Sources/CommonKit/Models/Node/Node.swift @@ -15,7 +15,7 @@ public struct Node: Equatable, Identifiable { public var mainOrigin: NodeOrigin public var altOrigin: NodeOrigin? public var wsEnabled: Bool - public var version: String? + public var version: Version? public var height: Int? public var ping: TimeInterval? public var connectionStatus: NodeConnectionStatus? @@ -29,7 +29,7 @@ public struct Node: Equatable, Identifiable { wsEnabled: Bool, mainOrigin: NodeOrigin, altOrigin: NodeOrigin?, - version: String?, + version: Version?, height: Int?, ping: TimeInterval?, connectionStatus: NodeConnectionStatus?, diff --git a/CommonKit/Sources/CommonKit/Models/Node/NodeStatusInfo.swift b/CommonKit/Sources/CommonKit/Models/Node/NodeStatusInfo.swift index d53cfc959..a661d5009 100644 --- a/CommonKit/Sources/CommonKit/Models/Node/NodeStatusInfo.swift +++ b/CommonKit/Sources/CommonKit/Models/Node/NodeStatusInfo.swift @@ -13,14 +13,14 @@ public struct NodeStatusInfo: Equatable { public let height: Int public let wsEnabled: Bool public let wsPort: Int? - public let version: String? + public let version: Version? public init( ping: TimeInterval, height: Int, wsEnabled: Bool, wsPort: Int?, - version: String? + version: Version? ) { self.ping = ping self.height = height diff --git a/CommonKit/Sources/CommonKit/Services/ApiService/AdamantApiCore.swift b/CommonKit/Sources/CommonKit/Services/ApiService/AdamantApiCore.swift index f1f9bffcb..1ada27df5 100644 --- a/CommonKit/Sources/CommonKit/Services/ApiService/AdamantApiCore.swift +++ b/CommonKit/Sources/CommonKit/Services/ApiService/AdamantApiCore.swift @@ -45,7 +45,7 @@ extension AdamantApiCore: BlockchainHealthCheckableService { height: statusDto.network?.height ?? .zero, wsEnabled: statusDto.wsClient?.enabled ?? false, wsPort: statusDto.wsClient?.port, - version: statusDto.version?.version + version: statusDto.version?.version.flatMap { .init($0) } ) } } diff --git a/CommonKit/Sources/CommonKit/Services/HealthCheck/BlockchainHealthCheckWrapper.swift b/CommonKit/Sources/CommonKit/Services/HealthCheck/BlockchainHealthCheckWrapper.swift index 7934415cc..c8f2569ee 100644 --- a/CommonKit/Sources/CommonKit/Services/HealthCheck/BlockchainHealthCheckWrapper.swift +++ b/CommonKit/Sources/CommonKit/Services/HealthCheck/BlockchainHealthCheckWrapper.swift @@ -151,8 +151,7 @@ private extension BlockchainHealthCheckWrapper { node.ping = info.ping guard - let versionString = info.version, - let version = Version(versionString), + let version = info.version, let minNodeVersion = params.minNodeVersion, version < minNodeVersion else { return } @@ -179,8 +178,7 @@ private extension BlockchainHealthCheckWrapper { var status: NodeConnectionStatus? if - let versionString = node.version, - let version = Version(versionString), + let version = node.version, let minNodeVersion = params.minNodeVersion, version < minNodeVersion { From 00b91fe881d160a398363fd1ad4894f65a9df3b3 Mon Sep 17 00:00:00 2001 From: just-software-dev Date: Thu, 29 Aug 2024 03:50:49 -0300 Subject: [PATCH 047/106] [trello.com/c/sf4UCx9f] Info Service finish --- Adamant.xcodeproj/project.pbxproj | 4 ++ Adamant/App/DI/AppAssembly.swift | 3 +- Adamant/Helpers/Node+UI.swift | 48 +++++++++---------- Adamant/Helpers/NodeGroup+Constants.swift | 19 ++++++-- .../ViewModel/CoinsNodesListMapper.swift | 5 +- .../InfoService/InfoService+Constants.swift | 25 ++++++++++ .../InfoService/InfoServiceAssembly.swift | 2 +- .../ApiService/InfoServiceApiService.swift | 6 ++- .../NodesEditor/NodesListViewController.swift | 2 +- Adamant/Services/ApiServiceCompose.swift | 3 +- .../Localization/de.lproj/Localizable.strings | 3 ++ .../Localization/en.lproj/Localizable.strings | 3 ++ .../Localization/ru.lproj/Localizable.strings | 3 ++ .../Localization/zh.lproj/Localizable.strings | 3 ++ 14 files changed, 92 insertions(+), 37 deletions(-) create mode 100644 Adamant/Modules/InfoService/InfoService+Constants.swift diff --git a/Adamant.xcodeproj/project.pbxproj b/Adamant.xcodeproj/project.pbxproj index b60a63a50..c1c8ed637 100644 --- a/Adamant.xcodeproj/project.pbxproj +++ b/Adamant.xcodeproj/project.pbxproj @@ -301,6 +301,7 @@ 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 */; }; + 937173F52C8049E0009D5191 /* InfoService+Constants.swift in Sources */ = {isa = PBXBuildFile; fileRef = 937173F42C8049E0009D5191 /* InfoService+Constants.swift */; }; 9371E561295CD53100438F2C /* ChatLocalization.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9371E560295CD53100438F2C /* ChatLocalization.swift */; }; 93760BD72C656CF8002507C3 /* DefaultNodesProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 93760BD62C656CF8002507C3 /* DefaultNodesProvider.swift */; }; 93760BDF2C65A284002507C3 /* WordList.swift in Sources */ = {isa = PBXBuildFile; fileRef = 93760BDE2C65A284002507C3 /* WordList.swift */; }; @@ -952,6 +953,7 @@ 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 = ""; }; + 937173F42C8049E0009D5191 /* InfoService+Constants.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "InfoService+Constants.swift"; sourceTree = ""; }; 9371E560295CD53100438F2C /* ChatLocalization.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChatLocalization.swift; sourceTree = ""; }; 93760BD62C656CF8002507C3 /* DefaultNodesProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DefaultNodesProvider.swift; sourceTree = ""; }; 93760BDD2C65A1FA002507C3 /* english.txt */ = {isa = PBXFileReference; lastKnownFileType = text; path = english.txt; sourceTree = ""; }; @@ -1767,6 +1769,7 @@ 934FD9A12C783CAC00336841 /* Protocols */, 934FD9A02C783CA700336841 /* Services */, 931224B02C7ACFE6009E0ED0 /* InfoServiceAssembly.swift */, + 937173F42C8049E0009D5191 /* InfoService+Constants.swift */, ); path = InfoService; sourceTree = ""; @@ -3553,6 +3556,7 @@ E90847302196FEA80095825D /* ChatTransaction+CoreDataClass.swift in Sources */, 3A96E37A2AED27D7001F5A52 /* AdamantPartnerQRService.swift in Sources */, E9EC342120052ABB00C0E546 /* TransferViewControllerBase.swift in Sources */, + 937173F52C8049E0009D5191 /* InfoService+Constants.swift in Sources */, 931224B32C7AD5DD009E0ED0 /* InfoServiceTicker.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 7fce2180b..c54c32595 100644 --- a/Adamant/App/DI/AppAssembly.swift +++ b/Adamant/App/DI/AppAssembly.swift @@ -421,7 +421,8 @@ struct AppAssembly: Assembly { doge: $0.resolve(DogeApiService.self)!, dash: $0.resolve(DashApiService.self)!, adm: $0.resolve(AdamantApiServiceProtocol.self)!, - ipfs: $0.resolve(IPFSApiService.self)! + ipfs: $0.resolve(IPFSApiService.self)!, + infoService: $0.resolve(InfoServiceApiServiceProtocol.self)! ) }.inObjectScope(.transient) diff --git a/Adamant/Helpers/Node+UI.swift b/Adamant/Helpers/Node+UI.swift index 095713cb7..5b731297f 100644 --- a/Adamant/Helpers/Node+UI.swift +++ b/Adamant/Helpers/Node+UI.swift @@ -10,38 +10,30 @@ import CommonKit import UIKit extension Node { - func statusString(showVersion: Bool) -> String? { - guard isEnabled else { return Strings.disabled } + func statusString(showVersion: Bool, dateHeight: Bool) -> String? { + guard + isEnabled, + let connectionStatus = connectionStatus + else { return Strings.disabled } - switch connectionStatus { + let statusTitle = switch connectionStatus { case .allowed: - return [ - pingString, - showVersion ? versionString : nil, - heightString - ] - .compactMap { $0 } - .joined(separator: " ") + pingString case .synchronizing: - return [ - Strings.synchronizing, - showVersion ? versionString : nil, - heightString - ] - .compactMap { $0 } - .joined(separator: " ") + Strings.synchronizing case .offline: - return Strings.offline + Strings.offline case .notAllowed(let reason): - return [ - reason.text, - version?.string - ] - .compactMap { $0 } - .joined(separator: " ") - case .none: - return nil + reason.text } + + return [ + statusTitle, + showVersion ? versionString : nil, + dateHeight ? dateHeightString : heightString + ] + .compactMap { $0 } + .joined(separator: " ") } func indicatorString(isRest: Bool, isWs: Bool) -> String { @@ -130,6 +122,10 @@ private extension Node { height.map { " ❐ \(getFormattedHeight(from: $0))" } } + var dateHeightString: String? { + height.map { Date(timeIntervalSince1970: .init($0)).humanizedTime().string } + } + var versionString: String? { version.map { "(v\($0.string))" } } diff --git a/Adamant/Helpers/NodeGroup+Constants.swift b/Adamant/Helpers/NodeGroup+Constants.swift index ebff868df..83326e1a1 100644 --- a/Adamant/Helpers/NodeGroup+Constants.swift +++ b/Adamant/Helpers/NodeGroup+Constants.swift @@ -28,7 +28,7 @@ extension NodeGroup { case .ipfs: return IPFSApiService.healthCheckParameters.onScreenUpdateInterval case .infoService: - return 1000 + return InfoService.healthCheckParameters.onScreenUpdateInterval } } @@ -51,7 +51,7 @@ extension NodeGroup { case .ipfs: return IPFSApiService.healthCheckParameters.crucialUpdateInterval case .infoService: - return 1000 + return InfoService.healthCheckParameters.crucialUpdateInterval } } @@ -74,7 +74,7 @@ extension NodeGroup { case .ipfs: return IPFSApiService.healthCheckParameters.threshold case .infoService: - return 1000 + return InfoService.healthCheckParameters.threshold } } @@ -97,7 +97,7 @@ extension NodeGroup { case .ipfs: return IPFSApiService.healthCheckParameters.normalUpdateInterval case .infoService: - return 1000 + return InfoService.healthCheckParameters.normalUpdateInterval } } @@ -145,7 +145,16 @@ extension NodeGroup { case .ipfs: return IPFSApiService.symbol case .infoService: - return "INFO SERVICE" + return InfoService.symbol + } + } + + var useDateHeight: Bool { + switch self { + case .btc, .eth, .klyNode, .klyService, .doge, .dash, .adm, .ipfs: + false + case .infoService: + true } } diff --git a/Adamant/Modules/CoinsNodesList/ViewModel/CoinsNodesListMapper.swift b/Adamant/Modules/CoinsNodesList/ViewModel/CoinsNodesListMapper.swift index b2e9a1ad2..379f709f6 100644 --- a/Adamant/Modules/CoinsNodesList/ViewModel/CoinsNodesListMapper.swift +++ b/Adamant/Modules/CoinsNodesList/ViewModel/CoinsNodesListMapper.swift @@ -53,7 +53,10 @@ private extension CoinsNodesListMapper { isEnabled: node.isEnabled, title: node.asString(), connectionStatus: indicatorAttrString, - description: node.statusString(showVersion: true) ?? .empty + description: node.statusString( + showVersion: true, + dateHeight: group.useDateHeight + ) ?? .empty ) } } diff --git a/Adamant/Modules/InfoService/InfoService+Constants.swift b/Adamant/Modules/InfoService/InfoService+Constants.swift new file mode 100644 index 000000000..49ce0605e --- /dev/null +++ b/Adamant/Modules/InfoService/InfoService+Constants.swift @@ -0,0 +1,25 @@ +// +// InfoService+Constants.swift +// Adamant +// +// Created by Andrew G on 29.08.2024. +// Copyright © 2024 Adamant. All rights reserved. +// + +import CommonKit + +extension InfoService { + static let healthCheckParameters = CoinHealthCheckParameters( + normalUpdateInterval: 210, + crucialUpdateInterval: 30, + onScreenUpdateInterval: 10, + threshold: 1800, + normalServiceUpdateInterval: .infinity, + crucialServiceUpdateInterval: .infinity, + onScreenServiceUpdateInterval: .infinity + ) + + static var symbol: String { + .localized("InfoService.InfoService") + } +} diff --git a/Adamant/Modules/InfoService/InfoServiceAssembly.swift b/Adamant/Modules/InfoService/InfoServiceAssembly.swift index b2c655ea0..e016a11d7 100644 --- a/Adamant/Modules/InfoService/InfoServiceAssembly.swift +++ b/Adamant/Modules/InfoService/InfoServiceAssembly.swift @@ -30,7 +30,7 @@ struct InfoServiceAssembly: Assembly { params: NodeGroup.infoService.blockchainHealthCheckParams, connection: r.resolve(ReachabilityMonitor.self)!.connectionPublisher )) - }.inObjectScope(.transient) + }.inObjectScope(.container) container.register(InfoServiceMapperProtocol.self) { _ in InfoServiceMapper() diff --git a/Adamant/Modules/InfoService/Services/ApiService/InfoServiceApiService.swift b/Adamant/Modules/InfoService/Services/ApiService/InfoServiceApiService.swift index c83110d65..1d2b8a39e 100644 --- a/Adamant/Modules/InfoService/Services/ApiService/InfoServiceApiService.swift +++ b/Adamant/Modules/InfoService/Services/ApiService/InfoServiceApiService.swift @@ -9,7 +9,7 @@ import Foundation import CommonKit -struct InfoServiceApiService { +final class InfoServiceApiService { let core: BlockchainHealthCheckWrapper func request( @@ -22,4 +22,8 @@ struct InfoServiceApiService { await request(core.apiCore, origin) }.mapError { .apiError($0) } } + + init(core: BlockchainHealthCheckWrapper) { + self.core = core + } } diff --git a/Adamant/Modules/NodesEditor/NodesListViewController.swift b/Adamant/Modules/NodesEditor/NodesListViewController.swift index b90cf05fe..3061d87f2 100644 --- a/Adamant/Modules/NodesEditor/NodesListViewController.swift +++ b/Adamant/Modules/NodesEditor/NodesListViewController.swift @@ -426,7 +426,7 @@ extension NodesListViewController { isWs: currentSocketsNodeId == node.id ), indicatorColor: node.indicatorColor, - statusString: node.statusString(showVersion: true) ?? .empty, + statusString: node.statusString(showVersion: true, dateHeight: false) ?? .empty, isEnabled: node.isEnabled, nodeUpdateAction: .init(id: node.id.uuidString) { [nodesStorage] isEnabled in nodesStorage.updateNode(id: node.id, group: .adm) { $0.isEnabled = isEnabled } diff --git a/Adamant/Services/ApiServiceCompose.swift b/Adamant/Services/ApiServiceCompose.swift index 5b55c542d..7bde7470f 100644 --- a/Adamant/Services/ApiServiceCompose.swift +++ b/Adamant/Services/ApiServiceCompose.swift @@ -18,6 +18,7 @@ struct ApiServiceCompose: ApiServiceComposeProtocol { let dash: ApiServiceProtocol let adm: ApiServiceProtocol let ipfs: ApiServiceProtocol + let infoService: ApiServiceProtocol func chosenFastestNodeId(group: NodeGroup) -> UUID? { getApiService(group: group).chosenFastestNodeId @@ -52,7 +53,7 @@ private extension ApiServiceCompose { case .ipfs: return ipfs case .infoService: - return ipfs + return infoService } } } diff --git a/CommonKit/Sources/CommonKit/Assets/Localization/de.lproj/Localizable.strings b/CommonKit/Sources/CommonKit/Assets/Localization/de.lproj/Localizable.strings index 034264e2f..4c6e45746 100644 --- a/CommonKit/Sources/CommonKit/Assets/Localization/de.lproj/Localizable.strings +++ b/CommonKit/Sources/CommonKit/Assets/Localization/de.lproj/Localizable.strings @@ -649,6 +649,9 @@ /* NodesList: Button label */ "NodesList.NodesList" = "ADM Node-Liste"; +/* Info Service: Info Service */ +"InfoService.InfoService" = "Info Service"; + /* NodesList: scene title */ "NodesList.Title" = "Liste der benutzten ADM Nodes"; diff --git a/CommonKit/Sources/CommonKit/Assets/Localization/en.lproj/Localizable.strings b/CommonKit/Sources/CommonKit/Assets/Localization/en.lproj/Localizable.strings index 24d1f315d..e45fea132 100644 --- a/CommonKit/Sources/CommonKit/Assets/Localization/en.lproj/Localizable.strings +++ b/CommonKit/Sources/CommonKit/Assets/Localization/en.lproj/Localizable.strings @@ -637,6 +637,9 @@ /* New Chat: 'What does it mean?', a help button for info about uninitialized accounts. */ "NewChatScene.NotInitialized.HelpButton" = "What does it mean?"; +/* Info Service: Info Service */ +"InfoService.InfoService" = "Info Service"; + /* NodesList: Button label */ "NodesList.NodesList" = "ADM node list"; diff --git a/CommonKit/Sources/CommonKit/Assets/Localization/ru.lproj/Localizable.strings b/CommonKit/Sources/CommonKit/Assets/Localization/ru.lproj/Localizable.strings index 154ed882b..f3900406f 100644 --- a/CommonKit/Sources/CommonKit/Assets/Localization/ru.lproj/Localizable.strings +++ b/CommonKit/Sources/CommonKit/Assets/Localization/ru.lproj/Localizable.strings @@ -640,6 +640,9 @@ /* NodesList: Button label */ "NodesList.NodesList" = "Список нод ADM"; +/* Info Service: Info Service */ +"InfoService.InfoService" = "Инфо Сервис"; + /* NodesList: scene title */ "NodesList.Title" = "Список нод ADM"; diff --git a/CommonKit/Sources/CommonKit/Assets/Localization/zh.lproj/Localizable.strings b/CommonKit/Sources/CommonKit/Assets/Localization/zh.lproj/Localizable.strings index 005f1d6fc..1763d2c74 100644 --- a/CommonKit/Sources/CommonKit/Assets/Localization/zh.lproj/Localizable.strings +++ b/CommonKit/Sources/CommonKit/Assets/Localization/zh.lproj/Localizable.strings @@ -637,6 +637,9 @@ /* New Chat: 'What does it mean?', a help button for info about uninitialized accounts. */ "NewChatScene.NotInitialized.HelpButton" = "这是什么意思?"; +/* Info Service: Info Service */ +"InfoService.InfoService" = "Info Service"; + /* NodesList: Button label */ "NodesList.NodesList" = "ADM节点列表"; From ed63db4e32ee4056048cfd1c568361609371b3ae Mon Sep 17 00:00:00 2001 From: just-software-dev Date: Thu, 29 Aug 2024 05:01:28 -0300 Subject: [PATCH 048/106] [trello.com/c/sf4UCx9f] ConcurrencyQueue optimization --- .../Models/InfoServiceApiError.swift | 12 ++-- .../InfoService/Services/InfoService.swift | 25 ++++++--- .../Localization/de.lproj/Localizable.strings | 3 + .../Localization/en.lproj/Localizable.strings | 3 + .../Localization/ru.lproj/Localizable.strings | 3 + .../Localization/zh.lproj/Localizable.strings | 3 + .../CommonKit/Helpers/ConcurrencyQueue.swift | 55 +++++++++---------- 7 files changed, 63 insertions(+), 41 deletions(-) diff --git a/Adamant/Modules/InfoService/Models/InfoServiceApiError.swift b/Adamant/Modules/InfoService/Models/InfoServiceApiError.swift index 10c1fb073..ca8fadd41 100644 --- a/Adamant/Modules/InfoService/Models/InfoServiceApiError.swift +++ b/Adamant/Modules/InfoService/Models/InfoServiceApiError.swift @@ -12,6 +12,7 @@ import CommonKit enum InfoServiceApiError: Error { case unknown case parsingError + case inconsistentData case apiError(ApiServiceError) } @@ -21,10 +22,9 @@ extension InfoServiceApiError: RichError { case .unknown: .adamant.sharedErrors.unknownError case .parsingError: - .localized( - "ApiService.InternalError.ParsingFailed", - comment: "Serious internal error: Error parsing response" - ) + .localized("ApiService.InternalError.ParsingFailed") + case .inconsistentData: + .localized("InfoService.InconsistentData") case let .apiError(error): error.message } @@ -32,7 +32,7 @@ extension InfoServiceApiError: RichError { var internalError: Error? { switch self { - case .unknown, .parsingError: + case .unknown, .parsingError, .inconsistentData: nil case let .apiError(error): error @@ -41,7 +41,7 @@ extension InfoServiceApiError: RichError { var level: ErrorLevel { switch self { - case .unknown, .parsingError: + case .unknown, .parsingError, .inconsistentData: .error case let .apiError(error): error.level diff --git a/Adamant/Modules/InfoService/Services/InfoService.swift b/Adamant/Modules/InfoService/Services/InfoService.swift index 40c6efccb..6d3eb136c 100644 --- a/Adamant/Modules/InfoService/Services/InfoService.swift +++ b/Adamant/Modules/InfoService/Services/InfoService.swift @@ -11,12 +11,14 @@ import CommonKit import UIKit final class InfoService: InfoServiceProtocol { + typealias Rates = [InfoServiceTicker: Decimal] + private let securedStore: SecuredStore private let api: InfoServiceApiServiceProtocol private let rateCoins: [String] - private let queue = ConcurrencyQueue() + private let queue = ConcurrencyQueue() - @Atomic private var rates = [InfoServiceTicker: Decimal]() + @Atomic private var rates = Rates() @Atomic private var currentCurrencyValue: Currency = .default @Atomic private var subscriptions = Set() @@ -38,11 +40,14 @@ final class InfoService: InfoServiceProtocol { } func update() { - queue.syncAdd { [weak self, rateCoins] in - (try? await self?.api.loadRates(coins: rateCoins).get()).map { - self?.rates = $0 - self?.sendRatesChangedNotification() + Task { [weak self, rateCoins] in + let rates = await self?.queue.perform { + try? await self?.api.loadRates(coins: rateCoins).get() } + + guard let rates = rates else { return } + self?.rates = rates + self?.sendRatesChangedNotification() } } @@ -54,7 +59,11 @@ final class InfoService: InfoServiceProtocol { for coin: String, date: Date ) async -> InfoServiceApiResult<[InfoServiceTicker: Decimal]> { - await api.getHistory(coin: coin, date: date).map { $0.tickers } + await api.getHistory(coin: coin, date: date).flatMap { + abs(date.timeIntervalSince($0.date)) < historyThreshold + ? .success($0.tickers) + : .failure(.inconsistentData) + } } } @@ -93,3 +102,5 @@ private extension InfoService { } } } + +private let historyThreshold: TimeInterval = 86400 diff --git a/CommonKit/Sources/CommonKit/Assets/Localization/de.lproj/Localizable.strings b/CommonKit/Sources/CommonKit/Assets/Localization/de.lproj/Localizable.strings index 4c6e45746..59f379679 100644 --- a/CommonKit/Sources/CommonKit/Assets/Localization/de.lproj/Localizable.strings +++ b/CommonKit/Sources/CommonKit/Assets/Localization/de.lproj/Localizable.strings @@ -652,6 +652,9 @@ /* Info Service: Info Service */ "InfoService.InfoService" = "Info Service"; +/* Info Service: inconsistent data */ +"InfoService.InconsistentData" = "Inconsistent data"; + /* NodesList: scene title */ "NodesList.Title" = "Liste der benutzten ADM Nodes"; diff --git a/CommonKit/Sources/CommonKit/Assets/Localization/en.lproj/Localizable.strings b/CommonKit/Sources/CommonKit/Assets/Localization/en.lproj/Localizable.strings index e45fea132..01cad9f0b 100644 --- a/CommonKit/Sources/CommonKit/Assets/Localization/en.lproj/Localizable.strings +++ b/CommonKit/Sources/CommonKit/Assets/Localization/en.lproj/Localizable.strings @@ -640,6 +640,9 @@ /* Info Service: Info Service */ "InfoService.InfoService" = "Info Service"; +/* Info Service: inconsistent data */ +"InfoService.InconsistentData" = "Inconsistent data"; + /* NodesList: Button label */ "NodesList.NodesList" = "ADM node list"; diff --git a/CommonKit/Sources/CommonKit/Assets/Localization/ru.lproj/Localizable.strings b/CommonKit/Sources/CommonKit/Assets/Localization/ru.lproj/Localizable.strings index f3900406f..922053509 100644 --- a/CommonKit/Sources/CommonKit/Assets/Localization/ru.lproj/Localizable.strings +++ b/CommonKit/Sources/CommonKit/Assets/Localization/ru.lproj/Localizable.strings @@ -643,6 +643,9 @@ /* Info Service: Info Service */ "InfoService.InfoService" = "Инфо Сервис"; +/* Info Service: inconsistent data */ +"InfoService.InconsistentData" = "Несоответствие в данных"; + /* NodesList: scene title */ "NodesList.Title" = "Список нод ADM"; diff --git a/CommonKit/Sources/CommonKit/Assets/Localization/zh.lproj/Localizable.strings b/CommonKit/Sources/CommonKit/Assets/Localization/zh.lproj/Localizable.strings index 1763d2c74..9269688f5 100644 --- a/CommonKit/Sources/CommonKit/Assets/Localization/zh.lproj/Localizable.strings +++ b/CommonKit/Sources/CommonKit/Assets/Localization/zh.lproj/Localizable.strings @@ -640,6 +640,9 @@ /* Info Service: Info Service */ "InfoService.InfoService" = "Info Service"; +/* Info Service: inconsistent data */ +"InfoService.InconsistentData" = "Inconsistent data"; + /* NodesList: Button label */ "NodesList.NodesList" = "ADM节点列表"; diff --git a/CommonKit/Sources/CommonKit/Helpers/ConcurrencyQueue.swift b/CommonKit/Sources/CommonKit/Helpers/ConcurrencyQueue.swift index 3090d7429..76b9dbfb9 100644 --- a/CommonKit/Sources/CommonKit/Helpers/ConcurrencyQueue.swift +++ b/CommonKit/Sources/CommonKit/Helpers/ConcurrencyQueue.swift @@ -7,41 +7,40 @@ import Foundation -public actor ConcurrencyQueue { - private var isPerforming = false - private var actions = [@Sendable () async -> Void]() +public actor ConcurrencyQueue { + private var inputCounter: Int = .zero + private var outputCounter: Int = .zero + private var queue: [Int: () -> Void] = .init() - public func add(_ action: @Sendable @escaping () async -> Void) { - actions.append(action) - guard !isPerforming else { return } - isPerforming = true - Task { await perform() } + public func perform(action: @escaping () async -> Output) async -> Output { + inputCounter += 1 + let id = inputCounter + let result = await action() + + return await withTaskCancellationHandler( + operation: { + await withCheckedContinuation { continuation in + enqueue(id: id) { continuation.resume(returning: result) } + } + }, + onCancel: { + Task { await enqueue(id: id, action: {}) } + } + ) } public init() {} } -public extension ConcurrencyQueue { - nonisolated func syncAdd(_ action: @Sendable @escaping () async -> Void) { - let semaphore = DispatchSemaphore(value: .zero) - - Task { - await add(action) - semaphore.signal() - } - - semaphore.wait() - } -} - private extension ConcurrencyQueue { - func perform() async { - while !actions.isEmpty { - // Now it's O(n). It's possible to achieve O(1). But we will not do it. - // "Premature optimization is the root of all evil." ~ Donald Knuth - await actions.removeFirst()() - } + func enqueue(id: Int, action: @escaping () -> Void) { + guard id == outputCounter + 1 else { return queue[id] = action } + action() + outputCounter += 1 - isPerforming = false + while let action = queue.removeValue(forKey: outputCounter + 1) { + action() + outputCounter += 1 + } } } From 644b5b584d8ecd64a4c74139ee057ae3aa107e17 Mon Sep 17 00:00:00 2001 From: just-software-dev Date: Thu, 29 Aug 2024 05:17:19 -0300 Subject: [PATCH 049/106] [trello.com/c/sf4UCx9f] Info service node fix --- Adamant/Modules/InfoService/InfoService+Constants.swift | 4 ++++ Adamant/Services/DataProviders/DefaultNodesProvider.swift | 2 +- CommonKit/Sources/CommonKit/AdamantResources.swift | 4 +--- 3 files changed, 6 insertions(+), 4 deletions(-) diff --git a/Adamant/Modules/InfoService/InfoService+Constants.swift b/Adamant/Modules/InfoService/InfoService+Constants.swift index 49ce0605e..4411b07d2 100644 --- a/Adamant/Modules/InfoService/InfoService+Constants.swift +++ b/Adamant/Modules/InfoService/InfoService+Constants.swift @@ -22,4 +22,8 @@ extension InfoService { static var symbol: String { .localized("InfoService.InfoService") } + + static var nodes: [Node] { + [.makeDefaultNode(url: .init(string: "https://info2.adm.im")!)] + } } diff --git a/Adamant/Services/DataProviders/DefaultNodesProvider.swift b/Adamant/Services/DataProviders/DefaultNodesProvider.swift index 60329ef71..07c96ebd4 100644 --- a/Adamant/Services/DataProviders/DefaultNodesProvider.swift +++ b/Adamant/Services/DataProviders/DefaultNodesProvider.swift @@ -36,7 +36,7 @@ private extension DefaultNodesProvider { case .ipfs: return IPFSApiService.nodes case .infoService: - return [.makeDefaultNode(url: .init(string: AdamantResources.coinsInfoService)!)] + return InfoService.nodes } } } diff --git a/CommonKit/Sources/CommonKit/AdamantResources.swift b/CommonKit/Sources/CommonKit/AdamantResources.swift index aab5ae711..749572179 100644 --- a/CommonKit/Sources/CommonKit/AdamantResources.swift +++ b/CommonKit/Sources/CommonKit/AdamantResources.swift @@ -8,9 +8,7 @@ import Foundation -public enum AdamantResources { - public static let coinsInfoService = "https://info2.adm.im" - +public enum AdamantResources { // MARK: ADAMANT Addresses public static let supportEmail = "business@adamant.im" public static let ansReadmeUrl = "https://github.com/Adamant-im/adamant-notificationService" From ca9ecd04a9e341ca99d28d28639153778f4bf84d Mon Sep 17 00:00:00 2001 From: StanislavDevIOS Date: Thu, 29 Aug 2024 13:08:24 +0300 Subject: [PATCH 050/106] [trello.com/c/WAXePS1N] fix: Transaction is already confirmed --- .../DataProviders/AdamantChatsProvider.swift | 90 ++++++++++++------- 1 file changed, 60 insertions(+), 30 deletions(-) diff --git a/Adamant/Services/DataProviders/AdamantChatsProvider.swift b/Adamant/Services/DataProviders/AdamantChatsProvider.swift index 346873b93..145cce076 100644 --- a/Adamant/Services/DataProviders/AdamantChatsProvider.swift +++ b/Adamant/Services/DataProviders/AdamantChatsProvider.swift @@ -1327,6 +1327,10 @@ extension AdamantChatsProvider { // MARK: 3. Send do { + let locallyID = signedTransaction.generateId() ?? UUID().uuidString + transaction.transactionId = locallyID + transaction.chatMessageId = locallyID + let id = try await apiService.sendMessageTransaction(transaction: signedTransaction).get() // Update ID with recieved, add to unconfirmed transactions. @@ -1334,38 +1338,66 @@ extension AdamantChatsProvider { transaction.chatMessageId = String(id) transaction.statusEnum = .pending - if let index = unconfirmedTransactionsBySignature.firstIndex( - of: signedTransaction.signature - ) { - unconfirmedTransactionsBySignature.remove(at: index) - } - - unconfirmedTransactions[id] = transaction.objectID + removeTxFromUnconfirmed( + signature: signedTransaction.signature, + transaction: transaction + ) return transaction } catch { - transaction.statusEnum = .failed - - switch error as? ApiServiceError { - case .networkError: - throw ChatsProviderError.networkError - case .accountNotFound: - throw ChatsProviderError.accountNotFound(recipientId) - case .notLogged: - 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: - throw ChatsProviderError.requestCancelled - case .none: - throw ChatsProviderError.serverError(error) + guard case let(apiError) = (error as? ApiServiceError), + case let(.serverError(text)) = apiError, + text.contains("Transaction is already confirmed") + || text.contains("Transaction is already processed") + else { + transaction.statusEnum = .failed + throw handleTransactionError(error, recipientId: recipientId) } + + transaction.statusEnum = .pending + + removeTxFromUnconfirmed( + signature: signedTransaction.signature, + transaction: transaction + ) + + return transaction + } + } + + func removeTxFromUnconfirmed( + signature: String, + transaction: ChatTransaction + ) { + if let index = unconfirmedTransactionsBySignature.firstIndex( + of: signature + ) { + unconfirmedTransactionsBySignature.remove(at: index) + } + + unconfirmedTransactions[UInt64(transaction.transactionId) ?? .zero] = transaction.objectID + } + + func handleTransactionError(_ error: Error, recipientId: String) -> Error { + switch error as? ApiServiceError { + case .networkError: + return ChatsProviderError.networkError + case .accountNotFound: + return ChatsProviderError.accountNotFound(recipientId) + case .notLogged: + return ChatsProviderError.notLogged + case .serverError(let e), .commonError(let e): + return ChatsProviderError.serverError(AdamantError(message: e)) + case .noEndpointsAvailable: + return ChatsProviderError.serverError(AdamantError( + message: error.localizedDescription + )) + case .internalError(let message, _): + return ChatsProviderError.internalError(AdamantError(message: message)) + case .requestCancelled: + return ChatsProviderError.requestCancelled + case .none: + return ChatsProviderError.serverError(error) } } } @@ -1706,7 +1738,6 @@ extension AdamantChatsProvider { transactionInProgress.append(trs.transaction.id) if let objectId = unconfirmedTransactions[trs.transaction.id], let unconfirmed = context.object(with: objectId) as? ChatTransaction { - print("confirmTransaction tr=\(trs.transaction.id)") confirmTransaction( unconfirmed, id: trs.transaction.id, @@ -1724,7 +1755,6 @@ extension AdamantChatsProvider { // if transaction in pending status then ignore it if unconfirmedTransactionsBySignature.contains(trs.transaction.signature) { - print("ignore tr=\(trs.transaction.id)") continue } From 960dfb622b028c2983e22cf0e4a40c4e0ea4e9f8 Mon Sep 17 00:00:00 2001 From: Iana Date: Fri, 30 Aug 2024 14:47:51 +0300 Subject: [PATCH 051/106] [trello.com/c/5Lqd9RFP] code refactoring --- Adamant/Modules/Onboard/OnboardPage.swift | 10 +--------- 1 file changed, 1 insertion(+), 9 deletions(-) diff --git a/Adamant/Modules/Onboard/OnboardPage.swift b/Adamant/Modules/Onboard/OnboardPage.swift index 26691cc65..6be25d660 100644 --- a/Adamant/Modules/Onboard/OnboardPage.swift +++ b/Adamant/Modules/Onboard/OnboardPage.swift @@ -45,7 +45,6 @@ final class OnboardPage: SwiftyOnboardPage { super.init(frame: .zero) setupView() - layoutScreen() } required public init?(coder aDecoder: NSCoder) { @@ -68,14 +67,7 @@ final class OnboardPage: SwiftyOnboardPage { make.horizontalEdges.equalToSuperview().inset(20) make.bottom.equalToSuperview().offset(-150) make.height.equalTo(260) - } - } - - private func layoutScreen() { - if UIScreen.main.bounds.height == 667 { - textView.snp.updateConstraints { make in - make.bottom.equalToSuperview().offset(-120) - } + make.top.greaterThanOrEqualTo(mainImageView.snp.bottom).offset(20) } } } From a0e74301c8c7e65c8c7f035f54f26bb94bddbb9f Mon Sep 17 00:00:00 2001 From: Iana Date: Fri, 30 Aug 2024 15:18:06 +0300 Subject: [PATCH 052/106] [trello.com/c/1tMg5Cbp] Rename var to `txBlockchainComment` for clarity --- .../KlyTransactionDetailsViewController.swift | 2 +- .../Klayr/KlyTransactionsViewController.swift | 2 +- .../Wallets/Models/TransactionDetails.swift | 4 +-- ...TransactionDetailsViewControllerBase.swift | 28 +++++++++---------- 4 files changed, 18 insertions(+), 18 deletions(-) diff --git a/Adamant/Modules/Wallets/Klayr/KlyTransactionDetailsViewController.swift b/Adamant/Modules/Wallets/Klayr/KlyTransactionDetailsViewController.swift index 14ddb3189..4b44cc1ef 100644 --- a/Adamant/Modules/Wallets/Klayr/KlyTransactionDetailsViewController.swift +++ b/Adamant/Modules/Wallets/Klayr/KlyTransactionDetailsViewController.swift @@ -28,7 +28,7 @@ final class KlyTransactionDetailsViewController: TransactionDetailsViewControlle return control }() - override var showTxRecordData: Bool { + override var showTxBlockchainComment: Bool { true } // MARK: - Lifecycle diff --git a/Adamant/Modules/Wallets/Klayr/KlyTransactionsViewController.swift b/Adamant/Modules/Wallets/Klayr/KlyTransactionsViewController.swift index 4b549e50f..eca2bf136 100644 --- a/Adamant/Modules/Wallets/Klayr/KlyTransactionsViewController.swift +++ b/Adamant/Modules/Wallets/Klayr/KlyTransactionsViewController.swift @@ -122,7 +122,7 @@ extension Transactions.TransactionModel: TransactionDetails { timestamp.map { Date(timeIntervalSince1970: TimeInterval($0)) } } - var txRecordData: String? { + var txBlockchainComment: String? { txData } } diff --git a/Adamant/Modules/Wallets/Models/TransactionDetails.swift b/Adamant/Modules/Wallets/Models/TransactionDetails.swift index d25191f8c..de0953454 100644 --- a/Adamant/Modules/Wallets/Models/TransactionDetails.swift +++ b/Adamant/Modules/Wallets/Models/TransactionDetails.swift @@ -47,7 +47,7 @@ protocol TransactionDetails { var nonceRaw: String? { get } - var txRecordData: String? { get } + var txBlockchainComment: String? { get } func summary( with url: String?, @@ -59,7 +59,7 @@ protocol TransactionDetails { extension TransactionDetails { var feeCurrencySymbol: String? { defaultCurrencySymbol } - var txRecordData: String? { nil } + var txBlockchainComment: String? { nil } func summary( with url: String? = nil, diff --git a/Adamant/Modules/Wallets/TransactionDetailsViewControllerBase.swift b/Adamant/Modules/Wallets/TransactionDetailsViewControllerBase.swift index 419df06ed..9f5ea8ea8 100644 --- a/Adamant/Modules/Wallets/TransactionDetailsViewControllerBase.swift +++ b/Adamant/Modules/Wallets/TransactionDetailsViewControllerBase.swift @@ -71,7 +71,7 @@ class TransactionDetailsViewControllerBase: FormViewController { case historyFiat case currentFiat case inconsistentReason - case txRecordData + case txBlockchainComment var tag: String { switch self { @@ -90,7 +90,7 @@ class TransactionDetailsViewControllerBase: FormViewController { case .historyFiat: return "hfiat" case .currentFiat: return "cfiat" case .inconsistentReason: return "incReason" - case .txRecordData: return "data" + case .txBlockchainComment: return "data" } } @@ -112,7 +112,7 @@ class TransactionDetailsViewControllerBase: FormViewController { case .currentFiat: return .localized("TransactionDetailsScene.Row.CurrentFiat", comment: "Transaction details: current fiat value") case .inconsistentReason: return .localized("TransactionStatus.Inconsistent.Reason.Title", comment: "Transaction status: inconsistent reason title") - case .txRecordData: + case .txBlockchainComment: return .localized("TransactionStatus.Inconsistent.RecordData.Title", comment: "Transaction details: Tx data record") } @@ -165,7 +165,7 @@ class TransactionDetailsViewControllerBase: FormViewController { // MARK: - Properties - var showTxRecordData: Bool { + var showTxBlockchainComment: Bool { false } @@ -656,13 +656,13 @@ class TransactionDetailsViewControllerBase: FormViewController { detailsSection.append(fiatRow) - // MARK: Tx data record - let txRecordData = LabelRow { + // MARK: Tx blockchain comment + let txBlockchainComment = LabelRow { $0.disabled = true - $0.tag = Rows.txRecordData.tag - $0.title = Rows.txRecordData.localized + $0.tag = Rows.txBlockchainComment.tag + $0.title = Rows.txBlockchainComment.localized - if let value = transaction?.txRecordData { + if let value = transaction?.txBlockchainComment { $0.value = value } else { $0.value = TransactionDetailsViewControllerBase.awaitingValueString @@ -672,7 +672,7 @@ class TransactionDetailsViewControllerBase: FormViewController { $0.cell.detailTextLabel?.lineBreakMode = .byTruncatingMiddle $0.hidden = Condition.function([], { [weak self] _ -> Bool in - guard let value = self?.transaction?.txRecordData else { + guard let value = self?.transaction?.txBlockchainComment else { return false } @@ -691,15 +691,15 @@ class TransactionDetailsViewControllerBase: FormViewController { } }.cellUpdate { [weak self] (cell, row) in cell.textLabel?.textColor = UIColor.adamant.textColor - if let value = self?.transaction?.txRecordData { + if let value = self?.transaction?.txBlockchainComment { row.value = value } else { row.value = TransactionDetailsViewControllerBase.awaitingValueString } } - if showTxRecordData { - detailsSection.append(txRecordData) + if showTxBlockchainComment { + detailsSection.append(txBlockchainComment) } // MARK: Comments section @@ -871,7 +871,7 @@ class TransactionDetailsViewControllerBase: FormViewController { } func updateTxDataRow() { - let row = form.rowBy(tag: Rows.txRecordData.tag) + let row = form.rowBy(tag: Rows.txBlockchainComment.tag) row?.evaluateHidden() } From 98f72d5a03d57c09340abcdcb64d9d506e561481 Mon Sep 17 00:00:00 2001 From: Iana Date: Fri, 30 Aug 2024 15:43:00 +0300 Subject: [PATCH 053/106] [trello.com/c/Kv0L5rYH] Code refactoring --- Adamant/Models/CoreData/Chatroom+CoreDataClass.swift | 4 ++++ Adamant/Modules/ChatsList/ChatListViewController.swift | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/Adamant/Models/CoreData/Chatroom+CoreDataClass.swift b/Adamant/Models/CoreData/Chatroom+CoreDataClass.swift index 89b5459c3..aff353eb2 100644 --- a/Adamant/Models/CoreData/Chatroom+CoreDataClass.swift +++ b/Adamant/Models/CoreData/Chatroom+CoreDataClass.swift @@ -14,6 +14,10 @@ import CoreData public class Chatroom: NSManagedObject { static let entityName = "Chatroom" + var hasUnread: Bool { + return hasUnreadMessages || (lastTransaction?.isUnread ?? false) + } + func markAsReaded() { hasUnreadMessages = false diff --git a/Adamant/Modules/ChatsList/ChatListViewController.swift b/Adamant/Modules/ChatsList/ChatListViewController.swift index 9dfb822f6..f19cecca0 100644 --- a/Adamant/Modules/ChatsList/ChatListViewController.swift +++ b/Adamant/Modules/ChatsList/ChatListViewController.swift @@ -1086,7 +1086,7 @@ extension ChatListViewController { style: .normal, title: "👀" ) { (_, _, completionHandler) in - if chatroom.hasUnreadMessages || (chatroom.lastTransaction?.isUnread ?? false) { + if chatroom.hasUnread { chatroom.markAsReaded() } else { chatroom.markAsUnread() From f4fa6e520e968a400da0667c3284ca48881f0c00 Mon Sep 17 00:00:00 2001 From: Iana Date: Fri, 30 Aug 2024 17:32:34 +0300 Subject: [PATCH 054/106] [trello.com/c/GR5OunFg] Code refactoring --- .../Container/ChatTransactionContainerView.swift | 6 +++--- .../Content/ChatTransactionContentView.swift | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/Adamant/Modules/Chat/View/Subviews/ChatTransaction/Container/ChatTransactionContainerView.swift b/Adamant/Modules/Chat/View/Subviews/ChatTransaction/Container/ChatTransactionContainerView.swift index 17efc280b..ab968ad69 100644 --- a/Adamant/Modules/Chat/View/Subviews/ChatTransaction/Container/ChatTransactionContainerView.swift +++ b/Adamant/Modules/Chat/View/Subviews/ChatTransaction/Container/ChatTransactionContainerView.swift @@ -63,7 +63,7 @@ final class ChatTransactionContainerView: UIView, ChatModelView { stack.addArrangedSubview(opponentReactionLabel) stack.snp.makeConstraints { - $0.width.equalTo(Self.opponentReactionWidth) + $0.width.equalTo(Self.maxVStackWidth) } return stack }() @@ -122,11 +122,11 @@ final class ChatTransactionContainerView: UIView, ChatModelView { private lazy var chatMenuManager = ChatMenuManager(delegate: self) private let ownReactionSize = CGSize(width: 40, height: 27) - private let opponentReactionSize = CGSize(width: opponentReactionWidth, height: 27) + private let opponentReactionSize = CGSize(width: maxVStackWidth, height: 27) private let opponentReactionImageSize = CGSize(width: 12, height: 12) - static let opponentReactionWidth: CGFloat = 55 static let horizontalStackSpacing: CGFloat = 12 + static let maxVStackWidth: CGFloat = 55 var isSelected: Bool = false { didSet { diff --git a/Adamant/Modules/Chat/View/Subviews/ChatTransaction/Content/ChatTransactionContentView.swift b/Adamant/Modules/Chat/View/Subviews/ChatTransaction/Content/ChatTransactionContentView.swift index 997df69d2..662660947 100644 --- a/Adamant/Modules/Chat/View/Subviews/ChatTransaction/Content/ChatTransactionContentView.swift +++ b/Adamant/Modules/Chat/View/Subviews/ChatTransaction/Content/ChatTransactionContentView.swift @@ -135,7 +135,7 @@ final class ChatTransactionContentView: UIView { extension ChatTransactionContentView.Model { func height(for width: CGFloat) -> CGFloat { - let opponentReactionWidth = ChatTransactionContainerView.opponentReactionWidth + let opponentReactionWidth = ChatTransactionContainerView.maxVStackWidth let containerHorizontalOffset = ChatTransactionContainerView.horizontalStackSpacing * 2 let contentHorizontalOffset = horizontalInsets * 2 From 9570e936f1f26264e8c832a0a9b7c1389625dacd Mon Sep 17 00:00:00 2001 From: Iana Date: Fri, 30 Aug 2024 18:09:22 +0300 Subject: [PATCH 055/106] [trello.com/c/hxbMeJhs] Cleaned up the code --- .../Delegates/DelegatesListViewController.swift | 12 +----------- 1 file changed, 1 insertion(+), 11 deletions(-) diff --git a/Adamant/Modules/Delegates/DelegatesListViewController.swift b/Adamant/Modules/Delegates/DelegatesListViewController.swift index c4a01aa64..94e361dcc 100644 --- a/Adamant/Modules/Delegates/DelegatesListViewController.swift +++ b/Adamant/Modules/Delegates/DelegatesListViewController.swift @@ -69,7 +69,7 @@ final class DelegatesListViewController: KeyboardObservingViewController { attributedString: MarkdownParser( font: UIFont.preferredFont(forTextStyle: .subheadline), color: .adamant.chatPlaceholderTextColor - ).parse(self.text) + ).parse(.localized("Delegates.HeaderText")) ) let paragraphStyle = NSMutableParagraphStyle() @@ -90,10 +90,6 @@ final class DelegatesListViewController: KeyboardObservingViewController { return textView } - var text: String { - .localized("Delegates.HeaderText") - } - private lazy var tableView: UITableView = { let tableView = UITableView(frame: .zero, style: .grouped) tableView.register(AdamantDelegateCell.self, forCellReuseIdentifier: cellIdentifier) @@ -236,12 +232,6 @@ final class DelegatesListViewController: KeyboardObservingViewController { present(safari, animated: true, completion: nil) } - private func calculateHeightForHeader() -> CGFloat { - headerTextView.sizeToFit() - let height = headerTextView.contentSize.height - return height - } - private func setupViews() { view.addSubview(tableView) view.addSubview(bottomPanel) From 6b4caa1e3e0c16254e7a65fd34c1c14f2c024b67 Mon Sep 17 00:00:00 2001 From: Iana Date: Fri, 30 Aug 2024 19:28:53 +0300 Subject: [PATCH 056/106] [trello.com/c/bSmPAIM2] Feat: Show fixed date on scroll --- .../Chat/View/ChatViewController.swift | 43 +++++++++++++++++++ .../View/Managers/ChatLayoutManager.swift | 2 +- .../Chat/ViewModel/ChatMessageFactory.swift | 7 ++- .../Chat/ViewModel/ChatViewModel.swift | 21 +++++++++ .../Chat/ViewModel/Models/ChatMessage.swift | 4 +- 5 files changed, 71 insertions(+), 6 deletions(-) diff --git a/Adamant/Modules/Chat/View/ChatViewController.swift b/Adamant/Modules/Chat/View/ChatViewController.swift index dc38bad0a..e99b26920 100644 --- a/Adamant/Modules/Chat/View/ChatViewController.swift +++ b/Adamant/Modules/Chat/View/ChatViewController.swift @@ -50,6 +50,11 @@ final class ChatViewController: MessagesViewController { private lazy var replyView = ReplyView() private lazy var filesToolbarView = FilesToolbarView() private lazy var chatDropView = ChatDropView() + private lazy var dateHeaderLabel = EdgeInsetLabel( + font: .adamantPrimary(ofSize: 13), + textColor: .adamant.textColor, + numberOfLines: 1 + ) private var sendTransaction: SendTransaction @@ -189,11 +194,29 @@ final class ChatViewController: MessagesViewController { super.collectionView(collectionView, willDisplay: cell, forItemAt: indexPath) } + override func scrollViewDidEndDragging(_: UIScrollView, willDecelerate _: Bool) { + viewModel.didEndScroll() + } + override func scrollViewDidScroll(_ scrollView: UIScrollView) { super.scrollViewDidScroll(scrollView) updateIsScrollPositionNearlyTheBottom() updateScrollDownButtonVisibility() + let targetY: CGFloat = 20 + view.safeAreaInsets.top + guard let visibleIndexPaths = messagesCollectionView.indexPathsForVisibleItems.sorted(by: { + $0.row < $1.row + }) as [IndexPath]? else { return } + for indexPath in visibleIndexPaths { + if let cell = messagesCollectionView.cellForItem(at: indexPath) { + let cellRect = messagesCollectionView.convert(cell.frame, to: self.view) + if cellRect.minY <= targetY && cellRect.maxY >= targetY { + viewModel.checkTopMessage(indexPath: indexPath) + break + } + } + } + guard viewAppeared, scrollView.contentOffset.y <= viewModel.minOffsetForStartLoadNewMessages @@ -391,6 +414,16 @@ private extension ChatViewController { .sink { [weak self] in self?.animateScroll(isStarted: $0) } .store(in: &subscriptions) + viewModel.$dateHeader + .removeDuplicates() + .sink { [weak self] in self?.dateHeaderLabel.text = $0 } + .store(in: &subscriptions) + + viewModel.$dateHeaderHidden + .removeDuplicates() + .sink { [weak self] in self?.dateHeaderLabel.isHidden = $0 } + .store(in: &subscriptions) + viewModel.updateChatRead .sink { [weak self] in self?.checkIsChatWasRead() } .store(in: &subscriptions) @@ -492,6 +525,16 @@ private extension ChatViewController { navigationItem.titleView?.addGestureRecognizer(tapGesture) navigationItem.titleView?.addGestureRecognizer(longPressGesture) + + view.addSubview(dateHeaderLabel) + dateHeaderLabel.backgroundColor = .adamant.chatSenderBackground + dateHeaderLabel.textInsets = .init(top: 4, left: 7, bottom: 4, right: 7) + dateHeaderLabel.layer.cornerRadius = 10 + dateHeaderLabel.clipsToBounds = true + dateHeaderLabel.snp.makeConstraints { make in + make.top.equalTo(view.safeAreaLayoutGuide).offset(10) + make.centerX.equalToSuperview() + } } func configureHeaderRightButton() { diff --git a/Adamant/Modules/Chat/View/Managers/ChatLayoutManager.swift b/Adamant/Modules/Chat/View/Managers/ChatLayoutManager.swift index f4d9e97a5..9f6af55bf 100644 --- a/Adamant/Modules/Chat/View/Managers/ChatLayoutManager.swift +++ b/Adamant/Modules/Chat/View/Managers/ChatLayoutManager.swift @@ -29,7 +29,7 @@ final class ChatLayoutManager: MessagesLayoutDelegate { at indexPath: IndexPath, in _: MessagesCollectionView ) -> CGFloat { - message.fullModel.dateHeader == nil + message.fullModel.dateHeaderIsHidden ? .zero : labelHeight } diff --git a/Adamant/Modules/Chat/ViewModel/ChatMessageFactory.swift b/Adamant/Modules/Chat/ViewModel/ChatMessageFactory.swift index 13fff642a..fe8289b3c 100644 --- a/Adamant/Modules/Chat/ViewModel/ChatMessageFactory.swift +++ b/Adamant/Modules/Chat/ViewModel/ChatMessageFactory.swift @@ -108,10 +108,9 @@ struct ChatMessageFactory { status: status, expireDate: &expireDate ).map { .init(string: $0) }, - dateHeader: dateHeaderOn - ? makeDateHeader(sentDate: sentDate) - : nil, - topSpinnerOn: topSpinnerOn + dateHeader: makeDateHeader(sentDate: sentDate), + topSpinnerOn: topSpinnerOn, + dateHeaderIsHidden: !dateHeaderOn ) } } diff --git a/Adamant/Modules/Chat/ViewModel/ChatViewModel.swift b/Adamant/Modules/Chat/ViewModel/ChatViewModel.swift index edc8eae10..f55465e60 100644 --- a/Adamant/Modules/Chat/ViewModel/ChatViewModel.swift +++ b/Adamant/Modules/Chat/ViewModel/ChatViewModel.swift @@ -71,6 +71,7 @@ final class ChatViewModel: NSObject { private let maxMessageLenght: Int = 10000 private var previousArg: ChatContextMenuArguments? private var havePartnerName: Bool = false + private var dateTimer: Timer? let minIndexForStartLoadNewMessages = 4 let minOffsetForStartLoadNewMessages: CGFloat = 100 @@ -104,6 +105,8 @@ final class ChatViewModel: NSObject { @ObservableValue private(set) var partnerName: String? @ObservableValue private(set) var partnerImage: UIImage? @ObservableValue private(set) var isNeedToAnimateScroll = false + @ObservableValue private(set) var dateHeader: String? + @ObservableValue private(set) var dateHeaderHidden: Bool = true @ObservableValue var swipeState: SwipeableView.State = .ended @ObservableValue var inputText = "" @ObservableValue var replyMessage: MessageModel? @@ -1014,6 +1017,17 @@ extension ChatViewModel { processFileResult(.failure(error)) } } + + func checkTopMessage(indexPath: IndexPath) { + guard let message = messages[safe: indexPath.section], + let date = message.dateHeader?.string.string + else { return } + dateHeader = date + dateHeaderHidden = false + } + func didEndScroll() { + startHideDateTimer() + } } extension ChatViewModel: NSFetchedResultsControllerDelegate { @@ -1023,6 +1037,13 @@ extension ChatViewModel: NSFetchedResultsControllerDelegate { } private extension ChatViewModel { + func startHideDateTimer() { + dateTimer?.invalidate() + dateTimer = Timer.scheduledTimer(withTimeInterval: 2.0, repeats: false) { [weak self] _ in + self?.dateHeaderHidden = true + } + } + func sendFiles(with text: String) async throws { guard nodesStorage.haveActiveNode(in: .ipfs) else { dialog.send(.alert(ApiServiceError.noEndpointsAvailable( diff --git a/Adamant/Modules/Chat/ViewModel/Models/ChatMessage.swift b/Adamant/Modules/Chat/ViewModel/Models/ChatMessage.swift index 115e9065f..0e7bcb115 100644 --- a/Adamant/Modules/Chat/ViewModel/Models/ChatMessage.swift +++ b/Adamant/Modules/Chat/ViewModel/Models/ChatMessage.swift @@ -20,6 +20,7 @@ struct ChatMessage: Identifiable, Equatable { let bottomString: ComparableAttributedString? let dateHeader: ComparableAttributedString? let topSpinnerOn: Bool + let dateHeaderIsHidden: Bool static let `default` = Self( id: "", @@ -30,7 +31,8 @@ struct ChatMessage: Identifiable, Equatable { backgroundColor: .failed, bottomString: nil, dateHeader: nil, - topSpinnerOn: false + topSpinnerOn: false, + dateHeaderIsHidden: true ) } From 16b350c789f37b392701c79a2eac9d932ed83e12 Mon Sep 17 00:00:00 2001 From: just-software-dev Date: Sat, 31 Aug 2024 18:56:44 -0300 Subject: [PATCH 057/106] [trello.com/c/pUGfzAtU] Small fixes --- Adamant/App/DI/AppAssembly.swift | 2 +- .../Wallets/Adamant/AdmWalletFactory.swift | 1 - .../Wallets/Bitcoin/BtcWalletFactory.swift | 1 - .../Wallets/Dash/DashWalletFactory.swift | 1 - .../Wallets/Doge/DogeWalletFactory.swift | 1 - .../Wallets/ERC20/ERC20WalletFactory.swift | 1 - .../Wallets/Ethereum/EthWalletFactory.swift | 1 - .../Wallets/Klayr/KlyWalletFactory.swift | 1 - .../Wallets/TransferViewControllerBase.swift | 3 -- .../Sources/CommonKit/AdamantSecret.swift | 14 -------- .../Sources/CommonKit/Helpers/Atomic.swift | 25 ++++++++++----- .../HealthCheck/HealthCheckWrapper.swift | 32 +++++++++---------- 12 files changed, 34 insertions(+), 49 deletions(-) delete mode 100644 CommonKit/Sources/CommonKit/AdamantSecret.swift diff --git a/Adamant/App/DI/AppAssembly.swift b/Adamant/App/DI/AppAssembly.swift index c54c32595..358190f88 100644 --- a/Adamant/App/DI/AppAssembly.swift +++ b/Adamant/App/DI/AppAssembly.swift @@ -411,7 +411,7 @@ struct AppAssembly: Assembly { } } - // MARK: Wallet Service Compose + // MARK: ApiService Compose container.register(ApiServiceComposeProtocol.self) { ApiServiceCompose( btc: $0.resolve(BtcApiService.self)!, diff --git a/Adamant/Modules/Wallets/Adamant/AdmWalletFactory.swift b/Adamant/Modules/Wallets/Adamant/AdmWalletFactory.swift index 819da85cf..71c147234 100644 --- a/Adamant/Modules/Wallets/Adamant/AdmWalletFactory.swift +++ b/Adamant/Modules/Wallets/Adamant/AdmWalletFactory.swift @@ -53,7 +53,6 @@ struct AdmWalletFactory: WalletFactory { vibroService: assembler.resolve(VibroService.self)!, walletService: service, reachabilityMonitor: assembler.resolve(ReachabilityMonitor.self)!, - nodesStorage: assembler.resolve(NodesStorageProtocol.self)!, apiServiceCompose: assembler.resolve(ApiServiceComposeProtocol.self)! ) } diff --git a/Adamant/Modules/Wallets/Bitcoin/BtcWalletFactory.swift b/Adamant/Modules/Wallets/Bitcoin/BtcWalletFactory.swift index 984a603cd..a575d21f9 100644 --- a/Adamant/Modules/Wallets/Bitcoin/BtcWalletFactory.swift +++ b/Adamant/Modules/Wallets/Bitcoin/BtcWalletFactory.swift @@ -49,7 +49,6 @@ struct BtcWalletFactory: WalletFactory { vibroService: assembler.resolve(VibroService.self)!, walletService: service, reachabilityMonitor: assembler.resolve(ReachabilityMonitor.self)!, - nodesStorage: assembler.resolve(NodesStorageProtocol.self)!, apiServiceCompose: assembler.resolve(ApiServiceComposeProtocol.self)! ) } diff --git a/Adamant/Modules/Wallets/Dash/DashWalletFactory.swift b/Adamant/Modules/Wallets/Dash/DashWalletFactory.swift index f41aa7930..254644865 100644 --- a/Adamant/Modules/Wallets/Dash/DashWalletFactory.swift +++ b/Adamant/Modules/Wallets/Dash/DashWalletFactory.swift @@ -48,7 +48,6 @@ struct DashWalletFactory: WalletFactory { vibroService: assembler.resolve(VibroService.self)!, walletService: service, reachabilityMonitor: assembler.resolve(ReachabilityMonitor.self)!, - nodesStorage: assembler.resolve(NodesStorageProtocol.self)!, apiServiceCompose: assembler.resolve(ApiServiceComposeProtocol.self)! ) } diff --git a/Adamant/Modules/Wallets/Doge/DogeWalletFactory.swift b/Adamant/Modules/Wallets/Doge/DogeWalletFactory.swift index 18f378589..0ece9a4e8 100644 --- a/Adamant/Modules/Wallets/Doge/DogeWalletFactory.swift +++ b/Adamant/Modules/Wallets/Doge/DogeWalletFactory.swift @@ -48,7 +48,6 @@ struct DogeWalletFactory: WalletFactory { vibroService: assembler.resolve(VibroService.self)!, walletService: service, reachabilityMonitor: assembler.resolve(ReachabilityMonitor.self)!, - nodesStorage: assembler.resolve(NodesStorageProtocol.self)!, apiServiceCompose: assembler.resolve(ApiServiceComposeProtocol.self)! ) } diff --git a/Adamant/Modules/Wallets/ERC20/ERC20WalletFactory.swift b/Adamant/Modules/Wallets/ERC20/ERC20WalletFactory.swift index c99459f7a..f77913123 100644 --- a/Adamant/Modules/Wallets/ERC20/ERC20WalletFactory.swift +++ b/Adamant/Modules/Wallets/ERC20/ERC20WalletFactory.swift @@ -48,7 +48,6 @@ struct ERC20WalletFactory: WalletFactory { vibroService: assembler.resolve(VibroService.self)!, walletService: service, reachabilityMonitor: assembler.resolve(ReachabilityMonitor.self)!, - nodesStorage: assembler.resolve(NodesStorageProtocol.self)!, apiServiceCompose: assembler.resolve(ApiServiceComposeProtocol.self)! ) } diff --git a/Adamant/Modules/Wallets/Ethereum/EthWalletFactory.swift b/Adamant/Modules/Wallets/Ethereum/EthWalletFactory.swift index c1b6a9fc6..51b00bc72 100644 --- a/Adamant/Modules/Wallets/Ethereum/EthWalletFactory.swift +++ b/Adamant/Modules/Wallets/Ethereum/EthWalletFactory.swift @@ -48,7 +48,6 @@ struct EthWalletFactory: WalletFactory { vibroService: assembler.resolve(VibroService.self)!, walletService: service, reachabilityMonitor: assembler.resolve(ReachabilityMonitor.self)!, - nodesStorage: assembler.resolve(NodesStorageProtocol.self)!, apiServiceCompose: assembler.resolve(ApiServiceComposeProtocol.self)! ) } diff --git a/Adamant/Modules/Wallets/Klayr/KlyWalletFactory.swift b/Adamant/Modules/Wallets/Klayr/KlyWalletFactory.swift index 29c695aae..f09d2701a 100644 --- a/Adamant/Modules/Wallets/Klayr/KlyWalletFactory.swift +++ b/Adamant/Modules/Wallets/Klayr/KlyWalletFactory.swift @@ -49,7 +49,6 @@ struct KlyWalletFactory: WalletFactory { vibroService: assembler.resolve(VibroService.self)!, walletService: service, reachabilityMonitor: assembler.resolve(ReachabilityMonitor.self)!, - nodesStorage: assembler.resolve(NodesStorageProtocol.self)!, apiServiceCompose: assembler.resolve(ApiServiceComposeProtocol.self)! ) } diff --git a/Adamant/Modules/Wallets/TransferViewControllerBase.swift b/Adamant/Modules/Wallets/TransferViewControllerBase.swift index f35c03f9e..9bafbf763 100644 --- a/Adamant/Modules/Wallets/TransferViewControllerBase.swift +++ b/Adamant/Modules/Wallets/TransferViewControllerBase.swift @@ -187,7 +187,6 @@ class TransferViewControllerBase: FormViewController { let walletService: WalletService let walletCore: WalletCoreProtocol let reachabilityMonitor: ReachabilityMonitor - let nodesStorage: NodesStorageProtocol let apiServiceCompose: ApiServiceComposeProtocol // MARK: - Properties @@ -320,7 +319,6 @@ class TransferViewControllerBase: FormViewController { vibroService: VibroService, walletService: WalletService, reachabilityMonitor: ReachabilityMonitor, - nodesStorage: NodesStorageProtocol, apiServiceCompose: ApiServiceComposeProtocol ) { self.accountService = accountService @@ -334,7 +332,6 @@ class TransferViewControllerBase: FormViewController { self.walletService = walletService self.walletCore = walletService.core self.reachabilityMonitor = reachabilityMonitor - self.nodesStorage = nodesStorage self.apiServiceCompose = apiServiceCompose super.init(nibName: nil, bundle: nil) } diff --git a/CommonKit/Sources/CommonKit/AdamantSecret.swift b/CommonKit/Sources/CommonKit/AdamantSecret.swift deleted file mode 100644 index e75915d0f..000000000 --- a/CommonKit/Sources/CommonKit/AdamantSecret.swift +++ /dev/null @@ -1,14 +0,0 @@ -// -// AdamantSecret.swift -// -// -// Created by Andrew G on 21.08.2024. -// - -import Foundation - -enum AdamantSecret { - static let oldKeychainPass = "JonJonesWalksOutOfTheFuckingRoom" - static let keychainValuePassword = "JonJonesWalksOutOfTheFuckingRoom" - static let appIdentifierPrefix = "JonJonesWalksOutOfTheFuckingRoom" -} diff --git a/CommonKit/Sources/CommonKit/Helpers/Atomic.swift b/CommonKit/Sources/CommonKit/Helpers/Atomic.swift index 38bf3473e..cff52971d 100644 --- a/CommonKit/Sources/CommonKit/Helpers/Atomic.swift +++ b/CommonKit/Sources/CommonKit/Helpers/Atomic.swift @@ -17,33 +17,42 @@ import Foundation /// In order to ensure you've acquired the lock for a certain amount of time use the `mutate` method. @propertyWrapper public final class Atomic: @unchecked Sendable { - private var value: Value + private var _value: Value private let lock = NSLock() public var projectedValue: Atomic { self } public var wrappedValue: Value { + get { value } + set { value = newValue } + } + + public var value: Value { get { lock.lock() defer { lock.unlock() } - return value + return _value } set { lock.lock() defer { lock.unlock() } - value = newValue + _value = newValue } } - - public init(wrappedValue: Value) { - value = wrappedValue + + public init(_ value: Value) { + _value = value + } + + public convenience init(wrappedValue: Value) { + self.init(wrappedValue) } /// Synchronises mutation to ensure the value doesn't get changed by another thread during this mutation. public func mutate(_ mutation: (inout Value) -> Void) { lock.lock() defer { lock.unlock() } - mutation(&value) + mutation(&_value) } /// Synchronises mutation to ensure the value doesn't get changed by another thread during this mutation. @@ -51,7 +60,7 @@ public final class Atomic: @unchecked Sendable { public func mutate(_ mutation: (inout Value) -> T) -> T { lock.lock() defer { lock.unlock() } - return mutation(&value) + return mutation(&_value) } } diff --git a/CommonKit/Sources/CommonKit/Services/HealthCheck/HealthCheckWrapper.swift b/CommonKit/Sources/CommonKit/Services/HealthCheck/HealthCheckWrapper.swift index 7792fd402..077c92cbe 100644 --- a/CommonKit/Sources/CommonKit/Services/HealthCheck/HealthCheckWrapper.swift +++ b/CommonKit/Sources/CommonKit/Services/HealthCheck/HealthCheckWrapper.swift @@ -70,6 +70,12 @@ open class HealthCheckWrapper { } .store(in: &subscriptions) + $sortedAllowedNodes + .map { $0.isEmpty } + .removeDuplicates() + .sink { [weak self] _ in self?.updateHealthCheckTimerSubscription() } + .store(in: &subscriptions) + connection? .filter { $0 == true } .sink { [weak self] _ in self?.healthCheck() } @@ -136,21 +142,13 @@ private extension HealthCheckWrapper { func didBecomeActiveAction() { defer { previousAppState = .active } - switch previousAppState { - case .background: - guard - isActive, - let timeToUpdate = lastUpdateTime? - .addingTimeInterval(normalUpdateInterval / 3), - Date.now > timeToUpdate - else { break } - - healthCheck() - case .inactive, .active, .none: - break - @unknown default: - break - } + guard + previousAppState == .background, + let timeToUpdate = lastUpdateTime?.addingTimeInterval(normalUpdateInterval / 3), + Date.now > timeToUpdate + else { return } + + healthCheck() } } @@ -163,7 +161,9 @@ private extension Node { } private extension Sequence where Element == Node { - func doesNeedHealthCheck(_ nodes: Nodes) -> Bool where Nodes.Element == Self.Element { + 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) }) From 1c0508e5b3bb0cd64f4d8b71ec705f984d2961e6 Mon Sep 17 00:00:00 2001 From: just-software-dev Date: Tue, 3 Sep 2024 06:09:00 -0300 Subject: [PATCH 058/106] [trello.com/c/pUGfzAtU] Threads + other fixes --- Adamant/Helpers/Node+UI.swift | 3 +- .../View/CoinsNodesListView.swift | 1 + .../InfoService/InfoService+Constants.swift | 6 +-- .../Protocols/InfoServiceProtocol.swift | 1 + .../InfoService/Services/InfoService.swift | 54 +++++++++---------- .../ExtensionsTools/ExtensionsApi.swift | 19 +------ .../Sources/CommonKit/Helpers/Atomic.swift | 8 +-- .../CommonKit/Helpers/ConcurrencyQueue.swift | 46 ---------------- .../CommonKit/Helpers/Task+Extension.swift | 20 +++++++ .../HealthCheck/HealthCheckWrapper.swift | 8 +-- 10 files changed, 60 insertions(+), 106 deletions(-) delete mode 100644 CommonKit/Sources/CommonKit/Helpers/ConcurrencyQueue.swift diff --git a/Adamant/Helpers/Node+UI.swift b/Adamant/Helpers/Node+UI.swift index 5b731297f..2c13fa873 100644 --- a/Adamant/Helpers/Node+UI.swift +++ b/Adamant/Helpers/Node+UI.swift @@ -10,6 +10,7 @@ import CommonKit import UIKit extension Node { + // swiftlint:disable switch_case_alignment func statusString(showVersion: Bool, dateHeight: Bool) -> String? { guard isEnabled, @@ -123,7 +124,7 @@ private extension Node { } var dateHeightString: String? { - height.map { Date(timeIntervalSince1970: .init($0)).humanizedTime().string } + height.map { " ❐ \(Date(timeIntervalSince1970: .init($0)).humanizedTime().string)" } } var versionString: String? { diff --git a/Adamant/Modules/CoinsNodesList/View/CoinsNodesListView.swift b/Adamant/Modules/CoinsNodesList/View/CoinsNodesListView.swift index ac0ade64e..7b6b0a74a 100644 --- a/Adamant/Modules/CoinsNodesList/View/CoinsNodesListView.swift +++ b/Adamant/Modules/CoinsNodesList/View/CoinsNodesListView.swift @@ -75,6 +75,7 @@ private extension CoinsNodesListView { Section { Button(action: showResetAlert) { Text(String.adamant.coinsNodesList.reset) + .foregroundStyle(Color(uiColor: .adamant.textColor)) .expanded(axes: .horizontal) }.listRowBackground(Color(uiColor: .adamant.cellColor)) } diff --git a/Adamant/Modules/InfoService/InfoService+Constants.swift b/Adamant/Modules/InfoService/InfoService+Constants.swift index 4411b07d2..a0a23f1c1 100644 --- a/Adamant/Modules/InfoService/InfoService+Constants.swift +++ b/Adamant/Modules/InfoService/InfoService+Constants.swift @@ -9,7 +9,7 @@ import CommonKit extension InfoService { - static let healthCheckParameters = CoinHealthCheckParameters( + nonisolated static let healthCheckParameters = CoinHealthCheckParameters( normalUpdateInterval: 210, crucialUpdateInterval: 30, onScreenUpdateInterval: 10, @@ -19,11 +19,11 @@ extension InfoService { onScreenServiceUpdateInterval: .infinity ) - static var symbol: String { + nonisolated static var symbol: String { .localized("InfoService.InfoService") } - static var nodes: [Node] { + nonisolated static var nodes: [Node] { [.makeDefaultNode(url: .init(string: "https://info2.adm.im")!)] } } diff --git a/Adamant/Modules/InfoService/Protocols/InfoServiceProtocol.swift b/Adamant/Modules/InfoService/Protocols/InfoServiceProtocol.swift index f2e8e0664..68f64b0e1 100644 --- a/Adamant/Modules/InfoService/Protocols/InfoServiceProtocol.swift +++ b/Adamant/Modules/InfoService/Protocols/InfoServiceProtocol.swift @@ -43,6 +43,7 @@ enum Currency: String { } // MARK: - protocol +@MainActor protocol InfoServiceProtocol: AnyObject { var currentCurrency: Currency { get set } diff --git a/Adamant/Modules/InfoService/Services/InfoService.swift b/Adamant/Modules/InfoService/Services/InfoService.swift index 6d3eb136c..93b69eeb1 100644 --- a/Adamant/Modules/InfoService/Services/InfoService.swift +++ b/Adamant/Modules/InfoService/Services/InfoService.swift @@ -10,24 +10,25 @@ import Combine import CommonKit import UIKit +@MainActor final class InfoService: InfoServiceProtocol { typealias Rates = [InfoServiceTicker: Decimal] private let securedStore: SecuredStore private let api: InfoServiceApiServiceProtocol private let rateCoins: [String] - private let queue = ConcurrencyQueue() - @Atomic private var rates = Rates() - @Atomic private var currentCurrencyValue: Currency = .default - @Atomic private var subscriptions = Set() + private var rates = Rates() + private var currentCurrencyValue: Currency = .default + private var subscriptions = Set() + private var isUpdating = false var currentCurrency: Currency { get { currentCurrencyValue } set { updateCurrency(newValue) } } - init( + nonisolated init( securedStore: SecuredStore, walletServiceCompose: WalletServiceCompose, api: InfoServiceApiServiceProtocol @@ -35,19 +36,16 @@ final class InfoService: InfoServiceProtocol { self.securedStore = securedStore self.api = api rateCoins = walletServiceCompose.getWallets().map { $0.core.tokenSymbol } - setupCurrency() - setupObservers() + Task { @MainActor in configure() } } func update() { - Task { [weak self, rateCoins] in - let rates = await self?.queue.perform { - try? await self?.api.loadRates(coins: rateCoins).get() - } - - guard let rates = rates else { return } - self?.rates = rates - self?.sendRatesChangedNotification() + Task { + guard !isUpdating else { return } + defer { isUpdating = false } + guard let newRates = try? await api.loadRates(coins: rateCoins).get() else { return } + rates = newRates + sendRatesChangedNotification() } } @@ -68,6 +66,15 @@ final class InfoService: InfoServiceProtocol { } private extension InfoService { + func configure() { + setupCurrency() + + NotificationCenter.default + .publisher(for: UIApplication.didBecomeActiveNotification) + .sink { _ in Task { [weak self] in self?.update() } } + .store(in: &subscriptions) + } + func sendRatesChangedNotification() { NotificationCenter.default.post( name: .AdamantCurrencyInfoService.currencyRatesUpdated, @@ -76,19 +83,10 @@ private extension InfoService { } func updateCurrency(_ newValue: Currency) { - $currentCurrencyValue.mutate { value in - guard newValue != value else { return } - value = newValue - securedStore.set(value.rawValue, for: StoreKey.CoinInfo.selectedCurrency) - sendRatesChangedNotification() - } - } - - func setupObservers() { - NotificationCenter.default - .publisher(for: UIApplication.didBecomeActiveNotification) - .sink { [weak self] _ in self?.update() } - .store(in: &subscriptions) + guard newValue != currentCurrencyValue else { return } + currentCurrencyValue = newValue + securedStore.set(currentCurrencyValue.rawValue, for: StoreKey.CoinInfo.selectedCurrency) + sendRatesChangedNotification() } func setupCurrency() { diff --git a/CommonKit/Sources/CommonKit/ExtensionsTools/ExtensionsApi.swift b/CommonKit/Sources/CommonKit/ExtensionsTools/ExtensionsApi.swift index 9ffc97860..9aadf6731 100644 --- a/CommonKit/Sources/CommonKit/ExtensionsTools/ExtensionsApi.swift +++ b/CommonKit/Sources/CommonKit/ExtensionsTools/ExtensionsApi.swift @@ -22,7 +22,7 @@ public final class ExtensionsApi { // MARK: Transactions public func getTransaction(by id: UInt64) -> Transaction? { - syncRequest { [apiService] in + Task.sync { [apiService] in try? await apiService.getTransaction(id: id).get() } } @@ -34,7 +34,7 @@ public final class ExtensionsApi { core: NativeAdamantCore, keypair: Keypair ) -> [String:ContactDescription]? { - let addressBookString = syncRequest { [apiService, addressBookKey] in + let addressBookString = Task.sync { [apiService, addressBookKey] in try? await apiService.get(key: addressBookKey, sender: address).get() } @@ -73,19 +73,4 @@ public final class ExtensionsApi { return nil } } - - private func syncRequest( - _ request: @Sendable @escaping () async -> T? - ) -> T? { - let result = Atomic(wrappedValue: nil) - let semaphore = DispatchSemaphore(value: .zero) - - Task.detached { - result.wrappedValue = await request() - semaphore.signal() - } - - semaphore.wait() - return result.wrappedValue - } } diff --git a/CommonKit/Sources/CommonKit/Helpers/Atomic.swift b/CommonKit/Sources/CommonKit/Helpers/Atomic.swift index cff52971d..4b83208df 100644 --- a/CommonKit/Sources/CommonKit/Helpers/Atomic.swift +++ b/CommonKit/Sources/CommonKit/Helpers/Atomic.swift @@ -48,15 +48,9 @@ public final class Atomic: @unchecked Sendable { self.init(wrappedValue) } - /// Synchronises mutation to ensure the value doesn't get changed by another thread during this mutation. - public func mutate(_ mutation: (inout Value) -> Void) { - lock.lock() - defer { lock.unlock() } - mutation(&_value) - } - /// Synchronises mutation to ensure the value doesn't get changed by another thread during this mutation. /// This method returns a value specified in the `mutation` closure. + @discardableResult public func mutate(_ mutation: (inout Value) -> T) -> T { lock.lock() defer { lock.unlock() } diff --git a/CommonKit/Sources/CommonKit/Helpers/ConcurrencyQueue.swift b/CommonKit/Sources/CommonKit/Helpers/ConcurrencyQueue.swift deleted file mode 100644 index 76b9dbfb9..000000000 --- a/CommonKit/Sources/CommonKit/Helpers/ConcurrencyQueue.swift +++ /dev/null @@ -1,46 +0,0 @@ -// -// ConcurrencyQueue.swift -// -// -// Created by Andrew G on 24.08.2024. -// - -import Foundation - -public actor ConcurrencyQueue { - private var inputCounter: Int = .zero - private var outputCounter: Int = .zero - private var queue: [Int: () -> Void] = .init() - - public func perform(action: @escaping () async -> Output) async -> Output { - inputCounter += 1 - let id = inputCounter - let result = await action() - - return await withTaskCancellationHandler( - operation: { - await withCheckedContinuation { continuation in - enqueue(id: id) { continuation.resume(returning: result) } - } - }, - onCancel: { - Task { await enqueue(id: id, action: {}) } - } - ) - } - - public init() {} -} - -private extension ConcurrencyQueue { - func enqueue(id: Int, action: @escaping () -> Void) { - guard id == outputCounter + 1 else { return queue[id] = action } - action() - outputCounter += 1 - - while let action = queue.removeValue(forKey: outputCounter + 1) { - action() - outputCounter += 1 - } - } -} diff --git a/CommonKit/Sources/CommonKit/Helpers/Task+Extension.swift b/CommonKit/Sources/CommonKit/Helpers/Task+Extension.swift index ccf203117..c73276b4a 100644 --- a/CommonKit/Sources/CommonKit/Helpers/Task+Extension.swift +++ b/CommonKit/Sources/CommonKit/Helpers/Task+Extension.swift @@ -12,4 +12,24 @@ public extension Task where Success == Never, Failure == Never { static func sleep(interval: TimeInterval) async { try? await Task.sleep(nanoseconds: UInt64(interval * 1_000_000_000)) } + + /// Avoid using it. It lowers performance due to changing threads. + @discardableResult + static func sync(_ action: @Sendable @escaping () async -> T) -> T { + _sync(action) + } +} + +@discardableResult +private func _sync(_ action: @Sendable @escaping () async -> T) -> T { + let result = Atomic(wrappedValue: nil) + let semaphore = DispatchSemaphore(value: .zero) + + Task { + result.value = await action() + semaphore.signal() + } + + semaphore.wait() + return result.value! } diff --git a/CommonKit/Sources/CommonKit/Services/HealthCheck/HealthCheckWrapper.swift b/CommonKit/Sources/CommonKit/Services/HealthCheck/HealthCheckWrapper.swift index 077c92cbe..140c0acf6 100644 --- a/CommonKit/Sources/CommonKit/Services/HealthCheck/HealthCheckWrapper.swift +++ b/CommonKit/Sources/CommonKit/Services/HealthCheck/HealthCheckWrapper.swift @@ -95,14 +95,14 @@ open class HealthCheckWrapper { public func request( _ request: @Sendable (Service, NodeOrigin) async -> Result ) async -> Result { - var lastConnectionError = sortedAllowedNodes.isEmpty - ? Error.noEndpointsError(nodeGroupName: name) - : nil - let nodesList = fastestNodeMode ? sortedAllowedNodes : sortedAllowedNodes.shuffled() + var lastConnectionError = nodesList.isEmpty + ? Error.noEndpointsError(nodeGroupName: name) + : nil + for node in nodesList { let response = await request(service, node.preferredOrigin) From 0705cf2def8c2142093ecca880c7b07b9f5904cf Mon Sep 17 00:00:00 2001 From: just-software-dev Date: Tue, 3 Sep 2024 07:20:19 -0300 Subject: [PATCH 059/106] [trello.com/c/pUGfzAtU] Removed extra health check on start --- .../ExtensionsApiFactory.swift | 4 +- .../CommonKit/Models/Node/NodeOrigin.swift | 2 +- .../BlockchainHealthCheckWrapper.swift | 10 +-- .../HealthCheck/HealthCheckWrapper.swift | 66 ++++++++++--------- .../CommonKit/Services/NodesStorage.swift | 53 +++++++-------- 5 files changed, 65 insertions(+), 70 deletions(-) diff --git a/CommonKit/Sources/CommonKit/ExtensionsTools/ExtensionsApiFactory.swift b/CommonKit/Sources/CommonKit/ExtensionsTools/ExtensionsApiFactory.swift index d29083070..d62b61826 100644 --- a/CommonKit/Sources/CommonKit/ExtensionsTools/ExtensionsApiFactory.swift +++ b/CommonKit/Sources/CommonKit/ExtensionsTools/ExtensionsApiFactory.swift @@ -5,6 +5,8 @@ // Created by Andrew G on 08.08.2024. // +import Combine + public struct ExtensionsApiFactory { public let core: AdamantCore public let securedStore: SecuredStore @@ -35,7 +37,7 @@ public struct ExtensionsApiFactory { minNodeVersion: nil, nodeHeightEpsilon: .zero ), - connection: nil + connection: Just(true).eraseToAnyPublisher() ), adamantCore: core )) diff --git a/CommonKit/Sources/CommonKit/Models/Node/NodeOrigin.swift b/CommonKit/Sources/CommonKit/Models/Node/NodeOrigin.swift index 1174f46d3..b59447547 100644 --- a/CommonKit/Sources/CommonKit/Models/Node/NodeOrigin.swift +++ b/CommonKit/Sources/CommonKit/Models/Node/NodeOrigin.swift @@ -7,7 +7,7 @@ import Foundation -public struct NodeOrigin: Codable, Equatable { +public struct NodeOrigin: Codable, Equatable, Hashable { public var scheme: URLScheme public var host: String public var port: Int? diff --git a/CommonKit/Sources/CommonKit/Services/HealthCheck/BlockchainHealthCheckWrapper.swift b/CommonKit/Sources/CommonKit/Services/HealthCheck/BlockchainHealthCheckWrapper.swift index c8f2569ee..b859b4ec2 100644 --- a/CommonKit/Sources/CommonKit/Services/HealthCheck/BlockchainHealthCheckWrapper.swift +++ b/CommonKit/Sources/CommonKit/Services/HealthCheck/BlockchainHealthCheckWrapper.swift @@ -29,7 +29,7 @@ public final class BlockchainHealthCheckWrapper< nodesAdditionalParamsStorage: NodesAdditionalParamsStorageProtocol, isActive: Bool, params: BlockchainHealthCheckParams, - connection: AnyObservable? + connection: AnyObservable ) { self.nodesStorage = nodesStorage self.params = params @@ -40,14 +40,10 @@ public final class BlockchainHealthCheckWrapper< name: params.name, normalUpdateInterval: params.normalUpdateInterval, crucialUpdateInterval: params.crucialUpdateInterval, - connection: connection + connection: connection, + nodes: nodesStorage.getNodesPublisher(group: params.group) ) - nodesStorage - .getNodesPublisher(group: params.group) - .sink { [weak self] in self?.nodes = $0 } - .store(in: &subscriptions) - nodesAdditionalParamsStorage .fastestNodeMode(group: params.group) .sink { [weak self] in self?.fastestNodeMode = $0 } diff --git a/CommonKit/Sources/CommonKit/Services/HealthCheck/HealthCheckWrapper.swift b/CommonKit/Sources/CommonKit/Services/HealthCheck/HealthCheckWrapper.swift index 140c0acf6..57011de65 100644 --- a/CommonKit/Sources/CommonKit/Services/HealthCheck/HealthCheckWrapper.swift +++ b/CommonKit/Sources/CommonKit/Services/HealthCheck/HealthCheckWrapper.swift @@ -17,7 +17,8 @@ public protocol HealthCheckableError: Error { } open class HealthCheckWrapper { - @ObservableValue public var nodes: [Node] = .init() + @ObservableValue public private(set) var nodes: [Node] = .init() + @ObservableValue public private(set) var sortedAllowedNodes: [Node] = .init() public let service: Service public let isActive: Bool @@ -33,8 +34,6 @@ open class HealthCheckWrapper { @Atomic private var previousAppState: UIApplication.State? @Atomic private var lastUpdateTime: Date? - @ObservableValue public private(set) var sortedAllowedNodes: [Node] = .init() - public var chosenFastestNodeId: UUID? { fastestNodeMode ? sortedAllowedNodes.first?.id @@ -47,7 +46,8 @@ open class HealthCheckWrapper { name: String, normalUpdateInterval: TimeInterval, crucialUpdateInterval: TimeInterval, - connection: AnyObservable? + connection: AnyObservable, + nodes: AnyObservable<[Node]> ) { self.service = service self.isActive = isActive @@ -55,19 +55,19 @@ open class HealthCheckWrapper { self.normalUpdateInterval = normalUpdateInterval self.crucialUpdateInterval = crucialUpdateInterval - $nodes + let connection = connection + .removeDuplicates() + .filter { $0 } + + nodes .removeDuplicates { !$0.doesNeedHealthCheck($1) } + .combineLatest(connection) .sink { [weak self] _ in self?.healthCheck() } .store(in: &subscriptions) - $nodes + nodes .removeDuplicates() - .sink { [weak self] in - self?.sortedAllowedNodes = $0.getAllowedNodes( - sortedBySpeedDescending: true, - needWS: false - ) - } + .sink { [weak self] in self?.updateNodes($0) } .store(in: &subscriptions) $sortedAllowedNodes @@ -76,11 +76,6 @@ open class HealthCheckWrapper { .sink { [weak self] _ in self?.updateHealthCheckTimerSubscription() } .store(in: &subscriptions) - connection? - .filter { $0 == true } - .sink { [weak self] _ in self?.healthCheck() } - .store(in: &subscriptions) - NotificationCenter.default .publisher(for: UIApplication.didBecomeActiveNotification, object: nil) .sink { [weak self] _ in self?.didBecomeActiveAction() } @@ -150,13 +145,14 @@ private extension HealthCheckWrapper { healthCheck() } -} - -private extension Node { - func doesNeedHealthCheck(_ node: Node) -> Bool { - mainOrigin != node.mainOrigin || - altOrigin != node.altOrigin || - isEnabled != node.isEnabled + + func updateNodes(_ newNodes: [Node]) { + nodes = newNodes + + sortedAllowedNodes = newNodes.getAllowedNodes( + sortedBySpeedDescending: true, + needWS: false + ) } } @@ -164,13 +160,19 @@ 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 - } + Set(self.map { NodeComparisonInfo(node: $0) }) + != Set(nodes.map { NodeComparisonInfo(node: $0) }) + } +} + +private struct NodeComparisonInfo: Hashable { + let mainOrigin: NodeOrigin + let altOrigin: NodeOrigin? + let isEnabled: Bool + + init(node: Node) { + mainOrigin = node.mainOrigin + altOrigin = node.altOrigin + isEnabled = node.isEnabled } } diff --git a/CommonKit/Sources/CommonKit/Services/NodesStorage.swift b/CommonKit/Sources/CommonKit/Services/NodesStorage.swift index 7ef9a3e48..980b0734e 100644 --- a/CommonKit/Sources/CommonKit/Services/NodesStorage.swift +++ b/CommonKit/Sources/CommonKit/Services/NodesStorage.swift @@ -10,7 +10,7 @@ import Foundation import Combine public final class NodesStorage: NodesStorageProtocol { - @Atomic private var items: ObservableValue<[NodeGroup: [Node]]> = .init(wrappedValue: .init()) + @Atomic private var items: ObservableValue<[NodeGroup: [Node]]> public var nodesPublisher: AnyObservable<[NodeGroup: [Node]]> { items @@ -21,7 +21,6 @@ public final class NodesStorage: NodesStorageProtocol { private var subscription: AnyCancellable? private let securedStore: SecuredStore - private let nodesMergingService: NodesMergingServiceProtocol private let defaultNodes: [NodeGroup: [Node]] public func getNodesPublisher(group: NodeGroup) -> AnyObservable<[Node]> { @@ -95,54 +94,50 @@ public final class NodesStorage: NodesStorageProtocol { defaultNodes: [NodeGroup: [Node]] ) { self.securedStore = securedStore - self.nodesMergingService = nodesMergingService self.defaultNodes = defaultNodes - setupNodes() - } -} - -private extension NodesStorage { - func saveNodes(nodes: [NodeGroup: [Node]]) { - let nodesDto = nodes.mapValues { $0.map { $0.mapToDto() } } - securedStore.set(nodesDto, for: StoreKey.NodesStorage.nodes) - } - - func setupNodes() { + let dto: SafeDecodingDictionary< NodeGroup, SafeDecodingArray >? = securedStore.get(StoreKey.NodesStorage.nodes) let savedNodes = dto?.values.mapValues { $0.map { $0.mapToModel() } } - ?? migrateOldNodesData() + ?? migrateOldNodesData(securedStore: securedStore) ?? .init() - items.wrappedValue = nodesMergingService.merge( + _items = .init(.init(wrappedValue: nodesMergingService.merge( savedNodes: savedNodes, defaultNodes: defaultNodes - ) + ))) subscription = items.removeDuplicates().sink { [weak self] in guard let self = self else { return } saveNodes(nodes: $0) } } +} + +private extension NodesStorage { + func saveNodes(nodes: [NodeGroup: [Node]]) { + let nodesDto = nodes.mapValues { $0.map { $0.mapToDto() } } + securedStore.set(nodesDto, for: StoreKey.NodesStorage.nodes) + } +} + +private func migrateOldNodesData(securedStore: SecuredStore) -> [NodeGroup: [Node]]? { + let dto: SafeDecodingArray? = securedStore.get(StoreKey.NodesStorage.nodes) + guard let dto = dto else { return nil } + var result: [NodeGroup: [Node]] = [:] - func migrateOldNodesData() -> [NodeGroup: [Node]]? { - let dto: SafeDecodingArray? = securedStore.get(StoreKey.NodesStorage.nodes) - guard let dto = dto else { return nil } - var result: [NodeGroup: [Node]] = [:] - - dto.forEach { - if result[$0.group] == nil { - result[$0.group] = [] - } - - result[$0.group]?.append($0.node.mapToModernDto().mapToModel()) + dto.forEach { + if result[$0.group] == nil { + result[$0.group] = [] } - return result + result[$0.group]?.append($0.node.mapToModernDto().mapToModel()) } + + return result } private extension Node { From 4ef7fd453023b42aa683b7f542be839c70ae92a7 Mon Sep 17 00:00:00 2001 From: just-software-dev Date: Tue, 3 Sep 2024 07:52:03 -0300 Subject: [PATCH 060/106] [trello.com/c/pUGfzAtU] Alternative IP fixes --- Adamant/Helpers/Node+UI.swift | 4 ++++ .../CoinsNodesList/ViewModel/CoinsNodesListMapper.swift | 2 +- Adamant/Modules/NodesEditor/NodesListViewController.swift | 8 ++------ CommonKit/Sources/CommonKit/Models/Node/Node.swift | 4 ---- .../HealthCheck/BlockchainHealthCheckWrapper.swift | 7 +++++-- 5 files changed, 12 insertions(+), 13 deletions(-) diff --git a/Adamant/Helpers/Node+UI.swift b/Adamant/Helpers/Node+UI.swift index 2c13fa873..c30213be0 100644 --- a/Adamant/Helpers/Node+UI.swift +++ b/Adamant/Helpers/Node+UI.swift @@ -67,6 +67,10 @@ extension Node { return .adamant.inactive } } + + var title: String { + mainOrigin.asString() + } } private extension Node { diff --git a/Adamant/Modules/CoinsNodesList/ViewModel/CoinsNodesListMapper.swift b/Adamant/Modules/CoinsNodesList/ViewModel/CoinsNodesListMapper.swift index 379f709f6..f6e7f7868 100644 --- a/Adamant/Modules/CoinsNodesList/ViewModel/CoinsNodesListMapper.swift +++ b/Adamant/Modules/CoinsNodesList/ViewModel/CoinsNodesListMapper.swift @@ -51,7 +51,7 @@ private extension CoinsNodesListMapper { id: node.id, group: group, isEnabled: node.isEnabled, - title: node.asString(), + title: node.title, connectionStatus: indicatorAttrString, description: node.statusString( showVersion: true, diff --git a/Adamant/Modules/NodesEditor/NodesListViewController.swift b/Adamant/Modules/NodesEditor/NodesListViewController.swift index 3061d87f2..a7035a574 100644 --- a/Adamant/Modules/NodesEditor/NodesListViewController.swift +++ b/Adamant/Modules/NodesEditor/NodesListViewController.swift @@ -414,13 +414,9 @@ extension NodesListViewController { } private func makeNodeCellModel(node: Node) -> NodeCell.Model { - let connectionStatus = node.isEnabled - ? node.connectionStatus - : .none - - return .init( + .init( id: node.id, - title: node.asString(), + title: node.title, indicatorString: node.indicatorString( isRest: chosenFastestNodeId == node.id, isWs: currentSocketsNodeId == node.id diff --git a/CommonKit/Sources/CommonKit/Models/Node/Node.swift b/CommonKit/Sources/CommonKit/Models/Node/Node.swift index 7ccf18384..672ce6387 100644 --- a/CommonKit/Sources/CommonKit/Models/Node/Node.swift +++ b/CommonKit/Sources/CommonKit/Models/Node/Node.swift @@ -73,10 +73,6 @@ public extension Node { ) } - func asString() -> String { - preferredOrigin.asString() - } - func asSocketURL() -> URL? { preferredOrigin.asSocketURL() } diff --git a/CommonKit/Sources/CommonKit/Services/HealthCheck/BlockchainHealthCheckWrapper.swift b/CommonKit/Sources/CommonKit/Services/HealthCheck/BlockchainHealthCheckWrapper.swift index b859b4ec2..5e893ed1d 100644 --- a/CommonKit/Sources/CommonKit/Services/HealthCheck/BlockchainHealthCheckWrapper.swift +++ b/CommonKit/Sources/CommonKit/Services/HealthCheck/BlockchainHealthCheckWrapper.swift @@ -79,7 +79,10 @@ private extension BlockchainHealthCheckWrapper { updateNodesAvailability(forceInclude: forceInclude) } - guard node.preferMainOrigin == nil else { + guard + node.preferMainOrigin == nil, + let altOrigin = node.altOrigin + else { switch await updateNodeStatusInfo( id: node.id, origin: node.preferredOrigin, @@ -105,7 +108,7 @@ private extension BlockchainHealthCheckWrapper { case .failure: switch await updateNodeStatusInfo( id: node.id, - origin: node.mainOrigin, + origin: altOrigin, markAsOfflineIfFailed: true ) { case .success: From 64c7e08de3d477cd13931adb7c6c59a74b257608 Mon Sep 17 00:00:00 2001 From: just-software-dev Date: Tue, 3 Sep 2024 17:29:46 -0300 Subject: [PATCH 061/106] [trello.com/c/pUGfzAtU] Pushes fix --- CommonKit/Sources/CommonKit/ExtensionsTools/ExtensionsApi.swift | 2 +- .../Services/HealthCheck/BlockchainHealthCheckWrapper.swift | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/CommonKit/Sources/CommonKit/ExtensionsTools/ExtensionsApi.swift b/CommonKit/Sources/CommonKit/ExtensionsTools/ExtensionsApi.swift index 9aadf6731..afbea385c 100644 --- a/CommonKit/Sources/CommonKit/ExtensionsTools/ExtensionsApi.swift +++ b/CommonKit/Sources/CommonKit/ExtensionsTools/ExtensionsApi.swift @@ -23,7 +23,7 @@ public final class ExtensionsApi { // MARK: Transactions public func getTransaction(by id: UInt64) -> Transaction? { Task.sync { [apiService] in - try? await apiService.getTransaction(id: id).get() + try? await apiService.getTransaction(id: id, withAsset: true).get() } } diff --git a/CommonKit/Sources/CommonKit/Services/HealthCheck/BlockchainHealthCheckWrapper.swift b/CommonKit/Sources/CommonKit/Services/HealthCheck/BlockchainHealthCheckWrapper.swift index 5e893ed1d..490b67aa5 100644 --- a/CommonKit/Sources/CommonKit/Services/HealthCheck/BlockchainHealthCheckWrapper.swift +++ b/CommonKit/Sources/CommonKit/Services/HealthCheck/BlockchainHealthCheckWrapper.swift @@ -52,6 +52,7 @@ public final class BlockchainHealthCheckWrapper< public override func healthCheck() { super.healthCheck() + guard isActive else { return } Task { updateNodesAvailability() From 153b9059b4a2aa3d6dfbb2969f3c6491864321d6 Mon Sep 17 00:00:00 2001 From: just-software-dev Date: Tue, 3 Sep 2024 22:00:47 -0300 Subject: [PATCH 062/106] [trello.com/c/pUGfzAtU] Info service fix --- Adamant/Modules/InfoService/Services/InfoService.swift | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Adamant/Modules/InfoService/Services/InfoService.swift b/Adamant/Modules/InfoService/Services/InfoService.swift index 93b69eeb1..90b8df859 100644 --- a/Adamant/Modules/InfoService/Services/InfoService.swift +++ b/Adamant/Modules/InfoService/Services/InfoService.swift @@ -42,7 +42,9 @@ final class InfoService: InfoServiceProtocol { func update() { Task { guard !isUpdating else { return } + isUpdating = true defer { isUpdating = false } + guard let newRates = try? await api.loadRates(coins: rateCoins).get() else { return } rates = newRates sendRatesChangedNotification() From 8d0aaaeaf5ce3111a95eaf78e02979624c67a5d2 Mon Sep 17 00:00:00 2001 From: StanislavDevIOS Date: Wed, 4 Sep 2024 16:45:31 +0300 Subject: [PATCH 063/106] [trello.com/c/Qa3Dx6EI] Merge fix --- Adamant.xcodeproj/project.pbxproj | 5 ++--- Adamant/Helpers/NodeGroup+Constants.swift | 22 ++++++++++--------- .../Chat/View/Helpers/FileMessageStatus.swift | 2 +- .../FilesToolBarView/FilesToolbarView.swift | 2 +- .../CommonKit/AdamantDynamicResources.swift | 4 ++-- .../NotificationViewController.swift | 6 ++--- .../NotificationService.swift | 2 +- Podfile.lock | 2 +- .../NotificationViewController.swift | 4 ++-- 9 files changed, 25 insertions(+), 24 deletions(-) diff --git a/Adamant.xcodeproj/project.pbxproj b/Adamant.xcodeproj/project.pbxproj index 03742bd1a..deb924f3c 100644 --- a/Adamant.xcodeproj/project.pbxproj +++ b/Adamant.xcodeproj/project.pbxproj @@ -3090,7 +3090,6 @@ E9484B7A227CA93B008E10F0 /* BalanceTableViewCell.xib in Resources */, 6406D74A21C7F06000196713 /* SearchResultsViewController.xib in Resources */, 269B832E2C74B4EC002AA1D7 /* milestone.mp3 in Resources */, - E96D64CE2295C7F500CA5587 /* english.txt in Resources */, E9B4E1AA210F1803007E77FC /* DoubleDetailsTableViewCell.xib in Resources */, 269B832C2C74B4EC002AA1D7 /* rebound.mp3 in Resources */, 6458548C211B3AB1004C5909 /* WelcomeViewController.xib in Resources */, @@ -4057,7 +4056,7 @@ "$(inherited)", "@executable_path/Frameworks", ); - MARKETING_VERSION = 3.8.0; + MARKETING_VERSION = 3.9.0; PRODUCT_BUNDLE_IDENTIFIER = "im.adamant.adamant-messenger-dev"; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; @@ -4088,7 +4087,7 @@ "$(inherited)", "@executable_path/Frameworks", ); - MARKETING_VERSION = 3.8.0; + MARKETING_VERSION = 3.9.0; PRODUCT_BUNDLE_IDENTIFIER = "im.adamant.adamant-messenger"; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; diff --git a/Adamant/Helpers/NodeGroup+Constants.swift b/Adamant/Helpers/NodeGroup+Constants.swift index 83326e1a1..b96ee3be1 100644 --- a/Adamant/Helpers/NodeGroup+Constants.swift +++ b/Adamant/Helpers/NodeGroup+Constants.swift @@ -103,24 +103,26 @@ extension NodeGroup { // swiftlint:disable switch_case_alignment var minNodeVersion: Version? { - guard let version = switch self { + let version: String? + switch self { case .adm: - AdmWalletService.minNodeVersion + version = AdmWalletService.minNodeVersion case .btc: - BtcWalletService.minNodeVersion + version = BtcWalletService.minNodeVersion case .eth: - EthWalletService.minNodeVersion + version = EthWalletService.minNodeVersion case .klyNode: - KlyWalletService.minNodeVersion + version = KlyWalletService.minNodeVersion case .klyService: - KlyWalletService.minNodeVersion + version = KlyWalletService.minNodeVersion case .doge: - DogeWalletService.minNodeVersion + version = DogeWalletService.minNodeVersion case .dash: - DashWalletService.minNodeVersion + version = DashWalletService.minNodeVersion case .ipfs, .infoService: - nil - } else { return nil } + version = nil + } + guard let version = version else { return nil } return .init(version) } diff --git a/Adamant/Modules/Chat/View/Helpers/FileMessageStatus.swift b/Adamant/Modules/Chat/View/Helpers/FileMessageStatus.swift index f2055198e..697e6a294 100644 --- a/Adamant/Modules/Chat/View/Helpers/FileMessageStatus.swift +++ b/Adamant/Modules/Chat/View/Helpers/FileMessageStatus.swift @@ -31,7 +31,7 @@ enum FileMessageStatus: Equatable { var imageTintColor: UIColor { switch self { case .busy, .needToDownload, .success: return .adamant.primary - case .failed: return .adamant.alert + case .failed: return .adamant.attention } } } diff --git a/Adamant/Modules/Chat/View/Subviews/FilesToolBarView/FilesToolbarView.swift b/Adamant/Modules/Chat/View/Subviews/FilesToolBarView/FilesToolbarView.swift index 8fe5294ba..03b98f85c 100644 --- a/Adamant/Modules/Chat/View/Subviews/FilesToolBarView/FilesToolbarView.swift +++ b/Adamant/Modules/Chat/View/Subviews/FilesToolBarView/FilesToolbarView.swift @@ -41,7 +41,7 @@ final class FilesToolbarView: UIView { private lazy var closeBtn: UIButton = { let btn = UIButton() btn.setImage( - UIImage(systemName: "xmark")?.withTintColor(.adamant.alert), + UIImage(systemName: "xmark")?.withTintColor(.adamant.attention), for: .normal ) btn.addTarget(self, action: #selector(didTapCloseBtn), for: .touchUpInside) diff --git a/CommonKit/Sources/CommonKit/AdamantDynamicResources.swift b/CommonKit/Sources/CommonKit/AdamantDynamicResources.swift index 763eae145..6bcdac826 100644 --- a/CommonKit/Sources/CommonKit/AdamantDynamicResources.swift +++ b/CommonKit/Sources/CommonKit/AdamantDynamicResources.swift @@ -5,7 +5,7 @@ public extension AdamantResources { static var nodes: [Node] { [ Node.makeDefaultNode(url: URL(string: "https://clown.adamant.im")!), - Node.makeDefaultNode(url: URL(string: "https://lake.adamant.im")!), +Node.makeDefaultNode(url: URL(string: "https://lake.adamant.im")!), Node.makeDefaultNode(url: URL(string: "https://endless.adamant.im")!, altUrl: URL(string: "http://149.102.157.15:36666")), Node.makeDefaultNode(url: URL(string: "https://bid.adamant.im")!), Node.makeDefaultNode(url: URL(string: "https://unusual.adamant.im")!), @@ -22,4 +22,4 @@ Node.makeDefaultNode(url: URL(string: "https://dschubba.adm.im")!), ] } -} +} \ No newline at end of file diff --git a/MessageNotificationContentExtension/NotificationViewController.swift b/MessageNotificationContentExtension/NotificationViewController.swift index 6317fcca6..07e7cf269 100644 --- a/MessageNotificationContentExtension/NotificationViewController.swift +++ b/MessageNotificationContentExtension/NotificationViewController.swift @@ -18,8 +18,8 @@ class NotificationViewController: UIViewController, UNNotificationContentExtensi private let passphraseStoreKey = "accountService.passphrase" private let sizeWithoutMessageLabel: CGFloat = 123.0 - private lazy var securedStore: SecuredStore = { - KeychainStore(secureStorage: AdamantSecureStorage()) + private lazy var securedStore: SecureStorageProtocol = { + AdamantSecureStorage() }() // MARK: - IBOutlets @@ -46,7 +46,7 @@ class NotificationViewController: UIViewController, UNNotificationContentExtensi func didReceive(_ notification: UNNotification) { // MARK: 0. Necessary services let avatarService = AdamantAvatarService() - let keychainStore = KeychainStore() + let keychainStore = KeychainStore(secureStorage: securedStore) let nativeCore = NativeAdamantCore() let extensionApi = ExtensionsApiFactory( diff --git a/NotificationServiceExtension/NotificationService.swift b/NotificationServiceExtension/NotificationService.swift index 615363e54..e69db6a90 100644 --- a/NotificationServiceExtension/NotificationService.swift +++ b/NotificationServiceExtension/NotificationService.swift @@ -323,7 +323,7 @@ class NotificationService: UNNotificationServiceExtension { } } - private func getSound(securedStore: KeychainStore, isReaction: Bool) -> UNNotificationSound? { + private func getSound(securedStore: SecuredStore, isReaction: Bool) -> UNNotificationSound? { guard isReaction else { let sound: String = securedStore.get(StoreKey.notificationsService.notificationsSound) ?? .empty diff --git a/Podfile.lock b/Podfile.lock index 22fee2d0a..4e31a8ce2 100644 --- a/Podfile.lock +++ b/Podfile.lock @@ -21,4 +21,4 @@ SPEC CHECKSUMS: PODFILE CHECKSUM: a30619b79caa4b5a7497b0600d449f34b5620eec -COCOAPODS: 1.12.1 +COCOAPODS: 1.15.2 diff --git a/TransferNotificationContentExtension/NotificationViewController.swift b/TransferNotificationContentExtension/NotificationViewController.swift index caf0d6aef..fcb42c158 100644 --- a/TransferNotificationContentExtension/NotificationViewController.swift +++ b/TransferNotificationContentExtension/NotificationViewController.swift @@ -22,7 +22,7 @@ class NotificationViewController: UIViewController, UNNotificationContentExtensi return AdamantProvider() }() - private lazy var securedStore: SecuredStore = { + private lazy var keychain: SecuredStore = { KeychainStore(secureStorage: AdamantSecureStorage()) }() @@ -127,7 +127,7 @@ class NotificationViewController: UIViewController, UNNotificationContentExtensi let avatarService = AdamantAvatarService() let api = ExtensionsApiFactory(core: core, securedStore: keychain).make() - guard let passphrase: String = securedStore.get(passphraseStoreKey), + guard let passphrase: String = keychain.get(passphraseStoreKey), let keypair = core.createKeypairFor(passphrase: passphrase) else { showError() From efa681ca23605f93b864d2c7c68d698066e3f784 Mon Sep 17 00:00:00 2001 From: just-software-dev Date: Thu, 5 Sep 2024 05:56:39 -0300 Subject: [PATCH 064/106] [trello.com/c/pUGfzAtU] Health check race condition fix --- .../BlockchainHealthCheckWrapper.swift | 114 ++++++++---------- 1 file changed, 52 insertions(+), 62 deletions(-) diff --git a/CommonKit/Sources/CommonKit/Services/HealthCheck/BlockchainHealthCheckWrapper.swift b/CommonKit/Sources/CommonKit/Services/HealthCheck/BlockchainHealthCheckWrapper.swift index 490b67aa5..c0b020b7e 100644 --- a/CommonKit/Sources/CommonKit/Services/HealthCheck/BlockchainHealthCheckWrapper.swift +++ b/CommonKit/Sources/CommonKit/Services/HealthCheck/BlockchainHealthCheckWrapper.swift @@ -55,11 +55,17 @@ public final class BlockchainHealthCheckWrapper< guard isActive else { return } Task { - updateNodesAvailability() + updateNodesAvailability(update: nil) + await withTaskGroup(of: Void.self, returning: Void.self) { group in nodes.filter { $0.isEnabled }.forEach { node in group.addTask { [weak self] in - await self?.updateNodeStatusInfo(node: node) + guard + let self = self, + let update = await updateNodeStatusInfo(node: node) + else { return } + + updateNodesAvailability(update: update) } } @@ -70,80 +76,60 @@ public final class BlockchainHealthCheckWrapper< } private extension BlockchainHealthCheckWrapper { - func updateNodeStatusInfo(node: Node) async { - guard !currentRequests.contains(node.id) else { return } + struct NodeUpdate { + let id: UUID + let info: NodeStatusInfo? + let preferMainOrigin: Bool? + } + + func updateNodeStatusInfo(node: Node) async -> NodeUpdate? { + guard !currentRequests.contains(node.id) else { return nil } currentRequests.insert(node.id) - var forceInclude: UUID? - - defer { - currentRequests.remove(node.id) - updateNodesAvailability(forceInclude: forceInclude) - } + defer { currentRequests.remove(node.id) } guard node.preferMainOrigin == nil, let altOrigin = node.altOrigin else { - switch await updateNodeStatusInfo( + return .init( id: node.id, - origin: node.preferredOrigin, - markAsOfflineIfFailed: true - ) { - case .success: - forceInclude = node.id - case .failure, .none: - break - } - - return + info: try? await service.getStatusInfo(origin: node.preferredOrigin).get(), + preferMainOrigin: nil + ) } - switch await updateNodeStatusInfo( - id: node.id, - origin: node.mainOrigin, - markAsOfflineIfFailed: false - ) { - case .success: - nodesStorage.updateNode(id: node.id, group: params.group) { $0.preferMainOrigin = true } - forceInclude = node.id - case .failure: - switch await updateNodeStatusInfo( + switch await service.getStatusInfo(origin: node.mainOrigin) { + case let .success(info): + return .init( id: node.id, - origin: altOrigin, - markAsOfflineIfFailed: true - ) { - case .success: - nodesStorage.updateNode(id: node.id, group: params.group) { $0.preferMainOrigin = false } - forceInclude = node.id - case .failure, .none: - break + info: info, + preferMainOrigin: true + ) + case .failure: + switch await service.getStatusInfo(origin: altOrigin) { + case let .success(info): + return .init( + id: node.id, + info: info, + preferMainOrigin: false + ) + case .failure: + return .init( + id: node.id, + info: nil, + preferMainOrigin: nil + ) } - case .none: - break } } - @discardableResult - func updateNodeStatusInfo( - id: UUID, - origin: NodeOrigin, - markAsOfflineIfFailed: Bool - ) async -> Result? { - switch await service.getStatusInfo(origin: origin) { - case let .success(info): - applyStatusInfo(id: id, info: info) - return .success(()) - case let .failure(error): - if markAsOfflineIfFailed { - updateNode(id: id) { $0.connectionStatus = .offline } + func applyUpdate(update: NodeUpdate) { + updateNode(id: update.id) { node in + if let preferMainOrigin = update.preferMainOrigin { + node.preferMainOrigin = preferMainOrigin } - return .failure(error) - } - } - - func applyStatusInfo(id: UUID, info: NodeStatusInfo) { - updateNode(id: id) { node in + guard let info = update.info else { return node.connectionStatus = .offline } node.wsEnabled = info.wsEnabled node.updateWsPort(info.wsPort) node.version = info.version @@ -160,12 +146,16 @@ private extension BlockchainHealthCheckWrapper { } } - func updateNodesAvailability(forceInclude: UUID? = nil) { + func updateNodesAvailability(update: NodeUpdate?) { updateNodesAvailabilityLock.lock() defer { updateNodesAvailabilityLock.unlock() } + if let update = update { + applyUpdate(update: update) + } + let workingNodes = nodes.filter { - $0.isEnabled && ($0.isWorkingStatus || $0.id == forceInclude) + $0.isEnabled && ($0.isWorkingStatus) } let actualHeightsRange = getActualNodeHeightsRange( From c0486e59f92483952b6db18b7bcfc980bac3f3ce Mon Sep 17 00:00:00 2001 From: just-software-dev Date: Thu, 5 Sep 2024 07:04:58 -0300 Subject: [PATCH 065/106] [trello.com/c/pUGfzAtU] Info service dirty hack --- .../Protocols/InfoServiceProtocol.swift | 2 +- .../Services/InfoServiceMapper.swift | 33 +++++++++++++++---- 2 files changed, 27 insertions(+), 8 deletions(-) diff --git a/Adamant/Modules/InfoService/Protocols/InfoServiceProtocol.swift b/Adamant/Modules/InfoService/Protocols/InfoServiceProtocol.swift index 68f64b0e1..14aabc11d 100644 --- a/Adamant/Modules/InfoService/Protocols/InfoServiceProtocol.swift +++ b/Adamant/Modules/InfoService/Protocols/InfoServiceProtocol.swift @@ -22,7 +22,7 @@ extension StoreKey { } // MARK: - Currencies -enum Currency: String { +enum Currency: String, CaseIterable { case RUB = "RUB" case USD = "USD" case EUR = "EUR" diff --git a/Adamant/Modules/InfoService/Services/InfoServiceMapper.swift b/Adamant/Modules/InfoService/Services/InfoServiceMapper.swift index 2c800fd79..dc8b4212f 100644 --- a/Adamant/Modules/InfoService/Services/InfoServiceMapper.swift +++ b/Adamant/Modules/InfoService/Services/InfoServiceMapper.swift @@ -10,6 +10,8 @@ import Foundation import CommonKit struct InfoServiceMapper: InfoServiceMapperProtocol { + private let currencies = Set(Currency.allCases.map { $0.rawValue }) + func mapToModel(_ dto: InfoServiceStatusDTO) -> InfoServiceStatus { .init( lastUpdated: dto.last_updated.map { @@ -71,21 +73,38 @@ struct InfoServiceMapper: InfoServiceMapperProtocol { private extension InfoServiceMapper { func mapToTickers(_ rawTickers: [String: Decimal]) -> [InfoServiceTicker: Decimal] { - .init(uniqueKeysWithValues: rawTickers.compactMap { key, value in - mapToTicker(key).map { ($0, value) } - }) + // TODO: info service server is so messed up so we have to do this dirty hack + + var dict = [InfoServiceTicker: (Decimal, maybeMessedUp: Bool)]() + + for raw in rawTickers { + guard + let ticker = mapToTicker(raw.key), + dict[ticker.ticker]?.maybeMessedUp ?? true + else { continue } + + dict[ticker.ticker] = (raw.value, maybeMessedUp: ticker.maybeMessedUp) + } + + return dict.mapValues { $0.0 } } - func mapToTicker(_ string: String) -> InfoServiceTicker? { + func mapToTicker(_ string: String) -> (maybeMessedUp: Bool, ticker: InfoServiceTicker)? { + // TODO: info service server is so messed up so we have to do this dirty hack + let list: [String] = string.split(separator: "/").map { .init($0) } guard list.count == 2, - let crypto = list.first, - let fiat = list.last + let first = list.first, + let last = list.last else { return nil } - return .init(crypto: crypto, fiat: fiat) + return currencies.contains(last) + ? (maybeMessedUp: false, .init(crypto: first, fiat: last)) + : currencies.contains(first) + ? (maybeMessedUp: true, .init(crypto: last, fiat: first)) + : nil } func mapResponseDTO( From 415665efcd5f4345a06ca43a945589027a24ee57 Mon Sep 17 00:00:00 2001 From: just-software-dev Date: Thu, 5 Sep 2024 08:38:02 -0300 Subject: [PATCH 066/106] [trello.com/c/pUGfzAtU] Fixed migration --- .../Helpers/Node+NodeKeychainDTO.swift | 4 +-- .../CommonKit/Helpers/SafeDecodingArray.swift | 2 +- .../Models/Keychain/NodeKeychainDTO.swift | 20 ------------ .../Models/Keychain/NodesKeychainDTO.swift | 30 +++++++++++++++++ .../Models/Keychain/OldNodeKeychainDTO.swift | 32 +++++++++---------- .../CommonKit/Services/NodesStorage.swift | 9 ++---- 6 files changed, 52 insertions(+), 45 deletions(-) delete mode 100644 CommonKit/Sources/CommonKit/Models/Keychain/NodeKeychainDTO.swift create mode 100644 CommonKit/Sources/CommonKit/Models/Keychain/NodesKeychainDTO.swift diff --git a/CommonKit/Sources/CommonKit/Helpers/Node+NodeKeychainDTO.swift b/CommonKit/Sources/CommonKit/Helpers/Node+NodeKeychainDTO.swift index 5a9b98261..d9196c714 100644 --- a/CommonKit/Sources/CommonKit/Helpers/Node+NodeKeychainDTO.swift +++ b/CommonKit/Sources/CommonKit/Helpers/Node+NodeKeychainDTO.swift @@ -5,7 +5,7 @@ // Created by Andrew G on 28.07.2024. // -public extension Node { +extension Node { func mapToDto() -> NodeKeychainDTO { .init( mainOrigin: mainOrigin, @@ -21,7 +21,7 @@ public extension Node { } } -public extension NodeKeychainDTO { +extension NodeKeychainDTO { func mapToModel() -> Node { .init( id: .init(), diff --git a/CommonKit/Sources/CommonKit/Helpers/SafeDecodingArray.swift b/CommonKit/Sources/CommonKit/Helpers/SafeDecodingArray.swift index 877cbd026..f3898e692 100644 --- a/CommonKit/Sources/CommonKit/Helpers/SafeDecodingArray.swift +++ b/CommonKit/Sources/CommonKit/Helpers/SafeDecodingArray.swift @@ -15,7 +15,7 @@ public struct SafeDecodingArray { extension SafeDecodingArray: Sequence { public typealias Element = T - public typealias Iterator = IndexingIterator> + public typealias Iterator = IndexingIterator<[Element]> public func makeIterator() -> Iterator { values.makeIterator() diff --git a/CommonKit/Sources/CommonKit/Models/Keychain/NodeKeychainDTO.swift b/CommonKit/Sources/CommonKit/Models/Keychain/NodeKeychainDTO.swift deleted file mode 100644 index d5cde63c0..000000000 --- a/CommonKit/Sources/CommonKit/Models/Keychain/NodeKeychainDTO.swift +++ /dev/null @@ -1,20 +0,0 @@ -// -// NodeKeychainDTO.swift -// -// -// Created by Andrew G on 28.07.2024. -// - -import Foundation - -public struct NodeKeychainDTO: Codable { - public let mainOrigin: NodeOrigin - public let altOrigin: NodeOrigin? - public let wsEnabled: Bool - public let isEnabled: Bool - public let version: String? - public let height: Int? - public let ping: TimeInterval? - public let connectionStatus: NodeConnectionStatus? - public let type: NodeType -} diff --git a/CommonKit/Sources/CommonKit/Models/Keychain/NodesKeychainDTO.swift b/CommonKit/Sources/CommonKit/Models/Keychain/NodesKeychainDTO.swift new file mode 100644 index 000000000..4ddc87d26 --- /dev/null +++ b/CommonKit/Sources/CommonKit/Models/Keychain/NodesKeychainDTO.swift @@ -0,0 +1,30 @@ +// +// NodesKeychainDTO.swift +// +// +// Created by Andrew G on 05.09.2024. +// + +import Foundation + +struct NodesKeychainDTO: Codable { + let version: String + let data: SafeDecodingDictionary> + + init(_ data: [NodeGroup: [NodeKeychainDTO]]) { + self.version = "1.0.0" + self.data = .init(data.mapValues { .init($0) }) + } +} + +struct NodeKeychainDTO: Codable { + let mainOrigin: NodeOrigin + let altOrigin: NodeOrigin? + let wsEnabled: Bool + let isEnabled: Bool + let version: String? + let height: Int? + let ping: TimeInterval? + let connectionStatus: NodeConnectionStatus? + let type: NodeType +} diff --git a/CommonKit/Sources/CommonKit/Models/Keychain/OldNodeKeychainDTO.swift b/CommonKit/Sources/CommonKit/Models/Keychain/OldNodeKeychainDTO.swift index dc1950455..fa4b0e689 100644 --- a/CommonKit/Sources/CommonKit/Models/Keychain/OldNodeKeychainDTO.swift +++ b/CommonKit/Sources/CommonKit/Models/Keychain/OldNodeKeychainDTO.swift @@ -8,28 +8,28 @@ import Foundation // TODO: delete after a few updates. It's used for migration. -public struct OldNodeKeychainDTO: Codable { - public let group: NodeGroup - public let node: NodeData +struct OldNodeKeychainDTO: Codable { + let group: NodeGroup + let node: NodeData } -public extension OldNodeKeychainDTO { +extension OldNodeKeychainDTO { struct NodeData: Codable { - public let id: UUID - public let scheme: URLScheme - public let host: String - public let isEnabled: Bool - public let wsEnabled: Bool - public let port: Int? - public let wsPort: Int? - public let version: String? - public let height: Int? - public let ping: TimeInterval? - public let connectionStatus: ConnectionStatus? + let id: UUID + let scheme: URLScheme + let host: String + let isEnabled: Bool + let wsEnabled: Bool + let port: Int? + let wsPort: Int? + let version: String? + let height: Int? + let ping: TimeInterval? + let connectionStatus: ConnectionStatus? } } -public extension OldNodeKeychainDTO.NodeData { +extension OldNodeKeychainDTO.NodeData { enum RejectedReason: Codable, Equatable { case outdatedApiVersion } diff --git a/CommonKit/Sources/CommonKit/Services/NodesStorage.swift b/CommonKit/Sources/CommonKit/Services/NodesStorage.swift index 980b0734e..e5b4a0834 100644 --- a/CommonKit/Sources/CommonKit/Services/NodesStorage.swift +++ b/CommonKit/Sources/CommonKit/Services/NodesStorage.swift @@ -96,12 +96,9 @@ public final class NodesStorage: NodesStorageProtocol { self.securedStore = securedStore self.defaultNodes = defaultNodes - let dto: SafeDecodingDictionary< - NodeGroup, - SafeDecodingArray - >? = securedStore.get(StoreKey.NodesStorage.nodes) + let dto: NodesKeychainDTO? = securedStore.get(StoreKey.NodesStorage.nodes) - let savedNodes = dto?.values.mapValues { $0.map { $0.mapToModel() } } + let savedNodes = dto?.data.values.mapValues { $0.map { $0.mapToModel() } } ?? migrateOldNodesData(securedStore: securedStore) ?? .init() @@ -119,7 +116,7 @@ public final class NodesStorage: NodesStorageProtocol { private extension NodesStorage { func saveNodes(nodes: [NodeGroup: [Node]]) { - let nodesDto = nodes.mapValues { $0.map { $0.mapToDto() } } + let nodesDto = NodesKeychainDTO(nodes.mapValues { $0.map { $0.mapToDto() } }) securedStore.set(nodesDto, for: StoreKey.NodesStorage.nodes) } } From 82db9b071b4b3f3ecf1bdba7f9c44b42e78d3150 Mon Sep 17 00:00:00 2001 From: just-software-dev Date: Thu, 5 Sep 2024 08:44:43 -0300 Subject: [PATCH 067/106] [trello.com/c/pUGfzAtU] Removed info service workaround --- .../Services/InfoServiceMapper.swift | 30 +++++++------------ 1 file changed, 10 insertions(+), 20 deletions(-) diff --git a/Adamant/Modules/InfoService/Services/InfoServiceMapper.swift b/Adamant/Modules/InfoService/Services/InfoServiceMapper.swift index dc8b4212f..29f0ba3d3 100644 --- a/Adamant/Modules/InfoService/Services/InfoServiceMapper.swift +++ b/Adamant/Modules/InfoService/Services/InfoServiceMapper.swift @@ -73,38 +73,28 @@ struct InfoServiceMapper: InfoServiceMapperProtocol { private extension InfoServiceMapper { func mapToTickers(_ rawTickers: [String: Decimal]) -> [InfoServiceTicker: Decimal] { - // TODO: info service server is so messed up so we have to do this dirty hack - - var dict = [InfoServiceTicker: (Decimal, maybeMessedUp: Bool)]() + var dict = [InfoServiceTicker: Decimal]() for raw in rawTickers { - guard - let ticker = mapToTicker(raw.key), - dict[ticker.ticker]?.maybeMessedUp ?? true - else { continue } - - dict[ticker.ticker] = (raw.value, maybeMessedUp: ticker.maybeMessedUp) + guard let ticker = mapToTicker(raw.key) else { continue } + dict[ticker] = raw.value } - return dict.mapValues { $0.0 } + return dict } - func mapToTicker(_ string: String) -> (maybeMessedUp: Bool, ticker: InfoServiceTicker)? { - // TODO: info service server is so messed up so we have to do this dirty hack - + func mapToTicker(_ string: String) -> InfoServiceTicker? { let list: [String] = string.split(separator: "/").map { .init($0) } guard list.count == 2, - let first = list.first, - let last = list.last + let crypto = list.first, + let fiat = list.last else { return nil } - return currencies.contains(last) - ? (maybeMessedUp: false, .init(crypto: first, fiat: last)) - : currencies.contains(first) - ? (maybeMessedUp: true, .init(crypto: last, fiat: first)) - : nil + return currencies.contains(fiat) + ? .init(crypto: crypto, fiat: fiat) + : nil } func mapResponseDTO( From 3cb518a60354982e0fc9a019088782cc4710ca17 Mon Sep 17 00:00:00 2001 From: Iana Date: Mon, 9 Sep 2024 12:40:47 +0300 Subject: [PATCH 068/106] [trello.com/c/44KDaoe5] Color adjustments and localization --- .../Notifications/NotificationSoundsView.swift | 2 +- .../Notifications/NotificationsFactory.swift | 10 ++++++---- .../Settings/Notifications/NotificationsView.swift | 14 ++++++++++++-- 3 files changed, 19 insertions(+), 7 deletions(-) diff --git a/Adamant/Modules/Settings/Notifications/NotificationSoundsView.swift b/Adamant/Modules/Settings/Notifications/NotificationSoundsView.swift index 5bacc2bab..5cee99cf4 100644 --- a/Adamant/Modules/Settings/Notifications/NotificationSoundsView.swift +++ b/Adamant/Modules/Settings/Notifications/NotificationSoundsView.swift @@ -102,7 +102,7 @@ private extension NotificationSoundsView { Spacer() if viewModel.selectedSound == sound { Image(systemName: "checkmark") - .foregroundColor(.black) + .foregroundColor(Color(uiColor: .adamant.textColor)) .frame(width: 30, height: 30) } } diff --git a/Adamant/Modules/Settings/Notifications/NotificationsFactory.swift b/Adamant/Modules/Settings/Notifications/NotificationsFactory.swift index e84405452..2058e46da 100644 --- a/Adamant/Modules/Settings/Notifications/NotificationsFactory.swift +++ b/Adamant/Modules/Settings/Notifications/NotificationsFactory.swift @@ -10,15 +10,17 @@ import Swinject import SwiftUI struct NotificationsFactory { - private let assembler: Assembler + private let parent: Assembler + private let assemblies = [NotificationsAssembly()] init(parent: Assembler) { - assembler = .init([NotificationsAssembly()], parent: parent) + self.parent = parent } @MainActor func makeViewController(screensFactory: ScreensFactory) -> UIViewController { - let viewModel = assembler.resolve(NotificationsViewModel.self)! + let assembler = Assembler(assemblies, parent: parent) + let viewModel = { assembler.resolver.resolve(NotificationsViewModel.self)! } let view = NotificationsView( viewModel: viewModel, @@ -38,6 +40,6 @@ private struct NotificationsAssembly: Assembly { dialogService: r.resolve(DialogService.self)!, notificationsService: r.resolve(NotificationsService.self)! ) - }.inObjectScope(.weak) + }.inObjectScope(.transient) } } diff --git a/Adamant/Modules/Settings/Notifications/NotificationsView.swift b/Adamant/Modules/Settings/Notifications/NotificationsView.swift index b3ef406a9..cbf8ac925 100644 --- a/Adamant/Modules/Settings/Notifications/NotificationsView.swift +++ b/Adamant/Modules/Settings/Notifications/NotificationsView.swift @@ -10,9 +10,17 @@ import SwiftUI import CommonKit struct NotificationsView: View { - @ObservedObject var viewModel: NotificationsViewModel + @StateObject var viewModel: NotificationsViewModel var screensFactory: ScreensFactory - + + init( + viewModel: @escaping () -> NotificationsViewModel, + screensFactory: ScreensFactory + ) { + _viewModel = .init(wrappedValue: viewModel()) + self.screensFactory = screensFactory + } + var body: some View { GeometryReader { geometry in Form { @@ -23,6 +31,8 @@ struct NotificationsView: View { settingsSection() moreDetailsSection() } + .withoutListBackground() + .background(Color(.adamant.secondBackgroundColor)) .navigationBarTitleDisplayMode(.inline) .toolbar { ToolbarItem(placement: .principal) { From baed02b481fdc621259a13befbed8bdda2d7511c Mon Sep 17 00:00:00 2001 From: Iana Date: Mon, 9 Sep 2024 20:22:32 +0300 Subject: [PATCH 069/106] [trello.com/c/Kv0L5rYH] Feat: Add read/unread quick action --- .../ChatsList/ChatListViewController.swift | 22 ++-- .../AdmWalletService+DynamicConstants.swift | 5 +- .../BtcWalletService+DynamicConstants.swift | 6 +- .../DogeWalletService+DynamicConstants.swift | 1 + .../EthWalletService+DynamicConstants.swift | 6 +- .../CommonKit/Models/ethereumTokensList.swift | 116 +++++++++--------- Podfile.lock | 2 +- 7 files changed, 84 insertions(+), 74 deletions(-) diff --git a/Adamant/Modules/ChatsList/ChatListViewController.swift b/Adamant/Modules/ChatsList/ChatListViewController.swift index 220cd8bb7..ffed0e2bd 100644 --- a/Adamant/Modules/ChatsList/ChatListViewController.swift +++ b/Adamant/Modules/ChatsList/ChatListViewController.swift @@ -1211,13 +1211,18 @@ extension ChatListViewController { return } + let closeAction: (() -> Void)? = { [completionHandler] in + completionHandler(true) + } + let share = self.makeShareAction( for: address, encodedAddress: encodedAddress, - sender: view + sender: view, + completion: closeAction ) - let rename = self.makeRenameAction(for: address) + let rename = self.makeRenameAction(for: address, completion: closeAction) let cancel = self.makeCancelAction() self.dialogService.showAlert( @@ -1227,8 +1232,6 @@ extension ChatListViewController { actions: [share, rename, cancel], from: .view(view) ) - - completionHandler(true) } more.image = .asset(named: "swipe_more") @@ -1239,7 +1242,8 @@ extension ChatListViewController { private func makeShareAction( for address: String, encodedAddress: String, - sender: UIView + sender: UIView, + completion: (() -> Void)? = nil ) -> UIAlertAction { .init( title: ShareType.share.localized, @@ -1261,12 +1265,15 @@ extension ChatListViewController { excludedActivityTypes: ShareContentType.address.excludedActivityTypes, animated: true, from: sender, - completion: nil + completion: completion ) } } - private func makeRenameAction(for address: String) -> UIAlertAction { + private func makeRenameAction( + for address: String, + completion: (() -> Void)? = nil + ) -> UIAlertAction { .init( title: .adamant.chat.rename, style: .default @@ -1274,6 +1281,7 @@ extension ChatListViewController { guard let alert = self?.makeRenameAlert(for: address) else { return } self?.dialogService.present(alert, animated: true) { self?.dialogService.selectAllTextFields(in: alert) + completion?() } } } diff --git a/Adamant/Modules/Wallets/Adamant/AdmWalletService+DynamicConstants.swift b/Adamant/Modules/Wallets/Adamant/AdmWalletService+DynamicConstants.swift index ef16ecc4b..d52aa1793 100644 --- a/Adamant/Modules/Wallets/Adamant/AdmWalletService+DynamicConstants.swift +++ b/Adamant/Modules/Wallets/Adamant/AdmWalletService+DynamicConstants.swift @@ -15,8 +15,8 @@ extension AdmWalletService { onScreenUpdateInterval: 10, threshold: 10, normalServiceUpdateInterval: 300, - crucialServiceUpdateInterval: 300, - onScreenServiceUpdateInterval: 300 + crucialServiceUpdateInterval: 30, + onScreenServiceUpdateInterval: 10 ) static var newPendingInterval: Int { @@ -88,6 +88,7 @@ Node.makeDefaultNode(url: URL(string: "https://dschubba.adm.im")!), static var serviceNodes: [Node] { [ Node.makeDefaultNode(url: URL(string: "https://info.adamant.im")!), +Node.makeDefaultNode(url: URL(string: "https://info2.adm.im")!), ] } } diff --git a/Adamant/Modules/Wallets/Bitcoin/BtcWalletService+DynamicConstants.swift b/Adamant/Modules/Wallets/Bitcoin/BtcWalletService+DynamicConstants.swift index 977b8abd5..1c325deb1 100644 --- a/Adamant/Modules/Wallets/Bitcoin/BtcWalletService+DynamicConstants.swift +++ b/Adamant/Modules/Wallets/Bitcoin/BtcWalletService+DynamicConstants.swift @@ -14,7 +14,7 @@ extension BtcWalletService { crucialUpdateInterval: 30, onScreenUpdateInterval: 10, threshold: 2, - normalServiceUpdateInterval: 360, + normalServiceUpdateInterval: 330, crucialServiceUpdateInterval: 30, onScreenServiceUpdateInterval: 10 ) @@ -75,8 +75,8 @@ extension BtcWalletService { static var nodes: [Node] { [ - Node.makeDefaultNode(url: URL(string: "https://btcnode1.adamant.im")!, altUrl: URL(string: "http://176.9.38.204:44099")), -Node.makeDefaultNode(url: URL(string: "https://btcnode3.adamant.im")!, altUrl: URL(string: "http://195.201.242.108:44099")), + Node.makeDefaultNode(url: URL(string: "https://btcnode1.adamant.im/bitcoind")!, altUrl: URL(string: "http://176.9.38.204:44099/bitcoind")), +Node.makeDefaultNode(url: URL(string: "https://btcnode3.adamant.im/bitcoind")!, altUrl: URL(string: "http://195.201.242.108:44099/bitcoind")), ] } diff --git a/Adamant/Modules/Wallets/Doge/DogeWalletService+DynamicConstants.swift b/Adamant/Modules/Wallets/Doge/DogeWalletService+DynamicConstants.swift index fd2449ad2..d940f4428 100644 --- a/Adamant/Modules/Wallets/Doge/DogeWalletService+DynamicConstants.swift +++ b/Adamant/Modules/Wallets/Doge/DogeWalletService+DynamicConstants.swift @@ -77,6 +77,7 @@ extension DogeWalletService { [ Node.makeDefaultNode(url: URL(string: "https://dogenode1.adamant.im")!, altUrl: URL(string: "http://5.9.99.62:44099")), Node.makeDefaultNode(url: URL(string: "https://dogenode2.adamant.im")!, altUrl: URL(string: "http://176.9.32.126:44098")), +Node.makeDefaultNode(url: URL(string: "https://dogenode3.adm.im")!, altUrl: URL(string: "http://95.216.45.88:44098")), ] } diff --git a/Adamant/Modules/Wallets/Ethereum/EthWalletService+DynamicConstants.swift b/Adamant/Modules/Wallets/Ethereum/EthWalletService+DynamicConstants.swift index f67891869..2e6db1660 100644 --- a/Adamant/Modules/Wallets/Ethereum/EthWalletService+DynamicConstants.swift +++ b/Adamant/Modules/Wallets/Ethereum/EthWalletService+DynamicConstants.swift @@ -14,7 +14,7 @@ extension EthWalletService { crucialUpdateInterval: 30, onScreenUpdateInterval: 10, threshold: 5, - normalServiceUpdateInterval: 300, + normalServiceUpdateInterval: 330, crucialServiceUpdateInterval: 30, onScreenServiceUpdateInterval: 10 ) @@ -48,7 +48,7 @@ extension EthWalletService { } var defaultGasPriceGwei: BigUInt { - 30 + 10 } var defaultGasLimit: BigUInt { @@ -56,7 +56,7 @@ extension EthWalletService { } var warningGasPriceGwei: BigUInt { - 70 + 25 } var tokenName: String { diff --git a/CommonKit/Sources/CommonKit/Models/ethereumTokensList.swift b/CommonKit/Sources/CommonKit/Models/ethereumTokensList.swift index ba9b9f614..defd1f647 100644 --- a/CommonKit/Sources/CommonKit/Models/ethereumTokensList.swift +++ b/CommonKit/Sources/CommonKit/Models/ethereumTokensList.swift @@ -12,9 +12,9 @@ defaultOrdinalLevel: nil, reliabilityGasPricePercent: 10, reliabilityGasLimitPercent: 10, - defaultGasPriceGwei: 30, + defaultGasPriceGwei: 10, defaultGasLimit: 58000, - warningGasPriceGwei: 70, + warningGasPriceGwei: 25, transferDecimals: 6), ERC20Token(symbol: "BUSD", name: "Binance USD", @@ -25,9 +25,9 @@ defaultOrdinalLevel: nil, reliabilityGasPricePercent: 10, reliabilityGasLimitPercent: 10, - defaultGasPriceGwei: 30, + defaultGasPriceGwei: 10, defaultGasLimit: 58000, - warningGasPriceGwei: 70, + warningGasPriceGwei: 25, transferDecimals: 6), ERC20Token(symbol: "BZZ", name: "Swarm", @@ -38,9 +38,9 @@ defaultOrdinalLevel: 95, reliabilityGasPricePercent: 10, reliabilityGasLimitPercent: 10, - defaultGasPriceGwei: 30, + defaultGasPriceGwei: 10, defaultGasLimit: 58000, - warningGasPriceGwei: 70, + warningGasPriceGwei: 25, transferDecimals: 6), ERC20Token(symbol: "DAI", name: "Dai", @@ -51,9 +51,9 @@ defaultOrdinalLevel: 80, reliabilityGasPricePercent: 10, reliabilityGasLimitPercent: 10, - defaultGasPriceGwei: 30, + defaultGasPriceGwei: 10, defaultGasLimit: 58000, - warningGasPriceGwei: 70, + warningGasPriceGwei: 25, transferDecimals: 6), ERC20Token(symbol: "ENS", name: "Ethereum Name Service", @@ -64,9 +64,9 @@ defaultOrdinalLevel: nil, reliabilityGasPricePercent: 10, reliabilityGasLimitPercent: 10, - defaultGasPriceGwei: 30, + defaultGasPriceGwei: 10, defaultGasLimit: 58000, - warningGasPriceGwei: 70, + warningGasPriceGwei: 25, transferDecimals: 6), ERC20Token(symbol: "FLOKI", name: "Floki", @@ -77,9 +77,9 @@ defaultOrdinalLevel: 100, reliabilityGasPricePercent: 10, reliabilityGasLimitPercent: 10, - defaultGasPriceGwei: 30, + defaultGasPriceGwei: 10, defaultGasLimit: 58000, - warningGasPriceGwei: 70, + warningGasPriceGwei: 25, transferDecimals: 6), ERC20Token(symbol: "FLUX", name: "Flux", @@ -90,9 +90,9 @@ defaultOrdinalLevel: 90, reliabilityGasPricePercent: 10, reliabilityGasLimitPercent: 10, - defaultGasPriceGwei: 30, + defaultGasPriceGwei: 10, defaultGasLimit: 58000, - warningGasPriceGwei: 70, + warningGasPriceGwei: 25, transferDecimals: 6), ERC20Token(symbol: "GT", name: "Gate", @@ -103,9 +103,9 @@ defaultOrdinalLevel: 115, reliabilityGasPricePercent: 10, reliabilityGasLimitPercent: 10, - defaultGasPriceGwei: 30, + defaultGasPriceGwei: 10, defaultGasLimit: 58000, - warningGasPriceGwei: 70, + warningGasPriceGwei: 25, transferDecimals: 6), ERC20Token(symbol: "HOT", name: "Holo", @@ -116,9 +116,9 @@ defaultOrdinalLevel: nil, reliabilityGasPricePercent: 10, reliabilityGasLimitPercent: 10, - defaultGasPriceGwei: 30, + defaultGasPriceGwei: 10, defaultGasLimit: 58000, - warningGasPriceGwei: 70, + warningGasPriceGwei: 25, transferDecimals: 6), ERC20Token(symbol: "INJ", name: "Injective", @@ -129,9 +129,9 @@ defaultOrdinalLevel: nil, reliabilityGasPricePercent: 10, reliabilityGasLimitPercent: 10, - defaultGasPriceGwei: 30, + defaultGasPriceGwei: 10, defaultGasLimit: 58000, - warningGasPriceGwei: 70, + warningGasPriceGwei: 25, transferDecimals: 6), ERC20Token(symbol: "LINK", name: "Chainlink", @@ -142,9 +142,9 @@ defaultOrdinalLevel: nil, reliabilityGasPricePercent: 10, reliabilityGasLimitPercent: 10, - defaultGasPriceGwei: 30, + defaultGasPriceGwei: 10, defaultGasLimit: 58000, - warningGasPriceGwei: 70, + warningGasPriceGwei: 25, transferDecimals: 6), ERC20Token(symbol: "MANA", name: "Decentraland", @@ -155,9 +155,9 @@ defaultOrdinalLevel: nil, reliabilityGasPricePercent: 10, reliabilityGasLimitPercent: 10, - defaultGasPriceGwei: 30, + defaultGasPriceGwei: 10, defaultGasLimit: 58000, - warningGasPriceGwei: 70, + warningGasPriceGwei: 25, transferDecimals: 6), ERC20Token(symbol: "MATIC", name: "Polygon", @@ -168,9 +168,9 @@ defaultOrdinalLevel: nil, reliabilityGasPricePercent: 10, reliabilityGasLimitPercent: 10, - defaultGasPriceGwei: 30, + defaultGasPriceGwei: 10, defaultGasLimit: 58000, - warningGasPriceGwei: 70, + warningGasPriceGwei: 25, transferDecimals: 6), ERC20Token(symbol: "PAXG", name: "PAX Gold", @@ -181,9 +181,9 @@ defaultOrdinalLevel: nil, reliabilityGasPricePercent: 10, reliabilityGasLimitPercent: 10, - defaultGasPriceGwei: 30, + defaultGasPriceGwei: 10, defaultGasLimit: 58000, - warningGasPriceGwei: 70, + warningGasPriceGwei: 25, transferDecimals: 6), ERC20Token(symbol: "QNT", name: "Quant", @@ -194,9 +194,9 @@ defaultOrdinalLevel: nil, reliabilityGasPricePercent: 10, reliabilityGasLimitPercent: 10, - defaultGasPriceGwei: 30, + defaultGasPriceGwei: 10, defaultGasLimit: 58000, - warningGasPriceGwei: 70, + warningGasPriceGwei: 25, transferDecimals: 6), ERC20Token(symbol: "REN", name: "Ren", @@ -207,9 +207,9 @@ defaultOrdinalLevel: nil, reliabilityGasPricePercent: 10, reliabilityGasLimitPercent: 10, - defaultGasPriceGwei: 30, + defaultGasPriceGwei: 10, defaultGasLimit: 58000, - warningGasPriceGwei: 70, + warningGasPriceGwei: 25, transferDecimals: 6), ERC20Token(symbol: "SKL", name: "SKALE", @@ -220,9 +220,9 @@ defaultOrdinalLevel: 85, reliabilityGasPricePercent: 10, reliabilityGasLimitPercent: 10, - defaultGasPriceGwei: 30, + defaultGasPriceGwei: 10, defaultGasLimit: 58000, - warningGasPriceGwei: 70, + warningGasPriceGwei: 25, transferDecimals: 6), ERC20Token(symbol: "SNT", name: "Status", @@ -233,9 +233,9 @@ defaultOrdinalLevel: nil, reliabilityGasPricePercent: 10, reliabilityGasLimitPercent: 10, - defaultGasPriceGwei: 30, + defaultGasPriceGwei: 10, defaultGasLimit: 58000, - warningGasPriceGwei: 70, + warningGasPriceGwei: 25, transferDecimals: 6), ERC20Token(symbol: "SNX", name: "Synthetix Network", @@ -246,9 +246,9 @@ defaultOrdinalLevel: nil, reliabilityGasPricePercent: 10, reliabilityGasLimitPercent: 10, - defaultGasPriceGwei: 30, + defaultGasPriceGwei: 10, defaultGasLimit: 58000, - warningGasPriceGwei: 70, + warningGasPriceGwei: 25, transferDecimals: 6), ERC20Token(symbol: "STORJ", name: "Storj", @@ -259,9 +259,9 @@ defaultOrdinalLevel: 105, reliabilityGasPricePercent: 10, reliabilityGasLimitPercent: 10, - defaultGasPriceGwei: 30, + defaultGasPriceGwei: 10, defaultGasLimit: 58000, - warningGasPriceGwei: 70, + warningGasPriceGwei: 25, transferDecimals: 6), ERC20Token(symbol: "TUSD", name: "TrueUSD", @@ -272,9 +272,9 @@ defaultOrdinalLevel: nil, reliabilityGasPricePercent: 10, reliabilityGasLimitPercent: 10, - defaultGasPriceGwei: 30, + defaultGasPriceGwei: 10, defaultGasLimit: 58000, - warningGasPriceGwei: 70, + warningGasPriceGwei: 25, transferDecimals: 6), ERC20Token(symbol: "UNI", name: "Uniswap", @@ -285,9 +285,9 @@ defaultOrdinalLevel: nil, reliabilityGasPricePercent: 10, reliabilityGasLimitPercent: 10, - defaultGasPriceGwei: 30, + defaultGasPriceGwei: 10, defaultGasLimit: 58000, - warningGasPriceGwei: 70, + warningGasPriceGwei: 25, transferDecimals: 6), ERC20Token(symbol: "USDC", name: "USD Coin", @@ -298,9 +298,9 @@ defaultOrdinalLevel: 40, reliabilityGasPricePercent: 10, reliabilityGasLimitPercent: 10, - defaultGasPriceGwei: 30, + defaultGasPriceGwei: 10, defaultGasLimit: 58000, - warningGasPriceGwei: 70, + warningGasPriceGwei: 25, transferDecimals: 6), ERC20Token(symbol: "USDP", name: "PAX Dollar", @@ -311,9 +311,9 @@ defaultOrdinalLevel: nil, reliabilityGasPricePercent: 10, reliabilityGasLimitPercent: 10, - defaultGasPriceGwei: 30, + defaultGasPriceGwei: 10, defaultGasLimit: 58000, - warningGasPriceGwei: 70, + warningGasPriceGwei: 25, transferDecimals: 6), ERC20Token(symbol: "USDS", name: "Stably USD", @@ -324,9 +324,9 @@ defaultOrdinalLevel: nil, reliabilityGasPricePercent: 10, reliabilityGasLimitPercent: 10, - defaultGasPriceGwei: 30, + defaultGasPriceGwei: 10, defaultGasLimit: 58000, - warningGasPriceGwei: 70, + warningGasPriceGwei: 25, transferDecimals: 6), ERC20Token(symbol: "USDT", name: "Tether", @@ -337,9 +337,9 @@ defaultOrdinalLevel: 30, reliabilityGasPricePercent: 10, reliabilityGasLimitPercent: 10, - defaultGasPriceGwei: 30, + defaultGasPriceGwei: 10, defaultGasLimit: 58000, - warningGasPriceGwei: 70, + warningGasPriceGwei: 25, transferDecimals: 6), ERC20Token(symbol: "VERSE", name: "Verse", @@ -350,9 +350,9 @@ defaultOrdinalLevel: 95, reliabilityGasPricePercent: 10, reliabilityGasLimitPercent: 10, - defaultGasPriceGwei: 30, + defaultGasPriceGwei: 10, defaultGasLimit: 58000, - warningGasPriceGwei: 70, + warningGasPriceGwei: 25, transferDecimals: 6), ERC20Token(symbol: "WOO", name: "WOO Network", @@ -363,9 +363,9 @@ defaultOrdinalLevel: nil, reliabilityGasPricePercent: 10, reliabilityGasLimitPercent: 10, - defaultGasPriceGwei: 30, + defaultGasPriceGwei: 10, defaultGasLimit: 58000, - warningGasPriceGwei: 70, + warningGasPriceGwei: 25, transferDecimals: 6), ERC20Token(symbol: "XCN", name: "Onyxcoin", @@ -376,9 +376,9 @@ defaultOrdinalLevel: 110, reliabilityGasPricePercent: 10, reliabilityGasLimitPercent: 10, - defaultGasPriceGwei: 30, + defaultGasPriceGwei: 10, defaultGasLimit: 58000, - warningGasPriceGwei: 70, + warningGasPriceGwei: 25, transferDecimals: 6), ] diff --git a/Podfile.lock b/Podfile.lock index 4e31a8ce2..22fee2d0a 100644 --- a/Podfile.lock +++ b/Podfile.lock @@ -21,4 +21,4 @@ SPEC CHECKSUMS: PODFILE CHECKSUM: a30619b79caa4b5a7497b0600d449f34b5620eec -COCOAPODS: 1.15.2 +COCOAPODS: 1.12.1 From a24b63605d5bf1a3ab23acfd62e3c32cf23066cd Mon Sep 17 00:00:00 2001 From: Iana Date: Tue, 10 Sep 2024 18:16:16 +0300 Subject: [PATCH 070/106] [trello.com/c/GJ5CEZSz] fix: dates not update --- Adamant/Modules/ChatsList/ChatListViewController.swift | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/Adamant/Modules/ChatsList/ChatListViewController.swift b/Adamant/Modules/ChatsList/ChatListViewController.swift index 220cd8bb7..9df505f97 100644 --- a/Adamant/Modules/ChatsList/ChatListViewController.swift +++ b/Adamant/Modules/ChatsList/ChatListViewController.swift @@ -355,8 +355,7 @@ final class ChatListViewController: KeyboardObservingViewController { /// If the user opens the app from the background and new chats are not loaded, /// update specific rows in the tableView to refresh the dates. private func refreshDatesIfNeeded() { - guard !Calendar.current.isDate(Date(), inSameDayAs: lastDatesUpdate), - !isBusy, + guard !isBusy, let indexPaths = tableView.indexPathsForVisibleRows else { return From 884a2f61adb6ab6951e6c37e4fd16e080cfad5ec Mon Sep 17 00:00:00 2001 From: Iana Date: Tue, 10 Sep 2024 18:42:38 +0300 Subject: [PATCH 071/106] [trello.com/c/Kv0L5rYH] Close menu when tapping Cancel --- .../Modules/ChatsList/ChatListViewController.swift | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/Adamant/Modules/ChatsList/ChatListViewController.swift b/Adamant/Modules/ChatsList/ChatListViewController.swift index ffed0e2bd..29bac1a17 100644 --- a/Adamant/Modules/ChatsList/ChatListViewController.swift +++ b/Adamant/Modules/ChatsList/ChatListViewController.swift @@ -1223,7 +1223,7 @@ extension ChatListViewController { ) let rename = self.makeRenameAction(for: address, completion: closeAction) - let cancel = self.makeCancelAction() + let cancel = self.makeCancelAction(completion: closeAction) self.dialogService.showAlert( title: nil, @@ -1320,8 +1320,13 @@ extension ChatListViewController { return alert } - private func makeCancelAction() -> UIAlertAction { - .init(title: .adamant.alert.cancel, style: .cancel, handler: nil) + private func makeCancelAction(completion: (() -> Void)? = nil) -> UIAlertAction { + .init( + title: .adamant.alert.cancel, + style: .cancel + ) { _ in + completion?() + } } } From a1462369d802654a4a733e11291f9561424daaeb Mon Sep 17 00:00:00 2001 From: Iana Date: Tue, 10 Sep 2024 20:38:10 +0300 Subject: [PATCH 072/106] [trello.com/c/bSmPAIM2] Feat: Show fixed date on scroll --- .../Chat/View/ChatViewController.swift | 39 ++++++++++++------- .../Chat/ViewModel/ChatViewModel.swift | 16 +++++--- 2 files changed, 36 insertions(+), 19 deletions(-) diff --git a/Adamant/Modules/Chat/View/ChatViewController.swift b/Adamant/Modules/Chat/View/ChatViewController.swift index db6af444a..888b5ad49 100644 --- a/Adamant/Modules/Chat/View/ChatViewController.swift +++ b/Adamant/Modules/Chat/View/ChatViewController.swift @@ -202,20 +202,7 @@ final class ChatViewController: MessagesViewController { super.scrollViewDidScroll(scrollView) updateIsScrollPositionNearlyTheBottom() updateScrollDownButtonVisibility() - - let targetY: CGFloat = 20 + view.safeAreaInsets.top - guard let visibleIndexPaths = messagesCollectionView.indexPathsForVisibleItems.sorted(by: { - $0.row < $1.row - }) as [IndexPath]? else { return } - for indexPath in visibleIndexPaths { - if let cell = messagesCollectionView.cellForItem(at: indexPath) { - let cellRect = messagesCollectionView.convert(cell.frame, to: self.view) - if cellRect.minY <= targetY && cellRect.maxY >= targetY { - viewModel.checkTopMessage(indexPath: indexPath) - break - } - } - } + updateDateHeaderIfNeeded() guard viewAppeared, @@ -713,6 +700,29 @@ private extension ChatViewController { func updateScrollDownButtonVisibility() { scrollDownButton.isHidden = isScrollPositionNearlyTheBottom } + + func updateDateHeaderIfNeeded() { + guard viewAppeared else { return } + + let targetY: CGFloat = targetYOffset + view.safeAreaInsets.top + guard let visibleIndexPaths = messagesCollectionView.indexPathsForVisibleItems.sorted(by: { + $0.row < $1.row + }) as [IndexPath]? else { return } + + for indexPath in visibleIndexPaths { + guard let cell = messagesCollectionView.cellForItem(at: indexPath) + else { continue } + + let cellRect = messagesCollectionView.convert(cell.frame, to: self.view) + + guard cellRect.minY <= targetY && cellRect.maxY >= targetY else { + continue + } + + viewModel.checkTopMessage(indexPath: indexPath) + break + } + } } // MARK: Making entities @@ -1076,3 +1086,4 @@ private var replyAction: Bool = false private var canReplyVibrate: Bool = true private var oldContentOffset: CGPoint? private let filesToolbarViewHeight: CGFloat = 140 +private let targetYOffset: CGFloat = 20 diff --git a/Adamant/Modules/Chat/ViewModel/ChatViewModel.swift b/Adamant/Modules/Chat/ViewModel/ChatViewModel.swift index 76f595625..3cb138fd4 100644 --- a/Adamant/Modules/Chat/ViewModel/ChatViewModel.swift +++ b/Adamant/Modules/Chat/ViewModel/ChatViewModel.swift @@ -65,6 +65,7 @@ final class ChatViewModel: NSObject { private(set) var chatroom: Chatroom? private(set) var chatTransactions: [ChatTransaction] = [] private var tempCancellables = Set() + private var timerCancellable: AnyCancellable? private let minDiffCountForOffset = 5 private let minDiffCountForAnimateScroll = 20 private let partnerImageSize: CGFloat = 25 @@ -72,7 +73,7 @@ final class ChatViewModel: NSObject { private var previousArg: ChatContextMenuArguments? private var lastDateHeaderUpdate: Date = Date() private var havePartnerName: Bool = false - private var dateTimer: Timer? + private let delayHideHeaderInSeconds: Double = 2.0 let minIndexForStartLoadNewMessages = 4 let minOffsetForStartLoadNewMessages: CGFloat = 100 @@ -1037,6 +1038,7 @@ extension ChatViewModel { dateHeader = date dateHeaderHidden = false } + func didEndScroll() { startHideDateTimer() } @@ -1050,10 +1052,14 @@ extension ChatViewModel: NSFetchedResultsControllerDelegate { private extension ChatViewModel { func startHideDateTimer() { - dateTimer?.invalidate() - dateTimer = Timer.scheduledTimer(withTimeInterval: 2.0, repeats: false) { [weak self] _ in - self?.dateHeaderHidden = true - } + timerCancellable?.cancel() + timerCancellable = Timer + .publish(every: delayHideHeaderInSeconds, on: .main, in: .common) + .autoconnect() + .first() + .sink { [weak self] _ in + self?.dateHeaderHidden = true + } } func sendFiles(with text: String) async throws { From 0c4890fbb095350920aaedf26edf16f1107701af Mon Sep 17 00:00:00 2001 From: Iana Date: Wed, 11 Sep 2024 14:39:48 +0300 Subject: [PATCH 073/106] [trello.com/c/44KDaoe5] Code refactoring --- Adamant.xcodeproj/project.pbxproj | 24 +- .../NotificationSoundsPickerView.swift | 27 -- .../NotificationSoundsFactory.swift | 16 +- .../NotificationSoundsView.swift | 2 +- .../NotificationSoundsViewModel.swift | 34 ++- .../NotificationSoundsViewController.swift | 139 --------- .../Notifications/NotificationsView.swift | 2 +- .../NotificationsViewModel.swift | 64 ++-- .../NotificationsViewController.swift | 288 ------------------ .../Modules/Settings/SettingsFactory.swift | 7 - .../NotificationService.swift | 20 +- 11 files changed, 81 insertions(+), 542 deletions(-) delete mode 100644 Adamant/Modules/Settings/Notifications/Helpers/NotificationSoundsPickerView.swift rename Adamant/Modules/Settings/Notifications/{ => NotificationSounds}/NotificationSoundsFactory.swift (64%) rename Adamant/Modules/Settings/Notifications/{ => NotificationSounds}/NotificationSoundsView.swift (98%) rename Adamant/Modules/Settings/Notifications/{ => NotificationSounds}/NotificationSoundsViewModel.swift (80%) delete mode 100644 Adamant/Modules/Settings/Notifications/NotificationSoundsViewController.swift delete mode 100644 Adamant/Modules/Settings/NotificationsViewController.swift diff --git a/Adamant.xcodeproj/project.pbxproj b/Adamant.xcodeproj/project.pbxproj index b982581ef..890c705f0 100644 --- a/Adamant.xcodeproj/project.pbxproj +++ b/Adamant.xcodeproj/project.pbxproj @@ -10,7 +10,6 @@ 2621AB372C60E74A00046D7A /* NotificationsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2621AB362C60E74A00046D7A /* NotificationsView.swift */; }; 2621AB392C60E7AE00046D7A /* NotificationsViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2621AB382C60E7AE00046D7A /* NotificationsViewModel.swift */; }; 2621AB3B2C613C8100046D7A /* NotificationsFactory.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2621AB3A2C613C8100046D7A /* NotificationsFactory.swift */; }; - 2657A0C92C7078750021E7E6 /* NotificationSoundsPickerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2657A0C82C7078750021E7E6 /* NotificationSoundsPickerView.swift */; }; 2657A0CA2C707D780021E7E6 /* notification.mp3 in Resources */ = {isa = PBXBuildFile; fileRef = E9A174B820587B83003667CD /* notification.mp3 */; }; 2657A0CB2C707D7B0021E7E6 /* so-proud-notification.mp3 in Resources */ = {isa = PBXBuildFile; fileRef = 4198D57A28C8B7DA009337F2 /* so-proud-notification.mp3 */; }; 2657A0CC2C707D7E0021E7E6 /* relax-message-tone.mp3 in Resources */ = {isa = PBXBuildFile; fileRef = 4198D57C28C8B7F9009337F2 /* relax-message-tone.mp3 */; }; @@ -162,7 +161,6 @@ 4164A9D928F17DA700EEF16D /* AdamantChatTransactionService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4164A9D828F17DA700EEF16D /* AdamantChatTransactionService.swift */; }; 416F5EA4290162EB00EF0400 /* SocketIO in Frameworks */ = {isa = PBXBuildFile; productRef = 416F5EA3290162EB00EF0400 /* SocketIO */; }; 4177E5E12A52DA7100C089FE /* AdvancedContextMenuKit in Frameworks */ = {isa = PBXBuildFile; productRef = 4177E5E02A52DA7100C089FE /* AdvancedContextMenuKit */; }; - 417BA7F428BF894F00DF94C5 /* NotificationSoundsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 417BA7F328BF894F00DF94C5 /* NotificationSoundsViewController.swift */; }; 4184F16E2A33023A00D7B8B9 /* GoogleService-Info.plist in Resources */ = {isa = PBXBuildFile; fileRef = 4184F16D2A33023A00D7B8B9 /* GoogleService-Info.plist */; }; 4184F1712A33044E00D7B8B9 /* FirebaseCrashlytics in Frameworks */ = {isa = PBXBuildFile; productRef = 4184F1702A33044E00D7B8B9 /* FirebaseCrashlytics */; }; 4184F1732A33102800D7B8B9 /* AdamantCrashlysticsService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4184F1722A33102800D7B8B9 /* AdamantCrashlysticsService.swift */; }; @@ -492,7 +490,6 @@ E90847372196FEA80095825D /* Chatroom+CoreDataProperties.swift in Sources */ = {isa = PBXBuildFile; fileRef = E90847292196FEA80095825D /* Chatroom+CoreDataProperties.swift */; }; E90847392196FEF50095825D /* BaseTransaction+TransactionDetails.swift in Sources */ = {isa = PBXBuildFile; fileRef = E90847382196FEF50095825D /* BaseTransaction+TransactionDetails.swift */; }; E908473B219707200095825D /* AccountViewController+StayIn.swift in Sources */ = {isa = PBXBuildFile; fileRef = E908473A219707200095825D /* AccountViewController+StayIn.swift */; }; - E908473D219713300095825D /* NotificationsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = E908473C219713300095825D /* NotificationsViewController.swift */; }; E90A4943204C5ED6009F6A65 /* EurekaPassphraseRow.swift in Sources */ = {isa = PBXBuildFile; fileRef = E90A4942204C5ED6009F6A65 /* EurekaPassphraseRow.swift */; }; E90A4945204C6204009F6A65 /* PassphraseCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = E90A4944204C5F60009F6A65 /* PassphraseCell.xib */; }; E90A494B204D9EB8009F6A65 /* AdamantAuthentication.swift in Sources */ = {isa = PBXBuildFile; fileRef = E90A494A204D9EB8009F6A65 /* AdamantAuthentication.swift */; }; @@ -732,7 +729,6 @@ 2621AB362C60E74A00046D7A /* NotificationsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationsView.swift; sourceTree = ""; }; 2621AB382C60E7AE00046D7A /* NotificationsViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationsViewModel.swift; sourceTree = ""; }; 2621AB3A2C613C8100046D7A /* NotificationsFactory.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationsFactory.swift; sourceTree = ""; }; - 2657A0C82C7078750021E7E6 /* NotificationSoundsPickerView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationSoundsPickerView.swift; sourceTree = ""; }; 265AA1612B74E6B900CF98B0 /* ChatPreservation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChatPreservation.swift; sourceTree = ""; }; 269B830F2C74A2FF002AA1D7 /* note.mp3 */ = {isa = PBXFileReference; lastKnownFileType = audio.mp3; path = note.mp3; sourceTree = ""; }; 269B83122C74B4EA002AA1D7 /* handoff.mp3 */ = {isa = PBXFileReference; lastKnownFileType = audio.mp3; path = handoff.mp3; sourceTree = ""; }; @@ -863,7 +859,6 @@ 416380E02A51765F00F90E6D /* ChatReactionsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChatReactionsView.swift; sourceTree = ""; }; 4164A9D628F17D4000EEF16D /* ChatTransactionService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChatTransactionService.swift; sourceTree = ""; }; 4164A9D828F17DA700EEF16D /* AdamantChatTransactionService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AdamantChatTransactionService.swift; sourceTree = ""; }; - 417BA7F328BF894F00DF94C5 /* NotificationSoundsViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationSoundsViewController.swift; sourceTree = ""; }; 4184F16D2A33023A00D7B8B9 /* GoogleService-Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = "GoogleService-Info.plist"; sourceTree = ""; }; 4184F1722A33102800D7B8B9 /* AdamantCrashlysticsService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AdamantCrashlysticsService.swift; sourceTree = ""; }; 4184F1742A33106200D7B8B9 /* CrashlysticsService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CrashlysticsService.swift; sourceTree = ""; }; @@ -1154,7 +1149,6 @@ E90847292196FEA80095825D /* Chatroom+CoreDataProperties.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Chatroom+CoreDataProperties.swift"; sourceTree = ""; }; E90847382196FEF50095825D /* BaseTransaction+TransactionDetails.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "BaseTransaction+TransactionDetails.swift"; sourceTree = ""; }; E908473A219707200095825D /* AccountViewController+StayIn.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "AccountViewController+StayIn.swift"; sourceTree = ""; }; - E908473C219713300095825D /* NotificationsViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationsViewController.swift; sourceTree = ""; }; E90A4942204C5ED6009F6A65 /* EurekaPassphraseRow.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EurekaPassphraseRow.swift; sourceTree = ""; }; E90A4944204C5F60009F6A65 /* PassphraseCell.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = PassphraseCell.xib; sourceTree = ""; }; E90A494A204D9EB8009F6A65 /* AdamantAuthentication.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AdamantAuthentication.swift; sourceTree = ""; }; @@ -1445,24 +1439,22 @@ 2621AB352C60E52900046D7A /* Notifications */ = { isa = PBXGroup; children = ( - 2657A0C72C7078630021E7E6 /* Helpers */, + 26F7EF3E2C9118E700E16A94 /* NotificationSounds */, 2621AB362C60E74A00046D7A /* NotificationsView.swift */, 2621AB382C60E7AE00046D7A /* NotificationsViewModel.swift */, 2621AB3A2C613C8100046D7A /* NotificationsFactory.swift */, - 417BA7F328BF894F00DF94C5 /* NotificationSoundsViewController.swift */, - 269B83362C74D1F9002AA1D7 /* NotificationSoundsView.swift */, - 269B83392C74D4AA002AA1D7 /* NotificationSoundsViewModel.swift */, - 269B833C2C74E661002AA1D7 /* NotificationSoundsFactory.swift */, ); path = Notifications; sourceTree = ""; }; - 2657A0C72C7078630021E7E6 /* Helpers */ = { + 26F7EF3E2C9118E700E16A94 /* NotificationSounds */ = { isa = PBXGroup; children = ( - 2657A0C82C7078750021E7E6 /* NotificationSoundsPickerView.swift */, + 269B83362C74D1F9002AA1D7 /* NotificationSoundsView.swift */, + 269B83392C74D4AA002AA1D7 /* NotificationSoundsViewModel.swift */, + 269B833C2C74E661002AA1D7 /* NotificationSoundsFactory.swift */, ); - path = Helpers; + path = NotificationSounds; sourceTree = ""; }; 3A20D9392AE7F305005475A6 /* Models */ = { @@ -2710,7 +2702,6 @@ E9942B7F203C058C00C163AF /* QRGeneratorViewController.swift */, E9484B7E2285C016008E10F0 /* PKGeneratorViewController.swift */, E9484B7C2285BAD8008E10F0 /* PrivateKeyGenerator.swift */, - E908473C219713300095825D /* NotificationsViewController.swift */, ); path = Settings; sourceTree = ""; @@ -3341,7 +3332,6 @@ 3AF9DF0B2BFE306C009A43A8 /* ChatFileProtocol.swift in Sources */, E987024920C2B1F700E393F4 /* AdamantChatsProvider+fakeMessages.swift in Sources */, 6403F5E222723F7500D58779 /* DashWallet.swift in Sources */, - 2657A0C92C7078750021E7E6 /* NotificationSoundsPickerView.swift in Sources */, 26A975FF2B7E843E0095C367 /* SelectTextView.swift in Sources */, 93294B822AAD0BB400911109 /* BtcWalletFactory.swift in Sources */, 648DD7A42237DB9E00B811FD /* DogeWalletService+Send.swift in Sources */, @@ -3437,7 +3427,6 @@ 645FEB34213E72C100D6BA2D /* OnboardViewController.swift in Sources */, 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 */, @@ -3638,7 +3627,6 @@ 418FDE502A25CA340055E3CD /* ChatMenuManager.swift in Sources */, E90055F520EBF5DA00D0CB2D /* AboutViewController.swift in Sources */, E965A53020B594120041A3EA /* AdamantApi+States.swift in Sources */, - E908473D219713300095825D /* NotificationsViewController.swift in Sources */, E908472E2196FEA80095825D /* BaseTransaction+CoreDataClass.swift in Sources */, 64D059FF20D3116B003AD655 /* NodesListViewController.swift in Sources */, E91E5BF220DAF05500B06B3C /* NodeCell.swift in Sources */, diff --git a/Adamant/Modules/Settings/Notifications/Helpers/NotificationSoundsPickerView.swift b/Adamant/Modules/Settings/Notifications/Helpers/NotificationSoundsPickerView.swift deleted file mode 100644 index afd61bac1..000000000 --- a/Adamant/Modules/Settings/Notifications/Helpers/NotificationSoundsPickerView.swift +++ /dev/null @@ -1,27 +0,0 @@ -// -// NotificationSoundsPickerView.swift -// Adamant -// -// Created by Yana Silosieva on 17.08.2024. -// Copyright © 2024 Adamant. All rights reserved. -// - -import SwiftUI - -struct NotificationSoundsPickerView: UIViewControllerRepresentable { - private let notificationService: NotificationsService - private let notificationTarget: NotificationTarget - - init(notificationService: NotificationsService, target: NotificationTarget) { - self.notificationService = notificationService - self.notificationTarget = target - } - - func makeUIViewController(context: Context) -> UINavigationController { - let vc = NotificationSoundsViewController(notificationsService: notificationService, target: notificationTarget) - let nav = UINavigationController(rootViewController: vc) - return nav - } - - func updateUIViewController(_ uiViewController: UINavigationController, context: Context) { } -} diff --git a/Adamant/Modules/Settings/Notifications/NotificationSoundsFactory.swift b/Adamant/Modules/Settings/Notifications/NotificationSounds/NotificationSoundsFactory.swift similarity index 64% rename from Adamant/Modules/Settings/Notifications/NotificationSoundsFactory.swift rename to Adamant/Modules/Settings/Notifications/NotificationSounds/NotificationSoundsFactory.swift index 6ffc0be5d..7e76f9210 100644 --- a/Adamant/Modules/Settings/Notifications/NotificationSoundsFactory.swift +++ b/Adamant/Modules/Settings/Notifications/NotificationSounds/NotificationSoundsFactory.swift @@ -10,15 +10,20 @@ import Swinject import SwiftUI struct NotificationSoundsFactory { - private let assembler: Assembler + private let parent: Assembler + private let assemblies = [NotificationSoundAssembly()] init(parent: Assembler) { - assembler = .init([NotificationSoundAssembly()], parent: parent) + self.parent = parent } @MainActor func makeView(target: NotificationTarget) -> NotificationSoundsView { - let viewModel = assembler.resolve(NotificationSoundsViewModel.self)! + let assembler = Assembler(assemblies, parent: parent) + let viewModel = { + assembler.resolver.resolve(NotificationSoundsViewModel.self)! + }() + viewModel.setup(notificationTarget: target) let view = NotificationSoundsView(viewModel: viewModel) @@ -31,8 +36,9 @@ private struct NotificationSoundAssembly: Assembly { container.register(NotificationSoundsViewModel.self) { r in NotificationSoundsViewModel( notificationsService: r.resolve(NotificationsService.self)!, - target: .baseMessage + target: .baseMessage, + dialogService: r.resolve(DialogService.self)! ) - } + }.inObjectScope(.transient) } } diff --git a/Adamant/Modules/Settings/Notifications/NotificationSoundsView.swift b/Adamant/Modules/Settings/Notifications/NotificationSounds/NotificationSoundsView.swift similarity index 98% rename from Adamant/Modules/Settings/Notifications/NotificationSoundsView.swift rename to Adamant/Modules/Settings/Notifications/NotificationSounds/NotificationSoundsView.swift index 5cee99cf4..697d036af 100644 --- a/Adamant/Modules/Settings/Notifications/NotificationSoundsView.swift +++ b/Adamant/Modules/Settings/Notifications/NotificationSounds/NotificationSoundsView.swift @@ -10,7 +10,7 @@ import SwiftUI import CommonKit struct NotificationSoundsView: View { - @ObservedObject var viewModel: NotificationSoundsViewModel + @StateObject var viewModel: NotificationSoundsViewModel @Environment(\.dismiss) var dismiss diff --git a/Adamant/Modules/Settings/Notifications/NotificationSoundsViewModel.swift b/Adamant/Modules/Settings/Notifications/NotificationSounds/NotificationSoundsViewModel.swift similarity index 80% rename from Adamant/Modules/Settings/Notifications/NotificationSoundsViewModel.swift rename to Adamant/Modules/Settings/Notifications/NotificationSounds/NotificationSoundsViewModel.swift index d6d924b74..4ee3bbc55 100644 --- a/Adamant/Modules/Settings/Notifications/NotificationSoundsViewModel.swift +++ b/Adamant/Modules/Settings/Notifications/NotificationSounds/NotificationSoundsViewModel.swift @@ -15,6 +15,7 @@ import AVFoundation final class NotificationSoundsViewModel: ObservableObject { private let notificationsService: NotificationsService private var notificationTarget: NotificationTarget + private let dialogService: DialogService private(set) var dismissAction = PassthroughSubject() @Published var isPresented: Bool = false @@ -25,10 +26,12 @@ final class NotificationSoundsViewModel: ObservableObject { nonisolated init( notificationsService: NotificationsService, - target: NotificationTarget + target: NotificationTarget, + dialogService: DialogService ) { self.notificationsService = notificationsService self.notificationTarget = target + self.dialogService = dialogService Task { @MainActor in switch notificationTarget { @@ -59,27 +62,34 @@ final class NotificationSoundsViewModel: ObservableObject { } func playSound(_ sound: NotificationSound) { - switch sound { - case .none: - break - default: - playSound(by: sound.fileName) - } + switch sound { + case .none: + break + default: + playSound(by: sound.fileName) } - - private func playSound(by fileName: String) { + } +} + +private extension NotificationSoundsViewModel { + func playSound(by fileName: String) { guard let url = Bundle.main.url(forResource: fileName.replacingOccurrences(of: ".mp3", with: ""), withExtension: "mp3") else { return } - + do { try AVAudioSession.sharedInstance().setCategory(.playback) try AVAudioSession.sharedInstance().setActive(true) audioPlayer = try AVAudioPlayer(contentsOf: url, fileTypeHint: AVFileType.mp3.rawValue) audioPlayer?.volume = 1.0 audioPlayer?.play() - } catch let error as NSError { - print("error: \(error.localizedDescription)") + } catch { + dialogService.showError( + withMessage: error.localizedDescription, + supportEmail: false, + error: error + ) } } } + diff --git a/Adamant/Modules/Settings/Notifications/NotificationSoundsViewController.swift b/Adamant/Modules/Settings/Notifications/NotificationSoundsViewController.swift deleted file mode 100644 index ce45ec60c..000000000 --- a/Adamant/Modules/Settings/Notifications/NotificationSoundsViewController.swift +++ /dev/null @@ -1,139 +0,0 @@ -// -// NotificationSoundsViewController.swift -// Adamant -// -// Created by Stanislav Jelezoglo on 31.08.2022. -// Copyright © 2022 Adamant. All rights reserved. -// - -import UIKit -import Eureka -import AudioToolbox -import AVFoundation - -final class NotificationSoundsViewController: FormViewController { - - // MARK: Sections & Rows - enum Sections { - case alerts - - var tag: String { - switch self { - case .alerts: return "al" - } - } - - var localized: String { - switch self { - case .alerts: return .localized("Notifications.Alert.Tones", comment: "Notifications: Select Alert Tones") - } - } - } - - private let notificationsService: NotificationsService - private let notificationTarget: NotificationTarget - - private var selectSound: NotificationSound = .inputDefault - private var section = SelectableSection>() - private var audioPlayer: AVAudioPlayer? - - init(notificationsService: NotificationsService, target: NotificationTarget) { - self.notificationsService = notificationsService - self.notificationTarget = target - super.init(nibName: nil, bundle: nil) - } - - required init?(coder aDecoder: NSCoder) { - fatalError("init(coder:) has not been implemented") - } - - override func viewDidLoad() { - super.viewDidLoad() - self.title = .localized("Notifications.Sounds.Name", comment: "Notifications: Select Sounds") - - switch notificationTarget { - case .baseMessage: - selectSound = notificationsService.notificationsSound - case .reaction: - selectSound = notificationsService.notificationsReactionSound - } - - section = SelectableSection>(Sections.alerts.localized, selectionType: .singleSelection(enableDeselection: false)) - - let sounds: [NotificationSound] = [.none, .noteDefault, .inputDefault, .proud, .relax, .success, .note, .antic, .cheers, .chord, .droplet, .handoff, .milestone, .passage, .portal, .rattle, .rebound, .slide, .welcome] - for sound in sounds { - section <<< ListCheckRow { listRow in - listRow.title = sound.localized - listRow.selectableValue = sound - if sound == selectSound { - listRow.value = sound - } else { - listRow.value = nil - } - } - } - - section.onSelectSelectableRow = { [weak self] _, row in - guard let value = row.selectableValue else { return } - self?.playSound(value) - } - - form.append(section) - - addBtns() - } - - private func addBtns() { - self.navigationItem.rightBarButtonItem = UIBarButtonItem( - title: .localized("Notifications.Alert.Save", comment: "Notifications: Select Alert Save"), - style: .done, - target: self, - action: #selector(save) - ) - self.navigationItem.leftBarButtonItem = UIBarButtonItem( - title: .localized("Notifications.Alert.Cancel", comment: "Notifications: Alerts Cancel"), - style: .done, - target: self, - action: #selector(close) - ) - } - - @objc private func save() { - guard let value = section.selectedRow()?.selectableValue else { return } - setNotificationSound(value) - close() - } - - @objc private func close() { - self.dismiss(animated: true) - } - - private func setNotificationSound(_ sound: NotificationSound) { - notificationsService.setNotificationSound(sound, for: notificationTarget) - } - - private func playSound(_ sound: NotificationSound) { - switch sound { - case .none: - break - default: - playSound(by: sound.fileName) - } - } - - private func playSound(by fileName: String) { - guard let url = Bundle.main.url(forResource: fileName.replacingOccurrences(of: ".mp3", with: ""), withExtension: "mp3") else { - return - } - - do { - try AVAudioSession.sharedInstance().setCategory(.playback) - try AVAudioSession.sharedInstance().setActive(true) - audioPlayer = try AVAudioPlayer(contentsOf: url, fileTypeHint: AVFileType.mp3.rawValue) - audioPlayer?.volume = 1.0 - audioPlayer?.play() - } catch let error as NSError { - print("error: \(error.localizedDescription)") - } - } -} diff --git a/Adamant/Modules/Settings/Notifications/NotificationsView.swift b/Adamant/Modules/Settings/Notifications/NotificationsView.swift index cbf8ac925..55e16a77b 100644 --- a/Adamant/Modules/Settings/Notifications/NotificationsView.swift +++ b/Adamant/Modules/Settings/Notifications/NotificationsView.swift @@ -11,7 +11,7 @@ import CommonKit struct NotificationsView: View { @StateObject var viewModel: NotificationsViewModel - var screensFactory: ScreensFactory + let screensFactory: ScreensFactory init( viewModel: @escaping () -> NotificationsViewModel, diff --git a/Adamant/Modules/Settings/Notifications/NotificationsViewModel.swift b/Adamant/Modules/Settings/Notifications/NotificationsViewModel.swift index 68eef7556..96c1c1a8f 100644 --- a/Adamant/Modules/Settings/Notifications/NotificationsViewModel.swift +++ b/Adamant/Modules/Settings/Notifications/NotificationsViewModel.swift @@ -18,8 +18,6 @@ final class NotificationsViewModel: ObservableObject { @Published var notificationsMode: NotificationsMode = .disabled @Published var notificationSound: NotificationSound = .inputDefault @Published var notificationReactionSound: NotificationSound = .none - @Published var githubRowImage: UIImage = .asset(named: "row_github") ?? UIImage() - @Published var notificationsTitle: String = .localized("SecurityPage.Row.Notifications") @Published var presentSoundsPicker: Bool = false @Published var presentReactionSoundsPicker: Bool = false @Published var openSafariURL: Bool = false @@ -27,7 +25,9 @@ final class NotificationsViewModel: ObservableObject { @Published var inAppVibrate: Bool = true @Published var inAppToasts: Bool = true - var safariURL = URL(string: "https://github.com/Adamant-im")! + let notificationsTitle: String = .localized("SecurityPage.Row.Notifications") + let safariURL = URL(string: "https://github.com/Adamant-im")! + let githubRowImage: UIImage = .asset(named: "row_github") ?? UIImage() private let dialogService: DialogService let notificationsService: NotificationsService @@ -43,15 +43,6 @@ final class NotificationsViewModel: ObservableObject { } } - func configure() { - notificationsMode = notificationsService.notificationsMode - notificationSound = notificationsService.notificationsSound - notificationReactionSound = notificationsService.notificationsReactionSound - inAppSounds = notificationsService.inAppSound - inAppVibrate = notificationsService.inAppVibrate - inAppToasts = notificationsService.inAppToasts - } - func presentNotificationSoundsPicker() { presentSoundsPicker = true } @@ -106,26 +97,6 @@ final class NotificationsViewModel: ObservableObject { ) } - private func presentNotificationsDeniedError() { - dialogService.showAlert( - title: nil, - message: NotificationStrings.notificationsDisabled, - style: .alert, - actions: [ - makeAction( - title: .adamant.alert.settings, - action: { _ in - self.openAppSettings() - }), - makeAction( - title: String.adamant.alert.cancel, - action: nil - ) - ], - from: nil - ) - } - func setNotificationMode(_ mode: NotificationsMode) { guard mode != notificationsService.notificationsMode else { return @@ -174,6 +145,15 @@ private extension NotificationsViewModel { .sink { [weak self] _ in self?.configure() } .store(in: &subscriptions) } + + func configure() { + notificationsMode = notificationsService.notificationsMode + notificationSound = notificationsService.notificationsSound + notificationReactionSound = notificationsService.notificationsReactionSound + inAppSounds = notificationsService.inAppSound + inAppVibrate = notificationsService.inAppVibrate + inAppToasts = notificationsService.inAppToasts + } } private extension NotificationsViewModel { @@ -192,4 +172,24 @@ private extension NotificationsViewModel { handler: nil ) } + + func presentNotificationsDeniedError() { + dialogService.showAlert( + title: nil, + message: NotificationStrings.notificationsDisabled, + style: .alert, + actions: [ + makeAction( + title: .adamant.alert.settings, + action: { _ in + self.openAppSettings() + }), + makeAction( + title: String.adamant.alert.cancel, + action: nil + ) + ], + from: nil + ) + } } diff --git a/Adamant/Modules/Settings/NotificationsViewController.swift b/Adamant/Modules/Settings/NotificationsViewController.swift deleted file mode 100644 index 2500a0c47..000000000 --- a/Adamant/Modules/Settings/NotificationsViewController.swift +++ /dev/null @@ -1,288 +0,0 @@ -// -// NotificationsViewController.swift -// Adamant -// -// Created by Anokhov Pavel on 10/11/2018. -// Copyright © 2018 Adamant. All rights reserved. -// - -import UIKit -import Eureka -import SafariServices -import MarkdownKit -import ProcedureKit -import CommonKit - -final class NotificationsViewController: FormViewController { - - // MARK: Sections & Rows - enum Sections { - case notifications - case aboutNotificationTypes - case messages - case settings - - var tag: String { - switch self { - case .notifications: return "st" - case .aboutNotificationTypes: return "ans" - case .messages: return "ms" - case .settings: return "settings" - } - } - - var localized: String { - switch self { - case .notifications: return .localized("SecurityPage.Section.NotificationsType", comment: "Security: Selected notifications types") - case .aboutNotificationTypes: return .localized("SecurityPage.Section.AboutNotificationTypes", comment: "Security: About Notification types") - case .messages: return .localized("SecurityPage.Section.Messages", comment: "Security: Messages Notification sound") - case .settings: return .localized("SecurityPage.Section.Settings", comment: "Security: Settings Notification") - } - } - } - - enum Rows { - case notificationsMode - case description, github - case systemSettings - case sound - - var tag: String { - switch self { - case .notificationsMode: return "rn" - case .description: return "rd" - case .github: return "git" - case .systemSettings: return "ss" - case .sound: return "sd" - } - } - - var localized: String { - switch self { - case .notificationsMode: return .localized("SecurityPage.Row.Notifications", comment: "Security: Show notifications") - case .description: return .localized("SecurityPage.Row.Notifications.ModesDescription", comment: "Security: Notification modes description. Markdown supported.") - case .github: return .localized("SecurityPage.Row.VisitGithub", comment: "Security: Visit Github") - case .systemSettings: return .localized("Notifications.Settings.System", comment: "Notifications: Open system Settings") - case .sound: return .localized("Notifications.Sound.Name", comment: "Notifications: Select Sound") - } - } - } - - // MARK: - Dependencies - - var dialogService: DialogService! - var notificationsService: NotificationsService! - - private lazy var markdownParser: MarkdownParser = { - let parser = MarkdownParser(font: UIFont.systemFont(ofSize: UIFont.systemFontSize), color: UIColor.adamant.textColor) - parser.link.color = UIColor.adamant.secondary - return parser - }() - - // MARK: - Lifecycle - - override func viewDidLoad() { - super.viewDidLoad() - - navigationItem.largeTitleDisplayMode = .always - navigationItem.title = String.adamant.security.title - navigationOptions = .Disabled - - // MARK: Notifications - // Type - let nType = ActionSheetRow { - $0.tag = Rows.notificationsMode.tag - $0.title = Rows.notificationsMode.localized - $0.selectorTitle = Rows.notificationsMode.localized - $0.options = [.disabled, .backgroundFetch, .push] - }.cellUpdate { [weak self] cell, row in - cell.accessoryType = .disclosureIndicator - - guard let notificationsMode = self?.notificationsService.notificationsMode else { return } - row.value = notificationsMode - }.onChange { [weak self] row in - let mode = row.value ?? NotificationsMode.disabled - self?.setNotificationMode(mode, completion: row.updateCell) - row.updateCell() - } - - // Section - let notificationsSection = Section(Sections.notifications.localized) { - $0.tag = Sections.notifications.tag - } - - notificationsSection.append(nType) - form.append(notificationsSection) - - // MARK: Messages - // Sound - let soundRow = LabelRow { - $0.tag = Rows.sound.tag - $0.title = Rows.sound.localized - $0.value = notificationsService.notificationsSound.localized - }.cellSetup { (cell, _) in - cell.selectionStyle = .gray - }.cellUpdate { (cell, _) in - cell.accessoryType = .disclosureIndicator - }.onCellSelection { [weak self] _, row in - guard let self = self else { return } - row.deselect() - let soundsVC = NotificationSoundsViewController(notificationsService: notificationsService, target: .baseMessage) -// soundsVC.notificationsService = self.notificationsService - let navigationController = UINavigationController(rootViewController: soundsVC) - self.present(navigationController, animated: true) - } - - // Section - let messagesSection = Section(Sections.messages.localized) { - $0.tag = Sections.messages.tag - } - - messagesSection.append(soundRow) - form.append(messagesSection) - - // MARK: Settings - // System Settings - let settingsRow = LabelRow { - $0.tag = Rows.systemSettings.tag - $0.title = Rows.systemSettings.localized - }.cellSetup { (cell, _) in - cell.selectionStyle = .gray - }.cellUpdate { (cell, _) in - cell.accessoryType = .disclosureIndicator - }.onCellSelection { _, row in - guard let url = URL(string: UIApplication.openSettingsURLString) else { - return - } - row.deselect() - UIApplication.shared.open(url, options: [:], completionHandler: nil) - } - - // Section - let settingsSection = Section(Sections.settings.localized) { - $0.tag = Sections.settings.tag - } - - settingsSection.append(settingsRow) - form.append(settingsSection) - - // MARK: ANS Description - // Description - let descriptionRow = TextAreaRow { - $0.textAreaHeight = .dynamic(initialTextViewHeight: 44) - $0.tag = Rows.description.tag - }.cellUpdate { [weak self] (cell, _) in - cell.textView.isSelectable = false - cell.textView.isEditable = false - if let parser = self?.markdownParser { - cell.textView.attributedText = parser.parse(Rows.description.localized) - } else { - cell.textView.text = Rows.description.localized - } - } - - // Github readme - let githubRow = LabelRow { - $0.tag = Rows.github.tag - $0.title = Rows.github.localized - $0.cell.imageView?.image = .asset(named: "row_github") - $0.cell.imageView?.tintColor = UIColor.adamant.tableRowIcons - }.cellSetup { (cell, _) in - cell.selectionStyle = .gray - }.cellUpdate { (cell, _) in - cell.accessoryType = .disclosureIndicator - }.onCellSelection { [weak self] (_, _) in - guard let url = URL(string: AdamantResources.ansReadmeUrl) else { - fatalError("Failed to build ANS URL") - } - - let safari = SFSafariViewController(url: url) - safari.preferredControlTintColor = UIColor.adamant.primary - safari.modalPresentationStyle = .overFullScreen - self?.present(safari, animated: true, completion: nil) - } - - let ansSection = Section(Sections.aboutNotificationTypes.localized) { - $0.tag = Sections.aboutNotificationTypes.tag - } - - ansSection.append(contentsOf: [descriptionRow, githubRow]) - form.append(ansSection) - - // MARK: Notifications - NotificationCenter.default.addObserver(forName: Notification.Name.AdamantNotificationService.notificationsModeChanged, object: nil, queue: OperationQueue.main) { [weak self] notification in - guard let newMode = notification.userInfo?[AdamantUserInfoKey.NotificationsService.newNotificationsMode] as? NotificationsMode else { - return - } - - guard let row: ActionSheetRow = self?.form.rowBy(tag: Rows.notificationsMode.tag) else { - return - } - - row.value = newMode - row.updateCell() - } - - NotificationCenter.default.addObserver(forName: Notification.Name.AdamantNotificationService.notificationsSoundChanged, object: nil, queue: OperationQueue.main) { [weak self] _ in - guard let row: LabelRow = self?.form.rowBy(tag: Rows.sound.tag) else { - return - } - - row.value = self?.notificationsService.notificationsSound.localized - row.updateCell() - } - setColors() - } - - // MARK: - Other - - func setColors() { - view.backgroundColor = UIColor.adamant.secondBackgroundColor - tableView.backgroundColor = .clear - } - - func setNotificationMode(_ mode: NotificationsMode, completion: @escaping () -> Void) { - guard mode != notificationsService.notificationsMode else { - return - } - - notificationsService.setNotificationsMode(mode) { [weak self] result in - DispatchQueue.onMainAsync { - defer { completion() } - - switch result { - case .success: - return - case .failure(let error): - switch error { - case .notEnoughMoney, .notStayedLoggedIn: - self?.dialogService.showRichError(error: error) - case .denied: - self?.presentNotificationsDeniedError() - } - } - } - } - } - - private func presentNotificationsDeniedError() { - let alert = UIAlertController( - title: nil, - message: NotificationStrings.notificationsDisabled, - preferredStyleSafe: .alert, - source: nil - ) - - alert.addAction(UIAlertAction(title: String.adamant.alert.settings, style: .default) { _ in - DispatchQueue.main.async { - if let settingsURL = URL(string: UIApplication.openSettingsURLString) { - UIApplication.shared.open(settingsURL) - } - } - }) - - alert.addAction(UIAlertAction(title: String.adamant.alert.cancel, style: .cancel, handler: nil)) - alert.modalPresentationStyle = .overFullScreen - present(alert, animated: true, completion: nil) - } -} diff --git a/Adamant/Modules/Settings/SettingsFactory.swift b/Adamant/Modules/Settings/SettingsFactory.swift index 09b68d3f5..5662f747c 100644 --- a/Adamant/Modules/Settings/SettingsFactory.swift +++ b/Adamant/Modules/Settings/SettingsFactory.swift @@ -46,13 +46,6 @@ struct SettingsFactory { ) } - 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)!, diff --git a/NotificationServiceExtension/NotificationService.swift b/NotificationServiceExtension/NotificationService.swift index f45691081..fed289d21 100644 --- a/NotificationServiceExtension/NotificationService.swift +++ b/NotificationServiceExtension/NotificationService.swift @@ -321,19 +321,15 @@ class NotificationService: UNNotificationServiceExtension { } private func getSound(securedStore: KeychainStore, isReaction: Bool) -> UNNotificationSound? { - guard isReaction else { - let sound: String = securedStore.get(StoreKey.notificationsService.notificationsSound) ?? .empty - - return !sound.isEmpty - ? UNNotificationSound(named: UNNotificationSoundName(sound)) - : nil - } - - let sound: String = securedStore.get(StoreKey.notificationsService.notificationsReactionSound) ?? .empty + let key = isReaction + ? StoreKey.notificationsService.notificationsReactionSound + : StoreKey.notificationsService.notificationsSound + + let sound: String = securedStore.get(key) ?? .empty - return !sound.isEmpty - ? UNNotificationSound(named: UNNotificationSoundName(sound)) - : nil + return sound.isEmpty + ? nil + : UNNotificationSound(named: UNNotificationSoundName(sound)) } private func handleAdamantTransfer( From d81fca1993f31db60a6bf9e0ac7d904230b7425c Mon Sep 17 00:00:00 2001 From: Iana Date: Wed, 11 Sep 2024 23:17:24 +0300 Subject: [PATCH 074/106] [trello.com/c/44KDaoe5] Code refactoring --- .../AdamantScreensFactory.swift | 19 ++-- .../NotificationSoundsFactory.swift | 9 +- .../NotificationSoundsView.swift | 4 + .../NotificationSoundsViewModel.swift | 3 +- .../Notifications/NotificationsFactory.swift | 8 +- .../Notifications/NotificationsView.swift | 88 +++++++------------ .../NotificationsViewModel.swift | 32 ++++++- .../Services/AdamantNotificationService.swift | 10 +-- 8 files changed, 96 insertions(+), 77 deletions(-) diff --git a/Adamant/Modules/ScreensFactory/AdamantScreensFactory.swift b/Adamant/Modules/ScreensFactory/AdamantScreensFactory.swift index 2a8149819..af7b12a8c 100644 --- a/Adamant/Modules/ScreensFactory/AdamantScreensFactory.swift +++ b/Adamant/Modules/ScreensFactory/AdamantScreensFactory.swift @@ -29,8 +29,8 @@ struct AdamantScreensFactory: ScreensFactory { private let partnerQRFactory: PartnerQRFactory private let coinsNodesListFactory: CoinsNodesListFactory private let chatSelectTextFactory: ChatSelectTextViewFactory - private let notificasionsFactory: NotificationsFactory - private let notificationSounds: NotificationSoundsFactory + private let notificationsFactory: NotificationsFactory + private let notificationSoundsFactory: NotificationSoundsFactory private let storageUsageFactory: StorageUsageFactory init(assembler: Assembler) { @@ -49,8 +49,8 @@ struct AdamantScreensFactory: ScreensFactory { partnerQRFactory = .init(parent: assembler) coinsNodesListFactory = .init(parent: assembler) chatSelectTextFactory = .init() - notificasionsFactory = .init(parent: assembler) - notificationSounds = .init(parent: assembler) + notificationsFactory = .init(parent: assembler) + notificationSoundsFactory = .init(parent: assembler) storageUsageFactory = .init(parent: assembler) walletFactoryCompose = AdamantWalletFactoryCompose( @@ -165,11 +165,18 @@ struct AdamantScreensFactory: ScreensFactory { } func makeNotifications() -> UIViewController { - notificasionsFactory.makeViewController(screensFactory: self) + notificationsFactory.makeViewController( + baseSoundsView: { + notificationSoundsFactory.makeView(target: .baseMessage) + }, + reactionSoundsView: { + notificationSoundsFactory.makeView(target: .reaction) + } + ) } func makeNotificationSounds(target: NotificationTarget) -> NotificationSoundsView { - notificationSounds.makeView(target: target) + notificationSoundsFactory.makeView(target: target) } func makeVisibleWallets() -> UIViewController { diff --git a/Adamant/Modules/Settings/Notifications/NotificationSounds/NotificationSoundsFactory.swift b/Adamant/Modules/Settings/Notifications/NotificationSounds/NotificationSoundsFactory.swift index 7e76f9210..485c9f5c8 100644 --- a/Adamant/Modules/Settings/Notifications/NotificationSounds/NotificationSoundsFactory.swift +++ b/Adamant/Modules/Settings/Notifications/NotificationSounds/NotificationSoundsFactory.swift @@ -21,10 +21,9 @@ struct NotificationSoundsFactory { func makeView(target: NotificationTarget) -> NotificationSoundsView { let assembler = Assembler(assemblies, parent: parent) let viewModel = { - assembler.resolver.resolve(NotificationSoundsViewModel.self)! - }() + assembler.resolver.resolve(NotificationSoundsViewModel.self, argument: target)! + } - viewModel.setup(notificationTarget: target) let view = NotificationSoundsView(viewModel: viewModel) return view @@ -33,10 +32,10 @@ struct NotificationSoundsFactory { private struct NotificationSoundAssembly: Assembly { func assemble(container: Container) { - container.register(NotificationSoundsViewModel.self) { r in + container.register(NotificationSoundsViewModel.self) { (r, target: NotificationTarget) in NotificationSoundsViewModel( notificationsService: r.resolve(NotificationsService.self)!, - target: .baseMessage, + target: target, dialogService: r.resolve(DialogService.self)! ) }.inObjectScope(.transient) diff --git a/Adamant/Modules/Settings/Notifications/NotificationSounds/NotificationSoundsView.swift b/Adamant/Modules/Settings/Notifications/NotificationSounds/NotificationSoundsView.swift index 697d036af..b3523c161 100644 --- a/Adamant/Modules/Settings/Notifications/NotificationSounds/NotificationSoundsView.swift +++ b/Adamant/Modules/Settings/Notifications/NotificationSounds/NotificationSoundsView.swift @@ -14,6 +14,10 @@ struct NotificationSoundsView: View { @Environment(\.dismiss) var dismiss + init(viewModel: @escaping () -> NotificationSoundsViewModel) { + _viewModel = .init(wrappedValue: viewModel()) + } + var body: some View { GeometryReader { _ in Form { diff --git a/Adamant/Modules/Settings/Notifications/NotificationSounds/NotificationSoundsViewModel.swift b/Adamant/Modules/Settings/Notifications/NotificationSounds/NotificationSoundsViewModel.swift index 4ee3bbc55..1efc26c91 100644 --- a/Adamant/Modules/Settings/Notifications/NotificationSounds/NotificationSoundsViewModel.swift +++ b/Adamant/Modules/Settings/Notifications/NotificationSounds/NotificationSoundsViewModel.swift @@ -86,10 +86,9 @@ private extension NotificationSoundsViewModel { } catch { dialogService.showError( withMessage: error.localizedDescription, - supportEmail: false, + supportEmail: true, error: error ) } } } - diff --git a/Adamant/Modules/Settings/Notifications/NotificationsFactory.swift b/Adamant/Modules/Settings/Notifications/NotificationsFactory.swift index 2058e46da..90a8fc09e 100644 --- a/Adamant/Modules/Settings/Notifications/NotificationsFactory.swift +++ b/Adamant/Modules/Settings/Notifications/NotificationsFactory.swift @@ -18,13 +18,17 @@ struct NotificationsFactory { } @MainActor - func makeViewController(screensFactory: ScreensFactory) -> UIViewController { + func makeViewController( + baseSoundsView: @escaping () -> NotificationSoundsView, + reactionSoundsView: @escaping () -> NotificationSoundsView + ) -> UIViewController { let assembler = Assembler(assemblies, parent: parent) let viewModel = { assembler.resolver.resolve(NotificationsViewModel.self)! } let view = NotificationsView( viewModel: viewModel, - screensFactory: screensFactory + baseSoundsView: baseSoundsView, + reactionSoundsView: reactionSoundsView ) return UIHostingController( diff --git a/Adamant/Modules/Settings/Notifications/NotificationsView.swift b/Adamant/Modules/Settings/Notifications/NotificationsView.swift index 55e16a77b..ed0cdc59a 100644 --- a/Adamant/Modules/Settings/Notifications/NotificationsView.swift +++ b/Adamant/Modules/Settings/Notifications/NotificationsView.swift @@ -11,60 +11,57 @@ import CommonKit struct NotificationsView: View { @StateObject var viewModel: NotificationsViewModel - let screensFactory: ScreensFactory + private let baseSoundsView: NotificationSoundsView + private let reactionSoundsView: NotificationSoundsView init( viewModel: @escaping () -> NotificationsViewModel, - screensFactory: ScreensFactory + baseSoundsView: @escaping () -> NotificationSoundsView, + reactionSoundsView: @escaping () -> NotificationSoundsView ) { _viewModel = .init(wrappedValue: viewModel()) - self.screensFactory = screensFactory + self.baseSoundsView = baseSoundsView() + self.reactionSoundsView = reactionSoundsView() } var body: some View { - GeometryReader { geometry in - Form { - notificationsSection() - messageSoundSection() - messageReactionsSection() - inAppNotificationsSection() - settingsSection() - moreDetailsSection() - } - .withoutListBackground() - .background(Color(.adamant.secondBackgroundColor)) - .navigationBarTitleDisplayMode(.inline) - .toolbar { - ToolbarItem(placement: .principal) { - toolbar(maxWidth: geometry.size.width) - } - } - .sheet(isPresented: $viewModel.presentSoundsPicker, content: { - NavigationView(content: { - screensFactory.makeNotificationSounds(target: .baseMessage) - }) - }) - .sheet(isPresented: $viewModel.presentReactionSoundsPicker, content: { - NavigationView(content: { - screensFactory.makeNotificationSounds(target: .reaction) - }) - }) - .fullScreenCover(isPresented: $viewModel.openSafariURL) { - SafariWebView(url: viewModel.safariURL).ignoresSafeArea() + Form { + notificationsSection() + messageSoundSection() + messageReactionsSection() + inAppNotificationsSection() + settingsSection() + moreDetailsSection() + } + .withoutListBackground() + .background(Color(.adamant.secondBackgroundColor)) + .navigationBarTitleDisplayMode(.inline) + .toolbar { + ToolbarItem(placement: .principal) { + toolbar() } } + .sheet(isPresented: $viewModel.presentSoundsPicker, content: { + NavigationView(content: { baseSoundsView }) + }) + .sheet(isPresented: $viewModel.presentReactionSoundsPicker, content: { + NavigationView(content: { reactionSoundsView }) + }) + .fullScreenCover(isPresented: $viewModel.openSafariURL) { + SafariWebView(url: viewModel.safariURL).ignoresSafeArea() + } } } private extension NotificationsView { - func toolbar(maxWidth: CGFloat) -> some View { + func toolbar() -> some View { HStack { Text(viewModel.notificationsTitle) .font(.headline) .minimumScaleFactor(0.7) .lineLimit(1) } - .frame(maxWidth: maxWidth - toolbarSpace, alignment: .center) + .frame(alignment: .center) } func notificationsSection() -> some View { @@ -127,25 +124,16 @@ private extension NotificationsView { Text(soundsTitle) } .tint(.init(uiColor: .adamant.active)) - .onChange(of: viewModel.inAppSounds) { value in - viewModel.applyInAppSounds(value: value) - } - + Toggle(isOn: $viewModel.inAppVibrate) { Text(vibrateTitle) } .tint(.init(uiColor: .adamant.active)) - .onChange(of: viewModel.inAppVibrate) { value in - viewModel.applyInAppVibrate(value: value) - } Toggle(isOn: $viewModel.inAppToasts) { Text(toastsTitle) } .tint(.init(uiColor: .adamant.active)) - .onChange(of: viewModel.inAppToasts) { value in - viewModel.applyInAppToasts(value: value) - } } header: { Text(inAppNotifications) } @@ -169,20 +157,16 @@ private extension NotificationsView { func moreDetailsSection() -> some View { Section { - if let attributedString = viewModel.parseMarkdown(descriptionText) { - Text(AttributedString(attributedString)) + if let description = viewModel.parsedMarkdownDescription { + Text(description) } - Button(action: { viewModel.presentSafariURL() }, label: { HStack { Image(uiImage: viewModel.githubRowImage) - Text(visitGithub) - Spacer() - NavigationLink(destination: { EmptyView() }, label: { EmptyView() }).fixedSize() } .padding() @@ -205,10 +189,6 @@ private var settingsHeader: String { .localized("Notifications.Settings.System") } -private var descriptionText: String { - .localized("SecurityPage.Row.Notifications.ModesDescription") -} - private var visitGithub: String { .localized("SecurityPage.Row.VisitGithub") } diff --git a/Adamant/Modules/Settings/Notifications/NotificationsViewModel.swift b/Adamant/Modules/Settings/Notifications/NotificationsViewModel.swift index 96c1c1a8f..2528bb459 100644 --- a/Adamant/Modules/Settings/Notifications/NotificationsViewModel.swift +++ b/Adamant/Modules/Settings/Notifications/NotificationsViewModel.swift @@ -29,9 +29,20 @@ final class NotificationsViewModel: ObservableObject { let safariURL = URL(string: "https://github.com/Adamant-im")! let githubRowImage: UIImage = .asset(named: "row_github") ?? UIImage() + private let descriptionText: String = .localized("SecurityPage.Row.Notifications.ModesDescription") + private let dialogService: DialogService - let notificationsService: NotificationsService + private let notificationsService: NotificationsService + private var subscriptions = Set() + private var cancellables = Set() + + var parsedMarkdownDescription: AttributedString? { + guard let attributedString = parseMarkdown(descriptionText) else { + return nil + } + return AttributedString(attributedString) + } nonisolated init(dialogService: DialogService, notificationsService: NotificationsService) { self.dialogService = dialogService @@ -40,6 +51,7 @@ final class NotificationsViewModel: ObservableObject { Task { await addObservers() await configure() + await setupSwitchHandlers() } } @@ -144,6 +156,24 @@ private extension NotificationsViewModel { .publisher(for: .AdamantNotificationService.notificationsSoundChanged) .sink { [weak self] _ in self?.configure() } .store(in: &subscriptions) + + $inAppSounds + .sink { [weak self] value in + self?.applyInAppSounds(value: value) + } + .store(in: &cancellables) + + $inAppVibrate + .sink { [weak self] value in + self?.applyInAppVibrate(value: value) + } + .store(in: &cancellables) + + $inAppToasts + .sink { [weak self] value in + self?.applyInAppToasts(value: value) + } + .store(in: &cancellables) } func configure() { diff --git a/Adamant/Services/AdamantNotificationService.swift b/Adamant/Services/AdamantNotificationService.swift index 70861714d..7e545651b 100644 --- a/Adamant/Services/AdamantNotificationService.swift +++ b/Adamant/Services/AdamantNotificationService.swift @@ -111,9 +111,9 @@ final class AdamantNotificationsService: NotificationsService { } } - inAppSound = getValue(for: StoreKey.notificationsService.inAppSounds) ?? defaultInAppSound - inAppVibrate = getValue(for: StoreKey.notificationsService.inAppVibrate) ?? defaultInAppVibrate - inAppToasts = getValue(for: StoreKey.notificationsService.inAppToasts) ?? defaultInAppToasts + inAppSound = securedStore.get(StoreKey.notificationsService.inAppSounds) ?? defaultInAppSound + inAppVibrate = securedStore.get(StoreKey.notificationsService.inAppVibrate) ?? defaultInAppVibrate + inAppToasts = securedStore.get(StoreKey.notificationsService.inAppToasts) ?? defaultInAppToasts preservedBadgeNumber = nil } @@ -351,10 +351,6 @@ extension AdamantNotificationsService { func setValue(for key: String, value: Bool) { securedStore.set(value, for: key) } - - func getValue(for key: String) -> T? { - securedStore.get(key) - } } // MARK: - Background batch notifications From b642576bca98724bb31960f3601de5d04a21bcd0 Mon Sep 17 00:00:00 2001 From: Iana Date: Wed, 11 Sep 2024 23:18:37 +0300 Subject: [PATCH 075/106] [trello.com/c/44KDaoe5] Removed unused code --- .../Modules/Settings/Notifications/NotificationsViewModel.swift | 1 - 1 file changed, 1 deletion(-) diff --git a/Adamant/Modules/Settings/Notifications/NotificationsViewModel.swift b/Adamant/Modules/Settings/Notifications/NotificationsViewModel.swift index 2528bb459..a6d551daf 100644 --- a/Adamant/Modules/Settings/Notifications/NotificationsViewModel.swift +++ b/Adamant/Modules/Settings/Notifications/NotificationsViewModel.swift @@ -51,7 +51,6 @@ final class NotificationsViewModel: ObservableObject { Task { await addObservers() await configure() - await setupSwitchHandlers() } } From 2cdf3c61437c541a4cca8d4321de9a7737c09ff1 Mon Sep 17 00:00:00 2001 From: Iana Date: Thu, 12 Sep 2024 01:13:09 +0300 Subject: [PATCH 076/106] [trello.com/c/bSmPAIM2] Code refactoring --- Adamant/Modules/Chat/View/ChatViewController.swift | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/Adamant/Modules/Chat/View/ChatViewController.swift b/Adamant/Modules/Chat/View/ChatViewController.swift index 888b5ad49..2c8295fb8 100644 --- a/Adamant/Modules/Chat/View/ChatViewController.swift +++ b/Adamant/Modules/Chat/View/ChatViewController.swift @@ -705,9 +705,7 @@ private extension ChatViewController { guard viewAppeared else { return } let targetY: CGFloat = targetYOffset + view.safeAreaInsets.top - guard let visibleIndexPaths = messagesCollectionView.indexPathsForVisibleItems.sorted(by: { - $0.row < $1.row - }) as [IndexPath]? else { return } + let visibleIndexPaths = messagesCollectionView.indexPathsForVisibleItems for indexPath in visibleIndexPaths { guard let cell = messagesCollectionView.cellForItem(at: indexPath) From 471e3dfdafaad16a6d67963ba00ddd653f12600a Mon Sep 17 00:00:00 2001 From: Iana Date: Thu, 12 Sep 2024 01:13:41 +0300 Subject: [PATCH 077/106] [trello.com/c/bSmPAIM2] Hidden fixed date for static messages --- Adamant/Modules/Chat/ViewModel/ChatViewModel.swift | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Adamant/Modules/Chat/ViewModel/ChatViewModel.swift b/Adamant/Modules/Chat/ViewModel/ChatViewModel.swift index 3cb138fd4..e54772959 100644 --- a/Adamant/Modules/Chat/ViewModel/ChatViewModel.swift +++ b/Adamant/Modules/Chat/ViewModel/ChatViewModel.swift @@ -1033,7 +1033,8 @@ extension ChatViewModel { func checkTopMessage(indexPath: IndexPath) { guard let message = messages[safe: indexPath.section], - let date = message.dateHeader?.string.string + let date = message.dateHeader?.string.string, + message.sentDate != .adamantNullDate else { return } dateHeader = date dateHeaderHidden = false From 9ddf25bed03d5a578cccd8ee0d9d7759839c6cfe Mon Sep 17 00:00:00 2001 From: Iana Date: Fri, 13 Sep 2024 14:55:32 +0300 Subject: [PATCH 078/106] [trello.com/c/44KDaoe5] Removed duplicate code and implemented a reusable solution --- .../NotificationSoundsViewModel.swift | 2 +- .../Notifications/NotificationsView.swift | 40 +++++-------------- 2 files changed, 12 insertions(+), 30 deletions(-) diff --git a/Adamant/Modules/Settings/Notifications/NotificationSounds/NotificationSoundsViewModel.swift b/Adamant/Modules/Settings/Notifications/NotificationSounds/NotificationSoundsViewModel.swift index 1efc26c91..d5e68b6dd 100644 --- a/Adamant/Modules/Settings/Notifications/NotificationSounds/NotificationSoundsViewModel.swift +++ b/Adamant/Modules/Settings/Notifications/NotificationSounds/NotificationSoundsViewModel.swift @@ -17,7 +17,7 @@ final class NotificationSoundsViewModel: ObservableObject { private var notificationTarget: NotificationTarget private let dialogService: DialogService - private(set) var dismissAction = PassthroughSubject() + let dismissAction = PassthroughSubject() @Published var isPresented: Bool = false @Published var selectedSound: NotificationSound = .inputDefault @Published var sounds: [NotificationSound] = [.none, .noteDefault, .inputDefault, .proud, .relax, .success, .note, .antic, .cheers, .chord, .droplet, .handoff, .milestone, .passage, .portal, .rattle, .rebound, .slide, .welcome] diff --git a/Adamant/Modules/Settings/Notifications/NotificationsView.swift b/Adamant/Modules/Settings/Notifications/NotificationsView.swift index ed0cdc59a..f879ca24b 100644 --- a/Adamant/Modules/Settings/Notifications/NotificationsView.swift +++ b/Adamant/Modules/Settings/Notifications/NotificationsView.swift @@ -66,17 +66,14 @@ private extension NotificationsView { func notificationsSection() -> some View { Section { - Button(action: { - viewModel.showAlert() - }, label: { + NavigationButton(action: { viewModel.showAlert() }) { HStack { Text(viewModel.notificationsTitle) Spacer() Text(viewModel.notificationsMode.localized) .foregroundColor(.gray) - NavigationLink(destination: { EmptyView() }, label: { EmptyView() }).fixedSize() } - }) + }.listRowBackground(Color(uiColor: .adamant.cellColor)) } header: { Text(viewModel.notificationsTitle) } @@ -84,17 +81,14 @@ private extension NotificationsView { func messageSoundSection() -> some View { Section { - Button(action: { - viewModel.presentNotificationSoundsPicker() - }, label: { + NavigationButton(action: { viewModel.presentNotificationSoundsPicker() }) { HStack { Text(soundTitle) Spacer() Text(viewModel.notificationSound.localized) .foregroundColor(.gray) - NavigationLink(destination: { EmptyView() }, label: { EmptyView() }).fixedSize() } - }) + } } header: { Text(messagesHeader) } @@ -102,17 +96,14 @@ private extension NotificationsView { func messageReactionsSection() -> some View { Section { - Button(action: { - viewModel.presentReactionNotificationSoundsPicker() - }, label: { + NavigationButton(action: { viewModel.presentReactionNotificationSoundsPicker() }) { HStack { Text(soundTitle) Spacer() Text(viewModel.notificationReactionSound.localized) .foregroundColor(.gray) - NavigationLink(destination: { EmptyView() }, label: { EmptyView() }).fixedSize() } - }) + } } header: { Text(reactionsHeader) } @@ -124,7 +115,7 @@ private extension NotificationsView { Text(soundsTitle) } .tint(.init(uiColor: .adamant.active)) - + Toggle(isOn: $viewModel.inAppVibrate) { Text(vibrateTitle) } @@ -141,15 +132,12 @@ private extension NotificationsView { func settingsSection() -> some View { Section { - Button(action: { - viewModel.openAppSettings() - }, label: { + NavigationButton(action: { viewModel.openAppSettings() }) { HStack { Text(settingsHeader) Spacer() - NavigationLink(destination: { EmptyView() }, label: { EmptyView() }).fixedSize() } - }) + } } header: { Text(settingsHeader) } @@ -160,23 +148,17 @@ private extension NotificationsView { if let description = viewModel.parsedMarkdownDescription { Text(description) } - Button(action: { - viewModel.presentSafariURL() - }, label: { + NavigationButton(action: { viewModel.presentSafariURL() }) { HStack { Image(uiImage: viewModel.githubRowImage) Text(visitGithub) Spacer() - NavigationLink(destination: { EmptyView() }, label: { EmptyView() }).fixedSize() } - .padding() - }) + } } } } -private let toolbarSpace: CGFloat = 150 - private var messagesHeader: String { .localized("SecurityPage.Section.Messages") } From 080f557ad1452716acd4ee7986a61f9e8648b7e7 Mon Sep 17 00:00:00 2001 From: Iana Date: Sun, 15 Sep 2024 10:41:55 +0300 Subject: [PATCH 079/106] [trello.com/c/44KDaoe5] Factory refactoring --- .../ScreensFactory/AdamantScreensFactory.swift | 9 +-------- .../Notifications/NotificationsFactory.swift | 11 +++++++---- .../Notifications/NotificationsView.swift | 16 ++++++++-------- .../Localization/de.lproj/Localizable.strings | 2 +- 4 files changed, 17 insertions(+), 21 deletions(-) diff --git a/Adamant/Modules/ScreensFactory/AdamantScreensFactory.swift b/Adamant/Modules/ScreensFactory/AdamantScreensFactory.swift index af7b12a8c..59795f7c1 100644 --- a/Adamant/Modules/ScreensFactory/AdamantScreensFactory.swift +++ b/Adamant/Modules/ScreensFactory/AdamantScreensFactory.swift @@ -165,14 +165,7 @@ struct AdamantScreensFactory: ScreensFactory { } func makeNotifications() -> UIViewController { - notificationsFactory.makeViewController( - baseSoundsView: { - notificationSoundsFactory.makeView(target: .baseMessage) - }, - reactionSoundsView: { - notificationSoundsFactory.makeView(target: .reaction) - } - ) + notificationsFactory.makeViewController() } func makeNotificationSounds(target: NotificationTarget) -> NotificationSoundsView { diff --git a/Adamant/Modules/Settings/Notifications/NotificationsFactory.swift b/Adamant/Modules/Settings/Notifications/NotificationsFactory.swift index 90a8fc09e..8bdc2abc0 100644 --- a/Adamant/Modules/Settings/Notifications/NotificationsFactory.swift +++ b/Adamant/Modules/Settings/Notifications/NotificationsFactory.swift @@ -18,13 +18,16 @@ struct NotificationsFactory { } @MainActor - func makeViewController( - baseSoundsView: @escaping () -> NotificationSoundsView, - reactionSoundsView: @escaping () -> NotificationSoundsView - ) -> UIViewController { + func makeViewController() -> UIViewController { let assembler = Assembler(assemblies, parent: parent) let viewModel = { assembler.resolver.resolve(NotificationsViewModel.self)! } + let baseSoundsFactory = NotificationSoundsFactory(parent: assembler) + let reactionSoundsFactory = NotificationSoundsFactory(parent: assembler) + + let baseSoundsView = { baseSoundsFactory.makeView(target: .baseMessage).eraseToAnyView() } + let reactionSoundsView = { reactionSoundsFactory.makeView(target: .reaction).eraseToAnyView() } + let view = NotificationsView( viewModel: viewModel, baseSoundsView: baseSoundsView, diff --git a/Adamant/Modules/Settings/Notifications/NotificationsView.swift b/Adamant/Modules/Settings/Notifications/NotificationsView.swift index f879ca24b..b5b0c2185 100644 --- a/Adamant/Modules/Settings/Notifications/NotificationsView.swift +++ b/Adamant/Modules/Settings/Notifications/NotificationsView.swift @@ -11,17 +11,17 @@ import CommonKit struct NotificationsView: View { @StateObject var viewModel: NotificationsViewModel - private let baseSoundsView: NotificationSoundsView - private let reactionSoundsView: NotificationSoundsView + private let baseSoundsView: () -> AnyView + private let reactionSoundsView: () -> AnyView init( viewModel: @escaping () -> NotificationsViewModel, - baseSoundsView: @escaping () -> NotificationSoundsView, - reactionSoundsView: @escaping () -> NotificationSoundsView + baseSoundsView: @escaping () -> AnyView, + reactionSoundsView: @escaping () -> AnyView ) { _viewModel = .init(wrappedValue: viewModel()) - self.baseSoundsView = baseSoundsView() - self.reactionSoundsView = reactionSoundsView() + self.baseSoundsView = baseSoundsView + self.reactionSoundsView = reactionSoundsView } var body: some View { @@ -42,10 +42,10 @@ struct NotificationsView: View { } } .sheet(isPresented: $viewModel.presentSoundsPicker, content: { - NavigationView(content: { baseSoundsView }) + NavigationView(content: { baseSoundsView() }) }) .sheet(isPresented: $viewModel.presentReactionSoundsPicker, content: { - NavigationView(content: { reactionSoundsView }) + NavigationView(content: { reactionSoundsView() }) }) .fullScreenCover(isPresented: $viewModel.openSafariURL) { SafariWebView(url: viewModel.safariURL).ignoresSafeArea() diff --git a/CommonKit/Sources/CommonKit/Assets/Localization/de.lproj/Localizable.strings b/CommonKit/Sources/CommonKit/Assets/Localization/de.lproj/Localizable.strings index 86034c4c2..9503de847 100644 --- a/CommonKit/Sources/CommonKit/Assets/Localization/de.lproj/Localizable.strings +++ b/CommonKit/Sources/CommonKit/Assets/Localization/de.lproj/Localizable.strings @@ -821,7 +821,7 @@ "Notifications.Mode.NotificationsDisabled" = "Deaktiviert"; /* Notifications: Use Background fetch notifications */ -"Notifications.Mode.BackgroundFetch" = "Hintergrundaktualisierung"; +"Notifications.Mode.BackgroundFetch" = "Hintergrund Fetch"; /* Notifications: Use Apple Push notifications */ "Notifications.Mode.ApplePush" = "Push"; From df9d04ef148a6818f3cb234a7c6df70ef750485e Mon Sep 17 00:00:00 2001 From: just-software-dev Date: Sun, 15 Sep 2024 18:25:51 -0300 Subject: [PATCH 080/106] [trello.com/c/WPTyfaeg] adaman-wallets update --- .../AdmWalletService+DynamicConstants.swift | 5 +- .../BtcWalletService+DynamicConstants.swift | 6 +- .../DogeWalletService+DynamicConstants.swift | 1 + .../EthWalletService+DynamicConstants.swift | 6 +- .../CommonKit/Models/ethereumTokensList.swift | 116 +++++++++--------- 5 files changed, 68 insertions(+), 66 deletions(-) diff --git a/Adamant/Modules/Wallets/Adamant/AdmWalletService+DynamicConstants.swift b/Adamant/Modules/Wallets/Adamant/AdmWalletService+DynamicConstants.swift index ef16ecc4b..d52aa1793 100644 --- a/Adamant/Modules/Wallets/Adamant/AdmWalletService+DynamicConstants.swift +++ b/Adamant/Modules/Wallets/Adamant/AdmWalletService+DynamicConstants.swift @@ -15,8 +15,8 @@ extension AdmWalletService { onScreenUpdateInterval: 10, threshold: 10, normalServiceUpdateInterval: 300, - crucialServiceUpdateInterval: 300, - onScreenServiceUpdateInterval: 300 + crucialServiceUpdateInterval: 30, + onScreenServiceUpdateInterval: 10 ) static var newPendingInterval: Int { @@ -88,6 +88,7 @@ Node.makeDefaultNode(url: URL(string: "https://dschubba.adm.im")!), static var serviceNodes: [Node] { [ Node.makeDefaultNode(url: URL(string: "https://info.adamant.im")!), +Node.makeDefaultNode(url: URL(string: "https://info2.adm.im")!), ] } } diff --git a/Adamant/Modules/Wallets/Bitcoin/BtcWalletService+DynamicConstants.swift b/Adamant/Modules/Wallets/Bitcoin/BtcWalletService+DynamicConstants.swift index 977b8abd5..1c325deb1 100644 --- a/Adamant/Modules/Wallets/Bitcoin/BtcWalletService+DynamicConstants.swift +++ b/Adamant/Modules/Wallets/Bitcoin/BtcWalletService+DynamicConstants.swift @@ -14,7 +14,7 @@ extension BtcWalletService { crucialUpdateInterval: 30, onScreenUpdateInterval: 10, threshold: 2, - normalServiceUpdateInterval: 360, + normalServiceUpdateInterval: 330, crucialServiceUpdateInterval: 30, onScreenServiceUpdateInterval: 10 ) @@ -75,8 +75,8 @@ extension BtcWalletService { static var nodes: [Node] { [ - Node.makeDefaultNode(url: URL(string: "https://btcnode1.adamant.im")!, altUrl: URL(string: "http://176.9.38.204:44099")), -Node.makeDefaultNode(url: URL(string: "https://btcnode3.adamant.im")!, altUrl: URL(string: "http://195.201.242.108:44099")), + Node.makeDefaultNode(url: URL(string: "https://btcnode1.adamant.im/bitcoind")!, altUrl: URL(string: "http://176.9.38.204:44099/bitcoind")), +Node.makeDefaultNode(url: URL(string: "https://btcnode3.adamant.im/bitcoind")!, altUrl: URL(string: "http://195.201.242.108:44099/bitcoind")), ] } diff --git a/Adamant/Modules/Wallets/Doge/DogeWalletService+DynamicConstants.swift b/Adamant/Modules/Wallets/Doge/DogeWalletService+DynamicConstants.swift index fd2449ad2..d940f4428 100644 --- a/Adamant/Modules/Wallets/Doge/DogeWalletService+DynamicConstants.swift +++ b/Adamant/Modules/Wallets/Doge/DogeWalletService+DynamicConstants.swift @@ -77,6 +77,7 @@ extension DogeWalletService { [ Node.makeDefaultNode(url: URL(string: "https://dogenode1.adamant.im")!, altUrl: URL(string: "http://5.9.99.62:44099")), Node.makeDefaultNode(url: URL(string: "https://dogenode2.adamant.im")!, altUrl: URL(string: "http://176.9.32.126:44098")), +Node.makeDefaultNode(url: URL(string: "https://dogenode3.adm.im")!, altUrl: URL(string: "http://95.216.45.88:44098")), ] } diff --git a/Adamant/Modules/Wallets/Ethereum/EthWalletService+DynamicConstants.swift b/Adamant/Modules/Wallets/Ethereum/EthWalletService+DynamicConstants.swift index f67891869..2e6db1660 100644 --- a/Adamant/Modules/Wallets/Ethereum/EthWalletService+DynamicConstants.swift +++ b/Adamant/Modules/Wallets/Ethereum/EthWalletService+DynamicConstants.swift @@ -14,7 +14,7 @@ extension EthWalletService { crucialUpdateInterval: 30, onScreenUpdateInterval: 10, threshold: 5, - normalServiceUpdateInterval: 300, + normalServiceUpdateInterval: 330, crucialServiceUpdateInterval: 30, onScreenServiceUpdateInterval: 10 ) @@ -48,7 +48,7 @@ extension EthWalletService { } var defaultGasPriceGwei: BigUInt { - 30 + 10 } var defaultGasLimit: BigUInt { @@ -56,7 +56,7 @@ extension EthWalletService { } var warningGasPriceGwei: BigUInt { - 70 + 25 } var tokenName: String { diff --git a/CommonKit/Sources/CommonKit/Models/ethereumTokensList.swift b/CommonKit/Sources/CommonKit/Models/ethereumTokensList.swift index ba9b9f614..defd1f647 100644 --- a/CommonKit/Sources/CommonKit/Models/ethereumTokensList.swift +++ b/CommonKit/Sources/CommonKit/Models/ethereumTokensList.swift @@ -12,9 +12,9 @@ defaultOrdinalLevel: nil, reliabilityGasPricePercent: 10, reliabilityGasLimitPercent: 10, - defaultGasPriceGwei: 30, + defaultGasPriceGwei: 10, defaultGasLimit: 58000, - warningGasPriceGwei: 70, + warningGasPriceGwei: 25, transferDecimals: 6), ERC20Token(symbol: "BUSD", name: "Binance USD", @@ -25,9 +25,9 @@ defaultOrdinalLevel: nil, reliabilityGasPricePercent: 10, reliabilityGasLimitPercent: 10, - defaultGasPriceGwei: 30, + defaultGasPriceGwei: 10, defaultGasLimit: 58000, - warningGasPriceGwei: 70, + warningGasPriceGwei: 25, transferDecimals: 6), ERC20Token(symbol: "BZZ", name: "Swarm", @@ -38,9 +38,9 @@ defaultOrdinalLevel: 95, reliabilityGasPricePercent: 10, reliabilityGasLimitPercent: 10, - defaultGasPriceGwei: 30, + defaultGasPriceGwei: 10, defaultGasLimit: 58000, - warningGasPriceGwei: 70, + warningGasPriceGwei: 25, transferDecimals: 6), ERC20Token(symbol: "DAI", name: "Dai", @@ -51,9 +51,9 @@ defaultOrdinalLevel: 80, reliabilityGasPricePercent: 10, reliabilityGasLimitPercent: 10, - defaultGasPriceGwei: 30, + defaultGasPriceGwei: 10, defaultGasLimit: 58000, - warningGasPriceGwei: 70, + warningGasPriceGwei: 25, transferDecimals: 6), ERC20Token(symbol: "ENS", name: "Ethereum Name Service", @@ -64,9 +64,9 @@ defaultOrdinalLevel: nil, reliabilityGasPricePercent: 10, reliabilityGasLimitPercent: 10, - defaultGasPriceGwei: 30, + defaultGasPriceGwei: 10, defaultGasLimit: 58000, - warningGasPriceGwei: 70, + warningGasPriceGwei: 25, transferDecimals: 6), ERC20Token(symbol: "FLOKI", name: "Floki", @@ -77,9 +77,9 @@ defaultOrdinalLevel: 100, reliabilityGasPricePercent: 10, reliabilityGasLimitPercent: 10, - defaultGasPriceGwei: 30, + defaultGasPriceGwei: 10, defaultGasLimit: 58000, - warningGasPriceGwei: 70, + warningGasPriceGwei: 25, transferDecimals: 6), ERC20Token(symbol: "FLUX", name: "Flux", @@ -90,9 +90,9 @@ defaultOrdinalLevel: 90, reliabilityGasPricePercent: 10, reliabilityGasLimitPercent: 10, - defaultGasPriceGwei: 30, + defaultGasPriceGwei: 10, defaultGasLimit: 58000, - warningGasPriceGwei: 70, + warningGasPriceGwei: 25, transferDecimals: 6), ERC20Token(symbol: "GT", name: "Gate", @@ -103,9 +103,9 @@ defaultOrdinalLevel: 115, reliabilityGasPricePercent: 10, reliabilityGasLimitPercent: 10, - defaultGasPriceGwei: 30, + defaultGasPriceGwei: 10, defaultGasLimit: 58000, - warningGasPriceGwei: 70, + warningGasPriceGwei: 25, transferDecimals: 6), ERC20Token(symbol: "HOT", name: "Holo", @@ -116,9 +116,9 @@ defaultOrdinalLevel: nil, reliabilityGasPricePercent: 10, reliabilityGasLimitPercent: 10, - defaultGasPriceGwei: 30, + defaultGasPriceGwei: 10, defaultGasLimit: 58000, - warningGasPriceGwei: 70, + warningGasPriceGwei: 25, transferDecimals: 6), ERC20Token(symbol: "INJ", name: "Injective", @@ -129,9 +129,9 @@ defaultOrdinalLevel: nil, reliabilityGasPricePercent: 10, reliabilityGasLimitPercent: 10, - defaultGasPriceGwei: 30, + defaultGasPriceGwei: 10, defaultGasLimit: 58000, - warningGasPriceGwei: 70, + warningGasPriceGwei: 25, transferDecimals: 6), ERC20Token(symbol: "LINK", name: "Chainlink", @@ -142,9 +142,9 @@ defaultOrdinalLevel: nil, reliabilityGasPricePercent: 10, reliabilityGasLimitPercent: 10, - defaultGasPriceGwei: 30, + defaultGasPriceGwei: 10, defaultGasLimit: 58000, - warningGasPriceGwei: 70, + warningGasPriceGwei: 25, transferDecimals: 6), ERC20Token(symbol: "MANA", name: "Decentraland", @@ -155,9 +155,9 @@ defaultOrdinalLevel: nil, reliabilityGasPricePercent: 10, reliabilityGasLimitPercent: 10, - defaultGasPriceGwei: 30, + defaultGasPriceGwei: 10, defaultGasLimit: 58000, - warningGasPriceGwei: 70, + warningGasPriceGwei: 25, transferDecimals: 6), ERC20Token(symbol: "MATIC", name: "Polygon", @@ -168,9 +168,9 @@ defaultOrdinalLevel: nil, reliabilityGasPricePercent: 10, reliabilityGasLimitPercent: 10, - defaultGasPriceGwei: 30, + defaultGasPriceGwei: 10, defaultGasLimit: 58000, - warningGasPriceGwei: 70, + warningGasPriceGwei: 25, transferDecimals: 6), ERC20Token(symbol: "PAXG", name: "PAX Gold", @@ -181,9 +181,9 @@ defaultOrdinalLevel: nil, reliabilityGasPricePercent: 10, reliabilityGasLimitPercent: 10, - defaultGasPriceGwei: 30, + defaultGasPriceGwei: 10, defaultGasLimit: 58000, - warningGasPriceGwei: 70, + warningGasPriceGwei: 25, transferDecimals: 6), ERC20Token(symbol: "QNT", name: "Quant", @@ -194,9 +194,9 @@ defaultOrdinalLevel: nil, reliabilityGasPricePercent: 10, reliabilityGasLimitPercent: 10, - defaultGasPriceGwei: 30, + defaultGasPriceGwei: 10, defaultGasLimit: 58000, - warningGasPriceGwei: 70, + warningGasPriceGwei: 25, transferDecimals: 6), ERC20Token(symbol: "REN", name: "Ren", @@ -207,9 +207,9 @@ defaultOrdinalLevel: nil, reliabilityGasPricePercent: 10, reliabilityGasLimitPercent: 10, - defaultGasPriceGwei: 30, + defaultGasPriceGwei: 10, defaultGasLimit: 58000, - warningGasPriceGwei: 70, + warningGasPriceGwei: 25, transferDecimals: 6), ERC20Token(symbol: "SKL", name: "SKALE", @@ -220,9 +220,9 @@ defaultOrdinalLevel: 85, reliabilityGasPricePercent: 10, reliabilityGasLimitPercent: 10, - defaultGasPriceGwei: 30, + defaultGasPriceGwei: 10, defaultGasLimit: 58000, - warningGasPriceGwei: 70, + warningGasPriceGwei: 25, transferDecimals: 6), ERC20Token(symbol: "SNT", name: "Status", @@ -233,9 +233,9 @@ defaultOrdinalLevel: nil, reliabilityGasPricePercent: 10, reliabilityGasLimitPercent: 10, - defaultGasPriceGwei: 30, + defaultGasPriceGwei: 10, defaultGasLimit: 58000, - warningGasPriceGwei: 70, + warningGasPriceGwei: 25, transferDecimals: 6), ERC20Token(symbol: "SNX", name: "Synthetix Network", @@ -246,9 +246,9 @@ defaultOrdinalLevel: nil, reliabilityGasPricePercent: 10, reliabilityGasLimitPercent: 10, - defaultGasPriceGwei: 30, + defaultGasPriceGwei: 10, defaultGasLimit: 58000, - warningGasPriceGwei: 70, + warningGasPriceGwei: 25, transferDecimals: 6), ERC20Token(symbol: "STORJ", name: "Storj", @@ -259,9 +259,9 @@ defaultOrdinalLevel: 105, reliabilityGasPricePercent: 10, reliabilityGasLimitPercent: 10, - defaultGasPriceGwei: 30, + defaultGasPriceGwei: 10, defaultGasLimit: 58000, - warningGasPriceGwei: 70, + warningGasPriceGwei: 25, transferDecimals: 6), ERC20Token(symbol: "TUSD", name: "TrueUSD", @@ -272,9 +272,9 @@ defaultOrdinalLevel: nil, reliabilityGasPricePercent: 10, reliabilityGasLimitPercent: 10, - defaultGasPriceGwei: 30, + defaultGasPriceGwei: 10, defaultGasLimit: 58000, - warningGasPriceGwei: 70, + warningGasPriceGwei: 25, transferDecimals: 6), ERC20Token(symbol: "UNI", name: "Uniswap", @@ -285,9 +285,9 @@ defaultOrdinalLevel: nil, reliabilityGasPricePercent: 10, reliabilityGasLimitPercent: 10, - defaultGasPriceGwei: 30, + defaultGasPriceGwei: 10, defaultGasLimit: 58000, - warningGasPriceGwei: 70, + warningGasPriceGwei: 25, transferDecimals: 6), ERC20Token(symbol: "USDC", name: "USD Coin", @@ -298,9 +298,9 @@ defaultOrdinalLevel: 40, reliabilityGasPricePercent: 10, reliabilityGasLimitPercent: 10, - defaultGasPriceGwei: 30, + defaultGasPriceGwei: 10, defaultGasLimit: 58000, - warningGasPriceGwei: 70, + warningGasPriceGwei: 25, transferDecimals: 6), ERC20Token(symbol: "USDP", name: "PAX Dollar", @@ -311,9 +311,9 @@ defaultOrdinalLevel: nil, reliabilityGasPricePercent: 10, reliabilityGasLimitPercent: 10, - defaultGasPriceGwei: 30, + defaultGasPriceGwei: 10, defaultGasLimit: 58000, - warningGasPriceGwei: 70, + warningGasPriceGwei: 25, transferDecimals: 6), ERC20Token(symbol: "USDS", name: "Stably USD", @@ -324,9 +324,9 @@ defaultOrdinalLevel: nil, reliabilityGasPricePercent: 10, reliabilityGasLimitPercent: 10, - defaultGasPriceGwei: 30, + defaultGasPriceGwei: 10, defaultGasLimit: 58000, - warningGasPriceGwei: 70, + warningGasPriceGwei: 25, transferDecimals: 6), ERC20Token(symbol: "USDT", name: "Tether", @@ -337,9 +337,9 @@ defaultOrdinalLevel: 30, reliabilityGasPricePercent: 10, reliabilityGasLimitPercent: 10, - defaultGasPriceGwei: 30, + defaultGasPriceGwei: 10, defaultGasLimit: 58000, - warningGasPriceGwei: 70, + warningGasPriceGwei: 25, transferDecimals: 6), ERC20Token(symbol: "VERSE", name: "Verse", @@ -350,9 +350,9 @@ defaultOrdinalLevel: 95, reliabilityGasPricePercent: 10, reliabilityGasLimitPercent: 10, - defaultGasPriceGwei: 30, + defaultGasPriceGwei: 10, defaultGasLimit: 58000, - warningGasPriceGwei: 70, + warningGasPriceGwei: 25, transferDecimals: 6), ERC20Token(symbol: "WOO", name: "WOO Network", @@ -363,9 +363,9 @@ defaultOrdinalLevel: nil, reliabilityGasPricePercent: 10, reliabilityGasLimitPercent: 10, - defaultGasPriceGwei: 30, + defaultGasPriceGwei: 10, defaultGasLimit: 58000, - warningGasPriceGwei: 70, + warningGasPriceGwei: 25, transferDecimals: 6), ERC20Token(symbol: "XCN", name: "Onyxcoin", @@ -376,9 +376,9 @@ defaultOrdinalLevel: 110, reliabilityGasPricePercent: 10, reliabilityGasLimitPercent: 10, - defaultGasPriceGwei: 30, + defaultGasPriceGwei: 10, defaultGasLimit: 58000, - warningGasPriceGwei: 70, + warningGasPriceGwei: 25, transferDecimals: 6), ] From b6ed008fe08440dec5af4f59f02bee50856a0739 Mon Sep 17 00:00:00 2001 From: just-software-dev Date: Sun, 15 Sep 2024 18:57:05 -0300 Subject: [PATCH 081/106] [trello.com/c/WPTyfaeg] Info service params fix --- Adamant/Helpers/NodeGroup+Constants.swift | 11 +++++------ .../InfoService/InfoService+Constants.swift | 16 ++-------------- .../DataProviders/DefaultNodesProvider.swift | 2 +- 3 files changed, 8 insertions(+), 21 deletions(-) diff --git a/Adamant/Helpers/NodeGroup+Constants.swift b/Adamant/Helpers/NodeGroup+Constants.swift index b96ee3be1..ff1ce0eaa 100644 --- a/Adamant/Helpers/NodeGroup+Constants.swift +++ b/Adamant/Helpers/NodeGroup+Constants.swift @@ -28,7 +28,7 @@ extension NodeGroup { case .ipfs: return IPFSApiService.healthCheckParameters.onScreenUpdateInterval case .infoService: - return InfoService.healthCheckParameters.onScreenUpdateInterval + return AdmWalletService.healthCheckParameters.onScreenServiceUpdateInterval } } @@ -51,7 +51,7 @@ extension NodeGroup { case .ipfs: return IPFSApiService.healthCheckParameters.crucialUpdateInterval case .infoService: - return InfoService.healthCheckParameters.crucialUpdateInterval + return AdmWalletService.healthCheckParameters.crucialServiceUpdateInterval } } @@ -74,7 +74,7 @@ extension NodeGroup { case .ipfs: return IPFSApiService.healthCheckParameters.threshold case .infoService: - return InfoService.healthCheckParameters.threshold + return InfoService.threshold } } @@ -97,11 +97,10 @@ extension NodeGroup { case .ipfs: return IPFSApiService.healthCheckParameters.normalUpdateInterval case .infoService: - return InfoService.healthCheckParameters.normalUpdateInterval + return AdmWalletService.healthCheckParameters.normalServiceUpdateInterval } } - // swiftlint:disable switch_case_alignment var minNodeVersion: Version? { let version: String? switch self { @@ -147,7 +146,7 @@ extension NodeGroup { case .ipfs: return IPFSApiService.symbol case .infoService: - return InfoService.symbol + return InfoService.name } } diff --git a/Adamant/Modules/InfoService/InfoService+Constants.swift b/Adamant/Modules/InfoService/InfoService+Constants.swift index a0a23f1c1..c0014450a 100644 --- a/Adamant/Modules/InfoService/InfoService+Constants.swift +++ b/Adamant/Modules/InfoService/InfoService+Constants.swift @@ -9,21 +9,9 @@ import CommonKit extension InfoService { - nonisolated static let healthCheckParameters = CoinHealthCheckParameters( - normalUpdateInterval: 210, - crucialUpdateInterval: 30, - onScreenUpdateInterval: 10, - threshold: 1800, - normalServiceUpdateInterval: .infinity, - crucialServiceUpdateInterval: .infinity, - onScreenServiceUpdateInterval: .infinity - ) + static let threshold = 1800 - nonisolated static var symbol: String { + nonisolated static var name: String { .localized("InfoService.InfoService") } - - nonisolated static var nodes: [Node] { - [.makeDefaultNode(url: .init(string: "https://info2.adm.im")!)] - } } diff --git a/Adamant/Services/DataProviders/DefaultNodesProvider.swift b/Adamant/Services/DataProviders/DefaultNodesProvider.swift index 07c96ebd4..e25040fc0 100644 --- a/Adamant/Services/DataProviders/DefaultNodesProvider.swift +++ b/Adamant/Services/DataProviders/DefaultNodesProvider.swift @@ -36,7 +36,7 @@ private extension DefaultNodesProvider { case .ipfs: return IPFSApiService.nodes case .infoService: - return InfoService.nodes + return AdmWalletService.serviceNodes } } } From 54a55dc8d8f78ff47aa074fb2ee0c8077e07d08a Mon Sep 17 00:00:00 2001 From: just-software-dev Date: Sun, 15 Sep 2024 20:30:21 -0300 Subject: [PATCH 082/106] [trello.com/c/zrhlmyTk] Offline -> Online nodes fix --- .../Services/HealthCheck/BlockchainHealthCheckWrapper.swift | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/CommonKit/Sources/CommonKit/Services/HealthCheck/BlockchainHealthCheckWrapper.swift b/CommonKit/Sources/CommonKit/Services/HealthCheck/BlockchainHealthCheckWrapper.swift index c0b020b7e..ced190455 100644 --- a/CommonKit/Sources/CommonKit/Services/HealthCheck/BlockchainHealthCheckWrapper.swift +++ b/CommonKit/Sources/CommonKit/Services/HealthCheck/BlockchainHealthCheckWrapper.swift @@ -149,13 +149,14 @@ private extension BlockchainHealthCheckWrapper { func updateNodesAvailability(update: NodeUpdate?) { updateNodesAvailabilityLock.lock() defer { updateNodesAvailabilityLock.unlock() } + let forceIncludeId = update?.info != nil ? update?.id : nil if let update = update { applyUpdate(update: update) } let workingNodes = nodes.filter { - $0.isEnabled && ($0.isWorkingStatus) + $0.isEnabled && ($0.isWorkingStatus) || $0.id == forceIncludeId } let actualHeightsRange = getActualNodeHeightsRange( From f13cbb9afc7456cfbffcfc5df1e85b1f6c7c2ccb Mon Sep 17 00:00:00 2001 From: just-software-dev Date: Sun, 15 Sep 2024 21:32:43 -0300 Subject: [PATCH 083/106] [trello.com/c/LE0ybZ9j] fixed: anomalous behavior of the chat list scroll --- .../DataProviders/AdamantChatsProvider.swift | 20 +++++------------- .../Protocols/AdamantApiServiceProtocol.swift | 3 ++- .../ApiService/AdamantApi+Chats.swift | 6 ++++-- .../ApiService/AdamantApiService.swift | 5 ++++- .../HealthCheck/HealthCheckWrapper.swift | 21 +++++++++++++------ 5 files changed, 30 insertions(+), 25 deletions(-) diff --git a/Adamant/Services/DataProviders/AdamantChatsProvider.swift b/Adamant/Services/DataProviders/AdamantChatsProvider.swift index ca208fe1d..fee22aaf9 100644 --- a/Adamant/Services/DataProviders/AdamantChatsProvider.swift +++ b/Adamant/Services/DataProviders/AdamantChatsProvider.swift @@ -359,7 +359,11 @@ extension AdamantChatsProvider { // MARK: 3. Get transactions - let chatrooms = try? await apiGetChatrooms(address: address, offset: offset) + let chatrooms = try? await apiService.getChatRooms( + address: address, + offset: offset, + waitsForConnectivity: true + ).get() guard let chatrooms = chatrooms else { if !isInitiallySynced { @@ -410,20 +414,6 @@ extension AdamantChatsProvider { } } - func apiGetChatrooms(address: String, offset: Int?) async throws -> ChatRooms? { - do { - let chatrooms = try await apiService.getChatRooms(address: address, offset: offset).get() - return chatrooms - } catch let error as ApiServiceError { - guard case .networkError = error else { - return nil - } - - await Task.sleep(interval: requestRepeatDelay) - return try await apiGetChatrooms(address: address, offset: offset) - } - } - func getChatMessages(with addressRecipient: String, offset: Int?) async { await getChatMessages(with: addressRecipient, offset: offset, loadedCount: .zero) } diff --git a/CommonKit/Sources/CommonKit/Protocols/AdamantApiServiceProtocol.swift b/CommonKit/Sources/CommonKit/Protocols/AdamantApiServiceProtocol.swift index 93a44aa1d..3d443f38e 100644 --- a/CommonKit/Sources/CommonKit/Protocols/AdamantApiServiceProtocol.swift +++ b/CommonKit/Sources/CommonKit/Protocols/AdamantApiServiceProtocol.swift @@ -45,7 +45,8 @@ public protocol AdamantApiServiceProtocol: ApiServiceProtocol { func getChatRooms( address: String, - offset: Int? + offset: Int?, + waitsForConnectivity: Bool ) async -> ApiServiceResult func getChatMessages( diff --git a/CommonKit/Sources/CommonKit/Services/ApiService/AdamantApi+Chats.swift b/CommonKit/Sources/CommonKit/Services/ApiService/AdamantApi+Chats.swift index ea5b4b289..9ca677061 100644 --- a/CommonKit/Sources/CommonKit/Services/ApiService/AdamantApi+Chats.swift +++ b/CommonKit/Sources/CommonKit/Services/ApiService/AdamantApi+Chats.swift @@ -63,7 +63,8 @@ extension AdamantApiService { public func getChatRooms( address: String, - offset: Int? + offset: Int?, + waitsForConnectivity: Bool ) async -> ApiServiceResult { var parameters = ["limit": "20"] @@ -71,7 +72,8 @@ extension AdamantApiService { parameters["offset"] = String(offset) } - return await request { [parameters] service, origin in + return await request(waitsForConnectivity: waitsForConnectivity) { + [parameters] service, origin in await service.sendRequestJsonResponse( origin: origin, path: ApiCommands.Chats.getChatRooms + "/\(address)", diff --git a/CommonKit/Sources/CommonKit/Services/ApiService/AdamantApiService.swift b/CommonKit/Sources/CommonKit/Services/ApiService/AdamantApiService.swift index 624b5a48b..9bc3904c2 100644 --- a/CommonKit/Sources/CommonKit/Services/ApiService/AdamantApiService.swift +++ b/CommonKit/Sources/CommonKit/Services/ApiService/AdamantApiService.swift @@ -21,9 +21,12 @@ public final class AdamantApiService { } public func request( + waitsForConnectivity: Bool = false, _ request: @Sendable (APICoreProtocol, NodeOrigin) async -> ApiServiceResult ) async -> ApiServiceResult { - await service.request { admApiCore, origin in + await service.request( + waitsForConnectivity: waitsForConnectivity + ) { admApiCore, origin in await request(admApiCore.apiCore, origin) } } diff --git a/CommonKit/Sources/CommonKit/Services/HealthCheck/HealthCheckWrapper.swift b/CommonKit/Sources/CommonKit/Services/HealthCheck/HealthCheckWrapper.swift index 57011de65..c5b8a4924 100644 --- a/CommonKit/Sources/CommonKit/Services/HealthCheck/HealthCheckWrapper.swift +++ b/CommonKit/Sources/CommonKit/Services/HealthCheck/HealthCheckWrapper.swift @@ -88,18 +88,17 @@ open class HealthCheckWrapper { } public func request( - _ request: @Sendable (Service, NodeOrigin) async -> Result + waitsForConnectivity: Bool = false, + _ requestAction: @Sendable (Service, NodeOrigin) async -> Result ) async -> Result { - let nodesList = fastestNodeMode - ? sortedAllowedNodes - : sortedAllowedNodes.shuffled() + let nodesList = await nodesForRequest(waitsForConnectivity: waitsForConnectivity) var lastConnectionError = nodesList.isEmpty ? Error.noEndpointsError(nodeGroupName: name) : nil for node in nodesList { - let response = await request(service, node.preferredOrigin) + let response = await requestAction(service, node.preferredOrigin) switch response { case .success: @@ -111,7 +110,10 @@ open class HealthCheckWrapper { } if lastConnectionError != nil { healthCheck() } - return .failure(lastConnectionError ?? .noEndpointsError(nodeGroupName: name)) + + return await waitsForConnectivity + ? request(waitsForConnectivity: waitsForConnectivity, requestAction) + : .failure(lastConnectionError ?? .noEndpointsError(nodeGroupName: name)) } open func healthCheck() { @@ -122,6 +124,13 @@ open class HealthCheckWrapper { } private extension HealthCheckWrapper { + func nodesForRequest(waitsForConnectivity: Bool) async -> [Node] { + await $sortedAllowedNodes.compactMap { [fastestNodeMode = $fastestNodeMode] in + guard !waitsForConnectivity || !$0.isEmpty else { return nil } + return fastestNodeMode.value ? $0 : $0.shuffled() + }.values.first { _ in true } ?? .init() + } + func updateHealthCheckTimerSubscription() { healthCheckTimerSubscription = Timer.publish( every: sortedAllowedNodes.isEmpty From 1cb51c65b279ceb848f187554c709a44b268ad0f Mon Sep 17 00:00:00 2001 From: Iana Date: Tue, 17 Sep 2024 17:50:36 +0300 Subject: [PATCH 084/106] [trello.com/c/1WmhFW3j] Show time instead of the 'Today' date in the Chat List --- CommonKit/Sources/CommonKit/Helpers/Date+adamant.swift | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/CommonKit/Sources/CommonKit/Helpers/Date+adamant.swift b/CommonKit/Sources/CommonKit/Helpers/Date+adamant.swift index 5e1e9e7b8..5aac1960d 100644 --- a/CommonKit/Sources/CommonKit/Helpers/Date+adamant.swift +++ b/CommonKit/Sources/CommonKit/Helpers/Date+adamant.swift @@ -57,9 +57,11 @@ public extension Date { /// Returns readable day string. "Today, Yesterday, etc" func humanizedDay() -> String { let dateString: String - if isToday { // Today - dateString = String.localized("Chats.Date.Today") + let formatter = defaultFormatter + formatter.dateStyle = .none + formatter.timeStyle = .short + dateString = formatter.string(from: self) } else if daysAgo < 2 { // Yesterday dateString = elapsedTime(from: self) } else if weeksAgo < 1 { // This week, show weekday, month and date From 1000c7a6af230dc1b3b8e87d399893c3592fb39c Mon Sep 17 00:00:00 2001 From: Iana Date: Tue, 17 Sep 2024 19:07:30 +0300 Subject: [PATCH 085/106] [trello.com/c/1WmhFW3j] Added check for date display format --- .../Chat/ViewModel/ChatMessageFactory.swift | 2 +- .../Modules/ChatsList/ChatListViewController.swift | 2 +- .../ChatsList/SearchResultsViewController.swift | 2 +- .../Sources/CommonKit/Helpers/Date+adamant.swift | 14 +++++++++----- 4 files changed, 12 insertions(+), 8 deletions(-) diff --git a/Adamant/Modules/Chat/ViewModel/ChatMessageFactory.swift b/Adamant/Modules/Chat/ViewModel/ChatMessageFactory.swift index d06bfc990..708325a1a 100644 --- a/Adamant/Modules/Chat/ViewModel/ChatMessageFactory.swift +++ b/Adamant/Modules/Chat/ViewModel/ChatMessageFactory.swift @@ -520,7 +520,7 @@ private extension ChatMessageFactory { func makeDateHeader(sentDate: Date) -> ComparableAttributedString { .init(string: .init( - string: sentDate.humanizedDay(), + string: sentDate.humanizedDay(useTimeFormat: false), attributes: [ .font: UIFont.boldSystemFont(ofSize: 10), .foregroundColor: UIColor.adamant.secondary diff --git a/Adamant/Modules/ChatsList/ChatListViewController.swift b/Adamant/Modules/ChatsList/ChatListViewController.swift index 4520bb96f..f3c3b9f48 100644 --- a/Adamant/Modules/ChatsList/ChatListViewController.swift +++ b/Adamant/Modules/ChatsList/ChatListViewController.swift @@ -681,7 +681,7 @@ extension ChatListViewController { } if let date = chatroom.updatedAt as Date?, date != .adamantNullDate { - cell.dateLabel.text = date.humanizedDay() + cell.dateLabel.text = date.humanizedDay(useTimeFormat: true) } else { cell.dateLabel.text = nil } diff --git a/Adamant/Modules/ChatsList/SearchResultsViewController.swift b/Adamant/Modules/ChatsList/SearchResultsViewController.swift index ebd2f7ffb..ad06b4ac5 100644 --- a/Adamant/Modules/ChatsList/SearchResultsViewController.swift +++ b/Adamant/Modules/ChatsList/SearchResultsViewController.swift @@ -202,7 +202,7 @@ final class SearchResultsViewController: UITableViewController { cell.lastMessageLabel.attributedText = shortDescription(for: message) if let date = message.dateValue, date != .adamantNullDate { - cell.dateLabel.text = date.humanizedDay() + cell.dateLabel.text = date.humanizedDay(useTimeFormat: false) } else { cell.dateLabel.text = nil } diff --git a/CommonKit/Sources/CommonKit/Helpers/Date+adamant.swift b/CommonKit/Sources/CommonKit/Helpers/Date+adamant.swift index 5aac1960d..69fba5488 100644 --- a/CommonKit/Sources/CommonKit/Helpers/Date+adamant.swift +++ b/CommonKit/Sources/CommonKit/Helpers/Date+adamant.swift @@ -55,13 +55,17 @@ public extension Date { } /// Returns readable day string. "Today, Yesterday, etc" - func humanizedDay() -> String { + func humanizedDay(useTimeFormat: Bool) -> String { let dateString: String if isToday { // Today - let formatter = defaultFormatter - formatter.dateStyle = .none - formatter.timeStyle = .short - dateString = formatter.string(from: self) + if useTimeFormat { + let formatter = defaultFormatter + formatter.dateStyle = .none + formatter.timeStyle = .short + dateString = formatter.string(from: self) + } else { + dateString = String.localized("Chats.Date.Today") + } } else if daysAgo < 2 { // Yesterday dateString = elapsedTime(from: self) } else if weeksAgo < 1 { // This week, show weekday, month and date From 77c6bd380b867ba917c8cae73207de3273b8b68b Mon Sep 17 00:00:00 2001 From: Iana Date: Tue, 17 Sep 2024 21:16:02 +0300 Subject: [PATCH 086/106] [trello.com/c/44KDaoe5] Fixed bugs --- Adamant/App/DI/AppAssembly.swift | 6 +- .../Notifications/NotificationsView.swift | 6 +- .../NotificationsViewModel.swift | 1 - Adamant/Services/AdamantDialogService.swift | 25 --- .../Services/AdamantNotificationService.swift | 168 +++++++++++++----- 5 files changed, 128 insertions(+), 78 deletions(-) diff --git a/Adamant/App/DI/AppAssembly.swift b/Adamant/App/DI/AppAssembly.swift index ada769a24..f54ae024f 100644 --- a/Adamant/App/DI/AppAssembly.swift +++ b/Adamant/App/DI/AppAssembly.swift @@ -53,11 +53,15 @@ struct AppAssembly: Assembly { // MARK: Notifications container.register(NotificationsService.self) { r in - AdamantNotificationsService(securedStore: r.resolve(SecuredStore.self)!) + AdamantNotificationsService( + securedStore: r.resolve(SecuredStore.self)!, + vibroService: r.resolve(VibroService.self)! + ) }.initCompleted { (r, c) in // Weak reference Task { @MainActor in guard let service = c as? AdamantNotificationsService else { return } service.accountService = r.resolve(AccountService.self) + service.chatsProvider = r.resolve(ChatsProvider.self) } }.inObjectScope(.container) diff --git a/Adamant/Modules/Settings/Notifications/NotificationsView.swift b/Adamant/Modules/Settings/Notifications/NotificationsView.swift index b5b0c2185..a7ff49e2a 100644 --- a/Adamant/Modules/Settings/Notifications/NotificationsView.swift +++ b/Adamant/Modules/Settings/Notifications/NotificationsView.swift @@ -73,7 +73,7 @@ private extension NotificationsView { Text(viewModel.notificationsMode.localized) .foregroundColor(.gray) } - }.listRowBackground(Color(uiColor: .adamant.cellColor)) + } } header: { Text(viewModel.notificationsTitle) } @@ -150,7 +150,7 @@ private extension NotificationsView { } NavigationButton(action: { viewModel.presentSafariURL() }) { HStack { - Image(uiImage: viewModel.githubRowImage) + Image(uiImage: githubRowImage) Text(visitGithub) Spacer() } @@ -159,6 +159,8 @@ private extension NotificationsView { } } +private let githubRowImage: UIImage = .asset(named: "row_github") ?? UIImage() + private var messagesHeader: String { .localized("SecurityPage.Section.Messages") } diff --git a/Adamant/Modules/Settings/Notifications/NotificationsViewModel.swift b/Adamant/Modules/Settings/Notifications/NotificationsViewModel.swift index 9d09927a8..28a328230 100644 --- a/Adamant/Modules/Settings/Notifications/NotificationsViewModel.swift +++ b/Adamant/Modules/Settings/Notifications/NotificationsViewModel.swift @@ -27,7 +27,6 @@ final class NotificationsViewModel: ObservableObject { let notificationsTitle: String = .localized("SecurityPage.Row.Notifications") let safariURL = URL(string: "https://github.com/Adamant-im")! - let githubRowImage: UIImage = .asset(named: "row_github") ?? UIImage() private let descriptionText: String = .localized("SecurityPage.Row.Notifications.ModesDescription") diff --git a/Adamant/Services/AdamantDialogService.swift b/Adamant/Services/AdamantDialogService.swift index d7a2d72f9..577954048 100644 --- a/Adamant/Services/AdamantDialogService.swift +++ b/Adamant/Services/AdamantDialogService.swift @@ -23,7 +23,6 @@ final class AdamantDialogService: DialogService { private let mailDelegate = MailDelegate() private weak var window: UIWindow? - private var audioPlayer: AVAudioPlayer? nonisolated init( vibroService: VibroService, @@ -214,14 +213,6 @@ extension AdamantDialogService { // MARK: - Notifications extension AdamantDialogService { func showNotification(title: String?, message: String?, image: UIImage?, tapHandler: (() -> Void)?) { - if notificationsService.inAppVibrate { - vibroService.applyVibration(.medium) - } - - if notificationsService.inAppSound { - playSound(by: notificationsService.notificationsSound.fileName) - } - guard notificationsService.inAppToasts else { return } popupManager.showNotification( @@ -236,22 +227,6 @@ extension AdamantDialogService { func dismissNotification() { popupManager.dismissNotification() } - - private func playSound(by fileName: String) { - guard let url = Bundle.main.url(forResource: fileName.replacingOccurrences(of: ".mp3", with: ""), withExtension: "mp3") else { - return - } - - do { - try AVAudioSession.sharedInstance().setCategory(.playback) - try AVAudioSession.sharedInstance().setActive(true) - audioPlayer = try AVAudioPlayer(contentsOf: url, fileTypeHint: AVFileType.mp3.rawValue) - audioPlayer?.volume = 1.0 - audioPlayer?.play() - } catch let error as NSError { - print("error: \(error.localizedDescription)") - } - } } // MAKR: - Activity controllers diff --git a/Adamant/Services/AdamantNotificationService.swift b/Adamant/Services/AdamantNotificationService.swift index 7e545651b..f4f9020b1 100644 --- a/Adamant/Services/AdamantNotificationService.swift +++ b/Adamant/Services/AdamantNotificationService.swift @@ -11,6 +11,8 @@ import UIKit import UserNotifications import CommonKit import Combine +import CoreData +import AVFoundation extension NotificationsMode { func toRaw() -> String { @@ -41,10 +43,12 @@ enum NotificationTarget: CaseIterable { } @MainActor -final class AdamantNotificationsService: NotificationsService { +final class AdamantNotificationsService: NSObject, NotificationsService { // MARK: Dependencies private let securedStore: SecuredStore + private let vibroService: VibroService weak var accountService: AccountService? + weak var chatsProvider: ChatsProvider? // MARK: Properties private let defaultNotificationsSound: NotificationSound = .inputDefault @@ -66,10 +70,18 @@ final class AdamantNotificationsService: NotificationsService { private var subscriptions = Set() private var preservedBadgeNumber: Int? + private var audioPlayer: AVAudioPlayer? + private var unreadController: NSFetchedResultsController? // MARK: Lifecycle - nonisolated init(securedStore: SecuredStore) { + nonisolated init( + securedStore: SecuredStore, + vibroService: VibroService + ) { self.securedStore = securedStore + self.vibroService = vibroService + + super.init() Task { @MainActor in NotificationCenter.default @@ -93,53 +105,6 @@ final class AdamantNotificationsService: NotificationsService { } } - private func onUserLoggedIn() { - UNUserNotificationCenter.current().removeAllDeliveredNotifications() - UIApplication.shared.applicationIconBadgeNumber = 0 - - if let raw: String = securedStore.get(StoreKey.notificationsService.notificationsMode), - let mode = NotificationsMode(string: raw) { - setNotificationsMode(mode, completion: nil) - } else { - setNotificationsMode(.disabled, completion: nil) - } - - NotificationTarget.allCases.forEach { target in - if let raw: String = securedStore.get(target.storeId), - let sound = NotificationSound(fileName: raw) { - setNotificationSound(sound, for: target) - } - } - - inAppSound = securedStore.get(StoreKey.notificationsService.inAppSounds) ?? defaultInAppSound - inAppVibrate = securedStore.get(StoreKey.notificationsService.inAppVibrate) ?? defaultInAppVibrate - inAppToasts = securedStore.get(StoreKey.notificationsService.inAppToasts) ?? defaultInAppToasts - - preservedBadgeNumber = nil - } - - private func onUserLoggedOut() { - setNotificationsMode(.disabled, completion: nil) - setNotificationSound(defaultNotificationsSound, for: .baseMessage) - setNotificationSound(defaultNotificationsReactionSound, for: .reaction) - securedStore.remove(StoreKey.notificationsService.notificationsMode) - securedStore.remove(StoreKey.notificationsService.notificationsSound) - securedStore.remove(StoreKey.notificationsService.notificationsReactionSound) - securedStore.remove(StoreKey.notificationsService.inAppSounds) - securedStore.remove(StoreKey.notificationsService.inAppVibrate) - securedStore.remove(StoreKey.notificationsService.inAppToasts) - preservedBadgeNumber = nil - } - - private func onStayInChanged(_ stayIn: Bool) { - if stayIn { - setBadge(number: preservedBadgeNumber, force: false) - } else { - preservedBadgeNumber = nil - setBadge(number: nil, force: true) - } - } - func setInAppSound(_ value: Bool) { setValue(for: StoreKey.notificationsService.inAppSounds, value: value) inAppSound = value @@ -365,3 +330,108 @@ extension AdamantNotificationsService { backgroundNotifications = 0 } } + +private extension AdamantNotificationsService { + func onUserLoggedIn() { + UNUserNotificationCenter.current().removeAllDeliveredNotifications() + UIApplication.shared.applicationIconBadgeNumber = 0 + + if let raw: String = securedStore.get(StoreKey.notificationsService.notificationsMode), + let mode = NotificationsMode(string: raw) { + setNotificationsMode(mode, completion: nil) + } else { + setNotificationsMode(.disabled, completion: nil) + } + + NotificationTarget.allCases.forEach { target in + if let raw: String = securedStore.get(target.storeId), + let sound = NotificationSound(fileName: raw) { + setNotificationSound(sound, for: target) + } + } + + inAppSound = securedStore.get(StoreKey.notificationsService.inAppSounds) ?? defaultInAppSound + inAppVibrate = securedStore.get(StoreKey.notificationsService.inAppVibrate) ?? defaultInAppVibrate + inAppToasts = securedStore.get(StoreKey.notificationsService.inAppToasts) ?? defaultInAppToasts + + preservedBadgeNumber = nil + + Task { + await setupUnreadController() + } + } + + func onUserLoggedOut() { + setNotificationsMode(.disabled, completion: nil) + setNotificationSound(defaultNotificationsSound, for: .baseMessage) + setNotificationSound(defaultNotificationsReactionSound, for: .reaction) + securedStore.remove(StoreKey.notificationsService.notificationsMode) + securedStore.remove(StoreKey.notificationsService.notificationsSound) + securedStore.remove(StoreKey.notificationsService.notificationsReactionSound) + securedStore.remove(StoreKey.notificationsService.inAppSounds) + securedStore.remove(StoreKey.notificationsService.inAppVibrate) + securedStore.remove(StoreKey.notificationsService.inAppToasts) + preservedBadgeNumber = nil + + resetUnreadController() + } + + func onStayInChanged(_ stayIn: Bool) { + if stayIn { + setBadge(number: preservedBadgeNumber, force: false) + } else { + preservedBadgeNumber = nil + setBadge(number: nil, force: true) + } + } + + func setupUnreadController() async { + unreadController = await chatsProvider?.getUnreadMessagesController() + unreadController?.delegate = self + try? unreadController?.performFetch() + } + + func resetUnreadController() { + unreadController = nil + unreadController?.delegate = nil + } + + func playSound(by fileName: String) { + guard let url = Bundle.main.url(forResource: fileName.replacingOccurrences(of: ".mp3", with: ""), withExtension: "mp3") else { + return + } + + try? AVAudioSession.sharedInstance().setCategory(.playback) + try? AVAudioSession.sharedInstance().setActive(true) + audioPlayer = try? AVAudioPlayer(contentsOf: url, fileTypeHint: AVFileType.mp3.rawValue) + audioPlayer?.volume = 1.0 + audioPlayer?.play() + } +} + +extension AdamantNotificationsService: NSFetchedResultsControllerDelegate { + func controller( + _ controller: NSFetchedResultsController, + didChange anObject: Any, + at indexPath: IndexPath?, + for type: NSFetchedResultsChangeType, + newIndexPath: IndexPath? + ) { + guard let transaction = anObject as? ChatTransaction, + type == .insert + else { return } + + if inAppVibrate { + vibroService.applyVibration(.medium) + } + + if inAppSound { + switch transaction { + case let tx as RichMessageTransaction where tx.additionalType == .reaction: + playSound(by: notificationsReactionSound.fileName) + default: + playSound(by: notificationsSound.fileName) + } + } + } +} From 4619b2043a941f40b3dbced744914055d43a390d Mon Sep 17 00:00:00 2001 From: Iana Date: Wed, 18 Sep 2024 13:33:29 +0300 Subject: [PATCH 087/106] [trello.com/c/bSmPAIM2] Showed fixed date only while dragging --- Adamant/Modules/Chat/View/ChatViewController.swift | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/Adamant/Modules/Chat/View/ChatViewController.swift b/Adamant/Modules/Chat/View/ChatViewController.swift index 2c8295fb8..6454f5c42 100644 --- a/Adamant/Modules/Chat/View/ChatViewController.swift +++ b/Adamant/Modules/Chat/View/ChatViewController.swift @@ -202,7 +202,6 @@ final class ChatViewController: MessagesViewController { super.scrollViewDidScroll(scrollView) updateIsScrollPositionNearlyTheBottom() updateScrollDownButtonVisibility() - updateDateHeaderIfNeeded() guard viewAppeared, @@ -211,6 +210,11 @@ final class ChatViewController: MessagesViewController { viewModel.loadMoreMessagesIfNeeded() } + + override func scrollViewWillBeginDragging(_ scrollView: UIScrollView) { + super.scrollViewWillBeginDragging(scrollView) + updateDateHeaderIfNeeded() + } } extension ChatViewController { From 3d46aeb0f0f2b2260bc754497baaf145b4ecc00f Mon Sep 17 00:00:00 2001 From: just-software-dev Date: Wed, 18 Sep 2024 22:39:06 -0300 Subject: [PATCH 088/106] [trello.com/c/uDROSQJV] Show commit hash --- .gitignore | 1 + Adamant.xcodeproj/project.pbxproj | 31 ++++++++++++++++--- .../xcshareddata/swiftpm/Package.resolved | 8 ++--- .../Settings/AboutViewController.swift | 20 ++++++++++-- Adamant/SharedViews/VersionFooter.xib | 24 +++++++------- CommonKit/Package.swift | 5 ++- CommonKit/Scripts/GitDataScript.sh | 10 ++++++ .../Localization/de.lproj/Localizable.strings | 3 ++ .../Localization/en.lproj/Localizable.strings | 3 ++ .../Localization/ru.lproj/Localizable.strings | 3 ++ .../Localization/zh.lproj/Localizable.strings | 3 ++ .../CommonKit/Helpers/AdamantUtilities.swift | 9 ++++++ Podfile.lock | 2 +- 13 files changed, 96 insertions(+), 26 deletions(-) create mode 100755 CommonKit/Scripts/GitDataScript.sh diff --git a/.gitignore b/.gitignore index 7a7f97098..bdfc17fd2 100644 --- a/.gitignore +++ b/.gitignore @@ -104,6 +104,7 @@ xcuserdata/ ### Here we store Release password. It's super-secret, not for git. ### AdamantSecret.swift +GitData.plist runkit ex.rtf to do.rtf tz.rtf diff --git a/Adamant.xcodeproj/project.pbxproj b/Adamant.xcodeproj/project.pbxproj index deb924f3c..97dd63fc1 100644 --- a/Adamant.xcodeproj/project.pbxproj +++ b/Adamant.xcodeproj/project.pbxproj @@ -2816,14 +2816,15 @@ buildConfigurationList = E913C9001FFFA51E001A83F7 /* Build configuration list for PBXNativeTarget "Adamant" */; buildPhases = ( 47866E9AB7D201F2CED0064C /* [CP] Check Pods Manifest.lock */, - 418BBB14293752F800CAB719 /* Run Script - Load wallets */, + 9372E0412C9BC178006DF0B3 /* Run Script - Git Data */, E913C8EA1FFFA51D001A83F7 /* Sources */, E913C8EB1FFFA51D001A83F7 /* Frameworks */, E913C8EC1FFFA51D001A83F7 /* Resources */, 629616F00016639A2AFC5FC7 /* [CP] Embed Pods Frameworks */, E96D64E62295CD4700CA5587 /* Embed App Extensions */, A5AC8E01262E0B030053A7E2 /* Embed Frameworks */, - 41079EBC28AE974300C32DAF /* ShellScript */, + 418BBB14293752F800CAB719 /* Run Script - Load wallets */, + 41079EBC28AE974300C32DAF /* Run Script - SwiftLint */, ); buildRules = ( ); @@ -3173,7 +3174,7 @@ /* End PBXResourcesBuildPhase section */ /* Begin PBXShellScriptBuildPhase section */ - 41079EBC28AE974300C32DAF /* ShellScript */ = { + 41079EBC28AE974300C32DAF /* Run Script - SwiftLint */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( @@ -3182,6 +3183,7 @@ ); inputPaths = ( ); + name = "Run Script - SwiftLint"; outputFileListPaths = ( ); outputPaths = ( @@ -3192,7 +3194,7 @@ }; 418BBB14293752F800CAB719 /* Run Script - Load wallets */ = { isa = PBXShellScriptBuildPhase; - buildActionMask = 12; + buildActionMask = 8; files = ( ); inputFileListPaths = ( @@ -3206,7 +3208,7 @@ ); outputPaths = ( ); - runOnlyForDeploymentPostprocessing = 0; + runOnlyForDeploymentPostprocessing = 1; shellPath = /bin/sh; shellScript = "$SCRIPT_INPUT_FILE_0 xcode\n$SCRIPT_INPUT_FILE_1 xcode\n\nrm -r $PWD/scripts\n"; }; @@ -3252,6 +3254,25 @@ shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Adamant/Pods-Adamant-frameworks.sh\"\n"; showEnvVarsInLog = 0; }; + 9372E0412C9BC178006DF0B3 /* Run Script - Git Data */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + "$(SRCROOT)/CommonKit/Scripts/GitDataScript.sh", + ); + name = "Run Script - Git Data"; + outputFileListPaths = ( + ); + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "$SCRIPT_INPUT_FILE_0 xcode\n"; + }; /* End PBXShellScriptBuildPhase section */ /* Begin PBXSourcesBuildPhase section */ diff --git a/Adamant.xcworkspace/xcshareddata/swiftpm/Package.resolved b/Adamant.xcworkspace/xcshareddata/swiftpm/Package.resolved index 88a2fda6f..84afaf1e9 100644 --- a/Adamant.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/Adamant.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -60,7 +60,7 @@ "repositoryURL": "https://github.com/EFPrefix/EFQRCode.git", "state": { "branch": null, - "revision": "2991c2f318ad9529d93b2a73a382a3f9c72c64ce", + "revision": "97f66a5800dc272206be453ad19604548ff0e0e0", "version": "6.2.2" } }, @@ -309,11 +309,11 @@ }, { "package": "swift_qrcodejs", - "repositoryURL": "https://github.com/ApolloZhu/swift_qrcodejs.git", + "repositoryURL": "https://github.com/EFPrefix/swift_qrcodejs.git", "state": { "branch": null, - "revision": "374dc7f7b9e76c6aeb393f6a84590c6d387e1ecb", - "version": "2.2.2" + "revision": "817ba220a2eba840bae888e7eeb11207bec05f8c", + "version": "2.3.0" } }, { diff --git a/Adamant/Modules/Settings/AboutViewController.swift b/Adamant/Modules/Settings/AboutViewController.swift index d69c88907..56a235f77 100644 --- a/Adamant/Modules/Settings/AboutViewController.swift +++ b/Adamant/Modules/Settings/AboutViewController.swift @@ -14,12 +14,20 @@ import CommonKit // MARK: - Localization extension String.adamant { - struct about { + enum about { static var title: String { String.localized("About.Title", comment: "About page: scene title") } - private init() { } + static func commit(_ commit: String) -> String { + String.localizedStringWithFormat( + String.localized( + "About.Version.Commit", + comment: "Commit Hash" + ), + commit + ) + } } } @@ -168,7 +176,13 @@ final class AboutViewController: FormViewController { if let footer = UINib(nibName: "VersionFooter", bundle: nil).instantiate(withOwner: nil, options: nil).first as? UIView { if let label = footer.viewWithTag(555) as? UILabel { - label.text = AdamantUtilities.applicationVersion + label.text = [ + AdamantUtilities.applicationVersion, + AdamantUtilities.Git.commitHash.map { + .adamant.about.commit(.init($0.prefix(20))) + } + ].compactMap { $0 }.joined(separator: "\n\n") + label.textColor = UIColor.adamant.primary tableView.tableFooterView = footer } diff --git a/Adamant/SharedViews/VersionFooter.xib b/Adamant/SharedViews/VersionFooter.xib index a01667958..68a47a3d7 100644 --- a/Adamant/SharedViews/VersionFooter.xib +++ b/Adamant/SharedViews/VersionFooter.xib @@ -1,11 +1,9 @@ - - - - + + - + @@ -18,24 +16,26 @@ - + - + - - + + + + - - + diff --git a/CommonKit/Package.swift b/CommonKit/Package.swift index b2d1515aa..ded30abb6 100644 --- a/CommonKit/Package.swift +++ b/CommonKit/Package.swift @@ -15,7 +15,7 @@ let package = Package( .library( name: "CommonKit", targets: ["CommonKit"] - ), + ) ], dependencies: [ .package( @@ -67,6 +67,9 @@ let package = Package( "RNCryptor", "Alamofire", "BitcoinKit" + ], + resources: [ + .process("./Assets/GitData.plist") ] ), .testTarget( diff --git a/CommonKit/Scripts/GitDataScript.sh b/CommonKit/Scripts/GitDataScript.sh new file mode 100755 index 000000000..e2ce85461 --- /dev/null +++ b/CommonKit/Scripts/GitDataScript.sh @@ -0,0 +1,10 @@ +ROOT="$PWD" + +echo """ + + + + CommitHash + $(git rev-parse HEAD) + +""" > $ROOT/CommonKit/Sources/CommonKit/Assets/GitData.plist diff --git a/CommonKit/Sources/CommonKit/Assets/Localization/de.lproj/Localizable.strings b/CommonKit/Sources/CommonKit/Assets/Localization/de.lproj/Localizable.strings index 86034c4c2..1a9f53c19 100644 --- a/CommonKit/Sources/CommonKit/Assets/Localization/de.lproj/Localizable.strings +++ b/CommonKit/Sources/CommonKit/Assets/Localization/de.lproj/Localizable.strings @@ -22,6 +22,9 @@ /* About scene: 'Contact us' section title. */ "About.Section.ContactUs" = "Kontaktieren Sie uns"; +/* Commit Hash */ +"About.Version.Commit" = "Commit: %@"; + /* About scene: Website row */ "About.Row.Website" = "Webseite"; diff --git a/CommonKit/Sources/CommonKit/Assets/Localization/en.lproj/Localizable.strings b/CommonKit/Sources/CommonKit/Assets/Localization/en.lproj/Localizable.strings index db573172a..89e3429c4 100644 --- a/CommonKit/Sources/CommonKit/Assets/Localization/en.lproj/Localizable.strings +++ b/CommonKit/Sources/CommonKit/Assets/Localization/en.lproj/Localizable.strings @@ -22,6 +22,9 @@ /* About scene: 'Contact us' section title. */ "About.Section.ContactUs" = "Contact us"; +/* Commit Hash */ +"About.Version.Commit" = "Commit: %@"; + /* Contribute scene: 'Crashlytics' section title. */ "Contribute.Section.Crashlytics" = "Crashlytics"; diff --git a/CommonKit/Sources/CommonKit/Assets/Localization/ru.lproj/Localizable.strings b/CommonKit/Sources/CommonKit/Assets/Localization/ru.lproj/Localizable.strings index de3d032bd..b03173776 100644 --- a/CommonKit/Sources/CommonKit/Assets/Localization/ru.lproj/Localizable.strings +++ b/CommonKit/Sources/CommonKit/Assets/Localization/ru.lproj/Localizable.strings @@ -22,6 +22,9 @@ /* About scene: 'Contact us' section title. */ "About.Section.ContactUs" = "Пишите нам"; +/* Commit Hash */ +"About.Version.Commit" = "Коммит: %@"; + /* About scene: Website row */ "About.Row.Website" = "Вебсайт"; diff --git a/CommonKit/Sources/CommonKit/Assets/Localization/zh.lproj/Localizable.strings b/CommonKit/Sources/CommonKit/Assets/Localization/zh.lproj/Localizable.strings index ca3ebcbbb..fa57aef0d 100644 --- a/CommonKit/Sources/CommonKit/Assets/Localization/zh.lproj/Localizable.strings +++ b/CommonKit/Sources/CommonKit/Assets/Localization/zh.lproj/Localizable.strings @@ -22,6 +22,9 @@ /* About scene: 'Contact us' section title. */ "About.Section.ContactUs" = "联系我们"; +/* Commit Hash */ +"About.Version.Commit" = "Commit: %@"; + /* Contribute scene: 'Crashlytics' section title. */ "Contribute.Section.Crashlytics" = "Crashlytics"; diff --git a/CommonKit/Sources/CommonKit/Helpers/AdamantUtilities.swift b/CommonKit/Sources/CommonKit/Helpers/AdamantUtilities.swift index 10f91c1e9..c86213044 100644 --- a/CommonKit/Sources/CommonKit/Helpers/AdamantUtilities.swift +++ b/CommonKit/Sources/CommonKit/Helpers/AdamantUtilities.swift @@ -10,6 +10,8 @@ import Foundation import os public enum AdamantUtilities { + public enum Git {} + public static let admCurrencyExponent: Int = -8 // MARK: - Dates @@ -58,3 +60,10 @@ public enum AdamantUtilities { os_log("adamant-console-log %{public}@", message) } } + +public extension AdamantUtilities.Git { + static let commitHash = Bundle.module.url( + forResource: "GitData", + withExtension: "plist" + ).flatMap { NSDictionary(contentsOf: $0)?.value(forKey: "CommitHash") as? String } +} diff --git a/Podfile.lock b/Podfile.lock index 22fee2d0a..4e31a8ce2 100644 --- a/Podfile.lock +++ b/Podfile.lock @@ -21,4 +21,4 @@ SPEC CHECKSUMS: PODFILE CHECKSUM: a30619b79caa4b5a7497b0600d449f34b5620eec -COCOAPODS: 1.12.1 +COCOAPODS: 1.15.2 From a837dc7df164745b8e6b069a6296965107532fc1 Mon Sep 17 00:00:00 2001 From: just-software-dev Date: Thu, 19 Sep 2024 02:04:10 -0300 Subject: [PATCH 089/106] [trello.com/c/U7L6ldlM] Nodes duplication fix --- .../Models/Keychain/OldNodeKeychainDTO.swift | 20 ++++--------------- .../Services/NodesMergingService.swift | 10 ++++++++-- .../CommonKit/Services/NodesStorage.swift | 2 +- 3 files changed, 13 insertions(+), 19 deletions(-) diff --git a/CommonKit/Sources/CommonKit/Models/Keychain/OldNodeKeychainDTO.swift b/CommonKit/Sources/CommonKit/Models/Keychain/OldNodeKeychainDTO.swift index fa4b0e689..968ed269f 100644 --- a/CommonKit/Sources/CommonKit/Models/Keychain/OldNodeKeychainDTO.swift +++ b/CommonKit/Sources/CommonKit/Models/Keychain/OldNodeKeychainDTO.swift @@ -46,7 +46,7 @@ extension OldNodeKeychainDTO.NodeData { case https } - func mapToModernDto() -> NodeKeychainDTO { + func mapToModernDto(group: NodeGroup) -> NodeKeychainDTO { .init( mainOrigin: .init( scheme: scheme.map(), @@ -61,7 +61,7 @@ extension OldNodeKeychainDTO.NodeData { height: height, ping: ping, connectionStatus: connectionStatus?.map(), - type: oldDefaultHosts.contains(host) + type: oldDefaultAdmHosts.contains(host) || group != .adm ? .default(isHidden: false) : .custom ) @@ -103,17 +103,7 @@ private extension OldNodeKeychainDTO.NodeData.RejectedReason { } } -private let oldDefaultHosts: [String] = [ - "btcnode1.adamant.im", - "btcnode3.adamant.im", - "ethnode2.adamant.im", - "ethnode3.adamant.im", - "klyservice1.adamant.im", - "klyservice2.adamant.im", - "dogenode1.adamant.im", - "dogenode2.adamant.im", - "dashnode1.adamant.im", - "dashnode2.adamant.im", +private let oldDefaultAdmHosts: [String] = [ "clown.adamant.im", "lake.adamant.im", "endless.adamant.im", @@ -128,7 +118,5 @@ private let oldDefaultHosts: [String] = [ "phecda.adm.im", "tegmine.adm.im", "tauri.adm.im", - "dschubba.adm.im", - "klynode1.adamant.im", - "klynode2.adamant.im" + "dschubba.adm.im" ] diff --git a/CommonKit/Sources/CommonKit/Services/NodesMergingService.swift b/CommonKit/Sources/CommonKit/Services/NodesMergingService.swift index 7d28d58e6..24aa3a00a 100644 --- a/CommonKit/Sources/CommonKit/Services/NodesMergingService.swift +++ b/CommonKit/Sources/CommonKit/Services/NodesMergingService.swift @@ -35,6 +35,7 @@ private extension NodesMergingService { var defaultNodes = defaultNodes var removedNodesIndexes: [Int] = .init() + // Merging default nodes resultNodes.enumerated().forEach { index, node in switch node.type { case .default: @@ -44,6 +45,8 @@ private extension NodesMergingService { resultNodes[index].merge(defaultNodes[defaultNodeIndex]) defaultNodes.remove(at: defaultNodeIndex) } else { + // If the default node saved, but not persists in the defaultNodes list, + // it has to be removed removedNodesIndexes.append(index) } case .custom: @@ -55,8 +58,11 @@ private extension NodesMergingService { resultNodes.remove(at: $0) } - resultNodes.append(contentsOf: defaultNodes) - return resultNodes + // We are filtering default nodes to avoid duplications. + // Maybe a new default node is a user's old custom node + return resultNodes + defaultNodes.filter { defaultNode in + !resultNodes.contains { $0.isSame(defaultNode) } + } } } diff --git a/CommonKit/Sources/CommonKit/Services/NodesStorage.swift b/CommonKit/Sources/CommonKit/Services/NodesStorage.swift index e5b4a0834..941868978 100644 --- a/CommonKit/Sources/CommonKit/Services/NodesStorage.swift +++ b/CommonKit/Sources/CommonKit/Services/NodesStorage.swift @@ -131,7 +131,7 @@ private func migrateOldNodesData(securedStore: SecuredStore) -> [NodeGroup: [Nod result[$0.group] = [] } - result[$0.group]?.append($0.node.mapToModernDto().mapToModel()) + result[$0.group]?.append($0.node.mapToModernDto(group: $0.group).mapToModel()) } return result From b006f5ae824599ce70aaead10bce6fff92e1c356 Mon Sep 17 00:00:00 2001 From: just-software-dev Date: Fri, 20 Sep 2024 00:17:10 -0300 Subject: [PATCH 090/106] [trello.com/c/uDROSQJV] Fixed commit label UI --- Adamant.xcodeproj/project.pbxproj | 8 +- .../Modules/Login/LoginViewController.swift | 24 +++-- .../Settings/AboutViewController.swift | 32 ++++--- Adamant/SharedViews/VersionFooter.xib | 41 -------- Adamant/SharedViews/VersionFooterView.swift | 96 +++++++++++++++++++ 5 files changed, 134 insertions(+), 67 deletions(-) delete mode 100644 Adamant/SharedViews/VersionFooter.xib create mode 100644 Adamant/SharedViews/VersionFooterView.swift diff --git a/Adamant.xcodeproj/project.pbxproj b/Adamant.xcodeproj/project.pbxproj index 97dd63fc1..9e12a9b4d 100644 --- a/Adamant.xcodeproj/project.pbxproj +++ b/Adamant.xcodeproj/project.pbxproj @@ -354,6 +354,7 @@ 93775E462A674FA9009061AC /* Markdown+Adamant.swift in Sources */ = {isa = PBXBuildFile; fileRef = 93775E452A674FA9009061AC /* Markdown+Adamant.swift */; }; 9377FBDF296C2A2F00C9211B /* ChatTransactionContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9377FBDE296C2A2F00C9211B /* ChatTransactionContentView.swift */; }; 9377FBE2296C2ACA00C9211B /* ChatTransactionContentView+Model.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9377FBE1296C2ACA00C9211B /* ChatTransactionContentView+Model.swift */; }; + 937EDFC02C9CF6B300F219BB /* VersionFooterView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 937EDFBF2C9CF6B300F219BB /* VersionFooterView.swift */; }; 9382F61329DEC0A3005E6216 /* ChatModelView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9382F61229DEC0A3005E6216 /* ChatModelView.swift */; }; 938F7D582955C1DA001915CA /* MessageKit in Frameworks */ = {isa = PBXBuildFile; productRef = 938F7D572955C1DA001915CA /* MessageKit */; }; 938F7D5B2955C8DA001915CA /* ChatDisplayManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 938F7D5A2955C8DA001915CA /* ChatDisplayManager.swift */; }; @@ -536,7 +537,6 @@ E9484B7F2285C016008E10F0 /* PKGeneratorViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = E9484B7E2285C016008E10F0 /* PKGeneratorViewController.swift */; }; E94883E7203F07CD00F6E1B0 /* PassphraseValidation.swift in Sources */ = {isa = PBXBuildFile; fileRef = E94883E6203F07CD00F6E1B0 /* PassphraseValidation.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 /* ShareQRFactory.swift in Sources */ = {isa = PBXBuildFile; fileRef = E94E7B07205D4CB80042B639 /* ShareQRFactory.swift */; }; E94E7B0C205D5E4A0042B639 /* TransactionsListViewControllerBase.xib in Resources */ = {isa = PBXBuildFile; fileRef = E94E7B0B205D5E4A0042B639 /* TransactionsListViewControllerBase.xib */; }; @@ -1023,6 +1023,7 @@ 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 = ""; }; + 937EDFBF2C9CF6B300F219BB /* VersionFooterView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = VersionFooterView.swift; sourceTree = ""; }; 9382F61229DEC0A3005E6216 /* ChatModelView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChatModelView.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 = ""; }; @@ -1179,7 +1180,6 @@ 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 /* 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 /* ShareQRFactory.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ShareQRFactory.swift; sourceTree = ""; }; E94E7B0B205D5E4A0042B639 /* TransactionsListViewControllerBase.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = TransactionsListViewControllerBase.xib; sourceTree = ""; }; @@ -2743,7 +2743,6 @@ 93496B9F2A6CAE9300DD062F /* LogoFullHeader.xib */, E9942B86203D9E5100C163AF /* EurekaQRRow.swift */, E9942B88203D9ECA00C163AF /* QrCell.xib */, - E948E0472024F02700975D6B /* VersionFooter.xib */, E921534C20EE1E8700C0843F /* EurekaAlertLabelRow.swift */, E921534D20EE1E8700C0843F /* AlertLabelCell.xib */, E926E031213EC43B005E536B /* FullscreenAlertView.swift */, @@ -2752,6 +2751,7 @@ E9B4E1A9210F08BE007E77FC /* DoubleDetailsTableViewCell.xib */, 649D6BEB21BD5A53009E727B /* UISuffixTextField.swift */, 55E69E162868D7920025D82E /* CheckmarkView.swift */, + 937EDFBF2C9CF6B300F219BB /* VersionFooterView.swift */, 557AC307287B1365004699D7 /* CheckmarkRowView.swift */, 551F66E528959A5200DE5D69 /* LoadingView.swift */, 93F3914F2962F5D400BFD6AE /* SpinnerView.swift */, @@ -3103,7 +3103,6 @@ E9A174B920587B84003667CD /* notification.mp3 in Resources */, 93760BE22C65A424002507C3 /* english.txt in Resources */, 645FEB35213E72C100D6BA2D /* OnboardViewController.xib in Resources */, - E948E0482024F02700975D6B /* VersionFooter.xib in Resources */, E9FAE5E3203ED1AE008D3A6B /* ShareQrViewController.xib in Resources */, 93496BB02A6CAED100DD062F /* Exo+2_300_normal.ttf in Resources */, E921534F20EE1E8700C0843F /* AlertLabelCell.xib in Resources */, @@ -3401,6 +3400,7 @@ 3AE0A42A2BC6A64900BF7125 /* FilesNetworkManager.swift in Sources */, 648CE3AC229AD2190070A2CC /* DashTransferViewController.swift in Sources */, 3A7BD0102AA9BD030045AAB0 /* AdamantVibroService.swift in Sources */, + 937EDFC02C9CF6B300F219BB /* VersionFooterView.swift in Sources */, A5E04227282A8BDC0076CD13 /* BtcBalanceResponse.swift in Sources */, 64F085D920E2D7600006DE68 /* AdmTransactionsViewController.swift in Sources */, 9322E87B2970431200B8357C /* ChatMessageFactory.swift in Sources */, diff --git a/Adamant/Modules/Login/LoginViewController.swift b/Adamant/Modules/Login/LoginViewController.swift index acb6ab91a..680a052bb 100644 --- a/Adamant/Modules/Login/LoginViewController.swift +++ b/Adamant/Modules/Login/LoginViewController.swift @@ -147,6 +147,8 @@ final class LoginViewController: FormViewController { private var firstTimeActive: Bool = true internal var hidingImagePicker: Bool = false + private lazy var versionFooterView = VersionFooterView() + /// On launch, request user biometry (TouchID/FaceID) if has an account with biometry active var requestBiometryOnFirstTimeActive: Bool = true @@ -179,6 +181,8 @@ final class LoginViewController: FormViewController { override func viewDidLoad() { super.viewDidLoad() navigationOptions = RowNavigationOptions.Disabled + tableView.tableFooterView = versionFooterView + setVersion() // MARK: Header & Footer if let header = UINib(nibName: "LogoFullHeader", bundle: nil).instantiate(withOwner: nil, options: nil).first as? UIView { @@ -190,14 +194,6 @@ final class LoginViewController: FormViewController { } } - if let footer = UINib(nibName: "VersionFooter", bundle: nil).instantiate(withOwner: nil, options: nil).first as? UIView { - if let label = footer.viewWithTag(555) as? UILabel { - label.text = AdamantUtilities.applicationVersion - label.textColor = UIColor.adamant.primary - tableView.tableFooterView = footer - } - } - // MARK: Login section form +++ Section(Sections.login.localized) { $0.tag = Sections.login.tag @@ -387,12 +383,24 @@ final class LoginViewController: FormViewController { setColors() } + override func viewWillLayoutSubviews() { + super.viewWillLayoutSubviews() + versionFooterView.sizeToFit() + } + // MARK: - Other private func setColors() { view.backgroundColor = UIColor.adamant.secondBackgroundColor tableView.backgroundColor = .clear } + + private func setVersion() { + versionFooterView.model = .init( + version: AdamantUtilities.applicationVersion, + commit: nil + ) + } } // MARK: - Login functions diff --git a/Adamant/Modules/Settings/AboutViewController.swift b/Adamant/Modules/Settings/AboutViewController.swift index 56a235f77..ff2151d41 100644 --- a/Adamant/Modules/Settings/AboutViewController.swift +++ b/Adamant/Modules/Settings/AboutViewController.swift @@ -128,6 +128,8 @@ final class AboutViewController: FormViewController { private var numerOfTap = 0 private let maxNumerOfTap = 10 + private lazy var versionFooterView = VersionFooterView() + // MARK: Init init( @@ -157,6 +159,8 @@ final class AboutViewController: FormViewController { navigationItem.largeTitleDisplayMode = .always navigationItem.title = String.adamant.about.title + tableView.tableFooterView = versionFooterView + setVersion() // MARK: Header & Footer if let header = UINib(nibName: "LogoFullHeader", bundle: nil).instantiate(withOwner: nil, options: nil).first as? UIView { @@ -174,20 +178,6 @@ final class AboutViewController: FormViewController { } } - if let footer = UINib(nibName: "VersionFooter", bundle: nil).instantiate(withOwner: nil, options: nil).first as? UIView { - if let label = footer.viewWithTag(555) as? UILabel { - label.text = [ - AdamantUtilities.applicationVersion, - AdamantUtilities.Git.commitHash.map { - .adamant.about.commit(.init($0.prefix(20))) - } - ].compactMap { $0 }.joined(separator: "\n\n") - - label.textColor = UIColor.adamant.primary - tableView.tableFooterView = footer - } - } - // MARK: About form +++ Section(Sections.about.localized) { $0.tag = Sections.about.tag @@ -295,6 +285,11 @@ final class AboutViewController: FormViewController { } } + override func viewWillLayoutSubviews() { + super.viewWillLayoutSubviews() + versionFooterView.sizeToFit() + } + // MARK: - Other private func setColors() { @@ -435,4 +430,13 @@ private extension AboutViewController { appSection.append(vibrationRow) } + + func setVersion() { + versionFooterView.model = .init( + version: AdamantUtilities.applicationVersion, + commit: AdamantUtilities.Git.commitHash.map { + .adamant.about.commit(.init($0.prefix(20))) + } + ) + } } diff --git a/Adamant/SharedViews/VersionFooter.xib b/Adamant/SharedViews/VersionFooter.xib deleted file mode 100644 index 68a47a3d7..000000000 --- a/Adamant/SharedViews/VersionFooter.xib +++ /dev/null @@ -1,41 +0,0 @@ - - - - - - - - - - - - Exo2-Regular - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/Adamant/SharedViews/VersionFooterView.swift b/Adamant/SharedViews/VersionFooterView.swift new file mode 100644 index 000000000..1cfbfeac7 --- /dev/null +++ b/Adamant/SharedViews/VersionFooterView.swift @@ -0,0 +1,96 @@ +// +// VersionFooterView.swift +// Adamant +// +// Created by Andrew G on 19.09.2024. +// Copyright © 2024 Adamant. All rights reserved. +// + +import UIKit +import SnapKit + +final class VersionFooterView: UIView { + struct Model { + let version: String + let commit: String? + + static let `default` = Self(version: .empty, commit: nil) + } + + var model: Model = .default { + didSet { update() } + } + + private let versionLabel = UILabel( + font: .adamantPrimary(ofSize: fontSize), + textColor: .adamant.primary + ) + + private let commitLabel = UILabel( + font: .adamantPrimary(ofSize: fontSize), + textColor: .adamant.primary + ) + + private lazy var labelsStack: UIStackView = { + let stack = UIStackView(arrangedSubviews: [versionLabel]) + stack.alignment = .center + stack.axis = .vertical + stack.spacing = labelsGap + return stack + }() + + private lazy var containerView: UIView = { + let view = UIView() + view.addSubview(labelsStack) + labelsStack.snp.makeConstraints { + $0.directionalHorizontalEdges.top.equalToSuperview() + $0.bottom.equalToSuperview().inset(bottomInset) + } + + return view + }() + + override init(frame: CGRect) { + super.init(frame: frame) + configure() + } + + required init?(coder: NSCoder) { + super.init(coder: coder) + configure() + } + + override func sizeThatFits(_ size: CGSize) -> CGSize { + .init( + width: size.width, + height: containerView.systemLayoutSizeFitting(size).height + ) + } +} + +private extension VersionFooterView { + func configure() { + addSubview(containerView) + containerView.snp.makeConstraints { + $0.directionalEdges.equalToSuperview() + } + } + + func update() { + versionLabel.text = model.version + commitLabel.text = model.commit + + switch (model.commit, commitLabel.superview) { + case (.some, .none): + labelsStack.addArrangedSubview(commitLabel) + case (.none, .some): + labelsStack.removeArrangedSubview(commitLabel) + case (.none, .none), (.some, .some): + break + } + } +} + +private let fontSize: CGFloat = 17 +private let labelsGap: CGFloat = 6 +private let bottomInset: CGFloat = 15 From a5ee24e6f3d97cd9900fafb3008a355024006895 Mon Sep 17 00:00:00 2001 From: just-software-dev Date: Fri, 20 Sep 2024 04:35:01 -0300 Subject: [PATCH 091/106] [trello.com/c/7oosWb7p] Fixed no nodes on start bug --- .../HealthCheck/HealthCheckWrapper.swift | 27 ++++++++++++------- 1 file changed, 18 insertions(+), 9 deletions(-) diff --git a/CommonKit/Sources/CommonKit/Services/HealthCheck/HealthCheckWrapper.swift b/CommonKit/Sources/CommonKit/Services/HealthCheck/HealthCheckWrapper.swift index 57011de65..478638c4c 100644 --- a/CommonKit/Sources/CommonKit/Services/HealthCheck/HealthCheckWrapper.swift +++ b/CommonKit/Sources/CommonKit/Services/HealthCheck/HealthCheckWrapper.swift @@ -60,16 +60,13 @@ open class HealthCheckWrapper { .filter { $0 } nodes + .removeDuplicates() + .handleEvents(receiveOutput: { [weak self] in self?.updateNodes($0) }) .removeDuplicates { !$0.doesNeedHealthCheck($1) } .combineLatest(connection) .sink { [weak self] _ in self?.healthCheck() } .store(in: &subscriptions) - nodes - .removeDuplicates() - .sink { [weak self] in self?.updateNodes($0) } - .store(in: &subscriptions) - $sortedAllowedNodes .map { $0.isEmpty } .removeDuplicates() @@ -166,13 +163,25 @@ private extension Sequence where Element == Node { } private struct NodeComparisonInfo: Hashable { - let mainOrigin: NodeOrigin - let altOrigin: NodeOrigin? + let mainOrigin: NodeOriginComparisonInfo + let altOrigin: NodeOriginComparisonInfo? let isEnabled: Bool init(node: Node) { - mainOrigin = node.mainOrigin - altOrigin = node.altOrigin + mainOrigin = .init(origin: node.mainOrigin) + altOrigin = node.altOrigin.map { .init(origin: $0) } isEnabled = node.isEnabled } } + +private struct NodeOriginComparisonInfo: Hashable { + let scheme: NodeOrigin.URLScheme + let host: String + let port: Int? + + init(origin: NodeOrigin) { + scheme = origin.scheme + host = origin.host + port = origin.port + } +} From 158ce019b39b456c678f5f647cc7bf893d4e94ec Mon Sep 17 00:00:00 2001 From: StanislavDevIOS Date: Sat, 21 Sep 2024 11:31:22 +0300 Subject: [PATCH 092/106] [trello.com/c/siM0qcTZ] fix: clear keychain on reinstall --- Adamant/App/AppDelegate.swift | 12 ---------- .../Sources/CommonKit/Core/SecuredStore.swift | 3 --- .../CommonKit/Services/KeychainStore.swift | 23 +++++++++++++++---- 3 files changed, 18 insertions(+), 20 deletions(-) diff --git a/Adamant/App/AppDelegate.swift b/Adamant/App/AppDelegate.swift index 16452eb44..32a247d4d 100644 --- a/Adamant/App/AppDelegate.swift +++ b/Adamant/App/AppDelegate.swift @@ -38,7 +38,6 @@ extension StoreKey { struct application { static let welcomeScreensIsShown = "app.welcomeScreensIsShown" static let eulaAccepted = "app.eulaAccepted" - static let firstRun = "app.firstRun" private init() {} } @@ -79,17 +78,6 @@ class AppDelegate: UIResponder, UIApplicationDelegate { .resolve(CrashlyticsService.self)? .configureIfNeeded() - // MARK: 1.2 First run flag - let firstRun = UserDefaults.standard.bool(forKey: StoreKey.application.firstRun) - - if !firstRun { - UserDefaults.standard.set(true, forKey: StoreKey.application.firstRun) - - if let securedStore = container.resolve(SecuredStore.self) { - securedStore.purgeStore() - } - } - // MARK: 2. Init UI let window = UIWindow(frame: UIScreen.main.bounds) self.window = window diff --git a/CommonKit/Sources/CommonKit/Core/SecuredStore.swift b/CommonKit/Sources/CommonKit/Core/SecuredStore.swift index acd27ca24..0dfab93d1 100644 --- a/CommonKit/Sources/CommonKit/Core/SecuredStore.swift +++ b/CommonKit/Sources/CommonKit/Core/SecuredStore.swift @@ -73,7 +73,4 @@ public protocol SecuredStore: AnyObject { func set(_ value: T, for key: String) func remove(_ key: String) - - /// Remove everything - func purgeStore() } diff --git a/CommonKit/Sources/CommonKit/Services/KeychainStore.swift b/CommonKit/Sources/CommonKit/Services/KeychainStore.swift index 5d781c5eb..e6fbd85dd 100644 --- a/CommonKit/Sources/CommonKit/Services/KeychainStore.swift +++ b/CommonKit/Sources/CommonKit/Services/KeychainStore.swift @@ -27,6 +27,7 @@ public final class KeychainStore: SecuredStore { public init(secureStorage: SecureStorageProtocol) { self.secureStorage = secureStorage + clearIfNeeded() configure() migrateIfNeeded() } @@ -57,11 +58,6 @@ public final class KeychainStore: SecuredStore { public func remove(_ key: String) { try? KeychainStore.keychain.remove(key) } - - public func purgeStore() { - try? KeychainStore.keychain.removeAll() - NotificationCenter.default.post(name: Notification.Name.SecuredStore.securedStorePurged, object: self) - } } private extension KeychainStore { @@ -93,6 +89,16 @@ private extension KeychainStore { setData(encryptedData, for: keychainStoreIdAlias) } + func clearIfNeeded() { + let isFirstRun = !UserDefaults.standard.bool(forKey: firstRun) + + guard isFirstRun else { return } + + UserDefaults.standard.set(true, forKey: firstRun) + + purgeStore() + } + func getValue(_ key: String) -> Data? { guard let keychainPassword = keychainPassword, let data = getData(for: key) @@ -151,6 +157,11 @@ private extension KeychainStore { } return try? RNCryptor.decrypt(data: encryptedData, withPassword: password) } + + func purgeStore() { + try? KeychainStore.keychain.removeAll() + NotificationCenter.default.post(name: Notification.Name.SecuredStore.securedStorePurged, object: self) + } } private extension KeychainStore { @@ -201,3 +212,5 @@ private extension KeychainStore { } } } + +private let firstRun = "app.firstRun" From 07d70bfe7b7a5ec55b6624e428750dbf48461cdc Mon Sep 17 00:00:00 2001 From: just-software-dev Date: Sat, 21 Sep 2024 17:21:35 -0300 Subject: [PATCH 093/106] [trello.com/c/sG2vnZbQ] Slow Health check fix --- .../xcshareddata/swiftpm/Package.resolved | 2 +- Adamant/App/DI/AppAssembly.swift | 4 +++- .../ViewModel/CoinsNodesListViewModel.swift | 4 +--- .../NodesEditor/NodesListViewController.swift | 6 +++--- .../DataProviders/DefaultNodesProvider.swift | 6 +++--- .../ExtensionsTools/ExtensionsApiFactory.swift | 2 +- .../Protocols/NodesStorageProtocol.swift | 2 +- .../HealthCheck/HealthCheckWrapper.swift | 2 ++ .../CommonKit/Services/NodesStorage.swift | 18 +++++++++++++----- 9 files changed, 28 insertions(+), 18 deletions(-) diff --git a/Adamant.xcworkspace/xcshareddata/swiftpm/Package.resolved b/Adamant.xcworkspace/xcshareddata/swiftpm/Package.resolved index 84afaf1e9..51f79127b 100644 --- a/Adamant.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/Adamant.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -60,7 +60,7 @@ "repositoryURL": "https://github.com/EFPrefix/EFQRCode.git", "state": { "branch": null, - "revision": "97f66a5800dc272206be453ad19604548ff0e0e0", + "revision": "3a6c5012f1a0df404a92e55bb01b4b685ff5a2d1", "version": "6.2.2" } }, diff --git a/Adamant/App/DI/AppAssembly.swift b/Adamant/App/DI/AppAssembly.swift index ada769a24..40e1bb1e3 100644 --- a/Adamant/App/DI/AppAssembly.swift +++ b/Adamant/App/DI/AppAssembly.swift @@ -111,7 +111,9 @@ struct AppAssembly: Assembly { NodesStorage( securedStore: r.resolve(SecuredStore.self)!, nodesMergingService: r.resolve(NodesMergingServiceProtocol.self)!, - defaultNodes: r.resolve(DefaultNodesProvider.self)!.nodes + defaultNodes: { [provider = r.resolve(DefaultNodesProvider.self)!] groups in + provider.get(groups) + } ) }.inObjectScope(.container) diff --git a/Adamant/Modules/CoinsNodesList/ViewModel/CoinsNodesListViewModel.swift b/Adamant/Modules/CoinsNodesList/ViewModel/CoinsNodesListViewModel.swift index 4f57b6d94..1f4c357cc 100644 --- a/Adamant/Modules/CoinsNodesList/ViewModel/CoinsNodesListViewModel.swift +++ b/Adamant/Modules/CoinsNodesList/ViewModel/CoinsNodesListViewModel.swift @@ -41,9 +41,7 @@ final class CoinsNodesListViewModel: ObservableObject { } func reset() { - processedGroups.forEach { - nodesStorage.resetNodes(group: $0) - } + nodesStorage.resetNodes(.init(processedGroups)) } } diff --git a/Adamant/Modules/NodesEditor/NodesListViewController.swift b/Adamant/Modules/NodesEditor/NodesListViewController.swift index a7035a574..8b4cb93ed 100644 --- a/Adamant/Modules/NodesEditor/NodesListViewController.swift +++ b/Adamant/Modules/NodesEditor/NodesListViewController.swift @@ -281,7 +281,7 @@ extension NodesListViewController { func resetToDefault(silent: Bool = false) { if silent { - nodesStorage.resetNodes(group: nodeGroup) + nodesStorage.resetNodes([nodeGroup]) return } @@ -290,7 +290,7 @@ extension NodesListViewController { alert.addAction(UIAlertAction( title: Rows.reset.localized, style: .destructive, - handler: { [weak self] _ in self?.nodesStorage.resetNodes(group: nodeGroup) } + handler: { [weak self] _ in self?.nodesStorage.resetNodes([nodeGroup]) } )) alert.modalPresentationStyle = .overFullScreen present(alert, animated: true, completion: nil) @@ -335,7 +335,7 @@ extension NodesListViewController: NodeEditorDelegate { extension NodesListViewController { func loadDefaultNodes(showAlert: Bool) { - nodesStorage.resetNodes(group: nodeGroup) + nodesStorage.resetNodes([nodeGroup]) if showAlert { dialogService.showSuccess(withMessage: String.adamant.nodesList.defaultNodesWasLoaded) diff --git a/Adamant/Services/DataProviders/DefaultNodesProvider.swift b/Adamant/Services/DataProviders/DefaultNodesProvider.swift index e25040fc0..a202d2de3 100644 --- a/Adamant/Services/DataProviders/DefaultNodesProvider.swift +++ b/Adamant/Services/DataProviders/DefaultNodesProvider.swift @@ -8,9 +8,9 @@ import CommonKit -struct DefaultNodesProvider { - var nodes: [NodeGroup: [Node]] { - .init(uniqueKeysWithValues: NodeGroup.allCases.map { +struct DefaultNodesProvider: Sendable { + func get(_ groups: Set) -> [NodeGroup: [Node]] { + .init(uniqueKeysWithValues: groups.map { ($0, defaultItems(group: $0)) }) } diff --git a/CommonKit/Sources/CommonKit/ExtensionsTools/ExtensionsApiFactory.swift b/CommonKit/Sources/CommonKit/ExtensionsTools/ExtensionsApiFactory.swift index d62b61826..0eb644d1e 100644 --- a/CommonKit/Sources/CommonKit/ExtensionsTools/ExtensionsApiFactory.swift +++ b/CommonKit/Sources/CommonKit/ExtensionsTools/ExtensionsApiFactory.swift @@ -23,7 +23,7 @@ public struct ExtensionsApiFactory { nodesStorage: NodesStorage( securedStore: securedStore, nodesMergingService: NodesMergingService(), - defaultNodes: .init() + defaultNodes: { _ in .init() } ), nodesAdditionalParamsStorage: NodesAdditionalParamsStorage( securedStore: securedStore diff --git a/CommonKit/Sources/CommonKit/Protocols/NodesStorageProtocol.swift b/CommonKit/Sources/CommonKit/Protocols/NodesStorageProtocol.swift index d47b2489d..04e633c52 100644 --- a/CommonKit/Sources/CommonKit/Protocols/NodesStorageProtocol.swift +++ b/CommonKit/Sources/CommonKit/Protocols/NodesStorageProtocol.swift @@ -20,7 +20,7 @@ public protocol NodesStorageProtocol { func getNodesPublisher(group: NodeGroup) -> AnyObservable<[Node]> func addNode(_ node: Node, group: NodeGroup) - func resetNodes(group: NodeGroup) + func resetNodes(_ groups: Set) func removeNode(id: UUID, group: NodeGroup) func updateNode(id: UUID, group: NodeGroup, mutate: (inout Node) -> Void) } diff --git a/CommonKit/Sources/CommonKit/Services/HealthCheck/HealthCheckWrapper.swift b/CommonKit/Sources/CommonKit/Services/HealthCheck/HealthCheckWrapper.swift index 478638c4c..636c04e6f 100644 --- a/CommonKit/Sources/CommonKit/Services/HealthCheck/HealthCheckWrapper.swift +++ b/CommonKit/Sources/CommonKit/Services/HealthCheck/HealthCheckWrapper.swift @@ -163,11 +163,13 @@ private extension Sequence where Element == Node { } private struct NodeComparisonInfo: Hashable { + let id: UUID let mainOrigin: NodeOriginComparisonInfo let altOrigin: NodeOriginComparisonInfo? let isEnabled: Bool init(node: Node) { + id = node.id mainOrigin = .init(origin: node.mainOrigin) altOrigin = node.altOrigin.map { .init(origin: $0) } isEnabled = node.isEnabled diff --git a/CommonKit/Sources/CommonKit/Services/NodesStorage.swift b/CommonKit/Sources/CommonKit/Services/NodesStorage.swift index e5b4a0834..fe4b9cda7 100644 --- a/CommonKit/Sources/CommonKit/Services/NodesStorage.swift +++ b/CommonKit/Sources/CommonKit/Services/NodesStorage.swift @@ -10,6 +10,8 @@ import Foundation import Combine public final class NodesStorage: NodesStorageProtocol { + public typealias DefaultNodesGetter = @Sendable (Set) -> [NodeGroup: [Node]] + @Atomic private var items: ObservableValue<[NodeGroup: [Node]]> public var nodesPublisher: AnyObservable<[NodeGroup: [Node]]> { @@ -21,7 +23,7 @@ public final class NodesStorage: NodesStorageProtocol { private var subscription: AnyCancellable? private let securedStore: SecuredStore - private let defaultNodes: [NodeGroup: [Node]] + private let defaultNodes: DefaultNodesGetter public func getNodesPublisher(group: NodeGroup) -> AnyObservable<[Node]> { nodesPublisher @@ -84,14 +86,20 @@ public final class NodesStorage: NodesStorageProtocol { } } - public func resetNodes(group: NodeGroup) { - items.wrappedValue[group] = defaultNodes[group] ?? .init() + public func resetNodes(_ groups: Set) { + let defaultNodes = defaultNodes(groups) + + $items.mutate { items in + for group in groups { + items.wrappedValue[group] = defaultNodes[group] ?? .init() + } + } } public init( securedStore: SecuredStore, nodesMergingService: NodesMergingServiceProtocol, - defaultNodes: [NodeGroup: [Node]] + defaultNodes: @escaping DefaultNodesGetter ) { self.securedStore = securedStore self.defaultNodes = defaultNodes @@ -104,7 +112,7 @@ public final class NodesStorage: NodesStorageProtocol { _items = .init(.init(wrappedValue: nodesMergingService.merge( savedNodes: savedNodes, - defaultNodes: defaultNodes + defaultNodes: defaultNodes(.init(NodeGroup.allCases)) ))) subscription = items.removeDuplicates().sink { [weak self] in From de1a1d17229795b4e69990e32a070a3c9d8a9d1e Mon Sep 17 00:00:00 2001 From: just-software-dev Date: Sun, 22 Sep 2024 02:23:50 -0300 Subject: [PATCH 094/106] [trello.com/c/Xj60TSNy] Stuck requests fix --- .../xcshareddata/swiftpm/Package.resolved | 4 +- .../Wallets/Doge/DogeWalletService+Send.swift | 1 + CommonKit/Package.swift | 2 +- .../CommonKit/Protocols/APICoreProtocol.swift | 38 ++++++-------- .../Sources/CommonKit/Services/APICore.swift | 49 +++++++++++++------ 5 files changed, 54 insertions(+), 40 deletions(-) diff --git a/Adamant.xcworkspace/xcshareddata/swiftpm/Package.resolved b/Adamant.xcworkspace/xcshareddata/swiftpm/Package.resolved index 51f79127b..2daa291d5 100644 --- a/Adamant.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/Adamant.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -15,8 +15,8 @@ "repositoryURL": "https://github.com/Alamofire/Alamofire.git", "state": { "branch": null, - "revision": "d120af1e8638c7da36c8481fd61a66c0c08dc4fc", - "version": "5.4.4" + "revision": "bc268c28fb170f494de9e9927c371b8342979ece", + "version": "5.7.1" } }, { diff --git a/Adamant/Modules/Wallets/Doge/DogeWalletService+Send.swift b/Adamant/Modules/Wallets/Doge/DogeWalletService+Send.swift index 7bfc5e560..6805b6c61 100644 --- a/Adamant/Modules/Wallets/Doge/DogeWalletService+Send.swift +++ b/Adamant/Modules/Wallets/Doge/DogeWalletService+Send.swift @@ -76,6 +76,7 @@ extension DogeWalletService: WalletServiceTwoStepSend { method: .post, parameters: ["rawtx": txHex], encoding: .json, + timeout: .common, downloadProgress: { _ in } ) diff --git a/CommonKit/Package.swift b/CommonKit/Package.swift index ded30abb6..a74078126 100644 --- a/CommonKit/Package.swift +++ b/CommonKit/Package.swift @@ -48,7 +48,7 @@ let package = Package( ), .package( url: "https://github.com/Alamofire/Alamofire.git", - .upToNextMinor(from: "5.4.2") + .upToNextMinor(from: "5.7.1") ), .package(path: "../BitcoinKit") ], diff --git a/CommonKit/Sources/CommonKit/Protocols/APICoreProtocol.swift b/CommonKit/Sources/CommonKit/Protocols/APICoreProtocol.swift index c9e7ac48d..26e86514b 100644 --- a/CommonKit/Sources/CommonKit/Protocols/APICoreProtocol.swift +++ b/CommonKit/Sources/CommonKit/Protocols/APICoreProtocol.swift @@ -11,11 +11,17 @@ import Alamofire public enum ApiCommands {} +public enum TimeoutSize: CaseIterable, Hashable { + case common + case extended +} + public protocol APICoreProtocol: Actor { func sendRequestMultipartFormData( origin: NodeOrigin, path: String, models: [MultipartFormDataModel], + timeout: TimeoutSize, uploadProgress: @escaping ((Progress) -> Void) ) async -> APIResponseModel @@ -25,6 +31,7 @@ public protocol APICoreProtocol: Actor { method: HTTPMethod, parameters: Parameters, encoding: APIParametersEncoding, + timeout: TimeoutSize, downloadProgress: @escaping ((Progress) -> Void) ) async -> APIResponseModel @@ -33,7 +40,8 @@ public protocol APICoreProtocol: Actor { origin: NodeOrigin, path: String, method: HTTPMethod, - jsonParameters: Any + jsonParameters: Any, + timeout: TimeoutSize ) async -> APIResponseModel } @@ -46,24 +54,6 @@ public extension APICoreProtocol { method: HTTPMethod, parameters: Parameters, encoding: APIParametersEncoding - ) async -> ApiServiceResult { - await sendRequestBasic( - origin: origin, - path: path, - method: method, - parameters: parameters, - encoding: encoding, - downloadProgress: { _ in } - ).result - } - - func sendRequest( - origin: NodeOrigin, - path: String, - method: HTTPMethod, - parameters: Parameters, - encoding: APIParametersEncoding, - downloadProgress: @escaping ((Progress) -> Void) ) async -> ApiServiceResult { await sendRequestBasic( origin: origin, @@ -71,7 +61,8 @@ public extension APICoreProtocol { method: method, parameters: parameters, encoding: encoding, - downloadProgress: downloadProgress + timeout: .common, + downloadProgress: { _ in } ).result } @@ -89,6 +80,7 @@ public extension APICoreProtocol { method: method, parameters: parameters, encoding: encoding, + timeout: .extended, downloadProgress: downloadProgress ) } @@ -147,7 +139,7 @@ public extension APICoreProtocol { parameters: emptyParameters, encoding: .url, downloadProgress: downloadProgress - ) + ).result } func sendRequest( @@ -175,7 +167,8 @@ public extension APICoreProtocol { origin: origin, path: path, method: method, - jsonParameters: jsonParameters + jsonParameters: jsonParameters, + timeout: .common ).result.flatMap { parseJSON(data: $0) } } @@ -189,6 +182,7 @@ public extension APICoreProtocol { origin: origin, path: path, models: models, + timeout: .extended, uploadProgress: uploadProgress ).result.flatMap { parseJSON(data: $0) } } diff --git a/CommonKit/Sources/CommonKit/Services/APICore.swift b/CommonKit/Sources/CommonKit/Services/APICore.swift index 0cc32e3f9..92845dd7e 100644 --- a/CommonKit/Sources/CommonKit/Services/APICore.swift +++ b/CommonKit/Sources/CommonKit/Services/APICore.swift @@ -15,24 +15,17 @@ public actor APICore: APICoreProtocol { qos: .userInteractive ) - private lazy var session: Session = { - let configuration = AF.sessionConfiguration - configuration.waitsForConnectivity = true - configuration.timeoutIntervalForRequest = timeoutIntervalForRequest - configuration.timeoutIntervalForResource = timeoutIntervalForResource - configuration.requestCachePolicy = .reloadIgnoringLocalCacheData - configuration.httpMaximumConnectionsPerHost = maximumConnectionsPerHost - return Alamofire.Session.init(configuration: configuration) - }() + private var sessions: [TimeoutSize: Session] = .init() public func sendRequestMultipartFormData( origin: NodeOrigin, path: String, models: [MultipartFormDataModel], + timeout: TimeoutSize, uploadProgress: @escaping ((Progress) -> Void) ) async -> APIResponseModel { do { - let request = session.upload( + let request = getSession(timeout).upload( multipartFormData: { multipartFormData in models.forEach { file in multipartFormData.append( @@ -61,10 +54,11 @@ public actor APICore: APICoreProtocol { method: HTTPMethod, parameters: Parameters, encoding: APIParametersEncoding, + timeout: TimeoutSize, downloadProgress: @escaping ((Progress) -> Void) ) async -> APIResponseModel { do { - let request = session.request( + let request = getSession(timeout).request( try buildUrl(origin: origin, path: path), method: method, parameters: parameters.asDictionary(), @@ -86,7 +80,8 @@ public actor APICore: APICoreProtocol { origin: NodeOrigin, path: String, method: HTTPMethod, - jsonParameters: Any + jsonParameters: Any, + timeout: TimeoutSize ) async -> APIResponseModel { do { let data = try JSONSerialization.data( @@ -100,7 +95,7 @@ public actor APICore: APICoreProtocol { request.httpBody = data request.headers.update(.contentType("application/json")) - return await sendRequest(request: session.request(request)) + return await sendRequest(request: getSession(timeout).request(request)) } catch { return .init( result: .failure(.internalError(message: error.localizedDescription, error: error)), @@ -136,8 +131,32 @@ private extension APICore { else { throw InternalAPIError.endpointBuildFailed } return url } + + func getSession(_ timeout: TimeoutSize) -> Session { + if let session = sessions[timeout] { + return session + } + + let configuration = AF.sessionConfiguration + configuration.waitsForConnectivity = true + configuration.timeoutIntervalForRequest = requestTimeout + configuration.requestCachePolicy = .reloadIgnoringLocalCacheData + configuration.httpMaximumConnectionsPerHost = maximumConnectionsPerHost + + configuration.timeoutIntervalForResource = switch timeout { + case .common: + resourceTimeout + case .extended: + extendedResourceTimeout + } + + let session = Alamofire.Session.init(configuration: configuration) + sessions[timeout] = session + return session + } } -private let timeoutIntervalForRequest: TimeInterval = 15 -private let timeoutIntervalForResource: TimeInterval = 24 * 3600 +private let requestTimeout: TimeInterval = 15 +private let resourceTimeout: TimeInterval = 15 +private let extendedResourceTimeout: TimeInterval = 24 * 3600 private let maximumConnectionsPerHost = 100 From f5dd8c83f2831a29f86eee92c77a0865d8a347d7 Mon Sep 17 00:00:00 2001 From: Iana Date: Mon, 23 Sep 2024 11:41:44 +0300 Subject: [PATCH 095/106] [trello.com/c/bSmPAIM2] Fixed bug --- Adamant/Modules/Chat/View/ChatViewController.swift | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/Adamant/Modules/Chat/View/ChatViewController.swift b/Adamant/Modules/Chat/View/ChatViewController.swift index 6454f5c42..a9443dfb5 100644 --- a/Adamant/Modules/Chat/View/ChatViewController.swift +++ b/Adamant/Modules/Chat/View/ChatViewController.swift @@ -203,6 +203,10 @@ final class ChatViewController: MessagesViewController { updateIsScrollPositionNearlyTheBottom() updateScrollDownButtonVisibility() + if scrollView.isTracking || scrollView.isDragging { + updateDateHeaderIfNeeded() + } + guard viewAppeared, scrollView.contentOffset.y <= viewModel.minOffsetForStartLoadNewMessages @@ -210,11 +214,6 @@ final class ChatViewController: MessagesViewController { viewModel.loadMoreMessagesIfNeeded() } - - override func scrollViewWillBeginDragging(_ scrollView: UIScrollView) { - super.scrollViewWillBeginDragging(scrollView) - updateDateHeaderIfNeeded() - } } extension ChatViewController { From 6e4962cc7215e822b1ee4d1b0bd85e88b9d1ac85 Mon Sep 17 00:00:00 2001 From: Iana Date: Mon, 23 Sep 2024 12:03:04 +0300 Subject: [PATCH 096/106] [trello.com/c/bSmPAIM2] Renamed the property --- Adamant/Modules/Chat/ViewModel/ChatViewModel.swift | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Adamant/Modules/Chat/ViewModel/ChatViewModel.swift b/Adamant/Modules/Chat/ViewModel/ChatViewModel.swift index e54772959..7f1e19f41 100644 --- a/Adamant/Modules/Chat/ViewModel/ChatViewModel.swift +++ b/Adamant/Modules/Chat/ViewModel/ChatViewModel.swift @@ -65,7 +65,7 @@ final class ChatViewModel: NSObject { private(set) var chatroom: Chatroom? private(set) var chatTransactions: [ChatTransaction] = [] private var tempCancellables = Set() - private var timerCancellable: AnyCancellable? + private var hideHeaderTimer: AnyCancellable? private let minDiffCountForOffset = 5 private let minDiffCountForAnimateScroll = 20 private let partnerImageSize: CGFloat = 25 @@ -1053,8 +1053,8 @@ extension ChatViewModel: NSFetchedResultsControllerDelegate { private extension ChatViewModel { func startHideDateTimer() { - timerCancellable?.cancel() - timerCancellable = Timer + hideHeaderTimer?.cancel() + hideHeaderTimer = Timer .publish(every: delayHideHeaderInSeconds, on: .main, in: .common) .autoconnect() .first() From 1fb084a5cb9e6458eb89c22fb441abe4c03a5a5f Mon Sep 17 00:00:00 2001 From: just-software-dev Date: Tue, 24 Sep 2024 00:49:56 -0300 Subject: [PATCH 097/106] [trello.com/c/Xj60TSNy] More explicit code --- .../Services/FilesNetworkManager/IPFSApiService.swift | 2 ++ .../Sources/CommonKit/Protocols/APICoreProtocol.swift | 10 ++++++++-- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/Adamant/Services/FilesNetworkManager/IPFSApiService.swift b/Adamant/Services/FilesNetworkManager/IPFSApiService.swift index 3e8496439..75f1df1ef 100644 --- a/Adamant/Services/FilesNetworkManager/IPFSApiService.swift +++ b/Adamant/Services/FilesNetworkManager/IPFSApiService.swift @@ -49,6 +49,7 @@ final class IPFSApiService: FileApiServiceProtocol { origin: origin, path: IPFSApiCommands.file.upload, models: [model], + timeout: .extended, uploadProgress: uploadProgress ) } @@ -72,6 +73,7 @@ final class IPFSApiService: FileApiServiceProtocol { let result: APIResponseModel = await core.sendRequest( origin: origin, path: "\(IPFSApiCommands.file.download)\(id)", + timeout: .extended, downloadProgress: downloadProgress ) diff --git a/CommonKit/Sources/CommonKit/Protocols/APICoreProtocol.swift b/CommonKit/Sources/CommonKit/Protocols/APICoreProtocol.swift index 26e86514b..fc67a2941 100644 --- a/CommonKit/Sources/CommonKit/Protocols/APICoreProtocol.swift +++ b/CommonKit/Sources/CommonKit/Protocols/APICoreProtocol.swift @@ -72,6 +72,7 @@ public extension APICoreProtocol { method: HTTPMethod, parameters: Parameters, encoding: APIParametersEncoding, + timeout: TimeoutSize, downloadProgress: @escaping ((Progress) -> Void) ) async -> APIResponseModel { await sendRequestBasic( @@ -80,7 +81,7 @@ public extension APICoreProtocol { method: method, parameters: parameters, encoding: encoding, - timeout: .extended, + timeout: timeout, downloadProgress: downloadProgress ) } @@ -130,6 +131,7 @@ public extension APICoreProtocol { func sendRequest( origin: NodeOrigin, path: String, + timeout: TimeoutSize, downloadProgress: @escaping ((Progress) -> Void) ) async -> ApiServiceResult { await sendRequest( @@ -138,6 +140,7 @@ public extension APICoreProtocol { method: .get, parameters: emptyParameters, encoding: .url, + timeout: timeout, downloadProgress: downloadProgress ).result } @@ -145,6 +148,7 @@ public extension APICoreProtocol { func sendRequest( origin: NodeOrigin, path: String, + timeout: TimeoutSize, downloadProgress: @escaping ((Progress) -> Void) ) async -> APIResponseModel { await sendRequest( @@ -153,6 +157,7 @@ public extension APICoreProtocol { method: .get, parameters: emptyParameters, encoding: .url, + timeout: timeout, downloadProgress: downloadProgress ) } @@ -176,13 +181,14 @@ public extension APICoreProtocol { origin: NodeOrigin, path: String, models: [MultipartFormDataModel], + timeout: TimeoutSize, uploadProgress: @escaping ((Progress) -> Void) ) async -> ApiServiceResult { await sendRequestMultipartFormData( origin: origin, path: path, models: models, - timeout: .extended, + timeout: timeout, uploadProgress: uploadProgress ).result.flatMap { parseJSON(data: $0) } } From a48fbdb5b968d0bf174ccf8be6a24ed0a3ed4bfa Mon Sep 17 00:00:00 2001 From: just-software-dev Date: Tue, 24 Sep 2024 01:09:28 -0300 Subject: [PATCH 098/106] [trello.com/c/bSmPAIM2] Date header fix --- .../Chat/View/ChatViewController.swift | 4 ++-- .../Chat/ViewModel/ChatViewModel.swift | 24 +++++++++---------- 2 files changed, 13 insertions(+), 15 deletions(-) diff --git a/Adamant/Modules/Chat/View/ChatViewController.swift b/Adamant/Modules/Chat/View/ChatViewController.swift index a9443dfb5..adf38f9ca 100644 --- a/Adamant/Modules/Chat/View/ChatViewController.swift +++ b/Adamant/Modules/Chat/View/ChatViewController.swift @@ -194,8 +194,8 @@ final class ChatViewController: MessagesViewController { super.collectionView(collectionView, willDisplay: cell, forItemAt: indexPath) } - override func scrollViewDidEndDragging(_: UIScrollView, willDecelerate _: Bool) { - viewModel.didEndScroll() + override func scrollViewDidEndDecelerating(_: UIScrollView) { + viewModel.startHideDateTimer() } override func scrollViewDidScroll(_ scrollView: UIScrollView) { diff --git a/Adamant/Modules/Chat/ViewModel/ChatViewModel.swift b/Adamant/Modules/Chat/ViewModel/ChatViewModel.swift index 7f1e19f41..a068075df 100644 --- a/Adamant/Modules/Chat/ViewModel/ChatViewModel.swift +++ b/Adamant/Modules/Chat/ViewModel/ChatViewModel.swift @@ -1038,20 +1038,10 @@ extension ChatViewModel { else { return } dateHeader = date dateHeaderHidden = false + hideHeaderTimer?.cancel() + hideHeaderTimer = nil } - func didEndScroll() { - startHideDateTimer() - } -} - -extension ChatViewModel: NSFetchedResultsControllerDelegate { - func controllerDidChangeContent(_: NSFetchedResultsController) { - updateTransactions(performFetch: false) - } -} - -private extension ChatViewModel { func startHideDateTimer() { hideHeaderTimer?.cancel() hideHeaderTimer = Timer @@ -1062,7 +1052,15 @@ private extension ChatViewModel { self?.dateHeaderHidden = true } } - +} + +extension ChatViewModel: NSFetchedResultsControllerDelegate { + func controllerDidChangeContent(_: NSFetchedResultsController) { + updateTransactions(performFetch: false) + } +} + +private extension ChatViewModel { func sendFiles(with text: String) async throws { guard apiServiceCompose.hasActiveNode(group: .ipfs) else { dialog.send(.alert(ApiServiceError.noEndpointsAvailable( From d92831c82262fce006ddea1da9a404f0eb5cc69b Mon Sep 17 00:00:00 2001 From: Iana Date: Wed, 25 Sep 2024 12:27:51 +0300 Subject: [PATCH 099/106] [trello.com/c/44KDaoe5] Code refactoring --- .../Settings/Notifications/NotificationsViewModel.swift | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/Adamant/Modules/Settings/Notifications/NotificationsViewModel.swift b/Adamant/Modules/Settings/Notifications/NotificationsViewModel.swift index 28a328230..1e3f17425 100644 --- a/Adamant/Modules/Settings/Notifications/NotificationsViewModel.swift +++ b/Adamant/Modules/Settings/Notifications/NotificationsViewModel.swift @@ -209,9 +209,8 @@ private extension NotificationsViewModel { actions: [ makeAction( title: .adamant.alert.settings, - action: { _ in - self.openAppSettings() - }), + action: { [weak self] _ in self?.openAppSettings() } + ), makeAction( title: String.adamant.alert.cancel, action: nil From ede5f098b21cd457d9ed4c9f7e70ffc2d3bcf84d Mon Sep 17 00:00:00 2001 From: StanislavDevIOS Date: Thu, 26 Sep 2024 01:00:06 +0300 Subject: [PATCH 100/106] [trello.com/c/siM0qcTZ] use shared userDefaults --- Adamant/Debug.entitlements | 4 ++++ Adamant/Release.entitlements | 4 ++++ .../CommonKit/Services/KeychainStore.swift | 17 +++++++++++++++-- .../Debug.entitlements | 4 ++++ .../Release.entitlements | 4 ++++ NotificationServiceExtension/Debug.entitlements | 4 ++++ .../Release.entitlements | 4 ++++ .../Debug.entitlements | 4 ++++ .../Release.entitlements | 4 ++++ 9 files changed, 47 insertions(+), 2 deletions(-) diff --git a/Adamant/Debug.entitlements b/Adamant/Debug.entitlements index 7713a96e4..8e1de5348 100644 --- a/Adamant/Debug.entitlements +++ b/Adamant/Debug.entitlements @@ -20,6 +20,10 @@ $(TeamIdentifierPrefix)$(CFBundleIdentifier) com.apple.security.app-sandbox + com.apple.security.application-groups + + group.adamant.adamant-messenger + com.apple.security.device.camera com.apple.security.network.client diff --git a/Adamant/Release.entitlements b/Adamant/Release.entitlements index 854da1316..85b260349 100644 --- a/Adamant/Release.entitlements +++ b/Adamant/Release.entitlements @@ -20,6 +20,10 @@ $(TeamIdentifierPrefix)$(CFBundleIdentifier) com.apple.security.app-sandbox + com.apple.security.application-groups + + group.adamant.adamant-messenger + com.apple.security.device.camera com.apple.security.network.client diff --git a/CommonKit/Sources/CommonKit/Services/KeychainStore.swift b/CommonKit/Sources/CommonKit/Services/KeychainStore.swift index e6fbd85dd..9be433fe9 100644 --- a/CommonKit/Sources/CommonKit/Services/KeychainStore.swift +++ b/CommonKit/Sources/CommonKit/Services/KeychainStore.swift @@ -23,10 +23,12 @@ public final class KeychainStore: SecuredStore { private let oldKeychainService = "im.adamant" private let migrationKey = "migrated" private let migrationValue = "2" + private lazy var userDefaults = UserDefaults(suiteName: sharedGroup) public init(secureStorage: SecureStorageProtocol) { self.secureStorage = secureStorage + migrateUserDefaultsIfNeeded() clearIfNeeded() configure() migrateIfNeeded() @@ -90,11 +92,13 @@ private extension KeychainStore { } func clearIfNeeded() { - let isFirstRun = !UserDefaults.standard.bool(forKey: firstRun) + guard let userDefaults = userDefaults else { return } + + let isFirstRun = !userDefaults.bool(forKey: firstRun) guard isFirstRun else { return } - UserDefaults.standard.set(true, forKey: firstRun) + userDefaults.set(true, forKey: firstRun) purgeStore() } @@ -211,6 +215,15 @@ private extension KeychainStore { setValue(value, for: key) } } + + func migrateUserDefaultsIfNeeded() { + let migrated = KeychainStore.keychain[migrationKey] + guard migrated != migrationValue else { return } + + let value = UserDefaults.standard.bool(forKey: firstRun) + userDefaults?.set(value, forKey: firstRun) + } } private let firstRun = "app.firstRun" +private let sharedGroup = "group.adamant.adamant-messenger" diff --git a/MessageNotificationContentExtension/Debug.entitlements b/MessageNotificationContentExtension/Debug.entitlements index 6aa188f4e..630574fd2 100644 --- a/MessageNotificationContentExtension/Debug.entitlements +++ b/MessageNotificationContentExtension/Debug.entitlements @@ -4,6 +4,10 @@ com.apple.security.app-sandbox + com.apple.security.application-groups + + group.adamant.adamant-messenger + com.apple.security.network.client keychain-access-groups diff --git a/MessageNotificationContentExtension/Release.entitlements b/MessageNotificationContentExtension/Release.entitlements index 155317021..125fd8842 100644 --- a/MessageNotificationContentExtension/Release.entitlements +++ b/MessageNotificationContentExtension/Release.entitlements @@ -4,6 +4,10 @@ com.apple.security.app-sandbox + com.apple.security.application-groups + + group.adamant.adamant-messenger + com.apple.security.network.client keychain-access-groups diff --git a/NotificationServiceExtension/Debug.entitlements b/NotificationServiceExtension/Debug.entitlements index 6aa188f4e..630574fd2 100644 --- a/NotificationServiceExtension/Debug.entitlements +++ b/NotificationServiceExtension/Debug.entitlements @@ -4,6 +4,10 @@ com.apple.security.app-sandbox + com.apple.security.application-groups + + group.adamant.adamant-messenger + com.apple.security.network.client keychain-access-groups diff --git a/NotificationServiceExtension/Release.entitlements b/NotificationServiceExtension/Release.entitlements index 155317021..125fd8842 100644 --- a/NotificationServiceExtension/Release.entitlements +++ b/NotificationServiceExtension/Release.entitlements @@ -4,6 +4,10 @@ com.apple.security.app-sandbox + com.apple.security.application-groups + + group.adamant.adamant-messenger + com.apple.security.network.client keychain-access-groups diff --git a/TransferNotificationContentExtension/Debug.entitlements b/TransferNotificationContentExtension/Debug.entitlements index 6aa188f4e..630574fd2 100644 --- a/TransferNotificationContentExtension/Debug.entitlements +++ b/TransferNotificationContentExtension/Debug.entitlements @@ -4,6 +4,10 @@ com.apple.security.app-sandbox + com.apple.security.application-groups + + group.adamant.adamant-messenger + com.apple.security.network.client keychain-access-groups diff --git a/TransferNotificationContentExtension/Release.entitlements b/TransferNotificationContentExtension/Release.entitlements index 155317021..125fd8842 100644 --- a/TransferNotificationContentExtension/Release.entitlements +++ b/TransferNotificationContentExtension/Release.entitlements @@ -4,6 +4,10 @@ com.apple.security.app-sandbox + com.apple.security.application-groups + + group.adamant.adamant-messenger + com.apple.security.network.client keychain-access-groups From c06d89c83f778ba853b0e796ad489d2d988d2c8a Mon Sep 17 00:00:00 2001 From: just-software-dev Date: Thu, 26 Sep 2024 01:03:23 -0300 Subject: [PATCH 101/106] [trello.com/c/bSmPAIM2] Fixed date header update --- Adamant/Modules/Chat/View/ChatViewController.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Adamant/Modules/Chat/View/ChatViewController.swift b/Adamant/Modules/Chat/View/ChatViewController.swift index adf38f9ca..f3f36c931 100644 --- a/Adamant/Modules/Chat/View/ChatViewController.swift +++ b/Adamant/Modules/Chat/View/ChatViewController.swift @@ -203,7 +203,7 @@ final class ChatViewController: MessagesViewController { updateIsScrollPositionNearlyTheBottom() updateScrollDownButtonVisibility() - if scrollView.isTracking || scrollView.isDragging { + if scrollView.isTracking || scrollView.isDragging || scrollView.isDecelerating { updateDateHeaderIfNeeded() } From 4ff6c5c8811600e337323eeafee7208c59024d06 Mon Sep 17 00:00:00 2001 From: just-software-dev Date: Thu, 26 Sep 2024 01:44:16 -0300 Subject: [PATCH 102/106] [trello.com/c/Jf4GpUjS] adamant-wallets update --- Adamant.xcodeproj/project.pbxproj | 21 ------------------ Adamant/Helpers/NodeGroup+Constants.swift | 4 ++-- .../AdmWalletService+DynamicConstants.swift | 10 +++++++-- .../KLYWalletService+DynamicConstants.swift | 14 ++++++++---- .../FilesNetworkManager/IPFS+Constants.swift | 13 +++++------ .../doge_notification.png | Bin 7757 -> 1493 bytes .../doge_notification@2x.png | Bin 22106 -> 2711 bytes .../doge_notification@3x.png | Bin 37813 -> 3923 bytes .../doge_wallet.imageset/doge_wallet.png | Bin 7757 -> 1493 bytes .../doge_wallet.imageset/doge_wallet@2x.png | Bin 22106 -> 2711 bytes .../doge_wallet.imageset/doge_wallet@3x.png | Bin 37813 -> 3923 bytes .../WalletImages/doge_notificationContent.png | Bin 37813 -> 3923 bytes 12 files changed, 26 insertions(+), 36 deletions(-) diff --git a/Adamant.xcodeproj/project.pbxproj b/Adamant.xcodeproj/project.pbxproj index f773c2c3c..7e58e03ef 100644 --- a/Adamant.xcodeproj/project.pbxproj +++ b/Adamant.xcodeproj/project.pbxproj @@ -2814,7 +2814,6 @@ 629616F00016639A2AFC5FC7 /* [CP] Embed Pods Frameworks */, E96D64E62295CD4700CA5587 /* Embed App Extensions */, A5AC8E01262E0B030053A7E2 /* Embed Frameworks */, - 418BBB14293752F800CAB719 /* Run Script - Load wallets */, 41079EBC28AE974300C32DAF /* Run Script - SwiftLint */, ); buildRules = ( @@ -3182,26 +3181,6 @@ shellPath = /bin/sh; shellScript = "\"${PODS_ROOT}/SwiftLint/swiftlint\"\n"; }; - 418BBB14293752F800CAB719 /* Run Script - Load wallets */ = { - isa = PBXShellScriptBuildPhase; - buildActionMask = 8; - files = ( - ); - inputFileListPaths = ( - ); - inputPaths = ( - "$(SRCROOT)/CommonKit/Scripts/UpdateWalletsScript.sh", - "$(SRCROOT)/CommonKit/Scripts/CoinsScript.rb", - ); - name = "Run Script - Load wallets"; - outputFileListPaths = ( - ); - outputPaths = ( - ); - runOnlyForDeploymentPostprocessing = 1; - shellPath = /bin/sh; - shellScript = "$SCRIPT_INPUT_FILE_0 xcode\n$SCRIPT_INPUT_FILE_1 xcode\n\nrm -r $PWD/scripts\n"; - }; 47866E9AB7D201F2CED0064C /* [CP] Check Pods Manifest.lock */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; diff --git a/Adamant/Helpers/NodeGroup+Constants.swift b/Adamant/Helpers/NodeGroup+Constants.swift index ff1ce0eaa..8fbeb847f 100644 --- a/Adamant/Helpers/NodeGroup+Constants.swift +++ b/Adamant/Helpers/NodeGroup+Constants.swift @@ -26,7 +26,7 @@ extension NodeGroup { case .dash: return DashWalletService.healthCheckParameters.onScreenUpdateInterval case .ipfs: - return IPFSApiService.healthCheckParameters.onScreenUpdateInterval + return 10 // TODO: Fix the adamant-wallets script and the repo itself case .infoService: return AdmWalletService.healthCheckParameters.onScreenServiceUpdateInterval } @@ -72,7 +72,7 @@ extension NodeGroup { case .dash: return DashWalletService.healthCheckParameters.threshold case .ipfs: - return IPFSApiService.healthCheckParameters.threshold + return IPFSApiService.healthCheckParameters.nodeHeightEpsilon case .infoService: return InfoService.threshold } diff --git a/Adamant/Modules/Wallets/Adamant/AdmWalletService+DynamicConstants.swift b/Adamant/Modules/Wallets/Adamant/AdmWalletService+DynamicConstants.swift index d52aa1793..b687a5565 100644 --- a/Adamant/Modules/Wallets/Adamant/AdmWalletService+DynamicConstants.swift +++ b/Adamant/Modules/Wallets/Adamant/AdmWalletService+DynamicConstants.swift @@ -87,8 +87,14 @@ Node.makeDefaultNode(url: URL(string: "https://dschubba.adm.im")!), static var serviceNodes: [Node] { [ - Node.makeDefaultNode(url: URL(string: "https://info.adamant.im")!), -Node.makeDefaultNode(url: URL(string: "https://info2.adm.im")!), + Node.makeDefaultNode( + url: URL(string: "https://info.adamant.im")!, + altUrl: URL(string: "http://88.198.156.44:44099")! + ), + Node.makeDefaultNode( + url: URL(string: "https://info2.adm.im")!, + altUrl: URL(string: "http://207.180.210.95:33088")! + ) ] } } diff --git a/Adamant/Modules/Wallets/Klayr/KLYWalletService+DynamicConstants.swift b/Adamant/Modules/Wallets/Klayr/KLYWalletService+DynamicConstants.swift index 5a9469fc8..7186b1e6c 100644 --- a/Adamant/Modules/Wallets/Klayr/KLYWalletService+DynamicConstants.swift +++ b/Adamant/Modules/Wallets/Klayr/KLYWalletService+DynamicConstants.swift @@ -75,15 +75,21 @@ extension KlyWalletService { static var nodes: [Node] { [ - Node.makeDefaultNode(url: URL(string: "https://klynode1.adamant.im")!, altUrl: URL(string: "http://195.26.255.137:44099")), -Node.makeDefaultNode(url: URL(string: "https://klynode2.adamant.im")!, altUrl: URL(string: "http://109.176.199.130:44099")), + Node.makeDefaultNode(url: URL(string: "https://klynode2.adamant.im")!, altUrl: URL(string: "http://109.176.199.130:44099")), +Node.makeDefaultNode(url: URL(string: "https://klynode3.adm.im")!, altUrl: URL(string: "http://37.27.205.78:44099")), ] } static var serviceNodes: [Node] { [ - Node.makeDefaultNode(url: URL(string: "https://klyservice1.adamant.im")!), -Node.makeDefaultNode(url: URL(string: "https://klyservice2.adamant.im")!), + Node.makeDefaultNode( + url: URL(string: "https://klyservice2.adamant.im")!, + altUrl: URL(string: "http://109.176.199.130:44098")! + ), + Node.makeDefaultNode( + url: URL(string: "https://klyservice3.adm.im")!, + altUrl: URL(string: "http://37.27.205.78:44098")! + ), ] } } diff --git a/Adamant/Services/FilesNetworkManager/IPFS+Constants.swift b/Adamant/Services/FilesNetworkManager/IPFS+Constants.swift index e6ec0afbd..b2422034c 100644 --- a/Adamant/Services/FilesNetworkManager/IPFS+Constants.swift +++ b/Adamant/Services/FilesNetworkManager/IPFS+Constants.swift @@ -43,13 +43,12 @@ extension IPFSApiService { ] } - static let healthCheckParameters = CoinHealthCheckParameters( - normalUpdateInterval: 210, + static let healthCheckParameters = BlockchainHealthCheckParams( + group: .ipfs, + name: symbol, + normalUpdateInterval: 300, crucialUpdateInterval: 30, - onScreenUpdateInterval: 10, - threshold: 3, - normalServiceUpdateInterval: 210, - crucialServiceUpdateInterval: 30, - onScreenServiceUpdateInterval: 10 + minNodeVersion: nil, + nodeHeightEpsilon: .zero ) } diff --git a/CommonKit/Sources/CommonKit/Assets/Shared.xcassets/Wallets/doge_notification.imageset/doge_notification.png b/CommonKit/Sources/CommonKit/Assets/Shared.xcassets/Wallets/doge_notification.imageset/doge_notification.png index 811c2e4dd482cbbf985e2f95f6a78ac55a4f8e96..c6c2fc33cdefada09b4ffac0a765233c4679c8e4 100644 GIT binary patch delta 1455 zcmV;g1yK6UJk<-3IDY^Eb5ch_0Itp)=>Px#1ZP1_K>z@;j|==^1pojAYDq*vRCoc^ znq5-kMi9rlXSA?+ijLr%00Wz+)Pj71kjfJfD(e$~Pk?g**b~4Ofqeu{U>Wiju(c;x za|7oQur%o&Y?nVqMk8Cc_*GHH)>xj_%=G-bdk|(!?WE|+C4blg>8wDc3JO#a6}o`n z_8Tc^D})vTv_W71QKue0LQpCPEtnw)Gk^_p-iv*u(4)5=q(lJ(7^yGeb96NeL#HzC z_N?eBDGy-^gC=l=VfgvuUf@GYQW;k}FBT=rBgn?6QT*^RETM?O&{MH!nkf2{|VO_^q^a ze`UI6G)_AYUSs*0ij!!QlUX%68RMGMUh!=h#<<*H-hZY>FF2zg+#TJG1de<9KtY@e?Rj<{1Dfkr*8fzV92yA{geQ6=T`hZJe@^ z6kS-!tbYN#+Sv!M_oi9y@+BjZo^pZ%@%nP2XDM!%j zc~0#uH7wg6cT-v+y>BR>-^wbp6 zk&85}PXur}=;;PYGkS7KYzZZl2RVT}oq3<_bONx1qM4$g`MCo5pA~|J(Z%8UB{LkDbdH{C);}=|-HLl@v4~#dWd)W2U zb$_M_ZJcH0{qlSBVN#pTo#rRg=k<%#Q!?5#N(EDU8i}^8afuQ{xd6#HOWuxg%6^79 z@G{(~re^06{rbgP_Z?nu?%a=Mq)hTlw|*&(m@tkIxpvpmHMD8XDY_Y^GHxiiMEEhL znQII}<9izC@~!$vs;foQ60JALETK_0x_^Az{g}&RLMinCIU>TyoQG6)`j;P)u-^GI zgr?Hx()M_Yjzn$bkO5}-g>6zC0sZi|=s7wql^d}ebecRWg8IU*+PLT`I)A2s z@enc~PJSgp2hs|wU`dECOHC`3MNdr=1I11&XtTfWIi~AG~ z<5K1-W(wy$c#TO+UB&ggc4&qQA(U&cP)fQ9b4DNec<1~|9r&bF`VfrV|4^-BOySkf z*N+h)P4>A(uF+52nl@!}*#&$FmVc+t2I%dCGu1;sF@jEsEsN%~6_k1sJD{S)5#$aw>~7*Ql0=bv?QDHZ3OLEbApl-uJQXL6SdZjqzrGd=lz)4#Ez*m! zS7hfAx5h@XPRG{IKU5J_CL((h9LPqGVYRD+6Orz`C5@s~`fz{;+2e;Qtdw>G>x{uv zCuO;4L6sR+Gg(w-eHQbBTG5uNjO(FCG89{iDzsA`obgFU)uX4*vCZ_0VkpAq0X*qL+cQ-~{{v8|DM;kr>HYu!002ov JPDHLkV1nc-wgLbE literal 7757 zcmV-T9-A=)`%TZRHLs(4+B5cy zZCP2CkWfP8oRcC*D3L*AC{hKgilPdN3KRuJ<)6BQi(`5J*T?v}D<`9NX>o-S2!C zfwIG_S@YwpTUAt1-~RSK`<(OL;v^zlZYji~U7VDz`cxOLB+ZpG&~-V|T(~lc4_BD& z&WY0aeYafvBMzZZDT4+ty~r?dZ;o z+d3?Y-8zvOyLC7-en+h-~ z;S}i(oLS_}m1THv=A}qqyjDp3xC*J?;`L#0|50$|s=f5(hd51+7iUm-|1Yh~!&{uR z)0BMN>UP4hjYtk(kMyVw$cov3-1tq9Bw9h5WF=g4PizuiPmkPyq~q(c_&?|`LpYNn@V^FJxx|+%B5;Z_TdGTD3b;S;Roz_8k z%z$=RyYcn3DfuF6#2#9Ml&H-g6p9@KRhh1wiNGzhCi37)(>;F(b9TgzA0>1Erz5=^ z(g*rfYc!NNp`rMD z16=-F17lim&ucSwK$Ex=4Fyj0za8lN)+6%3Dkw7SYPDjIpDHt4X+2(?R_e=X!~x_3 z{+xxtR7$=FI2FBFEe_)J>(>-q5gtDqKb14)Q)Ep_H=i`b6aqO!mi zv;86r*My-caw95K_Glzo*1s6wGr@pW7ulfM;DPd74`hc~A^VUuY6~3rzB+ealerDy z1Tg!=mdiy_uh)viK3q|rudq%{cEI-sS3t~ZasxS|Y9D7(`L9Tg+W3I&TqUv9Q{^t~ zHpxg>RHNPjrSk2VICTPpRiP-3vVuiyhep#607nw6E4D>*u`5oM?8E8O-LOcVk#l%6 z3R8BXxx|TQDq))#8L}EFk()j%QFyJ^<^^zSTBqiRFsG(B7fSYWB`W{7$*TVp9lBaD zh*K6kxN_Cgo81%|louqbR%Z+Nr`%M9rp1*2IC##ocd5tZ^? zXwwFvMIVgQ<{((|JdtzMn&i1d_#Dg2Q{}GAfWY!SC1U-b^$MSr8u1>IW#D&%lMpyb zhAo$$?aS%&{C}AiwdsrK&{gDOE?|jX^AxZpa3%7c=&e11PMs%8lC4oGvOzk)03~8ar6jwkXS|Q0+p4(GgX&5z6CRP+u5;+CqO6rV?urw#YiV6Zx^*=`{y5 z5h(WkY#k>{Tm*n=QC5G{$$WpU$?@eX#J&Q!$Aj3ic@&vDxtw$xu2AOv1#vAuwsKN(*W*_vJz0qa%L`R7mnu-XphCZhu z!_hdSN#}+NxdW6(HzF@`8%~$+ha%P%u|XRVzsCv*`?f+JwF5P3djTB#&f13nn~lzh zTFk*)I+@Q;%5#Y=v7f-;QgC@9Cm|FfXHJpfL{{&?DKhP9kMCQFiUK>FCMiAy_5`>_ zy_1k{CWSScw7$feFUDIA<6=(&E}V%&f4Lv}&0ZL(3c#Sn7Xt)zq$(Jr)w?lV6@-bp zKy(|upikJ2?9lb7BBwnbun}wi@JsAm`x-W{_&ovK40XH>s>q-y@0Q(`t%pgJId=Gg zl~APHHW=l*IYVABDUOU#wiuj7_ zk^%XlTCYsS)du8ol`o zL!SRD8j&Zb$)qkS(~~1@`fz1Zsu_wPER#UQ?>`wr(-eNcoZWPgEb+>^X~BGz``pZ@k|uwVZ#FpE8@3^+k~Vk=snUsi=8wdo}b~MD$Jms zmpUvKoP{dAh1!-f&7OZaLEX88`nqnaWwnpw_>eUx6lQJ2aB~dqUr=E7%n8hO#p9z3 zV%!*$Qk_e|-Pt0{4P>CJ)EhN2ThuCCFxC=*iMDuQL(X-?Q(i`(pBNmf3&B|eH_{M_ zzA}H*sl8yg>EE#G55I;k!x^0w`%o<>!=aif^ryP#cRFmHY`tvahz(nZ()_@GFsnnj zQmWCMo|rSrf~m!F$%#F5}6fABM1*Jsxi|Mg&QLxTNM9)_5$4E;! zF?tw1CNHXU9>^m0RLO2=)cRqh(g)o-56U@mNu9$B+c>sf@&-g7Tmh}rLsDam;4Ioh z9H-3j;WRS;AEiWYejuhf>x{*Xuhl-u8mnc+xmz%GIsqRNYd6O8aJl~kij|?*X|oxb zNxQKyl#QP11L&{Xi%O{zbm_JjX^z5hTQbgfrQ`Z&E(WXjqD}9EGo`^8sM<#o-A_4q z5F<^;>AlA>*-U1`SXa2AB*PBzzAIo6QDbiML{*{P(uDC0IJREa60&I`C4AGT<%+-` zms0n_>E*$kF3;ODj^-#fZm=4{x`D@QSw+Ei)YGos7)Zo}561A#pD&`*9D*`qI^KF` z1+1+%Vd_jY7P?|ET6+|Fkv*BpF=8_vH_t0E+!BZEXlwLW?8fc0QGzVHEqkb4BY83h zZ8|0ZoM?-o>tRea1wa(C4qoejhem}nx=Xxh4faAe^E~$0dfB?!`eO)(LFVOUl=^d= znmTJ~s$E4|_&Q;pqTP9-rZFQiNUhLj^u$;92l3gLcksn^BPLI$z}dkX9LFI$)&`%> z=HO;u3eHv?#30FUs)J0VE)*yA`{3=i0dl1r>sG&wP_H-8M<9o(*bB_nvG*SmKn^v8 zVYD@p#;4t=Czm~B|8FR!IyhMFiwdPZ>b^yqY~8GrN~H-xl5A6<6_Z;Q=Xn0l#PCgb z*#ym~eJIIC?G@R7vFIH#jAQ_R#7FP<;wnkCR-q z9Y<)v2|;h+pMUkY7#u7|*x^vxi2o*RMr!y@+#Za=5HU91N^CcWW0;(Dh-%;{$+N%e z0M$rOm@{pmPu`Axs)-d!YU8Mt`*vx%@eD^&xYa$QBIqTqIM;1;!qE-F(SRUH?cX2!>N!0b zPbj)bHNB-i=qU0)Re>{&L@&-ewr;lm;@s^>IQlO1dA{qoLaEo@#G`9bo^QwOp5Dm7 zvGWfHA>WL-D{)~^3ZB1$dpFy`e{%!hd^1QTK!VTa3-QGb18$vHW4bF9^S$Y~G?0tA z-ZZoo2cWm&0P2i~pv($HU1hT%Ib&#nB=PE#}6GF+Z*ofL@=e zz*Kh@nrW2kq?*`g*^OF_D=Ia+o(A^>K(>AhHROrM)E3Ky z9wO#CQ?byWOZHEtqJw1Dbcl+02(I=;W2`j_6J5mcWXU41k;3Je8_{5XqySgXmteXl z3rz|eoG$UBdUyaQbv|S&w!)b7B5>pn?9d76(eFZ@W~bmJNw)QwF&n715hxw8reokd zsHSB3Yfy zA?fB&=}1O@(+QmK&ct|o0?zk}ahYUz`F!a^K$_=(Q{&2{9&=}9XrNK(WU;4EUe1&T zEl&7)hZi1Q2&O_cFDv$4h!c0!b6N2_x^m(+qOEkPa65n<9WZH{rRy-%k$^vac>$l^ z?Ik&0gsbCf0nn?z{W)I!&Cl`ofB)Buj+wF>A6~7*PNN`wD*fPk#g3 zZ7a}PL-{sQjJ|d$hPxD)W55Pwm>n$^jN?Uso&m;UxBxe1E74yYfojP%>S%(gJRCrC zu@6d>JHJ($>}*t$xEZ2Y>rqY=w{?`A0zU?hUj*lha+=jf3EYEwL-^|BPRxuNaUgIf zUVrT$@$$=mg;!qrAF!g_)5xgs4yrMDN-D&;Aw7B;;xW`5N6hIlL*Qlvfa>RfVxh*| zP(ChF%ndIBygprq&awljmF*OmV{wMYt7;AFGwr_>9P2YPVyq{g#~cM|o1H=_wqdk0 z17Cl25r2Ac3L~9zcsg!CXz(uV*zzWB#<{^F^fe|>t^^4E z%_U$?^beV$CSnQ4( zA#J{cxfo(DD}MXaIVUjJXz)Ur+!{mF5`F#UG(Nu7fiLg3Au~A`tKR%I9Bkf!kIP1E zUjI7Ev{AS^Rf67j3Hs_|sm(fob0@>87G?-FYi4K(&|~h%SR_z_1j!xmT(sc*8OtJT zGvydaRW-#hq&%`1*@880jm-AAb7_tX=skR#9PI`Sx$HY5l*VwKfCy7A&|rC_;DT5#*

}3R?zG~=8+AAy;s8H) zYea>)V7K3PSgn5p8!7h^qWuuM&zZo!hV`re0DCf;!PaDA#UOy1qZ-C?>%B=GZcdlt zHZgRQ%ExtL?%FwnkaIWawMi=QX3<8W6#>%|OBJ|9hH;zn@ftn9 zD42(jx zpE&j|v`f8ZvBP5}!3ieu8VsC_#MhsWlI$vRV@iw9u9xEKm>eomAo5e((4gImu8KpH zx1ktqiNfiMa5R~YW2z?yS0|`vB&VDg9Fc)DEHieC^>b?6oH0`JYl)RSYLn7&?o1lacc#>)?o-q*D&l?GxSpdl-$^N!LHS#@ry`H-NYN-uch_Mcu!ppL3Q7LhtUe)6vKwor~ z2cepV*ZM~z&@)qkNg4Rk@;sMH^W3wh!q{_03F}l^BJijC{rKQg8SSH&}j=)n6lnxlQt8CUSYaNRZ^UDHF9= zluvnfXeWl+2$(T-CnRAT2%NpZU`?I_P856 z8|RC#Fp@{syaz2>U+UV$`0{oGKA1J&-W&@f?#$@${&X?!&gulv_b-(QU_V|k=d@cnHKN^!(nuiR4JRMkX{j zkb~a3Vr(?Sy&)_vE=HNmB0jk0pw|!hHj-j1sSuB0t-kQ)D{IV-Bu<;>5XJ zxtSkq3CHaVI$R!>ktFjm-JOR1>M+!4Jke1eioxbsnw&+r#HIi;mGO=w^wu9ohb0sz z$t3EEd{HX3M`gYndg>#{a1??p&vj>02P3EF`GPFR+LKU2c~c?v#PG>@%3FF*uMCsj zxtQn_Q5%s3h~r!ZpUJ>vXXX>Zz>SmRGJxl)@L%Yc2ok(BLFHtu zfXanNV3~nyF9}5{fvYZX=DTa$c(%^t2UbFt=jmfqkn_v@$^8R_;PNwFgeb^z<=9Ul zR5|V(`_)4DzLhA?w?V7PjwF|c>ty*$;1&T&$OVfiA5KvI1!AHl8Y7JnXf4@;`a&PH z7=mztYThhKkS+LWK+gaq7THFy&s?4?5<;0{#VE)w}W0yk4kbOTdB;ahYrd=$B3W`gxiU7qicExGL0u@4#KqY!TJKMGsnp14L#$W_7rFY)-A z&l9MS-ka0m!#OQ~k?enAM79{j;2A;t1 z1WpK&DS!83F@KiWtEEPl<=SX_3V)$b!cTUJcmgH_ff25hTm0DRG>UYda)>9#kO^QICpwRK6<>`N3Qvu~ziMz%k|mxI&p9S16@KRRz787Po#rEbw)-S482%{ zDJ4JORdbwYvLpa3=?O@-z|`b2I0h`8CmHf2J^u1UAy4un04gD#0XDkG*!7F0JmnZ~ zN!`k~=X>+&BwOL;es;>1OU68(e>N!CFPs9o!fYP`=l%V`6;g6)a|jsweI+HPHq(_F| zcSHey<$|7{?#<$ROZO97?npiIF2WD3Kwi2{jXulmr$reaoIcN&(<%IkIj`@@T&5i- z%dq3rNp_qjfv$;*j|<{#xcpcfu7nDsN$%+zedw(_;d|etTIhh@nu7!~f}dzRM!9#4 z@2uFvkF>@K;8-x3GKQYcsYRAl0$|4GJpKL3WD$R7-o)RYDdu}>qj{=Th&r?yi4p7W zniQUX1{%+b5*;~xiW66y>dfgVij$p$D{CRy?eFgdSCFuaQ^!-BSd4;r8v$s8W4EAm-2-s$p;N>RcyfL5|DM%=Eo=^!>V20+#H!9NHuY z;kEA%E|JQ?30qE`uow)ShQQUU_i-lLJ$X9q7%P+wVeN~oycBLlpcP3>pwT2E{34L#^dM~FJ*mC4_#2yRb@eg9|1>ou)f|I2=kURsp zs)9Y7k~$k>p7&2oa&Pa<6I)EtA#dG^3V9pJ;Tw=0zYUV4T~KGcp;+n*gFFCwk{WeG zkS6a!M$~5EH_ov%YQ=@Eye*2`RBBRqdz%!#Khe|wZ!bN@m8*7h;#6A!Tus4$5jZn7 zs1{|Q(7K&5M{pMUVo91EJ8=3@QLgJ6lI=cm;?_J-tW{k~#D=cKW2=V~j;Ji2O_ zJ_1+n!X1^52}-|U;C T>K&Tt00000NkvXXu0mjf|D6lI diff --git a/CommonKit/Sources/CommonKit/Assets/Shared.xcassets/Wallets/doge_notification.imageset/doge_notification@2x.png b/CommonKit/Sources/CommonKit/Assets/Shared.xcassets/Wallets/doge_notification.imageset/doge_notification@2x.png index f2d160d4b99df56d9ff759c2eca5046530634cb1..5f2cd56cf7ff6b284cefd5509bfbfad2dd065080 100644 GIT binary patch delta 2683 zcmV->3WW9AtO1u5kT`z;0drDELIAGL9O(c600d`2O+f$vv5yP_&_M?FmNQKr>H72m<%`fs5)v4)}n8cgeBd9R|fh*-FApT4VwIuI{Gp*ei)o5~1- z6Zu6R7A;_5!WqGYcnu{RT`7o|l!t$P(TJb`oC`Mxf;}dZcXHSD2y`Z#@nKKJ8*~^u zFpoGkp-zPRbh`Em9sI#e_^|0{VkmRGzIA;Bma!@vj(>zmwX7l3W^v{_h@YKS^*$^LyooqWO?NPpW)Lka(#ELMq`f zh4TiyXt_gmG~y4z>y+N|NkKB2(#u z5oc85Ww41%9C3x1NTv62j+1YZDpysZp`tmf;)humeoJ zWWax_P#o?09DvFE-nl=p#X%?BjSj<|H0Z2f_R2_qv%$fWt2rs_-Q#kAgLxqbARy{HG ze>9pKTUR?;vqNyqs8Kf{T%7O>!85?d_UC^CDd9Qow*z+!9d!f3nMl$zlKP9QbE(*K zaEJ)>z>0aFaAvg4Yl(P{P^2d@XaQGf*nGTt&?lUD!b9*RqK-j})^H4Npjy?P-hb$u zfHTIZ#($=u`4-;zvy`-DIId*#_Tf4jM*7hO{+ zzT%s(D705P@bilIAJ5i)GNU#lRP5u*WIlXhUBCLfk8AI}UPP*BYTu%O7g=gQ2z4(H zTuc$rm2GssH^RjcO9+ciNGl;(s;+-1S&eBi7nzl?6I~kNL?A-&5Y?dq%gR^Itz2k{*YEhAQ z$=BcHy|OMGj<`RfaK1u$BI%|}O|T5pupD5jrcy)`J<|gcMnqtoC63Hk`$0vJjxf!| zm_$w-2T=M8^PXUv0NOo_ouhvw+s~tRlB%#E#XfW@dKEfdy=Z!?MCE*}3ihaQznLQu zRVWt~YrVWYcJaa`B+Zn-1zxy>-2b9~h7!RY7kADqB|C?p6+rt04Cet#Am9y#%|joC z)N;R`@brzuRbDX|+GqK!=|r<1skZ_sh_S5^Rgn1MCa&^|$?`mP+DU)4p|Q1 zaWqwm%k#Xs90qM2rfq-3Nv}71=j|u$Bl^n-r<~LS&KBg`XzOsA_b6_E;|$){|LJRc z#5t&wm?cOw>a+kULH}lb;|v29(N`lJ1}IMI-e-Z1XlYC&5w{qrf@S2LjOeQouBSE9 zq7!8ijWDN9+NXQyZZ}s+b?-RTLs&rYv(qut`*9gWYTPwF&>w#~-*ttfuvh5CX_aBb z!ilT|IIeGBPvt7&W_8w)?01*e6^_gUF0({1V;evcS9#H)?F@cqN;gd}%m>-XV~x!$ zD>)c#R2A22!uywX6b?l`Tjnxtl~e~6oxLa;m#KsuO|nF#&udmFK#lGHJkbDr{MYIpW>emPQ}jNQc4}cX zk0;C`SjJ3Il?rnUJ{)2QY=3?0^U>HVAOX}_4tx_ECzzFYI`3ctiFWH4C+SmR)}a-P zq$}@M2->9P22XlMQXW{ zbm#43GH>3ew(U-jDVaAk-_0r-F?H(n;&gRKFt&fthXkeuMO)wg@dTzImz=lvdC4$7 z4OD%ak=7xXJ}1U(a=GH8&C$e(<6J}|a$RLIr}}DsQNE;YvC!O?rcqg<$5B{W9=tqV zJ7Nr8fyZP-aCz(??*=#4)^*Kt5EY9qsNZXsxbwp_X{&00KOndZ+gSo9?VQ?b9$bDDM+ML%e#jSOn*C$#eqKddY+7zdu zfTFWDbhdSTIC%)ONg;xDC``Bxvt&Yz5991yKCpL13D%)-;W`)-s(7dwd&SCk$ZkY? z2WcBxSsIR(Mz~&#h>~x{=eCAW43XCCQr&Y0DAxa)P%=s>TrZ~oV<18skCmuy*e*hE8hW1FWfN98Kz2AIXA_u&+t>w8RnRw(G^sn!dQQ+V)9LRjS+%T pV^s}w811bx`^QCtlH@RB{05X(OI-!%-wpr(002ovPDHLkV1iqo4a@)l literal 22106 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 P00000NkvXXu0mjf{BD16 literal 37813 zcmYIvV|ZOr*KKUuwr$%uanjgEPpn2Kjdfx)w$-R{+Sqm)+eUBRd%yR)Kh|E)vwzJ! z)|hLpIp)|=8fx;WNJK~w5D=(}3NnDtx#T|s0q*lD$}GD2IU%|!7<`3*K*s*hfP~1( zA%K9OfKZf?)b?3D%Z1nf@|gO-uEe*Z>V8I}mLt%BEv)wUV$j`bMVI|<(;qV*lo-xL zf$?h`CD4X|!0+-Gb=0Qhug4P?gT~y6i=dgD%bc&a7|Ds%7gkF_dApbcz3c6dfBYx? zKlYJobciG13C}%4qS!w*7vrMSBJEKa2g7)Dgfr;KQ|ZCP+DxziC_1ewE$761iI!6g`2 zJo3YO{)qP{xKBhqjzK0J)(sk2Jt_?99{3K|_prWVeicTlcYvaa`{>4iyII=SP@ze* zEz7*b?0E6)2E*Y*LF7@N&Py8k3{l<6hYuZ{kC*hXb7>O*`gQ{wIst#%VUk7bQ4(lw z?6U!FtZbOnL6Z3Bi|RX9yC*Rad~m|p2#5bcaxX*JF&ZBJ%-k29DlFYfa#O#VerxP<%hX6c z6D$%MQm&)0jLMH~z?-#T319btrxbUtNM!T;FgI*|zFlE7iQ#zTvTAKqsqjoyvfyP& z;0$DEUA?-r*90XJ*+WF@TPh@QhoVFw%9q~#o(@bfk*`ya;evSaeG$ETJ$jtIEPE_? zEP5!1ZtI8WahUAQJD-?@Sqp?N+5A{unYLI-R&Ge0F3f&wglGcj zM0sK^Glav`J-5Jkn%Kemp{4K(|2rW+(RY$}k$19}z&ror$GO+(*O}K!nly6j(Q5x^ zpR2Z`rz3%5pX0XUr{k~FhsP{v83iiuaHGMr4EIXbY}}*!-BPj-4wQnkGQ-M`_>F+B zyre(+?bd@UR^`Qu*qK~0?6VfJ7>%-q`mve|2tt9XfQ)WAqK+e_FIR=kGue1w5KYA| zm4;UZF^Nb4hpILZQ=-6Lk`aba-e zPCHq^t67Xe$z?AGh=g}(lNWG#4^4T@ia6oA>(Y)2+@f=rloF}^A^@|cP~Q=z)j;0z zgjCr$B6PHLm-`?L+w>S%U#rJN$l zx~bvquw!c@()v-hG;?}Xt@~U8F)Ic~jNheD{|IrJlxCQx zSvvu5jKV9dU&t&yxI`y2P~nYbc<_PdjLr_B9B}}LdsD6y^Yuo*csZ=*Z|r`Wm5kc9 z`ci?dw2-h%A#icNi&-PXLv@@)E|?ulD(~&5!R&B7pv|8Q6R`8ty8dLVb^}r>Q@&HGC?HFui72S*-M*4N?!ePjyJXRh)lX>|9n3DIET*wvp0Y;qM8XdSbNbi&?2l$-^Lhn0Et>|`} zFen4FHQ7w5z*f%)Cp2}33y70SzRgK;LlM80yGm2k>CA8!4f^c#HAERi)rr2tsYJ#J zTG1$wavrsJ1se<0A;UGd_k!ZE16_2wD4it3_AcM~vIT_1GZqbaQ|;bn3*#hmx4}ip z?c%1!^+*$cxe91PlnOn!Bz&0}kg1$)amxHp_UZi*Dq((JGFY&tmHf#xDFhd9e-!SB zTR`J;#4?L+{_QR_bQqtu)8byTYY{vuY;rR5kH>bJ`U;{;MV&7k*q;w{HR}h`9hf+b zc0Q9X{bS=~UiQ$VAa(+zJMEc7&A$nEA9?6aut(|Qui(NZhvb`)eYBy zEB$1~_aEG-eZJGAktntJronf#IedVHv+d`8D_q_(pe8iBO`e=6tHzBK|1b;GCKhB$g1O=SIpnE z^XDw{LM1HPUTETF7Pax1Ga+jHK1EGPMYO6tXz;+a>x94mM+m9iJzh$>b}QC3e_oQ% zr8b??8|Q?xq*(b&4|Do*c#%+0UbGq@q@{=jID;GNmbzqgR*cIjH@`Lq1g}Qh8~SSk z?Q6(Fsz(~blhRb(oG8#zXJK*YARVx4@&)=~9005Zl>a_LpQjQhpPLR1Rc+!X0m8bI zcly#3bRw=nCfvM0vXL4~#A3y`FR^mdcbA!f*YZ1`GWp8eG~|}dF~-!>_aC4}A?GNQ zka?Y9#aldWz*_oVLt5Bz!(W*1^9*61C+3iL9GX^`8&q7SMphQw?<#z_g6fIJ(4+%Z zHwP9>ub=S!@gnr)>J6dE3|b5^++meK){OQ?UNW&*U#jm5OvtMC(SkVinbP)+g5WV$ z9#0=M!|1_QtXzJfU=g;2LvD}Vfbdxf>BCG1HQ(wX{f@zxg@7Th0)>;`OZ)$D!23^> z)<&oiv!VNbsYtGr^h5H)Y7aNE@_~vZAv@Q0E`D0#Xs6Uq{7z0q{H9tK7AP!1bM;Q$@c*6c%jAg4x5e;m@+Ia4m zzAre{{s@K~1w=LFarHaNNrK7}bN{}4=bDroX|kF&NU18lHgRyRHsR7rA!^9z+tEY= zL@?ejgFBFFBOBogNE|m}HGcQ*bn7qAVWO;gAA!fN^I6Ke_rQVwOwA@h&ha$6X}UwP;lH+ph?f!XYQV*%cHm>`(pn>;C_ym`>EwGAe#c23@YmgkK>t>{VRt|51()3*| zE}4@R&yWXq{tQZPQMa)tB?B`zMt)Qlp7 zix0Z#&k)%Y_}coRv99q32sL%tr#%Z#WtYx_nZbS$*YQ%49X}GY4}|4$9sQCGaWZP_ zXj~S08Z}vdOLkaO<^sJIEaL1uvtlMcfvay8vIvfwx>tk<>9K9li%3@D3Rq1ZDiQp8)>XKVA*5d3Z!z4|YoNMaf~+ zYb>r5kuTtDn{|Jc7_MoJtt8OsB73l23N82S^fJ$a#OlK&X%))*7xyg2vvPgJKk$EL z89HScviB^EM>KvX8D>cuyg*0XbjwnAIx1^>bK4Djg>*;bPJ_=O&!0^+(l1bylvYCE z_{&S6<^@QC2qlU@D5YAyGHe+<9KM^ctf|#>-S92BjXNYg zdYdyFRGRwf6K`wSOvyw?tghNIFGi#Fx1YPk(2)-*w?#4_@Lx%OG4!S`K1;K<>-?Cy z7be#(qC%vbp@=LmvTA#w5J=9tkZ&^VghknF$y76C<3P4KG!%zu|GM{%G)9gd?!gNr z;T?iJU#72K>WUWLsNxWR>3Tm^l_r8@4XdLM-Fnb5;C9Y*#AXENiC`s7CzZKBDQT8Q zvnokcS#l#puV$aSX2vhr1H*u-w`gr7b$9SQf4{v?T;Y>DoViPFHB5p9u&9Z^sYsoU zml{}lgbaB$1Mo4?e8?V`C{%|J+Br&oo}3Z`U!EZAH2x*gVPGUS2jnxVlT?*rM!i}i z-K50j3(XP6-^mVWRx^7SVQ=M~B6yDI@=GW>7({`}8n%D%IPbda6lY0kl_E1+nJ##! zY~^W`c|;sIJt{I7d5Cbtb$(;olWU>jR|_2fZS*#CDi?m_M-$0TH&9W4$VNj^oaVvu zvoVEaE~Tg@+$xIJ*isZQ9&E_4R>|)UsWPLPz}QKbh|9&fDmSd?p@>1Rc_hsx)Fp8u z?l-a55phBYb;C?IwRs&euFNCVWQiVGpC=+l6VmX)JDur_-lC|h!G)`&v@>I)LCM{- zHIVQqODRjCI8oCEBB?e&bk1mXoZZORJfAEGONMZsbdRi7XtLMx37L0t;A=a!vLb3M z;fi($>QKw}IY|A$B{DBM^hhR2g|Vp|kLEVF4Xl4yb;T1p4b6zCBOPsBosg2AAu7O$ zG54^wawrFBE9WRu?izeh<2N-?NB(peQBegAA|N{nn^Cmb*mR84d4J`s5F1yOuufc4 zlUgxt#cACakl46CHcIcuR3Qp-8vl0)newe#*HB_o7g}49pP<$Vc1<~e6Sb2Q?3oEu zLd;{^Zkp2nb=Qf~zG#M5Ehm4xZ%VYptJ_{YwS$!BwY+FBJ{@wdnwm#4;2l79?<+*Y zWaFSz9cQB1NSsaHaiQ~6u2hfh5d9tzpEY614!+X^{OC~{j;qH+Dm;&X0-NThGX+-CP1o_Ngo79Oh8)(V%%1KsS4PX zbsyv3f#mo-%n`+!84lHuw^TF5PQ0O{Lt+`NdKBNuG)iO`bX>k@EmCKo8strXKkkw| zhb$RUOC+&^bcg(%h-ZaDSfE!h9*r?EsaLT-PfG;MZNyTsGbf!gC^=P#R?KAAI>0DP z>$tivJv9mKrWDgQEoaaVkz=^}0Wk^{OewyqR;O@Mn4W$xz?Q9oT0v9dWLT)QyL1O> z|M%QE2i)0R-l9)zt&CcLGRMD=^7=E9Dlz_xmRC8cU|{;-cMPo9z27lKD)|Vc4hA`l zCO%bl_`?C1!~ObN!)Uw^p5>T#VD{bS38o5C1aqx;{NP+>cZeZf=I^GqPz{c; zm`d1*)jbX#2&1tamgTw|(;qLRbmGQ!IWkbU&L)?6Bo*qaUVP$5qd)gb{g8nh%A8nK zu@R&FssDI1PH<%mPjQ*`5Krc5(Gd3jY1z^5!qsFARNZjkx}kfF8iHTZXmi~RrwZfw z&5FKy@Id}1g36~JL+iN7Dd?QWC7L@UkOGWTX`_>Q#qvigMZtcop+sMw5_S22Pk`?1 zf4F%O+A274F@jLV^dd@`*=Gq722l2h52a7w0+)5Wa47Gr2&ypq{a_j?$DpIJaw*-L z1Z7@{rS2T4!)FPsPE{qMC4SfP{^n70upiW@Yz1HkuI8QTu}*B8Z&t%t>c?fWO}p}C zlXrC!5%KGl`S{LxvZn}Of&D%|j*kL7#Q7lQ68cK46!pAS(LDvJ-y<*acqZT)uUpG&@%I!RJB;esRlr zN$ZewO+s!icbefa!&z2iODQQ(j?ND86AkFto{AY&ZA!B{_x>!fw5`8O{>yvuJ-w-J zKTjcdLGgY10|-?jJAXw}&|~l2HGpc#qyq1CRe@_0Nt_^)fBTrrEg^mZxJ zOys+?HVN+r8w7%;XpeI96E38YHR|oP~q|z|#Vk!Z~ntQ7>(u(y}jQf1$qem%6`^}DcJpj#}CpImx-H;<)MdkB>Z*5<4P&K zaQeyHAusD30U$wxIT^Fn?4s;+f#d6*q3CoJQU`+qABZc0ltg+-6wpL;qNPGO_*)wp zW)9D5ud`1hl|Odjg(`8AL;nEoop$m~fm1tP^B`XGpVtqRjYoI{eeJ~yEczn`w{Lji zohHsP_6u;1Vvd~Pzm}>(tONCZLJN3tQLIGRNW?7YP8_L*()L-jI0dIushN^$8JK(K zd-Bm5$x3pp*{Jt+Rwqqe1PUG-BiR!acne8@uQznqK`Q#LgOvSIrmh?j1S*F!A957v ze3%7;IBK`7mvBnP(^IYZYj%Ks{3Z*~u4kG&p~F!}nQ}EK74YW=4sH9~D)}#mRCeAK zax(DR=}&|TF2`Tv3XTN|JM#X5Ov9;~DXG63?MI|&7Nt9WKasLn{-URlJ#~L1}YlU^oi}UNAM|svFZ1kjxo%NK<=)R`}9!o9n@uEY0u^kXv60F~JMsvD0fau#qra;=! z3D4jHYwO`ry{W=_qn9kRH6ENQQH%4?jA(5Y4z9>DRXBTvSd?gB>@tAM{7yE`_u_-<&q0CB*oQL>Ywncj~ig$d$z9Xr) zycd^to4v&aoX8S!Cc>5{!`QVgsU$wJn?y1xr>M z3`;+-)aUV0WhQor%aQ$#lriZCo3=7;#ZghK?ZVlPR5E{-2lh}3%a#8owKTbNs=Ab0 zD?agOT(;^?ZoiYE80i-wxOZW*G}kWW&zkh5yf?A}9Q`Bwz4hwGo; z#QDX`k!JA=tdSoUhikfH=AlTqmzDi~y=KoXoZ354B_YH;ps`R!Ne$hCox;_mZ{xOq zoRJVezETdJv+9lzRa1}Q)?)N5BD5A#u{Gr)E+U&ir+Aw}7I~Y(6?vQd<;c9}f>um` zp|7yHbKsF6o{*5#NQOpVj6d1idL8%esO8Z$$#YS2oZuza>l$17A)t`J|NMo}_EyS0 zI$fj4npjl6w`6IPG~YlZ6C z=tK;~@O@n(K|MiZ5lcI#35DmkwuP5%hc`GDzf)NbpI2G^y8~pirw3&-PCab}V~l(k zy5NwSF}%VRN$a5(E>Upo}s;^()(!b6czDrXjkW#-vPwtKYZLNfeAP>!hrq zo<|Pqo2YU8qSNX-^X$+hB%}$3MbC-byo%+ckMq|Ty5I_cnXRojr!xFUvh>}e_{IW6 z*{v$VESOzp#gpieci86h5qL^3k9HeOz-yB(k}zFeten8|JX|hh-H|WAb=jGt*?~BU z@mFVr#~}RTszYfxfvjW^SG#^3vY(p}2k-j{9FJ4M;X|(72qt*09t4yb#fcsB&ao%Q zmse~FM~Tq%*u_~~j70)7Z&mqSMW=FyKMvSwK&*cW&$@n#tU}D%(OX4110`djoma}` zrDq;Vm58id8I9<{v~5SYw7%0a8{XPjP3+~8YS{s^SwvabE8ikGR7nD-B~D(xd9L8; zOA|@+Wo0{CGjqV!h15dvFVD3?q5)yK6FUuM3D6&9dq+44#X!{gr?skEYcdRViy?Zs z0wpU!+WZc^KMTWLGz++jnl9LGWGM6~eVAPcVOS;GMbT4vw<5x_pN_pgIS|2RKM{c7 zm5sQgyctrH$6dhCN4AO-n6F?1udB~(I1!Ax*gh39tP|tQHw~h_l_1AW`9?EDBjJqy zPS~Hu{#O^H{jymiW#m9G#30MTT{vd{!mcm74KX~`#<7>gfAlX%rM$&1?`!~XT; z`*&1%)_(m^j@dj(&lNU~nGVGG$M69);1W#Xt4J2#|Y>_94q zKY?2qSxm%r_y+d}=CAH#1nY&XWceSJ?{~SM1*&rEo^Oa%7$wZ}WaM8MmZ;fWO~H+P znh`{RG>ci&B~&%Lsm)hQj$2uM`+sPIwVdG?Te8(@qZsaQ+44ZC7HXkKLM3OSCGhBk z4<}JgG7TG`=sWst-%BIvR;?}zrs3$)sV6T8Ntj5go6CiGbd^N>*BcFc7Gh)UqRpJ& z&SwYA=T9{F`;eBr(*<+lU$-38glI4mqbl)LXkA)>dWPK!img0GnxRa47v8 zH)MM!FN9jG0>SS&VJ(5lbIp81xx!GmFGb*8)F7?T+K9ADB)_xQ0J(@2dZoCzm**^$ zcMnNrEXcGXxkFn`shxVu7fUJEaP|n0Igd4}mFu%-)3kev5zR!{WPz4ougCNyU!SgD zKl9`phmZuTn0ix0bP7q|%3htmQTMRKYJn2K|GS~2`Pe0QZk%MAwTQ1i+&aQ5YyB*u6>UJuCEm)=B4Rw!kmKWHwToHDa@*OI3D}V zL1o{0pJO*UD1n@=#WJtHP0{-sraxdQ{rd%N~u zFTjml;P)%v^y~wk=Rzvf0@cQ_SsH(|Jm zbAB)F?U0{G(@}?KQ@F>>%GM6Y1!+Y|n^6O99dj8fx|I?Bt)03Gx zs(AwWT$AuNMTLTY21REU7tTR%AnAfbS2j8YVYguu0WMi5`87^Rx+y7y24d<=DHjG1 z4nX^(DT?@o6kJ?hoH$>G69PO~O_Fdba=#^^C#b%P-~`^n(3DusIIAOiZENMzJjQ8Y z4O@B05aiN_Ihcl~{$3OI_@Xf+;@c>DHY$h_Z>C!;DDBHtNnPp;)o8eb;_Rzz#vS^& zh&{kg!n!n^N^a zz?96g|L0Qvn)5VXKr=4Fd6yeL`QJ2&MH|Z+T22_*c_{)1gm1{ZTyWPciy$F_1j<+; ziiCSPHKx4~Qlss`^4J~WrrZB;Cr=_3H}^>n1XwDfp|B5?t5V8tyl&wBlSEkz6y0pY zQwGIxEY^dS!*$2!f$v+`H#ci@4eIp^{e7xNzPIXUqnD z43ZVUrC6tD;`M@|8#gL z5zDEMuH57tt-V3_uc4c>u|Y{=z_x}1e*SfydbVyia&rTovvj$P3m9vR&9ke>gw|$j z@kG_{XI4F>HdCMrLI=9CK_$IwrDAsbVX-DQq^RO5Oi;-mqkyqE-J14x^4Ej< zf#32S%3&fNkU+gUP{Jc4MhAmH^yG_;1la|W`CMa@FDr3IQ|Eq8C_oNa$2Wq*4>0rT4mXOSzaWOGP*tH-K)l6LVE8>A#s^kU%Q!^tPl8s z-~4{RqT8Vg&GU#n8Vyv|9bTj&f3oYrQY(}KkP4C}f?LtOBl_8RSm0;&NaQ7^@}aPR zR!;G#W7}q?A~{(K-EiYy5l)gq^tD?(>+8N^$1Xngb4aaZokO*F7`gldR%)_dH*gK% zweB{Y$?D=5>? z?Vg0XZf7s){F6&F8c}V!A;j=p8{0>|H}5PZV^Ud{96RIM$FCQutlB}OM(G|M459w} zZ2oufidhAe*A~BU(Mo+;qfXaRh=hnvUOU6SLDVZM8D>tQLD#aT`eg)Ti%QKuYm_}pNgv~IiAF+Fe5k+A0 zFwD&7OYv2l)#o)uz-f;dv(6hL%}cdG(i!si`@G_1*sT>tcnrjuwNmyX(D5$GJLs*q zYF)D4|K-`t+eO;9XPeGBg|#ohGwXbs5;<_@xs?U3)wvPdMLlO@v^rt3hvgf{@E+Hq zSe2~f76A7RTR0X`lKBX?ZTnI3Td@~z`j%41Vb95xI+0#|e+|9P-m3LqmzHP5^9gEt zNm+*cL&55`FJMvg=%iFi{_@<-d?KSxX>9Q;_cyowS8ssgs%*jaWLfbBj#2}s%Yd3N z^&|wEmwYchyp9`|>O{llm3>HYe%2L5R@PTT0`&xi9Tu@X847Rb7&C@HYt-8kuIpB# zbOMN@?l6x2nd~U$#{7oIXlu8|1yn^fAaD59_A|eX z_ECVSJvcL8?IuJ0CmbET>?2z-tF8j=+NCP3>$hYtMdi2*^jzz)?Oo{O>SX#z3s)rC z5@6^JOzGnsHJ1Qytb+hu#BLZC3>Q*J?BE}TjHB<{IFpQ|yYJ9U0@2f6dTfz-4m^Yl z$}8K=Hs&2F4UvWs(ApNO%&N^12P}Hp7ErOX4>ln8SV&E1;D%7?B<0w$(2?60_t~ zm6CNZd5&)(nFlf>zc_z{?Cj|)DW;qJ=7^6lQ7chaK+gU2d@}t(qTwu;F+h2TASu)9rpKz4MUoy{*c~F)RsR96R_%m0TAEj^&W^O}2OmC_% z2zdhIAQ4_0S6+YF)xU!Kd~lPZUYtm;gMjV_p;CV1wcPkC1>drMgZzcIli z%c+f~oMV*k*Vj>hpS$%jI`ZUDL)G4Qp5a)pC-YNE@1>}uW-LZih`s>S7^)s!Ogg^r*iu>nn@oAq{x_e zI2rQz7PN%+X3UzYpH{rJSRDHbRdNw{hcM+%q0Q8ZOU`zqYlyx4`@wZn}DX^$nHYI`*2;dqF~^44LKw`ia>8FpM<(eQ2|PP%A!?RfBT zl^-SF)8)sv?jPIlFr!kqHxXLNtP~h_#O$8Ps&VL_jB+fN>Pyl~W*!cn*C>^=)s|mV zo@ub~%{xDPaaH}YoqeaRyK=TIME%E2@BCafqe<7FHr?(AbUaJkuA*$#bJ}HdMPP_wQ*{FXdg0U`%tfVSyA|W{#YD8ZL!J9kW*% zdHc?!5cmlf;IU-DLwnH9?dK1~p4ao;t3$mXaPX&@iA0C6)&`E|ZMdq_@eFW>M;cH$ za`0mF^>7n$?EP)gMLo0WTHax!N%CsIPu+Bp$=D2vl5&7+5syz4OlrYIUT%ym3WHu* zjMjf#SZ8q@r=)mJaiX_a6V6#p?`akfFhcz2_Pc?EK)OhFtp$khUw^Yugz408dBG?p zp_My*q#wpKn?~yI--?~Ez|Z8A4`&{0%$c38J6((g_yQR7dkc~U4MCG`w7WbdUAp4z zi4)s-GGEIK%%8~oS&r2=`t*dcgZJ66mA(ehOWQG;{+apG8lRm-tH_gc^inP|c?P4Y z>TR3f={u9#Wp{TF#5Y>S#Y!P+!+BPIw`+Ja{-i@O75h7eQ%z%3pB8a=3cak?%#z~m z!&^M8EboPo<;#`c(@a7){MJV++1t(G7Q8_Qb=ldkj8SikKwp^C@uX4+m|1wghWN7z z!w`*^0lNt;5!V&wMt^rM zq9I%d=B+7;R29P!#pvdhTi|PDY9i=axaLJ?rw(;7M zceNuQA<0A}xrq5}5wuuQ$p77W>qhapln^Vuu@V@l5Wx~#XY9Ki`quY`-?wQ)nIrlI z{@>Fpflmiv|E~DE7>m&Xas#F#g=qsyhvQGPllVjDQbud+*baeaR)XL4U-DbgOB~hK z5Ns7gQF*L8ilho-E>L=8SMe-SCZ=FQ#n;;q7iM2eH2^O*46Y7dZ?&4E$Dt4@|_Y+6>REwK{#GsnH|2_h`xwoO)n<@>(9nhmQkO;YDcPN7dMeyTi#tnZE|**D=C;i zdsD840??lgDTqi1H$h5EEvs>2Lw29uUKHj2?Wm-QVA$=_+oyE~&)eOFuBC(FUdwq! z_NQwf&Zo3XzL?7nyfwFvVBQE!cR^Mq)-J~)afnX(sk9gWG}kaJ9i4hCCS^HUNl43h zA*B}ms5R;ngI)nm-eZmVGeo-qE~}FO&~?v3(P3Oaj{wT%k7g3ggDH!&#E_tc@`c#t z`0@c{e>)eS>Wlp2xYIAga>-5DFhb^MgnPBqV5ksp3Y#GQ0sST!%g7z71<8D z(mJtq<7*8UK>EXsJgaPjv3yn24d@R+VrM+@>o@6;otIL;1%#5ba=HBmtIo};{#5iz z%t%!+9SEzI z6QQHbYPO|8mb0vd)OiPMacz&OIgj5u5vz32e-h;$)53A4U)ehY90$C`pJ&%wN=C~$IGC*kISCPJ> zjglyZ)q)7Z?GM=YCw~TB@)ML^L(d^pvrQZ&=ef6ska@W=KV99d7{wG(yY()7=~h26 zn2xTI&s;3s$8D+5U4NkZ<|*mY%`gNts)L6<6rrVoL_Z+?YCJ2NJRKqp8VmOQcw|%R)4!a;g=g&D)@}1?Lk@uUK7ZHS4yyTC5CsX#NT-py zLsRO^#iP$36i^u@`S!-GJvt`8eM8^5-EV@MLlzC1LSw8~LxJ%wljT^J{Q>kp|_SeAB(#J{lOXR=({S8pEcy&aG!>tv&HiORj$krHZ-=DSaq2cvH}$_R%K7J(Ab|M?}{J; zPzmLNgO$ZCMsFF>tp>vs>FWteMyyBMUWr!|>mR!~5Jig280J}Zzc-0lQM%sj(T6>+9^>RdaQU95GCK|1@&$NVxx`nB^m z3FGrmgYwIZU5=y`1v_cz6#GTkt^e%Ko&*7Le!}C}4ug=rcVv5FQZpV}+(BsW@!C;a zFoOEWjpox#(Ux?}8zIv-{IWyKauCdJ1EU01tnCY?K1@Ajn9aXwq1^l`q$rMytyWk# zruX(mmQ({Ny-e|+;K_59P|q<`{kVU3Cvy~Wcj4U^4sF0^<-{@Ex3{8%58%m=34(PVSl^e5b=Gy}WIQQzt{ z3sgJ4`8u*`KoAOB_tKx^xA6?{!d-u@)zjY$q(-f@&<3$Z)$E-5u6oe(Wj9o?&xIaG z@FMxl!b%fX-ZAnIl_iUCv-6)3lA=+^{`IryaFu!kc@+{CXG2-ZtHL@*!!adk8kygD z@jI?VUd)#FYzMamS0+KZ8;bceIj}F6>+QE@P6Fg6ejkb=@ut1%-(&>jiI>(z6Sd@nnbM=E#aBJW=h*;}N79K^`bKc=-G~u0bZ6aV zQU+DcAze64otc6_Z#EIV939ym<*K8k;Whjao4$F=V3r)3fyAFTx>eOvNPtKN3G0xh7I7-QcQ z7Qi^_LvTo?Ow7p6Z^wKUo`@``sz`1{YQ6RLIHkTL)!S#e`awcgwlVOswbhUyLZV#*`(TexqOXYt#NI4-^de(9RXR03SuFX zlQQGZJq*3C_TI%sKaXV6@rm2c=;sin=2J!=1RXphG6uBqV^%img|du8o)*$_81|59 zc{1ckrOre8#)2_-6})*!&SU<}v(L#veXto8Ve=TsbAb`q1#kKVxbreJe_S9W-^cWg z9P#?xM9FWL;0M8Or~;nWBG-0)VaGkebE4bidwkqA1c#OVc;RH)uQPuTW*@l37&aAu zW_Y`9r{8WD7eRJRKcNOQ`MuMuM>Xv&W zx{5$TGcE}Li+>6Z84Za97UlHw`?d30az;o7*J`fiR(L;Bia|9$su1-b(6(GqW9I6z z0eNYUqz)_Gk!tAxbaQ(X0!{$Rei@N7Wx2cDvuMDMAImE3H7;%9Xew^@)Jc z-j~gI90N{v~ce22JE5uz9h7SWftb2&iB_ zM6o8{y=!=#DcP_ODU!c$VNX3j!5WgVoR@igyFZEP>9uUb&8?6i=yks6ihL^OG2pAKeF-c zW?{=F$463@F$}`)^w?ewCyI|R9Y``4Bq37^la^+5v*Un8d!X+P2_h65-JX`f5|^(N z<|_^TxUohkY<#ct)qOM<(Zm;P@b?V2~rdo<5wN~Dq zJ$3-DbYk>M=d`j1a}7$EytJYBkcGsM0|x|&EJ#v%5wotUB5nkv{BjNqy&|G`ceSOZ zk2{#L(C+D}fl&w-pGr-XWqIDGIbOYzHZ!Lni&rDb37Oa4Yr4`4G$Gcso!;Yv)BSq6 z75d!_zb&93LhJEgz#_82sKnC_dQaH3b1f}ng!uib{LVT54|wVKOpK0AC^`%eqDB%I zgm32BB2Zt;7ttSpJJ-~S^4NM@{zS(pz2<7iJd-5&c#YwFdYg6Ov<~L_ z#_`|?oq9u6dN<|O*KTa@*5!>{b>glqADUEfhPFVIG3}_uC*@l%4vWk6Ea%xpS-iT+ zm8gm?e9087tw^jpdSXSqtlLcT7Kk5~Pfy_{9xIq)bbM}oxU2pQ+v+xTPd@ett%|HnCqo&7uvJwd0pqQPp@F9Lk? zgi(5tSL0?S9MYC5xwnirC_(m{=;o^T9HK%?8S%`1tKV}|j1`QFK)bP1MKyx=yPMLAdKqR&IgTv0L7o?T%eJ}2|!>& z1?=J|^|(gph$53>i74w%Hx1rLgr4Z*n2n}|th|%4B>-Eq$>RdU+gKkk2<@OXC0p~q zKYoK6@+fE4kE=rky_f&h7B+#V`S7n_MSt)r8ogxeawaH?$*!a=b@w}Cy*|i1)G9bD z9iig!MDHuUS6I99e@|5|5E{rEIuzTOdTUKhKGe4{O!hY4w9A8a{;pGrrmtOY!i*4c zJf_LGp?k$8?iA|wJcm~irR9y>=elcW`U&eDqa z3`{nz6<&<66Fh%_s+PMO)>PoXPCn$ilm7=|K%Kw-3SVfrHI2Kv@!4g?JG(5s;1la?m=L_ zAVc%fRdu}hp=fR{lfn6P6jS#e2J_+veemCxdlBMx*fUlHtu=8_u3iTfS?i#wI0y!s zVi?Bv_vgT;hYR3?{aEymD&Qb0=#l=Z5Ne}8{suTjvlGT^x)1k|qnE)61anVE1oSs- zhCU1elA@2l4#f8jf7$S_6riW+^5Utm#UCpa2kB(=< z5w0WeUL|i?S87dCU%4;ej)nF=YFFEFec6L0lL)1^Hhl1PK4^BlP$|uM#@0RDTKcZQ zq%17ivWQc7B63Hqp-h=wLWay)K$GL}Z8SNhG`~$2x|r)py3+&Y{hO*Jr*un(jUF3# z{PgA{19jQv&|KsMXOC3E<&XNOsAE5T_F*r4`rdYY?4Jbk6I93l`JxB9JLTZyun1mx z@i*}F(~rZio_v%W)8uw(Sa1HEmte&beaM%u!rCthE}tlZi$^d$4pDkX=!T?dj1u6) zXcE_^r^Zv^#8@gE8%df3^GIJT0vQiSdhy@A_#FP+iNORIYu^gpbsMHYi9lj{A8f>0 zv0)wWaWte$&r{PhZ9-7H3RCzd*ttCt4h*Hiv7I_&ll4HMq!iv?xq_kW$fb=9#QO0@ zD7`Twcq5qJP?>Ibw+{TCpd`L%5gASVrM;dZ4BX5C@+6PVD_M5 z4za=c;7l8Q@o@)~6-L5ikA5G%^KCtN@`)ev@ZB@NeH@ z@W>3xO_=8U;!r^) z3?c!ki(~y#*ta7JdbO!r7Yre);58s3pdyT`7{>9|AlOH0J)XmchMe}j!gxDS9-GL7 zle=?a7?qi!%n>(N=*V$~z6zfkp~RpRUZ-|MwKVx!pj1nZ7)s)fpiGaq9L|WddsN6t z^?pJbXMRE@F@fk!3pDhi{A3f&EtL{edsBfcG#7e83#I@H`Zq-ry1&K(7Y4Q=*LD)h zU3jrCe>}kE=4uP5kK+qW+vK)CI=2HpKHC5Xb{4_!o_zv-_=9ir@ZKMudxEE@>Ebz_ zs`hh!o`&;i6y>qUz6akTfPeWY#DzG+2M5WGro%*cDvWo_VWdkAqg@2398T|)!-;V@ zDro|PITc8qS{~|&;}lL{_B9}!b)ph-jfn73dgc^O|E}@J+7e9;nJk3x_@2TkEg$v6 zffDP!75diNfvqXw;HvyTyl>!gWvJ>$xJ*PX)mded_x2=O)6{`(-wyc!mh>asO z6(XnzWpklBrfaWBNE`B9VE>2`q3gs{y#ozS@722UgD!-U%*}2%cdQOBf7lMWnL+TQ zNB->w7;hHOJ^M3ptohlW^YpPFeE;8Iou4J_?pMK{K^2T4j0EN=g1M(3p`1vAlM@OC z^C+s~;hwk~prqiXvo-5cB?Do1H-d_v;lN-T?Ak$-e?u@rVRu&y!kEg9@Z)<59te!L z3+2h(dAy2~l(H?$0n;~TvvgY?z3Hu(4oV+}mHL$>3bVUe4}RBB(tDMsn`>xiwy<^c zu24;h9R63Ka^)*Ro_yJCz72A;0pE5aXw__}R86*nZt;e?I?{4VOsy+vlOtPJ%jO~% zIDN1TKDmHOc)k-pzOaq!#*6Q5gNtWdQ6={vsKapfXf+y}Ht_W_#nk;>p16BEP)-d} zP)h4hJ^2&JOkRWaTrTYFQ^0tSoWZ1^G=2uv@)Ux36wS@`P>LyB20J^pLSJJL92`-> zaT;BY?_XOp51`kml>`&s&3tH)(lo|HCFH&nZ)|KVnH$W+F zDdWp^WnPlW)OA=797;0MRT&nVVE@^$Wz8Jsk2uU%u9zjL6Wz?|HWM^Go#wc0Sv`mM z7pEV(`^LG|WKinv#J|W7PPd>U4s$z*8swMp@#6Vb`1D*m{O$671hog1vKn942*w6~ zg`fTO`wZBfLP=xKPhbuD%8O4!Vw5i&#*4XoAYFtKW2`$3CVJ&?Xjm?)<#6J(P#*1# zf9%Hyb*bcZ$C4MT*|9{=qqu1lW=%z?_K z(46ZESyyNJqPkt>k+6NhENCPRXF9at6u?!2ho)D#NGhq;hq?D@yV=& zN5&r>csGObD}mGosU_W@pd%M+dsNA0ty=EDQfj)olAzpCvIKGy%EuhpJoF^p`JReScGqA1Mqk-5Cl^jo4z&frhiBV4ZGUvG z13vu#-S!21JXM3WQ`VD|~de33d)d4N~Q6(zl za;T@>q>>_zy-W3bV`K5l?;h7WF|PS z74U!SRrm!xN#BeOS|IN9f5%W>+pBEMbHG~C9qMUo<(3e5|6~h%imFHoIhnc<%FjO@ z;a1SO+NQ614ly?H<*T4B&5WTWrR>OanN}$&=nfVwxh{O4 zblSD#VL?eZ9;JaA>W$MTYUIE(6CA<>`X7BkPtrH(MfBpv+j=9bYi0m7VSzo>c{dlh zLXFxSc4B(`^5QT%?hj74q1o9EpCObNF@1mW$r#p}yWsqZMl?b#piWy4cINNEv(%4# z>L*{B!s+|J{?()K^sj#mZg%q_aE%?98@!S3^#Te^MYheQT$)b(fG*T3m%P_vTRcvw|*x{6uqh;SXuZkvWrw zE}DcgQ)-G1+(d>@+C@%1H^8ycR+?y24U-Ls&HTRd|BxN-cOdYX$x0 zsUPzwkEO|5u+iTZmU|Y!YvubhCCaf276<^ zI}CS3V?DVY{`<>a+&sTlO6_J#YfZN%pgp#aH zP=n7O7|w*9-63urnF@S`+wdQq6IMa)Kijl{^J@4P@XSPdw}>T?N~V{h+sc zJ%e{lH%v%Mi2xFa^!&l$bcS^Qpj?C%zdk&s{@N5$7c|s(U?7?I7HU-4PP*@oJlAQB z4!4R*MZ-g1mnp2ECd=|+sO1BI@*2Zq#j6$@3!KE764SXj%t-0m2!=Z&5ynzDeYgfb z`=A@XxH!y>^G{FL!e?h25X@Tm&ri2QS$-JTg`|{gmYKscPa}Be&FA6e7k&*d{Nb0A z&GA1x_j7phk59rIe|i>PfAtwKT<~WISYZivma`!`)DsSlsu4gLbk@bfo&gmMbR@!P zS1Q++x|(vLMjC-PC?_;t!7Ad zV1AuIicsdku5HoKfN(Wuh%F&@-v#c|DrHBmD|D53PBzkO5oR(wUn`XNQd3SssjV$J zxSq6RAe#zZ@O$ns*cO4#dI$XHr7>|XrFMAn5(4?*u_`!!umt|+v+ap38c^nqOE zI;bt)3d2}C_9B=BBE|0h^c$QvrS!2K$uQCp2mN({3}kQBI({5&*$T(;0^icua9x=_ z1<>sK3}l8jboF8u*Ni8qNj@>%&!|}`2WrK*k-kbZj(Og|ZYcn~OtufD$?}gJ+?8~D=6Wvkbj38~({PHJ*aQVX> zyxigR-U9gWcs2ab=Uq^cu?d_IE*+FqUy=eEFa8U>@cUoDpAf)Bb6;a+BqQ|aSANSX z`q$TghpBrm1O_<3_J%kf@7^<*&Z(N<>}i%lbzunfw)zvkL@5dF>!Ow}Gx&q^r*NvZt$5gi%}CzT`>)j>&P;oI@ww5jUDfbtgZ`>y+x znrnNM77Qn~gIbE0^5hJHlxCa{wS~eb=Q~j)2Y5v1^NU09@i{a*c%nZ#Q41fPtYs+s z+ET#9b}rYH1f>|J45zC zw!%PrB6KxLSUFp&BB7xyjNLcYlRNv;Ss|%zWFV^o7)TwIU1h6aCz`X9dkT4&@_xJP zV-vT6QN@p>hy>w#htLonEEQqIcptw$kB_93*FcFscY1F=?AE64rYZGgTej16=EjF1 z)qP(FB`Yb`m*omtzVcwr&Mo_tw;Iv8y`3?{gD0omg6c>$WdrYvZqBK^J{SK=>o>rs z=iA`&M?DPWrBC|d^)bN`GH7PUH0NC9aFP)}#hG z0qEnZ4;IF6!Cx??&wux2h~DB3XAty`+8BoNz(^*7p2*612u%o0%$UI1k-+S$-8h-9 zua7Q#egM-sFKykGdtE!|UV#)>A5I745t`|P?)m(_qDc^^1_62DKq;ngGF_@0Ry3YM z_3SFu_9wL}Nwb7RljC#kl(igZbmunwn(h*>$<*Fg<^vT<@dgPGa+TvPM|AFC?3b~j#s7*Dy#&x%<9VZ*)BA8g~<8w4Col9jDp4&dg+GX$es!~iYNyT; ziZSGci#$u{?&njo(Ya>ayD`^J+YWM`GAYB7*XNwsUx2FE0GB@MMWyWH`0}$6RL^a2 zcvJ=FuztFHz8U`hQ9E2XS`ACw^x<6u^R<_s=04>wfB6Xf{AWMlO;R6!{QL0BUp&H- zFy8vh@4(LDT^`A?GJOXDwT8U|>D&tHZB5`bKC~?b#xZRVHEqTEX_>Z;yjnx{;A0;e zoY#3ES^^~$$VdbsAw)(JD3dbQ)DJnx5G? z<2}c%_k6$iS5*{HphBY8z25%go(fd`sQUdr^}XTU_nsZfX-^^iE`4t#CP-vd*IaMY zKDt%UZvCDOAfb|oCDDX|7Bz0dFlF7U>)y=j2PomD0V!Ywkln@T9`c)#l>^E_iYseO zOS;UXm2KWof-~^O{gkOz{gf}#d2YKYW20IQG7T|Kj*F|e?_M^tn?@<B^51Zr_a@TUqkz*@frHv#l|7(nHxj4>e z_)S5H<2LBlaXdMr^ZbS8=*$78tyzujW$Q_~vbhQ4w*(*eRFXn|`C*^Df2E3Rm4Ffn z&+nde%Rhe7D~oc%xn>z2xR%VB;Y7>`Lwvczf0gucUry?{SibYt-?FR!@Bj4&+~E5k z|HGe2U$5ozWM`WE|d6L#_%d6N-U`P?~nzCxRV5r{vq>hLY z=K|8KlE}noQ%TIr0o>SMvc15W$GXj~o1r_pouRbOwqb}R@tm!KI<|R6k7Z51^L!eA z=RHTKX0|yxFEX2PzRkYqK1WaGmmgCc`M6zv@qQPnX1n}|V0m&&b=zOO*TMGH2VL^- zKR+((OAksn=hu1oFgRc}XWVMmy@sA!M_KrKxtn6i7)o?v!d&@2GV(1HS1y0^Z{=V7 zr|{ z@<)ID=kkpuKbC78Uz7UGAo>240{P&Q-li*`%hZw$=FXH8*i~F>{K%w!h##K^Bxl@| z;Jim!wRI7FveUeT7q_~2?67RjU1iC*%_@14T=P&xnP`;9?4>ukmsn)m$zP@bTZ^XcP0N_Kj+4l!GYIr1PwsCHJo?gn9 z+Y8pq9*P&u(DjyjQdT`#dpu@Tv++rACnuGisPdhXp>vOAbCB$<^yN*N)G5^q59O@$ zzKuElN~zX61uL%2bA^m~KuG~}wYz*YQ76ALb)0|x$PD3BAo2K1Qqn(u*di~VN|*a0 zHjx`&&Y89?I8{K`@HqJnKh6L%^YKzV^8ZIOKf~vU$9X&m#kk|M6y>bY$B zca(j9@u-awk+x|tPC|*;^69+>`Ng}H^7e4H)Fk@L&8{k#s99PAxs#MK(07$u*WBqW z_l0ec`@=U-Jh+~ea~-*Qb+{6)9q{h(g|e=b(P0~9EX9xM2fbuo;$~Tpv_YmP<}wgmH!^NBtTTaB0q!xH3g}i)qB#S%yx&f-^MXNX zYb~LI$Doz$TOW$Tna*i@8=I&ZA1CEi*KLa zBL%rhih+d##`2^l!C$&>c#9!KoCzSIih%?fK+HJkA#>t9S>ZAcxXbkYWCz@2>V7ww zu*XI2CnbytSugeJezG)sE4|*G0Q8i(@m~DC>{w5N(Nks}@|3B2T?oh>1fW(v950Z! z&J&Qss&ScUmQ48GB^9JcSIVZ;7w1l7)S@`t@LU=8)C31ZiZ$|5U%U!rvHO&Ga*$l{ zd2+vi3~t=4hGy%-hV=OahR#K;X&Tj)*{t2S8OkcHvux0>TSzEpuDX`$ICa%EY&&L5 z^Y~#=*2edXD6XVTh#NDia@X>rsq@$%^trPdd#XL-<%yhC!dj3-HJ~`YGBXda+<9BV6$$Q;o3R&7dcZL$LI}o~B))ofK_ph4V_OKaB z4ZHArqk7v)Ii_}ge=y51;ap%sS?z+-G%k)T9*w-#P;vrOv6)A0kht`gdQAZmaU_65 zeX=HXjVbZ5t(51mFFM7es8?bw$%Rx(^ZJy#b}ZpCq`L08)%ui`sqRWq_U1n+*0}VV z_G5g8O&w88w__#WdAabG;??rd+>6q{e{KFAZp%#9b5D(J@u~S=T(@znqD- zW6g8B!E7mZk^|%xFdyf4ANHEma?)*oPI2ZZcd98CtB{Yc71M<(k)PhLCl_jv6GK_# z$_wPv+eUe|BTi->bd#9}Tp74hjlVpi50tg}+vu}~$ie1aa)1Cl+qR2TF`7W!%|M~XuZfW(-B{KymO&iuNu>Xm&;JWCB<#+JX~Fw(qbZ)8V>aQGf_Q`7 zP$J7VhX8XWP&|4=Ka3uurO$R39K7+G=Q&;m%bMp4usej^P$CP*64STF^zsi+2PfV4 zV{+Y4$-8x=jFqg8kFOTf#j27Y(S6)PwQgeCE8RZ1UPJ)J%6c7TmJQ)#5%MGf1{FNf z7%BT|!(=a6e_a?or^0ufUW4ahCmVMWh==73ECEPy*PbMFNDJSu$D_e z4C5u^bCa9ty^tu^AXc;&N@U=ZwawY4&zAD&4VrkZM{k~I0J5KeY|?I!rH36YZru7c zSUM0YI=#&eZoG}&|D0B~h20%u0rt_ES9z$p*ioKp3zwf`$k9|Z|B~*1_Mn**vYdeg zgdb9p^D(LBlN)7x`@!{6b*m0je22xU5Z|5eIw(&!M>BN&b>XangH3znaMvL@(sh_s z^W@P8a@l+2P{)2bK9I!65)wA-001BWNklO{pagJ8Y>E%8kZlDnteDm{OI2ZAxs|Q2+*@tl zFsUV=Y^AvKIrDbrwT})GC-Xi!;7y;g`VY0%`{>NI;~3U=>92F0=X{;#$Cj|6r<@*HX!xjcowQ7zlar*&52}qybt0gtlu^UI6hm07i39wMExq2#9ULHu6 zSLhay;dzvEZqw_AyY|UzSZ^H5|BV$efB^fJ$vp#ZJm#z#U|QP?%#%?w@NzB*apC;t z++*7)nyU>L#dFS5M8D*zx**w>zt%1w%`O{2>i7Oe)}^nt3FP#+6C*w5DHF)T+sVyi zxN#wp9Nuv()k%g~?Ko!4al2-)-f?WUy%{kkNtqGGz}4oA1==tahHmtMrXcz8-Fo@e z2i@$xp_ov_k8V|3pahIh%uwE~<-cL=AAmVoqR}#(?_SJj;Bb4_1RV?@VgdY1{c-#} zgJ^#5g?wIg0EYWmTy=f`7EiittA-06=e}5^#+TQsxKn&KAfL?9l4c#JswJQV6-WT| z94HYlV&T;0+zqlYW;wfUJXYh#+26;F8YZ2K6^9HwTtbZoeRH< zEvqH3#zty6l1(nWR~=>~`}F=%QWrI*Q~@P7K2gq)!agOnd`d|WyKb}64d*=9po~}u z|Hj!&)m`H`3zP+9g?0}gPDw__)yZ5)Y75IO@>q$NI!n>$K4(0Mc(J$CL)K@kV}&f-yIj_$t&>NyDW=ccDEmr1=L8zb zj(JQRLzSoM{bgsl%WT^)&ubUk){+e(I`}!e*xK6f8QIMH3{La|-C z{4JU@IJWHM5|Qe*tJEy~b`_U~%uwRF&+b;s&+pa9_bCy=wr~?xapqi5P7GzrYlEpO zl%%f6sPA$jqgTs3WAkXT7%S#3@h1}it#)#}`ljltr;lHMIAI&ek2%4qGtEq#1DezOfTMcBIG7^!nUwN}1NDTc5U4<|VEak=tLBsr#3+-Q)_M^X z8g3zbK=I}8KR!+wc$*18HLm3B`&N|&N)^m1`6<~41m&Y~y?k_8PvB|QVZi6u1SuuQ zq8Ig?#N2~In(oghUc^kok8d^FUGlM&ZKGQV$GLyGWcD%;g#buyuANL}x81CDBKPVd zhgzb@wT4*AIRFxFxv$)lp({DGQW{J!n$589H++j}5Lo5c7LZ7Ec-7w-?QGj+A3auw(JwAD7o-Y zZ&$Kne(`Rtd`NKKu!=KnswLb#sVdhTd3;*E=tfOD zqc~iXtmY+HFlqxRhngbzGhZ7_S6w+T%i_+;$L^XE8NfFkNHdhEQ370Z!5v8rMt7jB zT^5zJ(PP;#t5VtxWlyyW;!=U$iv8hl^7I>UR+_Upq$F#LQj+Dm2pob(9%gJZY*)I< z!xq)&`6}gk+x}9YvHmGWcQOyXBSQTbOGBC?QuzhT!g(RpmLRSRKBD;Wd`mc~K@!K8 zSjq*lqggF&*;D%4Q-Tuq8L8&SH_I8kn?tr;IbzQnrmUUIPNcpMNulmsBsj692v2(` zwaaxQOMX!x&9f0vZ@hESzzGgAYPB`3c*c@`$hglmhEj~TgT6za{ODebym3auBRMFX z11h*{3>TKiJ8?o&d|)P!c&;>drEDwM#EA^X!v`>r$~5ZJSLV%&74XhvGhFvJ-Y}}S zW`BfkW`99Pc>5cH&s9(xMM=s4t9A ziZmYe=x@tUa{L-q_ryJKTd#-xQcxnEY%5(S$NKim_bwac<0}Sv{X{&Slx_0fg*;LM z9lPYu?l)NC%b8$KLiq^+i8%9v8zmfHTF00-&5y}t->1y{V~S5dzgNv5LusE-67_&! zxN|Ot{;y$HFz=12%8HB{iOl?m5gWLJoJVaq(^iY?01pfN-z4B3n6|OAh7y}s-8hrQ z;6Ay1R9-c?aVRH%ggV0I0z4!$s6$Q%5{9@EF1<;+o?N#VcMlCzZLuup!qX)%kLSqD zF7&gerr-V=4{IprJ9ClQNs3t}F@&9$==e4GD$4e#uOr@?h&%9SW>GmpfhDZ5V5`!Q z4Ff3DfA#OM!`{0gq8Ud1qKdQ0jSK8#K_KYYt~O zKGw5ejiq$GjFU9bbGjfez<`=IgF<>AOkPFm1QYBfGe zgVtve_%pMp9BbUo)^lXH(%Z7vpW`%r6>h%aN{IX7t_Z}oL!{mj|J#x4;icUzFywZP|5`qfK%A4dTcc-c*$Cd=! zM>k42@lioj!_Irq3l@BMdzyJmUs<}#lAeAP+da97Bim4};UUFy! zXVm8@hPS1Lr|bOW=~^EZ#>PONIx$%1t9Gr^-#c{PvL%<2oHCaeol46I&VlfySXY3v zZg5qS?Uh`T?oNr#4(869L*-=0A&U)m9#&3t9aQqNT$M^)@Q({poPLL1H%jB5g>PHT zb8``}qETc1Eo~cFB?pddmG55Al3UeVphOJ$fK>BhcMPdygdD}@n*`@D*+^R?E9jl` zS==%7@tw(-5(&=pptHvSB~Urx3rdZ@p%=7X ze)X_Xe*SKayg#ay=jg}PUA2`gKpv~!M(PnDM~}skOE)|hNQ~%U1&qsL`DrTPt|PZ!nlE4IbusBH@fI$PhaNm*Dysn8~D?#%#BK4 z$9_{Rx!+<+q1Me}dab*n%W+lGlbw|OEDwcfuE69TEIx#`^@9=IIrI{0iH+!2%d>4E zmW~{YT9Q)T7)+Aq2}-Es#m;DXiZV|WjYr#eQ!Et1aptG*Hn8iqfO4m5LBK@f12=Bp z>(!E!S_LrQH^-?p^0yC;@bkBZGUQ-Or0gvBqx0oUE;~^6R0hcY`klOgO9TEG41imEN+KRI`#)(y&i;-36qc^%>3tenv>QWO-sjiNau9kn7N7v)#njgis>%= zSdAwoI+>j4n4tuu4=#~fcExZ^IYzf51m{4*PI;yY+Z1C@#3=5wL}vcK=lzw>FSmnLi~rQM@7u;!70H-yJKq zuV_z-iYy&&`~!+rQ9S2)-o=Z5?4FWVx|Q=-B#V}p79$skt8i@k^B z^|RS*lR&BlP$ww%#d0=BGBz@hZ3JeU-hEyzYTyGiV!;cz$u-52dd1~R3=x^W8F{QOB{jK|E*g)q&rK=`7VTdo`&Y_l2o8xsmcxdu>?={J<9yPK; zJvC+B_PgJiW`4i<7@wkqbg$L|-){-NuO1%ZI1!KW8vLkkoJpf_WH-f$+gTZVs*zRi zU>K2Ivr-bEWBrK?6xS!mOJ-S#_xba4KA_}DU0uzE7e-Z8(v?SX@^>v|AdeZn=vbQ;AhFJ=h)^?)jFiC2dLwjkE z(s3k4DM)c1Ku1SGvJ+E}j^U0nwd-R(YS~uiEV`;a*Uq^AI2Y37+!`6nqMw#~-Uiso&6S?j7D0$^nn!J84 zmmy?TJfmUv%x>BSkg$n!IdZHofmGOwVoCQ&D0QB8L8+>w3uoOFOA57EzEWbDzk+n< zv!!Vs|Eww3UpYpuyN?9WCX`M3?K7;UD3H5{=*^3aS}!F(!_Co{<@`04YfC@y8xi97 zs?<@yI-j9OeAw2~b>d80h_b%kHN+R<{F&&TsOe8>kq?j$n&Fm&m zQtSq|d2d9^HA^+oXdu8E7*afj>V0bJF#60iUJkd5gygrRdUy`tVaW3qB~zCv6UDrp zq4qs;usM?BL#SSN73Kg@(o*J27s%gIYc#7Ote0GNHzhN@bbq8bPQH6lT_*bGc{O&s zdOBT>pG={PuVq(mZL_z|shvU?c07GFl-#LW{z0v>JoX)#8}T~1SAPn7!YHu`k(Z9g z6NtH-xL9JvSs-;t~5La!b!HWjE4w3tgu?=NaiknTVPV-P2GB-P9$2&P76EDhi=|Op@AjRqH{b6tN zYFS5%*4sVG(^0-bU;_HZ-n~5kW;P(L!9<*S?L@pBMe;)`i9`o+Ccqp#8cvXGw?Kjz zZh+#>p^iP=JBdskeU~3xH*!4_<)e@27KzAv*ixCGzdo2ss&JU$J3*N(VnC=KTrN}& zDhQX1=Wu+e9~7~#ekzCzBR!8&Pd~-K=ezdHE95F~jpQ?oaGO^K(^U6KfZ+BzxlwbI z2S#_W%?O$=CgD^~b@ipZ*Z5##2&tEoY3B!bS$gO{EIg zb~*I6?6nCbLzxn{Vor-Yyljy%#YHLC`oC6?g6zqO*M12LSj@|A9IJM*ODzEqWi@@os*h&xu@MD02 zVLYhmNs39Kc*h%p86r+T@Ol78)@6e37-z~mrU3UmLmY|!ceZU0DPabyjajA4AWpsb z8EQg70N~bKAH?%Fhx09q@bjv@%A;CudFe#5yfa!r z$AJoGncOsvnK7#Y+_|6}?TIBIDf70DiG39V~l$b4^v~B88BrE5od{)l$t&v<_Dm)!F>a09P`4l!>o!o=13x@+e~0p6~+5;EI@mM6mOKW<>OVp^dsEG zbC95_<0sQt4JXd$OupX))9YV+5MU4cvFp~yEe8u$#n7&RW%#o1dHWyGyi>hgV+mb!4IsXBO8 zo0V2;>u=wuG-&;mTI25jRG7Brw@B^j*T6|n?5?*ssO_$FnRBEEP_|J_d9EW&AZwkT zdE2L)Iv$dQYsPRS;6xGxIFS&YBlmu~DU8cU$4OaE90_Ld=Bs=*jotMQh8qeC7<&my zDDh}tJYV$dcEb34STjuOslo*ip@0*^`3x4+7(l|VTEEW(HC}TE^NlWJMrM(`M~3_N zE@9$#HU>W;d*6wcm`X0ID#~79W}czRkU%Xj(320H>S!w<12K zSy8Q(_Bz2jP_@%;C{5R!uYW<(N|b=G_%1rkcnyL2l7P@g11X?!heM5Vt74DY3FTg; zuPNH0HqA{b*KBbxWVxNe2Y@^uptK(q``UtabB^?=G37>aiGp!^W6HS+?8arlxV=&sIsAe~e z&vAE*5~a&VRUK^rX>B)$b7vm&jHOA~hvm44|M|RV}H#^kj`YT1*K1=jzYyJ-MjAb`4 zmxp2q(P>&I#J8_9VIFs~Z8=4mb&*|Kxw;!lt(@2d`cE|hFFDA4be z=Q{Svv4JE`B%n|?c#@h5A!oJ=l=NPLdlqdGR#Ef+r*MvIM`T=?nqDM=2B{A~r^ zYK_taC0l`qO(=1_;kM&;EfwM0mdNb56;hw>B#YA5{+%(~_qFP5AEiO#?SPiFzBw}P zDcQF@FESg3ggPoh9m-PO6@7+lJjO9~sZJtw|8lX@?^U5W{0`SHO#|`MpmY{}^*-Y6 zNET<+3jrnKQY1jvPbG56$gGglRTJ^%X=L7ZK-o(%V_(eTz! z4k!nX_=|T(wNtu-7Q&6+K9?m2$jzQP8Y;)SV{C$n!aM4GC~nUVCAxV0*?mJL)v`{; zw+2RY@g1vum=hVhmC*`FR!LID8Ho-ubNbGG<$II|{eR7Hg%<^%U(ocfrGqm7B~9 zpNR&&GcQ;X6a2c&OLF{LU1xI5@Ac^}e^ZygRcS2=`W|?hZ8&j3pzLjmrhl+kIn$S@ z6y$7j(5Gxv%JTv?^L!pPz7&``u|Mohft3)rx$g*RaxD|83ltC4*@FwdHvbly1EA3a>3i z*EMzDG8w*gG1f6f94T=9i?TcmYqI^7>SQ-G>&`otd?_eVP%kdO-L7J!XEzTQJ{T)lQ1^`t{Muj&x!wRS<5*lV zvTcep(ZGNV{CsN^x4)S|w9dBC;4x4iL>YIC+W5-j#@`+oVQ{sUnD8ZF#QQPA!-aOP zDyT!9r;kQ4faJEPzzCJ(#{?x3oBlc%k(aPShWIb$>x}D-=3RZJ^J#;|`_JmMzDm7j zvjduX`rG~iq2x@wWgo{16vR5TR)#92Ia_{In!D)(UCIjHV=p%9ExFV8*8+w0!KN+Z z`l(o65n~~Nd}u19Uml2Or373vYLBdhX0rm)bRW$Vh@;G-B;Ts|@l?7xFB~hvS+eYx zizCe{iG=CKS&f)DuN7xo_Aq=c+DRa%D`i)ao9M51K|x%grx%rufllKak_L9#58x)Oa~v(DxEI&*ccLbljHDNzJT4w$tBw zOS}XsE2NOf8Tk6@(;Q`C*82Zlp5ym=Q`QzGCvo-nnrbN9enEv?P$)a9BOEFcHaaxq zY*R|}wz}n~uKGVbJvYgbz+5VKZ27t9u8R;K-!zJc-Og>(Uo+pwwv1GZ z|Nqs&bjy8mvEu#}qab%H2q-r5ecaB+2u8Xs7Pm(W1jUg8P!gQteCIyVoU;L$wrHCL zNLaUSvoI!XKyh3ENL*JA)i707>+4ys-KMrVCa!iMD3xysl;KKMiibmOy2m1{cwp3c z@5)bc;wibgi7TH)2QQJ4+kY+k8~2EhZWN10mkSssz&IU9dmfvgGv9wSUVu`Q7;4+Y zSu|7-$&0lrn(q(P1&M*WodRH9=s6@NNG$-tY(Ompbh8_N2wCFhaGtnxQ71^T1-Vwf zPuVrYWDTS>l(RuP9bk*m;~PUc{C_Dv6&HFBiFTcbDTeg2sgkg+LIN^=y@=fQnurd5 zUFMpA#Pv05yrOHfeU#dCPltvqA3&*m%b;{uu*P6LSq)Zi(|8^(OkE}Ol2-6GXS)J_ zNy!fsGfI4VyHvb?Rc{l@7g9^I_bwNTk*@vXSmjm<#sf{tI0+>n?5*0$3-ez-l`1YA zi&ddGn`KiC?X~%Ud6nT;4T^Y!{`YMF2%rc;3uFxBi!0+yK#Rqlu|fv^8vO`PHiW`u z3(B--YLVB{000v=NklmS` z1x#!8n?IC!;=L<{;_k3UoM;G|;==oDu#8;*1KCsICx^QZk}~BA1o`&3>V+?Zc1xixN>}+#qtG*5-S?C zSB5F&@tZ7DDeBWa9SY;uDhY8b=!1G>>(Ez6zp%U{r)T>@mdKqxKNHPmA>xx;r37Y? z-L8Dj=g5UW8ZQ)>CIJZ5n&mcD$q{nXBRvP{Z~6aFMxjK-zNcZg(ptI`SvXretSZbQ zH)o@gm*lLJ=XhrqaDN?EZ(a3lf9PVl-TNn^GT)yRPA5K~MCJZiF1uk{G2?7tTB|IM zfgVdJWFO|rkh=zy7kcANv04sY6u4{qVX_6KJ#nHXX(2;mffx>o#df)PimSF2FWQO^ z?E!H*keu95!gOy;FCI@+iJ|s=qA`7)XfN=x0i=iMpyxXZJVn-l6>_KluLPeDz)CZ*>P)>?2NSl(c$<@y6JBWO3X+DnA4}}p6yFgYRa}b?sV|pay zCo`B*m%94dp~yGnR*(NBvBT zUTimW_l)=Pj_1Ra(q)84~a+H`o*;J3Z6!B4<3MA)z|WTdB+SQF>eV zI$!|h*gS_1|KXsV#n7x-QS7JWC9F{zvo-0t-g8Nd4-F*cAQ zKD$$9Nq}rA>bH!+1yg-K!Vo2`xsqm-x@cE&7G@VAem|4DcPtji8>7W1H%r7v6U8&saC)}=a4er=HMnFb6J^z+yWGcA4%)^` zyje6ZEEt3|x$j7le+74xMCS=K4RBX=GH?slcl z7`nuex$Xh?Vx~Af(34%6e^pg6meD>h1t2 zU$n{M+X^L_w^Etp^1ZIgP^I(8?*CGhwebwb#!-w#0u)DaOn~Q4WWt^mV)$5sczUZu zJiby$U``gvACN*`Ihll2COCP)?TLo%;!G=65la{6Ngd9#M!%FoK2Isl&oPjcP2ay< zY*pTw4@3(KC^GE_*UH4jH;AZWc9}8)badG7+-%*W93+$3<}` z%CfQMkMmGo^7K2t}bH@R$Aiz=F7L^6X6 zyC8OzBv&oMx4+8n8pDQ$bajTRA;V2ypSShTN)0|rcEYMfjhZbExNiU7L)lsy%-uy; z4z4=e4-5bM>eE+$qT}&YC#g+XDfWdfk=s0fER**-iF4htJUoe-=EdH9;&jt?ajq>? z47G+3kf8)<1cPaLOknc1cjUg!Z7(2F03=@dw2t)ul-OjazFtp>xV;M}idBAveFpX~! zk6u5MVFEPEZWymx6~XNphh7K>?NLc2HP_B&iDP8}0v-5g+xLnKJ#k{ZKaq!C8!5@r z?0HA-+OdRpXuyV@YTKC;Px8{IZyGMk@%&%74rQsXfYPD9a)*)=|4$Q2r6|$a!JzX} zj#Wh}&*xzpz-l%X$?mDz4`q$5&XJsu-2_Uuw2 zv;n6IW{4Q=*(=8S<49o-l6#L57mn?xBL#xHOQ=-|6;PUs4!9mr%75ZeuIH1{T@|sYI@wjJ)%hxA*_+=mWV!Vj z(pR%P~=T|Z|a=EP6d5MwsNO`$GngH8H0NPMZQd79}q1IqlP8Hrz zehfQHVZf#SgVNTpBkGP&S=2eNT1I824h=3uaeHo94Ktq`H?XNALq;C0LKrfBB1MgJ z&(Y^Ul_r|XLd5>Cx5Rb|fntJRlbMHCJj+XV{2J$z&uf<3sfz3^Z?x#QD>&~s|0fkZEX*FgvXS8tHwwfniq|7s0v0FW^ zVJh&Ki!CET=20Q@b9Y7yxiEgACzjMPkwTITaiK3!G?j*kq`j*}(3W3_u&qDGjtmqx zuHv;taUS&<>%P*_d8PCF&yLc0K6Yfc1I|0n|94Q@hEnOOja2Hhw=6nZ;HMll1}fFL zfj=tIZqCn7StZmAQC(3u8C$9aE+)71D-j#DTvX+F$Wx6$;?l9*Vys)u#D_a(2Xdry zm%KHStE$a~e0I-QO^v?jBDyzTJZHnii^Pz#13B5cW`v-BH`o?Vj?7GJ|C(=&(bj9HAIDsD2XGiC=cZ%&VO%+x%0s76Z|;RWm(3tvJ55GV zj#KP7(6n2WX??`Oh-D(g@0W5<&|;amXDPM@5vpgQxIBH$-y5^tdyE?I-=ritITR#1 zE4?*Qe137Bzk_lfP&OO3bLSDZ+r=I~m04a&SKTh9EN9Caqt>k-i@F0&xFobzVdEey zwiUMZm#mXp0^yR4j#{sgWP+#Jdj6Xc$6(EamWvEoS44w15dt%%wVPw|S}7xo60OnHX$ zz}tdwiD!*8=C};e`P$f3x0`qUNQhgmXyaBn^i)Uk`TY*cmxj`FQAb6n(vJPfYj-QT z@oPQwSsVI_GMptg48neYC<154u6UMU+T=9Y?^iM=}j-}>8=e{v7!1rVnV2)8NxAuNydrQP=>g5 zjuNm_>0*dLY%L2G`6+Icyu2+!d|#0$l_l(Xi}%jNloNb^oCnM#EzI1|r;T6Z-Ch%^ zp0A2fY(cXqG43~X-c~E$LAh{HDjk(!N=xyMMeQX!mHGl-r83u7(WY!zS(f8nRG7N@ z@8PbBGFNj_l%M4Eb^5_~c%oBSz^gKB>tedUROBbS%9e_7Ioy{f?u_Wgd)G?D`x6!7 z{TmfrfaW2?acc;t_diZeNM-|e>ff#4y=>&iaM2h0<5?lEl3TvcrKxmw&13zE$n!f~kh=E1E_JoUe$s_$ zJf_a$x~cn@iRhqLWQgBS>@QvqNIWn9E#r_5~%N8FITf~ ziuLY}7Vv#^-WxBPMNZs zh~K?JPzXXGzCuY0x#y5yOAI7X4E;=9BFB-xA4)sAw6zZ_@_ZBU{nHayDIN7uN(ZSU z_Nl>n!ui5^`wq%)7|Iqp>Fwmo8}j`fQsY-R96tD_QfUlOI!eP8L+ZwVZ_M7j){yQ} zUzqOv)Tmj<6XtcM`dnU;6DiFyaX4~`hzWjGV3sAhLbm;(Z_12A>qL33pQtlz7Y)Th zqO&GaoM_rZ;2a_sOR9D{OH7>4rNqQEs>6_}T}uxL!pGMuSRK*ii_s#q&yCUh0N%;w zXgXe$s4oiS1f?L=U8EgYE%wp>jokh!vS#``ugaL<#UeK9b%D=GKPaJ3GsTI3k-iVU zmp*M%ePzD?y5_Q-KWHQ?|=BfQl+^n zL?I}Z@1Xn^Kv`9?Rq3FM(@`0UQpci}qU}mA1^Sx9E0qGeAl3QX{W z+o(~q{wrM?lB0LH_KDa`9dloaQqwae5EQ#TOWC{ZS^S>2o*jx>D)xrHA)~?^1%EGmKYUMoUwm(T|L>su_CjfS zKb`zVl!Y#eJN%YIRjQlPME|c$PyaL1`(G5KZu&Jr>Q|!i)EhLegT+}JAE^n_J8H~E zkd|oHNGv%6)lx^59MvQ$*-2n#CM-9>ancf<*vx;!>+t$QQ|SuF!SQfh9KX;EA$H`- z-0-16vuTJvPa!@RK3`#)`@d+Zk5t+Wfy&VwU!}1o2v9D<_cB9;@B1B;|G1!3v~*&V z;@@$oNO4`%X$*F#soJ6B$E{ZKlh-M=6o(l!UP?`t&%ZRJZu((K-qz)1d0RtDvOSZH z8rLI5nHw%p{PqzgPJfT)S1z|uq5-&|Yyhk{ivY`7YcV{=^Y}ZDK&7tY_u>6G4vvT8 z;`qjFw=Dd>(!8z945=G_RFk#k&+vKhx$yZ4=yO(<1t_ihK!--H@1lk(hH?s2-$D70 z7fPjql(M}jNU1L0=1`EZ&Y>X1+2KfSutRQ|v(k|1tyHJEDo2b_N_9~<_BmG!S)PBQ zOWpMS{N%NNURAjB$JP3fH%qiW&Saj(Z0`u8#`AzN+daXMu~AcyvMReEWpy@g@fgpm zfA@~S>+wFkAIHJ*a9kXp5;FdO{D0Hj_;cX%;B(>g;dA2iw(7Sl1f}v%1PJZy00000NkvXXu0mjfX|SbI diff --git a/CommonKit/Sources/CommonKit/Assets/Shared.xcassets/Wallets/doge_wallet.imageset/doge_wallet.png b/CommonKit/Sources/CommonKit/Assets/Shared.xcassets/Wallets/doge_wallet.imageset/doge_wallet.png index 811c2e4dd482cbbf985e2f95f6a78ac55a4f8e96..c6c2fc33cdefada09b4ffac0a765233c4679c8e4 100644 GIT binary patch delta 1455 zcmV;g1yK6UJk<-3IDY^Eb5ch_0Itp)=>Px#1ZP1_K>z@;j|==^1pojAYDq*vRCoc^ znq5-kMi9rlXSA?+ijLr%00Wz+)Pj71kjfJfD(e$~Pk?g**b~4Ofqeu{U>Wiju(c;x za|7oQur%o&Y?nVqMk8Cc_*GHH)>xj_%=G-bdk|(!?WE|+C4blg>8wDc3JO#a6}o`n z_8Tc^D})vTv_W71QKue0LQpCPEtnw)Gk^_p-iv*u(4)5=q(lJ(7^yGeb96NeL#HzC z_N?eBDGy-^gC=l=VfgvuUf@GYQW;k}FBT=rBgn?6QT*^RETM?O&{MH!nkf2{|VO_^q^a ze`UI6G)_AYUSs*0ij!!QlUX%68RMGMUh!=h#<<*H-hZY>FF2zg+#TJG1de<9KtY@e?Rj<{1Dfkr*8fzV92yA{geQ6=T`hZJe@^ z6kS-!tbYN#+Sv!M_oi9y@+BjZo^pZ%@%nP2XDM!%j zc~0#uH7wg6cT-v+y>BR>-^wbp6 zk&85}PXur}=;;PYGkS7KYzZZl2RVT}oq3<_bONx1qM4$g`MCo5pA~|J(Z%8UB{LkDbdH{C);}=|-HLl@v4~#dWd)W2U zb$_M_ZJcH0{qlSBVN#pTo#rRg=k<%#Q!?5#N(EDU8i}^8afuQ{xd6#HOWuxg%6^79 z@G{(~re^06{rbgP_Z?nu?%a=Mq)hTlw|*&(m@tkIxpvpmHMD8XDY_Y^GHxiiMEEhL znQII}<9izC@~!$vs;foQ60JALETK_0x_^Az{g}&RLMinCIU>TyoQG6)`j;P)u-^GI zgr?Hx()M_Yjzn$bkO5}-g>6zC0sZi|=s7wql^d}ebecRWg8IU*+PLT`I)A2s z@enc~PJSgp2hs|wU`dECOHC`3MNdr=1I11&XtTfWIi~AG~ z<5K1-W(wy$c#TO+UB&ggc4&qQA(U&cP)fQ9b4DNec<1~|9r&bF`VfrV|4^-BOySkf z*N+h)P4>A(uF+52nl@!}*#&$FmVc+t2I%dCGu1;sF@jEsEsN%~6_k1sJD{S)5#$aw>~7*Ql0=bv?QDHZ3OLEbApl-uJQXL6SdZjqzrGd=lz)4#Ez*m! zS7hfAx5h@XPRG{IKU5J_CL((h9LPqGVYRD+6Orz`C5@s~`fz{;+2e;Qtdw>G>x{uv zCuO;4L6sR+Gg(w-eHQbBTG5uNjO(FCG89{iDzsA`obgFU)uX4*vCZ_0VkpAq0X*qL+cQ-~{{v8|DM;kr>HYu!002ov JPDHLkV1nc-wgLbE literal 7757 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

}3R?zG~=8+AAy;s8H) zYea>)V7K3PSgn5p8!7h^qWuuM&zZo!hV`re0DCf;!PaDA#UOy1qZ-C?>%B=GZcdlt zHZgRQ%ExtL?%FwnkaIWawMi=QX3<8W6#>%|OBJ|9hH;zn@ftn9 zD42(jx zpE&j|v`f8ZvBP5}!3ieu8VsC_#MhsWlI$vRV@iw9u9xEKm>eomAo5e((4gImu8KpH zx1ktqiNfiMa5R~YW2z?yS0|`vB&VDg9Fc)DEHieC^>b?6oH0`JYl)RSYLn7&?o1lacc#>)?o-q*D&l?GxSpdl-$^N!LHS#@ry`H-NYN-uch_Mcu!ppL3Q7LhtUe)6vKwor~ z2cepV*ZM~z&@)qkNg4Rk@;sMH^W3wh!q{_03F}l^BJijC{rKQg8SSH&}j=)n6lnxlQt8CUSYaNRZ^UDHF9= zluvnfXeWl+2$(T-CnRAT2%NpZU`?I_P856 z8|RC#Fp@{syaz2>U+UV$`0{oGKA1J&-W&@f?#$@${&X?!&gulv_b-(QU_V|k=d@cnHKN^!(nuiR4JRMkX{j zkb~a3Vr(?Sy&)_vE=HNmB0jk0pw|!hHj-j1sSuB0t-kQ)D{IV-Bu<;>5XJ zxtSkq3CHaVI$R!>ktFjm-JOR1>M+!4Jke1eioxbsnw&+r#HIi;mGO=w^wu9ohb0sz z$t3EEd{HX3M`gYndg>#{a1??p&vj>02P3EF`GPFR+LKU2c~c?v#PG>@%3FF*uMCsj zxtQn_Q5%s3h~r!ZpUJ>vXXX>Zz>SmRGJxl)@L%Yc2ok(BLFHtu zfXanNV3~nyF9}5{fvYZX=DTa$c(%^t2UbFt=jmfqkn_v@$^8R_;PNwFgeb^z<=9Ul zR5|V(`_)4DzLhA?w?V7PjwF|c>ty*$;1&T&$OVfiA5KvI1!AHl8Y7JnXf4@;`a&PH z7=mztYThhKkS+LWK+gaq7THFy&s?4?5<;0{#VE)w}W0yk4kbOTdB;ahYrd=$B3W`gxiU7qicExGL0u@4#KqY!TJKMGsnp14L#$W_7rFY)-A z&l9MS-ka0m!#OQ~k?enAM79{j;2A;t1 z1WpK&DS!83F@KiWtEEPl<=SX_3V)$b!cTUJcmgH_ff25hTm0DRG>UYda)>9#kO^QICpwRK6<>`N3Qvu~ziMz%k|mxI&p9S16@KRRz787Po#rEbw)-S482%{ zDJ4JORdbwYvLpa3=?O@-z|`b2I0h`8CmHf2J^u1UAy4un04gD#0XDkG*!7F0JmnZ~ zN!`k~=X>+&BwOL;es;>1OU68(e>N!CFPs9o!fYP`=l%V`6;g6)a|jsweI+HPHq(_F| zcSHey<$|7{?#<$ROZO97?npiIF2WD3Kwi2{jXulmr$reaoIcN&(<%IkIj`@@T&5i- z%dq3rNp_qjfv$;*j|<{#xcpcfu7nDsN$%+zedw(_;d|etTIhh@nu7!~f}dzRM!9#4 z@2uFvkF>@K;8-x3GKQYcsYRAl0$|4GJpKL3WD$R7-o)RYDdu}>qj{=Th&r?yi4p7W zniQUX1{%+b5*;~xiW66y>dfgVij$p$D{CRy?eFgdSCFuaQ^!-BSd4;r8v$s8W4EAm-2-s$p;N>RcyfL5|DM%=Eo=^!>V20+#H!9NHuY z;kEA%E|JQ?30qE`uow)ShQQUU_i-lLJ$X9q7%P+wVeN~oycBLlpcP3>pwT2E{34L#^dM~FJ*mC4_#2yRb@eg9|1>ou)f|I2=kURsp zs)9Y7k~$k>p7&2oa&Pa<6I)EtA#dG^3V9pJ;Tw=0zYUV4T~KGcp;+n*gFFCwk{WeG zkS6a!M$~5EH_ov%YQ=@Eye*2`RBBRqdz%!#Khe|wZ!bN@m8*7h;#6A!Tus4$5jZn7 zs1{|Q(7K&5M{pMUVo91EJ8=3@QLgJ6lI=cm;?_J-tW{k~#D=cKW2=V~j;Ji2O_ zJ_1+n!X1^52}-|U;C T>K&Tt00000NkvXXu0mjf|D6lI diff --git a/CommonKit/Sources/CommonKit/Assets/Shared.xcassets/Wallets/doge_wallet.imageset/doge_wallet@2x.png b/CommonKit/Sources/CommonKit/Assets/Shared.xcassets/Wallets/doge_wallet.imageset/doge_wallet@2x.png index f2d160d4b99df56d9ff759c2eca5046530634cb1..5f2cd56cf7ff6b284cefd5509bfbfad2dd065080 100644 GIT binary patch delta 2683 zcmV->3WW9AtO1u5kT`z;0drDELIAGL9O(c600d`2O+f$vv5yP_&_M?FmNQKr>H72m<%`fs5)v4)}n8cgeBd9R|fh*-FApT4VwIuI{Gp*ei)o5~1- z6Zu6R7A;_5!WqGYcnu{RT`7o|l!t$P(TJb`oC`Mxf;}dZcXHSD2y`Z#@nKKJ8*~^u zFpoGkp-zPRbh`Em9sI#e_^|0{VkmRGzIA;Bma!@vj(>zmwX7l3W^v{_h@YKS^*$^LyooqWO?NPpW)Lka(#ELMq`f zh4TiyXt_gmG~y4z>y+N|NkKB2(#u z5oc85Ww41%9C3x1NTv62j+1YZDpysZp`tmf;)humeoJ zWWax_P#o?09DvFE-nl=p#X%?BjSj<|H0Z2f_R2_qv%$fWt2rs_-Q#kAgLxqbARy{HG ze>9pKTUR?;vqNyqs8Kf{T%7O>!85?d_UC^CDd9Qow*z+!9d!f3nMl$zlKP9QbE(*K zaEJ)>z>0aFaAvg4Yl(P{P^2d@XaQGf*nGTt&?lUD!b9*RqK-j})^H4Npjy?P-hb$u zfHTIZ#($=u`4-;zvy`-DIId*#_Tf4jM*7hO{+ zzT%s(D705P@bilIAJ5i)GNU#lRP5u*WIlXhUBCLfk8AI}UPP*BYTu%O7g=gQ2z4(H zTuc$rm2GssH^RjcO9+ciNGl;(s;+-1S&eBi7nzl?6I~kNL?A-&5Y?dq%gR^Itz2k{*YEhAQ z$=BcHy|OMGj<`RfaK1u$BI%|}O|T5pupD5jrcy)`J<|gcMnqtoC63Hk`$0vJjxf!| zm_$w-2T=M8^PXUv0NOo_ouhvw+s~tRlB%#E#XfW@dKEfdy=Z!?MCE*}3ihaQznLQu zRVWt~YrVWYcJaa`B+Zn-1zxy>-2b9~h7!RY7kADqB|C?p6+rt04Cet#Am9y#%|joC z)N;R`@brzuRbDX|+GqK!=|r<1skZ_sh_S5^Rgn1MCa&^|$?`mP+DU)4p|Q1 zaWqwm%k#Xs90qM2rfq-3Nv}71=j|u$Bl^n-r<~LS&KBg`XzOsA_b6_E;|$){|LJRc z#5t&wm?cOw>a+kULH}lb;|v29(N`lJ1}IMI-e-Z1XlYC&5w{qrf@S2LjOeQouBSE9 zq7!8ijWDN9+NXQyZZ}s+b?-RTLs&rYv(qut`*9gWYTPwF&>w#~-*ttfuvh5CX_aBb z!ilT|IIeGBPvt7&W_8w)?01*e6^_gUF0({1V;evcS9#H)?F@cqN;gd}%m>-XV~x!$ zD>)c#R2A22!uywX6b?l`Tjnxtl~e~6oxLa;m#KsuO|nF#&udmFK#lGHJkbDr{MYIpW>emPQ}jNQc4}cX zk0;C`SjJ3Il?rnUJ{)2QY=3?0^U>HVAOX}_4tx_ECzzFYI`3ctiFWH4C+SmR)}a-P zq$}@M2->9P22XlMQXW{ zbm#43GH>3ew(U-jDVaAk-_0r-F?H(n;&gRKFt&fthXkeuMO)wg@dTzImz=lvdC4$7 z4OD%ak=7xXJ}1U(a=GH8&C$e(<6J}|a$RLIr}}DsQNE;YvC!O?rcqg<$5B{W9=tqV zJ7Nr8fyZP-aCz(??*=#4)^*Kt5EY9qsNZXsxbwp_X{&00KOndZ+gSo9?VQ?b9$bDDM+ML%e#jSOn*C$#eqKddY+7zdu zfTFWDbhdSTIC%)ONg;xDC``Bxvt&Yz5991yKCpL13D%)-;W`)-s(7dwd&SCk$ZkY? z2WcBxSsIR(Mz~&#h>~x{=eCAW43XCCQr&Y0DAxa)P%=s>TrZ~oV<18skCmuy*e*hE8hW1FWfN98Kz2AIXA_u&+t>w8RnRw(G^sn!dQQ+V)9LRjS+%T pV^s}w811bx`^QCtlH@RB{05X(OI-!%-wpr(002ovPDHLkV1iqo4a@)l literal 22106 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 P00000NkvXXu0mjf{BD16 literal 37813 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 diff --git a/NotificationServiceExtension/WalletImages/doge_notificationContent.png b/NotificationServiceExtension/WalletImages/doge_notificationContent.png index d3e715bbb717373452012ac617d2995fccda761b..40f7b5d01d350fbe2853d2e5e18f3c43c3e905db 100644 GIT binary patch delta 3905 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 P00000NkvXXu0mjf{BD16 literal 37813 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 From ab117abe9798f6c50ff14c0e665c705497631a63 Mon Sep 17 00:00:00 2001 From: just-software-dev Date: Thu, 26 Sep 2024 02:23:12 -0300 Subject: [PATCH 103/106] Crash fix --- Adamant/Services/FilesNetworkManager/IPFS+Constants.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Adamant/Services/FilesNetworkManager/IPFS+Constants.swift b/Adamant/Services/FilesNetworkManager/IPFS+Constants.swift index b2422034c..99552bb27 100644 --- a/Adamant/Services/FilesNetworkManager/IPFS+Constants.swift +++ b/Adamant/Services/FilesNetworkManager/IPFS+Constants.swift @@ -49,6 +49,6 @@ extension IPFSApiService { normalUpdateInterval: 300, crucialUpdateInterval: 30, minNodeVersion: nil, - nodeHeightEpsilon: .zero + nodeHeightEpsilon: 1 ) } From e5abc3711c9f6ab4e13f5fa95e0dd91ae8ca4106 Mon Sep 17 00:00:00 2001 From: just-software-dev Date: Thu, 26 Sep 2024 04:40:16 -0300 Subject: [PATCH 104/106] Scroll view did finish scrolling observation --- Adamant/Modules/Chat/View/ChatViewController.swift | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/Adamant/Modules/Chat/View/ChatViewController.swift b/Adamant/Modules/Chat/View/ChatViewController.swift index f3f36c931..9bc33b991 100644 --- a/Adamant/Modules/Chat/View/ChatViewController.swift +++ b/Adamant/Modules/Chat/View/ChatViewController.swift @@ -195,7 +195,12 @@ final class ChatViewController: MessagesViewController { } override func scrollViewDidEndDecelerating(_: UIScrollView) { - viewModel.startHideDateTimer() + scrollDidStop() + } + + override func scrollViewDidEndDragging(_: UIScrollView, willDecelerate: Bool) { + guard !willDecelerate else { return } + scrollDidStop() } override func scrollViewDidScroll(_ scrollView: UIScrollView) { @@ -282,6 +287,10 @@ extension ChatViewController { // MARK: Observers private extension ChatViewController { + func scrollDidStop() { + viewModel.startHideDateTimer() + } + func setupObservers() { NotificationCenter.default .publisher(for: UITextView.textDidChangeNotification, object: inputBar.inputTextView) From 37212ee269fb81ad936f5ae7971a1b5b4e8a609a Mon Sep 17 00:00:00 2001 From: just-software-dev Date: Thu, 26 Sep 2024 04:44:00 -0300 Subject: [PATCH 105/106] Revert DOGE icon --- .../doge_notification.png | Bin 1493 -> 7757 bytes .../doge_notification@2x.png | Bin 2711 -> 22106 bytes .../doge_notification@3x.png | Bin 3923 -> 37813 bytes .../doge_wallet.imageset/doge_wallet.png | Bin 1493 -> 7757 bytes .../doge_wallet.imageset/doge_wallet@2x.png | Bin 2711 -> 22106 bytes .../doge_wallet.imageset/doge_wallet@3x.png | Bin 3923 -> 37813 bytes .../WalletImages/doge_notificationContent.png | Bin 3923 -> 37813 bytes 7 files changed, 0 insertions(+), 0 deletions(-) diff --git a/CommonKit/Sources/CommonKit/Assets/Shared.xcassets/Wallets/doge_notification.imageset/doge_notification.png b/CommonKit/Sources/CommonKit/Assets/Shared.xcassets/Wallets/doge_notification.imageset/doge_notification.png index c6c2fc33cdefada09b4ffac0a765233c4679c8e4..811c2e4dd482cbbf985e2f95f6a78ac55a4f8e96 100644 GIT binary patch literal 7757 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

}3R?zG~=8+AAy;s8H) zYea>)V7K3PSgn5p8!7h^qWuuM&zZo!hV`re0DCf;!PaDA#UOy1qZ-C?>%B=GZcdlt zHZgRQ%ExtL?%FwnkaIWawMi=QX3<8W6#>%|OBJ|9hH;zn@ftn9 zD42(jx zpE&j|v`f8ZvBP5}!3ieu8VsC_#MhsWlI$vRV@iw9u9xEKm>eomAo5e((4gImu8KpH zx1ktqiNfiMa5R~YW2z?yS0|`vB&VDg9Fc)DEHieC^>b?6oH0`JYl)RSYLn7&?o1lacc#>)?o-q*D&l?GxSpdl-$^N!LHS#@ry`H-NYN-uch_Mcu!ppL3Q7LhtUe)6vKwor~ z2cepV*ZM~z&@)qkNg4Rk@;sMH^W3wh!q{_03F}l^BJijC{rKQg8SSH&}j=)n6lnxlQt8CUSYaNRZ^UDHF9= zluvnfXeWl+2$(T-CnRAT2%NpZU`?I_P856 z8|RC#Fp@{syaz2>U+UV$`0{oGKA1J&-W&@f?#$@${&X?!&gulv_b-(QU_V|k=d@cnHKN^!(nuiR4JRMkX{j zkb~a3Vr(?Sy&)_vE=HNmB0jk0pw|!hHj-j1sSuB0t-kQ)D{IV-Bu<;>5XJ zxtSkq3CHaVI$R!>ktFjm-JOR1>M+!4Jke1eioxbsnw&+r#HIi;mGO=w^wu9ohb0sz z$t3EEd{HX3M`gYndg>#{a1??p&vj>02P3EF`GPFR+LKU2c~c?v#PG>@%3FF*uMCsj zxtQn_Q5%s3h~r!ZpUJ>vXXX>Zz>SmRGJxl)@L%Yc2ok(BLFHtu zfXanNV3~nyF9}5{fvYZX=DTa$c(%^t2UbFt=jmfqkn_v@$^8R_;PNwFgeb^z<=9Ul zR5|V(`_)4DzLhA?w?V7PjwF|c>ty*$;1&T&$OVfiA5KvI1!AHl8Y7JnXf4@;`a&PH z7=mztYThhKkS+LWK+gaq7THFy&s?4?5<;0{#VE)w}W0yk4kbOTdB;ahYrd=$B3W`gxiU7qicExGL0u@4#KqY!TJKMGsnp14L#$W_7rFY)-A z&l9MS-ka0m!#OQ~k?enAM79{j;2A;t1 z1WpK&DS!83F@KiWtEEPl<=SX_3V)$b!cTUJcmgH_ff25hTm0DRG>UYda)>9#kO^QICpwRK6<>`N3Qvu~ziMz%k|mxI&p9S16@KRRz787Po#rEbw)-S482%{ zDJ4JORdbwYvLpa3=?O@-z|`b2I0h`8CmHf2J^u1UAy4un04gD#0XDkG*!7F0JmnZ~ zN!`k~=X>+&BwOL;es;>1OU68(e>N!CFPs9o!fYP`=l%V`6;g6)a|jsweI+HPHq(_F| zcSHey<$|7{?#<$ROZO97?npiIF2WD3Kwi2{jXulmr$reaoIcN&(<%IkIj`@@T&5i- z%dq3rNp_qjfv$;*j|<{#xcpcfu7nDsN$%+zedw(_;d|etTIhh@nu7!~f}dzRM!9#4 z@2uFvkF>@K;8-x3GKQYcsYRAl0$|4GJpKL3WD$R7-o)RYDdu}>qj{=Th&r?yi4p7W zniQUX1{%+b5*;~xiW66y>dfgVij$p$D{CRy?eFgdSCFuaQ^!-BSd4;r8v$s8W4EAm-2-s$p;N>RcyfL5|DM%=Eo=^!>V20+#H!9NHuY z;kEA%E|JQ?30qE`uow)ShQQUU_i-lLJ$X9q7%P+wVeN~oycBLlpcP3>pwT2E{34L#^dM~FJ*mC4_#2yRb@eg9|1>ou)f|I2=kURsp zs)9Y7k~$k>p7&2oa&Pa<6I)EtA#dG^3V9pJ;Tw=0zYUV4T~KGcp;+n*gFFCwk{WeG zkS6a!M$~5EH_ov%YQ=@Eye*2`RBBRqdz%!#Khe|wZ!bN@m8*7h;#6A!Tus4$5jZn7 zs1{|Q(7K&5M{pMUVo91EJ8=3@QLgJ6lI=cm;?_J-tW{k~#D=cKW2=V~j;Ji2O_ zJ_1+n!X1^52}-|U;C T>K&Tt00000NkvXXu0mjf|D6lI delta 1455 zcmV;g1yK6UJk<-3IDY^Eb5ch_0Itp)=>Px#1ZP1_K>z@;j|==^1pojAYDq*vRCoc^ znq5-kMi9rlXSA?+ijLr%00Wz+)Pj71kjfJfD(e$~Pk?g**b~4Ofqeu{U>Wiju(c;x za|7oQur%o&Y?nVqMk8Cc_*GHH)>xj_%=G-bdk|(!?WE|+C4blg>8wDc3JO#a6}o`n z_8Tc^D})vTv_W71QKue0LQpCPEtnw)Gk^_p-iv*u(4)5=q(lJ(7^yGeb96NeL#HzC z_N?eBDGy-^gC=l=VfgvuUf@GYQW;k}FBT=rBgn?6QT*^RETM?O&{MH!nkf2{|VO_^q^a ze`UI6G)_AYUSs*0ij!!QlUX%68RMGMUh!=h#<<*H-hZY>FF2zg+#TJG1de<9KtY@e?Rj<{1Dfkr*8fzV92yA{geQ6=T`hZJe@^ z6kS-!tbYN#+Sv!M_oi9y@+BjZo^pZ%@%nP2XDM!%j zc~0#uH7wg6cT-v+y>BR>-^wbp6 zk&85}PXur}=;;PYGkS7KYzZZl2RVT}oq3<_bONx1qM4$g`MCo5pA~|J(Z%8UB{LkDbdH{C);}=|-HLl@v4~#dWd)W2U zb$_M_ZJcH0{qlSBVN#pTo#rRg=k<%#Q!?5#N(EDU8i}^8afuQ{xd6#HOWuxg%6^79 z@G{(~re^06{rbgP_Z?nu?%a=Mq)hTlw|*&(m@tkIxpvpmHMD8XDY_Y^GHxiiMEEhL znQII}<9izC@~!$vs;foQ60JALETK_0x_^Az{g}&RLMinCIU>TyoQG6)`j;P)u-^GI zgr?Hx()M_Yjzn$bkO5}-g>6zC0sZi|=s7wql^d}ebecRWg8IU*+PLT`I)A2s z@enc~PJSgp2hs|wU`dECOHC`3MNdr=1I11&XtTfWIi~AG~ z<5K1-W(wy$c#TO+UB&ggc4&qQA(U&cP)fQ9b4DNec<1~|9r&bF`VfrV|4^-BOySkf z*N+h)P4>A(uF+52nl@!}*#&$FmVc+t2I%dCGu1;sF@jEsEsN%~6_k1sJD{S)5#$aw>~7*Ql0=bv?QDHZ3OLEbApl-uJQXL6SdZjqzrGd=lz)4#Ez*m! zS7hfAx5h@XPRG{IKU5J_CL((h9LPqGVYRD+6Orz`C5@s~`fz{;+2e;Qtdw>G>x{uv zCuO;4L6sR+Gg(w-eHQbBTG5uNjO(FCG89{iDzsA`obgFU)uX4*vCZ_0VkpAq0X*qL+cQ-~{{v8|DM;kr>HYu!002ov JPDHLkV1nc-wgLbE diff --git a/CommonKit/Sources/CommonKit/Assets/Shared.xcassets/Wallets/doge_notification.imageset/doge_notification@2x.png b/CommonKit/Sources/CommonKit/Assets/Shared.xcassets/Wallets/doge_notification.imageset/doge_notification@2x.png index 5f2cd56cf7ff6b284cefd5509bfbfad2dd065080..f2d160d4b99df56d9ff759c2eca5046530634cb1 100644 GIT binary patch literal 22106 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 diff --git a/CommonKit/Sources/CommonKit/Assets/Shared.xcassets/Wallets/doge_notification.imageset/doge_notification@3x.png b/CommonKit/Sources/CommonKit/Assets/Shared.xcassets/Wallets/doge_notification.imageset/doge_notification@3x.png index 40f7b5d01d350fbe2853d2e5e18f3c43c3e905db..d3e715bbb717373452012ac617d2995fccda761b 100644 GIT binary patch literal 37813 zcmYIvV|ZOr*KKUuwr$%uanjgEPpn2Kjdfx)w$-R{+Sqm)+eUBRd%yR)Kh|E)vwzJ! z)|hLpIp)|=8fx;WNJK~w5D=(}3NnDtx#T|s0q*lD$}GD2IU%|!7<`3*K*s*hfP~1( zA%K9OfKZf?)b?3D%Z1nf@|gO-uEe*Z>V8I}mLt%BEv)wUV$j`bMVI|<(;qV*lo-xL zf$?h`CD4X|!0+-Gb=0Qhug4P?gT~y6i=dgD%bc&a7|Ds%7gkF_dApbcz3c6dfBYx? zKlYJobciG13C}%4qS!w*7vrMSBJEKa2g7)Dgfr;KQ|ZCP+DxziC_1ewE$761iI!6g`2 zJo3YO{)qP{xKBhqjzK0J)(sk2Jt_?99{3K|_prWVeicTlcYvaa`{>4iyII=SP@ze* zEz7*b?0E6)2E*Y*LF7@N&Py8k3{l<6hYuZ{kC*hXb7>O*`gQ{wIst#%VUk7bQ4(lw z?6U!FtZbOnL6Z3Bi|RX9yC*Rad~m|p2#5bcaxX*JF&ZBJ%-k29DlFYfa#O#VerxP<%hX6c z6D$%MQm&)0jLMH~z?-#T319btrxbUtNM!T;FgI*|zFlE7iQ#zTvTAKqsqjoyvfyP& z;0$DEUA?-r*90XJ*+WF@TPh@QhoVFw%9q~#o(@bfk*`ya;evSaeG$ETJ$jtIEPE_? zEP5!1ZtI8WahUAQJD-?@Sqp?N+5A{unYLI-R&Ge0F3f&wglGcj zM0sK^Glav`J-5Jkn%Kemp{4K(|2rW+(RY$}k$19}z&ror$GO+(*O}K!nly6j(Q5x^ zpR2Z`rz3%5pX0XUr{k~FhsP{v83iiuaHGMr4EIXbY}}*!-BPj-4wQnkGQ-M`_>F+B zyre(+?bd@UR^`Qu*qK~0?6VfJ7>%-q`mve|2tt9XfQ)WAqK+e_FIR=kGue1w5KYA| zm4;UZF^Nb4hpILZQ=-6Lk`aba-e zPCHq^t67Xe$z?AGh=g}(lNWG#4^4T@ia6oA>(Y)2+@f=rloF}^A^@|cP~Q=z)j;0z zgjCr$B6PHLm-`?L+w>S%U#rJN$l zx~bvquw!c@()v-hG;?}Xt@~U8F)Ic~jNheD{|IrJlxCQx zSvvu5jKV9dU&t&yxI`y2P~nYbc<_PdjLr_B9B}}LdsD6y^Yuo*csZ=*Z|r`Wm5kc9 z`ci?dw2-h%A#icNi&-PXLv@@)E|?ulD(~&5!R&B7pv|8Q6R`8ty8dLVb^}r>Q@&HGC?HFui72S*-M*4N?!ePjyJXRh)lX>|9n3DIET*wvp0Y;qM8XdSbNbi&?2l$-^Lhn0Et>|`} zFen4FHQ7w5z*f%)Cp2}33y70SzRgK;LlM80yGm2k>CA8!4f^c#HAERi)rr2tsYJ#J zTG1$wavrsJ1se<0A;UGd_k!ZE16_2wD4it3_AcM~vIT_1GZqbaQ|;bn3*#hmx4}ip z?c%1!^+*$cxe91PlnOn!Bz&0}kg1$)amxHp_UZi*Dq((JGFY&tmHf#xDFhd9e-!SB zTR`J;#4?L+{_QR_bQqtu)8byTYY{vuY;rR5kH>bJ`U;{;MV&7k*q;w{HR}h`9hf+b zc0Q9X{bS=~UiQ$VAa(+zJMEc7&A$nEA9?6aut(|Qui(NZhvb`)eYBy zEB$1~_aEG-eZJGAktntJronf#IedVHv+d`8D_q_(pe8iBO`e=6tHzBK|1b;GCKhB$g1O=SIpnE z^XDw{LM1HPUTETF7Pax1Ga+jHK1EGPMYO6tXz;+a>x94mM+m9iJzh$>b}QC3e_oQ% zr8b??8|Q?xq*(b&4|Do*c#%+0UbGq@q@{=jID;GNmbzqgR*cIjH@`Lq1g}Qh8~SSk z?Q6(Fsz(~blhRb(oG8#zXJK*YARVx4@&)=~9005Zl>a_LpQjQhpPLR1Rc+!X0m8bI zcly#3bRw=nCfvM0vXL4~#A3y`FR^mdcbA!f*YZ1`GWp8eG~|}dF~-!>_aC4}A?GNQ zka?Y9#aldWz*_oVLt5Bz!(W*1^9*61C+3iL9GX^`8&q7SMphQw?<#z_g6fIJ(4+%Z zHwP9>ub=S!@gnr)>J6dE3|b5^++meK){OQ?UNW&*U#jm5OvtMC(SkVinbP)+g5WV$ z9#0=M!|1_QtXzJfU=g;2LvD}Vfbdxf>BCG1HQ(wX{f@zxg@7Th0)>;`OZ)$D!23^> z)<&oiv!VNbsYtGr^h5H)Y7aNE@_~vZAv@Q0E`D0#Xs6Uq{7z0q{H9tK7AP!1bM;Q$@c*6c%jAg4x5e;m@+Ia4m zzAre{{s@K~1w=LFarHaNNrK7}bN{}4=bDroX|kF&NU18lHgRyRHsR7rA!^9z+tEY= zL@?ejgFBFFBOBogNE|m}HGcQ*bn7qAVWO;gAA!fN^I6Ke_rQVwOwA@h&ha$6X}UwP;lH+ph?f!XYQV*%cHm>`(pn>;C_ym`>EwGAe#c23@YmgkK>t>{VRt|51()3*| zE}4@R&yWXq{tQZPQMa)tB?B`zMt)Qlp7 zix0Z#&k)%Y_}coRv99q32sL%tr#%Z#WtYx_nZbS$*YQ%49X}GY4}|4$9sQCGaWZP_ zXj~S08Z}vdOLkaO<^sJIEaL1uvtlMcfvay8vIvfwx>tk<>9K9li%3@D3Rq1ZDiQp8)>XKVA*5d3Z!z4|YoNMaf~+ zYb>r5kuTtDn{|Jc7_MoJtt8OsB73l23N82S^fJ$a#OlK&X%))*7xyg2vvPgJKk$EL z89HScviB^EM>KvX8D>cuyg*0XbjwnAIx1^>bK4Djg>*;bPJ_=O&!0^+(l1bylvYCE z_{&S6<^@QC2qlU@D5YAyGHe+<9KM^ctf|#>-S92BjXNYg zdYdyFRGRwf6K`wSOvyw?tghNIFGi#Fx1YPk(2)-*w?#4_@Lx%OG4!S`K1;K<>-?Cy z7be#(qC%vbp@=LmvTA#w5J=9tkZ&^VghknF$y76C<3P4KG!%zu|GM{%G)9gd?!gNr z;T?iJU#72K>WUWLsNxWR>3Tm^l_r8@4XdLM-Fnb5;C9Y*#AXENiC`s7CzZKBDQT8Q zvnokcS#l#puV$aSX2vhr1H*u-w`gr7b$9SQf4{v?T;Y>DoViPFHB5p9u&9Z^sYsoU zml{}lgbaB$1Mo4?e8?V`C{%|J+Br&oo}3Z`U!EZAH2x*gVPGUS2jnxVlT?*rM!i}i z-K50j3(XP6-^mVWRx^7SVQ=M~B6yDI@=GW>7({`}8n%D%IPbda6lY0kl_E1+nJ##! zY~^W`c|;sIJt{I7d5Cbtb$(;olWU>jR|_2fZS*#CDi?m_M-$0TH&9W4$VNj^oaVvu zvoVEaE~Tg@+$xIJ*isZQ9&E_4R>|)UsWPLPz}QKbh|9&fDmSd?p@>1Rc_hsx)Fp8u z?l-a55phBYb;C?IwRs&euFNCVWQiVGpC=+l6VmX)JDur_-lC|h!G)`&v@>I)LCM{- zHIVQqODRjCI8oCEBB?e&bk1mXoZZORJfAEGONMZsbdRi7XtLMx37L0t;A=a!vLb3M z;fi($>QKw}IY|A$B{DBM^hhR2g|Vp|kLEVF4Xl4yb;T1p4b6zCBOPsBosg2AAu7O$ zG54^wawrFBE9WRu?izeh<2N-?NB(peQBegAA|N{nn^Cmb*mR84d4J`s5F1yOuufc4 zlUgxt#cACakl46CHcIcuR3Qp-8vl0)newe#*HB_o7g}49pP<$Vc1<~e6Sb2Q?3oEu zLd;{^Zkp2nb=Qf~zG#M5Ehm4xZ%VYptJ_{YwS$!BwY+FBJ{@wdnwm#4;2l79?<+*Y zWaFSz9cQB1NSsaHaiQ~6u2hfh5d9tzpEY614!+X^{OC~{j;qH+Dm;&X0-NThGX+-CP1o_Ngo79Oh8)(V%%1KsS4PX zbsyv3f#mo-%n`+!84lHuw^TF5PQ0O{Lt+`NdKBNuG)iO`bX>k@EmCKo8strXKkkw| zhb$RUOC+&^bcg(%h-ZaDSfE!h9*r?EsaLT-PfG;MZNyTsGbf!gC^=P#R?KAAI>0DP z>$tivJv9mKrWDgQEoaaVkz=^}0Wk^{OewyqR;O@Mn4W$xz?Q9oT0v9dWLT)QyL1O> z|M%QE2i)0R-l9)zt&CcLGRMD=^7=E9Dlz_xmRC8cU|{;-cMPo9z27lKD)|Vc4hA`l zCO%bl_`?C1!~ObN!)Uw^p5>T#VD{bS38o5C1aqx;{NP+>cZeZf=I^GqPz{c; zm`d1*)jbX#2&1tamgTw|(;qLRbmGQ!IWkbU&L)?6Bo*qaUVP$5qd)gb{g8nh%A8nK zu@R&FssDI1PH<%mPjQ*`5Krc5(Gd3jY1z^5!qsFARNZjkx}kfF8iHTZXmi~RrwZfw z&5FKy@Id}1g36~JL+iN7Dd?QWC7L@UkOGWTX`_>Q#qvigMZtcop+sMw5_S22Pk`?1 zf4F%O+A274F@jLV^dd@`*=Gq722l2h52a7w0+)5Wa47Gr2&ypq{a_j?$DpIJaw*-L z1Z7@{rS2T4!)FPsPE{qMC4SfP{^n70upiW@Yz1HkuI8QTu}*B8Z&t%t>c?fWO}p}C zlXrC!5%KGl`S{LxvZn}Of&D%|j*kL7#Q7lQ68cK46!pAS(LDvJ-y<*acqZT)uUpG&@%I!RJB;esRlr zN$ZewO+s!icbefa!&z2iODQQ(j?ND86AkFto{AY&ZA!B{_x>!fw5`8O{>yvuJ-w-J zKTjcdLGgY10|-?jJAXw}&|~l2HGpc#qyq1CRe@_0Nt_^)fBTrrEg^mZxJ zOys+?HVN+r8w7%;XpeI96E38YHR|oP~q|z|#Vk!Z~ntQ7>(u(y}jQf1$qem%6`^}DcJpj#}CpImx-H;<)MdkB>Z*5<4P&K zaQeyHAusD30U$wxIT^Fn?4s;+f#d6*q3CoJQU`+qABZc0ltg+-6wpL;qNPGO_*)wp zW)9D5ud`1hl|Odjg(`8AL;nEoop$m~fm1tP^B`XGpVtqRjYoI{eeJ~yEczn`w{Lji zohHsP_6u;1Vvd~Pzm}>(tONCZLJN3tQLIGRNW?7YP8_L*()L-jI0dIushN^$8JK(K zd-Bm5$x3pp*{Jt+Rwqqe1PUG-BiR!acne8@uQznqK`Q#LgOvSIrmh?j1S*F!A957v ze3%7;IBK`7mvBnP(^IYZYj%Ks{3Z*~u4kG&p~F!}nQ}EK74YW=4sH9~D)}#mRCeAK zax(DR=}&|TF2`Tv3XTN|JM#X5Ov9;~DXG63?MI|&7Nt9WKasLn{-URlJ#~L1}YlU^oi}UNAM|svFZ1kjxo%NK<=)R`}9!o9n@uEY0u^kXv60F~JMsvD0fau#qra;=! z3D4jHYwO`ry{W=_qn9kRH6ENQQH%4?jA(5Y4z9>DRXBTvSd?gB>@tAM{7yE`_u_-<&q0CB*oQL>Ywncj~ig$d$z9Xr) zycd^to4v&aoX8S!Cc>5{!`QVgsU$wJn?y1xr>M z3`;+-)aUV0WhQor%aQ$#lriZCo3=7;#ZghK?ZVlPR5E{-2lh}3%a#8owKTbNs=Ab0 zD?agOT(;^?ZoiYE80i-wxOZW*G}kWW&zkh5yf?A}9Q`Bwz4hwGo; z#QDX`k!JA=tdSoUhikfH=AlTqmzDi~y=KoXoZ354B_YH;ps`R!Ne$hCox;_mZ{xOq zoRJVezETdJv+9lzRa1}Q)?)N5BD5A#u{Gr)E+U&ir+Aw}7I~Y(6?vQd<;c9}f>um` zp|7yHbKsF6o{*5#NQOpVj6d1idL8%esO8Z$$#YS2oZuza>l$17A)t`J|NMo}_EyS0 zI$fj4npjl6w`6IPG~YlZ6C z=tK;~@O@n(K|MiZ5lcI#35DmkwuP5%hc`GDzf)NbpI2G^y8~pirw3&-PCab}V~l(k zy5NwSF}%VRN$a5(E>Upo}s;^()(!b6czDrXjkW#-vPwtKYZLNfeAP>!hrq zo<|Pqo2YU8qSNX-^X$+hB%}$3MbC-byo%+ckMq|Ty5I_cnXRojr!xFUvh>}e_{IW6 z*{v$VESOzp#gpieci86h5qL^3k9HeOz-yB(k}zFeten8|JX|hh-H|WAb=jGt*?~BU z@mFVr#~}RTszYfxfvjW^SG#^3vY(p}2k-j{9FJ4M;X|(72qt*09t4yb#fcsB&ao%Q zmse~FM~Tq%*u_~~j70)7Z&mqSMW=FyKMvSwK&*cW&$@n#tU}D%(OX4110`djoma}` zrDq;Vm58id8I9<{v~5SYw7%0a8{XPjP3+~8YS{s^SwvabE8ikGR7nD-B~D(xd9L8; zOA|@+Wo0{CGjqV!h15dvFVD3?q5)yK6FUuM3D6&9dq+44#X!{gr?skEYcdRViy?Zs z0wpU!+WZc^KMTWLGz++jnl9LGWGM6~eVAPcVOS;GMbT4vw<5x_pN_pgIS|2RKM{c7 zm5sQgyctrH$6dhCN4AO-n6F?1udB~(I1!Ax*gh39tP|tQHw~h_l_1AW`9?EDBjJqy zPS~Hu{#O^H{jymiW#m9G#30MTT{vd{!mcm74KX~`#<7>gfAlX%rM$&1?`!~XT; z`*&1%)_(m^j@dj(&lNU~nGVGG$M69);1W#Xt4J2#|Y>_94q zKY?2qSxm%r_y+d}=CAH#1nY&XWceSJ?{~SM1*&rEo^Oa%7$wZ}WaM8MmZ;fWO~H+P znh`{RG>ci&B~&%Lsm)hQj$2uM`+sPIwVdG?Te8(@qZsaQ+44ZC7HXkKLM3OSCGhBk z4<}JgG7TG`=sWst-%BIvR;?}zrs3$)sV6T8Ntj5go6CiGbd^N>*BcFc7Gh)UqRpJ& z&SwYA=T9{F`;eBr(*<+lU$-38glI4mqbl)LXkA)>dWPK!img0GnxRa47v8 zH)MM!FN9jG0>SS&VJ(5lbIp81xx!GmFGb*8)F7?T+K9ADB)_xQ0J(@2dZoCzm**^$ zcMnNrEXcGXxkFn`shxVu7fUJEaP|n0Igd4}mFu%-)3kev5zR!{WPz4ougCNyU!SgD zKl9`phmZuTn0ix0bP7q|%3htmQTMRKYJn2K|GS~2`Pe0QZk%MAwTQ1i+&aQ5YyB*u6>UJuCEm)=B4Rw!kmKWHwToHDa@*OI3D}V zL1o{0pJO*UD1n@=#WJtHP0{-sraxdQ{rd%N~u zFTjml;P)%v^y~wk=Rzvf0@cQ_SsH(|Jm zbAB)F?U0{G(@}?KQ@F>>%GM6Y1!+Y|n^6O99dj8fx|I?Bt)03Gx zs(AwWT$AuNMTLTY21REU7tTR%AnAfbS2j8YVYguu0WMi5`87^Rx+y7y24d<=DHjG1 z4nX^(DT?@o6kJ?hoH$>G69PO~O_Fdba=#^^C#b%P-~`^n(3DusIIAOiZENMzJjQ8Y z4O@B05aiN_Ihcl~{$3OI_@Xf+;@c>DHY$h_Z>C!;DDBHtNnPp;)o8eb;_Rzz#vS^& zh&{kg!n!n^N^a zz?96g|L0Qvn)5VXKr=4Fd6yeL`QJ2&MH|Z+T22_*c_{)1gm1{ZTyWPciy$F_1j<+; ziiCSPHKx4~Qlss`^4J~WrrZB;Cr=_3H}^>n1XwDfp|B5?t5V8tyl&wBlSEkz6y0pY zQwGIxEY^dS!*$2!f$v+`H#ci@4eIp^{e7xNzPIXUqnD z43ZVUrC6tD;`M@|8#gL z5zDEMuH57tt-V3_uc4c>u|Y{=z_x}1e*SfydbVyia&rTovvj$P3m9vR&9ke>gw|$j z@kG_{XI4F>HdCMrLI=9CK_$IwrDAsbVX-DQq^RO5Oi;-mqkyqE-J14x^4Ej< zf#32S%3&fNkU+gUP{Jc4MhAmH^yG_;1la|W`CMa@FDr3IQ|Eq8C_oNa$2Wq*4>0rT4mXOSzaWOGP*tH-K)l6LVE8>A#s^kU%Q!^tPl8s z-~4{RqT8Vg&GU#n8Vyv|9bTj&f3oYrQY(}KkP4C}f?LtOBl_8RSm0;&NaQ7^@}aPR zR!;G#W7}q?A~{(K-EiYy5l)gq^tD?(>+8N^$1Xngb4aaZokO*F7`gldR%)_dH*gK% zweB{Y$?D=5>? z?Vg0XZf7s){F6&F8c}V!A;j=p8{0>|H}5PZV^Ud{96RIM$FCQutlB}OM(G|M459w} zZ2oufidhAe*A~BU(Mo+;qfXaRh=hnvUOU6SLDVZM8D>tQLD#aT`eg)Ti%QKuYm_}pNgv~IiAF+Fe5k+A0 zFwD&7OYv2l)#o)uz-f;dv(6hL%}cdG(i!si`@G_1*sT>tcnrjuwNmyX(D5$GJLs*q zYF)D4|K-`t+eO;9XPeGBg|#ohGwXbs5;<_@xs?U3)wvPdMLlO@v^rt3hvgf{@E+Hq zSe2~f76A7RTR0X`lKBX?ZTnI3Td@~z`j%41Vb95xI+0#|e+|9P-m3LqmzHP5^9gEt zNm+*cL&55`FJMvg=%iFi{_@<-d?KSxX>9Q;_cyowS8ssgs%*jaWLfbBj#2}s%Yd3N z^&|wEmwYchyp9`|>O{llm3>HYe%2L5R@PTT0`&xi9Tu@X847Rb7&C@HYt-8kuIpB# zbOMN@?l6x2nd~U$#{7oIXlu8|1yn^fAaD59_A|eX z_ECVSJvcL8?IuJ0CmbET>?2z-tF8j=+NCP3>$hYtMdi2*^jzz)?Oo{O>SX#z3s)rC z5@6^JOzGnsHJ1Qytb+hu#BLZC3>Q*J?BE}TjHB<{IFpQ|yYJ9U0@2f6dTfz-4m^Yl z$}8K=Hs&2F4UvWs(ApNO%&N^12P}Hp7ErOX4>ln8SV&E1;D%7?B<0w$(2?60_t~ zm6CNZd5&)(nFlf>zc_z{?Cj|)DW;qJ=7^6lQ7chaK+gU2d@}t(qTwu;F+h2TASu)9rpKz4MUoy{*c~F)RsR96R_%m0TAEj^&W^O}2OmC_% z2zdhIAQ4_0S6+YF)xU!Kd~lPZUYtm;gMjV_p;CV1wcPkC1>drMgZzcIli z%c+f~oMV*k*Vj>hpS$%jI`ZUDL)G4Qp5a)pC-YNE@1>}uW-LZih`s>S7^)s!Ogg^r*iu>nn@oAq{x_e zI2rQz7PN%+X3UzYpH{rJSRDHbRdNw{hcM+%q0Q8ZOU`zqYlyx4`@wZn}DX^$nHYI`*2;dqF~^44LKw`ia>8FpM<(eQ2|PP%A!?RfBT zl^-SF)8)sv?jPIlFr!kqHxXLNtP~h_#O$8Ps&VL_jB+fN>Pyl~W*!cn*C>^=)s|mV zo@ub~%{xDPaaH}YoqeaRyK=TIME%E2@BCafqe<7FHr?(AbUaJkuA*$#bJ}HdMPP_wQ*{FXdg0U`%tfVSyA|W{#YD8ZL!J9kW*% zdHc?!5cmlf;IU-DLwnH9?dK1~p4ao;t3$mXaPX&@iA0C6)&`E|ZMdq_@eFW>M;cH$ za`0mF^>7n$?EP)gMLo0WTHax!N%CsIPu+Bp$=D2vl5&7+5syz4OlrYIUT%ym3WHu* zjMjf#SZ8q@r=)mJaiX_a6V6#p?`akfFhcz2_Pc?EK)OhFtp$khUw^Yugz408dBG?p zp_My*q#wpKn?~yI--?~Ez|Z8A4`&{0%$c38J6((g_yQR7dkc~U4MCG`w7WbdUAp4z zi4)s-GGEIK%%8~oS&r2=`t*dcgZJ66mA(ehOWQG;{+apG8lRm-tH_gc^inP|c?P4Y z>TR3f={u9#Wp{TF#5Y>S#Y!P+!+BPIw`+Ja{-i@O75h7eQ%z%3pB8a=3cak?%#z~m z!&^M8EboPo<;#`c(@a7){MJV++1t(G7Q8_Qb=ldkj8SikKwp^C@uX4+m|1wghWN7z z!w`*^0lNt;5!V&wMt^rM zq9I%d=B+7;R29P!#pvdhTi|PDY9i=axaLJ?rw(;7M zceNuQA<0A}xrq5}5wuuQ$p77W>qhapln^Vuu@V@l5Wx~#XY9Ki`quY`-?wQ)nIrlI z{@>Fpflmiv|E~DE7>m&Xas#F#g=qsyhvQGPllVjDQbud+*baeaR)XL4U-DbgOB~hK z5Ns7gQF*L8ilho-E>L=8SMe-SCZ=FQ#n;;q7iM2eH2^O*46Y7dZ?&4E$Dt4@|_Y+6>REwK{#GsnH|2_h`xwoO)n<@>(9nhmQkO;YDcPN7dMeyTi#tnZE|**D=C;i zdsD840??lgDTqi1H$h5EEvs>2Lw29uUKHj2?Wm-QVA$=_+oyE~&)eOFuBC(FUdwq! z_NQwf&Zo3XzL?7nyfwFvVBQE!cR^Mq)-J~)afnX(sk9gWG}kaJ9i4hCCS^HUNl43h zA*B}ms5R;ngI)nm-eZmVGeo-qE~}FO&~?v3(P3Oaj{wT%k7g3ggDH!&#E_tc@`c#t z`0@c{e>)eS>Wlp2xYIAga>-5DFhb^MgnPBqV5ksp3Y#GQ0sST!%g7z71<8D z(mJtq<7*8UK>EXsJgaPjv3yn24d@R+VrM+@>o@6;otIL;1%#5ba=HBmtIo};{#5iz z%t%!+9SEzI z6QQHbYPO|8mb0vd)OiPMacz&OIgj5u5vz32e-h;$)53A4U)ehY90$C`pJ&%wN=C~$IGC*kISCPJ> zjglyZ)q)7Z?GM=YCw~TB@)ML^L(d^pvrQZ&=ef6ska@W=KV99d7{wG(yY()7=~h26 zn2xTI&s;3s$8D+5U4NkZ<|*mY%`gNts)L6<6rrVoL_Z+?YCJ2NJRKqp8VmOQcw|%R)4!a;g=g&D)@}1?Lk@uUK7ZHS4yyTC5CsX#NT-py zLsRO^#iP$36i^u@`S!-GJvt`8eM8^5-EV@MLlzC1LSw8~LxJ%wljT^J{Q>kp|_SeAB(#J{lOXR=({S8pEcy&aG!>tv&HiORj$krHZ-=DSaq2cvH}$_R%K7J(Ab|M?}{J; zPzmLNgO$ZCMsFF>tp>vs>FWteMyyBMUWr!|>mR!~5Jig280J}Zzc-0lQM%sj(T6>+9^>RdaQU95GCK|1@&$NVxx`nB^m z3FGrmgYwIZU5=y`1v_cz6#GTkt^e%Ko&*7Le!}C}4ug=rcVv5FQZpV}+(BsW@!C;a zFoOEWjpox#(Ux?}8zIv-{IWyKauCdJ1EU01tnCY?K1@Ajn9aXwq1^l`q$rMytyWk# zruX(mmQ({Ny-e|+;K_59P|q<`{kVU3Cvy~Wcj4U^4sF0^<-{@Ex3{8%58%m=34(PVSl^e5b=Gy}WIQQzt{ z3sgJ4`8u*`KoAOB_tKx^xA6?{!d-u@)zjY$q(-f@&<3$Z)$E-5u6oe(Wj9o?&xIaG z@FMxl!b%fX-ZAnIl_iUCv-6)3lA=+^{`IryaFu!kc@+{CXG2-ZtHL@*!!adk8kygD z@jI?VUd)#FYzMamS0+KZ8;bceIj}F6>+QE@P6Fg6ejkb=@ut1%-(&>jiI>(z6Sd@nnbM=E#aBJW=h*;}N79K^`bKc=-G~u0bZ6aV zQU+DcAze64otc6_Z#EIV939ym<*K8k;Whjao4$F=V3r)3fyAFTx>eOvNPtKN3G0xh7I7-QcQ z7Qi^_LvTo?Ow7p6Z^wKUo`@``sz`1{YQ6RLIHkTL)!S#e`awcgwlVOswbhUyLZV#*`(TexqOXYt#NI4-^de(9RXR03SuFX zlQQGZJq*3C_TI%sKaXV6@rm2c=;sin=2J!=1RXphG6uBqV^%img|du8o)*$_81|59 zc{1ckrOre8#)2_-6})*!&SU<}v(L#veXto8Ve=TsbAb`q1#kKVxbreJe_S9W-^cWg z9P#?xM9FWL;0M8Or~;nWBG-0)VaGkebE4bidwkqA1c#OVc;RH)uQPuTW*@l37&aAu zW_Y`9r{8WD7eRJRKcNOQ`MuMuM>Xv&W zx{5$TGcE}Li+>6Z84Za97UlHw`?d30az;o7*J`fiR(L;Bia|9$su1-b(6(GqW9I6z z0eNYUqz)_Gk!tAxbaQ(X0!{$Rei@N7Wx2cDvuMDMAImE3H7;%9Xew^@)Jc z-j~gI90N{v~ce22JE5uz9h7SWftb2&iB_ zM6o8{y=!=#DcP_ODU!c$VNX3j!5WgVoR@igyFZEP>9uUb&8?6i=yks6ihL^OG2pAKeF-c zW?{=F$463@F$}`)^w?ewCyI|R9Y``4Bq37^la^+5v*Un8d!X+P2_h65-JX`f5|^(N z<|_^TxUohkY<#ct)qOM<(Zm;P@b?V2~rdo<5wN~Dq zJ$3-DbYk>M=d`j1a}7$EytJYBkcGsM0|x|&EJ#v%5wotUB5nkv{BjNqy&|G`ceSOZ zk2{#L(C+D}fl&w-pGr-XWqIDGIbOYzHZ!Lni&rDb37Oa4Yr4`4G$Gcso!;Yv)BSq6 z75d!_zb&93LhJEgz#_82sKnC_dQaH3b1f}ng!uib{LVT54|wVKOpK0AC^`%eqDB%I zgm32BB2Zt;7ttSpJJ-~S^4NM@{zS(pz2<7iJd-5&c#YwFdYg6Ov<~L_ z#_`|?oq9u6dN<|O*KTa@*5!>{b>glqADUEfhPFVIG3}_uC*@l%4vWk6Ea%xpS-iT+ zm8gm?e9087tw^jpdSXSqtlLcT7Kk5~Pfy_{9xIq)bbM}oxU2pQ+v+xTPd@ett%|HnCqo&7uvJwd0pqQPp@F9Lk? zgi(5tSL0?S9MYC5xwnirC_(m{=;o^T9HK%?8S%`1tKV}|j1`QFK)bP1MKyx=yPMLAdKqR&IgTv0L7o?T%eJ}2|!>& z1?=J|^|(gph$53>i74w%Hx1rLgr4Z*n2n}|th|%4B>-Eq$>RdU+gKkk2<@OXC0p~q zKYoK6@+fE4kE=rky_f&h7B+#V`S7n_MSt)r8ogxeawaH?$*!a=b@w}Cy*|i1)G9bD z9iig!MDHuUS6I99e@|5|5E{rEIuzTOdTUKhKGe4{O!hY4w9A8a{;pGrrmtOY!i*4c zJf_LGp?k$8?iA|wJcm~irR9y>=elcW`U&eDqa z3`{nz6<&<66Fh%_s+PMO)>PoXPCn$ilm7=|K%Kw-3SVfrHI2Kv@!4g?JG(5s;1la?m=L_ zAVc%fRdu}hp=fR{lfn6P6jS#e2J_+veemCxdlBMx*fUlHtu=8_u3iTfS?i#wI0y!s zVi?Bv_vgT;hYR3?{aEymD&Qb0=#l=Z5Ne}8{suTjvlGT^x)1k|qnE)61anVE1oSs- zhCU1elA@2l4#f8jf7$S_6riW+^5Utm#UCpa2kB(=< z5w0WeUL|i?S87dCU%4;ej)nF=YFFEFec6L0lL)1^Hhl1PK4^BlP$|uM#@0RDTKcZQ zq%17ivWQc7B63Hqp-h=wLWay)K$GL}Z8SNhG`~$2x|r)py3+&Y{hO*Jr*un(jUF3# z{PgA{19jQv&|KsMXOC3E<&XNOsAE5T_F*r4`rdYY?4Jbk6I93l`JxB9JLTZyun1mx z@i*}F(~rZio_v%W)8uw(Sa1HEmte&beaM%u!rCthE}tlZi$^d$4pDkX=!T?dj1u6) zXcE_^r^Zv^#8@gE8%df3^GIJT0vQiSdhy@A_#FP+iNORIYu^gpbsMHYi9lj{A8f>0 zv0)wWaWte$&r{PhZ9-7H3RCzd*ttCt4h*Hiv7I_&ll4HMq!iv?xq_kW$fb=9#QO0@ zD7`Twcq5qJP?>Ibw+{TCpd`L%5gASVrM;dZ4BX5C@+6PVD_M5 z4za=c;7l8Q@o@)~6-L5ikA5G%^KCtN@`)ev@ZB@NeH@ z@W>3xO_=8U;!r^) z3?c!ki(~y#*ta7JdbO!r7Yre);58s3pdyT`7{>9|AlOH0J)XmchMe}j!gxDS9-GL7 zle=?a7?qi!%n>(N=*V$~z6zfkp~RpRUZ-|MwKVx!pj1nZ7)s)fpiGaq9L|WddsN6t z^?pJbXMRE@F@fk!3pDhi{A3f&EtL{edsBfcG#7e83#I@H`Zq-ry1&K(7Y4Q=*LD)h zU3jrCe>}kE=4uP5kK+qW+vK)CI=2HpKHC5Xb{4_!o_zv-_=9ir@ZKMudxEE@>Ebz_ zs`hh!o`&;i6y>qUz6akTfPeWY#DzG+2M5WGro%*cDvWo_VWdkAqg@2398T|)!-;V@ zDro|PITc8qS{~|&;}lL{_B9}!b)ph-jfn73dgc^O|E}@J+7e9;nJk3x_@2TkEg$v6 zffDP!75diNfvqXw;HvyTyl>!gWvJ>$xJ*PX)mded_x2=O)6{`(-wyc!mh>asO z6(XnzWpklBrfaWBNE`B9VE>2`q3gs{y#ozS@722UgD!-U%*}2%cdQOBf7lMWnL+TQ zNB->w7;hHOJ^M3ptohlW^YpPFeE;8Iou4J_?pMK{K^2T4j0EN=g1M(3p`1vAlM@OC z^C+s~;hwk~prqiXvo-5cB?Do1H-d_v;lN-T?Ak$-e?u@rVRu&y!kEg9@Z)<59te!L z3+2h(dAy2~l(H?$0n;~TvvgY?z3Hu(4oV+}mHL$>3bVUe4}RBB(tDMsn`>xiwy<^c zu24;h9R63Ka^)*Ro_yJCz72A;0pE5aXw__}R86*nZt;e?I?{4VOsy+vlOtPJ%jO~% zIDN1TKDmHOc)k-pzOaq!#*6Q5gNtWdQ6={vsKapfXf+y}Ht_W_#nk;>p16BEP)-d} zP)h4hJ^2&JOkRWaTrTYFQ^0tSoWZ1^G=2uv@)Ux36wS@`P>LyB20J^pLSJJL92`-> zaT;BY?_XOp51`kml>`&s&3tH)(lo|HCFH&nZ)|KVnH$W+F zDdWp^WnPlW)OA=797;0MRT&nVVE@^$Wz8Jsk2uU%u9zjL6Wz?|HWM^Go#wc0Sv`mM z7pEV(`^LG|WKinv#J|W7PPd>U4s$z*8swMp@#6Vb`1D*m{O$671hog1vKn942*w6~ zg`fTO`wZBfLP=xKPhbuD%8O4!Vw5i&#*4XoAYFtKW2`$3CVJ&?Xjm?)<#6J(P#*1# zf9%Hyb*bcZ$C4MT*|9{=qqu1lW=%z?_K z(46ZESyyNJqPkt>k+6NhENCPRXF9at6u?!2ho)D#NGhq;hq?D@yV=& zN5&r>csGObD}mGosU_W@pd%M+dsNA0ty=EDQfj)olAzpCvIKGy%EuhpJoF^p`JReScGqA1Mqk-5Cl^jo4z&frhiBV4ZGUvG z13vu#-S!21JXM3WQ`VD|~de33d)d4N~Q6(zl za;T@>q>>_zy-W3bV`K5l?;h7WF|PS z74U!SRrm!xN#BeOS|IN9f5%W>+pBEMbHG~C9qMUo<(3e5|6~h%imFHoIhnc<%FjO@ z;a1SO+NQ614ly?H<*T4B&5WTWrR>OanN}$&=nfVwxh{O4 zblSD#VL?eZ9;JaA>W$MTYUIE(6CA<>`X7BkPtrH(MfBpv+j=9bYi0m7VSzo>c{dlh zLXFxSc4B(`^5QT%?hj74q1o9EpCObNF@1mW$r#p}yWsqZMl?b#piWy4cINNEv(%4# z>L*{B!s+|J{?()K^sj#mZg%q_aE%?98@!S3^#Te^MYheQT$)b(fG*T3m%P_vTRcvw|*x{6uqh;SXuZkvWrw zE}DcgQ)-G1+(d>@+C@%1H^8ycR+?y24U-Ls&HTRd|BxN-cOdYX$x0 zsUPzwkEO|5u+iTZmU|Y!YvubhCCaf276<^ zI}CS3V?DVY{`<>a+&sTlO6_J#YfZN%pgp#aH zP=n7O7|w*9-63urnF@S`+wdQq6IMa)Kijl{^J@4P@XSPdw}>T?N~V{h+sc zJ%e{lH%v%Mi2xFa^!&l$bcS^Qpj?C%zdk&s{@N5$7c|s(U?7?I7HU-4PP*@oJlAQB z4!4R*MZ-g1mnp2ECd=|+sO1BI@*2Zq#j6$@3!KE764SXj%t-0m2!=Z&5ynzDeYgfb z`=A@XxH!y>^G{FL!e?h25X@Tm&ri2QS$-JTg`|{gmYKscPa}Be&FA6e7k&*d{Nb0A z&GA1x_j7phk59rIe|i>PfAtwKT<~WISYZivma`!`)DsSlsu4gLbk@bfo&gmMbR@!P zS1Q++x|(vLMjC-PC?_;t!7Ad zV1AuIicsdku5HoKfN(Wuh%F&@-v#c|DrHBmD|D53PBzkO5oR(wUn`XNQd3SssjV$J zxSq6RAe#zZ@O$ns*cO4#dI$XHr7>|XrFMAn5(4?*u_`!!umt|+v+ap38c^nqOE zI;bt)3d2}C_9B=BBE|0h^c$QvrS!2K$uQCp2mN({3}kQBI({5&*$T(;0^icua9x=_ z1<>sK3}l8jboF8u*Ni8qNj@>%&!|}`2WrK*k-kbZj(Og|ZYcn~OtufD$?}gJ+?8~D=6Wvkbj38~({PHJ*aQVX> zyxigR-U9gWcs2ab=Uq^cu?d_IE*+FqUy=eEFa8U>@cUoDpAf)Bb6;a+BqQ|aSANSX z`q$TghpBrm1O_<3_J%kf@7^<*&Z(N<>}i%lbzunfw)zvkL@5dF>!Ow}Gx&q^r*NvZt$5gi%}CzT`>)j>&P;oI@ww5jUDfbtgZ`>y+x znrnNM77Qn~gIbE0^5hJHlxCa{wS~eb=Q~j)2Y5v1^NU09@i{a*c%nZ#Q41fPtYs+s z+ET#9b}rYH1f>|J45zC zw!%PrB6KxLSUFp&BB7xyjNLcYlRNv;Ss|%zWFV^o7)TwIU1h6aCz`X9dkT4&@_xJP zV-vT6QN@p>hy>w#htLonEEQqIcptw$kB_93*FcFscY1F=?AE64rYZGgTej16=EjF1 z)qP(FB`Yb`m*omtzVcwr&Mo_tw;Iv8y`3?{gD0omg6c>$WdrYvZqBK^J{SK=>o>rs z=iA`&M?DPWrBC|d^)bN`GH7PUH0NC9aFP)}#hG z0qEnZ4;IF6!Cx??&wux2h~DB3XAty`+8BoNz(^*7p2*612u%o0%$UI1k-+S$-8h-9 zua7Q#egM-sFKykGdtE!|UV#)>A5I745t`|P?)m(_qDc^^1_62DKq;ngGF_@0Ry3YM z_3SFu_9wL}Nwb7RljC#kl(igZbmunwn(h*>$<*Fg<^vT<@dgPGa+TvPM|AFC?3b~j#s7*Dy#&x%<9VZ*)BA8g~<8w4Col9jDp4&dg+GX$es!~iYNyT; ziZSGci#$u{?&njo(Ya>ayD`^J+YWM`GAYB7*XNwsUx2FE0GB@MMWyWH`0}$6RL^a2 zcvJ=FuztFHz8U`hQ9E2XS`ACw^x<6u^R<_s=04>wfB6Xf{AWMlO;R6!{QL0BUp&H- zFy8vh@4(LDT^`A?GJOXDwT8U|>D&tHZB5`bKC~?b#xZRVHEqTEX_>Z;yjnx{;A0;e zoY#3ES^^~$$VdbsAw)(JD3dbQ)DJnx5G? z<2}c%_k6$iS5*{HphBY8z25%go(fd`sQUdr^}XTU_nsZfX-^^iE`4t#CP-vd*IaMY zKDt%UZvCDOAfb|oCDDX|7Bz0dFlF7U>)y=j2PomD0V!Ywkln@T9`c)#l>^E_iYseO zOS;UXm2KWof-~^O{gkOz{gf}#d2YKYW20IQG7T|Kj*F|e?_M^tn?@<B^51Zr_a@TUqkz*@frHv#l|7(nHxj4>e z_)S5H<2LBlaXdMr^ZbS8=*$78tyzujW$Q_~vbhQ4w*(*eRFXn|`C*^Df2E3Rm4Ffn z&+nde%Rhe7D~oc%xn>z2xR%VB;Y7>`Lwvczf0gucUry?{SibYt-?FR!@Bj4&+~E5k z|HGe2U$5ozWM`WE|d6L#_%d6N-U`P?~nzCxRV5r{vq>hLY z=K|8KlE}noQ%TIr0o>SMvc15W$GXj~o1r_pouRbOwqb}R@tm!KI<|R6k7Z51^L!eA z=RHTKX0|yxFEX2PzRkYqK1WaGmmgCc`M6zv@qQPnX1n}|V0m&&b=zOO*TMGH2VL^- zKR+((OAksn=hu1oFgRc}XWVMmy@sA!M_KrKxtn6i7)o?v!d&@2GV(1HS1y0^Z{=V7 zr|{ z@<)ID=kkpuKbC78Uz7UGAo>240{P&Q-li*`%hZw$=FXH8*i~F>{K%w!h##K^Bxl@| z;Jim!wRI7FveUeT7q_~2?67RjU1iC*%_@14T=P&xnP`;9?4>ukmsn)m$zP@bTZ^XcP0N_Kj+4l!GYIr1PwsCHJo?gn9 z+Y8pq9*P&u(DjyjQdT`#dpu@Tv++rACnuGisPdhXp>vOAbCB$<^yN*N)G5^q59O@$ zzKuElN~zX61uL%2bA^m~KuG~}wYz*YQ76ALb)0|x$PD3BAo2K1Qqn(u*di~VN|*a0 zHjx`&&Y89?I8{K`@HqJnKh6L%^YKzV^8ZIOKf~vU$9X&m#kk|M6y>bY$B zca(j9@u-awk+x|tPC|*;^69+>`Ng}H^7e4H)Fk@L&8{k#s99PAxs#MK(07$u*WBqW z_l0ec`@=U-Jh+~ea~-*Qb+{6)9q{h(g|e=b(P0~9EX9xM2fbuo;$~Tpv_YmP<}wgmH!^NBtTTaB0q!xH3g}i)qB#S%yx&f-^MXNX zYb~LI$Doz$TOW$Tna*i@8=I&ZA1CEi*KLa zBL%rhih+d##`2^l!C$&>c#9!KoCzSIih%?fK+HJkA#>t9S>ZAcxXbkYWCz@2>V7ww zu*XI2CnbytSugeJezG)sE4|*G0Q8i(@m~DC>{w5N(Nks}@|3B2T?oh>1fW(v950Z! z&J&Qss&ScUmQ48GB^9JcSIVZ;7w1l7)S@`t@LU=8)C31ZiZ$|5U%U!rvHO&Ga*$l{ zd2+vi3~t=4hGy%-hV=OahR#K;X&Tj)*{t2S8OkcHvux0>TSzEpuDX`$ICa%EY&&L5 z^Y~#=*2edXD6XVTh#NDia@X>rsq@$%^trPdd#XL-<%yhC!dj3-HJ~`YGBXda+<9BV6$$Q;o3R&7dcZL$LI}o~B))ofK_ph4V_OKaB z4ZHArqk7v)Ii_}ge=y51;ap%sS?z+-G%k)T9*w-#P;vrOv6)A0kht`gdQAZmaU_65 zeX=HXjVbZ5t(51mFFM7es8?bw$%Rx(^ZJy#b}ZpCq`L08)%ui`sqRWq_U1n+*0}VV z_G5g8O&w88w__#WdAabG;??rd+>6q{e{KFAZp%#9b5D(J@u~S=T(@znqD- zW6g8B!E7mZk^|%xFdyf4ANHEma?)*oPI2ZZcd98CtB{Yc71M<(k)PhLCl_jv6GK_# z$_wPv+eUe|BTi->bd#9}Tp74hjlVpi50tg}+vu}~$ie1aa)1Cl+qR2TF`7W!%|M~XuZfW(-B{KymO&iuNu>Xm&;JWCB<#+JX~Fw(qbZ)8V>aQGf_Q`7 zP$J7VhX8XWP&|4=Ka3uurO$R39K7+G=Q&;m%bMp4usej^P$CP*64STF^zsi+2PfV4 zV{+Y4$-8x=jFqg8kFOTf#j27Y(S6)PwQgeCE8RZ1UPJ)J%6c7TmJQ)#5%MGf1{FNf z7%BT|!(=a6e_a?or^0ufUW4ahCmVMWh==73ECEPy*PbMFNDJSu$D_e z4C5u^bCa9ty^tu^AXc;&N@U=ZwawY4&zAD&4VrkZM{k~I0J5KeY|?I!rH36YZru7c zSUM0YI=#&eZoG}&|D0B~h20%u0rt_ES9z$p*ioKp3zwf`$k9|Z|B~*1_Mn**vYdeg zgdb9p^D(LBlN)7x`@!{6b*m0je22xU5Z|5eIw(&!M>BN&b>XangH3znaMvL@(sh_s z^W@P8a@l+2P{)2bK9I!65)wA-001BWNklO{pagJ8Y>E%8kZlDnteDm{OI2ZAxs|Q2+*@tl zFsUV=Y^AvKIrDbrwT})GC-Xi!;7y;g`VY0%`{>NI;~3U=>92F0=X{;#$Cj|6r<@*HX!xjcowQ7zlar*&52}qybt0gtlu^UI6hm07i39wMExq2#9ULHu6 zSLhay;dzvEZqw_AyY|UzSZ^H5|BV$efB^fJ$vp#ZJm#z#U|QP?%#%?w@NzB*apC;t z++*7)nyU>L#dFS5M8D*zx**w>zt%1w%`O{2>i7Oe)}^nt3FP#+6C*w5DHF)T+sVyi zxN#wp9Nuv()k%g~?Ko!4al2-)-f?WUy%{kkNtqGGz}4oA1==tahHmtMrXcz8-Fo@e z2i@$xp_ov_k8V|3pahIh%uwE~<-cL=AAmVoqR}#(?_SJj;Bb4_1RV?@VgdY1{c-#} zgJ^#5g?wIg0EYWmTy=f`7EiittA-06=e}5^#+TQsxKn&KAfL?9l4c#JswJQV6-WT| z94HYlV&T;0+zqlYW;wfUJXYh#+26;F8YZ2K6^9HwTtbZoeRH< zEvqH3#zty6l1(nWR~=>~`}F=%QWrI*Q~@P7K2gq)!agOnd`d|WyKb}64d*=9po~}u z|Hj!&)m`H`3zP+9g?0}gPDw__)yZ5)Y75IO@>q$NI!n>$K4(0Mc(J$CL)K@kV}&f-yIj_$t&>NyDW=ccDEmr1=L8zb zj(JQRLzSoM{bgsl%WT^)&ubUk){+e(I`}!e*xK6f8QIMH3{La|-C z{4JU@IJWHM5|Qe*tJEy~b`_U~%uwRF&+b;s&+pa9_bCy=wr~?xapqi5P7GzrYlEpO zl%%f6sPA$jqgTs3WAkXT7%S#3@h1}it#)#}`ljltr;lHMIAI&ek2%4qGtEq#1DezOfTMcBIG7^!nUwN}1NDTc5U4<|VEak=tLBsr#3+-Q)_M^X z8g3zbK=I}8KR!+wc$*18HLm3B`&N|&N)^m1`6<~41m&Y~y?k_8PvB|QVZi6u1SuuQ zq8Ig?#N2~In(oghUc^kok8d^FUGlM&ZKGQV$GLyGWcD%;g#buyuANL}x81CDBKPVd zhgzb@wT4*AIRFxFxv$)lp({DGQW{J!n$589H++j}5Lo5c7LZ7Ec-7w-?QGj+A3auw(JwAD7o-Y zZ&$Kne(`Rtd`NKKu!=KnswLb#sVdhTd3;*E=tfOD zqc~iXtmY+HFlqxRhngbzGhZ7_S6w+T%i_+;$L^XE8NfFkNHdhEQ370Z!5v8rMt7jB zT^5zJ(PP;#t5VtxWlyyW;!=U$iv8hl^7I>UR+_Upq$F#LQj+Dm2pob(9%gJZY*)I< z!xq)&`6}gk+x}9YvHmGWcQOyXBSQTbOGBC?QuzhT!g(RpmLRSRKBD;Wd`mc~K@!K8 zSjq*lqggF&*;D%4Q-Tuq8L8&SH_I8kn?tr;IbzQnrmUUIPNcpMNulmsBsj692v2(` zwaaxQOMX!x&9f0vZ@hESzzGgAYPB`3c*c@`$hglmhEj~TgT6za{ODebym3auBRMFX z11h*{3>TKiJ8?o&d|)P!c&;>drEDwM#EA^X!v`>r$~5ZJSLV%&74XhvGhFvJ-Y}}S zW`BfkW`99Pc>5cH&s9(xMM=s4t9A ziZmYe=x@tUa{L-q_ryJKTd#-xQcxnEY%5(S$NKim_bwac<0}Sv{X{&Slx_0fg*;LM z9lPYu?l)NC%b8$KLiq^+i8%9v8zmfHTF00-&5y}t->1y{V~S5dzgNv5LusE-67_&! zxN|Ot{;y$HFz=12%8HB{iOl?m5gWLJoJVaq(^iY?01pfN-z4B3n6|OAh7y}s-8hrQ z;6Ay1R9-c?aVRH%ggV0I0z4!$s6$Q%5{9@EF1<;+o?N#VcMlCzZLuup!qX)%kLSqD zF7&gerr-V=4{IprJ9ClQNs3t}F@&9$==e4GD$4e#uOr@?h&%9SW>GmpfhDZ5V5`!Q z4Ff3DfA#OM!`{0gq8Ud1qKdQ0jSK8#K_KYYt~O zKGw5ejiq$GjFU9bbGjfez<`=IgF<>AOkPFm1QYBfGe zgVtve_%pMp9BbUo)^lXH(%Z7vpW`%r6>h%aN{IX7t_Z}oL!{mj|J#x4;icUzFywZP|5`qfK%A4dTcc-c*$Cd=! zM>k42@lioj!_Irq3l@BMdzyJmUs<}#lAeAP+da97Bim4};UUFy! zXVm8@hPS1Lr|bOW=~^EZ#>PONIx$%1t9Gr^-#c{PvL%<2oHCaeol46I&VlfySXY3v zZg5qS?Uh`T?oNr#4(869L*-=0A&U)m9#&3t9aQqNT$M^)@Q({poPLL1H%jB5g>PHT zb8``}qETc1Eo~cFB?pddmG55Al3UeVphOJ$fK>BhcMPdygdD}@n*`@D*+^R?E9jl` zS==%7@tw(-5(&=pptHvSB~Urx3rdZ@p%=7X ze)X_Xe*SKayg#ay=jg}PUA2`gKpv~!M(PnDM~}skOE)|hNQ~%U1&qsL`DrTPt|PZ!nlE4IbusBH@fI$PhaNm*Dysn8~D?#%#BK4 z$9_{Rx!+<+q1Me}dab*n%W+lGlbw|OEDwcfuE69TEIx#`^@9=IIrI{0iH+!2%d>4E zmW~{YT9Q)T7)+Aq2}-Es#m;DXiZV|WjYr#eQ!Et1aptG*Hn8iqfO4m5LBK@f12=Bp z>(!E!S_LrQH^-?p^0yC;@bkBZGUQ-Or0gvBqx0oUE;~^6R0hcY`klOgO9TEG41imEN+KRI`#)(y&i;-36qc^%>3tenv>QWO-sjiNau9kn7N7v)#njgis>%= zSdAwoI+>j4n4tuu4=#~fcExZ^IYzf51m{4*PI;yY+Z1C@#3=5wL}vcK=lzw>FSmnLi~rQM@7u;!70H-yJKq zuV_z-iYy&&`~!+rQ9S2)-o=Z5?4FWVx|Q=-B#V}p79$skt8i@k^B z^|RS*lR&BlP$ww%#d0=BGBz@hZ3JeU-hEyzYTyGiV!;cz$u-52dd1~R3=x^W8F{QOB{jK|E*g)q&rK=`7VTdo`&Y_l2o8xsmcxdu>?={J<9yPK; zJvC+B_PgJiW`4i<7@wkqbg$L|-){-NuO1%ZI1!KW8vLkkoJpf_WH-f$+gTZVs*zRi zU>K2Ivr-bEWBrK?6xS!mOJ-S#_xba4KA_}DU0uzE7e-Z8(v?SX@^>v|AdeZn=vbQ;AhFJ=h)^?)jFiC2dLwjkE z(s3k4DM)c1Ku1SGvJ+E}j^U0nwd-R(YS~uiEV`;a*Uq^AI2Y37+!`6nqMw#~-Uiso&6S?j7D0$^nn!J84 zmmy?TJfmUv%x>BSkg$n!IdZHofmGOwVoCQ&D0QB8L8+>w3uoOFOA57EzEWbDzk+n< zv!!Vs|Eww3UpYpuyN?9WCX`M3?K7;UD3H5{=*^3aS}!F(!_Co{<@`04YfC@y8xi97 zs?<@yI-j9OeAw2~b>d80h_b%kHN+R<{F&&TsOe8>kq?j$n&Fm&m zQtSq|d2d9^HA^+oXdu8E7*afj>V0bJF#60iUJkd5gygrRdUy`tVaW3qB~zCv6UDrp zq4qs;usM?BL#SSN73Kg@(o*J27s%gIYc#7Ote0GNHzhN@bbq8bPQH6lT_*bGc{O&s zdOBT>pG={PuVq(mZL_z|shvU?c07GFl-#LW{z0v>JoX)#8}T~1SAPn7!YHu`k(Z9g z6NtH-xL9JvSs-;t~5La!b!HWjE4w3tgu?=NaiknTVPV-P2GB-P9$2&P76EDhi=|Op@AjRqH{b6tN zYFS5%*4sVG(^0-bU;_HZ-n~5kW;P(L!9<*S?L@pBMe;)`i9`o+Ccqp#8cvXGw?Kjz zZh+#>p^iP=JBdskeU~3xH*!4_<)e@27KzAv*ixCGzdo2ss&JU$J3*N(VnC=KTrN}& zDhQX1=Wu+e9~7~#ekzCzBR!8&Pd~-K=ezdHE95F~jpQ?oaGO^K(^U6KfZ+BzxlwbI z2S#_W%?O$=CgD^~b@ipZ*Z5##2&tEoY3B!bS$gO{EIg zb~*I6?6nCbLzxn{Vor-Yyljy%#YHLC`oC6?g6zqO*M12LSj@|A9IJM*ODzEqWi@@os*h&xu@MD02 zVLYhmNs39Kc*h%p86r+T@Ol78)@6e37-z~mrU3UmLmY|!ceZU0DPabyjajA4AWpsb z8EQg70N~bKAH?%Fhx09q@bjv@%A;CudFe#5yfa!r z$AJoGncOsvnK7#Y+_|6}?TIBIDf70DiG39V~l$b4^v~B88BrE5od{)l$t&v<_Dm)!F>a09P`4l!>o!o=13x@+e~0p6~+5;EI@mM6mOKW<>OVp^dsEG zbC95_<0sQt4JXd$OupX))9YV+5MU4cvFp~yEe8u$#n7&RW%#o1dHWyGyi>hgV+mb!4IsXBO8 zo0V2;>u=wuG-&;mTI25jRG7Brw@B^j*T6|n?5?*ssO_$FnRBEEP_|J_d9EW&AZwkT zdE2L)Iv$dQYsPRS;6xGxIFS&YBlmu~DU8cU$4OaE90_Ld=Bs=*jotMQh8qeC7<&my zDDh}tJYV$dcEb34STjuOslo*ip@0*^`3x4+7(l|VTEEW(HC}TE^NlWJMrM(`M~3_N zE@9$#HU>W;d*6wcm`X0ID#~79W}czRkU%Xj(320H>S!w<12K zSy8Q(_Bz2jP_@%;C{5R!uYW<(N|b=G_%1rkcnyL2l7P@g11X?!heM5Vt74DY3FTg; zuPNH0HqA{b*KBbxWVxNe2Y@^uptK(q``UtabB^?=G37>aiGp!^W6HS+?8arlxV=&sIsAe~e z&vAE*5~a&VRUK^rX>B)$b7vm&jHOA~hvm44|M|RV}H#^kj`YT1*K1=jzYyJ-MjAb`4 zmxp2q(P>&I#J8_9VIFs~Z8=4mb&*|Kxw;!lt(@2d`cE|hFFDA4be z=Q{Svv4JE`B%n|?c#@h5A!oJ=l=NPLdlqdGR#Ef+r*MvIM`T=?nqDM=2B{A~r^ zYK_taC0l`qO(=1_;kM&;EfwM0mdNb56;hw>B#YA5{+%(~_qFP5AEiO#?SPiFzBw}P zDcQF@FESg3ggPoh9m-PO6@7+lJjO9~sZJtw|8lX@?^U5W{0`SHO#|`MpmY{}^*-Y6 zNET<+3jrnKQY1jvPbG56$gGglRTJ^%X=L7ZK-o(%V_(eTz! z4k!nX_=|T(wNtu-7Q&6+K9?m2$jzQP8Y;)SV{C$n!aM4GC~nUVCAxV0*?mJL)v`{; zw+2RY@g1vum=hVhmC*`FR!LID8Ho-ubNbGG<$II|{eR7Hg%<^%U(ocfrGqm7B~9 zpNR&&GcQ;X6a2c&OLF{LU1xI5@Ac^}e^ZygRcS2=`W|?hZ8&j3pzLjmrhl+kIn$S@ z6y$7j(5Gxv%JTv?^L!pPz7&``u|Mohft3)rx$g*RaxD|83ltC4*@FwdHvbly1EA3a>3i z*EMzDG8w*gG1f6f94T=9i?TcmYqI^7>SQ-G>&`otd?_eVP%kdO-L7J!XEzTQJ{T)lQ1^`t{Muj&x!wRS<5*lV zvTcep(ZGNV{CsN^x4)S|w9dBC;4x4iL>YIC+W5-j#@`+oVQ{sUnD8ZF#QQPA!-aOP zDyT!9r;kQ4faJEPzzCJ(#{?x3oBlc%k(aPShWIb$>x}D-=3RZJ^J#;|`_JmMzDm7j zvjduX`rG~iq2x@wWgo{16vR5TR)#92Ia_{In!D)(UCIjHV=p%9ExFV8*8+w0!KN+Z z`l(o65n~~Nd}u19Uml2Or373vYLBdhX0rm)bRW$Vh@;G-B;Ts|@l?7xFB~hvS+eYx zizCe{iG=CKS&f)DuN7xo_Aq=c+DRa%D`i)ao9M51K|x%grx%rufllKak_L9#58x)Oa~v(DxEI&*ccLbljHDNzJT4w$tBw zOS}XsE2NOf8Tk6@(;Q`C*82Zlp5ym=Q`QzGCvo-nnrbN9enEv?P$)a9BOEFcHaaxq zY*R|}wz}n~uKGVbJvYgbz+5VKZ27t9u8R;K-!zJc-Og>(Uo+pwwv1GZ z|Nqs&bjy8mvEu#}qab%H2q-r5ecaB+2u8Xs7Pm(W1jUg8P!gQteCIyVoU;L$wrHCL zNLaUSvoI!XKyh3ENL*JA)i707>+4ys-KMrVCa!iMD3xysl;KKMiibmOy2m1{cwp3c z@5)bc;wibgi7TH)2QQJ4+kY+k8~2EhZWN10mkSssz&IU9dmfvgGv9wSUVu`Q7;4+Y zSu|7-$&0lrn(q(P1&M*WodRH9=s6@NNG$-tY(Ompbh8_N2wCFhaGtnxQ71^T1-Vwf zPuVrYWDTS>l(RuP9bk*m;~PUc{C_Dv6&HFBiFTcbDTeg2sgkg+LIN^=y@=fQnurd5 zUFMpA#Pv05yrOHfeU#dCPltvqA3&*m%b;{uu*P6LSq)Zi(|8^(OkE}Ol2-6GXS)J_ zNy!fsGfI4VyHvb?Rc{l@7g9^I_bwNTk*@vXSmjm<#sf{tI0+>n?5*0$3-ez-l`1YA zi&ddGn`KiC?X~%Ud6nT;4T^Y!{`YMF2%rc;3uFxBi!0+yK#Rqlu|fv^8vO`PHiW`u z3(B--YLVB{000v=NklmS` z1x#!8n?IC!;=L<{;_k3UoM;G|;==oDu#8;*1KCsICx^QZk}~BA1o`&3>V+?Zc1xixN>}+#qtG*5-S?C zSB5F&@tZ7DDeBWa9SY;uDhY8b=!1G>>(Ez6zp%U{r)T>@mdKqxKNHPmA>xx;r37Y? z-L8Dj=g5UW8ZQ)>CIJZ5n&mcD$q{nXBRvP{Z~6aFMxjK-zNcZg(ptI`SvXretSZbQ zH)o@gm*lLJ=XhrqaDN?EZ(a3lf9PVl-TNn^GT)yRPA5K~MCJZiF1uk{G2?7tTB|IM zfgVdJWFO|rkh=zy7kcANv04sY6u4{qVX_6KJ#nHXX(2;mffx>o#df)PimSF2FWQO^ z?E!H*keu95!gOy;FCI@+iJ|s=qA`7)XfN=x0i=iMpyxXZJVn-l6>_KluLPeDz)CZ*>P)>?2NSl(c$<@y6JBWO3X+DnA4}}p6yFgYRa}b?sV|pay zCo`B*m%94dp~yGnR*(NBvBT zUTimW_l)=Pj_1Ra(q)84~a+H`o*;J3Z6!B4<3MA)z|WTdB+SQF>eV zI$!|h*gS_1|KXsV#n7x-QS7JWC9F{zvo-0t-g8Nd4-F*cAQ zKD$$9Nq}rA>bH!+1yg-K!Vo2`xsqm-x@cE&7G@VAem|4DcPtji8>7W1H%r7v6U8&saC)}=a4er=HMnFb6J^z+yWGcA4%)^` zyje6ZEEt3|x$j7le+74xMCS=K4RBX=GH?slcl z7`nuex$Xh?Vx~Af(34%6e^pg6meD>h1t2 zU$n{M+X^L_w^Etp^1ZIgP^I(8?*CGhwebwb#!-w#0u)DaOn~Q4WWt^mV)$5sczUZu zJiby$U``gvACN*`Ihll2COCP)?TLo%;!G=65la{6Ngd9#M!%FoK2Isl&oPjcP2ay< zY*pTw4@3(KC^GE_*UH4jH;AZWc9}8)badG7+-%*W93+$3<}` z%CfQMkMmGo^7K2t}bH@R$Aiz=F7L^6X6 zyC8OzBv&oMx4+8n8pDQ$bajTRA;V2ypSShTN)0|rcEYMfjhZbExNiU7L)lsy%-uy; z4z4=e4-5bM>eE+$qT}&YC#g+XDfWdfk=s0fER**-iF4htJUoe-=EdH9;&jt?ajq>? z47G+3kf8)<1cPaLOknc1cjUg!Z7(2F03=@dw2t)ul-OjazFtp>xV;M}idBAveFpX~! zk6u5MVFEPEZWymx6~XNphh7K>?NLc2HP_B&iDP8}0v-5g+xLnKJ#k{ZKaq!C8!5@r z?0HA-+OdRpXuyV@YTKC;Px8{IZyGMk@%&%74rQsXfYPD9a)*)=|4$Q2r6|$a!JzX} zj#Wh}&*xzpz-l%X$?mDz4`q$5&XJsu-2_Uuw2 zv;n6IW{4Q=*(=8S<49o-l6#L57mn?xBL#xHOQ=-|6;PUs4!9mr%75ZeuIH1{T@|sYI@wjJ)%hxA*_+=mWV!Vj z(pR%P~=T|Z|a=EP6d5MwsNO`$GngH8H0NPMZQd79}q1IqlP8Hrz zehfQHVZf#SgVNTpBkGP&S=2eNT1I824h=3uaeHo94Ktq`H?XNALq;C0LKrfBB1MgJ z&(Y^Ul_r|XLd5>Cx5Rb|fntJRlbMHCJj+XV{2J$z&uf<3sfz3^Z?x#QD>&~s|0fkZEX*FgvXS8tHwwfniq|7s0v0FW^ zVJh&Ki!CET=20Q@b9Y7yxiEgACzjMPkwTITaiK3!G?j*kq`j*}(3W3_u&qDGjtmqx zuHv;taUS&<>%P*_d8PCF&yLc0K6Yfc1I|0n|94Q@hEnOOja2Hhw=6nZ;HMll1}fFL zfj=tIZqCn7StZmAQC(3u8C$9aE+)71D-j#DTvX+F$Wx6$;?l9*Vys)u#D_a(2Xdry zm%KHStE$a~e0I-QO^v?jBDyzTJZHnii^Pz#13B5cW`v-BH`o?Vj?7GJ|C(=&(bj9HAIDsD2XGiC=cZ%&VO%+x%0s76Z|;RWm(3tvJ55GV zj#KP7(6n2WX??`Oh-D(g@0W5<&|;amXDPM@5vpgQxIBH$-y5^tdyE?I-=ritITR#1 zE4?*Qe137Bzk_lfP&OO3bLSDZ+r=I~m04a&SKTh9EN9Caqt>k-i@F0&xFobzVdEey zwiUMZm#mXp0^yR4j#{sgWP+#Jdj6Xc$6(EamWvEoS44w15dt%%wVPw|S}7xo60OnHX$ zz}tdwiD!*8=C};e`P$f3x0`qUNQhgmXyaBn^i)Uk`TY*cmxj`FQAb6n(vJPfYj-QT z@oPQwSsVI_GMptg48neYC<154u6UMU+T=9Y?^iM=}j-}>8=e{v7!1rVnV2)8NxAuNydrQP=>g5 zjuNm_>0*dLY%L2G`6+Icyu2+!d|#0$l_l(Xi}%jNloNb^oCnM#EzI1|r;T6Z-Ch%^ zp0A2fY(cXqG43~X-c~E$LAh{HDjk(!N=xyMMeQX!mHGl-r83u7(WY!zS(f8nRG7N@ z@8PbBGFNj_l%M4Eb^5_~c%oBSz^gKB>tedUROBbS%9e_7Ioy{f?u_Wgd)G?D`x6!7 z{TmfrfaW2?acc;t_diZeNM-|e>ff#4y=>&iaM2h0<5?lEl3TvcrKxmw&13zE$n!f~kh=E1E_JoUe$s_$ zJf_a$x~cn@iRhqLWQgBS>@QvqNIWn9E#r_5~%N8FITf~ ziuLY}7Vv#^-WxBPMNZs zh~K?JPzXXGzCuY0x#y5yOAI7X4E;=9BFB-xA4)sAw6zZ_@_ZBU{nHayDIN7uN(ZSU z_Nl>n!ui5^`wq%)7|Iqp>Fwmo8}j`fQsY-R96tD_QfUlOI!eP8L+ZwVZ_M7j){yQ} zUzqOv)Tmj<6XtcM`dnU;6DiFyaX4~`hzWjGV3sAhLbm;(Z_12A>qL33pQtlz7Y)Th zqO&GaoM_rZ;2a_sOR9D{OH7>4rNqQEs>6_}T}uxL!pGMuSRK*ii_s#q&yCUh0N%;w zXgXe$s4oiS1f?L=U8EgYE%wp>jokh!vS#``ugaL<#UeK9b%D=GKPaJ3GsTI3k-iVU zmp*M%ePzD?y5_Q-KWHQ?|=BfQl+^n zL?I}Z@1Xn^Kv`9?Rq3FM(@`0UQpci}qU}mA1^Sx9E0qGeAl3QX{W z+o(~q{wrM?lB0LH_KDa`9dloaQqwae5EQ#TOWC{ZS^S>2o*jx>D)xrHA)~?^1%EGmKYUMoUwm(T|L>su_CjfS zKb`zVl!Y#eJN%YIRjQlPME|c$PyaL1`(G5KZu&Jr>Q|!i)EhLegT+}JAE^n_J8H~E zkd|oHNGv%6)lx^59MvQ$*-2n#CM-9>ancf<*vx;!>+t$QQ|SuF!SQfh9KX;EA$H`- z-0-16vuTJvPa!@RK3`#)`@d+Zk5t+Wfy&VwU!}1o2v9D<_cB9;@B1B;|G1!3v~*&V z;@@$oNO4`%X$*F#soJ6B$E{ZKlh-M=6o(l!UP?`t&%ZRJZu((K-qz)1d0RtDvOSZH z8rLI5nHw%p{PqzgPJfT)S1z|uq5-&|Yyhk{ivY`7YcV{=^Y}ZDK&7tY_u>6G4vvT8 z;`qjFw=Dd>(!8z945=G_RFk#k&+vKhx$yZ4=yO(<1t_ihK!--H@1lk(hH?s2-$D70 z7fPjql(M}jNU1L0=1`EZ&Y>X1+2KfSutRQ|v(k|1tyHJEDo2b_N_9~<_BmG!S)PBQ zOWpMS{N%NNURAjB$JP3fH%qiW&Saj(Z0`u8#`AzN+daXMu~AcyvMReEWpy@g@fgpm zfA@~S>+wFkAIHJ*a9kXp5;FdO{D0Hj_;cX%;B(>g;dA2iw(7Sl1f}v%1PJZy00000NkvXXu0mjfX|SbI delta 3905 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 P00000NkvXXu0mjf{BD16 diff --git a/CommonKit/Sources/CommonKit/Assets/Shared.xcassets/Wallets/doge_wallet.imageset/doge_wallet.png b/CommonKit/Sources/CommonKit/Assets/Shared.xcassets/Wallets/doge_wallet.imageset/doge_wallet.png index c6c2fc33cdefada09b4ffac0a765233c4679c8e4..811c2e4dd482cbbf985e2f95f6a78ac55a4f8e96 100644 GIT binary patch literal 7757 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

}3R?zG~=8+AAy;s8H) zYea>)V7K3PSgn5p8!7h^qWuuM&zZo!hV`re0DCf;!PaDA#UOy1qZ-C?>%B=GZcdlt zHZgRQ%ExtL?%FwnkaIWawMi=QX3<8W6#>%|OBJ|9hH;zn@ftn9 zD42(jx zpE&j|v`f8ZvBP5}!3ieu8VsC_#MhsWlI$vRV@iw9u9xEKm>eomAo5e((4gImu8KpH zx1ktqiNfiMa5R~YW2z?yS0|`vB&VDg9Fc)DEHieC^>b?6oH0`JYl)RSYLn7&?o1lacc#>)?o-q*D&l?GxSpdl-$^N!LHS#@ry`H-NYN-uch_Mcu!ppL3Q7LhtUe)6vKwor~ z2cepV*ZM~z&@)qkNg4Rk@;sMH^W3wh!q{_03F}l^BJijC{rKQg8SSH&}j=)n6lnxlQt8CUSYaNRZ^UDHF9= zluvnfXeWl+2$(T-CnRAT2%NpZU`?I_P856 z8|RC#Fp@{syaz2>U+UV$`0{oGKA1J&-W&@f?#$@${&X?!&gulv_b-(QU_V|k=d@cnHKN^!(nuiR4JRMkX{j zkb~a3Vr(?Sy&)_vE=HNmB0jk0pw|!hHj-j1sSuB0t-kQ)D{IV-Bu<;>5XJ zxtSkq3CHaVI$R!>ktFjm-JOR1>M+!4Jke1eioxbsnw&+r#HIi;mGO=w^wu9ohb0sz z$t3EEd{HX3M`gYndg>#{a1??p&vj>02P3EF`GPFR+LKU2c~c?v#PG>@%3FF*uMCsj zxtQn_Q5%s3h~r!ZpUJ>vXXX>Zz>SmRGJxl)@L%Yc2ok(BLFHtu zfXanNV3~nyF9}5{fvYZX=DTa$c(%^t2UbFt=jmfqkn_v@$^8R_;PNwFgeb^z<=9Ul zR5|V(`_)4DzLhA?w?V7PjwF|c>ty*$;1&T&$OVfiA5KvI1!AHl8Y7JnXf4@;`a&PH z7=mztYThhKkS+LWK+gaq7THFy&s?4?5<;0{#VE)w}W0yk4kbOTdB;ahYrd=$B3W`gxiU7qicExGL0u@4#KqY!TJKMGsnp14L#$W_7rFY)-A z&l9MS-ka0m!#OQ~k?enAM79{j;2A;t1 z1WpK&DS!83F@KiWtEEPl<=SX_3V)$b!cTUJcmgH_ff25hTm0DRG>UYda)>9#kO^QICpwRK6<>`N3Qvu~ziMz%k|mxI&p9S16@KRRz787Po#rEbw)-S482%{ zDJ4JORdbwYvLpa3=?O@-z|`b2I0h`8CmHf2J^u1UAy4un04gD#0XDkG*!7F0JmnZ~ zN!`k~=X>+&BwOL;es;>1OU68(e>N!CFPs9o!fYP`=l%V`6;g6)a|jsweI+HPHq(_F| zcSHey<$|7{?#<$ROZO97?npiIF2WD3Kwi2{jXulmr$reaoIcN&(<%IkIj`@@T&5i- z%dq3rNp_qjfv$;*j|<{#xcpcfu7nDsN$%+zedw(_;d|etTIhh@nu7!~f}dzRM!9#4 z@2uFvkF>@K;8-x3GKQYcsYRAl0$|4GJpKL3WD$R7-o)RYDdu}>qj{=Th&r?yi4p7W zniQUX1{%+b5*;~xiW66y>dfgVij$p$D{CRy?eFgdSCFuaQ^!-BSd4;r8v$s8W4EAm-2-s$p;N>RcyfL5|DM%=Eo=^!>V20+#H!9NHuY z;kEA%E|JQ?30qE`uow)ShQQUU_i-lLJ$X9q7%P+wVeN~oycBLlpcP3>pwT2E{34L#^dM~FJ*mC4_#2yRb@eg9|1>ou)f|I2=kURsp zs)9Y7k~$k>p7&2oa&Pa<6I)EtA#dG^3V9pJ;Tw=0zYUV4T~KGcp;+n*gFFCwk{WeG zkS6a!M$~5EH_ov%YQ=@Eye*2`RBBRqdz%!#Khe|wZ!bN@m8*7h;#6A!Tus4$5jZn7 zs1{|Q(7K&5M{pMUVo91EJ8=3@QLgJ6lI=cm;?_J-tW{k~#D=cKW2=V~j;Ji2O_ zJ_1+n!X1^52}-|U;C T>K&Tt00000NkvXXu0mjf|D6lI delta 1455 zcmV;g1yK6UJk<-3IDY^Eb5ch_0Itp)=>Px#1ZP1_K>z@;j|==^1pojAYDq*vRCoc^ znq5-kMi9rlXSA?+ijLr%00Wz+)Pj71kjfJfD(e$~Pk?g**b~4Ofqeu{U>Wiju(c;x za|7oQur%o&Y?nVqMk8Cc_*GHH)>xj_%=G-bdk|(!?WE|+C4blg>8wDc3JO#a6}o`n z_8Tc^D})vTv_W71QKue0LQpCPEtnw)Gk^_p-iv*u(4)5=q(lJ(7^yGeb96NeL#HzC z_N?eBDGy-^gC=l=VfgvuUf@GYQW;k}FBT=rBgn?6QT*^RETM?O&{MH!nkf2{|VO_^q^a ze`UI6G)_AYUSs*0ij!!QlUX%68RMGMUh!=h#<<*H-hZY>FF2zg+#TJG1de<9KtY@e?Rj<{1Dfkr*8fzV92yA{geQ6=T`hZJe@^ z6kS-!tbYN#+Sv!M_oi9y@+BjZo^pZ%@%nP2XDM!%j zc~0#uH7wg6cT-v+y>BR>-^wbp6 zk&85}PXur}=;;PYGkS7KYzZZl2RVT}oq3<_bONx1qM4$g`MCo5pA~|J(Z%8UB{LkDbdH{C);}=|-HLl@v4~#dWd)W2U zb$_M_ZJcH0{qlSBVN#pTo#rRg=k<%#Q!?5#N(EDU8i}^8afuQ{xd6#HOWuxg%6^79 z@G{(~re^06{rbgP_Z?nu?%a=Mq)hTlw|*&(m@tkIxpvpmHMD8XDY_Y^GHxiiMEEhL znQII}<9izC@~!$vs;foQ60JALETK_0x_^Az{g}&RLMinCIU>TyoQG6)`j;P)u-^GI zgr?Hx()M_Yjzn$bkO5}-g>6zC0sZi|=s7wql^d}ebecRWg8IU*+PLT`I)A2s z@enc~PJSgp2hs|wU`dECOHC`3MNdr=1I11&XtTfWIi~AG~ z<5K1-W(wy$c#TO+UB&ggc4&qQA(U&cP)fQ9b4DNec<1~|9r&bF`VfrV|4^-BOySkf z*N+h)P4>A(uF+52nl@!}*#&$FmVc+t2I%dCGu1;sF@jEsEsN%~6_k1sJD{S)5#$aw>~7*Ql0=bv?QDHZ3OLEbApl-uJQXL6SdZjqzrGd=lz)4#Ez*m! zS7hfAx5h@XPRG{IKU5J_CL((h9LPqGVYRD+6Orz`C5@s~`fz{;+2e;Qtdw>G>x{uv zCuO;4L6sR+Gg(w-eHQbBTG5uNjO(FCG89{iDzsA`obgFU)uX4*vCZ_0VkpAq0X*qL+cQ-~{{v8|DM;kr>HYu!002ov JPDHLkV1nc-wgLbE diff --git a/CommonKit/Sources/CommonKit/Assets/Shared.xcassets/Wallets/doge_wallet.imageset/doge_wallet@2x.png b/CommonKit/Sources/CommonKit/Assets/Shared.xcassets/Wallets/doge_wallet.imageset/doge_wallet@2x.png index 5f2cd56cf7ff6b284cefd5509bfbfad2dd065080..f2d160d4b99df56d9ff759c2eca5046530634cb1 100644 GIT binary patch literal 22106 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 diff --git a/CommonKit/Sources/CommonKit/Assets/Shared.xcassets/Wallets/doge_wallet.imageset/doge_wallet@3x.png b/CommonKit/Sources/CommonKit/Assets/Shared.xcassets/Wallets/doge_wallet.imageset/doge_wallet@3x.png index 40f7b5d01d350fbe2853d2e5e18f3c43c3e905db..d3e715bbb717373452012ac617d2995fccda761b 100644 GIT binary patch literal 37813 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 delta 3905 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 P00000NkvXXu0mjf{BD16 diff --git a/NotificationServiceExtension/WalletImages/doge_notificationContent.png b/NotificationServiceExtension/WalletImages/doge_notificationContent.png index 40f7b5d01d350fbe2853d2e5e18f3c43c3e905db..d3e715bbb717373452012ac617d2995fccda761b 100644 GIT binary patch literal 37813 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 delta 3905 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 P00000NkvXXu0mjf{BD16 From 8803443312d0cde52efb6cdc88f37fb6922a2847 Mon Sep 17 00:00:00 2001 From: just-software-dev Date: Thu, 26 Sep 2024 05:11:55 -0300 Subject: [PATCH 106/106] Sounds fix --- Adamant/Services/AdamantNotificationService.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Adamant/Services/AdamantNotificationService.swift b/Adamant/Services/AdamantNotificationService.swift index f4f9020b1..030963239 100644 --- a/Adamant/Services/AdamantNotificationService.swift +++ b/Adamant/Services/AdamantNotificationService.swift @@ -401,7 +401,7 @@ private extension AdamantNotificationsService { return } - try? AVAudioSession.sharedInstance().setCategory(.playback) + try? AVAudioSession.sharedInstance().setCategory(.soloAmbient) try? AVAudioSession.sharedInstance().setActive(true) audioPlayer = try? AVAudioPlayer(contentsOf: url, fileTypeHint: AVFileType.mp3.rawValue) audioPlayer?.volume = 1.0