NCCollectionViewCommon+Menu.swift 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389
  1. //
  2. // NCCollectionViewCommon+Menu.swift
  3. // Nextcloud
  4. //
  5. // Created by Philippe Weidmann on 24.01.20.
  6. // Copyright © 2020 Philippe Weidmann. All rights reserved.
  7. // Copyright © 2020 Marino Faggiana All rights reserved.
  8. // Copyright © 2022 Henrik Storch. All rights reserved.
  9. //
  10. // Author Philippe Weidmann
  11. // Author Marino Faggiana <marino.faggiana@nextcloud.com>
  12. // Author Henrik Storch <henrik.storch@nextcloud.com>
  13. //
  14. // This program is free software: you can redistribute it and/or modify
  15. // it under the terms of the GNU General Public License as published by
  16. // the Free Software Foundation, either version 3 of the License, or
  17. // (at your option) any later version.
  18. //
  19. // This program is distributed in the hope that it will be useful,
  20. // but WITHOUT ANY WARRANTY; without even the implied warranty of
  21. // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  22. // GNU General Public License for more details.
  23. //
  24. // You should have received a copy of the GNU General Public License
  25. // along with this program. If not, see <http://www.gnu.org/licenses/>.
  26. //
  27. import UIKit
  28. import FloatingPanel
  29. import NextcloudKit
  30. import Queuer
  31. extension NCCollectionViewCommon {
  32. func toggleMenu(metadata: tableMetadata, image: UIImage?) {
  33. guard let metadata = database.getMetadataFromOcId(metadata.ocId),
  34. let sceneIdentifier = self.controller?.sceneIdentifier else {
  35. return
  36. }
  37. let tableLocalFile = database.getResultsTableLocalFile(predicate: NSPredicate(format: "ocId == %@", metadata.ocId))?.first
  38. let fileExists = NCUtilityFileSystem().fileProviderStorageExists(metadata)
  39. var actions = [NCMenuAction]()
  40. let serverUrl = metadata.serverUrl + "/" + metadata.fileName
  41. var isOffline: Bool = false
  42. let applicationHandle = NCApplicationHandle()
  43. var iconHeader: UIImage!
  44. if metadata.directory, let directory = database.getTableDirectory(predicate: NSPredicate(format: "account == %@ AND serverUrl == %@", session.account, serverUrl)) {
  45. isOffline = directory.offline
  46. } else if let localFile = database.getTableLocalFile(predicate: NSPredicate(format: "ocId == %@", metadata.ocId)) {
  47. isOffline = localFile.offline
  48. }
  49. if let image {
  50. iconHeader = image
  51. } else {
  52. if metadata.directory {
  53. iconHeader = imageCache.getFolder(account: metadata.account)
  54. } else {
  55. iconHeader = imageCache.getImageFile()
  56. }
  57. }
  58. actions.append(
  59. NCMenuAction(
  60. title: metadata.fileNameView,
  61. boldTitle: true,
  62. icon: iconHeader,
  63. order: 0,
  64. action: nil
  65. )
  66. )
  67. actions.append(.seperator(order: 1))
  68. //
  69. // DETAILS
  70. //
  71. if NCNetworking.shared.isOnline,
  72. !NCCapabilities.shared.disableSharesView(account: metadata.account) {
  73. actions.append(
  74. NCMenuAction(
  75. title: NSLocalizedString("_details_", comment: ""),
  76. icon: utility.loadImage(named: "info.circle", colors: [NCBrandColor.shared.iconImageColor]),
  77. order: 10,
  78. action: { _ in
  79. NCActionCenter.shared.openShare(viewController: self, metadata: metadata, page: .activity)
  80. }
  81. )
  82. )
  83. }
  84. if metadata.lock {
  85. var lockOwnerName = metadata.lockOwnerDisplayName.isEmpty ? metadata.lockOwner : metadata.lockOwnerDisplayName
  86. var lockIcon = utility.loadUserImage(for: metadata.lockOwner, displayName: lockOwnerName, urlBase: metadata.urlBase)
  87. if metadata.lockOwnerType != 0 {
  88. lockOwnerName += " app"
  89. if !metadata.lockOwnerEditor.isEmpty, let appIcon = UIImage(named: metadata.lockOwnerEditor) {
  90. lockIcon = appIcon
  91. }
  92. }
  93. var lockTimeString: String?
  94. if let lockTime = metadata.lockTimeOut {
  95. if lockTime >= Date().addingTimeInterval(60),
  96. let timeInterval = (lockTime.timeIntervalSince1970 - Date().timeIntervalSince1970).format() {
  97. lockTimeString = String(format: NSLocalizedString("_time_remaining_", comment: ""), timeInterval)
  98. } else if lockTime > Date() {
  99. lockTimeString = NSLocalizedString("_less_a_minute_", comment: "")
  100. } // else: don't show negative time
  101. }
  102. if let lockTime = metadata.lockTime, lockTimeString == nil {
  103. lockTimeString = DateFormatter.localizedString(from: lockTime, dateStyle: .short, timeStyle: .short)
  104. }
  105. actions.append(
  106. NCMenuAction(
  107. title: String(format: NSLocalizedString("_locked_by_", comment: ""), lockOwnerName),
  108. details: lockTimeString,
  109. icon: lockIcon,
  110. order: 20,
  111. action: nil)
  112. )
  113. }
  114. //
  115. // VIEW IN FOLDER
  116. //
  117. if NCNetworking.shared.isOnline,
  118. layoutKey != NCGlobal.shared.layoutViewFiles {
  119. actions.append(
  120. NCMenuAction(
  121. title: NSLocalizedString("_view_in_folder_", comment: ""),
  122. icon: utility.loadImage(named: "questionmark.folder", colors: [NCBrandColor.shared.iconImageColor]),
  123. order: 21,
  124. action: { _ in
  125. NCActionCenter.shared.openFileViewInFolder(serverUrl: metadata.serverUrl, fileNameBlink: metadata.fileName, fileNameOpen: nil, sceneIdentifier: sceneIdentifier)
  126. }
  127. )
  128. )
  129. }
  130. //
  131. // LOCK / UNLOCK
  132. //
  133. if NCNetworking.shared.isOnline,
  134. !metadata.directory,
  135. metadata.canUnlock(as: metadata.userId),
  136. !NCCapabilities.shared.getCapabilities(account: metadata.account).capabilityFilesLockVersion.isEmpty {
  137. actions.append(NCMenuAction.lockUnlockFiles(shouldLock: !metadata.lock, metadatas: [metadata], order: 30))
  138. }
  139. //
  140. // SET FOLDER E2EE
  141. //
  142. if NCNetworking.shared.isOnline,
  143. metadata.canSetDirectoryAsE2EE {
  144. actions.append(
  145. NCMenuAction(
  146. title: NSLocalizedString("_e2e_set_folder_encrypted_", comment: ""),
  147. icon: utility.loadImage(named: "lock", colors: [NCBrandColor.shared.iconImageColor]),
  148. order: 30,
  149. action: { _ in
  150. Task {
  151. let error = await NCNetworkingE2EEMarkFolder().markFolderE2ee(account: metadata.account, fileName: metadata.fileName, serverUrl: metadata.serverUrl, userId: metadata.userId)
  152. if error != .success {
  153. NCContentPresenter().showError(error: error)
  154. }
  155. }
  156. }
  157. )
  158. )
  159. }
  160. //
  161. // UNSET FOLDER E2EE
  162. //
  163. if NCNetworking.shared.isOnline,
  164. metadata.canUnsetDirectoryAsE2EE {
  165. actions.append(
  166. NCMenuAction(
  167. title: NSLocalizedString("_e2e_remove_folder_encrypted_", comment: ""),
  168. icon: utility.loadImage(named: "lock", colors: [NCBrandColor.shared.iconImageColor]),
  169. order: 30,
  170. action: { _ in
  171. NextcloudKit.shared.markE2EEFolder(fileId: metadata.fileId, delete: true, account: metadata.account) { _, _, error in
  172. if error == .success {
  173. self.database.deleteE2eEncryption(predicate: NSPredicate(format: "account == %@ AND serverUrl == %@", metadata.account, serverUrl))
  174. self.database.setDirectory(serverUrl: serverUrl, encrypted: false, account: metadata.account)
  175. self.database.setMetadataEncrypted(ocId: metadata.ocId, encrypted: false)
  176. NotificationCenter.default.postOnMainThread(name: NCGlobal.shared.notificationCenterChangeStatusFolderE2EE, userInfo: ["serverUrl": metadata.serverUrl])
  177. } else {
  178. NCContentPresenter().messageNotification(NSLocalizedString("_e2e_error_", comment: ""), error: error, delay: NCGlobal.shared.dismissAfterSecond, type: .error)
  179. }
  180. }
  181. }
  182. )
  183. )
  184. }
  185. //
  186. // FAVORITE
  187. // FIXME: PROPPATCH doesn't work
  188. // https://github.com/nextcloud/files_lock/issues/68
  189. if !metadata.lock {
  190. actions.append(
  191. NCMenuAction(
  192. title: metadata.favorite ? NSLocalizedString("_remove_favorites_", comment: "") : NSLocalizedString("_add_favorites_", comment: ""),
  193. icon: utility.loadImage(named: metadata.favorite ? "star.slash" : "star", colors: [NCBrandColor.shared.yellowFavorite]),
  194. order: 50,
  195. action: { _ in
  196. NCNetworking.shared.favoriteMetadata(metadata) { error in
  197. if error != .success {
  198. NCContentPresenter().showError(error: error)
  199. }
  200. }
  201. }
  202. )
  203. )
  204. }
  205. //
  206. // OFFLINE
  207. //
  208. if NCNetworking.shared.isOnline,
  209. metadata.canSetAsAvailableOffline {
  210. actions.append(.setAvailableOfflineAction(selectedMetadatas: [metadata], isAnyOffline: isOffline, viewController: self, order: 60, completion: {
  211. self.reloadDataSource()
  212. }))
  213. }
  214. //
  215. // SHARE
  216. //
  217. if (NCNetworking.shared.isOnline || (tableLocalFile != nil && fileExists)) && metadata.canShare {
  218. actions.append(.share(selectedMetadatas: [metadata], controller: self.controller, order: 80))
  219. }
  220. //
  221. // SAVE LIVE PHOTO
  222. //
  223. if NCNetworking.shared.isOnline,
  224. let metadataMOV = database.getMetadataLivePhoto(metadata: metadata),
  225. let hudView = self.tabBarController?.view {
  226. actions.append(
  227. NCMenuAction(
  228. title: NSLocalizedString("_livephoto_save_", comment: ""),
  229. icon: NCUtility().loadImage(named: "livephoto", colors: [NCBrandColor.shared.iconImageColor]),
  230. order: 100,
  231. action: { _ in
  232. NCNetworking.shared.saveLivePhotoQueue.addOperation(NCOperationSaveLivePhoto(metadata: metadata, metadataMOV: metadataMOV, hudView: hudView))
  233. }
  234. )
  235. )
  236. }
  237. //
  238. // SAVE AS SCAN
  239. //
  240. if NCNetworking.shared.isOnline,
  241. metadata.isSavebleAsImage {
  242. actions.append(
  243. NCMenuAction(
  244. title: NSLocalizedString("_save_as_scan_", comment: ""),
  245. icon: utility.loadImage(named: "doc.viewfinder", colors: [NCBrandColor.shared.iconImageColor]),
  246. order: 110,
  247. action: { _ in
  248. if self.utilityFileSystem.fileProviderStorageExists(metadata) {
  249. NotificationCenter.default.postOnMainThread(name: NCGlobal.shared.notificationCenterDownloadedFile,
  250. object: nil,
  251. userInfo: ["ocId": metadata.ocId,
  252. "ocIdTransfer": metadata.ocIdTransfer,
  253. "session": metadata.session,
  254. "selector": NCGlobal.shared.selectorSaveAsScan,
  255. "error": NKError(),
  256. "account": metadata.account],
  257. second: 0.5)
  258. } else {
  259. guard let metadata = self.database.setMetadatasSessionInWaitDownload(metadatas: [metadata],
  260. session: NCNetworking.shared.sessionDownload,
  261. selector: NCGlobal.shared.selectorSaveAsScan,
  262. sceneIdentifier: sceneIdentifier) else { return }
  263. NCNetworking.shared.download(metadata: metadata, withNotificationProgressTask: true)
  264. }
  265. }
  266. )
  267. )
  268. }
  269. //
  270. // RENAME
  271. //
  272. if metadata.isRenameable {
  273. actions.append(
  274. NCMenuAction(
  275. title: NSLocalizedString("_rename_", comment: ""),
  276. icon: utility.loadImage(named: "text.cursor", colors: [NCBrandColor.shared.iconImageColor]),
  277. order: 120,
  278. action: { _ in
  279. self.present(UIAlertController.renameFile(metadata: metadata), animated: true)
  280. }
  281. )
  282. )
  283. }
  284. //
  285. // COPY - MOVE
  286. //
  287. if metadata.isCopyableMovable {
  288. actions.append(.moveOrCopyAction(selectedMetadatas: [metadata], viewController: self, order: 130))
  289. }
  290. //
  291. // MODIFY WITH QUICK LOOK
  292. //
  293. if NCNetworking.shared.isOnline,
  294. metadata.isModifiableWithQuickLook {
  295. actions.append(
  296. NCMenuAction(
  297. title: NSLocalizedString("_modify_", comment: ""),
  298. icon: utility.loadImage(named: "pencil.tip.crop.circle", colors: [NCBrandColor.shared.iconImageColor]),
  299. order: 150,
  300. action: { _ in
  301. if self.utilityFileSystem.fileProviderStorageExists(metadata) {
  302. NotificationCenter.default.postOnMainThread(name: NCGlobal.shared.notificationCenterDownloadedFile,
  303. object: nil,
  304. userInfo: ["ocId": metadata.ocId,
  305. "ocIdTransfer": metadata.ocIdTransfer,
  306. "session": metadata.session,
  307. "selector": NCGlobal.shared.selectorLoadFileQuickLook,
  308. "error": NKError(),
  309. "account": metadata.account],
  310. second: 0.5)
  311. } else {
  312. guard let metadata = self.database.setMetadatasSessionInWaitDownload(metadatas: [metadata],
  313. session: NCNetworking.shared.sessionDownload,
  314. selector: NCGlobal.shared.selectorLoadFileQuickLook,
  315. sceneIdentifier: sceneIdentifier) else { return }
  316. NCNetworking.shared.download(metadata: metadata, withNotificationProgressTask: true)
  317. }
  318. }
  319. )
  320. )
  321. }
  322. //
  323. // COLOR FOLDER
  324. //
  325. if self is NCFiles,
  326. metadata.directory {
  327. actions.append(
  328. NCMenuAction(
  329. title: NSLocalizedString("_change_color_", comment: ""),
  330. icon: utility.loadImage(named: "paintpalette", colors: [NCBrandColor.shared.iconImageColor]),
  331. order: 160,
  332. action: { _ in
  333. if let picker = UIStoryboard(name: "NCColorPicker", bundle: nil).instantiateInitialViewController() as? NCColorPicker {
  334. picker.metadata = metadata
  335. let popup = NCPopupViewController(contentController: picker, popupWidth: 200, popupHeight: 320)
  336. popup.backgroundAlpha = 0
  337. self.present(popup, animated: true)
  338. }
  339. }
  340. )
  341. )
  342. }
  343. //
  344. // DELETE
  345. //
  346. if metadata.isDeletable {
  347. actions.append(.deleteAction(selectedMetadatas: [metadata], metadataFolder: metadataFolder, controller: self.controller, order: 170))
  348. }
  349. applicationHandle.addCollectionViewCommonMenu(metadata: metadata, image: image, actions: &actions)
  350. presentMenu(with: actions)
  351. }
  352. }
  353. extension TimeInterval {
  354. func format() -> String? {
  355. let formatter = DateComponentsFormatter()
  356. formatter.allowedUnits = [.day, .hour, .minute]
  357. formatter.unitsStyle = .full
  358. formatter.maximumUnitCount = 1
  359. return formatter.string(from: self)
  360. }
  361. }