diff --git a/SWDestinyTrades/Classes/Sets/Controller/SetsListViewController.swift b/SWDestinyTrades/Classes/Sets/Controller/SetsListViewController.swift index f7f5716e..dbfc8bb6 100644 --- a/SWDestinyTrades/Classes/Sets/Controller/SetsListViewController.swift +++ b/SWDestinyTrades/Classes/Sets/Controller/SetsListViewController.swift @@ -9,16 +9,14 @@ import UIKit final class SetsListViewController: UIViewController { - private let setsView = SetsView() - private let database: DatabaseProtocol? - private let destinyService: SWDestinyServiceProtocol - private lazy var navigator = SetsListNavigator(self.navigationController) + + var presenter: SetsPresenterProtocol? + private let setsView: SetsView // MARK: - Life Cycle - init(service: SWDestinyServiceProtocol = SWDestinyService(), database: DatabaseProtocol?) { - destinyService = service - self.database = database + init(setsView: SetsView = SetsView()) { + self.setsView = setsView super.init(nibName: nil, bundle: nil) } @@ -33,18 +31,12 @@ final class SetsListViewController: UIViewController { override func viewDidLoad() { super.viewDidLoad() - view.backgroundColor = .blackWhite + setsView.pullToRefresh.addTarget(self, action: #selector(retrieveSets), for: .valueChanged) + presenter?.viewDidLoad() - setupNavigationItem() - - setsView.pullToRefresh.addTarget(self, action: #selector(retrieveSets(sender:)), for: .valueChanged) - - setsView.startAnimating() - retrieveSets(sender: setsView.pullToRefresh) - - setsView.setsTableView.didSelectSet = { [weak self] set in - self?.navigateToNextController(with: set) + setsView.didSelectSet = { [weak self] set in + self?.presenter?.didSelectSet(set) } } @@ -54,48 +46,42 @@ final class SetsListViewController: UIViewController { navigationItem.title = L10n.expansions } - func setupNavigationItem() { - navigationItem.leftBarButtonItem = UIBarButtonItem(image: Asset.NavigationBar.icAbout.image, style: .plain, target: self, action: #selector(aboutButtonTouched(_:))) - navigationItem.rightBarButtonItem = UIBarButtonItem(barButtonSystemItem: .search, target: self, action: #selector(searchButtonTouched(_:))) + @objc + func retrieveSets() { + presenter?.retrieveSets() } @objc - func retrieveSets(sender: UIRefreshControl) { - Task { [weak self] in - guard let self else { return } - - defer { - Task { @MainActor in - self.setsView.activityIndicator.stopAnimating() - self.setsView.endRefreshControl() - } - } - - do { - let setList = try await self.destinyService.retrieveSetList() - self.setsView.setsTableView.updateSetList(setList) - } catch { - ToastMessages.showNetworkErrorMessage() - LoggerManager.shared.log(event: .setsList, parameters: ["error": error.localizedDescription]) - } - } + func aboutButtonTouched() { + presenter?.aboutButtonTouched() + } + + @objc + func searchButtonTouched() { + presenter?.searchButtonTouched() } +} - // MARK: - +extension SetsListViewController: SetsViewProtocol { + + func startAnimating() { + setsView.startAnimating() + } - func navigateToNextController(with set: SetDTO) { - navigator.navigate(to: .cardList(database: database, with: set)) + func stopAnimating() { + setsView.stopAnimating() } - // MARK: - UIBarButton Actions + func endRefreshControl() { + setsView.endRefreshControl() + } - @objc - func aboutButtonTouched(_ sender: Any) { - navigator.navigate(to: .about) + func updateSetList(_ setList: [SetDTO]) { + setsView.updateSetList(setList) } - @objc - func searchButtonTouched(_ sender: Any) { - navigator.navigate(to: .search(database: database)) + func setupNavigationItem() { + navigationItem.leftBarButtonItem = UIBarButtonItem(image: Asset.NavigationBar.icAbout.image, style: .plain, target: self, action: #selector(aboutButtonTouched)) + navigationItem.rightBarButtonItem = UIBarButtonItem(barButtonSystemItem: .search, target: self, action: #selector(searchButtonTouched)) } } diff --git a/SWDestinyTrades/Classes/Sets/Interactor/SetsListInteractor.swift b/SWDestinyTrades/Classes/Sets/Interactor/SetsListInteractor.swift new file mode 100644 index 00000000..25648644 --- /dev/null +++ b/SWDestinyTrades/Classes/Sets/Interactor/SetsListInteractor.swift @@ -0,0 +1,26 @@ +// +// SetsListInteractor.swift +// SWDestinyTrades +// +// Created by Diogo Autilio on 29/11/23. +// Copyright © 2023 Diogo Autilio. All rights reserved. +// + +import Foundation + +protocol SetsInteractorProtocol { + func retrieveSets() async throws -> [SetDTO] +} + +final class SetsListInteractor: SetsInteractorProtocol { + + private let service: SWDestinyServiceProtocol + + init(service: SWDestinyServiceProtocol = SWDestinyService()) { + self.service = service + } + + func retrieveSets() async throws -> [SetDTO] { + return try await service.retrieveSetList() + } +} diff --git a/SWDestinyTrades/Classes/Sets/Navigator/SetsListNavigator.swift b/SWDestinyTrades/Classes/Sets/Navigator/SetsListNavigator.swift index 35062471..2791eb03 100644 --- a/SWDestinyTrades/Classes/Sets/Navigator/SetsListNavigator.swift +++ b/SWDestinyTrades/Classes/Sets/Navigator/SetsListNavigator.swift @@ -15,19 +15,18 @@ final class SetsListNavigator: Navigator { case search(database: DatabaseProtocol?) } - private weak var navigationController: UINavigationController? + private weak var viewController: UIViewController? // MARK: - Initializer - init(_ navigationController: UINavigationController?) { - self.navigationController = navigationController + init(_ viewController: UIViewController) { + self.viewController = viewController } // MARK: - Navigator func navigate(to destination: Destination) { - let viewController = makeViewController(for: destination) - navigationController?.pushViewController(viewController, animated: true) + viewController?.navigationController?.pushViewController(makeViewController(for: destination), animated: true) } // MARK: - Private diff --git a/SWDestinyTrades/Classes/Sets/Presenter/SetsListPresenter.swift b/SWDestinyTrades/Classes/Sets/Presenter/SetsListPresenter.swift new file mode 100644 index 00000000..25e1300f --- /dev/null +++ b/SWDestinyTrades/Classes/Sets/Presenter/SetsListPresenter.swift @@ -0,0 +1,76 @@ +// +// SetsListPresenter.swift +// SWDestinyTrades +// +// Created by Diogo Autilio on 28/11/23. +// Copyright © 2023 Diogo Autilio. All rights reserved. +// + +import Foundation + +protocol SetsPresenterProtocol { + func viewDidLoad() + func retrieveSets() + func aboutButtonTouched() + func searchButtonTouched() + func didSelectSet(_ set: SetDTO) +} + +final class SetsPresenter: SetsPresenterProtocol { + + weak var view: SetsViewProtocol? + private let interactor: SetsInteractorProtocol + private let database: DatabaseProtocol? + private let navigator: SetsListNavigator + + init(view: SetsViewProtocol, + interactor: SetsInteractorProtocol, + database: DatabaseProtocol?, + navigator: SetsListNavigator) { + self.view = view + self.interactor = interactor + self.database = database + self.navigator = navigator + } + + func viewDidLoad() { + view?.startAnimating() + view?.setupNavigationItem() + retrieveSets() + } + + func retrieveSets() { + Task { [weak self] in + do { + guard let self else { return } + + let setList = try await interactor.retrieveSets() + + await MainActor.run { [weak self] in + self?.view?.updateSetList(setList) + self?.view?.stopAnimating() + self?.view?.endRefreshControl() + } + } catch { + await MainActor.run { [weak self] in + ToastMessages.showNetworkErrorMessage() + LoggerManager.shared.log(event: .setsList, parameters: ["error": error.localizedDescription]) + self?.view?.stopAnimating() + self?.view?.endRefreshControl() + } + } + } + } + + func didSelectSet(_ set: SetDTO) { + navigator.navigate(to: .cardList(database: database, with: set)) + } + + func aboutButtonTouched() { + navigator.navigate(to: .about) + } + + func searchButtonTouched() { + navigator.navigate(to: .search(database: database)) + } +} diff --git a/SWDestinyTrades/Classes/Sets/View/SetsView.swift b/SWDestinyTrades/Classes/Sets/View/SetsView.swift index 35a60f91..4ab3dcc7 100644 --- a/SWDestinyTrades/Classes/Sets/View/SetsView.swift +++ b/SWDestinyTrades/Classes/Sets/View/SetsView.swift @@ -8,7 +8,18 @@ import UIKit +protocol SetsViewProtocol: AnyObject { + func startAnimating() + func stopAnimating() + func endRefreshControl() + func updateSetList(_ setList: [SetDTO]) + func setupNavigationItem() +} + final class SetsView: UIView { + + var didSelectSet: ((SetDTO) -> Void)? + let setsTableView = SetsTableView() let pullToRefresh = UIRefreshControl() @@ -24,6 +35,10 @@ final class SetsView: UIView { override init(frame: CGRect) { super.init(frame: frame) setupBaseView() + + setsTableView.didSelectSet = { [weak self] set in + self?.didSelectSet?(set) + } } @available(*, unavailable) @@ -52,6 +67,10 @@ final class SetsView: UIView { pullToRefresh.attributedTitle = attributedTitle pullToRefresh.endRefreshing() } + + func updateSetList(_ setList: [SetDTO]) { + setsTableView.updateSetList(setList) + } } extension SetsView: BaseViewConfiguration { diff --git a/SWDestinyTrades/Classes/TabBar/SWDTabBarFactory.swift b/SWDestinyTrades/Classes/TabBar/SWDTabBarFactory.swift new file mode 100644 index 00000000..f48a7080 --- /dev/null +++ b/SWDestinyTrades/Classes/TabBar/SWDTabBarFactory.swift @@ -0,0 +1,25 @@ +// +// SWDTabBarFactory.swift +// SWDestinyTrades +// +// Created by Diogo Autilio on 28/11/23. +// Copyright © 2023 Diogo Autilio. All rights reserved. +// + +import Foundation +import UIKit + +final class SWDTabBarFactory { + + func makeSetsList(with database: DatabaseProtocol?) -> UIViewController { + let viewController = SetsListViewController() + let router = SetsListNavigator(viewController) + let interactor = SetsListInteractor() + let presenter = SetsPresenter(view: viewController, + interactor: interactor, + database: database, + navigator: router) + viewController.presenter = presenter + return viewController + } +} diff --git a/SWDestinyTrades/Classes/TabBar/ViewController/SWDTabBarViewController.swift b/SWDestinyTrades/Classes/TabBar/ViewController/SWDTabBarViewController.swift index 614810b5..30ed1721 100644 --- a/SWDestinyTrades/Classes/TabBar/ViewController/SWDTabBarViewController.swift +++ b/SWDestinyTrades/Classes/TabBar/ViewController/SWDTabBarViewController.swift @@ -25,8 +25,10 @@ final class SWDTabBarViewController: UITabBarController { override func viewDidLoad() { super.viewDidLoad() + let factory = SWDTabBarFactory() + // Create SetsListViewController Tab - let setsTab = UINavigationController(rootViewController: SetsListViewController(database: database)) + let setsTab = UINavigationController(rootViewController: factory.makeSetsList(with: database)) setsTab.tabBarItem = UITabBarItem(title: L10n.cards, image: Asset.Tabbar.icCards.image, selectedImage: Asset.Tabbar.icCardsFilled.image) // Create DeckListViewController Tab diff --git a/SWDestinyTradesTests/Screens/Sets/Controller/SetsListViewControllerTests.swift b/SWDestinyTradesTests/Screens/Sets/Controller/SetsListViewControllerTests.swift index 5b97d24f..a8d0e268 100644 --- a/SWDestinyTradesTests/Screens/Sets/Controller/SetsListViewControllerTests.swift +++ b/SWDestinyTradesTests/Screens/Sets/Controller/SetsListViewControllerTests.swift @@ -43,7 +43,15 @@ final class SetsListViewControllerTests: QuickSpec { it("should have valid layout") { client.fileName = "sets" - sut = SetsListViewController(service: service, database: nil) + + sut = SetsListViewController() + let router = SetsListNavigator(sut) + let presenter = SetsPresenter(view: sut, + interactor: SetsListInteractor(service: service), + database: nil, + navigator: router) + sut.presenter = presenter + navigation = UINavigationController(rootViewController: sut) window.showTestWindow(controller: navigation) diff --git a/SWDestinyTradesTests/Screens/Sets/Interactor/SetsListInteractorTests.swift b/SWDestinyTradesTests/Screens/Sets/Interactor/SetsListInteractorTests.swift new file mode 100644 index 00000000..c67a508d --- /dev/null +++ b/SWDestinyTradesTests/Screens/Sets/Interactor/SetsListInteractorTests.swift @@ -0,0 +1,43 @@ +// +// SetsListInteractorTests.swift +// SWDestinyTradesTests +// +// Created by Diogo Autilio on 29/11/23. +// Copyright © 2023 Diogo Autilio. All rights reserved. +// + +import Nimble +import Nimble_Snapshots +import Quick +import UIKit + +@testable import SWDestinyTrades + +final class SetsListInteractorTests: AsyncSpec { + + override class func spec() { + + var sut: SetsListInteractor! + var service: SWDestinyService! + var client: HttpClientMock! + + describe("SetsListInteractor") { + + beforeEach { + client = HttpClientMock() + service = SWDestinyService(client: client) + sut = SetsListInteractor(service: service) + } + + it("should retrieve the set list with success") { + let setList = try await sut.retrieveSets() + expect(setList.count) == 20 + } + + it("should fail to retrieve the set list") { + client.error = true + await expect { try await sut.retrieveSets() }.to(throwError()) + } + } + } +}