Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
101 changes: 47 additions & 54 deletions hack_iOS/Sources/View/LoginViewController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -28,24 +28,25 @@ final class LoginViewController: UIViewController, UITextFieldDelegate {
@IBOutlet private weak var loginButton: UIButton! {
didSet {
loginButton.setTitle("Login", for: .normal)
loginButton.addTarget(self, action: #selector(tapLoginButton), for: .touchUpInside)
}
}

@IBOutlet private weak var moveToSignUpViewButton: UIButton! {
didSet {
moveToSignUpViewButton.setTitle("SignupView", for: .normal)
moveToSignUpViewButton.addTarget(self, action: #selector(tapMoveToSignUpViewButton), for: .touchUpInside)
}
}

private let authRepository: AuthRepository
private let keychainAccessRepository: KeychainAccessRepository
private let disposeBag = DisposeBag()
private let viewModel: LoginViewModelType

init(authRepository: AuthRepository = .init(), keychainAccessRepository: KeychainAccessRepository = .init()) {
self.authRepository = authRepository
self.keychainAccessRepository = keychainAccessRepository
init(
authRepository: AuthRepository = .init(),
keychainAccessRepository: KeychainAccessRepository = .init()
){
self.viewModel = LoginViewModel(
dependency: .init(authrepository: authRepository, keychainAccessRepository: keychainAccessRepository)
)
super.init(nibName: nil, bundle: nil)
}

Expand All @@ -56,61 +57,53 @@ final class LoginViewController: UIViewController, UITextFieldDelegate {

override func viewDidLoad() {
super.viewDidLoad()
bindViewModel()
}

override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
self.view.endEditing(true)
}

@objc private func tapLoginButton() {
login()
}

@objc private func tapMoveToSignUpViewButton() {
let rootVC = SignUpViewController()
let navVC = UINavigationController(rootViewController: rootVC)
navVC.modalPresentationStyle = .fullScreen
present(navVC, animated: true)
}

func textFieldShouldReturn(_ textField: UITextField) -> Bool {
userNameTextField.resignFirstResponder()
return true
}

private func login() {
guard
let username = userNameTextField.text,
let password = passwordTextField.text
else {
return
}
private func bindViewModel() {

//input
userNameTextField.rx.text.orEmpty.asObservable()
.bind(to: viewModel.input.username)
.disposed(by: disposeBag)

passwordTextField.rx.text.orEmpty.asObservable()
.bind(to: viewModel.input.password)
.disposed(by: disposeBag)

authRepository.login(username: username, password: password)
.subscribe(on: SerialDispatchQueueScheduler(qos: .background))
.observe(on: MainScheduler.instance)
.subscribe( onSuccess: { [weak self] token in
guard let me = self else { return }
guard let authToken = token["token"] else { return }
me.keychainAccessRepository.save(token: authToken)
loginButton.rx.tap
.bind(to: Binder(self) { me, _ in
me.viewModel.input.loginButtonTapped()
})
.disposed(by: disposeBag)

//output
viewModel.output.loginSucceeded.asObservable()
.bind(to: Binder(self) { me, _ in
let rootVC = ListViewController()
let navVC = UINavigationController(rootViewController: rootVC)
navVC.modalPresentationStyle = .fullScreen
me.present(navVC, animated: true)
})
.disposed(by: disposeBag)

//サインアップ画面へ遷移
moveToSignUpViewButton.rx.tap.asDriver()
.drive(onNext: { [weak self] in
let rootVC = SignUpViewController()
let navVC = UINavigationController(rootViewController: rootVC)
navVC.modalPresentationStyle = .fullScreen
self?.present(navVC, animated: true)
},
onFailure: { error in
guard let error = error as? APIError else { return }
switch error {
case .decode(let error):
print(error)
case .network(let error):
print(error)
case .unknown(let error):
print(error)
case .noResponse:
print("No Response")
}
})
.disposed(by: disposeBag)
}

override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
self.view.endEditing(true)
}

func textFieldShouldReturn(_ textField: UITextField) -> Bool {
userNameTextField.resignFirstResponder()
return true
}
}
86 changes: 85 additions & 1 deletion hack_iOS/Sources/ViewModel/LoginViewModel.swift
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,91 @@
//

import Foundation
import RxSwift
import RxCocoa

class LoginViewModel {
protocol LoginViewModelType {
var input: LoginViewModelInput { get }
var output: LoginViewModelOutput { get }
}

protocol LoginViewModelInput {
var username: BehaviorRelay<String> { get }
var password: BehaviorRelay<String> { get }
func loginButtonTapped()
func moveToSignUpButtonTapped()
}

protocol LoginViewModelOutput {
var loginSucceeded: Signal<Void> { get }
}

final class LoginViewModel: LoginViewModelType, LoginViewModelInput, LoginViewModelOutput {

struct Dependency {
var authrepository: AuthRepository
var keychainAccessRepository: KeychainAccessRepository
}

let username = BehaviorRelay<String>(value: "")
let password = BehaviorRelay<String>(value: "")

private let loginButtonTappedRelay = PublishRelay<Void>()
private let moveToSignUpButtonTappedRelay = PublishRelay<Void>()

private let loginSucceededRelay = PublishRelay<Void>()
var loginSucceeded: Signal<Void> {
loginSucceededRelay.asSignal()
}

private let dependency: Dependency
private let disposeBag = DisposeBag()

var input: LoginViewModelInput { self }
var output: LoginViewModelOutput { self }

init(dependency: Dependency) {
self.dependency = dependency

let loginEvent = loginButtonTappedRelay.asObservable()
.withLatestFrom(username)
.withLatestFrom(password) { ($0, $1) } // optionalの場合はエラーを吐く....
.filter { username, password in
!username.isEmpty && !password.isEmpty
}
.flatMap { username, password in
dependency.authrepository.login(username: username, password: password)
.asObservable().materialize()
}
.share()

loginEvent
.flatMap { $0.element.map(Observable.just) ?? .empty() }
.subscribe( Binder(self) { me, token in
guard let authToken = token["token"] else { return }
me.dependency.keychainAccessRepository.save(token: authToken)
me.loginSucceededRelay.accept(())
})
.disposed(by: disposeBag)

loginEvent
.flatMap { $0.error.map(Observable.just) ?? .empty() }
.subscribe( Binder(self) { _, error in
guard let error = error as? APIError else { return }
switch error {
case .decode(let error):
print(error)
case .network(let error):
print(error)
case .unknown(let error):
print(error)
case .noResponse:
print("No Response")
}
})
.disposed(by: disposeBag)
}

func loginButtonTapped() { loginButtonTappedRelay.accept(()) }
func moveToSignUpButtonTapped() { moveToSignUpButtonTappedRelay.accept(()) }
}