QrViewController.swift 14 KB

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