diff --git a/project/Projects/App/Sources/DI/Assembly/AuthAssembly.swift b/project/Projects/App/Sources/DI/Assembly/AuthAssembly.swift index 9233c9fa..a2d1107c 100644 --- a/project/Projects/App/Sources/DI/Assembly/AuthAssembly.swift +++ b/project/Projects/App/Sources/DI/Assembly/AuthAssembly.swift @@ -13,10 +13,21 @@ 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 + ) + } + + 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 c38974d3..a27d820e 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 - let repository = resolver.resolve(CenterRegisterRepository.self)! + container.register(AuthInputValidationUseCase.self) { resolver in + let repository = resolver.resolve(AuthInputValidationRepository.self)! - return DefaultCenterRegisterUseCase(repository: repository) + return DefaultAuthInputValidationUseCase(repository: repository) + } + + container.register(AuthUseCase.self) { resolver in + let repository = resolver.resolve(AuthRepository.self)! + + return DefaultAuthUseCase(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/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/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/DefaultCenterRegisterUseCase.swift b/project/Projects/Domain/ConcreteUseCase/Auth/DefaultAuthInputValidationUseCase.swift similarity index 75% rename from project/Projects/Domain/ConcreteUseCase/Auth/DefaultCenterRegisterUseCase.swift rename to project/Projects/Domain/ConcreteUseCase/Auth/DefaultAuthInputValidationUseCase.swift index cb05eb47..3bfc1d63 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,11 +11,11 @@ import Entity import UseCaseInterface import RepositoryInterface -public class DefaultCenterRegisterUseCase: CenterRegisterUseCase { +public class DefaultAuthInputValidationUseCase: AuthInputValidationUseCase { - let repository: CenterRegisterRepository + let repository: AuthInputValidationRepository - public init(repository: CenterRegisterRepository) { + public init(repository: AuthInputValidationRepository) { self.repository = repository } @@ -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..02b32876 --- /dev/null +++ b/project/Projects/Domain/ConcreteUseCase/Auth/DefaultAuthUseCase.swift @@ -0,0 +1,41 @@ +// +// DefaultAuthUseCase.swift +// ConcreteUseCase +// +// Created by choijunios on 7/10/24. +// + +import Foundation +import UseCaseInterface +import RepositoryInterface +import RxSwift +import Entity + +public class DefaultAuthUseCase: AuthUseCase { + + let repository: AuthRepository + + public init(repository: AuthRepository) { + 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() + ) + } + + // 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/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/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/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/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..0a924be0 --- /dev/null +++ b/project/Projects/Domain/UseCaseInterface/Auth/AuthUseCase.swift @@ -0,0 +1,44 @@ +// +// 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 + + // #2. + /// 센터 로그인 실행 + /// - parameters: + /// - id: String + /// - password: String + /// - returns: + /// - Bool, true: 성공, flase: 실패 + func loginCenterAccount( + id: String, + password: String + ) -> Observable +} 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/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/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 new file mode 100644 index 00000000..3946b270 --- /dev/null +++ b/project/Projects/Presentation/DSKit/Sources/CommonUI/IFType2.swift @@ -0,0 +1,85 @@ +// +// 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 + public private(set) var isCompletionImageAvailable: Bool + + // Output + public var eventPublisher: Observable { self.idleTextField.eventPublisher } + + // View + public private(set) lazy var titleLabel: ResizableUILabel = { + let label = ResizableUILabel() + label.text = self.titleLabelText + label.font = DSKitFontFamily.Pretendard.bold.font(size: 14) + label.textColor = DSKitAsset.Colors.gray500.color + return label + }() + + 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, + isCompletionImageAvailable: Bool = false, + keyboardType: UIKeyboardType = .default + ) { + self.placeHolderText = placeHolderText + self.titleLabelText = titleLabelText + self.keyboardType = keyboardType + self.isCompletionImageAvailable = isCompletionImageAvailable + 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, + idleTextField, + ].forEach { + self.addArrangedSubview($0) + } + + NSLayoutConstraint.activate([ + idleTextField.leftAnchor.constraint(equalTo: self.leftAnchor), + idleTextField.rightAnchor.constraint(equalTo: self.rightAnchor), + ]) + } + + public override func resignFirstResponder() -> Bool { + + _ = 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/ExampleApp/Sources/SceneDelegate.swift b/project/Projects/Presentation/Feature/Auth/ExampleApp/Sources/SceneDelegate.swift index 6d5e87c1..703cec7e 100644 --- a/project/Projects/Presentation/Feature/Auth/ExampleApp/Sources/SceneDelegate.swift +++ b/project/Projects/Presentation/Feature/Auth/ExampleApp/Sources/SceneDelegate.swift @@ -21,15 +21,13 @@ class SceneDelegate: UIResponder, UIWindowSceneDelegate { window = UIWindow(windowScene: windowScene) - let viewModel = CenterRegisterViewModel( - useCase: DefaultCenterRegisterUseCase( - repository: DefaultCenterRegisterRepository() + window?.rootViewController = CenterLoginViewController( + viewModel: CenterLoginViewModel( + authUseCase: DefaultAuthUseCase( + repository: DefaultAuthRepository() + ) ) ) - - window?.rootViewController = EnterNameViewController( - viewModel: viewModel - ) window?.makeKeyAndVisible() } } 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/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..d2cf074c --- /dev/null +++ b/project/Projects/Presentation/Feature/Auth/Sources/View/Center/Login/CenterLoginViewController.swift @@ -0,0 +1,310 @@ +// +// CenterLoginViewController.swift +// AuthFeature +// +// Created by choijunios on 7/1/24. +// + +import UIKit +import RxSwift +import DSKit +import PresentationCore + +public class CenterLoginViewController: DisposableViewController { + + let viewModel: CenterLoginViewModel + + var coordinator: CenterLoginCoordinator? + + // View + private let navigationBar: NavigationBarType1 = { + + let bar = NavigationBarType1(navigationTitle: "로그인") + + return bar + }() + + private let idField: IFType2 = { + + let field = IFType2( + titleLabelText: "아이디", + placeHolderText: "아이디를 입력해주세요.", + isCompletionImageAvailable: false + ) + + return field + }() + private let passwordField: IFType2 = { + + let field = IFType2( + titleLabelText: "비밀번호", + placeHolderText: "비밀번호를 입력해주세요.", + isCompletionImageAvailable: false + ) + + return field + }() + + private let inputStack: UIStackView = { + + let stack = UIStackView() + + stack.axis = .vertical + stack.spacing = 30 + stack.alignment = .fill + + 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() + + 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 + }() + + private let ctaButton: CTAButtonType1 = { + + let button = CTAButtonType1(labelText: "로그인") + + return button + }() + + private let disposeBag = DisposeBag() + + public init(coordinator: CenterLoginCoordinator? = nil, viewModel: CenterLoginViewModel) { + self.coordinator = coordinator + self.viewModel = viewModel + + 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, + loginFailedText, + ].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.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), + + 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() + + // 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 + } + } + + + public func cleanUp() { + + } +} + +extension CenterLoginViewController { + + + 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.uITextField.isFirstResponder { + // id field가 선택된 경우 + movingView = self.idField + } else if self.passwordField.uITextField.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) + } +} 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 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 b984842f..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 @@ -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? @@ -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 } @@ -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/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 new file mode 100644 index 00000000..effd3efa --- /dev/null +++ b/project/Projects/Presentation/Feature/Auth/Sources/ViewModel/Center/Login/CenterLoginViewModel.swift @@ -0,0 +1,74 @@ +// +// CenterLoginViewModel.swift +// AuthFeature +// +// Created by choijunios on 7/10/24. +// + +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 { + + 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 self.output + } +} + + +public extension CenterLoginViewModel { + + struct Input { + + public var loginButtonPressed: Observable<(id: String, pw: String)>? + + } + + struct Output { + + 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 855c1099..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 @@ -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,15 @@ 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 { + printIfDebug("deinit \(Self.self)") } let disposeBag = DisposeBag() @@ -31,7 +39,7 @@ public class CenterRegisterViewModel: ViewModelType { public func transform(input: Input) -> Output { // MARK: 성함 입력 - input + self.input .editingName? .subscribe(onNext: { [weak self] name in @@ -50,7 +58,7 @@ public class CenterRegisterViewModel: ViewModelType { // MARK: 전화번호 입력 - input + self.input .editingPhoneNumber? .subscribe(onNext: { [weak self] phoneNumber in @@ -60,12 +68,12 @@ public class CenterRegisterViewModel: ViewModelType { // 특정 조건 만족시 self.output.canSubmitPhoneNumber?.onNext( - self.useCase.checkPhoneNumberIsValid(phoneNumber: phoneNumber) + self.inputValidationUseCase.checkPhoneNumberIsValid(phoneNumber: phoneNumber) ) }) .disposed(by: disposeBag) - input + self.input .editingAuthNumber? .subscribe(onNext: { [weak self] authNumber in @@ -78,7 +86,7 @@ public class CenterRegisterViewModel: ViewModelType { // 인증중인 전화번호를 캐치 let currentAuthenticatingNumber = PublishSubject() - input + self.input .requestAuthForPhoneNumber? .subscribe(onNext: { [weak self] phoneNumber in @@ -107,7 +115,7 @@ public class CenterRegisterViewModel: ViewModelType { return #endif - self.useCase + self.inputValidationUseCase .requestPhoneNumberAuthentication(phoneNumber: formattedString) .subscribe { [weak self] result in switch result { @@ -150,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 { @@ -173,7 +181,7 @@ public class CenterRegisterViewModel: ViewModelType { .disposed(by: disposeBag) // MARK: 사업자 번호 입력 - input + self.input .editingBusinessNumber? .subscribe(onNext: { [weak self] businessNumber in @@ -181,12 +189,12 @@ 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) - input + self.input .requestBusinessNumberValidation? .subscribe(onNext: { [weak self] businessNumber in @@ -207,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 @@ -217,7 +225,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)") @@ -233,7 +241,7 @@ public class CenterRegisterViewModel: ViewModelType { .disposed(by: disposeBag) // MARK: Id & Password - input + self.input .editingId? .subscribe(onNext: { [weak self] id in @@ -241,14 +249,14 @@ 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) }) .disposed(by: disposeBag) // 중복성 검사 - input + self.input .requestIdDuplicationValidation? .subscribe(onNext: { [weak self] id in @@ -265,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 @@ -297,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 { @@ -323,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 @@ -331,13 +339,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) } @@ -349,7 +363,7 @@ public class CenterRegisterViewModel: ViewModelType { // MARK: ViewModel input output extension CenterRegisterViewModel { - public struct Input { + public class Input { // CTA 버튼 클릭시 public var ctaButtonClicked: Observable? @@ -393,6 +407,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 +433,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 } +}