MessagingViewController.swift 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336
  1. /*
  2. * Copyright 2017 Google
  3. *
  4. * Licensed under the Apache License, Version 2.0 (the "License");
  5. * you may not use this file except in compliance with the License.
  6. * You may obtain a copy of the License at
  7. *
  8. * http://www.apache.org/licenses/LICENSE-2.0
  9. *
  10. * Unless required by applicable law or agreed to in writing, software
  11. * distributed under the License is distributed on an "AS IS" BASIS,
  12. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13. * See the License for the specific language governing permissions and
  14. * limitations under the License.
  15. */
  16. import UIKit
  17. import FirebaseMessaging
  18. enum Row: String {
  19. case apnsToken
  20. case apnsStatus
  21. case requestAPNSPermissions
  22. case fcmToken
  23. }
  24. enum PermissionsButtonTitle: String {
  25. case requestPermissions = "Request User Notifications"
  26. case noAPNS = "Cannot Request Permissions (No APNs)"
  27. case alreadyRequested = "Already Requested Permissions"
  28. case simulator = "Cannot Request Permissions (Simulator)"
  29. }
  30. class MessagingViewController: UIViewController {
  31. let tableView: UITableView
  32. var sections = [[Row]]()
  33. var sectionHeaderTitles = [String?]()
  34. var allowedNotificationTypes: [NotificationsControllerAllowedNotificationType]?
  35. // Cached rows by Row type. Since this is largely a fixed table view, we'll
  36. // keep track of our created cells and UI, rather than have all the logic
  37. required init?(coder aDecoder: NSCoder) {
  38. tableView = UITableView(frame: CGRect.zero, style: .grouped)
  39. tableView.rowHeight = UITableViewAutomaticDimension
  40. tableView.estimatedRowHeight = 44
  41. // Allow UI Controls within the table to be immediately responsive
  42. tableView.delaysContentTouches = false
  43. super.init(coder: aDecoder)
  44. tableView.dataSource = self
  45. tableView.delegate = self
  46. }
  47. override func loadView() {
  48. super.loadView()
  49. view = UIView(frame: CGRect.zero)
  50. view.addSubview(tableView)
  51. // Ensure that the tableView always is the size of the view
  52. tableView.autoresizingMask = [.flexibleWidth, .flexibleHeight]
  53. }
  54. override func viewDidLoad() {
  55. super.viewDidLoad()
  56. let center = NotificationCenter.default
  57. center.addObserver(self,
  58. selector: #selector(onAPNSTokenReceived),
  59. name: APNSTokenReceivedNotification,
  60. object: nil)
  61. center.addObserver(self,
  62. selector: #selector(onUserNotificationSettingsChanged),
  63. name: UserNotificationsChangedNotification,
  64. object: nil)
  65. center.addObserver(self,
  66. selector: #selector(onFCMTokenRefreshed),
  67. name: Notification.Name.MessagingRegistrationTokenRefreshed,
  68. object: nil)
  69. updateAllowedNotificationTypes {
  70. self.resetTableContents()
  71. self.tableView.reloadData()
  72. }
  73. }
  74. func onAPNSTokenReceived() {
  75. // Reload the appropriate cells
  76. updateAllowedNotificationTypes {
  77. if let tokenPath = self.indexPathFor(.apnsToken),
  78. let statusPath = self.indexPathFor(.apnsStatus),
  79. let requestPath = self.indexPathFor(.requestAPNSPermissions) {
  80. self.updateIndexPaths(indexPaths: [tokenPath, statusPath, requestPath])
  81. }
  82. }
  83. }
  84. func onFCMTokenRefreshed() {
  85. if let indexPath = indexPathFor(.fcmToken) {
  86. updateIndexPaths(indexPaths: [indexPath])
  87. }
  88. }
  89. func onUserNotificationSettingsChanged() {
  90. updateAllowedNotificationTypes {
  91. if let statusPath = self.indexPathFor(.apnsStatus),
  92. let requestPath = self.indexPathFor(.requestAPNSPermissions) {
  93. self.updateIndexPaths(indexPaths: [statusPath, requestPath])
  94. }
  95. }
  96. }
  97. private func updateIndexPaths(indexPaths: [IndexPath]) {
  98. tableView.beginUpdates()
  99. tableView.reloadRows(at: indexPaths, with: .none)
  100. tableView.endUpdates()
  101. }
  102. fileprivate func updateAllowedNotificationTypes(_ completion: (() -> Void)?) {
  103. NotificationsController.shared.getAllowedNotificationTypes { types in
  104. self.allowedNotificationTypes = types
  105. self.updateRequestAPNSButton()
  106. completion?()
  107. }
  108. }
  109. fileprivate func updateRequestAPNSButton() {
  110. guard !Environment.isSimulator else {
  111. requestPermissionsButton.isEnabled = false
  112. requestPermissionsButton.setTitle(PermissionsButtonTitle.simulator.rawValue, for: .normal)
  113. return
  114. }
  115. guard let allowedTypes = allowedNotificationTypes else {
  116. requestPermissionsButton.isEnabled = false
  117. requestPermissionsButton.setTitle(PermissionsButtonTitle.noAPNS.rawValue, for: .normal)
  118. return
  119. }
  120. requestPermissionsButton.isEnabled =
  121. (allowedTypes.count == 1 && allowedTypes.first! == .silent)
  122. let title: PermissionsButtonTitle =
  123. (requestPermissionsButton.isEnabled ? .requestPermissions : .alreadyRequested)
  124. requestPermissionsButton.setTitle(title.rawValue, for: .normal)
  125. }
  126. // MARK: UI (Cells and Buttons) Defined as lazy properties
  127. lazy var apnsTableCell: UITableViewCell = {
  128. let cell = UITableViewCell(style: .subtitle, reuseIdentifier: Row.apnsToken.rawValue)
  129. cell.textLabel?.numberOfLines = 0
  130. cell.textLabel?.lineBreakMode = .byWordWrapping
  131. return cell
  132. }()
  133. lazy var apnsStatusTableCell: UITableViewCell = {
  134. let cell = UITableViewCell(style: UITableViewCellStyle.value1,
  135. reuseIdentifier: Row.apnsStatus.rawValue)
  136. cell.textLabel?.text = "Allowed:"
  137. cell.detailTextLabel?.numberOfLines = 0
  138. cell.detailTextLabel?.lineBreakMode = .byWordWrapping
  139. return cell
  140. }()
  141. lazy var requestPermissionsButton: UIButton = {
  142. let button = UIButton(type: .system)
  143. button.setTitle(PermissionsButtonTitle.requestPermissions.rawValue, for: .normal)
  144. button.setTitleColor(UIColor.gray, for: .highlighted)
  145. button.setTitleColor(UIColor.gray, for: .disabled)
  146. button.titleLabel?.font = UIFont.preferredFont(forTextStyle: .body)
  147. button.addTarget(self,
  148. action: #selector(onRequestUserNotificationsButtonTapped),
  149. for: .touchUpInside)
  150. return button
  151. }()
  152. lazy var apnsRequestPermissionsTableCell: UITableViewCell = {
  153. let cell = UITableViewCell(style: .default,
  154. reuseIdentifier: Row.requestAPNSPermissions.rawValue)
  155. cell.selectionStyle = .none
  156. cell.contentView.addSubview(self.requestPermissionsButton)
  157. self.requestPermissionsButton.frame = cell.contentView.bounds
  158. self.requestPermissionsButton.autoresizingMask = [.flexibleWidth, .flexibleHeight]
  159. return cell
  160. }()
  161. lazy var fcmTokenTableCell: UITableViewCell = {
  162. let cell = UITableViewCell(style: .subtitle, reuseIdentifier: Row.fcmToken.rawValue)
  163. cell.textLabel?.numberOfLines = 0
  164. cell.textLabel?.lineBreakMode = .byCharWrapping
  165. return cell
  166. }()
  167. }
  168. // MARK: - Configuring the table view and cells with information
  169. extension MessagingViewController {
  170. func resetTableContents() {
  171. sections.removeAll()
  172. sectionHeaderTitles.removeAll()
  173. // APNS
  174. let apnsSection: [Row] = [.apnsToken, .apnsStatus, .requestAPNSPermissions]
  175. sections.append(apnsSection)
  176. sectionHeaderTitles.append("APNs")
  177. // FCM
  178. let fcmSection: [Row] = [.fcmToken]
  179. sections.append(fcmSection)
  180. sectionHeaderTitles.append("FCM Token")
  181. }
  182. func indexPathFor(_ rowId: Row) -> IndexPath? {
  183. var sectionIndex = 0
  184. for section in sections {
  185. var rowIndex = 0
  186. for row in section {
  187. if row == rowId {
  188. return IndexPath(row: rowIndex, section: sectionIndex)
  189. }
  190. rowIndex += 1
  191. }
  192. sectionIndex += 1
  193. }
  194. return nil
  195. }
  196. func configureCell(_ cell: UITableViewCell, withAPNSToken apnsToken: Data?) {
  197. guard !Environment.isSimulator else {
  198. cell.textLabel?.text = "APNs notifications are not supported in the simulator."
  199. cell.detailTextLabel?.text = nil
  200. return
  201. }
  202. if let apnsToken = apnsToken {
  203. cell.textLabel?.text = apnsToken.hexByteString
  204. cell.detailTextLabel?.text = "Tap to Share"
  205. } else {
  206. cell.textLabel?.text = "None"
  207. cell.detailTextLabel?.text = nil
  208. }
  209. }
  210. func configureCellWithAPNSStatus(_ cell: UITableViewCell) {
  211. if let allowedNotificationTypes = allowedNotificationTypes {
  212. let displayableTypes: [String] = allowedNotificationTypes.map { $0.rawValue }
  213. cell.detailTextLabel?.text = displayableTypes.joined(separator: ", ")
  214. } else {
  215. cell.detailTextLabel?.text = "Retrieving..."
  216. }
  217. }
  218. func configureCell(_ cell: UITableViewCell, withFCMToken fcmToken: String?) {
  219. if let fcmToken = fcmToken {
  220. cell.textLabel?.text = fcmToken
  221. cell.detailTextLabel?.text = "Tap to Share"
  222. } else {
  223. cell.textLabel?.text = "None"
  224. cell.detailTextLabel?.text = nil
  225. }
  226. }
  227. }
  228. // MARK: - UITableViewDataSource
  229. extension MessagingViewController: UITableViewDataSource {
  230. func numberOfSections(in tableView: UITableView) -> Int {
  231. return sections.count
  232. }
  233. public func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
  234. return sections[section].count
  235. }
  236. public func tableView(_ tableView: UITableView,
  237. cellForRowAt indexPath: IndexPath) -> UITableViewCell {
  238. let row = sections[indexPath.section][indexPath.row]
  239. let cell: UITableViewCell
  240. switch row {
  241. case .apnsToken:
  242. cell = apnsTableCell
  243. configureCell(cell, withAPNSToken: Messaging.messaging().apnsToken)
  244. case .apnsStatus:
  245. cell = apnsStatusTableCell
  246. configureCellWithAPNSStatus(cell)
  247. case .requestAPNSPermissions:
  248. cell = apnsRequestPermissionsTableCell
  249. case .fcmToken:
  250. cell = fcmTokenTableCell
  251. configureCell(cell, withFCMToken: Messaging.messaging().fcmToken)
  252. }
  253. return cell
  254. }
  255. func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
  256. return sectionHeaderTitles[section]
  257. }
  258. }
  259. // MARK: - UITableViewDelegate
  260. extension MessagingViewController: UITableViewDelegate {
  261. func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
  262. tableView.deselectRow(at: indexPath, animated: true)
  263. let row = sections[indexPath.section][indexPath.row]
  264. switch row {
  265. case .apnsToken:
  266. if let apnsToken = Messaging.messaging().apnsToken {
  267. showActivityViewControllerFor(sharedItem: apnsToken.hexByteString)
  268. }
  269. case .fcmToken:
  270. if let fcmToken = Messaging.messaging().fcmToken {
  271. showActivityViewControllerFor(sharedItem: fcmToken)
  272. }
  273. default: break
  274. }
  275. }
  276. }
  277. // MARK: - UI Controls
  278. extension MessagingViewController {
  279. func onRequestUserNotificationsButtonTapped(sender: UIButton) {
  280. NotificationsController.shared.registerForUserFacingNotificationsFor(UIApplication.shared)
  281. }
  282. }
  283. // MARK: - Activity View Controller
  284. extension MessagingViewController {
  285. func showActivityViewControllerFor(sharedItem: Any) {
  286. let activityViewController = UIActivityViewController(activityItems: [sharedItem],
  287. applicationActivities: nil)
  288. present(activityViewController, animated: true, completion: nil)
  289. }
  290. }