diff --git a/project/Projects/Domain/DomainTests/RegisterValidationTests.swift b/project/Projects/Domain/DomainTests/RegisterValidationTests.swift
index f6842ff9..2cfaee19 100644
--- a/project/Projects/Domain/DomainTests/RegisterValidationTests.swift
+++ b/project/Projects/Domain/DomainTests/RegisterValidationTests.swift
@@ -71,11 +71,11 @@ final class RegisterValidationTests: XCTestCase {
let usecase = DefaultAuthInputValidationUseCase()
// 유효한 비밀번호 테스트
- XCTAssertTrue(usecase.checkPasswordIsValid(password: "Password1"))
- XCTAssertTrue(usecase.checkPasswordIsValid(password: "pass1234"))
- XCTAssertTrue(usecase.checkPasswordIsValid(password: "1234Abcd!"))
- XCTAssertTrue(usecase.checkPasswordIsValid(password: "Valid123"))
- XCTAssertTrue(usecase.checkPasswordIsValid(password: "StrongPass1!"))
+ XCTAssertTrue(usecase.checkPasswordIsValid(password: "Password1").isPasswordValid)
+ XCTAssertTrue(usecase.checkPasswordIsValid(password: "pass1234").isPasswordValid)
+ XCTAssertTrue(usecase.checkPasswordIsValid(password: "1234Abcd!").isPasswordValid)
+ XCTAssertTrue(usecase.checkPasswordIsValid(password: "Valid123").isPasswordValid)
+ XCTAssertTrue(usecase.checkPasswordIsValid(password: "StrongPass1!").isPasswordValid)
}
func testInvalidPassword() {
@@ -83,10 +83,10 @@ final class RegisterValidationTests: XCTestCase {
let usecase = DefaultAuthInputValidationUseCase()
// 유효하지 않은 비밀번호 테스트
- XCTAssertFalse(usecase.checkPasswordIsValid(password: "short1")) // 너무 짧음
- XCTAssertFalse(usecase.checkPasswordIsValid(password: "alllowercase")) // 숫자 없음
- XCTAssertFalse(usecase.checkPasswordIsValid(password: "ALLUPPERCASE")) // 숫자 없음
- XCTAssertFalse(usecase.checkPasswordIsValid(password: "12345678")) // 영문자 없음
- XCTAssertFalse(usecase.checkPasswordIsValid(password: "123456789012345678901")) // 너무 길음
+ XCTAssertFalse(usecase.checkPasswordIsValid(password: "short1").isPasswordValid) // 너무 짧음
+ XCTAssertFalse(usecase.checkPasswordIsValid(password: "alllowercase").isPasswordValid) // 숫자 없음
+ XCTAssertFalse(usecase.checkPasswordIsValid(password: "ALLUPPERCASE").isPasswordValid) // 숫자 없음
+ XCTAssertFalse(usecase.checkPasswordIsValid(password: "12345678").isPasswordValid) // 영문자 없음
+ XCTAssertFalse(usecase.checkPasswordIsValid(password: "123456789012345678901").isValid) // 너무 길음
}
}
diff --git a/project/Projects/Domain/Sources/ConcreteUseCase/Auth/DefaultAuthInputValidationUseCase.swift b/project/Projects/Domain/Sources/ConcreteUseCase/Auth/DefaultAuthInputValidationUseCase.swift
index 90033d75..8ec85610 100644
--- a/project/Projects/Domain/Sources/ConcreteUseCase/Auth/DefaultAuthInputValidationUseCase.swift
+++ b/project/Projects/Domain/Sources/ConcreteUseCase/Auth/DefaultAuthInputValidationUseCase.swift
@@ -67,10 +67,36 @@ public class DefaultAuthInputValidationUseCase: AuthInputValidationUseCase {
.requestCheckingIdDuplication(id: id)
}
- public func checkPasswordIsValid(password: String) -> Bool {
- let passwordLengthAndCharRegex = "^(?=.*[A-Za-z])(?=.*\\d)[A-Za-z\\d!@#$%^&*()_+=-]{8,20}$"
- let predicate = NSPredicate(format: "SELF MATCHES %@", passwordLengthAndCharRegex)
+ public func checkPasswordIsValid(password: String) -> PasswordValidationState {
- return predicate.evaluate(with: password)
+ // 1. 8자 ~ 20자 사이
+ let lengthRegex = "^.{8,20}$"
+ let lengthIsValid = evaluateStringWith(regex: lengthRegex, targetString: password)
+
+ // 2. 영문자와 숫자 반드시 하나씩 포함
+ let letterAndNumberRegex = "^(?=.*[A-Za-z])(?=.*[0-9]).*$"
+ let letterAndNumberIsValid = evaluateStringWith(regex: letterAndNumberRegex, targetString: password)
+
+ // 3. 공백 문자 사용 금지
+ let noWhitespaceRegex = "^\\S*$"
+ let noWhitespaceIsValid = evaluateStringWith(regex: noWhitespaceRegex, targetString: password)
+
+ // 4. 연속된 문자 3개 이상 사용 금지
+ let noTripleRepeatedCharsRegex = "^(?!.*(.)\\1{2,}).*$"
+ let noTripleRepeatedCharsIsValid = evaluateStringWith(regex: noTripleRepeatedCharsRegex, targetString: password)
+
+ return PasswordValidationState(
+ characterCount: lengthIsValid ? .valid : .invalid,
+ alphabetAndNumberIncluded: letterAndNumberIsValid ? .valid : .invalid,
+ noEmptySpace: noWhitespaceIsValid ? .valid : .invalid,
+ unsuccessiveSame3words: noTripleRepeatedCharsIsValid ? .valid : .invalid
+ )
+ }
+
+ private func evaluateStringWith(regex: String, targetString: String) -> Bool {
+
+ let predicate = NSPredicate(format: "SELF MATCHES %@", regex)
+
+ return predicate.evaluate(with: targetString)
}
}
diff --git a/project/Projects/Domain/Sources/Entity/State/Auth/Center/PasswordValidationState.swift b/project/Projects/Domain/Sources/Entity/State/Auth/Center/PasswordValidationState.swift
index 4107401a..0c13f984 100644
--- a/project/Projects/Domain/Sources/Entity/State/Auth/Center/PasswordValidationState.swift
+++ b/project/Projects/Domain/Sources/Entity/State/Auth/Center/PasswordValidationState.swift
@@ -1,15 +1,90 @@
//
// PasswordValidationState.swift
-// ConcreteUseCase
+// Domain
//
-// Created by choijunios on 7/7/24.
+// Created by choijunios on 10/22/24.
//
import Foundation
-public enum PasswordValidationState {
+public class PasswordValidationState {
- case invalidPassword
- case unMatch
- case match
+ public enum State {
+ case valid
+ case invalid
+ }
+
+ public let characterCount: State
+ public let alphabetAndNumberIncluded: State
+ public let noEmptySpace: State
+ public let unsuccessiveSame3words: State
+ public private(set) var isEditingAndCheckingPasswordsEqual: Bool = false
+
+ public init(
+ characterCount: State,
+ alphabetAndNumberIncluded: State,
+ noEmptySpace: State,
+ unsuccessiveSame3words: State
+ ) {
+ self.characterCount = characterCount
+ self.alphabetAndNumberIncluded = alphabetAndNumberIncluded
+ self.noEmptySpace = noEmptySpace
+ self.unsuccessiveSame3words = unsuccessiveSame3words
+ }
+
+ public var isValid: Bool {
+
+ return (
+ isPasswordValid
+ &&
+ isEditingAndCheckingPasswordsEqual
+ )
+ }
+
+ public var isPasswordValid: Bool {
+ characterCount == .valid
+ &&
+ alphabetAndNumberIncluded == .valid
+ &&
+ noEmptySpace == .valid
+ &&
+ unsuccessiveSame3words == .valid
+ }
+
+ public func setEqualState(state: Bool) {
+ isEditingAndCheckingPasswordsEqual = state
+ }
+}
+
+public extension PasswordValidationState {
+
+ var description: String {
+ var descriptions: [String] = []
+
+ if characterCount == .valid {
+ descriptions.append("비밀번호 길이: 유효함 (8자 이상 20자 이하)")
+ } else {
+ descriptions.append("비밀번호 길이: 유효하지 않음 (8자 이상 20자 이하이어야 함)")
+ }
+
+ if alphabetAndNumberIncluded == .valid {
+ descriptions.append("영문자와 숫자: 유효함 (영문자와 숫자가 모두 포함됨)")
+ } else {
+ descriptions.append("영문자와 숫자: 유효하지 않음 (영문자와 숫자가 반드시 포함되어야 함)")
+ }
+
+ if noEmptySpace == .valid {
+ descriptions.append("공백 문자: 없음 (공백 문자를 사용할 수 없음)")
+ } else {
+ descriptions.append("공백 문자: 유효하지 않음 (공백 문자가 포함되어 있음)")
+ }
+
+ if unsuccessiveSame3words == .valid {
+ descriptions.append("연속된 문자 3개 이상 사용: 유효함 (연속된 동일 문자가 없음)")
+ } else {
+ descriptions.append("연속된 문자 3개 이상 사용: 유효하지 않음 (연속된 동일 문자가 3개 이상 포함됨)")
+ }
+
+ return descriptions.joined(separator: "\n")
+ }
}
diff --git a/project/Projects/Domain/Sources/Entity/VO/UserInfo/CenterJoinStatusInfoVO.swift b/project/Projects/Domain/Sources/Entity/VO/UserInfo/CenterJoinStatusInfoVO.swift
index 24eace65..202a270e 100644
--- a/project/Projects/Domain/Sources/Entity/VO/UserInfo/CenterJoinStatusInfoVO.swift
+++ b/project/Projects/Domain/Sources/Entity/VO/UserInfo/CenterJoinStatusInfoVO.swift
@@ -12,6 +12,18 @@ public struct CenterJoinStatusInfoVO: Codable {
public let managerName: String
public let phoneNumber: String
public let centerManagerAccountStatus: CenterAccountStatus
+
+ public init(
+ id: String,
+ managerName: String,
+ phoneNumber: String,
+ centerManagerAccountStatus: CenterAccountStatus
+ ) {
+ self.id = id
+ self.managerName = managerName
+ self.phoneNumber = phoneNumber
+ self.centerManagerAccountStatus = centerManagerAccountStatus
+ }
}
public enum CenterAccountStatus: String, Codable {
diff --git a/project/Projects/Domain/Sources/UseCaseInterface/Auth/AuthInputValidationUseCase.swift b/project/Projects/Domain/Sources/UseCaseInterface/Auth/AuthInputValidationUseCase.swift
index 3c8bcd47..8e2bda65 100644
--- a/project/Projects/Domain/Sources/UseCaseInterface/Auth/AuthInputValidationUseCase.swift
+++ b/project/Projects/Domain/Sources/UseCaseInterface/Auth/AuthInputValidationUseCase.swift
@@ -86,8 +86,8 @@ public protocol AuthInputValidationUseCase: BaseUseCase {
/// - parameters:
/// - password : "password1234"
/// - returns:
- /// - Bool, true: 가능, flase: 불가능
- func checkPasswordIsValid(password: String) -> Bool
+ /// - PasswordValidationState
+ func checkPasswordIsValid(password: String) -> PasswordValidationState
// #9.
/// 이름 유효성 확인 로직
diff --git a/project/Projects/Presentation/DSKit/Resources/Colors.xcassets/green.colorset/Contents.json b/project/Projects/Presentation/DSKit/Resources/Colors.xcassets/green.colorset/Contents.json
new file mode 100644
index 00000000..f1e2fd65
--- /dev/null
+++ b/project/Projects/Presentation/DSKit/Resources/Colors.xcassets/green.colorset/Contents.json
@@ -0,0 +1,38 @@
+{
+ "colors" : [
+ {
+ "color" : {
+ "color-space" : "srgb",
+ "components" : {
+ "alpha" : "1.000",
+ "blue" : "0x4D",
+ "green" : "0xC3",
+ "red" : "0x2C"
+ }
+ },
+ "idiom" : "universal"
+ },
+ {
+ "appearances" : [
+ {
+ "appearance" : "luminosity",
+ "value" : "dark"
+ }
+ ],
+ "color" : {
+ "color-space" : "srgb",
+ "components" : {
+ "alpha" : "1.000",
+ "blue" : "0x4D",
+ "green" : "0xC3",
+ "red" : "0x2C"
+ }
+ },
+ "idiom" : "universal"
+ }
+ ],
+ "info" : {
+ "author" : "xcode",
+ "version" : 1
+ }
+}
diff --git a/project/Projects/Presentation/Feature/Auth/ExampleApp/Sources/SceneDelegate.swift b/project/Projects/Presentation/Feature/Auth/ExampleApp/Sources/SceneDelegate.swift
index 583c671d..c109150c 100644
--- a/project/Projects/Presentation/Feature/Auth/ExampleApp/Sources/SceneDelegate.swift
+++ b/project/Projects/Presentation/Feature/Auth/ExampleApp/Sources/SceneDelegate.swift
@@ -7,18 +7,68 @@
import UIKit
+import AuthFeature
+import BaseFeature
+import Testing
+import Core
+
class SceneDelegate: UIResponder, UIWindowSceneDelegate {
var window: UIWindow?
+ var authCoordinator: AuthCoordinator?
+ var centerAccountRegisterCoordinator: CenterAccountRegisterCoordinator?
+ var centerLogInCoordinator: CenterLogInCoordinator?
+
func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {
guard let windowScene = scene as? UIWindowScene else { return }
-
- window = UIWindow(windowScene: windowScene)
+ DependencyInjector.shared.assemble(MockAssemblies)
+ DependencyInjector.shared.register(CenterRegisterLogger.self, CenterAuthLogger())
+ authCoordinator = .init()
+ authCoordinator?.startFlow = { [weak self] desination in
+
+ switch desination {
+ case .centerRegisterPage:
+
+ let coordinator = CenterAccountRegisterCoordinator()
+ self?.centerAccountRegisterCoordinator = coordinator
+ coordinator.start()
+
+ case .loginPage:
+ let coordinator = CenterLogInCoordinator()
+
+ coordinator.startFlow = { desination in
+ switch desination {
+ default:
+ // 센터 메인페이지로 이동
+ return
+ }
+ }
+
+ self?.centerLogInCoordinator = coordinator
+ coordinator.start()
+ default:
+ // 테스트시 추가가능
+ return
+ }
+ }
+
+ window = UIWindow(windowScene: windowScene)
window?.makeKeyAndVisible()
+
+ authCoordinator?.start()
}
}
+
+class CenterAuthLogger: CenterRegisterLogger {
+
+ func logCenterRegisterStep(stepName: String, stepIndex: Int) { }
+
+ func startCenterRegister() { }
+
+ func logCenterRegisterDuration() { }
+}
diff --git a/project/Projects/Presentation/Feature/Auth/Project.swift b/project/Projects/Presentation/Feature/Auth/Project.swift
index 82be4acb..646ccde1 100644
--- a/project/Projects/Presentation/Feature/Auth/Project.swift
+++ b/project/Projects/Presentation/Feature/Auth/Project.swift
@@ -46,6 +46,7 @@ let project = Project(
resources: ["ExampleApp/Resources/**"],
dependencies: [
.target(name: "AuthFeature"),
+ D.Testing,
],
settings: .settings(
configurations: IdleConfiguration.presentationConfigurations
diff --git a/project/Projects/Presentation/Feature/Auth/Resources/Asset.xcassets/Contents.json b/project/Projects/Presentation/Feature/Auth/Resources/Asset.xcassets/Contents.json
new file mode 100644
index 00000000..73c00596
--- /dev/null
+++ b/project/Projects/Presentation/Feature/Auth/Resources/Asset.xcassets/Contents.json
@@ -0,0 +1,6 @@
+{
+ "info" : {
+ "author" : "xcode",
+ "version" : 1
+ }
+}
diff --git a/project/Projects/Presentation/Feature/Auth/Resources/Asset.xcassets/v_f_mark.imageset/Contents.json b/project/Projects/Presentation/Feature/Auth/Resources/Asset.xcassets/v_f_mark.imageset/Contents.json
new file mode 100644
index 00000000..05348944
--- /dev/null
+++ b/project/Projects/Presentation/Feature/Auth/Resources/Asset.xcassets/v_f_mark.imageset/Contents.json
@@ -0,0 +1,12 @@
+{
+ "images" : [
+ {
+ "filename" : "v_f_mark.svg",
+ "idiom" : "universal"
+ }
+ ],
+ "info" : {
+ "author" : "xcode",
+ "version" : 1
+ }
+}
diff --git a/project/Projects/Presentation/Feature/Auth/Resources/Asset.xcassets/v_f_mark.imageset/v_f_mark.svg b/project/Projects/Presentation/Feature/Auth/Resources/Asset.xcassets/v_f_mark.imageset/v_f_mark.svg
new file mode 100644
index 00000000..a16a660d
--- /dev/null
+++ b/project/Projects/Presentation/Feature/Auth/Resources/Asset.xcassets/v_f_mark.imageset/v_f_mark.svg
@@ -0,0 +1,4 @@
+
diff --git a/project/Projects/Presentation/Feature/Auth/Resources/Asset.xcassets/v_s_mark.imageset/Contents.json b/project/Projects/Presentation/Feature/Auth/Resources/Asset.xcassets/v_s_mark.imageset/Contents.json
new file mode 100644
index 00000000..15dc2b5b
--- /dev/null
+++ b/project/Projects/Presentation/Feature/Auth/Resources/Asset.xcassets/v_s_mark.imageset/Contents.json
@@ -0,0 +1,12 @@
+{
+ "images" : [
+ {
+ "filename" : "v_s_mark.svg",
+ "idiom" : "universal"
+ }
+ ],
+ "info" : {
+ "author" : "xcode",
+ "version" : 1
+ }
+}
diff --git a/project/Projects/Presentation/Feature/Auth/Resources/Asset.xcassets/v_s_mark.imageset/v_s_mark.svg b/project/Projects/Presentation/Feature/Auth/Resources/Asset.xcassets/v_s_mark.imageset/v_s_mark.svg
new file mode 100644
index 00000000..781e7cd1
--- /dev/null
+++ b/project/Projects/Presentation/Feature/Auth/Resources/Asset.xcassets/v_s_mark.imageset/v_s_mark.svg
@@ -0,0 +1,4 @@
+
diff --git a/project/Projects/Presentation/Feature/Auth/Sources/Center/AccountRegister/CenterAccountRegisterCoordinator.swift b/project/Projects/Presentation/Feature/Auth/Sources/Center/AccountRegister/CenterAccountRegisterCoordinator.swift
index 8c6ef06b..899a5dbf 100644
--- a/project/Projects/Presentation/Feature/Auth/Sources/Center/AccountRegister/CenterAccountRegisterCoordinator.swift
+++ b/project/Projects/Presentation/Feature/Auth/Sources/Center/AccountRegister/CenterAccountRegisterCoordinator.swift
@@ -89,6 +89,11 @@ public class CenterAccountRegisterCoordinator: Coordinator {
// 완료화면으로 이동
self?.router.presentAnonymousCompletePage(object)
}
+
+ vm.presentAlert = { [weak self] object in
+
+ self?.router.presentDefaultAlertController(object: object)
+ }
self.stageViewControllers = [
EnterNameViewController(viewModel: vm),
diff --git a/project/Projects/Presentation/Feature/Auth/Sources/Center/AccountRegister/CenterAccountRegisterViewModel.swift b/project/Projects/Presentation/Feature/Auth/Sources/Center/AccountRegister/CenterAccountRegisterViewModel.swift
index acc0288b..96216dae 100644
--- a/project/Projects/Presentation/Feature/Auth/Sources/Center/AccountRegister/CenterAccountRegisterViewModel.swift
+++ b/project/Projects/Presentation/Feature/Auth/Sources/Center/AccountRegister/CenterAccountRegisterViewModel.swift
@@ -14,19 +14,20 @@ import Core
import RxSwift
import RxCocoa
-public class CenterAccountRegisterViewModel: BaseViewModel, ViewModelType {
+class CenterAccountRegisterViewModel: BaseViewModel, ViewModelType {
// Injected
@Injected var inputValidationUseCase: AuthInputValidationUseCase
@Injected var authUseCase: AuthUseCase
- var presentNextPage: (() -> ())!
- var presentPrevPage: (() -> ())!
- var presentCompleteScreen: (() -> ())!
+ var presentNextPage: (() -> ())?
+ var presentPrevPage: (() -> ())?
+ var presentCompleteScreen: (() -> ())?
+ var presentAlert: ((DefaultAlertObject) -> ())?
// Input은 모든 ViewController에서 공유한다. (다만, 각가의 ViewController의 Input프로토콜에 의해 제한된다.)
- public let input = Input()
- public let output = Output()
+ let input = Input()
+ let output = Output()
internal let stateObject = CenterRegisterState()
@@ -56,21 +57,10 @@ public class CenterAccountRegisterViewModel: BaseViewModel, ViewModelType {
registerInOut()
validateBusinessNumberInOut()
- AuthInOutStreamManager.idInOut(
- input: input,
- output: output,
- useCase: inputValidationUseCase) { [weak self] validId in
- // 🚀 상태추적 🚀
- self?.stateObject.id = validId
- }
- AuthInOutStreamManager.passwordInOut(
- input: input,
- output: output,
- useCase: inputValidationUseCase) { [weak self] validPassword in
- // 🚀 상태추적 🚀
- self?.stateObject.password = validPassword
- }
+ // MARK: Id & Password
+ idAndPasswordValidationBinding()
+
input.alert
.subscribe(onNext: { [weak self] alertVO in
@@ -83,7 +73,7 @@ public class CenterAccountRegisterViewModel: BaseViewModel, ViewModelType {
.nextButtonClicked
.unretained(self)
.subscribe(onNext: { (obj, _) in
- obj.presentNextPage()
+ obj.presentNextPage?()
})
.disposed(by: disposeBag)
@@ -91,7 +81,7 @@ public class CenterAccountRegisterViewModel: BaseViewModel, ViewModelType {
.prevButtonClicked
.unretained(self)
.subscribe(onNext: { (obj, _) in
- obj.presentPrevPage()
+ obj.presentPrevPage?()
})
.disposed(by: disposeBag)
}
@@ -101,65 +91,155 @@ public class CenterAccountRegisterViewModel: BaseViewModel, ViewModelType {
}
}
+
+// MARK: Id & Password validation
+extension CenterAccountRegisterViewModel {
+
+ func idAndPasswordValidationBinding() {
+
+ // ID
+ output.idValidationResult = input
+ .editingId
+ .unretained(self)
+ .map { (vm, id) in
+ vm.inputValidationUseCase.checkIdIsValid(id: id)
+ }
+ .asDriver(onErrorDriveWith: .never())
+
+ let idDuplicationCheckResult = input
+ .isIdDuplicatedButtonPressed
+ .withLatestFrom(input.editingId)
+ .unretained(self)
+ .flatMap { (vm, id) in
+
+ printIfDebug("[CenterRegisterViewModel] 중복성 검사 대상 id: \(id)")
+
+ // 검증시 가장 최근 id저장
+ vm.stateObject.id = id
+
+ #if DEBUG
+ // 디버그시 아이디 중복체크 미실시
+ print("✅ 디버그모드에서 아이디 중복검사 미실시")
+ return Single.just(Result.success(()))
+ #endif
+
+ return vm.inputValidationUseCase.requestCheckingIdDuplication(id: id)
+ }
+ .share()
+
+ output.idDuplicationCheckResult = idDuplicationCheckResult
+ .map { result in
+ switch result {
+ case .success:
+ return true
+ case .failure:
+ return false
+ }
+ }
+ .asDriver(onErrorDriveWith: .never())
+
+ let idDuplicationFailure = idDuplicationCheckResult.compactMap { $0.error }
+
+ idDuplicationFailure
+ .unretained(self)
+ .subscribe(onNext: { (vm, error) in
+
+ let alertObject: DefaultAlertObject = .init()
+ alertObject.setTitle("아이디 중복검사 실패")
+ alertObject.setDescription(error.message)
+
+ vm.presentAlert?(alertObject)
+ })
+ .disposed(by: disposeBag)
+
+
+ // Passwords
+ output.passwordValidationState = Observable
+ .combineLatest(
+ input.editingPassword,
+ input.checkingPassword
+ )
+ .unretained(self)
+ .map { (vm, passwords) in
+
+ let (editing, checking) = passwords
+
+ let stateObject: PasswordValidationState = vm.inputValidationUseCase
+ .checkPasswordIsValid(password: editing)
+
+ stateObject.setEqualState(state: editing == checking)
+
+ // 가장 최근 비밀번호 저장
+ vm.stateObject.password = editing
+
+ printIfDebug(stateObject.description)
+
+ return stateObject
+ }
+ .asDriver(onErrorDriveWith: .never())
+ }
+}
+
// MARK: ViewModel input output
extension CenterAccountRegisterViewModel {
- public class Input {
+ class Input {
// CTA 버튼 클릭시
- public var nextButtonClicked: PublishSubject = .init()
- public var prevButtonClicked: PublishSubject = .init()
- public var completeButtonClicked: PublishSubject = .init()
+ var nextButtonClicked: PublishSubject = .init()
+ var prevButtonClicked: PublishSubject = .init()
+ var completeButtonClicked: PublishSubject = .init()
// 이름입력
public var editingName: PublishRelay = .init()
// 전화번호 입력
- public var editingPhoneNumber: BehaviorRelay = .init(value: "")
- public var editingAuthNumber: BehaviorRelay = .init(value: "")
- public var requestAuthForPhoneNumber: PublishRelay = .init()
- public var requestValidationForAuthNumber: PublishRelay = .init()
+ var editingPhoneNumber: BehaviorRelay = .init(value: "")
+ var editingAuthNumber: BehaviorRelay = .init(value: "")
+ var requestAuthForPhoneNumber: PublishRelay = .init()
+ var requestValidationForAuthNumber: PublishRelay = .init()
// 사업자 번호 입력
- public var editingBusinessNumber: BehaviorRelay = .init(value: "")
- public var requestBusinessNumberValidation: PublishRelay = .init()
+ var editingBusinessNumber: BehaviorRelay = .init(value: "")
+ var requestBusinessNumberValidation: PublishRelay = .init()
// Id
- public var editingId: BehaviorRelay = .init(value: "")
- public var requestIdDuplicationValidation: PublishRelay = .init()
+ var editingId: PublishSubject = .init()
+ var isIdDuplicatedButtonPressed: PublishSubject = .init()
// Password
- public var editingPasswords: PublishRelay<(pwd: String, cpwd: String)> = .init()
+ var editingPassword: PublishSubject = .init()
+ var checkingPassword: BehaviorSubject = .init(value: "")
// Alert
- public var alert: PublishSubject = .init()
+ var alert: PublishSubject = .init()
}
- public class Output {
+ class Output {
// 이름 입력
public var nameValidation: Driver?
// 전화번호 입력
- public var canSubmitPhoneNumber: Driver?
- public var canSubmitAuthNumber: Driver?
- public var phoneNumberValidation: Driver?
- public var authNumberValidation: Driver?
+ var canSubmitPhoneNumber: Driver?
+ var canSubmitAuthNumber: Driver?
+ var phoneNumberValidation: Driver?
+ var authNumberValidation: Driver?
// 사업자 번호 입력
- public var canSubmitBusinessNumber: Driver?
- public var businessNumberVO: Driver?
- public var businessNumberValidationFailure: Driver?
+ var canSubmitBusinessNumber: Driver?
+ var businessNumberVO: Driver?
+ var businessNumberValidationFailure: Driver?
// Id
- public var canCheckIdDuplication: Driver?
- public var idDuplicationValidation: Driver?
+ var idValidationResult: Driver = .empty()
+ var idDuplicationCheckResult: Driver = .empty()
// Password
- public var passwordValidation: Driver?
+ var passwordValidationState: Driver = .empty()
// Register success
- public var loginSuccess: Driver?
+ var loginSuccess: Driver?
}
}
@@ -169,9 +249,10 @@ extension CenterAccountRegisterViewModel {
// MARK: 최종 회원가입 버튼
let registerResult = input
.completeButtonClicked
- .flatMap { [unowned self] _ in
- self.authUseCase
- .registerCenterAccount(registerState: self.stateObject)
+ .unretained(self)
+ .flatMap { (vm, _) in
+ vm.authUseCase
+ .registerCenterAccount(registerState: vm.stateObject)
}
.share()
@@ -195,7 +276,7 @@ extension CenterAccountRegisterViewModel {
loginSuccess
.unretained(self)
.subscribe(onNext: { (obj, _) in
- obj.presentCompleteScreen()
+ obj.presentCompleteScreen?()
})
.disposed(by: disposeBag)
@@ -311,8 +392,6 @@ extension CenterAccountRegisterViewModel.Input: AuthBusinessOwnerInputable { }
extension CenterAccountRegisterViewModel.Output: AuthBusinessOwnerOutputable { }
// Id & Password
-extension CenterAccountRegisterViewModel.Input: SetIdInputable { }
-extension CenterAccountRegisterViewModel.Input: SetPasswordInputable { }
-extension CenterAccountRegisterViewModel.Output: SetIdOutputable { }
-extension CenterAccountRegisterViewModel.Output: SetPasswordOutputable { }
+extension CenterAccountRegisterViewModel.Input: SetIdAndPasswordInputable { }
+extension CenterAccountRegisterViewModel.Output: SetIdAndPasswordOutputable { }
diff --git a/project/Projects/Presentation/Feature/Auth/Sources/Center/AccountRegister/Model/asd.swift b/project/Projects/Presentation/Feature/Auth/Sources/Center/AccountRegister/Model/asd.swift
new file mode 100644
index 00000000..47f934a3
--- /dev/null
+++ b/project/Projects/Presentation/Feature/Auth/Sources/Center/AccountRegister/Model/asd.swift
@@ -0,0 +1,33 @@
+//
+// asd.swift
+// AuthFeature
+//
+// Created by choijunios on 10/22/24.
+//
+
+import Foundation
+
+enum PasswordValidationCase: Int, CaseIterable {
+ case characterCount
+ case alphabetAndNumberIncluded
+ case noEmptySpace
+ case unsuccessiveSame3words
+
+ var indicatorText: String {
+
+ switch self {
+ case .characterCount:
+ "8자~20자 사이"
+ case .alphabetAndNumberIncluded:
+ "영문자와 숫자 반드시 하나씩 포함"
+ case .noEmptySpace:
+ "공백 문자 사용 금지"
+ case .unsuccessiveSame3words:
+ "연속된 문자 3개 이상 사용 금지"
+ }
+ }
+
+ static var items: [PasswordValidationCase] {
+ PasswordValidationCase.allCases.sorted { $0.rawValue < $1.rawValue }
+ }
+}
diff --git a/project/Projects/Presentation/Feature/Auth/Sources/Center/AccountRegister/View/AuthInOutStreamManager+Signin.swift b/project/Projects/Presentation/Feature/Auth/Sources/Center/AccountRegister/View/AuthInOutStreamManager+Signin.swift
deleted file mode 100644
index 9a71a863..00000000
--- a/project/Projects/Presentation/Feature/Auth/Sources/Center/AccountRegister/View/AuthInOutStreamManager+Signin.swift
+++ /dev/null
@@ -1,100 +0,0 @@
-//
-// AuthInOutStreamManager.swift
-// AuthFeature
-//
-// Created by choijunios on 10/5/24.
-//
-
-import UIKit
-import PresentationCore
-import Domain
-import Core
-import BaseFeature
-
-
-import RxSwift
-import RxCocoa
-
-extension AuthInOutStreamManager {
-
- static func idInOut(
- input: SetIdInputable & AnyObject,
- output: SetIdOutputable & AnyObject,
- useCase: AuthInputValidationUseCase,
- stateTracker: @escaping (String) -> ()
- ) {
-
- var output = output
-
- // MARK: Id
- output.canCheckIdDuplication = input
- .editingId
- .map { [unowned useCase] id in
- useCase.checkIdIsValid(id: id)
- }
- .asDriver(onErrorJustReturn: false)
-
- // 중복성 검사
- let idDuplicationValidation = input
- .requestIdDuplicationValidation
- .flatMap { [useCase] id in
-
- printIfDebug("[CenterRegisterViewModel] 중복성 검사 대상 id: \(id)")
-
- #if DEBUG
- // 디버그시 아이디 중복체크 미실시
- print("✅ 디버그모드에서 아이디 중복검사 미실시")
- // ☑️ 상태추적 ☑️
- stateTracker(id)
- return Single.just(Result.success(()))
- #endif
-
- return useCase.requestCheckingIdDuplication(id: id)
- }
-
- output.idDuplicationValidation = Observable
- .combineLatest(idDuplicationValidation, input.requestIdDuplicationValidation)
- .map { [stateTracker] (result, id) in
- switch result {
- case .success:
- printIfDebug("[CenterRegisterViewModel] 중복체크 결과: ✅ 성공")
- // 🚀 상태추적 🚀
- stateTracker(id)
- return true
- case .failure(let error):
- printIfDebug("❌ 아이디중복검사 실패 \n 에러내용: \(error.message)")
- return false
- }
- }
- .asDriver(onErrorJustReturn: false)
- }
-
- static func passwordInOut(
- input: SetPasswordInputable & AnyObject,
- output: SetPasswordOutputable & AnyObject,
- useCase: AuthInputValidationUseCase,
- stateTracker: @escaping (String) -> ())
- {
- var output = output
- output.passwordValidation = input.editingPasswords
- .filter { (pwd, cpwd) in !pwd.isEmpty && !cpwd.isEmpty }
- .map { [unowned useCase] (pwd, cpwd) in
-
- printIfDebug("[CenterRegisterViewModel] \n 입력중인 비밀번호: \(pwd) \n 확인 비밀번호: \(cpwd)")
-
- let isValid = useCase.checkPasswordIsValid(password: pwd)
- if !isValid {
- printIfDebug("❌ 비밀번호가 유효하지 않습니다.")
- return PasswordValidationState.invalidPassword
- } else if pwd != cpwd {
- printIfDebug("☑️ 비밀번호가 일치하지 않습니다.")
- return PasswordValidationState.unMatch
- } else {
- printIfDebug("✅ 비밀번호가 일치합니다.")
- stateTracker(pwd)
- return PasswordValidationState.match
- }
- }
- .asDriver(onErrorJustReturn: .invalidPassword)
- }
-}
diff --git a/project/Projects/Presentation/Feature/Auth/Sources/Center/AccountRegister/View/Component/ValidationIndicator.swift b/project/Projects/Presentation/Feature/Auth/Sources/Center/AccountRegister/View/Component/ValidationIndicator.swift
new file mode 100644
index 00000000..03288065
--- /dev/null
+++ b/project/Projects/Presentation/Feature/Auth/Sources/Center/AccountRegister/View/Component/ValidationIndicator.swift
@@ -0,0 +1,77 @@
+//
+// ValidationIndicator.swift
+// AuthFeature
+//
+// Created by choijunios on 10/22/24.
+//
+
+import Foundation
+import UIKit
+
+import DSKit
+
+class ValidationIndicator: UIView {
+
+ enum State {
+ case valid
+ case invalid
+ }
+
+ // View
+ let iconView: UIImageView = {
+ let view: UIImageView = .init()
+ return view
+ }()
+
+ let label: IdleLabel = {
+ let label: IdleLabel = .init(typography: .Body3)
+ return label
+ }()
+
+ init(labelText: String) {
+
+ self.label.textString = labelText
+
+ super.init(frame: .zero)
+
+ setLayout()
+ }
+ required init?(coder: NSCoder) { nil }
+
+ private func setLayout() {
+
+ let mainStack: HStack = HStack(
+ [iconView, label, Spacer()],
+ spacing: 4,
+ alignment: .center,
+ distribution: .fill
+ )
+
+ self.addSubview(mainStack)
+ mainStack.translatesAutoresizingMaskIntoConstraints = false
+
+ NSLayoutConstraint.activate([
+
+ iconView.heightAnchor.constraint(equalToConstant: 24),
+ iconView.widthAnchor.constraint(equalTo: iconView.heightAnchor),
+
+ mainStack.topAnchor.constraint(equalTo: self.topAnchor),
+ mainStack.leftAnchor.constraint(equalTo: self.leftAnchor),
+ mainStack.rightAnchor.constraint(equalTo: self.rightAnchor),
+ mainStack.bottomAnchor.constraint(equalTo: self.bottomAnchor),
+ ])
+ }
+
+ func setState(_ state: State, animated: Bool = false) {
+
+ let animateDuration: TimeInterval = animated ? 0.2 : 0
+
+ UIView.transition(with: self, duration: animateDuration, options: .transitionCrossDissolve) {
+ self.iconView.image = state == .valid ? AuthFeatureAsset.vsMark.image : AuthFeatureAsset.vfMark.image
+ }
+
+ UIView.animate(withDuration: animateDuration) {
+ self.label.attrTextColor = state == .valid ? DSColor.green.color : DSColor.red200.color
+ }
+ }
+}
diff --git a/project/Projects/Presentation/Feature/Auth/Sources/Center/AccountRegister/View/SetIdPasswordViewController.swift b/project/Projects/Presentation/Feature/Auth/Sources/Center/AccountRegister/View/SetIdPasswordViewController.swift
index 1fdedbd8..b7f9aa9d 100644
--- a/project/Projects/Presentation/Feature/Auth/Sources/Center/AccountRegister/View/SetIdPasswordViewController.swift
+++ b/project/Projects/Presentation/Feature/Auth/Sources/Center/AccountRegister/View/SetIdPasswordViewController.swift
@@ -15,32 +15,34 @@ import BaseFeature
import RxSwift
import RxCocoa
-
-public protocol SetIdInputable {
- var editingId: BehaviorRelay { get set }
- var requestIdDuplicationValidation: PublishRelay { get set }
-}
-
-public protocol SetIdOutputable {
- var canCheckIdDuplication: Driver? { get set }
- var idDuplicationValidation: Driver? { get set }
-}
-
-public protocol SetPasswordInputable {
- var editingPasswords: PublishRelay<(pwd: String, cpwd: String)> { get set }
+protocol SetIdAndPasswordInputable {
+
+ // Id
+ var editingId: PublishSubject { get set }
+ var isIdDuplicatedButtonPressed: PublishSubject { get set }
+
+ // Password
+ var editingPassword: PublishSubject { get set }
+ var checkingPassword: BehaviorSubject { get set }
}
-public protocol SetPasswordOutputable {
- var passwordValidation: Driver? { get set }
+protocol SetIdAndPasswordOutputable {
+
+ // Id
+ var idValidationResult: Driver { get }
+ var idDuplicationCheckResult: Driver { get }
+
+ // Password
+ var passwordValidationState: Driver { get }
}
class SetIdPasswordViewController: BaseViewController
-where T.Input: SetIdInputable & SetPasswordInputable & PageProcessInputable,
- T.Output: SetIdOutputable & SetPasswordOutputable, T: BaseViewModel {
+where T.Input: SetIdAndPasswordInputable & PageProcessInputable,
+ T.Output: SetIdAndPasswordOutputable, T: BaseViewModel {
// View
- private let processTitleLabel: IdleLabel = {
+ let processTitleLabel: IdleLabel = {
let label = IdleLabel(typography: .Heading2)
label.textString = "아이디와 비밀번호를 설정해주세요."
label.textAlignment = .left
@@ -48,44 +50,39 @@ where T.Input: SetIdInputable & SetPasswordInputable & PageProcessInputable,
}()
// MARK: Id 입력
- private let idLabel: IdleLabel = {
+ let idLabel: IdleLabel = {
let label = IdleLabel(typography: .Subtitle4)
label.textString = "아이디 설정"
label.attrTextColor = DSColor.gray500.color
label.textAlignment = .left
return label
}()
- private let idField: IFType1 = {
-
- let textField = IFType1(
- placeHolderText: "아이디를 입력해주세요",
- submitButtonText: "중복 확인"
- )
-
+ let idField: IFType1 = {
+ let textField = IFType1(placeHolderText: "아이디를 입력해주세요", submitButtonText: "중복 확인")
textField.idleTextField.isCompleteImageAvailable = false
-
return textField
}()
- private let thisIsValidIdLabel: ResizableUILabel = {
-
- let label = ResizableUILabel()
- label.font = DSKitFontFamily.Pretendard.semiBold.font(size: 12)
- label.text = "사용 가능한 아이디입니다."
- label.textColor = DSKitAsset.Colors.gray300.color
+ let idGuideLabel: IdleLabel = {
+ let label = IdleLabel(typography: .Body3)
+ label.textString = "* 아이디는 아래의 조건에 맞추어주세요."
+ label.attrTextColor = DSColor.gray500.color
label.textAlignment = .left
-
return label
}()
+ // MARK: 아이디 검증 라벨
+ let idValidationIndicator: ValidationIndicator = .init(labelText: "6자~20자 사이")
+
+
// MARK: 비밀번호 입력
- private let passwordLabel: IdleLabel = {
+ let passwordLabel: IdleLabel = {
let label = IdleLabel(typography: .Subtitle4)
label.textString = "비밀번호 설정"
label.attrTextColor = DSColor.gray500.color
label.textAlignment = .left
return label
}()
- private let passwordField: IdleOneLineInputField = {
+ let passwordField: IdleOneLineInputField = {
let textField = IdleOneLineInputField(
placeHolderText: "비밀번호를 입력해주세요."
@@ -93,26 +90,32 @@ where T.Input: SetIdInputable & SetPasswordInputable & PageProcessInputable,
return textField
}()
- private let thisIsValidPasswordLabel: ResizableUILabel = {
-
- let label = ResizableUILabel()
- label.font = DSKitFontFamily.Pretendard.semiBold.font(size: 12)
- label.text = "사용 가능한 비밀번호입니다."
- label.textColor = DSKitAsset.Colors.gray300.color
+ let passwordGuideLabel: IdleLabel = {
+ let label = IdleLabel(typography: .Body3)
+ label.textString = "* 비밀번호는 아래의 조건에 맞추어주세요."
+ label.attrTextColor = DSColor.gray500.color
label.textAlignment = .left
-
return label
}()
+ // MARK: 비밀번호 검증 라벨
+ let passwordValidationIndicator: [PasswordValidationCase: ValidationIndicator] = {
+ var dict: [PasswordValidationCase: ValidationIndicator] = [:]
+ for item in PasswordValidationCase.items {
+ dict[item] = ValidationIndicator(labelText: item.indicatorText)
+ }
+ return dict
+ }()
+
// MARK: 비밀번호 확인 입력
- private let checlPasswordLabel: IdleLabel = {
+ let checkPasswordLabel: IdleLabel = {
let label = IdleLabel(typography: .Subtitle4)
label.textString = "비밀번호 확인"
label.attrTextColor = DSColor.gray500.color
label.textAlignment = .left
return label
}()
- private let checkPasswordField: IdleOneLineInputField = {
+ let checkPasswordField: IdleOneLineInputField = {
let textField = IdleOneLineInputField(
placeHolderText: "비밀번호를 한번 더 입력해주세요."
@@ -121,13 +124,13 @@ where T.Input: SetIdInputable & SetPasswordInputable & PageProcessInputable,
return textField
}()
- private let buttonContainer: PrevOrNextContainer = {
+ let buttonContainer: PrevOrNextContainer = {
let button = PrevOrNextContainer()
button.nextButton.label.textString = "완료"
return button
}()
- public init(viewModel: T) {
+ init(viewModel: T) {
super.init(nibName: nil, bundle: nil)
@@ -141,7 +144,7 @@ where T.Input: SetIdInputable & SetPasswordInputable & PageProcessInputable,
required init?(coder: NSCoder) { fatalError() }
- public override func viewDidLoad() {
+ override func viewDidLoad() {
view.backgroundColor = .clear
}
@@ -149,66 +152,120 @@ where T.Input: SetIdInputable & SetPasswordInputable & PageProcessInputable,
private func setAppearance() { }
private func setAutoLayout() {
+
+ // pw validation indicators
+
+ let pwValidationIndicators: VStack = VStack(
+ PasswordValidationCase.items.compactMap { item in passwordValidationIndicator[item] },
+ spacing: 4,
+ alignment: .fill
+ )
+
+ let scrollView: UIScrollView = .init()
+ let scrollView_contentGuide = scrollView.contentLayoutGuide
+ let scrollView_frameGuide = scrollView.frameLayoutGuide
+ let contentView: UIView = .init()
+
+ scrollView.addSubview(contentView)
+ contentView.translatesAutoresizingMaskIntoConstraints = false
+
+ NSLayoutConstraint.activate([
+
+ contentView.widthAnchor.constraint(equalTo: scrollView_frameGuide.widthAnchor),
+
+ contentView.topAnchor.constraint(equalTo: scrollView_contentGuide.topAnchor),
+ contentView.leftAnchor.constraint(equalTo: scrollView_contentGuide.leftAnchor),
+ contentView.rightAnchor.constraint(equalTo: scrollView_contentGuide.rightAnchor),
+ contentView.bottomAnchor.constraint(equalTo: scrollView_contentGuide.bottomAnchor),
+ ])
- view.layoutMargins = .init(top: 28, left: 20, bottom: 0, right: 20)
+ contentView.layoutMargins = .init(top: 28,left: 20, bottom: 48, right: 20)
[
processTitleLabel,
idLabel,
idField,
- thisIsValidIdLabel,
+ idGuideLabel,
+ idValidationIndicator,
passwordLabel,
passwordField,
- thisIsValidPasswordLabel,
- checlPasswordLabel,
+ passwordGuideLabel,
+ pwValidationIndicators,
+ checkPasswordLabel,
checkPasswordField,
- buttonContainer,
].forEach {
- view.addSubview($0)
$0.translatesAutoresizingMaskIntoConstraints = false
+ contentView.addSubview($0)
}
NSLayoutConstraint.activate([
-
- processTitleLabel.topAnchor.constraint(equalTo: view.layoutMarginsGuide.topAnchor),
- processTitleLabel.leadingAnchor.constraint(equalTo: view.layoutMarginsGuide.leadingAnchor),
- processTitleLabel.trailingAnchor.constraint(equalTo: view.layoutMarginsGuide.trailingAnchor),
- idLabel.topAnchor.constraint(equalTo: processTitleLabel.bottomAnchor, constant: 32),
- idLabel.leadingAnchor.constraint(equalTo: view.layoutMarginsGuide.leadingAnchor),
- idLabel.trailingAnchor.constraint(equalTo: view.layoutMarginsGuide.trailingAnchor),
+ processTitleLabel.topAnchor.constraint(equalTo: contentView.layoutMarginsGuide.topAnchor),
+ processTitleLabel.leadingAnchor.constraint(equalTo: contentView.layoutMarginsGuide.leadingAnchor),
+ processTitleLabel.trailingAnchor.constraint(equalTo: contentView.layoutMarginsGuide.trailingAnchor),
+
+ idLabel.topAnchor.constraint(equalTo: processTitleLabel.layoutMarginsGuide.topAnchor, constant: 28),
+ idLabel.leadingAnchor.constraint(equalTo: contentView.layoutMarginsGuide.leadingAnchor),
+ idLabel.trailingAnchor.constraint(equalTo: contentView.layoutMarginsGuide.trailingAnchor),
idField.topAnchor.constraint(equalTo: idLabel.bottomAnchor, constant: 4),
- idField.leadingAnchor.constraint(equalTo: view.layoutMarginsGuide.leadingAnchor),
- idField.trailingAnchor.constraint(equalTo: view.layoutMarginsGuide.trailingAnchor),
+ idField.leadingAnchor.constraint(equalTo: contentView.layoutMarginsGuide.leadingAnchor),
+ idField.trailingAnchor.constraint(equalTo: contentView.layoutMarginsGuide.trailingAnchor),
+
+ idGuideLabel.topAnchor.constraint(equalTo: idField.bottomAnchor, constant: 12),
+ idGuideLabel.leadingAnchor.constraint(equalTo: contentView.layoutMarginsGuide.leadingAnchor),
+ idGuideLabel.trailingAnchor.constraint(equalTo: contentView.layoutMarginsGuide.trailingAnchor),
- thisIsValidIdLabel.topAnchor.constraint(equalTo: idField.bottomAnchor, constant: 6),
- thisIsValidIdLabel.leadingAnchor.constraint(equalTo: view.layoutMarginsGuide.leadingAnchor),
- thisIsValidIdLabel.trailingAnchor.constraint(equalTo: view.layoutMarginsGuide.trailingAnchor),
+ idValidationIndicator.topAnchor.constraint(equalTo: idGuideLabel.bottomAnchor, constant: 6),
+ idValidationIndicator.leadingAnchor.constraint(equalTo: contentView.layoutMarginsGuide.leadingAnchor),
+ idValidationIndicator.trailingAnchor.constraint(equalTo: contentView.layoutMarginsGuide.trailingAnchor),
- passwordLabel.topAnchor.constraint(equalTo: idField.bottomAnchor, constant: 32),
- passwordLabel.leadingAnchor.constraint(equalTo: view.layoutMarginsGuide.leadingAnchor),
- passwordLabel.trailingAnchor.constraint(equalTo: view.layoutMarginsGuide.trailingAnchor),
+ passwordLabel.topAnchor.constraint(equalTo: idValidationIndicator.bottomAnchor, constant: 24),
+ passwordLabel.leadingAnchor.constraint(equalTo: contentView.layoutMarginsGuide.leadingAnchor),
+ passwordLabel.trailingAnchor.constraint(equalTo: contentView.layoutMarginsGuide.trailingAnchor),
passwordField.topAnchor.constraint(equalTo: passwordLabel.bottomAnchor, constant: 6),
- passwordField.leadingAnchor.constraint(equalTo: view.layoutMarginsGuide.leadingAnchor),
- passwordField.trailingAnchor.constraint(equalTo: view.layoutMarginsGuide.trailingAnchor),
+ passwordField.leadingAnchor.constraint(equalTo: contentView.layoutMarginsGuide.leadingAnchor),
+ passwordField.trailingAnchor.constraint(equalTo: contentView.layoutMarginsGuide.trailingAnchor),
- thisIsValidPasswordLabel.topAnchor.constraint(equalTo: passwordField.bottomAnchor, constant: 6),
- thisIsValidPasswordLabel.leadingAnchor.constraint(equalTo: view.layoutMarginsGuide.leadingAnchor),
- thisIsValidPasswordLabel.trailingAnchor.constraint(equalTo: view.layoutMarginsGuide.trailingAnchor),
+ passwordGuideLabel.topAnchor.constraint(equalTo: passwordField.bottomAnchor, constant: 12),
+ passwordGuideLabel.leadingAnchor.constraint(equalTo: contentView.layoutMarginsGuide.leadingAnchor),
+ passwordGuideLabel.trailingAnchor.constraint(equalTo: contentView.layoutMarginsGuide.trailingAnchor),
+
+ pwValidationIndicators.topAnchor.constraint(equalTo: passwordGuideLabel.bottomAnchor, constant: 6),
+ pwValidationIndicators.leadingAnchor.constraint(equalTo: contentView.layoutMarginsGuide.leadingAnchor),
+ pwValidationIndicators.trailingAnchor.constraint(equalTo: contentView.layoutMarginsGuide.trailingAnchor),
+
+ checkPasswordLabel.topAnchor.constraint(equalTo: pwValidationIndicators.bottomAnchor, constant: 24),
+ checkPasswordLabel.leadingAnchor.constraint(equalTo: contentView.layoutMarginsGuide.leadingAnchor),
+ checkPasswordLabel.trailingAnchor.constraint(equalTo: contentView.layoutMarginsGuide.trailingAnchor),
+
+ checkPasswordField.topAnchor.constraint(equalTo: checkPasswordLabel.bottomAnchor, constant: 6),
+ checkPasswordField.leadingAnchor.constraint(equalTo: contentView.layoutMarginsGuide.leadingAnchor),
+ checkPasswordField.trailingAnchor.constraint(equalTo: contentView.layoutMarginsGuide.trailingAnchor),
+ checkPasswordField.bottomAnchor.constraint(equalTo: contentView.layoutMarginsGuide.bottomAnchor)
+ ])
+
+
+ [
+ scrollView,
- checlPasswordLabel.topAnchor.constraint(equalTo: passwordField.bottomAnchor, constant: 32),
- checlPasswordLabel.leadingAnchor.constraint(equalTo: view.layoutMarginsGuide.leadingAnchor),
- checlPasswordLabel.trailingAnchor.constraint(equalTo: view.layoutMarginsGuide.trailingAnchor),
+ buttonContainer,
+ ].forEach {
+ view.addSubview($0)
+ $0.translatesAutoresizingMaskIntoConstraints = false
+ }
+
+ NSLayoutConstraint.activate([
- checkPasswordField.topAnchor.constraint(equalTo: checlPasswordLabel.bottomAnchor, constant: 6),
- checkPasswordField.leadingAnchor.constraint(equalTo: view.layoutMarginsGuide.leadingAnchor),
- checkPasswordField.trailingAnchor.constraint(equalTo: view.layoutMarginsGuide.trailingAnchor),
+ scrollView.topAnchor.constraint(equalTo: view.layoutMarginsGuide.topAnchor),
+ scrollView.leadingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.leadingAnchor),
+ scrollView.trailingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.trailingAnchor),
+ scrollView.bottomAnchor.constraint(equalTo: buttonContainer.topAnchor, constant: -12),
buttonContainer.bottomAnchor.constraint(equalTo: view.safeAreaLayoutGuide.bottomAnchor, constant: -14),
- buttonContainer.leftAnchor.constraint(equalTo: view.layoutMarginsGuide.leftAnchor),
- buttonContainer.rightAnchor.constraint(equalTo: view.layoutMarginsGuide.rightAnchor),
+ buttonContainer.leadingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.leadingAnchor, constant: 20),
+ buttonContainer.trailingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.trailingAnchor, constant: -20),
])
}
@@ -216,8 +273,10 @@ where T.Input: SetIdInputable & SetPasswordInputable & PageProcessInputable,
idField.button.setEnabled(false)
- thisIsValidIdLabel.isHidden = true
- thisIsValidPasswordLabel.isHidden = true
+ idValidationIndicator.setState(.invalid)
+ passwordValidationIndicator.values.forEach { indicator in
+ indicator.setState(.invalid)
+ }
// - CTA버튼 비활성화
buttonContainer.nextButton.setEnabled(false)
@@ -230,21 +289,32 @@ where T.Input: SetIdInputable & SetPasswordInputable & PageProcessInputable,
// MARK: Input
let input = viewModel.input
- // 현재 입력중인 정보 전송
+
+ // Id
idField.idleTextField.textField.rx.text
.compactMap{ $0 }
.bind(to: input.editingId)
.disposed(by: disposeBag)
- Observable
- .combineLatest(
- passwordField.eventPublisher,
- checkPasswordField.eventPublisher
- )
- .map({ ($0, $1) })
- .bind(to: input.editingPasswords)
+ idField.button.eventPublisher
+ .mapToVoid()
+ .bind(to: input.isIdDuplicatedButtonPressed)
+ .disposed(by: disposeBag)
+
+ // Password
+ passwordField
+ .textField.rx.text
+ .compactMap{ $0 }
+ .bind(to: input.editingPassword)
.disposed(by: disposeBag)
+ checkPasswordField
+ .textField.rx.text
+ .compactMap{ $0 }
+ .bind(to: input.checkingPassword)
+ .disposed(by: disposeBag)
+
+ // Navigation
buttonContainer.nextBtnClicked
.asObservable()
.bind(to: input.completeButtonClicked)
@@ -255,63 +325,84 @@ where T.Input: SetIdInputable & SetPasswordInputable & PageProcessInputable,
.bind(to: input.prevButtonClicked)
.disposed(by: disposeBag)
- // id 중복확인 요청 버튼
- idField.eventPublisher
- .map { [weak self] in
- // 증복검사 실행시 아이디 입력 필드 비활성화
- self?.idField.idleTextField.setEnabled(false)
- self?.idField.button.setEnabled(false)
- return $0
- }
- .bind(to: input.requestIdDuplicationValidation)
- .disposed(by: disposeBag)
// MARK: Output
let output = viewModel.output
// 중복확인이 가능한 아이디인가?
output
- .canCheckIdDuplication?
- .drive(onNext: { [weak self] in
- self?.idField.button.setEnabled($0)
+ .idValidationResult
+ .drive(onNext: { [weak self] isValid in
+
+ guard let self else { return }
+
+ // 검증 라벨 색상변경
+ idValidationIndicator.setState(isValid ? .valid : .invalid)
+
+ // 중복확인버튼 활성화
+ idField.button.setEnabled(isValid)
})
.disposed(by: disposeBag)
- // 아이디 중복확인 결과
- let idDuplicationValidation = output
- .idDuplicationValidation?
- .map { [weak self] isSuccess in
-
- self?.idField.idleTextField.setEnabled(true)
+ let idDuplicationResult = output
+ .idDuplicationCheckResult
+ .asObservable()
+ .share()
+
+ idDuplicationResult
+ .subscribe(onNext: { [weak self] isValid in
- if !isSuccess {
- self?.idField.idleTextField.textField.textString = ""
- self?.showAlert(vo: .init(
- title: "사용불가한 아이디",
- message: "다른 아이디를 사용해주세요.")
- )
- }
+ guard let self else { return }
- return isSuccess
- }
- .asObservable() ?? .empty()
+ // 비밀번호 필드 활성화
+ passwordField.setEnabled(isValid)
+ checkPasswordField.setEnabled(isValid)
+ })
+ .disposed(by: disposeBag)
- // 비밀번호 검증 결과
let passwordValidationResult = output
- .passwordValidation?
- .map { state in
- state == .match
- }
- .asObservable() ?? .empty()
+ .passwordValidationState
+ .asObservable()
+ .share()
+
+ passwordValidationResult
+ .subscribe(onNext: {
+ [weak self] state in
+
+ guard let self else { return }
+
+ // 비밀번호 체킹 상태 업데이트
+ passwordValidationIndicator[.characterCount]?.setState(
+ state.characterCount == .valid ? .valid : .invalid
+ )
+ passwordValidationIndicator[.alphabetAndNumberIncluded]?.setState(
+ state.alphabetAndNumberIncluded == .valid ? .valid : .invalid
+ )
+ passwordValidationIndicator[.noEmptySpace]?.setState(
+ state.noEmptySpace == .valid ? .valid : .invalid
+ )
+ passwordValidationIndicator[.unsuccessiveSame3words]?.setState(
+ state.unsuccessiveSame3words == .valid ? .valid : .invalid
+ )
+ })
+ .disposed(by: disposeBag)
+
// id, password 유효성 검사
Observable
.combineLatest(
- idDuplicationValidation,
+ idDuplicationResult,
passwordValidationResult
)
- .map { $0 && $1 }
- .subscribe(onNext: { [weak self] in self?.buttonContainer.nextButton.setEnabled($0) })
+ .map { idIsValid, passwordCheckingState in
+ idIsValid && passwordCheckingState.isValid
+ }
+ .subscribe(onNext: { [weak self] isValid in
+
+ guard let self else { return }
+
+ buttonContainer.nextButton.setEnabled(isValid)
+ })
.disposed(by: disposeBag)
}
diff --git a/project/Projects/Presentation/Feature/Auth/Sources/Center/SetNewPassword/CenterSetupNewPasswordViewModel.swift b/project/Projects/Presentation/Feature/Auth/Sources/Center/SetNewPassword/CenterSetupNewPasswordViewModel.swift
index 672a557e..7a7bcf38 100644
--- a/project/Projects/Presentation/Feature/Auth/Sources/Center/SetNewPassword/CenterSetupNewPasswordViewModel.swift
+++ b/project/Projects/Presentation/Feature/Auth/Sources/Center/SetNewPassword/CenterSetupNewPasswordViewModel.swift
@@ -36,13 +36,7 @@ class CenterSetupNewPasswordViewModel: BaseViewModel, ViewModelType {
super.init()
// 비밀번호
- AuthInOutStreamManager.passwordInOut(
- input: input,
- output: output,
- useCase: inputValidationUseCase) { [weak self] validPassword in
- // 🚀 상태추적 🚀
- self?.validPassword = validPassword
- }
+ passwordValidationBinding()
// 휴대전화 인증
AuthInOutStreamManager.validatePhoneNumberInOut(
@@ -108,6 +102,37 @@ class CenterSetupNewPasswordViewModel: BaseViewModel, ViewModelType {
}
}
+extension CenterSetupNewPasswordViewModel {
+
+ func passwordValidationBinding() {
+
+ // Passwords
+ output.passwordValidationState = Observable
+ .combineLatest(
+ input.editingPassword,
+ input.checkingPassword
+ )
+ .unretained(self)
+ .map { (vm, passwords) in
+
+ let (editing, checking) = passwords
+
+ let stateObject: PasswordValidationState = vm.inputValidationUseCase
+ .checkPasswordIsValid(password: editing)
+
+ stateObject.setEqualState(state: editing == checking)
+
+ // 가장 최근 비밀번호 저장
+ vm.validPassword = editing
+
+ printIfDebug(stateObject.description)
+
+ return stateObject
+ }
+ .asDriver(onErrorDriveWith: .never())
+ }
+}
+
extension CenterSetupNewPasswordViewModel {
class Input {
@@ -124,7 +149,8 @@ extension CenterSetupNewPasswordViewModel {
public var requestValidationForAuthNumber: PublishRelay = .init()
// Password
- public var editingPasswords: PublishRelay<(pwd: String, cpwd: String)> = .init()
+ var editingPassword: PublishSubject = .init()
+ var checkingPassword: BehaviorSubject = .init(value: "")
// Change password
public var changePasswordButtonClicked: PublishRelay = .init()
@@ -142,7 +168,7 @@ extension CenterSetupNewPasswordViewModel {
public var authNumberValidation: Driver?
// Password
- public var passwordValidation: Driver?
+ var passwordValidationState: Driver = .empty()
public var loginSuccess: Driver?
}
@@ -152,8 +178,7 @@ extension CenterSetupNewPasswordViewModel {
extension CenterSetupNewPasswordViewModel.Input: AuthPhoneNumberInputable { }
extension CenterSetupNewPasswordViewModel.Output: AuthPhoneNumberOutputable { }
-extension CenterSetupNewPasswordViewModel.Input: SetPasswordInputable { }
-extension CenterSetupNewPasswordViewModel.Output: SetPasswordOutputable { }
-
extension CenterSetupNewPasswordViewModel.Input: ChangePasswordSuccessInputable { }
+extension CenterSetupNewPasswordViewModel.Output: ChangePasswordSuccessOutputable { }
+
extension CenterSetupNewPasswordViewModel.Input: PageProcessInputable { }
diff --git a/project/Projects/Presentation/Feature/Auth/Sources/Center/SetNewPassword/View/ValidateNewPasswordViewController.swift b/project/Projects/Presentation/Feature/Auth/Sources/Center/SetNewPassword/View/ValidateNewPasswordViewController.swift
index b215375b..8eb78f20 100644
--- a/project/Projects/Presentation/Feature/Auth/Sources/Center/SetNewPassword/View/ValidateNewPasswordViewController.swift
+++ b/project/Projects/Presentation/Feature/Auth/Sources/Center/SetNewPassword/View/ValidateNewPasswordViewController.swift
@@ -6,20 +6,27 @@
//
import UIKit
+
+import BaseFeature
import DSKit
+import PresentationCore
+import Domain
+
import RxCocoa
import RxSwift
-import PresentationCore
-import BaseFeature
-public protocol ChangePasswordSuccessInputable {
+protocol ChangePasswordSuccessInputable {
+ var editingPassword: PublishSubject { get set }
+ var checkingPassword: BehaviorSubject { get set }
var changePasswordButtonClicked: PublishRelay { get }
}
+protocol ChangePasswordSuccessOutputable {
+ var passwordValidationState: Driver { get }
+}
class ValidateNewPasswordViewController: UIViewController
-where T.Input: SetPasswordInputable & ChangePasswordSuccessInputable,
- T.Output: SetPasswordOutputable {
+where T.Input: ChangePasswordSuccessInputable, T.Output: ChangePasswordSuccessOutputable {
let viewModel: T
@@ -45,17 +52,25 @@ where T.Input: SetPasswordInputable & ChangePasswordSuccessInputable,
)
return textField
}()
- private let thisIsValidPasswordLabel: IdleLabel = {
- let label = IdleLabel(typography: .caption)
- label.textString = "* 사용 가능한 비밀번호입니다."
- label.attrTextColor = DSKitAsset.Colors.gray300.color
+ let passwordGuideLabel: IdleLabel = {
+ let label = IdleLabel(typography: .Body3)
+ label.textString = "* 비밀번호는 아래의 조건에 맞추어주세요."
+ label.attrTextColor = DSColor.gray500.color
label.textAlignment = .left
- label.alpha = 0
return label
}()
+ // MARK: 비밀번호 검증 라벨
+ let passwordValidationIndicator: [PasswordValidationCase: ValidationIndicator] = {
+ var dict: [PasswordValidationCase: ValidationIndicator] = [:]
+ for item in PasswordValidationCase.items {
+ dict[item] = ValidationIndicator(labelText: item.indicatorText)
+ }
+ return dict
+ }()
+
// MARK: 비밀번호 확인 입력
- private let checlPasswordLabel: IdleLabel = {
+ private let checkPasswordLabel: IdleLabel = {
let label = IdleLabel(typography: .Subtitle4)
label.textString = "비밀번호 확인"
label.attrTextColor = DSKitAsset.Colors.gray500.color
@@ -63,11 +78,7 @@ where T.Input: SetPasswordInputable & ChangePasswordSuccessInputable,
return label
}()
private let checkPasswordField: IdleOneLineInputField = {
-
- let textField = IdleOneLineInputField(
- placeHolderText: "비밀번호를 한번 더 입력해주세요."
- )
-
+ let textField = IdleOneLineInputField(placeHolderText: "비밀번호를 한번 더 입력해주세요.")
return textField
}()
private let passwordDoesntMathLabel: IdleLabel = {
@@ -112,12 +123,21 @@ where T.Input: SetPasswordInputable & ChangePasswordSuccessInputable,
private func setAutoLayout() {
+ // pw validation indicators
+
+ let pwValidationIndicators: VStack = VStack(
+ PasswordValidationCase.items.compactMap { item in passwordValidationIndicator[item] },
+ spacing: 4,
+ alignment: .fill
+ )
+
[
processTitle,
passwordLabel,
passwordField,
- thisIsValidPasswordLabel,
- checlPasswordLabel,
+ passwordGuideLabel,
+ pwValidationIndicators,
+ checkPasswordLabel,
checkPasswordField,
passwordDoesntMathLabel,
ctaButton,
@@ -140,15 +160,19 @@ where T.Input: SetPasswordInputable & ChangePasswordSuccessInputable,
passwordField.leadingAnchor.constraint(equalTo: view.layoutMarginsGuide.leadingAnchor),
passwordField.trailingAnchor.constraint(equalTo: view.layoutMarginsGuide.trailingAnchor),
- thisIsValidPasswordLabel.topAnchor.constraint(equalTo: passwordField.bottomAnchor, constant: 2),
- thisIsValidPasswordLabel.leadingAnchor.constraint(equalTo: view.layoutMarginsGuide.leadingAnchor),
- thisIsValidPasswordLabel.trailingAnchor.constraint(equalTo: view.layoutMarginsGuide.trailingAnchor),
+ passwordGuideLabel.topAnchor.constraint(equalTo: passwordField.bottomAnchor, constant: 12),
+ passwordGuideLabel.leadingAnchor.constraint(equalTo: view.layoutMarginsGuide.leadingAnchor),
+ passwordGuideLabel.trailingAnchor.constraint(equalTo: view.layoutMarginsGuide.trailingAnchor),
- checlPasswordLabel.topAnchor.constraint(equalTo: thisIsValidPasswordLabel.bottomAnchor, constant: 12),
- checlPasswordLabel.leadingAnchor.constraint(equalTo: view.layoutMarginsGuide.leadingAnchor),
- checlPasswordLabel.trailingAnchor.constraint(equalTo: view.layoutMarginsGuide.trailingAnchor),
+ pwValidationIndicators.topAnchor.constraint(equalTo: passwordGuideLabel.bottomAnchor, constant: 6),
+ pwValidationIndicators.leadingAnchor.constraint(equalTo: view.layoutMarginsGuide.leadingAnchor),
+ pwValidationIndicators.trailingAnchor.constraint(equalTo: view.layoutMarginsGuide.trailingAnchor),
- checkPasswordField.topAnchor.constraint(equalTo: checlPasswordLabel.bottomAnchor, constant: 6),
+ checkPasswordLabel.topAnchor.constraint(equalTo: pwValidationIndicators.bottomAnchor, constant: 12),
+ checkPasswordLabel.leadingAnchor.constraint(equalTo: view.layoutMarginsGuide.leadingAnchor),
+ checkPasswordLabel.trailingAnchor.constraint(equalTo: view.layoutMarginsGuide.trailingAnchor),
+
+ checkPasswordField.topAnchor.constraint(equalTo: checkPasswordLabel.bottomAnchor, constant: 6),
checkPasswordField.leadingAnchor.constraint(equalTo: view.layoutMarginsGuide.leadingAnchor),
checkPasswordField.trailingAnchor.constraint(equalTo: view.layoutMarginsGuide.trailingAnchor),
@@ -163,6 +187,11 @@ where T.Input: SetPasswordInputable & ChangePasswordSuccessInputable,
}
private func initialUISettuing() {
+
+ passwordValidationIndicator.values.forEach { indicator in
+ indicator.setState(.invalid)
+ }
+
// - CTA버튼 비활성화
ctaButton.setEnabled(false)
}
@@ -172,13 +201,17 @@ where T.Input: SetPasswordInputable & ChangePasswordSuccessInputable,
// MARK: Input
let input = viewModel.input
- Observable
- .combineLatest(
- passwordField.eventPublisher,
- checkPasswordField.eventPublisher
- )
- .map({ ($0, $1) })
- .bind(to: input.editingPasswords)
+ // Password
+ passwordField
+ .textField.rx.text
+ .compactMap{ $0 }
+ .bind(to: input.editingPassword)
+ .disposed(by: disposeBag)
+
+ checkPasswordField
+ .textField.rx.text
+ .compactMap{ $0 }
+ .bind(to: input.checkingPassword)
.disposed(by: disposeBag)
ctaButton
@@ -193,40 +226,29 @@ where T.Input: SetPasswordInputable & ChangePasswordSuccessInputable,
// MARK: Output
let output = viewModel.output
- // 비밀번호 검증
output
- .passwordValidation?
- .drive(onNext: { [weak self] validationState in
+ .passwordValidationState
+ .drive(onNext: { [weak self] state in
guard let self else { return }
- switch validationState {
- case .invalidPassword:
- thisIsValidPasswordLabel.alpha = 0
- onPasswordUnMatched()
- case .unMatch:
- thisIsValidPasswordLabel.alpha = 1
- passwordDoesntMathLabel.alpha = 1
- onPasswordUnMatched()
- case .match:
- thisIsValidPasswordLabel.alpha = 1
- passwordDoesntMathLabel.alpha = 0
- onPasswordMatched()
- ctaButton.setEnabled(true)
- }
+ // 비밀번호 체킹 상태 업데이트
+ passwordValidationIndicator[.characterCount]?.setState(
+ state.characterCount == .valid ? .valid : .invalid
+ )
+ passwordValidationIndicator[.alphabetAndNumberIncluded]?.setState(
+ state.alphabetAndNumberIncluded == .valid ? .valid : .invalid
+ )
+ passwordValidationIndicator[.noEmptySpace]?.setState(
+ state.noEmptySpace == .valid ? .valid : .invalid
+ )
+ passwordValidationIndicator[.unsuccessiveSame3words]?.setState(
+ state.unsuccessiveSame3words == .valid ? .valid : .invalid
+ )
+
+ // 확인버튼 활성화
+ ctaButton.setEnabled(state.isValid)
})
.disposed(by: disposeBag)
}
-
- private func onPasswordMatched() {
-
- passwordField.setState(state: .complete)
- checkPasswordField.setState(state: .complete)
- }
-
- private func onPasswordUnMatched() {
-
- passwordField.setState(state: .editing)
- checkPasswordField.setState(state: .editing)
- }
}
diff --git a/project/Projects/Testing/Sources/Domain/Mock_Domain.swift b/project/Projects/Testing/Sources/Domain/Mock_Domain.swift
index 55620108..a94baa02 100644
--- a/project/Projects/Testing/Sources/Domain/Mock_Domain.swift
+++ b/project/Projects/Testing/Sources/Domain/Mock_Domain.swift
@@ -6,3 +6,43 @@
//
import Foundation
+import Domain
+
+
+import RxSwift
+
+class MockAuthUseCase: AuthUseCase {
+
+ func registerCenterAccount(registerState: Domain.CenterRegisterState) -> RxSwift.Single> {
+ .just(.success(()))
+ }
+
+ func loginCenterAccount(id: String, password: String) -> RxSwift.Single> {
+ .just(.success(()))
+ }
+
+ func checkCenterJoinStatus() -> RxSwift.Single> {
+ .just(.success(.mock))
+ }
+
+ func setNewPassword(phoneNumber: String, password: String) -> RxSwift.Single> {
+ .just(.success(()))
+ }
+
+ func registerWorkerAccount(registerState: Domain.WorkerRegisterState) -> RxSwift.Single> {
+ .just(.success(()))
+ }
+
+ func loginWorkerAccount(phoneNumber: String, authNumber: String) -> RxSwift.Single> {
+ .just(.success(()))
+ }
+}
+
+extension CenterJoinStatusInfoVO {
+ static let mock: Self = .init(
+ id: "123",
+ managerName: "관리자 성험",
+ phoneNumber: "010-1111-2222",
+ centerManagerAccountStatus: .approved
+ )
+}
diff --git a/project/Projects/Testing/Sources/MockAssemblies.swift b/project/Projects/Testing/Sources/MockAssemblies.swift
index b4d1e8e1..c02649d0 100644
--- a/project/Projects/Testing/Sources/MockAssemblies.swift
+++ b/project/Projects/Testing/Sources/MockAssemblies.swift
@@ -24,7 +24,13 @@ public let MockAssemblies: [Assembly] = [
struct MockDomainAssembly: Assembly {
func assemble(container: Container) {
+ container.register(AuthInputValidationUseCase.self) { _ in
+ DefaultAuthInputValidationUseCase()
+ }
+ container.register(AuthUseCase.self) { _ in
+ MockAuthUseCase()
+ }
}
}
diff --git a/project/graph.png b/project/graph.png
index 1727f8d7..0a11ca7f 100644
Binary files a/project/graph.png and b/project/graph.png differ