DiagnosticsTableViewController.swift 26 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656
  1. //
  2. // SPDX-FileCopyrightText: 2022 Nextcloud GmbH and Nextcloud contributors
  3. // SPDX-License-Identifier: GPL-3.0-or-later
  4. //
  5. import UIKit
  6. import Contacts
  7. import Photos
  8. import UserNotifications
  9. class DiagnosticsTableViewController: UITableViewController {
  10. enum DiagnosticsSections: Int {
  11. case kDiagnosticsSectionApp = 0
  12. case kDiagnosticsSectionAccount
  13. case kDiagnosticsSectionServer
  14. case kDiagnosticsSectionTalk
  15. case kDiagnosticsSectionSignaling
  16. case kDiagnosticsSectionCount
  17. }
  18. enum AppSections: Int {
  19. case kAppSectionName = 0
  20. case kAppSectionVersion
  21. case kAppSectionAllowNotifications
  22. case kAppSectionAllowMicrophoneAccess
  23. case kAppSectionAllowCameraAccess
  24. case kAppSectionAllowContactsAccess
  25. case kAppSectionAllowLocationAccess
  26. case kAppSectionAllowPhotoLibraryAccess
  27. case kAppSectionCallKitEnabled
  28. case kAppSectionOpenSettings
  29. case kAppSectionCount
  30. }
  31. enum AccountSections: Int {
  32. case kAccountSectionServer = 0
  33. case kAccountSectionUser
  34. case kAccountPushSubscribed
  35. case kAccountSectionCount
  36. }
  37. enum ServerSections: Int {
  38. case kServerSectionName = 0
  39. case kServerSectionVersion
  40. case kServerSectionUserStatusSupported
  41. case kServerSectionReferenceApiSupported
  42. case kServerSectionNotificationsAppEnabled
  43. case kServerSectionGuestsAppEnabled
  44. case kServerSectionReachable
  45. case kServerSectionCount
  46. }
  47. enum TalkSections: Int {
  48. case kTalkSectionVersion = 0
  49. case kTalkSectionCanCreate
  50. case kTalkSectionCallEnabled
  51. case kTalkSectionRecordingEnabled
  52. case kTalkSectionAttachmentsAllowed
  53. case kTalkSectionCount
  54. }
  55. enum AllSignalingSections: Int {
  56. case kSignalingSectionMode = 0
  57. case kSignalingSectionVersion
  58. case kSignalingSectionStunServers
  59. case kSignalingSectionTurnServers
  60. case kSignalingSectionCount
  61. }
  62. var signalingSections: [Int] = []
  63. var account: TalkAccount
  64. var serverCapabilities: ServerCapabilities
  65. var signalingConfiguration: SignalingSettings?
  66. var externalSignalingController: NCExternalSignalingController?
  67. var signalingVersion: Int
  68. var serverReachable: Bool?
  69. var serverReachableIndicator = UIActivityIndicatorView(frame: .init(x: 0, y: 0, width: 24, height: 24))
  70. var notificationSettings: UNNotificationSettings?
  71. var notificationSettingsIndicator = UIActivityIndicatorView(frame: .init(x: 0, y: 0, width: 24, height: 24))
  72. let allowedString = NSLocalizedString("Allowed", comment: "'{Microphone, Camera, ...} access is allowed'")
  73. let deniedString = NSLocalizedString("Denied", comment: "'{Microphone, Camera, ...} access is denied'")
  74. let notRequestedString = NSLocalizedString("Not requested", comment: "'{Microphone, Camera, ...} access was not requested'")
  75. let deniedFunctionalityString = NSLocalizedString("This will impact the functionality of this app. Please review your settings.", comment: "")
  76. let cellIdentifierOpenAppSettings = "cellIdentifierOpenAppSettings"
  77. let cellIdentifierSubtitle = "cellIdentifierSubtitle"
  78. let cellIdentifierSubtitleAccessory = "cellIdentifierSubtitleAccessory"
  79. init(withAccount account: TalkAccount) {
  80. self.account = account
  81. self.serverCapabilities = NCDatabaseManager.sharedInstance().serverCapabilities(forAccountId: account.accountId)!
  82. self.signalingConfiguration = NCSettingsController.sharedInstance().signalingConfigurations[account.accountId] as? SignalingSettings
  83. self.externalSignalingController = NCSettingsController.sharedInstance().externalSignalingController(forAccountId: account.accountId)
  84. self.signalingVersion = NCAPIController.sharedInstance().signalingAPIVersion(for: account)
  85. // Build signaling sections based on external signaling server
  86. signalingSections.append(AllSignalingSections.kSignalingSectionMode.rawValue)
  87. if externalSignalingController != nil {
  88. signalingSections.append(AllSignalingSections.kSignalingSectionVersion.rawValue)
  89. }
  90. signalingSections.append(AllSignalingSections.kSignalingSectionStunServers.rawValue)
  91. signalingSections.append(AllSignalingSections.kSignalingSectionTurnServers.rawValue)
  92. super.init(style: .insetGrouped)
  93. }
  94. required init?(coder: NSCoder) {
  95. fatalError("init(coder:) has not been implemented")
  96. }
  97. override func viewDidLoad() {
  98. super.viewDidLoad()
  99. self.navigationController?.navigationBar.isTranslucent = false
  100. self.navigationItem.title = NSLocalizedString("Diagnostics", comment: "")
  101. self.navigationController?.navigationBar.titleTextAttributes = [NSAttributedString.Key.foregroundColor: NCAppBranding.themeTextColor()]
  102. self.navigationController?.navigationBar.tintColor = NCAppBranding.themeTextColor()
  103. self.navigationController?.navigationBar.barTintColor = NCAppBranding.themeColor()
  104. self.tabBarController?.tabBar.tintColor = NCAppBranding.themeColor()
  105. let themeColor: UIColor = NCAppBranding.themeColor()
  106. let appearance = UINavigationBarAppearance()
  107. appearance.configureWithOpaqueBackground()
  108. appearance.backgroundColor = themeColor
  109. appearance.titleTextAttributes = [.foregroundColor: NCAppBranding.themeTextColor()]
  110. self.navigationItem.standardAppearance = appearance
  111. self.navigationItem.compactAppearance = appearance
  112. self.navigationItem.scrollEdgeAppearance = appearance
  113. self.tableView.register(UITableViewCell.self, forCellReuseIdentifier: cellIdentifierOpenAppSettings)
  114. self.tableView.register(SubtitleTableViewCell.self, forCellReuseIdentifier: cellIdentifierSubtitle)
  115. self.tableView.register(SubtitleTableViewCell.self, forCellReuseIdentifier: cellIdentifierSubtitleAccessory)
  116. runChecks()
  117. }
  118. // MARK: Async. checks
  119. func runChecks() {
  120. DispatchQueue.main.async {
  121. self.checkServerReachability()
  122. self.checkNotificationAuthorizationStatus()
  123. }
  124. }
  125. func checkServerReachability() {
  126. serverReachable = nil
  127. serverReachableIndicator.startAnimating()
  128. self.reloadRow(ServerSections.kServerSectionReachable.rawValue, in: DiagnosticsSections.kDiagnosticsSectionServer.rawValue)
  129. NCAPIController.sharedInstance().getServerCapabilities(for: account) { _, error in
  130. DispatchQueue.main.async {
  131. self.serverReachable = error == nil
  132. self.serverReachableIndicator.stopAnimating()
  133. self.reloadRow(ServerSections.kServerSectionReachable.rawValue, in: DiagnosticsSections.kDiagnosticsSectionServer.rawValue)
  134. }
  135. }
  136. }
  137. func checkNotificationAuthorizationStatus() {
  138. notificationSettings = nil
  139. notificationSettingsIndicator.startAnimating()
  140. self.reloadRow(AppSections.kAppSectionAllowNotifications.rawValue, in: DiagnosticsSections.kDiagnosticsSectionApp.rawValue)
  141. let current = UNUserNotificationCenter.current()
  142. current.getNotificationSettings(completionHandler: { settings in
  143. DispatchQueue.main.async {
  144. self.notificationSettings = settings
  145. self.notificationSettingsIndicator.stopAnimating()
  146. self.reloadRow(AppSections.kAppSectionAllowNotifications.rawValue, in: DiagnosticsSections.kDiagnosticsSectionApp.rawValue)
  147. }
  148. })
  149. }
  150. // MARK: Table view data source
  151. override func numberOfSections(in tableView: UITableView) -> Int {
  152. return DiagnosticsSections.kDiagnosticsSectionCount.rawValue
  153. }
  154. override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
  155. switch section {
  156. case DiagnosticsSections.kDiagnosticsSectionApp.rawValue:
  157. return AppSections.kAppSectionCount.rawValue
  158. case DiagnosticsSections.kDiagnosticsSectionAccount.rawValue:
  159. return AccountSections.kAccountSectionCount.rawValue
  160. case DiagnosticsSections.kDiagnosticsSectionServer.rawValue:
  161. return ServerSections.kServerSectionCount.rawValue
  162. case DiagnosticsSections.kDiagnosticsSectionTalk.rawValue:
  163. return TalkSections.kTalkSectionCount.rawValue
  164. case DiagnosticsSections.kDiagnosticsSectionSignaling.rawValue:
  165. return signalingSections.count
  166. default:
  167. return 1
  168. }
  169. }
  170. override func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
  171. switch section {
  172. case DiagnosticsSections.kDiagnosticsSectionApp.rawValue:
  173. return NSLocalizedString("App", comment: "")
  174. case DiagnosticsSections.kDiagnosticsSectionAccount.rawValue:
  175. return NSLocalizedString("Account", comment: "")
  176. case DiagnosticsSections.kDiagnosticsSectionServer.rawValue:
  177. return NSLocalizedString("Server", comment: "")
  178. case DiagnosticsSections.kDiagnosticsSectionTalk.rawValue:
  179. return NSLocalizedString("Talk", comment: "")
  180. case DiagnosticsSections.kDiagnosticsSectionSignaling.rawValue:
  181. return NSLocalizedString("Signaling", comment: "")
  182. default:
  183. return nil
  184. }
  185. }
  186. override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
  187. switch indexPath.section {
  188. case DiagnosticsSections.kDiagnosticsSectionApp.rawValue:
  189. return appCell(for: indexPath)
  190. case DiagnosticsSections.kDiagnosticsSectionAccount.rawValue:
  191. return accountCell(for: indexPath)
  192. case DiagnosticsSections.kDiagnosticsSectionServer.rawValue:
  193. return serverCell(for: indexPath)
  194. case DiagnosticsSections.kDiagnosticsSectionTalk.rawValue:
  195. return talkCell(for: indexPath)
  196. case DiagnosticsSections.kDiagnosticsSectionSignaling.rawValue:
  197. return signalingCell(for: indexPath)
  198. default:
  199. break
  200. }
  201. return UITableViewCell()
  202. }
  203. override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
  204. if indexPath.section == DiagnosticsSections.kDiagnosticsSectionApp.rawValue,
  205. indexPath.row == AppSections.kAppSectionOpenSettings.rawValue {
  206. UIApplication.shared.open(URL(string: UIApplication.openSettingsURLString)!, options: [:], completionHandler: nil)
  207. } else if indexPath.section == DiagnosticsSections.kDiagnosticsSectionTalk.rawValue,
  208. indexPath.row == TalkSections.kTalkSectionVersion.rawValue {
  209. presentCapabilitiesDetails()
  210. }
  211. self.tableView.deselectRow(at: indexPath, animated: true)
  212. }
  213. override func tableView(_ tableView: UITableView, shouldShowMenuForRowAt indexPath: IndexPath) -> Bool {
  214. return (tableView.cellForRow(at: indexPath)?.detailTextLabel?.text) != nil
  215. }
  216. override func tableView(_ tableView: UITableView, canPerformAction action: Selector, forRowAt indexPath: IndexPath, withSender sender: Any?) -> Bool {
  217. return action == #selector(copy(_:))
  218. }
  219. override func tableView(_ tableView: UITableView, performAction action: Selector, forRowAt indexPath: IndexPath, withSender sender: Any?) {
  220. if action == #selector(copy(_:)) {
  221. let cell = tableView.cellForRow(at: indexPath)
  222. let pasteboard = UIPasteboard.general
  223. pasteboard.string = cell?.detailTextLabel?.text
  224. }
  225. }
  226. // MARK: Table view cells
  227. func appCell(for indexPath: IndexPath) -> UITableViewCell {
  228. switch indexPath.row {
  229. case AppSections.kAppSectionAllowNotifications.rawValue:
  230. return appNotificationsCell(for: indexPath)
  231. case AppSections.kAppSectionAllowMicrophoneAccess.rawValue:
  232. return appAVAccessCell(for: .audio, for: indexPath)
  233. case AppSections.kAppSectionAllowCameraAccess.rawValue:
  234. return appAVAccessCell(for: .video, for: indexPath)
  235. case AppSections.kAppSectionAllowContactsAccess.rawValue:
  236. return appContactsAccessCell(for: indexPath)
  237. case AppSections.kAppSectionAllowLocationAccess.rawValue:
  238. return appLocationAccessCell(for: indexPath)
  239. case AppSections.kAppSectionAllowPhotoLibraryAccess.rawValue:
  240. return appPhotoLibraryAccessCell(for: indexPath)
  241. case AppSections.kAppSectionOpenSettings.rawValue:
  242. let cell = tableView.dequeueReusableCell(withIdentifier: cellIdentifierOpenAppSettings, for: indexPath)
  243. cell.textLabel?.text = NSLocalizedString("Open app settings", comment: "")
  244. cell.textLabel?.textAlignment = .center
  245. cell.textLabel?.textColor = UIColor.systemBlue
  246. return cell
  247. default:
  248. break
  249. }
  250. let cell = tableView.dequeueReusableCell(withIdentifier: cellIdentifierSubtitle, for: indexPath)
  251. switch indexPath.row {
  252. case AppSections.kAppSectionName.rawValue:
  253. let appName = (Bundle.main.object(forInfoDictionaryKey: "CFBundleDisplayName") as? String)!
  254. cell.textLabel?.text = NSLocalizedString("Name", comment: "")
  255. cell.detailTextLabel?.text = appName
  256. case AppSections.kAppSectionVersion.rawValue:
  257. cell.textLabel?.text = NSLocalizedString("Version", comment: "")
  258. cell.detailTextLabel?.text = NCAppBranding.getAppVersionString()
  259. case AppSections.kAppSectionCallKitEnabled.rawValue:
  260. cell.textLabel?.text = NSLocalizedString("CallKit supported?", comment: "")
  261. cell.detailTextLabel?.text = readableBool(for: CallKitManager.isCallKitAvailable())
  262. default:
  263. break
  264. }
  265. return cell
  266. }
  267. func appAVAccessCell(for mediaType: AVMediaType, for indexPath: IndexPath) -> UITableViewCell {
  268. let cell = tableView.dequeueReusableCell(withIdentifier: cellIdentifierSubtitle, for: indexPath)
  269. let authStatusAV = AVCaptureDevice.authorizationStatus(for: mediaType)
  270. if mediaType == .audio {
  271. cell.textLabel?.text = NSLocalizedString("Microphone access", comment: "")
  272. } else {
  273. cell.textLabel?.text = NSLocalizedString("Camera access", comment: "")
  274. }
  275. switch authStatusAV {
  276. case .authorized:
  277. cell.detailTextLabel?.text = allowedString
  278. case .denied:
  279. cell.detailTextLabel?.text = deniedString + "\n" + deniedFunctionalityString
  280. default:
  281. cell.detailTextLabel?.text = notRequestedString
  282. }
  283. return cell
  284. }
  285. func appContactsAccessCell(for indexPath: IndexPath) -> UITableViewCell {
  286. let cell = tableView.dequeueReusableCell(withIdentifier: cellIdentifierSubtitle, for: indexPath)
  287. cell.textLabel?.text = NSLocalizedString("Contact access", comment: "")
  288. switch CNContactStore.authorizationStatus(for: .contacts) {
  289. case .authorized:
  290. cell.detailTextLabel?.text = allowedString
  291. case .denied:
  292. cell.detailTextLabel?.text = deniedString + "\n" + deniedFunctionalityString
  293. default:
  294. cell.detailTextLabel?.text = notRequestedString
  295. }
  296. return cell
  297. }
  298. func appLocationAccessCell(for indexPath: IndexPath) -> UITableViewCell {
  299. let cell = tableView.dequeueReusableCell(withIdentifier: cellIdentifierSubtitle, for: indexPath)
  300. cell.textLabel?.text = NSLocalizedString("Location access", comment: "")
  301. if CLLocationManager.locationServicesEnabled() {
  302. switch CLLocationManager().authorizationStatus {
  303. case .authorizedAlways, .authorizedWhenInUse:
  304. cell.detailTextLabel?.text = allowedString
  305. case .denied:
  306. cell.detailTextLabel?.text = deniedString + "\n" + deniedFunctionalityString
  307. default:
  308. cell.detailTextLabel?.text = notRequestedString
  309. }
  310. } else {
  311. cell.detailTextLabel?.text = NSLocalizedString("Location service is not enabled", comment: "")
  312. }
  313. return cell
  314. }
  315. func appPhotoLibraryAccessCell(for indexPath: IndexPath) -> UITableViewCell {
  316. let cell = tableView.dequeueReusableCell(withIdentifier: cellIdentifierSubtitle, for: indexPath)
  317. cell.textLabel?.text = NSLocalizedString("Photo library access", comment: "")
  318. switch PHPhotoLibrary.authorizationStatus() {
  319. case .authorized:
  320. cell.detailTextLabel?.text = allowedString
  321. case .denied:
  322. cell.detailTextLabel?.text = deniedString + "\n" + deniedFunctionalityString
  323. default:
  324. cell.detailTextLabel?.text = notRequestedString
  325. }
  326. return cell
  327. }
  328. func appNotificationsCell(for indexPath: IndexPath) -> UITableViewCell {
  329. let cell = tableView.dequeueReusableCell(withIdentifier: cellIdentifierSubtitleAccessory, for: indexPath)
  330. cell.textLabel?.text = NSLocalizedString("Notifications", comment: "")
  331. cell.accessoryType = .none
  332. cell.accessoryView = nil
  333. if notificationSettingsIndicator.isAnimating {
  334. cell.accessoryView = notificationSettingsIndicator
  335. } else if let settings = notificationSettings {
  336. switch settings.authorizationStatus {
  337. case .authorized:
  338. cell.detailTextLabel?.text = allowedString
  339. case .denied:
  340. cell.detailTextLabel?.text = deniedString + "\n" + deniedFunctionalityString
  341. default:
  342. cell.detailTextLabel?.text = notRequestedString
  343. }
  344. }
  345. return cell
  346. }
  347. func accountCell(for indexPath: IndexPath) -> UITableViewCell {
  348. let cell = tableView.dequeueReusableCell(withIdentifier: cellIdentifierSubtitle, for: indexPath)
  349. switch indexPath.row {
  350. case AccountSections.kAccountSectionServer.rawValue:
  351. cell.textLabel?.text = NSLocalizedString("Server", comment: "")
  352. cell.detailTextLabel?.text = account.server
  353. case AccountSections.kAccountSectionUser.rawValue:
  354. cell.textLabel?.text = NSLocalizedString("User", comment: "")
  355. cell.detailTextLabel?.text = account.user
  356. case AccountSections.kAccountPushSubscribed.rawValue:
  357. cell.textLabel?.text = NSLocalizedString("Push notifications", comment: "")
  358. if account.lastPushSubscription > 0 {
  359. let lastSubsctiptionString = NSLocalizedString("Last subscription", comment: "Last subscription to the push notification server")
  360. let lastTime = NSDate(timeIntervalSince1970: TimeInterval(account.lastPushSubscription))
  361. cell.detailTextLabel?.text = lastSubsctiptionString + ": " + NCUtils.readableDateTime(fromDate: lastTime as Date)
  362. } else {
  363. cell.detailTextLabel?.text = NSLocalizedString("Never subscribed", comment: "Never subscribed to the push notification server")
  364. }
  365. default:
  366. break
  367. }
  368. return cell
  369. }
  370. func serverCell(for indexPath: IndexPath) -> UITableViewCell {
  371. let cell = tableView.dequeueReusableCell(withIdentifier: cellIdentifierSubtitleAccessory, for: indexPath)
  372. cell.accessoryType = .none
  373. cell.accessoryView = nil
  374. switch indexPath.row {
  375. case ServerSections.kServerSectionName.rawValue:
  376. cell.textLabel?.text = NSLocalizedString("Name", comment: "")
  377. cell.detailTextLabel?.text = serverCapabilities.name
  378. case ServerSections.kServerSectionVersion.rawValue:
  379. cell.textLabel?.text = NSLocalizedString("Version", comment: "")
  380. cell.detailTextLabel?.text = serverCapabilities.version
  381. case ServerSections.kServerSectionUserStatusSupported.rawValue:
  382. cell.textLabel?.text = NSLocalizedString("User status supported?", comment: "")
  383. cell.detailTextLabel?.text = readableBool(for: serverCapabilities.userStatus)
  384. case ServerSections.kServerSectionReferenceApiSupported.rawValue:
  385. cell.textLabel?.text = NSLocalizedString("Reference API supported?", comment: "")
  386. cell.detailTextLabel?.text = readableBool(for: serverCapabilities.referenceApiSupported)
  387. case ServerSections.kServerSectionNotificationsAppEnabled.rawValue:
  388. cell.textLabel?.text = NSLocalizedString("Notifications app enabled?", comment: "")
  389. cell.detailTextLabel?.text = readableBool(for: serverCapabilities.notificationsCapabilities.count > 0)
  390. case ServerSections.kServerSectionGuestsAppEnabled.rawValue:
  391. cell.textLabel?.text = NSLocalizedString("Guests app enabled?", comment: "")
  392. cell.detailTextLabel?.text = readableBool(for: serverCapabilities.guestsAppEnabled)
  393. case ServerSections.kServerSectionReachable.rawValue:
  394. cell.textLabel?.text = NSLocalizedString("Reachable?", comment: "")
  395. cell.detailTextLabel?.text = "-"
  396. if serverReachableIndicator.isAnimating {
  397. cell.accessoryView = serverReachableIndicator
  398. } else if let reachable = serverReachable {
  399. cell.detailTextLabel?.text = readableBool(for: reachable)
  400. }
  401. default:
  402. break
  403. }
  404. return cell
  405. }
  406. func talkCell(for indexPath: IndexPath) -> UITableViewCell {
  407. let cell = tableView.dequeueReusableCell(withIdentifier: cellIdentifierSubtitleAccessory, for: indexPath)
  408. cell.accessoryType = .none
  409. cell.accessoryView = nil
  410. switch indexPath.row {
  411. case TalkSections.kTalkSectionVersion.rawValue:
  412. cell.accessoryType = .disclosureIndicator
  413. if serverCapabilities.talkVersion.isEmpty {
  414. cell.textLabel?.text = NSLocalizedString("Capabilities", comment: "")
  415. cell.detailTextLabel?.text = String(serverCapabilities.talkCapabilities.count)
  416. } else {
  417. cell.textLabel?.text = NSLocalizedString("Version", comment: "")
  418. cell.detailTextLabel?.text = serverCapabilities.talkVersion
  419. }
  420. case TalkSections.kTalkSectionCanCreate.rawValue:
  421. cell.textLabel?.text = NSLocalizedString("Can create conversations?", comment: "")
  422. cell.detailTextLabel?.text = readableBool(for: serverCapabilities.canCreate)
  423. case TalkSections.kTalkSectionCallEnabled.rawValue:
  424. cell.textLabel?.text = NSLocalizedString("Calls enabled?", comment: "")
  425. cell.detailTextLabel?.text = readableBool(for: serverCapabilities.callEnabled)
  426. case TalkSections.kTalkSectionRecordingEnabled.rawValue:
  427. cell.textLabel?.text = NSLocalizedString("Call recording enabled?", comment: "")
  428. cell.detailTextLabel?.text = readableBool(for: serverCapabilities.recordingEnabled)
  429. case TalkSections.kTalkSectionAttachmentsAllowed.rawValue:
  430. cell.textLabel?.text = NSLocalizedString("Attachments allowed?", comment: "")
  431. cell.detailTextLabel?.text = readableBool(for: serverCapabilities.attachmentsAllowed)
  432. default:
  433. break
  434. }
  435. return cell
  436. }
  437. func signalingCell(for indexPath: IndexPath) -> UITableViewCell {
  438. let cell = tableView.dequeueReusableCell(withIdentifier: cellIdentifierSubtitle, for: indexPath)
  439. let allSectionsIndex = signalingSections[indexPath.row]
  440. switch allSectionsIndex {
  441. case AllSignalingSections.kSignalingSectionMode.rawValue:
  442. let externalSignalingServerUsed = externalSignalingController != nil
  443. cell.textLabel?.text = NSLocalizedString("Mode", comment: "The signaling mode used")
  444. if externalSignalingServerUsed {
  445. cell.detailTextLabel?.text = NSLocalizedString("External", comment: "External signaling used")
  446. } else {
  447. cell.detailTextLabel?.text = NSLocalizedString("Internal", comment: "Internal signaling used")
  448. }
  449. case AllSignalingSections.kSignalingSectionVersion.rawValue:
  450. cell.textLabel?.text = NSLocalizedString("Version", comment: "")
  451. if serverCapabilities.externalSignalingServerVersion.isEmpty {
  452. cell.detailTextLabel?.text = NSLocalizedString("Unknown", comment: "")
  453. } else {
  454. cell.detailTextLabel?.text = serverCapabilities.externalSignalingServerVersion
  455. }
  456. case AllSignalingSections.kSignalingSectionStunServers.rawValue:
  457. cell.textLabel?.text = NSLocalizedString("STUN servers", comment: "")
  458. cell.detailTextLabel?.text = NSLocalizedString("Unavailable", comment: "")
  459. var stunServers: [String] = []
  460. signalingConfiguration?.stunServers.forEach { stunServers += $0.urls ?? [] }
  461. if !stunServers.isEmpty {
  462. cell.detailTextLabel?.text = stunServers.joined(separator: "\n")
  463. }
  464. case AllSignalingSections.kSignalingSectionTurnServers.rawValue:
  465. cell.textLabel?.text = NSLocalizedString("TURN servers", comment: "")
  466. cell.detailTextLabel?.text = NSLocalizedString("Unavailable", comment: "")
  467. var turnServers: [String] = []
  468. signalingConfiguration?.turnServers.forEach { turnServers += $0.urls ?? [] }
  469. if !turnServers.isEmpty {
  470. cell.detailTextLabel?.text = turnServers.joined(separator: "\n")
  471. }
  472. default:
  473. break
  474. }
  475. return cell
  476. }
  477. // MARK: Capabilities details
  478. func presentCapabilitiesDetails() {
  479. let talkFeatures = serverCapabilities.talkCapabilities.value(forKey: "self") as? [String]
  480. guard let capabilities = talkFeatures else {
  481. return
  482. }
  483. let capabilitiesVC = SimpleTableViewController(withOptions: capabilities.sorted(),
  484. withTitle: NSLocalizedString("Capabilities", comment: ""))
  485. self.navigationController?.pushViewController(capabilitiesVC, animated: true)
  486. }
  487. // MARK: Utils
  488. func readableBool(for value: Bool) -> String {
  489. if value {
  490. return NSLocalizedString("Yes", comment: "")
  491. } else {
  492. return NSLocalizedString("No", comment: "")
  493. }
  494. }
  495. func reloadRow(_ row: Int, in section: Int) {
  496. DispatchQueue.main.async {
  497. self.tableView.reloadRows(at: [IndexPath(row: row, section: section)], with: .none)
  498. }
  499. }
  500. }