From 9a5793626ffc27eb9896f43f41c413b2af8e3655 Mon Sep 17 00:00:00 2001 From: Pavel Anokhov Date: Sun, 11 Mar 2018 23:12:11 +0300 Subject: [PATCH 01/12] TransactionsViewController: Using TransfersProvider. App: updating TransfersProvider. --- Adamant/AppDelegate.swift | 11 +- .../Stories/Account/AccountDependencies.swift | 1 + .../Account/AccountViewController.swift | 5 - .../Account/TransactionsViewController.swift | 152 +++++++++--------- Adamant/Utilities/AdamantUtilities.swift | 8 + 5 files changed, 96 insertions(+), 81 deletions(-) diff --git a/Adamant/AppDelegate.swift b/Adamant/AppDelegate.swift index 33d13a689..dfee42cad 100644 --- a/Adamant/AppDelegate.swift +++ b/Adamant/AppDelegate.swift @@ -65,9 +65,16 @@ class AppDelegate: UIResponder, UIApplicationDelegate { // MARK: 4 Autoupdate - let chatsProvider = container.resolve(ChatsProvider.self)! repeater = RepeaterService() - repeater.registerForegroundCall(label: "chatsProvider", interval: 3, queue: DispatchQueue.global(qos: .utility), callback: chatsProvider.update) + if let chatsProvider = container.resolve(ChatsProvider.self) { + repeater.registerForegroundCall(label: "chatsProvider", interval: 3, queue: .global(qos: .utility), callback: chatsProvider.update) + } else { + fatalError("Failed to get chatsProvider") + } + + if let transfersProvider = container.resolve(TransfersProvider.self) { + repeater.registerForegroundCall(label: "transfersProvider", interval: 15, queue: .global(qos: .utility), callback: transfersProvider.update) + } // MARK: 4. Notifications diff --git a/Adamant/Stories/Account/AccountDependencies.swift b/Adamant/Stories/Account/AccountDependencies.swift index 8badb6958..1b7746612 100644 --- a/Adamant/Stories/Account/AccountDependencies.swift +++ b/Adamant/Stories/Account/AccountDependencies.swift @@ -19,6 +19,7 @@ extension Container { c.apiService = r.resolve(ApiService.self) c.cellFactory = r.resolve(CellFactory.self) c.dialogService = r.resolve(DialogService.self) + c.transfersProvider = r.resolve(TransfersProvider.self) } self.storyboardInitCompleted(TransferViewController.self) { (r, c) in c.apiService = r.resolve(ApiService.self) diff --git a/Adamant/Stories/Account/AccountViewController.swift b/Adamant/Stories/Account/AccountViewController.swift index 0c0d76451..d8798ccb0 100644 --- a/Adamant/Stories/Account/AccountViewController.swift +++ b/Adamant/Stories/Account/AccountViewController.swift @@ -101,11 +101,6 @@ class AccountViewController: UIViewController { } switch identifier { - case showTransactionsSegue: - if let account = accountService.account?.address, let vc = segue.destination as? TransactionsViewController { - vc.account = account - } - case showTransferSegue: if let account = accountService.account, let vc = segue.destination as? TransferViewController { vc.account = account diff --git a/Adamant/Stories/Account/TransactionsViewController.swift b/Adamant/Stories/Account/TransactionsViewController.swift index cfe8addb5..257aa3525 100644 --- a/Adamant/Stories/Account/TransactionsViewController.swift +++ b/Adamant/Stories/Account/TransactionsViewController.swift @@ -7,19 +7,20 @@ // import UIKit +import CoreData class TransactionsViewController: UIViewController { // MARK: - Dependencies var cellFactory: CellFactory! var apiService: ApiService! var dialogService: DialogService! - + var transfersProvider: TransfersProvider! // MARK: - Properties - var account: String? private(set) var transactions: [Transaction]? private var updatingTransactions: Bool = false + var controller: NSFetchedResultsController? let transactionDetailsSegue = "showTransactionDetails" @@ -32,13 +33,15 @@ class TransactionsViewController: UIViewController { override func viewDidLoad() { super.viewDidLoad() + controller = transfersProvider.transfersController() + controller?.delegate = self + + tableView.register(cellFactory.nib(for: .TransactionCell), forCellReuseIdentifier: SharedCell.TransactionCell.cellIdentifier) tableView.dataSource = self tableView.delegate = self - if account != nil { - reloadTransactions() - } + tableView.reloadData() } override func viewWillAppear(_ animated: Bool) { @@ -47,64 +50,53 @@ class TransactionsViewController: UIViewController { tableView.deselectRow(at: indexPath, animated: animated) } } - - override func prepare(for segue: UIStoryboardSegue, sender: Any?) { - if segue.identifier == transactionDetailsSegue, - let vc = segue.destination as? TransactionDetailsViewController, - let transaction = sender as? Transaction{ - vc.transaction = transaction - } - } -} + // TODO: +// override func prepare(for segue: UIStoryboardSegue, sender: Any?) { +// if segue.identifier == transactionDetailsSegue, +// let vc = segue.destination as? TransactionDetailsViewController, +// let transaction = sender as? Transaction{ +// vc.transaction = transaction +// } +// } +} -// MARK: - Recieving data +// MARK: - UITableView Cells extension TransactionsViewController { - func reloadTransactions() { - guard !updatingTransactions else { - return - } - - transactions = nil + private func configureCell(_ cell: TransactionTableViewCell, for transfer: TransferTransaction) { + cell.accountLabel.tintColor = UIColor.adamantPrimary + cell.ammountLabel.tintColor = UIColor.adamantPrimary + cell.dateLabel.tintColor = UIColor.adamantSecondary + cell.avatarImageView.tintColor = UIColor.adamantPrimary - guard let account = account else { - tableView.reloadData() - return + if transfer.isOutgoing { + cell.transactionType = .outcome + cell.accountLabel.text = transfer.recipientId + } else { + cell.transactionType = .income + cell.accountLabel.text = transfer.senderId } - updatingTransactions = true + if let amount = transfer.amount { + cell.ammountLabel.text = AdamantUtilities.format(balance: amount) + } - apiService.getTransactions(forAccount: account, type: .send, fromHeight: nil) { response in - defer { - self.updatingTransactions = false - } - - switch response { - case .success(let transactions): - self.transactions = transactions.sorted(by: { $0.date.compare($1.date) == .orderedDescending }) - - case .failure(let error): - self.transactions = nil - self.dialogService.showToastMessage(String(describing: error)) - } - - DispatchQueue.main.async { - self.tableView.reloadData() - } + if let date = transfer.date { + cell.dateLabel.text = DateFormatter.localizedString(from: date as Date, dateStyle: .short, timeStyle: .medium) } } } -// MARK: - UITableViewDataSource +// MARK: - UITableView extension TransactionsViewController: UITableViewDataSource, UITableViewDelegate { func numberOfSections(in tableView: UITableView) -> Int { return 1 } func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { - if let transactions = transactions { - return transactions.count + if let f = controller?.fetchedObjects { + return f.count } else { return 0 } @@ -125,38 +117,50 @@ extension TransactionsViewController: UITableViewDataSource, UITableViewDelegate performSegue(withIdentifier: transactionDetailsSegue, sender: transaction) } -} - - -// MARK: - UITableView Cells -extension TransactionsViewController { + func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { - guard let account = account, let transaction = transactions?[indexPath.row] else { - // TODO: Display & Log error - return UITableViewCell(style: .default, reuseIdentifier: "cell") - } - - guard let cell = tableView.dequeueReusableCell(withIdentifier: SharedCell.TransactionCell.cellIdentifier, for: indexPath) as? TransactionTableViewCell else { - // TODO: Display & Log error - return UITableViewCell(style: .default, reuseIdentifier: "cell") + guard let cell = tableView.dequeueReusableCell(withIdentifier: SharedCell.TransactionCell.cellIdentifier, for: indexPath) as? TransactionTableViewCell, + let transfer = controller?.object(at: indexPath) else { + // TODO: Display & Log error + return UITableViewCell(style: .default, reuseIdentifier: "cell") } - cell.accountLabel.tintColor = UIColor.adamantPrimary - cell.ammountLabel.tintColor = UIColor.adamantPrimary - cell.dateLabel.tintColor = UIColor.adamantSecondary - cell.avatarImageView.tintColor = UIColor.adamantPrimary - - if account == transaction.senderId { - cell.transactionType = .outcome - cell.accountLabel.text = transaction.recipientId - } else { - cell.transactionType = .income - cell.accountLabel.text = transaction.senderId - } - - cell.ammountLabel.text = AdamantUtilities.format(balance: transaction.amount) - cell.dateLabel.text = DateFormatter.localizedString(from: transaction.date, dateStyle: .short, timeStyle: .medium) - + configureCell(cell, for: transfer) return cell } } + +extension TransactionsViewController: NSFetchedResultsControllerDelegate { + func controllerWillChangeContent(_ controller: NSFetchedResultsController) { + tableView.beginUpdates() + } + + func controllerDidChangeContent(_ controller: NSFetchedResultsController) { + tableView.endUpdates() + } + + func controller(_ controller: NSFetchedResultsController, didChange anObject: Any, at indexPath: IndexPath?, for type: NSFetchedResultsChangeType, newIndexPath: IndexPath?) { + switch type { + case .insert: + if let newIndexPath = newIndexPath { + tableView.insertRows(at: [newIndexPath], with: .automatic) + } + + case .delete: + if let indexPath = indexPath { + tableView.deleteRows(at: [indexPath], with: .automatic) + } + + case .update: + if let indexPath = indexPath, + let cell = self.tableView.cellForRow(at: indexPath) as? TransactionTableViewCell, + let transfer = anObject as? TransferTransaction { + configureCell(cell, for: transfer) + } + + default: + break + } + } +} + diff --git a/Adamant/Utilities/AdamantUtilities.swift b/Adamant/Utilities/AdamantUtilities.swift index ce3ef0340..d63453c65 100644 --- a/Adamant/Utilities/AdamantUtilities.swift +++ b/Adamant/Utilities/AdamantUtilities.swift @@ -45,6 +45,14 @@ extension AdamantUtilities { return currencyFormatter.string(from: NSNumber(value: balance))! } + static func format(balance: Decimal) -> String { + return currencyFormatter.string(from: balance as NSNumber)! + } + + static func format(balance: NSDecimalNumber) -> String { + return currencyFormatter.string(from: balance as NSNumber)! + } + static func from(double: Double) -> UInt64 { return UInt64(double / currencyShift) } From 7e3d87f06cd0673c0e67ef5c792337e91c4759f9 Mon Sep 17 00:00:00 2001 From: Pavel Anokhov Date: Mon, 12 Mar 2018 00:44:06 +0300 Subject: [PATCH 02/12] Balances as Decimal --- Adamant.xcodeproj/project.pbxproj | 20 ++++++++------ Adamant/Helpers/Decimal+adamant.swift | 19 +++++++++++++ Adamant/Models/Account.swift | 11 +++++--- Adamant/Models/AdamantMessage.swift | 27 +++++++++++++++++++ Adamant/Models/NormalizedTransaction.swift | 6 +++-- Adamant/Models/Transaction.swift | 12 ++++++--- Adamant/ServiceProtocols/ApiService.swift | 2 +- .../DataProviders/ChatsProvider.swift | 10 ------- .../DataProviders/TransfersProvider.swift | 2 ++ .../ApiService/AdamantApi+Transfers.swift | 6 ++--- .../DataProviders/AdamantChatsProvider.swift | 2 +- .../AdamantTransfersProvider.swift | 8 +++--- Adamant/Services/JSAdamantCore.swift | 2 +- .../Account/TransferViewController.swift | 18 +++++++------ .../Stories/Chats/ChatViewController.swift | 8 +++--- Adamant/Utilities/AdamantFeeCalculator.swift | 24 ----------------- Adamant/Utilities/AdamantUtilities.swift | 22 +++------------ AdamantTests/CurrencyFormatterTests.swift | 26 +++++++----------- ...eeCalculatorTests.swift => FeeTests.swift} | 26 +++++++----------- AdamantTests/Parsing/ParsingModelsTests.swift | 12 ++++----- 20 files changed, 134 insertions(+), 129 deletions(-) create mode 100644 Adamant/Helpers/Decimal+adamant.swift create mode 100644 Adamant/Models/AdamantMessage.swift delete mode 100644 Adamant/Utilities/AdamantFeeCalculator.swift rename AdamantTests/{FeeCalculatorTests.swift => FeeTests.swift} (67%) diff --git a/Adamant.xcodeproj/project.pbxproj b/Adamant.xcodeproj/project.pbxproj index 927685dfb..a767467e3 100644 --- a/Adamant.xcodeproj/project.pbxproj +++ b/Adamant.xcodeproj/project.pbxproj @@ -36,6 +36,8 @@ E9256F762039A9A200DE86E9 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = E9256F752039A9A200DE86E9 /* LaunchScreen.storyboard */; }; E9256F792039B29A00DE86E9 /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = E9256F7B2039B29A00DE86E9 /* Localizable.strings */; }; E9256F7E2039B29D00DE86E9 /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = E9256F802039B29D00DE86E9 /* InfoPlist.strings */; }; + E9393FA82055C92700EE6F30 /* Decimal+adamant.swift in Sources */ = {isa = PBXBuildFile; fileRef = E9393FA72055C92700EE6F30 /* Decimal+adamant.swift */; }; + E9393FAA2055D03300EE6F30 /* AdamantMessage.swift in Sources */ = {isa = PBXBuildFile; fileRef = E9393FA92055D03300EE6F30 /* AdamantMessage.swift */; }; E93B0D742028B21400126346 /* ChatsProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = E93B0D732028B21400126346 /* ChatsProvider.swift */; }; E93B0D762028B28E00126346 /* AdamantChatsProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = E93B0D752028B28E00126346 /* AdamantChatsProvider.swift */; }; E93D7ABE2052CEE1005D19DC /* NotificationsService.swift in Sources */ = {isa = PBXBuildFile; fileRef = E93D7ABD2052CEE1005D19DC /* NotificationsService.swift */; }; @@ -89,8 +91,7 @@ E9B3D39E201F99F40019EB36 /* DataProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = E9B3D39D201F99F40019EB36 /* DataProvider.swift */; }; E9B3D3A1201FA26B0019EB36 /* AdamantAccountsProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = E9B3D3A0201FA26B0019EB36 /* AdamantAccountsProvider.swift */; }; E9B3D3A9202082450019EB36 /* AdamantTransfersProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = E9B3D3A8202082450019EB36 /* AdamantTransfersProvider.swift */; }; - E9C51ECD200E2BF900385EB7 /* AdamantFeeCalculator.swift in Sources */ = {isa = PBXBuildFile; fileRef = E9C51ECC200E2BF900385EB7 /* AdamantFeeCalculator.swift */; }; - E9C51ECF200E2D1100385EB7 /* FeeCalculatorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = E9C51ECE200E2D1100385EB7 /* FeeCalculatorTests.swift */; }; + E9C51ECF200E2D1100385EB7 /* FeeTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = E9C51ECE200E2D1100385EB7 /* FeeTests.swift */; }; E9C51EED2011416E00385EB7 /* adamant-core.js in Resources */ = {isa = PBXBuildFile; fileRef = E9C51EEC2011416E00385EB7 /* adamant-core.js */; }; E9C51EEF20139DC600385EB7 /* ProcessTransactionResponse.swift in Sources */ = {isa = PBXBuildFile; fileRef = E9C51EEE20139DC600385EB7 /* ProcessTransactionResponse.swift */; }; E9C51EF12013F18000385EB7 /* NewChatViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = E9C51EF02013F18000385EB7 /* NewChatViewController.swift */; }; @@ -211,6 +212,8 @@ E9256F752039A9A200DE86E9 /* LaunchScreen.storyboard */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.storyboard; path = LaunchScreen.storyboard; sourceTree = ""; }; E9256F7A2039B29A00DE86E9 /* ru */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ru; path = ru.lproj/Localizable.strings; sourceTree = ""; }; E9256F7F2039B29D00DE86E9 /* ru */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ru; path = ru.lproj/InfoPlist.strings; sourceTree = ""; }; + E9393FA72055C92700EE6F30 /* Decimal+adamant.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Decimal+adamant.swift"; sourceTree = ""; }; + E9393FA92055D03300EE6F30 /* AdamantMessage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AdamantMessage.swift; sourceTree = ""; }; E93B0D732028B21400126346 /* ChatsProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChatsProvider.swift; sourceTree = ""; }; E93B0D752028B28E00126346 /* AdamantChatsProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AdamantChatsProvider.swift; sourceTree = ""; }; E93D7ABD2052CEE1005D19DC /* NotificationsService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationsService.swift; sourceTree = ""; }; @@ -265,8 +268,7 @@ E9B3D39D201F99F40019EB36 /* DataProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DataProvider.swift; sourceTree = ""; }; E9B3D3A0201FA26B0019EB36 /* AdamantAccountsProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AdamantAccountsProvider.swift; sourceTree = ""; }; E9B3D3A8202082450019EB36 /* AdamantTransfersProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AdamantTransfersProvider.swift; sourceTree = ""; }; - E9C51ECC200E2BF900385EB7 /* AdamantFeeCalculator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AdamantFeeCalculator.swift; sourceTree = ""; }; - E9C51ECE200E2D1100385EB7 /* FeeCalculatorTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FeeCalculatorTests.swift; sourceTree = ""; }; + E9C51ECE200E2D1100385EB7 /* FeeTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FeeTests.swift; sourceTree = ""; }; E9C51EEC2011416E00385EB7 /* adamant-core.js */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.javascript; path = "adamant-core.js"; sourceTree = ""; }; E9C51EEE20139DC600385EB7 /* ProcessTransactionResponse.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProcessTransactionResponse.swift; sourceTree = ""; }; E9C51EF02013F18000385EB7 /* NewChatViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NewChatViewController.swift; sourceTree = ""; }; @@ -440,6 +442,7 @@ E9E7CDC920040CC200DFC4DB /* Transaction.swift */, E95F858A200931410070534A /* TransactionAsset.swift */, E9E7CDCB20040FDC00DFC4DB /* TransactionType.swift */, + E9393FA92055D03300EE6F30 /* AdamantMessage.swift */, ); path = Models; sourceTree = ""; @@ -456,6 +459,7 @@ E9256F5E2034C21100DE86E9 /* String+localized.swift */, E9147B5E20500E9300145913 /* MyLittlePinpad+adamant.swift */, E9147B6220505C7500145913 /* QRCodeReader+adamant.swift */, + E9393FA72055C92700EE6F30 /* Decimal+adamant.swift */, ); path = Helpers; sourceTree = ""; @@ -517,7 +521,6 @@ isa = PBXGroup; children = ( E948E04B2027679300975D6B /* AdamantExportTools.swift */, - E9C51ECC200E2BF900385EB7 /* AdamantFeeCalculator.swift */, E9942B83203CBFCE00C163AF /* AdamantQRTools.swift */, E9E7CDB62003994E00DFC4DB /* AdamantUtilities.swift */, E950652220404C84008352E5 /* AdamantUriTools.swift */, @@ -673,7 +676,7 @@ E9EC344620066D4A00C0E546 /* AddressValidationTests.swift */, E94883E6203F07CD00F6E1B0 /* PassphraseValidation.swift */, E95F85702007D98D0070534A /* CurrencyFormatterTests.swift */, - E9C51ECE200E2D1100385EB7 /* FeeCalculatorTests.swift */, + E9C51ECE200E2D1100385EB7 /* FeeTests.swift */, E95F85742007E4790070534A /* HexAndBytesUtilitiesTest.swift */, E95F85762007E8EC0070534A /* JSAdamantCoreTests.swift */, E95F85B6200A4D8F0070534A /* TestTools.swift */, @@ -947,6 +950,7 @@ E9E7CDB52002BA6900DFC4DB /* SwinjectedRouter.swift in Sources */, E9CE7787202B9DC2009CF70D /* BaseTransaction+CoreDataClass.swift in Sources */, E95F85872008FDBF0070534A /* Chat.swift in Sources */, + E9393FAA2055D03300EE6F30 /* AdamantMessage.swift in Sources */, E90A494D204DA932009F6A65 /* LocalAuthentication.swift in Sources */, E95F856F2007B61D0070534A /* GetPublicKeyResponse.swift in Sources */, E9E7CDBB2003AAA700DFC4DB /* RoundAvatarTableViewCell.swift in Sources */, @@ -954,7 +958,6 @@ E9E7CDC72003F6D200DFC4DB /* TransactionTableViewCell.swift in Sources */, E9B3D3A9202082450019EB36 /* AdamantTransfersProvider.swift in Sources */, E948E04E20278D5600975D6B /* SettingsDependencies.swift in Sources */, - E9C51ECD200E2BF900385EB7 /* AdamantFeeCalculator.swift in Sources */, E9E7CDB32002B9FB00DFC4DB /* LoginRoutes.swift in Sources */, E91947B22000246A001362F8 /* AdamantError.swift in Sources */, E95F85802008C8D70070534A /* ChatsRoutes.swift in Sources */, @@ -995,6 +998,7 @@ E93D7AC02052CF63005D19DC /* AdamantNotificationService.swift in Sources */, E93B0D762028B28E00126346 /* AdamantChatsProvider.swift in Sources */, E90A4943204C5ED6009F6A65 /* EurekaPassphraseRow.swift in Sources */, + E9393FA82055C92700EE6F30 /* Decimal+adamant.swift in Sources */, E95F8589200900B10070534A /* ChatType.swift in Sources */, E913C9081FFFA943001A83F7 /* AdamantCore.swift in Sources */, E9EC342120052ABB00C0E546 /* TransferViewController.swift in Sources */, @@ -1013,7 +1017,7 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( - E9C51ECF200E2D1100385EB7 /* FeeCalculatorTests.swift in Sources */, + E9C51ECF200E2D1100385EB7 /* FeeTests.swift in Sources */, E9EC344720066D4A00C0E546 /* AddressValidationTests.swift in Sources */, E94883E7203F07CD00F6E1B0 /* PassphraseValidation.swift in Sources */, E95F85B7200A4D8F0070534A /* TestTools.swift in Sources */, diff --git a/Adamant/Helpers/Decimal+adamant.swift b/Adamant/Helpers/Decimal+adamant.swift new file mode 100644 index 000000000..851122731 --- /dev/null +++ b/Adamant/Helpers/Decimal+adamant.swift @@ -0,0 +1,19 @@ +// +// Decimal+adamant.swift +// Adamant +// +// Created by Anokhov Pavel on 11.03.2018. +// Copyright © 2018 Adamant. All rights reserved. +// + +import Foundation + +extension Decimal { + func shiftedFromAdamant() -> Decimal { + return Decimal(sign: self.isSignMinus ? .minus : .plus, exponent: AdamantUtilities.currencyExponent, significand: self) + } + + func shiftedToAdamant() -> Decimal { + return Decimal(sign: self.isSignMinus ? .minus : .plus, exponent: 0, significand: self) + } +} diff --git a/Adamant/Models/Account.swift b/Adamant/Models/Account.swift index c8f686525..551e53b91 100644 --- a/Adamant/Models/Account.swift +++ b/Adamant/Models/Account.swift @@ -10,8 +10,8 @@ import Foundation struct Account { let address: String - var unconfirmedBalance: UInt64 - var balance: UInt64 + var unconfirmedBalance: Decimal + var balance: Decimal let publicKey: String let unconfirmedSignature: Int let secondSignature: Int @@ -37,14 +37,17 @@ extension Account: Decodable { let container = try decoder.container(keyedBy: CodingKeys.self) self.address = try container.decode(String.self, forKey: .address) - self.unconfirmedBalance = UInt64(try container.decode(String.self, forKey: .unconfirmedBalance))! - self.balance = UInt64(try container.decode(String.self, forKey: .balance))! self.unconfirmedSignature = try container.decode(Int.self, forKey: .unconfirmedSignature) self.publicKey = try container.decode(String.self, forKey: .publicKey) self.secondSignature = try container.decode(Int.self, forKey: .secondSignature) self.secondPublicKey = try? container.decode(String.self, forKey: .secondPublicKey) self.multisignatures = try? container.decode([String].self, forKey: .multisignatures) self.uMultisignatures = try? container.decode([String].self, forKey: .uMultisignatures) + + let unconfirmedBalance = Decimal(string: try container.decode(String.self, forKey: .unconfirmedBalance))! + self.unconfirmedBalance = unconfirmedBalance.shiftedFromAdamant() + let balance = Decimal(string: try container.decode(String.self, forKey: .balance))! + self.balance = balance.shiftedFromAdamant() } } diff --git a/Adamant/Models/AdamantMessage.swift b/Adamant/Models/AdamantMessage.swift new file mode 100644 index 000000000..756178df9 --- /dev/null +++ b/Adamant/Models/AdamantMessage.swift @@ -0,0 +1,27 @@ +// +// AdamantMessage.swift +// Adamant +// +// Created by Anokhov Pavel on 11.03.2018. +// Copyright © 2018 Adamant. All rights reserved. +// + +import Foundation + +/// Adamant message types +/// +/// - text: Simple text message +enum AdamantMessage { + case text(String) +} + +extension AdamantMessage { + static private let textFee = Decimal(sign: .plus, exponent: -3, significand: 1) + + var fee: Decimal { + switch self { + case .text(let message): + return Decimal(ceil(Double(message.count) / 255.0)) * AdamantMessage.textFee + } + } +} diff --git a/Adamant/Models/NormalizedTransaction.swift b/Adamant/Models/NormalizedTransaction.swift index 8af790ac0..777c86465 100644 --- a/Adamant/Models/NormalizedTransaction.swift +++ b/Adamant/Models/NormalizedTransaction.swift @@ -10,7 +10,7 @@ import Foundation struct NormalizedTransaction { let type: TransactionType - let amount: UInt64 + let amount: Decimal let senderPublicKey: String let requesterPublicKey: String? let timestamp: UInt64 @@ -37,12 +37,14 @@ extension NormalizedTransaction: Decodable { let container = try decoder.container(keyedBy: CodingKeys.self) self.type = try container.decode(TransactionType.self, forKey: .type) - self.amount = try container.decode(UInt64.self, forKey: .amount) self.senderPublicKey = try container.decode(String.self, forKey: .senderPublicKey) self.requesterPublicKey = try? container.decode(String.self, forKey: .requesterPublicKey) self.timestamp = try container.decode(UInt64.self, forKey: .timestamp) self.recipientId = try container.decode(String.self, forKey: .recipientId) self.asset = try container.decode(TransactionAsset.self, forKey: .asset) + + let amount = try container.decode(Decimal.self, forKey: .amount) + self.amount = amount.shiftedFromAdamant() } } diff --git a/Adamant/Models/Transaction.swift b/Adamant/Models/Transaction.swift index d83c6f4c9..7d1dc5be1 100644 --- a/Adamant/Models/Transaction.swift +++ b/Adamant/Models/Transaction.swift @@ -19,8 +19,8 @@ struct Transaction { let requesterPublicKey: String? let recipientId: String let recipientPublicKey: String? - let amount: UInt64 - let fee: UInt64 + let amount: Decimal + let fee: Decimal let signature: String let signSignature: String? let confirmations: UInt64 @@ -63,14 +63,18 @@ extension Transaction: Decodable { self.senderId = try container.decode(String.self, forKey: .senderId) self.recipientId = try container.decode(String.self, forKey: .recipientId) self.recipientPublicKey = try? container.decode(String.self, forKey: .recipientPublicKey) - self.amount = try container.decode(UInt64.self, forKey: .amount) - self.fee = try container.decode(UInt64.self, forKey: .fee) self.signature = try container.decode(String.self, forKey: .signature) self.confirmations = (try? container.decode(UInt64.self, forKey: .confirmations)) ?? 0 self.requesterPublicKey = try? container.decode(String.self, forKey: .requesterPublicKey) self.signSignature = try? container.decode(String.self, forKey: .signSignature) self.signatures = try container.decode([String].self, forKey: .signatures) self.asset = try container.decode(TransactionAsset.self, forKey: .asset) + + let amount = try container.decode(Decimal.self, forKey: .amount) + self.amount = amount.shiftedFromAdamant() + + let fee = try container.decode(Decimal.self, forKey: .fee) + self.fee = fee.shiftedFromAdamant() self.date = AdamantUtilities.decodeAdamantDate(timestamp: TimeInterval(self.timestamp)) } diff --git a/Adamant/ServiceProtocols/ApiService.swift b/Adamant/ServiceProtocols/ApiService.swift index 93d629046..24430f150 100644 --- a/Adamant/ServiceProtocols/ApiService.swift +++ b/Adamant/ServiceProtocols/ApiService.swift @@ -75,7 +75,7 @@ protocol ApiService: class { // MARK: - Funds - func transferFunds(sender: String, recipient: String, amount: UInt64, keypair: Keypair, completion: @escaping (ApiServiceResult) -> Void) + func transferFunds(sender: String, recipient: String, amount: Decimal, keypair: Keypair, completion: @escaping (ApiServiceResult) -> Void) // MARK: - Chats diff --git a/Adamant/ServiceProtocols/DataProviders/ChatsProvider.swift b/Adamant/ServiceProtocols/DataProviders/ChatsProvider.swift index 514f0991e..7b4bf4cd0 100644 --- a/Adamant/ServiceProtocols/DataProviders/ChatsProvider.swift +++ b/Adamant/ServiceProtocols/DataProviders/ChatsProvider.swift @@ -34,16 +34,6 @@ enum ValidateMessageResult { } -// MARK: - Available message types - -/// Adamant message types -/// -/// - text: Simple text message -enum AdamantMessage { - case text(String) -} - - // MARK: - Notifications extension Notification.Name { static let adamantChatsProviderNewChatroom = Notification.Name("adamantChatsProviderNewChatroom") diff --git a/Adamant/ServiceProtocols/DataProviders/TransfersProvider.swift b/Adamant/ServiceProtocols/DataProviders/TransfersProvider.swift index 79769b7f2..1778fe255 100644 --- a/Adamant/ServiceProtocols/DataProviders/TransfersProvider.swift +++ b/Adamant/ServiceProtocols/DataProviders/TransfersProvider.swift @@ -44,6 +44,8 @@ extension AdamantUserInfoKey { } protocol TransfersProvider: DataProvider { + var transferFee: Decimal { get } + func transfersController() -> NSFetchedResultsController func transferFunds(toAddress recipient: String, amount: Decimal, completion: @escaping (TransfersProviderResult) -> Void) diff --git a/Adamant/Services/ApiService/AdamantApi+Transfers.swift b/Adamant/Services/ApiService/AdamantApi+Transfers.swift index 8e63d7f1d..b43f7722e 100644 --- a/Adamant/Services/ApiService/AdamantApi+Transfers.swift +++ b/Adamant/Services/ApiService/AdamantApi+Transfers.swift @@ -9,11 +9,11 @@ import Foundation extension AdamantApiService { - func transferFunds(sender: String, recipient: String, amount: UInt64, keypair: Keypair, completion: @escaping (ApiServiceResult) -> Void) { + func transferFunds(sender: String, recipient: String, amount: Decimal, keypair: Keypair, completion: @escaping (ApiServiceResult) -> Void) { // MARK: 1. Prepare params let params: [String : Any] = [ "type": TransactionType.send.rawValue, - "amount": amount, + "amount": amount.shiftedToAdamant(), "recipientId": recipient, "senderId": sender, "publicKey": keypair.publicKey @@ -54,7 +54,7 @@ extension AdamantApiService { // MARK: 4.2. Create transaction let transaction: [String: Any] = [ "type": TransactionType.send.rawValue, - "amount": amount, + "amount": amount.shiftedToAdamant(), "senderPublicKey": keypair.publicKey, "requesterPublicKey": normalizedTransaction.requesterPublicKey ?? NSNull(), "timestamp": normalizedTransaction.timestamp, diff --git a/Adamant/Services/DataProviders/AdamantChatsProvider.swift b/Adamant/Services/DataProviders/AdamantChatsProvider.swift index 0c1d56d31..4652b94bf 100644 --- a/Adamant/Services/DataProviders/AdamantChatsProvider.swift +++ b/Adamant/Services/DataProviders/AdamantChatsProvider.swift @@ -184,7 +184,7 @@ extension AdamantChatsProvider { return } - guard loggedAccount.balance >= AdamantFeeCalculator.estimatedFeeFor(message: message) else { + guard loggedAccount.balance >= message.fee else { completion(.error(.notEnoughtMoneyToSend)) return } diff --git a/Adamant/Services/DataProviders/AdamantTransfersProvider.swift b/Adamant/Services/DataProviders/AdamantTransfersProvider.swift index 461cec425..c63057f33 100644 --- a/Adamant/Services/DataProviders/AdamantTransfersProvider.swift +++ b/Adamant/Services/DataProviders/AdamantTransfersProvider.swift @@ -17,6 +17,8 @@ class AdamantTransfersProvider: TransfersProvider { var accountsProvider: AccountsProvider! // MARK: Properties + var transferFee: Decimal = Decimal(sign: .plus, exponent: -1, significand: 5) + private(set) var state: State = .empty private var lastHeight: UInt64? @@ -148,7 +150,7 @@ extension AdamantTransfersProvider { return } - apiService.transferFunds(sender: senderAddress, recipient: recipient, amount: (amount as NSDecimalNumber).uint64Value, keypair: keypair) { result in + apiService.transferFunds(sender: senderAddress, recipient: recipient, amount: amount, keypair: keypair) { result in switch result { case .success(_): completion(.success) @@ -238,9 +240,9 @@ extension AdamantTransfersProvider { var height = 0 for t in transactions { let transfer = TransferTransaction(entity: TransferTransaction.entity(), insertInto: context) - transfer.amount = Decimal(t.amount) as NSDecimalNumber + transfer.amount = t.amount as NSDecimalNumber transfer.date = t.date as NSDate - transfer.fee = Decimal(t.fee) as NSDecimalNumber + transfer.fee = t.fee as NSDecimalNumber transfer.height = Int64(t.height) transfer.recipientId = t.recipientId transfer.senderId = t.senderId diff --git a/Adamant/Services/JSAdamantCore.swift b/Adamant/Services/JSAdamantCore.swift index c763ec85f..b1d1c8153 100644 --- a/Adamant/Services/JSAdamantCore.swift +++ b/Adamant/Services/JSAdamantCore.swift @@ -273,7 +273,7 @@ extension JSAdamantCore { senderId: senderId, recipientId: t.recipientId, recipientPublicKey: t.requesterPublicKey, - amount: t.amount, + amount: (t.amount.shiftedToAdamant() as NSDecimalNumber).uint64Value, fee: 0, signature: "", confirmations: 0, diff --git a/Adamant/Stories/Account/TransferViewController.swift b/Adamant/Stories/Account/TransferViewController.swift index 0ff26f1cc..5cbc9370d 100644 --- a/Adamant/Stories/Account/TransferViewController.swift +++ b/Adamant/Stories/Account/TransferViewController.swift @@ -116,7 +116,7 @@ class TransferViewController: FormViewController { // MARK: - Wallet section if let account = account { sendButton.isEnabled = maxToTransfer > 0.0 - let balance = Double(account.balance) * AdamantUtilities.currencyShift + let balance = (account.balance as NSDecimalNumber).doubleValue maxToTransfer = balance - defaultFee > 0 ? balance - defaultFee : 0.0 form +++ Section(Sections.wallet.localized) @@ -186,7 +186,7 @@ class TransferViewController: FormViewController { guard let row: DecimalRow = form.rowBy(tag: Row.amount.tag), let amount = row.value, amount > 0, - AdamantUtilities.validateAmount(amount: amount), + AdamantUtilities.validateAmount(amount: Decimal(amount)), let maxToTransfer = self?.maxToTransfer else { return true } @@ -239,8 +239,8 @@ class TransferViewController: FormViewController { totalRow.evaluateDisabled() if let totalAmount = totalAmount { - if amount > 0, AdamantUtilities.validateAmount(amount: amount), - totalAmount > 0.0 && totalAmount < (Double(account.balance) * AdamantUtilities.currencyShift) { + if amount > 0, AdamantUtilities.validateAmount(amount: Decimal(amount)), + totalAmount > 0.0 && totalAmount < (account.balance as NSDecimalNumber).doubleValue { sendButton.isEnabled = true row.cell.titleLabel?.textColor = .black } else { @@ -268,11 +268,13 @@ class TransferViewController: FormViewController { guard let recipientRow = form.rowBy(tag: Row.address.tag) as? TextRow, let recipient = recipientRow.value, let amountRow = form.rowBy(tag: Row.amount.tag) as? DecimalRow, - let amount = amountRow.value else { + let raw = amountRow.value else { return } - guard amount > 0, AdamantUtilities.validateAmount(amount: amount) else { + let amount = Decimal(raw) + + guard AdamantUtilities.validateAmount(amount: amount) else { dialogService.showError(withMessage: String.adamantLocalized.transfer.amountZeroError) return } @@ -282,7 +284,7 @@ class TransferViewController: FormViewController { return } - guard amount <= maxToTransfer else { + guard amount <= Decimal(maxToTransfer) else { dialogService.showError(withMessage: String.adamantLocalized.transfer.amountTooHigh) return } @@ -296,7 +298,7 @@ class TransferViewController: FormViewController { apiService.getPublicKey(byAddress: recipient) { result in switch result { case .success(_): - apiService.transferFunds(sender: account.address, recipient: recipient, amount: AdamantUtilities.from(double: amount), keypair: keypair) { [weak self] result in + apiService.transferFunds(sender: account.address, recipient: recipient, amount: amount, keypair: keypair) { [weak self] result in switch result { case .success(_): DispatchQueue.main.async { diff --git a/Adamant/Stories/Chats/ChatViewController.swift b/Adamant/Stories/Chats/ChatViewController.swift index 10a6e89df..4a1b47817 100644 --- a/Adamant/Stories/Chats/ChatViewController.swift +++ b/Adamant/Stories/Chats/ChatViewController.swift @@ -60,7 +60,7 @@ class ChatViewController: MessagesViewController { private var feeIsVisible: Bool = false private var feeTimer: Timer? private var feeLabel: InputBarButtonItem? - private var prevFee: UInt64 = 0 + private var prevFee: Decimal = 0 // MARK: Lifecycle @@ -149,7 +149,7 @@ class ChatViewController: MessagesViewController { if let delegate = delegate, let address = chatroom?.partner?.address, let message = delegate.getPreservedMessageFor(address: address, thenRemoveIt: true) { messageInputBar.inputTextView.text = message - setEstimatedFee(AdamantFeeCalculator.estimatedFeeFor(message: AdamantMessage.text(message))) + setEstimatedFee(AdamantMessage.text(message).fee) } } @@ -187,7 +187,7 @@ class ChatViewController: MessagesViewController { // MARK: - EstimatedFee label extension ChatViewController { - private func setEstimatedFee(_ fee: UInt64) { + private func setEstimatedFee(_ fee: Decimal) { if prevFee != fee && fee > 0 { guard let feeLabel = feeLabel else { return @@ -427,7 +427,7 @@ extension ChatViewController: MessageInputBarDelegate { func messageInputBar(_ inputBar: MessageInputBar, textViewTextDidChangeTo text: String) { if text.count > 0 { - let fee = AdamantFeeCalculator.estimatedFeeFor(message: .text(text)) + let fee = AdamantMessage.text(text).fee setEstimatedFee(fee) } else { setEstimatedFee(0) diff --git a/Adamant/Utilities/AdamantFeeCalculator.swift b/Adamant/Utilities/AdamantFeeCalculator.swift deleted file mode 100644 index 48efe9f47..000000000 --- a/Adamant/Utilities/AdamantFeeCalculator.swift +++ /dev/null @@ -1,24 +0,0 @@ -// -// AdamantFeeCalculator.swift -// Adamant -// -// Created by Anokhov Pavel on 16.01.2018. -// Copyright © 2018 Adamant. All rights reserved. -// - -import Foundation - -class AdamantFeeCalculator { - static func estimatedFeeFor(message: AdamantMessage) -> UInt64 { - switch message { - case .text(let text): - return AdamantUtilities.from(double: ceil(Double(text.count) / 255.0) * 0.001) - } - } - - static func estimatedFeeFor(transfer: UInt64) -> UInt64 { - return AdamantUtilities.from(double: 0.5) - } - - private init() {} -} diff --git a/Adamant/Utilities/AdamantUtilities.swift b/Adamant/Utilities/AdamantUtilities.swift index d63453c65..65cf559f6 100644 --- a/Adamant/Utilities/AdamantUtilities.swift +++ b/Adamant/Utilities/AdamantUtilities.swift @@ -27,6 +27,7 @@ class AdamantUtilities { // MARK: - Currency extension AdamantUtilities { static let currencyShift: Double = 0.00_000_001 + static let currencyExponent: Int = -8 static let currencyCode = "ADM" static var currencyFormatter: NumberFormatter = { @@ -37,14 +38,6 @@ extension AdamantUtilities { return formatter }() - static func format(balance: UInt64) -> String { - return currencyFormatter.string(from: NSNumber(value: from(uInt: balance)))! - } - - static func format(balance: Double) -> String { - return currencyFormatter.string(from: NSNumber(value: balance))! - } - static func format(balance: Decimal) -> String { return currencyFormatter.string(from: balance as NSNumber)! } @@ -53,18 +46,11 @@ extension AdamantUtilities { return currencyFormatter.string(from: balance as NSNumber)! } - static func from(double: Double) -> UInt64 { - return UInt64(double / currencyShift) - } - - static func from(uInt: UInt64) -> Double { - return Double(uInt) * currencyShift - } - - static func validateAmount(amount: Double) -> Bool { - if amount < currencyShift { + static func validateAmount(amount: Decimal) -> Bool { + if amount < Decimal(sign: .plus, exponent: AdamantUtilities.currencyExponent, significand: 1) { return false } + return true } } diff --git a/AdamantTests/CurrencyFormatterTests.swift b/AdamantTests/CurrencyFormatterTests.swift index 7c2484481..2bd1ffe97 100644 --- a/AdamantTests/CurrencyFormatterTests.swift +++ b/AdamantTests/CurrencyFormatterTests.swift @@ -12,13 +12,8 @@ import XCTest class CurrencyFormatterTests: XCTestCase { var decimalSeparator: String = Locale.current.decimalSeparator! - override func setUp() { - super.setUp() - decimalSeparator = Locale.current.decimalSeparator! - } - func testInt() { - let number = 123 + let number = Decimal(123) let format = "123 \(AdamantUtilities.currencyCode)" let formatted = AdamantUtilities.currencyFormatter.string(for: number) @@ -26,7 +21,7 @@ class CurrencyFormatterTests: XCTestCase { } func testFracted() { - let number = 123.5 + let number = Decimal(123.5) let format = "123\(decimalSeparator)5 \(AdamantUtilities.currencyCode)" let formatted = AdamantUtilities.currencyFormatter.string(for: number) @@ -35,7 +30,7 @@ class CurrencyFormatterTests: XCTestCase { } func testZeroFracted() { - let number = 0.53 + let number = Decimal(0.53) let format = "0\(decimalSeparator)53 \(AdamantUtilities.currencyCode)" let formatted = AdamantUtilities.currencyFormatter.string(for: number) @@ -43,7 +38,7 @@ class CurrencyFormatterTests: XCTestCase { } func testVerySmallFracted() { - let number = 0.00000007 + let number = Decimal(0.00000007) let format = "0\(decimalSeparator)00000007 \(AdamantUtilities.currencyCode)" let formatted = AdamantUtilities.currencyFormatter.string(for: number) @@ -51,7 +46,7 @@ class CurrencyFormatterTests: XCTestCase { } func testTooSmallFracted() { - let number = 0.0000000699 + let number = Decimal(0.0000000699) let format = "0\(decimalSeparator)00000006 \(AdamantUtilities.currencyCode)" let formatted = AdamantUtilities.currencyFormatter.string(for: number) @@ -59,15 +54,15 @@ class CurrencyFormatterTests: XCTestCase { } func testLargeInt() { - let number = 349034839840234 - let format = "349034839840234 \(AdamantUtilities.currencyCode)" + let number = Decimal(34903483984) + let format = "34903483984 \(AdamantUtilities.currencyCode)" let formatted = AdamantUtilities.currencyFormatter.string(for: number) - + XCTAssertEqual(format, formatted) } func testLargeNumber() { - let number = 9342034.5848984 + let number = Decimal(9342034.5848984) let format = "9342034\(decimalSeparator)5848984 \(AdamantUtilities.currencyCode)" let formatted = AdamantUtilities.currencyFormatter.string(for: number) @@ -75,11 +70,10 @@ class CurrencyFormatterTests: XCTestCase { } func testNegative() { - let number = -34.504 + let number = Decimal(-34.504) let format = "-34\(decimalSeparator)504 \(AdamantUtilities.currencyCode)" let formatted = AdamantUtilities.currencyFormatter.string(for: number) XCTAssertEqual(format, formatted) } - } diff --git a/AdamantTests/FeeCalculatorTests.swift b/AdamantTests/FeeTests.swift similarity index 67% rename from AdamantTests/FeeCalculatorTests.swift rename to AdamantTests/FeeTests.swift index fe7a5cdcb..dbe987bf4 100644 --- a/AdamantTests/FeeCalculatorTests.swift +++ b/AdamantTests/FeeTests.swift @@ -1,5 +1,5 @@ // -// FeeCalculatorTests.swift +// FeeTests.swift // AdamantTests // // Created by Anokhov Pavel on 16.01.2018. @@ -9,21 +9,17 @@ import XCTest @testable import Adamant -class HardFeeCalculatorTests: XCTestCase { +class FeeTests: XCTestCase { func testTransferFee() { - let amountToTransfer: UInt64 = 50000000000000000 - let estimatedFee: UInt64 = 50000000 - - let calculatedFee = AdamantFeeCalculator.estimatedFeeFor(transfer: amountToTransfer) - XCTAssertEqual(estimatedFee, calculatedFee) + let estimatedFee = Decimal(0.5) + XCTAssertEqual(estimatedFee, AdamantTransfersProvider().transferFee) } func testShortMessageFee() { let message = "A quick brown fox bought bitcoins in 2009. Good for you, mr fox. You quick brown mother fucker." - let estimatedFee: UInt64 = 100000 + let estimatedFee = Decimal(0.001) - let calculatedFee = AdamantFeeCalculator.estimatedFeeFor(message: AdamantMessage.text(message)) - XCTAssertEqual(estimatedFee, calculatedFee) + XCTAssertEqual(estimatedFee, AdamantMessage.text(message).fee) } func testLongMessageFee() { @@ -33,10 +29,9 @@ The olfactory system is reduced, suggesting that the sperm whale has a poor sens By contrast, the auditory system is enlarged. The pyramidal tract is poorly developed, reflecting the reduction of its limbs. """ - let estimatedFee: UInt64 = 200000 + let estimatedFee = Decimal(0.002) - let calculatedFee = AdamantFeeCalculator.estimatedFeeFor(message: AdamantMessage.text(message)) - XCTAssertEqual(estimatedFee, calculatedFee) + XCTAssertEqual(estimatedFee, AdamantMessage.text(message).fee) } func testVeryLongMessageFee() { @@ -88,9 +83,8 @@ I'll tell you when Mastodon / The Hunter / All The Heavy Lifting Brann Timothy Dailor / Troy Jayson Sanders / William Breen Kelliher / William Brent Hinds """ - let estimatedFee: UInt64 = 400000 + let estimatedFee = Decimal(0.004) - let calculatedFee = AdamantFeeCalculator.estimatedFeeFor(message: AdamantMessage.text(message)) - XCTAssertEqual(estimatedFee, calculatedFee) + XCTAssertEqual(estimatedFee, AdamantMessage.text(message).fee) } } diff --git a/AdamantTests/Parsing/ParsingModelsTests.swift b/AdamantTests/Parsing/ParsingModelsTests.swift index 2995bbea1..c3c5c94e9 100644 --- a/AdamantTests/Parsing/ParsingModelsTests.swift +++ b/AdamantTests/Parsing/ParsingModelsTests.swift @@ -23,8 +23,8 @@ class ParsingModelsTests: XCTestCase { XCTAssertEqual(t.senderId, "U15423595369615486571") XCTAssertEqual(t.recipientId, "U2279741505997340299") XCTAssertEqual(t.recipientPublicKey, "8007a01493bb4b21ec67265769898eb19514d9427bd7b701f96bc9880a6e209f") - XCTAssertEqual(t.amount, 49000000) - XCTAssertEqual(t.fee, 50000000) + XCTAssertEqual(t.amount, Decimal(0.49)) + XCTAssertEqual(t.fee, Decimal(0.5)) XCTAssertEqual(t.signature, "539f80c8a71abc8d4d31e5bd0d0ddb1ea98499c1d43fe5ab07faec8d376cd12357cf17bca36dc7a561085cbd615e64c523f2b17807d3f4da787baaa657aa450a") XCTAssertNil(t.signSignature) XCTAssert(t.signatures.count == 0) @@ -46,7 +46,7 @@ class ParsingModelsTests: XCTestCase { XCTAssertEqual(t.recipientId, "U15423595369615486571") XCTAssertNil(t.recipientPublicKey) XCTAssertEqual(t.amount, 0) - XCTAssertEqual(t.fee, 500000) + XCTAssertEqual(t.fee, Decimal(0.005)) XCTAssertEqual(t.signature, "7c58921d29beb5fbc7886053d81b37d8495db53848ebe04a8847f06dbcb810d8d675ea6501b1fe9b5ce7fbf9d7660a09895ac915dc82e6e8878fd0e919538c0e") XCTAssertNil(t.signSignature) XCTAssert(t.signatures.count == 0) @@ -60,8 +60,8 @@ class ParsingModelsTests: XCTestCase { let t: Account = TestTools.LoadJsonAndDecode(filename: "Account") XCTAssertEqual(t.address, "U2279741505997340299") - XCTAssertEqual(t.unconfirmedBalance, 34500000) - XCTAssertEqual(t.balance, 34500000) + XCTAssertEqual(t.unconfirmedBalance, Decimal(0.345)) + XCTAssertEqual(t.balance, Decimal(0.345)) XCTAssertEqual(t.publicKey, "8007a01493bb4b21ec67265769898eb19514d9427bd7b701f96bc9880a6e209f") XCTAssertEqual(t.unconfirmedSignature, 0) XCTAssertEqual(t.secondSignature, 0) @@ -82,7 +82,7 @@ class ParsingModelsTests: XCTestCase { let t: NormalizedTransaction = TestTools.LoadJsonAndDecode(filename: "NormalizedTransaction") XCTAssertEqual(t.type, TransactionType.send) - XCTAssertEqual(t.amount, 50505050505) + XCTAssertEqual(t.amount, Decimal(505.05050505)) XCTAssertEqual(t.senderPublicKey, "8007a01493bb4b21ec67265769898eb19514d9427bd7b701f96bc9880a6e209f") XCTAssertNil(t.requesterPublicKey) XCTAssertEqual(t.timestamp, 11236791) From 4aa4c21541893c8bc05c91e1e01fb14d2f83fa26 Mon Sep 17 00:00:00 2001 From: Pavel Anokhov Date: Mon, 12 Mar 2018 23:22:37 +0300 Subject: [PATCH 03/12] BaseTransactions: missed properties. --- Adamant/CoreData/BaseTransaction+CoreDataProperties.swift | 6 ++++-- .../ChatModels.xcdatamodeld/ChatModels.xcdatamodel/contents | 6 ++++-- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/Adamant/CoreData/BaseTransaction+CoreDataProperties.swift b/Adamant/CoreData/BaseTransaction+CoreDataProperties.swift index 16e29fdd3..d07ad5698 100644 --- a/Adamant/CoreData/BaseTransaction+CoreDataProperties.swift +++ b/Adamant/CoreData/BaseTransaction+CoreDataProperties.swift @@ -2,7 +2,7 @@ // BaseTransaction+CoreDataProperties.swift // Adamant // -// Created by Anokhov Pavel on 10.02.2018. +// Created by Anokhov Pavel on 12.03.2018. // Copyright © 2018 Adamant. All rights reserved. // // @@ -21,10 +21,12 @@ extension BaseTransaction { @NSManaged public var date: NSDate? @NSManaged public var fee: NSDecimalNumber? @NSManaged public var height: Int64 + @NSManaged public var isOutgoing: Bool @NSManaged public var recipientId: String? @NSManaged public var senderId: String? @NSManaged public var transactionId: String? @NSManaged public var type: Int16 - @NSManaged public var isOutgoing: Bool + @NSManaged public var blockId: String? + @NSManaged public var confirmations: Int64 } diff --git a/Adamant/CoreData/ChatModels.xcdatamodeld/ChatModels.xcdatamodel/contents b/Adamant/CoreData/ChatModels.xcdatamodeld/ChatModels.xcdatamodel/contents index b21d6c73e..7e73ba319 100644 --- a/Adamant/CoreData/ChatModels.xcdatamodeld/ChatModels.xcdatamodel/contents +++ b/Adamant/CoreData/ChatModels.xcdatamodeld/ChatModels.xcdatamodel/contents @@ -2,7 +2,9 @@ - + + + @@ -38,7 +40,7 @@ - + From 3977984dde944010febcfac4da5fc67438347016 Mon Sep 17 00:00:00 2001 From: Pavel Anokhov Date: Mon, 12 Mar 2018 23:44:37 +0300 Subject: [PATCH 04/12] More models fixes. --- Adamant/Models/Transaction.swift | 12 ++++----- .../DataProviders/AdamantChatsProvider.swift | 8 ++++-- .../AdamantTransfersProvider.swift | 4 ++- .../TransactionDetailsViewController.swift | 24 ++++++++++++------ Adamant/Utilities/AdamantExportTools.swift | 25 +++++++++++++------ AdamantTests/Parsing/ParsingModelsTests.swift | 4 +-- 6 files changed, 51 insertions(+), 26 deletions(-) diff --git a/Adamant/Models/Transaction.swift b/Adamant/Models/Transaction.swift index 7d1dc5be1..ce9bcd386 100644 --- a/Adamant/Models/Transaction.swift +++ b/Adamant/Models/Transaction.swift @@ -10,8 +10,8 @@ import Foundation struct Transaction { let id: UInt64 - let height: Int - let blockId: UInt64 + let height: Int64 + let blockId: String let type: TransactionType let timestamp: UInt64 let senderPublicKey: String @@ -23,7 +23,7 @@ struct Transaction { let fee: Decimal let signature: String let signSignature: String? - let confirmations: UInt64 + let confirmations: Int64 let signatures: [String] let asset: TransactionAsset @@ -55,8 +55,8 @@ extension Transaction: Decodable { let container = try decoder.container(keyedBy: CodingKeys.self) self.id = UInt64(try container.decode(String.self, forKey: .id))! - self.height = try container.decode(Int.self, forKey: .height) - self.blockId = UInt64(try container.decode(String.self, forKey: .blockId))! + self.height = try container.decode(Int64.self, forKey: .height) + self.blockId = try container.decode(String.self, forKey: .blockId) self.type = try container.decode(TransactionType.self, forKey: .type) self.timestamp = try container.decode(UInt64.self, forKey: .timestamp) self.senderPublicKey = try container.decode(String.self, forKey: .senderPublicKey) @@ -64,7 +64,7 @@ extension Transaction: Decodable { self.recipientId = try container.decode(String.self, forKey: .recipientId) self.recipientPublicKey = try? container.decode(String.self, forKey: .recipientPublicKey) self.signature = try container.decode(String.self, forKey: .signature) - self.confirmations = (try? container.decode(UInt64.self, forKey: .confirmations)) ?? 0 + self.confirmations = (try? container.decode(Int64.self, forKey: .confirmations)) ?? 0 self.requesterPublicKey = try? container.decode(String.self, forKey: .requesterPublicKey) self.signSignature = try? container.decode(String.self, forKey: .signSignature) self.signatures = try container.decode([String].self, forKey: .signatures) diff --git a/Adamant/Services/DataProviders/AdamantChatsProvider.swift b/Adamant/Services/DataProviders/AdamantChatsProvider.swift index 4652b94bf..4158f2d18 100644 --- a/Adamant/Services/DataProviders/AdamantChatsProvider.swift +++ b/Adamant/Services/DataProviders/AdamantChatsProvider.swift @@ -576,7 +576,7 @@ extension AdamantChatsProvider { for trs in transactions { unconfirmedsSemaphore.wait() if unconfirmedTransactions.count > 0, let unconfirmed = unconfirmedTransactions[trs.transaction.id] { - confirmTransaction(unconfirmed, id: trs.transaction.id, height: Int64(trs.transaction.height)) + confirmTransaction(unconfirmed, id: trs.transaction.id, height: Int64(trs.transaction.height), blockId: trs.transaction.blockId, confirmations: trs.transaction.confirmations) let h = Int64(trs.transaction.height) if height < h { height = h @@ -732,6 +732,8 @@ extension AdamantChatsProvider { chatTransaction.height = Int64(transaction.height) chatTransaction.isConfirmed = true chatTransaction.isOutgoing = isOutgoing + chatTransaction.blockId = transaction.blockId + chatTransaction.confirmations = transaction.confirmations if let decodedMessage = adamantCore.decodeMessage(rawMessage: chat.message, rawNonce: chat.ownMessage, senderPublicKey: publicKey, privateKey: privateKey) { chatTransaction.message = decodedMessage @@ -746,13 +748,15 @@ extension AdamantChatsProvider { /// - Parameters: /// - transaction: Unconfirmed transaction /// - id: New transaction id /// - height: New transaction height - private func confirmTransaction(_ transaction: ChatTransaction, id: UInt64, height: Int64) { + private func confirmTransaction(_ transaction: ChatTransaction, id: UInt64, height: Int64, blockId: String, confirmations: Int64) { if transaction.isConfirmed { return } transaction.isConfirmed = true transaction.height = height + transaction.blockId = blockId + transaction.confirmations = confirmations self.unconfirmedTransactions.removeValue(forKey: id) if let lastHeight = receivedLastHeight, lastHeight < height { diff --git a/Adamant/Services/DataProviders/AdamantTransfersProvider.swift b/Adamant/Services/DataProviders/AdamantTransfersProvider.swift index c63057f33..f4e499270 100644 --- a/Adamant/Services/DataProviders/AdamantTransfersProvider.swift +++ b/Adamant/Services/DataProviders/AdamantTransfersProvider.swift @@ -237,7 +237,7 @@ extension AdamantTransfersProvider { } var totalTransactions = 0 - var height = 0 + var height: Int64 = 0 for t in transactions { let transfer = TransferTransaction(entity: TransferTransaction.entity(), insertInto: context) transfer.amount = t.amount as NSDecimalNumber @@ -248,6 +248,8 @@ extension AdamantTransfersProvider { transfer.senderId = t.senderId transfer.transactionId = String(t.id) transfer.type = Int16(t.type.rawValue) + transfer.blockId = t.blockId + transfer.confirmations = t.confirmations transfer.isOutgoing = t.senderId == address diff --git a/Adamant/Stories/Account/TransactionDetailsViewController.swift b/Adamant/Stories/Account/TransactionDetailsViewController.swift index 5b3ac8761..40e7d7bab 100644 --- a/Adamant/Stories/Account/TransactionDetailsViewController.swift +++ b/Adamant/Stories/Account/TransactionDetailsViewController.swift @@ -52,7 +52,7 @@ class TransactionDetailsViewController: UIViewController { // MARK: - Properties private let cellIdentifier = "cell" - var transaction: Transaction? + var transaction: TransferTransaction? var explorerUrl: URL! // MARK: - IBOutlets @@ -66,7 +66,9 @@ class TransactionDetailsViewController: UIViewController { if let transaction = transaction { tableView.reloadData() - explorerUrl = URL(string: "https://explorer.adamant.im/tx/\(transaction.id)") + if let id = transaction.transactionId { + explorerUrl = URL(string: "https://explorer.adamant.im/tx/\(id)") + } } else { self.navigationItem.rightBarButtonItems = nil } @@ -209,19 +211,27 @@ extension TransactionDetailsViewController { switch row { case .amount: - cell.detailTextLabel?.text = AdamantUtilities.format(balance: transaction.amount) + if let amount = transaction.amount { + cell.detailTextLabel?.text = AdamantUtilities.format(balance: amount) + } case .date: - cell.detailTextLabel?.text = DateFormatter.localizedString(from: transaction.date, dateStyle: .short, timeStyle: .medium) + if let date = transaction.date as Date? { + cell.detailTextLabel?.text = DateFormatter.localizedString(from: date, dateStyle: .short, timeStyle: .medium) + } case .confirmations: cell.detailTextLabel?.text = String(transaction.confirmations) case .fee: - cell.detailTextLabel?.text = AdamantUtilities.format(balance: transaction.fee) + if let fee = transaction.fee { + cell.detailTextLabel?.text = AdamantUtilities.format(balance: fee) + } case .transactionNumber: - cell.detailTextLabel?.text = String(transaction.id) + if let id = transaction.transactionId { + cell.detailTextLabel?.text = String(id) + } case .from: cell.detailTextLabel?.text = transaction.senderId @@ -230,7 +240,7 @@ extension TransactionDetailsViewController { cell.detailTextLabel?.text = transaction.recipientId case .block: - cell.detailTextLabel?.text = String(transaction.blockId) + cell.detailTextLabel?.text = transaction.blockId case .openInExplorer: cell.detailTextLabel?.text = nil diff --git a/Adamant/Utilities/AdamantExportTools.swift b/Adamant/Utilities/AdamantExportTools.swift index f9cfb803a..f7f7d2a10 100644 --- a/Adamant/Utilities/AdamantExportTools.swift +++ b/Adamant/Utilities/AdamantExportTools.swift @@ -9,18 +9,27 @@ import Foundation class AdamantExportTools { + static func summaryFor(transaction: BaseTransaction, url: URL) -> String { + return summaryFor(id: transaction.blockId!, sender: transaction.senderId!, recipient: transaction.recipientId!, date: transaction.date as Date!, amount: transaction.amount as Decimal!, fee: transaction.fee as Decimal!, confirmations: transaction.confirmations, blockId: transaction.blockId!, url: url) + } + static func summaryFor(transaction: Transaction, url: URL) -> String { + return summaryFor(id: String(transaction.id), sender: transaction.senderId, recipient: transaction.recipientId, date: transaction.date, amount: transaction.amount, fee: transaction.fee, confirmations: transaction.confirmations, blockId: transaction.blockId, url: url) + } + + private static func summaryFor(id: String, sender: String, recipient: String, date: Date, amount: Decimal, fee: Decimal, confirmations: Int64, blockId: String, url: URL) -> String { + return """ -Transaction #\(String(transaction.id)) +Transaction #\(id) Summary -Sender: \(transaction.senderId) -Recipient: \(transaction.recipientId) -Date: \(DateFormatter.localizedString(from: transaction.date, dateStyle: .short, timeStyle: .medium)) -Amount: \(AdamantUtilities.format(balance: transaction.amount)) -Fee: \(AdamantUtilities.format(balance: transaction.fee)) -Confirmations: \(String(transaction.confirmations)) -Block: \(transaction.blockId) +Sender: \(sender) +Recipient: \(recipient) +Date: \(DateFormatter.localizedString(from: date, dateStyle: .short, timeStyle: .medium)) +Amount: \(AdamantUtilities.format(balance: amount)) +Fee: \(AdamantUtilities.format(balance: fee)) +Confirmations: \(String(confirmations)) +Block: \(blockId) URL: \(url) """ } diff --git a/AdamantTests/Parsing/ParsingModelsTests.swift b/AdamantTests/Parsing/ParsingModelsTests.swift index c3c5c94e9..29864f460 100644 --- a/AdamantTests/Parsing/ParsingModelsTests.swift +++ b/AdamantTests/Parsing/ParsingModelsTests.swift @@ -15,7 +15,7 @@ class ParsingModelsTests: XCTestCase { XCTAssertEqual(t.id, 1873173140086400619) XCTAssertEqual(t.height, 777336) - XCTAssertEqual(t.blockId, 10172499053153614044) + XCTAssertEqual(t.blockId, "10172499053153614044") XCTAssertEqual(t.type, TransactionType.send) XCTAssertEqual(t.timestamp, 10724447) XCTAssertEqual(t.senderPublicKey, "cdab95b082b9774bd975677c868261618c7ce7bea97d02e0f56d483e30c077b6") @@ -37,7 +37,7 @@ class ParsingModelsTests: XCTestCase { XCTAssertEqual(t.id, 16214962152767034408) XCTAssertEqual(t.height, 857385) - XCTAssertEqual(t.blockId, 11054360802486546958) + XCTAssertEqual(t.blockId, "11054360802486546958") XCTAssertEqual(t.type, TransactionType(rawValue: 8)) XCTAssertEqual(t.timestamp, 11138999) XCTAssertEqual(t.senderPublicKey, "8007a01493bb4b21ec67265769898eb19514d9427bd7b701f96bc9880a6e209f") From 0c0ed58bda78a7459f9523bd96aa96b85ff1157a Mon Sep 17 00:00:00 2001 From: Pavel Anokhov Date: Mon, 12 Mar 2018 23:58:36 +0300 Subject: [PATCH 05/12] TransactionDetails updated. --- .../Stories/Account/AccountDependencies.swift | 2 -- .../Account/TransactionsViewController.swift | 23 ++++++++----------- 2 files changed, 9 insertions(+), 16 deletions(-) diff --git a/Adamant/Stories/Account/AccountDependencies.swift b/Adamant/Stories/Account/AccountDependencies.swift index 1b7746612..314d8b4a2 100644 --- a/Adamant/Stories/Account/AccountDependencies.swift +++ b/Adamant/Stories/Account/AccountDependencies.swift @@ -16,9 +16,7 @@ extension Container { c.router = r.resolve(Router.self) } self.storyboardInitCompleted(TransactionsViewController.self) { (r, c) in - c.apiService = r.resolve(ApiService.self) c.cellFactory = r.resolve(CellFactory.self) - c.dialogService = r.resolve(DialogService.self) c.transfersProvider = r.resolve(TransfersProvider.self) } self.storyboardInitCompleted(TransferViewController.self) { (r, c) in diff --git a/Adamant/Stories/Account/TransactionsViewController.swift b/Adamant/Stories/Account/TransactionsViewController.swift index 257aa3525..632753170 100644 --- a/Adamant/Stories/Account/TransactionsViewController.swift +++ b/Adamant/Stories/Account/TransactionsViewController.swift @@ -12,14 +12,9 @@ import CoreData class TransactionsViewController: UIViewController { // MARK: - Dependencies var cellFactory: CellFactory! - var apiService: ApiService! - var dialogService: DialogService! var transfersProvider: TransfersProvider! // MARK: - Properties - private(set) var transactions: [Transaction]? - private var updatingTransactions: Bool = false - var controller: NSFetchedResultsController? let transactionDetailsSegue = "showTransactionDetails" @@ -51,14 +46,13 @@ class TransactionsViewController: UIViewController { } } - // TODO: -// override func prepare(for segue: UIStoryboardSegue, sender: Any?) { -// if segue.identifier == transactionDetailsSegue, -// let vc = segue.destination as? TransactionDetailsViewController, -// let transaction = sender as? Transaction{ -// vc.transaction = transaction -// } -// } + override func prepare(for segue: UIStoryboardSegue, sender: Any?) { + if segue.identifier == transactionDetailsSegue, + let vc = segue.destination as? TransactionDetailsViewController, + let transaction = sender as? TransferTransaction { + vc.transaction = transaction + } + } } // MARK: - UITableView Cells @@ -111,7 +105,8 @@ extension TransactionsViewController: UITableViewDataSource, UITableViewDelegate } func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { - guard let transaction = transactions?[indexPath.row] else { + guard let transaction = controller?.object(at: indexPath) else { + tableView.deselectRow(at: indexPath, animated: true) return } From b7fb736fa11c103acd4984676239bdea947c2338 Mon Sep 17 00:00:00 2001 From: Pavel Anokhov Date: Tue, 13 Mar 2018 17:29:24 +0300 Subject: [PATCH 06/12] Updating transfers. Notifications with transfers. --- Adamant.xcodeproj/project.pbxproj | 14 ++- Adamant/AppDelegate.swift | 82 ++++++++--------- .../Assets/Base.lproj/Localizable.stringsdict | 22 ++++- Adamant/Assets/ru.lproj/Localizable.strings | 3 + .../Assets/ru.lproj/Localizable.stringsdict | 26 +++++- .../ChatModels.xcdatamodel/contents | 3 +- ...ansferTransaction+CoreDataProperties.swift | 3 +- Adamant/ServiceProtocols/ApiService.swift | 2 +- .../BackgroundFetchService.swift | 19 ++++ .../DataProviders/TransfersProvider.swift | 17 ++++ .../NotificationsService.swift | 7 +- .../ApiService/AdamantApi+Transactions.swift | 2 +- ...AdamantChatsProvider+backgroundFetch.swift | 59 +++++++++++++ .../DataProviders/AdamantChatsProvider.swift | 9 +- ...antTransfersProvider+backgroundFetch.swift | 62 +++++++++++++ .../AdamantTransfersProvider.swift | 88 ++++++++++++++++--- Adamant/SharedCells/ChatTableViewCell.swift | 1 - .../SharedCells/TransactionTableViewCell.xib | 10 +-- .../Account/TransactionsViewController.swift | 4 + Adamant/SwinjectDependencies.swift | 16 ++++ 20 files changed, 368 insertions(+), 81 deletions(-) create mode 100644 Adamant/ServiceProtocols/BackgroundFetchService.swift create mode 100644 Adamant/Services/DataProviders/AdamantChatsProvider+backgroundFetch.swift create mode 100644 Adamant/Services/DataProviders/AdamantTransfersProvider+backgroundFetch.swift diff --git a/Adamant.xcodeproj/project.pbxproj b/Adamant.xcodeproj/project.pbxproj index a767467e3..1cee89833 100644 --- a/Adamant.xcodeproj/project.pbxproj +++ b/Adamant.xcodeproj/project.pbxproj @@ -87,6 +87,9 @@ E9942B84203CBFCE00C163AF /* AdamantQRTools.swift in Sources */ = {isa = PBXBuildFile; fileRef = E9942B83203CBFCE00C163AF /* AdamantQRTools.swift */; }; E9942B87203D9E5100C163AF /* EurekaQRRow.swift in Sources */ = {isa = PBXBuildFile; fileRef = E9942B86203D9E5100C163AF /* EurekaQRRow.swift */; }; E9942B89203D9ECA00C163AF /* QrCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = E9942B88203D9ECA00C163AF /* QrCell.xib */; }; + E9A174B32057EC47003667CD /* BackgroundFetchService.swift in Sources */ = {isa = PBXBuildFile; fileRef = E9A174B22057EC47003667CD /* BackgroundFetchService.swift */; }; + E9A174B52057EDCE003667CD /* AdamantTransfersProvider+backgroundFetch.swift in Sources */ = {isa = PBXBuildFile; fileRef = E9A174B42057EDCE003667CD /* AdamantTransfersProvider+backgroundFetch.swift */; }; + E9A174B72057F1B3003667CD /* AdamantChatsProvider+backgroundFetch.swift in Sources */ = {isa = PBXBuildFile; fileRef = E9A174B62057F1B3003667CD /* AdamantChatsProvider+backgroundFetch.swift */; }; E9B3D39A201F90570019EB36 /* AccountsProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = E9B3D399201F90570019EB36 /* AccountsProvider.swift */; }; E9B3D39E201F99F40019EB36 /* DataProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = E9B3D39D201F99F40019EB36 /* DataProvider.swift */; }; E9B3D3A1201FA26B0019EB36 /* AdamantAccountsProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = E9B3D3A0201FA26B0019EB36 /* AdamantAccountsProvider.swift */; }; @@ -264,6 +267,9 @@ E9942B83203CBFCE00C163AF /* AdamantQRTools.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AdamantQRTools.swift; sourceTree = ""; }; E9942B86203D9E5100C163AF /* EurekaQRRow.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EurekaQRRow.swift; sourceTree = ""; }; E9942B88203D9ECA00C163AF /* QrCell.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = QrCell.xib; sourceTree = ""; }; + E9A174B22057EC47003667CD /* BackgroundFetchService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BackgroundFetchService.swift; sourceTree = ""; }; + E9A174B42057EDCE003667CD /* AdamantTransfersProvider+backgroundFetch.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "AdamantTransfersProvider+backgroundFetch.swift"; sourceTree = ""; }; + E9A174B62057F1B3003667CD /* AdamantChatsProvider+backgroundFetch.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "AdamantChatsProvider+backgroundFetch.swift"; sourceTree = ""; }; E9B3D399201F90570019EB36 /* AccountsProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccountsProvider.swift; sourceTree = ""; }; E9B3D39D201F99F40019EB36 /* DataProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DataProvider.swift; sourceTree = ""; }; E9B3D3A0201FA26B0019EB36 /* AdamantAccountsProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AdamantAccountsProvider.swift; sourceTree = ""; }; @@ -410,6 +416,7 @@ E905D39C204C13B900DDB504 /* SecuredStore.swift */, E90A494C204DA932009F6A65 /* LocalAuthentication.swift */, E93D7ABD2052CEE1005D19DC /* NotificationsService.swift */, + E9A174B22057EC47003667CD /* BackgroundFetchService.swift */, ); path = ServiceProtocols; sourceTree = ""; @@ -420,6 +427,7 @@ E9CAE8D02018AA5000345E76 /* ApiService */, E9B3D39F201FA2090019EB36 /* DataProviders */, E9E7CD922002740500DFC4DB /* AdamantAccountService.swift */, + E90A494A204D9EB8009F6A65 /* AdamantAuthentication.swift */, E9E7CDBF2003AF6D00DFC4DB /* AdamantCellFactory.swift */, E9E7CD8E20026CD300DFC4DB /* AdamantDialogService.swift */, E93D7ABF2052CF63005D19DC /* AdamantNotificationService.swift */, @@ -427,7 +435,6 @@ E9E7CDB42002BA6900DFC4DB /* SwinjectedRouter.swift */, E950273F202E257E002C1098 /* RepeaterService.swift */, E905D39A2048A9BD00DDB504 /* KeychainStore.swift */, - E90A494A204D9EB8009F6A65 /* AdamantAuthentication.swift */, ); path = Services; sourceTree = ""; @@ -602,7 +609,9 @@ children = ( E9B3D3A0201FA26B0019EB36 /* AdamantAccountsProvider.swift */, E93B0D752028B28E00126346 /* AdamantChatsProvider.swift */, + E9A174B62057F1B3003667CD /* AdamantChatsProvider+backgroundFetch.swift */, E9B3D3A8202082450019EB36 /* AdamantTransfersProvider.swift */, + E9A174B42057EDCE003667CD /* AdamantTransfersProvider+backgroundFetch.swift */, E9722067201F42CC004F2AAD /* InMemoryCoreDataStack.swift */, ); path = DataProviders; @@ -957,6 +966,7 @@ E9E7CDAF2002B8A100DFC4DB /* Router.swift in Sources */, E9E7CDC72003F6D200DFC4DB /* TransactionTableViewCell.swift in Sources */, E9B3D3A9202082450019EB36 /* AdamantTransfersProvider.swift in Sources */, + E9A174B72057F1B3003667CD /* AdamantChatsProvider+backgroundFetch.swift in Sources */, E948E04E20278D5600975D6B /* SettingsDependencies.swift in Sources */, E9E7CDB32002B9FB00DFC4DB /* LoginRoutes.swift in Sources */, E91947B22000246A001362F8 /* AdamantError.swift in Sources */, @@ -985,6 +995,7 @@ E9FAE5DA203DBFEF008D3A6B /* Comparable+Clamped.swift in Sources */, E95F857A2007F0260070534A /* ServerResponse.swift in Sources */, E9CE778B202B9DC2009CF70D /* CoreDataAccount+CoreDataClass.swift in Sources */, + E9A174B32057EC47003667CD /* BackgroundFetchService.swift in Sources */, E9CE7789202B9DC2009CF70D /* ChatTransaction+CoreDataClass.swift in Sources */, E9E7CDBE2003AEFB00DFC4DB /* CellFactory.swift in Sources */, E9CAE8D62018AC5300345E76 /* AdamantApi+Transactions.swift in Sources */, @@ -1006,6 +1017,7 @@ E948E04C2027679300975D6B /* AdamantExportTools.swift in Sources */, E9E7CDB12002B97B00DFC4DB /* AccountRoutes.swift in Sources */, E95F856B200789450070534A /* JSModels.swift in Sources */, + E9A174B52057EDCE003667CD /* AdamantTransfersProvider+backgroundFetch.swift in Sources */, E9CE778C202B9DC2009CF70D /* CoreDataAccount+CoreDataProperties.swift in Sources */, E9CE7788202B9DC2009CF70D /* BaseTransaction+CoreDataProperties.swift in Sources */, E9147B5F20500E9300145913 /* MyLittlePinpad+adamant.swift in Sources */, diff --git a/Adamant/AppDelegate.swift b/Adamant/AppDelegate.swift index dfee42cad..2f9c3e80e 100644 --- a/Adamant/AppDelegate.swift +++ b/Adamant/AppDelegate.swift @@ -127,67 +127,57 @@ class AppDelegate: UIResponder, UIApplicationDelegate { notificationService?.removeAllDeliveredNotifications() } } - - // MARK: Background fetch - +} + + +// MARK: - BackgroundFetch +extension AppDelegate { func application(_ application: UIApplication, performFetchWithCompletionHandler completionHandler: @escaping (UIBackgroundFetchResult) -> Void) { let container = Container() container.registerAdamantBackgroundFetchServices() - guard let securedStore = container.resolve(SecuredStore.self), - let apiService = container.resolve(ApiService.self), - let notificationsService = container.resolve(NotificationsService.self) else { - UIApplication.shared.setMinimumBackgroundFetchInterval(UIApplicationBackgroundFetchIntervalNever) - completionHandler(.failed) - return + guard let notificationsService = container.resolve(NotificationsService.self) else { + UIApplication.shared.setMinimumBackgroundFetchInterval(UIApplicationBackgroundFetchIntervalNever) + completionHandler(.failed) + return } - guard let address = securedStore.get(StoreKey.chatProvider.address) else { - UIApplication.shared.setMinimumBackgroundFetchInterval(UIApplicationBackgroundFetchIntervalNever) - completionHandler(.failed) - return - } + let services: [BackgroundFetchService] = [ + container.resolve(ChatsProvider.self) as! BackgroundFetchService, + container.resolve(TransfersProvider.self) as! BackgroundFetchService + ] - var lastHeight: Int64? - if let raw = securedStore.get(StoreKey.chatProvider.receivedLastHeight) { - lastHeight = Int64(raw) - } else { - lastHeight = nil - } + let group = DispatchGroup() + let semaphore = DispatchSemaphore(value: 1) + var results = [FetchResult]() - var notifiedCount = 0 - if let raw = securedStore.get(StoreKey.chatProvider.notifiedLastHeight), let notifiedHeight = Int64(raw), let h = lastHeight { - if h < notifiedHeight { - lastHeight = notifiedHeight - - if let raw = securedStore.get(StoreKey.chatProvider.notifiedMessagesCount), let count = Int(raw) { - notifiedCount = count + for service in services { + group.enter() + service.fetchBackgroundData(notificationService: notificationsService) { result in + defer { + group.leave() } + + semaphore.wait() + results.append(result) + semaphore.signal() } } + group.wait() - apiService.getChatTransactions(address: address, height: lastHeight, offset: nil) { result in + for result in results { switch result { - case .success(let transactions): - if transactions.count > 0 { - let total = transactions.count + notifiedCount - securedStore.set(String(total), for: StoreKey.chatProvider.notifiedMessagesCount) - - if let newLastHeight = transactions.map({$0.height}).sorted().last { - securedStore.set(String(newLastHeight), for: StoreKey.chatProvider.notifiedLastHeight) - } - - - notificationsService.showNotification(title: String.adamantLocalized.notifications.newMessageTitle, body: String.localizedStringWithFormat(String.adamantLocalized.notifications.newMessageBody, total), type: .newMessages(count: total)) - completionHandler(.newData) - } else { - completionHandler(.noData) - } + case .newData: + completionHandler(.newData) + return + + case .noData: + break - case .failure(_): + case .failed: completionHandler(.failed) + return } } - } -} + }} diff --git a/Adamant/Assets/Base.lproj/Localizable.stringsdict b/Adamant/Assets/Base.lproj/Localizable.stringsdict index 8031fe409..7c8e3072f 100644 --- a/Adamant/Assets/Base.lproj/Localizable.stringsdict +++ b/Adamant/Assets/Base.lproj/Localizable.stringsdict @@ -5,7 +5,7 @@ You have %d new message(s) NSStringLocalizedFormatKey - %#@messages@ + You have %#@messages@ messages NSStringFormatSpecTypeKey @@ -13,9 +13,25 @@ NSStringFormatValueTypeKey d one - You have %d new message + %d new message other - You have %d new messages + %d new messages + + + You have %d new transfer(s) + + NSStringLocalizedFormatKey + You have %#@transfers@ + transfers + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + d + one + %d new transaction + other + %d new transactions diff --git a/Adamant/Assets/ru.lproj/Localizable.strings b/Adamant/Assets/ru.lproj/Localizable.strings index 785cfd442..591f58afa 100644 --- a/Adamant/Assets/ru.lproj/Localizable.strings +++ b/Adamant/Assets/ru.lproj/Localizable.strings @@ -138,6 +138,9 @@ Notifications: New message notification title */ "New message" = "Новое сообщение"; +/* Notifications: New transfer transaction title */ +"New transfer" = "Новый перевод"; + /* ApiService: No connection message. Generally bad network. */ "No connection" = "Нет соединения с интернетом"; diff --git a/Adamant/Assets/ru.lproj/Localizable.stringsdict b/Adamant/Assets/ru.lproj/Localizable.stringsdict index 3f295e1d6..78a262f35 100644 --- a/Adamant/Assets/ru.lproj/Localizable.stringsdict +++ b/Adamant/Assets/ru.lproj/Localizable.stringsdict @@ -5,7 +5,7 @@ You have %d new message(s) NSStringLocalizedFormatKey - %#@messages@ + У вас %#@messages@ messages NSStringFormatSpecTypeKey @@ -13,11 +13,29 @@ NSStringFormatValueTypeKey d one - У вас %d новое сообщение + %d новое сообщение many - У вас %d новых сообщений + %d новых сообщений other - У вас %d новых сообщения + %d новых сообщения + + + You have %d new transfer(s) + + NSStringLocalizedFormatKey + У вас %#@transfers@ + transfers + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + d + one + %d новая транзакция + many + %d новых транзакций + other + %d новые транзакции diff --git a/Adamant/CoreData/ChatModels.xcdatamodeld/ChatModels.xcdatamodel/contents b/Adamant/CoreData/ChatModels.xcdatamodeld/ChatModels.xcdatamodel/contents index 7e73ba319..c0e261238 100644 --- a/Adamant/CoreData/ChatModels.xcdatamodeld/ChatModels.xcdatamodel/contents +++ b/Adamant/CoreData/ChatModels.xcdatamodeld/ChatModels.xcdatamodel/contents @@ -37,6 +37,7 @@ + @@ -44,6 +45,6 @@ - + \ No newline at end of file diff --git a/Adamant/CoreData/TransferTransaction+CoreDataProperties.swift b/Adamant/CoreData/TransferTransaction+CoreDataProperties.swift index f81f17943..d96c24883 100644 --- a/Adamant/CoreData/TransferTransaction+CoreDataProperties.swift +++ b/Adamant/CoreData/TransferTransaction+CoreDataProperties.swift @@ -2,7 +2,7 @@ // TransferTransaction+CoreDataProperties.swift // Adamant // -// Created by Anokhov Pavel on 07.02.2018. +// Created by Anokhov Pavel on 13.03.2018. // Copyright © 2018 Adamant. All rights reserved. // // @@ -17,6 +17,7 @@ extension TransferTransaction { return NSFetchRequest(entityName: "TransferTransaction") } + @NSManaged public var isUnread: Bool @NSManaged public var partner: CoreDataAccount? } diff --git a/Adamant/ServiceProtocols/ApiService.swift b/Adamant/ServiceProtocols/ApiService.swift index 24430f150..fa233e451 100644 --- a/Adamant/ServiceProtocols/ApiService.swift +++ b/Adamant/ServiceProtocols/ApiService.swift @@ -70,7 +70,7 @@ protocol ApiService: class { // MARK: - Transactions func getTransaction(id: UInt64, completion: @escaping (ApiServiceResult) -> Void) - func getTransactions(forAccount: String, type: TransactionType, fromHeight: UInt64?, completion: @escaping (ApiServiceResult<[Transaction]>) -> Void) + func getTransactions(forAccount: String, type: TransactionType, fromHeight: Int64?, completion: @escaping (ApiServiceResult<[Transaction]>) -> Void) // MARK: - Funds diff --git a/Adamant/ServiceProtocols/BackgroundFetchService.swift b/Adamant/ServiceProtocols/BackgroundFetchService.swift new file mode 100644 index 000000000..cb2c6c878 --- /dev/null +++ b/Adamant/ServiceProtocols/BackgroundFetchService.swift @@ -0,0 +1,19 @@ +// +// BackgroundFetchService.swift +// Adamant +// +// Created by Anokhov Pavel on 13.03.2018. +// Copyright © 2018 Adamant. All rights reserved. +// + +import Foundation + +enum FetchResult { + case newData + case noData + case failed +} + +protocol BackgroundFetchService { + func fetchBackgroundData(notificationService: NotificationsService, completion: @escaping (FetchResult) -> Void) +} diff --git a/Adamant/ServiceProtocols/DataProviders/TransfersProvider.swift b/Adamant/ServiceProtocols/DataProviders/TransfersProvider.swift index 1778fe255..d1afa6f0e 100644 --- a/Adamant/ServiceProtocols/DataProviders/TransfersProvider.swift +++ b/Adamant/ServiceProtocols/DataProviders/TransfersProvider.swift @@ -43,10 +43,27 @@ extension AdamantUserInfoKey { } } +extension StoreKey { + struct transfersProvider { + static let address = "transfersProvider.address" + static let receivedLastHeight = "transfersProvider.receivedLastHeight" + static let readedLastHeight = "transfersProvider.readedLastHeight" + static let notifiedLastHeight = "transfersProvider.notifiedLastHeight" + static let notifiedTransfersCount = "transfersProvider.notifiedCount" + } +} + protocol TransfersProvider: DataProvider { + // MARK: - Properties + var receivedLastHeight: Int64? { get } + var readedLastHeight: Int64? { get } + var transferFee: Decimal { get } + // MARK: Controller func transfersController() -> NSFetchedResultsController + func unreadTransfersController() -> NSFetchedResultsController + // MARK: - Sending funds func transferFunds(toAddress recipient: String, amount: Decimal, completion: @escaping (TransfersProviderResult) -> Void) } diff --git a/Adamant/ServiceProtocols/NotificationsService.swift b/Adamant/ServiceProtocols/NotificationsService.swift index ccac67091..f922e5bbc 100644 --- a/Adamant/ServiceProtocols/NotificationsService.swift +++ b/Adamant/ServiceProtocols/NotificationsService.swift @@ -13,7 +13,10 @@ extension String.adamantLocalized { static let notificationsDisabled = NSLocalizedString("Notifications disabled. You can reenable notifications in Settings", comment: "Notifications: User has disabled notifications. Head him into settings") static let newMessageTitle = NSLocalizedString("New message", comment: "Notifications: New message notification title") - static let newMessageBody = NSLocalizedString("You have %d new message(s)", comment: "Notifications: new messages notification details") + static let newMessageBody = NSLocalizedString("You have %d new message(s)", comment: "Notifications: new messages notification body") + + static let newTransferTitle = NSLocalizedString("New transfer", comment: "Notifications: New transfer transaction title") + static let newTransferBody = NSLocalizedString("You have %d new transfer(s)", comment: "Notifications: New transfer notification body") private init() {} } @@ -86,6 +89,4 @@ protocol NotificationsService: class { func removeAllPendingNotificationRequests() func removeAllDeliveredNotifications() -// func removeAllPendingNotificationRequests(ofType type: AdamantNotificationType) -// func removeAllDeliveredNotifications(ofType type: AdamantNotificationType) } diff --git a/Adamant/Services/ApiService/AdamantApi+Transactions.swift b/Adamant/Services/ApiService/AdamantApi+Transactions.swift index e9dead971..5b4818b2f 100644 --- a/Adamant/Services/ApiService/AdamantApi+Transactions.swift +++ b/Adamant/Services/ApiService/AdamantApi+Transactions.swift @@ -44,7 +44,7 @@ extension AdamantApiService { } } - func getTransactions(forAccount account: String, type: TransactionType, fromHeight: UInt64?, completion: @escaping (ApiServiceResult<[Transaction]>) -> Void) { + func getTransactions(forAccount account: String, type: TransactionType, fromHeight: Int64?, completion: @escaping (ApiServiceResult<[Transaction]>) -> Void) { var queryItems = [URLQueryItem(name: "inId", value: account), URLQueryItem(name: "and:type", value: String(type.rawValue))] diff --git a/Adamant/Services/DataProviders/AdamantChatsProvider+backgroundFetch.swift b/Adamant/Services/DataProviders/AdamantChatsProvider+backgroundFetch.swift new file mode 100644 index 000000000..17ce3bd1b --- /dev/null +++ b/Adamant/Services/DataProviders/AdamantChatsProvider+backgroundFetch.swift @@ -0,0 +1,59 @@ +// +// AdamantChatsProvider+backgroundFetch.swift +// Adamant +// +// Created by Anokhov Pavel on 13.03.2018. +// Copyright © 2018 Adamant. All rights reserved. +// + +import Foundation + +extension AdamantChatsProvider: BackgroundFetchService { + func fetchBackgroundData(notificationService: NotificationsService, completion: @escaping (FetchResult) -> Void) { + guard let address = securedStore.get(StoreKey.chatProvider.address) else { + completion(.failed) + return + } + + var lastHeight: Int64? + if let raw = securedStore.get(StoreKey.chatProvider.receivedLastHeight) { + lastHeight = Int64(raw) + } else { + lastHeight = nil + } + + var notifiedCount = 0 + if let raw = securedStore.get(StoreKey.chatProvider.notifiedLastHeight), let notifiedHeight = Int64(raw), let h = lastHeight { + if h < notifiedHeight { + lastHeight = notifiedHeight + + if let raw = securedStore.get(StoreKey.chatProvider.notifiedMessagesCount), let count = Int(raw) { + notifiedCount = count + } + } + } + + apiService.getChatTransactions(address: address, height: lastHeight, offset: nil) { [weak self] result in + switch result { + case .success(let transactions): + if transactions.count > 0 { + let total = transactions.count + notifiedCount + self?.securedStore.set(String(total), for: StoreKey.chatProvider.notifiedMessagesCount) + + if let newLastHeight = transactions.map({$0.height}).sorted().last { + self?.securedStore.set(String(newLastHeight), for: StoreKey.chatProvider.notifiedLastHeight) + } + + notificationService.showNotification(title: String.adamantLocalized.notifications.newMessageTitle, body: String.localizedStringWithFormat(String.adamantLocalized.notifications.newMessageBody, total), type: .newMessages(count: total)) + + completion(.newData) + } else { + completion(.noData) + } + + case .failure(_): + completion(.failed) + } + } + } +} diff --git a/Adamant/Services/DataProviders/AdamantChatsProvider.swift b/Adamant/Services/DataProviders/AdamantChatsProvider.swift index 4158f2d18..cc6478b8d 100644 --- a/Adamant/Services/DataProviders/AdamantChatsProvider.swift +++ b/Adamant/Services/DataProviders/AdamantChatsProvider.swift @@ -45,12 +45,16 @@ class AdamantChatsProvider: ChatsProvider { NotificationCenter.default.addObserver(forName: Notification.Name.adamantUserLoggedOut, object: nil, queue: nil) { [weak self] _ in self?.receivedLastHeight = nil + self?.readedLastHeight = nil if let prevState = self?.state { self?.setState(.empty, previous: prevState, notify: true) } - self?.securedStore.remove(StoreKey.chatProvider.address) - self?.securedStore.remove(StoreKey.chatProvider.receivedLastHeight) + if let store = self?.securedStore { + store.remove(StoreKey.chatProvider.address) + store.remove(StoreKey.chatProvider.receivedLastHeight) + store.remove(StoreKey.chatProvider.readedLastHeight) + } } } @@ -97,6 +101,7 @@ extension AdamantChatsProvider { let prevState = self.state setState(.updating, previous: prevState, notify: false) // Block update calls receivedLastHeight = nil + readedLastHeight = nil let chatrooms = NSFetchRequest(entityName: Chatroom.entityName) let context = NSManagedObjectContext(concurrencyType: .privateQueueConcurrencyType) diff --git a/Adamant/Services/DataProviders/AdamantTransfersProvider+backgroundFetch.swift b/Adamant/Services/DataProviders/AdamantTransfersProvider+backgroundFetch.swift new file mode 100644 index 000000000..25aabc586 --- /dev/null +++ b/Adamant/Services/DataProviders/AdamantTransfersProvider+backgroundFetch.swift @@ -0,0 +1,62 @@ +// +// AdamantTransfersProvider+backgroundFetch.swift +// Adamant +// +// Created by Anokhov Pavel on 13.03.2018. +// Copyright © 2018 Adamant. All rights reserved. +// + +import Foundation + +extension AdamantTransfersProvider: BackgroundFetchService { + func fetchBackgroundData(notificationService: NotificationsService, completion: @escaping (FetchResult) -> Void) { + guard let address = securedStore.get(StoreKey.transfersProvider.address) else { + completion(.failed) + return + } + + var lastHeight: Int64? + if let raw = securedStore.get(StoreKey.transfersProvider.receivedLastHeight) { + lastHeight = Int64(raw) + } else { + lastHeight = nil + } + + var notifiedCount = 0 + if let raw = securedStore.get(StoreKey.transfersProvider.notifiedLastHeight), let notifiedHeight = Int64(raw), let h = lastHeight { + if h < notifiedHeight { + lastHeight = notifiedHeight + + if let raw = securedStore.get(StoreKey.transfersProvider.notifiedTransfersCount), let count = Int(raw) { + notifiedCount = count + } + } + } + + apiService.getTransactions(forAccount: address, type: .send, fromHeight: lastHeight) { [weak self] result in + switch result { + case .success(let transactions): + let income = transactions.filter({$0.recipientId == address}).count + + if income > 0 { + let total = income + notifiedCount + self?.securedStore.set(String(total), for: StoreKey.transfersProvider.notifiedTransfersCount) + + if var newLastHeight = transactions.map({$0.height}).sorted().last { +// newLastHeight += 1 // Server will return new transactions including this one + self?.securedStore.set(String(newLastHeight), for: StoreKey.transfersProvider.notifiedLastHeight) + } + + notificationService.showNotification(title: String.adamantLocalized.notifications.newTransferTitle, body: String.localizedStringWithFormat(String.adamantLocalized.notifications.newTransferBody, total), type: .newTransactions(count: total)) + + completion(.newData) + } else { + completion(.noData) + } + + case .failure(_): + completion(.failed) + } + } + } +} diff --git a/Adamant/Services/DataProviders/AdamantTransfersProvider.swift b/Adamant/Services/DataProviders/AdamantTransfersProvider.swift index f4e499270..3fb86ddbe 100644 --- a/Adamant/Services/DataProviders/AdamantTransfersProvider.swift +++ b/Adamant/Services/DataProviders/AdamantTransfersProvider.swift @@ -15,12 +15,14 @@ class AdamantTransfersProvider: TransfersProvider { var stack: CoreDataStack! var accountService: AccountService! var accountsProvider: AccountsProvider! + var securedStore: SecuredStore! // MARK: Properties var transferFee: Decimal = Decimal(sign: .plus, exponent: -1, significand: 5) private(set) var state: State = .empty - private var lastHeight: UInt64? + private(set) var receivedLastHeight: Int64? + private(set) var readedLastHeight: Int64? private let processingQueue = DispatchQueue(label: "im.Adamant.processing.transfers", qos: .utility, attributes: [.concurrent]) private let stateSemaphore = DispatchSemaphore(value: 1) @@ -50,6 +52,39 @@ class AdamantTransfersProvider: TransfersProvider { } } } + + + // MARK: Lifecycle + init() { + NotificationCenter.default.addObserver(forName: Notification.Name.adamantUserLoggedIn, object: nil, queue: nil) { [weak self] notification in + self?.update() + + if let address = notification.userInfo?[AdamantUserInfoKey.AccountService.loggedAccountAddress] as? String { + self?.securedStore.set(address, for: StoreKey.transfersProvider.address) + } else { + print("Can't get logged address") + } + } + + NotificationCenter.default.addObserver(forName: Notification.Name.adamantUserLoggedOut, object: nil, queue: nil) { [weak self] _ in + self?.receivedLastHeight = nil + self?.readedLastHeight = nil + + if let prevState = self?.state { + self?.setState(.empty, previous: prevState) + } + + if let store = self?.securedStore { + store.remove(StoreKey.transfersProvider.address) + store.remove(StoreKey.transfersProvider.receivedLastHeight) + store.remove(StoreKey.transfersProvider.readedLastHeight) + } + } + } + + deinit { + NotificationCenter.default.removeObserver(self) + } } @@ -76,7 +111,7 @@ extension AdamantTransfersProvider { return } - apiService.getTransactions(forAccount: address, type: .send, fromHeight: lastHeight) { result in + apiService.getTransactions(forAccount: address, type: .send, fromHeight: receivedLastHeight) { result in switch result { case .success(let transactions): guard transactions.count > 0 else { @@ -115,7 +150,10 @@ extension AdamantTransfersProvider { private func reset(notify: Bool) { let prevState = self.state setState(.updating, previous: prevState, notify: false) - lastHeight = nil + receivedLastHeight = nil + readedLastHeight = nil + securedStore.remove(StoreKey.transfersProvider.receivedLastHeight) + securedStore.remove(StoreKey.transfersProvider.readedLastHeight) let request = NSFetchRequest(entityName: TransferTransaction.entityName) let context = NSManagedObjectContext(concurrencyType: .privateQueueConcurrencyType) @@ -144,6 +182,15 @@ extension AdamantTransfersProvider { return controller } + func unreadTransfersController() -> NSFetchedResultsController { + let request = NSFetchRequest(entityName: TransferTransaction.entityName) + request.predicate = NSPredicate(format: "isUnread == true") + request.sortDescriptors = [NSSortDescriptor(key: "date", ascending: false)] + let controller = NSFetchedResultsController(fetchRequest: request, managedObjectContext: stack.container.viewContext, sectionNameKeyPath: nil, cacheName: nil) + try! controller.performFetch() + return controller + } + func transferFunds(toAddress recipient: String, amount: Decimal, completion: @escaping (TransfersProviderResult) -> Void) { guard let senderAddress = accountService.account?.address, let keypair = accountService.keypair else { completion(.error(.notLogged)) @@ -171,7 +218,9 @@ extension AdamantTransfersProvider { case error(Error) } - private func processRawTransactions(_ transactions: [Transaction], currentAddress address: String, completion: @escaping (ProcessingResult) -> Void) { + private func processRawTransactions(_ transactions: [Transaction], + currentAddress address: String, + completion: @escaping (ProcessingResult) -> Void) { // MARK: 0. Transactions? guard transactions.count > 0 else { completion(.success(new: 0)) @@ -236,7 +285,7 @@ extension AdamantTransfersProvider { } } - var totalTransactions = 0 + var transfers = [TransferTransaction]() var height: Int64 = 0 for t in transactions { let transfer = TransferTransaction(entity: TransferTransaction.entity(), insertInto: context) @@ -261,28 +310,43 @@ extension AdamantTransfersProvider { height = t.height } - totalTransactions += 1 + transfers.append(transfer) } // MARK: 4. Check lastHeight // API returns transactions from lastHeight INCLUDING transaction with height == lastHeight, so +1 if height > 0 { - let uH = UInt64(height + 1) + let uH = Int64(height + 1) - if let lastHeight = lastHeight { + if let lastHeight = receivedLastHeight { if lastHeight < uH { - self.lastHeight = uH + self.receivedLastHeight = uH } } else { - self.lastHeight = uH + self.receivedLastHeight = uH } } - // MARK: 5. Dump transactions to viewContext + // MARK: 5. Unread transactions + if let unreadedHeight = readedLastHeight { + transfers.filter({$0.height > unreadedHeight}).forEach({$0.isUnread = true}) + + readedLastHeight = self.receivedLastHeight + } + + if let h = self.receivedLastHeight { + securedStore.set(String(h), for: StoreKey.transfersProvider.receivedLastHeight) + } + + if let h = self.readedLastHeight { + securedStore.set(String(h), for: StoreKey.transfersProvider.readedLastHeight) + } + + // MARK: 6. Dump transactions to viewContext if context.hasChanges { do { try context.save() - completion(.success(new: totalTransactions)) + completion(.success(new: transfers.count)) } catch { completion(.error(error)) } diff --git a/Adamant/SharedCells/ChatTableViewCell.swift b/Adamant/SharedCells/ChatTableViewCell.swift index 71db230ca..fda2d705a 100644 --- a/Adamant/SharedCells/ChatTableViewCell.swift +++ b/Adamant/SharedCells/ChatTableViewCell.swift @@ -25,7 +25,6 @@ class ChatTableViewCell: UITableViewCell { override func awakeFromNib() { badgeView.layer.cornerRadius = badgeView.bounds.height / 2 - badgeView.isHidden = true } var avatarImage: UIImage? { diff --git a/Adamant/SharedCells/TransactionTableViewCell.xib b/Adamant/SharedCells/TransactionTableViewCell.xib index b0c5eec12..0dc29b664 100644 --- a/Adamant/SharedCells/TransactionTableViewCell.xib +++ b/Adamant/SharedCells/TransactionTableViewCell.xib @@ -35,22 +35,22 @@ - + -