MessagingViewController.swift 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332
  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 = "apnsToken"
  20. case apnsStatus = "apnsStatus"
  21. case requestAPNSPermissions = "requestAPNSPermissions"
  22. case fcmToken = "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(self.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, reuseIdentifier: Row.apnsStatus.rawValue)
  135. cell.textLabel?.text = "Allowed:"
  136. cell.detailTextLabel?.numberOfLines = 0
  137. cell.detailTextLabel?.lineBreakMode = .byWordWrapping
  138. return cell
  139. }()
  140. lazy var requestPermissionsButton: UIButton = {
  141. let button = UIButton(type: .system)
  142. button.setTitle(PermissionsButtonTitle.requestPermissions.rawValue, for: .normal)
  143. button.setTitleColor(UIColor.gray, for: .highlighted)
  144. button.setTitleColor(UIColor.gray, for: .disabled)
  145. button.titleLabel?.font = UIFont.preferredFont(forTextStyle: .body)
  146. button.addTarget(self,
  147. action: #selector(onRequestUserNotificationsButtonTapped),
  148. for: .touchUpInside)
  149. return button
  150. }()
  151. lazy var apnsRequestPermissionsTableCell: UITableViewCell = {
  152. let cell = UITableViewCell(style: .default,
  153. reuseIdentifier: Row.requestAPNSPermissions.rawValue)
  154. cell.selectionStyle = .none
  155. cell.contentView.addSubview(self.requestPermissionsButton)
  156. self.requestPermissionsButton.frame = cell.contentView.bounds
  157. self.requestPermissionsButton.autoresizingMask = [.flexibleWidth, .flexibleHeight]
  158. return cell
  159. }()
  160. lazy var fcmTokenTableCell: UITableViewCell = {
  161. let cell = UITableViewCell(style: .subtitle, reuseIdentifier: Row.fcmToken.rawValue)
  162. cell.textLabel?.numberOfLines = 0
  163. cell.textLabel?.lineBreakMode = .byCharWrapping
  164. return cell
  165. }()
  166. }
  167. // MARK: - Configuring the table view and cells with information
  168. extension MessagingViewController {
  169. func resetTableContents() {
  170. sections.removeAll()
  171. sectionHeaderTitles.removeAll()
  172. // APNS
  173. let apnsSection: [Row] = [.apnsToken, .apnsStatus, .requestAPNSPermissions]
  174. sections.append(apnsSection)
  175. sectionHeaderTitles.append("APNs")
  176. // FCM
  177. let fcmSection: [Row] = [.fcmToken]
  178. sections.append(fcmSection)
  179. sectionHeaderTitles.append("FCM Token")
  180. }
  181. func indexPathFor(_ rowId: Row) -> IndexPath? {
  182. var sectionIndex = 0
  183. for section in sections {
  184. var rowIndex = 0
  185. for row in section {
  186. if row == rowId {
  187. return IndexPath(row: rowIndex, section: sectionIndex)
  188. }
  189. rowIndex += 1
  190. }
  191. sectionIndex += 1
  192. }
  193. return nil
  194. }
  195. func configureCell(_ cell: UITableViewCell, withAPNSToken apnsToken: Data?) {
  196. guard !Environment.isSimulator else {
  197. cell.textLabel?.text = "APNs notifications are not supported in the simulator."
  198. cell.detailTextLabel?.text = nil
  199. return
  200. }
  201. if let apnsToken = apnsToken {
  202. cell.textLabel?.text = apnsToken.hexByteString
  203. cell.detailTextLabel?.text = "Tap to Share"
  204. } else {
  205. cell.textLabel?.text = "None"
  206. cell.detailTextLabel?.text = nil
  207. }
  208. }
  209. func configureCellWithAPNSStatus(_ cell: UITableViewCell) {
  210. if let allowedNotificationTypes = allowedNotificationTypes {
  211. let displayableTypes: [String] = allowedNotificationTypes.map { return $0.rawValue }
  212. cell.detailTextLabel?.text = displayableTypes.joined(separator: ", ")
  213. } else {
  214. cell.detailTextLabel?.text = "Retrieving..."
  215. }
  216. }
  217. func configureCell(_ cell: UITableViewCell, withFCMToken fcmToken: String?) {
  218. if let fcmToken = fcmToken {
  219. cell.textLabel?.text = fcmToken
  220. cell.detailTextLabel?.text = "Tap to Share"
  221. } else {
  222. cell.textLabel?.text = "None"
  223. cell.detailTextLabel?.text = nil
  224. }
  225. }
  226. }
  227. // MARK: - UITableViewDataSource
  228. extension MessagingViewController: UITableViewDataSource {
  229. func numberOfSections(in tableView: UITableView) -> Int {
  230. return sections.count
  231. }
  232. public func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
  233. return sections[section].count
  234. }
  235. public func tableView(_ tableView: UITableView,
  236. cellForRowAt indexPath: IndexPath) -> UITableViewCell {
  237. let row = sections[indexPath.section][indexPath.row]
  238. let cell: UITableViewCell
  239. switch row {
  240. case .apnsToken:
  241. cell = apnsTableCell
  242. configureCell(cell, withAPNSToken: Messaging.messaging().apnsToken)
  243. case .apnsStatus:
  244. cell = apnsStatusTableCell
  245. configureCellWithAPNSStatus(cell)
  246. case .requestAPNSPermissions:
  247. cell = apnsRequestPermissionsTableCell
  248. case .fcmToken:
  249. cell = fcmTokenTableCell
  250. configureCell(cell, withFCMToken: Messaging.messaging().fcmToken)
  251. }
  252. return cell
  253. }
  254. func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
  255. return sectionHeaderTitles[section]
  256. }
  257. }
  258. // MARK: - UITableViewDelegate
  259. extension MessagingViewController: UITableViewDelegate {
  260. func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
  261. tableView.deselectRow(at: indexPath, animated: true)
  262. let row = sections[indexPath.section][indexPath.row]
  263. switch row {
  264. case .apnsToken:
  265. if let apnsToken = Messaging.messaging().apnsToken {
  266. showActivityViewControllerFor(sharedItem: apnsToken.hexByteString)
  267. }
  268. case .fcmToken:
  269. if let fcmToken = Messaging.messaging().fcmToken {
  270. showActivityViewControllerFor(sharedItem: fcmToken)
  271. }
  272. default: break
  273. }
  274. }
  275. }
  276. // MARK: - UI Controls
  277. extension MessagingViewController {
  278. func onRequestUserNotificationsButtonTapped(sender: UIButton) {
  279. NotificationsController.shared.registerForUserFacingNotificationsFor(UIApplication.shared)
  280. }
  281. }
  282. // MARK: - Activity View Controller
  283. extension MessagingViewController {
  284. func showActivityViewControllerFor(sharedItem: Any) {
  285. let activityViewController = UIActivityViewController(activityItems: [sharedItem],
  286. applicationActivities: nil)
  287. present(activityViewController, animated: true, completion: nil)
  288. }
  289. }