diff --git a/CleanCodeApp/Modules/Features/Rio/ResetPassword/Helpers/RioAlertHelper.swift b/CleanCodeApp/Modules/Features/Rio/ResetPassword/Helpers/RioAlertHelper.swift new file mode 100644 index 0000000..1911ba7 --- /dev/null +++ b/CleanCodeApp/Modules/Features/Rio/ResetPassword/Helpers/RioAlertHelper.swift @@ -0,0 +1,18 @@ +// +// RioAlertHelper.swift +// CleanCode +// +// Created by thaisa on 17/02/25. +// + +import Foundation +import UIKit + +class RioAlertHelper { + + static func showErrorAlert(on viewController: UIViewController, message: String) { + let alert = UIAlertController(title: "Ops...", message: message, preferredStyle: .alert) + alert.addAction(UIAlertAction(title: "OK", style: .default)) + viewController.present(alert, animated: true) + } +} diff --git a/CleanCodeApp/Modules/Features/Rio/ResetPassword/Helpers/RioCommonErrors.swift b/CleanCodeApp/Modules/Features/Rio/ResetPassword/Helpers/RioCommonErrors.swift new file mode 100644 index 0000000..54f2972 --- /dev/null +++ b/CleanCodeApp/Modules/Features/Rio/ResetPassword/Helpers/RioCommonErrors.swift @@ -0,0 +1,22 @@ +// +// RioCommonErrors.swift +// CleanCode +// +// Created by thaisa on 17/02/25. +// + +import Foundation + +enum RioCommonErrors: Error { + case noInternet + case invalidEmail + + var alertDescription: String { + switch self { + case .noInternet: + return "Você não tem conexão no momento." + case .invalidEmail: + return "O e-mail informado é inválido." + } + } +} diff --git a/CleanCodeApp/Modules/Features/Rio/ResetPassword/RioResetPasswordService.swift b/CleanCodeApp/Modules/Features/Rio/ResetPassword/RioResetPasswordService.swift new file mode 100644 index 0000000..955e527 --- /dev/null +++ b/CleanCodeApp/Modules/Features/Rio/ResetPassword/RioResetPasswordService.swift @@ -0,0 +1,24 @@ +// +// RioResetPasswordService.swift +// CleanCode +// +// Created by thaisa on 17/02/25. +// + +import Foundation +import UIKit + +class RioResetPasswordService { + + func resetPassword(email: String, completion: @escaping (Bool) -> Void) { + let parameters = ["email": email] + + if let topController = UIApplication.shared.windows.first?.rootViewController { + BadNetworkLayer.shared.resetPassword(topController, parameters: parameters) { success in + completion(success) + } + } else { + completion(false) + } + } +} diff --git a/CleanCodeApp/Modules/Features/Rio/ResetPassword/RioResetPasswordViewConfigurator.swift b/CleanCodeApp/Modules/Features/Rio/ResetPassword/RioResetPasswordViewConfigurator.swift new file mode 100644 index 0000000..6231cc4 --- /dev/null +++ b/CleanCodeApp/Modules/Features/Rio/ResetPassword/RioResetPasswordViewConfigurator.swift @@ -0,0 +1,52 @@ +// +// RioResetPasswordViewConfigurator.swift +// CleanCode +// +// Created by thaisa on 17/02/25. +// + +import Foundation +import UIKit + +class RioResetPasswordViewConfigurator { + + func setupView(for viewController: RioResetPasswordViewController) { + setupButtons(for: viewController) + setupTextField(for: viewController) + setupEmail(for: viewController) + updateRecoverPasswordButtonState(for: viewController) + viewController.viewSuccess.isHidden = true + } + + private func setupButtons(for vc: RioResetPasswordViewController) { + styleButton(vc.recoverPasswordButton, backgroundColor: .blue, titleColor: .white, borderWidth: 0) + styleButton(vc.loginButton, backgroundColor: .white, titleColor: .blue, borderWidth: 1) + styleButton(vc.helpButton, backgroundColor: .white, titleColor: .blue, borderWidth: 1) + styleButton(vc.createAccountButton, backgroundColor: .white, titleColor: .blue, borderWidth: 1) + } + + private func styleButton(_ button: UIButton, backgroundColor: UIColor, titleColor: UIColor, borderWidth: CGFloat) { + button.layer.cornerRadius = button.bounds.height / 2 + button.layer.borderWidth = borderWidth + button.layer.borderColor = UIColor.blue.cgColor + button.setTitleColor(titleColor, for: .normal) + button.backgroundColor = backgroundColor + } + + private func setupTextField(for vc: RioResetPasswordViewController) { + vc.emailTextfield.setDefaultColor() + } + + private func setupEmail(for vc: RioResetPasswordViewController) { + guard !vc.email.isEmpty else { return } + vc.emailTextfield.text = vc.email + vc.emailTextfield.isEnabled = false + } + + func updateRecoverPasswordButtonState(for vc: RioResetPasswordViewController) { + let isValid = !(vc.emailTextfield.text?.isEmpty ?? true) + vc.recoverPasswordButton.backgroundColor = isValid ? .blue : .gray + vc.recoverPasswordButton.setTitleColor(.white, for: .normal) + vc.recoverPasswordButton.isEnabled = isValid + } +} diff --git a/CleanCodeApp/Modules/Features/Rio/ResetPassword/RioResetPasswordViewController.swift b/CleanCodeApp/Modules/Features/Rio/ResetPassword/RioResetPasswordViewController.swift index 2ff498b..f4bf420 100644 --- a/CleanCodeApp/Modules/Features/Rio/ResetPassword/RioResetPasswordViewController.swift +++ b/CleanCodeApp/Modules/Features/Rio/ResetPassword/RioResetPasswordViewController.swift @@ -1,21 +1,12 @@ import UIKit -enum RioCommonErrors: Error { - case noInternet - case invalidEmail - - var alertDescription: String { - switch self { - case .noInternet: - return "Você não tem conexão no momento." - case .invalidEmail: - return "O e-mail informado é inválido." - } - } +protocol RioResetPasswordViewModelDelegate: AnyObject { + func didResetPasswordSuccess(email: String) + func didFailWithError(_ error: RioCommonErrors) } class RioResetPasswordViewController: UIViewController { - + @IBOutlet weak var emailTextfield: UITextField! @IBOutlet weak var recoverPasswordButton: UIButton! @IBOutlet weak var loginButton: UIButton! @@ -27,88 +18,32 @@ class RioResetPasswordViewController: UIViewController { @IBOutlet weak var emailLabel: UILabel! var email = "" - var recoveryEmail = false - + private var recoveryEmail = false + private let viewModel = RioResetPasswordViewModel() + private let viewConfigurator = RioResetPasswordViewConfigurator() + override func viewDidLoad() { super.viewDidLoad() - setupView() + viewModel.delegate = self + viewConfigurator.setupView(for: self) } open override var preferredStatusBarStyle: UIStatusBarStyle { return .lightContent } - + @IBAction func closeButtonAction(_ sender: Any) { dismiss(animated: true) } - + @IBAction func recoverPasswordButton(_ sender: Any) { if recoveryEmail { dismiss(animated: true) - return - } - - attemptPasswordReset() - } - - // MARK: - Nova função de reset de senha com tratamento de erro - - private func attemptPasswordReset() { - do { - try validateForm() - try validateInternetConnection() - - let emailUser = emailTextfield.text!.trimmingCharacters(in: .whitespaces) - resetPassword(email: emailUser) - } catch let error as RioCommonErrors { - showErrorAlert(message: error.alertDescription) - } catch { - showErrorAlert(message: "Ocorreu um erro inesperado. Tente novamente.") - } - } - - // MARK: - Validações - - private func validateInternetConnection() throws { - guard ConnectivityManager.shared.isConnected else { - throw RioCommonErrors.noInternet + } else { + guard let emailUser = emailTextfield.text?.trimmingCharacters(in: .whitespaces) else { return } + viewModel.attemptPasswordReset(email: emailUser) } } - - // MARK: - Reset de Senha - - private func resetPassword(email: String) { - let parameters = ["email": email] - - BadNetworkLayer.shared.resetPassword(self, parameters: parameters) { success in - success ? self.handleSuccess(email: email) : self.showErrorAlert(message: "Algo de errado aconteceu. Tente novamente mais tarde.") - } - } - - private func handleSuccess(email: String) { - recoveryEmail = true - emailTextfield.isHidden = true - textLabel.isHidden = true - viewSuccess.isHidden = false - emailLabel.text = email - updateRecoverPasswordButton() - } - - private func updateRecoverPasswordButton() { - recoverPasswordButton.titleLabel?.text = "REENVIAR E-MAIL" - recoverPasswordButton.setTitle("Voltar", for: .normal) - } - - private func showErrorAlert(message: String) { - let alert = UIAlertController( - title: "Ops..", - message: message, - preferredStyle: .alert - ) - alert.addAction(UIAlertAction(title: "OK", style: .default)) - present(alert, animated: true) - } - @IBAction func loginButton(_ sender: Any) { dismiss(animated: true) @@ -118,7 +53,7 @@ class RioResetPasswordViewController: UIViewController { let vc = RioContactUsViewController() vc.modalPresentationStyle = .fullScreen vc.modalTransitionStyle = .coverVertical - self.present(vc, animated: true, completion: nil) + present(vc, animated: true) } @IBAction func createAccountButton(_ sender: Any) { @@ -127,69 +62,13 @@ class RioResetPasswordViewController: UIViewController { present(newVc, animated: true) } - func validateForm() throws { - guard let email = emailTextfield.text, !isEmailFormatInvalid(email) else { - throw RioCommonErrors.invalidEmail - } - } - - func isEmailFormatInvalid(_ email: String) -> Bool { - return email.isEmpty || - !email.contains(".") || - !email.contains("@") || - email.count <= 5 - } -} - -// MARK: - Comportamentos de layout -extension RioResetPasswordViewController { - - func setupView() { - setupButtons() - setupTextField() - setupEmail() - updateRecoverPasswordButtonState() - } - - // MARK: - Configuração dos Botões - - private func setupButtons() { - styleButton(recoverPasswordButton, backgroundColor: .blue, titleColor: .white, borderWidth: 0) - styleButton(loginButton, backgroundColor: .white, titleColor: .blue, borderWidth: 1) - styleButton(helpButton, backgroundColor: .white, titleColor: .blue, borderWidth: 1) - styleButton(createAccountButton, backgroundColor: .white, titleColor: .blue, borderWidth: 1) - } - - private func styleButton(_ button: UIButton, backgroundColor: UIColor, titleColor: UIColor, borderWidth: CGFloat) { - button.layer.cornerRadius = button.bounds.height / 2 - button.layer.borderWidth = borderWidth - button.layer.borderColor = UIColor.blue.cgColor - button.setTitleColor(titleColor, for: .normal) - button.backgroundColor = backgroundColor - } - - // MARK: - Configuração do Campo de Texto - - private func setupTextField() { - emailTextfield.setDefaultColor() - } - - // MARK: - Configuração do E-mail - - private func setupEmail() { - guard !email.isEmpty else { return } - emailTextfield.text = email - emailTextfield.isEnabled = false - } - - //email @IBAction func emailBeginEditing(_ sender: Any) { emailTextfield.setEditingColor() } @IBAction func emailEditing(_ sender: Any) { emailTextfield.setEditingColor() - updateRecoverPasswordButtonState() + viewConfigurator.updateRecoverPasswordButtonState(for: self) } @IBAction func emailEndEditing(_ sender: Any) { @@ -197,12 +76,22 @@ extension RioResetPasswordViewController { } } -extension RioResetPasswordViewController { +extension RioResetPasswordViewController: RioResetPasswordViewModelDelegate { + + func didResetPasswordSuccess(email: String) { + recoveryEmail = true + emailTextfield.isHidden = true + textLabel.isHidden = true + viewSuccess.isHidden = false + emailLabel.text = email + updateRecoverPasswordButtonTitleForSuccess() + } + + func didFailWithError(_ error: RioCommonErrors) { + RioAlertHelper.showErrorAlert(on: self, message: error.alertDescription) + } - func updateRecoverPasswordButtonState() { - let isValid = !(emailTextfield.text?.isEmpty ?? true) - recoverPasswordButton.backgroundColor = isValid ? .blue : .gray - recoverPasswordButton.setTitleColor(.white, for: .normal) - recoverPasswordButton.isEnabled = isValid + private func updateRecoverPasswordButtonTitleForSuccess() { + recoverPasswordButton.setTitle("Voltar", for: .normal) } } diff --git a/CleanCodeApp/Modules/Features/Rio/ResetPassword/RioResetPasswordViewModel.swift b/CleanCodeApp/Modules/Features/Rio/ResetPassword/RioResetPasswordViewModel.swift new file mode 100644 index 0000000..4f597c9 --- /dev/null +++ b/CleanCodeApp/Modules/Features/Rio/ResetPassword/RioResetPasswordViewModel.swift @@ -0,0 +1,54 @@ +// +// RioResetPasswordViewModel.swift +// CleanCode +// +// Created by thaisa on 17/02/25. +// + +import Foundation + +class RioResetPasswordViewModel { + + weak var delegate: RioResetPasswordViewModelDelegate? + private let service: RioResetPasswordService + + init(service: RioResetPasswordService = RioResetPasswordService()) { + self.service = service + } + + func validateEmail(_ email: String) throws { + guard !email.isEmpty, + email.contains("@"), + email.contains("."), + email.count > 5 else { + throw RioCommonErrors.invalidEmail + } + } + + func validateInternetConnection() throws { + guard ConnectivityManager.shared.isConnected else { + throw RioCommonErrors.noInternet + } + } + + func attemptPasswordReset(email: String) { + do { + try validateEmail(email) + try validateInternetConnection() + + service.resetPassword(email: email) { [weak self] success in + DispatchQueue.main.async { + if success { + self?.delegate?.didResetPasswordSuccess(email: email) + } else { + self?.delegate?.didFailWithError(.noInternet) + } + } + } + } catch let error as RioCommonErrors { + delegate?.didFailWithError(error) + } catch { + delegate?.didFailWithError(.noInternet) + } + } +}