Instantly share code, notes, and snippets.
Last active
August 28, 2019 06:56
-
Star
(0)
0
You must be signed in to star a gist -
Fork
(0)
0
You must be signed in to fork a gist
-
Save danipralea/13b09fe6b09963cc465f7814cc83c8c2 to your computer and use it in GitHub Desktop.
login / register / social authentication
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// | |
// 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) | |
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) | |
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