QrViewController.swift 14 KB

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