NCCollectionViewCommon+Menu.swift 17 KB

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