NCViewerQuickLook.swift 10 KB

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