526 lines
18 KiB
Swift

//
// Copyright (c) 2016 Google Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
import UIKit
// [START usermanagement_view_import]
import Firebase
// [END usermanagement_view_import]
import GoogleSignIn
import FBSDKCoreKit
import FBSDKLoginKit
import TwitterKit
@objc(MainViewController)
class MainViewController: UITableViewController, GIDSignInDelegate, GIDSignInUIDelegate {
let kSectionToken = 3
let kSectionProviders = 2
let kSectionUser = 1
let kSectionSignIn = 0
enum AuthProvider {
case AuthEmail
case AuthAnonymous
case AuthFacebook
case AuthGoogle
case AuthTwitter
case AuthCustom
}
/*! @var kOKButtonText
@brief The text of the "OK" button for the Sign In result dialogs.
*/
let kOKButtonText = "OK"
/*! @var kTokenRefreshedAlertTitle
@brief The title of the "Token Refreshed" alert.
*/
let kTokenRefreshedAlertTitle = "Token"
/*! @var kTokenRefreshErrorAlertTitle
@brief The title of the "Token Refresh error" alert.
*/
let kTokenRefreshErrorAlertTitle = "Get Token Error"
/** @var kSetDisplayNameTitle
@brief The title of the "Set Display Name" error dialog.
*/
let kSetDisplayNameTitle = "Set Display Name"
/** @var kUnlinkTitle
@brief The text of the "Unlink from Provider" error Dialog.
*/
let kUnlinkTitle = "Unlink from Provider"
/** @var kChangeEmailText
@brief The title of the "Change Email" button.
*/
let kChangeEmailText = "Change Email"
/** @var kChangePasswordText
@brief The title of the "Change Password" button.
*/
let kChangePasswordText = "Change Password"
/** @var handle
@brief The handler for the auth state listener, to allow cancelling later.
*/
var handle: FIRAuthStateDidChangeListenerHandle?
func showAuthPicker(providers: [AuthProvider]) {
let picker = UIAlertController(title: "Select Provider",
message: nil,
preferredStyle: .ActionSheet)
for provider in providers {
var action : UIAlertAction
switch(provider) {
case .AuthEmail:
action = UIAlertAction(title: "Email", style: .Default, handler: { (UIAlertAction) in
self.performSegueWithIdentifier("email", sender:nil)
})
case .AuthCustom:
action = UIAlertAction(title: "Custom", style: .Default, handler: { (UIAlertAction) in
self.performSegueWithIdentifier("customToken", sender: nil)
})
case .AuthAnonymous:
action = UIAlertAction(title: "Guest", style: .Default, handler: { (UIAlertAction) in
self.showSpinner({
// [START firebase_auth_anonymous]
FIRAuth.auth()?.signInAnonymouslyWithCompletion() { (user, error) in
// [START_EXCLUDE]
self.hideSpinner({
if let error = error {
self.showMessagePrompt(error.localizedDescription)
return
}
})
// [END_EXCLUDE]
}
// [END firebase_auth_anonymous]
})
})
case .AuthFacebook:
action = UIAlertAction(title: "Facebook", style: .Default, handler: { (UIAlertAction) in
let loginManager = FBSDKLoginManager()
loginManager.logInWithReadPermissions(["email"], fromViewController: self, handler: { (result, error) in
if let error = error {
self.showMessagePrompt(error.localizedDescription)
} else if(result.isCancelled) {
print("FBLogin cancelled")
} else {
// [START headless_facebook_auth]
let credential = FIRFacebookAuthProvider.credentialWithAccessToken(FBSDKAccessToken.currentAccessToken().tokenString)
// [END headless_facebook_auth]
self.firebaseLogin(credential)
}
})
})
case .AuthGoogle:
action = UIAlertAction(title: "Google", style: .Default, handler: { (UIAlertAction) in
GIDSignIn.sharedInstance().clientID = FIRApp.defaultApp()?.options.clientID
GIDSignIn.sharedInstance().uiDelegate = self
GIDSignIn.sharedInstance().delegate = self
GIDSignIn.sharedInstance().signIn()
})
case .AuthTwitter:
action = UIAlertAction(title: "Twitter", style: .Default, handler: { (UIAlertAction) in
Twitter.sharedInstance().logInWithCompletion() { (session, error) in
if let session = session {
// [START headless_twitter_auth]
let credential = FIRTwitterAuthProvider.credentialWithToken(session.authToken, secret: session.authTokenSecret)
// [END headless_twitter_auth]
self.firebaseLogin(credential)
} else {
self.showMessagePrompt((error?.localizedDescription)!)
}
}
})
}
picker.addAction(action)
}
picker.addAction(UIAlertAction(title: "Cancel", style: .Cancel, handler: nil))
presentViewController(picker, animated: true, completion: nil)
}
@IBAction func didTapSignIn(sender: AnyObject) {
showAuthPicker([
AuthProvider.AuthEmail,
AuthProvider.AuthAnonymous,
AuthProvider.AuthGoogle,
AuthProvider.AuthFacebook,
AuthProvider.AuthTwitter,
AuthProvider.AuthCustom
])
}
@IBAction func didTapLink(sender: AnyObject) {
var providers = Set([
AuthProvider.AuthEmail,
AuthProvider.AuthGoogle,
AuthProvider.AuthFacebook,
AuthProvider.AuthTwitter
])
// Remove any existing providers. Note that this is not a complete list of
// providers, so always check the documentation for a complete reference:
// https://firebase.google.com/docs/auth
let user = FIRAuth.auth()?.currentUser
for info in (user?.providerData)! {
if (info.providerID == FIREmailPasswordAuthProviderID) {
providers.remove(AuthProvider.AuthEmail)
} else if (info.providerID == FIRTwitterAuthProviderID) {
providers.remove(AuthProvider.AuthTwitter)
} else if (info.providerID == FIRFacebookAuthProviderID) {
providers.remove(AuthProvider.AuthFacebook)
} else if (info.providerID == FIRGoogleAuthProviderID) {
providers.remove(AuthProvider.AuthGoogle)
}
}
showAuthPicker(Array(providers))
}
func setTitleDisplay(user: FIRUser?) {
if let name = user?.displayName {
self.navigationItem.title = "Welcome \(name)"
} else {
self.navigationItem.title = "Authentication Example"
}
}
func firebaseLogin(credential: FIRAuthCredential) {
showSpinner({
if let user = FIRAuth.auth()?.currentUser {
// [START link_credential]
user.linkWithCredential(credential) { (user, error) in
// [START_EXCLUDE]
self.hideSpinner({
if let error = error {
self.showMessagePrompt(error.localizedDescription)
return
}
})
// [END_EXCLUDE]
}
// [END link_credential]
} else {
// [START signin_credential]
FIRAuth.auth()?.signInWithCredential(credential) { (user, error) in
// [START_EXCLUDE]
self.hideSpinner({
if let error = error {
self.showMessagePrompt(error.localizedDescription)
return
}
})
// [END_EXCLUDE]
}
// [END signin_credential]
}
})
}
// [START headless_google_auth]
func signIn(signIn: GIDSignIn!, didSignInForUser user: GIDGoogleUser!, withError error: NSError?) {
if let error = error {
self.showMessagePrompt(error.localizedDescription)
return
}
let authentication = user.authentication
let credential = FIRGoogleAuthProvider.credentialWithIDToken(authentication.idToken,
accessToken: authentication.accessToken)
// [START_EXCLUDE]
firebaseLogin(credential)
// [END_EXCLUDE]
}
// [END headless_google_auth]
@IBAction func didTapSignOut(sender: AnyObject) {
// [START signout]
let firebaseAuth = FIRAuth.auth()
do {
try firebaseAuth?.signOut()
} catch let signOutError as NSError {
print ("Error signing out: %@", signOutError)
}
// [END signout]
}
override func viewWillAppear(animated: Bool) {
super.viewWillAppear(animated)
handle = FIRAuth.auth()?.addAuthStateDidChangeListener() { (auth, user) in
self.setTitleDisplay(user)
self.tableView.reloadData()
}
}
override func viewWillDisappear(animated: Bool) {
super.viewWillDisappear(animated)
FIRAuth.auth()?.removeAuthStateDidChangeListener(handle!)
}
override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
switch section {
case kSectionSignIn:
return 1
case kSectionUser, kSectionToken:
if FIRAuth.auth()?.currentUser != nil {
return 1
} else {
return 0
}
case kSectionProviders:
if let user = FIRAuth.auth()?.currentUser {
return user.providerData.count
}
return 0
default:
return 0
}
}
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell: UITableViewCell?
switch indexPath.section {
case kSectionSignIn:
if FIRAuth.auth()?.currentUser != nil {
cell = tableView.dequeueReusableCellWithIdentifier("SignOut")
} else {
cell = tableView.dequeueReusableCellWithIdentifier("SignIn")
}
case kSectionUser:
cell = tableView.dequeueReusableCellWithIdentifier("Profile")
let user = FIRAuth.auth()?.currentUser
let emailLabel = cell?.viewWithTag(1) as! UILabel
let userIDLabel = cell?.viewWithTag(2) as! UILabel
let profileImageView = cell?.viewWithTag(3) as! UIImageView
emailLabel.text = user?.email
userIDLabel.text = user?.uid
let photoURL = user?.photoURL
struct last {
static var photoURL: NSURL? = nil
}
last.photoURL = photoURL; // to prevent earlier image overwrites later one.
if let photoURL = photoURL {
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0), {
let data = NSData.init(contentsOfURL: photoURL)
if let data = data {
let image = UIImage.init(data: data)
dispatch_async(dispatch_get_main_queue(), {
if (photoURL == last.photoURL) {
profileImageView.image = image
}
})
}
})
} else {
profileImageView.image = UIImage.init(named: "ic_account_circle")
}
case kSectionProviders:
cell = tableView.dequeueReusableCellWithIdentifier("Provider")
let userInfo = FIRAuth.auth()?.currentUser?.providerData[indexPath.row]
cell?.textLabel?.text = userInfo?.providerID
cell?.detailTextLabel?.text = userInfo?.uid
case kSectionToken:
cell = tableView.dequeueReusableCellWithIdentifier("Token")
default:
cell = nil
}
return cell!
}
override func tableView(tableView: UITableView, titleForDeleteConfirmationButtonForRowAtIndexPath indexPath: NSIndexPath) -> String? {
return "Unlink"
}
// Swipe to delete
override func tableView(tableView: UITableView, commitEditingStyle editingStyle: UITableViewCellEditingStyle, forRowAtIndexPath indexPath: NSIndexPath) {
if editingStyle == .Delete {
let providerID = FIRAuth.auth()?.currentUser?.providerData[indexPath.row].providerID
showSpinner({
// [START unlink_provider]
FIRAuth.auth()?.currentUser?.unlinkFromProvider(providerID!) { (user, error) in
// [START_EXCLUDE]
self.hideSpinner({
if let error = error {
self.showMessagePrompt(error.localizedDescription)
return
}
tableView.reloadData()
})
// [END_EXCLUDE]
}
// [END unlink_provider]
})
}
}
override func tableView(tableView: UITableView, heightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat {
if indexPath.section == kSectionUser {
return 200
}
return 44
}
override func numberOfSectionsInTableView(tableView: UITableView) -> Int {
return 4
}
@IBAction func didTokenRefresh(sender: AnyObject) {
let action: FIRAuthTokenCallback = {(token, error) in
let okAction = UIAlertAction.init(title: self.kOKButtonText, style: .Default)
{action in print(self.kOKButtonText)}
if let error = error {
let alertController = UIAlertController.init(title: self.kTokenRefreshErrorAlertTitle,
message: error.localizedDescription, preferredStyle: .Alert)
alertController.addAction(okAction)
self.presentViewController(alertController, animated: true, completion: nil)
return
}
// Log token refresh event to Scion.
FIRAnalytics.logEventWithName("tokenrefresh", parameters: nil)
let alertController = UIAlertController.init(title: self.kTokenRefreshedAlertTitle,
message: token, preferredStyle: .Alert)
alertController.addAction(okAction)
self.presentViewController(alertController, animated: true, completion: nil)
}
// [START token_refresh]
FIRAuth.auth()?.currentUser?.getTokenForcingRefresh(true, completion: action)
// [END token_refresh]
}
/** @fn setDisplayName
@brief Changes the display name of the current user.
*/
@IBAction func didSetDisplayName(sender: AnyObject) {
showTextInputPromptWithMessage("Display Name:") { (userPressedOK, userInput) in
if let userInput = userInput {
self.showSpinner({
// [START profile_change]
let changeRequest = FIRAuth.auth()?.currentUser?.profileChangeRequest()
changeRequest?.displayName = userInput
changeRequest?.commitChangesWithCompletion() { (error) in
// [END profile_change]
self.hideSpinner({
self.showTypicalUIForUserUpdateResultsWithTitle(self.kSetDisplayNameTitle, error: error)
self.setTitleDisplay(FIRAuth.auth()?.currentUser)
})
}
})
} else {
self.showMessagePrompt("displayname can't be empty")
}
}
}
/** @fn requestVerifyEmail
@brief Requests a "verify email" email be sent.
*/
@IBAction func didRequestVerifyEmail(sender: AnyObject) {
showSpinner({
// [START send_verification_email]
FIRAuth.auth()?.currentUser?.sendEmailVerificationWithCompletion({ (error) in
// [START_EXCLUDE]
self.hideSpinner({
if let error = error {
self.showMessagePrompt(error.localizedDescription)
return
}
self.showMessagePrompt("Sent")
})
// [END_EXCLUDE]
})
// [END send_verification_email]
})
}
/** @fn changeEmail
@brief Changes the email address of the current user.
*/
@IBAction func didChangeEmail(sender: AnyObject) {
showTextInputPromptWithMessage("Email Address:") { (userPressedOK, userInput) in
if let userInput = userInput {
self.showSpinner({
// [START change_email]
FIRAuth.auth()?.currentUser?.updateEmail(userInput) { (error) in
// [START_EXCLUDE]
self.hideSpinner({
self.showTypicalUIForUserUpdateResultsWithTitle(self.kChangeEmailText, error:error)
})
// [END_EXCLUDE]
}
// [END change_email]
})
} else {
self.showMessagePrompt("email can't be empty")
}
}
}
/** @fn changePassword
@brief Changes the password of the current user.
*/
@IBAction func didChangePassword(sender: AnyObject) {
showTextInputPromptWithMessage("New Password:") { (userPressedOK, userInput) in
if let userInput = userInput {
self.showSpinner({
// [START change_password]
FIRAuth.auth()?.currentUser?.updatePassword(userInput) { (error) in
// [START_EXCLUDE]
self.hideSpinner({
self.showTypicalUIForUserUpdateResultsWithTitle(self.kChangePasswordText, error:error)
})
// [END_EXCLUDE]
}
// [END change_password]
})
} else {
self.showMessagePrompt("password can't be empty")
}
}
}
// MARK: - Helpers
/** @fn showTypicalUIForUserUpdateResultsWithTitle:error:
@brief Shows a @c UIAlertView if error is non-nil with the localized description of the error.
@param resultsTitle The title of the @c UIAlertView
@param error The error details to display if non-nil.
*/
func showTypicalUIForUserUpdateResultsWithTitle(resultsTitle: String, error: NSError?) {
if let error = error {
let message = "\(error.domain) (\(error.code))\n\(error.localizedDescription)"
let okAction = UIAlertAction.init(title: self.kOKButtonText, style: .Default)
{action in print(self.kOKButtonText)}
let alertController = UIAlertController.init(title: resultsTitle,
message: message, preferredStyle: .Alert)
alertController.addAction(okAction)
self.presentViewController(alertController, animated: true, completion: nil)
return
}
tableView.reloadData()
}
}