Skip to content

Commit

Permalink
Merge pull request #177 from LifePoop/176-sign_up_optional
Browse files Browse the repository at this point in the history
회원가입 처리 수정
  • Loading branch information
junu0516 authored Dec 15, 2023
2 parents 8b1d0ad + 2ed61a8 commit a4d88ca
Show file tree
Hide file tree
Showing 10 changed files with 145 additions and 41 deletions.
6 changes: 5 additions & 1 deletion Projects/Core/CoreEntity/Sources/AgreementCondition.swift
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@

import Foundation

public struct AgreementCondition {
public struct AgreementCondition: CustomStringConvertible {

public enum DescriptionTextSize {
case large
Expand All @@ -34,6 +34,10 @@ public struct AgreementCondition {
public let containsDetailView: Bool
public let selectionType: SelectionType

public var description: String {
descriptionText
}

public init(
descriptionText: String,
descriptionTextSize: DescriptionTextSize,
Expand Down
19 changes: 14 additions & 5 deletions Projects/Core/CoreEntity/Sources/SignupInput.swift
Original file line number Diff line number Diff line change
Expand Up @@ -8,19 +8,28 @@

import Foundation

public struct SignupInput {
public struct SignupInput: CustomStringConvertible {

public let nickname: String
public let birthDate: String
public let gender: GenderType
public let birthDate: String?
public let gender: GenderType?
public let conditions: Set<AgreementCondition>
public let oAuthAccessToken: String
public let provider: LoginType

public var description: String {
"""
nickname: \(nickname)
gender: \(gender?.description ?? "nil")
conditions: \(conditions)
loginType: \(provider.description)
"""
}

public init(
nickname: String,
birthDate: String,
gender: GenderType,
birthDate: String?,
gender: GenderType?,
conditions: Set<AgreementCondition>,
oAuthAccessToken: String,
provider: LoginType
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,16 @@ final public class ConditionalTextField: UIControl {
return label
}()

private let essentialMark: UILabel = {
let label = UILabel()
label.font = UIFont.systemFont(ofSize: 20, weight: .bold)
label.textColor = ColorAsset.primary.color
label.text = "*"
label.sizeToFit()
label.isHidden = true
return label
}()

private lazy var textField: UITextField = {
let textField = UITextField()
textField.addTarget(self, action: #selector(textFieldDidChange), for: .editingChanged)
Expand Down Expand Up @@ -105,6 +115,12 @@ final public class ConditionalTextField: UIControl {
}
}

public var markAsEssential: Bool = false {
didSet {
essentialMark.isHidden = !markAsEssential
}
}

public override var intrinsicContentSize: CGSize {
let sumOfSubViewsHeight = self.subviews.reduce(0) { $0 + $1.bounds.height }
let sumOfMargins: CGFloat = 42
Expand Down Expand Up @@ -140,6 +156,7 @@ final public class ConditionalTextField: UIControl {
private func configureUI() {

addSubview(titleLabel)
addSubview(essentialMark)
addSubview(textField)
addSubview(separatorView)
addSubview(subLabel)
Expand All @@ -149,6 +166,11 @@ final public class ConditionalTextField: UIControl {
make.leading.equalToSuperview()
}

essentialMark.snp.makeConstraints { make in
make.top.equalToSuperview()
make.leading.equalTo(titleLabel.snp.trailing)
}

textField.snp.makeConstraints { make in
make.top.equalTo(titleLabel.snp.bottom).offset(16)
make.leading.trailing.equalToSuperview()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,16 @@ final class ConditionSelectionView: UIControl {
return label
}()

private let essentialMark: UILabel = {
let label = UILabel()
label.font = UIFont.systemFont(ofSize: 20, weight: .bold)
label.textColor = ColorAsset.primary.color
label.text = "*"
label.sizeToFit()
label.isHidden = true
return label
}()

var isChecked: Bool = false {
didSet {
let image = isChecked ? selectedCheckBoxImage : deselectedCheckBoxImage
Expand All @@ -50,6 +60,12 @@ final class ConditionSelectionView: UIControl {
descriptionLabel.text = descriptionText
}
}

public var markAsEssential: Bool = false {
didSet {
essentialMark.isHidden = !markAsEssential
}
}

override var intrinsicContentSize: CGSize {
let targetHeight = max(descriptionLabel.bounds.height, checkBoxImageView.bounds.height)
Expand Down Expand Up @@ -80,6 +96,7 @@ final class ConditionSelectionView: UIControl {

addSubview(checkBoxImageView)
addSubview(descriptionLabel)
addSubview(essentialMark)

checkBoxImageView.snp.makeConstraints { make in
make.leading.equalToSuperview()
Expand All @@ -90,6 +107,11 @@ final class ConditionSelectionView: UIControl {
make.leading.equalTo(checkBoxImageView.snp.trailing).offset(16)
make.centerY.equalToSuperview()
}

essentialMark.snp.makeConstraints { make in
make.top.equalTo(descriptionLabel.snp.top)
make.leading.equalTo(descriptionLabel.snp.trailing)
}
}

func configure(with condition: AgreementCondition) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ public final class SignupViewController: LifePoopViewController, ViewType {
let textField = ConditionalTextField()
textField.title = LocalizableString.pleaseSetNickname
textField.placeholder = LocalizableString.nicknamePlaceholder
textField.markAsEssential = true
return textField
}()

Expand Down Expand Up @@ -73,7 +74,11 @@ public final class SignupViewController: LifePoopViewController, ViewType {
return stackView
}()

private let selectAllConditionView: ConditionSelectionView = ConditionSelectionView()
private let selectAllConditionView: ConditionSelectionView = {
let view = ConditionSelectionView()
view.markAsEssential = true
return view
}()

private let conditionSelectionCollectionViewDelegate = ConditionSelectionCollectionViewDelegate()
private lazy var conditionSelectionCollectionView: UICollectionView = {
Expand Down Expand Up @@ -229,6 +234,17 @@ public final class SignupViewController: LifePoopViewController, ViewType {
self.selectAllConditionView.configure(with: entity)
})
.disposed(by: disposeBag)

output.showError
.observe(on: MainScheduler.asyncInstance)
.withUnretained(self)
.bind(onNext: { `self`, error in
self.showSystemAlert(
title: "Signup Error",
message: error.localizedDescription
)
})
.disposed(by: disposeBag)
}

public override func configureUI() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -58,10 +58,13 @@ public final class SignupViewModel: ViewModelType {
let showDetailView = PublishRelay<DocumentType>()
let selectAllConditions = PublishRelay<Bool>()
let activateNextButton = BehaviorRelay<Bool>(value: false)
let showError = PublishRelay<Error>()
}

public struct State {
let selectedConditions = BehaviorRelay<Set<AgreementCondition>>(value: [])
let gender = BehaviorRelay<GenderType?>(value: nil)
let birthDate = BehaviorRelay<String?>(value: nil)
}

public let input = Input()
Expand Down Expand Up @@ -125,14 +128,17 @@ public final class SignupViewModel: ViewModelType {
.bind(to: output.nicknameTextFieldStatus)
.disposed(by: disposeBag)

let birthdayInputStatus = input.didEnterBirthDate
let birthDateInput = input.didEnterBirthDate.share()

birthDateInput
.bind(to: state.birthDate)
.disposed(by: disposeBag)

birthDateInput
.withUnretained(self)
.flatMap { `self`, input in
self.signupUseCase.isBirthdayInputValid(input)
}
.share()

birthdayInputStatus
.map { $0.status }
.bind(to: output.birthdayTextFieldStatus)
.disposed(by: disposeBag)
Expand All @@ -150,10 +156,17 @@ public final class SignupViewModel: ViewModelType {
})
.disposed(by: disposeBag)

input.didTapGenderButton
let genderInput = input.didTapGenderButton
.map { GenderType.allCases[$0] }
.share()

genderInput
.bind(to: output.selectGender)
.disposed(by: disposeBag)

genderInput
.bind(to: state.gender)
.disposed(by: disposeBag)

input.didTapLeftBarbutton
.bind(onNext: { _ in
Expand All @@ -164,32 +177,32 @@ public final class SignupViewModel: ViewModelType {
let signupInput = Observable
.combineLatest(
input.didEnterNickname,
input.didEnterBirthDate,
output.selectGender,
state.birthDate,
state.gender,
state.selectedConditions
)
.map {(nickname: $0, birthDate: $1, gender: $2, conditions: $3)}
.map { (nickname: $0, birthDate: $1, gender: $2, conditions: $3) }
.share()

signupInput
.withUnretained(self)
.flatMap { `self`, signupInfo in
Observable.combineLatest(
self.signupUseCase.isNicknameInputValid(signupInfo.nickname).map { $0.isValid },
self.signupUseCase.isBirthdayInputValid(signupInfo.birthDate).map { $0.isValid },
self.signupUseCase.isAllEsssentialConditionsSelected(signupInfo.conditions)
)
}
.map { $0 && $1 && $2 }
.map { $0 && $1 }
.bind(to: output.activateNextButton)
.disposed(by: disposeBag)

input.didTapNextButton
let requestSignup = input.didTapNextButton
.withLatestFrom(signupInput)
.compactMap { [weak self] nickname, birthDate, gender, conditions -> SignupInput? in
guard let oAuthAccessToekn = self?.authInfo.accessToken,
let provider = self?.authInfo.loginType,
let birthDate = self?.signupUseCase.createFormattedDateString(with: birthDate) else { return nil }
let provider = self?.authInfo.loginType else { return nil }

let birthDate = self?.signupUseCase.createFormattedDateString(with: birthDate)

return SignupInput(
nickname: nickname,
Expand All @@ -201,14 +214,23 @@ public final class SignupViewModel: ViewModelType {
)
}
.withUnretained(self)
.flatMapLatest { `self`, signupInput in
.flatMapLatestMaterialized { `self`, signupInput in
self.signupUseCase.requestSignup(signupInput)
}
.share()

requestSignup
.compactMap { $0.element }
.bind(onNext: { isSuccess in
guard isSuccess else { return }
coordinator.coordinate(by: .finishLoginFlow)
})
.disposed(by: disposeBag)

requestSignup
.compactMap { $0.error }
.bind(to: output.showError)
.disposed(by: disposeBag)
}

private func bindCellViewModelToParent(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,15 +22,25 @@ public final class DefaultSignupRepository: NSObject, SignupRepository {
public override init() { }

public func requestSignup(with signupInput: SignupInput) -> Single<UserAuthInfoEntity> {
urlSessionEndpointService

var requestBody: [String: String] = [
"nickname": signupInput.nickname,
"oAuthAccessToken": signupInput.oAuthAccessToken
]

if let birthDate = signupInput.birthDate,
birthDate.count > 0 {
requestBody["birth"] = birthDate
}

if let gender = signupInput.gender {
requestBody["sex"] = gender.description
}

return urlSessionEndpointService
.fetchNetworkResult(
endpoint: LifePoopLocalTarget.signup(provider: signupInput.provider.description),
with: [
"nickname": signupInput.nickname,
"birth": signupInput.birthDate,
"sex": signupInput.gender.description,
"oAuthAccessToken": signupInput.oAuthAccessToken
]
with: requestBody
)
.map { networkResult -> UserAuthInfoEntity in
let statusCode = networkResult.statusCode
Expand Down Expand Up @@ -61,7 +71,11 @@ public final class DefaultSignupRepository: NSObject, SignupRepository {
.logErrorIfDetected(category: .network, type: .error)
}

private func createUserAuthInfo(accessToken: String?, refreshToken: String?, loginType: LoginType?) throws -> UserAuthInfoEntity {
private func createUserAuthInfo(
accessToken: String?,
refreshToken: String?,
loginType: LoginType?
) throws -> UserAuthInfoEntity {
guard let accessToken = accessToken,
let refreshToken = refreshToken,
let loginType = loginType else {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,18 +32,13 @@ public final class DefaultSignupUseCase: SignupUseCase {

/** 사용자 입력 회원가입 정보로 서버에 새로운 회원정보 추가 요청 **/
public func requestSignup(_ signupInfo: SignupInput) -> Observable<Bool> {

Logger.log(
message: """
회원 가입 요청
nickname: \(signupInfo.nickname)
gender: \(signupInfo.gender.description)
loginType: \(signupInfo.provider.description)
""",
message: "회원가입요청:\n\(signupInfo)",
category: .authentication,
type: .debug
)

return signupRepository.requestSignup(with: signupInfo)
.asObservable()
.withUnretained(self)
Expand All @@ -57,7 +52,6 @@ public final class DefaultSignupUseCase: SignupUseCase {
)
})
}
.catchAndReturn(false)
}

public func fetchSelectableConditions() -> Observable<[AgreementCondition]> {
Expand Down Expand Up @@ -116,7 +110,8 @@ public final class DefaultSignupUseCase: SignupUseCase {
}

// TODO: 추후에 Utils에 구현된 부분으로 대체 가능한 지 확인 후 제거
public func createFormattedDateString(with dateString: String) -> String? {
public func createFormattedDateString(with dateString: String?) -> String? {
guard let dateString = dateString else { return nil }

let inputDateFormatter = DateFormatter()
inputDateFormatter.dateFormat = "yyMMdd"
Expand Down
Loading

0 comments on commit a4d88ca

Please sign in to comment.