NCCollectionViewCommon+CollectionViewDataSource.swift 33 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625
  1. //
  2. // NCCollectionViewCommon+CollectionViewDataSource.swift
  3. // Nextcloud
  4. //
  5. // Created by Marino Faggiana on 02/07/24.
  6. // Copyright © 2024 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 UIKit
  25. import NextcloudKit
  26. import RealmSwift
  27. extension NCCollectionViewCommon: UICollectionViewDataSource {
  28. func numberOfSections(in collectionView: UICollectionView) -> Int {
  29. return self.dataSource.numberOfSections()
  30. }
  31. func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
  32. // get auto upload folder
  33. self.autoUploadFileName = self.database.getAccountAutoUploadFileName()
  34. self.autoUploadDirectory = self.database.getAccountAutoUploadDirectory(session: self.session)
  35. // get layout for view
  36. self.layoutForView = self.database.getLayoutForView(account: self.session.account, key: self.layoutKey, serverUrl: self.serverUrl)
  37. return self.dataSource.numberOfItemsInSection(section)
  38. }
  39. func collectionView(_ collectionView: UICollectionView, didEndDisplaying cell: UICollectionViewCell, forItemAt indexPath: IndexPath) {
  40. if !collectionView.indexPathsForVisibleItems.contains(indexPath) {
  41. guard let metadata = self.dataSource.getMetadata(indexPath: indexPath) else { return }
  42. for case let operation as NCCollectionViewDownloadThumbnail in NCNetworking.shared.downloadThumbnailQueue.operations where operation.metadata.ocId == metadata.ocId {
  43. operation.cancel()
  44. }
  45. }
  46. }
  47. func collectionView(_ collectionView: UICollectionView, willDisplay cell: UICollectionViewCell, forItemAt indexPath: IndexPath) {
  48. guard let metadata = dataSource.getMetadata(indexPath: indexPath) else { return }
  49. let existsImagePreview = utilityFileSystem.fileProviderStorageImageExists(metadata.ocId, etag: metadata.etag)
  50. let ext = global.getSizeExtension(column: self.numberOfColumns)
  51. if metadata.hasPreview,
  52. !existsImagePreview,
  53. NCNetworking.shared.downloadThumbnailQueue.operations.filter({ ($0 as? NCMediaDownloadThumbnail)?.metadata.ocId == metadata.ocId }).isEmpty {
  54. NCNetworking.shared.downloadThumbnailQueue.addOperation(NCCollectionViewDownloadThumbnail(metadata: metadata, collectionView: collectionView, ext: ext))
  55. }
  56. }
  57. private func photoCell(cell: NCPhotoCell, indexPath: IndexPath, metadata: tableMetadata, ext: String) -> NCPhotoCell {
  58. let width = UIScreen.main.bounds.width / CGFloat(self.numberOfColumns)
  59. cell.ocId = metadata.ocId
  60. cell.ocIdTransfer = metadata.ocIdTransfer
  61. cell.hideButtonMore(true)
  62. cell.hideImageStatus(true)
  63. /// Image
  64. ///
  65. if let image = NCImageCache.shared.getImageCache(ocId: metadata.ocId, etag: metadata.etag, ext: ext) {
  66. cell.filePreviewImageView?.image = image
  67. cell.filePreviewImageView?.contentMode = .scaleAspectFill
  68. } else {
  69. if isPinchGestureActive || ext == global.previewExt512 || ext == global.previewExt1024 {
  70. cell.filePreviewImageView?.image = self.utility.getImage(ocId: metadata.ocId, etag: metadata.etag, ext: ext)
  71. }
  72. DispatchQueue.global(qos: .userInteractive).async {
  73. let image = self.utility.getImage(ocId: metadata.ocId, etag: metadata.etag, ext: ext)
  74. if let image {
  75. self.imageCache.addImageCache(ocId: metadata.ocId, etag: metadata.etag, image: image, ext: ext, cost: indexPath.row)
  76. DispatchQueue.main.async {
  77. cell.filePreviewImageView?.image = image
  78. cell.filePreviewImageView?.contentMode = .scaleAspectFill
  79. }
  80. } else if !metadata.hasPreview {
  81. DispatchQueue.main.async {
  82. cell.filePreviewImageView?.contentMode = .scaleAspectFit
  83. if metadata.iconName.isEmpty {
  84. cell.filePreviewImageView?.image = NCImageCache.shared.getImageFile()
  85. } else {
  86. cell.filePreviewImageView?.image = self.utility.loadImage(named: metadata.iconName, useTypeIconFile: true, account: metadata.account)
  87. }
  88. }
  89. }
  90. }
  91. }
  92. /// Status
  93. ///
  94. if metadata.isLivePhoto {
  95. cell.fileStatusImage?.image = utility.loadImage(named: "livephoto", colors: isLayoutPhoto ? [.white] : [NCBrandColor.shared.iconImageColor2])
  96. } else if metadata.isVideo {
  97. cell.fileStatusImage?.image = utility.loadImage(named: "play.circle", colors: NCBrandColor.shared.iconImageMultiColors)
  98. }
  99. /// Edit mode
  100. if fileSelect.contains(metadata.ocId) {
  101. cell.selected(true, isEditMode: isEditMode)
  102. } else {
  103. cell.selected(false, isEditMode: isEditMode)
  104. }
  105. if width > 100 && cell.filePreviewImageView?.image != nil {
  106. cell.hideButtonMore(false)
  107. cell.hideImageStatus(false)
  108. }
  109. return cell
  110. }
  111. func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
  112. var cell: NCCellProtocol & UICollectionViewCell
  113. let permissions = NCPermissions()
  114. var isShare = false
  115. var isMounted = false
  116. var a11yValues: [String] = []
  117. let metadata = self.dataSource.getMetadata(indexPath: indexPath) ?? tableMetadata()
  118. let existsImagePreview = utilityFileSystem.fileProviderStorageImageExists(metadata.ocId, etag: metadata.etag)
  119. let ext = global.getSizeExtension(column: self.numberOfColumns)
  120. defer {
  121. if !metadata.isSharable() || NCCapabilities.shared.disableSharesView(account: metadata.account) {
  122. cell.hideButtonShare(true)
  123. }
  124. }
  125. // E2EE create preview
  126. if self.isDirectoryEncrypted,
  127. metadata.isImageOrVideo,
  128. !utilityFileSystem.fileProviderStorageImageExists(metadata.ocId, etag: metadata.etag) {
  129. utility.createImageFileFrom(metadata: metadata)
  130. }
  131. // LAYOUT PHOTO
  132. if isLayoutPhoto {
  133. if metadata.isImageOrVideo {
  134. let photoCell = (collectionView.dequeueReusableCell(withReuseIdentifier: "photoCell", for: indexPath) as? NCPhotoCell)!
  135. photoCell.photoCellDelegate = self
  136. cell = photoCell
  137. return self.photoCell(cell: photoCell, indexPath: indexPath, metadata: metadata, ext: ext)
  138. } else {
  139. let gridCell = (collectionView.dequeueReusableCell(withReuseIdentifier: "gridCell", for: indexPath) as? NCGridCell)!
  140. gridCell.gridCellDelegate = self
  141. cell = gridCell
  142. }
  143. } else if isLayoutGrid {
  144. // LAYOUT GRID
  145. let gridCell = (collectionView.dequeueReusableCell(withReuseIdentifier: "gridCell", for: indexPath) as? NCGridCell)!
  146. gridCell.gridCellDelegate = self
  147. cell = gridCell
  148. } else {
  149. // LAYOUT LIST
  150. let listCell = (collectionView.dequeueReusableCell(withReuseIdentifier: "listCell", for: indexPath) as? NCListCell)!
  151. listCell.listCellDelegate = self
  152. cell = listCell
  153. }
  154. /// CONTENT MODE
  155. cell.fileAvatarImageView?.contentMode = .center
  156. cell.filePreviewImageView?.layer.borderWidth = 0
  157. if existsImagePreview {
  158. cell.filePreviewImageView?.contentMode = .scaleAspectFill
  159. } else {
  160. cell.filePreviewImageView?.contentMode = .scaleAspectFit
  161. }
  162. guard let metadata = self.dataSource.getMetadata(indexPath: indexPath) else { return cell }
  163. if metadataFolder != nil {
  164. isShare = metadata.permissions.contains(permissions.permissionShared) && !metadataFolder!.permissions.contains(permissions.permissionShared)
  165. isMounted = metadata.permissions.contains(permissions.permissionMounted) && !metadataFolder!.permissions.contains(permissions.permissionMounted)
  166. }
  167. cell.fileAccount = metadata.account
  168. cell.fileOcId = metadata.ocId
  169. cell.fileOcIdTransfer = metadata.ocIdTransfer
  170. cell.fileUser = metadata.ownerId
  171. if isSearchingMode {
  172. cell.fileTitleLabel?.text = metadata.fileName
  173. cell.fileTitleLabel?.lineBreakMode = .byTruncatingTail
  174. if metadata.name == global.appName {
  175. cell.fileInfoLabel?.text = NSLocalizedString("_in_", comment: "") + " " + utilityFileSystem.getPath(path: metadata.path, user: metadata.user)
  176. } else {
  177. cell.fileInfoLabel?.text = metadata.subline
  178. }
  179. cell.fileSubinfoLabel?.isHidden = true
  180. } else if !metadata.sessionError.isEmpty, metadata.status != global.metadataStatusNormal {
  181. cell.fileSubinfoLabel?.isHidden = false
  182. cell.fileInfoLabel?.text = metadata.sessionError
  183. } else {
  184. cell.fileSubinfoLabel?.isHidden = false
  185. cell.fileTitleLabel?.text = metadata.fileNameView
  186. cell.fileTitleLabel?.lineBreakMode = .byTruncatingMiddle
  187. cell.writeInfoDateSize(date: metadata.date, size: metadata.size)
  188. }
  189. // Accessibility [shared] if metadata.ownerId != appDelegate.userId, appDelegate.account == metadata.account {
  190. if metadata.ownerId != metadata.userId {
  191. a11yValues.append(NSLocalizedString("_shared_with_you_by_", comment: "") + " " + metadata.ownerDisplayName)
  192. }
  193. if metadata.directory {
  194. let tableDirectory = database.getTableDirectory(ocId: metadata.ocId)
  195. if metadata.e2eEncrypted {
  196. cell.filePreviewImageView?.image = imageCache.getFolderEncrypted(account: metadata.account)
  197. } else if isShare {
  198. cell.filePreviewImageView?.image = imageCache.getFolderSharedWithMe(account: metadata.account)
  199. } else if !metadata.shareType.isEmpty {
  200. metadata.shareType.contains(3) ?
  201. (cell.filePreviewImageView?.image = imageCache.getFolderPublic(account: metadata.account)) :
  202. (cell.filePreviewImageView?.image = imageCache.getFolderSharedWithMe(account: metadata.account))
  203. } else if !metadata.shareType.isEmpty && metadata.shareType.contains(3) {
  204. cell.filePreviewImageView?.image = imageCache.getFolderPublic(account: metadata.account)
  205. } else if metadata.mountType == "group" {
  206. cell.filePreviewImageView?.image = imageCache.getFolderGroup(account: metadata.account)
  207. } else if isMounted {
  208. cell.filePreviewImageView?.image = imageCache.getFolderExternal(account: metadata.account)
  209. } else if metadata.fileName == autoUploadFileName && metadata.serverUrl == autoUploadDirectory {
  210. cell.filePreviewImageView?.image = imageCache.getFolderAutomaticUpload(account: metadata.account)
  211. } else {
  212. cell.filePreviewImageView?.image = imageCache.getFolder(account: metadata.account)
  213. }
  214. // Local image: offline
  215. if let tableDirectory, tableDirectory.offline {
  216. cell.fileLocalImage?.image = imageCache.getImageOfflineFlag()
  217. }
  218. // color folder
  219. cell.filePreviewImageView?.image = cell.filePreviewImageView?.image?.colorizeFolder(metadata: metadata, tableDirectory: tableDirectory)
  220. } else {
  221. if metadata.hasPreviewBorder {
  222. cell.filePreviewImageView?.layer.borderWidth = 0.2
  223. cell.filePreviewImageView?.layer.borderColor = UIColor.lightGray.cgColor
  224. }
  225. if metadata.name == global.appName {
  226. if let image = NCImageCache.shared.getImageCache(ocId: metadata.ocId, etag: metadata.etag, ext: ext) {
  227. cell.filePreviewImageView?.image = image
  228. } else if let image = utility.getImage(ocId: metadata.ocId, etag: metadata.etag, ext: ext) {
  229. cell.filePreviewImageView?.image = image
  230. }
  231. if cell.filePreviewImageView?.image == nil {
  232. if metadata.iconName.isEmpty {
  233. cell.filePreviewImageView?.image = NCImageCache.shared.getImageFile()
  234. } else {
  235. cell.filePreviewImageView?.image = utility.loadImage(named: metadata.iconName, useTypeIconFile: true, account: metadata.account)
  236. }
  237. }
  238. } else {
  239. /// APP NAME - UNIFIED SEARCH
  240. switch metadata.iconName {
  241. case let str where str.contains("contacts"):
  242. cell.filePreviewImageView?.image = utility.loadImage(named: "person.crop.rectangle.stack", colors: [NCBrandColor.shared.iconImageColor])
  243. case let str where str.contains("conversation"):
  244. cell.filePreviewImageView?.image = UIImage(named: "talk-template")!.image(color: NCBrandColor.shared.getElement(account: metadata.account))
  245. case let str where str.contains("calendar"):
  246. cell.filePreviewImageView?.image = utility.loadImage(named: "calendar", colors: [NCBrandColor.shared.iconImageColor])
  247. case let str where str.contains("deck"):
  248. cell.filePreviewImageView?.image = utility.loadImage(named: "square.stack.fill", colors: [NCBrandColor.shared.iconImageColor])
  249. case let str where str.contains("mail"):
  250. cell.filePreviewImageView?.image = utility.loadImage(named: "mail", colors: [NCBrandColor.shared.iconImageColor])
  251. case let str where str.contains("talk"):
  252. cell.filePreviewImageView?.image = UIImage(named: "talk-template")!.image(color: NCBrandColor.shared.getElement(account: metadata.account))
  253. case let str where str.contains("confirm"):
  254. cell.filePreviewImageView?.image = utility.loadImage(named: "arrow.right", colors: [NCBrandColor.shared.iconImageColor])
  255. case let str where str.contains("pages"):
  256. cell.filePreviewImageView?.image = utility.loadImage(named: "doc.richtext", colors: [NCBrandColor.shared.iconImageColor])
  257. default:
  258. cell.filePreviewImageView?.image = utility.loadImage(named: "doc", colors: [NCBrandColor.shared.iconImageColor])
  259. }
  260. if !metadata.iconUrl.isEmpty {
  261. if let ownerId = getAvatarFromIconUrl(metadata: metadata) {
  262. let fileName = NCSession.shared.getFileName(urlBase: metadata.urlBase, user: ownerId)
  263. let results = NCManageDatabase.shared.getImageAvatarLoaded(fileName: fileName)
  264. if results.image == nil {
  265. cell.filePreviewImageView?.image = utility.loadUserImage(for: ownerId, displayName: nil, urlBase: metadata.urlBase)
  266. } else {
  267. cell.filePreviewImageView?.image = results.image
  268. }
  269. if !(results.tableAvatar?.loaded ?? false),
  270. NCNetworking.shared.downloadAvatarQueue.operations.filter({ ($0 as? NCOperationDownloadAvatar)?.fileName == fileName }).isEmpty {
  271. NCNetworking.shared.downloadAvatarQueue.addOperation(NCOperationDownloadAvatar(user: ownerId, fileName: fileName, account: metadata.account, view: collectionView, isPreviewImageView: true))
  272. }
  273. }
  274. }
  275. }
  276. let tableLocalFile = database.getResultsTableLocalFile(predicate: NSPredicate(format: "ocId == %@", metadata.ocId))?.first
  277. // image local
  278. if let tableLocalFile, tableLocalFile.offline {
  279. a11yValues.append(NSLocalizedString("_offline_", comment: ""))
  280. cell.fileLocalImage?.image = imageCache.getImageOfflineFlag()
  281. } else if utilityFileSystem.fileProviderStorageExists(metadata) {
  282. cell.fileLocalImage?.image = imageCache.getImageLocal()
  283. }
  284. }
  285. // image Favorite
  286. if metadata.favorite {
  287. cell.fileFavoriteImage?.image = imageCache.getImageFavorite()
  288. a11yValues.append(NSLocalizedString("_favorite_short_", comment: ""))
  289. }
  290. // Share image
  291. if isShare {
  292. cell.fileSharedImage?.image = imageCache.getImageShared()
  293. } else if !metadata.shareType.isEmpty {
  294. metadata.shareType.contains(3) ?
  295. (cell.fileSharedImage?.image = imageCache.getImageShareByLink()) :
  296. (cell.fileSharedImage?.image = imageCache.getImageShared())
  297. } else {
  298. cell.fileSharedImage?.image = imageCache.getImageCanShare()
  299. }
  300. // Button More
  301. if metadata.lock == true {
  302. cell.setButtonMore(image: imageCache.getImageButtonMoreLock())
  303. a11yValues.append(String(format: NSLocalizedString("_locked_by_", comment: ""), metadata.lockOwnerDisplayName))
  304. } else {
  305. cell.setButtonMore(image: imageCache.getImageButtonMore())
  306. }
  307. // Staus
  308. if metadata.isLivePhoto {
  309. cell.fileStatusImage?.image = utility.loadImage(named: "livephoto", colors: isLayoutPhoto ? [.white] : [NCBrandColor.shared.iconImageColor2])
  310. a11yValues.append(NSLocalizedString("_upload_mov_livephoto_", comment: ""))
  311. } else if metadata.isVideo {
  312. cell.fileStatusImage?.image = utility.loadImage(named: "play.circle", colors: NCBrandColor.shared.iconImageMultiColors)
  313. }
  314. switch metadata.status {
  315. case NCGlobal.shared.metadataStatusWaitCreateFolder:
  316. cell.fileStatusImage?.image = utility.loadImage(named: "arrow.triangle.2.circlepath", colors: NCBrandColor.shared.iconImageMultiColors)
  317. cell.fileInfoLabel?.text = NSLocalizedString("_status_wait_create_folder_", comment: "")
  318. case NCGlobal.shared.metadataStatusWaitFavorite:
  319. cell.fileStatusImage?.image = utility.loadImage(named: "star.circle", colors: NCBrandColor.shared.iconImageMultiColors)
  320. cell.fileInfoLabel?.text = NSLocalizedString("_status_wait_favorite_", comment: "")
  321. case NCGlobal.shared.metadataStatusWaitCopy:
  322. cell.fileStatusImage?.image = utility.loadImage(named: "c.circle", colors: NCBrandColor.shared.iconImageMultiColors)
  323. cell.fileInfoLabel?.text = NSLocalizedString("_status_wait_copy_", comment: "")
  324. case NCGlobal.shared.metadataStatusWaitMove:
  325. cell.fileStatusImage?.image = utility.loadImage(named: "m.circle", colors: NCBrandColor.shared.iconImageMultiColors)
  326. cell.fileInfoLabel?.text = NSLocalizedString("_status_wait_move_", comment: "")
  327. case NCGlobal.shared.metadataStatusWaitRename:
  328. cell.fileStatusImage?.image = utility.loadImage(named: "a.circle", colors: NCBrandColor.shared.iconImageMultiColors)
  329. cell.fileInfoLabel?.text = NSLocalizedString("_status_wait_rename_", comment: "")
  330. case NCGlobal.shared.metadataStatusWaitDownload:
  331. cell.fileStatusImage?.image = utility.loadImage(named: "arrow.triangle.2.circlepath", colors: NCBrandColor.shared.iconImageMultiColors)
  332. case NCGlobal.shared.metadataStatusDownloading:
  333. if #available(iOS 17.0, *) {
  334. cell.fileStatusImage?.image = utility.loadImage(named: "arrowshape.down.circle", colors: NCBrandColor.shared.iconImageMultiColors)
  335. }
  336. case NCGlobal.shared.metadataStatusDownloadError, NCGlobal.shared.metadataStatusUploadError:
  337. cell.fileStatusImage?.image = utility.loadImage(named: "exclamationmark.circle", colors: NCBrandColor.shared.iconImageMultiColors)
  338. default:
  339. break
  340. }
  341. // AVATAR
  342. if !metadata.ownerId.isEmpty, metadata.ownerId != metadata.userId {
  343. cell.fileAvatarImageView?.contentMode = .scaleAspectFill
  344. let fileName = NCSession.shared.getFileName(urlBase: metadata.urlBase, user: metadata.ownerId)
  345. let results = NCManageDatabase.shared.getImageAvatarLoaded(fileName: fileName)
  346. if results.image == nil {
  347. cell.fileAvatarImageView?.image = utility.loadUserImage(for: metadata.ownerId, displayName: metadata.ownerDisplayName, urlBase: metadata.urlBase)
  348. } else {
  349. cell.fileAvatarImageView?.image = results.image
  350. }
  351. if !(results.tableAvatar?.loaded ?? false),
  352. NCNetworking.shared.downloadAvatarQueue.operations.filter({ ($0 as? NCOperationDownloadAvatar)?.fileName == fileName }).isEmpty {
  353. NCNetworking.shared.downloadAvatarQueue.addOperation(NCOperationDownloadAvatar(user: metadata.ownerId, fileName: fileName, account: metadata.account, view: collectionView))
  354. }
  355. }
  356. // URL
  357. if metadata.classFile == NKCommon.TypeClassFile.url.rawValue {
  358. cell.fileLocalImage?.image = nil
  359. cell.hideButtonShare(true)
  360. cell.hideButtonMore(true)
  361. if let ownerId = getAvatarFromIconUrl(metadata: metadata) {
  362. cell.fileUser = ownerId
  363. }
  364. }
  365. // Separator
  366. if collectionView.numberOfItems(inSection: indexPath.section) == indexPath.row + 1 || isSearchingMode {
  367. cell.cellSeparatorView?.isHidden = true
  368. } else {
  369. cell.cellSeparatorView?.isHidden = false
  370. }
  371. // Edit mode
  372. if fileSelect.contains(metadata.ocId) {
  373. cell.selected(true, isEditMode: isEditMode)
  374. a11yValues.append(NSLocalizedString("_selected_", comment: ""))
  375. } else {
  376. cell.selected(false, isEditMode: isEditMode)
  377. }
  378. // Accessibility
  379. cell.setAccessibility(label: metadata.fileNameView + ", " + (cell.fileInfoLabel?.text ?? "") + (cell.fileSubinfoLabel?.text ?? ""), value: a11yValues.joined(separator: ", "))
  380. // Color string find in search
  381. cell.fileTitleLabel?.textColor = NCBrandColor.shared.textColor
  382. cell.fileTitleLabel?.font = .systemFont(ofSize: 15)
  383. if isSearchingMode, let literalSearch = self.literalSearch, let title = cell.fileTitleLabel?.text {
  384. let longestWordRange = (title.lowercased() as NSString).range(of: literalSearch)
  385. let attributedString = NSMutableAttributedString(string: title, attributes: [NSAttributedString.Key.font: UIFont.systemFont(ofSize: 15)])
  386. attributedString.setAttributes([NSAttributedString.Key.font: UIFont.boldSystemFont(ofSize: 15), NSAttributedString.Key.foregroundColor: UIColor.systemBlue], range: longestWordRange)
  387. cell.fileTitleLabel?.attributedText = attributedString
  388. }
  389. // TAGS
  390. cell.setTags(tags: Array(metadata.tags))
  391. // Layout photo
  392. if isLayoutPhoto {
  393. let width = UIScreen.main.bounds.width / CGFloat(self.numberOfColumns)
  394. cell.hideImageFavorite(false)
  395. cell.hideImageLocal(false)
  396. cell.hideImageItem(false)
  397. cell.hideButtonMore(false)
  398. cell.hideLabelInfo(false)
  399. cell.hideLabelSubinfo(false)
  400. cell.hideImageStatus(false)
  401. cell.fileTitleLabel?.font = UIFont.systemFont(ofSize: 15)
  402. if width < 120 {
  403. cell.hideImageFavorite(true)
  404. cell.hideImageLocal(true)
  405. cell.fileTitleLabel?.font = UIFont.systemFont(ofSize: 10)
  406. if width < 100 {
  407. cell.hideImageItem(true)
  408. cell.hideButtonMore(true)
  409. cell.hideLabelInfo(true)
  410. cell.hideLabelSubinfo(true)
  411. cell.hideImageStatus(true)
  412. }
  413. }
  414. }
  415. // Hide buttons
  416. if metadata.name != global.appName {
  417. cell.titleInfoTrailingFull()
  418. cell.hideButtonShare(true)
  419. cell.hideButtonMore(true)
  420. }
  421. cell.setIconOutlines()
  422. return cell
  423. }
  424. func collectionView(_ collectionView: UICollectionView, viewForSupplementaryElementOfKind kind: String, at indexPath: IndexPath) -> UICollectionReusableView {
  425. if kind == UICollectionView.elementKindSectionHeader || kind == mediaSectionHeader {
  426. if self.dataSource.isEmpty() {
  427. guard let header = collectionView.dequeueReusableSupplementaryView(ofKind: kind, withReuseIdentifier: "sectionFirstHeaderEmptyData", for: indexPath) as? NCSectionFirstHeaderEmptyData else { return NCSectionFirstHeaderEmptyData() }
  428. self.sectionFirstHeaderEmptyData = header
  429. header.delegate = self
  430. if !isSearchingMode, headerMenuTransferView, isHeaderMenuTransferViewEnabled() != nil {
  431. header.setViewTransfer(isHidden: false)
  432. } else {
  433. header.setViewTransfer(isHidden: true)
  434. }
  435. if isSearchingMode {
  436. header.emptyImage.image = utility.loadImage(named: "magnifyingglass", colors: [NCBrandColor.shared.getElement(account: session.account)])
  437. if self.dataSourceTask?.state == .running {
  438. header.emptyTitle.text = NSLocalizedString("_search_in_progress_", comment: "")
  439. } else {
  440. header.emptyTitle.text = NSLocalizedString("_search_no_record_found_", comment: "")
  441. }
  442. header.emptyDescription.text = NSLocalizedString("_search_instruction_", comment: "")
  443. } else if self.dataSourceTask?.state == .running {
  444. header.emptyImage.image = utility.loadImage(named: "wifi", colors: [NCBrandColor.shared.getElement(account: session.account)])
  445. header.emptyTitle.text = NSLocalizedString("_request_in_progress_", comment: "")
  446. header.emptyDescription.text = ""
  447. } else {
  448. if serverUrl.isEmpty {
  449. if let emptyImageName {
  450. header.emptyImage.image = utility.loadImage(named: emptyImageName, colors: emptyImageColors != nil ? emptyImageColors : [NCBrandColor.shared.getElement(account: session.account)])
  451. } else {
  452. header.emptyImage.image = imageCache.getFolder(account: session.account)
  453. }
  454. header.emptyTitle.text = NSLocalizedString(emptyTitle, comment: "")
  455. header.emptyDescription.text = NSLocalizedString(emptyDescription, comment: "")
  456. } else if metadataFolder?.status == global.metadataStatusWaitCreateFolder {
  457. header.emptyImage.image = utility.loadImage(named: "arrow.triangle.2.circlepath", colors: [NCBrandColor.shared.getElement(account: session.account)])
  458. header.emptyTitle.text = NSLocalizedString("_files_no_files_", comment: "")
  459. header.emptyDescription.text = NSLocalizedString("_folder_offline_desc_", comment: "")
  460. } else {
  461. header.emptyImage.image = imageCache.getFolder(account: session.account)
  462. header.emptyTitle.text = NSLocalizedString("_files_no_files_", comment: "")
  463. header.emptyDescription.text = NSLocalizedString("_no_file_pull_down_", comment: "")
  464. }
  465. }
  466. return header
  467. } else if indexPath.section == 0 {
  468. guard let header = collectionView.dequeueReusableSupplementaryView(ofKind: kind, withReuseIdentifier: "sectionFirstHeader", for: indexPath) as? NCSectionFirstHeader else { return NCSectionFirstHeader() }
  469. let (_, heightHeaderRichWorkspace, heightHeaderSection) = getHeaderHeight(section: indexPath.section)
  470. self.sectionFirstHeader = header
  471. header.delegate = self
  472. if !isSearchingMode, headerMenuTransferView, isHeaderMenuTransferViewEnabled() != nil {
  473. header.setViewTransfer(isHidden: false)
  474. } else {
  475. header.setViewTransfer(isHidden: true)
  476. }
  477. header.setRichWorkspaceHeight(heightHeaderRichWorkspace)
  478. header.setRichWorkspaceText(richWorkspaceText)
  479. header.setSectionHeight(heightHeaderSection)
  480. if heightHeaderSection == 0 {
  481. header.labelSection.text = ""
  482. } else {
  483. header.labelSection.text = self.dataSource.getSectionValueLocalization(indexPath: indexPath)
  484. }
  485. header.labelSection.textColor = NCBrandColor.shared.textColor
  486. return header
  487. } else {
  488. guard let header = collectionView.dequeueReusableSupplementaryView(ofKind: kind, withReuseIdentifier: "sectionHeader", for: indexPath) as? NCSectionHeader else { return NCSectionHeader() }
  489. header.labelSection.text = self.dataSource.getSectionValueLocalization(indexPath: indexPath)
  490. header.labelSection.textColor = NCBrandColor.shared.textColor
  491. return header
  492. }
  493. } else {
  494. guard let footer = collectionView.dequeueReusableSupplementaryView(ofKind: kind, withReuseIdentifier: "sectionFooter", for: indexPath) as? NCSectionFooter else { return NCSectionFooter() }
  495. let sections = self.dataSource.numberOfSections()
  496. let section = indexPath.section
  497. let metadataForSection = self.dataSource.getMetadataForSection(indexPath.section)
  498. let isPaginated = metadataForSection?.lastSearchResult?.isPaginated ?? false
  499. let metadatasCount: Int = metadataForSection?.metadatas.count ?? 0
  500. let unifiedSearchInProgress = metadataForSection?.unifiedSearchInProgress ?? false
  501. footer.delegate = self
  502. footer.metadataForSection = metadataForSection
  503. footer.setTitleLabel("")
  504. footer.setButtonText(NSLocalizedString("_show_more_results_", comment: ""))
  505. footer.separatorIsHidden(true)
  506. footer.buttonIsHidden(true)
  507. footer.hideActivityIndicatorSection()
  508. if isSearchingMode {
  509. if sections > 1 && section != sections - 1 {
  510. footer.separatorIsHidden(false)
  511. }
  512. // If the number of entries(metadatas) is lower than the cursor, then there are no more entries.
  513. // The blind spot in this is when the number of entries is the same as the cursor. If so, we don't have a way of knowing if there are no more entries.
  514. // This is as good as it gets for determining last page without server-side flag.
  515. let isLastPage = (metadatasCount < metadataForSection?.lastSearchResult?.cursor ?? 0) || metadataForSection?.lastSearchResult?.entries.isEmpty == true
  516. if isSearchingMode && isPaginated && metadatasCount > 0 && !isLastPage {
  517. footer.buttonIsHidden(false)
  518. }
  519. if unifiedSearchInProgress {
  520. footer.showActivityIndicatorSection()
  521. }
  522. } else {
  523. if sections == 1 || section == sections - 1 {
  524. let info = self.dataSource.getFooterInformation()
  525. footer.setTitleLabel(directories: info.directories, files: info.files, size: info.size)
  526. } else {
  527. footer.separatorIsHidden(false)
  528. }
  529. }
  530. return footer
  531. }
  532. }
  533. // MARK: -
  534. func getAvatarFromIconUrl(metadata: tableMetadata) -> String? {
  535. var ownerId: String?
  536. if metadata.iconUrl.contains("http") && metadata.iconUrl.contains("avatar") {
  537. let splitIconUrl = metadata.iconUrl.components(separatedBy: "/")
  538. var found: Bool = false
  539. for item in splitIconUrl {
  540. if found {
  541. ownerId = item
  542. break
  543. }
  544. if item == "avatar" { found = true}
  545. }
  546. }
  547. return ownerId
  548. }
  549. }