QrPageController.swift 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307
  1. import UIKit
  2. import DcCore
  3. class QrPageController: UIPageViewController {
  4. private let dcContext: DcContext
  5. var progressObserver: NSObjectProtocol?
  6. var qrCodeReaderController: QrCodeReaderController?
  7. private var selectedIndex: Int = 0
  8. private var qrCodeHint: String {
  9. var qrCodeHint = ""
  10. if dcContext.isConfigured() {
  11. // we cannot use dc_contact_get_displayname() as this would result in "Me" instead of the real name
  12. let name = dcContext.getConfig("displayname") ?? ""
  13. let addr = dcContext.getConfig("addr") ?? ""
  14. var nameAndAddress = ""
  15. if name.isEmpty {
  16. nameAndAddress = addr
  17. } else {
  18. nameAndAddress = "\(name) (\(addr))"
  19. }
  20. qrCodeHint = String.localizedStringWithFormat(
  21. String.localized("qrshow_join_contact_hint"),
  22. nameAndAddress
  23. )
  24. }
  25. return qrCodeHint
  26. }
  27. private lazy var qrSegmentControl: UISegmentedControl = {
  28. let control = UISegmentedControl(
  29. items: [String.localized("qrshow_title"), String.localized("qrscan_title")]
  30. )
  31. control.tintColor = DcColors.primary
  32. control.addTarget(self, action: #selector(qrSegmentControlChanged), for: .valueChanged)
  33. control.selectedSegmentIndex = 0
  34. return control
  35. }()
  36. init(dcContext: DcContext) {
  37. self.dcContext = dcContext
  38. super.init(transitionStyle: .scroll, navigationOrientation: .horizontal, options: [:])
  39. }
  40. required init?(coder: NSCoder) {
  41. fatalError("init(coder:) has not been implemented")
  42. }
  43. // MARK: - lifecycle
  44. override func viewDidLoad() {
  45. super.viewDidLoad()
  46. dataSource = self
  47. delegate = self
  48. navigationItem.titleView = qrSegmentControl
  49. let qrController = QrViewController(dcContext: dcContext, qrCodeHint: qrCodeHint)
  50. setViewControllers(
  51. [qrController],
  52. direction: .forward,
  53. animated: true,
  54. completion: nil
  55. )
  56. }
  57. override func viewWillAppear(_ animated: Bool) {
  58. // QrCodeReaderController::viewWillAppear() is on called on section change, not on main-tab change
  59. if let qrCodeReaderController = self.qrCodeReaderController {
  60. qrCodeReaderController.startSession()
  61. }
  62. updateHintTextIfNeeded() // needed in case user changes profile name
  63. }
  64. override func viewWillDisappear(_ animated: Bool) {
  65. // QrCodeReaderController::viewWillDisappear() is on called on section change, not on main-tab change
  66. if let qrCodeReaderController = self.qrCodeReaderController {
  67. qrCodeReaderController.stopSession()
  68. }
  69. self.progressObserver = nil
  70. }
  71. // MARK: - actions
  72. @objc private func qrSegmentControlChanged(_ sender: UISegmentedControl) {
  73. if sender.selectedSegmentIndex == 0 {
  74. let qrController = QrViewController(dcContext: dcContext, qrCodeHint: qrCodeHint)
  75. setViewControllers([qrController], direction: .reverse, animated: true, completion: nil)
  76. } else {
  77. let qrCodeReaderController = makeQRReader()
  78. self.qrCodeReaderController = qrCodeReaderController
  79. setViewControllers([qrCodeReaderController], direction: .forward, animated: false, completion: nil)
  80. }
  81. }
  82. // MARK: - factory
  83. private func makeQRReader() -> QrCodeReaderController {
  84. let qrReader = QrCodeReaderController()
  85. qrReader.delegate = self
  86. return qrReader
  87. }
  88. // MARK: - update
  89. private func updateHintTextIfNeeded() {
  90. for case let qrViewController as QrViewController in self.viewControllers ?? [] {
  91. let newHint = qrCodeHint
  92. if qrCodeHint != qrViewController.qrCodeHint {
  93. qrViewController.qrCodeHint = newHint
  94. }
  95. }
  96. }
  97. // MARK: - coordinator
  98. private func showChats() {
  99. if let appDelegate = UIApplication.shared.delegate as? AppDelegate {
  100. appDelegate.appCoordinator.showTab(index: appDelegate.appCoordinator.chatsTab)
  101. }
  102. }
  103. private func showChat(chatId: Int) {
  104. if let appDelegate = UIApplication.shared.delegate as? AppDelegate {
  105. appDelegate.appCoordinator.showChat(chatId: chatId, animated: false, clearViewControllerStack: true)
  106. }
  107. }
  108. }
  109. // MARK: - UIPageViewControllerDataSource, UIPageViewControllerDelegate
  110. extension QrPageController: UIPageViewControllerDataSource, UIPageViewControllerDelegate {
  111. func pageViewController(_ pageViewController: UIPageViewController, viewControllerBefore viewController: UIViewController) -> UIViewController? {
  112. if viewController is QrViewController {
  113. return nil
  114. }
  115. return QrViewController(dcContext: dcContext, qrCodeHint: qrCodeHint)
  116. }
  117. func pageViewController(_ pageViewController: UIPageViewController, viewControllerAfter viewController: UIViewController) -> UIViewController? {
  118. if viewController is QrViewController {
  119. return makeQRReader()
  120. }
  121. return nil
  122. }
  123. func pageViewController(_ pageViewController: UIPageViewController, didFinishAnimating finished: Bool, previousViewControllers: [UIViewController], transitionCompleted completed: Bool) {
  124. if completed {
  125. if previousViewControllers.first is QrViewController {
  126. qrSegmentControl.selectedSegmentIndex = 1
  127. } else {
  128. qrSegmentControl.selectedSegmentIndex = 0
  129. }
  130. }
  131. }
  132. }
  133. // MARK: - QRCodeDelegate
  134. extension QrPageController: QrCodeReaderDelegate {
  135. func handleQrCode(_ code: String) {
  136. self.showChats()
  137. let qrParsed: DcLot = self.dcContext.checkQR(qrCode: code)
  138. let state = Int32(qrParsed.state)
  139. switch state {
  140. case DC_QR_ASK_VERIFYCONTACT:
  141. let nameAndAddress = dcContext.getContact(id: qrParsed.id).nameNAddr
  142. joinSecureJoin(alertMessage: String.localizedStringWithFormat(String.localized("ask_start_chat_with"), nameAndAddress), code: code)
  143. case DC_QR_ASK_VERIFYGROUP:
  144. let groupName = qrParsed.text1 ?? "ErrGroupName"
  145. joinSecureJoin(alertMessage: String.localizedStringWithFormat(String.localized("qrscan_ask_join_group"), groupName), code: code)
  146. case DC_QR_FPR_WITHOUT_ADDR:
  147. let msg = String.localized("qrscan_no_addr_found") + "\n\n" +
  148. String.localized("qrscan_fingerprint_label") + ":\n" + (qrParsed.text1 ?? "")
  149. let alert = UIAlertController(title: msg, message: nil, preferredStyle: .alert)
  150. alert.addAction(UIAlertAction(title: String.localized("ok"), style: .default, handler: nil))
  151. present(alert, animated: true, completion: nil)
  152. case DC_QR_FPR_MISMATCH:
  153. let nameAndAddress = dcContext.getContact(id: qrParsed.id).nameNAddr
  154. let msg = String.localizedStringWithFormat(String.localized("qrscan_fingerprint_mismatch"), nameAndAddress)
  155. let alert = UIAlertController(title: msg, message: nil, preferredStyle: .alert)
  156. alert.addAction(UIAlertAction(title: String.localized("ok"), style: .default, handler: nil))
  157. present(alert, animated: true, completion: nil)
  158. case DC_QR_ADDR, DC_QR_FPR_OK:
  159. let nameAndAddress = dcContext.getContact(id: qrParsed.id).nameNAddr
  160. let msg = String.localizedStringWithFormat(String.localized(state==DC_QR_ADDR ? "ask_start_chat_with" : "qrshow_x_verified"), nameAndAddress)
  161. let alert = UIAlertController(title: msg, message: nil, preferredStyle: .alert)
  162. alert.addAction(UIAlertAction(title: String.localized("start_chat"), style: .default, handler: { _ in
  163. let chatId = self.dcContext.createChatByContactId(contactId: qrParsed.id)
  164. self.showChat(chatId: chatId)
  165. }))
  166. alert.addAction(UIAlertAction(title: String.localized("cancel"), style: .default, handler: nil))
  167. present(alert, animated: true, completion: nil)
  168. case DC_QR_TEXT:
  169. let msg = String.localizedStringWithFormat(String.localized("qrscan_contains_text"), qrParsed.text1 ?? "")
  170. let alert = UIAlertController(title: msg, message: nil, preferredStyle: .alert)
  171. alert.addAction(UIAlertAction(title: String.localized("ok"), style: .default, handler: nil))
  172. present(alert, animated: true, completion: nil)
  173. case DC_QR_URL:
  174. let url = qrParsed.text1 ?? ""
  175. let msg = String.localizedStringWithFormat(String.localized("qrscan_contains_url"), url)
  176. let alert = UIAlertController(title: msg, message: nil, preferredStyle: .alert)
  177. alert.addAction(UIAlertAction(title: String.localized("open"), style: .default, handler: { _ in
  178. if let url = URL(string: url) {
  179. UIApplication.shared.open(url)
  180. }
  181. }))
  182. alert.addAction(UIAlertAction(title: String.localized("cancel"), style: .default, handler: nil))
  183. present(alert, animated: true, completion: nil)
  184. case DC_QR_ACCOUNT:
  185. let alert = UIAlertController(title: String.localized("qraccount_use_on_new_install"), message: nil, preferredStyle: .alert)
  186. alert.addAction(UIAlertAction(title: String.localized("ok"), style: .default))
  187. present(alert, animated: true)
  188. case DC_QR_WEBRTC_INSTANCE:
  189. guard let domain = qrParsed.text1 else { return }
  190. let alert = UIAlertController(title: String.localizedStringWithFormat(String.localized("videochat_instance_from_qr"), domain),
  191. message: nil,
  192. preferredStyle: .alert)
  193. alert.addAction(UIAlertAction(title: String.localized("cancel"), style: .default))
  194. alert.addAction(UIAlertAction(title: String.localized("ok"), style: .default, handler: { [weak self] _ in
  195. guard let self = self else { return }
  196. let success = self.dcContext.setConfigFromQR(qrCode: code)
  197. if !success {
  198. logger.warning("Could not set webrtc instance from QR code.")
  199. // TODO: alert?!
  200. }
  201. }))
  202. present(alert, animated: true)
  203. case DC_QR_WITHDRAW_VERIFYCONTACT:
  204. let alert = UIAlertController(title: String.localized("withdraw_verifycontact_explain"),
  205. message: nil, preferredStyle: .alert)
  206. alert.addAction(UIAlertAction(title: String.localized("cancel"), style: .default))
  207. alert.addAction(UIAlertAction(title: String.localized("withdraw_qr_code"), style: .destructive, handler: { [weak self] _ in
  208. _ = self?.dcContext.setConfigFromQR(qrCode: code)
  209. }))
  210. present(alert, animated: true)
  211. case DC_QR_REVIVE_VERIFYCONTACT:
  212. let alert = UIAlertController(title: String.localized("revive_verifycontact_explain"),
  213. message: nil, preferredStyle: .alert)
  214. alert.addAction(UIAlertAction(title: String.localized("cancel"), style: .default))
  215. alert.addAction(UIAlertAction(title: String.localized("revive_qr_code"), style: .default, handler: { [weak self] _ in
  216. _ = self?.dcContext.setConfigFromQR(qrCode: code)
  217. }))
  218. present(alert, animated: true)
  219. case DC_QR_WITHDRAW_VERIFYGROUP:
  220. guard let groupName = qrParsed.text1 else { return }
  221. let alert = UIAlertController(title: String.localizedStringWithFormat(String.localized("withdraw_verifygroup_explain"), groupName),
  222. message: nil, preferredStyle: .alert)
  223. alert.addAction(UIAlertAction(title: String.localized("cancel"), style: .default))
  224. alert.addAction(UIAlertAction(title: String.localized("withdraw_qr_code"), style: .destructive, handler: { [weak self] _ in
  225. _ = self?.dcContext.setConfigFromQR(qrCode: code)
  226. }))
  227. present(alert, animated: true)
  228. case DC_QR_REVIVE_VERIFYGROUP:
  229. guard let groupName = qrParsed.text1 else { return }
  230. let alert = UIAlertController(title: String.localizedStringWithFormat(String.localized("revive_verifygroup_explain"), groupName),
  231. message: nil, preferredStyle: .alert)
  232. alert.addAction(UIAlertAction(title: String.localized("cancel"), style: .default))
  233. alert.addAction(UIAlertAction(title: String.localized("revive_qr_code"), style: .default, handler: { [weak self] _ in
  234. _ = self?.dcContext.setConfigFromQR(qrCode: code)
  235. }))
  236. present(alert, animated: true)
  237. default:
  238. var msg = String.localizedStringWithFormat(String.localized("qrscan_contains_text"), code)
  239. if state == DC_QR_ERROR {
  240. if let errorMsg = qrParsed.text1 {
  241. msg = errorMsg + "\n\n" + msg
  242. }
  243. }
  244. let alert = UIAlertController(title: msg, message: nil, preferredStyle: .alert)
  245. alert.addAction(UIAlertAction(title: String.localized("ok"), style: .default, handler: nil))
  246. present(alert, animated: true, completion: nil)
  247. }
  248. }
  249. private func joinSecureJoin(alertMessage: String, code: String) {
  250. let alert = UIAlertController(title: alertMessage,
  251. message: nil,
  252. preferredStyle: .alert)
  253. alert.addAction(UIAlertAction(title: String.localized("cancel"), style: .default, handler: nil))
  254. alert.addAction(UIAlertAction(title: String.localized("ok"), style: .default, handler: { _ in
  255. self.dcContext.lastErrorString = nil
  256. let chatId = self.dcContext.joinSecurejoin(qrCode: code)
  257. if chatId != 0 {
  258. self.showChat(chatId: chatId)
  259. } else {
  260. self.showErrorAlert(error: self.dcContext.lastErrorString ?? "ErrJoinNoString")
  261. }
  262. }))
  263. present(alert, animated: true, completion: nil)
  264. }
  265. private func showErrorAlert(error: String) {
  266. let alert = UIAlertController(title: String.localized("error"), message: error, preferredStyle: .alert)
  267. alert.addAction(UIAlertAction(title: String.localized("ok"), style: .default, handler: { _ in
  268. alert.dismiss(animated: true, completion: nil)
  269. }))
  270. }
  271. }