Skip to content

Instantly share code, notes, and snippets.

@danipralea
Last active August 28, 2019 06:56
Show Gist options
  • Save danipralea/13b09fe6b09963cc465f7814cc83c8c2 to your computer and use it in GitHub Desktop.
Save danipralea/13b09fe6b09963cc465f7814cc83c8c2 to your computer and use it in GitHub Desktop.
login / register / social authentication
//
// AuthorizationViewModel.swift
// Augsoma
//
// Created by Danut Pralea on 30/05/2019.
// Copyright © 2019 Augsoma. All rights reserved.
//
import Foundation
import RxCocoa
import RxSwift
import FacebookLogin
import FacebookCore
import FBSDKCoreKit
import FBSDKLoginKit
import Accounts
import RxSwiftExt
import GoogleSignIn
import SVProgressHUD
enum AuthorizationViewMode: String {
case login, signup
}
enum SignupType: String {
case patients, physicians
}
class AuthorizationViewModel: NSObject, HasDisposeBag {
struct UIInputs {
let actionPerformed: Observable<Void>
let connectWithFacebook: Observable<Void>
let connectWithGoogle: Observable<Void>
let signupTypeControl: Observable<Int>
let loginViewData: Observable<LoginData>
let patientViewData: Observable<SignupData>
let doctorViewData: Observable<SignupData>
}
let mode = BehaviorRelay<AuthorizationViewMode>(value: .login)
let signupType = BehaviorRelay<SignupType>(value: .patients)
let viewController = BehaviorRelay<UIViewController?>(value: nil)
let dismiss = PublishSubject<Void>()
let performSocialSignup = PublishSubject<SocialLoginType>()
let performLogin = PublishSubject<Void>()
let socialAuthorizationType = BehaviorRelay<SocialLoginType>(value: .facebook)
let googleSocialDetails = BehaviorRelay<SocialAuthentificationDetails?>(value: nil)
let socialLoginResponse = BehaviorRelay<Event<User>?>(value: nil)
init(inputs: UIInputs, mode: AuthorizationViewMode) {
self.mode.accept(mode)
super.init()
// check the selected segmented index from the patients/physicians segmented control
inputs.signupTypeControl
.map { $0 == 0 ? SignupType.patients : SignupType.physicians }
.bind(to: signupType)
.disposed(by: disposeBag)
// combine all the data
let fieldData: Observable<AuthProtocol> = Observable.combineLatest(signupType, inputs.loginViewData, inputs.patientViewData, inputs.doctorViewData) { signupType, login, patientSignup, doctorSignup in
if mode == .login {
return login
} else {
if signupType == .patients {
return patientSignup
} else {
return doctorSignup
}
}
}
let combined = Observable.combineLatest(fieldData, signupType)
// login
inputs.actionPerformed
.withLatestFrom(combined)
.filter { $0.0 is LoginData }
.do(onNext: { _,_ in
SVProgressHUD.show()
})
.flatMapLatest {
APIService.login(email: $0.0.email ?? "", password: $0.0.password ?? "")
.catchError({ error -> Observable<User> in
if let customError = error as? CustomError {
AugsomaMessages.displayStatusBarMessage(message: customError.localizedDescription, theme: .error)
}
return .empty()
})
}
.subscribe(onNext: { user in
CommonSessionData.shared.currentUser = user
SVProgressHUD.dismiss()
AppRouter.switchToHome()
})
.disposed(by: disposeBag)
// signup
inputs.actionPerformed
.share()
.withLatestFrom(combined)
.filter { $0.0 is SignupData }
.do(onNext: { _,_ in
SVProgressHUD.show()
})
.flatMapLatest {
// note: allowed downforced optional, as it's been previously type filtered
APIService.signup(data: $0.0 as! SignupData)
.catchError({ error -> Observable<User> in
AugsomaMessages.displayError(error)
return .empty()
})
}
.subscribe(onNext: { user in
CommonSessionData.shared.currentUser = user
SVProgressHUD.dismiss()
AppRouter.switchToAgreements()
})
.disposed(by: disposeBag)
// facebook
inputs.connectWithFacebook
.share()
.map {
SocialLoginType.facebook
}
.bind(to: self.socialAuthorizationType)
.disposed(by: disposeBag)
inputs.connectWithFacebook
.share()
.withLatestFrom(viewController)
.unwrap()
.flatMapLatest { requestFacebookAccess($0) }
.flatMap { _ in fetchFacebookDetails() }
.do(onNext: { details in
SVProgressHUD.show()
})
.flatMapLatest { [weak self] details in
APIService.facebookLogin(details: details, isDoctor: mode == .login ? nil : (self?.signupType.value == .physicians))
.materialize()
}
.do(onNext: { details in
SVProgressHUD.dismiss()
})
.bind(to: socialLoginResponse)
.disposed(by: disposeBag)
// google
inputs.connectWithGoogle
.share()
.map {
SocialLoginType.google
}
.bind(to: self.socialAuthorizationType)
.disposed(by: disposeBag)
inputs.connectWithGoogle
.share()
.subscribe(onNext: { _ in
GIDSignIn.sharedInstance()?.signIn()
}).disposed(by: disposeBag)
GIDSignIn.sharedInstance().delegate = self
googleSocialDetails
.filter{ $0 != nil }
.distinctUntilChanged()
.do(onNext: { details in
SVProgressHUD.show()
})
.flatMapLatest { [weak self] details in
APIService.googleLogin(details: details, isDoctor: mode == .login ? nil : (self?.signupType.value == .physicians))
.materialize()
}
.do(onNext: { details in
SVProgressHUD.dismiss()
})
.bind(to: socialLoginResponse)
.disposed(by: disposeBag)
Observable
.combineLatest(socialLoginResponse, socialAuthorizationType)
.subscribe(onNext: { [weak self] response, type in
guard let self = self, let response = response else { return }
switch response {
case .completed:
print("master sequence completed")
case .error(let error):
if let customError = error as? CustomError {
guard (customError == CustomError.responseFailed(reason: .googleMissingIsDoctor)) || (customError == CustomError.responseFailed(reason: .facebookMissingIsDoctor)) else {
AugsomaMessages.displayError(error)
return
}
}
self.performSocialSignup.onNext(type)
case .next(let user):
CommonSessionData.shared.currentUser = user
guard mode == .signup else {
AppRouter.switchToHome()
return
}
AppRouter.switchToAgreements()
}
}).disposed(by: disposeBag)
}
}
// MARK: Google
extension AuthorizationViewModel: GIDSignInDelegate {
func sign(_ signIn: GIDSignIn!, didSignInFor user: GIDGoogleUser!,
withError error: Error!) {
if let error = error {
print("\(error.localizedDescription)")
} else {
var socialDetails = SocialAuthentificationDetails()
// Perform any operations on signed in user here.
if let userId = user.userID { // For client-side use only!
socialDetails.userId = userId
}
if let idToken = user.authentication.idToken {
socialDetails.token = idToken
}
if let fullName = user.profile.name {
socialDetails.fullName = fullName
}
if let email = user.profile.email {
socialDetails.email = email
}
self.googleSocialDetails.accept(socialDetails)
AuthorizationManager.socialAuthentificationDetails = socialDetails
}
}
}
// MARK: Facebook
private
func fetchFacebookDetails() -> Observable<SocialAuthentificationDetails> {
return Observable.create { observer -> Disposable in
let graphRequest = GraphRequest(graphPath: "me", parameters: ["fields" : "email, name, id"])
graphRequest.start { (_, result, error) in
if let error = error {
observer.onError(error)
}
guard let objectMap = result as? [String: Any] else {
observer.onError(CustomError.missingDataFailed(reason: .missingData))
observer.onCompleted()
return
}
var socialDetails = SocialAuthentificationDetails()
if let email = objectMap["email"] as? String {
socialDetails.email = email
}
if let id = objectMap["id"] as? String {
socialDetails.userId = id
}
if let name = objectMap["name"] as? String {
socialDetails.fullName = name
}
AuthorizationManager.socialAuthentificationDetails = socialDetails
observer.onNext(socialDetails)
observer.onCompleted()
}
return Disposables.create()
}
}
private
func requestFacebookAccess(_ viewController: UIViewController) -> Observable<LoginResult> {
print("Access")
return Observable.create { observer -> Disposable in
let loginManager = LoginManager()
loginManager.logIn(permissions: [Permission.email, Permission.publicProfile],
viewController: viewController,
completion: { result in
observer.onNext(result)
observer.onCompleted()
})
return Disposables.create()
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment