From f9f031d914509621751677e201819c92c456f976 Mon Sep 17 00:00:00 2001 From: Julio Fernandes Date: Sat, 10 Sep 2022 15:32:12 -0300 Subject: [PATCH] adicionando projeto completo --- .../Components/ActivityCellView.swift | 93 ++++++++ .../Components/ActivityListView.swift | 101 +++++++++ .../Components/ContactCellView.swift | 93 ++++++++ .../HomeHeaderView.swift} | 77 +++---- .../FinanceApp/Components/LoadingView.swift | 34 --- .../Components/UserProfileHeaderView.swift | 102 +++++++++ .../Extensions/DispatchQueue+Extensions.swift | 20 ++ .../FinanceApp/Models/ContactModel.swift | 13 ++ .../FinanceApp/Models/UserProfileModel.swift | 21 ++ .../ActivityDetails/ActivityDetailsView.swift | 207 +++++++++--------- .../ActivityDetailsViewController.swift | 31 ++- .../Confirmation/ConfirmationView.swift | 90 +++++++- .../ConfirmationViewController.swift | 18 +- .../Screens/ContactList/ContactListView.swift | 98 ++++++++- .../ContactListViewController.swift | 35 ++- .../Screens/Home/ActivityCellView.swift | 91 -------- .../Screens/Home/ActivityListView.swift | 73 ------ .../FinanceApp/Screens/Home/HomeView.swift | 161 +++++++------- .../Screens/Home/HomeViewController.swift | 53 ++++- .../TabBarViewController.swift | 11 +- .../Screens/Transfers/TransfersView.swift | 92 +++++++- .../Transfers/TransfersViewController.swift | 27 ++- .../Screens/UserProfile/UserProfileView.swift | 110 +++++++++- .../UserProfileViewController.swift | 28 ++- .../FinanceApp/Service/FinanceService.swift | 67 ++++-- .../Doubles/FinanceServiceSpy.swift | 25 +++ .../Doubles/UINavigationControllerSpy.swift | 21 ++ .../FinanceAppTests/FinanceAppTests.swift | 36 --- .../HomeViewControllerTests.swift | 43 ++-- .../TabBarViewControllerTests.swift | 30 +++ .../FinanceAppUITests/FinanceAppUITests.swift | 10 +- 31 files changed, 1370 insertions(+), 541 deletions(-) create mode 100644 solutions/devsprint-julio-fernandes-6/FinanceApp/Components/ActivityCellView.swift create mode 100644 solutions/devsprint-julio-fernandes-6/FinanceApp/Components/ActivityListView.swift create mode 100644 solutions/devsprint-julio-fernandes-6/FinanceApp/Components/ContactCellView.swift rename solutions/devsprint-julio-fernandes-6/FinanceApp/{Screens/Home/ActivityInfoCellView.swift => Components/HomeHeaderView.swift} (71%) delete mode 100644 solutions/devsprint-julio-fernandes-6/FinanceApp/Components/LoadingView.swift create mode 100644 solutions/devsprint-julio-fernandes-6/FinanceApp/Components/UserProfileHeaderView.swift create mode 100644 solutions/devsprint-julio-fernandes-6/FinanceApp/Extensions/DispatchQueue+Extensions.swift create mode 100644 solutions/devsprint-julio-fernandes-6/FinanceApp/Models/ContactModel.swift create mode 100644 solutions/devsprint-julio-fernandes-6/FinanceApp/Models/UserProfileModel.swift delete mode 100644 solutions/devsprint-julio-fernandes-6/FinanceApp/Screens/Home/ActivityCellView.swift delete mode 100644 solutions/devsprint-julio-fernandes-6/FinanceApp/Screens/Home/ActivityListView.swift create mode 100644 solutions/devsprint-julio-fernandes-6/FinanceAppTests/Doubles/FinanceServiceSpy.swift create mode 100644 solutions/devsprint-julio-fernandes-6/FinanceAppTests/Doubles/UINavigationControllerSpy.swift delete mode 100644 solutions/devsprint-julio-fernandes-6/FinanceAppTests/FinanceAppTests.swift create mode 100644 solutions/devsprint-julio-fernandes-6/FinanceAppTests/TabBarViewControllerTests.swift diff --git a/solutions/devsprint-julio-fernandes-6/FinanceApp/Components/ActivityCellView.swift b/solutions/devsprint-julio-fernandes-6/FinanceApp/Components/ActivityCellView.swift new file mode 100644 index 0000000..4c2e55a --- /dev/null +++ b/solutions/devsprint-julio-fernandes-6/FinanceApp/Components/ActivityCellView.swift @@ -0,0 +1,93 @@ +// +// ActivityCellView.swift +// FinanceApp +// +// Created by Joao Gripp on 31/08/22. +// + +import UIKit + +final class ActivityCellView: UITableViewCell { + + private lazy var mainStackView: UIStackView = { + let stack = UIStackView(frame: .zero) + stack.translatesAutoresizingMaskIntoConstraints = false + stack.spacing = 8 + stack.alignment = .center + stack.isLayoutMarginsRelativeArrangement = true + stack.layoutMargins = UIEdgeInsets(top: 0, left: 16, bottom: 0, right: 0) + stack.addArrangedSubview(categoryImageView) + stack.addArrangedSubview(labelsStackView) + return stack + }() + + private lazy var labelsStackView: UIStackView = { + let stack = UIStackView(frame: .zero) + stack.translatesAutoresizingMaskIntoConstraints = false + stack.axis = .vertical + stack.spacing = 8 + stack.addArrangedSubview(activityNameLabel) + stack.addArrangedSubview(activityInfoLabel) + return stack + }() + + private lazy var categoryImageView: UIImageView = { + let imageView = UIImageView() + imageView.translatesAutoresizingMaskIntoConstraints = false + imageView.layer.cornerRadius = 25 + imageView.layer.masksToBounds = true + imageView.image = UIImage(named: "bag.circle.fill") + imageView.tintColor = .systemPurple + return imageView + }() + + private lazy var activityNameLabel: UILabel = { + let label = UILabel() + label.translatesAutoresizingMaskIntoConstraints = false + label.font = UIFont.boldSystemFont(ofSize: 17) + return label + }() + + private lazy var activityInfoLabel: UILabel = { + let label = UILabel() + label.translatesAutoresizingMaskIntoConstraints = false + label.textColor = .gray + label.font = UIFont.systemFont(ofSize: 14) + return label + }() + + override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) { + super.init(style: style, reuseIdentifier: reuseIdentifier) + accessoryType = .disclosureIndicator + setupView() + } + + required init?(coder: NSCoder) { + return nil + } +} + +// MARK: ViewCodeProtocol +extension ActivityCellView: ViewCodeProtocol { + func buildViewHierarchy() { + addSubview(mainStackView) + } + + func setupConstraints() { + NSLayoutConstraint.activate([ + mainStackView.topAnchor.constraint(equalTo: safeAreaLayoutGuide.topAnchor), + mainStackView.bottomAnchor.constraint(equalTo: safeAreaLayoutGuide.bottomAnchor), + mainStackView.leadingAnchor.constraint(equalTo: safeAreaLayoutGuide.leadingAnchor), + mainStackView.trailingAnchor.constraint(equalTo: safeAreaLayoutGuide.trailingAnchor), + categoryImageView.widthAnchor.constraint(equalToConstant: 50), + categoryImageView.heightAnchor.constraint(equalToConstant: 50), + ]) + } +} + +extension ActivityCellView { + func setupCell(activity: Activity) { + activityNameLabel.text = activity.name + activityInfoLabel.text = String.activityDetails(with: activity.price, and: activity.time) + } +} diff --git a/solutions/devsprint-julio-fernandes-6/FinanceApp/Components/ActivityListView.swift b/solutions/devsprint-julio-fernandes-6/FinanceApp/Components/ActivityListView.swift new file mode 100644 index 0000000..4106a45 --- /dev/null +++ b/solutions/devsprint-julio-fernandes-6/FinanceApp/Components/ActivityListView.swift @@ -0,0 +1,101 @@ +// +// ActivityListView.swift +// FinanceApp +// +// Created by Joao Gripp on 01/09/22. +// + +import UIKit + +protocol ActivityListViewDelegate: AnyObject { + func didSelectedActivity(_ activity: Activity) +} + +final class ActivityListView: UIView { + + private(set) lazy var tableView: UITableView = { + let tableView = UITableView(frame: .zero) + tableView.translatesAutoresizingMaskIntoConstraints = false + tableView.register(ActivityCellView.self, forCellReuseIdentifier: cellIdentifier) + tableView.dataSource = self + tableView.delegate = self + return tableView + }() + + var activities: [Activity] = [] { + didSet { + tableView.reloadData() + } + } + + weak var delegate: ActivityListViewDelegate? + static let cellSize = CGFloat(82) + private let cellIdentifier = "ActivityCellIdentifier" + + init() { + super.init(frame: .zero) + setupView() + } + + required init?(coder: NSCoder) { + return nil + } +} + +// MARK: ViewCodeProtocol +extension ActivityListView: ViewCodeProtocol { + func buildViewHierarchy() { + addSubview(tableView) + } + + func setupConstraints() { + NSLayoutConstraint.activate([ + tableView.leadingAnchor.constraint(equalTo: safeAreaLayoutGuide.leadingAnchor), + tableView.trailingAnchor.constraint(equalTo: safeAreaLayoutGuide.trailingAnchor), + tableView.topAnchor.constraint(equalTo: safeAreaLayoutGuide.topAnchor), + tableView.bottomAnchor.constraint(equalTo: safeAreaLayoutGuide.bottomAnchor), + ]) + } + + func setupAdditionalConfiguration() { + backgroundColor = .white + tableView.separatorStyle = .none + } + +} + +// MARK: UITableViewDataSource +extension ActivityListView: UITableViewDataSource { + + func numberOfSections(in tableView: UITableView) -> Int { + return 1 + } + + func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? { + return "Activity" + } + + func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { + return activities.count + } + + func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { + let cell = tableView.dequeueReusableCell(withIdentifier: cellIdentifier, for: indexPath) as! ActivityCellView + cell.setupCell(activity: activities[indexPath.row]) + return cell + } +} + +// MARK: UITableViewDelegate +extension ActivityListView: UITableViewDelegate { + + func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat { + return ActivityListView.cellSize + } + + public func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { + tableView.deselectRow(at: indexPath, animated: true) + delegate?.didSelectedActivity(activities[indexPath.row]) + } +} + diff --git a/solutions/devsprint-julio-fernandes-6/FinanceApp/Components/ContactCellView.swift b/solutions/devsprint-julio-fernandes-6/FinanceApp/Components/ContactCellView.swift new file mode 100644 index 0000000..1254a1b --- /dev/null +++ b/solutions/devsprint-julio-fernandes-6/FinanceApp/Components/ContactCellView.swift @@ -0,0 +1,93 @@ +// +// ContactCellView.swift +// FinanceApp +// +// Created by Julio Fernandes on 10/09/22. +// + +import UIKit + +final class ContactCellView: UITableViewCell { + + private lazy var mainStackView: UIStackView = { + let stack = UIStackView(frame: .zero) + stack.translatesAutoresizingMaskIntoConstraints = false + stack.spacing = 16 + stack.alignment = .center + stack.addArrangedSubview(avatarImageView) + stack.addArrangedSubview(labelsStackView) + return stack + }() + + private lazy var labelsStackView: UIStackView = { + let stack = UIStackView(frame: .zero) + stack.translatesAutoresizingMaskIntoConstraints = false + stack.axis = .vertical + stack.spacing = 8 + stack.addArrangedSubview(contactNameLabel) + stack.addArrangedSubview(contactPhoneLabel) + return stack + }() + + private lazy var avatarImageView: UIImageView = { + let imageView = UIImageView() + imageView.translatesAutoresizingMaskIntoConstraints = false + imageView.layer.cornerRadius = 25 + imageView.layer.masksToBounds = true + imageView.image = UIImage(named: "avatar-placeholder") + return imageView + }() + + private lazy var contactNameLabel: UILabel = { + let label = UILabel() + label.translatesAutoresizingMaskIntoConstraints = false + label.font = UIFont.boldSystemFont(ofSize: 17) + return label + }() + + private lazy var contactPhoneLabel: UILabel = { + let label = UILabel() + label.translatesAutoresizingMaskIntoConstraints = false + label.textColor = .gray + label.font = UIFont.systemFont(ofSize: 14) + return label + }() + + override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) { + super.init(style: style, reuseIdentifier: reuseIdentifier) + setupView() + } + + required init?(coder: NSCoder) { + return nil + } +} + +extension ContactCellView: ViewCodeProtocol { + func buildViewHierarchy() { + addSubview(mainStackView) + } + + func setupConstraints() { + NSLayoutConstraint.activate([ + mainStackView.topAnchor.constraint(equalTo: topAnchor), + mainStackView.bottomAnchor.constraint(equalTo: bottomAnchor), + mainStackView.leadingAnchor.constraint(equalTo: leadingAnchor, constant: 16), + mainStackView.trailingAnchor.constraint(equalTo: trailingAnchor), + avatarImageView.widthAnchor.constraint(equalToConstant: 50), + avatarImageView.heightAnchor.constraint(equalToConstant: 50), + ]) + } + + func setupAdditionalConfiguration() { + accessoryType = .disclosureIndicator + } + +} + +extension ContactCellView { + func setupCell(_ contact: ContactModel) { + contactNameLabel.text = contact.name + contactPhoneLabel.text = contact.phone + } +} diff --git a/solutions/devsprint-julio-fernandes-6/FinanceApp/Screens/Home/ActivityInfoCellView.swift b/solutions/devsprint-julio-fernandes-6/FinanceApp/Components/HomeHeaderView.swift similarity index 71% rename from solutions/devsprint-julio-fernandes-6/FinanceApp/Screens/Home/ActivityInfoCellView.swift rename to solutions/devsprint-julio-fernandes-6/FinanceApp/Components/HomeHeaderView.swift index 2690bb7..4611494 100644 --- a/solutions/devsprint-julio-fernandes-6/FinanceApp/Screens/Home/ActivityInfoCellView.swift +++ b/solutions/devsprint-julio-fernandes-6/FinanceApp/Components/HomeHeaderView.swift @@ -1,22 +1,23 @@ // -// ActivityInfoCellView.swift +// HomeHeaderView.swift // FinanceApp // -// Created by Julio Fernandes on 01/09/22. +// Created by Julio Fernandes on 10/09/22. // import UIKit -class ActivityInfoCellView: UITableViewCell { - - static let cellIdentifier = "ActivityInfoCellView" +final class HomeHeaderView: UIView { + let viewSize: CGFloat = 8 + private lazy var stackView: UIStackView = { let stackView = UIStackView() stackView.translatesAutoresizingMaskIntoConstraints = false stackView.axis = .vertical stackView.spacing = 16 stackView.isLayoutMarginsRelativeArrangement = true + stackView.layoutMargins = UIEdgeInsets(top: 0, left: 16, bottom: 0, right: 16) stackView.addArrangedSubview(label) stackView.addArrangedSubview(savingsStackView) stackView.addArrangedSubview(spendingStackView) @@ -34,9 +35,9 @@ class ActivityInfoCellView: UITableViewCell { let stackView = UIStackView() stackView.translatesAutoresizingMaskIntoConstraints = false stackView.axis = .horizontal - stackView.distribution = .fill stackView.alignment = .center stackView.spacing = 8 + stackView.distribution = .fillProportionally stackView.addArrangedSubview(savingsView) stackView.addArrangedSubview(savingsLabel) stackView.addArrangedSubview(savingsValueLabel) @@ -46,7 +47,6 @@ class ActivityInfoCellView: UITableViewCell { private lazy var savingsView: UIView = { let view = UIView() view.translatesAutoresizingMaskIntoConstraints = false - view.layer.cornerRadius = 4 view.backgroundColor = .green return view }() @@ -60,7 +60,6 @@ class ActivityInfoCellView: UITableViewCell { private lazy var savingsValueLabel: UILabel = { let label = UILabel() - label.text = "$100.00" label.textColor = .lightGray label.textAlignment = .right return label @@ -70,23 +69,22 @@ class ActivityInfoCellView: UITableViewCell { let stackView = UIStackView() stackView.translatesAutoresizingMaskIntoConstraints = false stackView.axis = .horizontal - stackView.distribution = .fill stackView.alignment = .center stackView.spacing = 8 + stackView.distribution = .fillProportionally stackView.addArrangedSubview(spendingView) stackView.addArrangedSubview(spendingLabel) stackView.addArrangedSubview(spendingValueLabel) return stackView }() - + private lazy var spendingView: UIView = { let view = UIView() view.translatesAutoresizingMaskIntoConstraints = false - view.layer.cornerRadius = 4 view.backgroundColor = .red return view }() - + private lazy var spendingLabel: UILabel = { let label = UILabel() label.text = "Spending" @@ -96,48 +94,51 @@ class ActivityInfoCellView: UITableViewCell { private lazy var spendingValueLabel: UILabel = { let label = UILabel() - label.text = "$100.00" label.textColor = .lightGray label.textAlignment = .right return label }() - - override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) { - super.init(style: style, reuseIdentifier: reuseIdentifier) + + init() { + super.init(frame: .zero) setupView() } - + required init?(coder: NSCoder) { return nil } - } -//MARK: - Extension and Protocol -extension ActivityInfoCellView: ViewCodeProtocol { - +// MARK: ViewCodeProtocol +extension HomeHeaderView: ViewCodeProtocol { func buildViewHierarchy() { - contentView.addSubview(stackView) + addSubview(stackView) } func setupConstraints() { NSLayoutConstraint.activate([ - stackView.topAnchor.constraint(equalTo: contentView.topAnchor, constant: 8), - stackView.trailingAnchor.constraint(equalTo: contentView.trailingAnchor, constant: -16), - stackView.bottomAnchor.constraint(equalTo: contentView.bottomAnchor, constant: -8), - stackView.leadingAnchor.constraint(equalTo: contentView.leadingAnchor, constant: 16), - - ]) - - NSLayoutConstraint.activate([ - savingsView.widthAnchor.constraint(equalToConstant: 8), - savingsView.heightAnchor.constraint(equalToConstant: 8), - ]) - - NSLayoutConstraint.activate([ - spendingView.widthAnchor.constraint(equalToConstant: 8), - spendingView.heightAnchor.constraint(equalToConstant: 8), + stackView.leadingAnchor.constraint(equalTo: safeAreaLayoutGuide.leadingAnchor), + stackView.trailingAnchor.constraint(equalTo: safeAreaLayoutGuide.trailingAnchor), + stackView.topAnchor.constraint(equalTo: safeAreaLayoutGuide.topAnchor), + stackView.bottomAnchor.constraint(equalTo: safeAreaLayoutGuide.bottomAnchor), + savingsView.widthAnchor.constraint(equalToConstant: viewSize), + savingsView.heightAnchor.constraint(equalToConstant: viewSize), + spendingView.widthAnchor.constraint(equalToConstant: viewSize), + spendingView.heightAnchor.constraint(equalToConstant: viewSize), ]) - + } + + func setupAdditionalConfiguration() { + backgroundColor = .white + savingsView.layer.cornerRadius = viewSize / 2 + spendingView.layer.cornerRadius = viewSize / 2 + } +} + +extension HomeHeaderView { + func setupView(balance: Float, savings: Float, spending: Float) { + label.text = "$\(balance)" + savingsValueLabel.text = "$\(savings)" + spendingValueLabel.text = "$\(spending)" } } diff --git a/solutions/devsprint-julio-fernandes-6/FinanceApp/Components/LoadingView.swift b/solutions/devsprint-julio-fernandes-6/FinanceApp/Components/LoadingView.swift deleted file mode 100644 index da37d6a..0000000 --- a/solutions/devsprint-julio-fernandes-6/FinanceApp/Components/LoadingView.swift +++ /dev/null @@ -1,34 +0,0 @@ -// -// LoadingView.swift -// FinanceApp -// -// Created by Joao Gripp on 30/08/22. -// - -import UIKit - -class LoadingView: UIView { - - var loadingSpinner = UIActivityIndicatorView(style: UIActivityIndicatorView.Style.large) - var titleLabel: UILabel = UILabel() - var title: String? - - init() { - super.init(frame: .zero) - - setupView() - - self.addSubview(loadingSpinner) - self.addSubview(titleLabel) - } - - required init?(coder: NSCoder) { - fatalError("init(coder:) has not been implemented") - } - - private func setupView() { - self.titleLabel.text = title ?? "Buscando Informações" - - self.loadingSpinner.startAnimating() - } -} diff --git a/solutions/devsprint-julio-fernandes-6/FinanceApp/Components/UserProfileHeaderView.swift b/solutions/devsprint-julio-fernandes-6/FinanceApp/Components/UserProfileHeaderView.swift new file mode 100644 index 0000000..9b8c50f --- /dev/null +++ b/solutions/devsprint-julio-fernandes-6/FinanceApp/Components/UserProfileHeaderView.swift @@ -0,0 +1,102 @@ +// +// UserProfileHeaderView.swift +// FinanceApp +// +// Created by Julio Fernandes on 10/09/22. +// + +import UIKit + +final class UserProfileHeaderView: UIView { + + private lazy var stackView: UIStackView = { + let stackView = UIStackView() + stackView.translatesAutoresizingMaskIntoConstraints = false + stackView.axis = .vertical + stackView.spacing = 8 + stackView.alignment = .center + stackView.addArrangedSubview(imageView) + stackView.addArrangedSubview(nameLabel) + stackView.addArrangedSubview(agencyLabel) + stackView.addArrangedSubview(accountLabel) + stackView.addArrangedSubview(bankLabel) + return stackView + }() + + private lazy var imageView: UIImageView = { + let imageView = UIImageView() + imageView.image = UIImage(named: "avatar-placeholder") + imageView.layer.cornerRadius = 50 + imageView.clipsToBounds = true + return imageView + }() + + private lazy var nameLabel: UILabel = { + let label = UILabel() + label.textAlignment = .center + label.font = UIFont.boldSystemFont(ofSize: 17) + return label + }() + + private lazy var agencyLabel: UILabel = { + let label = UILabel() + label.textAlignment = .center + label.font = UIFont.systemFont(ofSize: 15) + return label + }() + + private lazy var accountLabel: UILabel = { + let label = UILabel() + label.translatesAutoresizingMaskIntoConstraints = false + label.font = UIFont.systemFont(ofSize: 15) + return label + }() + + private lazy var bankLabel: UILabel = { + let label = UILabel() + label.translatesAutoresizingMaskIntoConstraints = false + label.font = UIFont.systemFont(ofSize: 15) + return label + }() + + init() { + super.init(frame: .zero) + setupView() + } + + required init?(coder: NSCoder) { + return nil + } +} + +// MARK: ViewCodeProtocol +extension UserProfileHeaderView: ViewCodeProtocol { + func buildViewHierarchy() { + addSubview(stackView) + } + + func setupConstraints() { + NSLayoutConstraint.activate([ + stackView.topAnchor.constraint(equalTo: safeAreaLayoutGuide.topAnchor, constant: 16), + stackView.trailingAnchor.constraint(equalTo: safeAreaLayoutGuide.trailingAnchor), + stackView.bottomAnchor.constraint(equalTo: safeAreaLayoutGuide.bottomAnchor, constant: -16), + stackView.leadingAnchor.constraint(equalTo: safeAreaLayoutGuide.leadingAnchor), + imageView.heightAnchor.constraint(equalToConstant: 100), + imageView.widthAnchor.constraint(equalToConstant: 100), + ]) + } + + func setupAdditionalConfiguration() { + backgroundColor = .clear + } + +} + +extension UserProfileHeaderView { + func setupView(name: String, agency: String, account: String, bank: String) { + nameLabel.text = name + agencyLabel.text = "Agency ".appending(agency) + accountLabel.text = "Account ".appending(account) + bankLabel.text = bank + } +} diff --git a/solutions/devsprint-julio-fernandes-6/FinanceApp/Extensions/DispatchQueue+Extensions.swift b/solutions/devsprint-julio-fernandes-6/FinanceApp/Extensions/DispatchQueue+Extensions.swift new file mode 100644 index 0000000..69b4e39 --- /dev/null +++ b/solutions/devsprint-julio-fernandes-6/FinanceApp/Extensions/DispatchQueue+Extensions.swift @@ -0,0 +1,20 @@ +// +// DispatchQueue+Extensions.swift +// FinanceApp +// +// Created by Julio Fernandes on 10/09/22. +// + +import UIKit + +public extension DispatchQueue { + + func safeAsync(_ block: @escaping () -> Void ) { + if self === DispatchQueue.main && Thread.isMainThread { + block() + } else { + async { block() } + } + } + +} diff --git a/solutions/devsprint-julio-fernandes-6/FinanceApp/Models/ContactModel.swift b/solutions/devsprint-julio-fernandes-6/FinanceApp/Models/ContactModel.swift new file mode 100644 index 0000000..b843526 --- /dev/null +++ b/solutions/devsprint-julio-fernandes-6/FinanceApp/Models/ContactModel.swift @@ -0,0 +1,13 @@ +// +// ContactModel.swift +// FinanceApp +// +// Created by Julio Fernandes on 10/09/22. +// + +import Foundation + +struct ContactModel: Decodable { + let name: String + let phone: String +} diff --git a/solutions/devsprint-julio-fernandes-6/FinanceApp/Models/UserProfileModel.swift b/solutions/devsprint-julio-fernandes-6/FinanceApp/Models/UserProfileModel.swift new file mode 100644 index 0000000..04f1d43 --- /dev/null +++ b/solutions/devsprint-julio-fernandes-6/FinanceApp/Models/UserProfileModel.swift @@ -0,0 +1,21 @@ +// +// UserProfileModel.swift +// FinanceApp +// +// Created by Julio Fernandes on 10/09/22. +// + +import Foundation + +struct UserProfileModel: Decodable { + let name: String + let phone: String + let email: String + let address: String + var account: UserProfileAccount +} + +struct UserProfileAccount: Decodable { + let agency: String + let account: String +} diff --git a/solutions/devsprint-julio-fernandes-6/FinanceApp/Screens/ActivityDetails/ActivityDetailsView.swift b/solutions/devsprint-julio-fernandes-6/FinanceApp/Screens/ActivityDetails/ActivityDetailsView.swift index aafddbf..5ea3319 100644 --- a/solutions/devsprint-julio-fernandes-6/FinanceApp/Screens/ActivityDetails/ActivityDetailsView.swift +++ b/solutions/devsprint-julio-fernandes-6/FinanceApp/Screens/ActivityDetails/ActivityDetailsView.swift @@ -6,142 +6,145 @@ // import UIKit -//TODO: Revisão -class ActivityDetailsView: UIView { + +protocol ActivityDetailsViewDelegate: AnyObject { + func didPressReportIssueButton() +} + +final class ActivityDetailsView: UIView { + + private lazy var iconInfoVStack: UIStackView = { + let stack = UIStackView() + stack.translatesAutoresizingMaskIntoConstraints = false + stack.axis = .vertical + stack.spacing = 3 + stack.alignment = .center + stack.addArrangedSubview(categoryIcon) + stack.addArrangedSubview(activityNameLabel) + stack.addArrangedSubview(activityCategoryLabel) + return stack + }() + + private lazy var categoryIcon: UIImageView = { + let image = UIImageView() + image.translatesAutoresizingMaskIntoConstraints = false + image.image = UIImage(named: "bag.circle.fill") + image.tintColor = .systemPurple + return image + }() + + private lazy var activityNameLabel: UILabel = { + let label = UILabel() + label.translatesAutoresizingMaskIntoConstraints = false + label.font = UIFont.boldSystemFont(ofSize: 18.0) + return label + }() - lazy var iconInfoVStack = UIStackView() + private lazy var activityCategoryLabel: UILabel = { + let label = UILabel() + label.translatesAutoresizingMaskIntoConstraints = false + label.text = "Shopping" + label.textColor = .systemGray2 + return label + }() - lazy var categoryIcon = UIImageView() - lazy var activityNameLabel = UILabel() - lazy var activityCategoryLabel = UILabel() + private lazy var amountInformationVStack: UIStackView = { + let stack = UIStackView() + stack.translatesAutoresizingMaskIntoConstraints = false + stack.axis = .vertical + stack.spacing = 8 + stack.alignment = .center + stack.addArrangedSubview(amountLabel) + stack.addArrangedSubview(timeLabel) + return stack + }() - lazy var amountInformationVStack = UIStackView() - lazy var amountLabel = UILabel() - lazy var timeLabel = UILabel() + private lazy var amountLabel: UILabel = { + let label = UILabel() + label.translatesAutoresizingMaskIntoConstraints = false + label.font = UIFont.boldSystemFont(ofSize: 30) + return label + }() - lazy var reportIssueButton = UIButton(type: .system) + private lazy var timeLabel: UILabel = { + let label = UILabel() + label.translatesAutoresizingMaskIntoConstraints = false + label.textColor = .systemGray2 + return label + }() + + private lazy var reportIssueButton: UIButton = { + let button = UIButton(type: .system) + button.translatesAutoresizingMaskIntoConstraints = false + button.setTitle("Report a issue", for: []) + button.titleLabel?.font = UIFont.boldSystemFont(ofSize: 14) + button.addTarget(self, action: #selector(reportIssueButtonAction), for: .touchUpInside) + button.tintColor = .white + button.backgroundColor = .systemBlue + button.clipsToBounds = true + button.layer.cornerRadius = 8 + return button + }() + + weak var delegate: ActivityDetailsViewDelegate? override init(frame: CGRect) { super.init(frame: frame) - - setupLayout() - setupStyle() + setupView() } required init?(coder: NSCoder) { - fatalError("init(coder:) has not been implemented") + return nil } } - -extension ActivityDetailsView { +// MARK: ViewCodeProtocol +extension ActivityDetailsView: ViewCodeProtocol { - private func setupLayout() { - // MARK: - Add views sections - + func buildViewHierarchy() { addSubview(iconInfoVStack) - iconInfoVStack.addArrangedSubview(categoryIcon) - iconInfoVStack.addArrangedSubview(activityNameLabel) - iconInfoVStack.addArrangedSubview(activityCategoryLabel) - addSubview(amountInformationVStack) - amountInformationVStack.addArrangedSubview(amountLabel) - amountInformationVStack.addArrangedSubview(timeLabel) - addSubview(reportIssueButton) - - + } + + func setupConstraints() { NSLayoutConstraint.activate([ - - // MARK: - Setup vStack constraints iconInfoVStack.topAnchor.constraint(equalToSystemSpacingBelow: safeAreaLayoutGuide.topAnchor, multiplier: 12), - iconInfoVStack.widthAnchor.constraint(equalToConstant: 100), + iconInfoVStack.widthAnchor.constraint(equalToConstant: 200), iconInfoVStack.centerXAnchor.constraint(equalTo: centerXAnchor), - - // MARK: - Setup categoryIcon constraints categoryIcon.widthAnchor.constraint(equalToConstant: 80), categoryIcon.heightAnchor.constraint(equalToConstant: 80), - - // MARK: - Setup amountInformationVStack constraints - amountInformationVStack.centerYAnchor.constraint(equalTo: self.centerYAnchor), amountInformationVStack.centerXAnchor.constraint(equalTo: self.centerXAnchor), - - // MARK: - Setup reportIssueButton constraints - reportIssueButton.bottomAnchor.constraint(equalTo: safeAreaLayoutGuide.bottomAnchor, constant: -20), reportIssueButton.centerXAnchor.constraint(equalTo: centerXAnchor), reportIssueButton.heightAnchor.constraint(equalToConstant: 45), reportIssueButton.leadingAnchor.constraint(equalTo: leadingAnchor, constant: 80), reportIssueButton.trailingAnchor.constraint(equalTo: trailingAnchor, constant: -80) - - - - ]) } - private func setupStyle() { + func setupAdditionalConfiguration() { backgroundColor = .white - - // MARK: - Stack view - - iconInfoVStack.translatesAutoresizingMaskIntoConstraints = false - iconInfoVStack.axis = .vertical - iconInfoVStack.spacing = 3 - iconInfoVStack.alignment = .center - - // MARK: - Icon view - - categoryIcon.translatesAutoresizingMaskIntoConstraints = false - categoryIcon.image = UIImage(named: "bag.circle.fill") - categoryIcon.tintColor = .systemPurple - - // MARK: - activityNameLabel view - - activityNameLabel.translatesAutoresizingMaskIntoConstraints = false - activityNameLabel.text = "Mall" - activityNameLabel.font = UIFont.boldSystemFont(ofSize: 18.0) - - - // MARK: - activityCategoryLabel view - - activityCategoryLabel.translatesAutoresizingMaskIntoConstraints = false - activityCategoryLabel.text = "Shopping" - activityCategoryLabel.textColor = .systemGray2 - - // MARK: - amountInformationVStack view - - amountInformationVStack.translatesAutoresizingMaskIntoConstraints = false - amountInformationVStack.axis = .vertical - amountInformationVStack.spacing = 8 - amountInformationVStack.alignment = .center - - // MARK: - amountLabel view - - amountLabel.translatesAutoresizingMaskIntoConstraints = false - amountLabel.text = "$100.00" - amountLabel.font = UIFont.boldSystemFont(ofSize: 30) - - // MARK: - timeLabel view - - timeLabel.translatesAutoresizingMaskIntoConstraints = false - timeLabel.text = "8:57 AM" - timeLabel.textColor = .systemGray2 - - // MARK: - reportIssueButton view - - reportIssueButton.translatesAutoresizingMaskIntoConstraints = false - reportIssueButton.setTitle("Report a issue", for: []) - reportIssueButton.titleLabel?.font = UIFont.boldSystemFont(ofSize: 14) - reportIssueButton.tintColor = .white - reportIssueButton.backgroundColor = .systemBlue - reportIssueButton.clipsToBounds = true - reportIssueButton.layer.cornerRadius = 8 + } + +} - - - +// MARK: Actions +extension ActivityDetailsView { + + @objc func reportIssueButtonAction() { + delegate?.didPressReportIssueButton() + } + +} + +// MARK: Setup +extension ActivityDetailsView { + func setupView(activity: Activity) { + activityNameLabel.text = activity.name + amountLabel.text = "$\(activity.price)" + timeLabel.text = activity.time } } diff --git a/solutions/devsprint-julio-fernandes-6/FinanceApp/Screens/ActivityDetails/ActivityDetailsViewController.swift b/solutions/devsprint-julio-fernandes-6/FinanceApp/Screens/ActivityDetails/ActivityDetailsViewController.swift index 1ba1558..a729fbc 100644 --- a/solutions/devsprint-julio-fernandes-6/FinanceApp/Screens/ActivityDetails/ActivityDetailsViewController.swift +++ b/solutions/devsprint-julio-fernandes-6/FinanceApp/Screens/ActivityDetails/ActivityDetailsViewController.swift @@ -7,9 +7,36 @@ import UIKit -class ActivityDetailsViewController: UIViewController { +final class ActivityDetailsViewController: UIViewController { + + // deixamos como "var" e sem ser private para que possamos nos testes injetar um Mock + var activity: Activity? { + willSet { + guard let data = newValue else { return } + activityDetailsView.setupView(activity: data) + } + } + private lazy var activityDetailsView: ActivityDetailsView = { + let view = ActivityDetailsView() + view.delegate = self + return view + }() + override func loadView() { - self.view = ActivityDetailsView() + self.view = activityDetailsView + } + +} + +extension ActivityDetailsViewController: ActivityDetailsViewDelegate { + func didPressReportIssueButton() { + let successAlert = UIAlertController( + title: "Success!", + message: "Issue Reported", + preferredStyle: .alert + ) + successAlert.addAction(UIAlertAction(title: "OK", style: .default)) + showDetailViewController(successAlert, sender: self) } } diff --git a/solutions/devsprint-julio-fernandes-6/FinanceApp/Screens/Confirmation/ConfirmationView.swift b/solutions/devsprint-julio-fernandes-6/FinanceApp/Screens/Confirmation/ConfirmationView.swift index e6bf907..4e759c3 100644 --- a/solutions/devsprint-julio-fernandes-6/FinanceApp/Screens/Confirmation/ConfirmationView.swift +++ b/solutions/devsprint-julio-fernandes-6/FinanceApp/Screens/Confirmation/ConfirmationView.swift @@ -7,6 +7,94 @@ import UIKit -class ConfirmationView: UIView { +protocol ConfirmationViewDelegate: AnyObject { + func didPressNiceButton() +} + +final class ConfirmationView: UIView { + + private lazy var stackView: UIStackView = { + let stackView = UIStackView() + stackView.translatesAutoresizingMaskIntoConstraints = false + stackView.axis = .vertical + stackView.spacing = 8 + stackView.alignment = .center + stackView.addArrangedSubview(confirmationImageView) + stackView.addArrangedSubview(confirmationLabel) + return stackView + }() + + private lazy var confirmationImageView: UIImageView = { + let imageView = UIImageView() + imageView.image = UIImage(named: "checkmark.circle.fill") + imageView.layer.cornerRadius = 50 + imageView.clipsToBounds = true + imageView.tintColor = .systemGreen + return imageView + }() + + private lazy var confirmationLabel: UILabel = { + let label = UILabel() + label.text = "Your transfer was successful" + label.font = UIFont.boldSystemFont(ofSize: 17) + label.textAlignment = .center + return label + }() + + private lazy var confirmationButton: UIButton = { + let button = UIButton() + button.translatesAutoresizingMaskIntoConstraints = false + button.setTitle("Nice!", for: .normal) + button.setTitleColor(.white, for: .normal) + button.backgroundColor = .systemBlue + button.layer.cornerRadius = 14 + button.addTarget(self, action: #selector(niceButtonDidClick), for: .touchUpInside) + return button + }() + + weak var delegate: ConfirmationViewDelegate? + + init() { + super.init(frame: .zero) + setupView() + } + + required init?(coder: NSCoder) { + return nil + } +} + +// MARK: ViewCodeProtocol +extension ConfirmationView: ViewCodeProtocol { + func buildViewHierarchy() { + addSubview(stackView) + addSubview(confirmationButton) + } + + func setupConstraints() { + NSLayoutConstraint.activate([ + stackView.centerXAnchor.constraint(equalTo: safeAreaLayoutGuide.centerXAnchor), + stackView.centerYAnchor.constraint(equalTo: safeAreaLayoutGuide.centerYAnchor), + stackView.leadingAnchor.constraint(equalTo: safeAreaLayoutGuide.leadingAnchor, constant: 16), + stackView.trailingAnchor.constraint(equalTo: safeAreaLayoutGuide.trailingAnchor, constant: -16), + confirmationImageView.heightAnchor.constraint(equalToConstant: 100), + confirmationImageView.widthAnchor.constraint(equalToConstant: 100), + confirmationButton.bottomAnchor.constraint(equalTo: safeAreaLayoutGuide.bottomAnchor, constant: -16), + confirmationButton.leadingAnchor.constraint(equalTo: safeAreaLayoutGuide.leadingAnchor, constant: 16), + confirmationButton.trailingAnchor.constraint(equalTo: safeAreaLayoutGuide.trailingAnchor, constant: -16), + confirmationButton.heightAnchor.constraint(equalToConstant: 56) + ]) + } + + func setupAdditionalConfiguration() { + backgroundColor = .white + } + +} +// MARK: Actions +extension ConfirmationView { + @objc func niceButtonDidClick() { + delegate?.didPressNiceButton() + } } diff --git a/solutions/devsprint-julio-fernandes-6/FinanceApp/Screens/Confirmation/ConfirmationViewController.swift b/solutions/devsprint-julio-fernandes-6/FinanceApp/Screens/Confirmation/ConfirmationViewController.swift index 19b54a4..d30a3f2 100644 --- a/solutions/devsprint-julio-fernandes-6/FinanceApp/Screens/Confirmation/ConfirmationViewController.swift +++ b/solutions/devsprint-julio-fernandes-6/FinanceApp/Screens/Confirmation/ConfirmationViewController.swift @@ -7,9 +7,23 @@ import UIKit -class ConfirmationViewController: UIViewController { +final class ConfirmationViewController: UIViewController { + private lazy var confirmationView: ConfirmationView = { + let view = ConfirmationView() + view.delegate = self + return view + }() + override func loadView() { - self.view = ConfirmationView() + self.view = confirmationView + } + +} + +// MARK: ConfirmationViewDelegate +extension ConfirmationViewController: ConfirmationViewDelegate { + func didPressNiceButton() { + dismiss(animated: true) } } diff --git a/solutions/devsprint-julio-fernandes-6/FinanceApp/Screens/ContactList/ContactListView.swift b/solutions/devsprint-julio-fernandes-6/FinanceApp/Screens/ContactList/ContactListView.swift index 06a4708..5821533 100644 --- a/solutions/devsprint-julio-fernandes-6/FinanceApp/Screens/ContactList/ContactListView.swift +++ b/solutions/devsprint-julio-fernandes-6/FinanceApp/Screens/ContactList/ContactListView.swift @@ -7,6 +7,102 @@ import UIKit -class ContactListView: UIView { +protocol ContactListViewDelegate: AnyObject { + func didPressContact() +} + +final class ContactListView: UIView { + + let cellSize = CGFloat(82) + + private let cellIdentifier = "ContactCellIdentifier" + + private lazy var tableView: UITableView = { + let tableView = UITableView(frame: .zero) + tableView.translatesAutoresizingMaskIntoConstraints = false + tableView.register(ContactCellView.self, forCellReuseIdentifier: self.cellIdentifier) + tableView.dataSource = self + tableView.delegate = self + return tableView + }() + + var contacts: [ContactModel] = [] { + didSet { + tableView.reloadData() + } + } + + weak var delegate: ContactListViewDelegate? + + init() { + super.init(frame: .zero) + setupView() + } + + required init?(coder: NSCoder) { + return nil + } +} + +// MARK: ViewCodeProtocol +extension ContactListView: ViewCodeProtocol { + + func buildViewHierarchy() { + addSubview(tableView) + } + + func setupConstraints() { + NSLayoutConstraint.activate([ + tableView.leadingAnchor.constraint(equalTo: safeAreaLayoutGuide.leadingAnchor), + tableView.trailingAnchor.constraint(equalTo: safeAreaLayoutGuide.trailingAnchor), + tableView.topAnchor.constraint(equalTo: safeAreaLayoutGuide.topAnchor), + tableView.bottomAnchor.constraint(equalTo: safeAreaLayoutGuide.bottomAnchor), + ]) + } + + func setupAdditionalConfiguration() { + backgroundColor = .white + tableView.reloadData() + } + +} + +// MARK: UITableViewDataSource +extension ContactListView: UITableViewDataSource { + + func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { + return contacts.count + } + + public func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { + let cell = tableView.dequeueReusableCell(withIdentifier: cellIdentifier, for: indexPath) as! ContactCellView + cell.setupCell(contacts[indexPath.row]) + return cell + } +} + +// MARK: UITableViewDelegate +extension ContactListView: UITableViewDelegate { + + func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat { + return cellSize + } + + public func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { + delegate?.didPressContact() + } +} + +// MARK: Setup view +extension ContactListView { + + enum RenderType { + case buildContactList(_ list: [ContactModel]) + } + func render(_ type: ContactListView.RenderType) { + switch type { + case let .buildContactList(dto): contacts = dto + } + } } diff --git a/solutions/devsprint-julio-fernandes-6/FinanceApp/Screens/ContactList/ContactListViewController.swift b/solutions/devsprint-julio-fernandes-6/FinanceApp/Screens/ContactList/ContactListViewController.swift index 19efab5..2151146 100644 --- a/solutions/devsprint-julio-fernandes-6/FinanceApp/Screens/ContactList/ContactListViewController.swift +++ b/solutions/devsprint-julio-fernandes-6/FinanceApp/Screens/ContactList/ContactListViewController.swift @@ -7,9 +7,40 @@ import UIKit -class ContactListViewController: UIViewController { +final class ContactListViewController: UIViewController { + // deixamos como "var" e sem ser private para que possamos nos testes injetar um Mock + var service: FinanceServiceProtocol = FinanceService() + + private lazy var contactListView: ContactListView = { + let view = ContactListView() + view.delegate = self + return view + }() + + override func viewDidLoad() { + super.viewDidLoad() + fetchData() + } + override func loadView() { - self.view = ContactListView() + self.view = contactListView + } + + private func fetchData() { + service.load(endpoint: .contactList, callbackQueue: .main) { [weak self] (response: Result<[ContactModel], FinanceServiceError>) in + switch response { + case let .success(dto): self?.contactListView.render(.buildContactList(dto)) + case .failure: break + } + } + } + +} + +// MARK: ContactListViewDelegate +extension ContactListViewController: ContactListViewDelegate { + func didPressContact() { + dismiss(animated: true) } } diff --git a/solutions/devsprint-julio-fernandes-6/FinanceApp/Screens/Home/ActivityCellView.swift b/solutions/devsprint-julio-fernandes-6/FinanceApp/Screens/Home/ActivityCellView.swift deleted file mode 100644 index 0c99be1..0000000 --- a/solutions/devsprint-julio-fernandes-6/FinanceApp/Screens/Home/ActivityCellView.swift +++ /dev/null @@ -1,91 +0,0 @@ -// -// ActivityCellView.swift -// FinanceApp -// -// Created by Joao Gripp on 31/08/22. -// - -import UIKit - -class ActivityCellView: UITableViewCell { - - static let cellIdentifier = "ActivityCellView" - - private lazy var titleLabel: UILabel = { - let label = UILabel() - label.numberOfLines = 1 - label.font = .preferredFont(forTextStyle: .title3) - label.translatesAutoresizingMaskIntoConstraints = false - return label - }() - - private lazy var detailLabel: UILabel = { - let label = UILabel() - label.numberOfLines = 1 - label.textColor = .black - label.font = .preferredFont(forTextStyle: .body) - label.translatesAutoresizingMaskIntoConstraints = false - return label - }() - - private lazy var activityImageView: UIImageView = { - let imageView = UIImageView() - imageView.translatesAutoresizingMaskIntoConstraints = false - return imageView - }() - - private lazy var stackVerticalView: UIStackView = { - let stack = UIStackView() - stack.distribution = .fill - stack.axis = .vertical - stack.addArrangedSubview(titleLabel) - stack.addArrangedSubview(detailLabel) - stack.translatesAutoresizingMaskIntoConstraints = false - return stack - }() - - private lazy var stackHorizontalView: UIStackView = { - let stack = UIStackView() - stack.distribution = .fill - stack.axis = .horizontal - stack.spacing = 16 - stack.addArrangedSubview(activityImageView) - stack.addArrangedSubview(stackVerticalView) - stack.translatesAutoresizingMaskIntoConstraints = false - return stack - }() - - required init?(coder: NSCoder) { - return nil - } - - init(title: String, price: Float = 100, time: String = "8:57 am") { - super.init(style: .default, reuseIdentifier: ActivityCellView.cellIdentifier) - setupView() - activityImageView.image = UIImage(systemName: "heart.circle.fill") - titleLabel.text = title - detailLabel.text = String.activityDetails(with: price, and: time) - - } -} - -//MARK: - Extension and Protocol -extension ActivityCellView: ViewCodeProtocol { - - func buildViewHierarchy() { - contentView.addSubview(stackHorizontalView) - } - - func setupConstraints() { - NSLayoutConstraint.activate([ - stackHorizontalView.topAnchor.constraint(equalTo: contentView.topAnchor, constant: 8), - stackHorizontalView.trailingAnchor.constraint(equalTo: contentView.trailingAnchor, constant: -16), - stackHorizontalView.bottomAnchor.constraint(equalTo: contentView.bottomAnchor, constant: -8), - stackHorizontalView.leadingAnchor.constraint(equalTo: contentView.leadingAnchor, constant: 16), - activityImageView.widthAnchor.constraint(equalToConstant: 60), - activityImageView.heightAnchor.constraint(equalToConstant: 60) - ]) - } -} - - diff --git a/solutions/devsprint-julio-fernandes-6/FinanceApp/Screens/Home/ActivityListView.swift b/solutions/devsprint-julio-fernandes-6/FinanceApp/Screens/Home/ActivityListView.swift deleted file mode 100644 index 50bc476..0000000 --- a/solutions/devsprint-julio-fernandes-6/FinanceApp/Screens/Home/ActivityListView.swift +++ /dev/null @@ -1,73 +0,0 @@ -// -// ActivityListView.swift -// FinanceApp -// -// Created by Joao Gripp on 01/09/22. -// - -import UIKit - -//struct HomeViewConfiguration { -// -// let activities: [String] -//} - -final class ActivityListView: UIView { - - private var activities: [UITableViewCell] = [] - - private lazy var tableView: UITableView = { - let tableView = UITableView(frame: .zero) - tableView.translatesAutoresizingMaskIntoConstraints = false - tableView.register(ActivityCellView.self, forCellReuseIdentifier: ActivityCellView.cellIdentifier) - tableView.register(ActivityInfoCellView.self, forCellReuseIdentifier: ActivityInfoCellView.cellIdentifier) - tableView.dataSource = self - return tableView - }() - - init() { - super.init(frame: .zero) - setupView() - } - - required init?(coder: NSCoder) { - fatalError("init(coder:) has not been implemented") - } - - func updateView(with activities: [String]) { - self.activities.append(ActivityInfoCellView(style: .default, reuseIdentifier: ActivityInfoCellView.cellIdentifier)) - self.activities.append(contentsOf: activities.map { title in return ActivityCellView(title: title) }) - self.tableView.reloadData() - } -} - -extension ActivityListView: ViewCodeProtocol { - func buildViewHierarchy() { - self.backgroundColor = .white - self.addSubview(self.tableView) - } - - func setupConstraints() { - NSLayoutConstraint.activate([ - self.tableView.leadingAnchor.constraint(equalTo: self.leadingAnchor), - self.tableView.trailingAnchor.constraint(equalTo: self.trailingAnchor), - self.tableView.topAnchor.constraint(equalTo: self.topAnchor), - self.tableView.bottomAnchor.constraint(equalTo: self.bottomAnchor) - ]) - } - - func setupAdditionalConfiguration() { - - } -} - -extension ActivityListView: UITableViewDataSource { - - public func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { - return self.activities.count - } - - public func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { - return activities[indexPath.row] - } -} diff --git a/solutions/devsprint-julio-fernandes-6/FinanceApp/Screens/Home/HomeView.swift b/solutions/devsprint-julio-fernandes-6/FinanceApp/Screens/Home/HomeView.swift index 9a2c6b1..3401b04 100644 --- a/solutions/devsprint-julio-fernandes-6/FinanceApp/Screens/Home/HomeView.swift +++ b/solutions/devsprint-julio-fernandes-6/FinanceApp/Screens/Home/HomeView.swift @@ -2,91 +2,82 @@ // HomeView.swift // FinanceApp // -// Created by Rodrigo Borges on 30/12/21. +// Created by Julio Fernandes on 10/09/22. // -//import UIKit -// -//struct HomeViewConfiguration { -// -// let activities: [String] -//} -// -//final class HomeView: UIView { -// -// private let listViewCellIdentifier = "ListViewCellIdentifier" -// -// private var activities: [String] = [] -// -// private lazy var tableView: UITableView = { -// -// let tableView = UITableView(frame: .zero) -// tableView.translatesAutoresizingMaskIntoConstraints = false -// tableView.register(ActivityCellView.self, forCellReuseIdentifier: self.listViewCellIdentifier) -// tableView.dataSource = self -// return tableView -// }() -// -// init() { -// -// super.init(frame: .zero) -// -// self.setupViews() -// } -// -// required init?(coder: NSCoder) { -// fatalError("init(coder:) has not been implemented") -// } -// -// func updateView(with activities: [String]) { -// -// self.activities = activities -// self.tableView.reloadData() -// } -//} -// -//private extension HomeView { -// -// func setupViews() { -// -// self.backgroundColor = .white -// -// self.configureSubviews() -// self.configureSubviewsConstraints() -// } -// -// func configureSubviews() { -// -// self.addSubview(self.tableView) -// } -// -// func configureSubviewsConstraints() { -// -// NSLayoutConstraint.activate([ -// -// self.tableView.leadingAnchor.constraint(equalTo: self.leadingAnchor), -// self.tableView.trailingAnchor.constraint(equalTo: self.trailingAnchor), -// self.tableView.topAnchor.constraint(equalTo: self.topAnchor), -// self.tableView.bottomAnchor.constraint(equalTo: self.bottomAnchor) -// ]) -// } -//} -// -//extension HomeView: UITableViewDataSource { -// -// public func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { -// -// return self.activities.count -// } -// -// public func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { -// -// let cell = tableView.dequeueReusableCell(withIdentifier: self.listViewCellIdentifier) as! ActivityCellView -// -// cell.setup(activityImage: nil, title: self.activities[indexPath.row]) -// -//// cell.textLabel?.text = self.activities[indexPath.row] -// return cell -// } -//} +import UIKit + +final class HomeView: UIView { + + private weak var delegate: ActivityListViewDelegate? + + private lazy var stackView: UIStackView = { + let stackView = UIStackView() + stackView.translatesAutoresizingMaskIntoConstraints = false + stackView.axis = .vertical + stackView.spacing = 16 + stackView.addArrangedSubview(headerView) + stackView.addArrangedSubview(listView) + return stackView + }() + + private lazy var headerView: HomeHeaderView = { + let view = HomeHeaderView() + view.translatesAutoresizingMaskIntoConstraints = false + return view + }() + + private lazy var listView: ActivityListView = { + let view = ActivityListView() + view.delegate = delegate + view.translatesAutoresizingMaskIntoConstraints = false + return view + }() + + init(delegate: ActivityListViewDelegate?) { + super.init(frame: .zero) + self.delegate = delegate + setupView() + } + + required init?(coder: NSCoder) { + return nil + } +} + +// MARK: ViewCodeProtocol +extension HomeView: ViewCodeProtocol { + func buildViewHierarchy() { + addSubview(stackView) + } + + func setupConstraints() { + NSLayoutConstraint.activate([ + stackView.topAnchor.constraint(equalTo: safeAreaLayoutGuide.topAnchor, constant: 16), + stackView.trailingAnchor.constraint(equalTo: safeAreaLayoutGuide.trailingAnchor), + stackView.bottomAnchor.constraint(equalTo: safeAreaLayoutGuide.bottomAnchor), + stackView.leadingAnchor.constraint(equalTo: safeAreaLayoutGuide.leadingAnchor), + ]) + } + + func setupAdditionalConfiguration() { + backgroundColor = .white + } + +} + +// MARK: Setup view +extension HomeView { + + enum RenderType { + case buildHeader(_ dto: HomeModel) + case buildActivityList(_ list: [Activity]) + } + func render(_ type: HomeView.RenderType) { + switch type { + case let .buildHeader(dto): headerView.setupView(balance: dto.balance, savings: dto.savings, spending: dto.spending) + case let .buildActivityList(dto): listView.activities = dto + } + } +} diff --git a/solutions/devsprint-julio-fernandes-6/FinanceApp/Screens/Home/HomeViewController.swift b/solutions/devsprint-julio-fernandes-6/FinanceApp/Screens/Home/HomeViewController.swift index 3de199e..6d41aa8 100644 --- a/solutions/devsprint-julio-fernandes-6/FinanceApp/Screens/Home/HomeViewController.swift +++ b/solutions/devsprint-julio-fernandes-6/FinanceApp/Screens/Home/HomeViewController.swift @@ -7,26 +7,57 @@ import UIKit -class HomeViewController: UIViewController { +final class HomeViewController: UIViewController { - var service: FinanceServiceInterface = FinanceService() + // deixamos como "var" e sem ser private para que possamos nos testes injetar um Mock + var service: FinanceServiceProtocol = FinanceService() - private let homeView: ActivityListView = { - let homeView = ActivityListView() + private lazy var barButton: UIBarButtonItem = { + let barButton = UIBarButtonItem(title: "Profile", style: .plain, target: self, action: #selector(profileDidClick)) + return barButton + }() + + private lazy var homeView: HomeView = { + let homeView = HomeView(delegate: self) return homeView }() override func viewDidLoad() { - navigationItem.title = "Finance App" - navigationController?.navigationBar.prefersLargeTitles = true - service.fetchHomeData { activities in - DispatchQueue.main.async { - self.homeView.updateView(with: activities) - } - } + super.viewDidLoad() + navigationItem.setRightBarButton(barButton, animated: false) + fetchData() } override func loadView() { self.view = homeView } + + private func fetchData() { + service.load(endpoint: .home, callbackQueue: .main) { [weak self] (response: Result) in + switch response { + case let .success(dto): + self?.homeView.render(.buildHeader(dto)) + self?.homeView.render(.buildActivityList(dto.activity)) + case .failure: break + } + } + } } + +// MARK: Actions && ActivityListViewDelegate +extension HomeViewController: ActivityListViewDelegate { + + func didSelectedActivity(_ activity: Activity) { + let controller = ActivityDetailsViewController() + controller.activity = activity + show(controller, sender: self) + } + + @objc func profileDidClick() { + let controller = UserProfileViewController() + showDetailViewController(controller, sender: self) + } + +} + + diff --git a/solutions/devsprint-julio-fernandes-6/FinanceApp/Screens/TabBarViewController/TabBarViewController.swift b/solutions/devsprint-julio-fernandes-6/FinanceApp/Screens/TabBarViewController/TabBarViewController.swift index 289d188..dd4c5c8 100644 --- a/solutions/devsprint-julio-fernandes-6/FinanceApp/Screens/TabBarViewController/TabBarViewController.swift +++ b/solutions/devsprint-julio-fernandes-6/FinanceApp/Screens/TabBarViewController/TabBarViewController.swift @@ -7,14 +7,10 @@ import UIKit -class TabBarViewController: UITabBarController { +final class TabBarViewController: UITabBarController { override func viewWillAppear(_ animated: Bool) { - self.setupTabBarViewController() - } - - private func setupTabBarViewController() { - + super.viewWillAppear(animated) let homeViewController = UINavigationController(rootViewController: HomeViewController()) let homeTabBar = UITabBarItem(title: "Home", image: UIImage(named: "house.fill"), tag: 0) homeViewController.tabBarItem = homeTabBar @@ -23,7 +19,6 @@ class TabBarViewController: UITabBarController { let transfersTabBar = UITabBarItem(title: "Transfers", image: UIImage(named: "arrow.up.arrow.down"), tag: 1) transferViewController.tabBarItem = transfersTabBar - self.viewControllers = [homeViewController, transferViewController] + viewControllers = [homeViewController, transferViewController] } - } diff --git a/solutions/devsprint-julio-fernandes-6/FinanceApp/Screens/Transfers/TransfersView.swift b/solutions/devsprint-julio-fernandes-6/FinanceApp/Screens/Transfers/TransfersView.swift index 65aef71..a2b3d43 100644 --- a/solutions/devsprint-julio-fernandes-6/FinanceApp/Screens/Transfers/TransfersView.swift +++ b/solutions/devsprint-julio-fernandes-6/FinanceApp/Screens/Transfers/TransfersView.swift @@ -7,7 +7,97 @@ import UIKit -class TransfersView: UIView { +protocol TransferViewDelegate: AnyObject { + func didPressChooseContactButton() + func didPressTransferButton() +} + +final class TransfersView: UIView { + + weak var delegate: TransferViewDelegate? + + private lazy var stackView: UIStackView = { + let stackView = UIStackView() + stackView.translatesAutoresizingMaskIntoConstraints = false + stackView.axis = .vertical + stackView.spacing = 8 + stackView.addArrangedSubview(amountTextField) + stackView.addArrangedSubview(chooseContactButton) + return stackView + }() + + private lazy var amountTextField: UITextField = { + let textField = UITextField() + textField.placeholder = "$0" + textField.font = UIFont.boldSystemFont(ofSize: 34) + textField.textAlignment = .center + textField.keyboardType = .numbersAndPunctuation + return textField + }() + private lazy var chooseContactButton: UIButton = { + let button = UIButton() + button.translatesAutoresizingMaskIntoConstraints = false + button.setTitle("Choose contact", for: .normal) + button.setTitleColor(.systemBlue, for: .normal) + button.addTarget(self, action: #selector(chooseContact), for: .touchUpInside) + return button + }() + + private lazy var transferButton: UIButton = { + let button = UIButton() + button.translatesAutoresizingMaskIntoConstraints = false + button.setTitle("Transfer", for: .normal) + button.setTitleColor(.white, for: .normal) + button.backgroundColor = .systemBlue + button.layer.cornerRadius = 14 + button.addTarget(self, action: #selector(transfer), for: .touchUpInside) + return button + }() + + init() { + super.init(frame: .zero) + setupView() + } + + required init?(coder: NSCoder) { + return nil + } +} + +// MARK: ViewCodeProtocol +extension TransfersView: ViewCodeProtocol { + func buildViewHierarchy() { + addSubview(stackView) + addSubview(transferButton) + } + + func setupConstraints() { + NSLayoutConstraint.activate([ + stackView.centerXAnchor.constraint(equalTo: safeAreaLayoutGuide.centerXAnchor), + stackView.centerYAnchor.constraint(equalTo: safeAreaLayoutGuide.centerYAnchor), + transferButton.bottomAnchor.constraint(equalTo: safeAreaLayoutGuide.bottomAnchor, constant: -16), + transferButton.leadingAnchor.constraint(equalTo: safeAreaLayoutGuide.leadingAnchor, constant: 16), + transferButton.trailingAnchor.constraint(equalTo: safeAreaLayoutGuide.trailingAnchor, constant: -16), + transferButton.heightAnchor.constraint(equalToConstant: 56) + ]) + + } + + func setupAdditionalConfiguration() { + backgroundColor = .white + } } + +// MARK: Actions +extension TransfersView { + + @objc func chooseContact() { + delegate?.didPressChooseContactButton() + } + + @objc func transfer() { + delegate?.didPressTransferButton() + } +} diff --git a/solutions/devsprint-julio-fernandes-6/FinanceApp/Screens/Transfers/TransfersViewController.swift b/solutions/devsprint-julio-fernandes-6/FinanceApp/Screens/Transfers/TransfersViewController.swift index 59e708b..befa55d 100644 --- a/solutions/devsprint-julio-fernandes-6/FinanceApp/Screens/Transfers/TransfersViewController.swift +++ b/solutions/devsprint-julio-fernandes-6/FinanceApp/Screens/Transfers/TransfersViewController.swift @@ -7,10 +7,31 @@ import UIKit -class TransfersViewController: UIViewController { +final class TransfersViewController: UIViewController { + private lazy var transfersView: TransfersView = { + let view = TransfersView() + view.delegate = self + return view + }() + override func loadView() { - self.view = TransfersView() - self.view.backgroundColor = .white + self.view = transfersView } + +} + +extension TransfersViewController: TransferViewDelegate { + + func didPressChooseContactButton() { + let controller = ContactListViewController() + showDetailViewController(controller, sender: self) + } + + func didPressTransferButton() { + let controller = ConfirmationViewController() + showDetailViewController(controller, sender: self) + } + + } diff --git a/solutions/devsprint-julio-fernandes-6/FinanceApp/Screens/UserProfile/UserProfileView.swift b/solutions/devsprint-julio-fernandes-6/FinanceApp/Screens/UserProfile/UserProfileView.swift index 6bf4b3a..0be5d89 100644 --- a/solutions/devsprint-julio-fernandes-6/FinanceApp/Screens/UserProfile/UserProfileView.swift +++ b/solutions/devsprint-julio-fernandes-6/FinanceApp/Screens/UserProfile/UserProfileView.swift @@ -7,6 +7,114 @@ import UIKit -class UserProfileView: UIView { +final class UserProfileView: UIView { + private lazy var headerView: UserProfileHeaderView = { + let headerView = UserProfileHeaderView() + headerView.frame = CGRect(x: 0, y: 0, width: 0, height: 232) + return headerView + }() + + private lazy var tableView: UITableView = { + let tableView = UITableView(frame: .zero, style: .insetGrouped) + tableView.translatesAutoresizingMaskIntoConstraints = false + tableView.register(UITableViewCell.self, forCellReuseIdentifier: "Cell") + tableView.dataSource = self + tableView.tableHeaderView = headerView + return tableView + }() + + private lazy var sectionAndRowsOne: [UITableViewCell] = [] + + private lazy var sectionAndRowsTwo: [UITableViewCell] = [ + setupCell(style: .default, title: "Need help?"), + setupCell(style: .default, title: "About DevPass"), + setupCell(title: "App Version", detail: "1.0.0"), + ] + + init() { + super.init(frame: .zero) + setupView() + } + + required init?(coder: NSCoder) { + return nil + } + +} + +// MARK: Private methods +private extension UserProfileView { + + func setupCell(style: UITableViewCell.CellStyle = .value1, title: String, detail: String? = nil) -> UITableViewCell { + let cell = UITableViewCell(style: style, reuseIdentifier: "Cell") + cell.textLabel?.text = title + cell.detailTextLabel?.text = detail + cell.selectionStyle = .none + return cell + } + +} + +// MARK: ViewCodeProtocol +extension UserProfileView: ViewCodeProtocol { + func buildViewHierarchy() { + addSubview(tableView) + } + + func setupConstraints() { + NSLayoutConstraint.activate([ + tableView.leadingAnchor.constraint(equalTo: safeAreaLayoutGuide.leadingAnchor), + tableView.trailingAnchor.constraint(equalTo: safeAreaLayoutGuide.trailingAnchor), + tableView.topAnchor.constraint(equalTo: safeAreaLayoutGuide.topAnchor), + tableView.bottomAnchor.constraint(equalTo: safeAreaLayoutGuide.bottomAnchor) + ]) + } + + func setupAdditionalConfiguration() { + backgroundColor = .white + tableView.reloadData() + } +} + +// MARK: UITableViewDataSource +extension UserProfileView: UITableViewDataSource { + + func numberOfSections(in tableView: UITableView) -> Int { + return 2 + } + + func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { + return section == 0 ? sectionAndRowsOne.count : sectionAndRowsTwo.count + } + + func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { + return indexPath.section == 0 ? sectionAndRowsOne[indexPath.row] : sectionAndRowsTwo[indexPath.row] + } + + func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? { + return section == 0 ? "My account" : "General" + } + +} + +// MARK: Setup view +extension UserProfileView { + + enum RenderType { + case buildPersonalData(_ dto: UserProfileModel) + } + + func render(_ type: UserProfileView.RenderType) { + switch type { + case let .buildPersonalData(dto): + headerView.setupView(name: dto.name, agency: dto.account.agency, account: dto.account.account, bank: "Devpass bank") + sectionAndRowsOne = [ + setupCell(title: "Phone", detail: dto.phone), + setupCell(title: "E-mail", detail: dto.email), + setupCell(title: "Address", detail: dto.address), + ] + tableView.reloadData() + } + } } diff --git a/solutions/devsprint-julio-fernandes-6/FinanceApp/Screens/UserProfile/UserProfileViewController.swift b/solutions/devsprint-julio-fernandes-6/FinanceApp/Screens/UserProfile/UserProfileViewController.swift index 6a10974..3bf976b 100644 --- a/solutions/devsprint-julio-fernandes-6/FinanceApp/Screens/UserProfile/UserProfileViewController.swift +++ b/solutions/devsprint-julio-fernandes-6/FinanceApp/Screens/UserProfile/UserProfileViewController.swift @@ -7,9 +7,33 @@ import UIKit -class UserProfileViewController: UIViewController { +final class UserProfileViewController: UIViewController { + + // deixamos como "var" e sem ser private para que possamos nos testes injetar um Mock + var service: FinanceServiceProtocol = FinanceService() + private lazy var userProfileView: UserProfileView = { + let view = UserProfileView() + return view + }() + + override func viewDidLoad() { + super.viewDidLoad() + fetchData() + } + override func loadView() { - self.view = UserProfileView() + self.view = userProfileView + } + + private func fetchData() { + service.load(endpoint: .userProfile, callbackQueue: .main) { [weak self] (response: Result) in + switch response { + case let .success(dto): self?.userProfileView.render(.buildPersonalData(dto)) + case .failure: break + } + } } + } + diff --git a/solutions/devsprint-julio-fernandes-6/FinanceApp/Service/FinanceService.swift b/solutions/devsprint-julio-fernandes-6/FinanceApp/Service/FinanceService.swift index 4c53ef9..525367d 100644 --- a/solutions/devsprint-julio-fernandes-6/FinanceApp/Service/FinanceService.swift +++ b/solutions/devsprint-julio-fernandes-6/FinanceApp/Service/FinanceService.swift @@ -7,33 +7,64 @@ import Foundation -protocol FinanceServiceInterface { - func fetchHomeData(completion: @escaping ([String]) -> Void) +protocol FinanceServiceProtocol { + func load(endpoint: FinanceEndpoint, callbackQueue: DispatchQueue, completion: @escaping (Result) -> Void) + func cancel() } -class FinanceService: FinanceServiceInterface { +final class FinanceService { - func fetchHomeData(completion: @escaping ([String]) -> Void) { + private var session: URLSession + private var dataTask: URLSessionDataTask? + + init(session: URLSession = .shared) { + self.session = session + } + + private func baseURL(_ endpoint: FinanceEndpoint) -> String { + return "https://raw.githubusercontent.com/devpass-tech/challenge-viper-finance/main/api/\(endpoint.rawValue).json" + } + +} + +extension FinanceService: FinanceServiceProtocol { + func load(endpoint: FinanceEndpoint, callbackQueue: DispatchQueue, completion: @escaping (Result) -> Void) { - guard let url = FinanceService.homeApiPath else { return } + guard let url = URL(string: baseURL(endpoint)) else { + callbackQueue.safeAsync { completion(.failure(FinanceServiceError.invalidURL)) } + return + } - URLSession.shared.dataTask(with: url) { data, response, error in - guard let data = data else { return } + let request = URLRequest(url: url) + + dataTask = session.dataTask(with: request) { data, response, error in + guard let data = data else { + callbackQueue.safeAsync { completion(.failure(FinanceServiceError.invalidData)) } + return + } - do { - let homeData = try JSONDecoder().decode(HomeModel.self, from: data) - completion(homeData.activity.map({$0.name})) - } catch { - print(error.localizedDescription) + guard let decodedData = try? JSONDecoder().decode(T.self, from: data) else { + callbackQueue.safeAsync { completion(.failure(FinanceServiceError.decode)) } + return } - }.resume() + callbackQueue.safeAsync { completion(.success(decodedData)) } + } + + dataTask?.resume() + } + + func cancel() { + dataTask?.cancel() } } -extension FinanceService { - - static let homeApiPath = URL(string:"https://raw.githubusercontent.com/devpass-tech/challenge-finance-app/main/api/home_endpoint.json") - - +enum FinanceEndpoint: String { + case home = "home_endpoint" + case contactList = "contact_list_endpoint" + case userProfile = "user_profile_endpoint" +} + +enum FinanceServiceError: Error { + case decode, invalidData, invalidURL } diff --git a/solutions/devsprint-julio-fernandes-6/FinanceAppTests/Doubles/FinanceServiceSpy.swift b/solutions/devsprint-julio-fernandes-6/FinanceAppTests/Doubles/FinanceServiceSpy.swift new file mode 100644 index 0000000..ea31f0d --- /dev/null +++ b/solutions/devsprint-julio-fernandes-6/FinanceAppTests/Doubles/FinanceServiceSpy.swift @@ -0,0 +1,25 @@ +// +// FinanceServiceSpy.swift +// FinanceAppTests +// +// Created by Julio Fernandes on 10/09/22. +// + +import Foundation +@testable import FinanceApp + +final class FinanceServiceSpy: FinanceServiceProtocol { + + private(set) var loadCalled = false + private(set) var loadEndpointToBeValue: String = "" + func load(endpoint: FinanceEndpoint, callbackQueue: DispatchQueue, completion: @escaping (Result) -> Void) where T : Decodable { + loadEndpointToBeValue = endpoint.rawValue + loadCalled = true + completion(.failure(.invalidData)) + } + + private(set) var cancelCalled = false + func cancel() { + cancelCalled = true + } +} diff --git a/solutions/devsprint-julio-fernandes-6/FinanceAppTests/Doubles/UINavigationControllerSpy.swift b/solutions/devsprint-julio-fernandes-6/FinanceAppTests/Doubles/UINavigationControllerSpy.swift new file mode 100644 index 0000000..a8442b0 --- /dev/null +++ b/solutions/devsprint-julio-fernandes-6/FinanceAppTests/Doubles/UINavigationControllerSpy.swift @@ -0,0 +1,21 @@ +// +// UINavigationControllerSpy.swift +// FinanceAppTests +// +// Created by Julio Fernandes on 10/09/22. +// + +import UIKit + +final class UINavigationControllerSpy: UINavigationController { + + private(set) var showCalled = false + override func show(_ vc: UIViewController, sender: Any?) { + showCalled = true + } + + private(set) var showDetailViewControllerCalled = false + override func showDetailViewController(_ vc: UIViewController, sender: Any?) { + showDetailViewControllerCalled = true + } +} diff --git a/solutions/devsprint-julio-fernandes-6/FinanceAppTests/FinanceAppTests.swift b/solutions/devsprint-julio-fernandes-6/FinanceAppTests/FinanceAppTests.swift deleted file mode 100644 index 5b18857..0000000 --- a/solutions/devsprint-julio-fernandes-6/FinanceAppTests/FinanceAppTests.swift +++ /dev/null @@ -1,36 +0,0 @@ -// -// FinanceAppTests.swift -// FinanceAppTests -// -// Created by Rodrigo Borges on 30/12/21. -// - -import XCTest -@testable import FinanceApp - -class FinanceAppTests: XCTestCase { - - override func setUpWithError() throws { - // Put setup code here. This method is called before the invocation of each test method in the class. - } - - override func tearDownWithError() throws { - // Put teardown code here. This method is called after the invocation of each test method in the class. - } - - func testExample() throws { - // This is an example of a functional test case. - // Use XCTAssert and related functions to verify your tests produce the correct results. - // Any test you write for XCTest can be annotated as throws and async. - // Mark your test throws to produce an unexpected failure when your test encounters an uncaught error. - // Mark your test async to allow awaiting for asynchronous code to complete. Check the results with assertions afterwards. - } - - func testPerformanceExample() throws { - // This is an example of a performance test case. - self.measure { - // Put the code you want to measure the time of here. - } - } - -} diff --git a/solutions/devsprint-julio-fernandes-6/FinanceAppTests/HomeViewControllerTests.swift b/solutions/devsprint-julio-fernandes-6/FinanceAppTests/HomeViewControllerTests.swift index afafc87..37aa812 100644 --- a/solutions/devsprint-julio-fernandes-6/FinanceAppTests/HomeViewControllerTests.swift +++ b/solutions/devsprint-julio-fernandes-6/FinanceAppTests/HomeViewControllerTests.swift @@ -8,9 +8,10 @@ import XCTest @testable import FinanceApp -class HomeViewControllerTests: XCTestCase { +final class HomeViewControllerTests: XCTestCase { - private let serviceSpy = ServiceHomeMock() + private let serviceSpy = FinanceServiceSpy() + var sut: HomeViewController? override func setUp() { @@ -24,31 +25,31 @@ class HomeViewControllerTests: XCTestCase { sut = nil } - func test_viewDidLoad_should_configurator_view() throws { + func test_loadView() throws { + sut?.loadView() + XCTAssertTrue(sut?.view is HomeView) + + } + + func test_viewDidLoad() { sut?.viewDidLoad() - XCTAssertEqual(sut?.navigationItem.title, "Finance App") - XCTAssertFalse(sut?.navigationController?.navigationBar.prefersLargeTitles ?? false, "vai quebrar pq não tem nenhuma NavigationController") + XCTAssertNotNil(sut?.navigationItem.rightBarButtonItem) + XCTAssertTrue(serviceSpy.loadCalled) + XCTAssertEqual(serviceSpy.loadEndpointToBeValue, FinanceEndpoint.home.rawValue) } - func test_viewDidLoad_should_configurator_view_with_navigation() throws { + func test_didSelectedActivity() throws { let sut = try XCTUnwrap(sut) - _ = UINavigationController(rootViewController: sut) - sut.viewDidLoad() - XCTAssertTrue(sut.navigationController?.navigationBar.prefersLargeTitles ?? false) + let nav = UINavigationControllerSpy(rootViewController: sut) + sut.didSelectedActivity(Activity(name: "", price: 0, time: "")) + XCTAssertTrue(nav.showCalled) } - func test_viewDidLoad_should_call_service() { - sut?.viewDidLoad() - XCTAssertTrue(serviceSpy.fetchHomeDataCalled) + func test_profileDidClick() throws { + let sut = try XCTUnwrap(sut) + let nav = UINavigationControllerSpy(rootViewController: sut) + sut.profileDidClick() + XCTAssertTrue(nav.showDetailViewControllerCalled) } } - -class ServiceHomeMock: FinanceServiceInterface { - - private(set) var fetchHomeDataCalled = false - func fetchHomeData(completion: @escaping ([String]) -> Void) { - fetchHomeDataCalled = true - completion([]) - } -} diff --git a/solutions/devsprint-julio-fernandes-6/FinanceAppTests/TabBarViewControllerTests.swift b/solutions/devsprint-julio-fernandes-6/FinanceAppTests/TabBarViewControllerTests.swift new file mode 100644 index 0000000..d89662d --- /dev/null +++ b/solutions/devsprint-julio-fernandes-6/FinanceAppTests/TabBarViewControllerTests.swift @@ -0,0 +1,30 @@ +// +// TabBarViewControllerTests.swift +// FinanceAppTests +// +// Created by Julio Fernandes on 10/09/22. +// + +import XCTest +@testable import FinanceApp + +final class TabBarViewControllerTests: XCTestCase { + + var sut: TabBarViewController? + + override func setUpWithError() throws { + try super.setUpWithError() + sut = TabBarViewController() + } + + override func tearDownWithError() throws { + sut = nil + try super.tearDownWithError() + } + + func test_viewWillAppear() throws { + sut?.viewWillAppear(false) + XCTAssertEqual(sut?.viewControllers?.count, 2) + } + +} diff --git a/solutions/devsprint-julio-fernandes-6/FinanceAppUITests/FinanceAppUITests.swift b/solutions/devsprint-julio-fernandes-6/FinanceAppUITests/FinanceAppUITests.swift index a623802..cd2c013 100644 --- a/solutions/devsprint-julio-fernandes-6/FinanceAppUITests/FinanceAppUITests.swift +++ b/solutions/devsprint-julio-fernandes-6/FinanceAppUITests/FinanceAppUITests.swift @@ -30,13 +30,5 @@ class FinanceAppUITests: XCTestCase { // Use recording to get started writing UI tests. // Use XCTAssert and related functions to verify your tests produce the correct results. } - - func testLaunchPerformance() throws { - if #available(macOS 10.15, iOS 13.0, tvOS 13.0, watchOS 7.0, *) { - // This measures how long it takes to launch your application. - measure(metrics: [XCTApplicationLaunchMetric()]) { - XCUIApplication().launch() - } - } - } + }