diff --git a/CMC/Resources/Configure/Dev.xcconfig b/CMC/Resources/Configure/Dev.xcconfig index 1955a80..fe5f130 100644 --- a/CMC/Resources/Configure/Dev.xcconfig +++ b/CMC/Resources/Configure/Dev.xcconfig @@ -8,3 +8,5 @@ // Configuration settings file format documentation can be found at: // https://help.apple.com/xcode/#/dev745c5c974 + +BASE_URL = http:/$()/cmcapiserverdev-env.eba-au6k5x3x.ap-northeast-2.elasticbeanstalk.com diff --git a/CMC/Sources/App/Splash/SplashViewModel.swift b/CMC/Sources/App/Splash/SplashViewModel.swift index 2880b76..e0a425d 100644 --- a/CMC/Sources/App/Splash/SplashViewModel.swift +++ b/CMC/Sources/App/Splash/SplashViewModel.swift @@ -48,7 +48,7 @@ class SplashViewModel: ViewModelType{ } private func checkAutoSignIn(){ - if let _: String = UserDefaultManager.shared.load(for: .jwtToken) { + if let _: String = UserDefaultManager.shared.load(for: .accessToken) { self.coordinator?.userActionState.accept(.tabBar) } else { self.coordinator?.userActionState.accept(.auth) diff --git a/CMC/Sources/Data/DTO/Auth/SignInDTO.swift b/CMC/Sources/Data/DTO/Auth/SignInDTO.swift new file mode 100644 index 0000000..77bcf16 --- /dev/null +++ b/CMC/Sources/Data/DTO/Auth/SignInDTO.swift @@ -0,0 +1,30 @@ +// +// SignInDTO.swift +// CMC +// +// Created by Siri on 10/27/23. +// Copyright © 2023 com.centralMakeusChallenge. All rights reserved. +// + +import Foundation + +// MARK: - SignInDTO +struct SignInDTO: Codable { + let isSuccess: Bool + let code, message: String + let result: SignInResponse + + struct SignInResponse: Codable { + let userId: Int + let accessToken: String + let refreshToken: String + } + + func toDomain() -> SignInModel { + return SignInModel( + userId: result.userId, + accessToken: result.accessToken, + refreshToken: result.refreshToken + ) + } +} diff --git a/CMC/Sources/Data/DTO/Auth/SignUpDTO.swift b/CMC/Sources/Data/DTO/Auth/SignUpDTO.swift new file mode 100644 index 0000000..f549f83 --- /dev/null +++ b/CMC/Sources/Data/DTO/Auth/SignUpDTO.swift @@ -0,0 +1,30 @@ +// +// SignUpDTO.swift +// CMC +// +// Created by Siri on 10/27/23. +// Copyright © 2023 com.centralMakeusChallenge. All rights reserved. +// + +import Foundation + +// MARK: - SignUpDTO +struct SignUpDTO: Codable { + let isSuccess: Bool + let code, message: String + let result: SignUpResponse + + struct SignUpResponse: Codable { + let userId: Int + let accessToken: String + let refreshToken: String + } + + func toDomain() -> SignUpModel { + return SignUpModel( + userId: result.userId, + accessToken: result.accessToken, + refreshToken: result.refreshToken + ) + } +} diff --git a/CMC/Sources/Data/DTO/LaunchDTO.swift b/CMC/Sources/Data/DTO/Splash/LaunchDTO.swift similarity index 100% rename from CMC/Sources/Data/DTO/LaunchDTO.swift rename to CMC/Sources/Data/DTO/Splash/LaunchDTO.swift diff --git a/CMC/Sources/Data/Endpoint/AuthEndpoint.swift b/CMC/Sources/Data/Endpoint/AuthEndpoint.swift new file mode 100644 index 0000000..43f9da3 --- /dev/null +++ b/CMC/Sources/Data/Endpoint/AuthEndpoint.swift @@ -0,0 +1,45 @@ +// +// AuthEndpoint.swift +// CMC +// +// Created by Siri on 10/27/23. +// Copyright © 2023 com.centralMakeusChallenge. All rights reserved. +// + +import Foundation + +enum AuthEndpoint: Endpoint { + + case signIn(body: SignInBody), signUp(body: SignUpBody) + + var baseURL: URL? { + return URL(string: Xcconfig.BASE_URL + "/auth") + } + + var method: HTTPMethod { + switch self { + case .signUp, .signIn: + return .POST + } + } + + var path: String { + switch self { + case .signUp: + return "/signUp" + case .signIn: + return "/signIn" + } + + } + + var parameters: HTTPRequestParameterType? { + switch self { + case .signUp(let body): + return .body(body) + case .signIn(let body): + return .body(body) + } + } + +} diff --git a/CMC/Sources/Data/Endpoint/Body/Body.swift b/CMC/Sources/Data/Endpoint/Body/Body.swift deleted file mode 100644 index 1cb6040..0000000 --- a/CMC/Sources/Data/Endpoint/Body/Body.swift +++ /dev/null @@ -1,9 +0,0 @@ -// -// Body.swift -// CMC -// -// Created by Siri on 2023/10/24. -// Copyright © 2023 com.centralMakeusChallenge. All rights reserved. -// - -import Foundation diff --git a/CMC/Sources/Data/Endpoint/LaunchEndpoint.swift b/CMC/Sources/Data/Endpoint/LaunchEndpoint.swift index 045b170..693eaec 100644 --- a/CMC/Sources/Data/Endpoint/LaunchEndpoint.swift +++ b/CMC/Sources/Data/Endpoint/LaunchEndpoint.swift @@ -8,12 +8,12 @@ import Foundation -enum EmailEndpoint: Endpoint { +enum LaunchEndpoint: Endpoint { case health var baseURL: URL? { - return URL(string: "http://cmcapiserverdev-env.eba-au6k5x3x.ap-northeast-2.elasticbeanstalk.com") + return URL(string: Xcconfig.BASE_URL) } var method: HTTPMethod { diff --git a/CMC/Sources/Data/NetworkService/Body/Auth/SignInBody.swift b/CMC/Sources/Data/NetworkService/Body/Auth/SignInBody.swift new file mode 100644 index 0000000..a7d5b2a --- /dev/null +++ b/CMC/Sources/Data/NetworkService/Body/Auth/SignInBody.swift @@ -0,0 +1,15 @@ +// +// SignInBody.swift +// CMC +// +// Created by Siri on 10/27/23. +// Copyright © 2023 com.centralMakeusChallenge. All rights reserved. +// + +import Foundation + +// MARK: - SignInBody +struct SignInBody: Codable { + let email: String + let password: String +} diff --git a/CMC/Sources/Data/NetworkService/Body/Auth/SignUpBody.swift b/CMC/Sources/Data/NetworkService/Body/Auth/SignUpBody.swift new file mode 100644 index 0000000..1335f39 --- /dev/null +++ b/CMC/Sources/Data/NetworkService/Body/Auth/SignUpBody.swift @@ -0,0 +1,19 @@ +// +// SignUpBody.swift +// CMC +// +// Created by Siri on 10/27/23. +// Copyright © 2023 com.centralMakeusChallenge. All rights reserved. +// + +import Foundation + +// MARK: - SignUpBody +struct SignUpBody: Codable { + let email: String + let password: String + let nickname: String + let name: String + let generation: Int + let part: String +} diff --git a/CMC/Sources/Data/Repositories/Auth/DefaultAuthRepository.swift b/CMC/Sources/Data/Repositories/Auth/DefaultAuthRepository.swift new file mode 100644 index 0000000..d53b1f4 --- /dev/null +++ b/CMC/Sources/Data/Repositories/Auth/DefaultAuthRepository.swift @@ -0,0 +1,43 @@ +// +// DefaultAuthRepository.swift +// CMC +// +// Created by Siri on 10/27/23. +// Copyright © 2023 com.centralMakeusChallenge. All rights reserved. +// + +import Foundation +import RxSwift + +final class DefaultAuthRepository: AuthRepository { + + private let networkService: NetworkService + + init() { + self.networkService = DefaultNetworkService() + } + + func signUp(body: SignUpBody) -> Single { + let endpoint = AuthEndpoint.signUp(body: body) + return networkService.request(endpoint) + .flatMap { data in + guard let dto = Utility.decode(SignUpDTO.self, from: data) else { + return Single.error(NetworkError.decodingFailed) + } + return Single.just(dto) + } + } + + func signIn(body: SignInBody) -> Single { + let endpoint = AuthEndpoint.signIn(body: body) + return networkService.request(endpoint) + .flatMap { data in + guard let dto = Utility.decode(SignInDTO.self, from: data) else { + return Single.error(NetworkError.decodingFailed) + } + return Single.just(dto) + } + } + + +} diff --git a/CMC/Sources/Data/Repositories/DefaultLaunchRepository.swift b/CMC/Sources/Data/Repositories/Splash/DefaultLaunchRepository.swift similarity index 94% rename from CMC/Sources/Data/Repositories/DefaultLaunchRepository.swift rename to CMC/Sources/Data/Repositories/Splash/DefaultLaunchRepository.swift index 8215941..0bf61cd 100644 --- a/CMC/Sources/Data/Repositories/DefaultLaunchRepository.swift +++ b/CMC/Sources/Data/Repositories/Splash/DefaultLaunchRepository.swift @@ -18,7 +18,7 @@ final class DefaultLaunchRepository: LaunchRepository { } func health() -> Single { - let endpoint = EmailEndpoint.health + let endpoint = LaunchEndpoint.health return networkService.request(endpoint) .flatMap { data in guard let dto = Utility.decode(LaunchDTO.self, from: data) else { diff --git a/CMC/Sources/Domain/Models/Auth/SignInModel.swift b/CMC/Sources/Domain/Models/Auth/SignInModel.swift new file mode 100644 index 0000000..9a88982 --- /dev/null +++ b/CMC/Sources/Domain/Models/Auth/SignInModel.swift @@ -0,0 +1,17 @@ +// +// SignInModel.swift +// CMC +// +// Created by Siri on 10/27/23. +// Copyright © 2023 com.centralMakeusChallenge. All rights reserved. +// + +import Foundation + +// MARK: - SignInModel +struct SignInModel: Codable { + let userId: Int + let accessToken: String + let refreshToken: String +} + diff --git a/CMC/Sources/Domain/Models/Auth/SignUpModel.swift b/CMC/Sources/Domain/Models/Auth/SignUpModel.swift new file mode 100644 index 0000000..8888cb8 --- /dev/null +++ b/CMC/Sources/Domain/Models/Auth/SignUpModel.swift @@ -0,0 +1,17 @@ +// +// SignUpModel.swift +// CMC +// +// Created by Siri on 10/27/23. +// Copyright © 2023 com.centralMakeusChallenge. All rights reserved. +// + +import Foundation + +// MARK: - SignUpModel +struct SignUpModel: Codable { + let userId: Int + let accessToken: String + let refreshToken: String +} + diff --git a/CMC/Sources/Domain/Models/LaunchModel.swift b/CMC/Sources/Domain/Models/Splash/LaunchModel.swift similarity index 100% rename from CMC/Sources/Domain/Models/LaunchModel.swift rename to CMC/Sources/Domain/Models/Splash/LaunchModel.swift diff --git a/CMC/Sources/Domain/Repositories/Auth/AuthRepository.swift b/CMC/Sources/Domain/Repositories/Auth/AuthRepository.swift new file mode 100644 index 0000000..ff042a0 --- /dev/null +++ b/CMC/Sources/Domain/Repositories/Auth/AuthRepository.swift @@ -0,0 +1,15 @@ +// +// AuthRepository.swift +// CMC +// +// Created by Siri on 10/27/23. +// Copyright © 2023 com.centralMakeusChallenge. All rights reserved. +// + +import Foundation +import RxSwift + +protocol AuthRepository { + func signUp(body: SignUpBody) -> Single + func signIn(body: SignInBody) -> Single +} diff --git a/CMC/Sources/Domain/Repositories/LaunchRepository.swift b/CMC/Sources/Domain/Repositories/Splash/LaunchRepository.swift similarity index 100% rename from CMC/Sources/Domain/Repositories/LaunchRepository.swift rename to CMC/Sources/Domain/Repositories/Splash/LaunchRepository.swift diff --git a/CMC/Sources/Domain/Usecases/Auth/AuthUsecase.swift b/CMC/Sources/Domain/Usecases/Auth/AuthUsecase.swift new file mode 100644 index 0000000..e2bbabc --- /dev/null +++ b/CMC/Sources/Domain/Usecases/Auth/AuthUsecase.swift @@ -0,0 +1,15 @@ +// +// AuthUsecase.swift +// CMC +// +// Created by Siri on 10/27/23. +// Copyright © 2023 com.centralMakeusChallenge. All rights reserved. +// + +import Foundation +import RxSwift + +protocol AuthUsecase { + func signUp(body: SignUpBody) -> Single + func signIn(body: SignInBody) -> Single +} diff --git a/CMC/Sources/Domain/Usecases/Auth/DefaultAuthUsecase.swift b/CMC/Sources/Domain/Usecases/Auth/DefaultAuthUsecase.swift new file mode 100644 index 0000000..94267df --- /dev/null +++ b/CMC/Sources/Domain/Usecases/Auth/DefaultAuthUsecase.swift @@ -0,0 +1,34 @@ +// +// DefaultAuthUsecase.swift +// CMC +// +// Created by Siri on 10/27/23. +// Copyright © 2023 com.centralMakeusChallenge. All rights reserved. +// + +import Foundation +import RxSwift + +final class DefaultAuthUsecase: AuthUsecase { + + private let authRepository: AuthRepository + + init(authRepository: AuthRepository) { + self.authRepository = authRepository + } + + func signUp(body: SignUpBody) -> Single { + return authRepository.signUp(body: body) + .map { dto in + return dto.toDomain() + } + } + + func signIn(body: SignInBody) -> Single { + return authRepository.signIn(body: body) + .map { dto in + return dto.toDomain() + } + } + +} diff --git a/CMC/Sources/Domain/Usecases/DefaultLaunchUsecase.swift b/CMC/Sources/Domain/Usecases/Splash/DefaultLaunchUsecase.swift similarity index 100% rename from CMC/Sources/Domain/Usecases/DefaultLaunchUsecase.swift rename to CMC/Sources/Domain/Usecases/Splash/DefaultLaunchUsecase.swift diff --git a/CMC/Sources/Domain/Usecases/LaunchUsecase.swift b/CMC/Sources/Domain/Usecases/Splash/LaunchUsecase.swift similarity index 100% rename from CMC/Sources/Domain/Usecases/LaunchUsecase.swift rename to CMC/Sources/Domain/Usecases/Splash/LaunchUsecase.swift diff --git a/CMC/Sources/Presenter/Auth/Coordinators/AuthCoordinator.swift b/CMC/Sources/Presenter/Auth/Coordinators/AuthCoordinator.swift index 25d75dc..ddb7e77 100644 --- a/CMC/Sources/Presenter/Auth/Coordinators/AuthCoordinator.swift +++ b/CMC/Sources/Presenter/Auth/Coordinators/AuthCoordinator.swift @@ -14,8 +14,8 @@ class AuthCoordinator: CoordinatorType { // MARK: - Navigation DEPTH 1 - enum AuthCoordinatorChild{ case main - case emailSignUp - case emailSignIn + case signUp + case signIn /// SignIn이 AuthHome의 역할 } @@ -53,7 +53,7 @@ class AuthCoordinator: CoordinatorType { }else { self.pushViewController(viewController: mainAuthViewController) } - case .emailSignUp: + case .signUp: print("111111") CMCToastManager.shared.addToast(message: "🍎 여기는 아직이지롱~ 😀") // let emailSignUpViewController = EmailSignUpViewController( @@ -69,22 +69,20 @@ class AuthCoordinator: CoordinatorType { // }else { // self.pushViewController(viewController: emailSignUpViewController) // } - case .emailSignIn: - print("2222222") - CMCToastManager.shared.addToast(message: "🍎 여기도 아직이지롱~ 😀") -// let emailSignInViewController = EmailSignInViewController( -// viewModel: EmailSignInViewModel( -// coordinator: self, -// userEmailSignInUsecase: DefaultUserEmailSignInUsecase( -// userRepository: DefaultUserRepository() -// ) -// ) -// ) -// if self.navigationController.viewControllers.contains(where: {$0 is EmailSignInViewController}) { -// self.navigationController.popViewController(animated: true) -// }else { -// self.pushViewController(viewController: emailSignInViewController) -// } + case .signIn: + let signInViewController = SignInViewController( + viewModel: SignInViewModel( + coordinator: self, + authUsecase: DefaultAuthUsecase( + authRepository: DefaultAuthRepository() + ) + ) + ) + if self.navigationController.viewControllers.contains(where: {$0 is SignInViewController}) { + self.navigationController.popViewController(animated: true) + }else { + self.pushViewController(viewController: signInViewController) + } } }).disposed(by: disposeBag) } diff --git a/CMC/Sources/Presenter/Auth/MainAuthViewModel.swift b/CMC/Sources/Presenter/Auth/MainAuthViewModel.swift index 62fd7e6..c72a1d0 100644 --- a/CMC/Sources/Presenter/Auth/MainAuthViewModel.swift +++ b/CMC/Sources/Presenter/Auth/MainAuthViewModel.swift @@ -35,13 +35,13 @@ class MainAuthViewModel: ViewModelType{ input.signInBtnTapped .withUnretained(self) .subscribe(onNext: { owner, _ in - owner.coordinator?.userActionState.accept(.emailSignIn) + owner.coordinator?.userActionState.accept(.signIn) }).disposed(by: disposeBag) input.signUpBtnTapped .withUnretained(self) .subscribe(onNext: { owner, _ in - owner.coordinator?.userActionState.accept(.emailSignUp) + owner.coordinator?.userActionState.accept(.signUp) }).disposed(by: disposeBag) return Output() diff --git a/CMC/Sources/Presenter/Auth/SignIn/SignInViewController.swift b/CMC/Sources/Presenter/Auth/SignIn/SignInViewController.swift index d81f085..813f966 100644 --- a/CMC/Sources/Presenter/Auth/SignIn/SignInViewController.swift +++ b/CMC/Sources/Presenter/Auth/SignIn/SignInViewController.swift @@ -7,3 +7,236 @@ // import Foundation + +import RxCocoa +import RxSwift + +import DesignSystem +import SnapKit + +import UIKit + +final class SignInViewController: BaseViewController { + + // MARK: - UI + private lazy var navigationBar: CMCNavigationBar = { + let navigationBar = CMCNavigationBar( + accessoryLabelHidden: true + ) + return navigationBar + }() + + private lazy var titleLabel: UILabel = { + let label = UILabel() + label.text = "로그인" + label.numberOfLines = 2 + label.font = CMCFontFamily.Pretendard.bold.font(size: 26) + label.textColor = DesignSystemAsset.gray50.color + return label + }() + + private lazy var emailTextField: CMCTextField = { + let textField = CMCTextField( + placeHolder: "이메일을 입력해주세요", + textFieldSubTitle: "이메일", + accessoryType: .none, + keyboardType: .default + ) + return textField + }() + + private lazy var passwordTextField: CMCTextField = { + let textField = CMCTextField( + placeHolder: "비밀번호를 입력해주세요", + textFieldSubTitle: "비밀번호", + accessoryType: .image(image: CMCAsset._24x24hide.image), + keyboardType: .emailAddress + + ) + return textField + }() + + private lazy var forgetEmailLabel: UILabel = { + let label = UILabel() + let text = "아이디를 잊으셨나요?" + let attributedString = NSMutableAttributedString(string: text) + + let underLineAttributes: [NSAttributedString.Key: Any] = [ + .underlineStyle: NSUnderlineStyle.single.rawValue, + .baselineOffset : NSNumber(value: 4) + ] + attributedString.addAttributes(underLineAttributes, range: NSRange(location: 0, length: text.count)) + label.attributedText = attributedString + label.font = CMCFontFamily.Pretendard.bold.font(size: 13) + label.textColor = CMCAsset.gray500.color + return label + }() + + private lazy var forgetPasswordLabel: UILabel = { + let label = UILabel() + let text = "비밀번호를 잊으셨나요?" + let attributedString = NSMutableAttributedString(string: text) + + let underLineAttributes: [NSAttributedString.Key: Any] = [ + .underlineStyle: NSUnderlineStyle.single.rawValue, + .baselineOffset : NSNumber(value: 4) + ] + attributedString.addAttributes(underLineAttributes, range: NSRange(location: 0, length: text.count)) + label.attributedText = attributedString + label.font = CMCFontFamily.Pretendard.bold.font(size: 13) + label.textColor = CMCAsset.gray500.color + return label + }() + + private lazy var forgetStackView: UIStackView = { + let stackView = UIStackView(arrangedSubviews: [forgetEmailLabel, forgetPasswordLabel]) + stackView.axis = .horizontal + stackView.alignment = .center + stackView.spacing = 26 + return stackView + }() + + + private lazy var askSignUpLabel: UILabel = { + let label = UILabel() + label.text = "계정이 없으신가요?" + label.font = CMCFontFamily.Pretendard.medium.font(size: 15) + label.textColor = CMCAsset.gray700.color + return label + }() + + private lazy var goSignUpLabel: UILabel = { + let label = UILabel() + let text = "회원가입 하기" + let attributedString = NSMutableAttributedString(string: text) + + let underLineAttributes: [NSAttributedString.Key: Any] = [ + .underlineStyle: NSUnderlineStyle.single.rawValue, + .baselineOffset : NSNumber(value: 4) + ] + attributedString.addAttributes(underLineAttributes, range: NSRange(location: 0, length: text.count)) + label.attributedText = attributedString + label.font = CMCFontFamily.Pretendard.bold.font(size: 13) + label.textColor = CMCAsset.gray500.color + return label + }() + + private lazy var goSignUpStackView: UIStackView = { + let stackView = UIStackView(arrangedSubviews: [askSignUpLabel, goSignUpLabel]) + stackView.axis = .vertical + stackView.alignment = .center + stackView.spacing = 7 + return stackView + }() + + private lazy var signInButton: CMCButton = { + let button = CMCButton( + isRound: false, + iconTitle: nil, + type: .login(.disabled), + title: "로그인") + return button + }() + + // MARK: - Properties + private let viewModel: SignInViewModel + + // MARK: - Initializers + init( + viewModel: SignInViewModel + ) { + self.viewModel = viewModel + super.init() + } + + // MARK: - LifeCycle + + // MARK: - Methods + override func setAddSubView() { + view.addSubview(navigationBar) + view.addSubview(titleLabel) + view.addSubview(emailTextField) + view.addSubview(passwordTextField) + view.addSubview(forgetStackView) + view.addSubview(goSignUpStackView) + view.addSubview(signInButton) + } + + override func setConstraint() { + navigationBar.snp.makeConstraints { make in + make.top.equalTo(view.safeAreaLayoutGuide.snp.top) + make.leading.trailing.equalToSuperview() + make.height.equalTo(68) + } + + titleLabel.snp.makeConstraints { make in + make.top.equalTo(navigationBar.snp.bottom).offset(20) + make.leading.equalToSuperview().offset(24) + } + + emailTextField.snp.makeConstraints { make in + make.top.equalTo(titleLabel.snp.bottom).offset(30) + make.leading.equalToSuperview().offset(20) + make.trailing.equalToSuperview().offset(-28) + make.height.equalTo(74) + } + + passwordTextField.snp.makeConstraints { make in + make.top.equalTo(emailTextField.snp.bottom).offset(20) + make.leading.equalToSuperview().offset(20) + make.trailing.equalToSuperview().offset(-28) + make.height.equalTo(74) + } + + forgetStackView.snp.makeConstraints { make in + make.top.equalTo(passwordTextField.snp.bottom).offset(32) + make.centerX.equalToSuperview() + } + + goSignUpStackView.snp.makeConstraints { make in + make.bottom.equalTo(signInButton.snp.top).offset(-30) + make.centerX.equalToSuperview() + } + + signInButton.snp.makeConstraints { make in + make.bottom.equalTo(view.safeAreaLayoutGuide.snp.bottom).offset(-20) + make.leading.equalToSuperview().offset(24) + make.trailing.equalToSuperview().offset(-24) + make.height.equalTo(56) + } + } + + override func bind() { + let input = SignInViewModel.Input( + email: emailTextField.rx.text.orEmpty.asObservable(), + password: passwordTextField.rx.text.orEmpty.asObservable(), + backBtnTapped: navigationBar.backButton.rx.tapped().asObservable(), + forgetIDBtnTapped: forgetEmailLabel.rx.tapGesture().when(.recognized).map{_ in }.asObservable(), + forgetPasswordBtnTapped: forgetPasswordLabel.rx.tapGesture().when(.recognized).map{_ in }.asObservable(), + goSignUpButtonTapped: goSignUpLabel.rx.tapGesture().when(.recognized).map{_ in }.asObservable(), + goSignInButtonTapped: signInButton.rx.tap.asObservable() + ) + + + let output = viewModel.transform(input: input) + + output.signInBtnEnable + .withUnretained(self) + .subscribe(onNext: { owner, enable in + enable == true + ? owner.signInButton.rxType.accept(.login(.inactive)) + : owner.signInButton.rxType.accept(.login(.disabled)) + }) + .disposed(by: disposeBag) + + } + +} + + +// MARK: - Extension +extension SignInViewController { + override func touchesBegan(_ touches: Set, with event: UIEvent?) { + self.view.endEditing(true) + } +} diff --git a/CMC/Sources/Presenter/Auth/SignIn/SignInViewModel.swift b/CMC/Sources/Presenter/Auth/SignIn/SignInViewModel.swift index 4942cb0..94755fc 100644 --- a/CMC/Sources/Presenter/Auth/SignIn/SignInViewModel.swift +++ b/CMC/Sources/Presenter/Auth/SignIn/SignInViewModel.swift @@ -7,3 +7,83 @@ // import Foundation + +import RxCocoa +import RxSwift + +class SignInViewModel: ViewModelType{ + + struct Input { + let email: Observable + let password: Observable + let backBtnTapped: Observable + let forgetIDBtnTapped: Observable + let forgetPasswordBtnTapped: Observable + let goSignUpButtonTapped: Observable + let goSignInButtonTapped: Observable + } + + struct Output { + let signInBtnEnable: Observable + } + + var disposeBag: DisposeBag = DisposeBag() + weak var coordinator: AuthCoordinator? + private let authUsecase: AuthUsecase + + init( + coordinator: AuthCoordinator, + authUsecase: AuthUsecase + ) { + self.coordinator = coordinator + self.authUsecase = authUsecase + } + + func transform(input: Input) -> Output { + let signInBtnEnable = Observable.combineLatest(input.email, input.password) + .map { email, password in + return !email.isEmpty && !password.isEmpty + } + + input.goSignInButtonTapped + .withLatestFrom(Observable.combineLatest(input.email, input.password)) + .flatMapLatest { [weak self] email, password -> Observable> in + guard let self = self else { return .empty() } + let body = SignInBody(email: email, password: password) + return self.authUsecase.signIn(body: body) + .map { Result.success($0) } + .catch { error in + return .just(Result.failure(error)) + } + .asObservable() + } + .observe(on: MainScheduler.instance) + .subscribe(onNext: { [weak self] result in + switch result { + case .success(let model): + UserDefaultManager.shared.save(model.accessToken, for: .accessToken) + UserDefaultManager.shared.save(model.refreshToken, for: .refreshToken) + print("🍎 발급받은 악세스토큰: \(model.accessToken) 🍎") + self?.coordinator?.finish() + case .failure(let error): + print("🍎 발생한 에러: \(error) 🍎") + CMCToastManager.shared.addToast(message: "😵‍💫 로그인에 실패했습니다 ㅜ..ㅜ 😵‍💫") + } + }) + .disposed(by: disposeBag) + + input.backBtnTapped + .withUnretained(self) + .observe(on: MainScheduler.instance) + .subscribe(onNext: { owner, _ in + owner.coordinator?.popViewController() + }) + .disposed(by: disposeBag) + + return Output( + signInBtnEnable: signInBtnEnable + ) + } + + +} diff --git a/CMC/Sources/Utils/Commons/UserDefaultManager.swift b/CMC/Sources/Utils/Commons/UserDefaultManager.swift index 6eb5d91..0ff6e75 100644 --- a/CMC/Sources/Utils/Commons/UserDefaultManager.swift +++ b/CMC/Sources/Utils/Commons/UserDefaultManager.swift @@ -15,7 +15,8 @@ class UserDefaultManager { /// 요기에는 내 맘대로 추가 해야징~ enum Key: String { - case jwtToken + case accessToken + case refreshToken } func save(_ value: T, for key: Key) { diff --git a/CMC/Sources/Utils/Commons/Xcconfigs.swift b/CMC/Sources/Utils/Commons/Xcconfigs.swift index 983ae2f..5491191 100644 --- a/CMC/Sources/Utils/Commons/Xcconfigs.swift +++ b/CMC/Sources/Utils/Commons/Xcconfigs.swift @@ -10,6 +10,4 @@ import Foundation struct Xcconfig { static let BASE_URL = Bundle.main.infoDictionary?["Base_Url"] as! String - static let BASE_API_URL = Bundle.main.infoDictionary?["Base_Api_Url"] as! String - static let BUNDLE_ID = Bundle.main.infoDictionary?["Custom_Bundle_Id"] as! String } diff --git a/DesignSystem/Sources/CMCNavigationBar.swift b/DesignSystem/Sources/CMCNavigationBar.swift index 46e5ae0..6fa7a1e 100644 --- a/DesignSystem/Sources/CMCNavigationBar.swift +++ b/DesignSystem/Sources/CMCNavigationBar.swift @@ -38,7 +38,7 @@ public final class CMCNavigationBar: UIView { private var accessoryLabelHidden: Bool // MARK: - Initializers - init(accessoryLabelHidden: Bool) { + public init(accessoryLabelHidden: Bool) { self.accessoryLabelHidden = accessoryLabelHidden super.init(frame: .zero) diff --git a/DesignSystem/Sources/CMCTextField.swift b/DesignSystem/Sources/CMCTextField.swift index af53f2c..4293511 100644 --- a/DesignSystem/Sources/CMCTextField.swift +++ b/DesignSystem/Sources/CMCTextField.swift @@ -44,6 +44,7 @@ public final class CMCTextField: UIView{ let label = UILabel() label.font = DesignSystemFontFamily.Pretendard.bold.font(size: 14) label.textColor = DesignSystemAsset.gray200.color + label.text = textFieldSubTitle return label }() @@ -77,6 +78,7 @@ public final class CMCTextField: UIView{ private var disposeBag = DisposeBag() private var placeHolder: String + private var textFieldSubTitle: String private var accessoryImage: UIImage = UIImage() private var accessoryTitle: String = "" private var accessoryType: AccessoryType @@ -85,10 +87,10 @@ public final class CMCTextField: UIView{ public var rxType = BehaviorRelay(value: .def) public var accessoryState = BehaviorRelay(value: false) - /// 텍스트필드의 `placeHolder`, `maxCount`를 설정합니다. /// - Parameters: /// - placeHolder : placeHolder로 들어갈 텍스트 /// - accessoryType: 우측 악세서리에 들어가는 타입 (.none 이면 없음) + /// - textFieldSubTitle: 텍스트필드의 이름을 나타냄 /// - keyboardType: 키보드 타입 /// - Parameters (Optional): /// - Parameters (Accessable): @@ -97,13 +99,16 @@ public final class CMCTextField: UIView{ // MARK: - Initializers public init( placeHolder: String, + textFieldSubTitle: String, accessoryType: AccessoryType, keyboardType: UIKeyboardType ) { self.placeHolder = placeHolder + self.textFieldSubTitle = textFieldSubTitle self.keyboardType = keyboardType self.accessoryType = accessoryType + switch accessoryType { case .button(let title): self.accessoryTitle = title