SettingsController.swift 29 KB

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