NCMediaViewerPageViewController.swift 7.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191
  1. //
  2. // SPDX-FileCopyrightText: 2024 Nextcloud GmbH and Nextcloud contributors
  3. // SPDX-License-Identifier: GPL-3.0-or-later
  4. //
  5. import Foundation
  6. import UIKit
  7. import SwiftyGif
  8. @objc protocol NCMediaViewerPageViewControllerDelegate {
  9. @objc func mediaViewerPageZoomDidChange(_ controller: NCMediaViewerPageViewController, _ scale: Double)
  10. @objc func mediaViewerPageImageDidLoad(_ controller: NCMediaViewerPageViewController)
  11. }
  12. @objcMembers class NCMediaViewerPageViewController: UIViewController, NCChatFileControllerDelegate, NCZoomableViewDelegate {
  13. public weak var delegate: NCMediaViewerPageViewControllerDelegate?
  14. public let message: NCChatMessage
  15. private let fileDownloader = NCChatFileController()
  16. private lazy var zoomableView = {
  17. let zoomableView = NCZoomableView()
  18. zoomableView.translatesAutoresizingMaskIntoConstraints = false
  19. zoomableView.disablePanningOnInitialZoom = true
  20. zoomableView.delegate = self
  21. return zoomableView
  22. }()
  23. private lazy var imageView = {
  24. let imageView = UIImageView()
  25. imageView.contentMode = .scaleAspectFill
  26. imageView.isUserInteractionEnabled = true
  27. return imageView
  28. }()
  29. private lazy var errorView = {
  30. let errorView = UIView()
  31. errorView.translatesAutoresizingMaskIntoConstraints = false
  32. let iconConfiguration = UIImage.SymbolConfiguration(pointSize: 36)
  33. let errorImage = UIImageView()
  34. errorImage.image = UIImage(systemName: "photo")?.withConfiguration(iconConfiguration)
  35. errorImage.contentMode = .scaleAspectFit
  36. errorImage.translatesAutoresizingMaskIntoConstraints = false
  37. errorImage.tintColor = .secondaryLabel
  38. let errorText = UILabel()
  39. errorText.translatesAutoresizingMaskIntoConstraints = false
  40. errorText.text = NSLocalizedString("An error occurred downloading the picture", comment: "")
  41. errorView.addSubview(errorImage)
  42. errorView.addSubview(errorText)
  43. NSLayoutConstraint.activate([
  44. errorImage.topAnchor.constraint(equalTo: errorView.topAnchor),
  45. errorImage.widthAnchor.constraint(equalToConstant: 150),
  46. errorImage.heightAnchor.constraint(greaterThanOrEqualToConstant: 0),
  47. errorImage.centerXAnchor.constraint(equalTo: errorView.centerXAnchor),
  48. errorText.topAnchor.constraint(equalTo: errorImage.bottomAnchor, constant: 10),
  49. errorText.bottomAnchor.constraint(equalTo: errorView.bottomAnchor),
  50. errorText.centerXAnchor.constraint(equalTo: errorView.centerXAnchor)
  51. ])
  52. return errorView
  53. }()
  54. public var currentImage: UIImage? {
  55. return self.imageView.image
  56. }
  57. private lazy var activityIndicator = {
  58. let indicator = NCActivityIndicator(frame: .init(x: 0, y: 0, width: 100, height: 100))
  59. indicator.translatesAutoresizingMaskIntoConstraints = false
  60. indicator.cycleColors = [.lightGray]
  61. return indicator
  62. }()
  63. init(message: NCChatMessage) {
  64. self.message = message
  65. super.init(nibName: nil, bundle: nil)
  66. }
  67. required init?(coder: NSCoder) {
  68. fatalError("init(coder:) has not been implemented")
  69. }
  70. override func viewDidLoad() {
  71. self.view.addSubview(self.zoomableView)
  72. self.view.addSubview(self.activityIndicator)
  73. NSLayoutConstraint.activate([
  74. self.zoomableView.leftAnchor.constraint(equalTo: self.view.safeAreaLayoutGuide.leftAnchor),
  75. self.zoomableView.rightAnchor.constraint(equalTo: self.view.safeAreaLayoutGuide.rightAnchor),
  76. self.zoomableView.topAnchor.constraint(equalTo: self.view.safeAreaLayoutGuide.topAnchor),
  77. self.zoomableView.bottomAnchor.constraint(equalTo: self.view.safeAreaLayoutGuide.bottomAnchor),
  78. self.activityIndicator.centerXAnchor.constraint(equalTo: self.view.safeAreaLayoutGuide.centerXAnchor),
  79. self.activityIndicator.centerYAnchor.constraint(equalTo: self.view.safeAreaLayoutGuide.centerYAnchor)
  80. ])
  81. self.zoomableView.replaceContentView(self.imageView)
  82. self.activityIndicator.startAnimating()
  83. fileDownloader.delegate = self
  84. fileDownloader.downloadFile(fromMessage: self.message.file())
  85. self.navigationItem.title = self.message.file().name
  86. NotificationCenter.default.addObserver(self, selector: #selector(didChangeDownloadProgress(notification:)), name: NSNotification.Name.NCChatFileControllerDidChangeDownloadProgress, object: nil)
  87. }
  88. override func viewDidLayoutSubviews() {
  89. // Make sure we have the correct bounds and center the view correctly
  90. self.zoomableView.resizeContentView()
  91. }
  92. func showErrorView() {
  93. self.imageView.image = nil
  94. self.view.addSubview(self.errorView)
  95. NSLayoutConstraint.activate([
  96. self.errorView.leadingAnchor.constraint(greaterThanOrEqualTo: self.view.safeAreaLayoutGuide.leadingAnchor, constant: 10),
  97. self.errorView.trailingAnchor.constraint(greaterThanOrEqualTo: self.view.safeAreaLayoutGuide.trailingAnchor, constant: -10),
  98. self.errorView.centerXAnchor.constraint(equalTo: self.view.safeAreaLayoutGuide.centerXAnchor),
  99. self.errorView.centerYAnchor.constraint(equalTo: self.view.safeAreaLayoutGuide.centerYAnchor)
  100. ])
  101. }
  102. // MARK: - NCChatFileController delegate
  103. func fileControllerDidLoadFile(_ fileController: NCChatFileController, with fileStatus: NCChatFileStatus) {
  104. self.activityIndicator.stopAnimating()
  105. self.activityIndicator.isHidden = true
  106. guard let localPath = fileStatus.fileLocalPath, let image = UIImage(contentsOfFile: localPath) else {
  107. self.showErrorView()
  108. return
  109. }
  110. if let file = message.file(), message.isAnimatableGif,
  111. let data = try? Data(contentsOf: URL(fileURLWithPath: localPath)), let gifImage = try? UIImage(gifData: data) {
  112. self.imageView.setGifImage(gifImage)
  113. } else {
  114. self.imageView.image = image
  115. }
  116. // Adjust the view to the new image (use the non-gif version here for correct dimensions)
  117. self.zoomableView.contentViewSize = image.size
  118. self.zoomableView.resizeContentView()
  119. self.delegate?.mediaViewerPageImageDidLoad(self)
  120. }
  121. func fileControllerDidFailLoadingFile(_ fileController: NCChatFileController, withErrorDescription errorDescription: String) {
  122. self.activityIndicator.stopAnimating()
  123. self.activityIndicator.isHidden = true
  124. self.showErrorView()
  125. print("Error downloading picture: " + errorDescription)
  126. }
  127. func didChangeDownloadProgress(notification: Notification) {
  128. DispatchQueue.main.async {
  129. // Make sure this notification is really for this view controller
  130. guard let userInfo = notification.userInfo,
  131. let receivedStatus = userInfo["fileStatus"] as? NCChatFileStatus,
  132. let fileParameter = self.message.file(),
  133. receivedStatus.fileId == fileParameter.parameterId,
  134. receivedStatus.filePath == fileParameter.path
  135. else { return }
  136. // Switch to determinate mode and set the progress
  137. if receivedStatus.canReportProgress {
  138. self.activityIndicator.indicatorMode = .determinate
  139. self.activityIndicator.setProgress(Float(receivedStatus.downloadProgress), animated: true)
  140. }
  141. }
  142. }
  143. // MARK: - NCZoomableView delegate
  144. func contentViewZoomDidChange(_ view: NCZoomableView, _ scale: Double) {
  145. self.delegate?.mediaViewerPageZoomDidChange(self, scale)
  146. }
  147. }