NCMedia.swift 43 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006
  1. //
  2. // NCMedia.swift
  3. // Nextcloud
  4. //
  5. // Created by Marino Faggiana on 12/02/2019.
  6. // Copyright © 2019 Marino Faggiana. All rights reserved.
  7. //
  8. // Author Marino Faggiana <marino.faggiana@nextcloud.com>
  9. //
  10. // This program is free software: you can redistribute it and/or modify
  11. // it under the terms of the GNU General Public License as published by
  12. // the Free Software Foundation, either version 3 of the License, or
  13. // (at your option) any later version.
  14. //
  15. // This program is distributed in the hope that it will be useful,
  16. // but WITHOUT ANY WARRANTY; without even the implied warranty of
  17. // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  18. // GNU General Public License for more details.
  19. //
  20. // You should have received a copy of the GNU General Public License
  21. // along with this program. If not, see <http://www.gnu.org/licenses/>.
  22. //
  23. import Foundation
  24. import NCCommunication
  25. class NCMedia: UIViewController, NCEmptyDataSetDelegate, NCSelectDelegate {
  26. @IBOutlet weak var collectionView : UICollectionView!
  27. private var emptyDataSet: NCEmptyDataSet?
  28. private var mediaCommandView: NCMediaCommandView?
  29. private var gridLayout: NCGridMediaLayout!
  30. private let appDelegate = UIApplication.shared.delegate as! AppDelegate
  31. public var metadatas: [tableMetadata] = []
  32. private var metadataTouch: tableMetadata?
  33. private var account: String = ""
  34. private var predicateDefault: NSPredicate?
  35. private var predicate: NSPredicate?
  36. private var isEditMode = false
  37. private var selectOcId: [String] = []
  38. private var filterTypeFileImage = false
  39. private var filterTypeFileVideo = false
  40. private let kMaxImageGrid: CGFloat = 7
  41. private var cellHeigth: CGFloat = 0
  42. private var oldInProgress = false
  43. private var newInProgress = false
  44. private var lastContentOffsetY: CGFloat = 0
  45. private var mediaPath = ""
  46. private var livePhoto: Bool = false
  47. private var timeIntervalSearchNewMedia: TimeInterval = 3.5
  48. private var timerSearchNewMedia: Timer?
  49. struct cacheImages {
  50. static var cellLivePhotoImage = UIImage()
  51. static var cellPlayImage = UIImage()
  52. }
  53. // MARK: - View Life Cycle
  54. required init?(coder aDecoder: NSCoder) {
  55. super.init(coder: aDecoder)
  56. appDelegate.activeMedia = self
  57. NotificationCenter.default.addObserver(self, selector: #selector(applicationWillEnterForeground), name: NSNotification.Name(rawValue: NCBrandGlobal.shared.notificationCenterApplicationWillEnterForeground), object: nil)
  58. }
  59. override func viewDidLoad() {
  60. super.viewDidLoad()
  61. collectionView.register(UINib.init(nibName: "NCGridMediaCell", bundle: nil), forCellWithReuseIdentifier: "gridCell")
  62. collectionView.alwaysBounceVertical = true
  63. collectionView.contentInset = UIEdgeInsets(top: 75, left: 0, bottom: 50, right: 0);
  64. gridLayout = NCGridMediaLayout()
  65. gridLayout.itemForLine = CGFloat(min(CCUtility.getMediaWidthImage(), 5))
  66. gridLayout.sectionHeadersPinToVisibleBounds = true
  67. collectionView.collectionViewLayout = gridLayout
  68. // Empty
  69. emptyDataSet = NCEmptyDataSet.init(view: collectionView, offset: 0, delegate: self)
  70. // 3D Touch peek and pop
  71. if traitCollection.forceTouchCapability == .available {
  72. registerForPreviewing(with: self, sourceView: view)
  73. }
  74. // Notification
  75. NotificationCenter.default.addObserver(self, selector: #selector(deleteFile(_:)), name: NSNotification.Name(rawValue: NCBrandGlobal.shared.notificationCenterDeleteFile), object: nil)
  76. NotificationCenter.default.addObserver(self, selector: #selector(changeTheming), name: NSNotification.Name(rawValue: NCBrandGlobal.shared.notificationCenterChangeTheming), object: nil)
  77. NotificationCenter.default.addObserver(self, selector: #selector(moveFile(_:)), name: NSNotification.Name(rawValue: NCBrandGlobal.shared.notificationCenterMoveFile), object: nil)
  78. NotificationCenter.default.addObserver(self, selector: #selector(renameFile(_:)), name: NSNotification.Name(rawValue: NCBrandGlobal.shared.notificationCenterRenameFile), object: nil)
  79. mediaCommandView = Bundle.main.loadNibNamed("NCMediaCommandView", owner: self, options: nil)?.first as? NCMediaCommandView
  80. self.view.addSubview(mediaCommandView!)
  81. mediaCommandView?.mediaView = self
  82. mediaCommandView?.zoomInButton.isEnabled = !(self.gridLayout.itemForLine == 1)
  83. mediaCommandView?.zoomOutButton.isEnabled = !(self.gridLayout.itemForLine == self.kMaxImageGrid - 1)
  84. mediaCommandView?.collapseControlButtonView(true)
  85. mediaCommandView?.translatesAutoresizingMaskIntoConstraints = false
  86. mediaCommandView?.topAnchor.constraint(equalTo: view.topAnchor, constant: 0).isActive = true
  87. mediaCommandView?.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 0).isActive = true
  88. mediaCommandView?.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: 0).isActive = true
  89. mediaCommandView?.heightAnchor.constraint(equalToConstant: 150).isActive = true
  90. self.updateMediaControlVisibility()
  91. collectionView.prefetchDataSource = self
  92. changeTheming()
  93. }
  94. override func viewWillAppear(_ animated: Bool) {
  95. super.viewWillAppear(animated)
  96. // hide nagigation controller
  97. navigationController?.navigationBar.prefersLargeTitles = true
  98. navigationController?.setNavigationBarHidden(true, animated: false)
  99. self.reloadDataSourceWithCompletion { (_) in
  100. self.timerSearchNewMedia?.invalidate()
  101. self.timerSearchNewMedia = Timer.scheduledTimer(timeInterval: self.timeIntervalSearchNewMedia, target: self, selector: #selector(self.searchNewMediaTimer), userInfo: nil, repeats: false)
  102. }
  103. }
  104. override func viewDidAppear(_ animated: Bool) {
  105. super.viewDidAppear(animated)
  106. }
  107. override func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) {
  108. super.viewWillTransition(to: size, with: coordinator)
  109. coordinator.animate(alongsideTransition: nil) { _ in
  110. self.reloadDataThenPerform { }
  111. }
  112. }
  113. override var preferredStatusBarStyle: UIStatusBarStyle {
  114. return .lightContent
  115. }
  116. //MARK: - Notification
  117. @objc func applicationWillEnterForeground() {
  118. if self.view.window != nil {
  119. self.viewDidAppear(false)
  120. }
  121. }
  122. //MARK: - Command
  123. func mediaCommandTitle() {
  124. mediaCommandView?.title.text = ""
  125. if let visibleCells = self.collectionView?.indexPathsForVisibleItems.sorted(by: { $0.row < $1.row }).compactMap({ self.collectionView?.cellForItem(at: $0) }) {
  126. if let cell = visibleCells.first as? NCGridMediaCell {
  127. if cell.date != nil {
  128. mediaCommandView?.title.text = CCUtility.getTitleSectionDate(cell.date)
  129. }
  130. }
  131. }
  132. }
  133. @objc func zoomOutGrid() {
  134. UIView.animate(withDuration: 0.0, animations: {
  135. if(self.gridLayout.itemForLine + 1 < self.kMaxImageGrid) {
  136. self.gridLayout.itemForLine += 1
  137. self.mediaCommandView?.zoomInButton.isEnabled = true
  138. }
  139. if(self.gridLayout.itemForLine == self.kMaxImageGrid - 1) {
  140. self.mediaCommandView?.zoomOutButton.isEnabled = false
  141. }
  142. self.collectionView.collectionViewLayout.invalidateLayout()
  143. CCUtility.setMediaWidthImage(Int(self.gridLayout.itemForLine))
  144. })
  145. }
  146. @objc func zoomInGrid() {
  147. UIView.animate(withDuration: 0.0, animations: {
  148. if(self.gridLayout.itemForLine - 1 > 0) {
  149. self.gridLayout.itemForLine -= 1
  150. self.mediaCommandView?.zoomOutButton.isEnabled = true
  151. }
  152. if(self.gridLayout.itemForLine == 1) {
  153. self.mediaCommandView?.zoomInButton.isEnabled = false
  154. }
  155. self.collectionView.collectionViewLayout.invalidateLayout()
  156. CCUtility.setMediaWidthImage(Int(self.gridLayout.itemForLine))
  157. })
  158. }
  159. @objc func openMenuButtonMore(_ sender: Any) {
  160. let mainMenuViewController = UIStoryboard.init(name: "NCMenu", bundle: nil).instantiateViewController(withIdentifier: "NCMainMenuTableViewController") as! NCMainMenuTableViewController
  161. var actions: [NCMenuAction] = []
  162. if !isEditMode {
  163. if metadatas.count > 0 {
  164. actions.append(
  165. NCMenuAction(
  166. title: NSLocalizedString("_select_", comment: ""),
  167. icon: UIImage(named: "selectFull")!.image(color: NCBrandColor.shared.icon, size: 50),
  168. action: { menuAction in
  169. self.isEditMode = true
  170. }
  171. )
  172. )
  173. }
  174. actions.append(
  175. NCMenuAction(
  176. title: NSLocalizedString(filterTypeFileImage ? "_media_viewimage_show_" : "_media_viewimage_hide_", comment: ""),
  177. icon: UIImage(named: filterTypeFileImage ? "imageno" : "imageyes")!.image(color: NCBrandColor.shared.icon, size: 50),
  178. action: { menuAction in
  179. self.filterTypeFileImage = !self.filterTypeFileImage
  180. self.filterTypeFileVideo = false
  181. self.reloadDataSource()
  182. }
  183. )
  184. )
  185. actions.append(
  186. NCMenuAction(
  187. title: NSLocalizedString(filterTypeFileVideo ? "_media_viewvideo_show_" : "_media_viewvideo_hide_", comment: ""),
  188. icon: UIImage(named: filterTypeFileVideo ? "videono" : "videoyes")!.image(color: NCBrandColor.shared.icon, size: 50),
  189. action: { menuAction in
  190. self.filterTypeFileVideo = !self.filterTypeFileVideo
  191. self.filterTypeFileImage = false
  192. self.reloadDataSource()
  193. }
  194. )
  195. )
  196. actions.append(
  197. NCMenuAction(
  198. title: NSLocalizedString("_select_media_folder_", comment: ""),
  199. icon: UIImage(named: "folderAutomaticUpload")!.image(color: NCBrandColor.shared.icon, size: 50),
  200. action: { menuAction in
  201. let navigationController = UIStoryboard(name: "NCSelect", bundle: nil).instantiateInitialViewController() as! UINavigationController
  202. let viewController = navigationController.topViewController as! NCSelect
  203. viewController.delegate = self
  204. viewController.hideButtonCreateFolder = true
  205. viewController.includeDirectoryE2EEncryption = false
  206. viewController.includeImages = false
  207. viewController.selectFile = false
  208. viewController.titleButtonDone = NSLocalizedString("_select_", comment: "")
  209. viewController.type = "mediaFolder"
  210. navigationController.modalPresentationStyle = UIModalPresentationStyle.fullScreen
  211. self.present(navigationController, animated: true, completion: nil)
  212. }
  213. )
  214. )
  215. actions.append(
  216. NCMenuAction(
  217. title: NSLocalizedString("_media_by_modified_date_", comment: ""),
  218. icon: UIImage(named: "sortModifiedDate")!.image(color: NCBrandColor.shared.icon, size: 50),
  219. selected: CCUtility.getMediaSortDate() == "date",
  220. on: true,
  221. action: { menuAction in
  222. CCUtility.setMediaSortDate("date")
  223. self.reloadDataSource()
  224. }
  225. )
  226. )
  227. actions.append(
  228. NCMenuAction(
  229. title: NSLocalizedString("_media_by_created_date_", comment: ""),
  230. icon: UIImage(named: "sortCreatedDate")!.image(color: NCBrandColor.shared.icon, size: 50),
  231. selected: CCUtility.getMediaSortDate() == "creationDate",
  232. on: true,
  233. action: { menuAction in
  234. CCUtility.setMediaSortDate("creationDate")
  235. self.reloadDataSource()
  236. }
  237. )
  238. )
  239. actions.append(
  240. NCMenuAction(
  241. title: NSLocalizedString("_media_by_upload_date_", comment: ""),
  242. icon: UIImage(named: "sortUploadDate")!.image(color: NCBrandColor.shared.icon, size: 50),
  243. selected: CCUtility.getMediaSortDate() == "uploadDate",
  244. on: true,
  245. action: { menuAction in
  246. CCUtility.setMediaSortDate("uploadDate")
  247. self.reloadDataSource()
  248. }
  249. )
  250. )
  251. } else {
  252. actions.append(
  253. NCMenuAction(
  254. title: NSLocalizedString("_cancel_", comment: ""),
  255. icon: UIImage(named: "cancel")!.image(color: NCBrandColor.shared.icon, size: 50),
  256. action: { menuAction in
  257. self.isEditMode = false
  258. self.selectOcId.removeAll()
  259. self.reloadDataThenPerform { }
  260. }
  261. )
  262. )
  263. //
  264. // COPY - MOVE
  265. //
  266. actions.append(
  267. NCMenuAction(
  268. title: NSLocalizedString("_move_or_copy_selected_files_", comment: ""),
  269. icon: UIImage(named: "move")!.image(color: NCBrandColor.shared.icon, size: 50),
  270. action: { menuAction in
  271. self.isEditMode = false
  272. var meradatasSelect = [tableMetadata]()
  273. for ocId in self.selectOcId {
  274. if let metadata = NCManageDatabase.shared.getMetadataFromOcId(ocId) {
  275. meradatasSelect.append(metadata)
  276. }
  277. }
  278. if meradatasSelect.count > 0 {
  279. NCCollectionCommon.shared.openSelectView(items: meradatasSelect)
  280. }
  281. self.selectOcId.removeAll()
  282. }
  283. )
  284. )
  285. //
  286. // DELETE
  287. //
  288. actions.append(
  289. NCMenuAction(
  290. title: NSLocalizedString("_delete_selected_files_", comment: ""),
  291. icon: UIImage(named: "trash")!.image(color: NCBrandColor.shared.icon, size: 50),
  292. action: { menuAction in
  293. self.isEditMode = false
  294. for ocId in self.selectOcId {
  295. if let metadata = NCManageDatabase.shared.getMetadataFromOcId(ocId) {
  296. NCNetworking.shared.deleteMetadata(metadata, account: self.appDelegate.account, urlBase: self.appDelegate.urlBase, onlyLocal: false) { (errorCode, errorDescription) in
  297. if errorCode != 0 {
  298. NCContentPresenter.shared.messageNotification("_error_", description: errorDescription, delay: NCBrandGlobal.shared.dismissAfterSecond, type: NCContentPresenter.messageType.error, errorCode: errorCode)
  299. }
  300. }
  301. }
  302. }
  303. self.selectOcId.removeAll()
  304. }
  305. )
  306. )
  307. }
  308. mainMenuViewController.actions = actions
  309. let menuPanelController = NCMenuPanelController()
  310. menuPanelController.parentPresenter = self
  311. menuPanelController.delegate = mainMenuViewController
  312. menuPanelController.set(contentViewController: mainMenuViewController)
  313. menuPanelController.track(scrollView: mainMenuViewController.tableView)
  314. self.present(menuPanelController, animated: true, completion: nil)
  315. }
  316. // MARK: Select Path
  317. func dismissSelect(serverUrl: String?, metadata: tableMetadata?, type: String, items: [Any], buttonType: String, overwrite: Bool) {
  318. if serverUrl != nil {
  319. let path = CCUtility.returnPathfromServerUrl(serverUrl, urlBase: appDelegate.urlBase, account: appDelegate.account) ?? ""
  320. NCManageDatabase.shared.setAccountMediaPath(path, account: appDelegate.account)
  321. reloadDataSourceWithCompletion { (_) in
  322. self.searchNewMedia()
  323. }
  324. }
  325. }
  326. //MARK: - NotificationCenter
  327. @objc func changeTheming() {
  328. view.backgroundColor = NCBrandColor.shared.backgroundView
  329. collectionView.backgroundColor = NCBrandColor.shared.backgroundView
  330. collectionView.reloadData()
  331. cacheImages.cellLivePhotoImage = UIImage.init(named: "livePhoto")!.image(color: .white, size: 50)
  332. cacheImages.cellPlayImage = UIImage.init(named: "play")!.image(color: .white, size: 50)
  333. }
  334. @objc func deleteFile(_ notification: NSNotification) {
  335. if self.view?.window == nil { return }
  336. if let userInfo = notification.userInfo as NSDictionary? {
  337. if let ocId = userInfo["ocId"] as? String {
  338. let indexes = self.metadatas.indices.filter { self.metadatas[$0].ocId == ocId }
  339. let metadatas = self.metadatas.filter { $0.ocId != ocId }
  340. self.metadatas = metadatas
  341. if self.metadatas.count == 0 {
  342. collectionView?.reloadData()
  343. } else if let row = indexes.first {
  344. let indexPath = IndexPath(row: row, section: 0)
  345. collectionView?.deleteItems(at: [indexPath])
  346. }
  347. self.updateMediaControlVisibility()
  348. }
  349. }
  350. }
  351. @objc func moveFile(_ notification: NSNotification) {
  352. if self.view?.window == nil { return }
  353. if let userInfo = notification.userInfo as NSDictionary? {
  354. if let ocId = userInfo["ocId"] as? String, let metadata = NCManageDatabase.shared.getMetadataFromOcId(ocId) {
  355. if metadata.account == appDelegate.account {
  356. let indexes = self.metadatas.indices.filter { self.metadatas[$0].ocId == metadata.ocId }
  357. let metadatas = self.metadatas.filter { $0.ocId != metadata.ocId }
  358. self.metadatas = metadatas
  359. if self.metadatas.count == 0 {
  360. collectionView?.reloadData()
  361. } else if let row = indexes.first {
  362. let indexPath = IndexPath(row: row, section: 0)
  363. collectionView?.deleteItems(at: [indexPath])
  364. }
  365. self.updateMediaControlVisibility()
  366. }
  367. }
  368. }
  369. }
  370. @objc func renameFile(_ notification: NSNotification) {
  371. if self.view?.window == nil { return }
  372. if let userInfo = notification.userInfo as NSDictionary? {
  373. if let ocId = userInfo["ocId"] as? String, let metadata = NCManageDatabase.shared.getMetadataFromOcId(ocId) {
  374. if metadata.account == appDelegate.account {
  375. self.reloadDataSource()
  376. }
  377. }
  378. }
  379. }
  380. // MARK: - Empty
  381. func emptyDataSetView(_ view: NCEmptyView) {
  382. view.emptyImage.image = UIImage.init(named: "media")?.image(color: .gray, size: UIScreen.main.bounds.width)
  383. if oldInProgress || newInProgress {
  384. view.emptyTitle.text = NSLocalizedString("_search_in_progress_", comment: "")
  385. } else {
  386. view.emptyTitle.text = NSLocalizedString("_tutorial_photo_view_", comment: "")
  387. }
  388. view.emptyDescription.text = ""
  389. }
  390. }
  391. // MARK: - 3D Touch peek and pop
  392. extension NCMedia: UIViewControllerPreviewingDelegate {
  393. func previewingContext(_ previewingContext: UIViewControllerPreviewing, viewControllerForLocation location: CGPoint) -> UIViewController? {
  394. guard let point = collectionView?.convert(location, from: collectionView?.superview) else { return nil }
  395. guard let indexPath = collectionView?.indexPathForItem(at: point) else { return nil }
  396. let metadata = metadatas[indexPath.row]
  397. guard let cell = collectionView?.cellForItem(at: indexPath) as? NCGridMediaCell else { return nil }
  398. guard let viewController = UIStoryboard(name: "CCPeekPop", bundle: nil).instantiateViewController(withIdentifier: "PeekPopImagePreview") as? CCPeekPop else { return nil }
  399. previewingContext.sourceRect = cell.frame
  400. viewController.metadata = metadata
  401. viewController.imageFile = cell.imageItem.image
  402. viewController.showOpenIn = true
  403. viewController.showShare = false
  404. viewController.showOpenQuickLook = false
  405. return viewController
  406. }
  407. func previewingContext(_ previewingContext: UIViewControllerPreviewing, commit viewControllerToCommit: UIViewController) {
  408. guard let indexPath = collectionView?.indexPathForItem(at: previewingContext.sourceRect.origin) else { return }
  409. collectionView(collectionView, didSelectItemAt: indexPath)
  410. }
  411. }
  412. // MARK: - Collection View
  413. extension NCMedia: UICollectionViewDelegate {
  414. func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
  415. let metadata = metadatas[indexPath.row]
  416. metadataTouch = metadata
  417. if isEditMode {
  418. if let index = selectOcId.firstIndex(of: metadata.ocId) {
  419. selectOcId.remove(at: index)
  420. } else {
  421. selectOcId.append(metadata.ocId)
  422. }
  423. if indexPath.section < collectionView.numberOfSections && indexPath.row < collectionView.numberOfItems(inSection: indexPath.section) {
  424. collectionView.reloadItems(at: [indexPath])
  425. }
  426. } else {
  427. appDelegate.activeServerUrl = metadataTouch!.serverUrl
  428. NCViewer.shared.view(viewController: self, metadata: metadataTouch!, metadatas: metadatas)
  429. }
  430. }
  431. }
  432. extension NCMedia: UICollectionViewDataSourcePrefetching {
  433. func collectionView(_ collectionView: UICollectionView, prefetchItemsAt indexPaths: [IndexPath]) {
  434. //print("[LOG] n. " + String(indexPaths.count))
  435. }
  436. }
  437. extension NCMedia: UICollectionViewDataSource {
  438. func reloadDataThenPerform(_ closure: @escaping (() -> Void)) {
  439. CATransaction.begin()
  440. CATransaction.setCompletionBlock(closure)
  441. collectionView?.reloadData()
  442. CATransaction.commit()
  443. }
  444. func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
  445. emptyDataSet?.numberOfItemsInSection(metadatas.count, section: section)
  446. return metadatas.count
  447. }
  448. func collectionView(_ collectionView: UICollectionView, willDisplay cell: UICollectionViewCell, forItemAt indexPath: IndexPath) {
  449. if indexPath.row < self.metadatas.count {
  450. let metadata = self.metadatas[indexPath.row]
  451. NCOperationQueue.shared.downloadThumbnail(metadata: metadata, urlBase: self.appDelegate.urlBase, view: self.collectionView as Any, indexPath: indexPath)
  452. }
  453. }
  454. func collectionView(_ collectionView: UICollectionView, didEndDisplaying cell: UICollectionViewCell, forItemAt indexPath: IndexPath) {
  455. if !collectionView.indexPathsForVisibleItems.contains(indexPath) && indexPath.row < metadatas.count {
  456. let metadata = metadatas[indexPath.row]
  457. NCOperationQueue.shared.cancelDownloadThumbnail(metadata: metadata)
  458. }
  459. }
  460. func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
  461. if indexPath.section < collectionView.numberOfSections && indexPath.row < collectionView.numberOfItems(inSection: indexPath.section) && indexPath.row < metadatas.count {
  462. let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "gridCell", for: indexPath) as! NCGridMediaCell
  463. let metadata = metadatas[indexPath.row]
  464. self.cellHeigth = cell.frame.size.height
  465. if FileManager().fileExists(atPath: CCUtility.getDirectoryProviderStorageIconOcId(metadata.ocId, etag: metadata.etag)) {
  466. cell.imageItem.backgroundColor = nil
  467. cell.imageItem.image = UIImage.init(contentsOfFile: CCUtility.getDirectoryProviderStorageIconOcId(metadata.ocId, etag: metadata.etag))
  468. } else if(!metadata.hasPreview) {
  469. cell.imageItem.backgroundColor = nil
  470. if metadata.iconName.count > 0 {
  471. cell.imageItem.image = UIImage.init(named: metadata.iconName)
  472. } else {
  473. cell.imageItem.image = NCCollectionCommon.images.cellFileImage
  474. }
  475. }
  476. cell.date = metadata.date as Date
  477. if metadata.typeFile == NCBrandGlobal.shared.metadataTypeFileVideo || metadata.typeFile == NCBrandGlobal.shared.metadataTypeFileAudio {
  478. cell.imageStatus.image = cacheImages.cellPlayImage
  479. } else if metadata.livePhoto && livePhoto {
  480. cell.imageStatus.image = cacheImages.cellLivePhotoImage
  481. }
  482. if isEditMode {
  483. cell.selectMode(true)
  484. if selectOcId.contains(metadata.ocId) {
  485. cell.selected(true)
  486. } else {
  487. cell.selected(false)
  488. }
  489. } else {
  490. cell.selectMode(false)
  491. }
  492. return cell
  493. } else {
  494. return collectionView.dequeueReusableCell(withReuseIdentifier: "gridCell", for: indexPath) as! NCGridMediaCell
  495. }
  496. }
  497. }
  498. extension NCMedia: UICollectionViewDelegateFlowLayout {
  499. func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, referenceSizeForHeaderInSection section: Int) -> CGSize {
  500. return CGSize(width: collectionView.frame.width, height: 0)
  501. }
  502. func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, referenceSizeForFooterInSection section: Int) -> CGSize {
  503. return CGSize(width: collectionView.frame.width, height: 0)
  504. }
  505. }
  506. // MARK: - NC API & Algorithm
  507. extension NCMedia {
  508. @objc func reloadDataSource() {
  509. self.reloadDataSourceWithCompletion { (_) in }
  510. }
  511. @objc func reloadDataSourceWithCompletion(_ completion: @escaping (_ metadatas: [tableMetadata]) -> Void) {
  512. if appDelegate.account == nil || appDelegate.account.count == 0 { return }
  513. if account != appDelegate.account {
  514. self.metadatas = []
  515. account = appDelegate.account
  516. collectionView?.reloadData()
  517. }
  518. livePhoto = CCUtility.getLivePhoto()
  519. if let tableAccount = NCManageDatabase.shared.getAccountActive() {
  520. self.mediaPath = tableAccount.mediaPath
  521. }
  522. let startServerUrl = NCUtility.shared.getHomeServer(urlBase: appDelegate.urlBase, account: appDelegate.account) + mediaPath
  523. predicateDefault = NSPredicate(format: "account == %@ AND serverUrl BEGINSWITH %@ AND (typeFile == %@ OR typeFile == %@) AND NOT (session CONTAINS[c] 'upload')", appDelegate.account, startServerUrl, NCBrandGlobal.shared.metadataTypeFileImage, NCBrandGlobal.shared.metadataTypeFileVideo)
  524. if filterTypeFileImage {
  525. predicate = NSPredicate(format: "account == %@ AND serverUrl BEGINSWITH %@ AND typeFile == %@ AND NOT (session CONTAINS[c] 'upload')", appDelegate.account, startServerUrl, NCBrandGlobal.shared.metadataTypeFileVideo)
  526. } else if filterTypeFileVideo {
  527. predicate = NSPredicate(format: "account == %@ AND serverUrl BEGINSWITH %@ AND typeFile == %@ AND NOT (session CONTAINS[c] 'upload')", appDelegate.account, startServerUrl, NCBrandGlobal.shared.metadataTypeFileImage)
  528. } else {
  529. predicate = predicateDefault
  530. }
  531. guard var predicateForGetMetadatasMedia = predicate else { return }
  532. if livePhoto {
  533. let predicateLivePhoto = NSPredicate(format: "!(ext == 'mov' AND livePhoto == true)")
  534. predicateForGetMetadatasMedia = NSCompoundPredicate.init(andPredicateWithSubpredicates:[predicateForGetMetadatasMedia, predicateLivePhoto])
  535. }
  536. DispatchQueue.global().async {
  537. self.metadatas = NCManageDatabase.shared.getMetadatasMedia(predicate: predicateForGetMetadatasMedia, sort: CCUtility.getMediaSortDate())
  538. DispatchQueue.main.sync {
  539. self.reloadDataThenPerform {
  540. self.updateMediaControlVisibility()
  541. self.mediaCommandTitle()
  542. completion(self.metadatas)
  543. }
  544. }
  545. }
  546. }
  547. func updateMediaControlVisibility() {
  548. if self.metadatas.count == 0 {
  549. if !self.filterTypeFileImage && !self.filterTypeFileVideo {
  550. self.mediaCommandView?.toggleEmptyView(isEmpty: true)
  551. self.mediaCommandView?.isHidden = false
  552. } else {
  553. self.mediaCommandView?.toggleEmptyView(isEmpty: true)
  554. self.mediaCommandView?.isHidden = false
  555. }
  556. } else {
  557. self.mediaCommandView?.toggleEmptyView(isEmpty: false)
  558. self.mediaCommandView?.isHidden = false
  559. }
  560. }
  561. private func searchOldMedia(value: Int = -30, limit: Int = 300) {
  562. if oldInProgress { return }
  563. else { oldInProgress = true }
  564. collectionView.reloadData()
  565. var lessDate = Date()
  566. if predicateDefault != nil {
  567. if let metadata = NCManageDatabase.shared.getMetadata(predicate: predicateDefault!, sorted: "date", ascending: true) {
  568. lessDate = metadata.date as Date
  569. }
  570. }
  571. var greaterDate: Date
  572. if value == -999 {
  573. greaterDate = Date.distantPast
  574. } else {
  575. greaterDate = Calendar.current.date(byAdding: .day, value:value, to: lessDate)!
  576. }
  577. let height = self.tabBarController?.tabBar.frame.size.height ?? 0
  578. NCUtility.shared.startActivityIndicator(view: self.view, bottom: height + 50)
  579. NCCommunication.shared.searchMedia(path: mediaPath, lessDate: lessDate, greaterDate: greaterDate, elementDate: "d:getlastmodified/", limit: limit, showHiddenFiles: CCUtility.getShowHiddenFiles(), timeout: 120) { (account, files, errorCode, errorDescription) in
  580. self.oldInProgress = false
  581. NCUtility.shared.stopActivityIndicator()
  582. self.collectionView.reloadData()
  583. if errorCode == 0 && account == self.appDelegate.account {
  584. if files.count > 0 {
  585. NCManageDatabase.shared.convertNCCommunicationFilesToMetadatas(files, useMetadataFolder: false, account: self.appDelegate.account) { (_, _, metadatas) in
  586. let predicateDate = NSPredicate(format: "date > %@ AND date < %@", greaterDate as NSDate, lessDate as NSDate)
  587. let predicateResult = NSCompoundPredicate.init(andPredicateWithSubpredicates:[predicateDate, self.predicateDefault!])
  588. let metadatasResult = NCManageDatabase.shared.getMetadatas(predicate: predicateResult)
  589. let metadatasChanged = NCManageDatabase.shared.updateMetadatas(metadatas, metadatasResult: metadatasResult, addCompareLivePhoto: false)
  590. if metadatasChanged.metadatasUpdate.count == 0 {
  591. self.researchOldMedia(value: value, limit: limit, withElseReloadDataSource: true)
  592. } else {
  593. self.reloadDataSource()
  594. }
  595. }
  596. } else {
  597. self.researchOldMedia(value: value, limit: limit, withElseReloadDataSource: false)
  598. }
  599. }
  600. }
  601. }
  602. private func researchOldMedia(value: Int , limit: Int, withElseReloadDataSource: Bool) {
  603. if value == -30 {
  604. searchOldMedia(value: -90)
  605. } else if value == -90 {
  606. searchOldMedia(value: -180)
  607. } else if value == -180 {
  608. searchOldMedia(value: -999)
  609. } else if value == -999 && limit > 0 {
  610. searchOldMedia(value: -999, limit: 0)
  611. } else {
  612. if withElseReloadDataSource {
  613. reloadDataSource()
  614. }
  615. }
  616. }
  617. @objc func searchNewMediaTimer() {
  618. self.searchNewMedia()
  619. }
  620. @objc func searchNewMedia(limit: Int = 300) {
  621. guard var lessDate = Calendar.current.date(byAdding: .second, value: 1, to: Date()) else { return }
  622. guard var greaterDate = Calendar.current.date(byAdding: .day, value: -30, to: Date()) else { return }
  623. newInProgress = true
  624. reloadDataThenPerform {
  625. if let visibleCells = self.collectionView?.indexPathsForVisibleItems.sorted(by: { $0.row < $1.row }).compactMap({ self.collectionView?.cellForItem(at: $0) }) {
  626. if let cell = visibleCells.first as? NCGridMediaCell {
  627. if cell.date != nil {
  628. if cell.date != self.metadatas.first?.date as Date? {
  629. lessDate = Calendar.current.date(byAdding: .second, value: 1, to: cell.date!)!
  630. }
  631. }
  632. }
  633. if let cell = visibleCells.last as? NCGridMediaCell {
  634. if cell.date != nil {
  635. greaterDate = Calendar.current.date(byAdding: .second, value: -1, to: cell.date!)!
  636. }
  637. }
  638. }
  639. NCCommunication.shared.searchMedia(path: self.mediaPath, lessDate: lessDate, greaterDate: greaterDate, elementDate: "d:getlastmodified/", limit: limit, showHiddenFiles: CCUtility.getShowHiddenFiles(), timeout: 120) { (account, files, errorCode, errorDescription) in
  640. self.newInProgress = false
  641. if errorCode == 0 && account == self.appDelegate.account && files.count > 0 {
  642. NCManageDatabase.shared.convertNCCommunicationFilesToMetadatas(files, useMetadataFolder: false, account: account) { (_, _, metadatas) in
  643. let predicate = NSPredicate(format: "date > %@ AND date < %@", greaterDate as NSDate, lessDate as NSDate)
  644. let predicateResult = NSCompoundPredicate.init(andPredicateWithSubpredicates:[predicate, self.predicate!])
  645. let metadatasResult = NCManageDatabase.shared.getMetadatas(predicate: predicateResult)
  646. let updateMetadatas = NCManageDatabase.shared.updateMetadatas(metadatas, metadatasResult: metadatasResult, addCompareLivePhoto: false)
  647. if updateMetadatas.metadatasUpdate.count > 0 {
  648. self.reloadDataSource()
  649. }
  650. }
  651. } else if errorCode == 0 && files.count == 0 && limit > 0 {
  652. self.searchNewMedia(limit: 0)
  653. } else if errorCode == 0 && files.count == 0 && self.metadatas.count == 0 {
  654. self.searchOldMedia()
  655. }
  656. }
  657. }
  658. }
  659. private func downloadThumbnail() {
  660. guard let collectionView = self.collectionView else { return }
  661. DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) {
  662. for indexPath in collectionView.indexPathsForVisibleItems {
  663. let metadata = self.metadatas[indexPath.row]
  664. NCOperationQueue.shared.downloadThumbnail(metadata: metadata, urlBase: self.appDelegate.urlBase, view: self.collectionView as Any, indexPath: indexPath)
  665. }
  666. }
  667. }
  668. }
  669. // MARK: - ScrollView
  670. extension NCMedia: UIScrollViewDelegate {
  671. func scrollViewDidScroll(_ scrollView: UIScrollView) {
  672. if lastContentOffsetY == 0 || lastContentOffsetY + cellHeigth/2 <= scrollView.contentOffset.y || lastContentOffsetY - cellHeigth/2 >= scrollView.contentOffset.y {
  673. mediaCommandTitle()
  674. lastContentOffsetY = scrollView.contentOffset.y
  675. }
  676. }
  677. func scrollViewWillBeginDragging(_ scrollView: UIScrollView) {
  678. mediaCommandView?.collapseControlButtonView(true)
  679. }
  680. func scrollViewDidEndDragging(_ scrollView: UIScrollView, willDecelerate decelerate: Bool) {
  681. if !decelerate {
  682. timerSearchNewMedia?.invalidate()
  683. timerSearchNewMedia = Timer.scheduledTimer(timeInterval: timeIntervalSearchNewMedia, target: self, selector: #selector(searchNewMediaTimer), userInfo: nil, repeats: false)
  684. if (scrollView.contentOffset.y >= (scrollView.contentSize.height - scrollView.frame.size.height)) {
  685. searchOldMedia()
  686. }
  687. }
  688. }
  689. func scrollViewDidEndDecelerating(_ scrollView: UIScrollView) {
  690. timerSearchNewMedia?.invalidate()
  691. timerSearchNewMedia = Timer.scheduledTimer(timeInterval: timeIntervalSearchNewMedia, target: self, selector: #selector(searchNewMediaTimer), userInfo: nil, repeats: false)
  692. if (scrollView.contentOffset.y >= (scrollView.contentSize.height - scrollView.frame.size.height)) {
  693. searchOldMedia()
  694. }
  695. }
  696. }
  697. // MARK: - Media Command View
  698. class NCMediaCommandView: UIView {
  699. @IBOutlet weak var moreView: UIVisualEffectView!
  700. @IBOutlet weak var gridSwitchButton: UIButton!
  701. @IBOutlet weak var separatorView: UIView!
  702. @IBOutlet weak var buttonControlWidthConstraint: NSLayoutConstraint!
  703. @IBOutlet weak var zoomInButton: UIButton!
  704. @IBOutlet weak var zoomOutButton: UIButton!
  705. @IBOutlet weak var moreButton: UIButton!
  706. @IBOutlet weak var controlButtonView: UIVisualEffectView!
  707. @IBOutlet weak var title : UILabel!
  708. var mediaView:NCMedia?
  709. private let gradient: CAGradientLayer = CAGradientLayer()
  710. override func awakeFromNib() {
  711. moreView.layer.cornerRadius = 20
  712. moreView.layer.masksToBounds = true
  713. controlButtonView.layer.cornerRadius = 20
  714. controlButtonView.layer.masksToBounds = true
  715. gradient.frame = bounds
  716. gradient.startPoint = CGPoint(x: 0, y: 0.50)
  717. gradient.endPoint = CGPoint(x: 0, y: 0.9)
  718. gradient.colors = [UIColor.black.withAlphaComponent(0.4).cgColor , UIColor.clear.cgColor]
  719. layer.insertSublayer(gradient, at: 0)
  720. moreButton.setImage(CCGraphics.changeThemingColorImage(UIImage.init(named: "more"), width: 50, height: 50, color: .white), for: .normal)
  721. title.text = ""
  722. }
  723. func toggleEmptyView(isEmpty: Bool) {
  724. if isEmpty {
  725. UIView.animate(withDuration: 0.3) {
  726. self.moreView.effect = UIBlurEffect(style: .dark)
  727. self.gradient.isHidden = true
  728. self.controlButtonView.isHidden = true
  729. }
  730. } else {
  731. UIView.animate(withDuration: 0.3) {
  732. self.moreView.effect = UIBlurEffect(style: .regular)
  733. self.gradient.isHidden = false
  734. self.controlButtonView.isHidden = false
  735. }
  736. }
  737. }
  738. @IBAction func moreButtonPressed(_ sender: UIButton) {
  739. mediaView?.openMenuButtonMore(sender)
  740. }
  741. @IBAction func zoomInPressed(_ sender: UIButton) {
  742. mediaView?.zoomInGrid()
  743. }
  744. @IBAction func zoomOutPressed(_ sender: UIButton) {
  745. mediaView?.zoomOutGrid()
  746. }
  747. @IBAction func gridSwitchButtonPressed(_ sender: Any) {
  748. self.collapseControlButtonView(false)
  749. }
  750. func collapseControlButtonView(_ collapse: Bool) {
  751. if (collapse) {
  752. self.buttonControlWidthConstraint.constant = 40
  753. UIView.animate(withDuration: 0.25) {
  754. self.zoomOutButton.isHidden = true
  755. self.zoomInButton.isHidden = true
  756. self.separatorView.isHidden = true
  757. self.gridSwitchButton.isHidden = false
  758. self.layoutIfNeeded()
  759. }
  760. } else {
  761. self.buttonControlWidthConstraint.constant = 80
  762. UIView.animate(withDuration: 0.25) {
  763. self.zoomOutButton.isHidden = false
  764. self.zoomInButton.isHidden = false
  765. self.separatorView.isHidden = false
  766. self.gridSwitchButton.isHidden = true
  767. self.layoutIfNeeded()
  768. }
  769. }
  770. }
  771. override func point(inside point: CGPoint, with event: UIEvent?) -> Bool {
  772. return moreView.frame.contains(point) || controlButtonView.frame.contains(point)
  773. }
  774. override func layoutSublayers(of layer: CALayer) {
  775. super.layoutSublayers(of: layer)
  776. gradient.frame = bounds
  777. }
  778. }
  779. // MARK: - Media Grid Layout
  780. class NCGridMediaLayout: UICollectionViewFlowLayout {
  781. var marginLeftRight: CGFloat = 6
  782. var itemForLine: CGFloat = 3
  783. override init() {
  784. super.init()
  785. sectionHeadersPinToVisibleBounds = false
  786. minimumInteritemSpacing = 0
  787. minimumLineSpacing = marginLeftRight
  788. self.scrollDirection = .vertical
  789. self.sectionInset = UIEdgeInsets(top: 0, left: marginLeftRight, bottom: 0, right: marginLeftRight)
  790. }
  791. required init?(coder aDecoder: NSCoder) {
  792. fatalError("init(coder:) has not been implemented")
  793. }
  794. override var itemSize: CGSize {
  795. get {
  796. if let collectionView = collectionView {
  797. let itemWidth: CGFloat = (collectionView.frame.width - marginLeftRight * 2 - marginLeftRight * (itemForLine - 1)) / itemForLine
  798. let itemHeight: CGFloat = itemWidth
  799. return CGSize(width: itemWidth, height: itemHeight)
  800. }
  801. // Default fallback
  802. return CGSize(width: 100, height: 100)
  803. }
  804. set {
  805. super.itemSize = newValue
  806. }
  807. }
  808. override func targetContentOffset(forProposedContentOffset proposedContentOffset: CGPoint) -> CGPoint {
  809. return proposedContentOffset
  810. }
  811. }