mirror of
https://github.com/AU-COVIDSafe/mobile-ios.git
synced 2025-04-30 10:25:23 +00:00
COVIDSafe code from version 1.1
This commit is contained in:
commit
3640e52eb2
330 changed files with 261540 additions and 0 deletions
CovidSafe/Feedback/Sources
274
CovidSafe/Feedback/Sources/FeedbackViewController.swift
Normal file
274
CovidSafe/Feedback/Sources/FeedbackViewController.swift
Normal file
|
@ -0,0 +1,274 @@
|
|||
// 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 = {
|
||||
let item = UIBarButtonItem(title: "Send", style: .done, target: self, action: #selector(sendButtonTapped))
|
||||
item.tintColor = .covidSafeColor
|
||||
return item
|
||||
}()
|
||||
|
||||
private lazy var doneButtonItem: UIBarButtonItem = {
|
||||
let item = UIBarButtonItem(title: "Done", style: .done, target: self, action: #selector(doneButtonTapped))
|
||||
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 {
|
||||
let errorMessage = "Please enter a valid email address!"
|
||||
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)
|
||||
}
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue