SettingsController.swift 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353
  1. import JGProgressHUD
  2. import QuickTableViewController
  3. import UIKit
  4. internal final class SettingsViewController: QuickTableViewController {
  5. weak var coordinator: SettingsCoordinator?
  6. private let sectionProfileInfo = 0
  7. private let rowProfile = 0
  8. private var dcContext: DcContext
  9. let documentInteractionController = UIDocumentInteractionController()
  10. var backupProgressObserver: Any?
  11. var configureProgressObserver: Any?
  12. private lazy var hudHandler: HudHandler = {
  13. let hudHandler = HudHandler(parentView: self.view)
  14. return hudHandler
  15. }()
  16. init(dcContext: DcContext) {
  17. self.dcContext = dcContext
  18. super.init(nibName: nil, bundle: nil)
  19. }
  20. required init?(coder _: NSCoder) {
  21. fatalError("init(coder:) has not been implemented")
  22. }
  23. override func viewDidLoad() {
  24. super.viewDidLoad()
  25. title = String.localized("menu_settings")
  26. let backButton = UIBarButtonItem(title: String.localized("menu_settings"), style: .plain, target: nil, action: nil)
  27. navigationItem.backBarButtonItem = backButton
  28. documentInteractionController.delegate = self as? UIDocumentInteractionControllerDelegate
  29. }
  30. override func viewDidAppear(_ animated: Bool) {
  31. super.viewDidAppear(animated)
  32. let nc = NotificationCenter.default
  33. backupProgressObserver = nc.addObserver(
  34. forName: dcNotificationImexProgress,
  35. object: nil,
  36. queue: nil
  37. ) { notification in
  38. if let ui = notification.userInfo {
  39. if ui["error"] as? Bool ?? false {
  40. self.hudHandler.setHudError(ui["errorMessage"] as? String)
  41. } else if ui["done"] as? Bool ?? false {
  42. self.hudHandler.setHudDone(callback: nil)
  43. } else {
  44. self.hudHandler.setHudProgress(ui["progress"] as? Int ?? 0)
  45. }
  46. }
  47. }
  48. configureProgressObserver = nc.addObserver(
  49. forName: dcNotificationConfigureProgress,
  50. object: nil,
  51. queue: nil
  52. ) { notification in
  53. if let ui = notification.userInfo {
  54. if ui["error"] as? Bool ?? false {
  55. self.hudHandler.setHudError(ui["errorMessage"] as? String)
  56. } else if ui["done"] as? Bool ?? false {
  57. self.hudHandler.setHudDone(callback: nil)
  58. } else {
  59. self.hudHandler.setHudProgress(ui["progress"] as? Int ?? 0)
  60. }
  61. }
  62. }
  63. }
  64. override func viewWillAppear(_ animated: Bool) {
  65. super.viewWillAppear(animated)
  66. setTable()
  67. }
  68. override func viewDidDisappear(_ animated: Bool) {
  69. super.viewDidDisappear(animated)
  70. let nc = NotificationCenter.default
  71. if let backupProgressObserver = self.backupProgressObserver {
  72. nc.removeObserver(backupProgressObserver)
  73. }
  74. if let configureProgressObserver = self.configureProgressObserver {
  75. nc.removeObserver(configureProgressObserver)
  76. }
  77. }
  78. override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
  79. if indexPath.section == sectionProfileInfo && indexPath.row == rowProfile {
  80. return customProfileCell(tableView, indexPath: indexPath)
  81. } else {
  82. return super.tableView(tableView, cellForRowAt: indexPath)
  83. }
  84. }
  85. private func customProfileCell(_ tableView: UITableView, indexPath: IndexPath) -> UITableViewCell {
  86. let cell = super.tableView(tableView, cellForRowAt: indexPath)
  87. cell.contentView.subviews.forEach({ $0.removeFromSuperview() })
  88. let badge = createProfileBadge()
  89. let nameLabel = createNameLabel()
  90. let signatureLabel = createSubtitle()
  91. cell.contentView.addSubview(badge)
  92. cell.contentView.addSubview(nameLabel)
  93. cell.contentView.addSubview(signatureLabel)
  94. let badgeConstraints = [badge.constraintAlignLeadingTo(cell.contentView, paddingLeading: 16),
  95. badge.constraintCenterYTo(cell.contentView),
  96. badge.constraintAlignTopTo(cell.contentView, paddingTop: 8),
  97. badge.constraintAlignBottomTo(cell.contentView, paddingBottom: 8)]
  98. let textViewConstraints = [nameLabel.constraintToTrailingOf(badge, paddingLeading: 12),
  99. nameLabel.constraintAlignTrailingTo(cell.contentView, paddingTrailing: 16),
  100. nameLabel.constraintAlignTopTo(cell.contentView, paddingTop: 14)]
  101. let subtitleViewConstraints = [signatureLabel.constraintToTrailingOf(badge, paddingLeading: 12),
  102. signatureLabel.constraintAlignTrailingTo(cell.contentView, paddingTrailing: 16),
  103. signatureLabel.constraintToBottomOf(nameLabel, paddingTop: 0),
  104. signatureLabel.constraintAlignBottomTo(cell.contentView, paddingBottom: 12)]
  105. cell.contentView.addConstraints(badgeConstraints)
  106. cell.contentView.addConstraints(textViewConstraints)
  107. cell.contentView.addConstraints(subtitleViewConstraints)
  108. return cell
  109. }
  110. private func createProfileBadge() -> InitialsBadge {
  111. let selfContact = DcContact(id: Int(DC_CONTACT_ID_SELF))
  112. let badgeSize: CGFloat = 48
  113. if let image = selfContact.profileImage {
  114. return InitialsBadge(image: image, size: badgeSize)
  115. } else {
  116. return InitialsBadge(name: DcConfig.displayname ?? selfContact.email, color: selfContact.color, size: badgeSize)
  117. }
  118. }
  119. private func createNameLabel() -> UILabel {
  120. let nameLabel = UILabel.init()
  121. nameLabel.translatesAutoresizingMaskIntoConstraints = false
  122. nameLabel.text = DcConfig.displayname ?? String.localized("pref_your_name")
  123. return nameLabel
  124. }
  125. private func createSubtitle() -> UILabel {
  126. let subtitle = (DcConfig.addr ?? "")
  127. let subtitleView = UILabel.init()
  128. subtitleView.translatesAutoresizingMaskIntoConstraints = false
  129. subtitleView.text = subtitle
  130. subtitleView.font = UIFont.systemFont(ofSize: 13)
  131. subtitleView.lineBreakMode = .byTruncatingTail
  132. return subtitleView
  133. }
  134. private func setTable() {
  135. var appNameAndVersion = "Delta Chat"
  136. if let appVersion = Bundle.main.infoDictionary?["CFBundleShortVersionString"] as? String {
  137. appNameAndVersion += " v" + appVersion
  138. }
  139. tableContents = [
  140. Section(
  141. title: String.localized("pref_profile_info_headline"),
  142. rows: [
  143. //The profile row has a custom view and is set in
  144. //tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath)
  145. NavigationRow(text: "",
  146. detailText: .none,
  147. action: { _ in
  148. self.coordinator?.showEditSettingsController()
  149. }),
  150. ]
  151. ),
  152. Section(
  153. title: String.localized("pref_communication"),
  154. rows: [
  155. NavigationRow(text: String.localized("menu_deaddrop"),
  156. detailText: .none,
  157. action: { [weak self] in self?.showDeaddrop($0) }),
  158. NavigationRow(text: String.localized("pref_show_emails"),
  159. detailText: .value1(SettingsClassicViewController.getValString(val: DcConfig.showEmails)),
  160. action: { [weak self] in self?.showClassicMail($0) }),
  161. NavigationRow(text: String.localized("pref_blocked_contacts"),
  162. detailText: .none,
  163. action: { [weak self] in self?.showBlockedContacts($0) }),
  164. SwitchRow(text: String.localized("pref_read_receipts"),
  165. switchValue: DcConfig.mdnsEnabled,
  166. action: { row in
  167. if let row = row as? SwitchRow {
  168. DcConfig.mdnsEnabled = row.switchValue
  169. }
  170. }),
  171. ],
  172. footer: String.localized("pref_read_receipts_explain")
  173. ),
  174. Section(
  175. title: String.localized("pref_imap_folder_handling"),
  176. rows: [
  177. SwitchRow(text: String.localized("pref_watch_inbox_folder"),
  178. switchValue: DcConfig.inboxWatch,
  179. action: { row in
  180. if let row = row as? SwitchRow {
  181. DcConfig.inboxWatch = row.switchValue
  182. }
  183. }),
  184. SwitchRow(text: String.localized("pref_watch_sent_folder"),
  185. switchValue: DcConfig.sentboxWatch,
  186. action: { row in
  187. if let row = row as? SwitchRow {
  188. DcConfig.sentboxWatch = row.switchValue
  189. }
  190. }),
  191. SwitchRow(text: String.localized("pref_watch_mvbox_folder"),
  192. switchValue: DcConfig.mvboxWatch,
  193. action: { row in
  194. if let row = row as? SwitchRow {
  195. DcConfig.mvboxWatch = row.switchValue
  196. }
  197. }),
  198. SwitchRow(text: String.localized("pref_send_copy_to_self"),
  199. switchValue: dcContext.getConfigBool("bcc_self"),
  200. action: { row in
  201. if let row = row as? SwitchRow {
  202. self.dcContext.setConfigBool("bcc_self", row.switchValue)
  203. }
  204. }),
  205. SwitchRow(text: String.localized("pref_auto_folder_moves"),
  206. switchValue: DcConfig.mvboxMove,
  207. action: { row in
  208. if let row = row as? SwitchRow {
  209. DcConfig.mvboxMove = row.switchValue
  210. }
  211. }),
  212. ],
  213. footer: String.localized("pref_auto_folder_moves_explain")
  214. ),
  215. Section(
  216. title: String.localized("autocrypt"),
  217. rows: [
  218. SwitchRow(text: String.localized("autocrypt_prefer_e2ee"),
  219. switchValue: DcConfig.e2eeEnabled,
  220. action: { row in
  221. if let row = row as? SwitchRow {
  222. DcConfig.e2eeEnabled = row.switchValue
  223. }
  224. }),
  225. TapActionRow(text: String.localized("autocrypt_send_asm_title"), action: { [weak self] in self?.sendAsm($0) }),
  226. ],
  227. footer: String.localized("autocrypt_explain")
  228. ),
  229. Section(
  230. title: String.localized("pref_backup"),
  231. rows: [
  232. TapActionRow(text: String.localized("export_backup_desktop"), action: { [weak self] in self?.createBackup($0) }),
  233. ],
  234. footer: String.localized("pref_backup_explain")
  235. ),
  236. Section(
  237. title: nil,
  238. rows: [
  239. TapActionRow(text: String.localized("menu_help"), action: { [weak self] in self?.openHelp($0) }),
  240. ],
  241. footer: appNameAndVersion
  242. ),
  243. ]
  244. }
  245. private func createBackup(_: Row) {
  246. let alert = UIAlertController(title: String.localized("pref_backup_export_explain"), message: nil, preferredStyle: .actionSheet)
  247. alert.addAction(UIAlertAction(title: String.localized("pref_backup_export_start_button"), style: .default, handler: { _ in
  248. self.dismiss(animated: true, completion: nil)
  249. let documents = NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true)
  250. if !documents.isEmpty {
  251. logger.info("create backup in \(documents)")
  252. self.hudHandler.showHud(String.localized("one_moment"))
  253. DispatchQueue.main.async {
  254. dc_imex(mailboxPointer, DC_IMEX_EXPORT_BACKUP, documents[0], nil)
  255. }
  256. } else {
  257. logger.error("document directory not found")
  258. }
  259. }))
  260. alert.addAction(UIAlertAction(title: String.localized("cancel"), style: .cancel, handler: nil))
  261. present(alert, animated: true, completion: nil)
  262. }
  263. private func openHelp(_: Row) {
  264. if let url = URL(string: String.localized("pref_help_url")) {
  265. UIApplication.shared.open(url)
  266. }
  267. }
  268. private func showDeaddrop(_: Row) {
  269. let deaddropViewController = MailboxViewController(dcContext: dcContext, chatId: Int(DC_CHAT_ID_DEADDROP))
  270. let deaddropNavigationController = DcNavigationController(rootViewController: deaddropViewController)
  271. let deaddropCoordinator = MailboxCoordinator(dcContext: dcContext, navigationController: deaddropNavigationController)
  272. deaddropViewController.coordinator = deaddropCoordinator
  273. self.coordinator?.navigationController.pushViewController(deaddropViewController, animated: true)
  274. }
  275. private func showClassicMail(_: Row) {
  276. coordinator?.showClassicMail()
  277. }
  278. private func showBlockedContacts(_: Row) {
  279. coordinator?.showBlockedContacts()
  280. }
  281. private func sendAsm(_: Row) {
  282. let askAlert = UIAlertController(title: String.localized("autocrypt_send_asm_explain_before"), message: nil, preferredStyle: .actionSheet)
  283. askAlert.addAction(UIAlertAction(title: String.localized("autocrypt_send_asm_title"), style: .default, handler: { _ in
  284. let waitAlert = UIAlertController(title: String.localized("one_moment"), message: nil, preferredStyle: .alert)
  285. waitAlert.addAction(UIAlertAction(title: String.localized("cancel"), style: .default, handler: { _ in self.dcContext.stopOngoingProcess() }))
  286. self.present(waitAlert, animated: true, completion: nil)
  287. DispatchQueue.global(qos: .background).async {
  288. let sc = self.dcContext.initiateKeyTransfer()
  289. DispatchQueue.main.async {
  290. waitAlert.dismiss(animated: true, completion: nil)
  291. guard var sc = sc else {
  292. return
  293. }
  294. if sc.count == 44 {
  295. // format setup code to the typical 3 x 3 numbers
  296. sc = sc.substring(0, 4) + " - " + sc.substring(5, 9) + " - " + sc.substring(10, 14) + " -\n\n" +
  297. sc.substring(15, 19) + " - " + sc.substring(20, 24) + " - " + sc.substring(25, 29) + " -\n\n" +
  298. sc.substring(30, 34) + " - " + sc.substring(35, 39) + " - " + sc.substring(40, 44)
  299. }
  300. let text = String.localizedStringWithFormat(String.localized("autocrypt_send_asm_explain_after"), sc)
  301. let showAlert = UIAlertController(title: text, message: nil, preferredStyle: .alert)
  302. showAlert.addAction(UIAlertAction(title: String.localized("ok"), style: .default, handler: nil))
  303. self.present(showAlert, animated: true, completion: nil)
  304. }
  305. }
  306. }))
  307. askAlert.addAction(UIAlertAction(title: String.localized("cancel"), style: .cancel, handler: nil))
  308. present(askAlert, animated: true, completion: nil)
  309. }
  310. private func configure(_: Row) {
  311. hudHandler.showHud(String.localized("configuring_account"))
  312. dc_configure(mailboxPointer)
  313. }
  314. }