From 90a062766e1457145139212fe5772cb8a3bca309 Mon Sep 17 00:00:00 2001 From: Hwee-Boon Yar Date: Mon, 14 May 2018 13:55:09 +0800 Subject: [PATCH] Style wallet list screen --- .../ViewModels/AccountViewModel.swift | 37 ++++- .../ViewModels/AccountsViewController.swift | 109 ++++++++++----- Trust/Accounts/Views/AccountViewCell.swift | 126 ++++++++++++++---- TrustTests/Settings/ConfigTests.swift | 7 +- 4 files changed, 211 insertions(+), 68 deletions(-) diff --git a/Trust/Accounts/ViewModels/AccountViewModel.swift b/Trust/Accounts/ViewModels/AccountViewModel.swift index 589d7a2d1..88e71202d 100644 --- a/Trust/Accounts/ViewModels/AccountViewModel.swift +++ b/Trust/Accounts/ViewModels/AccountViewModel.swift @@ -13,16 +13,45 @@ struct AccountViewModel { self.current = current self.walletBalance = walletBalance } - var isWatch: Bool { + var showWatchIcon: Bool { return wallet.type == .watch(wallet.address) } var balance: String { - return walletBalance?.amountFull ?? "--" + let amount = walletBalance?.amountFull ?? "--" + return "\(amount) ETH" } - var title: String { + var address: String { return wallet.address.description } - var isActive: Bool { + var showActiveIcon: Bool { return wallet == current } + + var backgroundColor: UIColor { + return Colors.appWhite + } + + var contentsBackgroundColor: UIColor { + return backgroundColor + } + + var contentsBorderColor: UIColor { + return Colors.appHighlightGreen + } + + var contentsBorderWidth: CGFloat { + return 1 + } + + var balanceFont: UIFont { + return Fonts.light(size: 20)! + } + + var addressFont: UIFont { + return Fonts.semibold(size: 12)! + } + + var addressTextColor: UIColor { + return Colors.gray + } } diff --git a/Trust/Accounts/ViewModels/AccountsViewController.swift b/Trust/Accounts/ViewModels/AccountsViewController.swift index aba8f38c7..6a504b36f 100644 --- a/Trust/Accounts/ViewModels/AccountsViewController.swift +++ b/Trust/Accounts/ViewModels/AccountsViewController.swift @@ -9,10 +9,13 @@ protocol AccountsViewControllerDelegate: class { func didSelectInfoForAccount(account: Wallet, sender: UIView, in viewController: AccountsViewController) } -class AccountsViewController: UITableViewController { +class AccountsViewController: UIViewController { + let headerHeight = CGFloat(70) weak var delegate: AccountsViewControllerDelegate? var allowsAccountDeletion: Bool = false - var headerTitle: String? + let roundedBackground = RoundedBackground() + let header = TicketsViewControllerTitleHeader() + let tableView = UITableView(frame: .zero, style: .plain) var viewModel: AccountsViewModel { return AccountsViewModel( wallets: wallets @@ -30,21 +33,41 @@ class AccountsViewController: UITableViewController { private var balances: [Address: Balance?] = [:] private let keystore: Keystore private let balanceCoordinator: GetBalanceCoordinator + init( keystore: Keystore, balanceCoordinator: GetBalanceCoordinator ) { self.keystore = keystore self.balanceCoordinator = balanceCoordinator - super.init(style: .grouped) + super.init(nibName: nil, bundle: nil) + + view.backgroundColor = Colors.appBackground + + roundedBackground.translatesAutoresizingMaskIntoConstraints = false + view.addSubview(roundedBackground) + + tableView.translatesAutoresizingMaskIntoConstraints = false + tableView.delegate = self + tableView.separatorStyle = .none + tableView.backgroundColor = Colors.appWhite + tableView.rowHeight = 80 + tableView.tableHeaderView = header + tableView.register(AccountViewCell.self, forCellReuseIdentifier: AccountViewCell.identifier) + roundedBackground.addSubview(tableView) + + NSLayoutConstraint.activate([ + header.heightAnchor.constraint(equalToConstant: headerHeight), + + tableView.leadingAnchor.constraint(equalTo: roundedBackground.leadingAnchor), + tableView.trailingAnchor.constraint(equalTo: roundedBackground.trailingAnchor), + tableView.topAnchor.constraint(equalTo: roundedBackground.topAnchor), + tableView.bottomAnchor.constraint(equalTo: view.bottomAnchor), + ] + roundedBackground.createConstraintsWithContainer(view: view)) + fetch() } - override func viewDidLoad() { - super.viewDidLoad() - tableView.rowHeight = UITableViewAutomaticDimension - tableView.estimatedRowHeight = 55 - tableView.register(R.nib.accountViewCell(), forCellReuseIdentifier: R.nib.accountViewCell.name) - } + override func viewWillAppear(_ animated: Bool) { super.viewWillAppear(animated) fetch() @@ -54,37 +77,14 @@ class AccountsViewController: UITableViewController { wallets = keystore.wallets } func configure(viewModel: AccountsViewModel) { - title = headerTitle ?? viewModel.title + tableView.dataSource = self + header.configure(title: viewModel.title) + header.frame.size.height = headerHeight + tableView.tableHeaderView = header } func account(for indexPath: IndexPath) -> Wallet { return viewModel.wallets[indexPath.row] } - override func numberOfSections(in tableView: UITableView) -> Int { - return 1 - } - override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { - return viewModel.wallets.count - } - override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { - let cell = self.tableView.dequeueReusableCell(withIdentifier: R.nib.accountViewCell.name, for: indexPath) as! AccountViewCell - cell.viewModel = getAccountViewModels(for: indexPath) - cell.delegate = self - return cell - } - override func tableView(_ tableView: UITableView, canEditRowAt indexPath: IndexPath) -> Bool { - return allowsAccountDeletion && (EtherKeystore.current != viewModel.wallets[indexPath.row] || viewModel.wallets.count == 1) - } - override func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCellEditingStyle, forRowAt indexPath: IndexPath) { - if editingStyle == UITableViewCellEditingStyle.delete { - let account = self.account(for: indexPath) - confirmDelete(account: account) - } - } - override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { - tableView.deselectRow(at: indexPath, animated: true) - let account = self.account(for: indexPath) - delegate?.didSelectAccount(account: account, in: self) - } func confirmDelete(account: Wallet) { confirm( title: R.string.localizable.accountsConfirmDeleteTitle(), @@ -136,6 +136,43 @@ class AccountsViewController: UITableViewController { fatalError("init(coder:) has not been implemented") } } + +extension AccountsViewController: UITableViewDelegate, UITableViewDataSource { + func numberOfSections(in tableView: UITableView) -> Int { + return 1 + } + + func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { + return viewModel.wallets.count + } + + func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { + let cell = tableView.dequeueReusableCell(withIdentifier: AccountViewCell.identifier, for: indexPath) as! AccountViewCell + let cellViewModel = getAccountViewModels(for: indexPath) + cell.configure(viewModel: cellViewModel) + cell.account = cellViewModel.wallet + cell.delegate = self + return cell + } + + func tableView(_ tableView: UITableView, canEditRowAt indexPath: IndexPath) -> Bool { + return allowsAccountDeletion && (EtherKeystore.current != viewModel.wallets[indexPath.row] || viewModel.wallets.count == 1) + } + + func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCellEditingStyle, forRowAt indexPath: IndexPath) { + if editingStyle == UITableViewCellEditingStyle.delete { + let account = self.account(for: indexPath) + confirmDelete(account: account) + } + } + + func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { + tableView.deselectRow(at: indexPath, animated: true) + let account = self.account(for: indexPath) + delegate?.didSelectAccount(account: account, in: self) + } +} + extension AccountsViewController: AccountViewCellDelegate { func accountViewCell(_ cell: AccountViewCell, didTapInfoViewForAccount account: Wallet) { self.delegate?.didSelectInfoForAccount(account: account, sender: cell.infoButton, in: self) diff --git a/Trust/Accounts/Views/AccountViewCell.swift b/Trust/Accounts/Views/AccountViewCell.swift index 0183b8075..07a30cd20 100644 --- a/Trust/Accounts/Views/AccountViewCell.swift +++ b/Trust/Accounts/Views/AccountViewCell.swift @@ -1,5 +1,4 @@ // Copyright SIX DAY LLC. All rights reserved. - import TrustKeystore import UIKit @@ -8,33 +7,110 @@ protocol AccountViewCellDelegate: class { } class AccountViewCell: UITableViewCell { - @IBOutlet weak var infoButton: UIButton! - @IBOutlet weak var activeView: UIView! - @IBOutlet weak var glassesImageView: UIImageView! - @IBOutlet weak var walletTypeImageView: UIImageView! - @IBOutlet weak var addressLable: UILabel! - @IBOutlet weak var balanceLable: UILabel! + static let identifier = "AccountViewCell" + + let background = UIView() + var infoButton = UIButton(type: .infoLight) + var activeIcon = UIImageView(image: R.image.ticket_bundle_checked()) + var watchIcon = UIImageView(image: R.image.glasses()) + var addressLabel = UILabel() + var balanceLabel = UILabel() weak var delegate: AccountViewCellDelegate? - var viewModel: AccountViewModel? { - didSet { - guard let model = viewModel else { - return - } - balanceLable.text = "\(model.balance) ETH" - glassesImageView.isHidden = !model.isWatch - activeView.isHidden = !model.isActive - addressLable.text = model.title - infoButton.tintColor = Colors.appBackground - } + var account: Wallet? = nil + + override init(style: UITableViewCellStyle, reuseIdentifier: String?) { + super.init(style: style, reuseIdentifier: reuseIdentifier) + + contentView.addSubview(background) + background.translatesAutoresizingMaskIntoConstraints = false + + activeIcon.translatesAutoresizingMaskIntoConstraints = false + activeIcon.contentMode = .scaleAspectFit + + balanceLabel.translatesAutoresizingMaskIntoConstraints = false + + addressLabel.translatesAutoresizingMaskIntoConstraints = false + addressLabel.lineBreakMode = .byTruncatingMiddle + + infoButton.translatesAutoresizingMaskIntoConstraints = false + infoButton.addTarget(self, action: #selector(infoAction), for: .touchUpInside) + + let leftStackView = [ + balanceLabel, + addressLabel, + ].asStackView(axis: .vertical, distribution: .fillProportionally, spacing: 6) + leftStackView.translatesAutoresizingMaskIntoConstraints = false + + let rightStackView = [infoButton].asStackView() + rightStackView.translatesAutoresizingMaskIntoConstraints = false + + let stackView = [activeIcon, leftStackView, watchIcon, rightStackView].asStackView(spacing: 15, alignment: .center) + stackView.translatesAutoresizingMaskIntoConstraints = false + + activeIcon.setContentHuggingPriority(UILayoutPriority.defaultLow, for: .horizontal) + addressLabel.setContentHuggingPriority(UILayoutPriority.defaultLow, for: .horizontal) + balanceLabel.setContentHuggingPriority(UILayoutPriority.defaultLow, for: .horizontal) + + infoButton.setContentHuggingPriority(UILayoutPriority.required, for: .horizontal) + infoButton.setContentCompressionResistancePriority(UILayoutPriority.required, for: .horizontal) + watchIcon.setContentHuggingPriority(UILayoutPriority.required, for: .horizontal) + watchIcon.setContentCompressionResistancePriority(UILayoutPriority.required, for: .horizontal) + watchIcon.setContentHuggingPriority(UILayoutPriority.required, for: .vertical) + watchIcon.setContentCompressionResistancePriority(UILayoutPriority.required, for: .vertical) + stackView.setContentHuggingPriority(UILayoutPriority.required, for: .horizontal) + + background.addSubview(stackView) + + // TODO extract constant. Maybe StyleLayout.sideMargin + let xMargin = CGFloat(7) + let yMargin = CGFloat(7) + NSLayoutConstraint.activate([ + activeIcon.widthAnchor.constraint(equalToConstant: 44), + + watchIcon.widthAnchor.constraint(lessThanOrEqualToConstant: 18), + watchIcon.heightAnchor.constraint(lessThanOrEqualToConstant: 18), + + stackView.topAnchor.constraint(equalTo: background.topAnchor, constant: StyleLayout.sideMargin), + stackView.trailingAnchor.constraint(equalTo: background.trailingAnchor, constant: -StyleLayout.sideMargin), + stackView.bottomAnchor.constraint(equalTo: background.bottomAnchor, constant: -StyleLayout.sideMargin), + stackView.leadingAnchor.constraint(equalTo: background.leadingAnchor, constant: StyleLayout.sideMargin), + + background.leadingAnchor.constraint(equalTo: leadingAnchor, constant: xMargin), + background.trailingAnchor.constraint(equalTo: trailingAnchor, constant: -xMargin), + background.topAnchor.constraint(equalTo: topAnchor, constant: yMargin), + background.bottomAnchor.constraint(equalTo: bottomAnchor, constant: -yMargin), + ]) } - override func prepareForReuse() { - super.prepareForReuse() - self.viewModel = nil + + required init?(coder aDecoder: NSCoder) { + fatalError("init(coder:) has not been implemented") } - @IBAction func infoAction(_ sender: Any) { - guard let account = viewModel?.wallet else { - return - } + + @objc func infoAction() { + guard let account = account else { return } delegate?.accountViewCell(self, didTapInfoViewForAccount: account) } + + func configure(viewModel: AccountViewModel) { + selectionStyle = .none + backgroundColor = viewModel.backgroundColor + + background.backgroundColor = viewModel.contentsBackgroundColor + background.layer.cornerRadius = 20 + background.borderColor = viewModel.contentsBorderColor + background.borderWidth = viewModel.contentsBorderWidth + + activeIcon.isHidden = !viewModel.showActiveIcon + + balanceLabel.font = viewModel.balanceFont + balanceLabel.text = viewModel.balance + + addressLabel.font = viewModel.addressFont + addressLabel.textColor = viewModel.addressTextColor + addressLabel.text = viewModel.address + + infoButton.tintColor = Colors.appBackground + + watchIcon.isHidden = !viewModel.showWatchIcon + } } diff --git a/TrustTests/Settings/ConfigTests.swift b/TrustTests/Settings/ConfigTests.swift index e36ff313e..179fd2eae 100644 --- a/TrustTests/Settings/ConfigTests.swift +++ b/TrustTests/Settings/ConfigTests.swift @@ -57,9 +57,10 @@ class ConfigTests: XCTestCase { config.locale = AppLocale.english.id config.locale = AppLocale.simplifiedChinese.id - let controller = AccountsViewController(keystore: FakeKeystore(), balanceCoordinator: FakeGetBalanceCoordinator()) - let _ = controller.view - XCTAssertNoThrow(controller.tableView.dequeueReusableCell(withIdentifier: R.nib.accountViewCell.name, for: .init(row: 0, section: 0))) + + let tableView = UITableView() + tableView.register(R.nib.editTokenTableViewCell(), forCellReuseIdentifier: R.nib.editTokenTableViewCell.name) + XCTAssertNoThrow(tableView.dequeueReusableCell(withIdentifier: R.nib.editTokenTableViewCell.name)) //Must change this back to system, otherwise other tests will break either immediately or the next run config.locale = AppLocale.system.id