Group tokens by chain in Wallet tab #2957

pull/3009/head
Vladyslav shepitko 3 years ago
parent 0f54ba8688
commit d7ac5855cd
  1. 9
      AlphaWallet.xcodeproj/project.pbxproj
  2. 19
      AlphaWallet/Browser/Coordinators/ScanQRCodeCoordinator.swift
  3. 65
      AlphaWallet/Extensions/UIBarButtonItem.swift
  4. 4
      AlphaWallet/Settings/ViewControllers/EnabledServersViewController.swift
  5. 4
      AlphaWallet/Settings/ViewControllers/ServersViewController.swift
  6. 40
      AlphaWallet/Settings/ViewModels/ServerViewModel.swift
  7. 17
      AlphaWallet/Settings/Views/ServerViewCell.swift
  8. 176
      AlphaWallet/Tokens/ViewControllers/TokensViewController.swift
  9. 73
      AlphaWallet/Tokens/ViewModels/TokensViewModel.swift
  10. 35
      AlphaWallet/Transfer/ViewControllers/ConfigureTransactionViewController.swift

@ -772,11 +772,11 @@
876C80CF267398F900B16595 /* AddRPCServerViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 876C80CE267398F900B16595 /* AddRPCServerViewController.swift */; };
876C80D12673992300B16595 /* AddRPCServerViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 876C80D02673992300B16595 /* AddRPCServerViewModel.swift */; };
87713EAE264ABF2800B1B9CB /* DecodedFunctionCall+Decode.swift in Sources */ = {isa = PBXBuildFile; fileRef = 87713EAD264ABF2800B1B9CB /* DecodedFunctionCall+Decode.swift */; };
8777A47E26660CAA005285BD /* PrivateTokensDataStoreType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8777A47D26660CAA005285BD /* PrivateTokensDataStoreType.swift */; };
87713EB0264BAB2500B1B9CB /* TokenPagesContainerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 87713EAF264BAB2500B1B9CB /* TokenPagesContainerView.swift */; };
87713EB2264BAB4700B1B9CB /* TokenInfoPageView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 87713EB1264BAB4700B1B9CB /* TokenInfoPageView.swift */; };
87713EB4264BAB5A00B1B9CB /* ActivityPageView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 87713EB3264BAB5A00B1B9CB /* ActivityPageView.swift */; };
87713EB6264BAB6E00B1B9CB /* AlertsPageView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 87713EB5264BAB6E00B1B9CB /* AlertsPageView.swift */; };
8777A47E26660CAA005285BD /* PrivateTokensDataStoreType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8777A47D26660CAA005285BD /* PrivateTokensDataStoreType.swift */; };
877D00AF25ADF60A008E22CC /* TransactionConfiguratorTransactionsTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 877D00AE25ADF60A008E22CC /* TransactionConfiguratorTransactionsTests.swift */; };
8782035D2431E66600792F12 /* FilterTokensCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8782035C2431E66600792F12 /* FilterTokensCoordinator.swift */; };
8782035F2431FBC300792F12 /* ShowAddHideTokensViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8782035E2431FBC300792F12 /* ShowAddHideTokensViewModel.swift */; };
@ -826,6 +826,7 @@
87BBF9972563DD7600FF4846 /* TransactionInProgressCoordinatorBridgeToPromise.swift in Sources */ = {isa = PBXBuildFile; fileRef = 87BBF97F2563DD7500FF4846 /* TransactionInProgressCoordinatorBridgeToPromise.swift */; };
87BBF9982563DD7600FF4846 /* TransactionConfirmationCoordinatorBridgeToPromise.swift in Sources */ = {isa = PBXBuildFile; fileRef = 87BBF9802563DD7500FF4846 /* TransactionConfirmationCoordinatorBridgeToPromise.swift */; };
87BBF9992563DD7600FF4846 /* WalletConnectSessionDetailsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 87BBF9822563DD7500FF4846 /* WalletConnectSessionDetailsViewController.swift */; };
87BC89B826B82288005482F4 /* UIBarButtonItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 87BC89B726B82287005482F4 /* UIBarButtonItem.swift */; };
87C10A6525ED1105008E9B1B /* SignatureConfirmationDetailsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 87C10A6425ED1105008E9B1B /* SignatureConfirmationDetailsViewController.swift */; };
87C447AA255970C5009DF2D2 /* ActiveWalletSessionView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 87C447A9255970C5009DF2D2 /* ActiveWalletSessionView.swift */; };
87C650C325F2408E007B02CB /* ServerUnavailableCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 87C650C225F2408E007B02CB /* ServerUnavailableCoordinator.swift */; };
@ -1720,11 +1721,11 @@
876C80CE267398F900B16595 /* AddRPCServerViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AddRPCServerViewController.swift; sourceTree = "<group>"; };
876C80D02673992300B16595 /* AddRPCServerViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AddRPCServerViewModel.swift; sourceTree = "<group>"; };
87713EAD264ABF2800B1B9CB /* DecodedFunctionCall+Decode.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "DecodedFunctionCall+Decode.swift"; sourceTree = "<group>"; };
8777A47D26660CAA005285BD /* PrivateTokensDataStoreType.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PrivateTokensDataStoreType.swift; sourceTree = "<group>"; };
87713EAF264BAB2500B1B9CB /* TokenPagesContainerView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TokenPagesContainerView.swift; sourceTree = "<group>"; };
87713EB1264BAB4700B1B9CB /* TokenInfoPageView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TokenInfoPageView.swift; sourceTree = "<group>"; };
87713EB3264BAB5A00B1B9CB /* ActivityPageView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ActivityPageView.swift; sourceTree = "<group>"; };
87713EB5264BAB6E00B1B9CB /* AlertsPageView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AlertsPageView.swift; sourceTree = "<group>"; };
8777A47D26660CAA005285BD /* PrivateTokensDataStoreType.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PrivateTokensDataStoreType.swift; sourceTree = "<group>"; };
877D00AE25ADF60A008E22CC /* TransactionConfiguratorTransactionsTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TransactionConfiguratorTransactionsTests.swift; sourceTree = "<group>"; };
8782035C2431E66600792F12 /* FilterTokensCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FilterTokensCoordinator.swift; sourceTree = "<group>"; };
8782035E2431FBC300792F12 /* ShowAddHideTokensViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ShowAddHideTokensViewModel.swift; sourceTree = "<group>"; };
@ -1773,6 +1774,7 @@
87BBF97F2563DD7500FF4846 /* TransactionInProgressCoordinatorBridgeToPromise.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TransactionInProgressCoordinatorBridgeToPromise.swift; sourceTree = "<group>"; };
87BBF9802563DD7500FF4846 /* TransactionConfirmationCoordinatorBridgeToPromise.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TransactionConfirmationCoordinatorBridgeToPromise.swift; sourceTree = "<group>"; };
87BBF9822563DD7500FF4846 /* WalletConnectSessionDetailsViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = WalletConnectSessionDetailsViewController.swift; sourceTree = "<group>"; };
87BC89B726B82287005482F4 /* UIBarButtonItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UIBarButtonItem.swift; sourceTree = "<group>"; };
87C10A6425ED1105008E9B1B /* SignatureConfirmationDetailsViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SignatureConfirmationDetailsViewController.swift; sourceTree = "<group>"; };
87C447A9255970C5009DF2D2 /* ActiveWalletSessionView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = ActiveWalletSessionView.swift; path = AlphaWallet/Tokens/Views/ActiveWalletSessionView.swift; sourceTree = SOURCE_ROOT; };
87C650C225F2408E007B02CB /* ServerUnavailableCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ServerUnavailableCoordinator.swift; sourceTree = "<group>"; };
@ -2657,6 +2659,7 @@
8722F86C25F79A4700293D89 /* UITableViewCell.swift */,
5E7C7BE3540E3AC73826F856 /* URL.swift */,
5F4D80A926A3F9C500BB1135 /* UIDevice.swift */,
87BC89B726B82287005482F4 /* UIBarButtonItem.swift */,
);
path = Extensions;
sourceTree = "<group>";
@ -5011,6 +5014,7 @@
87BBF9922563DD7600FF4846 /* UnconfirmedTransaction+Extensions.swift in Sources */,
8797362524E6C20C0042BBCC /* TransactionConfirmationCoordinator.swift in Sources */,
77872D302026DC570032D687 /* SplashViewController.swift in Sources */,
87BC89B826B82288005482F4 /* UIBarButtonItem.swift in Sources */,
29C80D4D1FB5202C0037B1E0 /* BalanceBaseViewModel.swift in Sources */,
87BB63E2265E759700FF702A /* PrivateBalanceFetcher.swift in Sources */,
29E14FD11F7F457D00185568 /* TransactionsStorage.swift in Sources */,
@ -5346,7 +5350,6 @@
5E7C760D5AF93B79BB9BDB5A /* OpenSeaNonFungibleTokenAttributeCellViewModel.swift in Sources */,
873F8063246E8E3E00EEE5EF /* SelectCurrencyButton.swift in Sources */,
8738DDDE2652CD1300064CCA /* MulticastDelegate.swift in Sources */,
5E7C725AC96979DEF4DE8B85 /* ConvertSVGToPNG.swift in Sources */,
5E7C729FA0EC60113B031391 /* ImageCache.swift in Sources */,
5E7C74438E1FBF28ADFAFAD1 /* BaseTokenCardTableViewCellViewModel.swift in Sources */,
5E7C72B3D5F75999937FCFA1 /* TokenListFormatRowViewModel.swift in Sources */,

@ -105,25 +105,6 @@ extension ScanQRCodeCoordinator: RequestCoordinatorDelegate {
}
}
extension UIBarButtonItem {
static func cancelBarButton(_ target: AnyObject, selector: Selector) -> UIBarButtonItem {
return .init(barButtonSystemItem: .cancel, target: target, action: selector)
}
static func closeBarButton(_ target: AnyObject, selector: Selector) -> UIBarButtonItem {
return .init(image: R.image.close(), style: .plain, target: target, action: selector)
}
static func backBarButton(_ target: AnyObject, selector: Selector) -> UIBarButtonItem {
return .init(image: R.image.backWhite(), style: .plain, target: target, action: selector)
}
static func addButton(_ target: AnyObject, selector: Selector) -> UIBarButtonItem {
return .init(image: R.image.iconsSystemPlus(), style: .plain, target: target, action: selector)
}
}
// MARK: Analytics
extension ScanQRCodeCoordinator {
private func logCompleteScan(result: String) {

@ -0,0 +1,65 @@
//
// UIBarButtonItem.swift
// AlphaWallet
//
// Created by Vladyslav Shepitko on 02.08.2021.
//
import UIKit
extension UIBarButtonItem {
static func cancelBarButton(_ target: AnyObject, selector: Selector) -> UIBarButtonItem {
return .init(barButtonSystemItem: .cancel, target: target, action: selector)
}
static func closeBarButton(_ target: AnyObject, selector: Selector) -> UIBarButtonItem {
return .init(image: R.image.close(), style: .plain, target: target, action: selector)
}
static func backBarButton(_ target: AnyObject, selector: Selector) -> UIBarButtonItem {
return .init(image: R.image.backWhite(), style: .plain, target: target, action: selector)
}
static func addButton(_ target: AnyObject, selector: Selector) -> UIBarButtonItem {
return .init(image: R.image.iconsSystemPlus(), style: .plain, target: target, action: selector)
}
static func qrCodeBarButton(_ target: AnyObject, selector: Selector) -> UIBarButtonItem {
return .init(image: R.image.qr_code_icon(), style: .plain, target: target, action: selector)
}
static func addBarButton(_ target: AnyObject, selector: Selector) -> UIBarButtonItem {
return .init(image: R.image.add_hide_tokens(), style: .plain, target: target, action: selector)
}
static func saveBarButton(_ target: AnyObject, selector: Selector) -> UIBarButtonItem {
.init(title: R.string.localizable.save(), style: .plain, target: target, action: selector)
}
static func backBarButton(selectionClosure: @escaping () -> Void) -> UIBarButtonItem {
let barButton = UIBarButtonItem(image: R.image.backWhite(), style: .plain, target: nil, action: nil)
barButton.selectionClosure = selectionClosure
return barButton
}
private struct AssociatedObject {
static var key = "action_closure_key"
}
var selectionClosure: (() -> Void)? {
get {
return objc_getAssociatedObject(self, &AssociatedObject.key) as? () -> Void
}
set {
objc_setAssociatedObject(self, &AssociatedObject.key, newValue, .OBJC_ASSOCIATION_RETAIN_NONATOMIC)
target = self
action = #selector(didTapButton)
}
}
@objc func didTapButton(_ sender: Any) {
selectionClosure?()
}
}

@ -22,7 +22,7 @@ class EnabledServersViewController: UIViewController {
tableView.separatorStyle = .singleLine
tableView.backgroundColor = GroupedTable.Color.background
tableView.tableFooterView = UIView.tableFooterToRemoveEmptyCellSeparators()
tableView.register(ServerViewCell.self)
tableView.register(ServerTableViewCell.self)
tableView.dataSource = self
return tableView
@ -138,7 +138,7 @@ extension EnabledServersViewController: UITableViewDelegate, UITableViewDataSour
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell: ServerViewCell = tableView.dequeueReusableCell(for: indexPath)
let cell: ServerTableViewCell = tableView.dequeueReusableCell(for: indexPath)
let server = viewModel.server(for: indexPath)
let cellViewModel = ServerViewModel(server: server, selected: viewModel.isServerSelected(server))
cell.configure(viewModel: cellViewModel)

@ -15,7 +15,7 @@ class ServersViewController: UIViewController {
tableView.separatorStyle = .singleLine
tableView.backgroundColor = GroupedTable.Color.background
tableView.tableFooterView = UIView.tableFooterToRemoveEmptyCellSeparators()
tableView.register(ServerViewCell.self)
tableView.register(ServerTableViewCell.self)
return tableView
}()
@ -57,7 +57,7 @@ extension ServersViewController: UITableViewDelegate, UITableViewDataSource {
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell: ServerViewCell = tableView.dequeueReusableCell(for: indexPath)
let cell: ServerTableViewCell = tableView.dequeueReusableCell(for: indexPath)
let server = viewModel.server(for: indexPath)
let cellViewModel = ServerViewModel(server: server, selected: viewModel.isServerSelected(server))
cell.configure(viewModel: cellViewModel)

@ -3,18 +3,30 @@
import Foundation
import UIKit
struct ServerViewModel {
protocol ServerTableViewCellViewModelType {
var isTopSeparatorHidden: Bool { get }
var accessoryType: UITableViewCell.AccessoryType { get }
var backgroundColor: UIColor { get }
var serverFont: UIFont { get }
var serverName: String { get }
var selectionStyle: UITableViewCell.SelectionStyle { get set }
}
struct ServerViewModel: ServerTableViewCellViewModelType {
private let server: RPCServerOrAuto
private let isSelected: Bool
let isTopSeparatorHidden: Bool
init(server: RPCServerOrAuto, selected: Bool) {
self.server = server
self.isSelected = selected
self.isTopSeparatorHidden = true
}
init(server: RPCServer, selected: Bool) {
self.server = .server(server)
self.isSelected = selected
self.isTopSeparatorHidden = true
}
var accessoryType: UITableViewCell.AccessoryType {
@ -25,15 +37,31 @@ struct ServerViewModel {
}
}
var backgroundColor: UIColor {
return Colors.appBackground
var backgroundColor: UIColor = Colors.appBackground
var serverFont: UIFont = Fonts.regular(size: 17)
var serverName: String {
return server.displayName
}
var selectionStyle: UITableViewCell.SelectionStyle = .default
}
struct TokenListServerTableViewCellViewModel: ServerTableViewCellViewModelType {
private let server: RPCServer
let isTopSeparatorHidden: Bool
var serverFont: UIFont {
return Fonts.regular(size: 17)
init(server: RPCServer, isTopSeparatorHidden: Bool) {
self.server = server
self.isTopSeparatorHidden = isTopSeparatorHidden
}
var accessoryType: UITableViewCell.AccessoryType = LocaleViewCell.selectionAccessoryType.unselected
var backgroundColor: UIColor = GroupedTable.Color.background
var serverFont: UIFont = Fonts.regular(size: 17)
var serverName: String {
return server.displayName
return server.displayName.uppercased()
}
var selectionStyle: UITableViewCell.SelectionStyle = .none
}

@ -2,34 +2,41 @@
import UIKit
class ServerViewCell: UITableViewCell {
class ServerTableViewCell: UITableViewCell {
static let selectionAccessoryType: (selected: UITableViewCell.AccessoryType, unselected: UITableViewCell.AccessoryType) = (selected: .checkmark, unselected: .none)
private let nameLabel = UILabel()
private lazy var topSeparator: UIView = UIView.separator()
override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
let stackView = [.spacerWidth(Table.Metric.plainLeftMargin), nameLabel].asStackView(axis: .horizontal)
stackView.translatesAutoresizingMaskIntoConstraints = false
contentView.addSubview(topSeparator)
contentView.addSubview(stackView)
NSLayoutConstraint.activate([
topSeparator.topAnchor.constraint(equalTo: contentView.topAnchor),
topSeparator.leadingAnchor.constraint(equalTo: contentView.leadingAnchor),
topSeparator.trailingAnchor.constraint(equalTo: contentView.trailingAnchor),
stackView.anchorsConstraint(to: contentView, edgeInsets: .init(top: 7, left: StyleLayout.sideMargin, bottom: 7, right: StyleLayout.sideMargin)),
stackView.heightAnchor.constraint(equalToConstant: 44),
])
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
return nil
}
func configure(viewModel: ServerViewModel) {
selectionStyle = .default
func configure(viewModel: ServerTableViewCellViewModelType) {
selectionStyle = viewModel.selectionStyle
backgroundColor = viewModel.backgroundColor
accessoryType = viewModel.accessoryType
topSeparator.isHidden = viewModel.isTopSeparatorHidden
nameLabel.font = viewModel.serverFont
nameLabel.text = viewModel.serverName
}

@ -46,6 +46,8 @@ class TokensViewController: UIViewController {
tableView.register(FungibleTokenViewCell.self)
tableView.register(EthTokenViewCell.self)
tableView.register(NonFungibleTokenViewCell.self)
tableView.register(ServerTableViewCell.self)
tableView.registerHeaderFooterView(TableViewSectionHeader.self)
tableView.registerHeaderFooterView(ShowAddHideTokensView.self)
tableView.registerHeaderFooterView(ActiveWalletSessionView.self)
@ -354,8 +356,12 @@ class TokensViewController: UIViewController {
private func contractsForCollectiblesFromViewModel() -> [AlphaWallet.Address] {
var contractsForCollectibles = [AlphaWallet.Address]()
for i in (0..<viewModel.numberOfItems()) {
let token = viewModel.item(for: i, section: 0)
contractsForCollectibles.append(token.contractAddress)
switch viewModel.item(for: i, section: 0) {
case .rpcServer:
break
case .tokenObject(let token):
contractsForCollectibles.append(token.contractAddress)
}
}
return contractsForCollectibles
}
@ -398,8 +404,13 @@ extension TokensViewController: StatefulViewController {
extension TokensViewController: UITableViewDelegate {
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
tableView.deselectRow(at: indexPath, animated: true)
let token = viewModel.item(for: indexPath.row, section: indexPath.section)
delegate?.didSelect(token: token, in: self)
switch viewModel.item(for: indexPath.row, section: indexPath.section) {
case .rpcServer:
break
case .tokenObject(let token):
delegate?.didSelect(token: token, in: self)
}
}
//Hide the footer
@ -462,37 +473,45 @@ extension TokensViewController: UITableViewDataSource {
case .addHideToken, .filters, .activeWalletSession:
return UITableViewCell()
case .tokens:
let token = viewModel.item(for: indexPath.row, section: indexPath.section)
let server = token.server
let session = sessions[server]
switch viewModel.item(for: indexPath.row, section: indexPath.section) {
case .rpcServer(let server):
let isTopSeparatorHidden = indexPath.row != 0 && indexPath.section != 0
let cell: ServerTableViewCell = tableView.dequeueReusableCell(for: indexPath)
cell.configure(viewModel: TokenListServerTableViewCellViewModel(server: server, isTopSeparatorHidden: isTopSeparatorHidden))
switch token.type {
case .nativeCryptocurrency:
let cell: EthTokenViewCell = tableView.dequeueReusableCell(for: indexPath)
cell.configure(viewModel: .init(
token: token,
ticker: viewModel.ticker(for: token),
currencyAmount: session.balanceCoordinator.viewModel.currencyAmount,
assetDefinitionStore: assetDefinitionStore
))
return cell
case .erc20:
let cell: FungibleTokenViewCell = tableView.dequeueReusableCell(for: indexPath)
cell.configure(viewModel: .init(token: token,
assetDefinitionStore: assetDefinitionStore,
isVisible: isVisible,
ticker: viewModel.ticker(for: token)
))
return cell
case .erc721, .erc721ForTickets:
let cell: NonFungibleTokenViewCell = tableView.dequeueReusableCell(for: indexPath)
cell.configure(viewModel: .init(token: token, server: server, assetDefinitionStore: assetDefinitionStore))
return cell
case .erc875:
let cell: NonFungibleTokenViewCell = tableView.dequeueReusableCell(for: indexPath)
cell.configure(viewModel: .init(token: token, server: server, assetDefinitionStore: assetDefinitionStore))
return cell
case .tokenObject(let token):
let server = token.server
let session = sessions[server]
switch token.type {
case .nativeCryptocurrency:
let cell: EthTokenViewCell = tableView.dequeueReusableCell(for: indexPath)
cell.configure(viewModel: .init(
token: token,
ticker: viewModel.ticker(for: token),
currencyAmount: session.balanceCoordinator.viewModel.currencyAmount,
assetDefinitionStore: assetDefinitionStore
))
return cell
case .erc20:
let cell: FungibleTokenViewCell = tableView.dequeueReusableCell(for: indexPath)
cell.configure(viewModel: .init(token: token,
assetDefinitionStore: assetDefinitionStore,
isVisible: isVisible,
ticker: viewModel.ticker(for: token)
))
return cell
case .erc721, .erc721ForTickets:
let cell: NonFungibleTokenViewCell = tableView.dequeueReusableCell(for: indexPath)
cell.configure(viewModel: .init(token: token, server: server, assetDefinitionStore: assetDefinitionStore))
return cell
case .erc875:
let cell: NonFungibleTokenViewCell = tableView.dequeueReusableCell(for: indexPath)
cell.configure(viewModel: .init(token: token, server: server, assetDefinitionStore: assetDefinitionStore))
return cell
}
}
}
}
@ -520,28 +539,33 @@ extension TokensViewController: UITableViewDataSource {
}
private func trailingSwipeActionsConfiguration(forRowAt indexPath: IndexPath) -> UISwipeActionsConfiguration? {
let title = R.string.localizable.walletsHideTokenTitle()
let hideAction = UIContextualAction(style: .destructive, title: title) { [weak self] (_, _, completionHandler) in
guard let strongSelf = self else { return }
let token = strongSelf.viewModel.item(for: indexPath.row, section: indexPath.section)
strongSelf.delegate?.didHide(token: token, in: strongSelf)
switch viewModel.item(for: indexPath.row, section: indexPath.section) {
case .rpcServer:
return nil
case .tokenObject(let token):
let title = R.string.localizable.walletsHideTokenTitle()
let hideAction = UIContextualAction(style: .destructive, title: title) { [weak self] (_, _, completionHandler) in
guard let strongSelf = self else { return }
strongSelf.delegate?.didHide(token: token, in: strongSelf)
let didHideToken = strongSelf.viewModel.markTokenHidden(token: token)
if didHideToken {
strongSelf.tableView.deleteRows(at: [indexPath], with: .automatic)
} else {
strongSelf.reloadTableData()
}
let didHideToken = strongSelf.viewModel.markTokenHidden(token: token)
if didHideToken {
strongSelf.tableView.deleteRows(at: [indexPath], with: .automatic)
} else {
strongSelf.reloadTableData()
completionHandler(didHideToken)
}
completionHandler(didHideToken)
}
hideAction.backgroundColor = R.color.danger()
hideAction.image = R.image.hideToken()
let configuration = UISwipeActionsConfiguration(actions: [hideAction])
configuration.performsFirstActionWithFullSwipe = true
hideAction.backgroundColor = R.color.danger()
hideAction.image = R.image.hideToken()
let configuration = UISwipeActionsConfiguration(actions: [hideAction])
configuration.performsFirstActionWithFullSwipe = true
return configuration
return configuration
}
}
}
@ -588,12 +612,17 @@ extension TokensViewController: UICollectionViewDataSource {
}
public func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let token = viewModel.item(for: indexPath.row, section: indexPath.section)
let server = token.server
let session = sessions[server]
let cell: OpenSeaNonFungibleTokenViewCell = collectionView.dequeueReusableCell(for: indexPath)
cell.configure(viewModel: .init(config: session.config, token: token, forWallet: account, assetDefinitionStore: assetDefinitionStore, eventsDataStore: eventsDataStore))
return cell
switch viewModel.item(for: indexPath.row, section: indexPath.section) {
case .rpcServer:
return UICollectionViewCell()
case .tokenObject(let token):
let server = token.server
let session = sessions[server]
let cell: OpenSeaNonFungibleTokenViewCell = collectionView.dequeueReusableCell(for: indexPath)
cell.configure(viewModel: .init(config: session.config, token: token, forWallet: account, assetDefinitionStore: assetDefinitionStore, eventsDataStore: eventsDataStore))
return cell
}
}
func collectionView(_ collectionView: UICollectionView, viewForSupplementaryElementOfKind kind: String, at indexPath: IndexPath) -> UICollectionReusableView {
@ -606,8 +635,13 @@ extension TokensViewController: UICollectionViewDataSource {
extension TokensViewController: UICollectionViewDelegate {
public func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
collectiblesCollectionView.deselectItem(at: indexPath, animated: true)
let token = viewModel.item(for: indexPath.item, section: indexPath.section)
delegate?.didSelect(token: token, in: self)
switch viewModel.item(for: indexPath.item, section: indexPath.section) {
case .rpcServer:
break
case .tokenObject(let token):
delegate?.didSelect(token: token, in: self)
}
}
}
@ -666,20 +700,13 @@ extension TokensViewController {
navigationItem.hidesSearchBarWhenScrolling = true
}
private func fixTableViewBackgroundColor() {
let v = UIView()
v.backgroundColor = viewModel.backgroundColor
tableView.backgroundView?.backgroundColor = viewModel.backgroundColor
tableView.backgroundView = v
}
private func fixNavigationBarAndStatusBarBackgroundColorForiOS13Dot1() {
view.superview?.backgroundColor = viewModel.backgroundColor
}
private func setupFilteringWithKeyword() {
wireUpSearchController()
fixTableViewBackgroundColor()
TokensViewController.functional.fixTableViewBackgroundColor(tableView: tableView, backgroundColor: viewModel.backgroundColor)
doNotDimTableViewToReuseTableForFilteringResult()
makeSwitchToAnotherTabWorkWhileFiltering()
}
@ -710,12 +737,15 @@ extension TokensViewController: ShowAddHideTokensViewDelegate {
}
}
extension UIBarButtonItem {
static func qrCodeBarButton(_ target: AnyObject, selector: Selector) -> UIBarButtonItem {
return .init(image: R.image.qr_code_icon(), style: .plain, target: target, action: selector)
}
extension TokensViewController {
class functional {}
}
static func addBarButton(_ target: AnyObject, selector: Selector) -> UIBarButtonItem {
return .init(image: R.image.add_hide_tokens(), style: .plain, target: target, action: selector)
extension TokensViewController.functional {
static func fixTableViewBackgroundColor(tableView: UITableView, backgroundColor: UIColor) {
let v = UIView()
v.backgroundColor = backgroundColor
tableView.backgroundView?.backgroundColor = backgroundColor
tableView.backgroundView = v
}
}

@ -3,6 +3,24 @@
import Foundation
import UIKit
enum TokenObjectOrRpcServerPair {
case tokenObject(TokenObject)
case rpcServer(RPCServer)
var canDelete: Bool {
switch self {
case .rpcServer:
return false
case .tokenObject(let token):
guard !token.isInvalidated else { return false }
if token.contractAddress.sameContract(as: Constants.nativeCryptoAddressInDatabase) {
return false
}
return true
}
}
}
//Must be a class, and not a struct, otherwise changing `filter` will silently create a copy of TokensViewModel when user taps to change the filter in the UI and break filtering
class TokensViewModel {
//Must be computed because localization can be overridden by user dynamically
@ -18,7 +36,7 @@ class TokensViewModel {
}
}
lazy var filteredTokens: [TokenObject] = {
lazy var filteredTokens: [TokenObjectOrRpcServerPair] = {
return filteredAndSortedTokens()
}()
@ -61,7 +79,7 @@ class TokensViewModel {
return filteredTokens.count
}
func item(for row: Int, section: Int) -> TokenObject {
func item(for row: Int, section: Int) -> TokenObjectOrRpcServerPair {
return filteredTokens[row]
}
@ -70,25 +88,15 @@ class TokensViewModel {
}
func canDelete(for row: Int, section: Int) -> Bool {
let token = item(for: row, section: section)
guard !token.isInvalidated else { return false }
if token.contractAddress.sameContract(as: Constants.nativeCryptoAddressInDatabase) {
return false
}
return true
return item(for: row, section: section).canDelete
}
init(filterTokensCoordinator: FilterTokensCoordinator, tokens: [TokenObject], tickers: [AddressAndRPCServer: CoinTicker]) {
self.filterTokensCoordinator = filterTokensCoordinator
self.tokens = Self.filterAwaySpuriousTokens(tokens)
self.tokens = TokensViewModel.functional.filterAwaySpuriousTokens(tokens)
self.tickers = tickers
}
//Remove tokens that look unwanted in the Wallet tab
private static func filterAwaySpuriousTokens(_ tokens: [TokenObject]) -> [TokenObject] {
tokens.filter { !($0.name.isEmpty && $0.symbol.isEmpty && $0.decimals == 0) }
}
func markTokenHidden(token: TokenObject) -> Bool {
if let index = tokens.firstIndex(where: { $0.primaryKey == token.primaryKey }) {
tokens.remove(at: index)
@ -100,9 +108,11 @@ class TokensViewModel {
return false
}
private func filteredAndSortedTokens() -> [TokenObject] {
private func filteredAndSortedTokens() -> [TokenObjectOrRpcServerPair] {
let displayedTokens = filterTokensCoordinator.filterTokens(tokens: tokens, filter: filter)
return filterTokensCoordinator.sortDisplayedTokens(tokens: displayedTokens)
let tokens = filterTokensCoordinator.sortDisplayedTokens(tokens: displayedTokens)
return TokensViewModel.functional.groupTokenObjectsWithServers(tokens: tokens)
}
func nativeCryptoCurrencyToken(forServer server: RPCServer) -> TokenObject? {
@ -160,3 +170,34 @@ fileprivate extension WalletFilter {
return WalletFilter.orderedTabs.firstIndex { $0 == self }.flatMap { UInt($0) }
}
}
extension TokensViewModel {
class functional {}
}
extension TokensViewModel.functional {
static func groupTokenObjectsWithServers(tokens: [TokenObject]) -> [TokenObjectOrRpcServerPair] {
var servers: [RPCServer] = []
var results: [TokenObjectOrRpcServerPair] = []
for each in tokens {
guard !servers.contains(each.server) else { continue }
servers.append(each.server)
}
for each in servers {
let tokens = tokens.filter { $0.server == each }.map { TokenObjectOrRpcServerPair.tokenObject($0) }
guard !tokens.isEmpty else { continue }
results.append(.rpcServer(each))
results.append(contentsOf: tokens)
}
return results
}
//Remove tokens that look unwanted in the Wallet tab
static func filterAwaySpuriousTokens(_ tokens: [TokenObject]) -> [TokenObject] {
tokens.filter { !($0.name.isEmpty && $0.symbol.isEmpty && $0.decimals == 0) }
}
}

@ -535,37 +535,4 @@ extension ConfigureTransactionViewController: TextFieldDelegate {
return true
}
}
extension UIBarButtonItem {
static func saveBarButton(_ target: AnyObject, selector: Selector) -> UIBarButtonItem {
.init(title: R.string.localizable.save(), style: .plain, target: target, action: selector)
}
static func backBarButton(selectionClosure: @escaping () -> Void) -> UIBarButtonItem {
let barButton = UIBarButtonItem(image: R.image.backWhite(), style: .plain, target: nil, action: nil)
barButton.selectionClosure = selectionClosure
return barButton
}
private struct AssociatedObject {
static var key = "action_closure_key"
}
var selectionClosure: (() -> Void)? {
get {
return objc_getAssociatedObject(self, &AssociatedObject.key) as? () -> Void
}
set {
objc_setAssociatedObject(self, &AssociatedObject.key, newValue, .OBJC_ASSOCIATION_RETAIN_NONATOMIC)
target = self
action = #selector(didTapButton)
}
}
@objc func didTapButton(_ sender: Any) {
selectionClosure?()
}
}
}

Loading…
Cancel
Save