diff --git a/iOS/Layover/Layover.xcodeproj/project.pbxproj b/iOS/Layover/Layover.xcodeproj/project.pbxproj index c64d88d..0e325c6 100644 --- a/iOS/Layover/Layover.xcodeproj/project.pbxproj +++ b/iOS/Layover/Layover.xcodeproj/project.pbxproj @@ -21,6 +21,10 @@ 194552132B03AFFC00299768 /* DummyViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 194552122B03AFFC00299768 /* DummyViewController.swift */; }; 19C7AFCE2B02410F003B35F2 /* AuthManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19C7AFCD2B02410F003B35F2 /* AuthManager.swift */; }; 19C7AFD62B02584D003B35F2 /* KeychainStored.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19C7AFD52B02584D003B35F2 /* KeychainStored.swift */; }; + FC2511A02B045C0A004717BC /* SignUpInteractor.swift in Sources */ = {isa = PBXBuildFile; fileRef = FC25119F2B045C0A004717BC /* SignUpInteractor.swift */; }; + FC2511A22B045C3F004717BC /* SignUpPresenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = FC2511A12B045C3F004717BC /* SignUpPresenter.swift */; }; + FC2511A42B045D6C004717BC /* SignUpModels.swift in Sources */ = {isa = PBXBuildFile; fileRef = FC2511A32B045D6C004717BC /* SignUpModels.swift */; }; + FC2511A62B049020004717BC /* SignUpConfigurator.swift in Sources */ = {isa = PBXBuildFile; fileRef = FC2511A52B049020004717BC /* SignUpConfigurator.swift */; }; FC49758F2B03432800D8627F /* Pretendard-SemiBold.ttf in Resources */ = {isa = PBXBuildFile; fileRef = FC4975862B03432700D8627F /* Pretendard-SemiBold.ttf */; }; FC4975932B03432800D8627F /* Pretendard-Bold.ttf in Resources */ = {isa = PBXBuildFile; fileRef = FC49758A2B03432800D8627F /* Pretendard-Bold.ttf */; }; FC4975942B03432800D8627F /* Pretendard-Regular.ttf in Resources */ = {isa = PBXBuildFile; fileRef = FC49758B2B03432800D8627F /* Pretendard-Regular.ttf */; }; @@ -46,6 +50,9 @@ FC7E45922AFF747A004F155A /* DummyScene.swift in Sources */ = {isa = PBXBuildFile; fileRef = FC7E45912AFF747A004F155A /* DummyScene.swift */; }; FC7E45942AFF7486004F155A /* DummyDesignSystem.swift in Sources */ = {isa = PBXBuildFile; fileRef = FC7E45932AFF7486004F155A /* DummyDesignSystem.swift */; }; FC7E45962AFF7497004F155A /* DummyExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = FC7E45952AFF7497004F155A /* DummyExtension.swift */; }; + FCEE0FF22B036B6000195BBE /* LOButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = FCEE0FF12B036B6000195BBE /* LOButton.swift */; }; + FCEE0FF62B03804000195BBE /* LOTextField.swift in Sources */ = {isa = PBXBuildFile; fileRef = FCEE0FF52B03804000195BBE /* LOTextField.swift */; }; + FCEE0FFA2B03AF8500195BBE /* SignUpViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = FCEE0FF92B03AF8400195BBE /* SignUpViewController.swift */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ @@ -79,6 +86,10 @@ 194552122B03AFFC00299768 /* DummyViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DummyViewController.swift; sourceTree = ""; }; 19C7AFCD2B02410F003B35F2 /* AuthManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AuthManager.swift; sourceTree = ""; }; 19C7AFD52B02584D003B35F2 /* KeychainStored.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KeychainStored.swift; sourceTree = ""; }; + FC25119F2B045C0A004717BC /* SignUpInteractor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SignUpInteractor.swift; sourceTree = ""; }; + FC2511A12B045C3F004717BC /* SignUpPresenter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SignUpPresenter.swift; sourceTree = ""; }; + FC2511A32B045D6C004717BC /* SignUpModels.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SignUpModels.swift; sourceTree = ""; }; + FC2511A52B049020004717BC /* SignUpConfigurator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SignUpConfigurator.swift; sourceTree = ""; }; FC4975862B03432700D8627F /* Pretendard-SemiBold.ttf */ = {isa = PBXFileReference; lastKnownFileType = file; path = "Pretendard-SemiBold.ttf"; sourceTree = ""; }; FC49758A2B03432800D8627F /* Pretendard-Bold.ttf */ = {isa = PBXFileReference; lastKnownFileType = file; path = "Pretendard-Bold.ttf"; sourceTree = ""; }; FC49758B2B03432800D8627F /* Pretendard-Regular.ttf */ = {isa = PBXFileReference; lastKnownFileType = file; path = "Pretendard-Regular.ttf"; sourceTree = ""; }; @@ -108,6 +119,9 @@ FC7E45912AFF747A004F155A /* DummyScene.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DummyScene.swift; sourceTree = ""; }; FC7E45932AFF7486004F155A /* DummyDesignSystem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DummyDesignSystem.swift; sourceTree = ""; }; FC7E45952AFF7497004F155A /* DummyExtension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DummyExtension.swift; sourceTree = ""; }; + FCEE0FF12B036B6000195BBE /* LOButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LOButton.swift; sourceTree = ""; }; + FCEE0FF52B03804000195BBE /* LOTextField.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LOTextField.swift; sourceTree = ""; }; + FCEE0FF92B03AF8400195BBE /* SignUpViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = SignUpViewController.swift; path = ../SignUpViewController.swift; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -302,6 +316,7 @@ isa = PBXGroup; children = ( 1945520E2B03AEA400299768 /* Configurator.swift */, + FCEE0FFB2B03AFAA00195BBE /* SingUpScene */, 194552032B038FC400299768 /* Tabbar */, 194551EB2B037F1E00299768 /* Login */, FC7E45912AFF747A004F155A /* DummyScene.swift */, @@ -313,6 +328,8 @@ isa = PBXGroup; children = ( FC7E45932AFF7486004F155A /* DummyDesignSystem.swift */, + FCEE0FF12B036B6000195BBE /* LOButton.swift */, + FCEE0FF52B03804000195BBE /* LOTextField.swift */, ); path = DesignSystem; sourceTree = ""; @@ -336,6 +353,18 @@ path = Resources; sourceTree = ""; }; + FCEE0FFB2B03AFAA00195BBE /* SingUpScene */ = { + isa = PBXGroup; + children = ( + FCEE0FF92B03AF8400195BBE /* SignUpViewController.swift */, + FC25119F2B045C0A004717BC /* SignUpInteractor.swift */, + FC2511A12B045C3F004717BC /* SignUpPresenter.swift */, + FC2511A32B045D6C004717BC /* SignUpModels.swift */, + FC2511A52B049020004717BC /* SignUpConfigurator.swift */, + ); + path = SingUpScene; + sourceTree = ""; + }; /* End PBXGroup section */ /* Begin PBXNativeTarget section */ @@ -479,12 +508,15 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( + FC2511A42B045D6C004717BC /* SignUpModels.swift in Sources */, FC68E2A12B023326001AABFF /* EndPoint.swift in Sources */, FC68E2A32B0233BC001AABFF /* NetworkError.swift in Sources */, FC7E45962AFF7497004F155A /* DummyExtension.swift in Sources */, 1945520D2B0399E500299768 /* MainTabBarViewController.swift in Sources */, 194551F62B037F2D00299768 /* LoginViewController.swift in Sources */, FC7E458E2AFF7462004F155A /* DummyService.swift in Sources */, + FCEE0FF22B036B6000195BBE /* LOButton.swift in Sources */, + FC2511A62B049020004717BC /* SignUpConfigurator.swift in Sources */, 194552132B03AFFC00299768 /* DummyViewController.swift in Sources */, 194551F22B037F2D00299768 /* LoginPresenter.swift in Sources */, 194552022B038B8300299768 /* OSLog+.swift in Sources */, @@ -496,15 +528,19 @@ FC7E458A2AFF70FB004F155A /* ViewController.swift in Sources */, FC7E453A2AFEB623004F155A /* AppDelegate.swift in Sources */, FC68E29B2B02325D001AABFF /* Requestable.swift in Sources */, + FC2511A22B045C3F004717BC /* SignUpPresenter.swift in Sources */, FC68E29D2B02326A001AABFF /* Responsable.swift in Sources */, + FC2511A02B045C0A004717BC /* SignUpInteractor.swift in Sources */, 19C7AFCE2B02410F003B35F2 /* AuthManager.swift in Sources */, FC7E45902AFF746E004F155A /* DummyWorker.swift in Sources */, 1945520F2B03AEA400299768 /* Configurator.swift in Sources */, FC68E29F2B023315001AABFF /* HTTPMethod.swift in Sources */, + FCEE0FFA2B03AF8500195BBE /* SignUpViewController.swift in Sources */, 194551F32B037F2D00299768 /* LoginWorker.swift in Sources */, FC7E45942AFF7486004F155A /* DummyDesignSystem.swift in Sources */, 194551F72B037F2D00299768 /* LoginInteractor.swift in Sources */, FC7E453C2AFEB623004F155A /* SceneDelegate.swift in Sources */, + FCEE0FF62B03804000195BBE /* LOTextField.swift in Sources */, FC68E2A52B0233D3001AABFF /* Provider.swift in Sources */, FC7E45922AFF747A004F155A /* DummyScene.swift in Sources */, 194552112B03AF2B00299768 /* MainTabBarConfigurator.swift in Sources */, diff --git a/iOS/Layover/Layover/DesignSystem/LOButton.swift b/iOS/Layover/Layover/DesignSystem/LOButton.swift new file mode 100644 index 0000000..3c9400c --- /dev/null +++ b/iOS/Layover/Layover/DesignSystem/LOButton.swift @@ -0,0 +1,69 @@ +// +// LOButton.swift +// Layover +// +// Created by kong on 2023/11/14. +// + +import UIKit + +final class LOButton: UIButton { + + // MARK: - Button Type + + enum Style { + case basic + } + + // MARK: - Properties + + private let style: Style + + private var buttonBackgroundColor: UIColor { + switch style { + case .basic: + return isEnabled ? .primaryPurple : .darkGrey + } + } + + private var buttonTitleColor: UIColor { + switch style { + case .basic: + return isEnabled ? .white : .grey500 + } + } + + override var isEnabled: Bool { + didSet { + setColors() + } + } + + // MARK: - Initializer + + init(style: Style) { + self.style = style + super.init(frame: .zero) + setInitialStateUI() + } + + required init?(coder: NSCoder) { + self.style = .basic + super.init(coder: coder) + setInitialStateUI() + } + + // MARK: - Custom Method + + private func setInitialStateUI() { + layer.cornerRadius = 8 + titleLabel?.font = .loFont(type: .body2Semibold) + setColors() + } + + private func setColors() { + setTitleColor(buttonTitleColor, for: state) + backgroundColor = buttonBackgroundColor + } + +} diff --git a/iOS/Layover/Layover/DesignSystem/LOTextField.swift b/iOS/Layover/Layover/DesignSystem/LOTextField.swift new file mode 100644 index 0000000..922cb99 --- /dev/null +++ b/iOS/Layover/Layover/DesignSystem/LOTextField.swift @@ -0,0 +1,67 @@ +// +// LOTextField.swift +// Layover +// +// Created by kong on 2023/11/14. +// + +import UIKit + +final class LOTextField: UITextField { + + override var placeholder: String? { + didSet { + setPlaceholderColor() + } + } + + init() { + super.init(frame: .zero) + setUI() + delegate = self + } + + required init?(coder: NSCoder) { + super.init(coder: coder) + setUI() + delegate = self + } + + private func setUI() { + layer.cornerRadius = 8 + layer.borderWidth = 1 + layer.borderColor = UIColor.grey500.cgColor + backgroundColor = UIColor.clear + setPadding() + } + private func setPlaceholderColor() { + guard let placeholder else { return } + attributedPlaceholder = NSAttributedString(string: placeholder, attributes: [.foregroundColor: UIColor.grey500]) + } + + private func setPadding() { + let paddingView = UIView(frame: CGRect(x: 0, y: 0, width: 16, height: frame.height)) + leftView = paddingView + leftViewMode = .always + rightView = paddingView + rightViewMode = .always + } + + private func setFocusStateColors(isFocused: Bool) { + let color = isFocused ? UIColor.grey200 : UIColor.grey500 + layer.borderColor = color.cgColor + textColor = color + } + +} + +extension LOTextField: UITextFieldDelegate { + func textFieldDidBeginEditing(_ textField: UITextField) { + setFocusStateColors(isFocused: true) + } + + func textFieldDidEndEditing(_ textField: UITextField) { + layer.borderColor = UIColor.grey500.cgColor + setFocusStateColors(isFocused: false) + } +} diff --git a/iOS/Layover/Layover/Scenes/SignUpViewController.swift b/iOS/Layover/Layover/Scenes/SignUpViewController.swift new file mode 100644 index 0000000..4f435d4 --- /dev/null +++ b/iOS/Layover/Layover/Scenes/SignUpViewController.swift @@ -0,0 +1,132 @@ +// +// SignUpViewController.swift +// Layover +// +// Created by kong on 2023/11/14. +// Copyright © 2023 CodeBomber. All rights reserved. +// + +import UIKit + +protocol SignUpDisplayLogic: AnyObject { + func displayNicknameValidation(response: SignUpModels.ValidateNickname.ViewModel) + func displayNickanmeDuplication() +} + +final class SignUpViewController: UIViewController { + + // MARK: - UI Components + + private let titleLabel: UILabel = { + let label = UILabel() + label.text = "여정 기록을 위한\n닉네임을 입력해주세요." + label.numberOfLines = 0 + label.font = .loFont(type: .header2) + label.textColor = .white + return label + }() + + private lazy var nicknameTextfield: LOTextField = { + let textField = LOTextField() + textField.placeholder = "닉네임을 입력해주세요." + textField.addTarget(self, action: #selector(setUpTextFieldState(_:)), for: .editingChanged) + return textField + }() + + private let nicknameAlertLabel: UILabel = { + let label = UILabel() + label.font = .loFont(type: .caption) + label.textColor = .error + return label + }() + + private let checkDuplicateNicknameButton: LOButton = { + let button = LOButton(style: .basic) + button.isEnabled = false + button.setTitle("중복확인", for: .normal) + return button + }() + + private let confirmButton: LOButton = { + let button = LOButton(style: .basic) + button.isEnabled = false + button.setTitle("회원가입", for: .normal) + return button + }() + + var interactor: SignUpBusinessLogic? + + // MARK: - Life Cycle + + override func viewDidLoad() { + super.viewDidLoad() + SignUpConfigurator.shared.configure(self) + setUI() + + // TODO: Base ViewController 로직으로 분리 + view.backgroundColor = .background + addTapGesture() + } + + // MARK: - UI + Layout + + private func setUI() { + [titleLabel, nicknameTextfield, nicknameAlertLabel, checkDuplicateNicknameButton, confirmButton].forEach { + view.addSubview($0) + $0.translatesAutoresizingMaskIntoConstraints = false + } + + NSLayoutConstraint.activate([ + titleLabel.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor, constant: 17), + titleLabel.leadingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.leadingAnchor, constant: 16), + titleLabel.trailingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.trailingAnchor, constant: -16), + + nicknameTextfield.heightAnchor.constraint(equalToConstant: 44), + nicknameTextfield.topAnchor.constraint(equalTo: titleLabel.bottomAnchor, constant: 17), + nicknameTextfield.leadingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.leadingAnchor, constant: 16), + nicknameTextfield.trailingAnchor.constraint(equalTo: checkDuplicateNicknameButton.leadingAnchor, constant: -16), + + nicknameAlertLabel.topAnchor.constraint(equalTo: nicknameTextfield.bottomAnchor, constant: 5), + nicknameAlertLabel.leadingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.leadingAnchor, constant: 16), + nicknameTextfield.trailingAnchor.constraint(equalTo: nicknameTextfield.trailingAnchor), + + checkDuplicateNicknameButton.heightAnchor.constraint(equalToConstant: 44), + checkDuplicateNicknameButton.widthAnchor.constraint(equalToConstant: 83), + checkDuplicateNicknameButton.centerYAnchor.constraint(equalTo: nicknameTextfield.centerYAnchor), + checkDuplicateNicknameButton.trailingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.trailingAnchor, constant: -16), + + confirmButton.heightAnchor.constraint(equalToConstant: 50), + confirmButton.bottomAnchor.constraint(equalTo: view.keyboardLayoutGuide.topAnchor), + confirmButton.leadingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.leadingAnchor, constant: 16), + confirmButton.trailingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.trailingAnchor, constant: -16) + ]) + } + + // MARK: - Custom Method + + private func addTapGesture() { + let tapGesture = UITapGestureRecognizer(target: self, action: #selector(hideKeyboard(_:))) + view.addGestureRecognizer(tapGesture) + } + + @objc private func setUpTextFieldState(_ sender: UITextField) { + guard let nickname = sender.text else { return } + interactor?.validateNickname(with: SignUpModels.ValidateNickname.Request(nickname: nickname)) + } + + @objc private func hideKeyboard(_ sender: Any) { + view.endEditing(true) + } +} + +extension SignUpViewController: SignUpDisplayLogic { + func displayNicknameValidation(response: SignUpModels.ValidateNickname.ViewModel) { + nicknameAlertLabel.isHidden = response.canCheckDuplication + checkDuplicateNicknameButton.isEnabled = response.canCheckDuplication + nicknameAlertLabel.text = response.alertDescription + } + + func displayNickanmeDuplication() { + + } +} diff --git a/iOS/Layover/Layover/Scenes/SingUpScene/SignUpConfigurator.swift b/iOS/Layover/Layover/Scenes/SingUpScene/SignUpConfigurator.swift new file mode 100644 index 0000000..9608e5e --- /dev/null +++ b/iOS/Layover/Layover/Scenes/SingUpScene/SignUpConfigurator.swift @@ -0,0 +1,26 @@ +// +// SignUpConfigurator.swift +// Layover +// +// Created by kong on 2023/11/15. +// Copyright © 2023 CodeBomber. All rights reserved. +// + +import Foundation + +final class SignUpConfigurator: Configurator { + typealias T = SignUpViewController + + static let shared = SignUpConfigurator() + + private init() { } + + func configure(_ viewController: SignUpViewController) { + let viewController = viewController + let interactor = SignUpInteractor() + let presenter = SignUpPresenter() + viewController.interactor = interactor + interactor.presenter = presenter + presenter.viewController = viewController + } +} diff --git a/iOS/Layover/Layover/Scenes/SingUpScene/SignUpInteractor.swift b/iOS/Layover/Layover/Scenes/SingUpScene/SignUpInteractor.swift new file mode 100644 index 0000000..47dc48d --- /dev/null +++ b/iOS/Layover/Layover/Scenes/SingUpScene/SignUpInteractor.swift @@ -0,0 +1,41 @@ +// +// SignUpInteractor.swift +// Layover +// +// Created by kong on 2023/11/15. +// Copyright © 2023 CodeBomber. All rights reserved. +// + +import UIKit + +protocol SignUpBusinessLogic { + func validateNickname(with request: SignUpModels.ValidateNickname.Request) +} + +protocol SignUpDataStore { } + +final class SignUpInteractor: SignUpBusinessLogic, SignUpDataStore { + + // MARK: - Properties + + typealias Models = SignUpModels + + var presenter: SignUpPresentationLogic? + + // MARK: - UseCase: 닉네임 유효성 검사 + + func validateNickname(with request: SignUpModels.ValidateNickname.Request) { + let response = check(nickname: request.nickname) + presenter?.presentNicknameValidation(with: response) + } + + private func check(nickname: String) -> SignUpModels.ValidateNickname.Response { + if nickname.count < 2 || nickname.count > 8 { + return .init(nicknameState: .lessThan2GreaterThan8) + } else if nickname.wholeMatch(of: /^[a-zA-Z0-9ㄱ-ㅎㅏ-ㅣ가-힣]+/) == nil { + return .init(nicknameState: .invalidCharacter) + } + return .init(nicknameState: .valid) + } + +} diff --git a/iOS/Layover/Layover/Scenes/SingUpScene/SignUpModels.swift b/iOS/Layover/Layover/Scenes/SingUpScene/SignUpModels.swift new file mode 100644 index 0000000..0bb197b --- /dev/null +++ b/iOS/Layover/Layover/Scenes/SingUpScene/SignUpModels.swift @@ -0,0 +1,45 @@ +// +// SignUpModels.swift +// Layover +// +// Created by kong on 2023/11/15. +// Copyright © 2023 CodeBomber. All rights reserved. +// + +import UIKit + +enum SignUpModels { + + // MARK: - Use Cases + + enum ValidateNickname { + struct Request { + var nickname: String + } + struct Response { + var nicknameState: NicknameState + } + struct ViewModel { + var canCheckDuplication: Bool + var alertDescription: String? + } + } + + // MARK: - State Type + enum NicknameState { + case valid + case lessThan2GreaterThan8 + case invalidCharacter + + var alertDescription: String? { + switch self { + case .valid: + return nil + case .lessThan2GreaterThan8: + return "2자 이상 8자 이하로 입력해주세요." + case .invalidCharacter: + return "입력할 수 없는 문자입니다." + } + } + } +} diff --git a/iOS/Layover/Layover/Scenes/SingUpScene/SignUpPresenter.swift b/iOS/Layover/Layover/Scenes/SingUpScene/SignUpPresenter.swift new file mode 100644 index 0000000..67c0ee3 --- /dev/null +++ b/iOS/Layover/Layover/Scenes/SingUpScene/SignUpPresenter.swift @@ -0,0 +1,30 @@ +// +// SignUpPresenter.swift +// Layover +// +// Created by kong on 2023/11/15. +// Copyright © 2023 CodeBomber. All rights reserved. +// + +import UIKit + +protocol SignUpPresentationLogic { + func presentNicknameValidation(with response: SignUpModels.ValidateNickname.Response) +} + +final class SignUpPresenter: SignUpPresentationLogic { + + // MARK: - Properties + + typealias Models = SignUpModels + weak var viewController: SignUpDisplayLogic? + + // MARK: - UseCase: 닉네임 유효성 검사 + + func presentNicknameValidation(with response: SignUpModels.ValidateNickname.Response) { + let viewModel = Models.ValidateNickname.ViewModel(canCheckDuplication: response.nicknameState == .valid, + alertDescription: response.nicknameState.alertDescription) + viewController?.displayNicknameValidation(response: viewModel) + } + +}