ConnectivityViewController.swift 7.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172
  1. import UIKit
  2. import DcCore
  3. class ConnectivityViewController: WebViewViewController {
  4. private let dcContext: DcContext
  5. private var connectivityChangedObserver: NSObjectProtocol?
  6. init(dcContext: DcContext) {
  7. self.dcContext = dcContext
  8. super.init()
  9. }
  10. required init?(coder: NSCoder) {
  11. fatalError("init(coder:) has not been implemented")
  12. }
  13. // called only once after loading
  14. override func viewDidLoad() {
  15. super.viewDidLoad()
  16. self.title = String.localized("connectivity")
  17. self.webView.isOpaque = false
  18. self.webView.backgroundColor = .clear
  19. view.backgroundColor = DcColors.defaultBackgroundColor
  20. }
  21. // called everytime the view will appear
  22. override func viewWillAppear(_ animated: Bool) {
  23. // set connectivity changed observer before we acutally init html,
  24. // otherwise, we may miss events and the html is not correct.
  25. connectivityChangedObserver = NotificationCenter.default.addObserver(forName: dcNotificationConnectivityChanged,
  26. object: nil,
  27. queue: nil) { [weak self] _ in
  28. self?.loadHtml()
  29. }
  30. loadHtml()
  31. }
  32. override func viewDidDisappear(_ animated: Bool) {
  33. if let connectivityChangedObserver = self.connectivityChangedObserver {
  34. NotificationCenter.default.removeObserver(connectivityChangedObserver)
  35. }
  36. }
  37. // this method needs to be run from a background thread
  38. private func getNotificationStatus() -> String {
  39. let title = " <b>" + String.localized("pref_notifications") + ":</b> "
  40. let notificationsEnabledInDC = !UserDefaults.standard.bool(forKey: "notifications_disabled")
  41. var notificationsEnabledInSystem = false
  42. let semaphore = DispatchSemaphore(value: 0)
  43. DispatchQueue.global(qos: .userInitiated).async {
  44. NotificationManager.notificationEnabledInSystem { enabled in
  45. notificationsEnabledInSystem = enabled
  46. semaphore.signal()
  47. }
  48. }
  49. if semaphore.wait(timeout: .now() + 1) == .timedOut {
  50. return "<span class=\"red dot\"></span>"
  51. .appending(title)
  52. .appending("Timeout Error")
  53. }
  54. if !notificationsEnabledInDC {
  55. return "<span class=\"disabled dot\"></span>"
  56. .appending(title)
  57. .appending(String.localized("notifications_disabled_dc"))
  58. }
  59. if !notificationsEnabledInSystem {
  60. return "<span class=\"disabled dot\"></span>"
  61. .appending(title)
  62. .appending(String.localized("notifications_disabled"))
  63. }
  64. if let appDelegate = UIApplication.shared.delegate as? AppDelegate {
  65. if appDelegate.notifyToken == nil {
  66. return "<span class=\"red dot\"></span>"
  67. .appending(title)
  68. .appending("Service error")
  69. }
  70. }
  71. let timestamps = UserDefaults.standard.array(forKey: Constants.Keys.notificationTimestamps) as? [Double]
  72. guard let timestamps = timestamps else {
  73. // in most cases, here the app was just installed and we do not have any data.
  74. // so, do not show something error-like here.
  75. // (in case of errors, it usually converts to an error sooner or later)
  76. return "<span class=\"green dot\"></span>"
  77. .appending(title)
  78. .appending(String.localized("on"))
  79. }
  80. var averageDelta: Double = 0
  81. if timestamps.isEmpty {
  82. // this should not happen:
  83. // the array should not be empty as old notifications are only removed if a new one is added
  84. return "<span class=\"red dot\"></span>"
  85. .appending(title)
  86. .appending("Bad Data")
  87. } else if timestamps.count == 1 {
  88. averageDelta = Double(Date().timeIntervalSince1970) - timestamps.first!
  89. } else {
  90. averageDelta = (timestamps.last! - timestamps.first!) / Double(timestamps.count-1)
  91. }
  92. let lastWakeup = DateUtils.getExtendedAbsTimeSpanString(timeStamp: timestamps.last!)
  93. if Int(averageDelta / Double(60 * 60)) > 1 {
  94. // more than 1 hour in average
  95. return "<span class=\"red dot\"></span>"
  96. .appending(title)
  97. .appending(String.localized("delayed"))
  98. .appending(", ")
  99. .appending(String.localizedStringWithFormat(String.localized("last_check_at"), lastWakeup))
  100. .appending(", ")
  101. .appending(String.localized(stringID: "notifications_avg_hours", count: Int(averageDelta / Double(60 * 60))))
  102. }
  103. if averageDelta / Double(60 * 20) > 1 {
  104. // more than 20 minutes in average
  105. return "<span class=\"yellow dot\"></span>"
  106. .appending(title)
  107. .appending(String.localized("delayed"))
  108. .appending(", ")
  109. .appending(String.localizedStringWithFormat(String.localized("last_check_at"), lastWakeup))
  110. .appending(", ")
  111. .appending(String.localized(stringID: "notifications_avg_minutes", count: Int(averageDelta / 60)))
  112. }
  113. return "<span class=\"green dot\"></span>"
  114. .appending(title)
  115. .appending(String.localizedStringWithFormat(String.localized("last_check_at"), lastWakeup))
  116. .appending(", ")
  117. .appending(String.localized(stringID: "notifications_avg_minutes", count: Int(averageDelta / 60)))
  118. }
  119. private func loadHtml() {
  120. DispatchQueue.global(qos: .userInitiated).async { [weak self] in
  121. guard let self = self else { return }
  122. var html = self.dcContext.getConnectivityHtml()
  123. .replacingOccurrences(of: "</style>", with:
  124. """
  125. body {
  126. font-size: 13pt;
  127. font-family: -apple-system, sans-serif;
  128. padding: 0 .5rem .5rem .5rem;
  129. -webkit-text-size-adjust: none;
  130. }
  131. .disabled {
  132. background-color: #aaaaaa;
  133. }
  134. @media (prefers-color-scheme: dark) {
  135. body {
  136. background-color: black !important;
  137. color: #eee;
  138. }
  139. }
  140. </style>
  141. """)
  142. let notificationStatus = self.getNotificationStatus()
  143. if let range = html.range(of: "</ul>") {
  144. html = html.replacingCharacters(in: range, with: "<li>" + notificationStatus + "</li></ul>")
  145. }
  146. DispatchQueue.main.async {
  147. self.webView.loadHTMLString(html, baseURL: nil)
  148. }
  149. }
  150. }
  151. }