diff --git a/CleanCodeApp/Modules/Features/Luz/ContactsUs/ContactUSService.swift b/CleanCodeApp/Modules/Features/Luz/ContactsUs/ContactUSService.swift new file mode 100644 index 0000000..04e75dc --- /dev/null +++ b/CleanCodeApp/Modules/Features/Luz/ContactsUs/ContactUSService.swift @@ -0,0 +1,44 @@ +import Foundation + +protocol ContactUSServiceProtocol { + func fetch() async throws -> Data + func send(with parameters: [String: String]) async throws +} + +final class ContactUSService: ContactUSServiceProtocol { + func fetch() async throws -> Data { + return try await withCheckedThrowingContinuation { continuation in + AF.shared.request( + Endpoints.contactUs, + method: .get, + parameters: nil, + headers: nil + ) { result in + switch result { + case .success(let data): + continuation.resume(returning: data) + case .failure(let error): + continuation.resume(throwing: error) + } + } + } + } + + func send(with parameters: [String: String]) async throws { + return try await withCheckedThrowingContinuation { continuation in + AF.shared.request( + Endpoints.sendMessage, + method: .post, + parameters: parameters, + headers: nil + ) { result in + switch result { + case .success: + continuation.resume() + case .failure(let error): + continuation.resume(throwing: error) + } + } + } + } +} diff --git a/CleanCodeApp/Modules/Features/Luz/ContactsUs/ContactUSView.swift b/CleanCodeApp/Modules/Features/Luz/ContactsUs/ContactUSView.swift new file mode 100644 index 0000000..93d71e2 --- /dev/null +++ b/CleanCodeApp/Modules/Features/Luz/ContactsUs/ContactUSView.swift @@ -0,0 +1,211 @@ +import UIKit + +final class ContactUSView: UIView { + let symbolConfiguration = UIImage.SymbolConfiguration(pointSize: 36) + + lazy var textView: UITextView = { + let text = UITextView() + text.text = "Escreva sua mensagem aqui" + text.translatesAutoresizingMaskIntoConstraints = false + return text + }() + + lazy var titleLabel: UILabel = { + let label = UILabel() + label.textColor = .black + label.font = UIFont.systemFont(ofSize: 24, weight: .semibold) + label.text = "Escolha o canal para contato" + label.translatesAutoresizingMaskIntoConstraints = false + return label + }() + + lazy var messageLabel: UILabel = { + let label = UILabel() + label.textColor = .black + label.font = UIFont.systemFont(ofSize: 16, weight: .semibold) + label.text = "Ou envie uma mensagem" + label.numberOfLines = 2 + label.setContentHuggingPriority(.defaultLow, for: .horizontal) + label.translatesAutoresizingMaskIntoConstraints = false + return label + }() + + // MARK: - UIButton's + lazy var phoneButton: UIButton = { + let button = UIButton() + button.backgroundColor = .systemGray4 + button.layer.cornerRadius = 10 + button.setImage( + .init(systemName: "phone")?.withConfiguration(symbolConfiguration), + for: .normal + ) + button.translatesAutoresizingMaskIntoConstraints = false + return button + }() + + lazy var emailButton: UIButton = { + let button = UIButton() + button.backgroundColor = .systemGray4 + button.layer.cornerRadius = 10 + button.setImage( + .init(systemName: "envelope")?.withConfiguration(symbolConfiguration), + for: .normal + ) + button.translatesAutoresizingMaskIntoConstraints = false + return button + }() + + lazy var chatButton: UIButton = { + let button = UIButton() + button.backgroundColor = .systemGray4 + button.layer.cornerRadius = 10 + button.setImage( + .init(systemName: "message")?.withConfiguration(symbolConfiguration), + for: .normal + ) + button.translatesAutoresizingMaskIntoConstraints = false + return button + }() + + lazy var sendMessageButton: UIButton = { + let button = UIButton() + button.backgroundColor = .blue + button.setTitle("Enviar", for: .normal) + button.setTitleColor(.white, for: .normal) + button.layer.cornerRadius = 10 + button.setContentHuggingPriority(.required, for: .horizontal) + button.translatesAutoresizingMaskIntoConstraints = false + return button + }() + + lazy var closeButton: UIButton = { + let button = UIButton() + button.setTitle("Voltar", for: .normal) + button.setTitleColor(.blue, for: .normal) + button.backgroundColor = .clear + button.layer.borderWidth = 1 + button.layer.borderColor = UIColor.blue.cgColor + button.layer.cornerRadius = 10 + button.translatesAutoresizingMaskIntoConstraints = false + return button + }() + + override init(frame: CGRect) { + super.init(frame: frame) + backgroundColor = .systemGray6 + configureUI() + setupLayout() + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + func configureUI() { + [ + titleLabel, + phoneButton, + emailButton, + chatButton, + messageLabel, + textView, + sendMessageButton, + closeButton + ].forEach { addSubview($0) } + } + + func setupLayout() { + let contactButtonsStackView = createContactButtonsStackView() + let messageStackView = createMessageStackView() + let ctaStackView = createCTAStackView() + + let mainStackView = UIStackView( + arrangedSubviews: [ + titleLabel, + contactButtonsStackView, + messageStackView, + ctaStackView + ] + ) + mainStackView.axis = .vertical + mainStackView.spacing = 30 + mainStackView.translatesAutoresizingMaskIntoConstraints = false + + addSubview(mainStackView) + + setupConstraints(for: mainStackView) + } + + func setupConstraints(for mainStackView: UIStackView) { + NSLayoutConstraint.activate( + [ + mainStackView.topAnchor.constraint( + equalTo: safeAreaLayoutGuide.topAnchor, + constant: 30 + ), + mainStackView.leadingAnchor.constraint( + equalTo: safeAreaLayoutGuide.leadingAnchor, + constant: 20 + ), + mainStackView.trailingAnchor.constraint( + equalTo: safeAreaLayoutGuide.trailingAnchor, + constant: -20 + ), + mainStackView.bottomAnchor.constraint( + equalTo: safeAreaLayoutGuide.bottomAnchor, + constant: -20 + ) + ] + ) + } +} + +private extension ContactUSView { + func createContactButtonsStackView() -> UIStackView { + let stackView = UIStackView( + arrangedSubviews: [ + phoneButton, + emailButton, + chatButton + ] + ) + stackView.axis = .horizontal + stackView.alignment = .center + stackView.distribution = .equalSpacing + + [phoneButton, emailButton, chatButton].forEach { view in + view.widthAnchor.constraint(equalToConstant: 80).isActive = true + view.heightAnchor.constraint(equalToConstant: 80).isActive = true + } + + return stackView + } + + func createMessageStackView() -> UIStackView { + let stackView = UIStackView( + arrangedSubviews: [ + messageLabel, + textView + ] + ) + stackView.axis = .vertical + stackView.spacing = 20 + return stackView + } + + func createCTAStackView() -> UIStackView { + let stackView = UIStackView( + arrangedSubviews: [ + sendMessageButton, + closeButton + ] + ) + stackView.axis = .vertical + stackView.spacing = 20 + + [sendMessageButton, closeButton].forEach { view in + view.heightAnchor.constraint(equalToConstant: 40).isActive = true + } + return stackView + } +} diff --git a/CleanCodeApp/Modules/Features/Luz/ContactsUs/LuzContactUSViewModel.swift b/CleanCodeApp/Modules/Features/Luz/ContactsUs/LuzContactUSViewModel.swift new file mode 100644 index 0000000..8e9ab3f --- /dev/null +++ b/CleanCodeApp/Modules/Features/Luz/ContactsUs/LuzContactUSViewModel.swift @@ -0,0 +1,20 @@ +import Foundation + +final class LuzContactUSViewModel { + var model: ContactUsModel? + private let serivce: ContactUSServiceProtocol + + init(serivce: ContactUSServiceProtocol) { + self.serivce = serivce + } + + func fetch() async throws { + guard let data = try? await serivce.fetch() else { return } + let model = try? JSONDecoder().decode(ContactUsModel.self, from: data) + self.model = model + } + + func send(parameters: [String: String]) async throws { + try? await serivce.send(with: parameters) + } +} diff --git a/CleanCodeApp/Modules/Features/Luz/ContactsUs/LuzContactUsFactory.swift b/CleanCodeApp/Modules/Features/Luz/ContactsUs/LuzContactUsFactory.swift new file mode 100644 index 0000000..167f441 --- /dev/null +++ b/CleanCodeApp/Modules/Features/Luz/ContactsUs/LuzContactUsFactory.swift @@ -0,0 +1,9 @@ +import UIKit + +enum LuzContactUsFactory { + static func make() -> UIViewController { + let service = ContactUSService() + let viewModel = LuzContactUSViewModel(serivce: service) + return LuzContactUsViewController(viewModel: viewModel) + } +} diff --git a/CleanCodeApp/Modules/Features/Luz/ContactsUs/LuzContactUsViewController.swift b/CleanCodeApp/Modules/Features/Luz/ContactsUs/LuzContactUsViewController.swift index 58caa17..abf1eef 100644 --- a/CleanCodeApp/Modules/Features/Luz/ContactsUs/LuzContactUsViewController.swift +++ b/CleanCodeApp/Modules/Features/Luz/ContactsUs/LuzContactUsViewController.swift @@ -2,169 +2,50 @@ import UIKit final class LuzContactUsViewController: LoadingInheritageController { var model: ContactUsModel? - let symbolConfiguration = UIImage.SymbolConfiguration(pointSize: 36) + private let viewModel: LuzContactUSViewModel + private let contactUSView = ContactUSView() - // MARK: - TextView - private lazy var textView: UITextView = { - let text = UITextView() - text.text = "Escreva sua mensagem aqui" - text.translatesAutoresizingMaskIntoConstraints = false - return text - }() - - // MARK: - UILabel's - private lazy var titleLabel: UILabel = { - let label = UILabel() - label.textColor = .black - label.font = UIFont.systemFont(ofSize: 24, weight: .semibold) - label.text = "Escolha o canal para contato" - label.translatesAutoresizingMaskIntoConstraints = false - return label - }() - - private lazy var messageLabel: UILabel = { - let label = UILabel() - label.textColor = .black - label.font = UIFont.systemFont(ofSize: 16, weight: .semibold) - label.text = "Ou envie uma mensagem" - label.numberOfLines = 2 - label.setContentHuggingPriority(.defaultLow, for: .horizontal) - label.translatesAutoresizingMaskIntoConstraints = false - return label - }() + init(viewModel: LuzContactUSViewModel) { + self.viewModel = viewModel + super.init(nibName: nil, bundle: nil) + } + + @MainActor required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + // MARK: - Lifecycle + override func viewDidLoad() { + super.viewDidLoad() + setupButtonActions() + fetch() + } - // MARK: - UIButton's - private lazy var phoneButton: UIButton = { - let button = UIButton() - button.backgroundColor = .systemGray4 - button.layer.cornerRadius = 10 - button.addTarget( - self, - action: #selector(phoneDidTap), - for: .touchUpInside - ) - button.setImage( - .init(systemName: "phone")?.withConfiguration(symbolConfiguration), - for: .normal - ) - button.translatesAutoresizingMaskIntoConstraints = false - return button - }() + override func loadView() { + view = contactUSView + } - private lazy var emailButton: UIButton = { - let button = UIButton() - button.backgroundColor = .systemGray4 - button.layer.cornerRadius = 10 - button.addTarget( + func setupButtonActions() { + contactUSView.emailButton.addTarget( self, action: #selector(emailDipTap), for: .touchUpInside ) - button.setImage( - .init(systemName: "envelope")?.withConfiguration(symbolConfiguration), - for: .normal - ) - button.translatesAutoresizingMaskIntoConstraints = false - return button - }() - - private lazy var chatButton: UIButton = { - let button = UIButton() - button.backgroundColor = .systemGray4 - button.layer.cornerRadius = 10 - button.addTarget( + contactUSView.chatButton.addTarget( self, action: #selector(chatDidTap), for: .touchUpInside ) - button.setImage( - .init(systemName: "message")?.withConfiguration(symbolConfiguration), - for: .normal - ) - button.translatesAutoresizingMaskIntoConstraints = false - return button - }() - - private lazy var sendMessageButton: UIButton = { - let button = UIButton() - button.backgroundColor = .blue - button.setTitle("Enviar", for: .normal) - button.setTitleColor(.white, for: .normal) - button.layer.cornerRadius = 10 - button.setContentHuggingPriority(.required, for: .horizontal) - button.addTarget( + contactUSView.sendMessageButton.addTarget( self, action: #selector(send), for: .touchUpInside ) - button.translatesAutoresizingMaskIntoConstraints = false - return button - }() - - private lazy var closeButton: UIButton = { - let button = UIButton() - button.setTitle("Voltar", for: .normal) - button.setTitleColor(.blue, for: .normal) - button.backgroundColor = .clear - button.layer.borderWidth = 1 - button.layer.borderColor = UIColor.blue.cgColor - button.layer.cornerRadius = 10 - button.addTarget(self, action: #selector(close), for: .touchUpInside) - button.translatesAutoresizingMaskIntoConstraints = false - return button - }() - - // MARK: - Lifecycle - override func viewDidLoad() { - super.viewDidLoad() - view.backgroundColor = .systemGray6 - configureUI() - setupLayout() - fetch() - } - - func configureUI() { - [ - titleLabel, - phoneButton, - emailButton, - chatButton, - messageLabel, - textView, - sendMessageButton, - closeButton - ].forEach { view.addSubview($0) } - } - - func setupLayout() { - let contactButtonsStackView = createContactButtonsStackView() - let messageStackView = createMessageStackView() - let ctaStackView = createCTAStackView() - - let mainStackView = UIStackView( - arrangedSubviews: [ - titleLabel, - contactButtonsStackView, - messageStackView, - ctaStackView - ] + contactUSView.closeButton.addTarget( + self, + action: #selector(close), + for: .touchUpInside ) - mainStackView.axis = .vertical - mainStackView.spacing = 30 - mainStackView.translatesAutoresizingMaskIntoConstraints = false - - view.addSubview(mainStackView) - - setupConstraints(for: mainStackView) - } - - func setupConstraints(for mainStackView: UIStackView) { - NSLayoutConstraint.activate([ - mainStackView.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor, constant: 30), - mainStackView.leadingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.leadingAnchor, constant: 20), - mainStackView.trailingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.trailingAnchor, constant: -20), - mainStackView.bottomAnchor.constraint(equalTo: view.safeAreaLayoutGuide.bottomAnchor, constant: -20) - ]) } @objc @@ -192,34 +73,15 @@ final class LuzContactUsViewController: LoadingInheritageController { func fetch() { showLoadingView() - AF.shared.request( - Endpoints.contactUs, - method: .get, - parameters: nil, - headers: nil - ) { result in - self.removeLoadingView() - switch result { - case .success(let data): - self.handleSuccess(data: data) - case .failure(let error): - self.handleError(error) + Task { + do { + try await viewModel.fetch() + } catch { + handleError(error) } } } - // MARK: - Handlers - private func handleSuccess(data: Data) { - return if let returned = try? JSONDecoder().decode( - ContactUsModel.self, - from: data - ) { - self.model = returned - } else { - showAlertError() - } - } - private func handleError(_ error: Error) { print("error api: \(error.localizedDescription)") showAlertError() @@ -228,26 +90,24 @@ final class LuzContactUsViewController: LoadingInheritageController { @objc func send() { view.endEditing(true) + + guard + let message = contactUSView.textView.text, message.isEmpty == false + else { return } let email = model?.mail ?? "" - if let message = textView.text, textView.text.count > 0 { - let parameters: [String: String] = [ - "email": email, - "mensagem": message - ] - showLoadingView() - AF.shared.request( - Endpoints.sendMessage, - method: .post, - parameters: parameters, - headers: nil - ) { result in - self.removeLoadingView() - switch result { - case .success: - self.showAlertSuccess() - case .failure: - self.showAlertError() - } + + let parameters: [String: String] = [ + "email": email, + "mensagem": message + ] + + showLoadingView() + + Task { + do { + try await viewModel.send(parameters: parameters) + } catch { + showAlertError() } } } @@ -292,58 +152,6 @@ final class LuzContactUsViewController: LoadingInheritageController { } } -// MARK: - StackView's -extension LuzContactUsViewController { - private func createContactButtonsStackView() -> UIStackView { - let stackView = UIStackView( - arrangedSubviews: [ - phoneButton, - emailButton, - chatButton - ] - ) - stackView.axis = .horizontal - stackView.alignment = .center - stackView.distribution = .equalSpacing - - [phoneButton, emailButton, chatButton].forEach { view in - view.widthAnchor.constraint(equalToConstant: 80).isActive = true - view.heightAnchor.constraint(equalToConstant: 80).isActive = true - } - - return stackView - } - - private func createMessageStackView() -> UIStackView { - let stackView = UIStackView( - arrangedSubviews: [ - messageLabel, - textView - ] - ) - stackView.axis = .vertical - stackView.spacing = 20 - return stackView - } - - private func createCTAStackView() -> UIStackView { - let stackView = UIStackView( - arrangedSubviews: [ - sendMessageButton, - closeButton - ] - ) - stackView.axis = .vertical - stackView.spacing = 20 - - [sendMessageButton, closeButton].forEach { view in - view.heightAnchor.constraint(equalToConstant: 40).isActive = true - } - return stackView - } - -} - //#Preview { // LuzContactUsViewController() //} diff --git a/CleanCodeApp/Modules/Features/Luz/ResetPassword/LuzResetPasswordViewController.swift b/CleanCodeApp/Modules/Features/Luz/ResetPassword/LuzResetPasswordViewController.swift index 7f27328..9413498 100644 --- a/CleanCodeApp/Modules/Features/Luz/ResetPassword/LuzResetPasswordViewController.swift +++ b/CleanCodeApp/Modules/Features/Luz/ResetPassword/LuzResetPasswordViewController.swift @@ -60,7 +60,7 @@ final class LuzResetPasswordViewController: UIViewController { } @IBAction func helpDidTap(_ sender: Any) { - let vc = LuzContactUsViewController() + let vc = LuzContactUsFactory.make() vc.modalPresentationStyle = .fullScreen vc.modalTransitionStyle = .coverVertical self.present(vc, animated: true, completion: nil)