SettingsTableViewController.swift 47 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068
  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 NextcloudKit
  7. import SafariServices
  8. import SwiftUI
  9. import ReplayKit
  10. import SDWebImage
  11. enum SettingsSection: Int {
  12. case kSettingsSectionUser = 0
  13. case kSettingsSectionUserStatus
  14. case kSettingsSectionAccountSettings
  15. case kSettingsSectionOtherAccounts
  16. case kSettingsSectionConfiguration
  17. case kSettingsSectionAdvanced
  18. case kSettingsSectionAbout
  19. }
  20. enum AccountSettingsOptions: Int {
  21. case kAccountSettingsReadStatusPrivacy = 0
  22. case kAccountSettingsTypingPrivacy
  23. case kAccountSettingsContactsSync
  24. }
  25. enum ConfigurationSectionOption: Int {
  26. case kConfigurationSectionOptionVideo = 0
  27. case kConfigurationSectionOptionRecents
  28. }
  29. enum AdvancedSectionOption: Int {
  30. case kAdvancedSectionOptionDiagnostics = 0
  31. case kAdvancedSectionOptionCachedImages
  32. case kAdvancedSectionOptionCachedFiles
  33. case kAdvancedSectionOptionCallFromOldAccount
  34. }
  35. enum AboutSection: Int {
  36. case kAboutSectionPrivacy = 0
  37. case kAboutSectionSourceCode
  38. }
  39. class SettingsTableViewController: UITableViewController, UITextFieldDelegate, UserStatusViewDelegate, CallsFromOldAccountViewControllerDelegate {
  40. let kPhoneTextFieldTag = 99
  41. let kUserSettingsCellIdentifier = "UserSettingsCellIdentifier"
  42. let kUserSettingsTableCellNibName = "UserSettingsTableViewCell"
  43. let kAccountCellIdentifier: String = "AccountCellIdentifier"
  44. let iconConfiguration = UIImage.SymbolConfiguration(pointSize: 18)
  45. var activeUserStatus: NCUserStatus?
  46. var readStatusSwitch = UISwitch()
  47. var typingIndicatorSwitch = UISwitch()
  48. var contactSyncSwitch = UISwitch()
  49. var setPhoneAction: UIAlertAction?
  50. var phoneUtil = NBPhoneNumberUtil()
  51. var includeInRecentsSwitch = UISwitch()
  52. var totalImageCacheSize = 0
  53. var totalFileCacheSize = 0
  54. var activeAccount = NCDatabaseManager.sharedInstance().activeAccount()
  55. var inactiveAccounts = NCDatabaseManager.sharedInstance().inactiveAccounts()
  56. var serverCapabilities: ServerCapabilities? {
  57. // Since NCDatabaseManager already caches the capabilities, we don't need a lazy var here
  58. NCDatabaseManager.sharedInstance().serverCapabilities(forAccountId: activeAccount.accountId)
  59. }
  60. lazy var profilePictures: [String: UIImage] = {
  61. var result: [String: UIImage] = [:]
  62. for account in NCDatabaseManager.sharedInstance().allAccounts() {
  63. guard let account = account as? TalkAccount else {
  64. continue
  65. }
  66. if let image = NCAPIController.sharedInstance().userProfileImage(for: account, with: self.traitCollection.userInterfaceStyle) {
  67. result[account.accountId] = image
  68. }
  69. }
  70. return result
  71. }()
  72. @IBOutlet weak var cancelButton: UIBarButtonItem!
  73. override func viewDidLoad() {
  74. super.viewDidLoad()
  75. self.navigationItem.title = NSLocalizedString("Settings", comment: "")
  76. self.navigationController?.navigationBar.titleTextAttributes = [NSAttributedString.Key.foregroundColor: NCAppBranding.themeTextColor()]
  77. self.navigationController?.navigationBar.tintColor = NCAppBranding.themeColor()
  78. self.tabBarController?.tabBar.tintColor = NCAppBranding.themeColor()
  79. self.cancelButton.tintColor = NCAppBranding.themeTextColor()
  80. contactSyncSwitch.frame = .zero
  81. contactSyncSwitch.addTarget(self, action: #selector(contactSyncValueChanged(_:)), for: .valueChanged)
  82. readStatusSwitch.frame = .zero
  83. readStatusSwitch.addTarget(self, action: #selector(readStatusValueChanged(_:)), for: .valueChanged)
  84. includeInRecentsSwitch.frame = .zero
  85. includeInRecentsSwitch.addTarget(self, action: #selector(includeInRecentsValueChanged(_:)), for: .valueChanged)
  86. typingIndicatorSwitch.frame = .zero
  87. typingIndicatorSwitch.addTarget(self, action: #selector(typingIndicatorValueChanged(_:)), for: .valueChanged)
  88. let themeColor: UIColor = NCAppBranding.themeColor()
  89. let themeTextColor: UIColor = NCAppBranding.themeTextColor()
  90. let appearance = UINavigationBarAppearance()
  91. appearance.configureWithOpaqueBackground()
  92. appearance.titleTextAttributes = [.foregroundColor: themeTextColor]
  93. appearance.backgroundColor = themeColor
  94. self.navigationItem.standardAppearance = appearance
  95. self.navigationItem.compactAppearance = appearance
  96. self.navigationItem.scrollEdgeAppearance = appearance
  97. tableView.register(UINib(nibName: kUserSettingsTableCellNibName, bundle: nil), forCellReuseIdentifier: kUserSettingsCellIdentifier)
  98. NotificationCenter.default.addObserver(self, selector: #selector(appStateHasChanged(notification:)), name: NSNotification.Name.NCAppStateHasChanged, object: nil)
  99. NotificationCenter.default.addObserver(self, selector: #selector(contactsHaveBeenUpdated(notification:)), name: NSNotification.Name.NCContactsManagerContactsUpdated, object: nil)
  100. NotificationCenter.default.addObserver(self, selector: #selector(contactsAccessHasBeenUpdated(notification:)), name: NSNotification.Name.NCContactsManagerContactsAccessUpdated, object: nil)
  101. NotificationCenter.default.addObserver(self, selector: #selector(userProfileImageUpdated), name: NSNotification.Name.NCUserProfileImageUpdated, object: nil)
  102. self.updateTotalImageCacheSize()
  103. self.updateTotalFileCacheSize()
  104. self.adaptInterfaceForAppState(appState: NCConnectionController.sharedInstance().appState)
  105. }
  106. override func didReceiveMemoryWarning() {
  107. super.didReceiveMemoryWarning()
  108. }
  109. @IBAction func cancelButtonPressed(_ sender: Any) {
  110. self.dismiss(animated: true, completion: nil)
  111. }
  112. func getSettingsSections() -> [Int] {
  113. var sections = [Int]()
  114. // Active user section
  115. sections.append(SettingsSection.kSettingsSectionUser.rawValue)
  116. // User status section
  117. if serverCapabilities?.userStatus ?? false {
  118. sections.append(SettingsSection.kSettingsSectionUserStatus.rawValue)
  119. }
  120. // Account settings section
  121. sections.append(SettingsSection.kSettingsSectionAccountSettings.rawValue)
  122. // Other accounts section
  123. if !inactiveAccounts.isEmpty {
  124. sections.append(SettingsSection.kSettingsSectionOtherAccounts.rawValue)
  125. }
  126. // Configuration section
  127. sections.append(SettingsSection.kSettingsSectionConfiguration.rawValue)
  128. // Advanced section
  129. sections.append(SettingsSection.kSettingsSectionAdvanced.rawValue)
  130. // About section
  131. sections.append(SettingsSection.kSettingsSectionAbout.rawValue)
  132. return sections
  133. }
  134. func getAccountSettingsSectionOptions() -> [Int] {
  135. var options = [Int]()
  136. // Read status privacy setting
  137. if NCDatabaseManager.sharedInstance().serverHasTalkCapability(kCapabilityChatReadStatus) {
  138. options.append(AccountSettingsOptions.kAccountSettingsReadStatusPrivacy.rawValue)
  139. }
  140. // Typing indicator privacy setting
  141. if NCDatabaseManager.sharedInstance().serverHasTalkCapability(kCapabilityTypingIndicators) {
  142. options.append(AccountSettingsOptions.kAccountSettingsTypingPrivacy.rawValue)
  143. }
  144. // Contacts sync
  145. if NCDatabaseManager.sharedInstance().serverHasTalkCapability(kCapabilityPhonebookSearch) {
  146. options.append(AccountSettingsOptions.kAccountSettingsContactsSync.rawValue)
  147. }
  148. return options
  149. }
  150. func getConfigurationSectionOptions() -> [Int] {
  151. var options = [Int]()
  152. // Video quality
  153. options.append(ConfigurationSectionOption.kConfigurationSectionOptionVideo.rawValue)
  154. // Calls in recents
  155. options.append(ConfigurationSectionOption.kConfigurationSectionOptionRecents.rawValue)
  156. return options
  157. }
  158. func getAdvancedSectionOptions() -> [Int] {
  159. var options = [Int]()
  160. // Diagnostics
  161. options.append(AdvancedSectionOption.kAdvancedSectionOptionDiagnostics.rawValue)
  162. // Caches
  163. options.append(AdvancedSectionOption.kAdvancedSectionOptionCachedImages.rawValue)
  164. options.append(AdvancedSectionOption.kAdvancedSectionOptionCachedFiles.rawValue)
  165. // Received calls from old accounts
  166. if NCSettingsController.sharedInstance().didReceiveCallsFromOldAccount() {
  167. options.append(AdvancedSectionOption.kAdvancedSectionOptionCallFromOldAccount.rawValue)
  168. }
  169. return options
  170. }
  171. func getAboutSectionOptions() -> [Int] {
  172. var options = [Int]()
  173. // Privacy
  174. options.append(AboutSection.kAboutSectionPrivacy.rawValue)
  175. // Source code
  176. if !isBrandedApp.boolValue {
  177. options.append(AboutSection.kAboutSectionSourceCode.rawValue)
  178. }
  179. return options
  180. }
  181. func getSectionForSettingsSection(section: SettingsSection) -> Int {
  182. let section = getSettingsSections().firstIndex(of: section.rawValue)
  183. return section ?? 0
  184. }
  185. func getIndexPathForConfigurationOption(option: ConfigurationSectionOption) -> IndexPath {
  186. let section = getSectionForSettingsSection(section: SettingsSection.kSettingsSectionConfiguration)
  187. let row = getConfigurationSectionOptions().firstIndex(of: option.rawValue)
  188. return IndexPath(row: row ?? 0, section: section)
  189. }
  190. // MARK: - User Profile
  191. func refreshUserProfile() {
  192. NCSettingsController.sharedInstance().getUserProfile(forAccountId: activeAccount.accountId) { _ in
  193. self.tableView.reloadData()
  194. }
  195. self.getActiveUserStatus()
  196. }
  197. func getActiveUserStatus() {
  198. NCAPIController.sharedInstance().getUserStatus(for: activeAccount) { userStatus, error in
  199. if let userStatus = userStatus, error == nil {
  200. self.activeUserStatus = NCUserStatus(dictionary: userStatus)
  201. self.tableView.reloadData()
  202. }
  203. }
  204. }
  205. // MARK: - Notifications
  206. @objc func appStateHasChanged(notification: NSNotification) {
  207. let appState = notification.userInfo?["appState"]
  208. if let rawAppState = appState as? Int, let appState = AppState(rawValue: rawAppState) {
  209. self.adaptInterfaceForAppState(appState: appState)
  210. }
  211. }
  212. @objc func contactsHaveBeenUpdated(notification: NSNotification) {
  213. DispatchQueue.main.async {
  214. self.activeAccount = NCDatabaseManager.sharedInstance().activeAccount()
  215. self.tableView.reloadData()
  216. }
  217. }
  218. @objc func contactsAccessHasBeenUpdated(notification: NSNotification) {
  219. DispatchQueue.main.async {
  220. self.activeAccount = NCDatabaseManager.sharedInstance().activeAccount()
  221. self.tableView.reloadData()
  222. }
  223. }
  224. @objc func userProfileImageUpdated(notification: NSNotification) {
  225. self.tableView.reloadSections(IndexSet(integer: SettingsSection.kSettingsSectionUser.rawValue), with: .none)
  226. }
  227. // MARK: - User Interface
  228. func adaptInterfaceForAppState(appState: AppState) {
  229. switch appState {
  230. case .ready:
  231. refreshUserProfile()
  232. default:
  233. break
  234. }
  235. }
  236. // MARK: - Profile actions
  237. func userProfilePressed() {
  238. let userProfileVC = UserProfileTableViewController(withAccount: activeAccount)
  239. self.navigationController?.pushViewController(userProfileVC, animated: true)
  240. }
  241. // MARK: - User Status (SwiftUI)
  242. func presentUserStatusOptions() {
  243. if let activeUserStatus = activeUserStatus {
  244. var userStatusView = UserStatusSwiftUIView(userStatus: activeUserStatus)
  245. userStatusView.delegate = self
  246. let hostingController = UIHostingController(rootView: userStatusView)
  247. self.present(hostingController, animated: true)
  248. }
  249. }
  250. func userStatusViewDidDisappear() {
  251. self.getActiveUserStatus()
  252. }
  253. // MARK: - User phone number
  254. func checkUserPhoneNumber() {
  255. NCSettingsController.sharedInstance().getUserProfile(forAccountId: activeAccount.accountId) { _ in
  256. if self.activeAccount.phone.isEmpty {
  257. self.presentSetPhoneNumberDialog()
  258. }
  259. }
  260. }
  261. func presentSetPhoneNumberDialog() {
  262. let alertTitle = NSLocalizedString("Phone number", comment: "")
  263. let alertMessage = NSLocalizedString("You can set your phone number so other users will be able to find you", comment: "")
  264. let setPhoneNumberDialog = UIAlertController(title: alertTitle, message: alertMessage, preferredStyle: .alert)
  265. setPhoneNumberDialog.addTextField { [self] textField in
  266. let location = NSLocale.current.regionCode
  267. let countryCode = phoneUtil.getCountryCode(forRegion: location)
  268. if let countryCode = countryCode {
  269. textField.text = "+\(countryCode)"
  270. }
  271. if let exampleNumber = try? phoneUtil.getExampleNumber(location) {
  272. textField.placeholder = try? phoneUtil.format(exampleNumber, numberFormat: NBEPhoneNumberFormat.INTERNATIONAL)
  273. }
  274. textField.keyboardType = .phonePad
  275. textField.delegate = self
  276. textField.tag = kPhoneTextFieldTag
  277. }
  278. setPhoneAction = UIAlertAction(title: NSLocalizedString("Set", comment: ""), style: .default, handler: { _ in
  279. let phoneNumber = setPhoneNumberDialog.textFields?[0].text
  280. NCAPIController.sharedInstance().setUserProfileField(kUserProfilePhone, withValue: phoneNumber, for: self.activeAccount) { error, _ in
  281. if error != nil {
  282. if let phoneNumber = phoneNumber {
  283. self.presentPhoneNumberErrorDialog(phoneNumber: phoneNumber)
  284. }
  285. print("Error setting phone number ", error ?? "")
  286. } else {
  287. NotificationPresenter.shared().present(text: NSLocalizedString("Phone number set successfully", comment: ""), dismissAfterDelay: 5.0, includedStyle: .success)
  288. }
  289. self.refreshUserProfile()
  290. }
  291. })
  292. if let setPhoneAction = setPhoneAction {
  293. setPhoneAction.isEnabled = false
  294. setPhoneNumberDialog.addAction(setPhoneAction)
  295. }
  296. let cancelAction = UIAlertAction(title: NSLocalizedString("Skip", comment: ""), style: .default) { _ in
  297. self.refreshUserProfile()
  298. }
  299. setPhoneNumberDialog.addAction(cancelAction)
  300. self.present(setPhoneNumberDialog, animated: true, completion: nil)
  301. }
  302. func presentPhoneNumberErrorDialog(phoneNumber: String) {
  303. let alertTitle = NSLocalizedString("Could not set phone number", comment: "")
  304. var alertMessage = NSLocalizedString("An error occurred while setting phone number", comment: "")
  305. let failedPhoneNumber = try? phoneUtil.parse(phoneNumber, defaultRegion: nil)
  306. if let formattedPhoneNumber = try? phoneUtil.format(failedPhoneNumber, numberFormat: NBEPhoneNumberFormat.INTERNATIONAL) {
  307. alertMessage = NSLocalizedString("An error occurred while setting \(formattedPhoneNumber) as phone number", comment: "")
  308. }
  309. let failedPhoneNumberDialog = UIAlertController(
  310. title: alertTitle,
  311. message: alertMessage,
  312. preferredStyle: .alert)
  313. let retryAction = UIAlertAction(title: NSLocalizedString("Retry", comment: ""), style: .default) { _ in
  314. self.presentSetPhoneNumberDialog()
  315. }
  316. failedPhoneNumberDialog.addAction(retryAction)
  317. let cancelAction = UIAlertAction(title: NSLocalizedString("Cancel", comment: ""), style: .default, handler: nil)
  318. failedPhoneNumberDialog.addAction(cancelAction)
  319. self.present(failedPhoneNumberDialog, animated: true, completion: nil)
  320. }
  321. // MARK: UITextField delegate
  322. func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
  323. if textField.tag == kPhoneTextFieldTag {
  324. let inputPhoneNumber = (textField.text as NSString?)?.replacingCharacters(in: range, with: string)
  325. let phoneNumber = try? phoneUtil.parse(inputPhoneNumber, defaultRegion: nil)
  326. setPhoneAction?.isEnabled = phoneUtil.isValidNumber(phoneNumber)
  327. }
  328. return true
  329. }
  330. // MARK: - Configuration
  331. func presentVideoResoultionsSelector() {
  332. let videoConfIndexPath = self.getIndexPathForConfigurationOption(option: ConfigurationSectionOption.kConfigurationSectionOptionVideo)
  333. let videoResolutions = NCSettingsController.sharedInstance().videoSettingsModel.availableVideoResolutions()
  334. let storedResolution = NCSettingsController.sharedInstance().videoSettingsModel.currentVideoResolutionSettingFromStore()
  335. let optionsActionSheet = UIAlertController(title: NSLocalizedString("Video quality", comment: ""), message: nil, preferredStyle: .actionSheet)
  336. for resolution in videoResolutions {
  337. let readableResolution = NCSettingsController.sharedInstance().videoSettingsModel.readableResolution(resolution)
  338. let isStoredResolution = resolution == storedResolution
  339. let action = UIAlertAction(title: readableResolution, style: .default) { _ in
  340. NCSettingsController.sharedInstance().videoSettingsModel.storeVideoResolutionSetting(resolution)
  341. self.tableView.beginUpdates()
  342. self.tableView.reloadRows(at: [videoConfIndexPath], with: .none)
  343. self.tableView.endUpdates()
  344. }
  345. if isStoredResolution {
  346. action.setValue(UIImage(named: "checkmark")?.withRenderingMode(_: .alwaysOriginal), forKey: "image")
  347. }
  348. optionsActionSheet.addAction(action)
  349. }
  350. optionsActionSheet.addAction(UIAlertAction(title: NSLocalizedString("Cancel", comment: ""), style: .cancel, handler: nil))
  351. // Presentation on iPads
  352. optionsActionSheet.popoverPresentationController?.sourceView = self.tableView
  353. optionsActionSheet.popoverPresentationController?.sourceRect = self.tableView.rectForRow(at: videoConfIndexPath)
  354. self.present(optionsActionSheet, animated: true, completion: nil)
  355. }
  356. @objc func contactSyncValueChanged(_ sender: Any?) {
  357. NCSettingsController.sharedInstance().setContactSync(contactSyncSwitch.isOn)
  358. if contactSyncSwitch.isOn {
  359. if !NCContactsManager.sharedInstance().isContactAccessDetermined() {
  360. NCContactsManager.sharedInstance().requestContactsAccess { granted in
  361. if granted {
  362. self.checkUserPhoneNumber()
  363. NCContactsManager.sharedInstance().searchInServer(forAddressBookContacts: true)
  364. }
  365. }
  366. } else if NCContactsManager.sharedInstance().isContactAccessAuthorized() {
  367. self.checkUserPhoneNumber()
  368. NCContactsManager.sharedInstance().searchInServer(forAddressBookContacts: true)
  369. }
  370. } else {
  371. NCContactsManager.sharedInstance().removeStoredContacts()
  372. }
  373. // Reload to update configuration section footer
  374. self.tableView.reloadData()
  375. }
  376. @objc func readStatusValueChanged(_ sender: Any?) {
  377. readStatusSwitch.isEnabled = false
  378. NCAPIController.sharedInstance().setReadStatusPrivacySettingEnabled(!readStatusSwitch.isOn, for: activeAccount) { error in
  379. if error == nil {
  380. NCSettingsController.sharedInstance().getCapabilitiesForAccountId(self.activeAccount.accountId) { error in
  381. if error == nil {
  382. self.readStatusSwitch.isEnabled = true
  383. self.tableView.reloadData()
  384. } else {
  385. self.showReadStatusModificationError()
  386. }
  387. }
  388. } else {
  389. self.showReadStatusModificationError()
  390. }
  391. }
  392. }
  393. func showReadStatusModificationError() {
  394. readStatusSwitch.isEnabled = true
  395. self.tableView.reloadData()
  396. let errorDialog = UIAlertController(
  397. title: NSLocalizedString("An error occurred changing read status setting", comment: ""),
  398. message: nil,
  399. preferredStyle: .alert)
  400. let okAction = UIAlertAction(title: NSLocalizedString("OK", comment: ""), style: .default, handler: nil)
  401. errorDialog.addAction(okAction)
  402. self.present(errorDialog, animated: true, completion: nil)
  403. }
  404. @objc func typingIndicatorValueChanged(_ sender: Any?) {
  405. typingIndicatorSwitch.isEnabled = false
  406. NCAPIController.sharedInstance().setTypingPrivacySettingEnabled(!typingIndicatorSwitch.isOn, for: activeAccount) { error in
  407. if error == nil {
  408. NCSettingsController.sharedInstance().getCapabilitiesForAccountId(self.activeAccount.accountId) { error in
  409. if error == nil {
  410. self.typingIndicatorSwitch.isEnabled = true
  411. self.tableView.reloadData()
  412. } else {
  413. self.showTypeIndicatorModificationError()
  414. }
  415. }
  416. } else {
  417. self.showTypeIndicatorModificationError()
  418. }
  419. }
  420. }
  421. func showTypeIndicatorModificationError() {
  422. self.typingIndicatorSwitch.isEnabled = true
  423. self.tableView.reloadData()
  424. let errorDialog = UIAlertController(
  425. title: NSLocalizedString("An error occurred changing typing privacy setting", comment: ""),
  426. message: nil,
  427. preferredStyle: .alert)
  428. let okAction = UIAlertAction(title: NSLocalizedString("OK", comment: ""), style: .default, handler: nil)
  429. errorDialog.addAction(okAction)
  430. self.present(errorDialog, animated: true, completion: nil)
  431. }
  432. @objc func includeInRecentsValueChanged(_ sender: Any?) {
  433. NCUserDefaults.setIncludeCallsInRecentsEnabled(includeInRecentsSwitch.isOn)
  434. CallKitManager.sharedInstance().setDefaultProviderConfiguration()
  435. }
  436. // MARK: - Advanced actions
  437. func diagnosticsPressed() {
  438. let diagnosticsVC = DiagnosticsTableViewController(withAccount: activeAccount)
  439. self.navigationController?.pushViewController(diagnosticsVC, animated: true)
  440. }
  441. func cachedImagesPressed() {
  442. let clearCacheDialog = UIAlertController(
  443. title: NSLocalizedString("Clear cache", comment: ""),
  444. message: NSLocalizedString("Do you really want to clear the image cache?", comment: ""),
  445. preferredStyle: .alert)
  446. let clearAction = UIAlertAction(title: NSLocalizedString("Clear cache", comment: ""), style: .destructive) { _ in
  447. NCImageSessionManager.shared.cache.removeAllCachedResponses()
  448. SDImageCache.shared.clearMemory()
  449. SDImageCache.shared.clearDisk {
  450. self.updateTotalImageCacheSize()
  451. self.tableView.reloadData()
  452. }
  453. }
  454. clearCacheDialog.addAction(clearAction)
  455. let cancelAction = UIAlertAction(title: NSLocalizedString("Cancel", comment: ""), style: .cancel, handler: nil)
  456. clearCacheDialog.addAction(cancelAction)
  457. self.present(clearCacheDialog, animated: true, completion: nil)
  458. }
  459. func cachedFilesPressed() {
  460. let clearCacheDialog = UIAlertController(
  461. title: NSLocalizedString("Clear cache", comment: ""),
  462. message: NSLocalizedString("Do you really want to clear the file cache?", comment: ""),
  463. preferredStyle: .alert)
  464. let clearAction = UIAlertAction(title: NSLocalizedString("Clear cache", comment: ""), style: .destructive) { _ in
  465. let fileController = NCChatFileController()
  466. let talkAccounts = NCDatabaseManager.sharedInstance().allAccounts()
  467. if let talkAccounts = talkAccounts as? [TalkAccount] {
  468. for account in talkAccounts {
  469. fileController.clearDownloadDirectory(for: account)
  470. }
  471. }
  472. self.updateTotalFileCacheSize()
  473. self.tableView.reloadData()
  474. }
  475. clearCacheDialog.addAction(clearAction)
  476. let cancelAction = UIAlertAction(title: NSLocalizedString("Cancel", comment: ""), style: .cancel, handler: nil)
  477. clearCacheDialog.addAction(cancelAction)
  478. self.present(clearCacheDialog, animated: true, completion: nil)
  479. }
  480. func callsFromOldAccountPressed() {
  481. let vc = CallsFromOldAccountViewController()
  482. vc.delegate = self
  483. self.navigationController?.pushViewController(vc, animated: true)
  484. }
  485. func callsFromOldAccountWarningAcknowledged() {
  486. self.tableView.reloadData()
  487. }
  488. // MARK: - Table view data source
  489. override func numberOfSections(in tableView: UITableView) -> Int {
  490. return getSettingsSections().count
  491. }
  492. override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
  493. let sections = getSettingsSections()
  494. let settingsSection = sections[section]
  495. switch settingsSection {
  496. case SettingsSection.kSettingsSectionUser.rawValue:
  497. return 1
  498. case SettingsSection.kSettingsSectionUserStatus.rawValue:
  499. return 1
  500. case SettingsSection.kSettingsSectionAccountSettings.rawValue:
  501. return getAccountSettingsSectionOptions().count
  502. case SettingsSection.kSettingsSectionConfiguration.rawValue:
  503. return getConfigurationSectionOptions().count
  504. case SettingsSection.kSettingsSectionAdvanced.rawValue:
  505. return getAdvancedSectionOptions().count
  506. case SettingsSection.kSettingsSectionAbout.rawValue:
  507. return getAboutSectionOptions().count
  508. case SettingsSection.kSettingsSectionOtherAccounts.rawValue:
  509. return inactiveAccounts.count
  510. default:
  511. break
  512. }
  513. return 1
  514. }
  515. override func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
  516. let sections = getSettingsSections()
  517. let settingsSection = sections[section]
  518. switch settingsSection {
  519. case SettingsSection.kSettingsSectionOtherAccounts.rawValue:
  520. return NSLocalizedString("Other Accounts", comment: "")
  521. case SettingsSection.kSettingsSectionConfiguration.rawValue:
  522. return NSLocalizedString("Configuration", comment: "")
  523. case SettingsSection.kSettingsSectionAdvanced.rawValue:
  524. return NSLocalizedString("Advanced", comment: "")
  525. case SettingsSection.kSettingsSectionAbout.rawValue:
  526. return NSLocalizedString("About", comment: "")
  527. default:
  528. break
  529. }
  530. return nil
  531. }
  532. override func tableView(_ tableView: UITableView, titleForFooterInSection section: Int) -> String? {
  533. let sections = getSettingsSections()
  534. let settingsSection = sections[section]
  535. if settingsSection == SettingsSection.kSettingsSectionAbout.rawValue {
  536. let appName = (Bundle.main.object(forInfoDictionaryKey: "CFBundleDisplayName") as? String)!
  537. return "\(appName) \(NCAppBranding.getAppVersionString())\n\(copyright)"
  538. }
  539. if settingsSection == SettingsSection.kSettingsSectionAccountSettings.rawValue && contactSyncSwitch.isOn {
  540. if NCContactsManager.sharedInstance().isContactAccessDetermined() && !NCContactsManager.sharedInstance().isContactAccessAuthorized() {
  541. return NSLocalizedString("Contact access has been denied", comment: "")
  542. }
  543. if activeAccount.lastContactSync > 0 {
  544. let lastUpdate = Date(timeIntervalSince1970: TimeInterval(activeAccount.lastContactSync))
  545. let dateFormatter = DateFormatter()
  546. dateFormatter.dateStyle = .medium
  547. dateFormatter.timeStyle = .short
  548. return NSLocalizedString("Last sync", comment: "") + ": " + dateFormatter.string(from: lastUpdate)
  549. }
  550. }
  551. if settingsSection == SettingsSection.kSettingsSectionUser.rawValue && contactSyncSwitch.isOn {
  552. if activeAccount.phone.isEmpty {
  553. let missingPhoneString = NSLocalizedString("Missing phone number information", comment: "")
  554. return "⚠ " + missingPhoneString
  555. }
  556. }
  557. return nil
  558. }
  559. override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
  560. var cell = UITableViewCell()
  561. let sections = getSettingsSections()
  562. let settingsSection = sections[indexPath.section]
  563. switch settingsSection {
  564. case SettingsSection.kSettingsSectionUser.rawValue:
  565. guard let cell = tableView.dequeueReusableCell(withIdentifier: kUserSettingsCellIdentifier, for: indexPath) as? UserSettingsTableViewCell else { return UITableViewCell() }
  566. cell.userDisplayNameLabel.text = activeAccount.userDisplayName
  567. let accountServer = activeAccount.server
  568. cell.serverAddressLabel.text = accountServer.replacingOccurrences(of: "https://", with: "")
  569. cell.userImageView.image = self.getProfilePicture(for: activeAccount)
  570. cell.accessoryType = .disclosureIndicator
  571. return cell
  572. case SettingsSection.kSettingsSectionUserStatus.rawValue:
  573. cell = UITableViewCell(style: .subtitle, reuseIdentifier: "UserStatusCellIdentifier")
  574. if activeUserStatus != nil {
  575. cell.textLabel?.text = activeUserStatus!.readableUserStatus()
  576. let statusMessage = activeUserStatus!.readableUserStatusMessage()
  577. if !statusMessage.isEmpty {
  578. cell.textLabel?.text = statusMessage
  579. }
  580. if activeUserStatus!.status == kUserStatusDND {
  581. cell.detailTextLabel?.text = NSLocalizedString("All notifications are muted", comment: "")
  582. cell.detailTextLabel?.numberOfLines = 0
  583. cell.detailTextLabel?.textColor = .secondaryLabel
  584. }
  585. let statusImage = activeUserStatus!.getSFUserStatusIcon()
  586. cell.imageView?.image = statusImage
  587. } else {
  588. cell.textLabel?.text = NSLocalizedString("Fetching status …", comment: "")
  589. }
  590. return cell
  591. case SettingsSection.kSettingsSectionAccountSettings.rawValue:
  592. cell = userSettingsCell(for: indexPath)
  593. case SettingsSection.kSettingsSectionOtherAccounts.rawValue:
  594. cell = userAccountsCell(for: indexPath)
  595. case SettingsSection.kSettingsSectionConfiguration.rawValue:
  596. cell = sectionConfigurationCell(for: indexPath)
  597. case SettingsSection.kSettingsSectionAdvanced.rawValue:
  598. cell = advancedCell(for: indexPath)
  599. case SettingsSection.kSettingsSectionAbout.rawValue:
  600. cell = sectionAboutCell(for: indexPath)
  601. default:
  602. break
  603. }
  604. return cell
  605. }
  606. func didSelectOtherAccountSectionCell(for indexPath: IndexPath) {
  607. if let account = inactiveAccounts[indexPath.row] as? TalkAccount {
  608. NCSettingsController.sharedInstance().setActiveAccountWithAccountId(account.accountId)
  609. }
  610. }
  611. func didSelectAccountSettingsSectionCell(for indexPath: IndexPath) {
  612. let options = getAccountSettingsSectionOptions()
  613. let option = options[indexPath.row]
  614. switch option {
  615. case AccountSettingsOptions.kAccountSettingsContactsSync.rawValue:
  616. NCContactsManager.sharedInstance().searchInServer(forAddressBookContacts: true)
  617. default:
  618. break
  619. }
  620. }
  621. func didSelectSettingsSectionCell(for indexPath: IndexPath) {
  622. let options = getConfigurationSectionOptions()
  623. let option = options[indexPath.row]
  624. switch option {
  625. case ConfigurationSectionOption.kConfigurationSectionOptionVideo.rawValue:
  626. self.presentVideoResoultionsSelector()
  627. default:
  628. break
  629. }
  630. }
  631. func didSelectAdvancedSectionCell(for indexPath: IndexPath) {
  632. let options = getAdvancedSectionOptions()
  633. let option = options[indexPath.row]
  634. switch option {
  635. case AdvancedSectionOption.kAdvancedSectionOptionDiagnostics.rawValue:
  636. self.diagnosticsPressed()
  637. case AdvancedSectionOption.kAdvancedSectionOptionCachedImages.rawValue:
  638. self.cachedImagesPressed()
  639. case AdvancedSectionOption.kAdvancedSectionOptionCachedFiles.rawValue:
  640. self.cachedFilesPressed()
  641. case AdvancedSectionOption.kAdvancedSectionOptionCallFromOldAccount.rawValue:
  642. self.callsFromOldAccountPressed()
  643. default:
  644. break
  645. }
  646. }
  647. func didSelectAboutSectionCell(for indexPath: IndexPath) {
  648. let options = getAboutSectionOptions()
  649. let option = options[indexPath.row]
  650. switch option {
  651. case AboutSection.kAboutSectionPrivacy.rawValue:
  652. if let url = URL(string: privacyURL), ["http", "https"].contains(url.scheme?.lowercased() ?? "") {
  653. let safariVC = SFSafariViewController(url: url)
  654. self.present(safariVC, animated: true, completion: nil)
  655. }
  656. case AboutSection.kAboutSectionSourceCode.rawValue:
  657. let safariVC = SFSafariViewController(url: URL(string: "https://github.com/nextcloud/talk-ios")!)
  658. self.present(safariVC, animated: true, completion: nil)
  659. default:
  660. break
  661. }
  662. }
  663. override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
  664. let sections = getSettingsSections()
  665. let settingsSection = sections[indexPath.section]
  666. switch settingsSection {
  667. case SettingsSection.kSettingsSectionUser.rawValue:
  668. self.userProfilePressed()
  669. case SettingsSection.kSettingsSectionUserStatus.rawValue:
  670. self.presentUserStatusOptions()
  671. case SettingsSection.kSettingsSectionAccountSettings.rawValue:
  672. self.didSelectAccountSettingsSectionCell(for: indexPath)
  673. case SettingsSection.kSettingsSectionOtherAccounts.rawValue:
  674. self.didSelectOtherAccountSectionCell(for: indexPath)
  675. case SettingsSection.kSettingsSectionConfiguration.rawValue:
  676. self.didSelectSettingsSectionCell(for: indexPath)
  677. case SettingsSection.kSettingsSectionAdvanced.rawValue:
  678. self.didSelectAdvancedSectionCell(for: indexPath)
  679. case SettingsSection.kSettingsSectionAbout.rawValue:
  680. didSelectAboutSectionCell(for: indexPath)
  681. default:
  682. break
  683. }
  684. self.tableView.deselectRow(at: indexPath, animated: true)
  685. }
  686. }
  687. private extension UITableViewCell {
  688. func setSettingsImage(image: UIImage?, renderingMode: UIImage.RenderingMode = .alwaysTemplate) {
  689. // Render all images to a size of 20x20 so all cells have the same width for the imageView
  690. self.imageView?.image = NCUtils.renderAspectImage(image: image, ofSize: .init(width: 20, height: 20), centerImage: true)?.withRenderingMode(renderingMode)
  691. self.imageView?.tintColor = .secondaryLabel
  692. self.imageView?.contentMode = .scaleAspectFit
  693. }
  694. }
  695. extension SettingsTableViewController {
  696. // Cell configuration for every section
  697. func userSettingsCell(for indexPath: IndexPath) -> UITableViewCell {
  698. let readStatusCellIdentifier = "ReadStatusCellIdentifier"
  699. let typingIndicatorCellIdentifier = "TypingIndicatorCellIdentifier"
  700. let contactsSyncCellIdentifier = "ContactsSyncCellIdentifier"
  701. let options = getAccountSettingsSectionOptions()
  702. let option = options[indexPath.row]
  703. var cell = UITableViewCell()
  704. switch option {
  705. case AccountSettingsOptions.kAccountSettingsReadStatusPrivacy.rawValue:
  706. cell = tableView.dequeueReusableCell(withIdentifier: readStatusCellIdentifier) ?? UITableViewCell(style: .subtitle, reuseIdentifier: readStatusCellIdentifier)
  707. cell.textLabel?.text = NSLocalizedString("Read status", comment: "")
  708. cell.selectionStyle = .none
  709. cell.setSettingsImage(image: UIImage(named: "check-all"))
  710. cell.accessoryView = readStatusSwitch
  711. readStatusSwitch.isOn = !(serverCapabilities?.readStatusPrivacy ?? true)
  712. case AccountSettingsOptions.kAccountSettingsTypingPrivacy.rawValue:
  713. cell = tableView.dequeueReusableCell(withIdentifier: typingIndicatorCellIdentifier) ?? UITableViewCell(style: .subtitle, reuseIdentifier: typingIndicatorCellIdentifier)
  714. cell.textLabel?.text = NSLocalizedString("Typing indicator", comment: "")
  715. cell.selectionStyle = .none
  716. cell.setSettingsImage(image: UIImage(systemName: "rectangle.and.pencil.and.ellipsis")?.applyingSymbolConfiguration(iconConfiguration))
  717. cell.accessoryView = typingIndicatorSwitch
  718. typingIndicatorSwitch.isOn = !(serverCapabilities?.typingPrivacy ?? true)
  719. let externalSignalingController = NCSettingsController.sharedInstance().externalSignalingController(forAccountId: activeAccount.accountId)
  720. let externalSignalingServerUsed = externalSignalingController != nil
  721. if !externalSignalingServerUsed {
  722. cell.detailTextLabel?.text = NSLocalizedString("Typing indicators are only available when using a high performance backend (HPB)", comment: "")
  723. } else {
  724. cell.detailTextLabel?.text = nil
  725. }
  726. case AccountSettingsOptions.kAccountSettingsContactsSync.rawValue:
  727. cell = tableView.dequeueReusableCell(withIdentifier: contactsSyncCellIdentifier) ?? UITableViewCell(style: .subtitle, reuseIdentifier: contactsSyncCellIdentifier)
  728. cell.textLabel?.text = NSLocalizedString("Phone number integration", comment: "")
  729. cell.detailTextLabel?.text = NSLocalizedString("Match system contacts", comment: "")
  730. cell.selectionStyle = contactSyncSwitch.isOn ? .default : .none
  731. cell.setSettingsImage(image: UIImage(systemName: "iphone")?.applyingSymbolConfiguration(iconConfiguration))
  732. cell.accessoryView = contactSyncSwitch
  733. contactSyncSwitch.isOn = NCSettingsController.sharedInstance().isContactSyncEnabled()
  734. default:
  735. break
  736. }
  737. cell.textLabel?.numberOfLines = 0
  738. cell.detailTextLabel?.numberOfLines = 0
  739. cell.detailTextLabel?.textColor = .secondaryLabel
  740. return cell
  741. }
  742. func userAccountsCell(for indexPath: IndexPath) -> UITableViewCell {
  743. guard let account = inactiveAccounts[indexPath.row] as? TalkAccount else { return UITableViewCell() }
  744. let accountServer = account.server.replacingOccurrences(of: "https://", with: "")
  745. let cell = tableView.dequeueReusableCell(withIdentifier: kAccountCellIdentifier) ?? UITableViewCell(style: .subtitle, reuseIdentifier: kAccountCellIdentifier)
  746. cell.textLabel?.text = account.userDisplayName
  747. cell.detailTextLabel?.text = accountServer
  748. cell.detailTextLabel?.textColor = .secondaryLabel
  749. cell.imageView?.image = nil
  750. if let accountImage = self.getProfilePicture(for: account) {
  751. cell.setSettingsImage(image: NCUtils.roundedImage(fromImage: accountImage), renderingMode: .alwaysOriginal)
  752. }
  753. cell.accessoryView = nil
  754. if account.unreadBadgeNumber > 0 {
  755. let badgeView = RoundedNumberView()
  756. badgeView.highlightType = .important
  757. badgeView.number = account.unreadBadgeNumber
  758. cell.accessoryView = badgeView
  759. }
  760. return cell
  761. }
  762. func sectionConfigurationCell(for indexPath: IndexPath) -> UITableViewCell {
  763. let videoConfigurationCellIdentifier = "VideoConfigurationCellIdentifier"
  764. let options = getConfigurationSectionOptions()
  765. let option = options[indexPath.row]
  766. var cell = UITableViewCell()
  767. switch option {
  768. case ConfigurationSectionOption.kConfigurationSectionOptionVideo.rawValue:
  769. cell = UITableViewCell(style: .default, reuseIdentifier: videoConfigurationCellIdentifier)
  770. cell.textLabel?.text = NSLocalizedString("Video quality", comment: "")
  771. cell.setSettingsImage(image: UIImage(systemName: "video")?.applyingSymbolConfiguration(iconConfiguration))
  772. let resolution = NCSettingsController.sharedInstance().videoSettingsModel.currentVideoResolutionSettingFromStore()
  773. let resolutionLabel = UILabel()
  774. resolutionLabel.text = NCSettingsController.sharedInstance().videoSettingsModel.readableResolution(resolution)
  775. resolutionLabel.textColor = .secondaryLabel
  776. resolutionLabel.sizeToFit()
  777. cell.accessoryView = resolutionLabel
  778. case ConfigurationSectionOption.kConfigurationSectionOptionRecents.rawValue:
  779. cell = UITableViewCell(style: .default, reuseIdentifier: videoConfigurationCellIdentifier)
  780. cell.textLabel?.text = NSLocalizedString("Include calls in call history", comment: "")
  781. cell.setSettingsImage(image: UIImage(systemName: "clock.arrow.circlepath")?.applyingSymbolConfiguration(iconConfiguration))
  782. cell.selectionStyle = .none
  783. cell.accessoryView = includeInRecentsSwitch
  784. includeInRecentsSwitch.isOn = NCUserDefaults.includeCallsInRecents()
  785. default:
  786. break
  787. }
  788. cell.textLabel?.numberOfLines = 0
  789. return cell
  790. }
  791. func advancedCell(for indexPath: IndexPath) -> UITableViewCell {
  792. let advancedCellDisclosureIdentifier = "AdvancedCellDisclosureIdentifier"
  793. let advancedCellValue1Identifier = "AdvancedCellType1Identifier"
  794. let options = getAdvancedSectionOptions()
  795. let option = options[indexPath.row]
  796. var cell = UITableViewCell()
  797. switch option {
  798. case AdvancedSectionOption.kAdvancedSectionOptionDiagnostics.rawValue:
  799. cell = UITableViewCell(style: .default, reuseIdentifier: advancedCellDisclosureIdentifier)
  800. cell.textLabel?.text = NSLocalizedString("Diagnostics", comment: "")
  801. cell.textLabel?.numberOfLines = 0
  802. cell.setSettingsImage(image: UIImage(systemName: "gear")?.applyingSymbolConfiguration(iconConfiguration))
  803. cell.accessoryType = .disclosureIndicator
  804. case AdvancedSectionOption.kAdvancedSectionOptionCallFromOldAccount.rawValue:
  805. cell = UITableViewCell(style: .default, reuseIdentifier: advancedCellDisclosureIdentifier)
  806. cell.textLabel?.text = NSLocalizedString("Calls from old accounts", comment: "")
  807. cell.textLabel?.numberOfLines = 0
  808. cell.setSettingsImage(image: UIImage(systemName: "exclamationmark.triangle.fill")?.applyingSymbolConfiguration(iconConfiguration))
  809. cell.accessoryType = .disclosureIndicator
  810. case AdvancedSectionOption.kAdvancedSectionOptionCachedImages.rawValue:
  811. let byteFormatter = ByteCountFormatter()
  812. byteFormatter.allowedUnits = [.useMB]
  813. byteFormatter.countStyle = .file
  814. cell = UITableViewCell(style: .default, reuseIdentifier: advancedCellValue1Identifier)
  815. cell.textLabel?.text = NSLocalizedString("Cached images", comment: "")
  816. cell.textLabel?.numberOfLines = 0
  817. cell.setSettingsImage(image: UIImage(systemName: "photo")?.applyingSymbolConfiguration(iconConfiguration))
  818. let byteCounterLabel = UILabel()
  819. byteCounterLabel.text = byteFormatter.string(fromByteCount: Int64(self.totalImageCacheSize))
  820. byteCounterLabel.textColor = .secondaryLabel
  821. byteCounterLabel.sizeToFit()
  822. cell.accessoryView = byteCounterLabel
  823. case AdvancedSectionOption.kAdvancedSectionOptionCachedFiles.rawValue:
  824. let byteFormatter = ByteCountFormatter()
  825. byteFormatter.allowedUnits = [.useMB]
  826. byteFormatter.countStyle = .file
  827. cell = UITableViewCell(style: .default, reuseIdentifier: advancedCellValue1Identifier)
  828. cell.textLabel?.text = NSLocalizedString("Cached files", comment: "")
  829. cell.textLabel?.numberOfLines = 0
  830. cell.setSettingsImage(image: UIImage(systemName: "doc")?.applyingSymbolConfiguration(iconConfiguration))
  831. let byteCounterLabel = UILabel()
  832. byteCounterLabel.text = byteFormatter.string(fromByteCount: Int64(self.totalFileCacheSize))
  833. byteCounterLabel.textColor = .secondaryLabel
  834. byteCounterLabel.sizeToFit()
  835. cell.accessoryView = byteCounterLabel
  836. default:
  837. break
  838. }
  839. return cell
  840. }
  841. func sectionAboutCell(for indexPath: IndexPath) -> UITableViewCell {
  842. let privacyCellIdentifier = "PrivacyCellIdentifier"
  843. let sourceCodeCellIdentifier = "SourceCodeCellIdentifier"
  844. let options = getAboutSectionOptions()
  845. let option = options[indexPath.row]
  846. var cell = UITableViewCell()
  847. switch option {
  848. case AboutSection.kAboutSectionPrivacy.rawValue:
  849. cell =
  850. UITableViewCell(style: .default, reuseIdentifier: privacyCellIdentifier)
  851. cell.textLabel?.text = NSLocalizedString("Privacy", comment: "")
  852. cell.setSettingsImage(image: UIImage(systemName: "lock.shield")?.applyingSymbolConfiguration(iconConfiguration))
  853. case AboutSection.kAboutSectionSourceCode.rawValue:
  854. cell =
  855. UITableViewCell(style: .default, reuseIdentifier: sourceCodeCellIdentifier)
  856. cell.textLabel?.text = NSLocalizedString("Get source code", comment: "")
  857. cell.setSettingsImage(image: UIImage(named: "github"))
  858. default:
  859. break
  860. }
  861. cell.textLabel?.numberOfLines = 0
  862. return cell
  863. }
  864. // UIImage should be optional because userProfileImage (objC) can return a nil value
  865. func getProfilePicture(for account: TalkAccount) -> UIImage? {
  866. if let avatar = self.profilePictures[account.accountId] {
  867. return avatar
  868. }
  869. return NCAPIController.sharedInstance().userProfileImage(for: account, with: self.traitCollection.userInterfaceStyle)
  870. }
  871. func updateTotalImageCacheSize() {
  872. let imageCacheSize = NCImageSessionManager.shared.cache.currentDiskUsage
  873. let sdImageCacheSize = SDImageCache.shared.totalDiskSize()
  874. self.totalImageCacheSize = imageCacheSize + Int(sdImageCacheSize)
  875. }
  876. func updateTotalFileCacheSize() {
  877. self.totalFileCacheSize = 0
  878. let fileController = NCChatFileController()
  879. let talkAccounts = NCDatabaseManager.sharedInstance().allAccounts()
  880. if let talkAccounts = talkAccounts as? [TalkAccount] {
  881. for account in talkAccounts {
  882. self.totalFileCacheSize += Int(fileController.getDiskUsage(for: account))
  883. }
  884. }
  885. }
  886. }