weareopensource/Swift

View on GitHub
waosSwift/modules/tasks/controllers/TasksListController.swift

Summary

Maintainability
A
2 hrs
Test Coverage
/**
 * Dependencies
 */

import UIKit
import Reusable
import ReactorKit
import RxDataSources
import RxRelay
import SwiftMessages
import MessageUI

/**
 * Controller
 */

final class TasksListController: CoreController, View {

    // MARK: Constants

    fileprivate var notification = BehaviorRelay<String?>(value: nil)
    struct Reusable {
        static let taskCell = ReusableCell<TasksCellController>()
    }

    // MARK: UI

    let tableView = CoreUITableView().then {
        $0.register(Reusable.taskCell)
        $0.allowsSelectionDuringEditing = true
    }
    let barButtonAdd = UIBarButtonItem(barButtonSystemItem: .add, target: nil, action: nil)
    let refreshControl = CoreUIRefreshControl()

    // MARK: Properties

    let dataSource = RxTableViewSectionedReloadDataSource<TasksSections>(
        configureCell: { _, tableView, indexPath, reactor in
            let cell = tableView.dequeue(Reusable.taskCell, for: indexPath)
            cell.reactor = reactor
            return cell
        })
    //let steps = PublishRelay<Step>()
    let application = UIApplication.shared

    // MARK: Initializing

    init(reactor: TasksListReactor) {
        super.init()
        self.reactor = reactor
    }

    required init?(coder aDecoder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }

    // MARK: View Life Cycle

    override func viewDidLoad() {
        super.viewDidLoad()
        self.navigationItem.rightBarButtonItem = self.barButtonAdd
        self.tableView.refreshControl = refreshControl
        self.view.addSubview(self.tableView)
        // notification
        NotificationCenter.default.addObserver(self, selector: #selector(self.ReceivedNotification(notification:)), name: Notification.Name("openTask"), object: nil)
    }

    override func setupConstraints() {
        self.tableView.snp.makeConstraints { make in
            make.edges.equalTo(0)
        }
    }

    // MARK: Notification

    @objc func ReceivedNotification(notification: Notification) {
        if let id = notification.userInfo?["id"] as? String {
            self.notification.accept(id)
        } else {
            self.notification.accept(nil)
        }
    }

    // MARK: Binding

    func bind(reactor: TasksListReactor) {
        bindView(reactor)
        bindAction(reactor)
        bindState(reactor)
    }
}

private extension TasksListController {

    // MARK: views (View -> View)

    func bindView(_ reactor: TasksListReactor) {
        // tableview
        self.tableView.rx.setDelegate(self).disposed(by: self.disposeBag)
        self.dataSource.canEditRowAtIndexPath = { _, _  in true }
        // item selected
        self.tableView.rx.modelSelected(TasksSections.Item.self)
            .subscribe(onNext: { result in
                let viewController = TasksViewController(reactor: reactor.editReactor(result))
                let navigationController = UINavigationController(rootViewController: viewController)
                self.present(navigationController, animated: true, completion: nil)
            })
            .disposed(by: self.disposeBag)
        // add button
        self.barButtonAdd.rx.tap
            .subscribe(onNext: { _ in
                let viewController = TasksViewController(reactor: reactor.addReactor())
                let navigationController = UINavigationController(rootViewController: viewController)
                self.present(navigationController, animated: true, completion: nil)
            })
            .disposed(by: self.disposeBag)
        // error
        self.error.button?.rx.tap
            .subscribe(onNext: { _ in
                if MFMailComposeViewController.canSendMail() {
                    let mvc = MFMailComposeViewController()
                    mvc.mailComposeDelegate = self
                    mvc.setToRecipients([(config["app"]["mails"]["report"].string ?? "")])
                    mvc.setSubject(L10n.userReport)
                    mvc.setMessageBody(setMailError("\(reactor.currentState.error?.title ?? "") \n \(reactor.currentState.error?.description ?? "") \n  \(reactor.currentState.error?.source ?? "")"), isHTML: true)
                    self.present(mvc, animated: true)
                }
            })
            .disposed(by: disposeBag)
    }

    // MARK: actions (View -> Reactor)

    func bindAction(_ reactor: TasksListReactor) {
        // checks
        self.application.rx.didOpenApp
            .map { Reactor.Action.checkUserToken }
            .bind(to: reactor.action)
            .disposed(by: self.disposeBag)
        self.application.rx.didOpenApp
            .map { Reactor.Action.checkUserTerms }
            .bind(to: reactor.action)
            .disposed(by: self.disposeBag)
        // init
        self.rx.viewWillAppear
            .throttle(.milliseconds(Metric.timesRefreshData), latest: false, scheduler: MainScheduler.instance)
            .map { _ in Reactor.Action.get }
            .bind(to: reactor.action)
            .disposed(by: self.disposeBag)
        // refresh
        self.refreshControl.rx.controlEvent(.valueChanged)
            .map { Reactor.Action.get }
            .bind(to: reactor.action)
            .disposed(by: self.disposeBag)
        // delete
        self.tableView.rx.itemDeleted
            .map(Reactor.Action.delete)
            .bind(to: reactor.action)
            .disposed(by: self.disposeBag)
        // notification
        self.notification
            .map {Reactor.Action.getIndexPath($0)}
            .bind(to: reactor.action)
            .disposed(by: self.disposeBag)
    }

    // MARK: states (Reactor -> View)

    func bindState(_ reactor: TasksListReactor) {
        // tasks
        reactor.state
            .map { $0.sections }
            .bind(to: self.tableView.rx.items(dataSource: self.dataSource))
            .disposed(by: self.disposeBag)
        // refreshing
        reactor.state
            .map { $0.isRefreshing }
            .distinctUntilChanged()
            .bind(to: refreshControl.rx.isRefreshing)
            .disposed(by: disposeBag)
        // notification
        reactor.state
            .map { $0.indexPath }
            .filterNil()
            .debounce(.milliseconds(500), scheduler: MainScheduler.instance)
            .subscribe(onNext: { indexPath in
                self.tableView.selectRow(at: indexPath, animated: true, scrollPosition: .top)
                self.tableView.delegate?.tableView!(self.tableView, didSelectRowAt: indexPath)
            })
            .disposed(by: self.disposeBag)
        // user
        reactor.state
            .map { $0.terms }
            .filterNil()
            .distinctUntilChanged()
            .subscribe(onNext: { terms in
                let viewController = HomeTermsController(reactor: reactor.termsReactor(terms: terms))
                viewController.title = L10n.modalRequirementTitle
                let navigationController = UINavigationController(rootViewController: viewController)
                navigationController.modalPresentationStyle = .fullScreen
                self.present(navigationController, animated: true, completion: nil)
            })
            .disposed(by: self.disposeBag)
        // error
        reactor.state
            .map { $0.error }
            .filterNil()
            .throttle(.milliseconds(Metric.timesButtonsThrottle), latest: false, scheduler: MainScheduler.instance)
            .subscribe(onNext: { error in
                self.error.configureContent(title: error.title, body: error.description == "" ? error.title : error.description)
                self.error.button?.isHidden = (error.source != nil && error.code != 401) ? false : true
                SwiftMessages.hideAll()
                SwiftMessages.show(config: self.popupConfig, view: self.error)
            })
            .disposed(by: self.disposeBag)
    }
}

/**
 * Extensions
 */

extension TasksListController: UITableViewDelegate {
    func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
        tableView.deselectRow(at: indexPath, animated: true)
    }
}