SettingsViewController.swift 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298
  1. import UIKit
  2. import DcCore
  3. import Intents
  4. internal final class SettingsViewController: UITableViewController {
  5. private struct SectionConfigs {
  6. let headerTitle: String?
  7. let footerTitle: String?
  8. let cells: [UITableViewCell]
  9. }
  10. private enum CellTags: Int {
  11. case profile
  12. case chatsAndMedia
  13. case addAnotherDevice
  14. case notifications
  15. case selectBackground
  16. case advanced
  17. case help
  18. case connectivity
  19. }
  20. private var dcContext: DcContext
  21. internal let dcAccounts: DcAccounts
  22. private var connectivityChangedObserver: NSObjectProtocol?
  23. // MARK: - cells
  24. private lazy var profileCell: ContactCell = {
  25. let cell = ContactCell(style: .default, reuseIdentifier: nil)
  26. let cellViewModel = ProfileViewModel(context: dcContext)
  27. cell.updateCell(cellViewModel: cellViewModel)
  28. cell.tag = CellTags.profile.rawValue
  29. cell.accessoryType = .disclosureIndicator
  30. return cell
  31. }()
  32. private lazy var chatsAndMediaCell: UITableViewCell = {
  33. let cell = UITableViewCell(style: .value1, reuseIdentifier: nil)
  34. cell.tag = CellTags.chatsAndMedia.rawValue
  35. cell.textLabel?.text = String.localized("pref_chats_and_media")
  36. if #available(iOS 16.0, *) {
  37. cell.imageView?.image = UIImage(systemName: "message") // added in ios13
  38. }
  39. cell.accessoryType = .disclosureIndicator
  40. return cell
  41. }()
  42. private lazy var notificationSwitch: UISwitch = {
  43. let switchControl = UISwitch()
  44. switchControl.isOn = !UserDefaults.standard.bool(forKey: "notifications_disabled")
  45. switchControl.addTarget(self, action: #selector(handleNotificationToggle(_:)), for: .valueChanged)
  46. return switchControl
  47. }()
  48. private lazy var notificationCell: UITableViewCell = {
  49. let cell = UITableViewCell(style: .default, reuseIdentifier: nil)
  50. cell.tag = CellTags.notifications.rawValue
  51. cell.textLabel?.text = String.localized("pref_notifications")
  52. if #available(iOS 16.0, *) {
  53. cell.imageView?.image = UIImage(systemName: "bell") // added in ios13
  54. }
  55. cell.accessoryView = notificationSwitch
  56. cell.selectionStyle = .none
  57. return cell
  58. }()
  59. private lazy var addAnotherDeviceCell: UITableViewCell = {
  60. let cell = UITableViewCell(style: .default, reuseIdentifier: nil)
  61. cell.tag = CellTags.addAnotherDevice.rawValue
  62. cell.textLabel?.text = String.localized("multidevice_title")
  63. if #available(iOS 16.0, *) {
  64. cell.imageView?.image = UIImage(systemName: "macbook.and.iphone") // added in ios16
  65. }
  66. cell.accessoryType = .disclosureIndicator
  67. return cell
  68. }()
  69. private lazy var advancedCell: UITableViewCell = {
  70. let cell = UITableViewCell(style: .value1, reuseIdentifier: nil)
  71. cell.tag = CellTags.advanced.rawValue
  72. cell.textLabel?.text = String.localized("menu_advanced")
  73. if #available(iOS 16.0, *) {
  74. cell.imageView?.image = UIImage(systemName: "chevron.left.forwardslash.chevron.right") // added in ios15
  75. }
  76. cell.accessoryType = .disclosureIndicator
  77. return cell
  78. }()
  79. private lazy var helpCell: UITableViewCell = {
  80. let cell = UITableViewCell(style: .value1, reuseIdentifier: nil)
  81. cell.tag = CellTags.help.rawValue
  82. cell.textLabel?.text = String.localized("menu_help")
  83. if #available(iOS 16.0, *) {
  84. cell.imageView?.image = UIImage(systemName: "questionmark.circle") // added in ios13
  85. }
  86. cell.accessoryType = .disclosureIndicator
  87. return cell
  88. }()
  89. private lazy var connectivityCell: UITableViewCell = {
  90. let cell = UITableViewCell(style: .value1, reuseIdentifier: nil)
  91. cell.tag = CellTags.connectivity.rawValue
  92. cell.textLabel?.text = String.localized("connectivity")
  93. if #available(iOS 16.0, *) {
  94. cell.imageView?.image = UIImage(systemName: "arrow.up.arrow.down") // added in ios13
  95. }
  96. cell.accessoryType = .disclosureIndicator
  97. return cell
  98. }()
  99. private lazy var selectBackgroundCell: UITableViewCell = {
  100. let cell = UITableViewCell()
  101. cell.tag = CellTags.selectBackground.rawValue
  102. cell.textLabel?.text = String.localized("pref_background")
  103. if #available(iOS 16.0, *) {
  104. cell.imageView?.image = UIImage(systemName: "photo") // added in ios13
  105. }
  106. cell.accessoryType = .disclosureIndicator
  107. return cell
  108. }()
  109. private lazy var sections: [SectionConfigs] = {
  110. var appNameAndVersion = "Delta Chat"
  111. if let appVersion = Bundle.main.infoDictionary?["CFBundleShortVersionString"] as? String {
  112. appNameAndVersion += " v" + appVersion
  113. }
  114. let profileSection = SectionConfigs(
  115. headerTitle: String.localized("pref_profile_info_headline"),
  116. footerTitle: nil,
  117. cells: [profileCell]
  118. )
  119. let preferencesSection = SectionConfigs(
  120. headerTitle: nil,
  121. footerTitle: nil,
  122. cells: [chatsAndMediaCell, notificationCell, selectBackgroundCell, addAnotherDeviceCell, connectivityCell, advancedCell]
  123. )
  124. let helpSection = SectionConfigs(
  125. headerTitle: nil,
  126. footerTitle: appNameAndVersion,
  127. cells: [helpCell]
  128. )
  129. return [profileSection, preferencesSection, helpSection]
  130. }()
  131. init(dcAccounts: DcAccounts) {
  132. self.dcContext = dcAccounts.getSelected()
  133. self.dcAccounts = dcAccounts
  134. super.init(style: .grouped)
  135. }
  136. required init?(coder _: NSCoder) {
  137. fatalError("init(coder:) has not been implemented")
  138. }
  139. // MARK: - lifecycle
  140. override func viewDidLoad() {
  141. super.viewDidLoad()
  142. title = String.localized("menu_settings")
  143. tableView.rowHeight = UITableView.automaticDimension
  144. }
  145. override func viewWillAppear(_ animated: Bool) {
  146. super.viewWillAppear(animated)
  147. // set connectivity changed observer before we acutally init `connectivityCell.detailTextLabel` in `updateCells()`,
  148. // otherwise, we may miss events and the label is not correct.
  149. connectivityChangedObserver = NotificationCenter.default.addObserver(forName: dcNotificationConnectivityChanged,
  150. object: nil,
  151. queue: nil) { [weak self] _ in
  152. guard let self = self else { return }
  153. self.connectivityCell.detailTextLabel?.text = DcUtils.getConnectivityString(dcContext: self.dcContext,
  154. connectedString: String.localized("connectivity_connected"))
  155. }
  156. updateCells()
  157. }
  158. override func viewDidDisappear(_ animated: Bool) {
  159. super.viewDidDisappear(animated)
  160. if let connectivityChangedObserver = self.connectivityChangedObserver {
  161. NotificationCenter.default.removeObserver(connectivityChangedObserver)
  162. }
  163. }
  164. // MARK: - UITableViewDelegate + UITableViewDatasource
  165. override func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
  166. if indexPath.section == 0 && indexPath.row == 0 {
  167. return ContactCell.cellHeight
  168. } else {
  169. return UITableView.automaticDimension
  170. }
  171. }
  172. override func numberOfSections(in tableView: UITableView) -> Int {
  173. return sections.count
  174. }
  175. override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
  176. return sections[section].cells.count
  177. }
  178. override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
  179. return sections[indexPath.section].cells[indexPath.row]
  180. }
  181. override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
  182. guard let cell = tableView.cellForRow(at: indexPath), let cellTag = CellTags(rawValue: cell.tag) else {
  183. safe_fatalError()
  184. return
  185. }
  186. tableView.deselectRow(at: indexPath, animated: false)
  187. switch cellTag {
  188. case .profile: showEditSettingsController()
  189. case .chatsAndMedia: showChatsAndMedia()
  190. case .addAnotherDevice: showBackupProviderViewController()
  191. case .notifications: break
  192. case .advanced: showAdvanced()
  193. case .help: showHelp()
  194. case .connectivity: showConnectivity()
  195. case .selectBackground: selectBackground()
  196. }
  197. }
  198. override func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
  199. return sections[section].headerTitle
  200. }
  201. override func tableView(_ tableView: UITableView, titleForFooterInSection section: Int) -> String? {
  202. return sections[section].footerTitle
  203. }
  204. // MARK: - actions
  205. @objc private func handleNotificationToggle(_ sender: UISwitch) {
  206. UserDefaults.standard.set(!sender.isOn, forKey: "notifications_disabled")
  207. if sender.isOn {
  208. if let appDelegate = UIApplication.shared.delegate as? AppDelegate {
  209. appDelegate.registerForNotifications()
  210. }
  211. } else {
  212. NotificationManager.removeAllNotifications()
  213. }
  214. UserDefaults.standard.synchronize()
  215. NotificationManager.updateApplicationIconBadge(dcContext: dcContext, reset: !sender.isOn)
  216. }
  217. // MARK: - updates
  218. private func updateCells() {
  219. profileCell.updateCell(cellViewModel: ProfileViewModel(context: dcContext))
  220. connectivityCell.detailTextLabel?.text = DcUtils.getConnectivityString(dcContext: dcContext,
  221. connectedString: String.localized("connectivity_connected"))
  222. }
  223. // MARK: - coordinator
  224. private func showEditSettingsController() {
  225. let editController = SelfProfileViewController(dcAccounts: dcAccounts)
  226. navigationController?.pushViewController(editController, animated: true)
  227. }
  228. private func showChatsAndMedia() {
  229. navigationController?.pushViewController(ChatsAndMediaViewController(dcAccounts: dcAccounts), animated: true)
  230. }
  231. private func showBackupProviderViewController() {
  232. let alert = UIAlertController(title: String.localized("multidevice_title"), message: String.localized("multidevice_this_creates_a_qr_code"), preferredStyle: .alert)
  233. alert.addAction(UIAlertAction(title: String.localized("cancel"), style: .cancel, handler: nil))
  234. alert.addAction(UIAlertAction(
  235. title: String.localized("perm_continue"),
  236. style: .default,
  237. handler: { [weak self] _ in
  238. guard let self = self else { return }
  239. self.navigationController?.pushViewController(BackupTransferViewController(dcAccounts: self.dcAccounts), animated: true)
  240. }
  241. ))
  242. present(alert, animated: true)
  243. }
  244. private func showAdvanced() {
  245. navigationController?.pushViewController(AdvancedViewController(dcAccounts: dcAccounts), animated: true)
  246. }
  247. private func showHelp() {
  248. navigationController?.pushViewController(HelpViewController(dcContext: dcContext), animated: true)
  249. }
  250. private func showConnectivity() {
  251. navigationController?.pushViewController(ConnectivityViewController(dcContext: dcContext), animated: true)
  252. }
  253. private func selectBackground() {
  254. navigationController?.pushViewController(BackgroundOptionsViewController(dcContext: dcContext), animated: true)
  255. }
  256. }