SettingsController.swift 29 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657
  1. import UIKit
  2. import DcCore
  3. import DBDebugToolkit
  4. import Intents
  5. internal final class SettingsViewController: UITableViewController, ProgressAlertHandler {
  6. private struct SectionConfigs {
  7. let headerTitle: String?
  8. let footerTitle: String?
  9. let cells: [UITableViewCell]
  10. }
  11. private enum CellTags: Int {
  12. case profile = 0
  13. case showArchive = 1
  14. case showEmails = 2
  15. case blockedContacts = 3
  16. case notifications = 4
  17. case receiptConfirmation = 5
  18. case autocryptPreferences = 6
  19. case sendAutocryptMessage = 7
  20. case exportBackup = 8
  21. case advanced = 9
  22. case help = 10
  23. case autodel = 11
  24. case mediaQuality = 12
  25. case downloadOnDemand = 13
  26. case videoChat = 14
  27. case connectivity = 15
  28. case selectBackground = 16
  29. }
  30. private var dcContext: DcContext
  31. internal let dcAccounts: DcAccounts
  32. private let externalPathDescr = "File Sharing/Delta Chat"
  33. let documentInteractionController = UIDocumentInteractionController()
  34. private var connectivityChangedObserver: NSObjectProtocol?
  35. // MARK: - ProgressAlertHandler
  36. weak var progressAlert: UIAlertController?
  37. var progressObserver: NSObjectProtocol?
  38. // MARK: - cells
  39. private lazy var profileCell: ContactCell = {
  40. let cell = ContactCell(style: .default, reuseIdentifier: nil)
  41. let cellViewModel = ProfileViewModel(context: dcContext)
  42. cell.updateCell(cellViewModel: cellViewModel)
  43. cell.tag = CellTags.profile.rawValue
  44. cell.accessoryType = .disclosureIndicator
  45. return cell
  46. }()
  47. private lazy var showArchiveCell: UITableViewCell = {
  48. let cell = UITableViewCell(style: .value1, reuseIdentifier: nil)
  49. cell.tag = CellTags.showArchive.rawValue
  50. cell.textLabel?.text = String.localized("chat_archived_chats_title")
  51. cell.accessoryType = .disclosureIndicator
  52. return cell
  53. }()
  54. private lazy var showEmailsCell: UITableViewCell = {
  55. let cell = UITableViewCell(style: .value1, reuseIdentifier: nil)
  56. cell.tag = CellTags.showEmails.rawValue
  57. cell.textLabel?.text = String.localized("pref_show_emails")
  58. cell.accessoryType = .disclosureIndicator
  59. cell.detailTextLabel?.text = SettingsClassicViewController.getValString(val: dcContext.showEmails)
  60. return cell
  61. }()
  62. private lazy var blockedContactsCell: UITableViewCell = {
  63. let cell = UITableViewCell(style: .default, reuseIdentifier: nil)
  64. cell.tag = CellTags.blockedContacts.rawValue
  65. cell.textLabel?.text = String.localized("pref_blocked_contacts")
  66. cell.accessoryType = .disclosureIndicator
  67. return cell
  68. }()
  69. func autodelSummary() -> String {
  70. let delDeviceAfter = dcContext.getConfigInt("delete_device_after")
  71. let delServerAfter = dcContext.getConfigInt("delete_server_after")
  72. if delDeviceAfter==0 && delServerAfter==0 {
  73. return String.localized("never")
  74. } else {
  75. return String.localized("on")
  76. }
  77. }
  78. private lazy var autodelCell: UITableViewCell = {
  79. let cell = UITableViewCell(style: .value1, reuseIdentifier: nil)
  80. cell.tag = CellTags.autodel.rawValue
  81. cell.textLabel?.text = String.localized("delete_old_messages")
  82. cell.accessoryType = .disclosureIndicator
  83. cell.detailTextLabel?.text = autodelSummary()
  84. return cell
  85. }()
  86. private lazy var mediaQualityCell: UITableViewCell = {
  87. let cell = UITableViewCell(style: .value1, reuseIdentifier: nil)
  88. cell.tag = CellTags.mediaQuality.rawValue
  89. cell.textLabel?.text = String.localized("pref_outgoing_media_quality")
  90. cell.accessoryType = .disclosureIndicator
  91. cell.detailTextLabel?.text = MediaQualityController.getValString(val: dcContext.getConfigInt("media_quality"))
  92. return cell
  93. }()
  94. private lazy var downloadOnDemandCell: UITableViewCell = {
  95. let cell = UITableViewCell(style: .value1, reuseIdentifier: nil)
  96. cell.tag = CellTags.downloadOnDemand.rawValue
  97. cell.textLabel?.text = String.localized("auto_download_messages")
  98. cell.accessoryType = .disclosureIndicator
  99. cell.detailTextLabel?.text = DownloadOnDemandViewController.getValString(val: dcContext.getConfigInt("download_limit"))
  100. return cell
  101. }()
  102. private lazy var videoChatInstanceCell: UITableViewCell = {
  103. let cell = UITableViewCell(style: .value1, reuseIdentifier: nil)
  104. cell.tag = CellTags.videoChat.rawValue
  105. cell.textLabel?.text = String.localized("videochat_instance")
  106. cell.accessoryType = .disclosureIndicator
  107. return cell
  108. }()
  109. private lazy var notificationSwitch: UISwitch = {
  110. let switchControl = UISwitch()
  111. switchControl.isOn = !UserDefaults.standard.bool(forKey: "notifications_disabled")
  112. switchControl.addTarget(self, action: #selector(handleNotificationToggle(_:)), for: .valueChanged)
  113. return switchControl
  114. }()
  115. private lazy var notificationCell: UITableViewCell = {
  116. let cell = UITableViewCell(style: .default, reuseIdentifier: nil)
  117. cell.tag = CellTags.notifications.rawValue
  118. cell.textLabel?.text = String.localized("pref_notifications")
  119. cell.accessoryView = notificationSwitch
  120. cell.selectionStyle = .none
  121. return cell
  122. }()
  123. private lazy var receiptConfirmationSwitch: UISwitch = {
  124. let switchControl = UISwitch()
  125. switchControl.isOn = dcContext.mdnsEnabled
  126. switchControl.addTarget(self, action: #selector(handleReceiptConfirmationToggle(_:)), for: .valueChanged)
  127. return switchControl
  128. }()
  129. private lazy var receiptConfirmationCell: UITableViewCell = {
  130. let cell = UITableViewCell(style: .default, reuseIdentifier: nil)
  131. cell.tag = CellTags.receiptConfirmation.rawValue
  132. cell.textLabel?.text = String.localized("pref_read_receipts")
  133. cell.accessoryView = receiptConfirmationSwitch
  134. cell.selectionStyle = .none
  135. return cell
  136. }()
  137. private lazy var autocryptSwitch: UISwitch = {
  138. let switchControl = UISwitch()
  139. switchControl.isOn = dcContext.e2eeEnabled
  140. switchControl.addTarget(self, action: #selector(handleAutocryptPreferencesToggle(_:)), for: .valueChanged)
  141. return switchControl
  142. }()
  143. private lazy var autocryptPreferencesCell: UITableViewCell = {
  144. let cell = UITableViewCell(style: .default, reuseIdentifier: nil)
  145. cell.tag = CellTags.autocryptPreferences.rawValue
  146. cell.textLabel?.text = String.localized("autocrypt_prefer_e2ee")
  147. cell.accessoryView = autocryptSwitch
  148. cell.selectionStyle = .none
  149. return cell
  150. }()
  151. private lazy var sendAutocryptMessageCell: ActionCell = {
  152. let cell = ActionCell()
  153. cell.tag = CellTags.sendAutocryptMessage.rawValue
  154. cell.actionTitle = String.localized("autocrypt_send_asm_title")
  155. return cell
  156. }()
  157. private lazy var exportBackupCell: ActionCell = {
  158. let cell = ActionCell()
  159. cell.tag = CellTags.exportBackup.rawValue
  160. cell.actionTitle = String.localized("export_backup_desktop")
  161. return cell
  162. }()
  163. private lazy var advancedCell: ActionCell = {
  164. let cell = ActionCell()
  165. cell.tag = CellTags.advanced.rawValue
  166. cell.actionTitle = String.localized("menu_advanced")
  167. return cell
  168. }()
  169. private lazy var helpCell: ActionCell = {
  170. let cell = ActionCell()
  171. cell.tag = CellTags.help.rawValue
  172. cell.actionTitle = String.localized("menu_help")
  173. return cell
  174. }()
  175. private lazy var connectivityCell: UITableViewCell = {
  176. let cell = UITableViewCell(style: .value1, reuseIdentifier: nil)
  177. cell.tag = CellTags.connectivity.rawValue
  178. cell.textLabel?.text = String.localized("connectivity")
  179. cell.accessoryType = .disclosureIndicator
  180. return cell
  181. }()
  182. private lazy var selectBackgroundCell: UITableViewCell = {
  183. let cell = UITableViewCell()
  184. cell.tag = CellTags.selectBackground.rawValue
  185. cell.textLabel?.text = String.localized("pref_background")
  186. cell.accessoryType = .disclosureIndicator
  187. return cell
  188. }()
  189. private lazy var sections: [SectionConfigs] = {
  190. var appNameAndVersion = "Delta Chat"
  191. if let appVersion = Bundle.main.infoDictionary?["CFBundleShortVersionString"] as? String {
  192. appNameAndVersion += " v" + appVersion
  193. }
  194. let profileSection = SectionConfigs(
  195. headerTitle: String.localized("pref_profile_info_headline"),
  196. footerTitle: nil,
  197. cells: [profileCell]
  198. )
  199. let preferencesSection = SectionConfigs(
  200. headerTitle: String.localized("pref_chats_and_media"),
  201. footerTitle: String.localized("pref_read_receipts_explain"),
  202. cells: [showArchiveCell, showEmailsCell, blockedContactsCell, mediaQualityCell, downloadOnDemandCell,
  203. autodelCell, videoChatInstanceCell, notificationCell, receiptConfirmationCell]
  204. )
  205. let appearanceSection = SectionConfigs(
  206. headerTitle: String.localized("pref_appearance"),
  207. footerTitle: nil,
  208. cells: [selectBackgroundCell]
  209. )
  210. let autocryptSection = SectionConfigs(
  211. headerTitle: String.localized("autocrypt"),
  212. footerTitle: String.localized("autocrypt_explain"),
  213. cells: [autocryptPreferencesCell, sendAutocryptMessageCell]
  214. )
  215. let backupSection = SectionConfigs(
  216. headerTitle: nil,
  217. footerTitle: String.localized("pref_backup_explain"),
  218. cells: [advancedCell, exportBackupCell])
  219. let helpSection = SectionConfigs(
  220. headerTitle: nil,
  221. footerTitle: appNameAndVersion,
  222. cells: [connectivityCell, helpCell]
  223. )
  224. return [profileSection, preferencesSection, appearanceSection, autocryptSection, backupSection, helpSection]
  225. }()
  226. init(dcAccounts: DcAccounts) {
  227. self.dcContext = dcAccounts.getSelected()
  228. self.dcAccounts = dcAccounts
  229. super.init(style: .grouped)
  230. }
  231. required init?(coder _: NSCoder) {
  232. fatalError("init(coder:) has not been implemented")
  233. }
  234. // MARK: - lifecycle
  235. override func viewDidLoad() {
  236. super.viewDidLoad()
  237. title = String.localized("menu_settings")
  238. documentInteractionController.delegate = self as? UIDocumentInteractionControllerDelegate
  239. tableView.rowHeight = UITableView.automaticDimension
  240. }
  241. override func viewWillAppear(_ animated: Bool) {
  242. super.viewWillAppear(animated)
  243. // set connectivity changed observer before we acutally init `connectivityCell.detailTextLabel` in `updateCells()`,
  244. // otherwise, we may miss events and the label is not correct.
  245. connectivityChangedObserver = NotificationCenter.default.addObserver(forName: dcNotificationConnectivityChanged,
  246. object: nil,
  247. queue: nil) { [weak self] _ in
  248. guard let self = self else { return }
  249. self.connectivityCell.detailTextLabel?.text = DcUtils.getConnectivityString(dcContext: self.dcContext,
  250. connectedString: String.localized("connectivity_connected"))
  251. }
  252. updateCells()
  253. }
  254. override func viewDidAppear(_ animated: Bool) {
  255. super.viewDidAppear(animated)
  256. addProgressAlertListener(dcAccounts: dcAccounts, progressName: dcNotificationImexProgress) { [weak self] in
  257. guard let self = self else { return }
  258. self.progressAlert?.dismiss(animated: true) {
  259. if let appDelegate = UIApplication.shared.delegate as? AppDelegate {
  260. appDelegate.reloadDcContext()
  261. }
  262. }
  263. }
  264. }
  265. override func viewDidDisappear(_ animated: Bool) {
  266. super.viewDidDisappear(animated)
  267. let nc = NotificationCenter.default
  268. if let backupProgressObserver = self.progressObserver {
  269. nc.removeObserver(backupProgressObserver)
  270. }
  271. if let connectivityChangedObserver = self.connectivityChangedObserver {
  272. NotificationCenter.default.removeObserver(connectivityChangedObserver)
  273. }
  274. }
  275. // MARK: - UITableViewDelegate + UITableViewDatasource
  276. override func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
  277. if indexPath.section == 0 && indexPath.row == 0 {
  278. return ContactCell.cellHeight
  279. } else {
  280. return UITableView.automaticDimension
  281. }
  282. }
  283. override func numberOfSections(in tableView: UITableView) -> Int {
  284. return sections.count
  285. }
  286. override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
  287. return sections[section].cells.count
  288. }
  289. override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
  290. return sections[indexPath.section].cells[indexPath.row]
  291. }
  292. override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
  293. guard let cell = tableView.cellForRow(at: indexPath), let cellTag = CellTags(rawValue: cell.tag) else {
  294. safe_fatalError()
  295. return
  296. }
  297. tableView.deselectRow(at: indexPath, animated: false)
  298. switch cellTag {
  299. case .profile: showEditSettingsController()
  300. case .showArchive: showArchivedCharts()
  301. case .showEmails: showClassicMail()
  302. case .blockedContacts: showBlockedContacts()
  303. case .autodel: showAutodelOptions()
  304. case .mediaQuality: showMediaQuality()
  305. case .downloadOnDemand: showDownloadOnDemand()
  306. case .videoChat: showVideoChatInstance()
  307. case .notifications: break
  308. case .receiptConfirmation: break
  309. case .autocryptPreferences: break
  310. case .sendAutocryptMessage: sendAutocryptSetupMessage()
  311. case .exportBackup: createBackup()
  312. case .advanced: showAdvancedDialog()
  313. case .help: showHelp()
  314. case .connectivity: showConnectivity()
  315. case .selectBackground: selectBackground()
  316. }
  317. }
  318. override func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
  319. return sections[section].headerTitle
  320. }
  321. override func tableView(_ tableView: UITableView, titleForFooterInSection section: Int) -> String? {
  322. return sections[section].footerTitle
  323. }
  324. // MARK: - actions
  325. private func createBackup() {
  326. let alert = UIAlertController(title: String.localized("pref_backup_export_explain"), message: nil, preferredStyle: .safeActionSheet)
  327. alert.addAction(UIAlertAction(title: String.localized("pref_backup_export_start_button"), style: .default, handler: { _ in
  328. self.dismiss(animated: true, completion: nil)
  329. self.startImex(what: DC_IMEX_EXPORT_BACKUP)
  330. }))
  331. alert.addAction(UIAlertAction(title: String.localized("cancel"), style: .cancel, handler: nil))
  332. present(alert, animated: true, completion: nil)
  333. }
  334. @objc private func handleNotificationToggle(_ sender: UISwitch) {
  335. UserDefaults.standard.set(!sender.isOn, forKey: "notifications_disabled")
  336. if sender.isOn {
  337. if let appDelegate = UIApplication.shared.delegate as? AppDelegate {
  338. appDelegate.registerForNotifications()
  339. }
  340. } else {
  341. NotificationManager.removeAllNotifications()
  342. }
  343. UserDefaults.standard.synchronize()
  344. NotificationManager.updateApplicationIconBadge(dcContext: dcContext, reset: !sender.isOn)
  345. }
  346. @objc private func handleReceiptConfirmationToggle(_ sender: UISwitch) {
  347. dcContext.mdnsEnabled = sender.isOn
  348. }
  349. @objc private func handleAutocryptPreferencesToggle(_ sender: UISwitch) {
  350. dcContext.e2eeEnabled = sender.isOn
  351. }
  352. private func sendAutocryptSetupMessage() {
  353. let askAlert = UIAlertController(title: String.localized("autocrypt_send_asm_explain_before"), message: nil, preferredStyle: .safeActionSheet)
  354. askAlert.addAction(UIAlertAction(title: String.localized("autocrypt_send_asm_title"), style: .default, handler: { _ in
  355. let sc = self.dcContext.initiateKeyTransfer()
  356. guard var sc = sc else {
  357. return
  358. }
  359. if sc.count == 44 {
  360. // format setup code to the typical 3 x 3 numbers
  361. sc = sc.substring(0, 4) + " - " + sc.substring(5, 9) + " - " + sc.substring(10, 14) + " -\n\n" +
  362. sc.substring(15, 19) + " - " + sc.substring(20, 24) + " - " + sc.substring(25, 29) + " -\n\n" +
  363. sc.substring(30, 34) + " - " + sc.substring(35, 39) + " - " + sc.substring(40, 44)
  364. }
  365. let text = String.localizedStringWithFormat(String.localized("autocrypt_send_asm_explain_after"), sc)
  366. let showAlert = UIAlertController(title: text, message: nil, preferredStyle: .alert)
  367. showAlert.addAction(UIAlertAction(title: String.localized("ok"), style: .default, handler: nil))
  368. self.present(showAlert, animated: true, completion: nil)
  369. }))
  370. askAlert.addAction(UIAlertAction(title: String.localized("cancel"), style: .cancel, handler: nil))
  371. present(askAlert, animated: true, completion: nil)
  372. }
  373. private func showExperimentalDialog() {
  374. let alert = UIAlertController(title: String.localized("pref_experimental_features"), message: nil, preferredStyle: .safeActionSheet)
  375. let broadcastLists = UserDefaults.standard.bool(forKey: "broadcast_lists")
  376. alert.addAction(UIAlertAction(title: (broadcastLists ? "✔︎ " : "") + String.localized("broadcast_lists"),
  377. style: .default, handler: { [weak self] _ in
  378. guard let self = self else { return }
  379. UserDefaults.standard.set(!broadcastLists, forKey: "broadcast_lists")
  380. if !broadcastLists {
  381. let alert = UIAlertController(title: "Thanks for trying out the experimental feature 🧪 \"Broadcast Lists\"!",
  382. message: "You can now create new \"Broadcast Lists\" from the \"New Chat\" dialog\n\n"
  383. + "In case you are using more than one device, broadcast lists are currently not synced between them\n\n"
  384. + "If you want to quit the experimental feature, you can disable it at \"Settings / Advanced\".",
  385. preferredStyle: .alert)
  386. alert.addAction(UIAlertAction(title: String.localized("ok"), style: .default, handler: nil))
  387. self.navigationController?.present(alert, animated: true, completion: nil)
  388. }
  389. }))
  390. let locationStreaming = UserDefaults.standard.bool(forKey: "location_streaming")
  391. let title = (locationStreaming ? "✔︎ " : "") + String.localized("pref_on_demand_location_streaming")
  392. alert.addAction(UIAlertAction(title: title, style: .default, handler: { [weak self] _ in
  393. guard let self = self else { return }
  394. UserDefaults.standard.set(!locationStreaming, forKey: "location_streaming")
  395. if !locationStreaming {
  396. let alert = UIAlertController(title: "Thanks for trying out the experimental feature 🧪 \"Location streaming\"",
  397. message: "You will find a corresponding option in the attach menu (the paper clip) of each chat now.\n\n"
  398. + "If you want to quit the experimental feature, you can disable it at \"Settings / Advanced\".",
  399. preferredStyle: .alert)
  400. alert.addAction(UIAlertAction(title: String.localized("ok"), style: .default, handler: nil))
  401. self.navigationController?.present(alert, animated: true, completion: nil)
  402. } else if self.dcContext.isSendingLocationsToChat(chatId: 0) {
  403. guard let appDelegate = UIApplication.shared.delegate as? AppDelegate else {
  404. return
  405. }
  406. appDelegate.locationManager.disableLocationStreamingInAllChats()
  407. }
  408. }))
  409. alert.addAction(UIAlertAction(title: String.localized("cancel"), style: .cancel, handler: nil))
  410. present(alert, animated: true, completion: nil)
  411. }
  412. private func showAdvancedDialog() {
  413. let alert = UIAlertController(title: String.localized("menu_advanced"), message: nil, preferredStyle: .safeActionSheet)
  414. alert.addAction(UIAlertAction(title: String.localized("pref_managekeys_export_secret_keys"), style: .default, handler: { _ in
  415. let msg = String.localizedStringWithFormat(String.localized("pref_managekeys_export_explain"), self.externalPathDescr)
  416. let alert = UIAlertController(title: nil, message: msg, preferredStyle: .alert)
  417. alert.addAction(UIAlertAction(title: String.localized("ok"), style: .default, handler: { _ in
  418. self.startImex(what: DC_IMEX_EXPORT_SELF_KEYS)
  419. }))
  420. alert.addAction(UIAlertAction(title: String.localized("cancel"), style: .cancel, handler: nil))
  421. self.present(alert, animated: true, completion: nil)
  422. }))
  423. alert.addAction(UIAlertAction(title: String.localized("pref_managekeys_import_secret_keys"), style: .default, handler: { _ in
  424. let msg = String.localizedStringWithFormat(String.localized("pref_managekeys_import_explain"), self.externalPathDescr)
  425. let alert = UIAlertController(title: nil, message: msg, preferredStyle: .alert)
  426. alert.addAction(UIAlertAction(title: String.localized("ok"), style: .default, handler: { _ in
  427. self.startImex(what: DC_IMEX_IMPORT_SELF_KEYS)
  428. }))
  429. alert.addAction(UIAlertAction(title: String.localized("cancel"), style: .cancel, handler: nil))
  430. self.present(alert, animated: true, completion: nil)
  431. }))
  432. alert.addAction(UIAlertAction(title: String.localized("pref_experimental_features"), style: .default, handler: { [weak self] _ in
  433. self?.showExperimentalDialog()
  434. }))
  435. let logAction = UIAlertAction(title: String.localized("pref_view_log"), style: .default, handler: { [weak self] _ in
  436. guard let self = self else { return }
  437. SettingsViewController.showDebugToolkit(dcContext: self.dcContext)
  438. })
  439. alert.addAction(logAction)
  440. alert.addAction(UIAlertAction(title: String.localized("cancel"), style: .cancel, handler: nil))
  441. present(alert, animated: true, completion: nil)
  442. }
  443. private func presentError(message: String) {
  444. let error = UIAlertController(title: nil, message: message, preferredStyle: .alert)
  445. error.addAction(UIAlertAction(title: String.localized("ok"), style: .cancel))
  446. present(error, animated: true)
  447. }
  448. private func startImex(what: Int32, passphrase: String? = nil) {
  449. let documents = NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true)
  450. if !documents.isEmpty {
  451. showProgressAlert(title: String.localized("imex_progress_title_desktop"), dcContext: dcContext)
  452. DispatchQueue.main.async {
  453. self.dcAccounts.stopIo()
  454. self.dcContext.imex(what: what, directory: documents[0], passphrase: passphrase)
  455. }
  456. } else {
  457. logger.error("document directory not found")
  458. }
  459. }
  460. // MARK: - updates
  461. private func updateCells() {
  462. profileCell.updateCell(cellViewModel: ProfileViewModel(context: dcContext))
  463. showEmailsCell.detailTextLabel?.text = SettingsClassicViewController.getValString(val: dcContext.showEmails)
  464. mediaQualityCell.detailTextLabel?.text = MediaQualityController.getValString(val: dcContext.getConfigInt("media_quality"))
  465. downloadOnDemandCell.detailTextLabel?.text = DownloadOnDemandViewController.getValString(
  466. val: dcContext.getConfigInt("download_limit"))
  467. videoChatInstanceCell.detailTextLabel?.text = SettingsVideoChatViewController.getValString(val: dcContext.getConfig("webrtc_instance") ?? "")
  468. autodelCell.detailTextLabel?.text = autodelSummary()
  469. connectivityCell.detailTextLabel?.text = DcUtils.getConnectivityString(dcContext: dcContext,
  470. connectedString: String.localized("connectivity_connected"))
  471. }
  472. // MARK: - coordinator
  473. private func showEditSettingsController() {
  474. let editController = EditSettingsController(dcAccounts: dcAccounts)
  475. navigationController?.pushViewController(editController, animated: true)
  476. }
  477. private func showClassicMail() {
  478. let settingsClassicViewController = SettingsClassicViewController(dcContext: dcContext)
  479. navigationController?.pushViewController(settingsClassicViewController, animated: true)
  480. }
  481. private func showMediaQuality() {
  482. let mediaQualityController = MediaQualityController(dcContext: dcContext)
  483. navigationController?.pushViewController(mediaQualityController, animated: true)
  484. }
  485. private func showDownloadOnDemand() {
  486. let downloadOnDemandViewController = DownloadOnDemandViewController(dcContext: dcContext)
  487. navigationController?.pushViewController(downloadOnDemandViewController, animated: true)
  488. }
  489. private func showVideoChatInstance() {
  490. let videoInstanceController = SettingsVideoChatViewController(dcContext: dcContext)
  491. navigationController?.pushViewController(videoInstanceController, animated: true)
  492. }
  493. private func showArchivedCharts() {
  494. guard let appDelegate = UIApplication.shared.delegate as? AppDelegate else { return }
  495. appDelegate.appCoordinator.showArchivedChats()
  496. }
  497. private func showBlockedContacts() {
  498. let blockedContactsController = BlockedContactsViewController(dcContext: dcContext)
  499. navigationController?.pushViewController(blockedContactsController, animated: true)
  500. }
  501. private func showAutodelOptions() {
  502. let settingsAutodelOverviewController = SettingsAutodelOverviewController(dcContext: dcContext)
  503. navigationController?.pushViewController(settingsAutodelOverviewController, animated: true)
  504. }
  505. private func showHelp() {
  506. navigationController?.pushViewController(HelpViewController(dcContext: dcContext), animated: true)
  507. }
  508. private func showConnectivity() {
  509. navigationController?.pushViewController(ConnectivityViewController(dcContext: dcContext), animated: true)
  510. }
  511. private func selectBackground() {
  512. navigationController?.pushViewController(SettingsBackgroundSelectionController(dcContext: dcContext), animated: true)
  513. }
  514. public static func showDebugToolkit(dcContext: DcContext) {
  515. var info = ""
  516. let systemVersion = UIDevice.current.systemVersion
  517. info += "iosVersion=\(systemVersion)\n"
  518. let notifyEnabled = !UserDefaults.standard.bool(forKey: "notifications_disabled")
  519. info += "notify-enabled=\(notifyEnabled)\n"
  520. if let appDelegate = UIApplication.shared.delegate as? AppDelegate {
  521. info += "notify-token=\(appDelegate.notifyToken ?? "<unset>")\n"
  522. }
  523. for name in ["notify-remote-launch", "notify-remote-receive", "notify-local-wakeup"] {
  524. let cnt = UserDefaults.standard.integer(forKey: name + "-count")
  525. let startDbl = UserDefaults.standard.double(forKey: name + "-start")
  526. let startStr = startDbl==0.0 ? "" : " since " + DateUtils.getExtendedRelativeTimeSpanString(timeStamp: startDbl)
  527. let timestampDbl = UserDefaults.standard.double(forKey: name + "-last")
  528. let timestampStr = timestampDbl==0.0 ? "" : ", last " + DateUtils.getExtendedRelativeTimeSpanString(timeStamp: timestampDbl)
  529. info += "\(name)=\(cnt)x\(startStr)\(timestampStr)\n"
  530. }
  531. info += "notify-timestamps="
  532. if let timestamps = UserDefaults.standard.array(forKey: Constants.Keys.notificationTimestamps) as? [Double] {
  533. for currTimestamp in timestamps {
  534. info += DateUtils.getExtendedAbsTimeSpanString(timeStamp: currTimestamp) + " "
  535. }
  536. }
  537. info += "\n"
  538. info += "notify-fetch-info2="
  539. if let infos = UserDefaults.standard.array(forKey: "notify-fetch-info2") as? [String] {
  540. for currInfo in infos {
  541. info += currInfo
  542. .replacingOccurrences(of: "📡", with: "\n📡")
  543. .replacingOccurrences(of: "🏠", with: "\n🏠") + " "
  544. }
  545. }
  546. info += "\n"
  547. var val = "?"
  548. switch UIApplication.shared.backgroundRefreshStatus {
  549. case .restricted: val = "restricted"
  550. case .available: val = "available"
  551. case .denied: val = "denied"
  552. }
  553. info += "backgroundRefreshStatus=\(val)\n"
  554. #if DEBUG
  555. info += "DEBUG=1\n"
  556. #else
  557. info += "DEBUG=0\n"
  558. #endif
  559. info += "\n" + dcContext.getInfo()
  560. DBDebugToolkit.add(DBCustomVariable(name: "", value: info))
  561. DBDebugToolkit.showMenu()
  562. }
  563. }