diff --git a/AlphaWallet.xcodeproj/project.pbxproj b/AlphaWallet.xcodeproj/project.pbxproj index 21905975d..4ad9fd43c 100644 --- a/AlphaWallet.xcodeproj/project.pbxproj +++ b/AlphaWallet.xcodeproj/project.pbxproj @@ -732,6 +732,7 @@ 87374A4525DFB17600267160 /* HoneySwapERC20Token.swift in Sources */ = {isa = PBXBuildFile; fileRef = 87374A4425DFB17600267160 /* HoneySwapERC20Token.swift */; }; 8738DDDC2652B98200064CCA /* ActivitiesService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8738DDDB2652B98200064CCA /* ActivitiesService.swift */; }; 8738DDDE2652CD1300064CCA /* MulticastDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8738DDDD2652CD1300064CCA /* MulticastDelegate.swift */; }; + 8739BB8F26CCF2F70045CFED /* ActivitiesView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8739BB8E26CCF2F70045CFED /* ActivitiesView.swift */; }; 873F8063246E8E3E00EEE5EF /* SelectCurrencyButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 873F8062246E8E3E00EEE5EF /* SelectCurrencyButton.swift */; }; 8743CB50255059780039E469 /* DomainResolver.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8743CB4F255059780039E469 /* DomainResolver.swift */; }; 874AF0832603405F00D613A5 /* LoadingIndicatorView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 874AF0822603405F00D613A5 /* LoadingIndicatorView.swift */; }; @@ -1689,6 +1690,7 @@ 87374A4425DFB17600267160 /* HoneySwapERC20Token.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HoneySwapERC20Token.swift; sourceTree = ""; }; 8738DDDB2652B98200064CCA /* ActivitiesService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ActivitiesService.swift; sourceTree = ""; }; 8738DDDD2652CD1300064CCA /* MulticastDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MulticastDelegate.swift; sourceTree = ""; }; + 8739BB8E26CCF2F70045CFED /* ActivitiesView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ActivitiesView.swift; sourceTree = ""; }; 873F8062246E8E3E00EEE5EF /* SelectCurrencyButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SelectCurrencyButton.swift; sourceTree = ""; }; 8743CB4F255059780039E469 /* DomainResolver.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DomainResolver.swift; sourceTree = ""; }; 874AF0822603405F00D613A5 /* LoadingIndicatorView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoadingIndicatorView.swift; sourceTree = ""; }; @@ -3298,6 +3300,7 @@ 5E7C7AEDAAE638601C7122A5 /* DefaultActivityView.swift */, 8795994426049EF8006722B2 /* ActivityStateView.swift */, 5E7C7E58DD0CF4E2B35B6ED2 /* GroupActivityViewCell.swift */, + 8739BB8E26CCF2F70045CFED /* ActivitiesView.swift */, ); path = Views; sourceTree = ""; @@ -5170,6 +5173,7 @@ 2963B6C11F9AE0E4003063C1 /* Data.swift in Sources */, 2932045E1F8EEE760095B7C1 /* BalanceCoordinator.swift in Sources */, 29F114F61FA8147300114A29 /* RequestCoordinator.swift in Sources */, + 8739BB8F26CCF2F70045CFED /* ActivitiesView.swift in Sources */, 73ED85A520349BE400593BF3 /* StringFormatter.swift in Sources */, 293112101FC4ADCB00966EEA /* InCoordinatorViewModel.swift in Sources */, 87BBF9972563DD7600FF4846 /* TransactionInProgressCoordinatorBridgeToPromise.swift in Sources */, diff --git a/AlphaWallet/Activities/ActivitiesService.swift b/AlphaWallet/Activities/ActivitiesService.swift index 77bf9bebb..7ba290626 100644 --- a/AlphaWallet/Activities/ActivitiesService.swift +++ b/AlphaWallet/Activities/ActivitiesService.swift @@ -125,12 +125,9 @@ class ActivitiesService: NSObject, ActivitiesServiceType { private var rateLimitedUpdater: RateLimiter? private var rateLimitedViewControllerReloader: RateLimiter? private var hasLoadedActivitiesTheFirstTime = false - private var lastActivitiesCount: Int = 0 - private var lastTransactionRowsCount: Int = 0 - private var lastTransactionBlockNumbers: [Int] = .init() let subscribableUpdatedActivity: Subscribable = .init(nil) - let subscribableViewModel: Subscribable = .init(nil) + let subscribableViewModel: Subscribable = .init(.init(activities: [])) private var tokensInDatabase: [TokenObject] { tokensStorages.values.flatMap { $0.enabledObject } @@ -174,11 +171,11 @@ class ActivitiesService: NSObject, ActivitiesServiceType { self.transactionsFilterStrategy = transactionsFilterStrategy super.init() - filteredTransactionsSubscriptionKey = filteredTransactionsSubscription.subscribe { [weak self] txs in + filteredTransactionsSubscriptionKey = filteredTransactionsSubscription.subscribe { [weak self] _ in self?.reloadImpl(reloadImmediately: true) } - recentEventsSubscriptionKey = recentEventsSubscribable.subscribe { [weak self] activities in + recentEventsSubscriptionKey = recentEventsSubscribable.subscribe { [weak self] _ in self?.reloadImpl(reloadImmediately: true) } } @@ -202,7 +199,7 @@ class ActivitiesService: NSObject, ActivitiesServiceType { } } - func reloadImpl(reloadImmediately: Bool) { + private func reloadImpl(reloadImmediately: Bool) { let contractServerXmlHandlers: [(contract: AlphaWallet.Address, server: RPCServer, xmlHandler: XMLHandler)] = tokensInDatabase.compactMap { each in let eachContract = each.contractAddress let eachServer = each.server @@ -244,7 +241,6 @@ class ActivitiesService: NSObject, ActivitiesServiceType { } return contractAndCard } - let contractsAndCards = contractsAndCardsOptional.flatMap { $0 } fetchAndRefreshActivities(contractsAndCards: contractsAndCards, reloadImmediately: reloadImmediately) } @@ -259,12 +255,15 @@ class ActivitiesService: NSObject, ActivitiesServiceType { activities = activitiesAndTokens.map { $0.0 } activities.sort { $0.blockNumber > $1.blockNumber } updateActivitiesIndexLookup() + reloadViewController(reloadImmediately: reloadImmediately) for (activity, tokenObject, tokenHolder) in activitiesAndTokens { refreshActivity(tokenObject: tokenObject, tokenHolder: tokenHolder, activity: activity) } } + //Cache tokens lookup for performance + private static var tokensCache: ThreadSafeDictionary = .init() private func getActivities(_ allActivities: [EventActivity], forTokenContract contract: AlphaWallet.Address, server: RPCServer, card: TokenScriptCard, interpolatedFilter: String) -> [(Activity, Activity.AssignedToken, TokenHolder)] { let events = allActivities.filter { @@ -274,17 +273,14 @@ class ActivitiesService: NSObject, ActivitiesServiceType { && $0.filter == interpolatedFilter } - //Cache tokens lookup for performance - var tokensCache: [AlphaWallet.Address: Activity.AssignedToken] = .init() let activitiesForThisCard: [(activity: Activity, tokenObject: Activity.AssignedToken, tokenHolder: TokenHolder)] = events.compactMap { eachEvent in let token: Activity.AssignedToken - if let t = tokensCache[contract] { + if let t = Self.tokensCache[contract] { token = t } else { guard let tokensDatastore = tokensStorages[safe: server] else { return nil } - guard let t = tokensDatastore.tokenThreadSafe(forContract: contract) else { return nil } - let tt = Activity.AssignedToken(tokenObject: t) - tokensCache[contract] = tt + guard let tt = tokensDatastore.tokenThreadSafe(forContract: contract).flatMap({ Activity.AssignedToken(tokenObject: $0) }) else { return nil } + Self.tokensCache[contract] = tt token = tt } diff --git a/AlphaWallet/Activities/ViewControllers/ActivitiesViewController.swift b/AlphaWallet/Activities/ViewControllers/ActivitiesViewController.swift index 2f2decedf..6b2d7839c 100644 --- a/AlphaWallet/Activities/ViewControllers/ActivitiesViewController.swift +++ b/AlphaWallet/Activities/ViewControllers/ActivitiesViewController.swift @@ -9,217 +9,6 @@ protocol ActivitiesViewControllerDelegate: AnyObject { func didPressTransaction(transaction: TransactionInstance, in viewController: ActivitiesViewController) } -protocol ActivitiesViewDelegate: class { - func didPressActivity(activity: Activity, in view: ActivitiesView) - func didPressTransaction(transaction: TransactionInstance, in view: ActivitiesView) -} - -class ActivitiesView: UIView { - private var viewModel: ActivitiesViewModel - private let sessions: ServerDictionary - private let tableView = UITableView(frame: .zero, style: .grouped) - - weak var delegate: ActivitiesViewDelegate? - - init(viewModel: ActivitiesViewModel, sessions: ServerDictionary) { - self.viewModel = viewModel - self.sessions = sessions - - super.init(frame: .zero) - translatesAutoresizingMaskIntoConstraints = false - - tableView.register(ActivityViewCell.self) - tableView.register(DefaultActivityItemViewCell.self) - tableView.register(TransactionViewCell.self) - tableView.register(GroupActivityViewCell.self) - tableView.translatesAutoresizingMaskIntoConstraints = false - tableView.delegate = self - tableView.dataSource = self - tableView.separatorStyle = .singleLine - tableView.backgroundColor = viewModel.backgroundColor - tableView.estimatedRowHeight = TokensCardViewController.anArbitraryRowHeightSoAutoSizingCellsWorkIniOS10 - - addSubview(tableView) - - NSLayoutConstraint.activate([ - tableView.topAnchor.constraint(equalTo: topAnchor), - tableView.leadingAnchor.constraint(equalTo: leadingAnchor), - tableView.trailingAnchor.constraint(equalTo: trailingAnchor), - tableView.bottomAnchor.constraint(equalTo: bottomAnchor), - ]) - - emptyView = TransactionsEmptyView(title: R.string.localizable.activityEmpty(), image: R.image.activities_empty_list()) - } - - func resetStatefulStateToReleaseObjectToAvoidMemoryLeak() { - // NOTE: Stateful lib set to object state machine that later causes ref cycle when applying it to view - // here we release all associated objects to release state machine - // this method callget get called while parent's view deinit get called - objc_removeAssociatedObjects(self) - } - - required init?(coder: NSCoder) { - return nil - } - - func reloadData() { - tableView.reloadData() - } - - func configure(viewModel: ActivitiesViewModel) { - self.viewModel = viewModel - } - - func applySearch(keyword: String?) { - viewModel.filter(.keyword(keyword)) - - reloadData() - } -} - -extension ActivitiesView: StatefulViewController { - func hasContent() -> Bool { - return viewModel.numberOfSections > 0 - } -} - -extension ActivitiesView: UITableViewDelegate { - func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { - tableView.deselectRow(at: indexPath, animated: true ) - let item = viewModel.item(for: indexPath.row, section: indexPath.section) - switch item { - case .parentTransaction: - break - case .childActivity(_, activity: let activity): - delegate?.didPressActivity(activity: activity, in: self) - case .childTransaction(let transaction, _, let activity): - if let activity = activity { - delegate?.didPressActivity(activity: activity, in: self) - } else { - delegate?.didPressTransaction(transaction: transaction, in: self) - } - case .standaloneTransaction(transaction: let transaction, let activity): - if let activity = activity { - delegate?.didPressActivity(activity: activity, in: self) - } else { - delegate?.didPressTransaction(transaction: transaction, in: self) - } - case .standaloneActivity(activity: let activity): - delegate?.didPressActivity(activity: activity, in: self) - } - } - - func tableView(_ tableView: UITableView, willSelectRowAt indexPath: IndexPath) -> IndexPath? { - let item = viewModel.item(for: indexPath.row, section: indexPath.section) - switch item { - case .parentTransaction: - return nil - case .childActivity, .childTransaction, .standaloneTransaction, .standaloneActivity: - return indexPath - } - } - - //Hide the footer - func tableView(_ tableView: UITableView, heightForFooterInSection section: Int) -> CGFloat { - .leastNormalMagnitude - } - func tableView(_ tableView: UITableView, viewForFooterInSection section: Int) -> UIView? { - nil - } -} - -extension ActivitiesView: UITableViewDataSource { - func numberOfSections(in tableView: UITableView) -> Int { - return viewModel.numberOfSections - } - - func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { - let item = viewModel.item(for: indexPath.row, section: indexPath.section) - switch item { - case .parentTransaction(_, isSwap: let isSwap, _): - let cell: GroupActivityViewCell = tableView.dequeueReusableCell(for: indexPath) - cell.configure(viewModel: .init(groupType: isSwap ? .swap : .unknown)) - return cell - case .childActivity(_, activity: let activity): - let activity: Activity = { - var a = activity - a.rowType = .item - return a - }() - switch activity.nativeViewType { - case .erc20Received, .erc20Sent, .erc20OwnerApproved, .erc20ApprovalObtained, .erc721Received, .erc721Sent, .erc721OwnerApproved, .erc721ApprovalObtained, .nativeCryptoSent, .nativeCryptoReceived: - let cell: DefaultActivityItemViewCell = tableView.dequeueReusableCell(for: indexPath) - cell.configure(viewModel: .init(activity: activity)) - return cell - case .none: - let cell: ActivityViewCell = tableView.dequeueReusableCell(for: indexPath) - cell.configure(viewModel: .init(activity: activity)) - return cell - } - case .childTransaction(transaction: let transaction, operation: let operation, let activity): - if let activity = activity { - let cell: DefaultActivityItemViewCell = tableView.dequeueReusableCell(for: indexPath) - cell.configure(viewModel: .init(activity: activity)) - return cell - } else { - let cell: TransactionViewCell = tableView.dequeueReusableCell(for: indexPath) - let session = sessions[transaction.server] - cell.configure(viewModel: .init(transactionRow: .item(transaction: transaction, operation: operation), chainState: session.chainState, currentWallet: session.account, server: transaction.server)) - return cell - } - case .standaloneTransaction(transaction: let transaction, let activity): - if let activity = activity { - let cell: DefaultActivityItemViewCell = tableView.dequeueReusableCell(for: indexPath) - cell.configure(viewModel: .init(activity: activity)) - return cell - } else { - let cell: TransactionViewCell = tableView.dequeueReusableCell(for: indexPath) - let session = sessions[transaction.server] - cell.configure(viewModel: .init(transactionRow: .standalone(transaction), chainState: session.chainState, currentWallet: session.account, server: transaction.server)) - return cell - } - case .standaloneActivity(activity: let activity): - switch activity.nativeViewType { - case .erc20Received, .erc20Sent, .erc20OwnerApproved, .erc20ApprovalObtained, .erc721Received, .erc721Sent, .erc721OwnerApproved, .erc721ApprovalObtained, .nativeCryptoSent, .nativeCryptoReceived: - let cell: DefaultActivityItemViewCell = tableView.dequeueReusableCell(for: indexPath) - cell.configure(viewModel: .init(activity: activity)) - return cell - case .none: - let cell: ActivityViewCell = tableView.dequeueReusableCell(for: indexPath) - cell.configure(viewModel: .init(activity: activity)) - return cell - } - } - } - - func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { - return viewModel.numberOfItems(for: section) - } - - func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? { - return ActivitiesViewController.functional.headerView(for: section, viewModel: viewModel) - } - - func tableView(_ tableView: UITableView, willDisplayHeaderView view: UIView, forSection section: Int) { - } - - fileprivate func headerView(for section: Int) -> UIView { - let container = UIView() - container.backgroundColor = viewModel.headerBackgroundColor - let title = UILabel() - title.text = viewModel.titleForHeader(in: section) - title.sizeToFit() - title.textColor = viewModel.headerTitleTextColor - title.font = viewModel.headerTitleFont - container.addSubview(title) - title.translatesAutoresizingMaskIntoConstraints = false - NSLayoutConstraint.activate([ - title.anchorsConstraint(to: container, edgeInsets: .init(top: 18, left: 20, bottom: 16, right: 0)) - ]) - return container - } -} - class ActivitiesViewController: UIViewController { private var viewModel: ActivitiesViewModel private let searchController: UISearchController @@ -237,7 +26,7 @@ class ActivitiesViewController: UIViewController { title = R.string.localizable.activityTabbarItemTitle() activitiesView.delegate = self - view.backgroundColor = self.viewModel.backgroundColor + view.backgroundColor = viewModel.backgroundColor bottomConstraint = activitiesView.bottomAnchor.constraint(equalTo: view.bottomAnchor) keyboardChecker.constraint = bottomConstraint @@ -371,7 +160,7 @@ extension ActivitiesViewController { extension ActivitiesViewController.functional { - fileprivate static func headerView(for section: Int, viewModel: ActivitiesViewModel) -> UIView { + static func headerView(for section: Int, viewModel: ActivitiesViewModel) -> UIView { let container = UIView() container.backgroundColor = viewModel.headerBackgroundColor let title = UILabel() diff --git a/AlphaWallet/Activities/ViewModels/ActivitiesViewModel.swift b/AlphaWallet/Activities/ViewModels/ActivitiesViewModel.swift index 9bc9e3525..2bd03cc47 100644 --- a/AlphaWallet/Activities/ViewModels/ActivitiesViewModel.swift +++ b/AlphaWallet/Activities/ViewModels/ActivitiesViewModel.swift @@ -24,7 +24,6 @@ struct ActivitiesViewModel { hasher.combine(stringValue) } - static func == (_ lhs: ActivityDateKey, _ rhs: ActivityDateKey) -> Bool { return lhs.stringValue == rhs.stringValue } diff --git a/AlphaWallet/Activities/Views/ActivitiesView.swift b/AlphaWallet/Activities/Views/ActivitiesView.swift new file mode 100644 index 000000000..068754fbb --- /dev/null +++ b/AlphaWallet/Activities/Views/ActivitiesView.swift @@ -0,0 +1,218 @@ +// +// ActivitiesView.swift +// AlphaWallet +// +// Created by Vladyslav Shepitko on 18.08.2021. +// + +import UIKit +import BigInt +import StatefulViewController + +protocol ActivitiesViewDelegate: class { + func didPressActivity(activity: Activity, in view: ActivitiesView) + func didPressTransaction(transaction: TransactionInstance, in view: ActivitiesView) +} + +class ActivitiesView: UIView { + private var viewModel: ActivitiesViewModel + private let sessions: ServerDictionary + private let tableView = UITableView(frame: .zero, style: .grouped) + + weak var delegate: ActivitiesViewDelegate? + + init(viewModel: ActivitiesViewModel, sessions: ServerDictionary) { + self.viewModel = viewModel + self.sessions = sessions + + super.init(frame: .zero) + translatesAutoresizingMaskIntoConstraints = false + + tableView.register(ActivityViewCell.self) + tableView.register(DefaultActivityItemViewCell.self) + tableView.register(TransactionViewCell.self) + tableView.register(GroupActivityViewCell.self) + tableView.translatesAutoresizingMaskIntoConstraints = false + tableView.delegate = self + tableView.dataSource = self + tableView.separatorStyle = .singleLine + tableView.backgroundColor = viewModel.backgroundColor + tableView.estimatedRowHeight = TokensCardViewController.anArbitraryRowHeightSoAutoSizingCellsWorkIniOS10 + + addSubview(tableView) + + NSLayoutConstraint.activate([ + tableView.topAnchor.constraint(equalTo: topAnchor), + tableView.leadingAnchor.constraint(equalTo: leadingAnchor), + tableView.trailingAnchor.constraint(equalTo: trailingAnchor), + tableView.bottomAnchor.constraint(equalTo: bottomAnchor), + ]) + + emptyView = TransactionsEmptyView(title: R.string.localizable.activityEmpty(), image: R.image.activities_empty_list()) + } + + func resetStatefulStateToReleaseObjectToAvoidMemoryLeak() { + // NOTE: Stateful lib set to object state machine that later causes ref cycle when applying it to view + // here we release all associated objects to release state machine + // this method callget get called while parent's view deinit get called + objc_removeAssociatedObjects(self) + } + + required init?(coder: NSCoder) { + return nil + } + + func reloadData() { + tableView.reloadData() + } + + func configure(viewModel: ActivitiesViewModel) { + self.viewModel = viewModel + } + + func applySearch(keyword: String?) { + viewModel.filter(.keyword(keyword)) + + reloadData() + } +} + +extension ActivitiesView: StatefulViewController { + func hasContent() -> Bool { + return viewModel.numberOfSections > 0 + } +} + +extension ActivitiesView: UITableViewDelegate { + func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { + tableView.deselectRow(at: indexPath, animated: true ) + switch viewModel.item(for: indexPath.row, section: indexPath.section) { + case .parentTransaction: + break + case .childActivity(_, activity: let activity): + delegate?.didPressActivity(activity: activity, in: self) + case .childTransaction(let transaction, _, let activity): + if let activity = activity { + delegate?.didPressActivity(activity: activity, in: self) + } else { + delegate?.didPressTransaction(transaction: transaction, in: self) + } + case .standaloneTransaction(transaction: let transaction, let activity): + if let activity = activity { + delegate?.didPressActivity(activity: activity, in: self) + } else { + delegate?.didPressTransaction(transaction: transaction, in: self) + } + case .standaloneActivity(activity: let activity): + delegate?.didPressActivity(activity: activity, in: self) + } + } + + func tableView(_ tableView: UITableView, willSelectRowAt indexPath: IndexPath) -> IndexPath? { + switch viewModel.item(for: indexPath.row, section: indexPath.section) { + case .parentTransaction: + return nil + case .childActivity, .childTransaction, .standaloneTransaction, .standaloneActivity: + return indexPath + } + } + + //Hide the footer + func tableView(_ tableView: UITableView, heightForFooterInSection section: Int) -> CGFloat { + .leastNormalMagnitude + } + func tableView(_ tableView: UITableView, viewForFooterInSection section: Int) -> UIView? { + nil + } +} + +extension ActivitiesView: UITableViewDataSource { + func numberOfSections(in tableView: UITableView) -> Int { + return viewModel.numberOfSections + } + + func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { + switch viewModel.item(for: indexPath.row, section: indexPath.section) { + case .parentTransaction(_, isSwap: let isSwap, _): + let cell: GroupActivityViewCell = tableView.dequeueReusableCell(for: indexPath) + cell.configure(viewModel: .init(groupType: isSwap ? .swap : .unknown)) + return cell + case .childActivity(_, activity: let activity): + let activity: Activity = { + var a = activity + a.rowType = .item + return a + }() + switch activity.nativeViewType { + case .erc20Received, .erc20Sent, .erc20OwnerApproved, .erc20ApprovalObtained, .erc721Received, .erc721Sent, .erc721OwnerApproved, .erc721ApprovalObtained, .nativeCryptoSent, .nativeCryptoReceived: + let cell: DefaultActivityItemViewCell = tableView.dequeueReusableCell(for: indexPath) + cell.configure(viewModel: .init(activity: activity)) + return cell + case .none: + let cell: ActivityViewCell = tableView.dequeueReusableCell(for: indexPath) + cell.configure(viewModel: .init(activity: activity)) + return cell + } + case .childTransaction(transaction: let transaction, operation: let operation, let activity): + if let activity = activity { + let cell: DefaultActivityItemViewCell = tableView.dequeueReusableCell(for: indexPath) + cell.configure(viewModel: .init(activity: activity)) + return cell + } else { + let cell: TransactionViewCell = tableView.dequeueReusableCell(for: indexPath) + let session = sessions[transaction.server] + cell.configure(viewModel: .init(transactionRow: .item(transaction: transaction, operation: operation), chainState: session.chainState, currentWallet: session.account, server: transaction.server)) + return cell + } + case .standaloneTransaction(transaction: let transaction, let activity): + if let activity = activity { + let cell: DefaultActivityItemViewCell = tableView.dequeueReusableCell(for: indexPath) + cell.configure(viewModel: .init(activity: activity)) + return cell + } else { + let cell: TransactionViewCell = tableView.dequeueReusableCell(for: indexPath) + let session = sessions[transaction.server] + cell.configure(viewModel: .init(transactionRow: .standalone(transaction), chainState: session.chainState, currentWallet: session.account, server: transaction.server)) + return cell + } + case .standaloneActivity(activity: let activity): + switch activity.nativeViewType { + case .erc20Received, .erc20Sent, .erc20OwnerApproved, .erc20ApprovalObtained, .erc721Received, .erc721Sent, .erc721OwnerApproved, .erc721ApprovalObtained, .nativeCryptoSent, .nativeCryptoReceived: + let cell: DefaultActivityItemViewCell = tableView.dequeueReusableCell(for: indexPath) + cell.configure(viewModel: .init(activity: activity)) + return cell + case .none: + let cell: ActivityViewCell = tableView.dequeueReusableCell(for: indexPath) + cell.configure(viewModel: .init(activity: activity)) + return cell + } + } + } + + func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { + return viewModel.numberOfItems(for: section) + } + + func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? { + return ActivitiesViewController.functional.headerView(for: section, viewModel: viewModel) + } + + func tableView(_ tableView: UITableView, willDisplayHeaderView view: UIView, forSection section: Int) { + } + + fileprivate func headerView(for section: Int) -> UIView { + let container = UIView() + container.backgroundColor = viewModel.headerBackgroundColor + let title = UILabel() + title.text = viewModel.titleForHeader(in: section) + title.sizeToFit() + title.textColor = viewModel.headerTitleTextColor + title.font = viewModel.headerTitleFont + container.addSubview(title) + title.translatesAutoresizingMaskIntoConstraints = false + NSLayoutConstraint.activate([ + title.anchorsConstraint(to: container, edgeInsets: .init(top: 18, left: 20, bottom: 16, right: 0)) + ]) + return container + } +} diff --git a/AlphaWallet/Tokens/ViewControllers/TokenViewController.swift b/AlphaWallet/Tokens/ViewControllers/TokenViewController.swift index 5bbbb4d59..4c7f70476 100644 --- a/AlphaWallet/Tokens/ViewControllers/TokenViewController.swift +++ b/AlphaWallet/Tokens/ViewControllers/TokenViewController.swift @@ -46,7 +46,7 @@ class TokenViewController: UIViewController { private lazy var alertsPageView = AlertsPageView() private let sessions: ServerDictionary private let activitiesService: ActivitiesServiceType - + private var activitiesSubscriptionKey: Subscribable.SubscribableKey? init(session: WalletSession, tokensDataStore: TokensDataStore, assetDefinition: AssetDefinitionStore, transactionType: TransactionType, analyticsCoordinator: AnalyticsCoordinator, token: TokenObject, viewModel: TokenViewControllerViewModel, activitiesService: ActivitiesServiceType, sessions: ServerDictionary) { self.tokenObject = token self.viewModel = viewModel @@ -80,10 +80,10 @@ class TokenViewController: UIViewController { navigationItem.largeTitleDisplayMode = .never - activitiesService.subscribableViewModel.subscribe { [weak self] viewModel in - guard let strongSelf = self, let viewModel = viewModel else { return } + activitiesSubscriptionKey = activitiesService.subscribableViewModel.subscribe { [weak activityPageView] viewModel in + guard let view = activityPageView else { return } - strongSelf.activityPageView.configure(viewModel: .init(activitiesViewModel: viewModel)) + view.configure(viewModel: .init(activitiesViewModel: viewModel ?? .init(activities: []))) } } @@ -91,6 +91,10 @@ class TokenViewController: UIViewController { return nil } + deinit { + activitiesSubscriptionKey.flatMap { activitiesService.subscribableViewModel.unsubscribe($0) } + } + override func viewDidLoad() { super.viewDidLoad() diff --git a/AlphaWallet/Transactions/Views/ActivityPageView.swift b/AlphaWallet/Transactions/Views/ActivityPageView.swift index 35b4bf12d..ef7169727 100644 --- a/AlphaWallet/Transactions/Views/ActivityPageView.swift +++ b/AlphaWallet/Transactions/Views/ActivityPageView.swift @@ -27,9 +27,7 @@ protocol ActivityPageViewDelegate: class { class ActivityPageView: UIView, TokenPageViewType { - var title: String { - viewModel.title - } + var title: String { viewModel.title } private var activitiesView: ActivitiesView var viewModel: ActivityPageViewModel @@ -44,9 +42,7 @@ class ActivityPageView: UIView, TokenPageViewType { translatesAutoresizingMaskIntoConstraints = false addSubview(activitiesView) - NSLayoutConstraint.activate([ - activitiesView.anchorsConstraint(to: self) - ]) + NSLayoutConstraint.activate([activitiesView.anchorsConstraint(to: self)]) configure(viewModel: viewModel) }