NCCollectionViewCommon+CollectionViewDataSource.swift 32 KB

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