QrViewController.swift 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309
  1. import Foundation
  2. import UIKit
  3. class QrViewController: UITableViewController, QrCodeReaderDelegate {
  4. private let rowContact = 0
  5. private let rowQRCode = 1
  6. private let rowScanQR = 2
  7. weak var coordinator: QrViewCoordinator?
  8. let qrCodeReaderController = QrCodeReaderController()
  9. var secureJoinObserver: Any?
  10. var dcContext: DcContext
  11. var contact: DcContact? {
  12. // This is nil if we do not have an account setup yet
  13. if !DcConfig.configured {
  14. return nil
  15. }
  16. return DcContact(id: Int(DC_CONTACT_ID_SELF))
  17. }
  18. init(dcContext: DcContext) {
  19. self.dcContext = dcContext
  20. super.init(nibName: nil, bundle: nil)
  21. }
  22. required init?(coder _: NSCoder) {
  23. fatalError("init(coder:) has not been implemented")
  24. }
  25. override func viewDidLoad() {
  26. super.viewDidLoad()
  27. title = String.localized("qr_code")
  28. qrCodeReaderController.delegate = self
  29. tableView.separatorStyle = .none
  30. }
  31. override func viewDidAppear(_ animated: Bool) {
  32. let indexPath = IndexPath(row: rowContact, section: 0)
  33. tableView.beginUpdates()
  34. tableView.reloadRows(at: [indexPath], with: UITableView.RowAnimation.none)
  35. tableView.endUpdates()
  36. }
  37. override func tableView(_: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
  38. let row = indexPath.row
  39. switch row {
  40. case rowContact:
  41. return createContactCell()
  42. case rowQRCode:
  43. return createQRCodeCell()
  44. case rowScanQR:
  45. return createQRCodeScanCell()
  46. default:
  47. return UITableViewCell(style: .default, reuseIdentifier: nil)
  48. }
  49. }
  50. override func numberOfSections(in _: UITableView) -> Int {
  51. return 1
  52. }
  53. override func tableView(_: UITableView, numberOfRowsInSection section: Int) -> Int {
  54. return 3
  55. }
  56. override func tableView(_: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
  57. switch indexPath.row {
  58. case rowContact:
  59. return 72
  60. case rowQRCode:
  61. return 225
  62. case rowScanQR:
  63. return 40
  64. default:
  65. return 10
  66. }
  67. }
  68. private lazy var progressAlert: UIAlertController = {
  69. let alert = UIAlertController(title: String.localized("one_moment"), message: nil, preferredStyle: .alert)
  70. let rect = CGRect(x: 0, y: 0, width: 25, height: 25)
  71. let activityIndicator = UIActivityIndicatorView(frame: rect)
  72. activityIndicator.translatesAutoresizingMaskIntoConstraints = false
  73. activityIndicator.style = .gray
  74. alert.addAction(UIAlertAction(title: String.localized("cancel"), style: .default, handler: { _ in
  75. self.dcContext.stopOngoingProcess()
  76. self.dismiss(animated: true, completion: nil)
  77. }))
  78. return alert
  79. }()
  80. private func showProgressAlert() {
  81. self.present(self.progressAlert, animated: true, completion: {
  82. let rect = CGRect(x: 10, y: 10, width: 20, height: 20)
  83. let progressView = UIActivityIndicatorView(frame: rect)
  84. progressView.tintColor = .blue
  85. progressView.startAnimating()
  86. self.progressAlert.view.addSubview(progressView)
  87. })
  88. }
  89. private func showErrorAlert(error: String) {
  90. let alert = UIAlertController(title: String.localized("error"), message: error, preferredStyle: .alert)
  91. alert.addAction(UIAlertAction(title: String.localized("ok"), style: .default, handler: { _ in
  92. alert.dismiss(animated: true, completion: nil)
  93. }))
  94. }
  95. private func addSecureJoinProgressListener() {
  96. let nc = NotificationCenter.default
  97. secureJoinObserver = nc.addObserver(
  98. forName: dcNotificationSecureJoinerProgress,
  99. object: nil,
  100. queue: nil
  101. ) { notification in
  102. print("secure join: ", notification)
  103. if let ui = notification.userInfo {
  104. if ui["progress"] as? Int == 400 {
  105. if let contactId = ui["contact_id"] as? Int {
  106. self.progressAlert.message = String.localizedStringWithFormat(
  107. String.localized("qrscan_x_verified_introduce_myself"),
  108. DcContact(id: contactId).nameNAddr)
  109. }
  110. }
  111. }
  112. }
  113. }
  114. private func removeSecureJoinProgressListener() {
  115. let nc = NotificationCenter.default
  116. if let secureJoinObserver = self.secureJoinObserver {
  117. nc.removeObserver(secureJoinObserver)
  118. }
  119. }
  120. //QRCodeDelegate
  121. func handleQrCode(_ code: String) {
  122. //remove qr code scanner view
  123. if let ctrl = navigationController,
  124. let lastController = ctrl.viewControllers.last {
  125. if type(of: lastController) === QrCodeReaderController.self {
  126. ctrl.viewControllers.removeLast()
  127. }
  128. }
  129. let qrParsed: DcLot = self.dcContext.checkQR(qrCode: code)
  130. let state = Int32(qrParsed.state)
  131. switch state {
  132. case DC_QR_ASK_VERIFYCONTACT:
  133. let nameAndAddress = DcContact(id: qrParsed.id).nameNAddr
  134. joinSecureJoin(alertMessage: String.localizedStringWithFormat(String.localized("ask_start_chat_with"), nameAndAddress), code: code)
  135. case DC_QR_ASK_VERIFYGROUP:
  136. let groupName = qrParsed.text1 ?? "ErrGroupName"
  137. joinSecureJoin(alertMessage: String.localizedStringWithFormat(String.localized("qrscan_ask_join_group"), groupName), code: code)
  138. case DC_QR_TEXT:
  139. let msg = String.localizedStringWithFormat(String.localized("qrscan_contains_text"), qrParsed.text1 ?? "")
  140. let alert = UIAlertController(title: msg, message: nil, preferredStyle: .alert)
  141. alert.addAction(UIAlertAction(title: String.localized("ok"), style: .default, handler: nil))
  142. present(alert, animated: true, completion: nil)
  143. default:
  144. var msg = String.localizedStringWithFormat(String.localized("qrscan_contains_text"), code)
  145. if state == DC_QR_ERROR {
  146. if let errorMsg = qrParsed.text1 {
  147. msg = errorMsg + "\n\n" + msg
  148. }
  149. }
  150. let alert = UIAlertController(title: msg, message: nil, preferredStyle: .alert)
  151. alert.addAction(UIAlertAction(title: String.localized("ok"), style: .default, handler: nil))
  152. present(alert, animated: true, completion: nil)
  153. }
  154. }
  155. private func joinSecureJoin(alertMessage: String, code: String) {
  156. let alert = UIAlertController(title: alertMessage,
  157. message: nil,
  158. preferredStyle: .alert)
  159. alert.addAction(UIAlertAction(title: String.localized("cancel"), style: .default, handler: nil))
  160. alert.addAction(UIAlertAction(title: String.localized("ok"), style: .default, handler: { _ in
  161. alert.dismiss(animated: true, completion: nil)
  162. self.showProgressAlert()
  163. // execute blocking secure join in background
  164. DispatchQueue.global(qos: .background).async {
  165. self.addSecureJoinProgressListener()
  166. AppDelegate.lastErrorString = nil
  167. let chatId = self.dcContext.joinSecurejoin(qrCode: code)
  168. let errorString = AppDelegate.lastErrorString
  169. self.removeSecureJoinProgressListener()
  170. DispatchQueue.main.async {
  171. self.progressAlert.dismiss(animated: true, completion: nil)
  172. if chatId != 0 {
  173. self.coordinator?.showChat(chatId: chatId)
  174. } else if errorString != nil {
  175. self.showErrorAlert(error: errorString!)
  176. }
  177. }
  178. }
  179. }))
  180. present(alert, animated: true, completion: nil)
  181. }
  182. private func createContactCell() -> UITableViewCell {
  183. let cell = ContactCell(style: .default, reuseIdentifier: "contactCell")
  184. let bg = UIColor(red: 248 / 255, green: 248 / 255, blue: 255 / 255, alpha: 1.0)
  185. if let contact = self.contact {
  186. let name = DcConfig.displayname ?? contact.displayName
  187. cell.backgroundColor = bg
  188. cell.nameLabel.text = name
  189. cell.emailLabel.text = contact.email
  190. if let img = contact.profileImage {
  191. cell.setImage(img)
  192. } else {
  193. cell.setBackupImage(name: name, color: contact.color)
  194. }
  195. } else {
  196. cell.nameLabel.text = String.localized("no_account_setup")
  197. }
  198. return cell
  199. }
  200. private func createQRCodeCell() -> UITableViewCell {
  201. let cell = UITableViewCell(style: .default, reuseIdentifier: "qrCodeCell")
  202. let qrCode = createQRCodeView()
  203. let infolabel = createInfoLabel()
  204. cell.contentView.addSubview(qrCode)
  205. cell.contentView.addSubview(infolabel)
  206. cell.selectionStyle = .none
  207. let qrCodeConstraints = [qrCode.constraintAlignTopTo(cell.contentView, paddingTop: 25),
  208. qrCode.constraintCenterXTo(cell.contentView)]
  209. let infoLabelConstraints = [infolabel.constraintToBottomOf(qrCode, paddingTop: 25),
  210. infolabel.constraintAlignLeadingTo(cell.contentView, paddingLeading: 8),
  211. infolabel.constraintAlignTrailingTo(cell.contentView, paddingTrailing: 8)]
  212. cell.contentView.addConstraints(qrCodeConstraints)
  213. cell.contentView.addConstraints(infoLabelConstraints)
  214. return cell
  215. }
  216. private func createQRCodeScanCell() -> UITableViewCell {
  217. let cell = UITableViewCell(style: .default, reuseIdentifier: "scanQR")
  218. let scanButton = createQRCodeScannerButton()
  219. cell.contentView.addSubview(scanButton)
  220. cell.selectionStyle = .none
  221. let scanButtonConstraints = [scanButton.constraintCenterXTo(cell.contentView),
  222. scanButton.constraintCenterYTo(cell.contentView)]
  223. cell.contentView.addConstraints(scanButtonConstraints)
  224. return cell
  225. }
  226. private func createInfoLabel() -> UIView {
  227. let label = UILabel.init()
  228. label.translatesAutoresizingMaskIntoConstraints = false
  229. if let contact = contact {
  230. label.text = String.localizedStringWithFormat(String.localized("qrshow_join_contact_hint"), contact.email)
  231. }
  232. label.lineBreakMode = .byWordWrapping
  233. label.numberOfLines = 0
  234. label.textAlignment = .center
  235. label.font = UIFont.systemFont(ofSize: 14)
  236. return label
  237. }
  238. private func createQRCodeScannerButton() -> UIView {
  239. let btn = UIButton.init(type: UIButton.ButtonType.system)
  240. btn.translatesAutoresizingMaskIntoConstraints = false
  241. btn.setTitle(String.localized("qrscan_title"), for: .normal)
  242. btn.addTarget(self, action: #selector(self.openQRCodeScanner), for: .touchUpInside)
  243. return btn
  244. }
  245. @objc func openQRCodeScanner() {
  246. if let ctrl = navigationController {
  247. ctrl.pushViewController(qrCodeReaderController, animated: true)
  248. }
  249. }
  250. private func createQRCodeView() -> UIView {
  251. let width: CGFloat = 130
  252. let frame = CGRect(origin: .zero, size: .init(width: width, height: width))
  253. let imageView = QRCodeView(frame: frame)
  254. if let qrCode = dcContext.getSecurejoinQr(chatId: 0) {
  255. imageView.generateCode(
  256. qrCode,
  257. foregroundColor: .darkText,
  258. backgroundColor: .white
  259. )
  260. }
  261. imageView.translatesAutoresizingMaskIntoConstraints = false
  262. imageView.widthAnchor.constraint(equalToConstant: width).isActive = true
  263. imageView.heightAnchor.constraint(equalToConstant: width).isActive = true
  264. return imageView
  265. }
  266. func displayNewChat(contactId: Int) {
  267. let chatId = dc_create_chat_by_contact_id(mailboxPointer, UInt32(contactId))
  268. let chatVC = ChatViewController(dcContext: dcContext, chatId: Int(chatId))
  269. chatVC.hidesBottomBarWhenPushed = true
  270. navigationController?.pushViewController(chatVC, animated: true)
  271. }
  272. }