mobile-ios/CovidSafe/Feedback/Sources/FeedbackViewController.swift

279 lines
9.4 KiB
Swift
Raw Normal View History

2020-05-08 07:49:14 +00:00
// Copyright © 2020 Australian Government All rights reserved.
import UIKit
final class FeedbackViewController: UIViewController {
@IBOutlet var issueTextView: UITextView!
@IBOutlet var issuePlaceholderLabel: UILabel!
@IBOutlet var emailTextField: UITextField!
@IBOutlet var scrollView: UIScrollView!
@IBOutlet var thankYouView: UIView!
private var sendFeebackAction: SendFeedbackAction?
var settings: FeedbackSettings?
var onDidFinish: (() -> Void)?
var flowNavBarStyle: UIStatusBarStyle = UIApplication.shared.statusBarStyle
enum State {
case idle
case sending
case sent
}
private var state: State = .idle {
didSet {
updateUI()
}
}
private lazy var sendBarButtonItem: UIBarButtonItem = {
2020-06-05 00:26:40 +00:00
let item = UIBarButtonItem(title: NSLocalizedString("global_send_button_title",
tableName: "Feedback",
bundle: Bundle.main,
comment: "Send"), style: .done, target: self, action: #selector(sendButtonTapped))
item.tintColor = .covidCareColor
2020-05-08 07:49:14 +00:00
return item
}()
private lazy var doneButtonItem: UIBarButtonItem = {
2020-06-05 00:26:40 +00:00
let item = UIBarButtonItem(title: NSLocalizedString("Done", comment: "Done"), style: .done, target: self, action: #selector(doneButtonTapped))
2020-05-08 07:49:14 +00:00
item.tintColor = .covidSafeColor
return item
}()
override func viewDidLoad() {
super.viewDidLoad()
if #available(iOS 13.0, *) {
isModalInPresentation = true
}
setup()
}
@objc func cancel() {
if issueTextView.isFirstResponder {
issueTextView.resignFirstResponder()
}
if emailTextField.isFirstResponder {
emailTextField.resignFirstResponder()
}
}
func presentKeyboard() {
issueTextView.becomeFirstResponder()
}
private func updateUI() {
switch state {
case .idle:
dismissKeyboard()
showSendButton()
issueTextView.isEditable = true
emailTextField.isEnabled = true
case .sending:
showSpinner()
issueTextView.isEditable = false
emailTextField.isEnabled = false
case .sent:
hideCancelButton()
showDoneButton()
showThankYouView()
}
}
private func showSendButton() {
navigationItem.rightBarButtonItem = sendBarButtonItem
}
private func showSpinner() {
let activityView = UIActivityIndicatorView(style: .gray)
activityView.startAnimating()
let spinnerBarItem = UIBarButtonItem(customView: activityView)
navigationItem.rightBarButtonItem = spinnerBarItem
}
private func showDoneButton() {
navigationItem.rightBarButtonItem = doneButtonItem
}
private func hideCancelButton() {
navigationItem.leftBarButtonItem = nil
}
private func showThankYouView() {
UIView.animate(withDuration: 0.3) { [weak self] in
self?.scrollView.isHidden = true
self?.thankYouView.isHidden = false
}
}
private func setup() {
self.title = NSLocalizedString("newFeedbackFlow_navigationTitle",
tableName: "Feedback",
bundle: Bundle.main,
comment: "Title for feedback flow navigation"
)
issueTextView.textContainer.lineFragmentPadding = 0.0
setupDelegates()
setupKeyboardNotifications()
setupBarButtonItems()
}
private func setupDelegates() {
issueTextView.delegate = self
emailTextField.addTarget(self, action: #selector(updateSendButton), for: .editingChanged)
}
private func setupKeyboardNotifications() {
let notificationCenter = NotificationCenter.default
notificationCenter.addObserver(self, selector: #selector(adjustForKeyboard), name: UIResponder.keyboardWillChangeFrameNotification, object: nil)
notificationCenter.addObserver(self, selector: #selector(adjustForKeyboard), name: UIResponder.keyboardWillHideNotification, object: nil)
}
private func setupBarButtonItems() {
navigationItem.rightBarButtonItem = sendBarButtonItem
sendBarButtonItem.isEnabled = false
}
@objc private func updateSendButton() {
sendBarButtonItem.isEnabled = !issueTextView.text.isEmpty && !(emailTextField.text?.isEmpty ?? true)
}
private func updatePlaceholder() {
issuePlaceholderLabel.isHidden = !issueTextView.text.isEmpty
}
@objc private func sendButtonTapped(_ sender: Any) {
guard emailTextField.isValid else {
2020-06-05 00:26:40 +00:00
let errorMessage = NSLocalizedString("newFeedback_invalidEmail_errorMessage",tableName: "Feedback",
bundle: Bundle.main, comment: "Please enter a valid email address!")
2020-05-08 07:49:14 +00:00
showErrorMessage(errorMessage)
return
}
state = .sending
send()
}
@objc private func doneButtonTapped(_ sender: Any) {
finish()
}
private func finish() {
onDidFinish?()
// Make sure we call the closure only once
onDidFinish = nil
}
private func send() {
guard let settings = settings else {
assertionFailure("Feedback settings not provided, feedback will be lost")
state = .idle
return
}
let deviceInfo = UIDevice.current.infoAsDictionary
let modelName = UIDevice.modelName
let bundleInfo = Bundle.main.infoAsDictionary
var customFields = settings.customFields
if let email = emailTextField.text {
customFields["E-mail"] = email as AnyObject
}
if let osVersion = deviceInfo["systemVersion"] {
customFields["OS version"] = osVersion
}
if let appVersion = bundleInfo["appVersion"] {
customFields["App version"] = appVersion
}
customFields["Phone model"] = modelName as AnyObject
let issue = Issue(
feedback: issueTextView.text,
components: settings.issueComponents,
type: settings.issueType,
customFields: customFields,
reporterUsernameOrEmail: nil
)
let action = SendFeedbackAction(issue: issue, screenshotImageOrNil: nil) { outcome in
switch outcome {
case .success:
self.state = .sent
delayOnMainQueue(2) {
self.finish()
}
case.error:
self.state = .idle
let errorMessage = NSLocalizedString("newFeedback_send_errorMessage",
tableName: "Feedback",
bundle: Bundle.main,
comment: "Generic error message shown when feedback could not be sent"
)
self.showErrorMessage(errorMessage)
case .cancelled:
break
}
}
action.start()
sendFeebackAction = action
}
private func showErrorMessage(_ message: String) {
let alert = AlertController(title: "COVIDSafe", message: message, preferredStyle: .alert)
let okActionTitle = NSLocalizedString("global_ok_button_title",
tableName: "Feedback",
bundle: Bundle.main,
comment: "Title for ok button")
let okAction = UIAlertAction(title: okActionTitle, style: .default)
alert.addAction(okAction)
present(alert, animated: true)
}
@objc func adjustForKeyboard(notification: Notification) {
guard let keyboardValue = notification.userInfo?[UIResponder.keyboardFrameEndUserInfoKey] as? NSValue else { return }
let keyboardScreenEndFrame = keyboardValue.cgRectValue
let keyboardViewEndFrame = view.convert(keyboardScreenEndFrame, from: view.window)
if notification.name == UIResponder.keyboardWillHideNotification {
scrollView.contentInset = .zero
} else {
if #available(iOS 11.0, *) {
scrollView.contentInset = UIEdgeInsets(top: 0, left: 0, bottom: keyboardViewEndFrame.height - view.safeAreaInsets.bottom, right: 0)
} else {
scrollView.contentInset = UIEdgeInsets(top: 0, left: 0, bottom: keyboardViewEndFrame.height, right: 0)
}
}
scrollView.scrollIndicatorInsets = scrollView.contentInset
}
}
func delayOnMainQueue(_ delay: Double, closure:@escaping () -> Void) {
DispatchQueue.main.asyncAfter(
deadline: DispatchTime.now() + Double(Int64(delay * Double(NSEC_PER_SEC))) / Double(NSEC_PER_SEC), execute: closure)
}
extension FeedbackViewController: UITextViewDelegate {
func textViewDidChange(_ textView: UITextView) {
updatePlaceholder()
updateSendButton()
}
}
private extension UITextField {
var isValid: Bool {
let emailRegEx = "[A-Z0-9a-z._%+-]+@[A-Za-z0-9.-]+\\.[A-Za-z]{2,64}"
let emailPred = NSPredicate(format:"SELF MATCHES %@", emailRegEx)
return emailPred.evaluate(with: text)
}
}