Skip to content

Commit

Permalink
Merge pull request #23 from lsj8706/feat/#12-회원가입뷰-구현
Browse files Browse the repository at this point in the history
[Feat] #12 - 회원가입 뷰 구현
  • Loading branch information
lsj8706 authored Dec 1, 2022
2 parents b2d5b5c + 0434efb commit 39c106d
Show file tree
Hide file tree
Showing 14 changed files with 650 additions and 8 deletions.
16 changes: 16 additions & 0 deletions SOPT-Stamp-iOS/Projects/Core/Sources/Literals/StringLiterals.swift
Original file line number Diff line number Diff line change
Expand Up @@ -29,4 +29,20 @@ public struct I18N {
public static let enterSearch = "검색어를 입력해 주세요."
public static let noSearchData = "등록된 게시물이 없습니다"
}

public struct SignUp {
public static let signUp = "회원가입"
public static let nickname = "닉네임"
public static let nicknameTextFieldPlaceholder = "한글/영문 NN자로 입력해주세요."
public static let email = "이메일"
public static let emailTextFieldPlaceholder = "이메일을 입력해주세요."
public static let password = "비밀번호"
public static let passwordTextFieldPlaceholder = "영문, 숫자, 특수문자 포함 8-15자로 입력해주세요."
public static let passwordCheckTextFieldPlaceholder = "확인을 위해 비밀번호를 한 번 더 입력해주세요."
public static let register = "가입하기"
public static let duplicatedNickname = "사용 중인 이름입니다."
public static let invalidEmailForm = "잘못된 이메일 형식입니다."
public static let invalidPasswordForm = "영문, 숫자, 특수문자 포함 8-15자로 입력해주세요."
public static let passwordNotAccord = "비밀번호가 일치하지 않습니다."
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
//
// SignUpRepository.swift
// Data
//
// Created by sejin on 2022/11/28.
// Copyright © 2022 SOPT-Stamp-iOS. All rights reserved.
//

import Combine

import Domain
import Network

public class SignUpRepository {

private let networkService: AuthService
private let cancelBag = Set<AnyCancellable>()

public init(service: AuthService) {
self.networkService = service
}
}

extension SignUpRepository: SignUpRepositoryInterface {

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
//
// SignUpTransform.swift
// Data
//
// Created by sejin on 2022/11/28.
// Copyright © 2022 SOPT-Stamp-iOS. All rights reserved.
//

import Foundation

import Domain
import Network

extension SignUpEntity {

public func toDomain() -> SignUpModel {
return SignUpModel.init()
}
}
16 changes: 16 additions & 0 deletions SOPT-Stamp-iOS/Projects/Domain/Sources/Model/SignUpModel.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
//
// SignUpModel.swift
// Domain
//
// Created by sejin on 2022/11/28.
// Copyright © 2022 SOPT-Stamp-iOS. All rights reserved.
//

import Foundation

public struct SignUpModel {

public init() {

}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
//
// SignUpRepositoryInterface.swift
// Domain
//
// Created by sejin on 2022/11/28.
// Copyright © 2022 SOPT-Stamp-iOS. All rights reserved.
//

import Combine

public protocol SignUpRepositoryInterface {

}
109 changes: 109 additions & 0 deletions SOPT-Stamp-iOS/Projects/Domain/Sources/UseCase/SignUpUseCase.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
//
// SignUpUseCase.swift
// Domain
//
// Created by sejin on 2022/11/28.
// Copyright © 2022 SOPT-Stamp-iOS. All rights reserved.
//

import Foundation
import Combine

import Core

public protocol SignUpUseCase {
func checkNickname(nickname: String)
func checkEmail(email: String)
func checkPassword(password: String)
func checkAccordPassword(firstPassword: String, secondPassword: String)

var isNicknameValid: CurrentValueSubject<Bool, Error> { get set }
var isEmailFormValid: CurrentValueSubject<Bool, Error> { get set }
var isPasswordFormValid: CurrentValueSubject<Bool, Error> { get set }
var isAccordPassword: CurrentValueSubject<Bool, Error> { get set }
var isValidForm: CurrentValueSubject<Bool, Error> { get set }
}

public class DefaultSignUpUseCase {

private let repository: SignUpRepositoryInterface
private var cancelBag = CancelBag()

public var isNicknameValid = CurrentValueSubject<Bool, Error>(false)
public var isEmailFormValid = CurrentValueSubject<Bool, Error>(false)
public var isPasswordFormValid = CurrentValueSubject<Bool, Error>(false)
public var isAccordPassword = CurrentValueSubject<Bool, Error>(false)
public var isValidForm = CurrentValueSubject<Bool, Error>(false)

public init(repository: SignUpRepositoryInterface) {
self.repository = repository
self.bindFormValid()
}
}

extension DefaultSignUpUseCase: SignUpUseCase {
public func checkNickname(nickname: String) {
// 닉네임 글자 수 정해지면 로직 추가
let isValid = checkNicknameForm(nickname: nickname)
guard isValid else { return }
// 서버 통신
}

public func checkEmail(email: String) {
let isValid = checkEmailForm(email: email)
guard isValid else { return }
// 서버 통신
}

public func checkPassword(password: String) {
checkPasswordForm(password: password)
}

public func checkAccordPassword(firstPassword: String, secondPassword: String) {
checkAccordPasswordForm(firstPassword: firstPassword, secondPassword: secondPassword)
}
}

// MARK: - Methods

extension DefaultSignUpUseCase {
func bindFormValid() {
isNicknameValid.combineLatest(
isEmailFormValid,
isPasswordFormValid,
isAccordPassword)
.map { (isNicknameValid, isEmailValid, isPasswordValid, isAccordPassword) in
(isNicknameValid && isEmailValid && isPasswordValid && isAccordPassword)
}
.sink { event in
print("SignUpUseCase - completion: \(event)")
} receiveValue: { isValid in
self.isValidForm.send(isValid)
}.store(in: cancelBag)
}

func checkNicknameForm(nickname: String) -> Bool {
isNicknameValid.send(true)
return true
}

func checkEmailForm(email: String) -> Bool {
let emailRegEx = "[A-Z0-9a-z._%+-]+@[A-Za-z0-9.-]+\\.[A-Za-z]{2,64}"
let emailTest = NSPredicate(format: "SELF MATCHES %@", emailRegEx)
let isValid = emailTest.evaluate(with: email)
isEmailFormValid.send(isValid)
return isValid
}

func checkPasswordForm(password: String) {
let passwordRegEx = "^(?=.*[A-Za-z])(?=.*[0-9])(?=.*[!@#$%^&*()_+=-]).{8,15}" // 8자리 ~ 15자리 영어+숫자+특수문자
let passwordTest = NSPredicate(format: "SELF MATCHES %@", passwordRegEx)
let isValid = passwordTest.evaluate(with: password)
isPasswordFormValid.send(isValid)
}

func checkAccordPasswordForm(firstPassword: String, secondPassword: String) {
let isValid = (firstPassword == secondPassword)
isAccordPassword.send(isValid)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,12 @@ extension CustomNavigationBar {
self.rightButton.addTarget(self, action: #selector(touchupRightButton), for: .touchUpInside)
return self
}

@discardableResult
public func setTitleTypoStyle(_ font: UIFont) -> Self {
titleLabel.setTypoStyle(font)
return self
}
}

// MARK: - @objc
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import Core
public enum TextFieldViewType {
case plain
case subTitle
case title
case titleWithRightButton

var height: Float {
Expand All @@ -26,7 +27,7 @@ public enum TextFieldViewType {
return 48
case .subTitle:
return 60
case .titleWithRightButton:
case .title, .titleWithRightButton:
return 83
}
}
Expand Down Expand Up @@ -76,9 +77,11 @@ public class CustomTextFieldView: UIView {

private var type: TextFieldViewType!

public var rightButtonTapped: Driver<Void> {
public var rightButtonTapped: Driver<String?> {
rightButton.publisher(for: .touchUpInside)
.map { _ in () }
.map { _ in
self.textField.text
}
.asDriver()
}

Expand Down Expand Up @@ -189,6 +192,7 @@ extension CustomTextFieldView {
/// 경고 문구 라벨의 text 설정
public func changeAlertLabelText(_ alertText: String) {
self.alertlabel.text = alertText
self.alertlabel.isHidden = false
}

private func setDelegate() {
Expand All @@ -197,6 +201,12 @@ extension CustomTextFieldView {

/// textField의 state를 지정하여 자동으로 배경색과 테두리 색이 바뀌도록 설정
public func setTextFieldViewState(_ state: TextFieldViewState) {

var state = state
if state == .normal && (textField.isEditing || !textField.isEmpty) {
state = .editing
}

textFieldContainerView.backgroundColor = state.backgroundColor

if let borderColor = state.borderColor {
Expand All @@ -219,6 +229,38 @@ extension CustomTextFieldView {
}
}

// MARK: - Input Binding

extension CustomTextFieldView {
var alertText: String {
get { return alertlabel.text ?? "" }
set { bindAlertText(newValue) }
}

private func bindAlertText(_ alertText: String) {
self.changeAlertLabelText(alertText)
if !alertText.isEmpty {
self.setTextFieldViewState(.alert)
}
}

public enum InputCase {
case alert
case passwordAlert

var keyPath: AnyKeyPath {
switch self {
case .alert: return \CustomTextFieldView.alertText
case .passwordAlert: return \CustomTextFieldView.textChanged
}
}
}

public func bindableInput<T>(_ input: InputCase) -> ReferenceWritableKeyPath<CustomTextFieldView, T> {
return input.keyPath as! ReferenceWritableKeyPath<CustomTextFieldView, T>
}
}

// MARK: - UI & Layout

extension CustomTextFieldView {
Expand All @@ -239,6 +281,7 @@ extension CustomTextFieldView {

alertlabel.font = UIFont.caption3
alertlabel.textColor = SoptampColor.error300.color
alertlabel.isHidden = true

rightButton.clipsToBounds = true
rightButton.isEnabled = false
Expand Down Expand Up @@ -267,6 +310,8 @@ extension CustomTextFieldView {
self.setPlainLayout()
case .subTitle:
self.setSubTitleLayout()
case .title:
self.setTitleLayout()
case .titleWithRightButton:
self.setTitleWithRightButtonLayout()
}
Expand All @@ -284,7 +329,8 @@ extension CustomTextFieldView {

textFieldContainerView.addSubviews(textField)
textField.snp.makeConstraints { make in
make.edges.equalToSuperview().inset(16)
make.top.bottom.equalToSuperview()
make.leading.trailing.equalToSuperview().inset(16)
}

setCornerRadius(10)
Expand Down Expand Up @@ -314,6 +360,31 @@ extension CustomTextFieldView {
setCornerRadius(12)
}

private func setTitleLayout() {
self.snp.makeConstraints { make in
make.height.equalTo(self.type.height)
}

self.addSubview(titleLabel)
titleLabel.snp.makeConstraints { make in
make.leading.top.equalToSuperview()
}

textFieldContainerView.snp.makeConstraints { make in
make.top.equalTo(titleLabel.snp.bottom).offset(16)
make.leading.trailing.equalToSuperview()
make.height.equalTo(48)
}

textFieldContainerView.addSubviews(textField)
textField.snp.makeConstraints { make in
make.top.bottom.equalToSuperview()
make.leading.trailing.equalToSuperview().inset(16)
}

setCornerRadius(10)
}

private func setTitleWithRightButtonLayout() {
self.snp.makeConstraints { make in
make.height.equalTo(self.type.height)
Expand All @@ -339,7 +410,8 @@ extension CustomTextFieldView {

textFieldContainerView.addSubview(textField)
textField.snp.makeConstraints { make in
make.edges.equalToSuperview().inset(16)
make.top.bottom.equalToSuperview()
make.leading.trailing.equalToSuperview().inset(16)
}

setCornerRadius(10)
Expand All @@ -354,6 +426,8 @@ extension CustomTextFieldView: UITextFieldDelegate {
}

public func textFieldDidEndEditing(_ textField: UITextField) {
self.setTextFieldViewState(.normal)
if let isEmpty = textField.text?.isEmpty {
isEmpty ? self.setTextFieldViewState(.normal) : self.setTextFieldViewState(.editing)
}
}
}
Loading

0 comments on commit 39c106d

Please sign in to comment.