source/UberRides/RideRequestViewController.swift (134 lines of code) (raw):
//
// RideRequestViewController.swift
// UberRides
//
// Copyright © 2015 Uber Technologies, Inc. All rights reserved.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
import UIKit
import MapKit
import UberCore
/**
* Delegate Protocol to pass errors from the internal RideRequestView outward if necessary.
* For example, you might want to dismiss the View Controller if it experiences an error
- Warning: The Ride Request Widget is deprecated, and will no longer work for new apps.
Existing apps have until 05/31/2018 to migrate. See the Uber API Changelog for more details.
*/
@objc(UBSDKRideRequestViewControllerDelegate) public protocol RideRequestViewControllerDelegate {
/**
Delegate method to pass on errors from the RideRequestView that can't be handled
by the RideRequestViewController
- parameter rideRequestViewController: The RideRequestViewController that experienced the error
- parameter error: The NSError that was experienced, with a code related to the appropriate RideRequestViewErrorType
*/
@objc func rideRequestViewController(_ rideRequestViewController: RideRequestViewController, didReceiveError error: NSError)
}
/**
View controller to wrap the RideRequestView
- Warning: The Ride Request Widget is deprecated, and will no longer work for new apps.
Existing apps have until 05/31/2018 to migrate. See the Uber API Changelog for more details.
*/
@objc (UBSDKRideRequestViewController) public class RideRequestViewController: UIViewController {
/// The RideRequestViewControllerDelegate to handle the errors
@objc public var delegate: RideRequestViewControllerDelegate?
/// The LoginManager to use for managing the login process
@objc public var loginManager: LoginManager
lazy var rideRequestView: RideRequestView = RideRequestView()
static let sourceString = "ride_request_widget"
private var accessTokenWasUnauthorizedOnPreviousAttempt = false
private var loginCompletion: ((_ accessToken: AccessToken?, _ error: NSError?) -> Void)?
/**
Initializes a RideRequestViewController using the provided coder. By default,
uses the default token identifier and access group
- parameter aDecoder: The Coder to use
- returns: An initialized RideRequestViewController, or nil if something went wrong
*/
@objc public required init?(coder aDecoder: NSCoder) {
loginManager = LoginManager()
super.init(coder: aDecoder)
let defaultRideParameters = RideParametersBuilder()
defaultRideParameters.source = RideRequestViewController.sourceString
rideRequestView.rideParameters = defaultRideParameters.build()
}
/**
Designated initializer for the RideRequestViewController.
- parameter rideParameters: The RideParameters to use for prefilling the RideRequestView.
- parameter loginManager: The LoginManger to use for logging in (if required). Also uses its values for token identifier & access group to check for an access token
- returns: An initialized RideRequestViewController
*/
@objc public init(rideParameters: RideParameters, loginManager: LoginManager) {
self.loginManager = loginManager
super.init(nibName: nil, bundle: nil)
rideParameters.source = rideParameters.source ?? RideRequestViewController.sourceString
rideRequestView.rideParameters = rideParameters
rideRequestView.accessToken = TokenManager.fetchToken(identifier: loginManager.accessTokenIdentifier, accessGroup: loginManager.keychainAccessGroup)
}
// MARK: View Lifecycle
public override func viewDidLoad() {
super.viewDidLoad()
self.edgesForExtendedLayout = UIRectEdge()
self.view.backgroundColor = UIColor.white
setupRideRequestView()
}
public override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
load()
}
public override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
accessTokenWasUnauthorizedOnPreviousAttempt = false
}
// MARK: UIViewController
public override var supportedInterfaceOrientations : UIInterfaceOrientationMask {
return [.portrait, .portraitUpsideDown]
}
// MARK: Internal
func load() {
if let accessToken = TokenManager.fetchToken(identifier: loginManager.accessTokenIdentifier, accessGroup: loginManager.keychainAccessGroup) {
rideRequestView.accessToken = accessToken
rideRequestView.load()
} else {
loginManager.login(requestedScopes: [.rideWidgets], presentingViewController: self) { accessToken, error in
if let accessToken = accessToken {
self.rideRequestView.accessToken = accessToken
self.rideRequestView.load()
} else {
self.delegate?.rideRequestViewController(self, didReceiveError: RideRequestViewErrorFactory.errorForType(.accessTokenMissing))
}
}
}
}
func stopLoading() {
rideRequestView.cancelLoad()
}
func displayNetworkErrorAlert() {
self.rideRequestView.cancelLoad()
let alertController = UIAlertController(title: nil, message: NSLocalizedString("The Ride Request Widget encountered a problem.", bundle: Bundle(for: type(of: self)), comment: "The Ride Request Widget encountered a problem."), preferredStyle: .alert)
let tryAgainAction = UIAlertAction(title: NSLocalizedString("Try Again", bundle: Bundle(for: type(of: self)), comment: "Try Again"), style: .default, handler: { (UIAlertAction) -> Void in
self.load()
})
let cancelAction = UIAlertAction(title: NSLocalizedString("Cancel", bundle: Bundle(for: type(of: self)), comment: "Cancel"), style: .cancel, handler: { (UIAlertAction) -> Void in
self.delegate?.rideRequestViewController(self, didReceiveError: RideRequestViewErrorFactory.errorForType(.networkError))
})
alertController.addAction(tryAgainAction)
alertController.addAction(cancelAction)
self.present(alertController, animated: true, completion: nil)
}
func displayNotSupportedErrorAlert() {
let alertController = UIAlertController(title: nil, message: NSLocalizedString("The operation you are attempting is not supported on the current device.", bundle: Bundle(for: type(of: self)), comment: "The operation you are attempting is not supported on the current device."), preferredStyle: .alert)
let okayAction = UIAlertAction(title: NSLocalizedString("OK", bundle: Bundle(for: type(of: self)), comment: "OK"), style: .default, handler: nil)
alertController.addAction(okayAction)
self.present(alertController, animated: true, completion: nil)
}
//MARK: Private
private func setupRideRequestView() {
self.view.addSubview(rideRequestView)
rideRequestView.translatesAutoresizingMaskIntoConstraints = false
let views = ["rideRequestView": rideRequestView]
let horizontalConstraints = NSLayoutConstraint.constraints(withVisualFormat: "H:|[rideRequestView]|", options: NSLayoutConstraint.FormatOptions(rawValue: 0), metrics: nil, views: views)
let verticalConstraints = NSLayoutConstraint.constraints(withVisualFormat: "V:|[rideRequestView]|", options: NSLayoutConstraint.FormatOptions(rawValue: 0), metrics: nil, views: views)
self.view.addConstraints(horizontalConstraints)
self.view.addConstraints(verticalConstraints)
rideRequestView.delegate = self
}
}
//MARK: RideRequestView Delegate
extension RideRequestViewController : RideRequestViewDelegate {
public func rideRequestView(_ rideRequestView: RideRequestView, didReceiveError error: NSError) {
let errorType = RideRequestViewErrorType(rawValue: error.code) ?? .unknown
switch errorType {
case .networkError:
self.displayNetworkErrorAlert()
break
case .notSupported:
self.displayNotSupportedErrorAlert()
break
case .accessTokenMissing:
fallthrough
case .accessTokenExpired:
if accessTokenWasUnauthorizedOnPreviousAttempt {
fallthrough
}
attemptTokenRefresh()
break
default:
self.delegate?.rideRequestViewController(self, didReceiveError: error)
break
}
}
private func attemptTokenRefresh() {
let identifer = loginManager.accessTokenIdentifier
let group = loginManager.keychainAccessGroup
guard let accessToken = TokenManager.fetchToken(identifier: identifer, accessGroup: group), let refreshToken = accessToken.refreshToken else {
accessTokenWasUnauthorizedOnPreviousAttempt = true
_ = TokenManager.deleteToken(identifier: identifer, accessGroup: group)
self.load()
return
}
_ = TokenManager.deleteToken(identifier: identifer, accessGroup: group)
let ridesClient = RidesClient()
ridesClient.refreshAccessToken(usingRefreshToken: refreshToken) { (accessToken, response) in
if let token = accessToken {
_ = TokenManager.save(accessToken: token, tokenIdentifier: identifer, accessGroup: group)
}
self.load()
}
}
}