mobile-ios/CovidSafe/HomeViewController.swift

462 lines
22 KiB
Swift
Raw Normal View History

2020-05-08 07:49:14 +00:00
import UIKit
import Lottie
2020-06-19 07:43:33 +00:00
import KeychainSwift
2020-05-08 07:49:14 +00:00
import SafariServices
class HomeViewController: UIViewController {
private var observer: NSObjectProtocol?
2020-07-21 05:42:48 +00:00
let animatedHeaderActiveHeightConstant: CGFloat = 160
let animatedHeaderInactiveHeightConstant: CGFloat = 80
let animatedHeaderActiveVerticalMarginConstant: CGFloat = 60
let animatedHeaderInactiveVerticalMarginConstant: CGFloat = 24
2020-05-08 07:49:14 +00:00
@IBOutlet weak var screenStack: UIStackView!
@IBOutlet weak var bluetoothStatusOffView: UIView!
@IBOutlet weak var bluetoothStatusOnView: UIView!
@IBOutlet weak var bluetoothPermissionOffView: UIView!
@IBOutlet weak var bluetoothPermissionOnView: UIView!
@IBOutlet weak var homeHeaderView: UIView!
@IBOutlet weak var homeHeaderInfoText: UILabel!
@IBOutlet weak var homeHeaderPermissionsOffImage: UIImageView!
@IBOutlet weak var shareView: UIView!
@IBOutlet weak var appPermissionsLabel: UIView!
@IBOutlet weak var animatedBluetoothHeader: UIView!
@IBOutlet weak var versionNumberLabel: UILabel!
@IBOutlet weak var versionView: UIView!
@IBOutlet weak var uploadView: UIView!
@IBOutlet weak var helpButton: UIButton!
@IBOutlet weak var seeOurFAQ: UIButton!
2020-05-15 07:47:40 +00:00
@IBOutlet weak var pushNotificationStatusTitle: UILabel!
@IBOutlet weak var pushNotificationStatusIcon: UIImageView!
@IBOutlet weak var pushNotificationStatusLabel: UILabel!
2020-07-03 04:26:13 +00:00
@IBOutlet weak var pushNotificationContainerView: UIView!
2020-05-26 07:13:26 +00:00
@IBOutlet weak var uploadDateLabel: UILabel!
2020-06-05 00:26:40 +00:00
@IBOutlet weak var pairingRequestsLabel: UILabel!
2020-07-21 05:42:48 +00:00
@IBOutlet weak var topLeftIcon: UIImageView!
@IBOutlet weak var animatedBluetoothHeaderHeightConstraint: NSLayoutConstraint!
@IBOutlet weak var animatedBluetoothHeaderTopMarginConstraint: NSLayoutConstraint!
@IBOutlet weak var animatedBluetoothHeaderBottomMarginConstraint: NSLayoutConstraint!
2020-05-08 07:49:14 +00:00
var lottieBluetoothView: AnimationView!
var allPermissionOn = true
var bluetoothStatusOn = true
var bluetoothPermissionOn = true
var pushNotificationOn = true
var didUploadData: Bool {
let uploadTimestamp = UserDefaults.standard.double(forKey: "uploadDataDate")
let lastUpload = Date(timeIntervalSince1970: uploadTimestamp)
2020-05-26 07:13:26 +00:00
return Date().timeIntervalSince(lastUpload) < 86400 * 14
2020-05-08 07:49:14 +00:00
}
2020-05-26 07:13:26 +00:00
var dataUploadedAttributedString: NSAttributedString? {
let uploadTimestamp = UserDefaults.standard.double(forKey: "uploadDataDate")
if(uploadTimestamp > 0){
let lastUpload = Date(timeIntervalSince1970: uploadTimestamp)
let dateFormatterPrint = DateFormatter()
dateFormatterPrint.dateFormat = "dd MMM yyyy"
let formattedDate = dateFormatterPrint.string(from: lastUpload)
2020-06-05 00:26:40 +00:00
let newAttributedString = NSMutableAttributedString(
string: String.localizedStringWithFormat(
2020-06-19 07:43:33 +00:00
"InformationUploaded".localizedString(comment: "Information uploaded template"),
2020-06-05 00:26:40 +00:00
formattedDate)
)
2020-05-26 07:13:26 +00:00
guard let dateRange = newAttributedString.string.range(of: formattedDate) else { return nil }
let nsRange = NSRange(dateRange, in: newAttributedString.string)
newAttributedString.addAttribute(.font,
value: UIFont.boldSystemFont(ofSize: 18),
range: nsRange)
return newAttributedString
}
return nil
}
var shouldShowUploadDate: Bool {
let uploadTimestamp = UserDefaults.standard.double(forKey: "uploadDataDate")
2020-05-08 07:49:14 +00:00
if(uploadTimestamp > 0){
let lastUpload = Date(timeIntervalSince1970: uploadTimestamp)
2020-05-26 07:13:26 +00:00
return Date().timeIntervalSince(lastUpload) <= 86400 * 14
2020-05-08 07:49:14 +00:00
}
return false
}
var _preferredScreenEdgesDeferringSystemGestures: UIRectEdge = []
override var preferredScreenEdgesDeferringSystemGestures: UIRectEdge {
return _preferredScreenEdgesDeferringSystemGestures
}
override func viewDidLoad() {
super.viewDidLoad()
2020-06-19 07:43:33 +00:00
// this is to show the settings prompt initially if bluetooth is off
if !BluetraceManager.shared.isBluetoothOn() {
BluetraceManager.shared.turnOn()
}
2020-05-08 07:49:14 +00:00
observer = NotificationCenter.default.addObserver(forName: UIApplication.didBecomeActiveNotification, object: nil, queue: .main) { [unowned self] notification in
self.toggleViews()
}
updateAnimationViewWithAnimationName(name: "Spinner_home")
NotificationCenter.default.addObserver(self, selector: #selector(enableDeferringSystemGestures(_:)), name: .enableDeferringSystemGestures, object: nil)
NotificationCenter.default.addObserver(self, selector: #selector(disableDeferringSystemGestures(_:)), name: .disableDeferringSystemGestures, object: nil)
NotificationCenter.default.addObserver(self, selector: #selector(disableUserInteraction(_:)), name: .disableUserInteraction, object: nil)
NotificationCenter.default.addObserver(self, selector: #selector(enableUserInteraction(_:)), name: .enableUserInteraction, object: nil)
NotificationCenter.default.addObserver(self, selector: #selector(appWillResignActive(_:)), name: UIApplication.willResignActiveNotification, object: nil)
NotificationCenter.default.addObserver(self, selector: #selector(appWillEnterForeground(_:)), name: UIApplication.willEnterForegroundNotification, object: nil)
2020-05-26 07:13:26 +00:00
2020-05-08 07:49:14 +00:00
if let versionNumber = Bundle.main.versionShort, let buildNumber = Bundle.main.version {
2020-06-05 00:26:40 +00:00
self.versionNumberLabel.text = String.localizedStringWithFormat(
2020-07-03 04:26:13 +00:00
"home_version_number_ios".localizedString(comment: "Version number template"),
2020-06-05 00:26:40 +00:00
versionNumber,buildNumber
)
2020-05-08 07:49:14 +00:00
} else {
toggleViewVisibility(view: versionView, isVisible: false)
}
2020-07-03 04:26:13 +00:00
// Some translators are adding the ** for this link, just cleaning that up.
let pairingRequestString = NSLocalizedString("PairingRequestsInfo", comment: "Text explaining COVIDSafe does not send pairing requests").replacingOccurrences(of: "*", with: "")
2020-06-05 00:26:40 +00:00
let pairingRequestText = NSMutableAttributedString(string: pairingRequestString,
attributes: [.font: UIFont.preferredFont(forTextStyle: .body)])
let pairingRequestUnderlinedString = NSLocalizedString("PairingRequestsInfoUnderline", comment: "section of text that should be underlined from the PairingRequestsInfo text")
2020-07-03 04:26:13 +00:00
if let requestsRange = pairingRequestText.string.range(of: pairingRequestUnderlinedString) {
let nsRange = NSRange(requestsRange, in: pairingRequestText.string)
pairingRequestText.addAttribute(.underlineStyle, value: NSUnderlineStyle.single.rawValue, range: nsRange)
pairingRequestsLabel.attributedText = pairingRequestText
}
2020-05-08 07:49:14 +00:00
}
deinit {
if let observer = observer {
NotificationCenter.default.removeObserver(observer)
}
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
self.toggleViews()
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
self.lottieBluetoothView?.play()
self.becomeFirstResponder()
2020-06-19 07:43:33 +00:00
self.updateJWTKeychainAccess()
2020-07-21 05:42:48 +00:00
getMessagesFromServer()
2020-05-08 07:49:14 +00:00
}
override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
self.lottieBluetoothView?.stop()
}
override var canBecomeFirstResponder: Bool {
return true
}
override func motionEnded(_ motion: UIEvent.EventSubtype, with event: UIEvent?) {
#if DEBUG
guard let allowDebug = PlistHelper.getBoolFromInfoPlist(withKey: "Allow_Debug", plistName: "CovidSafe-config") else {
return
}
if allowDebug == true && event?.subtype == .motionShake {
guard let debugVC = UIStoryboard(name: "Debug", bundle: nil).instantiateInitialViewController() else {
return
}
debugVC.modalTransitionStyle = .coverVertical
debugVC.modalPresentationStyle = .fullScreen
present(debugVC, animated: true, completion: nil)
}
#endif
}
2020-06-19 07:43:33 +00:00
func updateJWTKeychainAccess() {
let hasUpdatedKeychainAccess = UserDefaults.standard.bool(forKey: "HasUpdatedKeychainAccess")
if (!hasUpdatedKeychainAccess) {
let keychain = KeychainSwift()
if let jwt = keychain.get("JWT_TOKEN") {
if (keychain.set(jwt, forKey: "JWT_TOKEN", withAccess: .accessibleAfterFirstUnlock)) {
DLog("Updated access class on JWT")
UserDefaults.standard.set(true, forKey: "HasUpdatedKeychainAccess")
}
}
}
}
2020-07-21 05:42:48 +00:00
func getMessagesFromServer() {
MessageAPI.getMessagesIfNeeded() { (messageResponse, error) in
if let error = error {
DLog("Get messages error: \(error.localizedDescription)")
}
// We currently dont do anything with the response. Messages are delivered via APN
}
}
2020-06-19 07:43:33 +00:00
2020-05-08 07:49:14 +00:00
fileprivate func toggleViews() {
DispatchQueue.main.async {
UNUserNotificationCenter.current().getNotificationSettings(completionHandler: { [weak self] settings in
DispatchQueue.main.async {
self?.readPermissions(notificationSettings: settings)
self?.togglePushNotificationsStatusView()
self?.toggleBluetoothStatusView()
self?.toggleBluetoothPermissionStatusView()
self?.toggleHeaderView()
self?.toggleUploadView()
2020-05-26 07:13:26 +00:00
self?.toggleUploadDateView()
2020-05-08 07:49:14 +00:00
}
})
}
}
2020-05-26 07:13:26 +00:00
fileprivate func toggleUploadDateView() {
if shouldShowUploadDate, let lastUploadText = self.dataUploadedAttributedString {
uploadDateLabel.attributedText = lastUploadText
uploadDateLabel.isHidden = false
} else {
uploadDateLabel.isHidden = true
}
}
2020-05-08 07:49:14 +00:00
fileprivate func readPermissions(notificationSettings: UNNotificationSettings) {
self.bluetoothStatusOn = BluetraceManager.shared.isBluetoothOn()
self.bluetoothPermissionOn = BluetraceManager.shared.isBluetoothAuthorized()
self.pushNotificationOn = notificationSettings.authorizationStatus == .authorized
2020-05-15 07:47:40 +00:00
self.allPermissionOn = self.bluetoothStatusOn && self.bluetoothPermissionOn
2020-05-08 07:49:14 +00:00
}
fileprivate func toggleViewVisibility(view: UIView, isVisible: Bool) {
view.isHidden = !isVisible
}
func updateAnimationViewWithAnimationName(name: String) {
let bluetoothAnimation = AnimationView(name: name)
bluetoothAnimation.loopMode = .loop
bluetoothAnimation.frame = CGRect(origin: CGPoint(x: 0, y: 0), size: self.animatedBluetoothHeader.frame.size)
if lottieBluetoothView != nil {
lottieBluetoothView.stop()
lottieBluetoothView.removeFromSuperview()
}
self.animatedBluetoothHeader.addSubview(bluetoothAnimation)
lottieBluetoothView = bluetoothAnimation
lottieBluetoothView.play()
}
fileprivate func toggleUploadView() {
toggleViewVisibility(view: self.uploadView, isVisible: !self.didUploadData)
}
fileprivate func toggleHeaderView() {
self.allPermissionOn ? self.lottieBluetoothView?.play() : self.lottieBluetoothView?.stop()
toggleViewVisibility(view: appPermissionsLabel, isVisible: !self.allPermissionOn)
toggleViewVisibility(view: homeHeaderPermissionsOffImage, isVisible: !self.allPermissionOn)
toggleViewVisibility(view: lottieBluetoothView, isVisible: self.allPermissionOn)
2020-07-21 05:42:48 +00:00
2020-08-03 06:01:39 +00:00
var newAttributedLabel = NSMutableAttributedString(string: "home_header_active_title".localizedString(comment: "Header with no action req"), attributes: [.font: UIFont.preferredFont(for: .title1, weight: .bold)])
2020-07-21 05:42:48 +00:00
newAttributedLabel.append(NSAttributedString(string: "\n" + "home_header_active_no_action_required".localizedString(), attributes: [.font: UIFont.preferredFont(forTextStyle: .body)]))
self.homeHeaderInfoText.attributedText = newAttributedLabel
2020-05-15 07:47:40 +00:00
2020-05-08 07:49:14 +00:00
if (!self.allPermissionOn) {
2020-08-03 06:01:39 +00:00
newAttributedLabel = NSMutableAttributedString(string: "home_header_inactive_title".localizedString(comment: "Header with no action req"), attributes: [.font: UIFont.preferredFont(for: .title1, weight: .bold)])
2020-07-21 05:42:48 +00:00
newAttributedLabel.append(NSAttributedString(string: "\n" + "home_header_inactive_check_your_permissions".localizedString(), attributes: [.font: UIFont.preferredFont(forTextStyle: .body)]))
animatedBluetoothHeaderHeightConstraint.constant = animatedHeaderInactiveHeightConstant
animatedBluetoothHeaderTopMarginConstraint.constant = animatedHeaderInactiveVerticalMarginConstant
animatedBluetoothHeaderBottomMarginConstraint.constant = animatedHeaderInactiveVerticalMarginConstant
self.homeHeaderInfoText.attributedText = newAttributedLabel
2020-05-08 07:49:14 +00:00
self.homeHeaderView.backgroundColor = UIColor.covidHomePermissionErrorColor
2020-07-21 05:42:48 +00:00
topLeftIcon.isHidden = false
2020-05-08 07:49:14 +00:00
} else {
2020-07-21 05:42:48 +00:00
animatedBluetoothHeaderHeightConstraint.constant = animatedHeaderActiveHeightConstant
animatedBluetoothHeaderTopMarginConstraint.constant = animatedHeaderActiveVerticalMarginConstant
animatedBluetoothHeaderBottomMarginConstraint.constant = animatedHeaderActiveVerticalMarginConstant
2020-05-08 07:49:14 +00:00
self.homeHeaderView.backgroundColor = UIColor.covidHomeActiveColor
updateAnimationViewWithAnimationName(name: "Spinner_home")
2020-07-21 05:42:48 +00:00
topLeftIcon.isHidden = true
2020-05-08 07:49:14 +00:00
}
}
fileprivate func toggleBluetoothStatusView() {
2020-06-19 07:43:33 +00:00
toggleViewVisibility(view: bluetoothStatusOnView, isVisible: !self.bluetoothPermissionOn && self.bluetoothStatusOn)
toggleViewVisibility(view: bluetoothStatusOffView, isVisible: self.bluetoothPermissionOn && !self.bluetoothStatusOn)
2020-05-08 07:49:14 +00:00
}
fileprivate func toggleBluetoothPermissionStatusView() {
toggleViewVisibility(view: bluetoothPermissionOnView, isVisible: !self.allPermissionOn && self.bluetoothPermissionOn)
toggleViewVisibility(view: bluetoothPermissionOffView, isVisible: !self.allPermissionOn && !self.bluetoothPermissionOn)
}
fileprivate func togglePushNotificationsStatusView() {
2020-05-15 07:47:40 +00:00
let paragraphStyle = NSMutableParagraphStyle()
paragraphStyle.lineSpacing = 4
switch self.pushNotificationOn {
case true:
2020-07-03 04:26:13 +00:00
pushNotificationContainerView.accessibilityLabel = "NotificationsEnabled_VOLabel".localizedString()
2020-05-15 07:47:40 +00:00
pushNotificationStatusIcon.isHighlighted = false
2020-07-03 04:26:13 +00:00
pushNotificationStatusTitle.text = NSLocalizedString("home_set_complete_external_link_notifications_title_iOS", comment: "Notifications Enabled")
2020-06-19 07:43:33 +00:00
let newAttributedLabel = NSMutableAttributedString(string: NSLocalizedString("NotificationsEnabledBlurb", comment: "Notifications Enabled content blurb"), attributes: [.font: UIFont.preferredFont(forTextStyle: .callout)])
2020-05-15 07:47:40 +00:00
//set some attributes
2020-06-19 07:43:33 +00:00
guard let linkRange = newAttributedLabel.string.range(of: "NotificationsBlurbLink".localizedString( comment: "Notifications blurb link")) else { return }
2020-05-15 07:47:40 +00:00
let nsRange = NSRange(linkRange, in: newAttributedLabel.string)
newAttributedLabel.addAttribute(.foregroundColor,
value: UIColor.covidSafeColor,
range: nsRange)
newAttributedLabel.addAttribute(.paragraphStyle, value:paragraphStyle, range:NSMakeRange(0, newAttributedLabel.length))
pushNotificationStatusLabel.attributedText = newAttributedLabel
default:
2020-07-03 04:26:13 +00:00
pushNotificationContainerView.accessibilityLabel = "NotificationsDisabled_VOLabel".localizedString()
2020-05-15 07:47:40 +00:00
pushNotificationStatusIcon.isHighlighted = true
2020-07-03 04:26:13 +00:00
pushNotificationStatusTitle.text = "home_set_complete_external_link_notifications_title_iOS_off".localizedString(comment: "Notifications Disabled")
2020-06-05 00:26:40 +00:00
let newAttributedLabel = NSMutableAttributedString(string:
2020-07-03 04:26:13 +00:00
NSLocalizedString("NotificationsDisabledBlurb", comment: "Notifications Disabled content blurb"), attributes: [.font: UIFont.preferredFont(forTextStyle: .callout)])
2020-05-15 07:47:40 +00:00
//set some attributes
2020-06-19 07:43:33 +00:00
guard let linkRange = newAttributedLabel.string.range(of: "NotificationsBlurbLink".localizedString()) else { return }
2020-05-15 07:47:40 +00:00
let nsRange = NSRange(linkRange, in: newAttributedLabel.string)
newAttributedLabel.addAttribute(.foregroundColor,
value: UIColor.covidSafeColor,
range: nsRange)
newAttributedLabel.addAttribute(.paragraphStyle, value:paragraphStyle, range:NSMakeRange(0, newAttributedLabel.length))
pushNotificationStatusLabel.attributedText = newAttributedLabel
}
2020-05-08 07:49:14 +00:00
}
2020-06-19 07:43:33 +00:00
func attemptTurnOnBluetooth() {
BluetraceManager.shared.toggleScanning(false)
BluetraceManager.shared.turnOn()
}
// MARK: IBActions
@IBAction func onAppSettingsTapped(_ sender: UITapGestureRecognizer) {
2020-05-08 07:49:14 +00:00
guard let settingsURL = URL(string: UIApplication.openSettingsURLString) else {
return
}
UIApplication.shared.open(settingsURL)
}
2020-06-19 07:43:33 +00:00
2020-08-03 06:01:39 +00:00
@IBAction func onChangeLanguageTapped(_ sender: UITapGestureRecognizer) {
if #available(iOS 13.0, *) {
guard let settingsURL = URL(string: UIApplication.openSettingsURLString) else {
return
}
UIApplication.shared.open(settingsURL)
} else {
let nav = HelpNavController()
nav.pageSectionId = "other-languages"
nav.modalTransitionStyle = .coverVertical
nav.modalPresentationStyle = .fullScreen
present(nav, animated: true, completion: nil)
}
}
2020-06-19 07:43:33 +00:00
@IBAction func onBluetoothPhoneSettingsTapped(_ sender: Any) {
attemptTurnOnBluetooth()
}
2020-05-08 07:49:14 +00:00
@IBAction func onShareTapped(_ sender: UITapGestureRecognizer) {
let shareText = TracerRemoteConfig.defaultShareText
let activity = UIActivityViewController(activityItems: [shareText], applicationActivities: nil)
activity.popoverPresentationController?.sourceView = shareView
present(activity, animated: true, completion: nil)
}
@IBAction func onLatestNewsTapped(_ sender: UITapGestureRecognizer) {
// open in safari https://www.australia.gov.au
guard let url = URL(string: "https://www.australia.gov.au") else {
return
}
let safariVC = SFSafariViewController(url: url)
present(safariVC, animated: true, completion: nil)
}
@IBAction func getCoronaVirusApp(_ sender: UITapGestureRecognizer) {
guard let url = URL(string: "https://www.health.gov.au/resources/apps-and-tools/coronavirus-australia-app") else {
return
}
let safariVC = SFSafariViewController(url: url)
present(safariVC, animated: true, completion: nil)
}
2020-05-26 07:13:26 +00:00
@IBAction func bluetoothPairingTapped(_ sender: Any) {
2020-07-03 04:26:13 +00:00
guard let url = URL(string: "\(URLHelper.getHelpURL())#bluetooth-pairing-request") else {
2020-05-26 07:13:26 +00:00
return
}
let safariVC = SFSafariViewController(url: url)
present(safariVC, animated: true, completion: nil)
}
2020-05-08 07:49:14 +00:00
@IBAction func onPositiveButtonTapped(_ sender: UITapGestureRecognizer) {
guard let helpVC = UIStoryboard(name: "UploadData", bundle: nil).instantiateInitialViewController() else {
return
}
let nav = UploadDataNavigationController(rootViewController: helpVC)
nav.modalTransitionStyle = .coverVertical
nav.modalPresentationStyle = .fullScreen
present(nav, animated: true, completion: nil)
}
@IBAction func onHelpButtonTapped(_ sender: Any) {
let nav = HelpNavController()
nav.modalTransitionStyle = .coverVertical
nav.modalPresentationStyle = .fullScreen
present(nav, animated: true, completion: nil)
}
@objc
func appWillResignActive(_ notification: Notification) {
self.lottieBluetoothView?.stop()
}
@objc
func appWillEnterForeground(_ notification: Notification) {
self.lottieBluetoothView?.play()
}
@objc
func enableUserInteraction(_ notification: Notification) {
self.view.isUserInteractionEnabled = true
lottieBluetoothView?.play()
}
@objc
func disableUserInteraction(_ notification: Notification) {
self.view.isUserInteractionEnabled = false
lottieBluetoothView?.stop()
}
@objc
func enableDeferringSystemGestures(_ notification: Notification) {
if #available(iOS 11.0, *) {
_preferredScreenEdgesDeferringSystemGestures = .bottom
setNeedsUpdateOfScreenEdgesDeferringSystemGestures()
}
}
@objc
func disableDeferringSystemGestures(_ notification: Notification) {
if #available(iOS 11.0, *) {
_preferredScreenEdgesDeferringSystemGestures = []
setNeedsUpdateOfScreenEdgesDeferringSystemGestures()
}
}
}
struct TracerRemoteConfig {
static let defaultShareText = """
2020-07-03 04:26:13 +00:00
\("share_this_app_content".localizedString(comment: "Share app with friends text"))
2020-05-08 07:49:14 +00:00
"""
}