QrPageController.swift 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276
  1. import UIKit
  2. import DcCore
  3. class QrPageController: UIPageViewController, ProgressAlertHandler {
  4. private let dcContext: DcContext
  5. weak var progressAlert: UIAlertController?
  6. var progressObserver: Any?
  7. var qrCodeReaderController: QrCodeReaderController?
  8. private var selectedIndex: Int = 0
  9. private lazy var qrCodeHint: String = {
  10. var qrCodeHint = ""
  11. if dcContext.isConfigured() {
  12. let contact = DcContact(id: Int(DC_CONTACT_ID_SELF))
  13. qrCodeHint = String.localizedStringWithFormat(
  14. String.localized("qrshow_join_contact_hint"),
  15. contact.email
  16. )
  17. }
  18. return qrCodeHint
  19. }()
  20. private lazy var qrSegmentControl: UISegmentedControl = {
  21. let control = UISegmentedControl(
  22. items: [String.localized("qrshow_title"), String.localized("qrscan_title")]
  23. )
  24. control.tintColor = DcColors.primary
  25. control.addTarget(self, action: #selector(qrSegmentControlChanged), for: .valueChanged)
  26. control.selectedSegmentIndex = 0
  27. return control
  28. }()
  29. init(dcContext: DcContext) {
  30. self.dcContext = dcContext
  31. super.init(transitionStyle: .scroll, navigationOrientation: .horizontal, options: [:])
  32. }
  33. required init?(coder: NSCoder) {
  34. fatalError("init(coder:) has not been implemented")
  35. }
  36. // MARK: - lifecycle
  37. override func viewDidLoad() {
  38. super.viewDidLoad()
  39. dataSource = self
  40. delegate = self
  41. navigationItem.titleView = qrSegmentControl
  42. let qrController = QrViewController(dcContext: dcContext, qrCodeHint: qrCodeHint)
  43. setViewControllers(
  44. [qrController],
  45. direction: .forward,
  46. animated: true,
  47. completion: nil
  48. )
  49. }
  50. override func viewWillAppear(_ animated: Bool) {
  51. // QrCodeReaderController::viewWillAppear() is on called on section change, not on main-tab change
  52. if let qrCodeReaderController = self.qrCodeReaderController {
  53. qrCodeReaderController.startSession()
  54. }
  55. }
  56. override func viewWillDisappear(_ animated: Bool) {
  57. // QrCodeReaderController::viewWillDisappear() is on called on section change, not on main-tab change
  58. if let qrCodeReaderController = self.qrCodeReaderController {
  59. qrCodeReaderController.stopSession()
  60. }
  61. self.progressObserver = nil
  62. }
  63. // MARK: - actions
  64. @objc private func qrSegmentControlChanged(_ sender: UISegmentedControl) {
  65. if sender.selectedSegmentIndex == 0 {
  66. let qrController = QrViewController(dcContext: dcContext, qrCodeHint: qrCodeHint)
  67. setViewControllers([qrController], direction: .reverse, animated: true, completion: nil)
  68. } else {
  69. let qrCodeReaderController = makeQRReader()
  70. self.qrCodeReaderController = qrCodeReaderController
  71. setViewControllers([qrCodeReaderController], direction: .forward, animated: true, completion: nil)
  72. }
  73. }
  74. // MARK: - factory
  75. private func makeQRReader() -> QrCodeReaderController {
  76. let qrReader = QrCodeReaderController()
  77. qrReader.delegate = self
  78. return qrReader
  79. }
  80. // MARK: - coordinator
  81. private func showChats() {
  82. if let appDelegate = UIApplication.shared.delegate as? AppDelegate {
  83. appDelegate.appCoordinator.showTab(index: appDelegate.appCoordinator.chatsTab)
  84. }
  85. }
  86. private func showChat(chatId: Int) {
  87. if let appDelegate = UIApplication.shared.delegate as? AppDelegate {
  88. appDelegate.appCoordinator.showChat(chatId: chatId)
  89. }
  90. }
  91. }
  92. // MARK: - UIPageViewControllerDataSource, UIPageViewControllerDelegate
  93. extension QrPageController: UIPageViewControllerDataSource, UIPageViewControllerDelegate {
  94. func pageViewController(_ pageViewController: UIPageViewController, viewControllerBefore viewController: UIViewController) -> UIViewController? {
  95. if viewController is QrViewController {
  96. return nil
  97. }
  98. return QrViewController(dcContext: dcContext, qrCodeHint: qrCodeHint)
  99. }
  100. func pageViewController(_ pageViewController: UIPageViewController, viewControllerAfter viewController: UIViewController) -> UIViewController? {
  101. if viewController is QrViewController {
  102. return makeQRReader()
  103. }
  104. return nil
  105. }
  106. func pageViewController(_ pageViewController: UIPageViewController, didFinishAnimating finished: Bool, previousViewControllers: [UIViewController], transitionCompleted completed: Bool) {
  107. if completed {
  108. if previousViewControllers.first is QrViewController {
  109. qrSegmentControl.selectedSegmentIndex = 1
  110. } else {
  111. qrSegmentControl.selectedSegmentIndex = 0
  112. }
  113. }
  114. }
  115. }
  116. // MARK: - QRCodeDelegate
  117. extension QrPageController: QrCodeReaderDelegate {
  118. func handleQrCode(_ code: String) {
  119. self.showChats()
  120. let qrParsed: DcLot = self.dcContext.checkQR(qrCode: code)
  121. let state = Int32(qrParsed.state)
  122. switch state {
  123. case DC_QR_ASK_VERIFYCONTACT:
  124. let nameAndAddress = DcContact(id: qrParsed.id).nameNAddr
  125. joinSecureJoin(alertMessage: String.localizedStringWithFormat(String.localized("ask_start_chat_with"), nameAndAddress), code: code)
  126. case DC_QR_ASK_VERIFYGROUP:
  127. let groupName = qrParsed.text1 ?? "ErrGroupName"
  128. joinSecureJoin(alertMessage: String.localizedStringWithFormat(String.localized("qrscan_ask_join_group"), groupName), code: code)
  129. case DC_QR_FPR_WITHOUT_ADDR:
  130. let msg = String.localized("qrscan_no_addr_found") + "\n\n" +
  131. String.localized("qrscan_fingerprint_label") + ":\n" + (qrParsed.text1 ?? "")
  132. let alert = UIAlertController(title: msg, message: nil, preferredStyle: .alert)
  133. alert.addAction(UIAlertAction(title: String.localized("ok"), style: .default, handler: nil))
  134. present(alert, animated: true, completion: nil)
  135. case DC_QR_FPR_MISMATCH:
  136. let nameAndAddress = DcContact(id: qrParsed.id).nameNAddr
  137. let msg = String.localizedStringWithFormat(String.localized("qrscan_fingerprint_mismatch"), nameAndAddress)
  138. let alert = UIAlertController(title: msg, message: nil, preferredStyle: .alert)
  139. alert.addAction(UIAlertAction(title: String.localized("ok"), style: .default, handler: nil))
  140. present(alert, animated: true, completion: nil)
  141. case DC_QR_ADDR, DC_QR_FPR_OK:
  142. let nameAndAddress = DcContact(id: qrParsed.id).nameNAddr
  143. let msg = String.localizedStringWithFormat(String.localized(state==DC_QR_ADDR ? "ask_start_chat_with" : "qrshow_x_verified"), nameAndAddress)
  144. let alert = UIAlertController(title: msg, message: nil, preferredStyle: .alert)
  145. alert.addAction(UIAlertAction(title: String.localized("start_chat"), style: .default, handler: { _ in
  146. let chatId = self.dcContext.createChatByContactId(contactId: qrParsed.id)
  147. self.showChat(chatId: chatId)
  148. }))
  149. alert.addAction(UIAlertAction(title: String.localized("cancel"), style: .default, handler: nil))
  150. present(alert, animated: true, completion: nil)
  151. case DC_QR_TEXT:
  152. let msg = String.localizedStringWithFormat(String.localized("qrscan_contains_text"), qrParsed.text1 ?? "")
  153. let alert = UIAlertController(title: msg, message: nil, preferredStyle: .alert)
  154. alert.addAction(UIAlertAction(title: String.localized("ok"), style: .default, handler: nil))
  155. present(alert, animated: true, completion: nil)
  156. case DC_QR_URL:
  157. let url = qrParsed.text1 ?? ""
  158. let msg = String.localizedStringWithFormat(String.localized("qrscan_contains_url"), url)
  159. let alert = UIAlertController(title: msg, message: nil, preferredStyle: .alert)
  160. alert.addAction(UIAlertAction(title: String.localized("open"), style: .default, handler: { _ in
  161. if let url = URL(string: url) {
  162. UIApplication.shared.open(url)
  163. }
  164. }))
  165. alert.addAction(UIAlertAction(title: String.localized("cancel"), style: .default, handler: nil))
  166. present(alert, animated: true, completion: nil)
  167. case DC_QR_ACCOUNT:
  168. let alert = UIAlertController(title: String.localized("qraccount_use_on_new_install"), message: nil, preferredStyle: .alert)
  169. alert.addAction(UIAlertAction(title: String.localized("ok"), style: .default))
  170. present(alert, animated: true)
  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(title: String.localized("one_moment")+"\n\n", dcContext: self.dcContext)
  191. // execute blocking secure join in background
  192. DispatchQueue.global(qos: .background).async {
  193. self.addSecureJoinProgressListener()
  194. self.dcContext.lastErrorString = nil
  195. let chatId = self.dcContext.joinSecurejoin(qrCode: code)
  196. let errorString = self.dcContext.lastErrorString
  197. self.removeSecureJoinProgressListener()
  198. DispatchQueue.main.async {
  199. self.progressAlert?.dismiss(animated: true, completion: nil)
  200. if chatId != 0 {
  201. self.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 showErrorAlert(error: String) {
  211. let alert = UIAlertController(title: String.localized("error"), message: error, preferredStyle: .alert)
  212. alert.addAction(UIAlertAction(title: String.localized("ok"), style: .default, handler: { _ in
  213. alert.dismiss(animated: true, completion: nil)
  214. }))
  215. }
  216. private func addSecureJoinProgressListener() {
  217. let nc = NotificationCenter.default
  218. progressObserver = nc.addObserver(
  219. forName: dcNotificationSecureJoinerProgress,
  220. object: nil,
  221. queue: nil
  222. ) { [weak self] notification in
  223. guard let self = self else { return }
  224. if let ui = notification.userInfo,
  225. ui["progress"] as? Int == 400,
  226. let contactId = ui["contact_id"] as? Int {
  227. self.progressAlert?.message = String.localizedStringWithFormat(
  228. String.localized("qrscan_x_verified_introduce_myself"),
  229. DcContact(id: contactId).nameNAddr
  230. )
  231. }
  232. }
  233. }
  234. private func removeSecureJoinProgressListener() {
  235. let nc = NotificationCenter.default
  236. if let observer = self.progressObserver {
  237. nc.removeObserver(observer)
  238. }
  239. }
  240. }