From 5e91e363504cba4610142fd781c46eb35f661bfb Mon Sep 17 00:00:00 2001 From: Pierre Brisorgueil Date: Fri, 15 May 2020 11:25:55 +0200 Subject: [PATCH] =?UTF-8?q?fix(global):=20error=20handling=20=F0=9F=90=9B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit #185 --- waosSwift/config/default/development.json | 10 +++++- waosSwift/lib/helpers/Errors.swift | 14 ++++---- waosSwift/lib/services/Networking.swift | 1 + waosSwift/modules/app/AppDelegate.swift | 2 +- .../controllers/AuthSigninController.swift | 28 +++++---------- .../controllers/AuthSignupController.swift | 31 +++++++--------- .../auth/reactors/AuthSigninReactor.swift | 22 +++++++----- .../auth/reactors/AuthSignupReactor.swift | 24 ++++++++----- .../core/controllers/CoreController.swift | 2 ++ .../core/controllers/CoreFormController.swift | 4 +++ .../controllers/OnBoardingController.swift | 2 +- .../controllers/TasksListController.swift | 9 ----- .../controllers/TasksViewController.swift | 11 +----- .../tasks/reactors/TasksListReactor.swift | 8 +++-- .../tasks/reactors/TasksViewReactor.swift | 6 +++- .../user/controllers/UserController.swift | 23 ++---------- .../UserPreferenceController.swift | 17 +-------- .../user/controllers/UserViewController.swift | 36 +++++++++---------- waosSwift/modules/user/models/UserModel.swift | 2 +- .../user/reactors/UserPreferenceReactor.swift | 11 +++--- .../modules/user/reactors/UserReactor.swift | 6 +++- .../user/reactors/UserViewReactor.swift | 25 ++++++++----- 22 files changed, 136 insertions(+), 158 deletions(-) diff --git a/waosSwift/config/default/development.json b/waosSwift/config/default/development.json index 0625ffc..5e342b9 100644 --- a/waosSwift/config/default/development.json +++ b/waosSwift/config/default/development.json @@ -67,13 +67,21 @@ } } ], - "img" : { + "img": { "compresion": 0.25, "styles": { "blured": 10, "overlayFraction": 0.9 } }, + "times": { + "buttons": { + "throttle": 3000 + }, + "errors": { + "debounce": 2000 + }, + }, "theme": { "global": { "radius": 5, diff --git a/waosSwift/lib/helpers/Errors.swift b/waosSwift/lib/helpers/Errors.swift index dd04f60..664d90f 100644 --- a/waosSwift/lib/helpers/Errors.swift +++ b/waosSwift/lib/helpers/Errors.swift @@ -48,14 +48,16 @@ func getError(_ error: Error, file: StaticString = #file, function: StaticString * @param {Error} error * @return {CustomError} */ -func purgeErrors(errors: [DisplayError], titles: [String]) -> [DisplayError] { - var _error: [DisplayError] = errors - for title in titles { - if let index = _error.firstIndex(where: { $0.title == title }) { - _error.remove(at: index) +func purgeErrors(errors: [DisplayError], specificTitles: [String]? = nil) -> [DisplayError] { + var _errors: [DisplayError] = errors + if let _titles = specificTitles { + for title in _titles { + if let index = _errors.firstIndex(where: { $0.title == title }) { + _errors.remove(at: index) + } } } - return _error + return _errors } /** diff --git a/waosSwift/lib/services/Networking.swift b/waosSwift/lib/services/Networking.swift index a728706..b9255ad 100644 --- a/waosSwift/lib/services/Networking.swift +++ b/waosSwift/lib/services/Networking.swift @@ -49,6 +49,7 @@ final class Networking: MoyaProvider { let message = "🌎 failure -> \(requestString) (\(response.statusCode)) (\(target))" log.warning(message, file: file, function: function, line: line) } + print("totot", response.statusCode) } else { let message = "🌎 failure -> \(requestString)\n\(error)" log.warning(message, file: file, function: function, line: line) diff --git a/waosSwift/modules/app/AppDelegate.swift b/waosSwift/modules/app/AppDelegate.swift index a6efa05..9749fbb 100644 --- a/waosSwift/modules/app/AppDelegate.swift +++ b/waosSwift/modules/app/AppDelegate.swift @@ -39,7 +39,7 @@ class AppDelegate: UIResponder, UIApplicationDelegate { ToastView.appearance().bottomOffsetPortrait = 100 ToastView.appearance().bottomOffsetLandscape = 100 } else { - let marginTop: CGFloat = 165 + let marginTop: CGFloat = 170 ToastView.appearance().bottomOffsetPortrait = max(UIScreen.main.bounds.width, UIScreen.main.bounds.height) - marginTop ToastView.appearance().bottomOffsetLandscape = min(UIScreen.main.bounds.width, UIScreen.main.bounds.height) - marginTop } diff --git a/waosSwift/modules/auth/controllers/AuthSigninController.swift b/waosSwift/modules/auth/controllers/AuthSigninController.swift index 2371c87..5a014bf 100644 --- a/waosSwift/modules/auth/controllers/AuthSigninController.swift +++ b/waosSwift/modules/auth/controllers/AuthSigninController.swift @@ -156,7 +156,7 @@ private extension AuthSignInController { func bindAction(_ reactor: AuthSigninReactor) { // button signin buttonSignin.rx.tap - .throttle(.seconds(3), latest: false, scheduler: MainScheduler.instance) + .throttle(.milliseconds(Metric.timesButtonsThrottle), latest: false, scheduler: MainScheduler.instance) .map { _ in Reactor.Action.signIn } .bind(to: reactor.action) .disposed(by: self.disposeBag) @@ -173,7 +173,7 @@ private extension AuthSignInController { .bind(to: reactor.action) .disposed(by: self.disposeBag) self.inputEmail.rx.controlEvent(.editingChanged).asObservable() - .debounce(.seconds(2), scheduler: MainScheduler.instance) + .debounce(.milliseconds(Metric.timesErrorsDebounce), scheduler: MainScheduler.instance) .map {Reactor.Action.validateEmail} .bind(to: reactor.action) .disposed(by: self.disposeBag) @@ -183,11 +183,6 @@ private extension AuthSignInController { .map {Reactor.Action.updatePassword($0!)} .bind(to: reactor.action) .disposed(by: self.disposeBag) - self.inputPassword.rx.controlEvent(.editingChanged).asObservable() - .debounce(.seconds(2), scheduler: MainScheduler.instance) - .map {Reactor.Action.validatePassword} - .bind(to: reactor.action) - .disposed(by: self.disposeBag) } // MARK: states (Reactor -> View) @@ -199,19 +194,14 @@ private extension AuthSignInController { .distinctUntilChanged() .bind(to: self.rx.isAnimating) .disposed(by: disposeBag) - // error + // validation errors reactor.state - .map { $0.errors.count } - .distinctUntilChanged() - .subscribe(onNext: { count in - if(count > 0) { - let message: [String] = reactor.currentState.errors.map { "\($0.description)." } - ToastCenter.default.cancelAll() - Toast(text: message.joined(separator: "\n"), delay: 0, duration: Delay.long).show() - } else { - ToastCenter.default.cancelAll() - } - + .map { $0.errors } + .filter { $0.count > 0 } + .distinctUntilChanged { $0.count == $1.count } + .subscribe(onNext: { errors in + ToastCenter.default.cancelAll() + Toast(text: errors.map { "\($0.description)." }.joined(separator: "\n"), delay: 0, duration: Delay.long).show() }) .disposed(by: self.disposeBag) reactor.state diff --git a/waosSwift/modules/auth/controllers/AuthSignupController.swift b/waosSwift/modules/auth/controllers/AuthSignupController.swift index c66eedd..9a5c4c7 100644 --- a/waosSwift/modules/auth/controllers/AuthSignupController.swift +++ b/waosSwift/modules/auth/controllers/AuthSignupController.swift @@ -177,7 +177,7 @@ private extension AuthSignUpController { .disposed(by: self.disposeBag) // button signup buttonSignup.rx.tap - .throttle(.seconds(3), latest: false, scheduler: MainScheduler.instance) + .throttle(.milliseconds(Metric.timesButtonsThrottle), latest: false, scheduler: MainScheduler.instance) .map { _ in Reactor.Action.signUp } .bind(to: reactor.action) .disposed(by: self.disposeBag) @@ -194,7 +194,7 @@ private extension AuthSignUpController { .bind(to: reactor.action) .disposed(by: self.disposeBag) self.inputFirstName.rx.controlEvent(.editingChanged).asObservable() - .debounce(.seconds(2), scheduler: MainScheduler.instance) + .debounce(.milliseconds(Metric.timesErrorsDebounce), scheduler: MainScheduler.instance) .map {Reactor.Action.validateFirstName} .bind(to: reactor.action) .disposed(by: self.disposeBag) @@ -205,7 +205,7 @@ private extension AuthSignUpController { .bind(to: reactor.action) .disposed(by: self.disposeBag) self.inputLastName.rx.controlEvent(.editingChanged).asObservable() - .debounce(.seconds(2), scheduler: MainScheduler.instance) + .debounce(.milliseconds(Metric.timesErrorsDebounce), scheduler: MainScheduler.instance) .map {Reactor.Action.validateLastName} .bind(to: reactor.action) .disposed(by: self.disposeBag) @@ -216,7 +216,7 @@ private extension AuthSignUpController { .bind(to: reactor.action) .disposed(by: self.disposeBag) self.inputEmail.rx.controlEvent(.editingChanged).asObservable() - .debounce(.seconds(2), scheduler: MainScheduler.instance) + .debounce(.milliseconds(Metric.timesErrorsDebounce), scheduler: MainScheduler.instance) .map {Reactor.Action.validateEmail} .bind(to: reactor.action) .disposed(by: self.disposeBag) @@ -227,7 +227,7 @@ private extension AuthSignUpController { .bind(to: reactor.action) .disposed(by: self.disposeBag) self.inputPassword.rx.controlEvent(.editingChanged).asObservable() - .debounce(.seconds(2), scheduler: MainScheduler.instance) + .debounce(.milliseconds(Metric.timesErrorsDebounce), scheduler: MainScheduler.instance) .map {Reactor.Action.validatePassword} .bind(to: reactor.action) .disposed(by: self.disposeBag) @@ -251,19 +251,14 @@ private extension AuthSignUpController { self?.dismiss(animated: true, completion: nil) }) .disposed(by: self.disposeBag) - // errors + // validation errors reactor.state - .map { $0.errors.count } - .distinctUntilChanged() - .subscribe(onNext: { count in - if(count > 0) { - let message: [String] = reactor.currentState.errors.map { "\($0.description)." } - ToastCenter.default.cancelAll() - Toast(text: message.joined(separator: "\n"), delay: 0, duration: Delay.long).show() - } else { - ToastCenter.default.cancelAll() - } - + .map { $0.errors } + .filter { $0.count > 0 } + .distinctUntilChanged { $0.count == $1.count } + .subscribe(onNext: { errors in + ToastCenter.default.cancelAll() + Toast(text: errors.map { "\($0.description)." }.joined(separator: "\n"), delay: 0, duration: Delay.long).show() }) .disposed(by: self.disposeBag) reactor.state @@ -302,7 +297,7 @@ private extension AuthSignUpController { ) .map { [$0.0, $0.1] } .map { !$0.contains(true) } - .bind(to: self.buttonSignup.rx.isEnabled) + .bind(to: self.buttonSignin.rx.isEnabled) .disposed(by: disposeBag) } } diff --git a/waosSwift/modules/auth/reactors/AuthSigninReactor.swift b/waosSwift/modules/auth/reactors/AuthSigninReactor.swift index 07c03f5..bda4132 100644 --- a/waosSwift/modules/auth/reactors/AuthSigninReactor.swift +++ b/waosSwift/modules/auth/reactors/AuthSigninReactor.swift @@ -18,7 +18,6 @@ final class AuthSigninReactor: Reactor { case updateEmail(String) case validateEmail case updatePassword(String) - case validatePassword case updateIsFilled(Bool) // others case signIn @@ -35,6 +34,7 @@ final class AuthSigninReactor: Reactor { case goSignUp // default case setRefreshing(Bool) + case validationError(CustomError) case success(String) case error(CustomError) } @@ -76,13 +76,11 @@ final class AuthSigninReactor: Reactor { case .validateEmail: switch currentState.user.validate(.email) { case .valid: return .just(.success("\(User.Validators.email)")) - case let .invalid(err): return .just(.error(err[0] as! CustomError)) + case let .invalid(err): return .just(.validationError(err[0] as! CustomError)) } // password case let .updatePassword(password): return .just(.updatePassword(password)) - case .validatePassword: - return .just(.success("password")) // form case let .updateIsFilled(isFilled): return .just(.updateIsFilled(isFilled)) @@ -135,18 +133,24 @@ final class AuthSigninReactor: Reactor { // success case let .success(success): log.verbose("♻️ Mutation -> State : succes \(success)") - state.errors = purgeErrors(errors: state.errors, titles: [success, "Schema validation error", "jwt", "unknow"]) + state.errors = purgeErrors(errors: state.errors, specificTitles: [success]) // error + case let .validationError(error): + log.verbose("♻️ Mutation -> State : validation error \(error)") + if state.errors.firstIndex(where: { $0.title == error.message }) == nil { + state.errors.insert(DisplayError(title: error.message, description: (error.description ?? "Unknown error")), at: 0) + } case let .error(error): log.verbose("♻️ Mutation -> State : error \(error)") + let _error: DisplayError if error.code == 401 { self.provider.preferencesService.isLogged = false - state.errors.insert(DisplayError(title: "jwt", description: "Wrong Password or Email."), at: 0) + _error = DisplayError(title: "jwt", description: "Wrong Password or Email.", type: error.type) } else { - if state.errors.firstIndex(where: { $0.title == error.message }) == nil { - state.errors.insert(DisplayError(title: error.message, description: (error.description ?? "Unknown error")), at: 0) - } + _error = DisplayError(title: error.message, description: (error.description ?? "Unknown error"), type: error.type) } + ToastCenter.default.cancelAll() + Toast(text: _error.description, delay: 0, duration: Delay.long).show() } return state } diff --git a/waosSwift/modules/auth/reactors/AuthSignupReactor.swift b/waosSwift/modules/auth/reactors/AuthSignupReactor.swift index 159ef38..2c99a2c 100644 --- a/waosSwift/modules/auth/reactors/AuthSignupReactor.swift +++ b/waosSwift/modules/auth/reactors/AuthSignupReactor.swift @@ -42,6 +42,7 @@ final class AuthSignUpReactor: Reactor { // default case dismiss case setRefreshing(Bool) + case validationError(CustomError) case success(String) case error(CustomError) } @@ -85,7 +86,7 @@ final class AuthSignUpReactor: Reactor { case .validateFirstName: switch currentState.user.validate(.firstname) { case .valid: return .just(.success("\(User.Validators.firstname)")) - case let .invalid(err): return .just(.error(err[0] as! CustomError)) + case let .invalid(err): return .just(.validationError(err[0] as! CustomError)) } // update password // lastname @@ -94,7 +95,7 @@ final class AuthSignUpReactor: Reactor { case .validateLastName: switch currentState.user.validate(.lastname) { case .valid: return .just(.success("\(User.Validators.lastname)")) - case let .invalid(err): return .just(.error(err[0] as! CustomError)) + case let .invalid(err): return .just(.validationError(err[0] as! CustomError)) } // email case let .updateEmail(email): @@ -102,7 +103,7 @@ final class AuthSignUpReactor: Reactor { case .validateEmail: switch currentState.user.validate(.email) { case .valid: return .just(.success("\(User.Validators.email)")) - case let .invalid(err): return .just(.error(err[0] as! CustomError)) + case let .invalid(err): return .just(.validationError(err[0] as! CustomError)) } // password case let .updatePassword(password): @@ -110,7 +111,7 @@ final class AuthSignUpReactor: Reactor { case .validatePassword: switch currentState.user.validate(.password) { case .valid: return .just(.success("\(User.Validators.password)")) - case let .invalid(err): return .just(.error(err[0] as! CustomError)) + case let .invalid(err): return .just(.validationError(err[0] as! CustomError)) } // form case let .updateIsFilled(isFilled): @@ -173,17 +174,24 @@ final class AuthSignUpReactor: Reactor { state.isRefreshing = isRefreshing case let .success(success): log.verbose("♻️ Mutation -> State : succes \(success)") - state.errors = purgeErrors(errors: state.errors, titles: [success, "Schema validation error", "jwt", "unknow"]) + state.errors = purgeErrors(errors: state.errors, specificTitles: [success]) // error + case let .validationError(error): + log.verbose("♻️ Mutation -> State : validation error \(error)") + if state.errors.firstIndex(where: { $0.title == error.message }) == nil { + state.errors.insert(DisplayError(title: error.message, description: (error.description ?? "Unknown error")), at: 0) + } case let .error(error): log.verbose("♻️ Mutation -> State : error \(error)") + let _error: DisplayError if error.code == 401 { self.provider.preferencesService.isLogged = false + _error = DisplayError(title: "jwt", description: "Wrong Password or Email.", type: error.type) } else { - if state.errors.firstIndex(where: { $0.title == error.message }) == nil { - state.errors.insert(DisplayError(title: error.message, description: (error.description ?? "Unknown error")), at: 0) - } + _error = DisplayError(title: error.message, description: (error.description ?? "Unknown error"), type: error.type) } + ToastCenter.default.cancelAll() + Toast(text: _error.description, delay: 0, duration: Delay.long).show() } return state } diff --git a/waosSwift/modules/core/controllers/CoreController.swift b/waosSwift/modules/core/controllers/CoreController.swift index a0d57f1..c09aeed 100644 --- a/waosSwift/modules/core/controllers/CoreController.swift +++ b/waosSwift/modules/core/controllers/CoreController.swift @@ -11,6 +11,8 @@ class CoreController: UIViewController { static let tabBarColor = NSString(string: config["theme"]["tabBar"]["color"].string ?? "").boolValue static let tabBarTintColor = NSString(string: config["theme"]["tabBar"]["tintColor"].string ?? "").boolValue static let tabBarTitle = NSString(string: config["theme"]["tabBar"]["title"].string ?? "").boolValue + static let timesButtonsThrottle = Int(config["times"]["buttons"]["throttle"].int ?? 2000) + static let timesErrorsDebounce = Int(config["times"]["errors"]["debounce"].int ?? 2000) } lazy private(set) var className: String = { diff --git a/waosSwift/modules/core/controllers/CoreFormController.swift b/waosSwift/modules/core/controllers/CoreFormController.swift index 980f53f..f16bac7 100644 --- a/waosSwift/modules/core/controllers/CoreFormController.swift +++ b/waosSwift/modules/core/controllers/CoreFormController.swift @@ -20,6 +20,10 @@ class CoreFormController: FormViewController { static let tabBarTitle = NSString(string: config["theme"]["tabBar"]["title"].string ?? "").boolValue static let imgCompression = CGFloat(config["img"]["compresion"].float ?? 1.0) static let margin = CGFloat(config["theme"]["global"]["margin"].int ?? 0) + static let radius = CGFloat(config["theme"]["global"]["radius"].int ?? 0) + static let timesButtonsThrottle = Int(config["times"]["buttons"]["throttle"].int ?? 2000) + static let timesErrorsDebounce = Int(config["times"]["errors"]["debounce"].int ?? 2000) + static let avatar = CGFloat(100) } lazy private(set) var className: String = { diff --git a/waosSwift/modules/onBoarding/controllers/OnBoardingController.swift b/waosSwift/modules/onBoarding/controllers/OnBoardingController.swift index 2a534fc..9d1c6e2 100755 --- a/waosSwift/modules/onBoarding/controllers/OnBoardingController.swift +++ b/waosSwift/modules/onBoarding/controllers/OnBoardingController.swift @@ -132,7 +132,7 @@ private extension OnboardingController { func bindAction(_ reactor: OnboardingReactor) { completeButton.rx.tap - .throttle(.seconds(3), latest: false, scheduler: MainScheduler.instance) + .throttle(.milliseconds(Metric.timesButtonsThrottle), latest: false, scheduler: MainScheduler.instance) .map { _ in Reactor.Action.complete } .bind(to: reactor.action) .disposed(by: self.disposeBag) diff --git a/waosSwift/modules/tasks/controllers/TasksListController.swift b/waosSwift/modules/tasks/controllers/TasksListController.swift index cc769ab..f1929cd 100644 --- a/waosSwift/modules/tasks/controllers/TasksListController.swift +++ b/waosSwift/modules/tasks/controllers/TasksListController.swift @@ -139,15 +139,6 @@ private extension TasksListController { .distinctUntilChanged() .bind(to: refreshControl.rx.isRefreshing) .disposed(by: disposeBag) - // error - reactor.state - .map { $0.error?.description } - .throttle(.seconds(5), scheduler: MainScheduler.instance) - .filterNil() - .subscribe(onNext: { result in - Toast(text: result, delay: 0, duration: Delay.long).show() - }) - .disposed(by: self.disposeBag) } } diff --git a/waosSwift/modules/tasks/controllers/TasksViewController.swift b/waosSwift/modules/tasks/controllers/TasksViewController.swift index c19cd0c..f1191b0 100644 --- a/waosSwift/modules/tasks/controllers/TasksViewController.swift +++ b/waosSwift/modules/tasks/controllers/TasksViewController.swift @@ -90,7 +90,7 @@ private extension TasksViewController { func bindAction(_ reactor: TasksViewReactor) { self.barButtonDone.rx.tap - .throttle(.seconds(3), latest: false, scheduler: MainScheduler.instance) + .throttle(.milliseconds(Metric.timesButtonsThrottle), latest: false, scheduler: MainScheduler.instance) .map { Reactor.Action.done } .bind(to: reactor.action) .disposed(by: self.disposeBag) @@ -121,14 +121,5 @@ private extension TasksViewController { self?.dismiss(animated: true, completion: nil) }) .disposed(by: self.disposeBag) - // error - reactor.state - .map { $0.error?.description } - .throttle(.seconds(5), scheduler: MainScheduler.instance) - .filterNil() - .subscribe(onNext: { result in - Toast(text: result, delay: 0, duration: Delay.long).show() - }) - .disposed(by: self.disposeBag) } } diff --git a/waosSwift/modules/tasks/reactors/TasksListReactor.swift b/waosSwift/modules/tasks/reactors/TasksListReactor.swift index 5a7243e..d9131a4 100644 --- a/waosSwift/modules/tasks/reactors/TasksListReactor.swift +++ b/waosSwift/modules/tasks/reactors/TasksListReactor.swift @@ -41,7 +41,6 @@ final class TasksListReactor: Reactor { var tasks: [Tasks] var sections: [TasksSections] var isRefreshing: Bool - var error: DisplayError? init() { self.tasks = [] @@ -160,15 +159,18 @@ final class TasksListReactor: Reactor { // success case let .success(success): log.verbose("♻️ Mutation -> State : succes \(success)") - state.error = nil // error case let .error(error): log.verbose("♻️ Mutation -> State : error \(error)") + let _error: DisplayError if error.code == 401 { self.provider.preferencesService.isLogged = false + _error = DisplayError(title: "jwt", description: "Wrong Password or Email.", type: error.type) } else { - state.error = DisplayError(title: error.message, description: (error.description ?? "Unknown error")) + _error = DisplayError(title: error.message, description: (error.description ?? "Unknown error"), type: error.type) } + ToastCenter.default.cancelAll() + Toast(text: _error.description, delay: 0, duration: Delay.long).show() } return state } diff --git a/waosSwift/modules/tasks/reactors/TasksViewReactor.swift b/waosSwift/modules/tasks/reactors/TasksViewReactor.swift index b1fbb2c..e80baa7 100644 --- a/waosSwift/modules/tasks/reactors/TasksViewReactor.swift +++ b/waosSwift/modules/tasks/reactors/TasksViewReactor.swift @@ -121,11 +121,15 @@ final class TasksViewReactor: Reactor { // error case let .error(error): log.verbose("♻️ Mutation -> State : error \(error)") + let _error: DisplayError if error.code == 401 { self.provider.preferencesService.isLogged = false + _error = DisplayError(title: "jwt", description: "Wrong Password or Email.", type: error.type) } else { - state.error = DisplayError(title: error.message, description: (error.description ?? "Unknown error")) + _error = DisplayError(title: error.message, description: (error.description ?? "Unknown error"), type: error.type) } + ToastCenter.default.cancelAll() + Toast(text: _error.description, delay: 0, duration: Delay.long).show() } return state } diff --git a/waosSwift/modules/user/controllers/UserController.swift b/waosSwift/modules/user/controllers/UserController.swift index 4fd3206..698ec87 100644 --- a/waosSwift/modules/user/controllers/UserController.swift +++ b/waosSwift/modules/user/controllers/UserController.swift @@ -14,14 +14,6 @@ import MessageUI class UserController: CoreFormController, View { - // MARK: Constants - - struct Metric { - static let margin = CGFloat(config["theme"]["global"]["margin"].int ?? 0) - static let radius = CGFloat(config["theme"]["global"]["radius"].int ?? 0) - static let avatar = CGFloat(100) - } - // MARK: UI let refreshControl = CoreUIRefreshControl() @@ -276,7 +268,7 @@ private extension UserController { .disposed(by: self.disposeBag) // buttons self.buttonData.rx.tap - .throttle(.seconds(3), latest: false, scheduler: MainScheduler.instance) + .throttle(.milliseconds(Metric.timesButtonsThrottle), latest: false, scheduler: MainScheduler.instance) .subscribe(onNext: { _ in let actions: [AlertAction] = [AlertAction.action(title: L10n.modalConfirmationCancel, style: .cancel), AlertAction.action(title: L10n.modalConfirmationOk, style: .default)] self.showAlert(title: L10n.userData, message: L10n.userModalConfirmationDataMessage, style: .alert, actions: actions) @@ -287,7 +279,7 @@ private extension UserController { }) .disposed(by: self.disposeBag) self.buttonLogout.rx.tap - .throttle(.seconds(3), latest: false, scheduler: MainScheduler.instance) + .throttle(.milliseconds(Metric.timesButtonsThrottle), latest: false, scheduler: MainScheduler.instance) .subscribe(onNext: { _ in let actions: [AlertAction] = [AlertAction.action(title: L10n.modalConfirmationCancel, style: .cancel), AlertAction.action(title: L10n.modalConfirmationOk, style: .destructive)] self.showAlert(title: L10n.userLogout, message: L10n.modalConfirmationMessage, style: .alert, actions: actions) @@ -298,7 +290,7 @@ private extension UserController { }) .disposed(by: self.disposeBag) self.buttonDelete.rx.tap - .throttle(.seconds(3), latest: false, scheduler: MainScheduler.instance) + .throttle(.milliseconds(Metric.timesButtonsThrottle), latest: false, scheduler: MainScheduler.instance) .subscribe(onNext: { _ in let actions: [AlertAction] = [AlertAction.action(title: L10n.modalConfirmationCancel, style: .cancel), AlertAction.action(title: L10n.modalConfirmationOk, style: .destructive)] self.showAlert(title: L10n.userDelete, message: L10n.userModalConfirmationDeleteMessage, style: .alert, actions: actions) @@ -361,15 +353,6 @@ private extension UserController { .distinctUntilChanged() .bind(to: refreshControl.rx.isRefreshing) .disposed(by: disposeBag) - // error - reactor.state - .map { $0.error?.description } - .throttle(.seconds(5), scheduler: MainScheduler.instance) - .filterNil() - .subscribe(onNext: { result in - Toast(text: result, delay: 0, duration: Delay.long).show() - }) - .disposed(by: self.disposeBag) } } diff --git a/waosSwift/modules/user/controllers/UserPreferenceController.swift b/waosSwift/modules/user/controllers/UserPreferenceController.swift index 0c50f45..73085d6 100644 --- a/waosSwift/modules/user/controllers/UserPreferenceController.swift +++ b/waosSwift/modules/user/controllers/UserPreferenceController.swift @@ -88,7 +88,7 @@ private extension UserPreferenceController { func bindAction(_ reactor: UserPreferenceReactor) { // buttons self.barButtonDone.rx.tap - .throttle(.seconds(3), latest: false, scheduler: MainScheduler.instance) + .throttle(.milliseconds(Metric.timesButtonsThrottle), latest: false, scheduler: MainScheduler.instance) .map { Reactor.Action.done } .bind(to: reactor.action) .disposed(by: self.disposeBag) @@ -122,20 +122,5 @@ private extension UserPreferenceController { self?.dismiss(animated: true, completion: nil) }) .disposed(by: self.disposeBag) - // error - reactor.state - .map { $0.errors.count } - .distinctUntilChanged() - .subscribe(onNext: { count in - if(count > 0) { - let message: [String] = reactor.currentState.errors.map { "\($0.description)." } - ToastCenter.default.cancelAll() - Toast(text: message.joined(separator: "\n"), delay: 0, duration: Delay.long).show() - } else { - ToastCenter.default.cancelAll() - } - - }) - .disposed(by: self.disposeBag) } } diff --git a/waosSwift/modules/user/controllers/UserViewController.swift b/waosSwift/modules/user/controllers/UserViewController.swift index 96a25b5..7a06b70 100644 --- a/waosSwift/modules/user/controllers/UserViewController.swift +++ b/waosSwift/modules/user/controllers/UserViewController.swift @@ -172,7 +172,7 @@ private extension UserViewController { func bindAction(_ reactor: UserViewReactor) { // buttons self.barButtonDone.rx.tap - .throttle(.seconds(3), latest: false, scheduler: MainScheduler.instance) + .throttle(.milliseconds(Metric.timesButtonsThrottle), latest: false, scheduler: MainScheduler.instance) .map { Reactor.Action.done } .bind(to: reactor.action) .disposed(by: self.disposeBag) @@ -184,7 +184,7 @@ private extension UserViewController { .bind(to: reactor.action) .disposed(by: self.disposeBag) observableFirstName - .debounce(.seconds(2), scheduler: MainScheduler.instance) + .debounce(.milliseconds(Metric.timesErrorsDebounce), scheduler: MainScheduler.instance) .map {_ in Reactor.Action.validateFirstName("account")} .bind(to: reactor.action) .disposed(by: self.disposeBag) @@ -195,7 +195,7 @@ private extension UserViewController { .bind(to: reactor.action) .disposed(by: self.disposeBag) observableLastName - .debounce(.seconds(2), scheduler: MainScheduler.instance) + .debounce(.milliseconds(Metric.timesErrorsDebounce), scheduler: MainScheduler.instance) .map {_ in Reactor.Action.validateLastName("account")} .bind(to: reactor.action) .disposed(by: self.disposeBag) @@ -206,7 +206,7 @@ private extension UserViewController { .bind(to: reactor.action) .disposed(by: self.disposeBag) observableEmail - .debounce(.seconds(2), scheduler: MainScheduler.instance) + .debounce(.milliseconds(Metric.timesErrorsDebounce), scheduler: MainScheduler.instance) .map {_ in Reactor.Action.validateEmail("account")} .bind(to: reactor.action) .disposed(by: self.disposeBag) @@ -218,7 +218,7 @@ private extension UserViewController { .bind(to: reactor.action) .disposed(by: self.disposeBag) observableBio - .debounce(.seconds(2), scheduler: MainScheduler.instance) + .debounce(.milliseconds(Metric.timesErrorsDebounce), scheduler: MainScheduler.instance) .map {_ in Reactor.Action.validateBio("profil")} .bind(to: reactor.action) .disposed(by: self.disposeBag) @@ -239,7 +239,7 @@ private extension UserViewController { self.avatar .skip(1) .filter { $0 == nil && reactor.currentState.user.avatar != "" } - .throttle(.seconds(3), latest: false, scheduler: MainScheduler.instance) + .throttle(.milliseconds(Metric.timesButtonsThrottle), latest: false, scheduler: MainScheduler.instance) .map { _ in Reactor.Action.deleteAvatar } .bind(to: reactor.action) .disposed(by: self.disposeBag) @@ -284,7 +284,7 @@ private extension UserViewController { // avatar reactor.state .take(1) - .debounce(.seconds(2), scheduler: MainScheduler.instance) + .debounce(.milliseconds(Metric.timesErrorsDebounce), scheduler: MainScheduler.instance) .map { $0.user.email } .distinctUntilChanged() .subscribe(onNext: { email in @@ -325,23 +325,19 @@ private extension UserViewController { self?.dismiss(animated: true, completion: nil) }) .disposed(by: self.disposeBag) - // error + // validation errors reactor.state - .map { $0.errors.count } - .distinctUntilChanged() - .subscribe(onNext: { count in - self.labelErrorsAccount.text = reactor.currentState.errors.filter({ $0.type == "account" }).map { $0.description }.joined(separator: ". ") - self.labelErrorsProfil.text = reactor.currentState.errors.filter({ $0.type == "profil" }).map { $0.description }.joined(separator: ". ") - if(count > 0 && self.labelErrorsAccount.text?.count == 0 && self.labelErrorsProfil.text?.count == 0) { - let message: [String] = reactor.currentState.errors.map { "\($0.description)." } + .map { $0.errors } + .distinctUntilChanged { $0.count == $1.count } + .subscribe(onNext: { errors in + self.labelErrorsAccount.text = errors.filter({ $0.type == "account" }).map { $0.description }.joined(separator: ". ") + self.labelErrorsProfil.text = errors.filter({ $0.type == "profil" }).map { $0.description }.joined(separator: ". ") + if(errors.count > 0 && self.labelErrorsAccount.text?.count == 0 && self.labelErrorsProfil.text?.count == 0) { + let message: [String] = errors.map { "\($0.description)." } ToastCenter.default.cancelAll() Toast(text: message.joined(separator: "\n"), delay: 0, duration: Delay.long).show() } - if(count > 0) { - self.barButtonDone.isEnabled = false - } else { - self.barButtonDone.isEnabled = true - } + self.barButtonDone.isEnabled = errors.count > 0 ? false : true }) .disposed(by: self.disposeBag) } diff --git a/waosSwift/modules/user/models/UserModel.swift b/waosSwift/modules/user/models/UserModel.swift index 14ef9da..16b88e8 100644 --- a/waosSwift/modules/user/models/UserModel.swift +++ b/waosSwift/modules/user/models/UserModel.swift @@ -64,7 +64,7 @@ extension User: Hashable, Codable { extension User: Validatable { - enum Validators: String { + enum Validators: String, CaseIterable { case firstname = "Wrong firstname (letters, 1 - 30)" case lastname = "Wrong lastname (letters, 1 - 30)" case email = "Wrong mail format" diff --git a/waosSwift/modules/user/reactors/UserPreferenceReactor.swift b/waosSwift/modules/user/reactors/UserPreferenceReactor.swift index 6fdb728..d237ed8 100644 --- a/waosSwift/modules/user/reactors/UserPreferenceReactor.swift +++ b/waosSwift/modules/user/reactors/UserPreferenceReactor.swift @@ -84,18 +84,19 @@ final class UserPreferenceReactor: Reactor { // success case let .success(success): log.verbose("♻️ Mutation -> State : succes \(success)") - state.errors = purgeErrors(errors: state.errors, titles: [success, "Schema validation error", "jwt", "unknow"]) + state.errors = purgeErrors(errors: state.errors, specificTitles: [success]) // error case let .error(error): log.verbose("♻️ Mutation -> State : error \(error)") + let _error: DisplayError if error.code == 401 { self.provider.preferencesService.isLogged = false - state.errors.insert(DisplayError(title: "jwt", description: "Wrong Password or Email."), at: 0) + _error = DisplayError(title: "jwt", description: "Wrong Password or Email.", type: error.type) } else { - if state.errors.firstIndex(where: { $0.title == error.message }) == nil { - state.errors.insert(DisplayError(title: error.message, description: error.description, type: error.type), at: 0) - } + _error = DisplayError(title: error.message, description: (error.description ?? "Unknown error"), type: error.type) } + ToastCenter.default.cancelAll() + Toast(text: _error.description, delay: 0, duration: Delay.long).show() } return state } diff --git a/waosSwift/modules/user/reactors/UserReactor.swift b/waosSwift/modules/user/reactors/UserReactor.swift index 254f561..ed0bdc8 100644 --- a/waosSwift/modules/user/reactors/UserReactor.swift +++ b/waosSwift/modules/user/reactors/UserReactor.swift @@ -139,11 +139,15 @@ final class UserReactor: Reactor { // error case let .error(error): log.verbose("♻️ Mutation -> State : error \(error)") + let _error: DisplayError if error.code == 401 { self.provider.preferencesService.isLogged = false + _error = DisplayError(title: "jwt", description: "Wrong Password or Email.") } else { - state.error = DisplayError(title: error.message, description: (error.description ?? "Unknown error")) + _error = DisplayError(title: error.message, description: (error.description ?? "Unknown error")) } + ToastCenter.default.cancelAll() + Toast(text: _error.description, delay: 0, duration: Delay.long).show() } return state } diff --git a/waosSwift/modules/user/reactors/UserViewReactor.swift b/waosSwift/modules/user/reactors/UserViewReactor.swift index 8b0b056..a2ec8ee 100644 --- a/waosSwift/modules/user/reactors/UserViewReactor.swift +++ b/waosSwift/modules/user/reactors/UserViewReactor.swift @@ -42,6 +42,7 @@ final class UserViewReactor: Reactor { // default case dismiss case setRefreshing(Bool) + case validationError(CustomError) case success(String) case error(CustomError) } @@ -83,21 +84,21 @@ final class UserViewReactor: Reactor { case let .validateFirstName(section): switch currentState.user.validate(.firstname, section) { case .valid: return .just(.success("\(User.Validators.firstname)")) - case let .invalid(err): return .just(.error(err[0] as! CustomError)) + case let .invalid(err): return .just(.validationError(err[0] as! CustomError)) } case let .updateLastName(lastName): return .just(.updateLastName(lastName)) case let .validateLastName(section): switch currentState.user.validate(.lastname, section) { case .valid: return .just(.success("\(User.Validators.lastname)")) - case let .invalid(err): return .just(.error(err[0] as! CustomError)) + case let .invalid(err): return .just(.validationError(err[0] as! CustomError)) } case let .updateEmail(email): return .just(.updateEmail(email)) case let .validateEmail(section): switch currentState.user.validate(.email, section) { case .valid: return .just(.success("\(User.Validators.email)")) - case let .invalid(err): return .just(.error(err[0] as! CustomError)) + case let .invalid(err): return .just(.validationError(err[0] as! CustomError)) } // extra case let .updateBio(bio): @@ -105,7 +106,7 @@ final class UserViewReactor: Reactor { case let .validateBio(section): switch currentState.user.validate(.bio, section) { case .valid: return .just(.success("\(User.Validators.bio)")) - case let .invalid(err): return .just(.error(err[0] as! CustomError)) + case let .invalid(err): return .just(.validationError(err[0] as! CustomError)) } // avatar case let .updateAvatar(data): @@ -175,18 +176,24 @@ final class UserViewReactor: Reactor { // success case let .success(success): log.verbose("♻️ Mutation -> State : succes \(success)") - state.errors = purgeErrors(errors: state.errors, titles: [success, "Schema validation error", "jwt", "unknow"]) + state.errors = purgeErrors(errors: state.errors, specificTitles: [success]) // error + case let .validationError(error): + log.verbose("♻️ Mutation -> State : validation error \(error)") + if state.errors.firstIndex(where: { $0.title == error.message }) == nil { + state.errors.insert(DisplayError(title: error.message, description: error.description, type: error.type), at: 0) + } case let .error(error): log.verbose("♻️ Mutation -> State : error \(error)") + let _error: DisplayError if error.code == 401 { self.provider.preferencesService.isLogged = false - state.errors.insert(DisplayError(title: "jwt", description: "Wrong Password or Email."), at: 0) + _error = DisplayError(title: "jwt", description: "Wrong Password or Email.", type: error.type) } else { - if state.errors.firstIndex(where: { $0.title == error.message }) == nil { - state.errors.insert(DisplayError(title: error.message, description: error.description, type: error.type), at: 0) - } + _error = DisplayError(title: error.message, description: (error.description ?? "Unknown error"), type: error.type) } + ToastCenter.default.cancelAll() + Toast(text: _error.description, delay: 0, duration: Delay.long).show() } return state }