-
Notifications
You must be signed in to change notification settings - Fork 313
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Make FeedbackViewController public #1605
Changes from 11 commits
2692fd5
5dd01cd
194a5b1
bc32b5e
e77d7f4
83783c1
f9c3b5e
b7b79f3
4fa5157
0801b8b
0d465ed
4f56aed
c058839
72e3740
eaa640a
41bf3ac
7ccfa2d
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -3,29 +3,46 @@ import MapboxCoreNavigation | |
import AVFoundation | ||
|
||
extension FeedbackViewController: UIViewControllerTransitioningDelegate { | ||
func animationController(forDismissed dismissed: UIViewController) -> UIViewControllerAnimatedTransitioning? { | ||
@objc public func animationController(forDismissed dismissed: UIViewController) -> UIViewControllerAnimatedTransitioning? { | ||
abortAutodismiss() | ||
return DismissAnimator() | ||
} | ||
|
||
func animationController(forPresented presented: UIViewController, presenting: UIViewController, source: UIViewController) -> UIViewControllerAnimatedTransitioning? { | ||
@objc public func animationController(forPresented presented: UIViewController, presenting: UIViewController, source: UIViewController) -> UIViewControllerAnimatedTransitioning? { | ||
return PresentAnimator() | ||
} | ||
|
||
func interactionControllerForDismissal(using animator: UIViewControllerAnimatedTransitioning) -> UIViewControllerInteractiveTransitioning? { | ||
@objc public func interactionControllerForDismissal(using animator: UIViewControllerAnimatedTransitioning) -> UIViewControllerInteractiveTransitioning? { | ||
return interactor.hasStarted ? interactor : nil | ||
} | ||
} | ||
|
||
typealias FeedbackSection = [FeedbackItem] | ||
|
||
class FeedbackViewController: UIViewController, DismissDraggable, UIGestureRecognizerDelegate { | ||
/** | ||
The `FeedbackViewControllerDelegate` protocol provides methods for responding to feedback events. | ||
*/ | ||
@objc public protocol FeedbackViewControllerDelegate { | ||
|
||
/** | ||
Called when the user opens the feedback form. | ||
*/ | ||
@objc optional func feedbackViewControllerDidOpenFeedback(_ feedbackViewController: FeedbackViewController) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Name this method |
||
|
||
typealias SendFeedbackHandler = (FeedbackItem) -> Void | ||
/** | ||
Called when the user submits a feedback event. | ||
*/ | ||
@objc optional func feedbackViewController(_ feedbackViewController: FeedbackViewController, didSendFeedbackAssigned: UUID, feedback: FeedbackItem) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Name this method |
||
|
||
var sendFeedbackHandler: SendFeedbackHandler? | ||
var dismissFeedbackHandler: (() -> Void)? | ||
var sections = [FeedbackSection]() | ||
/** | ||
Called when a `FeedbackViewController` is dismissed for any reason without giving explicit feedback. | ||
*/ | ||
@objc optional func feedbackViewControllerDidCancelFeedback(_ feedbackViewController: FeedbackViewController) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Would it be possible to pass in the FeedbackItem so the application can distinguish between cancellations? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Negative. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Then |
||
} | ||
|
||
/** | ||
A view controller containing a grid of buttons the user can use to denote an issue their current navigation experience. | ||
*/ | ||
@objc(MBFeedbackViewController) | ||
public class FeedbackViewController: UIViewController, DismissDraggable, UIGestureRecognizerDelegate { | ||
var activeFeedbackItem: FeedbackItem? | ||
|
||
static let sceneTitle = NSLocalizedString("FEEDBACK_TITLE", value: "Report Problem", comment: "Title of view controller for sending feedback") | ||
|
@@ -35,6 +52,13 @@ class FeedbackViewController: UIViewController, DismissDraggable, UIGestureRecog | |
|
||
let interactor = Interactor() | ||
|
||
/** | ||
The feedback items that are visible and selectable by the user. | ||
*/ | ||
public var sections: [FeedbackItem] = [.turnNotAllowed, .closure, .reportTraffic, .confusingInstructions, .generalMapError, .badRoute] | ||
|
||
public var delegate: FeedbackViewControllerDelegate? | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. nit: weak |
||
|
||
lazy var collectionView: UICollectionView = { | ||
let view: UICollectionView = UICollectionView(frame: .zero, collectionViewLayout: flowLayout) | ||
view.translatesAutoresizingMaskIntoConstraints = false | ||
|
@@ -71,7 +95,40 @@ class FeedbackViewController: UIViewController, DismissDraggable, UIGestureRecog | |
return fullHeight | ||
} | ||
|
||
override func viewDidLoad() { | ||
/** | ||
The events manager used to send feedback events. | ||
*/ | ||
public var eventsManager: EventsManager | ||
|
||
var uuid: UUID { | ||
return eventsManager.recordFeedback() | ||
} | ||
|
||
/** | ||
Initialize a new FeedbackViewController from an `EventsManager`. | ||
*/ | ||
@objc public init(eventsManager: EventsManager) { | ||
self.eventsManager = eventsManager | ||
super.init(nibName: nil, bundle: nil) | ||
commonInit() | ||
} | ||
|
||
public override func encode(with aCoder: NSCoder) { | ||
aCoder.encode(eventsManager, forKey: "EventsManager") | ||
} | ||
|
||
required public init?(coder aDecoder: NSCoder) { | ||
eventsManager = aDecoder.decodeObject(of: [EventsManager.self], forKey: "EventsManager") as? EventsManager ?? EventsManager(accessToken: nil) | ||
super.init(coder: aDecoder) | ||
commonInit() | ||
} | ||
|
||
func commonInit() { | ||
self.modalPresentationStyle = .custom | ||
self.transitioningDelegate = self | ||
} | ||
|
||
override public func viewDidLoad() { | ||
super.viewDidLoad() | ||
setupViews() | ||
setupConstraints() | ||
|
@@ -82,22 +139,24 @@ class FeedbackViewController: UIViewController, DismissDraggable, UIGestureRecog | |
enableDraggableDismiss() | ||
} | ||
|
||
override func viewWillAppear(_ animated: Bool) { | ||
override public func viewWillAppear(_ animated: Bool) { | ||
super.viewWillAppear(animated) | ||
progressBar.progress = 1 | ||
} | ||
|
||
override func viewDidAppear(_ animated: Bool) { | ||
override public func viewDidAppear(_ animated: Bool) { | ||
super.viewDidAppear(animated) | ||
|
||
delegate?.feedbackViewControllerDidOpenFeedback?(self) | ||
|
||
UIView.animate(withDuration: FeedbackViewController.autoDismissInterval) { | ||
self.progressBar.progress = 0 | ||
} | ||
|
||
enableAutoDismiss() | ||
} | ||
|
||
override func willTransition(to newCollection: UITraitCollection, with coordinator: UIViewControllerTransitionCoordinator) { | ||
override public func willTransition(to newCollection: UITraitCollection, with coordinator: UIViewControllerTransitionCoordinator) { | ||
super.willTransition(to: newCollection, with: coordinator) | ||
|
||
// Dismiss the feedback view when switching between landscape and portrait mode. | ||
|
@@ -126,12 +185,15 @@ class FeedbackViewController: UIViewController, DismissDraggable, UIGestureRecog | |
NSObject.cancelPreviousPerformRequests(withTarget: self, selector: #selector(dismissFeedback), object: nil) | ||
} | ||
|
||
@objc func dismissFeedback() { | ||
/** | ||
Instantly dismisses the FeedbackViewController if it is currently presented. | ||
*/ | ||
@objc public func dismissFeedback() { | ||
abortAutodismiss() | ||
dismissFeedbackHandler?() | ||
defaultDismissFeedback() | ||
} | ||
|
||
func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldReceive touch: UITouch) -> Bool { | ||
public func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldReceive touch: UITouch) -> Bool { | ||
// Only respond to touches outside/behind the view | ||
let isDescendant = touch.view?.isDescendant(of: view) ?? true | ||
return !isDescendant | ||
|
@@ -165,12 +227,32 @@ class FeedbackViewController: UIViewController, DismissDraggable, UIGestureRecog | |
progressBar.trailingAnchor.constraint(equalTo: view.trailingAnchor).isActive = true | ||
progressBar.bottomAnchor.constraint(equalTo: view.safeBottomAnchor).isActive = true | ||
} | ||
|
||
func sendFeedback(_ item: FeedbackItem) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Nit: |
||
delegate?.feedbackViewController?(self, didSendFeedbackAssigned: uuid, feedback: item) | ||
eventsManager.updateFeedback(uuid: uuid, type: item.feedbackType, source: .user, description: nil) | ||
|
||
guard let parent = presentingViewController else { | ||
dismiss(animated: true) | ||
return | ||
} | ||
|
||
dismiss(animated: true) { | ||
DialogViewController().present(on: parent) | ||
} | ||
} | ||
|
||
func defaultDismissFeedback() { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Just |
||
delegate?.feedbackViewControllerDidCancelFeedback?(self) | ||
eventsManager.cancelFeedback(uuid: uuid) | ||
dismiss(animated: true, completion: nil) | ||
} | ||
} | ||
|
||
extension FeedbackViewController: UICollectionViewDataSource { | ||
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell { | ||
@objc public func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell { | ||
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: FeedbackCollectionViewCell.defaultIdentifier, for: indexPath) as! FeedbackCollectionViewCell | ||
let item = sections[indexPath.section][indexPath.row] | ||
let item = sections[indexPath.row] | ||
|
||
cell.titleLabel.text = item.title | ||
cell.imageView.tintColor = .clear | ||
|
@@ -179,15 +261,15 @@ extension FeedbackViewController: UICollectionViewDataSource { | |
return cell | ||
} | ||
|
||
func numberOfSections(in collectionView: UICollectionView) -> Int { | ||
return sections.count | ||
@objc public func numberOfSections(in collectionView: UICollectionView) -> Int { | ||
return 1 | ||
} | ||
|
||
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int { | ||
return sections[section].count | ||
@objc public func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int { | ||
return sections.count | ||
} | ||
|
||
func scrollViewDidScroll(_ scrollView: UIScrollView) { | ||
@objc public func scrollViewDidScroll(_ scrollView: UIScrollView) { | ||
// In case the view is scrolled, dismiss the feedback window immediately | ||
// and reset the `progressBar` back to a full progress. | ||
abortAutodismiss() | ||
|
@@ -196,24 +278,22 @@ extension FeedbackViewController: UICollectionViewDataSource { | |
} | ||
|
||
extension FeedbackViewController: UICollectionViewDelegate { | ||
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) { | ||
@objc public func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) { | ||
abortAutodismiss() | ||
let item = sections[indexPath.section][indexPath.row] | ||
sendFeedbackHandler?(item) | ||
let item = sections[indexPath.row] | ||
sendFeedback(item) | ||
} | ||
} | ||
|
||
extension FeedbackViewController: UICollectionViewDelegateFlowLayout { | ||
|
||
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize { | ||
@objc public func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize { | ||
let availableWidth = collectionView.bounds.width | ||
// 3 columns and 2 rows in portrait mode. | ||
// 6 columns and 1 row in landscape mode. | ||
let items = sections[indexPath.section] | ||
let width = traitCollection.verticalSizeClass == .compact | ||
? floor(availableWidth / CGFloat(items.count)) | ||
: floor(availableWidth / CGFloat(items.count / 2)) | ||
let item = sections[indexPath.section][indexPath.row] | ||
? floor(availableWidth / CGFloat(sections.count)) | ||
: floor(availableWidth / CGFloat(sections.count / 2)) | ||
let item = sections[indexPath.row] | ||
let titleHeight = item.title.height(constrainedTo: width, font: FeedbackCollectionViewCell.Constants.titleFont) | ||
let cellHeight: CGFloat = FeedbackCollectionViewCell.Constants.imageSize.height | ||
+ FeedbackCollectionViewCell.Constants.padding | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This class needs documentation.