parent
8b8ca20160
commit
b07e158a10
@ -0,0 +1,77 @@ |
||||
// Copyright SIX DAY LLC, Inc. All rights reserved. |
||||
|
||||
import Foundation |
||||
import UIKit |
||||
|
||||
class TokenViewCell: UITableViewCell { |
||||
|
||||
static let identifier = "TokenViewCell" |
||||
|
||||
let titleLabel = UILabel() |
||||
let amountLabel = UILabel() |
||||
let symbolImageView = UIImageView() |
||||
let subTitleLabel = UILabel() |
||||
|
||||
override init(style: UITableViewCellStyle, reuseIdentifier: String?) { |
||||
super.init(style: style, reuseIdentifier: reuseIdentifier) |
||||
|
||||
titleLabel.translatesAutoresizingMaskIntoConstraints = false |
||||
|
||||
subTitleLabel.translatesAutoresizingMaskIntoConstraints = false |
||||
subTitleLabel.lineBreakMode = .byTruncatingMiddle |
||||
|
||||
symbolImageView.translatesAutoresizingMaskIntoConstraints = false |
||||
symbolImageView.image = R.image.accountsSwitch() |
||||
|
||||
amountLabel.textAlignment = .right |
||||
amountLabel.translatesAutoresizingMaskIntoConstraints = false |
||||
|
||||
let leftStackView = UIStackView(arrangedSubviews: [titleLabel, subTitleLabel]) |
||||
leftStackView.translatesAutoresizingMaskIntoConstraints = false |
||||
leftStackView.axis = .vertical |
||||
leftStackView.spacing = 6 |
||||
|
||||
let rightStackView = UIStackView(arrangedSubviews: [amountLabel]) |
||||
rightStackView.translatesAutoresizingMaskIntoConstraints = false |
||||
rightStackView.axis = .vertical |
||||
|
||||
let stackView = UIStackView(arrangedSubviews: [symbolImageView, leftStackView, rightStackView]) |
||||
stackView.translatesAutoresizingMaskIntoConstraints = false |
||||
stackView.axis = .horizontal |
||||
stackView.spacing = 10 |
||||
stackView.distribution = .fill |
||||
|
||||
symbolImageView.setContentHuggingPriority(UILayoutPriorityDefaultLow, for: .horizontal) |
||||
titleLabel.setContentHuggingPriority(UILayoutPriorityDefaultLow, for: .horizontal) |
||||
|
||||
amountLabel.setContentHuggingPriority(UILayoutPriorityRequired, for: .horizontal) |
||||
stackView.setContentHuggingPriority(UILayoutPriorityRequired, for: .horizontal) |
||||
|
||||
addSubview(stackView) |
||||
|
||||
NSLayoutConstraint.activate([ |
||||
stackView.topAnchor.constraint(equalTo: topAnchor, constant: Layout.sideMargin), |
||||
stackView.trailingAnchor.constraint(equalTo: trailingAnchor, constant: -Layout.sideMargin), |
||||
stackView.bottomAnchor.constraint(lessThanOrEqualTo: bottomAnchor, constant: -Layout.sideMargin), |
||||
stackView.leadingAnchor.constraint(equalTo: leadingAnchor, constant: Layout.sideMargin), |
||||
]) |
||||
} |
||||
|
||||
required init?(coder aDecoder: NSCoder) { |
||||
fatalError("init(coder:) has not been implemented") |
||||
} |
||||
|
||||
func configure(viewModel: TokenViewCellViewModel) { |
||||
titleLabel.text = viewModel.title |
||||
|
||||
amountLabel.text = viewModel.amount |
||||
amountLabel.textColor = viewModel.amountTextColor |
||||
amountLabel.font = viewModel.amountFont |
||||
|
||||
subTitleLabel.text = viewModel.subTitle |
||||
subTitleLabel.textColor = viewModel.subTitleTextColor |
||||
subTitleLabel.font = viewModel.subTitleFont |
||||
|
||||
backgroundColor = viewModel.backgroundColor |
||||
} |
||||
} |
@ -0,0 +1,45 @@ |
||||
// Copyright SIX DAY LLC, Inc. All rights reserved. |
||||
|
||||
import Foundation |
||||
import UIKit |
||||
|
||||
struct TokenViewCellViewModel { |
||||
|
||||
let token: Token |
||||
|
||||
init(token: Token) { |
||||
self.token = token |
||||
} |
||||
|
||||
var title: String { |
||||
return token.name |
||||
} |
||||
|
||||
var amount: String { |
||||
return "\(token.balance)" |
||||
} |
||||
|
||||
var amountTextColor: UIColor { |
||||
return Colors.black |
||||
} |
||||
|
||||
var amountFont: UIFont { |
||||
return UIFont.systemFont(ofSize: 17, weight: UIFontWeightMedium) |
||||
} |
||||
|
||||
var subTitle: String { |
||||
return token.symbol |
||||
} |
||||
|
||||
var subTitleTextColor: UIColor { |
||||
return Colors.gray |
||||
} |
||||
|
||||
var subTitleFont: UIFont { |
||||
return UIFont.systemFont(ofSize: 12, weight: UIFontWeightThin) |
||||
} |
||||
|
||||
var backgroundColor: UIColor { |
||||
return .white |
||||
} |
||||
} |
@ -0,0 +1,37 @@ |
||||
// Copyright SIX DAY LLC, Inc. All rights reserved. |
||||
|
||||
import Foundation |
||||
import Result |
||||
import APIKit |
||||
|
||||
protocol TokensDataStoreDelegate: class { |
||||
func didUpdate(result: Result<TokensViewModel, TokenError>) |
||||
} |
||||
|
||||
class TokensDataStore { |
||||
|
||||
let account: Account |
||||
weak var delegate: TokensDataStoreDelegate? |
||||
var tokens: [Token] = [] |
||||
|
||||
init(account: Account) { |
||||
self.account = account |
||||
} |
||||
|
||||
func update(tokens: [Token]) { |
||||
self.tokens = tokens |
||||
delegate?.didUpdate(result: .success(TokensViewModel(tokens: tokens))) |
||||
} |
||||
|
||||
func fetch() { |
||||
let request = GetTokensRequest(address: account.address.address) |
||||
Session.send(request) { result in |
||||
switch result { |
||||
case .success(let response): |
||||
self.update(tokens: response) |
||||
case .failure: |
||||
self.delegate?.didUpdate(result: .failure(TokenError.failedToFetch)) |
||||
} |
||||
} |
||||
} |
||||
} |
@ -0,0 +1,133 @@ |
||||
// Copyright SIX DAY LLC, Inc. All rights reserved. |
||||
|
||||
import Foundation |
||||
import UIKit |
||||
import StatefulViewController |
||||
import Result |
||||
|
||||
class TokensViewController: UIViewController { |
||||
|
||||
private lazy var dataStore: TokensDataStore = { |
||||
return .init(account: self.account) |
||||
}() |
||||
|
||||
var viewModel: TokensViewModel = TokensViewModel(tokens: []) |
||||
let account: Account |
||||
let tableView: UITableView |
||||
let refreshControl = UIRefreshControl() |
||||
|
||||
init( |
||||
account: Account |
||||
) { |
||||
self.account = account |
||||
tableView = UITableView(frame: .zero, style: .plain) |
||||
|
||||
super.init(nibName: nil, bundle: nil) |
||||
|
||||
dataStore.delegate = self |
||||
|
||||
tableView.translatesAutoresizingMaskIntoConstraints = false |
||||
tableView.delegate = self |
||||
tableView.dataSource = self |
||||
tableView.separatorStyle = .none |
||||
tableView.backgroundColor = .white |
||||
tableView.rowHeight = 72 |
||||
view.addSubview(tableView) |
||||
|
||||
NSLayoutConstraint.activate([ |
||||
tableView.topAnchor.constraint(equalTo: view.topAnchor), |
||||
tableView.trailingAnchor.constraint(equalTo: view.trailingAnchor), |
||||
tableView.bottomAnchor.constraint(equalTo: view.bottomAnchor), |
||||
tableView.leadingAnchor.constraint(equalTo: view.leadingAnchor), |
||||
]) |
||||
|
||||
refreshControl.addTarget(self, action: #selector(pullToRefresh), for: .valueChanged) |
||||
tableView.addSubview(refreshControl) |
||||
|
||||
errorView = { |
||||
let view = ErrorView() |
||||
view.onRetry = fetch |
||||
return view |
||||
}() |
||||
|
||||
loadingView = { |
||||
let view = LoadingView() |
||||
return view |
||||
}() |
||||
|
||||
emptyView = { |
||||
let view = EmptyView() |
||||
view.onRetry = fetch |
||||
return view |
||||
}() |
||||
|
||||
title = viewModel.title |
||||
view.backgroundColor = viewModel.backgroundColor |
||||
} |
||||
|
||||
override func viewWillAppear(_ animated: Bool) { |
||||
super.viewWillAppear(animated) |
||||
|
||||
fetch() |
||||
} |
||||
|
||||
func pullToRefresh() { |
||||
refreshControl.beginRefreshing() |
||||
fetch() |
||||
} |
||||
|
||||
func fetch() { |
||||
dataStore.fetch() |
||||
startLoading() |
||||
} |
||||
|
||||
required init?(coder aDecoder: NSCoder) { |
||||
fatalError("init(coder:) has not been implemented") |
||||
} |
||||
} |
||||
|
||||
extension TokensViewController: StatefulViewController { |
||||
func hasContent() -> Bool { |
||||
return viewModel.hasContent |
||||
} |
||||
} |
||||
|
||||
extension TokensViewController: UITableViewDelegate { |
||||
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { |
||||
tableView.deselectRow(at: indexPath, animated: true ) |
||||
} |
||||
} |
||||
|
||||
extension TokensViewController: TokensDataStoreDelegate { |
||||
func didUpdate(result: Result<TokensViewModel, TokenError>) { |
||||
switch result { |
||||
case .success(let viewModel): |
||||
self.viewModel = viewModel |
||||
endLoading() |
||||
case .failure(let error): |
||||
endLoading(error: error) |
||||
} |
||||
tableView.reloadData() |
||||
|
||||
if refreshControl.isRefreshing { |
||||
refreshControl.endRefreshing() |
||||
} |
||||
} |
||||
} |
||||
|
||||
extension TokensViewController: UITableViewDataSource { |
||||
func numberOfSections(in tableView: UITableView) -> Int { |
||||
return viewModel.numberOfSections |
||||
} |
||||
|
||||
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { |
||||
let token = viewModel.item(for: indexPath.row, section: indexPath.section) |
||||
let cell = TokenViewCell(style: .default, reuseIdentifier: TokenViewCell.identifier) |
||||
cell.configure(viewModel: .init(token: token)) |
||||
return cell |
||||
} |
||||
|
||||
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { |
||||
return viewModel.numberOfItems(for: section) |
||||
} |
||||
} |
@ -0,0 +1,37 @@ |
||||
// Copyright SIX DAY LLC, Inc. All rights reserved. |
||||
|
||||
import Foundation |
||||
import UIKit |
||||
|
||||
struct TokensViewModel { |
||||
|
||||
var tokens: [Token] = [] |
||||
|
||||
init(tokens: [Token]) { |
||||
self.tokens = tokens |
||||
} |
||||
|
||||
var title: String { |
||||
return "Tokens" |
||||
} |
||||
|
||||
var backgroundColor: UIColor { |
||||
return .white |
||||
} |
||||
|
||||
var hasContent: Bool { |
||||
return !tokens.isEmpty |
||||
} |
||||
|
||||
var numberOfSections: Int { |
||||
return 1 |
||||
} |
||||
|
||||
func numberOfItems(for section: Int) -> Int { |
||||
return tokens.count |
||||
} |
||||
|
||||
func item(for row: Int, section: Int) -> Token { |
||||
return tokens[row] |
||||
} |
||||
} |
Loading…
Reference in new issue