From 09dd652690460b69607abd77ed34bd039f05d59c Mon Sep 17 00:00:00 2001 From: J0onYEong Date: Wed, 10 Jul 2024 09:41:04 +0900 Subject: [PATCH 1/6] =?UTF-8?q?[IDLE-72]=20=ED=9A=8C=EC=9B=90=EA=B0=80?= =?UTF-8?q?=EC=9E=85=20=EA=B2=B0=EA=B3=BC=20=ED=99=95=EC=9D=B8=ED=9B=84=20?= =?UTF-8?q?=EA=B0=80=EC=9E=85=ED=99=94=EB=A9=B4=20=EB=8B=AB=EC=9D=8C?= =?UTF-8?q?=EC=9C=BC=EB=A1=9C=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../State/Auth/CenterRegisterState.swift | 5 ++++ .../SetIdPasswordViewController.swift | 23 +++++++++++++--- .../Register/CenterRegisterViewModel.swift | 26 +++++++++++++++---- .../Auth/RegisterSuccessOutputable.swift | 13 ++++++++++ 4 files changed, 59 insertions(+), 8 deletions(-) create mode 100644 project/Projects/Presentation/PresentationCore/Sources/ViewModelType/Constraint/Auth/RegisterSuccessOutputable.swift diff --git a/project/Projects/Domain/Entity/State/Auth/CenterRegisterState.swift b/project/Projects/Domain/Entity/State/Auth/CenterRegisterState.swift index 31d754ab..eef9ddd3 100644 --- a/project/Projects/Domain/Entity/State/Auth/CenterRegisterState.swift +++ b/project/Projects/Domain/Entity/State/Auth/CenterRegisterState.swift @@ -35,4 +35,9 @@ public class CenterRegisterState { id = nil password = nil } + + public var descroption: String { + return "이름: \(name ?? "")\n전화번호: \(phoneNumber ?? "")\n비즈니스번호: \(businessNumber ?? "")\n아이디: \(id ?? "")\n패스워드: \(password ?? "")" + } } + diff --git a/project/Projects/Presentation/Feature/Auth/Sources/View/Center/Register/SetIdPasswordViewController.swift b/project/Projects/Presentation/Feature/Auth/Sources/View/Center/Register/SetIdPasswordViewController.swift index b984842f..5a4cf657 100644 --- a/project/Projects/Presentation/Feature/Auth/Sources/View/Center/Register/SetIdPasswordViewController.swift +++ b/project/Projects/Presentation/Feature/Auth/Sources/View/Center/Register/SetIdPasswordViewController.swift @@ -26,7 +26,7 @@ public protocol SetIdPasswordOutputable { } class SetIdPasswordViewController: DisposableViewController -where T.Input: SetIdPasswordInputable & CTAButtonEnableInputable, T.Output: SetIdPasswordOutputable { +where T.Input: SetIdPasswordInputable & CTAButtonEnableInputable, T.Output: SetIdPasswordOutputable & RegisterSuccessOutputable { var coordinator: CenterRegisterCoordinator? @@ -316,11 +316,28 @@ where T.Input: SetIdPasswordInputable & CTAButtonEnableInputable, T.Output: SetI .subscribe(onNext: { [weak self] in self?.ctaButton.setEnabled($0) }) .disposed(by: disposeBag) + Observable + .zip(output.registerValidation ?? .empty(), ctaButton.eventPublisher.asObservable()) + .subscribe { [weak self] (isSuccess, _) in + + if isSuccess { + // 회원가입 성공 + self?.coordinator?.next() + } else { + // 회원가입실패 + self?.ctaButton.setEnabled(true) + } + } + .disposed(by: disposeBag) + // MARK: ViewController한정 로직 - // CTA버튼 클릭시 화면전환 + // CTA버튼 클릭시 버튼 비활성화 ctaButton .eventPublisher - .emit { [weak self] _ in self?.coordinator?.next() } + .emit { [weak self] _ in + + self?.ctaButton.setEnabled(false) + } .disposed(by: disposeBag) } diff --git a/project/Projects/Presentation/Feature/Auth/Sources/ViewModel/Center/Register/CenterRegisterViewModel.swift b/project/Projects/Presentation/Feature/Auth/Sources/ViewModel/Center/Register/CenterRegisterViewModel.swift index 855c1099..c4419943 100644 --- a/project/Projects/Presentation/Feature/Auth/Sources/ViewModel/Center/Register/CenterRegisterViewModel.swift +++ b/project/Projects/Presentation/Feature/Auth/Sources/ViewModel/Center/Register/CenterRegisterViewModel.swift @@ -26,6 +26,10 @@ public class CenterRegisterViewModel: ViewModelType { self.useCase = useCase } + deinit { + printIfDebug("deinit \(Self.self)") + } + let disposeBag = DisposeBag() public func transform(input: Input) -> Output { @@ -217,7 +221,7 @@ public class CenterRegisterViewModel: ViewModelType { self?.output.businessNumberValidation?.onNext(vo) // 🚀 상태추적 🚀 - self?.stateObject.businessNumber = businessNumber + self?.stateObject.businessNumber = formattedString case .failure(let error): printIfDebug("❌ \(formattedString)번호 검색실패 \n 에러내용: \(error.message)") @@ -331,13 +335,19 @@ public class CenterRegisterViewModel: ViewModelType { switch result { case .success(_): - printIfDebug("[CenterRegisterViewModel] ✅ 획원가입 성공 \n 가임정보 \(self.stateObject)") + self.output.registerValidation?.onNext(true) + printIfDebug("[CenterRegisterViewModel] ✅ 획원가입 성공 \n 가임정보 \(self.stateObject.descroption)") + + // 현재까지 입력정보를 모두 삭제 + self.stateObject.clear() + case .failure(let error): + self.output.registerValidation?.onNext(false) printIfDebug("❌ 회원가입 실패: \(error.message)") + + // 현재까지 입력정보를 모두 삭제 + self.stateObject.clear() } - - // 현재까지 입력정보를 모두 삭제 - self.stateObject.clear() }) .disposed(by: self.disposeBag) } @@ -393,6 +403,9 @@ extension CenterRegisterViewModel { public var canCheckIdDuplication: PublishSubject? = .init() public var idValidation: PublishSubject<(isValid: Bool, id: String)>? = .init() public var passwordValidation: PublishSubject<(state: PasswordValidationState, password: String)>? = .init() + + // Register success + public var registerValidation: PublishSubject? = .init() } } @@ -416,3 +429,6 @@ extension CenterRegisterViewModel.Output: AuthBusinessOwnerOutputable { } // Id & Password extension CenterRegisterViewModel.Input: SetIdPasswordInputable { } extension CenterRegisterViewModel.Output: SetIdPasswordOutputable { } + +// Register +extension CenterRegisterViewModel.Output: RegisterSuccessOutputable { } diff --git a/project/Projects/Presentation/PresentationCore/Sources/ViewModelType/Constraint/Auth/RegisterSuccessOutputable.swift b/project/Projects/Presentation/PresentationCore/Sources/ViewModelType/Constraint/Auth/RegisterSuccessOutputable.swift new file mode 100644 index 00000000..d8ebdefe --- /dev/null +++ b/project/Projects/Presentation/PresentationCore/Sources/ViewModelType/Constraint/Auth/RegisterSuccessOutputable.swift @@ -0,0 +1,13 @@ +// +// RegisterSuccessOutputable.swift +// PresentationCore +// +// Created by choijunios on 7/10/24. +// + +import Foundation +import RxSwift + +public protocol RegisterSuccessOutputable { + var registerValidation: PublishSubject? { get } +} From 9e43c47616705d1c8bca850ed48cd1537592f9ab Mon Sep 17 00:00:00 2001 From: J0onYEong Date: Wed, 10 Jul 2024 10:00:32 +0900 Subject: [PATCH 2/6] =?UTF-8?q?[IDLE-70]=20IFType2=20=EC=BB=B4=ED=8F=AC?= =?UTF-8?q?=EB=84=8C=ED=8A=B8=20=EA=B0=9C=EB=B0=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../ExampleApp/Sources/ViewController2.swift | 13 +-- .../DSKit/Sources/CommonUI/IFType2.swift | 80 +++++++++++++++++++ .../Center/CenterLoginViewController.swift | 68 ---------------- .../CenterFindPasswordController.swift | 0 .../Login/CenterLoginViewController.swift | 65 +++++++++++++++ .../CenterSetNewPasswordController.swift | 0 6 files changed, 153 insertions(+), 73 deletions(-) create mode 100644 project/Projects/Presentation/DSKit/Sources/CommonUI/IFType2.swift delete mode 100644 project/Projects/Presentation/Feature/Auth/Sources/View/Center/CenterLoginViewController.swift rename project/Projects/Presentation/Feature/Auth/Sources/View/Center/{ => Login}/CenterFindPasswordController.swift (100%) create mode 100644 project/Projects/Presentation/Feature/Auth/Sources/View/Center/Login/CenterLoginViewController.swift rename project/Projects/Presentation/Feature/Auth/Sources/View/Center/{ => Login}/CenterSetNewPasswordController.swift (100%) diff --git a/project/Projects/Presentation/DSKit/ExampleApp/Sources/ViewController2.swift b/project/Projects/Presentation/DSKit/ExampleApp/Sources/ViewController2.swift index 6682f260..6c922497 100644 --- a/project/Projects/Presentation/DSKit/ExampleApp/Sources/ViewController2.swift +++ b/project/Projects/Presentation/DSKit/ExampleApp/Sources/ViewController2.swift @@ -62,12 +62,15 @@ class ViewController2: UIViewController { } - let rawTextField = UITextField() - rawTextField.backgroundColor = .red + let textFieldType2 = IFType2( + titleLabelText: "타이틀 라벨", + placeHolderText: "아이디 입력" + ) [ box, textField, + textFieldType2, ].forEach { view.addSubview($0) @@ -83,9 +86,9 @@ class ViewController2: UIViewController { textField.leadingAnchor.constraint(equalTo: view.layoutMarginsGuide.leadingAnchor), textField.trailingAnchor.constraint(equalTo: view.layoutMarginsGuide.trailingAnchor), -// rawTextField.topAnchor.constraint(equalTo: textField.bottomAnchor, constant: 30), -// rawTextField.leadingAnchor.constraint(equalTo: view.layoutMarginsGuide.leadingAnchor), -// rawTextField.trailingAnchor.constraint(equalTo: view.layoutMarginsGuide.trailingAnchor), + textFieldType2.topAnchor.constraint(equalTo: textField.bottomAnchor, constant: 30), + textFieldType2.leadingAnchor.constraint(equalTo: view.layoutMarginsGuide.leadingAnchor), + textFieldType2.trailingAnchor.constraint(equalTo: view.layoutMarginsGuide.trailingAnchor), ]) } diff --git a/project/Projects/Presentation/DSKit/Sources/CommonUI/IFType2.swift b/project/Projects/Presentation/DSKit/Sources/CommonUI/IFType2.swift new file mode 100644 index 00000000..330d7365 --- /dev/null +++ b/project/Projects/Presentation/DSKit/Sources/CommonUI/IFType2.swift @@ -0,0 +1,80 @@ +// +// IFType2.swift +// DSKit +// +// Created by choijunios on 7/10/24. +// + +import UIKit +import RxSwift + +public class IFType2: UIStackView { + + // Init parameters + public private(set) var titleLabelText: String + public private(set) var placeHolderText: String + public private(set) var keyboardType: UIKeyboardType + + // Observable + /// 버튼을 누를시 텍스트를 반환하는 event publisher입니다. + public let eventPublisher: PublishSubject = .init() + + // View + public private(set) lazy var titleLabel: ResizableUILabel = { + let label = ResizableUILabel() + label.text = titleLabelText + label.font = DSKitFontFamily.Pretendard.bold.font(size: 14) + label.textColor = DSKitAsset.Colors.gray500.color + return label + }() + + public private(set) lazy var textField = IdleOneLineInputField( + placeHolderText: placeHolderText, + keyboardType: keyboardType + ) + + public init( + titleLabelText: String, + placeHolderText: String, + keyboardType: UIKeyboardType = .default + ) { + self.placeHolderText = placeHolderText + self.titleLabelText = titleLabelText + self.keyboardType = keyboardType + super.init(frame: .zero) + + setStack() + setAutoLayout() + } + + public required init(coder: NSCoder) { fatalError() } + + func setStack() { + + self.alignment = .leading + self.axis = .vertical + self.spacing = 6.0 + } + + func setAutoLayout() { + + [ + titleLabel, + textField, + ].forEach { + self.addArrangedSubview($0) + } + + NSLayoutConstraint.activate([ + textField.leftAnchor.constraint(equalTo: self.leftAnchor), + textField.rightAnchor.constraint(equalTo: self.rightAnchor), + ]) + } + + public override func resignFirstResponder() -> Bool { + + _ = textField.resignFirstResponder() + + return super.resignFirstResponder() + } +} diff --git a/project/Projects/Presentation/Feature/Auth/Sources/View/Center/CenterLoginViewController.swift b/project/Projects/Presentation/Feature/Auth/Sources/View/Center/CenterLoginViewController.swift deleted file mode 100644 index c0d23664..00000000 --- a/project/Projects/Presentation/Feature/Auth/Sources/View/Center/CenterLoginViewController.swift +++ /dev/null @@ -1,68 +0,0 @@ -// -// CenterLoginViewController.swift -// AuthFeature -// -// Created by choijunios on 7/1/24. -// - -import UIKit -import DSKit -import PresentationCore - -class CenterLoginViewController: DisposableViewController { - - private lazy var loginButton = ButtonPrototype(text: "로그인(실행)") { [weak self] in - - // 화면 닫기 - self?.coordinator?.parent?.authFinished() - } - - private lazy var findPasswordButton = ButtonPrototype(text: "비밀번호 찾기") { [weak self] in - - self?.coordinator?.parent?.findPassword() - } - - var coordinator: CenterLoginCoordinator? - - override func viewDidLoad() { - super.viewDidLoad() - - view.backgroundColor = .white - view.layoutMargins = .init(top: 0, left: 20, bottom: 0, right: 20) - - let title = UILabel() - title.text = "로그인 화면: 아이디 패스워드 입력" - title.font = .boldSystemFont(ofSize: 24) - - [ - title, - loginButton, - findPasswordButton - ].forEach { - $0.translatesAutoresizingMaskIntoConstraints = false - view.addSubview($0) - } - - NSLayoutConstraint.activate([ - - // 비밀번호 찾기 버튼 - findPasswordButton.bottomAnchor.constraint(equalTo: view.safeAreaLayoutGuide.bottomAnchor), - findPasswordButton.leadingAnchor.constraint(equalTo: view.layoutMarginsGuide.leadingAnchor), - findPasswordButton.trailingAnchor.constraint(equalTo: view.layoutMarginsGuide.trailingAnchor), - - // 로그인 버튼 - loginButton.bottomAnchor.constraint(equalTo: findPasswordButton.topAnchor, constant: -10), - loginButton.leadingAnchor.constraint(equalTo: view.layoutMarginsGuide.leadingAnchor), - loginButton.trailingAnchor.constraint(equalTo: view.layoutMarginsGuide.trailingAnchor), - - // 타이틀 - title.centerXAnchor.constraint(equalTo: view.centerXAnchor), - title.centerYAnchor.constraint(equalTo: view.centerYAnchor), - ]) - - } - - func cleanUp() { - - } -} diff --git a/project/Projects/Presentation/Feature/Auth/Sources/View/Center/CenterFindPasswordController.swift b/project/Projects/Presentation/Feature/Auth/Sources/View/Center/Login/CenterFindPasswordController.swift similarity index 100% rename from project/Projects/Presentation/Feature/Auth/Sources/View/Center/CenterFindPasswordController.swift rename to project/Projects/Presentation/Feature/Auth/Sources/View/Center/Login/CenterFindPasswordController.swift diff --git a/project/Projects/Presentation/Feature/Auth/Sources/View/Center/Login/CenterLoginViewController.swift b/project/Projects/Presentation/Feature/Auth/Sources/View/Center/Login/CenterLoginViewController.swift new file mode 100644 index 00000000..e3801b2a --- /dev/null +++ b/project/Projects/Presentation/Feature/Auth/Sources/View/Center/Login/CenterLoginViewController.swift @@ -0,0 +1,65 @@ +// +// CenterLoginViewController.swift +// AuthFeature +// +// Created by choijunios on 7/1/24. +// + +import UIKit +import DSKit +import PresentationCore + +class CenterLoginViewController: DisposableViewController { + + // View + let idField: IdleOneLineInputField = { + + let field = IdleOneLineInputField( + placeHolderText: "아이디를 입력해주세요.", + isCompleteImageAvailable: false + ) + + return field + }() + let passwordField: IdleOneLineInputField = { + + let field = IdleOneLineInputField( + placeHolderText: "비밀번호를 입력해주세요.", + isCompleteImageAvailable: false + ) + + return field + }() + + + var coordinator: CenterLoginCoordinator? + + override func viewDidLoad() { + super.viewDidLoad() + + setAppearance() + setAutoLayout() + setObservable() + } + + + private func setAppearance() { + + view.layoutMargins = .init(top: 0, left: 20, bottom: 0, right: 20) + } + + private func setAutoLayout() { + + + } + + private func setObservable() { + + + } + + + func cleanUp() { + + } +} diff --git a/project/Projects/Presentation/Feature/Auth/Sources/View/Center/CenterSetNewPasswordController.swift b/project/Projects/Presentation/Feature/Auth/Sources/View/Center/Login/CenterSetNewPasswordController.swift similarity index 100% rename from project/Projects/Presentation/Feature/Auth/Sources/View/Center/CenterSetNewPasswordController.swift rename to project/Projects/Presentation/Feature/Auth/Sources/View/Center/Login/CenterSetNewPasswordController.swift From 91b6b0aae1efc3a6f8360a45ce5828a42b603121 Mon Sep 17 00:00:00 2001 From: J0onYEong Date: Wed, 10 Jul 2024 11:09:13 +0900 Subject: [PATCH 3/6] =?UTF-8?q?[IDLE-70]=20=EB=A1=9C=EA=B7=B8=EC=9D=B8=20U?= =?UTF-8?q?I=20=EA=B5=AC=ED=98=84=20=EC=99=84=EB=A3=8C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../DSKit/Sources/CommonUI/IFType2.swift | 13 +- .../ExampleApp/Sources/SceneDelegate.swift | 4 +- .../Login/CenterLoginViewController.swift | 164 ++++++++++++++++-- 3 files changed, 162 insertions(+), 19 deletions(-) diff --git a/project/Projects/Presentation/DSKit/Sources/CommonUI/IFType2.swift b/project/Projects/Presentation/DSKit/Sources/CommonUI/IFType2.swift index 330d7365..00719dab 100644 --- a/project/Projects/Presentation/DSKit/Sources/CommonUI/IFType2.swift +++ b/project/Projects/Presentation/DSKit/Sources/CommonUI/IFType2.swift @@ -14,15 +14,15 @@ public class IFType2: UIStackView { public private(set) var titleLabelText: String public private(set) var placeHolderText: String public private(set) var keyboardType: UIKeyboardType + public private(set) var isCompletionImageAvailable: Bool - // Observable - /// 버튼을 누를시 텍스트를 반환하는 event publisher입니다. - public let eventPublisher: PublishSubject = .init() + // Output + public var eventPublisher: Observable { self.textField.eventPublisher } // View public private(set) lazy var titleLabel: ResizableUILabel = { let label = ResizableUILabel() - label.text = titleLabelText + label.text = self.titleLabelText label.font = DSKitFontFamily.Pretendard.bold.font(size: 14) label.textColor = DSKitAsset.Colors.gray500.color return label @@ -30,17 +30,20 @@ public class IFType2: UIStackView { public private(set) lazy var textField = IdleOneLineInputField( placeHolderText: placeHolderText, - keyboardType: keyboardType + keyboardType: keyboardType, + isCompleteImageAvailable: self.isCompletionImageAvailable ) public init( titleLabelText: String, placeHolderText: String, + isCompletionImageAvailable: Bool = false, keyboardType: UIKeyboardType = .default ) { self.placeHolderText = placeHolderText self.titleLabelText = titleLabelText self.keyboardType = keyboardType + self.isCompletionImageAvailable = isCompletionImageAvailable super.init(frame: .zero) setStack() diff --git a/project/Projects/Presentation/Feature/Auth/ExampleApp/Sources/SceneDelegate.swift b/project/Projects/Presentation/Feature/Auth/ExampleApp/Sources/SceneDelegate.swift index 6d5e87c1..92c6a933 100644 --- a/project/Projects/Presentation/Feature/Auth/ExampleApp/Sources/SceneDelegate.swift +++ b/project/Projects/Presentation/Feature/Auth/ExampleApp/Sources/SceneDelegate.swift @@ -27,9 +27,7 @@ class SceneDelegate: UIResponder, UIWindowSceneDelegate { ) ) - window?.rootViewController = EnterNameViewController( - viewModel: viewModel - ) + window?.rootViewController = CenterLoginViewController() window?.makeKeyAndVisible() } } diff --git a/project/Projects/Presentation/Feature/Auth/Sources/View/Center/Login/CenterLoginViewController.swift b/project/Projects/Presentation/Feature/Auth/Sources/View/Center/Login/CenterLoginViewController.swift index e3801b2a..4ce9f033 100644 --- a/project/Projects/Presentation/Feature/Auth/Sources/View/Center/Login/CenterLoginViewController.swift +++ b/project/Projects/Presentation/Feature/Auth/Sources/View/Center/Login/CenterLoginViewController.swift @@ -9,57 +9,199 @@ import UIKit import DSKit import PresentationCore -class CenterLoginViewController: DisposableViewController { +public class CenterLoginViewController: DisposableViewController { + + var coordinator: CenterLoginCoordinator? // View - let idField: IdleOneLineInputField = { + private let navigationBar: NavigationBarType1 = { + + let bar = NavigationBarType1(navigationTitle: "로그인") + + return bar + }() + + private let idField: IFType2 = { - let field = IdleOneLineInputField( + let field = IFType2( + titleLabelText: "아이디", placeHolderText: "아이디를 입력해주세요.", - isCompleteImageAvailable: false + isCompletionImageAvailable: false ) return field }() - let passwordField: IdleOneLineInputField = { + private let passwordField: IFType2 = { - let field = IdleOneLineInputField( + let field = IFType2( + titleLabelText: "비밀번호", placeHolderText: "비밀번호를 입력해주세요.", - isCompleteImageAvailable: false + isCompletionImageAvailable: false ) return field }() + private let inputStack: UIStackView = { + + let stack = UIStackView() + + stack.axis = .vertical + stack.spacing = 30 + stack.alignment = .fill + + return stack + }() - var coordinator: CenterLoginCoordinator? + private let forgotPasswordButton: UIButton = { + + let button = UIButton() + + let text = "비밀번호가 기억나지 않나요?" + + let attributedText = NSAttributedString(string: text, attributes: [ + .font : DSKitFontFamily.Pretendard.medium.font(size: 14), + .foregroundColor : DSKitAsset.Colors.gray500.color.cgColor, + .underlineStyle : NSUnderlineStyle.single.rawValue + ]) + button.setAttributedTitle(attributedText, for: .normal) + + return button + }() - override func viewDidLoad() { - super.viewDidLoad() + private let ctaButton: CTAButtonType1 = { + + let button = CTAButtonType1(labelText: "로그인") + + return button + }() + + public init(coordinator: CenterLoginCoordinator? = nil) { + self.coordinator = coordinator + + super.init(nibName: nil, bundle: nil) setAppearance() setAutoLayout() setObservable() } + public required init?(coder: NSCoder) { fatalError() } + + public override func viewDidLoad() { + super.viewDidLoad() + } private func setAppearance() { + view.backgroundColor = .white view.layoutMargins = .init(top: 0, left: 20, bottom: 0, right: 20) } private func setAutoLayout() { + [ + idField, + passwordField, + ].forEach { + inputStack.addArrangedSubview($0) + } + + [ + navigationBar, + inputStack, + forgotPasswordButton, + ctaButton, + ].forEach { + $0.translatesAutoresizingMaskIntoConstraints = false + view.addSubview($0) + view.bringSubviewToFront($0) + } + + NSLayoutConstraint.activate([ + + navigationBar.topAnchor.constraint(equalTo: view.layoutMarginsGuide.topAnchor, constant: 20), + navigationBar.leadingAnchor.constraint(equalTo: view.layoutMarginsGuide.leadingAnchor), + navigationBar.trailingAnchor.constraint(equalTo: view.layoutMarginsGuide.trailingAnchor), + + inputStack.topAnchor.constraint(equalTo: navigationBar.bottomAnchor, constant: 125), + inputStack.leadingAnchor.constraint(equalTo: view.layoutMarginsGuide.leadingAnchor), + inputStack.trailingAnchor.constraint(equalTo: view.layoutMarginsGuide.trailingAnchor), + + forgotPasswordButton.centerXAnchor.constraint(equalTo: view.centerXAnchor), + forgotPasswordButton.bottomAnchor.constraint(equalTo: ctaButton.topAnchor, constant: -16), + + ctaButton.bottomAnchor.constraint(equalTo: view.safeAreaLayoutGuide.bottomAnchor), + ctaButton.leadingAnchor.constraint(equalTo: view.layoutMarginsGuide.leadingAnchor), + ctaButton.trailingAnchor.constraint(equalTo: view.layoutMarginsGuide.trailingAnchor), + ]) } private func setObservable() { + setKeyboardAvoidance() + } + + + public func cleanUp() { } +} + +extension CenterLoginViewController { - func cleanUp() { + private func setKeyboardAvoidance() { + // 키보드 어보이던스 설정 + NotificationCenter.default.addObserver(self, selector: #selector(onKeyboardAction(_:)), name: UIResponder.keyboardWillShowNotification, object: nil) + NotificationCenter.default.addObserver(self, selector: #selector(onKeyboardAction(_:)), name: UIResponder.keyboardWillHideNotification, object: nil) } + + @objc + private func onKeyboardAction(_ notification: Notification) { + + guard let userInfo = notification.userInfo else { return } + + let keyboardFrame = userInfo[UIResponder.keyboardFrameEndUserInfoKey] as! CGRect + let duration = userInfo[UIResponder.keyboardAnimationDurationUserInfoKey] as! TimeInterval + let curve = userInfo[UIResponder.keyboardAnimationCurveUserInfoKey] as! UInt + + UIView.animate(withDuration: duration, delay: 0, options: UIView.AnimationOptions(rawValue: curve), animations: { [weak self] in + + guard let self else { return } + + if notification.name == UIResponder.keyboardWillShowNotification { + + let movingView: UIView! + + if self.idField.textField.textField.isFirstResponder { + // id field가 선택된 경우 + movingView = self.idField + } else if self.passwordField.textField.textField.isFirstResponder { + // password field가 선택된 경우 + movingView = self.passwordField + } else { return } + + let idFieldFrame = movingView.convert(movingView.bounds, to: nil) + let maxY = idFieldFrame.origin.y + idFieldFrame.height + + if maxY > keyboardFrame.origin.y { + // 키보드가 field를 가리는 경우 + let diff = maxY - keyboardFrame.origin.y + let inset: CGFloat = 10 + + inputStack.transform = CGAffineTransform(translationX: 0, y: -(diff+inset)) + } + + } else { + + // 키보드가 사라자니는 경우 + self.inputStack.transform = .identity + } + }, completion: nil) + } + + } From e03791c116126e7439b9977bb5c0444809778d47 Mon Sep 17 00:00:00 2001 From: J0onYEong Date: Wed, 10 Jul 2024 11:54:26 +0900 Subject: [PATCH 4/6] =?UTF-8?q?[IDLE-70]=20UseCase=EA=B5=AC=EC=A1=B0=20?= =?UTF-8?q?=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 유저의 인풋을 검증하는 UseCase와 로그인/회원가입/회원탈퇴를 실행하는 로직을 두가지 UseCase로 분리하였습니다. --- .../Sources/DI/Assembly/AuthAssembly.swift | 6 ++- .../Sources/DI/Assembly/DomainAssembly.swift | 10 ++++- ...> DefaultAuthInputValidationUseCase.swift} | 20 ++-------- .../Auth/DefaultAuthUseCase.swift | 36 ++++++++++++++++++ ...swift => AuthInputValidationUseCase.swift} | 17 ++------- .../UseCaseInterface/Auth/AuthUseCase.swift | 32 ++++++++++++++++ .../ExampleApp/Sources/SceneDelegate.swift | 6 --- .../Center/Login/CenterLoginViewModel.swift | 38 +++++++++++++++++++ .../Register/CenterRegisterViewModel.swift | 28 ++++++++------ 9 files changed, 140 insertions(+), 53 deletions(-) rename project/Projects/Domain/ConcreteUseCase/Auth/{DefaultCenterRegisterUseCase.swift => DefaultAuthInputValidationUseCase.swift} (78%) create mode 100644 project/Projects/Domain/ConcreteUseCase/Auth/DefaultAuthUseCase.swift rename project/Projects/Domain/UseCaseInterface/Auth/{CenterRegisterUseCase.swift => AuthInputValidationUseCase.swift} (85%) create mode 100644 project/Projects/Domain/UseCaseInterface/Auth/AuthUseCase.swift create mode 100644 project/Projects/Presentation/Feature/Auth/Sources/ViewModel/Center/Login/CenterLoginViewModel.swift diff --git a/project/Projects/App/Sources/DI/Assembly/AuthAssembly.swift b/project/Projects/App/Sources/DI/Assembly/AuthAssembly.swift index 9233c9fa..52c92b63 100644 --- a/project/Projects/App/Sources/DI/Assembly/AuthAssembly.swift +++ b/project/Projects/App/Sources/DI/Assembly/AuthAssembly.swift @@ -13,10 +13,12 @@ import Swinject public struct AuthAssembly: Assembly { public func assemble(container: Container) { container.register(CenterRegisterViewModel.self) { resolver in - let useCase = resolver.resolve(CenterRegisterUseCase.self)! + let inputValidationUseCase = resolver.resolve(AuthInputValidationUseCase.self)! + let authUseCase = resolver.resolve(AuthUseCase.self)! return CenterRegisterViewModel( - useCase: useCase + inputValidationUseCase: inputValidationUseCase, + authUseCase: authUseCase ) } } diff --git a/project/Projects/App/Sources/DI/Assembly/DomainAssembly.swift b/project/Projects/App/Sources/DI/Assembly/DomainAssembly.swift index c38974d3..c0c48398 100644 --- a/project/Projects/App/Sources/DI/Assembly/DomainAssembly.swift +++ b/project/Projects/App/Sources/DI/Assembly/DomainAssembly.swift @@ -13,10 +13,16 @@ import Swinject public struct DomainAssembly: Assembly { public func assemble(container: Container) { - container.register(CenterRegisterUseCase.self) { resolver in + container.register(AuthInputValidationUseCase.self) { resolver in let repository = resolver.resolve(CenterRegisterRepository.self)! - return DefaultCenterRegisterUseCase(repository: repository) + return DefaultAuthInputValidationUseCase(repository: repository) + } + + container.register(AuthUseCase.self) { resolver in + let repository = resolver.resolve(CenterRegisterRepository.self)! + + return DefualtAuthseeCase(repository: repository) } } } diff --git a/project/Projects/Domain/ConcreteUseCase/Auth/DefaultCenterRegisterUseCase.swift b/project/Projects/Domain/ConcreteUseCase/Auth/DefaultAuthInputValidationUseCase.swift similarity index 78% rename from project/Projects/Domain/ConcreteUseCase/Auth/DefaultCenterRegisterUseCase.swift rename to project/Projects/Domain/ConcreteUseCase/Auth/DefaultAuthInputValidationUseCase.swift index cb05eb47..23dd4d82 100644 --- a/project/Projects/Domain/ConcreteUseCase/Auth/DefaultCenterRegisterUseCase.swift +++ b/project/Projects/Domain/ConcreteUseCase/Auth/DefaultAuthInputValidationUseCase.swift @@ -1,8 +1,8 @@ // -// DefaultCenterRegisterUseCase.swift +// DefaultAuthInputValidationUseCase.swift // ConcreteUseCase // -// Created by choijunios on 7/8/24. +// Created by choijunios on 7/10/24. // import Foundation @@ -11,7 +11,7 @@ import Entity import UseCaseInterface import RepositoryInterface -public class DefaultCenterRegisterUseCase: CenterRegisterUseCase { +public class DefaultAuthInputValidationUseCase: AuthInputValidationUseCase { let repository: CenterRegisterRepository @@ -73,18 +73,4 @@ public class DefaultCenterRegisterUseCase: CenterRegisterUseCase { return predicate.evaluate(with: password) } - - // MARK: 로그인 실행 - public func registerCenterAccount(registerState: CenterRegisterState) -> Observable { - - filteringDataLayer( - domainTask: repository.requestRegisterCenterAccount( - managerName: registerState.name!, - phoneNumber: registerState.phoneNumber!, - businessNumber: registerState.businessNumber!, - id: registerState.id!, - password: registerState.password! - ).asObservable() - ) - } } diff --git a/project/Projects/Domain/ConcreteUseCase/Auth/DefaultAuthUseCase.swift b/project/Projects/Domain/ConcreteUseCase/Auth/DefaultAuthUseCase.swift new file mode 100644 index 00000000..bebcdded --- /dev/null +++ b/project/Projects/Domain/ConcreteUseCase/Auth/DefaultAuthUseCase.swift @@ -0,0 +1,36 @@ +// +// DefaultAuthUseCase.swift +// ConcreteUseCase +// +// Created by choijunios on 7/10/24. +// + +import Foundation +import UseCaseInterface +import RepositoryInterface +import RxSwift +import Entity + +public class DefualtAuthseeCase: AuthUseCase { + + let repository: CenterRegisterRepository + + public init(repository: CenterRegisterRepository) { + self.repository = repository + } + + // MARK: 센터 회원가입 실행 + public func registerCenterAccount(registerState: Entity.CenterRegisterState) -> RxSwift.Observable { + filteringDataLayer( + domainTask: repository.requestRegisterCenterAccount( + managerName: registerState.name!, + phoneNumber: registerState.phoneNumber!, + businessNumber: registerState.businessNumber!, + id: registerState.id!, + password: registerState.password! + ).asObservable() + ) + } + + +} diff --git a/project/Projects/Domain/UseCaseInterface/Auth/CenterRegisterUseCase.swift b/project/Projects/Domain/UseCaseInterface/Auth/AuthInputValidationUseCase.swift similarity index 85% rename from project/Projects/Domain/UseCaseInterface/Auth/CenterRegisterUseCase.swift rename to project/Projects/Domain/UseCaseInterface/Auth/AuthInputValidationUseCase.swift index e6cf0c40..de869058 100644 --- a/project/Projects/Domain/UseCaseInterface/Auth/CenterRegisterUseCase.swift +++ b/project/Projects/Domain/UseCaseInterface/Auth/AuthInputValidationUseCase.swift @@ -1,8 +1,8 @@ // -// CenterRegisterUseCase.swift +// AuthInputValidationUseCase.swift // UseCaseInterface // -// Created by choijunios on 7/8/24. +// Created by choijunios on 7/10/24. // import Foundation @@ -18,9 +18,8 @@ import Entity /// - #6. 아이디 유효성 확인 /// - #7. 아이디 중복확인 /// - #8. 패스워드 유효성 확인 -/// - #9. 회원가입 -public protocol CenterRegisterUseCase: UseCaseBase { +public protocol AuthInputValidationUseCase: UseCaseBase { // #1. /// 전화번호 인증 요청 @@ -88,14 +87,4 @@ public protocol CenterRegisterUseCase: UseCaseBase { /// - returns: /// - Bool, true: 가능, flase: 불가능 func checkPasswordIsValid(password: String) -> Bool - - // #9. - /// 센터 로그인 실행 - /// - parameters: - /// - registerState: CenterRegisterState - /// - returns: - /// - Bool, true: 성공, flase: 실패 - func registerCenterAccount( - registerState: CenterRegisterState - ) -> Observable } diff --git a/project/Projects/Domain/UseCaseInterface/Auth/AuthUseCase.swift b/project/Projects/Domain/UseCaseInterface/Auth/AuthUseCase.swift new file mode 100644 index 00000000..6f8cb202 --- /dev/null +++ b/project/Projects/Domain/UseCaseInterface/Auth/AuthUseCase.swift @@ -0,0 +1,32 @@ +// +// AuthUseCase.swift +// UseCaseInterface +// +// Created by choijunios on 7/10/24. +// + +import Foundation +import RxSwift +import Entity + +/// 요구사항 +/// - #1. 센터 회원가입 실행 +/// - #2. 센터 로그인 실행 +/// - #3. 샌터 회원 탈퇴 +/// +/// - #4. 요양보호사 회원가입 실행 +/// - #5. 요양보호사 로그인 실행 +/// - #5. 요양보호사 회원탈퇴 실행 + +public protocol AuthUseCase: UseCaseBase { + + // #1. + /// 센터 회원가입 실행 + /// - parameters: + /// - registerState: CenterRegisterState + /// - returns: + /// - Bool, true: 성공, flase: 실패 + func registerCenterAccount( + registerState: CenterRegisterState + ) -> Observable +} diff --git a/project/Projects/Presentation/Feature/Auth/ExampleApp/Sources/SceneDelegate.swift b/project/Projects/Presentation/Feature/Auth/ExampleApp/Sources/SceneDelegate.swift index 92c6a933..8061de21 100644 --- a/project/Projects/Presentation/Feature/Auth/ExampleApp/Sources/SceneDelegate.swift +++ b/project/Projects/Presentation/Feature/Auth/ExampleApp/Sources/SceneDelegate.swift @@ -21,12 +21,6 @@ class SceneDelegate: UIResponder, UIWindowSceneDelegate { window = UIWindow(windowScene: windowScene) - let viewModel = CenterRegisterViewModel( - useCase: DefaultCenterRegisterUseCase( - repository: DefaultCenterRegisterRepository() - ) - ) - window?.rootViewController = CenterLoginViewController() window?.makeKeyAndVisible() } diff --git a/project/Projects/Presentation/Feature/Auth/Sources/ViewModel/Center/Login/CenterLoginViewModel.swift b/project/Projects/Presentation/Feature/Auth/Sources/ViewModel/Center/Login/CenterLoginViewModel.swift new file mode 100644 index 00000000..38a7d8b0 --- /dev/null +++ b/project/Projects/Presentation/Feature/Auth/Sources/ViewModel/Center/Login/CenterLoginViewModel.swift @@ -0,0 +1,38 @@ +// +// CenterLoginViewModel.swift +// AuthFeature +// +// Created by choijunios on 7/10/24. +// + +import RxSwift +import PresentationCore + +public class CenterLoginViewModel: ViewModelType { + + public var input: Input = .init() + + public func transform(input: Input) -> Output { + + let output = Output() + + return output + } +} + + +public extension CenterLoginViewModel { + + struct Input { + + public var editingId: Observable? + public var editingPassword: Observable? + + } + + struct Output { + + public var loginValidation: Observable? + + } +} diff --git a/project/Projects/Presentation/Feature/Auth/Sources/ViewModel/Center/Register/CenterRegisterViewModel.swift b/project/Projects/Presentation/Feature/Auth/Sources/ViewModel/Center/Register/CenterRegisterViewModel.swift index c4419943..972da5f4 100644 --- a/project/Projects/Presentation/Feature/Auth/Sources/ViewModel/Center/Register/CenterRegisterViewModel.swift +++ b/project/Projects/Presentation/Feature/Auth/Sources/ViewModel/Center/Register/CenterRegisterViewModel.swift @@ -14,7 +14,8 @@ import Entity public class CenterRegisterViewModel: ViewModelType { // UseCase - public let useCase: CenterRegisterUseCase + public let inputValidationUseCase: AuthInputValidationUseCase + public let authUseCase: AuthUseCase // Input은 모든 ViewController에서 공유한다. (다만, 각가의 ViewController의 Input프로토콜에 의해 제한된다.) public let input = Input() @@ -22,8 +23,11 @@ public class CenterRegisterViewModel: ViewModelType { private let stateObject = CenterRegisterState() - public init(useCase: CenterRegisterUseCase) { - self.useCase = useCase + public init( + inputValidationUseCase: AuthInputValidationUseCase, + authUseCase: AuthUseCase) { + self.inputValidationUseCase = inputValidationUseCase + self.authUseCase = authUseCase } deinit { @@ -64,7 +68,7 @@ public class CenterRegisterViewModel: ViewModelType { // 특정 조건 만족시 self.output.canSubmitPhoneNumber?.onNext( - self.useCase.checkPhoneNumberIsValid(phoneNumber: phoneNumber) + self.inputValidationUseCase.checkPhoneNumberIsValid(phoneNumber: phoneNumber) ) }) .disposed(by: disposeBag) @@ -111,7 +115,7 @@ public class CenterRegisterViewModel: ViewModelType { return #endif - self.useCase + self.inputValidationUseCase .requestPhoneNumberAuthentication(phoneNumber: formattedString) .subscribe { [weak self] result in switch result { @@ -154,7 +158,7 @@ public class CenterRegisterViewModel: ViewModelType { return #endif - self.useCase + self.inputValidationUseCase .authenticateAuthNumber(phoneNumber: phoneNumber, authNumber: authNumber) .subscribe { [weak self] result in switch result { @@ -185,7 +189,7 @@ public class CenterRegisterViewModel: ViewModelType { guard let self else { return } - let isValid = self.useCase.checkBusinessNumberIsValid(businessNumber: businessNumber) + let isValid = self.inputValidationUseCase.checkBusinessNumberIsValid(businessNumber: businessNumber) self.output.canSubmitBusinessNumber?.onNext(isValid) }) .disposed(by: disposeBag) @@ -211,7 +215,7 @@ public class CenterRegisterViewModel: ViewModelType { guard let self else { return } - self.useCase + self.inputValidationUseCase .requestBusinessNumberAuthentication(businessNumber: formattedString) .subscribe(onNext: { [weak self] result in @@ -245,7 +249,7 @@ public class CenterRegisterViewModel: ViewModelType { guard let self else { return } - let isValid = self.useCase.checkIdIsValid(id: id) + let isValid = self.inputValidationUseCase.checkIdIsValid(id: id) self.output.canCheckIdDuplication?.onNext(isValid) }) @@ -269,7 +273,7 @@ public class CenterRegisterViewModel: ViewModelType { guard let self else { return } - self.useCase + self.inputValidationUseCase .requestCheckingIdDuplication(id: id) .subscribe(onNext: { [weak self] result in @@ -301,7 +305,7 @@ public class CenterRegisterViewModel: ViewModelType { guard let self else { return } - let isValid = self.useCase.checkPasswordIsValid(password: pwd) + let isValid = self.inputValidationUseCase.checkPasswordIsValid(password: pwd) if !isValid { @@ -327,7 +331,7 @@ public class CenterRegisterViewModel: ViewModelType { guard let self else { return } - self.useCase + self.authUseCase .registerCenterAccount(registerState: self.stateObject) .subscribe(onNext: { [weak self] result in From b2a0b6a951033c519374935400b3240b133ad262 Mon Sep 17 00:00:00 2001 From: J0onYEong Date: Wed, 10 Jul 2024 14:27:06 +0900 Subject: [PATCH 5/6] =?UTF-8?q?[IDLE-70]=20=EB=A1=9C=EA=B7=B8=EC=9D=B8=20U?= =?UTF-8?q?I=EB=B0=8F=20=EA=B8=B0=EB=8A=A5=20=EA=B5=AC=ED=98=84=20?= =?UTF-8?q?=EC=99=84=EB=A3=8C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Sources/DI/Assembly/AuthAssembly.swift | 9 ++ .../Sources/DI/Assembly/DataAssembly.swift | 11 +- .../Sources/DI/Assembly/DomainAssembly.swift | 4 +- .../Auth/Center/CenterAuthCoorinator.swift | 1 + ...efaultAuthInputValidationRepository.swift} | 35 +----- .../Auth/DefaultAuthRepository.swift | 63 ++++++++++ .../NetworkDataSource/API/Auth/AuthAPI.swift | 10 ++ .../DefaultAuthInputValidationUseCase.swift | 4 +- .../Auth/DefaultAuthUseCase.swift | 11 +- .../Auth/Login/AuthRepository.swift | 15 +++ .../Register/CenterRegisterRepository.swift | 5 +- .../UseCaseInterface/Auth/AuthUseCase.swift | 12 ++ .../DSKit/Sources/CommonUI/IFType1.swift | 15 +-- .../DSKit/Sources/CommonUI/IFType2.swift | 14 ++- .../TextField/IdleOneLineInputField.swift | 12 +- .../Center/CenterLoginCoordinator.swift | 16 ++- .../Login/CenterLoginViewController.swift | 117 ++++++++++++++++-- .../AuthBusinessOwnerViewController.swift | 4 +- .../SetIdPasswordViewController.swift | 14 +-- .../ValidatePhoneNumberViewController.swift | 22 ++-- .../Center/Login/CenterLoginViewModel.swift | 48 ++++++- .../Register/CenterRegisterViewModel.swift | 18 +-- 22 files changed, 353 insertions(+), 107 deletions(-) rename project/Projects/Data/ConcreteRepository/Auth/{DefaultCenterRegisterRepository.swift => DefaultAuthInputValidationRepository.swift} (71%) create mode 100644 project/Projects/Data/ConcreteRepository/Auth/DefaultAuthRepository.swift create mode 100644 project/Projects/Domain/RepositoryInterface/Auth/Login/AuthRepository.swift diff --git a/project/Projects/App/Sources/DI/Assembly/AuthAssembly.swift b/project/Projects/App/Sources/DI/Assembly/AuthAssembly.swift index 52c92b63..a2d1107c 100644 --- a/project/Projects/App/Sources/DI/Assembly/AuthAssembly.swift +++ b/project/Projects/App/Sources/DI/Assembly/AuthAssembly.swift @@ -21,5 +21,14 @@ public struct AuthAssembly: Assembly { authUseCase: authUseCase ) } + + container.register(CenterLoginViewModel.self) { resolver in + + let authUseCase = resolver.resolve(AuthUseCase.self)! + + return CenterLoginViewModel( + authUseCase: authUseCase + ) + } } } diff --git a/project/Projects/App/Sources/DI/Assembly/DataAssembly.swift b/project/Projects/App/Sources/DI/Assembly/DataAssembly.swift index 34f62842..6feccd93 100644 --- a/project/Projects/App/Sources/DI/Assembly/DataAssembly.swift +++ b/project/Projects/App/Sources/DI/Assembly/DataAssembly.swift @@ -13,9 +13,14 @@ import Swinject public struct DataAssembly: Assembly { public func assemble(container: Container) { - // MARK: 센터 회원가입 레포지토리 - container.register(CenterRegisterRepository.self) { _ in - return DefaultCenterRegisterRepository() + // MARK: 회원가입 입력 검증 레포지토리 + container.register(AuthInputValidationRepository.self) { _ in + return DefaultAuthInputValidationRepository() + } + + // MARK: 로그인/회원가입 레포지토리 + container.register(AuthRepository.self) { _ in + return DefaultAuthRepository() } } } diff --git a/project/Projects/App/Sources/DI/Assembly/DomainAssembly.swift b/project/Projects/App/Sources/DI/Assembly/DomainAssembly.swift index c0c48398..782de428 100644 --- a/project/Projects/App/Sources/DI/Assembly/DomainAssembly.swift +++ b/project/Projects/App/Sources/DI/Assembly/DomainAssembly.swift @@ -14,13 +14,13 @@ import Swinject public struct DomainAssembly: Assembly { public func assemble(container: Container) { container.register(AuthInputValidationUseCase.self) { resolver in - let repository = resolver.resolve(CenterRegisterRepository.self)! + let repository = resolver.resolve(AuthInputValidationRepository.self)! return DefaultAuthInputValidationUseCase(repository: repository) } container.register(AuthUseCase.self) { resolver in - let repository = resolver.resolve(CenterRegisterRepository.self)! + let repository = resolver.resolve(AuthRepository.self)! return DefualtAuthseeCase(repository: repository) } diff --git a/project/Projects/App/Sources/RootCoordinator/Auth/Center/CenterAuthCoorinator.swift b/project/Projects/App/Sources/RootCoordinator/Auth/Center/CenterAuthCoorinator.swift index 9e760e33..33eca367 100644 --- a/project/Projects/App/Sources/RootCoordinator/Auth/Center/CenterAuthCoorinator.swift +++ b/project/Projects/App/Sources/RootCoordinator/Auth/Center/CenterAuthCoorinator.swift @@ -45,6 +45,7 @@ extension CenterAuthCoorinator: CenterAuthCoordinatable { func login() { let coordinator = CenterLoginCoordinator( + viewModel: injector.resolve(CenterLoginViewModel.self), navigationController: navigationController ) diff --git a/project/Projects/Data/ConcreteRepository/Auth/DefaultCenterRegisterRepository.swift b/project/Projects/Data/ConcreteRepository/Auth/DefaultAuthInputValidationRepository.swift similarity index 71% rename from project/Projects/Data/ConcreteRepository/Auth/DefaultCenterRegisterRepository.swift rename to project/Projects/Data/ConcreteRepository/Auth/DefaultAuthInputValidationRepository.swift index 89d4c46e..a8bbb7ab 100644 --- a/project/Projects/Data/ConcreteRepository/Auth/DefaultCenterRegisterRepository.swift +++ b/project/Projects/Data/ConcreteRepository/Auth/DefaultAuthInputValidationRepository.swift @@ -1,8 +1,8 @@ // -// DefaultCenterRegisterRepository.swift +// DefaultAuthInputValidationRepository.swift // ConcreteRepository // -// Created by choijunios on 7/8/24. +// Created by choijunios on 7/10/24. // import Foundation @@ -11,12 +11,10 @@ import RepositoryInterface import NetworkDataSource import Entity -public class DefaultCenterRegisterRepository: CenterRegisterRepository { +public class DefaultAuthInputValidationRepository: AuthInputValidationRepository { let networkService = CenterRegisterService() - let disposeBag = DisposeBag() - public init() { } public func requestPhoneNumberAuthentication(phoneNumber: String) -> RxSwift.Single { @@ -88,32 +86,5 @@ public class DefaultCenterRegisterRepository: CenterRegisterRepository { } } } - - public func requestRegisterCenterAccount(managerName: String, phoneNumber: String, businessNumber: String, id: String, password: String) -> RxSwift.Single { - - let dto = CenterRegistrationDTO( - identifier: id, - password: password, - phoneNumber: phoneNumber, - managerName: managerName, - centerBusinessRegistrationNumber: businessNumber - ) - - let data = (try? JSONEncoder().encode(dto)) ?? Data() - - return networkService.request(api: .registerCenterAccount(data: data)) - .catch { [weak self] in .error(self?.filterNetworkConnection($0) ?? $0) } - .map { [weak self] response in - - guard let self = self else { return BoolResult.failure(.unknownError) } - - switch response.statusCode { - case 201: - return .success(true) - default: - return .failure(self.decodeError(of: CenterRegisterError.self, data: response.data)) - } - } - } } diff --git a/project/Projects/Data/ConcreteRepository/Auth/DefaultAuthRepository.swift b/project/Projects/Data/ConcreteRepository/Auth/DefaultAuthRepository.swift new file mode 100644 index 00000000..11b9cd25 --- /dev/null +++ b/project/Projects/Data/ConcreteRepository/Auth/DefaultAuthRepository.swift @@ -0,0 +1,63 @@ +// +// DefaultAuthRepository.swift +// ConcreteRepository +// +// Created by choijunios on 7/10/24. +// + +import Foundation +import RxSwift +import RepositoryInterface +import NetworkDataSource +import Entity + +public class DefaultAuthRepository: AuthRepository { + + let networkService = CenterRegisterService() + + public init() { } + + public func requestRegisterCenterAccount(managerName: String, phoneNumber: String, businessNumber: String, id: String, password: String) -> RxSwift.Single { + + let dto = CenterRegistrationDTO( + identifier: id, + password: password, + phoneNumber: phoneNumber, + managerName: managerName, + centerBusinessRegistrationNumber: businessNumber + ) + + let data = (try? JSONEncoder().encode(dto)) ?? Data() + + return networkService.request(api: .registerCenterAccount(data: data)) + .catch { [weak self] in .error(self?.filterNetworkConnection($0) ?? $0) } + .map { [weak self] response in + + guard let self = self else { return BoolResult.failure(.unknownError) } + + switch response.statusCode { + case 201: + return .success(true) + default: + return .failure(self.decodeError(of: CenterRegisterError.self, data: response.data)) + } + } + } + + public func requestCenterLogin(id: String, password: String) -> RxSwift.Single { + + return networkService.request(api: .centerLogin(id: id, password: password)) + .catch { [weak self] in .error(self?.filterNetworkConnection($0) ?? $0) } + .map { [weak self] response in + + guard let self = self else { return BoolResult.failure(.unknownError) } + + switch response.statusCode { + case 200: + return .success(true) + default: + return .failure(self.decodeError(of: CenterRegisterError.self, data: response.data)) + } + } + } +} diff --git a/project/Projects/Data/NetworkDataSource/API/Auth/AuthAPI.swift b/project/Projects/Data/NetworkDataSource/API/Auth/AuthAPI.swift index 52446c4d..6dd35512 100644 --- a/project/Projects/Data/NetworkDataSource/API/Auth/AuthAPI.swift +++ b/project/Projects/Data/NetworkDataSource/API/Auth/AuthAPI.swift @@ -19,6 +19,7 @@ public enum AuthAPI { case authenticateBusinessNumber(businessNumber: String) case checkIdDuplication(id: String) case registerCenterAccount(data: Data) + case centerLogin(id: String, password: String) } extension AuthAPI: BaseAPI { @@ -38,6 +39,8 @@ extension AuthAPI: BaseAPI { return .get case .registerCenterAccount: return .post + case .centerLogin: + return .post } } @@ -53,6 +56,8 @@ extension AuthAPI: BaseAPI { "center/validation/\(id)" case .registerCenterAccount: "center/join" + case .centerLogin: + "center/login" } } @@ -64,6 +69,9 @@ extension AuthAPI: BaseAPI { case .checkAuthNumber(let phoneNumber, let authNumber): params["phoneNumber"] = phoneNumber params["verificationNumber"] = authNumber + case .centerLogin(let id, let password): + params["identifier"] = id + params["password"] = password default: break } @@ -85,6 +93,8 @@ extension AuthAPI: BaseAPI { return .requestParameters(parameters: bodyParameters ?? [:], encoding: parameterEncoding) case .registerCenterAccount(let data): return .requestData(data) + case .centerLogin: + return .requestParameters(parameters: bodyParameters ?? [:], encoding: parameterEncoding) default: return .requestPlain } diff --git a/project/Projects/Domain/ConcreteUseCase/Auth/DefaultAuthInputValidationUseCase.swift b/project/Projects/Domain/ConcreteUseCase/Auth/DefaultAuthInputValidationUseCase.swift index 23dd4d82..3bfc1d63 100644 --- a/project/Projects/Domain/ConcreteUseCase/Auth/DefaultAuthInputValidationUseCase.swift +++ b/project/Projects/Domain/ConcreteUseCase/Auth/DefaultAuthInputValidationUseCase.swift @@ -13,9 +13,9 @@ import RepositoryInterface public class DefaultAuthInputValidationUseCase: AuthInputValidationUseCase { - let repository: CenterRegisterRepository + let repository: AuthInputValidationRepository - public init(repository: CenterRegisterRepository) { + public init(repository: AuthInputValidationRepository) { self.repository = repository } diff --git a/project/Projects/Domain/ConcreteUseCase/Auth/DefaultAuthUseCase.swift b/project/Projects/Domain/ConcreteUseCase/Auth/DefaultAuthUseCase.swift index bebcdded..37a5e3ec 100644 --- a/project/Projects/Domain/ConcreteUseCase/Auth/DefaultAuthUseCase.swift +++ b/project/Projects/Domain/ConcreteUseCase/Auth/DefaultAuthUseCase.swift @@ -13,9 +13,9 @@ import Entity public class DefualtAuthseeCase: AuthUseCase { - let repository: CenterRegisterRepository + let repository: AuthRepository - public init(repository: CenterRegisterRepository) { + public init(repository: AuthRepository) { self.repository = repository } @@ -32,5 +32,10 @@ public class DefualtAuthseeCase: AuthUseCase { ) } - + // MARK: 센터 로그인 실행 + public func loginCenterAccount(id: String, password: String) -> RxSwift.Observable { + filteringDataLayer( + domainTask: repository.requestCenterLogin(id: id, password: password).asObservable() + ) + } } diff --git a/project/Projects/Domain/RepositoryInterface/Auth/Login/AuthRepository.swift b/project/Projects/Domain/RepositoryInterface/Auth/Login/AuthRepository.swift new file mode 100644 index 00000000..648c34a4 --- /dev/null +++ b/project/Projects/Domain/RepositoryInterface/Auth/Login/AuthRepository.swift @@ -0,0 +1,15 @@ +// +// AuthRepository.swift +// RepositoryInterface +// +// Created by choijunios on 7/10/24. +// + +import RxSwift +import Entity + +public protocol AuthRepository: RepositoryBase { + + func requestRegisterCenterAccount(managerName: String, phoneNumber: String, businessNumber: String, id: String, password: String) -> Single + func requestCenterLogin(id: String, password: String) -> Single +} diff --git a/project/Projects/Domain/RepositoryInterface/Auth/Register/CenterRegisterRepository.swift b/project/Projects/Domain/RepositoryInterface/Auth/Register/CenterRegisterRepository.swift index 4652b611..1cf27095 100644 --- a/project/Projects/Domain/RepositoryInterface/Auth/Register/CenterRegisterRepository.swift +++ b/project/Projects/Domain/RepositoryInterface/Auth/Register/CenterRegisterRepository.swift @@ -9,13 +9,10 @@ import Foundation import RxSwift import Entity -public protocol CenterRegisterRepository: RepositoryBase { +public protocol AuthInputValidationRepository: RepositoryBase { func requestPhoneNumberAuthentication(phoneNumber: String) -> Single func authenticateAuthNumber(phoneNumber: String, authNumber: String) -> Single - func requestBusinessNumberAuthentication(businessNumber: String) -> Single - func requestCheckingIdDuplication(id: String) -> Single - func requestRegisterCenterAccount(managerName: String, phoneNumber: String, businessNumber: String, id: String, password: String) -> Single } diff --git a/project/Projects/Domain/UseCaseInterface/Auth/AuthUseCase.swift b/project/Projects/Domain/UseCaseInterface/Auth/AuthUseCase.swift index 6f8cb202..0a924be0 100644 --- a/project/Projects/Domain/UseCaseInterface/Auth/AuthUseCase.swift +++ b/project/Projects/Domain/UseCaseInterface/Auth/AuthUseCase.swift @@ -29,4 +29,16 @@ public protocol AuthUseCase: UseCaseBase { func registerCenterAccount( registerState: CenterRegisterState ) -> Observable + + // #2. + /// 센터 로그인 실행 + /// - parameters: + /// - id: String + /// - password: String + /// - returns: + /// - Bool, true: 성공, flase: 실패 + func loginCenterAccount( + id: String, + password: String + ) -> Observable } diff --git a/project/Projects/Presentation/DSKit/Sources/CommonUI/IFType1.swift b/project/Projects/Presentation/DSKit/Sources/CommonUI/IFType1.swift index c94f19d9..3455c333 100644 --- a/project/Projects/Presentation/DSKit/Sources/CommonUI/IFType1.swift +++ b/project/Projects/Presentation/DSKit/Sources/CommonUI/IFType1.swift @@ -25,10 +25,11 @@ public class IFType1: UIStackView { public let eventPublisher: PublishSubject = .init() // View - public private(set) lazy var textField = IdleOneLineInputField( + public private(set) lazy var idleTextField = IdleOneLineInputField( placeHolderText: placeHolderText, keyboardType: keyboardType ) + public var uITextField: UITextField { idleTextField.textField } public private(set) lazy var button: TextButtonType1 = { let btn = TextButtonType1(labelText: submitButtonText) @@ -68,17 +69,17 @@ public class IFType1: UIStackView { func setAutoLayout() { [ - textField, + idleTextField, button, ].forEach { $0.translatesAutoresizingMaskIntoConstraints = false self.addArrangedSubview($0) } - textField.setContentHuggingPriority(.defaultLow, for: .horizontal) + idleTextField.setContentHuggingPriority(.defaultLow, for: .horizontal) button.setContentHuggingPriority(.defaultHigh, for: .horizontal) - textField.setContentCompressionResistancePriority(.defaultLow, for: .horizontal) + idleTextField.setContentCompressionResistancePriority(.defaultLow, for: .horizontal) button.setContentCompressionResistancePriority(.defaultHigh, for: .horizontal) NSLayoutConstraint.activate([ @@ -97,17 +98,17 @@ public class IFType1: UIStackView { // 문자열 전송 self?.eventPublisher .onNext( - self?.textField.textField.text ?? "" + self?.idleTextField.textField.text ?? "" ) - _ = self?.textField.resignFirstResponder() + _ = self?.idleTextField.resignFirstResponder() } .disposed(by: disposeBag) } public override func resignFirstResponder() -> Bool { - _ = textField.resignFirstResponder() + _ = idleTextField.resignFirstResponder() return super.resignFirstResponder() } diff --git a/project/Projects/Presentation/DSKit/Sources/CommonUI/IFType2.swift b/project/Projects/Presentation/DSKit/Sources/CommonUI/IFType2.swift index 00719dab..3946b270 100644 --- a/project/Projects/Presentation/DSKit/Sources/CommonUI/IFType2.swift +++ b/project/Projects/Presentation/DSKit/Sources/CommonUI/IFType2.swift @@ -17,7 +17,7 @@ public class IFType2: UIStackView { public private(set) var isCompletionImageAvailable: Bool // Output - public var eventPublisher: Observable { self.textField.eventPublisher } + public var eventPublisher: Observable { self.idleTextField.eventPublisher } // View public private(set) lazy var titleLabel: ResizableUILabel = { @@ -28,12 +28,14 @@ public class IFType2: UIStackView { return label }() - public private(set) lazy var textField = IdleOneLineInputField( + public private(set) lazy var idleTextField = IdleOneLineInputField( placeHolderText: placeHolderText, keyboardType: keyboardType, isCompleteImageAvailable: self.isCompletionImageAvailable ) + public var uITextField: UITextField { idleTextField.textField } + public init( titleLabelText: String, placeHolderText: String, @@ -63,20 +65,20 @@ public class IFType2: UIStackView { [ titleLabel, - textField, + idleTextField, ].forEach { self.addArrangedSubview($0) } NSLayoutConstraint.activate([ - textField.leftAnchor.constraint(equalTo: self.leftAnchor), - textField.rightAnchor.constraint(equalTo: self.rightAnchor), + idleTextField.leftAnchor.constraint(equalTo: self.leftAnchor), + idleTextField.rightAnchor.constraint(equalTo: self.rightAnchor), ]) } public override func resignFirstResponder() -> Bool { - _ = textField.resignFirstResponder() + _ = idleTextField.resignFirstResponder() return super.resignFirstResponder() } diff --git a/project/Projects/Presentation/DSKit/Sources/Component/TextField/IdleOneLineInputField.swift b/project/Projects/Presentation/DSKit/Sources/Component/TextField/IdleOneLineInputField.swift index 5ee9bf4d..520c54ee 100644 --- a/project/Projects/Presentation/DSKit/Sources/Component/TextField/IdleOneLineInputField.swift +++ b/project/Projects/Presentation/DSKit/Sources/Component/TextField/IdleOneLineInputField.swift @@ -23,6 +23,7 @@ public class IdleOneLineInputField: UIView { // Init parameters var state: BehaviorSubject = .init(value: .editing) let initialText: String + public let normalBorderColor: UIColor public var eventPublisher: Observable { textField.rx.text.compactMap { $0 }} @@ -117,10 +118,12 @@ public class IdleOneLineInputField: UIView { initialText: String = "", placeHolderText: String, keyboardType: UIKeyboardType = .default, - isCompleteImageAvailable: Bool = true + isCompleteImageAvailable: Bool = true, + borderColor: UIColor = DSKitAsset.Colors.gray100.color ) { self.initialText = initialText self.isCompleteImageAvailable = isCompleteImageAvailable + self.normalBorderColor = borderColor super.init(frame: .zero) @@ -129,7 +132,7 @@ public class IdleOneLineInputField: UIView { // Border self.layer.cornerRadius = 6 self.layer.borderWidth = 1 - self.layer.borderColor = DSKitAsset.Colors.gray100.color.cgColor + self.layer.borderColor = borderColor.cgColor // Initial setting textField.placeholder = placeHolderText @@ -250,6 +253,11 @@ public extension IdleOneLineInputField { self.layer.borderColor = DSKitAsset.Colors.gray100.color.cgColor } + + @MainActor + func onCustomState(changingClosure: (IdleOneLineInputField) -> Void) { + changingClosure(self) + } } extension IdleOneLineInputField: UITextFieldDelegate { diff --git a/project/Projects/Presentation/Feature/Auth/Sources/Coordinator/Center/CenterLoginCoordinator.swift b/project/Projects/Presentation/Feature/Auth/Sources/Coordinator/Center/CenterLoginCoordinator.swift index e0664547..198a0406 100644 --- a/project/Projects/Presentation/Feature/Auth/Sources/Coordinator/Center/CenterLoginCoordinator.swift +++ b/project/Projects/Presentation/Feature/Auth/Sources/Coordinator/Center/CenterLoginCoordinator.swift @@ -16,19 +16,27 @@ public class CenterLoginCoordinator: ChildCoordinator { public var parent: CenterAuthCoordinatable? - public init(navigationController: UINavigationController) { + private var viewModel: CenterLoginViewModel? + + public init( + viewModel: CenterLoginViewModel, + navigationController: UINavigationController + ) { self.navigationController = navigationController + self.viewModel = viewModel } deinit { printIfDebug("deinit \(Self.self)") } public func start() { - let viewController = CenterLoginViewController() - viewController.coordinator = self + let viewController = CenterLoginViewController( + coordinator: self, + viewModel: self.viewModel! + ) + self.viewModel = nil viewControllerRef = viewController - navigationController.pushViewController(viewController, animated: true) } diff --git a/project/Projects/Presentation/Feature/Auth/Sources/View/Center/Login/CenterLoginViewController.swift b/project/Projects/Presentation/Feature/Auth/Sources/View/Center/Login/CenterLoginViewController.swift index 4ce9f033..d2cf074c 100644 --- a/project/Projects/Presentation/Feature/Auth/Sources/View/Center/Login/CenterLoginViewController.swift +++ b/project/Projects/Presentation/Feature/Auth/Sources/View/Center/Login/CenterLoginViewController.swift @@ -6,11 +6,14 @@ // import UIKit +import RxSwift import DSKit import PresentationCore public class CenterLoginViewController: DisposableViewController { + let viewModel: CenterLoginViewModel + var coordinator: CenterLoginCoordinator? // View @@ -53,6 +56,18 @@ public class CenterLoginViewController: DisposableViewController { return stack }() + private let loginFailedText: ResizableUILabel = { + + let label = ResizableUILabel() + + label.text = "비밀번호를 다시 확인해 주세요" + label.font = DSKitFontFamily.Pretendard.medium.font(size: 12) + label.textColor = .red + label.isHidden = true + + return label + }() + private let forgotPasswordButton: UIButton = { let button = UIButton() @@ -76,8 +91,11 @@ public class CenterLoginViewController: DisposableViewController { return button }() - public init(coordinator: CenterLoginCoordinator? = nil) { + private let disposeBag = DisposeBag() + + public init(coordinator: CenterLoginCoordinator? = nil, viewModel: CenterLoginViewModel) { self.coordinator = coordinator + self.viewModel = viewModel super.init(nibName: nil, bundle: nil) @@ -112,6 +130,7 @@ public class CenterLoginViewController: DisposableViewController { inputStack, forgotPasswordButton, ctaButton, + loginFailedText, ].forEach { $0.translatesAutoresizingMaskIntoConstraints = false view.addSubview($0) @@ -121,13 +140,16 @@ public class CenterLoginViewController: DisposableViewController { NSLayoutConstraint.activate([ navigationBar.topAnchor.constraint(equalTo: view.layoutMarginsGuide.topAnchor, constant: 20), - navigationBar.leadingAnchor.constraint(equalTo: view.layoutMarginsGuide.leadingAnchor), - navigationBar.trailingAnchor.constraint(equalTo: view.layoutMarginsGuide.trailingAnchor), + navigationBar.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 12), + navigationBar.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: 12), inputStack.topAnchor.constraint(equalTo: navigationBar.bottomAnchor, constant: 125), inputStack.leadingAnchor.constraint(equalTo: view.layoutMarginsGuide.leadingAnchor), inputStack.trailingAnchor.constraint(equalTo: view.layoutMarginsGuide.trailingAnchor), + loginFailedText.leadingAnchor.constraint(equalTo: view.layoutMarginsGuide.leadingAnchor), + loginFailedText.topAnchor.constraint(equalTo: inputStack.bottomAnchor, constant: 4), + forgotPasswordButton.centerXAnchor.constraint(equalTo: view.centerXAnchor), forgotPasswordButton.bottomAnchor.constraint(equalTo: ctaButton.topAnchor, constant: -16), @@ -141,6 +163,89 @@ public class CenterLoginViewController: DisposableViewController { private func setObservable() { setKeyboardAvoidance() + + // MARK: Input + var input = viewModel.input + + let inputPublisher = Observable + .combineLatest( + idField.eventPublisher, + passwordField.eventPublisher + ) + + // 로그인 버튼 활성화 옵저버블 + inputPublisher + .map({ (id, pw) in + + return !(id.isEmpty || pw.isEmpty) + }) + .subscribe(onNext: { [weak self] canLogin in + + self?.ctaButton.setEnabled(canLogin) + }) + .disposed(by: disposeBag) + + // 로그인 버튼 눌렀을 때 + let loginPublisher = ctaButton.eventPublisher + .map { [weak self] _ in + + let id = self?.idField.uITextField.text ?? "" + let pw = self?.passwordField.uITextField.text ?? "" + + return (id: id, pw: pw) + } + + input.loginButtonPressed = loginPublisher.asObservable() + + // MARK: Output + let output = viewModel.transform(input: input) + + // 로그인 시도 결과 전송 + output + .loginValidation? + .subscribe(onNext: { [weak self] isSuccess in + + if isSuccess { + self?.onLoginSucceed() + } else { + self?.onLoginFailed() + } + + }) + .disposed(by: disposeBag) + + + // MARK: ViewController only + navigationBar + .eventPublisher + .subscribe { [weak self] _ in + + self?.coordinator?.coordinatorDidFinish() + } + .disposed(by: disposeBag) + } + + private func onLoginFailed() { + + loginFailedText.isHidden = false + passwordField + .idleTextField + .onCustomState { textField in + textField.layer.borderColor = UIColor.red.cgColor + } + + // 메인화면으로 이동 + coordinator?.parent?.authFinished() + } + + private func onLoginSucceed() { + + loginFailedText.isHidden = true + passwordField + .idleTextField + .onCustomState { textField in + textField.layer.borderColor = textField.normalBorderColor.cgColor + } } @@ -176,10 +281,10 @@ extension CenterLoginViewController { let movingView: UIView! - if self.idField.textField.textField.isFirstResponder { + if self.idField.uITextField.isFirstResponder { // id field가 선택된 경우 movingView = self.idField - } else if self.passwordField.textField.textField.isFirstResponder { + } else if self.passwordField.uITextField.isFirstResponder { // password field가 선택된 경우 movingView = self.passwordField } else { return } @@ -202,6 +307,4 @@ extension CenterLoginViewController { } }, completion: nil) } - - } diff --git a/project/Projects/Presentation/Feature/Auth/Sources/View/Center/Register/AuthBusinessOwnerViewController.swift b/project/Projects/Presentation/Feature/Auth/Sources/View/Center/Register/AuthBusinessOwnerViewController.swift index a2c66d93..b751d798 100644 --- a/project/Projects/Presentation/Feature/Auth/Sources/View/Center/Register/AuthBusinessOwnerViewController.swift +++ b/project/Projects/Presentation/Feature/Auth/Sources/View/Center/Register/AuthBusinessOwnerViewController.swift @@ -49,7 +49,7 @@ where T.Input: AuthBusinessOwnerInputable & CTAButtonEnableInputable, T.Output: keyboardType: .numberPad ) - textField.textField.isCompleteImageAvailable = false + textField.idleTextField.isCompleteImageAvailable = false return textField }() @@ -163,7 +163,7 @@ where T.Input: AuthBusinessOwnerInputable & CTAButtonEnableInputable, T.Output: var input = viewModel.input // 현재 입력중인 정보 전송 - input.editingBusinessNumber = businessNumberField.textField.eventPublisher.asObservable() + input.editingBusinessNumber = businessNumberField.idleTextField.eventPublisher.asObservable() // 인증, 확인 버튼이 눌린 경우 input.requestBusinessNumberValidation = businessNumberField.eventPublisher.asObservable() diff --git a/project/Projects/Presentation/Feature/Auth/Sources/View/Center/Register/SetIdPasswordViewController.swift b/project/Projects/Presentation/Feature/Auth/Sources/View/Center/Register/SetIdPasswordViewController.swift index 5a4cf657..00c657d9 100644 --- a/project/Projects/Presentation/Feature/Auth/Sources/View/Center/Register/SetIdPasswordViewController.swift +++ b/project/Projects/Presentation/Feature/Auth/Sources/View/Center/Register/SetIdPasswordViewController.swift @@ -61,7 +61,7 @@ where T.Input: SetIdPasswordInputable & CTAButtonEnableInputable, T.Output: SetI submitButtonText: "중복 확인" ) - textField.textField.isCompleteImageAvailable = false + textField.idleTextField.isCompleteImageAvailable = false return textField }() @@ -237,7 +237,7 @@ where T.Input: SetIdPasswordInputable & CTAButtonEnableInputable, T.Output: SetI var input = viewModel.input // 현재 입력중인 정보 전송 - input.editingId = idField.textField.eventPublisher.asObservable() + input.editingId = idField.idleTextField.eventPublisher.asObservable() input.editingPassword = passwordField.eventPublisher.asObservable() input.editingCheckPassword = checkPasswordField.eventPublisher.asObservable() input.ctaButtonClicked = ctaButton.eventPublisher.map({ _ in CTAButtonAction.complete }).asObservable() @@ -247,7 +247,7 @@ where T.Input: SetIdPasswordInputable & CTAButtonEnableInputable, T.Output: SetI .map { [weak self] in // 증복검사 실행시 아이디 입력 필드 비활성화 - self?.idField.textField.setEnabled(false) + self?.idField.idleTextField.setEnabled(false) self?.idField.button.setEnabled(false) return $0 @@ -272,17 +272,17 @@ where T.Input: SetIdPasswordInputable & CTAButtonEnableInputable, T.Output: SetI .compactMap { [weak self] (isValid, id) in // 입력 필드 활성화 - self?.idField.textField.setEnabled(true) + self?.idField.idleTextField.setEnabled(true) printIfDebug(isValid ? "✅ 중복되지 않은 아이디: \(id)" : "❌ 중복된 아이디: \(id)") return isValid ? id : nil } let idValidation = Observable - .combineLatest(idField.textField.eventPublisher, checkIdDuplication ?? .empty()) + .combineLatest(idField.idleTextField.eventPublisher, checkIdDuplication ?? .empty()) .observe(on: MainScheduler.instance) - .map { [weak self] (id, validId) in - let isValid = id == validId + .map { [weak self] (editingId, validId) in + let isValid = editingId == validId self?.thisIsValidIdLabel.isHidden = !isValid return isValid } diff --git a/project/Projects/Presentation/Feature/Auth/Sources/View/Common/Register/ValidatePhoneNumberViewController.swift b/project/Projects/Presentation/Feature/Auth/Sources/View/Common/Register/ValidatePhoneNumberViewController.swift index 10ac4c81..efc3a69d 100644 --- a/project/Projects/Presentation/Feature/Auth/Sources/View/Common/Register/ValidatePhoneNumberViewController.swift +++ b/project/Projects/Presentation/Feature/Auth/Sources/View/Common/Register/ValidatePhoneNumberViewController.swift @@ -65,7 +65,7 @@ where keyboardType: .numberPad ) - textField.textField.isCompleteImageAvailable = false + textField.idleTextField.isCompleteImageAvailable = false return textField }() @@ -88,7 +88,7 @@ where keyboardType: .numberPad ) - textField.textField.isCompleteImageAvailable = false + textField.idleTextField.isCompleteImageAvailable = false return textField }() @@ -195,7 +195,7 @@ where phoneNumberField.button.setEnabled(false) // - 인증번호 입력및 '확인'버튼 비활성화 - authNumberField.textField.setEnabled(false) + authNumberField.idleTextField.setEnabled(false) authNumberField.button.setEnabled(false) // - CTA버튼 비활성화 @@ -208,8 +208,8 @@ where var input = viewModel.input // 현재 입력중인 정보 전송 - input.editingPhoneNumber = phoneNumberField.textField.eventPublisher.asObservable() - input.editingAuthNumber = authNumberField.textField.eventPublisher.asObservable() + input.editingPhoneNumber = phoneNumberField.idleTextField.eventPublisher.asObservable() + input.editingAuthNumber = authNumberField.idleTextField.eventPublisher.asObservable() // 인증, 확인 버튼이 눌린 경우 input.requestAuthForPhoneNumber = phoneNumberField.eventPublisher.asObservable() @@ -242,9 +242,9 @@ where printIfDebug("☑️ \(phoneNumber)의 인증을 시작합니다.") // 인증 텍스트 필드 활성화 - self?.authNumberField.textField.setEnabled(true) - self?.authNumberField.textField.createTimer() - self?.authNumberField.textField.startTimer(minute: 5, seconds: 0) + self?.authNumberField.idleTextField.setEnabled(true) + self?.authNumberField.idleTextField.createTimer() + self?.authNumberField.idleTextField.startTimer(minute: 5, seconds: 0) } }) .disposed(by: disposeBag) @@ -258,13 +258,13 @@ where // 인증번호 인증성공한 경우 // 입력과 관려된 필드와 버튼 비활성화 - self?.phoneNumberField.textField.setEnabled(false) + self?.phoneNumberField.idleTextField.setEnabled(false) self?.phoneNumberField.button.setEnabled(false) - self?.authNumberField.textField.setEnabled(false) + self?.authNumberField.idleTextField.setEnabled(false) self?.authNumberField.button.setEnabled(false) // 타이머 비활성화 - self?.authNumberField.textField.removeTimer() + self?.authNumberField.idleTextField.removeTimer() // 인증 완료 텍스트 self?.authSuccessText.isHidden = false diff --git a/project/Projects/Presentation/Feature/Auth/Sources/ViewModel/Center/Login/CenterLoginViewModel.swift b/project/Projects/Presentation/Feature/Auth/Sources/ViewModel/Center/Login/CenterLoginViewModel.swift index 38a7d8b0..effd3efa 100644 --- a/project/Projects/Presentation/Feature/Auth/Sources/ViewModel/Center/Login/CenterLoginViewModel.swift +++ b/project/Projects/Presentation/Feature/Auth/Sources/ViewModel/Center/Login/CenterLoginViewModel.swift @@ -6,17 +6,55 @@ // import RxSwift +import UseCaseInterface import PresentationCore public class CenterLoginViewModel: ViewModelType { + // Init + let authUseCase: AuthUseCase + public var input: Input = .init() + private var output: Output = .init() + + private let disposeBag = DisposeBag() + + public init(authUseCase: AuthUseCase) { + self.authUseCase = authUseCase + } + + deinit { + printIfDebug("deinit \(Self.self)") + } public func transform(input: Input) -> Output { - let output = Output() + input + .loginButtonPressed? + .subscribe { [weak self] (id, pw) in + + guard let self else { return } + + self.authUseCase + .loginCenterAccount(id: id, password: pw) + .subscribe(onNext: { [weak self] result in + + switch result { + case .success(_): + printIfDebug("✅ 로그인 성공") + self?.output.loginValidation?.onNext(true) + case .failure(let error): + printIfDebug("❌ 로그인 실패, 에러내용: \(error.message)") + self?.output.loginValidation?.onNext(false) + } + }) + .disposed(by: self.disposeBag) + + } + .disposed(by: disposeBag) + - return output + return self.output } } @@ -25,14 +63,12 @@ public extension CenterLoginViewModel { struct Input { - public var editingId: Observable? - public var editingPassword: Observable? + public var loginButtonPressed: Observable<(id: String, pw: String)>? } struct Output { - public var loginValidation: Observable? - + public var loginValidation: PublishSubject? = .init() } } diff --git a/project/Projects/Presentation/Feature/Auth/Sources/ViewModel/Center/Register/CenterRegisterViewModel.swift b/project/Projects/Presentation/Feature/Auth/Sources/ViewModel/Center/Register/CenterRegisterViewModel.swift index 972da5f4..9b8ca81b 100644 --- a/project/Projects/Presentation/Feature/Auth/Sources/ViewModel/Center/Register/CenterRegisterViewModel.swift +++ b/project/Projects/Presentation/Feature/Auth/Sources/ViewModel/Center/Register/CenterRegisterViewModel.swift @@ -39,7 +39,7 @@ public class CenterRegisterViewModel: ViewModelType { public func transform(input: Input) -> Output { // MARK: 성함 입력 - input + self.input .editingName? .subscribe(onNext: { [weak self] name in @@ -58,7 +58,7 @@ public class CenterRegisterViewModel: ViewModelType { // MARK: 전화번호 입력 - input + self.input .editingPhoneNumber? .subscribe(onNext: { [weak self] phoneNumber in @@ -73,7 +73,7 @@ public class CenterRegisterViewModel: ViewModelType { }) .disposed(by: disposeBag) - input + self.input .editingAuthNumber? .subscribe(onNext: { [weak self] authNumber in @@ -86,7 +86,7 @@ public class CenterRegisterViewModel: ViewModelType { // 인증중인 전화번호를 캐치 let currentAuthenticatingNumber = PublishSubject() - input + self.input .requestAuthForPhoneNumber? .subscribe(onNext: { [weak self] phoneNumber in @@ -181,7 +181,7 @@ public class CenterRegisterViewModel: ViewModelType { .disposed(by: disposeBag) // MARK: 사업자 번호 입력 - input + self.input .editingBusinessNumber? .subscribe(onNext: { [weak self] businessNumber in @@ -194,7 +194,7 @@ public class CenterRegisterViewModel: ViewModelType { }) .disposed(by: disposeBag) - input + self.input .requestBusinessNumberValidation? .subscribe(onNext: { [weak self] businessNumber in @@ -241,7 +241,7 @@ public class CenterRegisterViewModel: ViewModelType { .disposed(by: disposeBag) // MARK: Id & Password - input + self.input .editingId? .subscribe(onNext: { [weak self] id in @@ -256,7 +256,7 @@ public class CenterRegisterViewModel: ViewModelType { .disposed(by: disposeBag) // 중복성 검사 - input + self.input .requestIdDuplicationValidation? .subscribe(onNext: { [weak self] id in @@ -363,7 +363,7 @@ public class CenterRegisterViewModel: ViewModelType { // MARK: ViewModel input output extension CenterRegisterViewModel { - public struct Input { + public class Input { // CTA 버튼 클릭시 public var ctaButtonClicked: Observable? From d19eeb2e4af480662ff21ae907b541b45cdd40f0 Mon Sep 17 00:00:00 2001 From: J0onYEong Date: Wed, 10 Jul 2024 14:46:15 +0900 Subject: [PATCH 6/6] =?UTF-8?q?[IDLE-70]=20=EC=98=A4=ED=83=88=EC=9E=90=20?= =?UTF-8?q?=EC=88=98=EC=A0=95(=ED=85=8C=EC=8A=A4=ED=8A=B8=20=EC=8B=A4?= =?UTF-8?q?=ED=8C=A8=20=ED=95=B4=EA=B2=B0)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Projects/App/Sources/DI/Assembly/DomainAssembly.swift | 2 +- project/Projects/Data/ConcretesTests/ConcretesTests.swift | 2 +- .../Domain/ConcreteUseCase/Auth/DefaultAuthUseCase.swift | 2 +- project/Projects/Domain/ConcreteUseCaseTests/Tests.swift | 4 +++- .../DSKit/ExampleApp/Sources/ViewController.swift | 6 +++--- .../Feature/Auth/ExampleApp/Sources/SceneDelegate.swift | 8 +++++++- 6 files changed, 16 insertions(+), 8 deletions(-) diff --git a/project/Projects/App/Sources/DI/Assembly/DomainAssembly.swift b/project/Projects/App/Sources/DI/Assembly/DomainAssembly.swift index 782de428..a27d820e 100644 --- a/project/Projects/App/Sources/DI/Assembly/DomainAssembly.swift +++ b/project/Projects/App/Sources/DI/Assembly/DomainAssembly.swift @@ -22,7 +22,7 @@ public struct DomainAssembly: Assembly { container.register(AuthUseCase.self) { resolver in let repository = resolver.resolve(AuthRepository.self)! - return DefualtAuthseeCase(repository: repository) + return DefaultAuthUseCase(repository: repository) } } } diff --git a/project/Projects/Data/ConcretesTests/ConcretesTests.swift b/project/Projects/Data/ConcretesTests/ConcretesTests.swift index e5da8ed7..dc7f3473 100644 --- a/project/Projects/Data/ConcretesTests/ConcretesTests.swift +++ b/project/Projects/Data/ConcretesTests/ConcretesTests.swift @@ -29,7 +29,7 @@ final class ConcretesTests: XCTestCase { func testAuth() { - let repo = DefaultCenterRegisterRepository() + let repo = DefaultAuthInputValidationRepository() let expectation = expectation(description: "center register test") diff --git a/project/Projects/Domain/ConcreteUseCase/Auth/DefaultAuthUseCase.swift b/project/Projects/Domain/ConcreteUseCase/Auth/DefaultAuthUseCase.swift index 37a5e3ec..02b32876 100644 --- a/project/Projects/Domain/ConcreteUseCase/Auth/DefaultAuthUseCase.swift +++ b/project/Projects/Domain/ConcreteUseCase/Auth/DefaultAuthUseCase.swift @@ -11,7 +11,7 @@ import RepositoryInterface import RxSwift import Entity -public class DefualtAuthseeCase: AuthUseCase { +public class DefaultAuthUseCase: AuthUseCase { let repository: AuthRepository diff --git a/project/Projects/Domain/ConcreteUseCaseTests/Tests.swift b/project/Projects/Domain/ConcreteUseCaseTests/Tests.swift index 978d1dcc..db81842d 100644 --- a/project/Projects/Domain/ConcreteUseCaseTests/Tests.swift +++ b/project/Projects/Domain/ConcreteUseCaseTests/Tests.swift @@ -12,7 +12,9 @@ import XCTest final class ConcreteTests: XCTestCase { - let usecase = DefaultCenterRegisterUseCase(repository: DefaultCenterRegisterRepository()) + let usecase = DefaultAuthInputValidationUseCase( + repository: DefaultAuthInputValidationRepository() + ) func testPhoneNumberRegex() { diff --git a/project/Projects/Presentation/DSKit/ExampleApp/Sources/ViewController.swift b/project/Projects/Presentation/DSKit/ExampleApp/Sources/ViewController.swift index dd0073c2..454f47ac 100644 --- a/project/Projects/Presentation/DSKit/ExampleApp/Sources/ViewController.swift +++ b/project/Projects/Presentation/DSKit/ExampleApp/Sources/ViewController.swift @@ -47,8 +47,8 @@ class ViewController: UIViewController { text: "테스트1", onTouch: { - iFType1.textField.createTimer() - iFType1.textField.startTimer(minute: 5, seconds: 0) + iFType1.idleTextField.createTimer() + iFType1.idleTextField.startTimer(minute: 5, seconds: 0) } ) @@ -57,7 +57,7 @@ class ViewController: UIViewController { onTouch: { iFType1.button.setEnabled(!iFType1.button.isEnabled) - iFType1.textField.setEnabled(!iFType1.textField.isEnabled) + iFType1.idleTextField.setEnabled(!iFType1.idleTextField.isEnabled) } ) diff --git a/project/Projects/Presentation/Feature/Auth/ExampleApp/Sources/SceneDelegate.swift b/project/Projects/Presentation/Feature/Auth/ExampleApp/Sources/SceneDelegate.swift index 8061de21..703cec7e 100644 --- a/project/Projects/Presentation/Feature/Auth/ExampleApp/Sources/SceneDelegate.swift +++ b/project/Projects/Presentation/Feature/Auth/ExampleApp/Sources/SceneDelegate.swift @@ -21,7 +21,13 @@ class SceneDelegate: UIResponder, UIWindowSceneDelegate { window = UIWindow(windowScene: windowScene) - window?.rootViewController = CenterLoginViewController() + window?.rootViewController = CenterLoginViewController( + viewModel: CenterLoginViewModel( + authUseCase: DefaultAuthUseCase( + repository: DefaultAuthRepository() + ) + ) + ) window?.makeKeyAndVisible() } }