NCViewerQuickLook.swift 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270
  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 metadata: tableMetadata?
  38. var timer: Timer?
  39. required init?(coder: NSCoder) {
  40. fatalError("init(coder:) has not been implemented")
  41. }
  42. @objc init(with url: URL, isEditingEnabled: Bool, metadata: tableMetadata?) {
  43. self.url = url
  44. self.isEditingEnabled = isEditingEnabled
  45. if let metadata = metadata {
  46. self.metadata = tableMetadata.init(value: metadata)
  47. }
  48. let previewItem = PreviewItem()
  49. previewItem.previewItemURL = url
  50. self.previewItems.append(previewItem)
  51. super.init(nibName: nil, bundle: nil)
  52. self.dataSource = self
  53. self.delegate = self
  54. self.currentPreviewItemIndex = 0
  55. hasChangesQuickLook = false
  56. }
  57. override func viewDidLoad() {
  58. super.viewDidLoad()
  59. guard isEditingEnabled else { return }
  60. if metadata?.livePhoto == true {
  61. let error = NKError(errorCode: NCGlobal.shared.errorCharactersForbidden, errorDescription: "_message_disable_overwrite_livephoto_")
  62. NCContentPresenter.shared.showInfo(error: error)
  63. }
  64. let buttonDone = UIBarButtonItem(barButtonSystemItem: .done, target: self, action: #selector(dismission))
  65. if metadata?.classFile == NKCommon.typeClassFile.image.rawValue {
  66. let buttonCrop = UIBarButtonItem(image: UIImage(systemName: "crop"), style: .plain, target: self, action: #selector(crop))
  67. navigationItem.leftBarButtonItems = [buttonDone, buttonCrop]
  68. } else {
  69. navigationItem.leftBarButtonItems = [buttonDone]
  70. }
  71. startTimer(navigationItem: navigationItem)
  72. }
  73. func startTimer(navigationItem: UINavigationItem) {
  74. self.timer = Timer.scheduledTimer(withTimeInterval: 0.5, repeats: true, block: { _ in
  75. guard let buttonDone = navigationItem.leftBarButtonItems?.first, let buttonCrop = navigationItem.leftBarButtonItems?.last else { return }
  76. buttonCrop.isEnabled = true
  77. buttonDone.isEnabled = true
  78. if let markup = navigationItem.rightBarButtonItems?.first(where: { $0.accessibilityIdentifier == "QLOverlayMarkupButtonAccessibilityIdentifier" }) {
  79. if let originalButton = markup.value(forKey: "originalButton") as AnyObject? {
  80. if let symbolImageName = originalButton.value(forKey: "symbolImageName") as? String {
  81. if symbolImageName == "pencil.tip.crop.circle.on" {
  82. buttonCrop.isEnabled = false
  83. buttonDone.isEnabled = false
  84. }
  85. }
  86. }
  87. }
  88. })
  89. }
  90. func stopTimer() {
  91. self.timer?.invalidate()
  92. }
  93. @objc func dismission() {
  94. guard isEditingEnabled, hasChangesQuickLook, let metadata = metadata else {
  95. dismiss(animated: true)
  96. return
  97. }
  98. let alertController = UIAlertController(title: NSLocalizedString("_save_", comment: ""), message: nil, preferredStyle: .alert)
  99. var message: String?
  100. if metadata.livePhoto {
  101. message = NSLocalizedString("_message_disable_overwrite_livephoto_", comment: "")
  102. } else if metadata.lock {
  103. message = NSLocalizedString("_file_locked_no_override_", comment: "")
  104. } else {
  105. alertController.addAction(UIAlertAction(title: NSLocalizedString("_overwrite_original_", comment: ""), style: .default) { _ in
  106. self.saveModifiedFile(override: true)
  107. })
  108. }
  109. alertController.message = message
  110. alertController.addAction(UIAlertAction(title: NSLocalizedString("_save_as_copy_", comment: ""), style: .default) { _ in
  111. self.saveModifiedFile(override: false)
  112. })
  113. alertController.addAction(UIAlertAction(title: NSLocalizedString("_cancel_", comment: ""), style: .cancel) { _ in
  114. })
  115. alertController.addAction(UIAlertAction(title: NSLocalizedString("_discard_changes_", comment: ""), style: .destructive) { _ in
  116. self.dismiss(animated: true)
  117. })
  118. present(alertController, animated: true)
  119. }
  120. @objc func crop() {
  121. guard let image = UIImage(contentsOfFile: url.path) else { return }
  122. var toolbarConfig = CropToolbarConfig()
  123. toolbarConfig.heightForVerticalOrientation = 80
  124. toolbarConfig.widthForHorizontalOrientation = 100
  125. toolbarConfig.optionButtonFontSize = 16
  126. toolbarConfig.optionButtonFontSizeForPad = 21
  127. toolbarConfig.backgroundColor = .systemGray6
  128. toolbarConfig.foregroundColor = .systemBlue
  129. var viewConfig = CropViewConfig()
  130. viewConfig.cropMaskVisualEffectType = .none
  131. viewConfig.cropBorderColor = .red
  132. var config = Mantis.Config()
  133. if let bundleIdentifier = Bundle.main.bundleIdentifier {
  134. config.localizationConfig.bundle = Bundle(identifier: bundleIdentifier)
  135. config.localizationConfig.tableName = "Localizable"
  136. }
  137. config.cropToolbarConfig = toolbarConfig
  138. config.cropViewConfig = viewConfig
  139. let toolbar = CropToolbar()
  140. toolbar.iconProvider = CropToolbarIcon()
  141. let cropViewController = Mantis.cropViewController(image: image, config: config, cropToolbar: toolbar)
  142. cropViewController.delegate = self
  143. cropViewController.backgroundColor = .systemBackground
  144. cropViewController.modalPresentationStyle = .fullScreen
  145. self.present(cropViewController, animated: true)
  146. }
  147. }
  148. extension NCViewerQuickLook: QLPreviewControllerDataSource, QLPreviewControllerDelegate {
  149. func numberOfPreviewItems(in controller: QLPreviewController) -> Int {
  150. previewItems.count
  151. }
  152. func previewController(_ controller: QLPreviewController, previewItemAt index: Int) -> QLPreviewItem {
  153. previewItems[index]
  154. }
  155. func previewController(_ controller: QLPreviewController, editingModeFor previewItem: QLPreviewItem) -> QLPreviewItemEditingMode {
  156. return isEditingEnabled ? .createCopy : .disabled
  157. }
  158. fileprivate func saveModifiedFile(override: Bool) {
  159. guard let metadata = self.metadata else { return }
  160. let ocId = NSUUID().uuidString
  161. let size = NCUtilityFileSystem.shared.getFileSize(filePath: url.path)
  162. if !override {
  163. let fileName = NCUtilityFileSystem.shared.createFileName(metadata.fileNameView, serverUrl: metadata.serverUrl, account: metadata.account)
  164. metadata.fileName = fileName
  165. metadata.fileNameView = fileName
  166. }
  167. guard let fileNamePath = CCUtility.getDirectoryProviderStorageOcId(ocId, fileNameView: metadata.fileNameView),
  168. NCUtilityFileSystem.shared.copyFile(atPath: url.path, toPath: fileNamePath) else { return }
  169. let metadataForUpload = NCManageDatabase.shared.createMetadata(
  170. account: metadata.account,
  171. user: metadata.user,
  172. userId: metadata.userId,
  173. fileName: metadata.fileName,
  174. fileNameView: metadata.fileNameView,
  175. ocId: ocId,
  176. serverUrl: metadata.serverUrl,
  177. urlBase: metadata.urlBase,
  178. url: url.path,
  179. contentType: "")
  180. metadataForUpload.session = NCNetworking.shared.sessionIdentifierBackground
  181. if override {
  182. metadataForUpload.sessionSelector = NCGlobal.shared.selectorUploadFileNODelete
  183. } else {
  184. metadataForUpload.sessionSelector = NCGlobal.shared.selectorUploadFile
  185. }
  186. metadataForUpload.size = size
  187. metadataForUpload.status = NCGlobal.shared.metadataStatusWaitUpload
  188. NCNetworkingProcessUpload.shared.createProcessUploads(metadatas: [metadataForUpload]) { _ in
  189. self.dismiss(animated: true)
  190. }
  191. }
  192. func previewController(_ controller: QLPreviewController, didSaveEditedCopyOf previewItem: QLPreviewItem, at modifiedContentsURL: URL) {
  193. // easier to handle that way than to use `.updateContents`
  194. // needs to be moved otherwise it will only be called once!
  195. guard NCUtilityFileSystem.shared.moveFile(atPath: modifiedContentsURL.path, toPath: url.path) else { return }
  196. hasChangesQuickLook = true
  197. }
  198. }
  199. extension NCViewerQuickLook: CropViewControllerDelegate {
  200. func cropViewControllerDidCrop(_ cropViewController: Mantis.CropViewController, cropped: UIImage, transformation: Mantis.Transformation, cropInfo: Mantis.CropInfo) {
  201. cropViewController.dismiss(animated: true)
  202. guard let data = cropped.jpegData(compressionQuality: 1) else { return }
  203. do {
  204. try data.write(to: self.url)
  205. hasChangesQuickLook = true
  206. reloadData()
  207. } catch { }
  208. }
  209. func cropViewControllerDidCancel(_ cropViewController: Mantis.CropViewController, original: UIImage) {
  210. cropViewController.dismiss(animated: true)
  211. }
  212. }
  213. class PreviewItem: NSObject, QLPreviewItem {
  214. var previewItemURL: URL?
  215. }
  216. class CropToolbarIcon: CropToolbarIconProvider {
  217. func getCropIcon() -> UIImage? {
  218. return UIImage(systemName: "checkmark.circle")
  219. }
  220. func getCancelIcon() -> UIImage? {
  221. return UIImage(systemName: "xmark.circle")
  222. }
  223. }