diff --git a/Common/Sources/Components/AgreeAllTermButton.swift b/Common/Sources/Components/AgreeAllTermButton.swift new file mode 100644 index 0000000..2b73aa6 --- /dev/null +++ b/Common/Sources/Components/AgreeAllTermButton.swift @@ -0,0 +1,53 @@ +// +// AgreeToAllButton.swift +// Common +// +// Created by Chandrala on 7/11/24. +// Copyright © 2024 com.recordy. All rights reserved. +// + +import UIKit + +import SnapKit +import Then + +import UIKit +import SnapKit +import Then + +public class AgreeAllTermButton: RecordyTermButton { + + public override init(frame: CGRect) { + super.init(frame: frame) + setStyle() + setAutolayout() + } + + required init?(coder aDecoder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + private func setStyle() { + backgroundColor = CommonAsset.recordyGrey09.color + layer.cornerRadius = 8 + + agreeLabel.do { + $0.text = "전체동의" + $0.font = RecordyFont.subtitle.font + $0.textColor = CommonAsset.recordyGrey01.color + } + } + + private func setAutolayout() { + self.agreeImageView.snp.makeConstraints { + $0.leading.equalTo(snp.leading).offset(20) + $0.centerY.equalToSuperview() + $0.width.height.equalTo(24) + } + + self.agreeLabel.snp.makeConstraints { + $0.leading.equalTo(agreeImageView.snp.trailing).offset(16) + $0.centerY.equalToSuperview() + } + } +} diff --git a/Common/Sources/Components/AgreeToAllButton.swift b/Common/Sources/Components/AgreeToAllButton.swift deleted file mode 100644 index b79e661..0000000 --- a/Common/Sources/Components/AgreeToAllButton.swift +++ /dev/null @@ -1,82 +0,0 @@ -// -// AgreeToAllButton.swift -// Common -// -// Created by Chandrala on 7/11/24. -// Copyright © 2024 com.recordy. All rights reserved. -// - -public enum AgreeToAllButtonState { - case agree - case disagree -} - -import UIKit - -import SnapKit -import Then - -public class AgreeToAllButton: UIButton { - - let agreeImageView = UIImageView() - let agreeToAllLabel = UILabel() - - public var currentState: AgreeToAllButtonState = .disagree - - public override init(frame: CGRect) { - super.init(frame: frame) - setStyle() - setUI() - setAutolayout() - } - - required init?(coder aDecoder: NSCoder) { - fatalError("init(coder:) has not been implemented") - } - - private func setStyle() { - self.backgroundColor = CommonAsset.recordyGrey09.color - self.cornerRadius(8) - - agreeToAllLabel.do { - $0.text = "전체동의" - $0.font = RecordyFont.subtitle.font - $0.textColor = CommonAsset.recordyGrey01.color - } - - agreeImageView.do { - $0.image = CommonAsset.deactivateCheck.image - } - } - - private func setUI() { - self.addSubviews(agreeToAllLabel, agreeImageView) - } - - private func setAutolayout() { - self.agreeImageView.snp.makeConstraints { - $0.leading.equalTo(self.snp.leading).offset(20) - $0.centerY.equalToSuperview() - $0.width.height.equalTo(24) - } - - self.agreeToAllLabel.snp.makeConstraints { - $0.leading.equalTo(agreeImageView.snp.trailing).offset(16) - $0.centerY.equalToSuperview() - } - } - - public func toggleState() { - currentState = (currentState == .agree) ? .disagree : .agree - updateAgreeToggleButton() - } - - public func updateAgreeToggleButton() { - self.agreeImageView.image = (currentState == .agree) ? CommonAsset.activateCheck.image : CommonAsset.deactivateCheck.image - } - - public func updateState(_ state: AgreeToAllButtonState) { - currentState = state - updateAgreeToggleButton() - } -} diff --git a/Common/Sources/Components/RecordyButton.swift b/Common/Sources/Components/RecordyButton.swift index f78d7b6..c19bd4e 100644 --- a/Common/Sources/Components/RecordyButton.swift +++ b/Common/Sources/Components/RecordyButton.swift @@ -19,7 +19,7 @@ public class RecordyButton: UIButton { public var buttonState: ButtonState = .inactive { didSet { - updateButtonAppearance() + updateButtonStyle() } } @@ -35,10 +35,10 @@ public class RecordyButton: UIButton { private func setUI() { layer.cornerRadius = 12 titleLabel?.font = RecordyFont.button1.font - updateButtonAppearance() + updateButtonStyle() } - private func updateButtonAppearance() { + private func updateButtonStyle() { switch buttonState { case .active: backgroundColor = CommonAsset.recordyMain.color diff --git a/Common/Sources/Components/RecordyProgressView.swift b/Common/Sources/Components/RecordyProgressView.swift index 2e80d93..f392103 100644 --- a/Common/Sources/Components/RecordyProgressView.swift +++ b/Common/Sources/Components/RecordyProgressView.swift @@ -1,4 +1,5 @@ import UIKit + import SnapKit import Then @@ -8,17 +9,18 @@ protocol RecordyProgressViewDelegate: AnyObject { public final class RecordyProgressView: UIView { - weak var delegate: RecordyProgressViewDelegate? + public static let shared = RecordyProgressView() + private var totalPages: Int = 0 private var currentPage: Int = 0 var ratio: CGFloat = 0.0 { didSet { - self.progressBarView.snp.remakeConstraints { + progressBarView.snp.remakeConstraints { $0.leading.equalToSuperview() - $0.top.equalToSuperview().offset(-2) - $0.bottom.equalToSuperview().offset(2) - $0.width.equalToSuperview().multipliedBy(self.ratio) + $0.centerY.equalToSuperview() + $0.height.equalTo(6) + $0.width.equalToSuperview().multipliedBy(ratio) } UIView.animate( @@ -26,7 +28,7 @@ public final class RecordyProgressView: UIView { delay: 0, options: .curveEaseInOut, animations: { - self.layoutIfNeeded() + layoutIfNeeded() }, completion: nil ) @@ -48,20 +50,20 @@ public final class RecordyProgressView: UIView { } func setStyle() { - self.isUserInteractionEnabled = false - self.backgroundColor = CommonAsset.recordySub01.color - self.layer.cornerRadius = 4 - self.clipsToBounds = true + isUserInteractionEnabled = false + backgroundColor = CommonAsset.recordySub01.color + layer.cornerRadius = 4 + clipsToBounds = true progressBarView.layer.cornerRadius = 6 progressBarView.clipsToBounds = true } func setUI() { - self.addSubview(progressBarView) + addSubview(progressBarView) } public func updateProgress(currentPage: Int, totalPages: Int) { let newRatio = CGFloat(currentPage + 1) / CGFloat(totalPages) - self.ratio = newRatio + ratio = newRatio } } diff --git a/Common/Sources/Components/TermButton.swift b/Common/Sources/Components/RecordyTermButton.swift similarity index 61% rename from Common/Sources/Components/TermButton.swift rename to Common/Sources/Components/RecordyTermButton.swift index dc41a52..4ec314d 100644 --- a/Common/Sources/Components/TermButton.swift +++ b/Common/Sources/Components/RecordyTermButton.swift @@ -6,23 +6,22 @@ // Copyright © 2024 com.recordy. All rights reserved. // -public enum TermButtonState { - case agree - case disagree -} - - import UIKit import SnapKit import Then -public class TermButton: UIButton { +public enum ToggleButtonState { + case activate + case deactivate +} + +public class RecordyTermButton: UIButton { public let agreeImageView = UIImageView() public let agreeLabel = UILabel() - public var currentState: TermButtonState = .disagree + public var currentState: ToggleButtonState = .deactivate public override init(frame: CGRect) { super.init(frame: frame) @@ -47,37 +46,26 @@ public class TermButton: UIButton { } private func setUI() { - self.addSubviews( + addSubviews( agreeImageView, agreeLabel ) } private func setAutolayout() { - self.agreeImageView.snp.makeConstraints { + agreeImageView.snp.makeConstraints { $0.leading.equalToSuperview().offset(24) $0.width.height.equalTo(16) $0.centerY.equalToSuperview() } - self.agreeLabel.snp.makeConstraints { + agreeLabel.snp.makeConstraints { $0.leading.equalTo(agreeImageView.snp.trailing).offset(20) $0.centerY.equalToSuperview() } } - public func toggleState() { - currentState = (currentState == .agree) ? .disagree : .agree - updateAgreeToggleButton() - } - - public func updateAgreeToggleButton() { - self.agreeImageView.image = (currentState == .agree) ? CommonAsset.activateCheck.image : CommonAsset.deactivateCheck.image - } - - public func updateState(_ state: TermButtonState) { - currentState = state - updateAgreeToggleButton() + public func updateUI(_ state: ToggleButtonState) { + agreeImageView.image = (state == .activate) ? CommonAsset.activateCheck.image : CommonAsset.deactivateCheck.image } } - diff --git a/Common/Sources/Components/RecordyTextField.swift b/Common/Sources/Components/RecordyTextField.swift index e6d0dba..a56ba31 100644 --- a/Common/Sources/Components/RecordyTextField.swift +++ b/Common/Sources/Components/RecordyTextField.swift @@ -8,57 +8,52 @@ import UIKit -public final class RecordyTextField: UITextField { - - public var onStateChange: ((RecordyTextFieldState) -> Void)? - public var style: RecordyTextFieldStyle { - didSet { setStyle(style) } - } - public var textState: RecordyTextFieldState = .unselected { - didSet { - setState(textState) - } - } +public enum RecordyTextFieldState { + case unselected + case selected + case error +} + +public class RecordyTextField: UITextField { public init( frame: CGRect = .zero, - style: RecordyTextFieldStyle = .unselected, placeholder: String ) { - self.style = style super.init(frame: frame) self.placeholder = placeholder - self.delegate = self - setStyle(style) - self.textState = .unselected + setStyle() } required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") } - func setState(_ style: RecordyTextFieldState) { - self.layer.borderColor = style.borderColor.cgColor - self.layer.borderWidth = style.borderWidth - onStateChange?(style) - } - - func setStyle(_ style: RecordyTextFieldStyle) { - self.setLayer( - borderColor: style.borderColor, - borderWidth: style.borderWidth, - cornerRadius: 8 - ) - + private func setStyle() { + layer.cornerRadius = 8 + font = RecordyFont.body2.font + addPadding(left: 18, right: 18) + backgroundColor = CommonAsset.recordyGrey08.color + textColor = CommonAsset.recordyGrey01.color self.setPlaceholder( placeholder: self.placeholder ?? "", placeholderColor: CommonAsset.recordyGrey04, font: .body2 ) - - self.addPadding(left: 18, right: 18) - self.backgroundColor = CommonAsset.recordyGrey08.color - self.textColor = CommonAsset.recordyGrey01.color + } + + public func updateTextFieldStyle(for state: RecordyTextFieldState) { + switch state { + case .unselected: + layer.borderColor = UIColor.clear.cgColor + layer.borderWidth = 0 + case .selected: + layer.borderColor = CommonAsset.recordyMain.color.cgColor + layer.borderWidth = 1 + case .error: + layer.borderColor = CommonAsset.recordyAlert.color.cgColor + layer.borderWidth = 1 + } } } @@ -74,30 +69,6 @@ extension RecordyTextField: UITextFieldDelegate { } let updatedText = currentText.replacingCharacters(in: stringRange, with: string) - let pattern = "^[가-힣0-9._]{1,10}$" - let regex = try! NSRegularExpression(pattern: pattern) - let matches = regex.matches(in: updatedText, range: NSRange(location: 0, length: updatedText.utf16.count)) - - if matches.isEmpty { - onStateChange?(.error) - } else { - onStateChange?(.selected) - } - return true - } - - public func textFieldShouldBeginEditing(_ textField: UITextField) -> Bool { - return true - } - - public func textFieldShouldEndEditing(_ textField: UITextField) -> Bool { - onStateChange?(.unselected) - return true - } - - public func textFieldShouldReturn(_ textField: UITextField) -> Bool { - textField.resignFirstResponder() - onStateChange?(.unselected) - return true + return updatedText.count <= 10 } } diff --git a/Common/Sources/Components/RecordyTextFieldStyle.swift b/Common/Sources/Components/RecordyTextFieldStyle.swift deleted file mode 100644 index 7fcc023..0000000 --- a/Common/Sources/Components/RecordyTextFieldStyle.swift +++ /dev/null @@ -1,57 +0,0 @@ -// -// RecordyTextFieldStyle.swift -// Common -// -// Created by Chandrala on 7/4/24. -// Copyright © 2024 com.recordy. All rights reserved. -// - -import UIKit - -public enum RecordyTextFieldState { - case unselected - case selected - case error - - public var borderColor: UIColor { - switch self { - case .unselected: - return .clear - case .selected: - return CommonAsset.recordyMain.color - case .error: - return CommonAsset.recordyAlert.color - } - } - - public var borderWidth: CGFloat { - switch self { - case .unselected: - return 0 - case .selected: - return 1 - case .error: - return 1 - } - } -} - -public struct RecordyTextFieldStyle { - let borderColor: CommonColors? - let borderWidth: CGFloat? - - public static let unselected = RecordyTextFieldStyle( - borderColor: nil, - borderWidth: nil - ) - - public static let selected = RecordyTextFieldStyle( - borderColor: CommonAsset.recordyMain, - borderWidth: 1 - ) - - public static let error = RecordyTextFieldStyle( - borderColor: CommonAsset.recordyAlert, - borderWidth: 1 - ) -} diff --git a/Common/Sources/Extension/String+.swift b/Common/Sources/Extension/String+.swift index f184f1d..608d2fc 100644 --- a/Common/Sources/Extension/String+.swift +++ b/Common/Sources/Extension/String+.swift @@ -56,4 +56,9 @@ extension String { let matches = regex.matches(in: self, range: range) return !matches.isEmpty } + + public func isNicknamePatternValid(_ nickname: String) -> Bool { + let pattern = "^[가-힣0-9._]{1,10}$" + return nickname.matches(pattern: pattern) + } } diff --git a/Common/Sources/Extension/UIViewController+.swift b/Common/Sources/Extension/UIViewController+.swift index 06fef58..e775797 100644 --- a/Common/Sources/Extension/UIViewController+.swift +++ b/Common/Sources/Extension/UIViewController+.swift @@ -19,7 +19,7 @@ extension UIViewController { view.addGestureRecognizer(tap) } - @objc func dismissKeyboard() { + @objc public func dismissKeyboard() { view.endEditing(true) } diff --git a/Presentation/Sources/Login/LoginViewController.swift b/Presentation/Sources/Login/LoginViewController.swift index 22ba2ee..073b14b 100644 --- a/Presentation/Sources/Login/LoginViewController.swift +++ b/Presentation/Sources/Login/LoginViewController.swift @@ -115,7 +115,7 @@ public final class LoginViewController: UIViewController { tabBarController.modalPresentationStyle = .fullScreen self.present(tabBarController, animated: false) } else { - let signUpViewController = BaseNavigationController(rootViewController: SignupViewController()) + let signUpViewController = BaseNavigationController(rootViewController: TermsViewController()) signUpViewController.modalPresentationStyle = .fullScreen self.present(signUpViewController, animated: false) } diff --git a/Presentation/Sources/Signup/Controller/CompleteViewController.swift b/Presentation/Sources/Signup/Controller/CompleteViewController.swift new file mode 100644 index 0000000..4b3cc75 --- /dev/null +++ b/Presentation/Sources/Signup/Controller/CompleteViewController.swift @@ -0,0 +1,51 @@ +// +// CompleteViewController.swift +// Presentation +// +// Created by Chandrala on 8/25/24. +// Copyright © 2024 com.recordy. All rights reserved. +// + +import UIKit + +import Core +import Common + +@available(iOS 16.0, *) +final class CompleteViewController: UIViewController{ + + private let completeView = CompleteView() + + override func loadView() { + view = completeView + } + + public override func viewDidLoad() { + super.viewDidLoad() + setStyle() + } + + private func setStyle() { + completeView.completeButton.addTarget(self, action: #selector(completeButtonTapped), for: .touchUpInside) + } + + @objc private func completeButtonTapped() { + guard let text = nicknameView.nicknameTextField.text else { return } + let apiProvider = APIProvider() + let userId = UserDefaults.standard.integer(forKey: "userId") + let request = DTO.SignUpRequest( + nickname: text, + termsAgreement: DTO.SignUpRequest.TermsAgreement() + ) + apiProvider.justRequest(.signUp(request)) { result in + switch result { + case .success: + let tabBarController = RecordyTabBarController() + tabBarController.modalPresentationStyle = .fullScreen + present(tabBarController, animated: false) + case .failure: + print("failed to login") + } + } + } +} diff --git a/Presentation/Sources/Signup/Controller/NicknameViewController.swift b/Presentation/Sources/Signup/Controller/NicknameViewController.swift new file mode 100644 index 0000000..0ba685c --- /dev/null +++ b/Presentation/Sources/Signup/Controller/NicknameViewController.swift @@ -0,0 +1,133 @@ +// +// NicknameViewController.swift +// Presentation +// +// Created by Chandrala on 8/25/24. +// Copyright © 2024 com.recordy. All rights reserved. +// + +import UIKit + +import Common +import Core + +@available(iOS 16.0, *) +public final class NicknameViewController: UIViewController { + + private let nicknameView = NicknameView() + + private var errorMessage: String? + + private var currentState: RecordyTextFieldState = .unselected { + didSet { + nicknameView.updateUI(state: currentState, errorMessage: (currentState == .error) ? errorMessage : nil) + } + } + + public override func loadView() { + view = nicknameView + } + + public override func viewDidLoad() { + super.viewDidLoad() + setStyle() + setDelegate() + } + + func setStyle() { + nicknameView.nicknameTextField.addTarget(self, action: #selector(textFieldDidChange(_:)), for: .editingChanged) + nicknameView.nextButton.addTarget(self, action: #selector(nextButtonTapped), for: .touchUpInside) + setTapGesture() + } + + private func setDelegate() { + nicknameView.nicknameTextField.delegate = self + } + + private func getNicknameRequest(completion: @escaping (Bool) -> Void) { + let apiProvider = APIProvider() + let request = DTO.CheckNicknameRequest(nickname: nicknameView.nicknameTextField.text!) + apiProvider.justRequest(.checkNickname(request)) { result in + switch result { + case .success: + completion(true) + case .failure: + completion(false) + } + } + } + + private func updateTextFieldState(_ text: String) { + if text.isEmpty { + currentState = .unselected + } else if isNicknamePatternValid(text) { + getNicknameRequest { [weak self] isAvailable in + guard let self = self else { return } + if isAvailable { + currentState = .selected + nicknameView.nextButton.buttonState = .active + } else { + currentState = .error + errorMessage = "ⓘ 이미 사용 중인 닉네임이에요." + nicknameView.nextButton.buttonState = .inactive + } + } + } else { + currentState = .error + errorMessage = "ⓘ 한글, 숫자, 밑줄 및 마침표만 사용할 수 있어요." + nicknameView.nextButton.buttonState = .inactive + } + } + + private func setTapGesture() { + let tapGesture = UITapGestureRecognizer( + target: self, + action: #selector( + dismissKeyboard + ) + ) + addGestureRecognizer(tapGesture) + } + + @objc private func nextButtonTapped() { + let completeViewController = CompleteViewController() + navigationController?.pushViewController(completeViewController, animated: true) + } +} + +@available(iOS 16.0, *) +extension NicknameViewController: UITextFieldDelegate { + @objc public func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool { + let currentText = textField.text ?? "" + guard let stringRange = Range(range, in: currentText) else { + return false + } + + let updatedText = currentText.replacingCharacters(in: stringRange, with: string) + + updateTextFieldState(updatedText) + + return updatedText.count <= 10 + } + + @objc private func textFieldDidChange(_ textField: UITextField) { + updateTextFieldState(textField.text ?? "") + } + + public func textFieldShouldReturn(_ textField: UITextField) -> Bool { + textField.resignFirstResponder() + + if nicknameView.nextButton.buttonState == .active { + nextButtonTapped() + } + return true + } + + public func textFieldDidBeginEditing(_ textField: UITextField) { + updateTextFieldState(textField.text ?? "") + } + + public func textFieldDidEndEditing(_ textField: UITextField) { + updateTextFieldState(textField.text ?? "") + } +} diff --git a/Presentation/Sources/Signup/Controller/SignupViewController.swift b/Presentation/Sources/Signup/Controller/SignupViewController.swift deleted file mode 100644 index 9f1fc86..0000000 --- a/Presentation/Sources/Signup/Controller/SignupViewController.swift +++ /dev/null @@ -1,121 +0,0 @@ -// -// SignupViewController.swift -// Presentation -// -// Created by Chandrala on 7/4/24. -// Copyright © 2024 com.recordy. All rights reserved. -// - -import UIKit - -import Core -import Common - -import SnapKit - -@available(iOS 16.0, *) -public final class SignupViewController: UIViewController { - - let totalPages = 3 - var currentPage = 0 - - var rootView: UIView = UIView() - let signupView = SignupView() - let termsView = TermsView() - let nicknameView = NicknameView() - let completeView = CompleteView() - - public override func viewDidLoad() { - super.viewDidLoad() - showTermsView() - signupView.progressView.updateProgress( - currentPage: currentPage, - totalPages: totalPages - ) - self.hideKeyboard() - } - - func switchView(_ newView: UIView) { - rootView.removeFromSuperview() - view.addSubview(newView) - newView.snp.makeConstraints { - $0.edges.equalToSuperview() - } - rootView = newView - - view.addSubview(signupView.progressView) - signupView.progressView.snp.remakeConstraints { - $0.top.equalTo(view.safeAreaLayoutGuide.snp.top).offset(12) - $0.leading.trailing.equalToSuperview().inset(20) - $0.height.equalTo(6) - } - } - - @objc - func showTermsView() { - termsView.nextButton.addTarget( - self, - action: #selector(showNicknameView), - for: .touchUpInside - ) - self.title = "이용 약관" - switchView(termsView) - } - - @objc - func showNicknameView() { - if let termsView = rootView as? TermsView, termsView.areAllButtonsActive() { - currentPage += 1 - signupView.progressView.updateProgress( - currentPage: currentPage, - totalPages: totalPages - ) - nicknameView.nextButton.addTarget( - self, - action: #selector(showCompleteView), - for: .touchUpInside - ) - self.title = "닉네임 설정" - switchView(nicknameView) - } - } - - @objc - func showCompleteView() { - guard nicknameView.nextButton.buttonState == .active else { return } - currentPage += 1 - signupView.progressView.updateProgress( - currentPage: currentPage, - totalPages: totalPages - ) - completeView.completeButton.buttonState = .active - completeView.completeButton.addTarget( - self, - action: #selector(completeSignup), - for: .touchUpInside - ) - self.title = "회원가입 완료" - switchView(completeView) - } - - @objc - func completeSignup() { - guard let text = nicknameView.nicknameTextField.text else { return } - let apiProvider = APIProvider() - let userId = UserDefaults.standard.integer(forKey: "userId") - let request = DTO.SignUpRequest( - nickname: text, - termsAgreement: DTO.SignUpRequest.TermsAgreement() - ) - apiProvider.justRequest(.signUp(request)) { result in - switch result { - case .success: - let tabBarController = RecordyTabBarController() - tabBarController.modalPresentationStyle = .fullScreen - self.present(tabBarController, animated: false) - case .failure: - print("failed to login") - } - } - } -} diff --git a/Presentation/Sources/Signup/Controller/TermsViewController.swift b/Presentation/Sources/Signup/Controller/TermsViewController.swift new file mode 100644 index 0000000..fd46d76 --- /dev/null +++ b/Presentation/Sources/Signup/Controller/TermsViewController.swift @@ -0,0 +1,82 @@ +// +// TermsViewController.swift +// Presentation +// +// Created by Chandrala on 8/25/24. +// Copyright © 2024 com.recordy. All rights reserved. +// + +import UIKit + +import Common + +@available(iOS 16.0, *) +public final class TermsViewController: UIViewController { + + private let termsView = TermsView() + + private var firstTermButtonState: ToggleButtonState = .deactivate + private var secondTermButtonState: ToggleButtonState = .deactivate + private var thirdTermButtonState: ToggleButtonState = .deactivate + + private var isAllTermsAgreed: Bool { + return term1State == .activate && term2State == .activate && term3State == .activate + } + + public override func loadView() { + view = termsView + } + + public override func viewDidLoad() { + super.viewDidLoad() + setStyle() + } + + private func setStyle() { + termsView.agreeAllTermButton.addTarget(self, action: #selector(agreeAllTermButtonTapped), for: .touchUpInside) + termsView.termButton1.addTarget(self, action: #selector(termButtonTapped(_:)), for: .touchUpInside) + termsView.termButton2.addTarget(self, action: #selector(termButtonTapped(_:)), for: .touchUpInside) + termsView.termButton3.addTarget(self, action: #selector(termButtonTapped(_:)), for: .touchUpInside) + termsView.nextButton.addTarget(self, action: #selector(nextButtonTapped), for: .touchUpInside) + } + + @objc private func agreeAllTermButtonTapped() { + toggleAllTerms() + updateButtonState() + } + + @objc private func termButtonTapped(_ sender: RecordyTermButton) { + toggleTerm(for: sender) + updateButtonState() + } + + private func toggleAllTerms() { + let newState: ToggleButtonState = isAllTermsAgreed ? .deactivate : .activate + firstTermButtonState = newState + secondTermButtonState = newState + thirdTermButtonState = newState + } + + private func toggleTerm(for button: RecordyTermButton) { + if button == termsView.termButton1 { + firstTermButtonState = firstTermButtonState == .activate ? .deactivate : .activate + } else if button == termsView.termButton2 { + secondTermButtonState = secondTermButtonState == .activate ? .deactivate : .activate + } else if button == termsView.termButton3 { + thirdTermButtonState = thirdTermButtonState == .activate ? .deactivate : .activate + } + } + + private func updateButtonState() { + termsView.termButton1.updateUI(firstTermButtonState) + termsView.termButton2.updateUI(secondTermButtonState) + termsView.termButton3.updateUI(thirdTermButtonState) + termsView.agreeAllTermButton.updateUI(isAllTermsAgreed ? .activate : .deactivate) + termsView.nextButton.buttonState = isAllTermsAgreed ? .active : .inactive + } + + @objc private func nextButtonTapped() { + let nicknameViewController = NicknameViewController() + self.navigationController?.pushViewController(nicknameViewController, animated: true) + } +} diff --git a/Presentation/Sources/Signup/View/CompleteView.swift b/Presentation/Sources/Signup/View/CompleteView.swift index d60b62c..a57242d 100644 --- a/Presentation/Sources/Signup/View/CompleteView.swift +++ b/Presentation/Sources/Signup/View/CompleteView.swift @@ -55,7 +55,7 @@ final class CompleteView: UIView { } func setUI() { - self.addSubviews( + addSubviews( gradientView, completeImage, completeText1, @@ -69,7 +69,7 @@ final class CompleteView: UIView { $0.top.horizontalEdges.equalToSuperview() $0.height.equalTo(400.adaptiveHeight) } - + completeImage.snp.makeConstraints { $0.top.equalToSuperview().offset(294) $0.width.equalTo(100.adaptiveWidth) diff --git a/Presentation/Sources/Signup/View/NicknameView.swift b/Presentation/Sources/Signup/View/NicknameView.swift index ab81c9e..4429db1 100644 --- a/Presentation/Sources/Signup/View/NicknameView.swift +++ b/Presentation/Sources/Signup/View/NicknameView.swift @@ -1,7 +1,9 @@ import UIKit +import SnapKit +import Then + import Common -import Core final class NicknameView: UIView { @@ -14,17 +16,13 @@ final class NicknameView: UIView { let textFieldCountLabel = UILabel() let errorLabel = UILabel() + var onTextChange: ((String) -> Void)? + public override init(frame: CGRect) { super.init(frame: frame) setStyle() setUI() setAutoLayout() - setTextFieldObserver() - setTextFieldStateHandler() - } - - deinit { - NotificationCenter.default.removeObserver(self, name: UITextField.textDidChangeNotification, object: nicknameTextField) } required init?(coder: NSCoder) { @@ -32,7 +30,7 @@ final class NicknameView: UIView { } func setStyle() { - self.backgroundColor = CommonAsset.recordyBG.color + backgroundColor = CommonAsset.recordyBG.color nicknameText.do { $0.text = "당신의 첫 번째 기록,\n닉네임을 정해주세요" @@ -59,12 +57,10 @@ final class NicknameView: UIView { $0.font = RecordyFont.caption2.font $0.textColor = CommonAsset.recordyAlert.color } - - nicknameTextField.delegate = self } func setUI() { - self.addSubviews( + addSubviews( gradientView, nicknameText, nicknameTextField, @@ -108,79 +104,20 @@ final class NicknameView: UIView { } } - func getCheckNicknameRequest(completion: @escaping (Bool) -> Void) { - print("getCheckNicknameRequest") - let apiProvider = APIProvider() - let request = DTO.CheckNicknameRequest(nickname: nicknameTextField.text!) - apiProvider.justRequest(.checkNickname(request)) { result in - switch result { - case .success: - completion(true) - case .failure: - completion(false) - } - } - } - - @objc - func textFieldDidChange(_ sender: Any?) { - let textCount = nicknameTextField.text?.count ?? 0 - textFieldCountLabel.text = "\(textCount) / 10" - validateTextField() - } - - func setTextFieldObserver() { - NotificationCenter.default.addObserver(self, selector: #selector(textFieldDidChange), name: UITextField.textDidChangeNotification, object: nicknameTextField) - } - - private func setTextFieldStateHandler() { - nicknameTextField.onStateChange = { [weak self] state in - guard let self = self else { return } - self.nicknameTextField.layer.borderColor = state.borderColor.cgColor - self.nicknameTextField.layer.borderWidth = state.borderWidth + public func updateUI(state: RecordyTextFieldState, errorMessage: String? = nil) { + switch state { + case .selected: + errorLabel.text = "사용 가능한 닉네임이에요!" + errorLabel.textColor = CommonAsset.recordyMain.color - switch state { - case .unselected, .error: - self.nextButton.buttonState = .inactive - case .selected: - self.nextButton.buttonState = .active - } - } - } - - func validateTextField() { - guard let text = nicknameTextField.text else { - nicknameTextField.onStateChange?(.error) - return - } - - let pattern = "^[가-힣0-9._]{1,10}$" - - if text.matches(pattern: pattern) { - getCheckNicknameRequest { isSuccess in - if isSuccess { - self.errorLabel.text = "사용 가능한 닉네임이에요" - self.errorLabel.textColor = CommonAsset.recordyGrey09.color - self.nicknameTextField.onStateChange?(.selected) - } else { - self.nicknameTextField.onStateChange?(.error) - self.errorLabel.text = "ⓘ 이미 사용중인 닉네임이에요" - self.errorLabel.textColor = CommonAsset.recordyAlert.color - } - } - } else { - nicknameTextField.onStateChange?(.error) - errorLabel.text = "ⓘ 한글, 숫자, 밑줄 및 마침표만 사용할 수 있어요" + case .error: + errorLabel.text = errorMessage errorLabel.textColor = CommonAsset.recordyAlert.color + + case .unselected: + nicknameView.errorLabel.text = "" + nextButton.buttonState = .inactive } } } -extension NicknameView: UITextFieldDelegate { - func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool { - let currentText = textField.text ?? "" - guard let stringRange = Range(range, in: currentText) else { return false } - let updatedText = currentText.replacingCharacters(in: stringRange, with: string) - return updatedText.count <= 10 - } -} diff --git a/Presentation/Sources/Signup/View/SignupView.swift b/Presentation/Sources/Signup/View/SignupView.swift deleted file mode 100644 index 666dce3..0000000 --- a/Presentation/Sources/Signup/View/SignupView.swift +++ /dev/null @@ -1,50 +0,0 @@ -// -// SignupView.swift -// Presentation -// -// Created by Chandrala on 8/12/24. -// Copyright © 2024 com.recordy. All rights reserved. -// - -import UIKit - -import Core -import Common - -import SnapKit - -class SignupView: UIView { - - let gradientView = RecordyGradientView() - let progressView = RecordyProgressView() - - override init(frame: CGRect) { - super.init(frame: frame) - setUI() - setLayout() - } - - required init?(coder: NSCoder) { - fatalError("init(coder:) has not been implemented") - } - - private func setUI() { - self.addSubviews( - gradientView, - progressView - ) - } - - private func setLayout() { - progressView.snp.makeConstraints { - $0.top.equalTo(self.safeAreaLayoutGuide.snp.top).offset(12) - $0.leading.trailing.equalToSuperview().inset(20) - $0.height.equalTo(6.adaptiveHeight) - } - - gradientView.snp.makeConstraints { - $0.top.horizontalEdges.equalToSuperview() - $0.height.equalTo(400.adaptiveHeight) - } - } -} diff --git a/Presentation/Sources/Signup/View/TermsView.swift b/Presentation/Sources/Signup/View/TermsView.swift index b3420fc..2a51726 100644 --- a/Presentation/Sources/Signup/View/TermsView.swift +++ b/Presentation/Sources/Signup/View/TermsView.swift @@ -18,11 +18,10 @@ final class TermsView: UIView { let gradientView = RecordyGradientView() let termImage = UIImageView() let termText = UILabel() - - let agreeToAllButton = AgreeToAllButton() - let termButton1 = TermButton() - let termButton2 = TermButton() - let termButton3 = TermButton() + let agreeAllTermButton = AgreeAllTermButton() + let termButton1 = RecordyTermButton() + let termButton2 = RecordyTermButton() + let termButton3 = RecordyTermButton() let moreButton1 = MoreButton(url: "https://bohyunnkim.notion.site/e5c0a49d73474331a21b1594736ee0df") let moreButton2 = MoreButton(url: "https://bohyunnkim.notion.site/c2bdf3572df1495c92aedd0437158cf0?pvs=74") let moreButton3 = MoreButton(url: "https://bohyunnkim.notion.site/98d0fa7eac84431ab6f6dd63be0fb8ff?pvs=74") @@ -30,8 +29,8 @@ final class TermsView: UIView { public override init(frame: CGRect) { super.init(frame: frame) - setStyle() setUI() + setStyle() setAutoLayout() } @@ -40,12 +39,13 @@ final class TermsView: UIView { } func setStyle() { - self.backgroundColor = CommonAsset.recordyBG.color + backgroundColor = CommonAsset.recordyBG.color termImage.do { $0.image = CommonAsset.signupImage.image $0.contentMode = .scaleAspectFit } + termText.do { $0.text = "유영하러 오신 것을\n환영합니다!" $0.font = RecordyFont.title1.font @@ -54,23 +54,16 @@ final class TermsView: UIView { $0.setLineSpacing(lineHeightMultiple: 1.3) } - agreeToAllButton.do { - $0.addTarget(self, action: #selector(agreeToAllButtonTapped), for: .touchUpInside) - } - termButton1.do { $0.agreeLabel.text = "(필수) 서비스 이용약관 동의" - $0.addTarget(self, action: #selector(termButtonTapped), for: .touchUpInside) } termButton2.do { $0.agreeLabel.text = "(필수) 개인정보 수집·이용 동의" - $0.addTarget(self, action: #selector(termButtonTapped), for: .touchUpInside) } termButton3.do { $0.agreeLabel.text = "(필수) 만 14세 이상입니다" - $0.addTarget(self, action: #selector(termButtonTapped), for: .touchUpInside) } nextButton.do { @@ -80,11 +73,11 @@ final class TermsView: UIView { } func setUI() { - self.addSubviews( + addSubviews( gradientView, termImage, termText, - agreeToAllButton, + agreeAllTermButton, termButton1, termButton2, termButton3, @@ -100,7 +93,7 @@ final class TermsView: UIView { $0.top.horizontalEdges.equalToSuperview() $0.height.equalTo(400.adaptiveHeight) } - + termImage.snp.makeConstraints { $0.top.equalToSuperview().offset(148) $0.leading.equalToSuperview().offset(20) @@ -113,14 +106,14 @@ final class TermsView: UIView { $0.leading.equalToSuperview().offset(20) } - agreeToAllButton.snp.makeConstraints { + agreeAllTermButton.snp.makeConstraints { $0.top.equalTo(termText.snp.bottom).offset(32) $0.horizontalEdges.equalToSuperview().inset(20) $0.height.equalTo(48) } termButton1.snp.makeConstraints { - $0.top.equalTo(agreeToAllButton.snp.bottom).offset(8) + $0.top.equalTo(agreeAllTermButton.snp.bottom).offset(8) $0.leading.equalToSuperview().offset(20) $0.trailing.equalTo(moreButton1.snp.leading).offset(-10) $0.height.equalTo(40) @@ -141,7 +134,7 @@ final class TermsView: UIView { } moreButton1.snp.makeConstraints { - $0.top.equalTo(agreeToAllButton.snp.bottom).offset(17) + $0.top.equalTo(agreeAllTermButton.snp.bottom).offset(17) $0.trailing.equalToSuperview().offset(-40) $0.width.equalTo(32.adaptiveWidth) $0.height.equalTo(18.adaptiveHeight) @@ -167,41 +160,4 @@ final class TermsView: UIView { $0.height.equalTo(54.adaptiveHeight) } } - - @objc private func agreeToAllButtonTapped(_ sender: AgreeToAllButton) { - sender.toggleState() - updateAllTermButtonsState(sender.currentState == .agree) - updateNextButtonState() - } - - @objc private func termButtonTapped(_ sender: TermButton) { - sender.toggleState() - updateAgreeToAllButtonState() - updateNextButtonState() - } - - private func updateAllTermButtonsState(_ isActive: Bool) { - let state: TermButtonState = isActive ? .agree : .disagree - termButton1.updateState(state) - termButton2.updateState(state) - termButton3.updateState(state) - } - - private func updateAgreeToAllButtonState() { - if termButton1.currentState == .agree && termButton2.currentState == .agree && termButton3.currentState == .agree { - agreeToAllButton.currentState = .agree - } else { - agreeToAllButton.currentState = .disagree - } - agreeToAllButton.updateAgreeToggleButton() - } - - private func updateNextButtonState() { - let allTermsAgreed = termButton1.currentState == .agree && termButton2.currentState == .agree && termButton3.currentState == .agree - nextButton.buttonState = allTermsAgreed ? .active : .inactive - } - - func areAllButtonsActive() -> Bool { - return termButton1.currentState == .agree && termButton2.currentState == .agree && termButton3.currentState == .agree && nextButton.buttonState == .active - } } diff --git a/Presentation/Sources/UploadVideo/UploadVideoViewController.swift b/Presentation/Sources/UploadVideo/UploadVideoViewController.swift index 531b091..5544dbd 100644 --- a/Presentation/Sources/UploadVideo/UploadVideoViewController.swift +++ b/Presentation/Sources/UploadVideo/UploadVideoViewController.swift @@ -276,7 +276,7 @@ public class UploadVideoViewController: UIViewController { guard let self = self else { return } if text.count > 20 { self.locationTextField.text = String(text.prefix(20)) - self.locationTextField.textState = .error + self.locationTextField.updateTextFieldStyle(for: .error) } }) .disposed(by: disposeBag) @@ -383,7 +383,7 @@ extension Reactive where Base: UploadVideoViewController { extension Reactive where Base: RecordyTextField { var state: Binder { return Binder(self.base) { textField, state in - textField.textState = state + textField.updateTextFieldStyle(for: state) } } }