NCViewerQuickLook.swift 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272
  1. //
  2. // NCViewerQuickLook.swift
  3. // Nextcloud
  4. //
  5. // Created by Marino Faggiana on 03/05/2020.
  6. // Copyright © 2020 Marino Faggiana. All rights reserved.
  7. // Copyright © 2022 Henrik Storch. All rights reserved.
  8. // Copyright © 2023 Marino Faggiana. All rights reserved.
  9. //
  10. // Author Marino Faggiana <marino.faggiana@nextcloud.com>
  11. // Author Henrik Storch <henrik.storch@nextcloud.com>
  12. //
  13. // This program is free software: you can redistribute it and/or modify
  14. // it under the terms of the GNU General Public License as published by
  15. // the Free Software Foundation, either version 3 of the License, or
  16. // (at your option) any later version.
  17. //
  18. // This program is distributed in the hope that it will be useful,
  19. // but WITHOUT ANY WARRANTY; without even the implied warranty of
  20. // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  21. // GNU General Public License for more details.
  22. //
  23. // You should have received a copy of the GNU General Public License
  24. // along with this program. If not, see <http://www.gnu.org/licenses/>.
  25. //
  26. import UIKit
  27. import QuickLook
  28. import NextcloudKit
  29. import Mantis
  30. import SwiftUI
  31. // if the document has any changes
  32. private var hasChangesQuickLook: Bool = false
  33. @objc class NCViewerQuickLook: QLPreviewController {
  34. let url: URL
  35. var previewItems: [PreviewItem] = []
  36. var isEditingEnabled: Bool
  37. var isCropEnabled: Bool
  38. var metadata: tableMetadata?
  39. var timer: Timer?
  40. required init?(coder: NSCoder) {
  41. fatalError("init(coder:) has not been implemented")
  42. }
  43. @objc init(with url: URL, isEditingEnabled: Bool, isCropEnabled: Bool, metadata: tableMetadata?) {
  44. self.url = url
  45. self.isEditingEnabled = isEditingEnabled
  46. self.isCropEnabled = isCropEnabled
  47. if let metadata = metadata {
  48. self.metadata = tableMetadata.init(value: metadata)
  49. }
  50. let previewItem = PreviewItem()
  51. previewItem.previewItemURL = url
  52. self.previewItems.append(previewItem)
  53. super.init(nibName: nil, bundle: nil)
  54. self.dataSource = self
  55. self.delegate = self
  56. self.currentPreviewItemIndex = 0
  57. hasChangesQuickLook = false
  58. }
  59. override func viewDidLoad() {
  60. super.viewDidLoad()
  61. guard isEditingEnabled else { return }
  62. if metadata?.livePhoto == true {
  63. let error = NKError(errorCode: NCGlobal.shared.errorCharactersForbidden, errorDescription: "_message_disable_overwrite_livephoto_")
  64. NCContentPresenter.shared.showInfo(error: error)
  65. }
  66. if isCropEnabled {
  67. let buttonDone = UIBarButtonItem(barButtonSystemItem: .done, target: self, action: #selector(dismission))
  68. if metadata?.classFile == NKCommon.typeClassFile.image.rawValue {
  69. let buttonCrop = UIBarButtonItem(image: UIImage(systemName: "crop"), style: .plain, target: self, action: #selector(crop))
  70. navigationItem.leftBarButtonItems = [buttonDone, buttonCrop]
  71. } else {
  72. navigationItem.leftBarButtonItems = [buttonDone]
  73. }
  74. startTimer(navigationItem: navigationItem)
  75. }
  76. }
  77. func startTimer(navigationItem: UINavigationItem) {
  78. self.timer = Timer.scheduledTimer(withTimeInterval: 0.5, repeats: true, block: { _ in
  79. guard let buttonDone = navigationItem.leftBarButtonItems?.first, let buttonCrop = navigationItem.leftBarButtonItems?.last else { return }
  80. buttonCrop.isEnabled = true
  81. buttonDone.isEnabled = true
  82. if let markup = navigationItem.rightBarButtonItems?.first(where: { $0.accessibilityIdentifier == "QLOverlayMarkupButtonAccessibilityIdentifier" }) {
  83. if let originalButton = markup.value(forKey: "originalButton") as AnyObject? {
  84. if let symbolImageName = originalButton.value(forKey: "symbolImageName") as? String {
  85. if symbolImageName == "pencil.tip.crop.circle.on" {
  86. buttonCrop.isEnabled = false
  87. buttonDone.isEnabled = false
  88. }
  89. }
  90. }
  91. }
  92. })
  93. }
  94. func stopTimer() {
  95. self.timer?.invalidate()
  96. }
  97. @objc func dismission() {
  98. guard isEditingEnabled, hasChangesQuickLook, let metadata = metadata else {
  99. dismiss(animated: true)
  100. return
  101. }
  102. let alertController = UIAlertController(title: NSLocalizedString("_save_", comment: ""), message: nil, preferredStyle: .alert)
  103. var message: String?
  104. if metadata.livePhoto {
  105. message = NSLocalizedString("_message_disable_overwrite_livephoto_", comment: "")
  106. } else if metadata.lock {
  107. message = NSLocalizedString("_file_locked_no_override_", comment: "")
  108. } else {
  109. alertController.addAction(UIAlertAction(title: NSLocalizedString("_overwrite_original_", comment: ""), style: .default) { _ in
  110. self.saveModifiedFile(override: true)
  111. })
  112. }
  113. alertController.message = message
  114. alertController.addAction(UIAlertAction(title: NSLocalizedString("_save_as_copy_", comment: ""), style: .default) { _ in
  115. self.saveModifiedFile(override: false)
  116. })
  117. alertController.addAction(UIAlertAction(title: NSLocalizedString("_cancel_", comment: ""), style: .cancel) { _ in
  118. })
  119. alertController.addAction(UIAlertAction(title: NSLocalizedString("_discard_changes_", comment: ""), style: .destructive) { _ in
  120. self.dismiss(animated: true)
  121. })
  122. present(alertController, animated: true)
  123. }
  124. @objc func crop() {
  125. guard let image = UIImage(contentsOfFile: url.path) else { return }
  126. var toolbarConfig = CropToolbarConfig()
  127. toolbarConfig.heightForVerticalOrientation = 80
  128. toolbarConfig.widthForHorizontalOrientation = 100
  129. toolbarConfig.optionButtonFontSize = 16
  130. toolbarConfig.optionButtonFontSizeForPad = 21
  131. toolbarConfig.backgroundColor = .systemGray6
  132. toolbarConfig.foregroundColor = .systemBlue
  133. var viewConfig = CropViewConfig()
  134. viewConfig.cropMaskVisualEffectType = .none
  135. viewConfig.cropBorderColor = .red
  136. var config = Mantis.Config()
  137. if let bundleIdentifier = Bundle.main.bundleIdentifier {
  138. config.localizationConfig.bundle = Bundle(identifier: bundleIdentifier)
  139. config.localizationConfig.tableName = "Localizable"
  140. }
  141. config.cropToolbarConfig = toolbarConfig
  142. config.cropViewConfig = viewConfig
  143. let toolbar = CropToolbar()
  144. toolbar.iconProvider = CropToolbarIcon()
  145. let cropViewController = Mantis.cropViewController(image: image, config: config, cropToolbar: toolbar)
  146. cropViewController.delegate = self
  147. cropViewController.backgroundColor = .systemBackground
  148. cropViewController.modalPresentationStyle = .fullScreen
  149. self.present(cropViewController, animated: true)
  150. }
  151. }
  152. extension NCViewerQuickLook: QLPreviewControllerDataSource, QLPreviewControllerDelegate {
  153. func numberOfPreviewItems(in controller: QLPreviewController) -> Int {
  154. previewItems.count
  155. }
  156. func previewController(_ controller: QLPreviewController, previewItemAt index: Int) -> QLPreviewItem {
  157. previewItems[index]
  158. }
  159. func previewController(_ controller: QLPreviewController, editingModeFor previewItem: QLPreviewItem) -> QLPreviewItemEditingMode {
  160. return isEditingEnabled ? .createCopy : .disabled
  161. }
  162. fileprivate func saveModifiedFile(override: Bool) {
  163. guard let metadata = self.metadata else { return }
  164. let ocId = NSUUID().uuidString
  165. let size = NCUtilityFileSystem.shared.getFileSize(filePath: url.path)
  166. if !override {
  167. let fileName = NCUtilityFileSystem.shared.createFileName(metadata.fileNameView, serverUrl: metadata.serverUrl, account: metadata.account)
  168. metadata.fileName = fileName
  169. metadata.fileNameView = fileName
  170. }
  171. guard let fileNamePath = CCUtility.getDirectoryProviderStorageOcId(ocId, fileNameView: metadata.fileNameView),
  172. NCUtilityFileSystem.shared.copyFile(atPath: url.path, toPath: fileNamePath) else { return }
  173. let metadataForUpload = NCManageDatabase.shared.createMetadata(
  174. account: metadata.account,
  175. user: metadata.user,
  176. userId: metadata.userId,
  177. fileName: metadata.fileName,
  178. fileNameView: metadata.fileNameView,
  179. ocId: ocId,
  180. serverUrl: metadata.serverUrl,
  181. urlBase: metadata.urlBase,
  182. url: url.path,
  183. contentType: "")
  184. metadataForUpload.session = NCNetworking.shared.sessionIdentifierBackground
  185. if override {
  186. metadataForUpload.sessionSelector = NCGlobal.shared.selectorUploadFileNODelete
  187. } else {
  188. metadataForUpload.sessionSelector = NCGlobal.shared.selectorUploadFile
  189. }
  190. metadataForUpload.size = size
  191. metadataForUpload.status = NCGlobal.shared.metadataStatusWaitUpload
  192. NCNetworkingProcessUpload.shared.createProcessUploads(metadatas: [metadataForUpload]) { _ in
  193. self.dismiss(animated: true)
  194. }
  195. }
  196. func previewController(_ controller: QLPreviewController, didSaveEditedCopyOf previewItem: QLPreviewItem, at modifiedContentsURL: URL) {
  197. // easier to handle that way than to use `.updateContents`
  198. // needs to be moved otherwise it will only be called once!
  199. guard NCUtilityFileSystem.shared.moveFile(atPath: modifiedContentsURL.path, toPath: url.path) else { return }
  200. hasChangesQuickLook = true
  201. }
  202. }
  203. extension NCViewerQuickLook: CropViewControllerDelegate {
  204. func cropViewControllerDidCrop(_ cropViewController: Mantis.CropViewController, cropped: UIImage, transformation: Mantis.Transformation, cropInfo: Mantis.CropInfo) {
  205. cropViewController.dismiss(animated: true)
  206. guard let data = cropped.jpegData(compressionQuality: 1) else { return }
  207. do {
  208. try data.write(to: self.url)
  209. hasChangesQuickLook = true
  210. reloadData()
  211. } catch { }
  212. }
  213. func cropViewControllerDidCancel(_ cropViewController: Mantis.CropViewController, original: UIImage) {
  214. cropViewController.dismiss(animated: true)
  215. }
  216. }
  217. class PreviewItem: NSObject, QLPreviewItem {
  218. var previewItemURL: URL?
  219. }
  220. class CropToolbarIcon: CropToolbarIconProvider {
  221. func getCropIcon() -> UIImage? {
  222. return UIImage(systemName: "checkmark.circle")
  223. }
  224. func getCancelIcon() -> UIImage? {
  225. return UIImage(systemName: "xmark.circle")
  226. }
  227. }