SettingsController.swift 34 KB

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