SettingsViewController.swift 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371
  1. // _____ _
  2. // |_ _| |_ _ _ ___ ___ _ __ __ _
  3. // | | | ' \| '_/ -_) -_) ' \/ _` |_
  4. // |_| |_||_|_| \___\___|_|_|_\__,_(_)
  5. //
  6. // Threema iOS Client
  7. // Copyright (c) 2019-2020 Threema GmbH
  8. //
  9. // This program is free software: you can redistribute it and/or modify
  10. // it under the terms of the GNU Affero General Public License, version 3,
  11. // as published by the Free Software Foundation.
  12. //
  13. // This program is distributed in the hope that it will be useful,
  14. // but WITHOUT ANY WARRANTY; without even the implied warranty of
  15. // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  16. // GNU Affero General Public License for more details.
  17. //
  18. // You should have received a copy of the GNU Affero General Public License
  19. // along with this program. If not, see <https://www.gnu.org/licenses/>.
  20. import Foundation
  21. class SettingsViewController: ThemedTableViewController {
  22. @IBOutlet weak var privacyCell: UITableViewCell!
  23. @IBOutlet weak var appearanceCell: UITableViewCell!
  24. @IBOutlet weak var notificationCell: UITableViewCell!
  25. @IBOutlet weak var chatCell: UITableViewCell!
  26. @IBOutlet weak var mediaCell: UITableViewCell!
  27. @IBOutlet weak var storageManagementCell: UITableViewCell!
  28. @IBOutlet weak var passcodeLockCell: UITableViewCell!
  29. @IBOutlet weak var threemaCallsCell: UITableViewCell!
  30. @IBOutlet weak var threemaWebCell: UITableViewCell!
  31. @IBOutlet weak var networkStatusCell: UITableViewCell!
  32. @IBOutlet weak var versionCell: UITableViewCell!
  33. @IBOutlet weak var usernameCell: UITableViewCell!
  34. @IBOutlet weak var inviteAFriendCell: UITableViewCell!
  35. @IBOutlet weak var threemaChannelCell: UITableViewCell!
  36. @IBOutlet weak var threemaWorkCell: UITableViewCell!
  37. @IBOutlet weak var supportCell: UITableViewCell!
  38. @IBOutlet weak var privacyPolicyCell: UITableViewCell!
  39. @IBOutlet weak var licenseCell: UITableViewCell!
  40. @IBOutlet weak var advancedCell: UITableViewCell!
  41. @IBOutlet weak var devModeCell: UITableViewCell!
  42. @IBOutlet weak var usernameCellLabel: UILabel!
  43. @IBOutlet weak var userNameCellDetailLabel: UILabel!
  44. private var inviteController: InviteController?
  45. private var observing: Bool = false
  46. override var shouldAutorotate: Bool {
  47. return true
  48. }
  49. override var supportedInterfaceOrientations: UIInterfaceOrientationMask {
  50. if UIDevice.current.userInterfaceIdiom == .pad {
  51. return .all
  52. }
  53. return .allButUpsideDown
  54. }
  55. override func viewDidLoad() {
  56. super.viewDidLoad()
  57. var version = BundleUtil.mainBundle()?.object(forInfoDictionaryKey: "CFBundleShortVersionString") as! String
  58. if let suffix = BundleUtil.mainBundle()?.object(forInfoDictionaryKey: "ThreemaVersionSuffix") as? String {
  59. version = version.appending(suffix)
  60. let build = BundleUtil.mainBundle()?.object(forInfoDictionaryKey: kCFBundleVersionKey as String)
  61. versionCell.detailTextLabel?.text = "\(version) (\(build!))"
  62. if let versionCopyLabel = versionCell.detailTextLabel as? CopyLabel {
  63. versionCopyLabel.textForCopying = "\(version)b\(build ?? "0")"
  64. }
  65. userNameCellDetailLabel?.text = LicenseStore.shared().licenseUsername
  66. NotificationCenter.default.addObserver(self, selector: #selector(colorThemeChanged), name: NSNotification.Name(rawValue: kNotificationColorThemeChanged) , object: nil)
  67. BrandingUtils.updateTitleLogo(of: self.navigationItem, navigationController: self.navigationController)
  68. }
  69. }
  70. override func viewWillAppear(_ animated: Bool) {
  71. super.viewWillAppear(animated)
  72. updateTitleLabels()
  73. updateConnectionStatus()
  74. updatePasscodeLock()
  75. updateThreemaWeb()
  76. registerObserver()
  77. if #available(iOS 11.0, *) {
  78. self.navigationItem.largeTitleDisplayMode = UINavigationItem.LargeTitleDisplayMode(rawValue: (UserSettings.shared()?.largeTitleDisplayMode)!)!
  79. }
  80. // iOS fix where the logo is moved to the right sometimes
  81. if navigationController!.navigationBar.frame.size.height == 44.0 && LicenseStore.requiresLicenseKey() {
  82. BrandingUtils.updateTitleLogo(of: navigationItem, navigationController: navigationController)
  83. }
  84. else if navigationController!.navigationBar.frame.size.height == 44.0 && LicenseStore.requiresLicenseKey() == false && navigationItem.titleView != nil {
  85. BrandingUtils.updateTitleLogo(of: navigationItem, navigationController: navigationController)
  86. }
  87. }
  88. override func viewWillDisappear(_ animated: Bool) {
  89. super.viewWillDisappear(animated)
  90. unregisterObserver()
  91. }
  92. override func shouldPerformSegue(withIdentifier identifier: String, sender: Any?) -> Bool {
  93. if identifier == "ThreemaWorkSegue" {
  94. let appUrl = URL.init(string: "threemawork://app")
  95. if UIApplication.shared.canOpenURL(appUrl!) {
  96. UIApplication.shared.open(appUrl!, options: [:], completionHandler: nil)
  97. return false
  98. }
  99. }
  100. return true
  101. }
  102. @objc func colorThemeChanged(notification: Notification) {
  103. BrandingUtils.updateTitleLogo(of: self.navigationItem, navigationController: self.navigationController)
  104. if #available(iOS 11.0, *) {
  105. // set large title color for settingsviewcontroller; it will not automaticly change the color when set new appearance
  106. self.navigationController?.navigationBar.largeTitleTextAttributes = [NSAttributedString.Key.foregroundColor: Colors.fontNormal()!]
  107. }
  108. }
  109. }
  110. extension SettingsViewController {
  111. // MARK: private functions
  112. private func registerObserver() {
  113. if observing == false {
  114. ServerConnector.shared()?.addObserver(self, forKeyPath: "connectionState", options: [], context: nil)
  115. WCSessionManager.shared.addObserver(self, forKeyPath: "running", options: [], context: nil)
  116. observing = true
  117. }
  118. }
  119. private func unregisterObserver() {
  120. if observing == true {
  121. ServerConnector.shared()?.removeObserver(self, forKeyPath: "connectionState")
  122. WCSessionManager.shared.removeObserver(self, forKeyPath: "running")
  123. observing = false
  124. }
  125. }
  126. private func updateTitleLabels() {
  127. let suffix = Colors.getTheme() == ColorThemeDark || Colors.getTheme() == ColorThemeDarkWork ? "Dark" : "Light"
  128. privacyCell.textLabel?.text = BundleUtil.localizedString(forKey: "settings_privacy")
  129. privacyCell.imageView?.image = BundleUtil.imageNamed("Privacy\(suffix)")
  130. appearanceCell.textLabel?.text = BundleUtil.localizedString(forKey: "settings_appearance")
  131. appearanceCell.imageView?.image = BundleUtil.imageNamed("Appearance\(suffix)")
  132. notificationCell.textLabel?.text = BundleUtil.localizedString(forKey: "settings_notification")
  133. notificationCell.imageView?.image = BundleUtil.imageNamed("Notifications\(suffix)")
  134. chatCell.textLabel?.text = BundleUtil.localizedString(forKey: "settings_chat")
  135. chatCell.imageView?.image = BundleUtil.imageNamed("Chat\(suffix)")
  136. mediaCell.textLabel?.text = BundleUtil.localizedString(forKey: "settings_media")
  137. mediaCell.imageView?.image = BundleUtil.imageNamed("Media\(suffix)")
  138. storageManagementCell.textLabel?.text = BundleUtil.localizedString(forKey: "settings_storage_management")
  139. storageManagementCell.imageView?.image = BundleUtil.imageNamed("StorageManagement\(suffix)")
  140. devModeCell.textLabel?.text = BundleUtil.localizedString(forKey: "settings_dev_mode")
  141. devModeCell.imageView?.image = BundleUtil.imageNamed("DevMode\(suffix)")
  142. passcodeLockCell.textLabel?.text = BundleUtil.localizedString(forKey: "settings_passcode_lock")
  143. passcodeLockCell.imageView?.image = BundleUtil.imageNamed("PasscodeLock\(suffix)")
  144. threemaCallsCell.textLabel?.text = BundleUtil.localizedString(forKey: "settings_threema_calls")
  145. threemaCallsCell.imageView?.image = BundleUtil.imageNamed("ThreemaCallsSettings\(suffix)")
  146. threemaWebCell.textLabel?.text = BundleUtil.localizedString(forKey: "settings_threema_web")
  147. threemaWebCell.imageView?.image = BundleUtil.imageNamed("ThreemaWeb\(suffix)")
  148. networkStatusCell.textLabel?.text = BundleUtil.localizedString(forKey: "settings_network_status")
  149. versionCell.textLabel?.text = BundleUtil.localizedString(forKey: "settings_version")
  150. usernameCellLabel.text = BundleUtil.localizedString(forKey: "settings_license_username")
  151. inviteAFriendCell.textLabel?.text = BundleUtil.localizedString(forKey: "settings_invite_a_friend")
  152. inviteAFriendCell.imageView?.image = BundleUtil.imageNamed("InviteAFriend\(suffix)")
  153. threemaChannelCell.textLabel?.text = BundleUtil.localizedString(forKey: "settings_threema_channel")
  154. threemaChannelCell.imageView?.image = BundleUtil.imageNamed("ThreemaChannel\(suffix)")
  155. threemaWorkCell.textLabel?.text = BundleUtil.localizedString(forKey: "settings_threema_work")
  156. threemaWorkCell.imageView?.image = BundleUtil.imageNamed("ThreemaWorkSettings\(suffix)")
  157. supportCell.textLabel?.text = BundleUtil.localizedString(forKey: "settings_support")
  158. supportCell.imageView?.image = BundleUtil.imageNamed("Support\(suffix)")
  159. privacyPolicyCell.textLabel?.text = BundleUtil.localizedString(forKey: "settings_privacy_policy")
  160. privacyPolicyCell.imageView?.image = BundleUtil.imageNamed("PrivacyPolicy\(suffix)")
  161. licenseCell.textLabel?.text = BundleUtil.localizedString(forKey: "settings_license")
  162. licenseCell.imageView?.image = BundleUtil.imageNamed("License\(suffix)")
  163. advancedCell.textLabel?.text = BundleUtil.localizedString(forKey: "settings_advanced")
  164. advancedCell.imageView?.image = BundleUtil.imageNamed("Advanced\(suffix)")
  165. }
  166. private func updateConnectionStatus() {
  167. let stateName = ServerConnector.shared().name(for: ServerConnector.shared().connectionState)
  168. let locKey = "status_\(stateName!)"
  169. var statusText = BundleUtil.localizedString(forKey: locKey)
  170. if ServerConnector.shared()?.isIPv6Connection == true {
  171. statusText = statusText?.appending(" (IPv6)")
  172. }
  173. if ServerConnector.shared()?.isProxyConnection == true {
  174. statusText = statusText?.appending(" (Proxy)")
  175. }
  176. networkStatusCell.detailTextLabel?.text = statusText
  177. }
  178. private func updatePasscodeLock() {
  179. if KKPasscodeLock.shared().isPasscodeRequired() == true {
  180. passcodeLockCell.detailTextLabel!.text = BundleUtil.localizedString(forKey: "On")
  181. } else {
  182. passcodeLockCell.detailTextLabel!.text = BundleUtil.localizedString(forKey: "Off")
  183. }
  184. }
  185. private func updateThreemaWeb() {
  186. if UserSettings.shared().threemaWeb == true {
  187. if WCSessionManager.shared.isRunningWCSession() == true {
  188. threemaWebCell.detailTextLabel?.text = BundleUtil.localizedString(forKey: "status_loggedin")
  189. } else {
  190. threemaWebCell.detailTextLabel?.text = BundleUtil.localizedString(forKey: "On")
  191. }
  192. } else {
  193. threemaWebCell.detailTextLabel?.text = BundleUtil.localizedString(forKey: "Off")
  194. }
  195. }
  196. }
  197. extension SettingsViewController {
  198. // MARK: DB observer
  199. override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) {
  200. if object as? ServerConnector == ServerConnector.shared() && keyPath == "connectionState" {
  201. DispatchQueue.main.async {
  202. self.updateConnectionStatus()
  203. }
  204. }
  205. else if object as? ServerConnector == ServerConnector.shared() && keyPath == "running" {
  206. DispatchQueue.main.async {
  207. self.updateThreemaWeb()
  208. }
  209. }
  210. }
  211. }
  212. extension SettingsViewController {
  213. // MARK: Table view delegate
  214. override func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat {
  215. if #available(iOS 11.0, *) {
  216. if section == 0 {
  217. return 38.0
  218. }
  219. }
  220. if LicenseStore.requiresLicenseKey() {
  221. if section == 3 {
  222. return 0.0
  223. }
  224. }
  225. return UITableView.automaticDimension
  226. }
  227. override func tableView(_ tableView: UITableView, heightForFooterInSection section: Int) -> CGFloat {
  228. if LicenseStore.requiresLicenseKey() {
  229. if section == 3 {
  230. return 0.0
  231. }
  232. }
  233. return UITableView.automaticDimension
  234. }
  235. override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
  236. if section == 0 {
  237. #if !DEBUG
  238. return super.tableView(tableView, numberOfRowsInSection: section) - 1
  239. #endif
  240. }
  241. // hide Threema Channel for work
  242. if LicenseStore.requiresLicenseKey() {
  243. if section == 2 {
  244. return 3
  245. }
  246. if section == 3 {
  247. return 0
  248. }
  249. } else {
  250. if section == 2 {
  251. return 2
  252. }
  253. }
  254. return super.tableView(tableView, numberOfRowsInSection: section)
  255. }
  256. override func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
  257. if LicenseStore.requiresLicenseKey() {
  258. if indexPath.section == 3 {
  259. return 0.0
  260. }
  261. }
  262. return super.tableView(tableView, heightForRowAt: indexPath)
  263. }
  264. override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
  265. if indexPath.section == 0 && indexPath.row == 6 {
  266. let vc = KKPasscodeSettingsViewController.init(style: .grouped)
  267. vc.delegate = self
  268. navigationController?.pushViewController(vc, animated: true)
  269. }
  270. else if indexPath.section == 3 && indexPath.row == 0 {
  271. inviteController = InviteController.init()
  272. inviteController!.parentViewController = self
  273. inviteController!.shareViewController = self
  274. inviteController!.actionSheetViewController = self
  275. inviteController!.rect = tableView.rectForRow(at: indexPath)
  276. inviteController!.invite()
  277. }
  278. else if indexPath.section == 3 && indexPath.row == 1 {
  279. let addThreemaChannelController = AddThreemaChannelController.init()
  280. addThreemaChannelController.parentViewController = self
  281. addThreemaChannelController.addThreemaChannel()
  282. }
  283. tableView.deselectRow(at: indexPath, animated: true)
  284. }
  285. }
  286. extension SettingsViewController {
  287. // MARK: UIScrollViewDelegate
  288. override func scrollViewDidScroll(_ scrollView: UIScrollView) {
  289. if LicenseStore.requiresLicenseKey() == false {
  290. if navigationController!.navigationBar.frame.size.height < 60.0 && navigationItem.titleView != nil {
  291. navigationItem.titleView = nil
  292. navigationItem.title = BundleUtil.localizedString(forKey: "settings")
  293. }
  294. else if navigationController!.navigationBar.frame.size.height >= 59.5 && navigationItem.titleView == nil {
  295. BrandingUtils.updateTitleLogo(of: navigationItem, navigationController: navigationController)
  296. }
  297. }
  298. }
  299. }
  300. extension SettingsViewController: KKPasscodeSettingsViewControllerDelegate {
  301. func didSettingsChanged(_ viewController: KKPasscodeSettingsViewController!) {
  302. updatePasscodeLock()
  303. }
  304. }